字符串与字符指针——没有 string 类型的世界

C 语言没有 string 类型

这是很多初学者的误区。C 语言没有内置的 string 类型。字符串在 C 里就是char数组,末尾加一个\0(空字符)标记结束

char str[] = "hello"; // 内存布局:'h' 'e' 'l' 'l' 'o' '\0' // 共 6 字节,最后的 \0 是字符串结束标记

字符串的两种创建方式

方式 1:字符数组(可修改)

char str[] = "hello"; str[0] = 'H'; // OK,数组在栈上,可以修改 printf("%s\n", str); // "Hello"

char str[] = "hello"会把字符串复制到栈上的数组里。

方式 2:字符指针(不可修改)

char *str = "hello"; str[0] = 'H'; // 未定义行为!可能崩溃

char *str = "hello"str指向字符串常量所在的只读内存区。修改它就是写只读内存——未定义行为,通常段错误。

实际项目中:

  • 需要修改内容 →char str[]

  • 只读访问 →char *str(更省内存,多处共用同一份)

\0的重要性

char s1[] = {'h', 'e', 'l', 'l', 'o'}; // 没有 \0 char s2[] = {'h', 'e', 'l', 'l', 'o', '\0'}; // 有 \0 char s3[] = "hello"; // 自动加 \0

s1不是合法的字符串——没有\0结束标记。

所有字符串函数都依赖\0判断结束:

printf("%s", s1); // 一直读到碰到 \0 为止——越界,输出垃圾值 strlen(s1) // 一直数到碰到 \0 为止——越界,返回随机值 strcpy(dest, s1) // 一直复制到碰到 \0 为止——越界,可能覆盖别的数据

sizeof vs strlen

char s[] = "hello"; printf("%zu\n", sizeof(s)); // 6(整个数组,含 \0) printf("%zu\n", strlen(s)); // 5(实际字符数,不含 \0)
  • sizeof是编译时确定的,算整个数组大小

  • strlen是运行时计算的,遍历到\0为止

声明字符数组时要多留 1 字节给\0

char name[5]; // 能存 4 个字符 + 1 个 \0 char name[100]; // 能存 99 个字符 + 1 个 \0

字符串比较:不能用 ==

char s1[] = "hello"; char s2[] = "hello"; ​ if (s1 == s2) // 错!比较的是两个数组的地址,永远不等 if (strcmp(s1, s2) == 0) // 对!比较内容

s1s2是两个不同的数组,地址不同,==永远返回假。

strcmp 返回值

strcmp(s1, s2) == 0 // 相等 strcmp(s1, s2) < 0 // s1 在 s2 前面(字典序) strcmp(s1, s2) > 0 // s1 在 s2 后面

常用字符串函数

#include <string.h> ​ char s[] = "hello"; ​ strlen(s) // 5,字符串长度 strcpy(dest, src) // 复制 src 到 dest(不安全) strncpy(dest, src, n) // 最多复制 n 字节(安全) strcat(dest, src) // 拼接 src 到 dest 末尾 strcmp(s1, s2) // 比较内容

strcpy 的安全隐患

char small[5]; char big[] = "hello world"; strcpy(small, big); // 缓冲区溢出!small 只有 5 字节

strcpy不检查目标数组大小,超出的部分会覆盖相邻内存。实际项目中用strncpy

char dest[10]; strncpy(dest, src, sizeof(dest) - 1); // 最多复制 9 字节 dest[sizeof(dest) - 1] = '\0'; // 手动确保 \0 结尾

字符串遍历

char s[] = "hello"; ​ // 方式1:下标 for (int i = 0; s[i] != '\0'; i++) { printf("%c", s[i]); } ​ // 方式2:指针 for (char *p = s; *p != '\0'; p++) { printf("%c", *p); }

字符串数组

指针数组(灵活)

char *fruits[] = {"apple", "banana", "cherry"}; printf("%s\n", fruits[0]); // "apple" printf("%c\n", fruits[1][0]); // 'b'

字符串长度可以不同,每个char *指向各自的字符串常量。

二维数组(安全)

char fruits[3][10] = {"apple", "banana", "cherry"};

内存连续,不指向只读区,但每行长度固定,浪费空间。

main 的命令行参数

int main(int argc, char *argv[]) { for (int i = 0; i < argc; i++) { printf("%s\n", argv[i]); } return 0; }

argv就是指针数组,argv[0]是程序名,后续是参数。

常见误区速查

误区正确理解
C 有 string 类型没有,就是 char 数组加\0
char *s = "hello"; s[0] = 'H'未定义行为,只读区不能改
==比较字符串比较的是地址,用strcmp
strlen包含\0不包含
strcpy安全不安全,可能溢出,用strncpy
char s[5] = "hello"能存 5 字节但需要 6 字节(含\0),溢出

总结

  • C 语言没有 string 类型,字符串是\0结尾的 char 数组

  • char str[]可修改(栈上副本),char *str不可修改(只读区)

  • sizeof算整个数组含\0strlen不含\0

  • 字符串比较用strcmp==比较的是地址

  • strcpy不安全,实际项目用strncpy

  • 声明字符数组时多留 1 字节给\0