• C语言指针进阶内容讲解-成长路上必看



    前言: 本文内容相较于初学者可能会有些难度,适合已经对C语言指针有过一点了解,或者指针部分基础薄弱的老铁们学习!耐心看完一定会有所收获的。

    1,指针数组

    关于指针数组,我们首先一定要先认清它的含义是什么?从名字中不难得出,指针数组,是一个存放指针的数组,指针是这个数组的类型,就像整形数组和字符数组一样

    int arr[10];-—》整形数组
    char ch[20];—》字符数组

    那么既然指针数组也是数组,那它也一定有自己的命名格式

    int* arr[10];
    char* ch[10];

    指针类型加上数组名就是指针数组的命名格式,arr是数组名,由于[]的优先级比* 高,所以arr先和[]组成一个数组,数组的类型是int *
    我们参考数组,数组有一维和二维之分,那么指针数组也有一级和二级,甚至三级之分

    int** arr[10];—》二级指针数组

    没错,在一级的基础上多加一个 * 便是二级指针的命名
    那么C语言中指针数组是怎么使用的呢?

    #include 
    
    int main()
    {
    	int arr[] = { 0, 1, 2 };
    	int arr1[] = { 1, 2, 3 };
    	int arr2[] = { 2, 3, 4 };
    	int arr3[] = { 3, 4, 5 };
    	int arr4[] = { 4, 5, 6 };
    
    	int* p[5] = { arr, arr1, arr2, arr3, arr4};
    
    	int i = 0;
    	for (i = 0; i < 5; i++)
    	{
    		int j = 0;
    		for (j = 0; j < 3; j++)
    		{
    			printf("%d ", *(*(p + i) + j));
    			//*(p+i)找到是指针数组的元素,再加j找到的是对应数组的元素
    			//*(*(p + i) + j)再进行解引用找到对应数组的元素
    		}
    		printf("\n");
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    当我们要使用多个数组时,一个一个的进行打印的话,代码会很臃肿,代码量也会增多,这时创建一个指针数组,利用指针数组去找到对应的数组会提高代码的灵活性,也会方便日后的修改
    运行结果:
    在这里插入图片描述

    2,数组指针

    2.1数组指针的定义

    数组指针的概念也是从名字就可以得知,是一个指向数组的指针,大家一定不要和指针数组搞混了

    数组指针指向数组的一个指针
    指针数组存放指针的一个数组

    他俩的本质区别还是很大的,我们先来回归一个指针

    指向int类型的指针是int *p;
    指向char类型的指针是char *p;
    指向floar类型的指针是float* p;
    
    • 1
    • 2
    • 3

    那么同样的,指向数组的指针如下:

    int (*p)[10];
    char (*p)[10]

    p是数组名,由于()的优先级最高,所以p先和 * 结合组成一个指针,指针指向的内容是int [10]类型的数组。

    为了防止大家搞混指针数组和数组指针的概念,这里我们再次将它们俩放在一起看一下

    指针数组:int* arr[10];
    	是一个大小为10的数组,数组存放int*的指针
    数组指针:int (*p)[10];
    	是一个指针,指向的是一个大小为10的int类型的数组
    
    • 1
    • 2
    • 3
    • 4

    2.2数组名详解

    我们通过一段代码来观察数组名

    int main()
    {
    	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    	int(*p)[10] = arr;
    
    	printf("arr:   %p\n", arr);
    	printf("arr+1: %p\n", arr+1);
    	printf("&arr:  %p\n", &arr);
    	printf("&arr+1:%p\n", &arr+1);
    
    	printf("P:     %p\n", p);
    	printf("P+1:   %p\n", p+1);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    运行结果
    在这里插入图片描述
    我们知道,数组名是首元素的地址,所以arr+1跳过4个字节(int类型的字节大小是4)指向数组中的第二个元素。
    通过运行结果来看&arr与arr的地址是相同的,但是加一之后的结果却是不同的,从004FF89C到004FF8C4,增加了10,即数组的大小,所以虽然arr和&arr的地址相同,但是本质却是不同的

    &arr表示的是整个数组的地址
    arr表示的是数组首元素的地址
    
    • 1
    • 2

    那么我们再看,p和p+1的结果与&arr和&arr+1的结果是相同的,这也证明了,数组指针指向的是数组的地址,并非首元素的地址
    了解完以上内容之后,我们再来看一下,关于数组指针到底是怎么使用的?

    2.3数组指针的使用

    数组指针的使用,多用在二维数组传参时,当然一维数组传参时,也是可以使用的,我们先从一维数组开始讲起

    void print(int (*p)[5], int sz)//使用数组指针来接收
    {
    	int i = 0;
    	for (i = 0; i < sz; i++)
    	{
    		printf("%d ", (*p)[i]);
    	}
    }
    //打印数组内容
    int main()
    {
    	int arr[5] = { 1,2,3,4,5 };
    	
    	print(arr, 5);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这里我们不再使用数组或者指针作为形参,而是改用我们刚学的数组指针。
    这里int (*p)[5]中的5可有可无,不过最好还是写上。

    然后就是在打印数组内容的时候要注意一点的是,要先对p解引用(*p)之后再通[]找到对应的元素下标,千万不能写成 *(p+i),因为数组指针指向的是数组的地址,而不是首元素地址,这样写将造成不可预料的结果
    如果在上面的代码中想要不使用[]也是可以的,应该写成

    printf("%d ", *((*p) + i));
    
    • 1

    先对p解引用找到数组,然后再加上i,再解引用找到对应的元素,输出的结果也是一样的

    接下来我们再来看一段二维数组的代码

    #include 
    
    void print_arr1(int arr[3][5], int row, int col)
    {
    	int i = 0;
    	int j = 0;
    	for (i = 0; i < row; i++)
    	{
    		for (j = 0; j < col; j++)
    		{
    			printf("%d ", arr[i][j]);
    		}
    		printf("\n");
    	}
    }
    void print_arr2(int(*arr)[5], int row, int col)
    {
    	int i = 0;
    	int j = 0;
    	for (i = 0; i < row; i++)
    	{
    		for (j = 0; j < col; j++)
    		{
    			//printf("%d ", arr[i][j]);
    			printf("%d ", *(*(arr + i) + j));
    		}
    		printf("\n");
    	}
    }
    int main()
    {
    	int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
    	print_arr1(arr, 3, 5);
    	//数组名arr,表示首元素的地址
    	//但是二维数组的首元素是二维数组的第一行
    	//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
    	//可以数组指针来接收
    	print_arr2(arr, 3, 5);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    print_arr1是使用同等大小的二维数组作为形参接受,没什么好说的,唯一值得注意的一点是,二维数组作为形参时,列的大小是不能省略的也就是

    void print_arr1(int arr[3][5], int row, int col)
    行(3)可以省略,但是列(5不能省略)
    
    • 1
    • 2

    这和二维数组的创建是一个道理,编译器可以通过你有几列来自动确定行数,但是不能不知道列数

    print_arr2便是使用了一个数组指针作为形参

    void print_arr2(int(*arr)[5], int row, int col)
    arr是数组指针变量名,指向的是一个大小为10的int类型的数组
    
    • 1
    • 2

    要注意的是,数组指针在指向一个二维数组的时候,指向的其实是第一行的地址,并非第一个元素的地址
    在这里插入图片描述
    所以我们在接下来打印的时候,就要格外注意一下

    printf("%d ", arr[i][j]);基础写法--没问题
    
    • 1

    我们来讲一下这个写法

    printf("%d ", *(*(arr + i) + j));
    
    • 1

    首先*(arr+i)找到对应的行数,之后再再加上列(j)找到对应元素的地址,之后再解引用找对应的元素。
    知道之后逻辑就是很简单的了

    3,数组参数&指针参数

    在写代码的时候,难免会将【数组】或者【指针】作为实参传给函数,那么函数的参数该如何设计呢?

    3.1一维数组传参

    先来看一下一维数组的情况
    我们来判断一下test函数的形参设计的是否合理

    #include 
    void test(int arr[])//ok?
    {}
    void test(int arr[10])//ok?
    {}
    void test(int* arr)//ok?
    {}
    int main()
    {
    	int arr[10] = { 0 };
    	test(arr);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    arr是一个大小为10的一维数组
    所以函数的形参同样设计成一维数组时肯定可以,形参arr的大小可有可无。
    一维数组使用一级指针也是ok的,本身test(arr)传的就是函数的地址,使用一级指针接受,没毛病。

    void test2(int* arr[20])//ok?
    {}
    void test2(int** arr)//ok?
    {}
    int main()
    {
    	int* arr2[20] = { 0 };
    	test2(arr2);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这里创建的是一个大小为20的一级指针数组。
    同样使用一个大小相同的一级指针数组来接受,肯定也是没问题的,道理和使用数组来接受数组一样。
    那么使用

    void test2(int** arr)
    
    • 1

    是否也行呢?
    不难看出int** arr是一个二级的指针,test2传过去的是一个一级指针,那么使用一个二级指针来接受也是可以的
    在这里插入图片描述

    3.2二维数组传参

    接下来看一下二维数组的情况

    void test(int arr[3][5])//ok?1
    {}
    void test(int arr[][])//ok?2
    {}
    void test(int arr[][5])//ok?3
    {}
    void test(int* arr)//ok?4
    {}
    void test(int* arr[5])//ok?5
    {}
    void test(int(*arr)[5])//ok?6
    {}
    void test(int** arr)//ok?7
    {}
    int main()
    {
    	int arr[3][5] = { 0 };
    	test(arr);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    第一个test和第三个test肯定是可以的没问题,第二个test因为缺少列数,所以是错误的。

    第四个test函数参数是一个int类型的一级指针,可以使用一级指针来接受二维数组的地址吗?
    并不可以,因为我们知道,二维数组在传参的时候,数组名代表的是一行的地址(一个一维数组的地址),你用一个int类型的指针来接受一个数组肯定是行不通的

    第五个test是个指针数组,指针数组存放的是指针,而我们二维数组名代表的是一个一维数组的地址,所以这种写法肯定也是不行,类型不匹配!

    第六个test参数是数组指针,我们知道二维数组其实可以看成是由多个一维数组构成的,那么这里的arr就可以看成是由三个一维数组,每个数组有五个元素构成的二维数组,而二维数组名代表的就是第一个数组的地址,所以使用数组指针作为函数参数也是可以的。

    第七个test的形参是一个二级指针,首先我们要搞清楚二级指针是用来存放一级指针的地址的,而我们test传过去的是一个二维数组名(一个一维数组的地址),那么肯定也是会发生类型不匹配。
    所以在使用指针接收二维数组时,只有第六个test是可以的,其他都不行。

    3.3一级指针传参

    了解完了一维数组和二维数组传参,我们现在来看一下
    一级指针和二级指针传参的情况
    
    • 1
    • 2
    #include 
    void print(int* p, int sz)
    {
    	int i = 0;
    	for (i = 0; i < sz; i++)
    	{
    		printf("%d\n", *(p + i));
    	}
    }
    int main()
    {
    	int arr[10] = { 1,2,3,4,5,6,7,8,9 };
    	int* p = arr;
    	int sz = sizeof(arr) / sizeof(arr[0]);
    	//一级指针p,传给函数
    	print(p, sz);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    这里我们在设计函数的参数时,一级指针传参就还是用一级指针来接收一级指针就可以了, 代码不会有任何毛病,然后遍历整个数组就可以将数组的内容全部打印出来。但是有一个很值得思考的问题

    当我们函数形参是一级指针的时候,函数的实参可以是什么?
    假设我们设计了一个函数如下:

    int test(int* pt);
    
    • 1

    大家可以思考几分钟…
    该函数的实参可以是一下几种类型:
    1.一个整形变量的地址

     int a = 0;
     test(&a);
    
    • 1
    • 2

    2.一个一级指针(如上面代码所示)
    3.一个数组名

    int arr[10];
    test(arr);
    
    • 1
    • 2

    数组名其实是首元素的地址,所以我们用一个一级的指针来接受也是可以的

    3.4二级指针传参

    同样的,当二级指针传参的时候,也用二级指针来接收就好了

    #include 
    void test(int** ptr)
    {
    	printf("num = %d\n", **ptr);
    }
    int main()
    {
    	int n = 10;
    	int* p = &n;
    	int** pp = &p;
    	test(pp);
    	test(&p);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    二级指针用来存放一级指针的地址,所以test(&p)也是没问题的

    同样的一个问题:当函数的形参设计成二级指针时,函数的形参可以是什么呢?

    首先二级指针和一级指针的地址肯定是没问题的(见上面代码区)
    那么还有什么呢?
    我们知道二级指针可以用来存放一级指针的地址,那联合我们上面讲过的指针数组,我们说指针数组是用来存放指针的数组,那么数组名便是首元素(第一个指针)的地址,所以形参还可以是同类型的指针数组名,如下图:
    在这里插入图片描述

    4,函数指针

    因为内容太多,所以有关函数指针的内容,后期我会单出一篇博客来讲解,见谅!

    希望这边文章能给大家带来帮助,欢迎大家评论留言Bey!

  • 相关阅读:
    取代 Mybatis Generator,这款代码生成神器配置更简单,开发效率更高!
    Ai-WB2系列的固件烧录指导
    scipy最优化
    这部《从零开始学架构》神书就此霸榜
    Spring事务失效常见的八种场景
    喜报!易基因“同源基因特异性甲基化时序数据分析方法”获专利授权
    一、T100应收管理之应收基础数据设置篇
    猿创征文|十 BLE协议之L2CAP
    运行ps软件提示由于找不到vcruntime140.dll无法继续执行代码怎么修复
    仅需4步,即可用 Docker搭建测试用例平台 TestLink
  • 原文地址:https://blog.csdn.net/Javaxaiobai/article/details/126299190