C语言指针详解-包过系列(二)目录版

渴望脱下狼皮的羊 2024-09-04 16:35:01 阅读 56

C语言指针详解-包过系列(二)目录版

1、数组名的深入理解1.1、数组名的本质1.2、数组名本质的两个例外1.2.1、sizeof(数组名)1.2.2、&数组名

2、使用指针访问数组3、一维数组传参本质4、二级指针4.1、二级指针介绍4.2、二级指针运算

5、指针数组(模拟实现二维数组)

1、数组名的深入理解

1.1、数组名的本质

数组名的本质其实就是数组首元素的地址。我们以下面的代码测试为例:

<code>#include<stdio.h>

int main()

{

int arr[10] = { 1,2,3,4,5,6,7,8,9,10};

printf("&arr[0] = %p\n ", &arr[0]);//取数组首元素地址

printf("arr = %p\n ", arr);//取数组数组名

return 0;

}

在这里插入图片描述

可见二者一模一样,故验证了数组名即为数组首元素地址。

但是可能会有读者提出以下质疑:

<code>#include<stdio.h>

int main()

{

int arr[10] = { 1,2,3,4,5,6,7,8,9,10};

printf("%d", sizeof(arr));

return 0;

}

在这里插入图片描述

数组名 arr 表示的是数组首元素的地址的话,那么输出结果应当为一个地址的大小,也就是4/8字节。这就涉及到了数组名是数组首元素地址的两个例外了,请见 1.2

1.2、数组名本质的两个例外

1.2.1、sizeof(数组名)

在关键字 sizeof 中单独放入数组名时,这个数组名表示的是整个数组,计算的是整个数组的大小,单位是字节。如上 1.1 中所示。

注意:只有是单独放入数组名时,数组名才表示整个数组,其余情况下均是计算数据类型大小。

如下面代码所示:

<code>#include<stdio.h>

int main()

{

int arr[10] = { 1,2,3,4,5,6,7,8,9,10};

printf("%d", sizeof(arr + 1));//此时 arr 就又恢复成数组首元素的地址了

return 0;

}

在 x86 环境下的运行结果:

在这里插入图片描述

在 x64 环境下的运行结果:

在这里插入图片描述

如上所示,当sizeof内部非单独放置数组名时,sizeof 计算的就是对应运行环境的地址的大小。

1.2.2、&数组名

&数组名 ,这里的数组名表示的是整个数组,取出的是整个数组的地址(注意:整个数组的地址与数组首元素地址是有区别的)

我们通过下面代码的演示使大家对此有更加清晰的认识。

<code>#include<stdio.h>

int main()

{

int arr[10] = { 1,2,3,4,5,6,7,8,9,10};

printf("&arr[0] = %p\n", &arr[0]);

printf("arr = %p\n", arr );

printf("&arr = %p\n", &arr );

return 0;

}

在这里插入图片描述

我们发现三个输出结果完全一样。不急,我们通过对地址进行加减整数运算来使他们原形毕露。演示如下:

<code>#include<stdio.h>

int main()

{

int arr[10] = { 1,2,3,4,5,6,7,8,9,10};

printf("&arr[0] = %p\n", &arr[0]);

printf("&arr[0] + 1 = %p\n", &arr[0] + 1);

printf("arr = %p\n", arr );

printf("arr + 1 = %p\n", arr + 1);

printf("&arr = %p\n", &arr);

printf("&arr + 1 = %p\n", &arr + 1);

return 0;

}

在这里插入图片描述

通过上述演示我们发现,在 x86 环境下,&arr[0] 和 &arr[0] + 1 之间相差4字节,arr 与 arr + 1 也是相差4字节。这是因为二者均表示首元素的地址, + 1 也就是跳过一个元素。

但是 &arr 和 &arr + 1 则相差40个字节,这就是因为 &arr 表示的是整个数组的地址,+ 1 操作是跳过整个数组的。

2、使用指针访问数组

由上文我们可知数组名表示数组首元素的地址,那么数组名加整数就可以访问首元素后面的元素。

请看下面代码:

<code>#include<stdio.h>

int main()

{

int arr[10] = { 0 };

int i = 0;

size_t sz = sizeof(arr) / sizeof(arr[0]);

int* parr = arr;//将数组首元素地址存储到指针 parr 中

//输入

for (i = 0; i < sz; i++)

{

scanf("%d", parr + i);//遍历数组地址

//注意,这里parr+i就已经表示数组对应下标元素的地址了,&parr+i表示的就是指针变量parr的地址加减整数了。

//scanf("%d", arr + i);也可以这样写

}

//输出

for (i = 0; i < sz; i++)

{

printf("parr[%d] = %d\n", i, *(parr + i));//输出数组元素

}

return 0;

}

在这里插入图片描述

在这里我们补充一点数组 <code>arr[ i ] 在编译前系统会将其转化为 *(arr + i) 然后再进行编译。

在这里插入图片描述

由上所述,我们进一步分析,数组名 arr 是首元素地址,可以赋值给 parr ,其实数组名 arr 和 parr 在这里是等价的。既然我们可以使用 arr[i] 访问数组,那 parr[i]也应该能访问数组。如下演示:

<code>#include<stdio.h>

int main()

{

int arr[10] = { 0 };

int i = 0;

size_t sz = sizeof(arr) / sizeof(arr[0]);

int* parr = arr;//将数组首元素地址存储到指针 parr 中

//输入

for (i = 0; i < sz; i++)

{

scanf("%d", parr + i);

}

//输出

for (i = 0; i < sz; i++)

{

printf("parr[%d] = %d\n", i, parr[i]);//等价于arr[i]

}

return 0;

}

