write in front :

个人主页 : @啊森要自信的主页

真正相信奇迹的家伙,本身和奇迹一样了不起啊!

欢迎大家关注点赞收藏⭐️留言>希望看完我的文章对你有小小的帮助,如有错误,可以指出,让我们一起探讨学习交流,一起加油鸭。

文章目录

  • 前言
  • ▶️、 数组名的理解
  • ▶️、 使⽤指针访问数组
  • ➡️、⼀维数组传参的本质
  • ➡️、⼆级指针
  • 、指针数组
  • ️总结

前言

本小节,我们继续深入理解指针,阿森将在本小节带你理解数组名,怎么使用指针访问数组,一维数组传参的本质,冒泡排序的方法,还有我们的二级指针创建,指针数组的,生命,创建和运用。接下来让我们启程!


▶️、 数组名的理解

  1. %d:用于打印整数。
  2. %f:用于打印浮点数。
  3. %c:用于打印单个字符。
  4. %s:用于打印字符串。
  5. %p:用于打印指针地址。
  6. %x:用于以十六进制格式打印整数。
  7. %o:用于以八进制格式打印整数。
  8. %e:用于以科学计数法打印浮点数。
  9. %u:用于以无符号整数格式打印整数。

对于数组名,我们在学习函数的时候,我们就了解到数组名arr就是数组首元素的地址,当然也可以取地址数组首元素&arr[0].

#include 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;}

运行结果:

我们看到运行结果可以看出数组名和数组首元素的地址一模一样。因此,数组名就是数组首元素(第一个元素)的地址

既然arr是是首元素的地址,那sizeof(arr)计算的也应该是计算的是数组首元素的大小,单位字节。那么他应该计算的是首元素的大小,也就是4或者8(因为分别是32位或64位环境),事实真的如此吗?

sizeof是一个运算符,用于获取数据类型或变量的大小(以字节为单位)。它的语法如下:

sizeof(type)sizeof(variable)

其中,type可以是任何数据类型,比如int、char、float等,而variable可以是任何变量名。

sizeof返回的是一个size_t类型的值,表示对应类型或变量所占用的字节数。在实际编程中,sizeof经常用于在程序中动态计算数组的大小,或者确保在处理内存分配和复制时不会出现越界的情况。

其实不然,并没有打印我们想要的? 那这怎么解释呢?
输出的结果是:40,如果arr是数组⾸元素的地址,那输出应该的应该是4/8才对。
其实数组名就是数组⾸元素(第⼀个元素)的地址是对的,但是有两个例外:

  • 1️⃣ sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰(整个数组),计算的是整个数组的⼤⼩,单位是字节
  • 2️⃣ &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素的地址是有区别的)
    除此之外,任何地⽅使⽤数组名,数组名都表⽰⾸元素的地址。
#include int main(){int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("%d\n", sizeof(arr));printf("%p\n", &arr[0]);printf("%p\n", arr);printf("%p\n", &arr);return 0;}

运行结果:

打印结果⼀模⼀样,这时候⼜纳闷了,那arr&arr有啥区别呢?

#include 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;}


这⾥我们发现&arr[0]&arr[0]+1相差4个字节,arr和arr+1 相差4个字节,是因为&arr[0] 和 arr 都是⾸元素的地址,+1就是跳过⼀个元素。
但是&arr&arr+1相差40个字节,这就是因为&arr是数组的地址,+1 操作是跳过整个数组的。
在C语言中,arr&arr有着不同的含义和用法。

  1. arr:表示数组的名称,它代表数组的首元素的地址。在大多数情况下,当使用数组名arr时,它会被隐式转换为指向数组第一个元素的指针。因此,arr表示的是数组的地址,而不是整个数组的内容。

  2. &arr:表示对整个数组的取地址操作。它得到的是整个数组的地址,而不是数组的第一个元素的地址。因此,&arr表示的是整个数组的地址,而不是数组的内容。

总结来说,arr表示数组的首元素地址,而&arr表示整个数组的地址。在大多数情况下,当我们需要传递数组给函数时,实际上传递的是数组的首元素地址,因此arr&arr在传递参数时的用法可能会有所不同。

▶️、 使⽤指针访问数组

知道了数组名,数组的地址,那我们不就可以用指针访问数组了,遍历数组的元素了,好接下来,启动!

#include int main(){int arr[10] = { 0 };//输⼊int i = 0;//整个数组大小40/单个数组字节4=10int sz = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数//输⼊int* p = arr;printf("请输入数组元素:\n");for (i = 0; i < sz; i++){/*scanf("%d", p + i);*///p每一次移动 i 个地址scanf("%d", arr+i);//既然数组名是首元素地址,理所应当也可以这样写//scanf("%d", &arr[0] + i);//那第一个地址也可以}//输出printf("输出:\n");for (i = 0; i < sz; i++){printf("%d ", *(p + i));//解引找到指针变量的空间的值进行访问}return 0;}

每个都进行验证一下:

  1. 数组名访问地址

  1. 数组首元素地址访问


