深入理解C语言数组

深入学习C语言数组


阅读目录:

1、多角度理解数组

2、数组中几个关键符号

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

4、指针与数组的类型匹配问题


1、多角度理解数组:

1.1、从内存角度理解数组

(1)从内存角度讲,数组变量就是一次分配多个变量,而且这多个变量在内存中的存储单元是依次相连接的。

(2)我们分开定义多个变量(譬如int a, b, c, d;)和一次定义一个数组(int a[4]);这两种定义方法相同点是都定义了4个int型变量,而且这4个变量都是独立的单个使用的;不同点是单独定义时a、b、c、d在内存中的地址不一定相连,但是定义成数组后,数组中的4个元素地址肯定是依次相连的。

(3)数组中多个变量虽然必须单独访问,但是因为他们的地址彼此相连,因此很适合用指针来操作,因此数组和指针天生就叫纠结在一起

int a, b, c, d;     // 分开独立定义4个int型变量
int a[4];           // 一次定义一个数组,包含4个int型变量

// 注意数组和指针在初始化时的式子,和平时赋值有不同。
int a[10] = {1, 3, 4, 0};       // 定义同时初始化
int *p = &a;                    // 定义同时初始化


a[0] = 4;
a[1] = 44;
a = {1, 4, 5, 32};  // 错误的,数组元素必须单个访问,不能整个数组来访问

1.2、从编译器角度来理解数组

(1)从编译器角度来讲,数组变量也是变量,和普通变量和指针变量并没有本质不同。变量的本质就是一个地址,这个地址在编译器中决定具体数值,具体数值和变量名绑定,变量类型决定这个地址的延续长度(整形4字节、字符型1字节)。

(2)搞清楚:变量、变量名、变量类型的具体含义


2、数组中几个关键符号(a a[0] &a &a[0]):

这4个符号搞清楚了,数组相关的很多问题都有答案了。理解这些符号的时候要和左值右值结合起来,也就是搞清楚每个符号分别做左值和右值时的不同含义。

int a[5] = {1, 2, 3, 4, 5};
int *p;
p = &a;

printf("a = %x.\n", a);                //a = 7c3345e0
printf("&a = %x.\n", &a);              //&a = 7c3345e0
printf("&a[0] = %x.\n", &a[0]);       //&a[0] = 7c3345e0
printf("a[0] = %x.\n", a[0]);          //a[0] = 1

2.1、a左右值含义

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

解释:为什么数组的地址是常量?

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

2.2、a[0]左右值含义

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

2.3、&a左右值含义

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

2.4、&a[0]左右值含义

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

2.5总结:

1:&a和a做右值时的区别:&a是整个数组的首地址,而a是数组首元素的首地址。这两个在数字上是相等的,但是意义不相同。意义不相同会导致他们在参与运算的时候有不同的表现。
2:a和&a[0]做右值时意义和数值完全相同,完全可以互相替代。
3:&a是常量,不能做左值。
4:a为常量,不能做左值


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

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

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

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

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

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

int a[5] = {1, 2, 3, 4, 5};

printf("a[3] = %d.\n", a[3]);           //4
printf("*(a+3) = %d.\n", *(a+3));       //4
//  等效于:int b = *(a+3); printf("*(a+3) = %d.\n", b);

int *p;
p = a;      // a做右值表示数组首元素首地址,等同于&a[0]
printf("*(p+3) = %d.\n", *(p+3));       // 等同于a[3] 值为4
printf("*(p-1) = %d.\n", *(p-1));       // 等同于a[-1] 值为32764,可以访问

p = &a[2];
printf("*(p+1) = %d.\n", *(p+1));       // 等同于a[3]  值为4
printf("*(p-1) = %d.\n", *(p-1));       // 等同于a[1]   值为2
printf("*(p+3) = %d.\n", *(p+3));       // 等同于a[5]  值为32764 每次值不固定

可以访问a[-1]这样的值,但是没意义

3.2、内存角度理解指针访问数组的本质

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

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


4、指针与数组类型的匹配问题:

4.1、类型匹配概念

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

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

4.2、指针类型决定了指针的取址能力

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

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

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

int a[5] = {1, 2, 3, 4, 5};
int *p;
p = a;

printf("*(p+1) = %d.\n", *(p+1));        //2
printf("*(p+1) = %d.\n", *((char *)p+1));  //0仅取一个字节
printf("*(p+1) = %d.\n", *(int *)((unsigned int)p+1));    //Segmentation fault??

char *p2;
p2 = (char *)p;
printf("*(p+1) = %d.\n", *(p2+1));       //0

你可能感兴趣的:(C/C++,c语言,内存,指针,数值)