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;  //正确

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2022-06-07 23:49  学习&笔记  阅读(52)  评论(0)    收藏  举报