1. 简介
数组是一种存储于连续内存空间且具有相同类型的数据集合,也是一种随机存取的数据结构。数组中可以存取整数型、字符型、指针型等多种类型。
数组可以通过下标随机获取数组中的任意元素,而无需遍历整个数组,因此数组的存取效率很高。
数组也是一种数据类型,通常使用数组变量名来操作数组,在C语言中,还可以使用指针来操作数组,使其更加方便和灵活。
2. 数组声明与初始化
数组也是一种数据类型,也需要先声明初始化后才能使用。具体形式为:类型说明符 变量名[元素个数];
int arr[5];//声明数组
上面的示例声明了一个数组arr,包含5个int类型的元素,数组名与变量名的命名规则一样,以小写为主,数据类型可以是C语言中的任意有效类型,且元素个数必须大于0。
数组的初始化分为两种,一种是使用大括号{}对每个元素进行逐个赋值,元素个数可以不写,自动确定数组长度,注意:这种初始化方式必须和声明在同一行,而不能声明和初始化分开:
int arr[5] = {1,2,3,4,5};//正确,对每个元素初始化
int arr[] = {1,2,3};//自动确定数组长度为3
int arr[5];
arr = {1,2,3,4,5};//报错
如果初始化的元素个数少于数组的元素,那么未赋值的元素将初始化为0:
int arr[5] = {1,2,3};//初始化了前3个元素
int arr[5] = {1,2,3,0,0}//这与上面的初始化是相同的
//将数组所有元素初始化为0
int arr[5] = {0};
还可以为指定位置的元素进行赋值,还可以和顺序赋值结合使用:
int arr[5] = {[0] = 1,[3] = 5};//为指定位置元素赋值
int arr[5] = {1,[3] = 3,10};//指定位置和顺序结合使用
上面的示例为指定位置元素赋值中,为第1个元素和第4个元素赋值;第二条语句结合顺序赋值分别对第1、4、5个元素进行了赋值。
第二种方式就是通过下标对数组的指定元素赋值,数组的下标是从0开始的,所以最后一个元素的下标就是整个数组的长度减1。如果对不存在的元素赋值,就会发生越界。
int arr[5];
arr[0] = 1;//通过下标赋值
arr[4] = 10;
arr[5] = 5;//越界,编译器不管
上面的示例中,分别对数组arr的第一个元素和最后一个元素进行了赋值,但是使用arr[5]即对数组的第6个元素进行了赋值,这个元素并不存在,所以就会发生越界访问错误。在C语言中,编译器并未对数据越界行为进行处理,也不会提示错误,而是对数组后面的内存地址进行了赋值,这是一种很危险的行为。
同样,也可以通过数组下标来访问数组元素:
int arr[5] = {1,2,3,4,5};
int a = arr[3];//获取数组中第4个元素的值
//循环获取数组中的元素
for(int i = 0;i < 10;i++){
printf("元素[%d] = %d\n",i,arr[i]);
}
3. 数组长度
数组可以通过sizeof运算符,返回以字节为大小的长度,除以数组元素的大小,就是数组元素的个数。
int arr[5] = {1,2,3};
int length = sizeof(arr);//20个字节
int num = sizeof(arr) / sizeof(arr[0]);//元素数量为5
printf("数组的长度为%zu\n",length);//20
printf("数组的元素个数为%zu\n",num);//5
上面的示例中数组arr有5个元素,且都是int类型的,所以每个元素都是4个字节,数组的长度就是20,sizeof(arr[0])是首个数组元素的字节长度,所以两者相除就能得到元素的个数;由于sizeof运算符的返回值类型为size_t,所以printf()函数的占位符使用%zu。
4. 数组的地址
C语言中,数组在内存中表示为一段连续的内存空间,所以只要获取到第一个元素的内存地址,后面的其他元素地址都能推算出来。数组通常与指针结合起来使用。
int arr[5] = {1,2,3};
int *p = NULL;
p = &arr[0];//获取第一个元素的地址
指针变量是存储内存地址的,所以声明了指针变量p来存储数组第一个元素arr[0]的地址,也就是整个数组的起始地址。
为了更方便的获取数组的地址,C语言中提供了更为方便的表示方式,直接使用数组名就可以获取数组的地址,数组名是一个常量指针,是不可更改的。它不是直接表示地址的,而是进行了内部转换。
int arr[5] = {1,2,3};
int *p = arr;//使用数组名获取数组地址,和&arr[0]的结果是一样的
5. 数组指针
数组指针就是指向数组的指针,指向的对象是数组,指针变量存放的是数组的首地址。
一般在实际的项目中,使用“指针获取地址”方式访问数组的元素比较普遍,通常使用数组名加上元素索引来遍历整个数组,它和通过下标方式访问数组的元素是等价的。
int arr[5] = {1,2,3,4,5};
for(int i = 0;i < 5;i++){
printf("数组元素的地址为%d\n",*(arr + i)); //arr[i]等同于*(arr + i)
}
上面的示例中,*(arr + i)表示数组名、*取值符再加上索引遍历每个元素的值,等同于arr[i]。
当然,如果将数组的地址存到指针变量中,那么就可以使用指针变量来遍历数组元素了。
int arr[5] = {1,2,3,4,5};
int *p = arr;
for(int i = 0;i < 5;i++){
printf("数组的元素为%d\n",*p);
p++;//指向下一个数组元素的地址
}
数组作为函数的参数时,可以传入一个数组,也可以传入一个数组指针变量。
int arr[5] = {1,2,3,4,5};
//数组作为参数传入函数
int func(int arr[]);//传入数组
//等价于
int func(int *arr);//传入指针变量
6. 多维数组
C语言中,还可以声明存取多个维度的数据,也就是多维数组,本质上还是一维数组,在内存中还是按照一维数组的方式连续存储的,如果是按行来计算,即先存储第一行数组的元素,再存储第二行数组的元素,然后通过内存偏移(移动指针)来访问每一个数据元素。
int arr[2][3];//声明一个二维数组
int arr2[2][3] = { //声明一个二维数组并初始化
{1,2,3},//第一行
{4,5,6} //第二行
}
上面示例中,声明了一个二维数组arr,第一个维度有2个元素,第二个维度有3个元素;声明并初始化了一个二维数组arr2,在内存中先存储第一行的数据即{1,2,3},再存储第二行的数据即{4,5,6}。
二维数组初始化的嵌套的大括号是可以省略的,直接顺序初始化元素,存储的原理也是连续存储:
int arr[2][3] = {1,2,3,4,5,6};
//等价于
int arr[2][3] = { //声明一个二维数组并初始化
{1,2,3},//第一行
{4,5,6} //第二行
}
通过数组的下标(行下标和列下标)可以访问二维数组的元素,通过嵌套for循环的方式实现。
int arr[2][3] = { //声明一个二维数组并初始化
{1,2,3},//第一行
{4,5,6} //第二行
}
for(int i = 0;i < 2;i++){ //嵌套for循环获取二维数组arr的每个元素
for(int j = 0;j < 3;j++){
printf("arr[%d][%d] = %d\n", i, j, arr[i][j]);
}
}
7. 变长数组VLA
C99标准中引入了一种新型数组,允许程序员可以使用变量来控制数组的长度,即数组的长度不再是常量,而是由变量自由控制,由运行时确定,这就是变长数组,即VLA(variable-length array)。
变长数组只能在函数内作为局部变量使用,即不能使用extern或static变量说明符。
int length;
int arr[length];//定义数组长度
for(int i = 0; i < length; i++){ //循环初始化
arr[i] = i;
}
8. 总结
- 数组是一种存储于连续内存空间且具有相同类型的数据集合;
- 数组的声明使用[]符号,初始化可以使用下标[]或者大括号{}来实现;
- 数组的元素可以用下标[]或者指针来获取,arr[i]和*(arr + i)是等价的;
- 数组的地址可以通过第一个元素的地址获取,也可以直接使用数组名获取;
- 数组也可以定义多维数组,他们也是存储于连续的内存空间中;
- C99标准引入了变长数组,即数组的长度由变量确定的。