数组是在内存中连续存储的具有相同数据类型的一系列数据的集合。编译器会根据代码中声明的数据信息来正确地创建数组。
一维数组
声明数组时,需要指定数据类型和数量,如下所示:
int nums[365]; // 声明含有 365 个 int 类型元素的数组
char code[12]; // 声明含有 12 个 char 类型元素的数组
···
声明时,在变量后跟 []
的都是数组,[]
中的数字表示声明数组中的元素数量。声明后的 code
在内存中的存储顺序如下所示。
看下声明数组中都存储的是什么?
int nums[12];
for (int i = 0; i < 12; ++i) {
printf("array element %d is %d n", i+1, nums[i]);
}
/*
array element 1 is 0
···
array element 7 is 4199705
···
array element 12 is 0
*/
这时,数组中存储的值都不是合理的值,因此,就需要对数组初始化。
初始化
初始化是指创建数组的同时给数组每个元素一个合理的初始值。
int nums[12] = {12, 24, 36, 48, 64, 76, 88, 100, 112, 124, 136, 148};
for (int i = 0; i < 12; ++i) {
printf("array element %d is %d n", i+1, nums[i]);
}
/*
array element 1 is 12
···
array element 7 is 88
···
array element 12 is 148
*/
也可以不完全初始化,如下所示:
int nums[12] = {12, 24, 36, 48, 64, 76, 88};
for (int i = 0; i < 12; ++i) {
printf("array element %d is %d n", i+1, nums[i]);
/*
array element 1 is 12
···
array element 7 is 88
···
array element 12 is 0
*/
当初始化值的数目小于数组元素的数目时,编译器会知道初始值不够,但不知道缺少的是哪些值,因此只会为初始化前几个,之后的元素会初始化为 0
。当初始化值的个数大于数组元素的数目时,编译器会报错 warning: excess elements in array initializer
。
当声明时未给出数组长度,编译器会把数组的长度设置为刚好容纳所有初始值的长度。
int nums[] = {12, 24, 36, 48, 64};
想知道数组长度,可以通过 sizeof()
来计算。
int len = sizeof(nums)/ sizeof(nums[0]);
C语言对 char
数组可以与 int
等类型数组初始化一样:
char msg[] = {'H', 'e', 'l', 'l', 'o'};
还可以下面的快速方法来初始化:
char msg[] = "Hello";
下标引用
数组初始化后,就需要引用数组中元素。数组通过下标访问数组元素,如下所示。
int nums[12] = {12, 24, 36, 48, 64, 76, 88, 100, 112, 124, 136, 148};
int num = nums[5];
printf("array element 6 is %d", num);
// array element 6 is 76
上述使用下标引用数组中的元素可以使用指针表达式来代替。
int *p = nums;
int num = *(p+5);
printf("array element 6 is %d", num);
// array element 6 is 76
在使用数组时,要防止数组下标超出边界,必须确保下标是有效的值,因为编译器不会检查数组下标是否使用得当。
数组和指针
指针和数组并不是相等的。如下面两个说明:
int a[5];
int *b;
a
和 b
都具有指针值,都可以进行间接访问和下标引用操作。但它们还是存在相当大的区别。
声明一个数组时,编译器将根据声明所指定的元素数量为数组保存内存空间,然后再创建数组名,它的值是一个常量,指向这段空间的起始位置。声明一个指针变量时,编译器只为指针本身保留内存空间,它并不为任何整型值分配内存空间。而且,指针变量并未被初始化为指向任何现有的内存空间,如果它是一个自动变量,它甚至根本不会被初始化。
上述声明之后,表达式 *a
是完全合法的,但表达式 *b
却是非法的。*b
将访问内存中某个不确定的位置,或者导致程序终止。另一方面,表达式 b++
可以通过编译,但 a++
却不行,因为 a
的值是个常量。
多维数组
多维数组是指数组的维数不止一个,如下给出二维数组的表示形式。
int tda[3][4];
声明后的多维数组在内存中的存储顺序如下图所示。
多维数组的元素存储顺序按照最右边的下标率先变化的原则,称为行主序 row major order
。
初始化
初始化多维数组时,数组元素的存储顺序就变得非常重要。编写初始化列表有两种形式。
第一种形式与一维数组初始化类似,给出初始值列表即可,如下所示。
int tda[3][4] = {11, 12, 13, 14, 21, 22, 23, 24, 31, 32, 33, 34};
第二种形式基于多维数组实际上是复杂元素的一维数组的概念。如下所示。
int tda[3][4] = {
{11, 12, 13, 14},
{21, 22, 23, 24},
{31, 32, 33, 34}
};
把二维数组初始化为一个包含 3 个复杂的元素的一维数组。该一维数组的每个元素实际上都是包含 4 个元素的整型数组,所以用一个花括号包围 4 个整形值,花括号起到在初始化列表内部逐行定界的作用。
如果使用第一种形式进行不完整的初始化,只能在初始化列表中省略最后几个初始值。
int tda[3][4] = {11, 12, 13, 14, 21, 22, 23, 24, 31};
这样的话,对一个大型多维数组初始化几个元素,也必须提供一个非常长的初始化列表,因为中间元素的初始化值不能省略。而使用第二种形式进行不完整初始化时,每个子初始列表都可以省略尾部的几个初始值。
int tda[3][4] = {
{11, 12},
{21, 22, 23},
{31}
};
多维数组中只有第 1 维才能根据初始化列表缺省地提供。剩余的几个维必须显示地写出,这样编译器就能推断出每个子数组维数的长度。例如:
int tda[][4] = {
{11, 12, 13},
{21, 22},
{31, 32, 33, 34}
};
编译器只要数一下初始化列表中所包含的初始值个数,就可以推断出最左边一维为 3。
三维数组的初始化如下所示。
int tda[2][3][4] = {
{
{111, 112, 113, 114},
{121, 122, 123, 124},
{131, 132, 133, 134}
},
{
{211, 212, 213, 214},
{221, 222, 223, 224},
{231, 232, 233, 234}
}
};
四位数组的初始化如下所示。
int fda[2][2][3][4] = {
{
{
{1111, 1112, 1113, 1114},
{1121, 1122, 1123, 1124},
{1131, 1132, 1133, 1134}
},
{
{1211, 1212, 1213, 1214},
{1221, 1222, 1223, 1224},
{1231, 1232, 1233, 1234}
}
},
{
{
{2111, 2112, 2113, 2114},
{2121, 2122, 2123, 2124},
{2131, 2132, 2133, 2134}
},
{
{2211, 2212, 2213, 2214},
{2221, 2222, 2223, 2224},
{2231, 2232, 2233, 2234}
}
}
};
下标引用
多维数组也是使用下标引用访问数组元素,如下所示:
int tda[3][4];
int rc = tda[1][2];
根据下标会访问到多维数组元素在内存中的位置,获取其中存储的值。
使用指针访问多维数组中的元素可以与下标访问元素一致,如下所示。
*(*(tda+1)+2)
元素 *(tda+1)+2
访问到元素的地址,然后执行间接访问操作 *(*(tda+1)+2)
访问整型元素。
指向数组的指针
如下所示,使用指针指向数组。
int v[10], *vp = v;
int m[3][4], *mp=m;
第一个声明是合法的。它为一个整型数组分配内存,并把 vp
声明为一个指向整型的指针,并把它初始化为指向 v
数组的第 1 个元素。v
和 vp
具有相同的类型:指向整型的指针。第二个声明是非法的。它正确地创建了 m
数组,并把 mp
声明为一个指向整型的指针。但是,mp
的初始化是不正确的,因为 m
并不是一个指向整型的指针,而是一个指向整型数组的指针。正确的声明如下所示。
int (*mp)[4] = m;
下标引用的优先级高于间接访问,但由于括号的存在,首先执行的还是间接访问。所以,mp
是一个指向整型数组的指针,数组的每个元素都是整数。mp
指向 m
的第 1 行。
mp
是一个指向拥有 4 个整型元素的数组的指针。当你把 mp
与一个整数相加时,该整数值首先根据 4 个整型值的长度进行调整,然后再执行加法。所以可以使用这个指针一行一行地在 m
中移动,如下所示。
(*mp)+a
a
为正确的整数值,可以使得上述表达式可以在 m
中一行一行地移动。
如果需要一个指针逐个访问整型元素而不是逐行在数组中移动,如下所示,声明一个简单的整型指针,并以两种不同的方式进行初始化,指向 m
的第 1 个整型元素。
int *p = &m[0][0];
int *p = m[0];
增加这个指针的值使它指向下一个整型元素。
指针数组
除了类型之外,数组也可以声明指针数组,如下所示:
int *pi[10]; <=> int *(pi[10]);
下标引用优先级高于间接访问,所以先执行下标引用。因此,pi
是某种类型的数组。在取得一个数组元素之后,随即执行的是间接访问操作。这个表达式不再有其他操作符,所以它的结果是一个整形值,它的元素类型是指向整型的指针。
指针数组与字符串数组结合使用,如下所示。
char *keyword[] = {
"do",
"for",
"if",
"register",
"return",
"switch",
"while"
};
这里的指针数组本身占用空间,而其中的每个字符串常量占据的内存空间只是它本身的长度。如果使用二维数组来存储,就需要对每一行的长度都被固定为刚好容纳最长的字符数组,如下所示。
char keyword[][9] = {
"do",
"for",
"if",
"register",
"return",
"switch",
"while"
};
当选择指针数组方案时,对其指针数组元素的末尾增加一个 NULL
指针。
char *keyword[] = {
"do",
"for",
"if",
"register",
"return",
"switch",
"while",
NULL
};
这个 NULL
指针在循环指针数组时,通过检测 NULL
指针而结束,无需预先知道表的长度。
原文始发于微信公众号(海人为记):一文讲解C语言数组
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/27496.html