编译并运行
gcc -std=c11 -Wall -Wextra lesson07.c -o lesson07.exe
./lesson07.exeC foundations / Lesson 07
从四次温度采样开始学习数组:如何创建、访问和遍历,再理解字符串为什么需要结束标记。最后将数组交给函数,认识地址和长度各承担什么作用。
如果要保存 4 次采样,写 4 个独立变量可以做到,但采样数量变多后,逐个命名和处理会越来越麻烦。数组让同类型数据按顺序组成一组,我们再用下标找到其中一个元素。
'\0' 结束的字符串。创建并运行 lesson07.c,打印四次采样及平均值;解释字符串长度和字符数组占用字节数为何不同。
在工作目录中创建 lesson07.c。先计算四个数的总和和平均值,再输入程序。
#include <stdio.h>
#include <stddef.h>
#include <string.h>
int main(void)
{
int samples[] = {18, 21, 24, 20};
size_t count = sizeof samples / sizeof samples[0];
int total = 0;
char label[] = "TEMP";
for (size_t i = 0; i < count; i++) {
total += samples[i];
printf("sample[%zu]=%d\n", i, samples[i]);
}
printf("count=%zu, sum=%d, mean=%.2f\n",
count, total, (double)total / (double)count);
printf("label=%s, characters=%zu, storage=%zu\n",
label, strlen(label), sizeof label);
return 0;
}gcc -std=c11 -Wall -Wextra lesson07.c -o lesson07.exe
./lesson07.exegcc -std=c11 -Wall -Wextra lesson07.c -o lesson07
./lesson07请在保存源文件的目录执行。以下输出中的 storage=5 来自这个只包含 4 个基本英文字符的特定字符数组和末尾终止符。
sample[0]=18
sample[1]=21
sample[2]=24
sample[3]=20
count=4, sum=83, mean=20.75
label=TEMP, characters=4, storage=5int samples[] = {18, 21, 24, 20}; 定义一个含 4 个 int 元素的数组。编译器由初始化列表确定长度;元素按下标依次对应:
| 下标 | 表达式 | 值 |
|---|---|---|
| 0 | samples[0] | 18 |
| 1 | samples[1] | 21 |
| 2 | samples[2] | 24 |
| 3 | samples[3] | 20 |
长度为 4 的数组,可访问的下标是 0 到 3;最后一个元素使用 长度 - 1。数组中每个元素都可以作为同类型变量使用,例如 samples[1] = 22; 改变第二个元素。
也可以显式写 int samples[4] = {18, 21, 24, 20};。若初始化列表比数组短,未列出的元素会被初始化为零;不要以此推断“未初始化的局部数组自动全是零”。
在例程中,samples 此时仍是实际数组对象。sizeof samples 得到整个数组的字节数;sizeof samples[0] 得到一个元素的字节数。前者除以后者,得到元素数量。此处使用 size_t 存长度,用 %zu 输出。
size_t count = sizeof samples / sizeof samples[0];
for (size_t i = 0; i < count; i++) {
/* 处理 samples[i] */
}循环先检查 i < count,再读取元素。对长度 4 的数组,访问 0、1、2、3 后,i 变为 4,条件为假,循环结束。不要写成 i <= count,否则最后一次会访问不存在的第五个元素。
sizeof 求原数组长度,后面会解释。samples[4] 不是数组的第五项:这个数组只有四个元素。对它读取或写入会产生未定义行为,可能出现错误结果、崩溃或看似正常的输出,不能通过一次运行判断代码安全。不要运行越界示例。
先写清长度,再检查每一次下标:从 0 开始,循环条件使用 i < count。把长度传给函数时,调用者必须保证长度没有超过真实数组中的元素数量;函数不能仅靠指针自动知道缓冲区有多大。
本例的固定采样值相加不会超出 int。换成更多数据或更大数值时,还要检查求和是否超出类型范围;有符号整数溢出同样不能忽略。
'A' 是字符常量,"TEMP" 是字符串字面量。C 中约定以值为零的字符 '\0' 标记字符串结束。char label[] = "TEMP"; 创建可修改的字符数组,内容可以想成:
下标 0 1 2 3 4
label 'T' 'E' 'M' 'P' '\0'最后的 '\0' 不显示成普通字母,却占一个 char 元素。printf("%s", label) 会按字符串读取直到遇到终止符,因此传给 %s 的数据必须在有效数组范围内有终止符。'\0' 与字符 '0' 不同,前者的值为零,后者表示数字字符。
label 是由字符串字面量初始化的独立字符数组,可以改写其中合法的元素,例如 label[0] = 't';。不要尝试修改字符串字面量本身;本课也不让用户输入任意长度的字符串。
strlen(label) 从第一个字符数到终止符之前,得到 4;需要包含 <string.h>。sizeof label 得到整个数组占用的字节数,当前例子是 5:4 个字符加 1 个终止符。前者不计终止符,后者涵盖数组全部存储空间。
如果声明 char label[10] = "TEMP";,字符串长度仍为 4,而数组有 10 个 char 元素。容量足够时才能写入内容并保留终止符。char word[4] = "TEMP"; 在 C 中可定义,但其四个元素没有末尾的 '\0';不能把它直接交给要求字符串的 %s 或 strlen。这是判断题,不要运行错误调用。
strlen 统计终止符之前的字节数,不一定等于用户看到的汉字或字符数量。上述可验证的 4 和 5 使用的是英文示例;处理多字节文本时要另行学习编码与容量。
在大多数表达式中,数组会转换为指向首元素的指针;当数组作为函数实参传递时,函数接收到的是地址信息。参数写成 const int values[] 时,函数参数实际按指针处理,原数组的长度不会自动一同传入。const 表示这个函数不通过 values 修改元素。
int sum_samples(const int values[], size_t length)
{
int sum = 0;
for (size_t i = 0; i < length; i++) {
sum += values[i];
}
return sum;
}调用时可在数组定义所在作用域计算数量:sum_samples(samples, count)。约定传入的 values 要指向至少 length 个可读取的 int,并保证和不会溢出。length 为 0 时循环不会访问元素。完整指针、地址和生命周期会在后续课程继续讲;这里先理解“地址告诉函数从哪里读,长度告诉函数允许读多少”。
| 现象 | 检查 | 下一步 |
|---|---|---|
| 访问最后一个元素时输出不对 | 长度为 N 时最后下标是否写成 N? | 最后下标是 N−1;循环使用 i < N。 |
strlen 结果不符合预期 | 数组中是否确有范围内的 '\0'? | 先检查容量和终止符;不要对非字符串调用 strlen。 |
函数里算出的 sizeof 不同 | 参数是数组对象还是指针? | 在调用方计算长度,并作为额外参数传入。 |
| 平均值没有小数 | 两个操作数是否都是整数? | 按需要转换为 double 再除,并确保分母不为零。 |
将样本改成 {18, 21, 24, 20, 22},第 5 项的下标是什么?count 会是多少?
第 5 项是 samples[4];在定义数组的作用域按例程计算得到 count = 5。循环会访问 0 至 4。
对于 char label[10] = "TEMP";,strlen(label) 和 sizeof label 各是多少?
分别为 4 和 10。strlen 不计终止符;sizeof 包含数组全部 10 个 char 元素。
四元素数组使用 i <= count 会在哪个下标出问题?应该怎样修改?
i = 4 时访问越界。应使用 i < count,使最后一次访问为 samples[3]。
只把数组交给函数,函数能否可靠地用参数上的 sizeof 反推出原数组的元素个数?
不能。函数中的数组形式参数会调整为指针,sizeof 测得的是指针大小。调用者需要传递正确的元素数,并确保不超出实际数组。
i < count 为什么不会访问第 5 个元素。"TEMP" 的四个字符和终止符,解释 4 与 5。进入多文件与调试:把读取、计算和主程序拆开,并用 GDB 停在函数里观察数组。