本博客主要内容为 “小甲鱼” 视频课程《带你学C带你飞》【第一季】 学习笔记,文章的主题内容均来自该课程,在这里仅作学习交流。在文章中可能出现一些错误或者不准确的地方,如发现请积极指出,十分感谢。
也欢迎大家一起讨论交流,如果你觉得这篇文章对你有所帮助,记得评论、点赞哦 ~(。・∀・)ノ゙
一言以蔽之,数组就是存储一批同类型数据的地方。
在 c 语言中往往需要先定义再使用,数组的定义方式如下
类型 数组名[常量表达式]
// 举例说明
int a[6]; // 定义一个整型数组,总共存放6个元素
char b[24]; // 定义一个字符型数组,总共存放24个元素
double c[3]; // 定义一个双精度浮点型数组,总共存放3个元素
在定义数组时,需要在数组名后边紧跟着一对方括号,其中用常量表达式来指定数组中元素的个数。因为只有告诉编译器元素的个数,编译器才能申请对应大小的内存给它存放。
现在回顾一下最开始的时候讲过的一个问题,上边的几个类型,都占用多少个字节的内存?如下图所示
回顾《4. C语言 – 数据类型和取值范围》中的 2.3 节的内容可以知道,int 型占 4 个字节,char 型占 1 个字节,double 型占 8 个字节。所以上面所定义的三个号数组都占据 24 个字节~
在定义数组的同时对其各个元素进行赋值,称之为数组的初始化,主要有以下五种方式
(1) 将数组中首位初始化为某个数值,其余位置初始化为 0
int a[10] = {0}; // 将数组中所有元素初始化为0
在这种方式中有一点需要注意,其中 {0}
并不是将数组内的所有元素初始化为 0 的含义,而是数组中的第一个元素的值为 0,剩余元素值为 0 的意思。因此通过 int a[10] = {3}
的数组,实际上只有第一个数字为 3 ,其余数字均为0。
(2) 只给一部分元素赋值,未被赋值的元素自动初始化为 0
// 表示为前边 6 个元素赋值,后边 4 个元素系统自动初始化为 0
int a[10] = {1, 2, 3, 4, 5, 6};
所以第一种方法实际上是着一种方法的特例。
(3) 如果是赋予不同的值,那么用逗号分隔开即可
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
(4) 只给出各个元素的值,而不指定数组的长度
int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
(5) 指定初始化的元素
// 编译的时候记得加上–std=c99选项
int a[10] = {[3] = 3, [5] = 5, [8] = [8]};
C99 增加了一种新特性——指定初始化的元素。这样就可以只对数组中的某些指定元素进行初始化赋值,而未被赋值的元素自动初始化为 0。
在上面的初始化方法中,要注意区分第二种和第四种初始化的方法。其中第二种初始化的方法已经指定了数组的长度,因此即使只给出前面一部分的数字,也会自动补充后面的数字;但是第四种方法是通过 {}
给出元素自动确定数组的长度。比如说下面这两种初始化的方式
// 长度为10 ,指定前六个元素,后面的自动补零
int a[10] = {1, 2, 3, 4, 5, 6};
// 长度为 5
int b[] = {1, 2, 3, 4, 5, 6};
所以如果访问 b[5] 就会发生数组越界,要么报错,要么得到一个随机数(具体是那种情况根据编译器而定)。
如果现在只定义了一个数组,但是没有给数组进行初始化,那么会出现什么样的情况呢,比如说下面的这段代码
#include
int main()
{
int a[5];
for (int i=0; i<5; i++)
{
printf("%d\n",a[i]);
}
return 0;
}
会得到如下的输出
4195840
0
4195488
0
-1332944752
会得到一堆乱起八糟的数据,这是为什么呢?这主要涉及到栈结构中的数据是随机的,栈结构的内容会在后面进行介绍。
在 C99 标准中,数组的尺寸如果是整型常量或者整型常量表达式,或者确定他的尺寸的时候,他就不是一种长度可变的数组,相反(即指其余条件下)则是一个长度可变的数组。即在 C99 标准中,c 语言已经支持变长数组了。注意这里的变长指的是数组的长度是在运行的时候才会被决定,就是说可以用一个变量来指定组的长度。
上面是在 C99 的标准中关于变长数组的说明,接着我们来一下,如果使用了 C99 标准中的变长数组是否可以使用 gcc 来进行编译呢?答案是可以的。因为作为编译器的扩展,gcc 在 C90 模式下和 C++ 模式下都是遵守 C99 标准的。所以 gcc 即使没有加上 -std=c99,就是他默认以 C90 标准进行编译的时候,也是遵守 C99 标准的,所以使用变长数组的时候,不用加上 -std=c99 也是可以正常运行的。
所以下面代码是合法的
#include
int main()
{
int n, i;
printf("请输入字符的个数:");
scanf("%d", &n);
char a[n+1];
printf("请开始输入字符:");
getchar(); // 将标准输入流中剩下的 '\n' 扔掉
for (i = 0; i < n; i++)
{
scanf("%c", &a[i]);
}
a[n] = '\0';
printf("你输入的字符串是:%s\n", a);
return 0;
}
在这段代码中,我们使用了一个整型的变量 n+1 来表示数组的长度,仍然可以编译通过并执行,说明变长数组确实是可用的。
但是在这段代码中,仍有几个部分是需要注意的。首先是数组的长度,通过第 7、8 两行可以知道输入的字符个数应该是 n 个 ,但是为什么数组的长度会定义为 n+1 呢?这主要是因为字符串都是以 '\0'
结尾的,但是 '\0'
又不可能通过用户来输入,所以要定义一个 n+1 长度的数组,然后自己在里面加入'\0'
作为字符串的结尾。
其次我们在第 13 行加入了 getchar();
,这主要是将标准输入流中剩下的 '\n'
扔掉。之所以 '\n'
会在输入缓冲区可以参考 《9. C 语言 – 循环结构:while语句和 do … while语句》 3.3 三种输入函数的注意事项 中的内容。读取字符时 scanf()
以 Enter 结束一次输入,不会舍弃最后的回车符。所以如果不使用 scanf()
会造成首先读入键盘缓冲区的回车符,之后再读入数组中的字符,会导致数组中的最后一个字符无法正确显示。
数组的访问方式很直觉,即通过下标进行访问;但是比较反直觉的是,数组的小标从 0 开始,所以某个数组中第一个元素的下表是 0 而不是 1。所以在第一张图中,每一行中的数字实际上是数组的下标。
访问数组具体方式如下
// 数组访问方式
数组名[下标]
a[0]; // 访问a数组中的第一个元素
b[1]; // 访问b数组中的第二个元素
有两点个需要注意,首先定义数组与访问数组的写法十分相似,区别在于定义数组需要申明数组内变量的类型,但是访问不需要;在访问数组的过程中要注意数组越界的问题,如下
int a[5]; // 创建一个具有五个元素的数组
a[0]; // 访问第一个元素的下标是0,不是1
a[5]; // 数组越界,因为第五个元素的下标是a[4]
当数组发生越界情况时,本身在 C语言中属于未定义行为,不同的编译器可能会有不同的对待方式。比如说在 gcc 中,会随机给越界访问的数组元素分配一个数字。
实现一个执行10次的循环,我们通常下面的第一种写法,而不是第二种写法
// 常用
for (i = 0; i < 10; i++)
{
……
}
// 不常用
for (i = 1; i <= 10; i++)
{
……
}
这是因为我们常常需要使用循环来访问数组,而数组的下标是从 0 开始的。
如果我们采用只给出各个元素的值,而不指定数组的长度的方法来初始化数组,比如说int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
,这个时候我们并不知道数组的长度。如果要写一个 for 循环来遍历数组中的每一个元素,应该使用下面这种方法
for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
printf("%2d月份:%d天\n", i+1, days[i]);
}
使用这样的方法表达数组的长度是在实际开发中是很常见的技巧。
[1] “小甲鱼” 视频课程《带你学C带你飞》【第一季】P17
[2] “小甲鱼” 视频课程《带你学C带你飞》【第一季】P18