第 3 讲 · 数组、字符串与文件
数组、字符串和文件看起来是三类语法,背后其实都在处理“连续的数据序列”。数组解决内存中的连续元素,字符串给字符序列增加结束标志,文件则把序列放到外部存储中并通过流读写。
数组的连续性
数组元素类型相同、地址连续,因此 a[i] 可以在常数时间内定位。若首地址为 ,每个元素占 字节,则 a[i] 地址是 。
二维数组在 C 中按行连续存放。对于 a[m][n],a[i][j] 的偏移量是
把二维数组传给函数时,编译器必须知道每行有多少列,才能完成这个地址计算。
数组作为函数参数
数组名传入函数时,会转换成指向首元素的指针,因此函数收不到数组长度。长度必须另外传入,或用某个约定的结束标志。
int sum(const int a[], int n) {
int result = 0;
for (int i = 0; i < n; i++) result += a[i];
return result;
}
const 表明函数只读数组,使接口更清楚。
C 字符串的真正边界
C 字符串是以 \0 结尾的字符数组。字符数组不一定是合法字符串:
char a[3] = {'A', 'B', 'C'}; /* 不是字符串 */
char b[4] = {'A', 'B', 'C', '\0'}; /* 是字符串 */
strlen、strcpy、strcat 等函数都会沿内存查找 \0。若目标数组没有足够空间,或者源串没有终止符,函数会越界。
常用字符串操作应知道什么
strlen(s)返回终止符之前的字符数;strcmp(a,b)返回负数、零或正数,不承诺只返回-1/0/1;strcpy(dst,src)连同终止符复制,目标必须足够大;strncpy不保证所有情况下自动补终止符;strcat从目标串原终止符处继续写,仍需检查总容量;strstr查找子串,失败返回NULL;strchr查找字符,失败返回NULL。
数组不能在赋值语句中整体赋值,因此不能写 a = b 来复制字符数组;应逐元素处理或调用复制函数。
格式化输入输出
printf 和 scanf 的格式必须与参数类型匹配。宽度和精度可以控制对齐与小数位,例如 %8.2f 表示字段宽度至少为 8、小数点后保留 2 位;%-8s 表示字符串在宽度 8 的字段中左对齐。
scanf 读取数值通常要传地址,读取字符数组时数组名已经是首地址。要检查返回值是否等于成功读入的字段数,不能默认输入一定合法。格式中的空白会消费任意数量空白字符,而 %c 不会自动跳过换行,这些差异是源课件期中提醒列出的易错点。
朴素字符串匹配
给主串 和模式串 ,朴素方法从每个可能起点开始逐字符比较。失配后主串起点向右移动一位,模式串从头再来。最坏情况下会重复比较大量已经相同的字符,复杂度为 。
课程后面用 KMP 解决这种重复,关键不是“更复杂的循环”,而是利用模式串自身的前后缀信息,让失配后不回退主串指针。
文件是有状态的流
文件操作的一般流程是:
fopen打开并检查返回值;- 通过
fgetc、fgets、fscanf、fread等读取,或对应函数写入; - 检查读取结果,不使用
feof预判下一次读取; fclose关闭。
FILE *fp = fopen("input.txt", "r");
if (fp == NULL) return 1;
char line[256];
while (fgets(line, sizeof line, fp) != NULL) {
/* 处理本行 */
}
fclose(fp);
fgets 最多读取“缓冲区大小减一”个字符,并补上终止符,比没有长度限制的输入方式安全。
文本文件与二进制文件
文本模式把数据转成字符表示,便于人工查看;二进制模式直接保存字节,格式更紧凑,但读写双方必须约定类型、字节序和布局。不能把内存中的指针值直接写入文件并指望下次仍有效,因为指针只在当前进程地址空间中有意义。
常见边界
- 数组容量是否包含字符串终止符;
- 读取一行时换行符是否保留;
- 文本最后一行可能没有换行符;
- 文件打开失败或中途读取失败;
- 输入记录过长;
- 多个文件流是否都按正确路径关闭。
这些边界一旦先想清楚,词频统计、文件加密、通讯录整理等作业就会从“字符串技巧题”变成普通的数据流处理。