数组,存储同类型的复合类型;结构体,存储不同类型的复合类型,用于自定义数据结构。
计算机中,针对存储大量数据的集合,有着两种方式,一种是以块式集中存储数据,这就是数组的存储方式,大量同类型的数据集中放在一块;另外一种大量数据逐个分开,但其存储的数据项就包括下一个数据的存储地址,就像一个方向标,指向下一个数据,整体来看就像连接起来的表格,所以这种结构被称为链表。
一、数组和指针
数组作为顺序存储的典型,存储相同类型的值,以该类型存储大小为单位划分,其长度就是其容量,可容纳多少个该类型的值在声明之初就定好了,数组内元素的访问可通过下标进行访问(下标就是整数索引,从0开始)。
1.1 初探数组
数组的常见声明如下:
type array[size];
数组常见的声明就是数据类型加,数组名后面中括号括起来数组大小。在程序中,数组往往用来存储重要数据,它们的使用往往需要先进行初始化,比较直接的就是用大括号括起来的数值列表对数组进行初始化(数值个数不得大于数组大小)。如下:
//完整的初始化
int nums1[4] = {1, 2, 3, 4};
//部分初始化
int nums2[4] = {1, 2};
//C99后支持的新特性,只初始化最后一个元素
int nums3[4] = {[3] = 4};
//不明确指定数组大小的声明初始化
int nums4[] = {1, 2, 3, 4, 5};
//错误示范
int nums5[4] = {1, 2, 3, 4, 5};
int nums6[4];
//下面这步是非法的
nums6 = {1, 2, 3, 4};
如上,数组的初始化是在声明之初就该进行了的,而且初始化接受部分的初始化,这种初始化是从数组第一个元素开始初始化直到用完常量值,剩下的由编译器自动赋以类型零值。上面这种用大括号括起来的列表来对数组进行初始化,在c++里面被称为列表初始化,所以这里也这么称呼吧,方便点。如上面的错误例子,当数组在初始化时初始化列表大于数组容量时,编译器直接报错,而且声明以后,在后面的语句进行列表初始化也是非法的,同样会报错,但可以声明固定大小的数组而不初始化。还有如上面nums4这样不指定size的,声明初始化以后,其大小就是初始化的列表长度,也就是说nums4的长度是5。
数组的使用
数组是一个集合,数组的使用往往就是数组内元素的读取和写入,而数组内元素的调用可以通过数组下标来索引该元素,而数组的下标索引从0开始。下面是几个应用方式:
//1
int nums[4] = {1, 2, 3, 4};
scanf("%d", &nums[0]); //"5"
printf("%d\n", nums[0]); //"5"
//2
int nums1[4] = {[3] = 10};
printf("%d, %d\n", nums1[0], nums1[3]); //"0, 10"
scanf("%d", &nums[0]); //"8"
printf("%d, %d\n", nums1[0], nums1[3]); //"8, 10"
//3
int nums2[4];
printf("%d, %d, %d, %d\n", nums2[0], nums2[1], nums2[2], nums2[3]); //"8, 0, 20, 0"
scanf("%d%d%d%d", &nums2[0], &nums2[1], &nums2[2], &nums2[3]); //"0 1 2 3"
printf("%d, %d, %d, %d\n", nums2[0], nums2[1], nums2[2], nums2[3]); //"0, 1, 2, 3"
如上面的三个例子所示,三个整型数组,三种状态下(完全初始化、部分初始化,只声明)的读取和写入。其实,数组的使用,和for这种计数循环天然适配,如下:
int nums[4], i;
//写入数据
for(i = 0;i < 4;i++)
scanf("%d", &nums[i]); //逐行输入"0"、"1"、"2"、"3"
//读取
for(i=0 ;i < 4; i++)
printf("%d, ", nums[i]);
//"0, 1, 2, 3,"
调用数组元素的另一种方式
上面使用数组元素的方式是基于下标来进行,但也有另一种方式进行调用。数组名的值,本身就是一个指针常量,它是数组第一个元素的地址,所以,数组元素的调用也可以用指针来进行。如下:
int nums[4] = {0, 1, 2, 3};
printf("%d, %d\n", *nums, *(nums + 2)); //"0, 2"
int *p = nums, i;
for(i = 0;i < 4; i++)
printf("%d, ", *(p + i)); //"0, 1, 2, 3, "
如上使用都是可以的,不过使用指针要注意的就是*取值符和&取址符,指针本身是指向某地址的变量,而*取值符的作用就是用来取指针指向地址的值,而取址符对指针本身往往没有多大使用,因为使用场景往往更关注指针指向地址的值,这里是想提醒不能把指针当做寻常变量那样使用取址符&。
要注意的,数组的使用和指针有共通之初,但并非等同于指针。
1.2 多维数组
在实际生活中,数据集有集合形式,也有矩阵形式,针对这种种数据的处理,C中往往都用数组进行,只是数组的形式有所不同。集合列表用一维数组,矩阵用二维数组,乃至有三维数组、四维数组应付更复杂的数据结构。这部分进行的就是对数组的学习解读。
二维数组
数组的所谓二维三维方面,在这里的体现,用下标展示会更加直观,如下声明定义一个二维数组:
//完全初始化
int matrix[2][3] = {
{0, 1, 2},
{3, 4, 5}
};
//部分初始化
int matrix1[2][3] = {
{0, 1},
{3, 4}
};
//不指定数组大小的声明初始化
int matrix2[][3] = {
{0, 1, 2},
{3, 4, 5}
};
回顾一下,数组是相同类型元素的列表,一维数组是一个简单列表,里面存放着同类型的一个个常量值,那二维数组呢?它则是存放着一个个一维数组的另类列表,所以,不去深究数组内的元素,其实二维数组和一维数组乃至多维数组都是一样的,它们都是一个有序列表。
就着上面的结论来看上面的例子就简单多了,matrix是一个存放着两个数组的列表,内层数组则存放着三个整型数据。因此,可以在数组大小范围已定的情况下,用不足其大小的列表去对其进行初始化,比如matrix1,还有不明确外层数组数量的情况用符合内层大小的一定个数的数组去对数组初始化,比如matrix2(还是有点拗口)。上面的例子也可以改成下面的样子:
//完全初始化
i