跳转至

字符数组(字符串)

背景:C语言没有"字符串"类型

很多语言有专门的 string 类型,但 C 没有。那C里怎么表示一串文字?

答案:用字符数组。 最末尾加一个特殊的 \0(空字符)表示"到这里结束"。


一、字符数组的两种写法

写法1:老老实实一个一个写

char str[] = {'H', 'e', 'l', 'l', 'o', '\0'};
// 数组长度 = 6(5个字母 + 1个结束符)

写法2:用双引号(更常用)

char str[] = "Hello";
// 编译器自动在末尾加 \0,数组长度还是6

两种写法完全等价,写法2更方便。

验证一下长度

#include <stdio.h>

int main() {
    char str[] = "Hello";

    printf("数组长度: %zu\n", sizeof(str));         // 6(包含\0)
    printf("字符串长度: %zu\n", strlen(str));       // 5(不包含\0)
    // sizeof 数的是所有字符(含\0)
    // strlen 只数到\0为止(不含\0)

    return 0;
}

%zu 是 sizeof 和 strlen 返回值的格式符(size_t 类型)。


二、内存里的样子

char str[] = "Hello";

内存中:

┌────┬────┬────┬────┬────┬────┐
│ H  │ e  │ l  │ l  │ o  │ \0 │
└─┬──┴─┬──┴─┬──┴─┬──┴─┬──┴─┬──┘
  0    1    2    3    4    5    ← 下标

⚠️ \0 不是字母,是结束标志! 告诉程序:"字符串到这里就完了"。


三、strlen 和 sizeof 的区别

表达式 结果 解释
sizeof("Hello") 6 整个数组的字节数(含\0)
strlen("Hello") 5 有效字符个数(不含\0)
strlen("") 0 空字符串,长度0
sizeof("") 1 空字符串也有一个\0

⚠️ strlen 需要引入 <string.h> 头文件!


四、遍历字符数组

和普通数组一样,用 for 循环:

#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "Friday";
    int len = strlen(str);

    for (int i = 0; i < len; i++) {
        printf("str[%d] = '%c'\n", i, str[i]);
    }

    return 0;
}

输出:

str[0] = 'F'
str[1] = 'r'
str[2] = 'i'
str[3] = 'd'
str[4] = 'a'
str[5] = 'y'

也可以直接用 %s 一口气打印整个字符串:

printf("%s", str);     // 输出: Friday

%s 会从数组开头一直输出到遇到 \0 为止。


五、用户输入字符串

scanf 读取

char name[20];                // 预留20个字符的空间
scanf("%s", name);            // 注意:没有 & 符号!
printf("你好, %s!\n", name);

⚠️ 为什么没有 & 普通变量用 scanf("%d", &x) 要加 &,但数组名 name 本身就是地址,所以不用加。

⚠️ scanf 的坑:遇到空格就停了! 输入 "Friday Hello" → 只读到 "Friday"。

fgets 读取(推荐)

char name[20];
fgets(name, sizeof(name), stdin);    // 安全!最多读19个字符+1个\0
printf("你输入了: %s", name);

fgets 的三个参数: 1. 存到哪里 2. 最多读多少个字符(含\0) 3. 从哪里读(stdin = 键盘)


六、常用字符串操作函数(

函数 作用 示例
strlen(s) 获取字符串长度 strlen("Hi") → 2
strcpy(dest, src) 复制字符串 strcpy(copy, str);
strcat(dest, src) 拼接字符串 strcat(greeting, name);
strcmp(a, b) 比较两个字符串 strcmp(s1, s2) == 0 表示相等
strncpy(dest, src, n) 安全复制(限n个字符) 防溢出
strncat(dest, src, n) 安全拼接 防溢出

strcmp 的返回值

char a[] = "apple";
char b[] = "banana";
char c[] = "apple";

strcmp(a, b);   // < 0 (a 比 b 小)
strcmp(b, a);   // > 0 (b 比 a 大)
strcmp(a, c);   // == 0 (相等!)

⚠️ 千万不要用 == 比较字符串!

if (str1 == str2)    // ❌ 这是在比较地址,不是内容!
if (strcmp(str1, str2) == 0)    // ✅ 这才是比较内容


七、完整示例:姓名问候程序

#include <stdio.h>
#include <string.h>

int main() {
    char name[50];
    char greeting[100] = "你好, ";   // 先存好前半句

    printf("请输入你的名字: ");
    fgets(name, sizeof(name), stdin);

    // fgets 会把换行符也读进去,把它去掉
    name[strcspn(name, "\n")] = '\0';   // strcspn 找到\n的位置,改成\0

    // 拼接
    strcat(greeting, name);
    strcat(greeting, "!");

    printf("%s\n", greeting);
    printf("你的名字有 %zu 个字符\n", strlen(name));

    return 0;
}

strcspn(str, "\n") 返回字符串中第一个 \n 的位置。用这个技巧去掉 fgets 带进来的换行符。


八、数组越界——字符串的隐患

char str[5] = "Hello";    // ❌ 危险!
// "Hello" 实际需要6个字符(含\0),但只给了5个空间
// \0 会写到数组外面,破坏其他数据

改了也不够位:

char str[5] = "Hi";       // ✅ 安全,"Hi" + "\0" 只占3个字符

安全做法: 预留足够的空间,用 strncpy 代替 strcpy

char dest[10];
strncpy(dest, "Hello World", sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';   // 确保以\0结尾

九、逐字符操作(字符数组的真正威力)

因为字符串本质就是数组,你可以像操作普通数组那样逐个改字符:

char str[] = "hello";

str[0] = 'H';          // 首字母大写
str[4] = 'Y';          // 改成 hellY
printf("%s\n", str);   // hellY

// 全部转大写
for (int i = 0; i < strlen(str); i++) {
    if (str[i] >= 'a' && str[i] <= 'z') {
        str[i] = str[i] - 32;   // ASCII码:'a'-'A' = 32
    }
}
printf("%s\n", str);   // HELLY

小写字母转大写:减32。大写转小写:加32。 因为 ASCII 表中 'A'=65'a'=97,相差32。


总结

概念 要点
字符串本质 字符数组 + \0 结束符
声明 char str[] = "Hello";
长度 sizeof 含\0,strlen 不含\0
打印 %s 一口气输出,或用 for 逐个输出
输入 fgetsscanf 安全
比较 strcmp不能用 ==
复制/拼接 strcpy / strcat,注意空间够不够
逐字符操作 直接改 str[i],跟普通数组一样