----在今天开始写文章之前,让我不由的想起高中里面学的一篇文章中一段话语,是荀子写的《劝学》:积土成山,风雨兴焉;积水成渊,蛟龙生焉;积善成德,而神明自得,圣心备焉。故不积跬步,无以至千里;不积小流,无以成江海。骐骥一跃,不能十步;驽马十驾,功在不舍。锲而舍之,朽木不折;锲而不舍,金石可镂。蚓无爪牙之利,筋骨之强,上食埃土,下饮黄泉,用心一也。蟹六跪而二螯,非蛇鳝之穴无可寄托者,用心躁也。

有一个礼拜没写文章了,所以不由的想起这段话来,好好学习,不要浮躁。好了进入今天的主题:


一、深入理解数组:

1、什么是数组?

这个简单来理解的话,类似我们高中学的集合,只不过我们数组里装的是同类型的数据元素,而且数据元素之间的物理内存是连续的,而且一般也是有内存限制的,可以有重复的数据元素,是因为内存单元之间是独立的。

2、两方面来深入理解数组:


a):从内存角度来理解数组:

从内存角度讲,数组变量就是一次分配多个变量,而且这多个变量在内存中的存储单元是依次相连接的;我们分开定义多个变量(譬如int a, b, c, d;)和一次定义一个数组(int a[4]);这两种定义方法相同点是都定义了4个int型变量,而且这4个变量都是独立的单个使用的;不同点是单独定义时a、b、c、d在内存中的地址不一定相连,但是定义成数组后,数组中的4个元素地址肯定是依次相连的。数组中多个变量虽然必须单独访问,但是因为他们的地址彼此相连,因此很适合用指针来操作,因此数组和指针天生就叫纠结在一起。这里以int   a[4]为例,它里面有四个元素,也就有四个内存单元:



b):从编译器角度来理解数组:

从编译器角度来讲,数组变量也是变量,和普通变量和指针变量并没有本质不同。变量的本质就是一个地址,这个地址在编译器中决定具体数值,具体数值和变量名绑定,变量类型决定这个地址的延续长度。注意地址是一个点:


3、数组中几个关键符号(a a[0] &a &a[0])的理解(前提是 int a[10]):


a):a就是数组名。a做左值时表示整个数组的所有空间(10×4=40字节),又因为C语言规定数组操作时要独立单个操作,不能整体操作数组,所以a不能做左值;a做右值表示数组首元素(数组的第1个元素,也就是a[0])的首地址(首地址就是起始地址,就是4个字节中最开始第一个字节的地址)。a做右值等同于&a[0]。

b):a[0]表示数组的首元素,也就是数组的第1个元素。做左值时表示数组第1个元素对应的内存空间(连续4字节);做右值时表示数组第0个元素的值(也就是数组第0个元素对应的内存空间中存储的那个数)。

c):&a就是数组名a取地址,字面意思来看就应该是数组的地址。&a不能做左值(&a实质是一个常量,不是变量因此不能赋值,所以自然不能做左值。);&a做右值时表示整个数组的首地址。

d):&a[0]字面意思就是数组第1个元素的首地址(搞清楚[]和&的优先级,[]的优先级要高于&,所以a先和[]结合再取地址)。做左值时表示数组首元素对应的内存空间,做右值时表示数组首元素的值(也就是数组首元素对应的内存空间中存储的那个数值)。做右值时&a[0]等同于a。


 1#include  2 3 4int main(void) 5{ 6int a, b, c, d;     // 分开独立定义4个int型变量 7int a[4];           // 一次定义一个数组,包含4个int型变量 8 9// 注意数组和指针在初始化时的式子,和平时赋值有不同。10int a[10] = {1, 3, 4, 0};       // 定义同时初始化11int *p = &a;                    // 定义同时初始化121314a[0] = 4;15a[1] = 44;16a = {1, 4, 5, 32};  // 错误的,数组元素必须单个访问,不能整个数组来访问171819}

------注意:为什么数组的地址是常量?因为数组是编译器在内存中自动分配的。当我们每次执行程序时,运行时都会帮我们分配一块内存给这个数组,只要完成了分配,这个数组的地址就定好了,本次程序运行直到终止都无法再改了。那么我们在程序中只能通过&a来获取这个分配的地址,却不能去用赋值运算符修改它。


4、小结:

(1):&a和a做右值时的区别:&a是整个数组的首地址,而a是数组首元素 的首地址。这两个在数字上是相等的,但是意义不相同。意义不相同会导致 他们在参与运算的时候有不同的表现。

