C语言学习第二阶段整理复习——数组
数组
什么是数组? 数组是一组具有相同数据类型的变量的集合体, 其存储的位置紧挨在一起,是连续的。
我把数组联想为一个大的阶梯教室,按照椅子背上面的座位号顺序排列的座位。
椅背上没有编号,可以和自己本校的带有编号的椅子进行配对,也可以将图中的椅子按照顺序进行编号即可。每个椅子可以坐下一个人(相当于可以存放相同数据类型的内存单元),椅背的编号就是内存单元所对应的数组下标。
一维数组
形如:
数据类型 变量名[常量表达式]
的定义形式,即为一维数组。其[]
内存放的是具有相同数据类型的变量的个数。
一维数组的数据在内存中的存储
按照低地址优先的话,一维数组的数据在内存中的存放顺序为:从数组下标为0的内存单元开始存放数据,一直到最后一个数据
写个代码测试一下:
void initArr(int arr[], int len)
{
for (int i = 0; i < len; ++i)
{
arr[i] = rand() % 100;
}
}
void printArr(int arr[], int len)
{
for (int i = 0; i < len; ++i)
{
printf("%d ", arr[i]);
}
printf("\n");
}
#define ARRSIZE(a) sizeof(a) / sizeof(a[0])
int main(int argc, char* argv[])
{
srand((unsigned int)time(NULL));
int arr[100] = {0};
initArr(arr, ARRSIZE(arr));
printArr(arr, ARRSIZE(arr));
return 0;
}
DEBUG
版本扔到OllyDBG
中进行查看:
默认采用的是__cdecl
调用约定,调用者负责清理堆栈,参数入栈顺序为从右到左,故可以写出其调用函数的形式:
EBP
== 0x0012FF80h
所以lea eax, [ebp - 190]
==0x0012FDF0h
initArr(0x0012FDF0h, 0x64);
到堆栈窗口查看是否符合我的逻辑推理:
在执行完毕for循环后,看打印出来的结果是不是和其存储在内存的数据是一样的。按照低地址优先原则,在0012FDF0
处存的数据应该是0x0000000Fh
,转换成10进制数据为15
输出结果:
故实验结果与所想一致。
数组名与内存单元的关系
数组名的本质是常量指针。
把数组名理解为内存单元的别名(相当于阶梯教室的门牌号),代表的是这块连续内存单元的首单元的地址,通过这个别名,可以直接读取相应的内存单元或者写入相应的内存单元。而数组下标是阶梯教室的座椅的座位号。通过数组名 + 数组下标
可以唯一确定一个内存单元。
我个人的理解是,通过数组名[常量表达式]
所在位置(在赋值号的左侧还是右侧)来确定是读取相应的内存单元数据还是写入相应的内存单元的数据,而不代表其所在的内存单元的地址。
类比于学生的学生名和学生的学号。通过学生名可以唯一找到这名学生(在现实生活中可能会重名,但是在C程序的编译阶段会保证变量签名是唯一的),可以读取这名学生的其他信息,也可以修改其相应的一些信息,但是不代表其学生号。因此,数据名[下标]
仅仅是其别名,而不代表该内存单元的地址。
综上,获取数组名所对应的内存单元的地址需要利用到取地址符号&
,故:&数组名[常量表达式]
代表的是该内存单元的地址。
一维数组的数据个数的计算
可以利用#define
进行计算
#define ARRSIZE(a) sizeof(a)/sizeof(a[0])
而如果用数据做函数的参数的话,它就会退化为一级指针,不能通过该定义进行求解数组的数据个数。
在上述例子中,也验证了这点,看initArr(int arr[], int len)
的反汇编
其采用的是LEA EAX, DWORD PTR SS:[EBP - 190]
和PUSH EAX
的形式,给函数initArr
传递所需的参数,对于参数int arr[]
,其传递的是数组内存所在的内存单元的首地址,而不是将整块内存拷贝过去,因此当数组名作为函数参数传递时,退化为指针。
综上,当数组名作为函数参数传递时,不能直接计算其数据个数。
数组首地址的加减法运算
int a[10]
- 定义一个数据类型为int
型的数组。
注意:
int a[10] = {0};
a + 1; // 结果为多少?
&a + 1; // 结果为多少?
在这里,如果不带&
的话,是加的sizeof(type(a))
;如果带&
的话,是加的sizeof(type(&a))
。(在这里的type是我自己定义的函数,即变量所对应的数据类型)
二维数组
二维数组就简单多了,是一维数组的扩展。不过在遍历二维数组的时候,要根据数据量的大小,来采用变量方式(行优先遍历或者列优先遍历)
如果数据量过大的话,采用列优先变量违背了局部性原理,可能会频繁的产生缺页中断,进行数据的换入换出,影响效率。