• 售前

  • 售后

热门帖子
入门百科

【深入理解指针与数组】

[复制链接]
匣中剑他娘官 显示全部楼层 发表于 2022-1-15 19:28:57 |阅读模式 打印 上一主题 下一主题
大家好,这里是我时隔许久又一良心巨著,主要是介绍了指针和数组的一些深入的理解,特意整理出来一篇博客供我们一起学习,如果文章中有理解不当的地方,还希望朋友们在评论区指出,我们相互学习,共同进步!

文章目录



一:指针

1.1什么是指针

   重新理解变量:定义一个变量,本质是在内存中根据类型来开辟空间。有了空间,就必须具有地址来标识空间,来方便CPU进行寻址。有了空间,就可以把数据保存起来。
  指针就是地址!那么地址的本质是什么呢?地址是数据,数据可以被保存在变量空间中。
1.2为什么要有指针

一句话,为了方便CPU寻址的效率。
1.3指针解引用

  1. int main()
  2. {
  3.         int a = 10;
  4.         int *p = &a;
  5.        
  6.         *p = 20;
  7.         system("pause");
  8.         return 0;
  9. }
复制代码
  *p的完整理解是,取出p的地址,访问该地址指向的内存单元(空间或内容)(其实通过指针变量访问,本质是一种间接寻址的方式)
口诀:对指针解引用,就是指针指向的目标
  二:数组

2.1概念

   数组是具有相同类型的集合
  1. #define N 10
  2. int        main()
  3. {
  4.         int a[N] = { 0 };
  5.         system("pause");
  6.         return 0;
  7. }
复制代码
我们都知道临时变量是在栈区上开辟空间,且地址由高到低,那么数组是如何开辟空间的呢?
  1. int main()
  2. {
  3.         int a[10] = { 0 };
  4.         for (int i = 0; i < 10; i++)
  5.         {
  6.                 printf("&a[%d]:%p\n", i, &a[i]);
  7.         }
  8.         system("pause");
  9.         return 0;
  10. }
  11. &a[0]:003CFAB8
  12. &a[1]:003CFABC
  13. &a[2]:003CFAC0
  14. &a[3]:003CFAC4
  15. &a[4]:003CFAC8
  16. &a[5]:003CFACC
  17. &a[6]:003CFAD0
  18. &a[7]:003CFAD4
  19. &a[8]:003CFAD8
  20. &a[9]:003CFADC
  21. 请按任意键继续. . .
复制代码
有代码结果我们可以看出,数组是整体申请空间的,然后将地址最低的空间,作a【0】元素,以此类推。
   引用自比特课件
  2.2理解指针+1

  1. int main()
  2. {
  3.         char * c = NULL;
  4.         short * s = NULL;
  5.         int * i = NULL;
  6.         double * d = NULL;
  7.         printf("%d\n", c);//0
  8.         printf("%d\n", c + 1);//1
  9.         printf("%d\n", s);//0
  10.         printf("%d\n", s + 1);//2
  11.         printf("%d\n", i);//0
  12.         printf("%d\n", i + 1);//4
  13.         printf("%d\n", d);//0
  14.         printf("%d\n", d + 1);//8
  15.         system("pause");
  16.         return 0;
  17. }
复制代码
  口诀:对指针+1,本质加上其所指类型大小
  2.3数组名a作为左值和右值的区别

   数组名可以做右值,代表数组首元素的地址。
  1. int main()
  2. {
  3.         int a[10] = { 0 };
  4.         char *p = a;
  5.         system("pause");
  6.         return 0;
  7. }
复制代码

   数组名不可以做左值!能够当左值的,必须是有空间可以被修改的,数组名不可以整体使用,只能按照元素为单位使用。
  三:指针和数组的关系

   没关系
  3.1以指针的形式和以数组的形式访问

  1. int main()
  2. {
  3.         char * str = "hello world";
  4.         char arr[] = "hello world";
  5.         int len = strlen(str);
  6.         for (int i = 0; i < len; i++)
  7.         {
  8.                 printf("%c\t", *(str + i));
  9.                 printf("%c\n", str[i]);
  10.         }
  11.         printf("\n");
  12.         for (int i = 0; i < len; i++)
  13.         {
  14.                 printf("%c\t", *(arr + i));
  15.                 printf("%c\n", arr[i]);
  16.         }
  17.         system("pause");
  18.         return 0;
  19. }