(2):a和&a[0]做右值时意义和数值完全相同,完全可以互相替代。

(3):&a是常量,不能做左值。

(4):a做左值代表整个数组所有空间,所以a不能做左值。


二、指针与数组的天生姻缘:

1、以指针方式来访问数组元素:

(1)数组元素使用时不能整体访问,只能单个访问。访问方式有2种:数组形式和指针形式。

(2)数组格式访问数组元素是:数组名[下标]; (注意下标从0开始)。

(3)指针格式访问数组元素是:*(指针+偏移量); 如果指针是数组首元素地址(a或者&a[0]),那么偏移量就是下标;指针也可以不是首元素地址而是其他哪个元素的地址,这时候偏移量就要考虑叠加了。

(4)数组下标方式和指针方式均可以访问数组元素,两者的实质其实是一样的。在编译器内部都是用指针方式来访问数组元素的,数组下标方式只是编译器提供给编程者一种壳(语法糖)而已。所以用指针方式来访问数组才是本质的做法。

2、从内存角度理解指针访问数组的实质:


(1)数组的特点就是:数组中各个元素的地址是依次相连的,而且数组还有一个很大的特点(其实也是数组的一个限制)就是数组中各个元素的类型比较相同。类型相同就决定了每个数组元素占几个字节是相同的(譬如int数组每个元素都占4字节,没有例外)。

(2)数组中的元素其实就是地址相连接、占地大小相同的一串内存空间。这两个特点就决定了只要知道数组中一个元素的地址,就可以很容易推算出其他元素的地址。


3、指针和数组类型的匹配问题:


(1)int *p; int a[5];p = a;// 类型匹配

(2)int *p; int a[5];p = &a;// 类型不匹配。p是int *,   &a是整个数组的指针,也就是一个数组指针类型,不是int指针类型,所以不匹配

(3)&a、a、&a[0]从数值上来看是完全相等的,但是意义来看就不同了。从意义上来看,a和&a[0]是数组首元素首地址,而&a是整个数组的首地址;从类型来看,a和&a[0]是元素的指针,也就是int *类型;而&a是数组指针,是int (*)[5];类型。

 1#include  2 3 4int main(void) 5{ 6int a[5] = {1, 2, 3, 4, 5}; 7int *p; 8p = a; 910printf("*(p+1) = %d.\n", *(p+1));11printf("*(p+1) = %d.\n", *((char *)p+1));12printf("*(p+1) = %d.\n", *(int *)((unsigned int)p+1));1314char *p2;15p2 = (char *)p;16printf("*(p+1) = %d.\n", *(p2+1));171819int a[5] = {1, 2, 3, 4, 5};20int *p;21p = &a;2223printf("a = %x.\n", a);24printf("&a = %x.\n", &a);25printf("&a[0] = %x.\n", &a[0]);26printf("a[0] = %x.\n", a[0]);272829int a[5] = {1, 2, 3, 4, 5};3031printf("a[3] = %d.\n", a[3]);32printf("*(a+3) = %d.\n", *(a+3));33//  等效于:int b = *(a+3); printf("*(a+3) = %d.\n", b);3435int *p;36p = a;      // a做右值表示数组首元素首地址,等同于&a[0]37printf("*(p+3) = %d.\n", *(p+3));       // 等同于a[3]38printf("*(p-1) = %d.\n", *(p-1));       // 等同于a[-1]3940p = &a[2];41printf("*(p+1) = %d.\n", *(p+1));       // 等同于a[3]42printf("*(p-1) = %d.\n", *(p-1));       // 等同于a[1]43printf("*(p+3) = %d.\n", *(p+3));       // 等同于a[5]4445return 0;46}

4、小结:


(1)指针参与运算时,因为指针变量本身存储的数值是表示地址的,所以运算也是地址的运算。

(2)指针参与运算的特点是,指针变量+1,并不是真的加1,而是加1*sizeof(指针类型);如果是int *指针,则+1就实际表示地址+4,如果是char *指针,则+1就表示地址+1;如果是double *指针,则+1就表示地址+8.

(3)指针变量+1时实际不是加1而是加1×sizeof(指针类型),主要原因是希望指针+1后刚好指向下一个元素(而不希望错位)。

三、指针、数组与sizeof运算符

1、sizeof的错误纠正:

sizeof是C语言的一个运算符(主要sizeof不是函数,虽然用法很像函数),sizeof的作用是用来返回()里面的变量或者数据类型占用的内存字节数。

