文章目录
- 一、字符串函数
- 1. 长度不受限制的字符串函数
- 1.1 strcpy – 字符串拷贝
- 1.2 strcat – 字符串追加
- 1.3 strcmp – 字符串比较
- 2. 长度受限制的字符串函数(更安全)
- 2.1 strncpy
- 2.2 strncat
- 2.3 strncmp
- 3. strstr – 查找子串
- 4. strtok – 字符串分割
- 5. strlen – 字符串长度
- 二、文件操作核心知识
- 1. 标准流
- 2. 文件指针 FILE
- 3. 对比一组函数:scanf/fscanf/sscanf 与 printf/fprintf/sprintf
- 输入方向(从…读入)
- 输出方向(向…写入)
- Linux 重定向的对应关系
- 三、如何高效记忆这些函数?
- 1. 字符串函数记忆法
- ① 根据功能分类
- ② 图解英文缩写
- ③ 特别记忆易错点
- 2. 格式化输入输出函数记忆法
- 3. 文件操作记忆法
- 总结
大家好,这里是小J,最近在深入学习Linux系统编程时,我发现自己的C语言字符串函数和文件操作知识有些生疏了。为此,我花时间系统地整理了一遍相关知识点,形成了这篇笔记。本文将结合我的学习心得和实战代码,详细讲解C语言中常用的字符串处理函数(包括长度不受限制与受限制的函数、strstr、strtok等)以及文件操作的核心概念(标准流、FILE指针、scanf/printf家族函数)。最后,我还会分享一套高效的记忆方法,帮助你彻底掌握这些内容。
一、字符串函数
C语言中的字符串函数主要定义在 <string.h> 头文件中。根据安全性(是否容易导致缓冲区溢出)可以分成两大类:长度不受限制 和 长度受限制。
1. 长度不受限制的字符串函数
这类函数在处理字符串时依赖 ‘\0’ 结束符,不指定操作的字符个数,容易造成数组越界,使用时要格外小心。
1.1 strcpy – 字符串拷贝
char*strcpy(char*dest,constchar*src);·功能:将 src 指向的字符串(包括结束符 ‘\0’)复制到 dest 指向的空间。
·返回值:返回 dest。
·注意事项:
· dest 必须有足够的空间容纳 src。
· src 和 dest 不能有重叠(标准未定义重叠行为)。
· 自己复制给自己时,由于 ‘\0’ 会被覆盖,导致无法终止,形成未定义行为。
模拟实现:
char*my_strcpy(char*dest,constchar*src){char*ret=dest;while((*dest++=*src++)){;// 赋值操作本身即可判断结束}returnret;}1.2 strcat – 字符串追加
char*strcat(char*dest,constchar*src);·功能:将 src 附加到 dest 末尾(覆盖 dest 的 ‘\0’,并追加新的 ‘\0’)。
·返回值:返回 dest。
·注意事项:
· dest 空间必须足够大。
· 不能自己追加自己,否则会死循环(因为追加过程中 ‘\0’ 被覆盖,永远找不到结束符)。
模拟实现:
char*my_strcat(char*dest,constchar*src){char*ret=dest;while(*dest){// 找到 dest 的末尾dest++;}while((*dest++=*src++)){;}returnret;}1.3 strcmp – 字符串比较
intstrcmp(constchar*s1,constchar*s2);·功能:按字典序比较两个字符串。
·返回值:
· 0:相等
· >0:s1 大于 s2
· <0:s1 小于 s2
·注意事项:比较的是字符的ASCII码,不是字符串长度。
模拟实现:
intmy_strcmp(constchar*s1,constchar*s2){while(*s1&&*s2&&(*s1==*s2)){s1++;s2++;}return*s1-*s2;}2. 长度受限制的字符串函数(更安全)
这类函数要求你显式指定最多操作的字符个数,可以有效防止缓冲区溢出,推荐优先使用。
2.1 strncpy
char*strncpy(char*dest,constchar*src,size_tn);·功能:从 src 复制最多 n 个字符到 dest。
· 若 src 长度小于 n,则剩余部分用 ‘\0’ 填充。
· 若 src 长度大于等于 n,则不会自动追加 ‘\0’。
·返回值:返回 dest。
2.2 strncat
char*strncat(char*dest,constchar*src,size_tn);·功能:从 src 追加最多 n 个字符到 dest 末尾,并总是添加 ‘\0’。
·返回值:返回 dest。
2.3 strncmp
intstrncmp(constchar*s1,constchar*s2,size_tn);·功能:比较两个字符串的前 n 个字符。
·返回值:同 strcmp。
3. strstr – 查找子串
char*strstr(constchar*haystack,constchar*needle);·功能:在字符串 haystack 中查找第一次出现子串 needle 的位置。
·返回值:若找到,返回指向该位置的指针;否则返回 NULL。若 needle 为空字符串,返回 haystack。
模拟实现:
char*my_strstr(constchar*str1,constchar*str2){constchar*s1=NULL;constchar*s2=NULL;constchar*cur=str1;// 1. 处理特殊情况:如果子串是空字符串,直接返回原串if(*str2=='\0')return(char*)str1;// 2. 遍历主串中的每一个位置作为起点while(*cur){// 用 s1 和 s2 来匹配,不破坏 cur 和 str2 的原始位置s1=cur;// s1 从当前位置开始s2=str2;// s2 从子串开头开始// 3. 逐个字符比较while(*s1!='\0'&&*s2!='\0'&&*s1==*s2){s1++;s2++;}// 4. 判断是否匹配成功if(*s2=='\0')// 子串的所有字符都比较完了且都相等{return(char*)cur;// 返回匹配成功的位置}// 5. 匹配失败,cur 后移一位,重新开始cur++;}// 6. 遍历完整个主串都没找到,返回 NULLreturnNULL;}图解执行过程
假设主串"ababc",子串"abc":
第1轮:cur 指向位置0 a b a b c ↑ (cur) a b c → a==a, b==b, 但 a!=c → 失败 ↑ (s1) ↑ (s2) 第2轮:cur 指向位置1 a b a b c ↑ (cur) a b c → b!=a → 失败 ↑ (s1) ↑ (s2) 第3轮:cur 指向位置2 a b a b c ↑ (cur) a b c → a==a, b==b, c==c → 成功! ↑ (s1) ↑ (s2) ↑ (s2移动) 返回位置2的指针思路:用 cur 遍历主串,每到一个位置就尝试匹配子串。
4. strtok – 字符串分割
char*strtok(char*str,constchar*sep);·功能:将字符串按分隔符集合 sep 拆分成多个标记(token)。
·使用规则:
第一次调用时传入待分割的字符串 str,后续调用传入 NULL 以继续分割同一字符串。
· 函数内部会修改原字符串,将分隔符替换成 ‘\0’,因此原字符串会被破坏(通常先拷贝再操作)。
· 当找不到更多标记时返回 NULL。
·注意事项:不支持重入(多线程中使用需谨慎)。
示例:
chararr[]="Julian@yeah.net";chararr2[30]={0};strcpy(arr2,arr);// 先备份,因为 strtok 会修改原串constchar*sep="@.";char*ret=NULL;for(ret=strtok(arr,sep);ret!=NULL;ret=strtok(NULL,sep)){printf("%s\n",ret);}// 输出:// Julian// yeah// net另一个例子包含多个连续分隔符:
chararr[]="Julian@yeah.net@hehe";// 同样用法,输出:// zpengwei// yeah// net// hehe5. strlen – 字符串长度
size_tstrlen(constchar*s);·功能:返回字符串 s 的长度(不包括 ‘\0’)。
·返回值:size_t 无符号整数类型。
模拟实现(三种经典写法):
// 计数器法size_tmy_strlen(constchar*s){size_tcount=0;while(*s++)count++;returncount;}// 指针相减法size_tmy_strlen2(constchar*s){constchar*p=s;while(*p)p++;returnp-s;}// 递归法(不推荐,仅展示)size_tmy_strlen3(constchar*s){if(*s=='\0')return0;return1+my_strlen3(s+1);}二、文件操作核心知识
文件操作涉及到流、文件指针、以及一组格式化输入输出函数。
1. 标准流
C语言程序启动时会默认打开三个流,我们无需手动打开即可使用:
| 流名称 | 含义 | 默认关联设备 | 常用函数 |
|---|---|---|---|
stdin | 标准输入流 | 键盘 | scanf |
stdout | 标准输出流 | 显示器 | printf |
stderr | 标准错误流 | 显示器 | perror,fprintf(stderr, ...) |
正是因为这些默认打开的流,我们才能直接使用 printf 和 scanf 进行输入输出。
2. 文件指针 FILE
缓冲文件系统中,每个被使用的文件都在内存中对应一个文件信息区,这个区域是一个结构体变量,类型名为 FILE(在 stdio.h 中定义)。
例如,VS2013 中的声明如下:
struct_iobuf{char*ptr;// 文件输入的下一个位置int_cnt;// 当前缓冲区的剩余字符数char*base;// 缓冲区基址int_flag;// 文件状态标志int_file;// 文件描述符int_charbuf;// 单字符缓冲int_bufsize;// 缓冲区大小char*tmpfname;// 临时文件名};typedefstruct_iobufFILE;我们通常用 FILE* 指针来操作文件,例如:
FILE*fp=fopen("data.txt","r");if(fp==NULL){perror("fopen");return1;}// ... 读取或写入操作fclose(fp);Linux 设计中“一切皆文件”的理念,使得FILE*不仅能操作普通文件,还能操作标准流:
FILE*fp=fopen("/proc/meminfo","r");// 读取内存信息FILE*fp=popen("ls -l","r");// 读取命令输出FILE*fp=fdopen(3,"r");// 将文件描述符转为 FILE*3. 对比一组函数:scanf/fscanf/sscanf 与 printf/fprintf/sprintf
这一组函数功能相似,但作用的“目标”不同。
输入方向(从…读入)
| 函数 | 作用对象 | 说明 |
|---|---|---|
scanf | 标准输入stdin | 从键盘读取格式化数据。 |
fscanf | 任意文件流 | 从指定的FILE*流中读取格式化数据。 |
sscanf | 字符串 | 从字符串中提取格式化数据(相当于反向的sprintf)。 |
示例:
inta,b;sscanf("123 456","%d %d",&a,&b);// a=123, b=456输出方向(向…写入)
| 函数 | 作用对象 | 说明 |
|---|---|---|
printf | 标准输出stdout | 向屏幕打印格式化字符串。 |
fprintf | 任意文件流 | 向指定的FILE*流(如文件或stderr)写入格式化数据。 |
sprintf | 字符串 | 将格式化数据输出到字符数组中(注意缓冲区溢出风险,建议用snprintf)。 |
示例:
charbuf[100];sprintf(buf,"答案 = %d",42);// buf 内容:"答案 = 42"Linux 重定向的对应关系
| C 代码 | Linux 命令行等价 |
|---|---|
printf("hello") | echo "hello" |
fprintf(fp, "hello") | echo "hello" > file.txt |
fprintf(stderr, "error") | echo "error" >&2 |
scanf("%d", &n) | read n |
三、如何高效记忆这些函数?
C语言的字符串和文件函数数量较多,但大多遵循一定的命名规律。下面是我的记忆方法,希望能帮你告别死记硬背。
1. 字符串函数记忆法
① 根据功能分类
| 功能 | 核心动词 | 相关函数 |
|---|---|---|
| 复制 | cpy | strcpy,strncpy |
| 连接 | cat | strcat,strncat |
| 比较 | cmp | strcmp,strncmp |
| 查找子串 | str | strstr |
| 分割 | tok | strtok |
| 求长度 | len | strlen |
其中 n 表示长度受限制,例如 strncpy 中的 n 就是最多复制的字符数。
② 图解英文缩写
· str → string
· cpy → copy
· cat → concatenate
· cmp → compare
· tok → tokenize
· len → length
理解了这些缩写,函数名就变得非常直观。
③ 特别记忆易错点
· strtok 会破坏原字符串 → 使用前建议先 strcpy 备份。
· strcat 不能自己连接自己 → 可改用 strncat 并指定长度。
· strncpy 不自动补 ‘\0’ → 需手动添加结束符。
2. 格式化输入输出函数记忆法
记住一个核心公式:前缀决定目标,后缀决定方向。
| 前缀 | 含义 | 方向 | 示例 |
|---|---|---|---|
| 无 | 标准流 | 输入/输出 | printf,scanf |
f | 文件流(file) | 输入/输出 | fprintf,fscanf |
s | 字符串(string) | 输入/输出 | sprintf,sscanf |
sprintf 中的 s 是输出到字符串,sscanf 中的 s 是从字符串输入。一正一反,对称记忆。
3. 文件操作记忆法
· 打开文件:fopen — file open
· 关闭文件:fclose — file close
· 读文件:fgetc, fgets, fread
· 写文件:fputc, fputs, fwrite
· 格式化读写:fprintf, fscanf
· 文件指针定位:fseek, ftell, rewind
只要记住 f 前缀代表文件操作,后面跟动词即可。
总结
这次回炉整理让我对C语言的字符串和文件操作有了更深的理解。总结一下重点:
- 优先使用长度受限制的字符串函数(strncpy/strncat/strncmp),避免缓冲区溢出。
- strtok 很好用,但注意它会修改原字符串,而且不可重入。
- FILE 是核心抽象,标准流 stdin/stdout/stderr 让我们能立即使用 printf/scanf。
- printf 家族:无前缀→标准流,f→文件流,s→字符串。
希望这篇笔记加心得能帮助你快速回忆起这些重要的知识点。如果你也有自己的记忆妙招,欢迎在评论区交流!
附录:完整代码示例可以参考文中的 my_strstr、my_strcpy 等模拟实现,建议你自己动手敲一遍,加深理解。