一文讲解C语言数组

数组是在内存中连续存储的具有相同数据类型的一系列数据的集合。编译器会根据代码中声明的数据信息来正确地创建数组。

一文讲解C语言数组

一维数组

声明数组时,需要指定数据类型和数量,如下所示:

int nums[365];    // 声明含有 365 个 int 类型元素的数组
char code[12];    // 声明含有 12 个 char 类型元素的数组
···

声明时,在变量后跟 [] 的都是数组,[] 中的数字表示声明数组中的元素数量。声明后的 code 在内存中的存储顺序如下所示。

一文讲解C语言数组

看下声明数组中都存储的是什么?

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] = {12243648647688100112124136148};
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] = {12243648647688};
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[] = {1224364864};

想知道数组长度,可以通过 sizeof() 来计算。

int len = sizeof(nums)/ sizeof(nums[0]);

C语言char 数组可以与 int 等类型数组初始化一样:

char msg[] = {'H''e''l''l''o'};

还可以下面的快速方法来初始化:

char msg[] = "Hello";

下标引用

数组初始化后,就需要引用数组中元素。数组通过下标访问数组元素,如下所示。

int nums[12] = {12243648647688100112124136148};
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;

ab 都具有指针值,都可以进行间接访问和下标引用操作。但它们还是存在相当大的区别。

声明一个数组时,编译器将根据声明所指定的元素数量为数组保存内存空间,然后再创建数组名,它的值是一个常量,指向这段空间的起始位置。声明一个指针变量时,编译器只为指针本身保留内存空间,它并不为任何整型值分配内存空间。而且,指针变量并未被初始化为指向任何现有的内存空间,如果它是一个自动变量,它甚至根本不会被初始化。

一文讲解C语言数组

上述声明之后,表达式 *a 是完全合法的,但表达式 *b 却是非法的。*b 将访问内存中某个不确定的位置,或者导致程序终止。另一方面,表达式 b++ 可以通过编译,但 a++ 却不行,因为 a 的值是个常量。

多维数组

多维数组是指数组的维数不止一个,如下给出二维数组的表示形式。

int tda[3][4];

声明后的多维数组在内存中的存储顺序如下图所示。

一文讲解C语言数组

多维数组的元素存储顺序按照最右边的下标率先变化的原则,称为行主序 row major order

初始化

初始化多维数组时,数组元素的存储顺序就变得非常重要。编写初始化列表有两种形式。

第一种形式与一维数组初始化类似,给出初始值列表即可,如下所示。

int tda[3][4] = {111213142122232431323334};

第二种形式基于多维数组实际上是复杂元素的一维数组的概念。如下所示。

int tda[3][4] = {
        {11121314},
        {21222324},
        {31323334}
};

把二维数组初始化为一个包含 3 个复杂的元素的一维数组。该一维数组的每个元素实际上都是包含 4 个元素的整型数组,所以用一个花括号包围 4 个整形值,花括号起到在初始化列表内部逐行定界的作用。

如果使用第一种形式进行不完整的初始化,只能在初始化列表中省略最后几个初始值。

int tda[3][4] = {111213142122232431};

这样的话,对一个大型多维数组初始化几个元素,也必须提供一个非常长的初始化列表,因为中间元素的初始化值不能省略。而使用第二种形式进行不完整初始化时,每个子初始列表都可以省略尾部的几个初始值。

int tda[3][4] = {
        {1112},
        {212223},
        {31}
};

多维数组中只有第 1 维才能根据初始化列表缺省地提供。剩余的几个维必须显示地写出,这样编译器就能推断出每个子数组维数的长度。例如:

int tda[][4] = {
    {111213},
    {2122},
    {31323334}
};

编译器只要数一下初始化列表中所包含的初始值个数,就可以推断出最左边一维为 3。

三维数组的初始化如下所示。

int tda[2][3][4] = {
    {
        {111112113114},
        {121122123124},
        {131132133134}
    },
    {
        {211212213214},
        {221222223224},
        {231232233234}
    }
};

四位数组的初始化如下所示。

int fda[2][2][3][4] = {
    {
        {
            {1111111211131114},
            {1121112211231124},
            {1131113211331134}
        },
        {
            {1211121212131214},
            {1221122212231224},
            {1231123212331234}
        }
    },
    {
        {
            {2111211221132114},
            {2121212221232124},
            {2131213221332134}
        },
        {
            {2211221222132214},
            {2221222222232224},
            {2231223222332234}
        }
    }
};

下标引用

多维数组也是使用下标引用访问数组元素,如下所示:

int tda[3][4];
int rc = tda[1][2];

根据下标会访问到多维数组元素在内存中的位置,获取其中存储的值。

一文讲解C语言数组

使用指针访问多维数组中的元素可以与下标访问元素一致,如下所示。

*(*(tda+1)+2)

元素 *(tda+1)+2 访问到元素的地址,然后执行间接访问操作 *(*(tda+1)+2) 访问整型元素。

指向数组的指针

如下所示,使用指针指向数组。

int v[10], *vp = v;
int m[3][4], *mp=m;

第一个声明是合法的。它为一个整型数组分配内存,并把 vp 声明为一个指向整型的指针,并把它初始化为指向 v 数组的第 1 个元素。vvp 具有相同的类型:指向整型的指针。第二个声明是非法的。它正确地创建了 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语言数组



原文始发于微信公众号(海人为记):一文讲解C语言数组

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/27496.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!