2、代码示例讲解:

 1  #include  2  #include  3 4 5   #define dpChar char * 6   typedef char * tpChar;        // typedef用来重命名类型,或者说用来制造用户自定义类型 7 8 9  // func完全等同于func110     void func(int a[])11     {12       printf("数组大小 = %d.\n", sizeof(a));13     }1415     void func1(int *a)16     {17         printf("数组大小 = %d.\n", sizeof(a));18    }1920     void func2(int *a, int num)21   {22   // 在子函数内,a是传进来的数组的指针(首地址)23   // 在子函数内,num是数组的大小24   }25262728  int main(void)29 {30      int a[56];31      int b = sizeof(a) / sizeof(a[0]); // 整个数组字节数/数组中一个元素的字节数32          printf("b = %d.\n", b);                // 结果应该是数组的元素个数333435/*    36   dpChar p1,  p2;          // 展开:char *p1, p2; 相当于char *p1, char p2;37tpChar p3,  p4;         // 等价于:char *p3, char *p4;  38printf("sizeof(p1) = %d.\n", sizeof(p1));       // 439printf("sizeof(p2) = %d.\n", sizeof(p2));       // 140printf("sizeof(p3) = %d.\n", sizeof(p3));       // 441printf("sizeof(p4) = %d.\n", sizeof(p4));       // 442 */4344 /*45int a[20];46func(a);            // 4 因为a在函数func内部就是指针,而不是数组4748func1(a);4950func2(a, sizeof(a));51  */5253/*    54int b1[100] = {0};55printf("sizeof(b1) = %d.\n", sizeof(b1));        56 // 400 100×sizeof(int)5758short b2[100] = {};59printf("sizeof(b2) = %d.\n", sizeof(b2));       // 200 100×sizeof(short)6061double b3[100];62printf("sizeof(b3) = %d.\n", sizeof(b3));       // 800 100×sizeof(double)63*/    6465/*66int n = 10;67printf("sizeof(n) = %d.\n", sizeof(n));         // 468printf("sizeof(int) = %d.\n", sizeof(int));     // 469  */7071  /*72char str[] = "hello";  73char *p = str; 74printf("sizeof(p) = %d.\n", sizeof(p));         // 4 相当于sizeof(char *)75printf("sizeof(*p) = %d.\n", sizeof(*p));       // 1 相当于sizeof(char)76printf("strlen(p) = %d.\n", strlen(p));         // 5 相当于strlen(str)77 */7879  /*80char str[] = "hello";  81printf("sizeof(str) = %d.\n", sizeof(str));             // 682printf("sizeof(str[0]) = %d.\n", sizeof(str[0]));       // 183printf("strlen(str) = %d.\n", strlen(str));             // 584  */    8586          return 0;87  }88

说明:

(1):函数传参,形参是可以用数组的

(2):函数形参是数组时,实际传递是不是整个数组,而是数组的首元素首地址。也就是说函数传参用数组来传,实际相当于传递的是指针(指针指向数组的首元素首地址)。

(3):strlen是一个C库函数,用来返回一个字符串的长度(注意,字符串的长度是不计算字符串末尾的'\0'的)。一定要注意strlen接收的参数必须是一个字符串(字符串的特征是以'\0'结尾) 。

四、总结:

今天的分享就结束了,讲解的不是很深,但是基础非常重要,后面会慢慢的增加难度了。所有最好理解的方法就是自己简单写代码测试一下,你就会理解的,这是最近自己找到一个比较好的学习方法,你看别人的示例,还不一定能完全吃透,自己亲身搞一下,那收获是不一样的。

---欢迎关注公众号,可以查看往期的文章,可以得到三本经典的c语言进阶电子书:

对技术热爱的一个Linux爱好者(对文章中写有不对的地方,可以批评指出,虚心向您学习,大家一起进步。群里只能讨论技术方面的,发广告,立刻飞机):