综上,我们可以总结出以下四点:

1、数组就是数组,是一块连续的空间(与数组大小,数组元素个数,数组元素类型都有关系)

2、指针变量就是指针变量,是一个变量(4/8bytes)

3、数组名除两个特例外,表示的都是数组首元素的地址

4、可以使用指针来访问数组,数组在系统编译时转化的内容就是指针。

3、一维数组传参本质

在之前函数章节,我们已经讲过数组可以作为参数传递给函数,在本节我们讲述一下数组传参本质。

我们先用一个问题来作引,我们之前在函数外部计算过数组元素的个数,在本节我们不妨尝试在函数内部计算求数组元素个数。

#include<stdio.h>

void test(int arr[])

{

size_t sz2 = sizeof(arr) / sizeof(arr[0]);

printf("sz2 = %d\n", sz2);

}

int main()

{

int arr[] = { 1,2,3,4,5,6,7,8,9,10 };

size_t sz1 = sizeof(arr) / sizeof(arr[0]);

printf("sz1 = %d\n", sz1);

test(arr);

return 0;

}

在 x86 环境下的结果:

在这里插入图片描述

我们可见 sz2 的值并不等于 sz1 。这就涉及到数组传参的本质了。数组传参的时候传递的是数组名,也就是数组首元素的地址。

所以函数形参部分理论上应当使用指针来接收数组首元素地址。故在函数内部我们所写的<code>sizeof(arr)计算的其实是地址的大小在 x86 环境下就是4字节。sizeof(arr[0])计算的是数组首元素的大小即int类型,大小为4字节。故 sz2 的结果为1.

补充:在x64环境下的运行结果:

在这里插入图片描述

<code>sizeof(arr)/sizeof(arr[0]) == 8/4 == 2

在 x64 环境下地址的大小为8字节。

#include<stdio.h>

void test(int* arr)

{

printf("%d\n", sizeof(arr));//计算一个指针变量的大小

}

int main()

{

int arr[] = { 1,2,3,4,5,6,7,8,9,10 };

size_t sz1 = sizeof(arr) / sizeof(arr[0]);//计算数组内元素个数多少。

printf("sz1 = %d\n", sz1);

test(arr);

return 0;

}

综上所述,我们总结出以下两点:

1、数组传参的本质是传递了数组首元素的地址,所以形参访问的数组和实参的数组是同一个数组。故在形参数组中更改数组内容也会更改实参数组中的内容(传址调用由此区别于传值调用)。

2、形参的数组是不会再单独创建空间,所以形参的数组是可以省略数组大小的。

4、二级指针

4.1、二级指针介绍

指针变量也是变量,是变量就有地址,那指针变量的地址存放于何处?这就涉及到二级指针。

#include<stdio.h>

int main()

{

int a = 0;

int* p = &a;//一级指针p存储a的地址

int** pp = &p;//二级指针存储p的地址

printf("p = %p\n", p);

printf("pp = %p\n", pp);

return 0;

}

在这里插入图片描述

4.2、二级指针运算

1、<code>*pp 通过对 pp 中地址的解引用,如此找到 p ,*pp访问的就是 p

#include<stdio.h>

int main()

{

int a = 0;

int b = 10;

int* p = &a;//一级指针p存储a的地址

int** pp = &p;//二级指针存储p的地址

*pp = &b;//通过解引用更改一级指针 p 所指向的对象

printf("%d", *p);

return 0;

}

在这里插入图片描述

2、<code>**pp 先通过*pp 找到 p 然后再通过对 p 解引用找到 a。

#include<stdio.h>

int main()

{

int a = 0;

int* p = &a;//一级指针p存储a的地址

int** pp = &p;//二级指针存储p的地址

**pp = 20;//通过两次解引用更改一级指针 p 所指向的对象的内容。

//等价于*p =20

//等价于 a = 20

printf("%d", a);

return 0;

}

在这里插入图片描述

5、指针数组(模拟实现二维数组)

我们知道有整型数组,字符型数组,浮点型数组等,由此可知指针数组就是存储的元素是指针的数组。指针数组的每个元素又指向一块区域。

我们用指针数组模拟二维数组为例:

首先先编写好3个一维数组:

<code>#include<stdio.h>

int main()

{

int arr1[] = { 1,2,3,4,5 };

int arr2[] = { 6,7,8,9,10 };

int arr3[] = { 11,12,13,14,15 };

return 0;

}

然后来设置指针数组,让指针数组内存储一维数组的地址,由上文知就是数组名。

#include<stdio.h>

int main()

{

int arr1[] = { 1,2,3,4,5 };

int arr2[] = { 6,7,8,9,10 };

int arr3[] = { 11,12,13,14,15 };

int* arr[] = { arr1 , arr2 , arr3 };//编写指针数组

int i = 0;

for (i = 0; i < 3; i++)//打印模拟的二维数组

{

int j = 0;

for (j = 0; j < 5; j++)

{

printf("%d ", arr[i][j]);

}

printf("\n");

}

return 0;

}

在这里插入图片描述

在这里插入图片描述

上述代码只是模拟出了二维数组的效果,但并不是完全等于二维数组,因为上面模拟出的数组的每一行并不是连续的。

全文至此结束!!!

写作不易,不知各位老板能否给个一键三连或是一个免费的赞呢(-▽-)(-▽-),这将是对我最大的肯定与支持!!!谢谢!!!(-▽-)(-▽-)



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。