11、 数组指针
回顾:
数组名
是数组的首地址
,也是第0个元素的地址,是个常量,数组名加 1 指向下一个元素。
所以,在二维数组a中,a + 1指向下一个元素,即下一个一维数组,即下一行。
🌰例子:
int a[2][3] = { {1, 2, 3}, {4, 5, 6} };
a是二维数组的首地址,也是数组中第一个元素一维数组{1, 2, 3}的首地址;
a+1则表示首地址的下一个元素地址,即二维数组a中的第二个元素{4, 5, 6} 的首地址;
数组指针
指向数组的指针;即本身是个指针,指向一个数组,加 1 跳一个数组,即指向下一个数组。数组指针的作用就是可以保存
二维数组的首地址
。
格式:int(* p)[n] = { 0 }
;// n为要定义的个数int a[5] = {1, 2, 3, 4, 5};// 定义并初始化5个int类型的数组 int (\*p)[5] = &a;// 定义一个指向含有5个int类型数组的指针p(数组指针),并把**a数组的首地址**给了指针p。 int I; for(i = 0; i < 5; I++) { printf("%d\n", *(*p + i)); //或者 printf("%d\n", (*p)[I]); }
⚠️⚠️⚠️ 注意:
为什么不直接用 int (*p)[5] = a; 呢?
这是因为虽然a和&a的值都是相同的,但它们的意义却是不相同的:
1、a指的是这个数组的第一个元素
的首地址。
2、&a 指的是这整个数组
的首地址。
3、a是指针类型
,即int *类型;&a是数组指针类型
,即int (*)[]类型;打印数组中的元素,为什么用*(*p + i)呢?
1、*p+i 所指的是元素在数组中的位置(地址)
2、*(*p + i) 就表示相应位置上元素的值
3、p+1 表示p + 整个数组a的长度
4、二维数组p[x][y]等价于*(*(p + x) + y),即:p[x][y] <==> *(*(p + x) + y)
容易混淆的内容
指针数组
:是个数组,有若干个相同类型的指针构成的集合
🌰:int *p[10]; // 数组p有10个 int*类型的指针变量构成,分别是p[0]~p[9]
数组指针
:是个指针,指向一个数组,加 1 跳一个数组
🌰:int (*p)[10];// p是个数组指针,p + 1指向下一个数组,跳过10个整型内存空间
指针的指针
:一个二级指针
🌰:int **p;// p是指针的指针
int *q;
p = &q;
附录:
【数组指针】 仅此一篇 让你深刻理解数组指针
12、数组名取地址:变成 数组指针
一维数组名取地址,变成一维数组指针,即加 1 跳一个一维数组。
🌰:
int a[10];
a指的是这个数组的第一个元素
的首地址
a + 1 跳一个整型元素,即为a[1]的地址
a 和 a + 1 相差一个元素,4个字节&a 指的是这
整个数组
的首地址,就变成了一个一维数组指针,是int (*a)[10]类型的。
&a 和 (&a) + 1相差一个数组,即10个元素,40个字节
🌰:int a[4][5]; // a + 1跳5个整型,(&a) + 1 跳 4行5列(80个字节)
总结:c语言规定,数组名取地址,变成数组指针,加 1 跳一个数组。
13、数组名 和 指针变量的区别
🌰:int a[10];
int *p;
p = a;
相同点
:
a 是数组名,是数组首地址,是a[0]的地址,当p = a时,此时p也保存了a[0]的地址,即a和p都指向a[0],所以在引用数组元素的时候,a和p等价。如:a[1]、*(a+1)、p[1]、*(p+1)都是对数组a中a[1]元素的引用。
不同点
:
1、a是常量、p是变量
可以给p赋值,但不能给a赋值。
2、对a、p取地址结果不同
因为a是数组名字,所以对a取地址结果为数组指针
。
p是指针变量,所以对p取地址(&p)结果为指针的指针
。
14、指针和函数的关系
14.1、函数接受的参数:可分为实际参数、形式参数两种。
实际参数:传递给函数的参数。
形式参数:函数内使用的参数。
14.2、函数参数的传递方式:可分为值传递、地址(指针)传递两种。
值传递:实参和形参是两个对象,内存地址不同,形参是对实参值的复制。
地址(指针)传递:实参和形参是同一个对象,它们两个指针指向同一内存地址,即实参值的地址,这里形参是对实参值的引用。
⚠️注意:
1、想要改变主调函数中变量的值,必须传变量的地址,而且必须是通过 *+地址 去赋值,无论这个变量是什么类型。
2、如果实参是一个普通变量,地址传递的话就需要形参是一级指针
。
3、如果实参是一个一级指针,地址传递的话就需要形参是二级指针
。
4、依此类推。
14.3、函数接受的参数是数组
将数组作为参数传递给函数,
不存在
值传递和地址传递,本质都是地址传参
;所以在函数内部对数组进行改变,则函数执行完毕后,原本的数组也会改变,因为传递给函数的都是数组的地址。
14.4、指针函数:指针作为函数的返回值
一个函数可以返回整型、浮点型、字符型的数据,也可以返回一个指针。
指针函数本质是一个函数,只不过函数的返回值是个指针。
14.5、函数指针:指针保存函数的地址
在程序运行时,会将函数的指令加载到内存代码段,所以函数也有起始地址。
C语言规定:
函数的名字
就是函数的首地址
(和数组相同),即函数的入口地址;我们可以定义一个指针变量,来存放函数的地址,这个指针变量就是函数指针变量。定义方式:
返回值类型 (*函数指针变量名)(形参列表)
int (*p)(int, int); //定义一个函数指针变量p,p指向函数,返回类型为整型,有两个整型参数。
int method(int x, int y) { }//定义一个函数
p = method;//此时就可以用函数指针变量p,存放这里函数的地址
14.6、函数指针数组:本质是一个数组,数组里面的每个元素都是一个函数指针。
定义方式:
返回值类型 (*函数指针变量名 [函数指针的个数] )(形参列表)
int (*p[10])(int, int);// 定义函数指针数组,有10个元素;每个元素都是函数指针变量,指向的函数,返回类型为整型,有两个整型参数。
14.7、函数指针最常用的地方
最常用于将一个函数作为参数传递给另一个函数的时候,这个“将一个函数作为参数传递给另一个函数”,将这个函数称为
回调函数
。
15、经常容易混淆的指针
第一组:指针数组、数组指针、指针的指针
1、int *a[10;] //
指针数组
,数组a中有10个整型的指针变量。
2、int (*a)[10];//数组指针
(一般用于二维数组),它是个指针变量,占4个字节,存地址遍号。它指向一个数组,加1的话表示指向下一个数组。
3、int **p;//指针的指针
,保存指针变量的地址。常见用法1:
int **p;
int *q;
p = &q;常见用法2:
int **p;
int *q[10];
// q是指针数组的名字,是指针数组首地址,是q[0]的地址;q[0]是个int*类型的指针;所以q[0]指针变量的地址,即为int **类型。
p = &q[0]; // 等价于p = q
第二组:指针函数、函数指针
1、int *func(void);//
指针函数
,注意:*func没有用括号括起来,它是个函数声明,声明这个函数的返回值为int*类型.
2、int (*func)(void);// 注意:*func用括号括起来了,*修饰func是个指针变量,是个函数指针变量
,存放函数地址;它指向的函数,返回值类型为int型,没有参数列表。
16、特殊指针
1、void *:通用指针
对应类型的指针只能存放对应类型的数据地址。
char *类型的指针指向char型的数据;
int *类型的指针指向int型的数据;
float *类型的指针指向float型的数据;
void *为通用指针,任何类型的指针都可以给void *类型的指针变量赋值。
eg:int *p; void *q; q = p;//这样是可以行得通的,且不用强制类型转换
⚠️注意:void *类型的指针变量,在32位系统下,占4个字节。
2、NULL:空指针
char *p = NULL;// p哪都不指向,也可以认为指向内存编号为0的存储单元;在p的四个字节中,存放的是0x00 00 00 00,一般NULL用在给指针初始化。
main函数传参
int main(int argc, char *argv[]) { }
argc:int类型的变量,标识命令终端传入的参数的个数。
argc:指针数组,用于保存每个终端命令传入的参数。
写在最后:
指针的详解与应用
指针的详解与应用笔记
网友评论