🌈write in front :
🔍个人主页 : @啊森要自信的主页
真正相信奇迹的家伙,本身和奇迹一样了不起啊!
欢迎大家关注🔍点赞👍收藏⭐️留言📝>希望看完我的文章对你有小小的帮助,如有错误,可以指出,让我们一起探讨学习交流,一起加油鸭。
文章目录
- 前言
- ▶️、 数组名的理解
- ▶️、 使⽤指针访问数组
- ➡️、⼀维数组传参的本质
- ➡️、⼆级指针
- 🔼、指针数组
- 🅿️总结
前言
本小节,我们继续深入理解指针,阿森将在本小节带你理解数组名,怎么使用指针访问数组,一维数组传参的本质,冒泡排序的方法,还有我们的二级指针创建,指针数组的,生命,创建和运用。接下来让我们启程!
▶️、 数组名的理解
- %d:用于打印整数。
- %f:用于打印浮点数。
- %c:用于打印单个字符。
- %s:用于打印字符串。
- %p:用于打印指针地址。
- %x:用于以十六进制格式打印整数。
- %o:用于以八进制格式打印整数。
- %e:用于以科学计数法打印浮点数。
- %u:用于以无符号整数格式打印整数。
对于数组名,我们在学习函数的时候,我们就了解到数组名arr
就是数组首元素的地址,当然也可以取地址数组首元素&arr[0]
.
#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;
}
运行结果:
我们看到运行结果可以看出数组名和数组首元素的地址一模一样。因此,数组名就是数组首元素(第一个元素)的地址。
既然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 <stdio.h>
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 <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;
}
这⾥我们发现&arr[0]
和&arr[0]+1
相差4个字节,arr和arr+1 相差4个字节,是因为&arr[0] 和 arr 都是⾸元素的地址,+1就是跳过⼀个元素。
但是&arr
和 &arr+1
相差40个字节,这就是因为&arr
是数组的地址,+1
操作是跳过整个数组的。
在C语言中,arr
和&arr
有着不同的含义和用法。
-
arr
:表示数组的名称,它代表数组的首元素的地址。在大多数情况下,当使用数组名arr
时,它会被隐式转换为指向数组第一个元素的指针。因此,arr
表示的是数组的地址,而不是整个数组的内容。 -
&arr
:表示对整个数组的取地址操作。它得到的是整个数组的地址,而不是数组的第一个元素的地址。因此,&arr
表示的是整个数组的地址,而不是数组的内容。
总结来说,arr
表示数组的首元素地址,而&arr
表示整个数组的地址。在大多数情况下,当我们需要传递数组给函数时,实际上传递的是数组的首元素地址,因此arr
和&arr
在传递参数时的用法可能会有所不同。
▶️、 使⽤指针访问数组
知道了数组名,数组的地址,那我们不就可以用指针访问数组了,遍历数组的元素了,好接下来,启动!
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
//输⼊
int i = 0;//整个数组大小40/单个数组字节4=10
int 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;
}
每个都进行验证一下:
- 数组名访问地址
- 数组首元素地址访问
3. 使用指针访问数组
- ⚠️ 这段代码弄清楚后,我们再试一下,如果我们再分析一下,数组名
arr
是数组首元素的地址,可以赋值给p
,其实数组名arr
和p
在这里是等价的。那我们可以使用arr[i]
可以访问数组的元素,那p[i]
是否也可以访问数组呢?测试一下!
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
//输⼊
int i = 0;//整个数组大小40/单个数组字节4=10
int 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]
也可以打印
- 在C语言中,数组名和指针的运算符
[]
是可以互换使用的。这是因为在C语言中,a[b]
和*(a + b)
是等价的,即数组下标运算和指针运算是等效的。当然,如果你不太明白,可以尝试使用交换律来理解
*(i+arr)-->*(arr+i)-->arr[i]
arr[i] == *(arr+i)
*(i+arr) == i[arr]
p[i] == *(p+i)
- 因此,当你使用
i[arr]
时,它实际上等同于arr[i]
。这是因为arr
本身就代表数组的首元素地址,而i[arr]
会被解释为*(arr + i)
,即数组首元素地址加上偏移量i,得到第i个元素的地址。同样,i[p]
也等同于p[i]
,因为指针p也可以进行类似的偏移量运算。 - 虽然
i[arr]
和i[p]
在语法上是合法的,但通常不推荐这样的写法,因为它会增加代码的可读性和理解难度。更好的做法是直接使用arr[i]
和p[i]
,这样可以更清晰地表达代码的意图。
➡️、⼀维数组传参的本质
首先,让我们从一个问题开始。我们之前一直在函数外部计算数组的元素个数,但是我们能否将函数传递给另一个函数,在函数内部计算数组的元素个数呢?
void test(int arr[])//int* arr
{ //4/4
int 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;
}
- 环境下debug,x86环境下,结果为1.
- 环境下debug,x64环境下,结果为2.
分析:
-
当数组作为函数参数进行传递时,实际上传递的是数组的首元素地址,而不是整个数组。因此,在函数内部,无法通过sizeof操作符来获取数组的大小,因为此时的
arr
已经退化为指针。 -
在代码中,test函数的参数arr实际上是一个指针,因此在函数内部使用sizeof(arr)并不能得到数组的大小,而是得到指针的大小。因此,在
32位环境下(x86)
,指针的大小为4字节,所以sizeof(arr) / sizeof(arr[0])的结果为1。(同理64位,指针大小字节为8字节)
数组名是数组首元素的地址;因此在数组传参时,传递的是数组名,也就是说本质上数组传参本质上传递的是数组首元素的地址。
⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。
➡️、⼆级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪⾥?
在C语言中,二级指针是指一个指针变量,它存储的是另一个指针变量的地址。换句话说,它指向一个指针变量,而这个指针变量又指向某个数据的地址。在C语言中,我们通常使用二级指针来处理动态内存分配和多级数据结构。
下面是一个简单的示例,演示了如何声明和使用二级指针:
#include <stdio.h>
int main() {
int num = 10;
int *ptr1 = # // 一级指针,指向int类型的数据
int **ptr2 = &ptr1; // 二级指针,指向int*类型的数据
// 通过二级指针访问num的值
printf("Value of num: %d\n", **ptr2);
return 0;
}
在这个示例中,ptr1
是一个一级指针,它指向一个整数类型的数据num
。ptr2
是一个二级指针,它指向一个一级指针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 <stdio.h>
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';//err
return 0;
}
在C语言中,字符指针数组是一个数组,其中的每个元素都是一个指向字符的指针。这种数组通常用于存储字符串数组,其中每个元素指向一个以null结尾的字符数组。
下面是一个简单的示例,演示了如何声明和使用字符指针数组:
#include <stdio.h>
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';//err
return 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个整数指针的数组。
感谢你的收看,如果文章有错误,可以指出,我不胜感激,让我们一起学习交流,如果文章可以给你一个帮助,可以给博主点一个小小的赞😘