c++ char数组初始化_c专题指针数组与指针的关联相关推荐

  1. c++ char*初始化_C开发实战-深入理解指针

    Visual Studio 2019 解决方案的多项目应用 在讲述变量,数据类型,运算符和表达式以及程序流程控制,数组,函数的相关内容,所有的代码都放在解决方案c-core的c-core-founda ...

  2. java 结构体数组初始化_C数组结构体联合体快速初始化

    背景 C89标准规定初始化语句的元素以固定顺序出现,该顺序即待初始化数组或结构体元素的定义顺序. C99标准新增指定初始化(Designated Initializer),即可按照任意顺序对数组某些元 ...

  3. c语言定义不定长数组初始化_C语言如何定义一组长度不定的数组?

    1 引言 定长数组包 在平时的开发中,缓冲区数据收发时,如果采用缓冲区定长包,假定大小是 1k,MAX_LENGTH 为 1024.结构体如下: // 定长缓冲区 //公众号:c语言与cpp编程 st ...

  4. c语言定义不定长数组初始化_C语言数组初始化的三种方式

    1.{0} 初始化 int arr1[3] = {0}; 使用 {0} 的方式最简洁,一般在定义的时候使用. 2.for 循环初始化 int arr2[3]; for (int i = 0; i &l ...

  5. qt 二维数组初始化_C语言二维数组的定义、初始化、赋值

    上节讲解的数组可以看作是一行连续的数据,只有一个下标,称为一维数组.在实际问题中有很多数据是二维的或多维的,因此C语言允许构造多维数组.多维数组元素有多个下标,以确定它在数组中的位置.本节只介绍 二维 ...

  6. c++ fill 二维数组初始化_C++如何给二维数组初始化

    C++给二维数组初始化的方法:首先定义两个整型变量,并给二维数组初始化不同的数值:然后用二重循环,输出数组中的各个数值:最后用大括号括起来的数字直接赋值. 本文操作环境:Windows7系统,Dev- ...

  7. java数组初始化的方式_java数组初始化方式

    在使用一个新的数组之前,要先对其中的数值进行设置,也就是我们常说的初始化工作.因为数组有长度和内容的区分,所以常见的两种初始化方法是动态和静态,另外一种就是默认初始化.下面我们对数组的初始化概念进行理 ...

  8. vba二维数组初始化_VBA二维数组的基础介绍

    既然你已经知道了如何有规划地产生一个清单(一维数组),是时候仔细看一下如何使用数据表了.下面的过程产生一个二维数组,储存国家名称,货币名称和交换汇率.Sub Exchange() Dim t As S ...

  9. java 数组 初始化 个数_Java中数组的初始化

    一.什么是初始化 在Java程序开发中,使用数组之前都会对其进行初始化,这是因为数组是引用类型,声明数组只是声明一个引用类型的变量,并不是数组对象本身,只要让数组变量指向有效的数组对象,程序中就可使用 ...

最新文章

  1. 通过浏览器navigator判断浏览器版本或者手机类型判断微信访问
  2. jquery一个控件绑定多个事件
  3. iBatis.Net系列(一)-简介
  4. 联想ghost重装系统_联想一键ghost重装系统步骤
  5. 在Visual Studio 2005下配置WinPcap开发环境
  6. .html(),.text()和.val()的差异总结:
  7. 编程初学者必须知道的十件事
  8. vsftp的简单搭建
  9. Django使用orm创建数据表字段常用命令
  10. jq ajax购物车,jquery制作的移动端购物车效果完整示例
  11. 【光学】基于matlab GUI干涉衍射仿真【含Matlab源码 1723期】
  12. 考研数据结构常考的代码题总结 C语言实现
  13. 高等数学 中值定理 一张思维导图解决中值定理所有题型
  14. 学习笔记--爬虫(11)---js逆向思路和解密思路(初级)
  15. VC++6.0共享内存技术总结
  16. win10计算机怎么拨号上网,Win10怎么设置自动连接宽带_Win10怎么设置自动拨号连接上网-192路由网...
  17. windows下管理员用户与标准用户切换过程中的坑
  18. 农场阳光 (simpson)
  19. mac os 录屏快捷键_5款好用的Mac录屏软件推荐
  20. 计算机学硕报考院校人数,2020考研报考人数:这些学校人数过万!

热门文章

  1. 自动驾驶关键技术分解和流程
  2. 横竖屏切换时Activity的生命周期
  3. 实用的Linux 安装 zip unzip
  4. TabLayout 在宽屏幕上tab不能平均分配的问题解决
  5. android studio 的中文网站
  6. php token的生成和使用
  7. 常用 Git 命令清单
  8. 把项目放到码云上,通过git 进行项目管理
  9. shiro和Spring整合使用注解时没有执行realm的doGetAuthorizationInfo回调方法的解决
  10. Go 学习笔记(49)— Go 标准库之 io/ioutil(读写文件、获取目录下的文件和子目录、创建临时目录和文件)