复制代码
  str指针变量在栈上保存,“hello world”在字符常量区,不可修改。
arr整个数组在栈上保存,可以被修改。
    代码中,虽然*(str + i)与*(arr + i)的写法一样,但是寻址方法是完全不一样的
    指针和数组指向或表示一块空间的时候,访问方式是可以互通的,既有相似性。
  那么C语言为什么要这么设计?
   说白了就是方便程序员编程,如果没有将指针和数组元素访问打通,那么在C语言中(面向过程)如果有大量的函数调用和大量数组传参,会要求程序员进行各种各种访问习惯的变化,只要是要求人做的,就会提升代码出错的概率和调试i的难度。
  四:指针数组和数组指针

4.1数组名与&数组名的区别


  1. int main()
  2. {
  3.         char arr[5] = { 'a', 'b', 'c', 'd', 'e' };
  4.         char(*p1)[3] = &arr;
  5.         char(*p2)[3]= arr;
  6.         system("pause");
  7.         return 0;
  8. }
复制代码

结论:数组元素个数,也是数值指针类型的一部分。
4.2地址的强制转换

  1. struct test
  2. {
  3.         int NUM;
  4.         char * pcName;
  5.         char ch[2];
  6. }*p = (struct test *)0x100000;
  7. int main()
  8. {
  9.         printf("%p\n", p + 0x1);
  10.         printf("%p\n", (unsigned long)p + 0x1);
  11.         printf("%p\n", (unsigned int *)p + 0x1);
  12.         system("pause");
  13.         return 0;
  14. }
  15. 0010000C
  16. 00100001
  17. 00100004
  18. 请按任意键继续. . .
复制代码
结论:强制类型转换,改变的是对特定内容的看待方式,对数据本身不发生改变。
五:多维数组和多级指针

5.1 二维数组基本内存布局

  1. int main()
  2. {
  3.         char arr[3][4] = { 0 };
  4.         for (int i = 0; i < 3; i++)
  5.         {
  6.                 for (int j = 0; j < 4; j++)
  7.                 {
  8.                         printf("arr[%d][%d] : %p\n", i, j, &arr[i][j]);
  9.                 }
  10.         }
  11.         system("pause");
  12.         return 0;
  13. }
  14. arr[0][0] : 012FFAFC
  15. arr[0][1] : 012FFAFD
  16. arr[0][2] : 012FFAFE
  17. arr[0][3] : 012FFAFF
  18. arr[1][0] : 012FFB00
  19. arr[1][1] : 012FFB01
  20. arr[1][2] : 012FFB02
  21. arr[1][3] : 012FFB03
  22. arr[2][0] : 012FFB04
  23. arr[2][1] : 012FFB05
  24. arr[2][2] : 012FFB06
  25. arr[2][3] : 012FFB07
  26. 请按任意键继续.
复制代码
结论:二维数组在内存地址空间排布上,也是连续递且递增的。
5.2二维数组如何画图

只有正确画出二维数组的布局图,才能算真正深刻理解二维数组的空间布局。
以 char【3】【4】 = { 0 };为例

   理解链:数组的定义是既有相同元素类型的集合,特征是数组中可以保存任意类型。那么可以保存数组吗,答案是可以的!
在理解上,我们甚至可以认为所有的数组都可以当作一维数组,就二维数组而言,可以被当作“一维数组”, 只不过内部“元素”也是一维数组。
    那么内部一维数组是在内存中布局是“线性连续且递增”的,多个该一维数组构成另一个“一维数组”,那么整体也是线性且连续递增的。这也就解释了为何上述二维数组的地址是连续递增的。
  如果愿意的话,我们也可采用如下代码遍历二维数组:
  1. int main()
  2. {
  3.         char arr[3][4] = { 0 };
  4.         char *p = (char *)arr;
  5.         for (int i = 0; i < 3 * 4; i++)
  6.         {
  7.                 printf("%p\n", p + i);
  8.         }
  9.         system("pause");
  10.         return 0;
  11. }
  12. 00FDFCCC
  13. 00FDFCCD
  14. 00FDFCCE
  15. 00FDFCCF
  16. 00FDFCD0
  17. 00FDFCD1
  18. 00FDFCD2
  19. 00FDFCD3
  20. 00FDFCD4
  21. 00FDFCD5
  22. 00FDFCD6
  23. 00FDFCD7
  24. 请按任意键继续. . .
