• 【C语言】深入解开指针(三)


      🌈write in front :
    
    • 1

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

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

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


    前言

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


    ▶️、 数组名的理解

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    运行结果:
    在这里插入图片描述
    我们看到运行结果可以看出数组名和数组首元素的地址一模一样。因此,数组名就是数组首元素(第一个元素)的地址

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

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

    sizeof(type)
    sizeof(variable)
    
    • 1
    • 2

    其中,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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    运行结果:
    在这里插入图片描述
    打印结果⼀模⼀样,这时候⼜纳闷了,那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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述
    这⾥我们发现&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=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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    每个都进行验证一下:

    1. 数组名访问地址

    在这里插入图片描述

    1. 数组首元素地址访问

    在这里插入图片描述
    3. 使用指针访问数组
    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述
    哎,为什么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
    • 2
    • 3
    • 4
    • 5
    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/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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

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

    int main()
    {
    	int a = 10;
    	int* p = &a;//取出a的地址
    	//p是指针变量,是一级指针
    	int * * pp = &p;//pp是二级指针
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

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

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

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

    **ppa = 88;
    //等价于*pa = 88;
    //等价于a = 88;
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    🔼、指针数组

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

    在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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这个示例中,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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    }

    在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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这个示例中,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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    }
    在这里插入图片描述

    如果强行修改,他就会报错:
    在这里插入图片描述


    🅿️总结

    本小节我们的学习总结:

    1️⃣. 数组名的理解:

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

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

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

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

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

    4️⃣. 二级指针:

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

    5️⃣. 指针数组:

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

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

  • 相关阅读:
    openjdk8 JVM源码阅读==windows&centos Clion debug openJDK8
    Python将已标注的两张图片进行上下拼接并修改、合并其对应的Labelme标注文件
    KCP协议浅析
    静态路由配置实验:构建多路由器网络拓扑实现不同业务网段互通
    【ESP32】串口+wifi 透传,以及回调函数的使用
    RocketMQ中Broker接收消息流程代码解析
    机器学习-吴恩达老师笔记
    window下redis安装
    “torch.load“中出现的“Unexpected key(s) in state_dict“报错问题
    jvm中提前进入老年代
  • 原文地址:https://blog.csdn.net/a_hong_sen/article/details/134409911