[一篇读懂]C语言五讲:指针
[一篇读懂]C语言五讲:指针
- 1. 指针的本质(间接访问原理)
- 1 指针的定义
- 2 取地址操作符与取值操作符,指针本质
- 2. 指针的==传递==使用场景
- 1 指针的传递
- 【例1.1】指针的传递使用场景。
- 【例1.2】在子函数中修改main函数中某个变量的值。
- 3. 指针的==偏移==使用场景
- 1 指针的偏移
- 【例1.1】指针的偏移使用场景。
- 2 指针与一维数组
- 【例】数组传递给子函数的实战练习
- 4. 指针与malloc动态内存申请,栈空间与堆空间的差异
- ==1 指针与动态内存申请==
- 【例1.1】动态内存申请。
- 2 栈空间与堆空间的差异(了解)
- 【例2.1】堆空间与栈空间的差异。
- 总结
- 1.1
- 1.2
- 2.1
- 3.1
- 3.2
- 4.1
- 4.2
1. 指针的本质(间接访问原理)
重要
1 指针的定义
- 内存区域中的每字节都对应一个编号,这个编号就是“地址”。
- 按变量地址存取变量值的方式称为“直接访问”,如:
printf("%d",i);
scanf("%d",&i);
- 另一种存取变量值的方式称为“间接访问”,即将变量 i i i的地址存放到另一个变量中。
- 在C语言中,指针变量是一种特殊的变量,它用来存放变量地址。
说某个变量的地址时,讲的都是它的
起始地址
直接访问:
访问地址拿到宝藏
间接访问:
把地址
看作藏宝图
通过指针变量 - 找到地址
(藏宝图) - 再拿到宝藏
即多了一次跳转
- 指针变量的定义格式如下:
/*基类型 *指针变量名;*/
int *i_pointer;//定义了一个指针变量,i_pointer是指针变量名
- 指针与指针变量是两个概念
一个变量的地址称为该变量的
“指针”
。
如果有一个变量专门用来存放另一变量的地址(即指针),那么称它为“指针变量”
。
- 指针变量占用的内存空间
本章中编写的程序都是64位应用程序,寻址范围为64位即8字节,所以对于本章来说sizeof(i_pointer)=8。如果编写的程序是32位,那么寻址范围就是4字节(考研中往往会强调程序是32位的程序)。
2 取地址操作符与取值操作符,指针本质
- 取地址操作符为&,也称引用。
通过该操作符我们可以获取一个变量的地址值。
- 取值操作符为*,也称解引用。
通过该操作符我们可以得到一个地址对应的数据。
- 例:
#include <stdio.h>
int main()
{//&符号是取地址,指针变量的初始化一定是某个变量取地址int i = 5;//指针变量的初始化是某个变量取地址来赋值,不能随机写个数//指针变量的基类型与所指向的变量的基类型相同int* p = &i;printf("i = %d\n", i);//直接访问printf("p = %d\n", *p);//间接访问return 0;
}
注意!!
(1)指针变量前面的“*”表示该变量为指针型变量。
例如,float *pointer_1;
注意指针变量名是pointer_1,而不是*pointer_1。
(2)在定义指针变量时必须指定其类型。
需要注意的是,指针变量的基类型与所指向的变量的基类型相同。例如,下面的赋值是错误的:float a; int *pointer_1; pointer_1 = &a; //毫无意义而且会出错
(3)如果已执行了语句
pointer_1 = &a;
那么
&*pointer_1
的含义是什么呢?(犯傻)
“&”和“*”两个运算符的优先级别相同,但要按自右向左的方向结合。因
此,&*pointer_1与*&a相同,都表示变量a的地址
,也就是pointer_1。
*&a
的含义是什么呢?
首先进行&a运算,得到a的地址,再进行*运算。*&a和*pointer_1的作用是一样的,它们都等价于变量a
,即*&a 与a等价。
(4)为什么要让*和指针变量连着
C语言本质上是一种自由形式的语言,这很容易诱使我们把“*”
写在靠近类型的一侧,如int* a
这个声明与int *a
具有相同的意思,而且看上去更清晰,a被声明成类型为int*的指针。
但是,这并不是一个好习惯,因为类似int* a,b,c的语句会使人们很自然地认为这条语句把所有三个变量声明为指向整型的指针,但事实上并非如此,“*”
实际上是*a
的一部分,只对a标识符起作用,但其余两个变量只是普通的整型变量。
要声明三个指针变量,正确的语句如下:int *a, *b, *c;
2. 指针的传递使用场景
指针的使用场景通常只有两个,即传递与偏移
1 指针的传递
程序启动起来就是进程
【例1.1】指针的传递使用场景。
#include <stdio.h>
//在子函数中去改变主函数中某个变量的值
void change(int j)//j是形参
{j = 5;
}int main()
{int i = 10;printf("before change i = %d\n", i);change(i);//C语言的函数调用是值传递,实参赋值给形参,i是实参//j = i - 把i赋值给了jprintf("after change i = %d\n", i);return 0;
}
运行结果:
change后 i i i的值没有改变。
C语言的函数调用是值传递,实参赋值给形参, i i i是实参, j j j是形参。
运行change -j = i
-把i赋值给了j
然后j = 5
- j的值改变了
所以 i i i的值不会改变!
- 通过调试查看:
i i i的地址:61fe1c
j j j的地址:61fdf0
运行change,先把 i i i赋值给 j j j -j = i
,然后执行j = 5
改变的都是61fdf0地址的值 - 即 j j j;
不会影响到61fe1c地址的值 - 即 i i i
【例1.1】的原理图:
变量 i i i在 m a i n main main函数中;
变量 j j j在 c h a n g e change change函数中。
任何时候都是实参赋值给形参
【例1.2】在子函数中修改main函数中某个变量的值。
#include <stdio.h>
//在子函数中去改变主函数中某个变量的值
void change(int *j)//j是形参
{*j = 5;//*j等价于变量i,只是间接访问//间接访问得到变量i
}//指针的传递
int main()
{int i = 10;printf("before change i = %d\n", i);change(&i);//传递变量i的地址 - j = &iprintf("after change i = %d\n", i);return 0;
}
运行结果:
程序执行后, i i i的值变成了5。
将变量 i i i的地址传递给change函数时,实际效果是j=&i
,依然是值传递,只是这时 j j j是一个指针变量,内部存储的是变量 i i i的地址,所以通过*j
就间接访问到了与变量 i i i相同的区域,通过*j=5
就实现了对变量 i i i的值的改变。
3. 指针的偏移使用场景
1 指针的偏移
指针即地址,就像是找到了一栋楼,这栋楼的楼号是B,那么往前就是A,往后就是C,所以应用指针的另一个场景就是对其进行加减,但对指针进行乘除是没有意义的,就像家庭地址乘以5没有意义那样。在工作中,我们把对指针的加减称为指针的偏移,加就是向后偏移,减就是向前偏移。
【例1.1】指针的偏移使用场景。
实现数组的正序输出和逆序输出:
#include <stdio.h>
//指针的偏移使用场景,也就是对指针的加减
#define N 5//定义一个符号常量
int main()
{int a[N] = { 1,2,3,4,5 };//数组名内存储了数组的起始地址,a中存储的就是一个地址值int *p;//定义指针变量pp = a;//保证等号两边的数值类型一致 - p中存储数组起始地址int i;for (i = 0; i < N; i++)//正序输出{//printf("%3d", a[i]);//等价下一句printf("%3d", *(p + i));//*(p + 0)拿到a[0];*(p + 1)拿到a[1]……}printf("\n-----------------\n");//分隔p = &a[4];//对整型变量取地址 - 指针变量p指向了数组的最后一个元素for (i = 0; i < N; i++)//逆序输出{printf("%3d", *(p - i));}printf("\n");return 0;
}
数组名内存储了数组的起始地址,a中存储的就是一个地址值
运行结果:
- 数组名中存储着数组的起始地址0x61fdf0,其类型为整形指针,可以赋值给整型指针变量p,通过
*p
可以得到元素a[0]。 - p + 1的值为0x61fdf4,因为指针变量加1后,偏移的长度是其基类型的长度,也就是偏移sizeof(int)长度,这样通过
*(p+1)
就可以得到元素a[1]。
2 指针与一维数组
- 数组名作为实参传递给子函数是,是弱化为指针的
直接写
*d
void change(char *d)
【例】数组传递给子函数的实战练习
#include <stdio.h>
//指针与一维数组的传递//数组名作为实参传递给子函数是,是弱化为指针的
void change(char *d)//数组名存储的是一个指针值,形参写指针变量,不写数组变量
{*d = 'H';d[1] = 'E';//等价于*(d+1) = 'E';*(d + 2) = 'L';}
int main()
{char c[10] = "hello";change(c);puts(c);return 0;
}
运行结果:
4. 指针与malloc动态内存申请,栈空间与堆空间的差异
1 指针与动态内存申请
- int a[10]数组长度固定 - 很不方便
- 数组长度固定是因为其定义的整型、浮点型、字符型变量、数组变量都在栈空间中,而栈空间的大小在编译时是确定的。
- 如果使用的空间大小不确定,那么就要使用堆空间。
【例1.1】动态内存申请。
#include <stdio.h>
#include <stdlib.h>//malloc需要使用的头文件
#include <string.h>int main()
{int size;//size代表我们要申请多大字节的空间char* p;//void*类型的指针是不能偏移的,因此自己不会去定义无类型指针scanf("%d", &size);//输入要申请的空间大小//注意指针本身大小,和其指向的空间大小,是两码事,不能和前面的变量类比去理解!//malloc返回的void*代表无类型指针//使用malloc动态申请堆空间p = (char*)malloc(size); //强制类型转换与p的类型一致//p[0] = 'H';//p[1] = 'O';//p[2] = 'W';//p[3] = '\0';strcpy(p, "malloc success");puts(p);//不用时要free!释放申请的空间!//释放申请的空间时,给的地址,必须是最初malloc返回给我们的地址free(p); //free时必须使用malloc申请时返回的指针值,不能进行任何偏移printf("free success\n");return 0;
}
- 向malloc申请一片空间,malloc返回对应空间的首地址,分配接下来一部分空间。
- malloc返回值为void*,即无类型指针,其只能用来存储一个地址且不能偏移。
- 堆空间是借来的,不用的时候要free掉,释放申请的空间。
- 释放申请的空间时,给的地址必须是最初使用malloc申请时返回给我们的地址。
- 注意指针本身大小,和其指向的空间大小,是两码事,不能和前面的变量类比去理解。
堆是动态的,但是堆的效率比栈低得多。
2 栈空间与堆空间的差异(了解)
记住即可
【例2.1】堆空间与栈空间的差异。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//堆和栈的差异//函数栈空间释放后,函数内的所有局部变量消失
//低级错误
char* print_stack()//栈空间stack
{char c[100] = "I am print_stack func";//栈空间在子函数结束后被释放了char *p;p = c;puts(p);//可以打印出I am print_stack funcreturn p;
}
//堆空间不会因为函数执行结束而释放
char* print_malloc()//堆空间heap
{char* p = (char*)malloc(100);//堆空间在整个进程中一直有效,不因为函数结束而消亡strcpy(p, "I am print malloc func");puts(p);return p;
}int main()
{char *p;p = print_stack();//数据放在栈空间puts(p);//子函数中造的数据到主函数中打印出乱码//是因为栈空间已经被释放掉了p = print_malloc();//数据放在堆空间puts(p);free(p);//只有free时,堆空间才会释放return 0;
}
- 栈空间在子函数结束后被释放了,函数栈空间释放后,函数内的所有局部变量消失。
- 堆空间在整个进程中一直有效,不会因为函数执行结束而释放,只有free时,堆空间才会释放。
总结
1.1
- 按变量地址存取变量值的方式称为“直接访问”,如:
printf("%d",i);
scanf("%d",&i);
- 另一种存取变量值的方式称为“间接访问”,即将变量 i i i的地址存放到另一个变量中。
/*基类型 *指针变量名;*/
int *i_pointer;//定义了一个指针变量,i_pointer是指针变量名
1.2
- 取地址操作符为&,也称引用。
通过该操作符我们可以获取一个变量的地址值。
- 取值操作符为*,也称解引用。
通过该操作符我们可以得到一个地址对应的数据。
- 指针变量前面的“*”表示该变量为指针型变量。
- 在定义指针变量时必须指定其类型。
2.1
- C语言的函数调用是值传递,实参赋值给形参。
- 通过指针变量可以访问变量所在的区域。
3.1
- 对指针的加减称为指针的偏移,加就是向后偏移,减就是向前偏移。
- 偏移的长度是其基类型的长度,也就是偏移sizeof(int)长度。
3.2
- 数组名作为实参传递给子函数是,是弱化为指针的。
4.1
- 数组长度固定是因为其定义的整型、浮点型、字符型变量、数组变量都在栈空间中,而栈空间的大小在编译时是确定的。
- 如果使用的空间大小不确定,那么就要使用堆空间。
- malloc需要使用的头文件#include <stdlib.h>
- 向malloc申请一片空间,malloc返回对应空间的首地址,分配接下来一部分空间。
- malloc返回值为void*,即无类型指针,其只能用来存储一个地址且不能偏移。
- 堆空间是借来的,不用的时候要free掉,释放申请的空间。
- 释放申请的空间时,给的地址必须是最初使用malloc申请时返回给我们的地址。
- 注意指针本身大小,和其指向的空间大小,是两码事,不能和前面的变量类比去理解。
4.2
- 堆是动态的,但是堆的效率比栈低得多。
- 栈空间在子函数结束后被释放了,函数栈空间释放后,函数内的所有局部变量消失。
- 堆空间在整个进程中一直有效,不会因为函数执行结束而释放,只有free时,堆空间才会释放。
[一篇读懂]C语言五讲:指针相关推荐
- c语言位向量机伞_一文读懂C语言精华-指针变量和指向指针的指针
1978年贝尔实验室正式发表C语言,受到众多IT从业者的热捧,即使41年过去了,C语言仍然牢牢占据最受欢迎编程语言前二的位置,许多人的程序开发之路也是从C语言开始的. 编程语言受欢迎排名 C语言简洁紧 ...
- 如何读懂 C 语言复杂的声明
如何读懂 C 语言复杂的声明 作者能力有限, 如果您在阅读过程中发现任何错误, 还请您务必联系本人,指出错误, 避免后来读者再学习错误的知识.谢谢! 参考<<C专家编程>> 废 ...
- 面试官问你斐波那契数列的时候不要高兴得太早 搞懂C语言函数指针 搜索引擎还可以这么玩? 那些相见恨晚的搜索技巧...
面试官问你斐波那契数列的时候不要高兴得太早 前言 假如面试官让你编写求斐波那契数列的代码时,是不是心中暗喜?不就是递归么,早就会了.如果真这么想,那就危险了. 递归求斐波那契数列 递归,在数学与计算机 ...
- 一篇读懂分布式架构下的负载均衡技术:分类、原理、算法、常见方案等
1.引言 关于"负载均衡"的解释,百度词条里:负载均衡,英文叫Load Balance,意思就是将请求或者数据分摊到多个操作单元上进行执行,共同完成工作任务. 负载均衡(Load ...
- 一篇读懂--mybatis的缓存
一篇读懂–mybatis的缓存 MyBatis的缓存指的是缓存查询结果,当以后使用相同的sql语句.传入相同的参数进行查询时,可直接从mybatis本地缓存中获取查询结果,而不必查询数据库. myba ...
- 语言编程思维陈萌_这本书告诉你,计算机为何能读懂人类语言,编程思维让你更聪明...
在这个互联网时代,我们出门购物.拍照.打电话.浏览信息,只需要带上一部智能手机就可以了.如果在20年前,要实现这些功能,需要带一大堆东西.如今,我们口袋里的这台计算机,和50年前送宇航员上月球的那台计 ...
- 一篇读懂:Android手机如何通过USB接口与外设通信(附原理分析及方案选型)
更多技术干货,欢迎扫码关注博主微信公众号:HowieXue,共同探讨软件知识经验,关注就有海量学习资料免费领哦: 目录 0背景 1.手机USB接口通信特点 1.1 使用方便 1.2 通用性强 1.3 ...
- 一篇读懂无线充电技术(附方案选型及原理分析)
更多技术干货,欢迎扫码关注博主微信公众号:HowieXue,一起学习探讨软硬件技术知识经验,关注就有海量学习资料免费领哦: 目录 一篇读懂无线充电技术(附方案选型及原理分析) 0.背景 1.无线供电特 ...
- 一篇读懂:Android/iOS手机如何通过音频接口(耳机孔)与外设通信
一文读懂Android/iOS手机如何通过音频接口与外设通信 更多技术干货,欢迎扫码关注博主微信公众号:HowieXue,一起学习探讨软硬件技术知识经验,关注就有海量学习资料免费领哦: 目录 一文读懂 ...
最新文章
- Electron-builder打包详解
- uniapph5授权成功后返回上一页_被成功验证过的的7条选品思路(收藏)
- CSS3运算 calc()函数是怎么实现计算
- 微信朋友圈+html+字体,一键修改微信朋友圈字体,快来试试吧
- HDU1069 最长上升子序列
- python可视化分析网易云音乐评论_网易云音乐热门评论api分析
- 解决openresty http客户端不支持https的问题
- (3)FPGA面试题2倍分频
- 二代征信系统上线 这些内容与你有关
- xpath mysql_xpath查询是否具有像mysql这样的Limit选项
- jsp开发教程之 仿MOP论坛 二(数据库,界面设计篇)
- Android Fragment 切换多个界面 重叠问题 隐藏问题
- 计算机配件详情图解,电脑装机教程,详细教您怎么组装电脑
- JavaScript(JS)网页--动态生成表格
- 富媒体超级短信(多媒体短信、视频短信)亮点、应用场景
- 蓝牙初学者要知道的几件事
- 在电路中运用叠加定理时,储能元件(电容,电感)的初始值只能计算一次
- [网易IM通讯]推送小结
- 一文读懂linux 下zram
- springcloudalibaba学习分享