3. 使用指针访问数组

  • ⚠️ 这段代码弄清楚后,我们再试一下,如果我们再分析一下,数组名arr是数组首元素的地址,可以赋值给p,其实数组名arrp在这里是等价的。那我们可以使用arr[i]可以访问数组的元素,p[i]是否也可以访问数组呢?测试一下!

#include int main(){int arr[10] = { 0 };//输⼊int i = 0;//整个数组大小40/单个数组字节4=10int sz = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数//输⼊int* p = arr;printf("请输入数组元素:\n");for (i = 0; i < sz; i++){/*scanf("%d", p + i);*///p每一次移动 i 个地址scanf("%d", arr+i);//既然数组名是首元素地址,理所应当也可以这样写//scanf("%d", &arr[0] + i);//那第一个地址也可以}//输出printf("输出:\n");for (i = 0; i < sz; i++){printf("%d ", i[arr]);//这样子是否也可以呢?printf("%d ", i[p]);//}return 0;}


哎,为什么i[arr]可以打印,其实i[p]也可以打印

  1. 在C语言中,数组名和指针的运算符[]是可以互换使用的。这是因为在C语言中,a[b]*(a + b)是等价的,即数组下标运算和指针运算是等效的。当然,如果你不太明白,可以尝试使用交换律来理解
 *(i+arr)-->*(arr+i)-->arr[i]arr[i] == *(arr+i) *(i+arr) == i[arr] p[i] == *(p+i)
  1. 因此,当你使用i[arr]时,它实际上等同于arr[i]。这是因为arr本身就代表数组的首元素地址,而i[arr]会被解释为*(arr + i),即数组首元素地址加上偏移量i,得到第i个元素的地址。同样,i[p]也等同于p[i],因为指针p也可以进行类似的偏移量运算。
  2. 虽然i[arr]i[p]在语法上是合法的,但通常不推荐这样的写法,因为它会增加代码的可读性和理解难度。更好的做法是直接使用arr[i]p[i],这样可以更清晰地表达代码的意图。

➡️、⼀维数组传参的本质

首先,让我们从一个问题开始。我们之前一直在函数外部计算数组的元素个数,但是我们能否将函数传递给另一个函数,在函数内部计算数组的元素个数呢?

void test(int arr[])//int* arr{//4/4int sz = sizeof(arr) / sizeof(arr[0]);printf("%d\n", sz);//}int main(){//数组传参的时候,传递的是并非是数组//传递的是数组首元素的地址int arr[12] = { 1,2,3,4,5,6,7,8,9,10,11,12 };int sz = sizeof(arr) / sizeof(arr[0]);test(arr);//这里的数组名就是数组首元素的地址return 0;}
  1. 环境下debug,x86环境下,结果为1.

  1. 环境下debug,x64环境下,结果为2.


分析:

  • 当数组作为函数参数进行传递时,实际上传递的是数组的首元素地址,而不是整个数组。因此,在函数内部,无法通过sizeof操作符来获取数组的大小,因为此时的arr已经退化为指针

  • 在代码中,test函数的参数arr实际上是一个指针,因此在函数内部使用sizeof(arr)并不能得到数组的大小,而是得到指针的大小。因此,在32位环境下(x86),指针的大小为4字节,所以sizeof(arr) / sizeof(arr[0])的结果为1。(同理64位,指针大小字节为8字节)

数组名是数组首元素的地址;因此在数组传参时,传递的是数组名,也就是说本质上数组传参本质上传递的是数组首元素的地址。

⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。

➡️、⼆级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪⾥?

在C语言中,二级指针是指一个指针变量,它存储的是另一个指针变量的地址。换句话说,它指向一个指针变量,而这个指针变量又指向某个数据的地址。在C语言中,我们通常使用二级指针来处理动态内存分配和多级数据结构。

下面是一个简单的示例,演示了如何声明和使用二级指针:

#include int main() {int num = 10;int *ptr1 = &num; // 一级指针,指向int类型的数据int **ptr2 = &ptr1; // 二级指针,指向int*类型的数据// 通过二级指针访问num的值printf("Value of num: %d\n", **ptr2);return 0;}

在这个示例中,ptr1是一个一级指针,它指向一个整数类型的数据numptr2是一个二级指针,它指向一个一级指针ptr1。通过**ptr2可以访问num的值。

二级指针在C语言中通常用于动态内存分配,例如在使用malloc函数分配内存时,可以返回一个指向指针的指针,以便在程序中对内存进行操作。此外,在处理多级数据结构(如多级指针数组或多级链表)时,二级指针也非常有用。
举个简单的例子:

