一、字符数组概述
C语言没有专门的字符串类型,一切字符串操作都靠字符数组完成。
c
char str[20]; // 定义一个能存20个字符的数组
1. 字符数组的存储
字符数组的每个元素存一个字符,末尾用'\0'(ASCII码为0)标识字符串结束。
c
char str[6] = {'h', 'e', 'l', 'l', 'o', '\0'}; // 内存布局:h e l l o \0💡感悟:'\0'是字符串的"终止符",它告诉printf和puts"到这里就停了"。没有它,函数会一直往下读内存里的垃圾数据,直到碰巧遇到一个0为止——这就是为什么字符数组初始化时一定要考虑'\0'的位置。
2. 三个容易混淆的"零"
| 写法 | 含义 | ASCII码 |
|---|---|---|
0 | 整数零 | 00000000 |
'\0' | 字符串结束符 | 00000000(与0等价) |
'0' | 字符零 | 00110000(48) |
💡感悟:0、'\0'、'0'这三个东西看起来像,实际完全不同。刚开始写代码的时候经常搞混,尤其是用strlen统计长度时,明明字符串里有'0'字符,长度却按实际字符数算——因为strlen只认'\0'作为结束标志,而'0'只是普通字符。搞清楚它们之间的区别,字符串相关的代码就少了一半的Bug。
二、字符数组的初始化
1. 全部初始化
c
char str[6] = {'h', 'e', 'l', 'l', 'o', '\0'}; char str[6] = {"hello"}; // 自动加 \0 char str[6] = "hello"; // 最常用写法2. 部分初始化
c
char str[6] = {'h', 'e', 'l', 'l', 'o'}; // 少了 \0,剩余元素补0(即 \0) // str 内容是:h e l l o \0 char str[6] = {'h', 'e'}; // 剩余4个元素自动补0 char str[6] = {0}; // 全部置零,常用写法💡感悟:部分初始化自动补0的特性,在清空字符串时特别实用——写一个char str[32] = {0};,整个数组就全是'\0'了,不用写循环逐个赋值。这是C语言提供的"语法糖",用好了能省不少事。
3. 默认初始化(省略数组长度)
c
char str[] = {'h', 'e', 'l', 'l', 'o', '\0'}; char str[] = {"hello"}; char str[] = "hello"; // 长度为6(包含\0)⚠️ 实际开发中不建议省略长度,容易造成数组越界。
三、字符串的输入与输出
1. 输出方式
c
char str[20] = "hello world"; printf("str = %s\n", str); // %s 控制符专门输出字符串 puts(str); // 输出后自动加换行⚠️
puts比printf多打印一个换行符。
2. 输入方式
c
char str[32] = {0}; scanf("%s", str); // 不能接收带空格的字符串 gets(str); // 可以接收带空格的字符串(但不安全,已废弃)scanf 与 gets 的区别:
scanf("%s") | gets() | |
|---|---|---|
| 能否接收空格 | ❌ 遇到空格停止 | ✅ 整行读取 |
| 安全性 | 较好 | 不检查边界,已废弃 |
💡感悟:gets已经被废弃了,因为它不检查数组边界,输入过长会导致缓冲区溢出。实际项目中推荐用fgets替代。虽然学校练习里可能还会用gets,但心里要清楚——写代码不光要能用,还要安全。
四、字符串常用操作
1. 获取字符串长度 —— strlen
c
char str[32] = "hello"; int len = strlen(str); // len = 5(不含 \0)
⚠️
strlen统计的是从开头到'\0'之间的字符个数,不包含'\0';sizeof获得的是数组占用总字节数。
c
char str[32] = "hello"; printf("%lu\n", strlen(str)); // 5 printf("%lu\n", sizeof(str)); // 322. 字符串拷贝 —— strcpy
c
char str[32] = {0}; strcpy(str, "hello world"); // 将 "hello world" 拷贝到 str 中⚠️ 目标空间必须足够大,否则会内存越界访问。
3. 字符串拼接 —— strcat
c
char str1[32] = "hello"; char str2[32] = "world"; strcat(str1, str2); // str1 变为 "helloworld"
⚠️ 目标空间必须能容纳拼接后的完整字符串。
💡感悟:strcat的工作原理是先找到目标字符串的'\0',然后从这里开始往后面粘贴源字符串的内容,最后再补一个'\0'。理解了这个过程,就明白了为什么目标数组要足够大——否则'\0'被覆盖后找不到结束位置,程序就会越界访问。
4. 字符串比较 —— strcmp
c
int ret = strcmp(str1, str2); // ret == 0 → 两个字符串相等 // ret > 0 → str1 大于 str2 // ret < 0 → str1 小于 str2
比较规则:从左到右逐字符比较ASCII码,第一个不同的字符决定大小。
💡感悟:字符串比较不是比长短,而是比"字典序"——"apple"和"banana"比较,先看第一个字符 'a' vs 'b','a' 小,所以整体就小,后面的字符都不用看了。这和查字典是一个道理。
五、二维数组
1. 定义与访问
二维数组本质上是"数组的数组"——可以理解为一个表格。
c
int a[2][3]; // 2行3列 // 元素:a[0][0] a[0][1] a[0][2] // a[1][0] a[1][1] a[1][2]
2. 存储顺序
二维数组在内存中按行连续存储,先存第0行,再存第1行。
c
int a[2][3] = {1, 2, 3, 4, 5, 6}; // 内存布局:1 2 3 4 5 63. 初始化
c
// 全部初始化 int a[2][3] = {1, 2, 3, 4, 5, 6}; int a[2][3] = {{1, 2, 3}, {4, 5, 6}}; // 按行清晰 // 部分初始化(未赋值的补0) int a[2][3] = {1, 2, 3, 4}; int a[2][3] = {{1, 2}, {3}}; // 默认初始化(行可省,列不可省) int a[][3] = {1, 2, 3, 4, 5, 6}; // 自动推断为2行⚠️ 二维数组行数可以省略(编译器自动推断),但列数不能省略。
4. 二维字符数组
存储多个字符串时用二维字符数组:
c
char str[3][10] = {"hello", "world", "C"}; // 存放3个字符串,每个最长9个字符(留1个给\0)六、今日总结
今天学习了字符数组和二维数组,核心要点如下:
字符数组(字符串):
用
'\0'标识字符串结束,0、'\0'、'0'要区分清楚初始化推荐
char str[32] = {0};或char str[32] = "hello";输入用
scanf("%s")(无空格)或gets(可空格,但不安全)常用函数:
strlen(求长度)、strcpy(拷贝)、strcat(拼接)、strcmp(比较)操作字符串时务必保证数组空间足够,防止越界
二维数组:
本质是"数组的数组",按行连续存储
定义:
数据类型 数组名[行数][列数]初始化时行可省,列不可省
今日总结感悟:
字符数组让我第一次意识到——原来C语言里压根没有"字符串类型"这种东西,所谓的字符串就是普通字符数组加上一个'\0''的约定。这就是C语言的设计哲学:不提供复杂的封装,只提供简单的原材料,怎么用全看你。习惯了高级语言字符串的同学可能觉得不方便,但正是这种"不替你做决定"的设计,给了程序员最大的掌控力。
字符串函数的练习我建议自己动手实现一遍——用循环写一个strlen、写一个strcpy,能帮你真正理解字符串在内存中是怎么存放和操作的。在纸上画一画数组的格子,填上字符,比盯着代码硬想要有效得多。