字符串
C 没有专门的「字符串类型」。一串文字放在一个 char 数组里,用 %s 显示:
1#include <stdio.h>2 3int main(void) {4 char city[] = "上海";5 char greeting[20] = "早上好";6 printf("%s,%s!\n", greeting, city);7 return 0;8}
早上好,上海!
char city[] = "上海"; 让编译器照字符串的长度定数组的大小;greeting[20] 留了 20 个字节,只用了前面一部分。printf 的 %s 从数组的开头一个一个地显示,直到碰到字符串的结尾。结尾在哪里?下面就讲。
字符串就是 char 数组
"cat" 看起来是 3 个字符,其实占 4 个字节:最后还有一个 '\0',值是 0 的字符,表示「到这里结束」。下面两种写法得到一模一样的数组:
1#include <stdio.h>2 3int main(void) {4 char a[] = {'c', 'a', 't', '\0'};5 char b[] = "cat";6 printf("%s %s\n", a, b);7 printf("%zu %zu\n", sizeof a, sizeof b);8 printf("%c\n", b[1]);9 return 0;10}
cat cat 4 4 a
单引号 'c' 是一个字符,双引号 "cat" 是一个字符串,两个不一样。点单步,看 b 画成 4 个格子,最后一格是 '\0'。
这段程序显示什么?
1#include <stdio.h>2 3int main(void) {4 char word[] = "hello";5 printf("%zu\n", sizeof word);6 return 0;7}
hello 是 5 个字母,再加上结尾的 '\0',一共 6 个字节。数组的大小总要比文字多留 1 个。
结尾的 '\0'
'\0' 是字符串唯一的「长度记号」。想知道一个字符串有几个字符,就从头数,数到 '\0' 为止:
1#include <stdio.h>2 3int main(void) {4 char name[] = "Alexandra";5 int len = 0;6 while (name[len] != '\0') {7 len++;8 }9 printf("%s 有 %d 个字母\n", name, len);10 return 0;11}
Alexandra 有 9 个字母
%s 也是这样做的:碰到 '\0' 就停。所以在中间放一个 '\0',后面的字就显示不出来了,虽然它们还在数组里:
1#include <stdio.h>2 3int main(void) {4 char word[] = "sunflower";5 word[3] = '\0';6 printf("%s\n", word);7 printf("%c\n", word[4]);8 return 0;9}
sun l
字符就是数
在电脑里,每个字符都是一个小整数,叫它的编码。英文字母、数字和常见符号用的是 ASCII 编码:'A' 是 65,'a' 是 97,'0' 是 48。同一个 char,用 %c 显示是字符,用 %d 显示是数:
1#include <stdio.h>2 3int main(void) {4 char c = 'A';5 printf("%c %d\n", c, c);6 printf("%c\n", c + 2);7 printf("%d\n", 'a' - 'A');8 printf("%d\n", '7' - '0');9 return 0;10}
A 65 C 32 7
| 字符 | 编码 |
|---|---|
'0' 到 '9' | 48 到 57 |
'A' 到 'Z' | 65 到 90 |
'a' 到 'z' | 97 到 122 |
'\0' | 0 |
字母是连着排的,所以 'A' + 2 是 'C';小写比大写大 32;'7' - '0' 把字符 '7' 变成数 7。
汉字不是一个 char。网页和大多数系统用 UTF-8 编码,一个汉字占 3 个字节,所以 "上海" 占 7 个字节(还有 '\0')。整个显示没问题,一个一个字节地看就乱了,所以这几课里一个一个数字符的例子都用英文。
一个一个字符地走
用循环从下标 0 走到 '\0',就能检查每个字符。比如数空格,空格数加一就是单词数:
1#include <stdio.h>2 3int main(void) {4 char text[] = "the quick brown fox";5 int spaces = 0;6 for (int i = 0; text[i] != '\0'; i++) {7 if (text[i] == ' ') {8 spaces++;9 }10 }11 printf("%d 个单词\n", spaces + 1);12 return 0;13}
4 个单词
数组里的字符也能改。把每个空格换成下划线:
1#include <stdio.h>2 3int main(void) {4 char file[] = "my summer photos.jpg";5 for (int i = 0; file[i] != '\0'; i++) {6 if (file[i] == ' ') {7 file[i] = '_';8 }9 }10 printf("%s\n", file);11 return 0;12}
my_summer_photos.jpg
点单步,看数组里的格子一个一个变。
数组放不下
字符串放进数组,数组至少要比文字多 1 个字节,留给 '\0'。下面把 "hello" 一个一个复制进只有 4 个字节的 word,前 4 个放得下,第 5 个就越界了:
1#include <stdio.h>2 3int main(void) {4 char text[] = "hello";5 char word[4];6 for (int i = 0; text[i] != '\0'; i++) {7 word[i] = text[i];8 }9 printf("复制完了\n");10 return 0;11}
index 4 out of bounds for type 'char [4]'发生了什么: 停在第 7 行:你访问了 word[4],可 word 只有 4 格(word[0]~word[3])。
真正的 C 不会拦你:多出来的字节会写到别的变量那里,程序可能接着跑,过一会儿才出怪事。我们的引擎在越界的那一行就停下来。点「看看是怎么出错的」,或者点单步,看 i 走到 4 时发生了什么。
小技巧: 写 char a[] = "..." 不写大小,让编译器去数,就不会忘了 '\0'。
练一练
写好代码,点检查。提示随便看,实在想不出来再看答案。
数字母
✓数一数 word 里有几个 a,显示 a 出现了 3 次。
a 出现了 3 次
1#include <stdio.h>2 3int main(void) {4 char word[] = "banana";5 6 return 0;7}
倒过来
✓把 word 倒过来显示,显示 olleh,然后换行。
olleh
1#include <stdio.h>2 3int main(void) {4 char word[] = "hello";5 6 return 0;7}
变成大写
✓把 text 里的小写字母都变成大写,别的字符不动,然后显示 HELLO, WORLD 2。
HELLO, WORLD 2
1#include <stdio.h>2 3int main(void) {4 char text[] = "Hello, World 2";5 6 return 0;7}
这一课学会了
- 字符串就是
char数组,结尾有一个'\0':"cat"占 4 个字节。 'c'是一个字符,"cat"是一个字符串;%c显示字符,%s显示字符串。%s和循环都靠'\0'知道在哪里结束:for (int i = 0; s[i] != '\0'; i++)。- 字符就是数:
'A'是 65,'a' - 'A'是 32,'7' - '0'是 7。一个汉字在 UTF-8 里占 3 个字节。 - 数组要比文字多留 1 个字节给
'\0';放不下就是越界。