指针知识梳理
指针1
指针即内存地址
-
地址
- 内存中每一个字节都用自己的地址(门牌号)
- &——取地址操作符:如果一个变量a的数据占据多个字节,那么&a就取第一个字节的地址(较小那个的地址),之后便可顺藤摸瓜找到剩余几个地址。
-
指针
-
指针变量:用于存放地址。
-
创建一个指针—— 数据类型 * 指针名
- 数据类型说明了指针指向的数据的类型
- “*” 号表示这是一个指针
- 指针名 即一个指针变量
-
不同指针类型的影响
- ( 指针类型应尽量与指针指向的数据的类型相同)
- 指针±整数:指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离),如:int类型的指针+1,其地址+sizeof(int)
- 解引用时:指针的类型决定了该指针在解引用时的权限(一次能操作几个字节)
如int类型的指针在解引用时可一次访问sizeof(int)个字节的内存空间 - void* 类型的指针
- 优点:可以存放任意类型的地址
- 缺点:无法进行指针±整数运算
- 用处:用在函数中,用于接收不同类型数据的地址,处理不同类型的数据
-
使用指针
- 解引用操作符——*
- 若int * pa=&a ;即pa中存放了a的地址,那么* pa 就等价于a,可用* pa 对a进行修改操作。
- 解引用操作符——*
-
指针变量的大小:
- 相同平台下指针变量的大小相同,与指针类型无关。
- 32位机器(VS中的X86环境)有32条地址总线表示0或1,将其当作一个地址,即占用了32个bit位,需要4个字节存储
- 64位机器 (X64)同理需要8个字节
-
const修饰
- const修饰变量:
- 如const int a = 0,此后就无法直接对a进行修改。
- 但可以通过指针修改
- const修饰指针变量:
- (1)const int * a,修饰指针指向的内容:这样就无法对指针指向的内容进行修改,但可以修改指针变量中存放的地址。
- (2)int * const a,修饰指针变量:指针中存放的地址不能改变,但指针指向的变量可以改变。
- const修饰变量:
-
指针运算
- 指针±整数(此处假设整数=1):
- (1)按该类型的大小移动一步
- (2)如果指针指向数组中的一个元素,那么指针±1就表示指针指向下/上一个元素
- 指针-指针
- (同类型指针)返回两指针间元素个数
- 指针的关系运算:
- (即地址比大小,判断谁在前,谁在后)
- 指针±整数(此处假设整数=1):
-
野指针
- 野指针指向的位置随机
- 成因:
- 指针未初始化
- 指针越界访问
- 指针指向的变量已经被销毁了
- 避免:
- 指针未使用时,及时置NULL(如指针变量P: P=NULL),使用前再检查其有效性(使用前判断是否为NULL)
- (NULL的值为0,0也是地址,但该地址无法使用,会报错)
- 小心指针越界!
- 避免返回局部变量的地址
- 指针未使用时,及时置NULL(如指针变量P: P=NULL),使用前再检查其有效性(使用前判断是否为NULL)
-
assert断言
- assert()包含在头文件<assert.h>中,判断内容写再括号内
- 作用:引入额外的检查,当括号条件不被满足时,就会报错并中止程序
- assert(变量):断言该变量不为零(或不为空指针)[[#^028b98|布尔语句]]
- 如何禁用assert():
- release版本下自动优化掉assert()
- debug版本可以在开头写入**#define NDEBUG**
-
传值调用与传址调用
-
-
对数组名的理解
- 数组名即数组首元素的地址(但有且只有只两个例外)
- 例外(1):sizeof(数组名)——sizeof中单独放数组名,此处数组名表示整个数组,计算整个数组的大小。
- 例外(2):&数组名——取出整个数组的地址
- &数组名+1——跳过整个数组的下一个
- &(数组名+1)——数组的第二个元素
-
一维数组传参的本质
- 数组传参传递的是数组名,将数组名传参给函数,实际上传的是数组首元素的地址,在函数中sizeof(arr)则得到的是该地址的大小。
- 一维数组传参,形参可以写成数组形式,也可以写成指针形式。
- PS:在函数中使用arr【i】调用某一数组元素还是可行的!
-
二维数组传参的本质
- 二维数组传参实际上传递的是第一行一维数组的地址
- 二维数组传参时,形参可以写成数组形式,也可以写成指针形式
-
- ( * (p+i)+j)——理解(p是一个二维数组的指针):
- ![[Pasted image 20240123092542.png]]
- 可以将p看作是一个一位数组指针:指针中的每一个元素是一组一维数组。那么p+1这是跳过一个元素来到第二个一位数组开头,即第二行一维数组开头
-
- (p+1)——对其解应用,进入一位数组内部,每一个元素是一个单独的元素。
-
二级指针
- 用于存放指针变量的地址(套娃)
- 指针数组
- 指针数组中的每一个元素都是用来存放指针的
指针3
字符指针变量
- char * pc=&ch——这串代码实际上是将ch的地址放入pc中去
数组指针变量
- 指针数组与数组指针
- 指针数组:
- 是一个数组,其中的元素为指针
- 声明语法eg:int* arr【10】;
- 数组指针:
- 是一个指针,指向一个数组
- 声明语法eg:int (* p)【10】;(理解:p是一个指针,指向一个含有10个元素的数组。int为该数组元素的类型)
- 指针数组:
函数指针变量
- 指向函数的指针,以后可通过指针来调用函数
- 函数指针变量的创建与解析:
- ![
类型重命名——typedef关键字
- 语法格式:
- (1)普通的数据类型名更改:typedef 原名 自定义名
typedef unsigned int uint; 将unsignedd int 重命名为uint
- (2)指针重命名:
- (1)普通的数据类型名更改:typedef 原名 自定义名
函数指针数组
-
是一个数组,数组中每一个元素是一个函数指针
-
声明eg:int (* arr【3】)();
- arr先于【】结合,说明它是一个数组
- 数组中每一个元素是一个int (* )()类型的函数指针
- 括号中用于存放函数参数
-
用途:转移表
-
回调函数
- 我的理解:一个程序中有函数1,和加法函数2、减法函数3、乘法函数4、除法函数5。在函数1中会根据用户输入的参数选择调用的是函数2~5中的任一个。函数1调用其它函数时是通过传递其他函数的指针来实现调用函数这一功能的。
- 函数1中要被调用函数的地址,以及运算需要用到的参数都将作为函数1的参数
- 函数名实质上也是函数的地址。
- 回调函数的声明:
函数1的返回类型 函数1名称 (被调用函数的返回类型 (*指针名)(参数类型1,参数类型2、、、)) //举个例子: void calc(int (*pf)(int,int)) { int ret=0; int x,y; scanf("%d %d",&x,&y); ret=pf(x,y);\\值得一提的是:函数1中被调用函数需要用到的参数可以在函数1中输入,如此句 printf("ret=%d\n",ret); }
-
qsort()函数的使用
- qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
- arr为数组名,此处得到数组首元素的地址
- sizeof(arr) / sizeof(arr[0])——得到该数组元素个数
- sizeof(int)——每个元素的大小(字节)
- int_cmp——函数名,一个自己创建的compare函数,返回类型为int
int int_cmp(const void *p1,const void* p2) { return(*(int* )p1 - *(int* )p2); //将void *强制类型转换为int* 后解引用,拿到地址上的数据进行比大小 }
- qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);