指针的高级应用
1、指针数组和数组指针
(1)指针数组
也就是说是数组,只不过数组保存的是指针,也就是专门用于保存地址的数组
int *p[5];
也就是说,p[] 数组里面保存的都是地址,、
使用:
*a[i]; // 保存的地址的嘛,使用肯定就是加上 星号
(2)数组指针
也就是说是一个指针,但是这个指针是只能指向数组的一个指针,
int (*p)[5]
例子代码:
int a[4] = { 1, 2, 3, 4 }; int *b[4]; //指针数组 int(*c)[4]; //数组指针 //将数组c中元素赋给数组a for (int i = 0; i < 4; i++) { // 指针数组 保存数组的每个元素的地址 b[i] = &a[i]; } cout << "指针数组进行打印"<<endl; for (int i = 0; i < 4; i++) { // 指针数组 保存数组的每个元素的地址 cout << *b[i] << ""; } c = &a; // 数组指针指向数组 cout << endl; cout << "数组指针进行打印" << endl; for (int i = 0; i < 4; i++) { // 数组指针 cout << (*c)[i] << ""; } while (1); }
2、函数指针与指针函数
函数体的本质其实就是一块代码体,这一块代码体是在内存连续分布的,所以这块代码的第一个地址就重要了,而函数名其实就是一个指针,指向函数首地址的指针。
(1)指针函数
比如:
int *func(int x, int y)
因此,函数func()运行,结束的时候,返回一个int * 类型的地址。
当然函数的接收的,也必须是相同的类型(int *)的进行接收。
格式:
类型 * 函数名(参数类型),返回的是地址,这个比较好理解
(2)函数指针
函数指针,实质就一个指针变量,是指向一个函数的指针变量。格式如下:
类型说明(*函数名)(参数)
其实,严格来说,这边的函数名,确切应该叫做指针,括号必须存在,如果没有了括号的话,就变成指针函数了。
int (*ptr)(int, int) // 函数指针的声明
ptr 是函数指针变量名,它的类型是int(*)(int,int),而它的返回值是int 类型。
函数指针,实质就是函数的入口地址。既然函数名表示了函数的入口的地址,因此函数名就是一个函数指针(常量)。
函数指针变量:存放了函数口入地址的变量,与其他的指针变量类型,只不过他是指向的目标是指令代码,而不是数据。
函数指针的的赋值:
函数指针变量名 = 函数名
函数指针变量名 = &函数名
这两种方法都是可以的。实际使用:
int add(int x, int y) { return x + y; } int main() { int(*ptr)(int, int); ptr = add; //ptr = &add; // 都可以的 printf("sum = %d\n",ptr(1,2)); while (1); }
int(*ptr)(int, int);
是函数指针的声明,声明的时候,要么必须和需要复制的add函数的类型全部一致,要么就必须是void,使用的时候再加以指定。
赋值可以是:ptr = add; 或者ptr = &add,这样,函数指针ptr,就指向了函数add()函数的入口地址
使用void类型做函数只针对的声明:
int add(int x, int y) { return x + y; } int main() { void(*ptr); // 函数指针的声明 // void *ptr;也可以这样声明 ptr = add;// 函数指针变量的赋值 printf("sum = %d\n",((int(*)(int,int))ptr)(4,5)); }
函数指针声明:void(*ptr);可以直接是void *ptr;但是,在调用的时候,就可以进相知类型zhuanhuan :
((int(*)(int,int))ptr)(4,5)
函数的理解: 函数其实就是一大块代码体,这一块代码体是在内存连续分布的,而指向这块代码体的首地址就是函数名。
3、typedef 关键字与函数指针的结合
typedef 关键字,是用来重命名作用。
int add(int x, int y) { return x + y; } typedef int(*FUNC)(int, int); //移植性比较好 int main() { //FUNC ptr = add; FUNC ptr = &add; // 两个函数指针的赋值都是可以的 printf("sum = %d\n",((int(*)(int,int))ptr)(4,7)); while (1); }
typedef补充:
C语言的两种类型:内建类型(int、char)、自定义类型(typedef)。
经过typedef 定义之后,其实是创建了一个新的类型typedef 与 define 的区别,别的帖子做总结。
4、二重指针
二重指针和一般的指针都是类似的,但是二重的指针是指向指针变量的指针变量,也就是指向指针的指针。
(1)二重指针指向指针变量
int a = 1;
int a = 1; int *p1; int **p2; p1 = &a; p2 = &p1; // 指向指针变量
p1 指针变量 a,而 p2 指向了 p1, 也就是指向了指针变量的指针变量。
(2)二重指针指向指针数组
int *p1[5]; // 指针数组 int *p2; int **p3; // 二重指针 p3 = p1; int a[5] = { 1, 2, 3, 4, 5 }; for (int i = 0; i < 5; i++) { p3[i] = &a[i]; } for (int i = 0; i < 5; i++) { printf("a[%d] = %d\n", i, *p3[i]); } while (1);
对于指针数组(本质上是数组,但是数组保存的内容是指针,即 int*),而数组的数组名也是指针,所以二重指针是可以指向指针数组的。
(3)应用
A、传变量的地址,来修编变量的值
一般编程中,函数传值的参数,可以通过传递一个变量的地址,然后可以通过这个地址来修改这个地址上保存的值。
void setvalue(int *p) { *p = 3.14; } int main(int argc, char *argv[]) { int a = 1; setvalue(&a); printf("a = %d\n", a); while (1); } 打印的结果是: 2
B、传地址的地址,来修改地址
当函数传参的是一个地址的时候,又想修改这个地址,这个时候必须传递指针的指针,也就是二重指针。
错误的例子:传递地址并不能修改地址
void getmemory(char *p) { p = (char *)malloc(100); } int main(int argc, char *argv[]) { char *p = NULL; getmemory(p); printf("p = %p\n", p); while (1); }
打印的结果:
p = 00000000
想通过子函数来达到修改地址,显然没有成功。
原因分析: 对于函数的传参,并没有使用C++的引用,使得对变量i引用的操作就可以等同于对变量进行操作。上面函数传递的地址,也就是指针p将值传递给形参p,传递的是值,这个时候,主函数的p并不会随着子函数的操作而受到影响。
解决的办法1:返回值解决
char *getmemory(void) { char *p = (char *)malloc(100); return p; } int main(int argc, char *argv[]) { char *p = NULL; p = getmemory(); printf("p = %p\n", p); while (1); }
打印输出: p = 0071A180
并没有传递任何值,而是通过子函数分配了一个指针之后进行返回。
解决办法2:传递指针的指针
void getmemory(char **p) { *p = (char *)malloc(100); } int main(int argc, char *argv[]) { char *p = NULL; getmemory(&p); printf("p = %p\n", p); while (1); }
打印输出:p = 0049A180
通过传递指针变量的地址,也就是传递的是一个二重地址,去修改指针变量。
总结:
(1)想要修改值,那么可以传递地址
(2)想要修改地址,那么就传递地址的地址
5、二维数组
二维数组: int a[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
定义了两行五列的二维数组,第一维:行,2;第二维:列,5
a[0][0] a[0][1] a[0][2] a[0][3] a[0][4] a[1][0] a[1][1] a[1][2] a[1][3] a[1][4]
一维数组:
int b[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
相比于一维数组而言,一维二维从内存的存储上面来说都是一样的。一维数组:在一一块连续的内存空间上,一次保存数组的成员;对于二维数组:也是在一块连续的内存空间上面,先连续保存第一行的数据,紧接着保存第二行;所以从内存管理的角度上来说,二维数组与一维数组都是一样的。
二维数组的访问:
(1)通过下标访问
int a[i][j];
通过下标来访问 第 i 行,第 j 列。
(2)通过指针进行访问
访问 a[i][j] 的话可以通过:
*(*(a+i) + j)
来进行访问。可以将二维的数组名理解为指向第一行的行指针,指向的是是第一行第一个元素的地址,
int main(int argc, char *argv[]) { int a[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; printf("*a = %d\n", *a); printf("*a+1 = %d\n", *a+1); printf("*(a+1) = %d\n", *(a + 1)); printf("&a[0][0] = %d\n", &a[0][0]); printf("&a[1][0] = %d\n", &a[1][0]); while (1); }
打印输出:
*a = 4127892 // 代表了第一行的地址
*a+1 = 4127896 // 地址 加 1 嘛,相当于加 4
*(a+1) = 4127912 // 代表了第二行的地址
&a[0][0] = 4127892 // 第一行第一列的地址
&a[1][0] = 4127912 // 第二行第二列的地址
可见,二维数组名确实是指向了第一行第一列元素的地址,代表的是第一行的首地址。所以才可以通知,*(*(a+i)+j) 的方式进行访问二维数组,其实就是先指定了行号: i,接着执行了列号: j。而*(a+1)则是代表了二维数组第二行的地址。