C语言指针深度解析
一.指针到底是什么
指针的实质就是个变量,它跟普通变量没有任何本质区别。指针完整的名字应该叫指针变量,简称为指针。
二.指针使用三部曲
定义指针变量、关联指针变量、解引用
1 // 演示指针的标准使用方式 2 // 指针使用分3步:定义指针变量、给指针变量赋值(绑定指针)、解引用 3 int a = 23; 4 // 第一步,定义指针变量 5 int *p; 6 printf("p = %p.\n", p); // %p打印指针和%x打印指针,打印出的值是一样的 7 printf("p = 0x%x.\n", p); 8 9 // 第二步,绑定指针,其实就是给指针变量赋值,也就是让这个指针指向另外一个变量 10 // 当我们没有绑定指针变量之前,这个指针不能被解引用。 11 p = &a; // 实现指针绑定,让p指向变量a 12 13 // 第三步,解引用。 14 // 如果没有绑定指针到某个变量就去解引用,几乎一定会出错。 15 *p = 555; // 把555放入p指向的变量中
三.什么是野指针?
未初始化的指针
1 int *p; 2 *p=10;//编译器会报错
指针的越界访问
1 int arr[10]; 2 int i; 3 int*p=arr;//数组名表示数组首元素地址 4 for(i=0;i<=10;i++) 5 { 6 *p=i; 7 p++ 8 }
由于数组中只有10个元素,但是指针已经访问到第十个元素的下一个元素了,但该元素没在定义的内存中,最终p成为野指针。
怎么避免野指针?
->定义指针时,同时初始化为NULL
->在指针解引用之前,先去判断这个指针是不是NULL
->指针使用完之后,将其赋值为NULL
->在指针使用之前,将其赋值绑定给一个可用地址空间
1 int b,a=5; 2 int *p = NULL; 3 p = &a; // 正确的使用指针的方式,是解引用指针前跟一个绝对可用的地址绑定 4 if (NULL != p) 5 { 6 b=*p ; 7 } 8 p = NULL; // 使用完指针变量后,记得将其重新赋值为NULL
四.指针数组,数组指针
数组int a[10]中a ,a[0] ,&a ,&a[0])的理解.:
a就是数组名,a[0]表示数组的首元素,&a表示整个数组的首地址,&a[0]就是数组第0个元素的首地址。
字面意思来理解指针数组与数组指针:
指针数组的实质是一个数组,这个数组中存储的内容全部是指针变量。
数组指针的实质是一个指针,这个指针指向的是一个数组。
指针数组与数组指针的表达式:
1 int *p[10]; //指针数组 2 int (*p)[10]; //数组指针 3 int *(p[10]); //指针数组
指针数组:
即数组中存放的全都是指针
1 int a=10; 2 int b=90; 3 int c=40; 4 int* parr[3]={&a,&b,&c};
1 #include<stdio.h> 2 int main() 3 { 4 int arr1[] = { 1,2,3,4,5 }; 5 int arr2[] = { 2,3,4,5,6 }; 6 int arr3[] = { 3,4,5,6,7 }; 7 int* arr4[] = {arr1,arr2,arr3}; 8 int i,j; 9 for (i = 0; i < 3; i++) 10 { 11 for (j = 0; j < 5; j++) 12 { 13 printf("%d ", arr4[i][j]); 14 } 15 printf("\n"); 16 } 17 return 0; 18 }
数组指针:
1 #include<stdio.h> 2 void print1(int* p, int sz) 3 { 4 int i; 5 for (i = 0; i < 10; i++) 6 { 7 printf("%d ", *(p + i)); 8 } 9 } 10 void print2(int(*p)[10], int sz) 11 { 12 int i; 13 for (i = 0; i < 10; i++) 14 { 15 printf("%d ", *(*p + i)); 16 } 17 } 18 int main() 19 { 20 int arr[10] = { 1,2,3,4,5,6,7,8,9,0 }; 21 int sz = sizeof(arr) / sizeof(arr[0]); 22 print1(arr, sz); 23 printf("\n"); 24 print2(&arr, sz); 25 return 0; 26 }
五.函数指针
所谓函数指针,就是可以指向函数的指针,函数和各类型变量一样,也有自己的地址。
1 #include <stdio.h> 2 3 4 void func1(void) 5 { 6 printf("I am func1.\n"); 7 } 8 9 int main(void) 10 { 11 void (*pFunc)(void); 12 //pFunc = func1; 13 pFunc = &func1; // &func1和func1做右值时是一模一样的,没任何区别 14 pFunc(); // 用函数指针来解引用以调用该函数 15 16 return 0; 17 18 } 19
六.指针与函数传参
普通变量作为函数形参
1 2 void func1(int b) 3 { 4 // 在函数内部,形参b的值等于实参a 5 printf("b = %d.\n", b); 6 printf("in func1, &b = %p.\n", &b); 7 } 8 int main(void) 9 { 10 int a = 4; 11 printf("&a = %p.\n", &a); // &a = 0x7ffc3826c2f4. 12 func1(a); //b = 4, in func1, &b = 0x7ffc3826c2dc. 13 14 return 0; 15 }
数组作为函数形参
1 void func2(int a[]) 2 { 3 printf("sizeof(a) = %d.\n", sizeof(a)); 4 printf("in func2, a = %p.\n", a); 5 } 6 int main(void) 7 { 8 int a[5]; 9 printf("a = %p.\n", a); //a = 0x7ffc5e60ef00. 10 func2(a); //sizeof(a) = 8. in func2, a = 0x7ffc5e60ef00. 11 //用到的是64位操作系统,所以sizeof(a)是8 12 13 return 0; 14 }
指针作为函数形参
1 void func3(int *a) 2 { 3 printf("sizeof(a) = %d.\n", sizeof(a)); 4 printf("in func2, a = %p.\n", a); 5 } 6 int main(void) 7 { 8 int a[5]; 9 printf("a = %p.\n", a); 10 func3(a); 11 12 return 0; 13 }
结构体变量传参
1 struct A 2 { 3 char a; // 结构体变量对齐问题 4 int b; // 因为要对齐存放,所以大小是8 5 }; 6 7 void func4(struct A a1) 8 { 9 printf("sizeof(a1) = %d.\n", sizeof(a1)); 10 printf("&a1 = %p.\n", &a1); 11 printf("a1.b = %d.\n", a1.b); 12 } 13 14 15 int main(void) 16 { 17 struct A a = 18 { 19 .a = 4, 20 .b = 5555, 21 }; 22 printf("sizeof(a) = %d.\n", sizeof(a)); //sizeof(a) = 8. 23 printf("&a = %p.\n", &a); //&a = 0x7ffc93bee420. 24 printf("a.b = %d.\n", a.b); //a.b = 5555. 25 func4(a); //sizeof(a1) = 8. 26 //&a1 = 0x7ffc93bee400. 27 //a1.b = 5555. 28 29 return 0; 30 }
函数间的传参注意点:
1 #include "stdio.h" 2 3 void swap_fail(int a,int b); 4 5 int main() 6 { 7 int i,j; 8 i = 2,j = 4; 9 printf("调用前 i = %d, j=%d\n",i,j); 10 swap_fail(i,j); 11 printf("调用后 i = %d, j=%d\n",i,j); 12 return 0; 13 } 14 15 void swap_fail(int x,int y) 16 { 17 int temp; 18 printf("交换前x=%d,y=%d\n",x,y); 19 temp=x; x=y; y=temp; 20 printf("交换后x=%d,y=%d\n",x,y); 21 }
运行结果:
调用前 i=2, j=4 交换前 x=2,y=4
交换后 x=4,y=2 调用后 i=2, j=4
本程序中函数swap_fail()不能 将两个数据进行交换,因为i和j作为实参只是将自己的值传给形参x和y,i和j并没有因为形参的变化而变化。
传递的是地址:
1 #include "stdio.h" 2 3 void swap(int &a,int &b) 4 { 5 int temp; 6 temp=a; 7 a=b; 8 b=temp; 9 cout<<a<<’ ‘<<b<<’\n’; 10 } 11 12 int main(){ 13 14 int x=1; 15 int y=2; 16 swap(x,y); 17 cout<<x<<’ ‘<<y<<’\n’; 18 return 0; 19 20 }
七.二重指针,二维数组
本质上来说,二重指针和一重指针的本质都是指针变量,指针变量的本质就是变量,一重指针变量和二重指针变量本身都占4字节内存空间,二重指针本质上
也是指针变量,和普通指针的差别就是它指向的变量类型必须是个一重指针。
二重指针指向一重指针的地址.:
1 char a; 2 char **p1; // 二重指针 3 char *p2; // 一重指针 4 5 printf("sizeof(p1) = %d.\n", sizeof(p1)); //4 6 printf("sizeof(p2) = %d.\n", sizeof(p2)); //4 7 8 p2 = &a; 9 //p1 = &a; // p1是char **类型,&a是char *类型。 10 // char **类型就是指针指向的变量是char *类型 11 // char *类型表示指针指向的变量是char类型。 12 p1 = &p2; // p2本身是char *类型,再取地址变成char **类型,和p1兼容。
二重指针指向指针数组:
1 int *p1[5]; 2 int **p3; 3 p3 = p1; // p1是指针数组名,本质上是数组名,数组名做右值表示数组首元素 4 // 首地址。数组的元素就是int *类型,所以p1做右值就表示一个int * 5 // 类型变量的地址,所以p1就是一个int类型变量的指针的指针,所以 6 // 它就是一个二重指针int **;
二维数组哪个是第一维哪个是第二维:
二维数组int a[2][5]中,2是第一维,5是第二维。
一维数组的两种访问方式:
1 以int b[10]为例, int *p = b;。 2 b[0] 等同于 *(p+0); b[9] 等同于 *(p+9); b[i] 等同于 *(p+i)
二维数组的两种访问方式:
1 以int a[2][5]为例,(合适类型的)p = a; 2 a[0][0]等同于*(*(p+0)+0); a[i][j]等同于 *(*(p+i)+j)
指针指向二维数组的第二维:
1 #include <stdio.h> 2 3 int main(void) 4 { 5 int a[2][5] = {{1, 2, 3, 4, 5}, {6, 7, 8, 9, 10}}; 6 7 printf("a[1][3] = %d.\n", a[1][3]); //9 8 printf("a[1][3] = %d.\n", *(*(a+1)+3)); //9 9 10 //int *p1 = a; // 类型不匹配 11 //int **p2 = a; // 类型不匹配,会报警告 12 13 // 指针指向二维数组的数组名 14 int (*p3)[5]; // 数组指针,指针指向一个数组,数组有5个int类型元素 15 p3 = a; // a是二维数组的数组名,作为右值表示二维数组第一维的数组 16 // 的首元素首地址,等同于&a[0] 17 p3 = &a[0]; 18 19 printf("a[0][3] = %d.\n", *(*(p3+0)+3)); //4 20 printf("a[1][4] = %d.\n", *(*(p3+1)+4)); //10 21 22 // 指针指向二维数组的第一维 23 //int *p4 = &a[0]; // 不可以 24 int *p4 = a[0]; // a[0]表示二维数组的第一维的第一个元素,相当于是 25 // 第二维的整体数组的数组名。数组名又表示数组首元素 26 // 首地址,因此a[0]等同于&a[0][0]; 27 28 int *p5 = &a[0][0]; 29 printf("a[0][4] = %d.\n", *(p4+4)); //5 30 31 // 指向二维数组的第二维 32 int *p6 = a[1]; 33 printf("a[1][1] = %d.\n", *(p6+1)); //7 34 35 return 0; 36 }
八.字符串指针
字符串存放方式:
1 char a[5] = "windows"; 2 char *p = "linuxddd";
C语言没有原生字符串类型,C语言使用指针来管理字符串:
C语言中定义字符串方法:char *p = "linux";此时p就叫做字符串,但是实际上p只是一个字符指针(本质上就是一个指针变量,只是p指向了
一个字符串的起始地址而已),本质上是指针指向头、固定尾部的地址相连的一段内存。
指向字符串的指针和字符串本身是分开的:
char *p = “linux”;在这段代码中,p本质上是一个字符指针,占4字节;"linux"分配在代码段,占6个字节;实际上总共耗费了10个字节,这10个字节中:4字节的指针p叫做字符串指针,5字节的用来存linux这5个字符的内存才是真正的字符串,最后一个用来存’\0’的内存是字符串结尾标志。
字符数组初始化与sizeof、strlen:
char a[5] = "windows"; printf("sizeof(a) = %d.\n", sizeof(a)); // 5 printf("strlen(a) = %d.\n", strlen(a)); // 5 char *p = "linuxddd"; printf("sizeof(p) = %d.\n", sizeof(p)); // 4 printf("strlen(p) = %d.\n", strlen(p)); // 8
九.指针的注意点
什么时候数组和指针是相同的:
表达式中的数组名就是就是指针
1 int a[10], *p, i = 2; 2 p = a; 3 p[i];
C语言把数组下标作为指针的变异量
1 int a[10],p = a; 2 for(i =0; i < 10; i++) 3 *(p + i) = 0;
在函数参数的声明中,数组名被编译器当作指向该数组第一元素的指针
1 my_function(int turnip[]) 2 { 3 }
静态指针的错误程序:
1 #include "stdio.h" 2 int main() 3 { 4 int a[9]; 5 int *pa=a; 6 for(;a<pa+9;a++) 7 scanf("%d",a); //错误 8 for(a=pa;a<pa+9;a++) 9 printf("%d",*a); //错误 10 }
a是数组名,是静态指针,其值是不能修改的,a++这样的操作不能通过编译。
正确的程序如下:
1 #include "stdio.h" 2 int main() 3 { 4 int a[9]; 5 int *pa=a; 6 for(;pa<a+9;pa++) 7 scanf("%d",pa); 8 for(pa=a;pa<a+9;pa++) 9 printf("%d",*pa); 10 return 0; 11 }
错误程序:使用没有引用任何对象的指针
1 int x[1],*px; 2 *x=2; //正确 3 *px=2; //错误
1 int x[1],*px=x; 2 *x=2; //正确 3 *px=2; //正确
浙公网安备 33010602011771号