复制代码
下面我给出一组题目,看朋友们能否明白答案的由来:
  1. int main()
  2. {
  3.         int arr[3][4] = { 0 };
  4.         printf("%d\n", sizeof(arr));
  5.         printf("%d\n", sizeof(arr[0][0]));
  6.         printf("%d\n", sizeof(arr[0]));
  7.         printf("%d\n", sizeof(arr[0] + 1));
  8.         printf("%d\n", sizeof(*(arr[0] + 1)));
  9.         printf("%d\n", sizeof(arr + 1));
  10.         printf("%d\n", sizeof(*(arr + 1)));
  11.         printf("%d\n", sizeof(&arr[0] + 1));
  12.         printf("%d\n", sizeof(*(&arr[0] + 1)));
  13.         printf("%d\n", sizeof(*arr));
  14.         system("pause");
  15.         return 0;
  16. }
  17. 48
  18. 4
  19. 16
  20. 4
  21. 4
  22. 4
  23. 16
  24. 4
  25. 16
  26. 16
  27. 请按任意键继续. . .
复制代码
欢迎评论区讨论
5.3二级指针

   同样的,二级指针是变量,变量有地址,地址是数据,数据可以被保存。
  1. int main()
  2. {
  3.         int a = 10;
  4.         int * p = &a;
  5.         int *pp = &p;
  6.         p = 100;
  7.         *p = 100;
  8.         pp = 100;
  9.         *pp = 100;
  10.         ** pp = 100;
  11.         return 0;
  12. }
复制代码
六:数组参数和指针参数

6.1一维数组传参

   数组传参是要发生降维的。
  1. void show(int * arr)
  2. {
  3.         printf("main: %d\n", sizeof(arr));
  4. }
  5. int main()
  6. {
  7.         int arr[10];
  8.         printf("main: %d\n", sizeof(arr));
  9.         show(arr);
  10.         system("pause");
  11.         return 0;
  12. }
  13. main: 40
  14. main: 4
  15. 请按任意键继续. . .
复制代码
为什么要降维?
   在C语言中,只要函数调用,必定发生拷贝,形参是实参的一份临时拷贝,所以如果不发生降维,那么会导致成本高,效率低。
  降维成什么?
   降维成指向其内部元素的指针
  6.2一级指针传参

发生函数调用,指针作为参数,要不要发生临时拷贝?
  1. void test(char * p)
  2. {
  3.         printf("test: &p = %p\n", &p);
  4. }
  5. int main()
  6. {
  7.         char * p = "hello world";
  8.         printf("main: &p = %p\n", &p);
  9.         test(p);
  10.         system("pause");
  11.         return 0;
  12. }
  13. main: &p = 012FFB50
  14. test: &p = 012FFA7C
  15. 请按任意键继续. . .
复制代码
  需要! 因为指针变量,也是变量,在传参上,它也必须符合变量的要求,进行临时拷贝.所以上述代码形参与实参地址不一样!
  6.3二维数组参数和二级指针参数

二维数组传参也要发生降维
降维成什么?
   指向其内部元素的指针
  1. void show(char arr[][4])
  2. {
  3.         printf("hello world!");
  4. }
  5. int main()
  6. {
  7.         char arr[3][4] = { 0 };
  8.         printf("main: %d\n", sizeof(arr));
  9.         show(arr);
  10.         system("pause");
  11.         return 0;
  12. }
复制代码
  数组名arr代表首元素地址,此时首元素是一个一维数组,其地址是一个数组指针,须知所有的数组传参,都会发生降维,这里则是降维成一个一级数组指针来传参
  思考为什么在形参接收是行标可以省略,而列标不可以呢?
   这里就涉及到前面所说的数组指针的类型与元素个数有关,如上述代码在形参列表也可表示成降维成:
void show(char (*arr)[4])
所以列标不可以省
    综上:任何维度的数组,在传参是时候都要发生降维,降维成指向其内部元素类型的指针.
那么二维数组内部元素是"一维数组",就应该降维成指向一维数组的指针.
同理三维数组,四维数组一样的道理!
  谢谢大家的支持,还希望看到这里的能够给一个三连支持,谢谢大家!

来源:https://blog.caogenba.net/qq_43727529/article/details/122501512
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作