int main(){int a = 10;int* p = &a;//取出a的地址//p是指针变量,是一级指针int * * pp = &p;//pp是二级指针return 0;}

对于⼆级指针的运算有:
• *ppa 通过对ppa中的地址进⾏解引⽤,这样找到的是 pa , *ppa 其实访问的就是 pa .

int b = 20;*ppa = &b;//等价于 pa = &b;

• **ppa 先通过 *ppa 找到 pa ,然后对 pa 进⾏解引⽤操作: *pa ,那找到的是 a .

**ppa = 88;//等价于*pa = 88;//等价于a = 88;

、指针数组

指针数组是指针还是数组?
我们类⽐⼀下,整型数组,是存放整型的数组,字符数组是存放字符的数组。
那指针数组呢?是存放指针的数组。

在C语言中,指针数组是一个数组,其中的每个元素都是一个指针。这意味着每个数组元素都存储着另一个变量的地址,而这个变量可以是任何类型的数据,包括整数、浮点数、字符,甚至是其他指针。

下面是一个简单的示例,演示了如何声明和使用指针数组:

#include int main() {int num1 = 10, num2 = 20, num3 = 30;int *ptrArr[3]; // 声明一个包含3个指针的数组ptrArr[0] = &num1; // 将num1的地址存储在数组的第一个元素中ptrArr[1] = &num2; // 将num2的地址存储在数组的第二个元素中ptrArr[2] = &num3; // 将num3的地址存储在数组的第三个元素中// 通过指针数组访问num1、num2和num3的值printf("Value of num1: %d\n", *ptrArr[0]);printf("Value of num2: %d\n", *ptrArr[1]);printf("Value of num3: %d\n", *ptrArr[2]);return 0;}

在这个示例中,ptrArr是一个包含3个指针的数组。每个数组元素都存储着一个整数类型变量的地址。通过ptrArr[i]可以访问第i个元素所指向的变量。

int main(){//char ch = 'w';//char* pc = &ch;//pc就是字符指针const char* p = "abcdef";//不是把字符串abcdef\0存放在p中,而是把第一个字符的地址存放在p中//printf("%c\n", *p);////1. 你可以把字符串想象为一个字符数组,但是这个数组是不能修改的//2. 当常量字符串出现在表达式中的时候,他的值是第一个字符的地址printf("%c\n", "abcdef"[3]);printf("%c\n", p[3]);//p[3] = 'q';//errreturn 0;}

在C语言中,字符指针数组是一个数组,其中的每个元素都是一个指向字符的指针。这种数组通常用于存储字符串数组,其中每个元素指向一个以null结尾的字符数组。

下面是一个简单的示例,演示了如何声明和使用字符指针数组:

#include int main() {char *strArr[3]; // 声明一个包含3个字符指针的数组strArr[0] = "Hello"; // 将指向字符串"Hello"的指针存储在数组的第一个元素中strArr[1] = "World"; // 将指向字符串"World"的指针存储在数组的第二个元素中strArr[2] = "C"; // 将指向字符串"C"的指针存储在数组的第三个元素中// 通过字符指针数组访问存储的字符串printf("String 1: %s\n", strArr[0]);printf("String 2: %s\n", strArr[1]);printf("String 3: %s\n", strArr[2]);return 0;}

在这个示例中,strArr是一个包含3个字符指针的数组。每个数组元素都存储着一个指向以null结尾的字符数组的指针。通过strArr[i]可以访问第i个元素所指向的字符串。

但是也有例外
比如这个代码:

int main(){//char ch = 'w';//char* pc = &ch;//pc就是字符指针const char* p = "abcdef";//不是把字符串abcdef\0存放在p中,而是把第一个字符的地址存放在p中//printf("%c\n", *p);
//1. 你可以把字符串想象为一个字符数组,但是这个数组是不能修改的//2. 当常量字符串出现在表达式中的时候,他的值是第一个字符的地址printf("%c\n", "abcdef"[3]);printf("%c\n", p[3]);//p[3] = 'q';//errreturn 0;}

如果强行修改,他就会报错:


️总结

本小节我们的学习总结:

1️⃣. 数组名的理解:

  • 数组名实际上是指向数组第一个元素的指针。在大多数情况下,数组名可以被解释为指向数组首元素的指针常量。
  • 例如,对于int arr[5]arr可以被视为指向arr[0]的指针。

2️⃣. 使用指针访问数组:

  • 数组名可以被解释为指向数组首元素的指针,因此可以使用指针算术或指针解引用来访问数组元素。
  • 例如,*(arr + i)或者arr[i]都可以用来访问数组arr的第i个元素。

3️⃣. 一维数组传参的本质:

  • 在C语言中,当将数组传递给函数时,实际上传递的是数组的首元素的地址。
  • 因此,函数参数声明中的数组形参实际上被解释为指向数组首元素的指针。

4️⃣. 二级指针:

  • 二级指针是指向指针的指针。它们用于处理指针的指针,通常用于动态内存分配和多级数据结构。
  • 例如,int **ptr是一个指向指向整数的指针的指针。

5️⃣. 指针数组:

  • 指针数组是一个数组,其中的每个元素都是一个指针。这些指针可以指向不同类型的数据,包括其他指针。
  • 例如,int *ptrArr[5]是一个包含5个整数指针的数组。

感谢你的收看,如果文章有错误,可以指出,我不胜感激,让我们一起学习交流,如果文章可以给你一个帮助,可以给博主点一个小小的赞