知识铺垫

1
2
3
4
5
// 字符数组
char arr[] = { 'm', 'o', 'r', 'n', 'i', 'n', 'g' };

// 字符数组 + '\0' <=> 字符串
char str[] = { 'm', 'o', 'r', 'n', 'i', 'n', 'g', '\0' };

所以:可以用访问数组的方式访问字符串~

注意

'\0',而不是 ‘0’

另外,0也能将字符数组变为字符串

但**推荐用 ‘\0’**,因为这能从字面上强调 「字符串的结束标志占 1 Byte」!

1
2
3
char str1[] = { 'a', 's', '\0' };
char str2[] = { 'a', 's', 0 };
printf("str1 = %s, str2 = %s\n", str1, str2);

下例代码编译运行输出 3,但其实字符串 str 的长度为 2,因为:**’\0’ 仅标志着字符串的结束,而并非字符串的一部分**

1
2
char str[] = { 'h', 'i', '\0' };
printf("%d\n", sizeof(str) / sizeof(str[0]));

字符串常量

≥ 0 个字符,被双引号引起来,就是字符串常量(eg:””、”hello”、…)

eg:"asd" => 字符串常量 => 会被转换成{'a', 's', 'd', '\0'} => 系统只会保留一份 => 放在一片只读的存储空间中 => 共享

1
2
3
4
char * str1 = "asd";
char * str2 = "asd";
printf("position of str1: %p\n", str1);
printf("position of str2: %p\n", str2);

试想:

如果字符串常量所存放的位置不是只读的

那当有很多指针指向它,其中任何一个指针对其的修改,都会导致其它指针对这种修改的不知情

从而可能导致不可预知的严重后果

举例如下

1
char * s = "hello";

解释:

s 是一个指针,初始化为指向一个字符串常量

所以这行代码实际上是 const char * s = "hello";

虽然由于历史原因,在这里,编译器不接受带 const 的写法

但任何试图对 s 所指向的内存空间做写入的操作都会导致严重后果 !

相邻的字符串常量默认会被连接起来 !

1
printf("%s\n", "hel"  "lo world");

字符串变量

可用字符串常量初始化字符串变量

1
char str[] = "hello";

解释:

用只读的 {'h', 'e', 'l', 'l', 'o', '\0'} 初始化一个可读写的普通字符数组 str

‘\0’ 让字符数组变成了字符串

当然写成下面这样也完全 ok

1
char str[10] = "hello";

空字符串 “” 其实是 { ‘\0’ }

1
2
char str[] = "";
printf("length of str = %d\n", sizeof(str) / sizeof(str[0]));

字符串数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 类比字符数组
char arr[] = { 'a', 's', 'd' };

// 字符串数组
char* arr[] = {
"hello",
"world",
};

// 也可用二维字符数组,但第二维一定要有确切的大小(strArr[0] <=> char str[10])
char strArr[][10] = {
"hello",
"world"
};

image-20211119204540151

习题
1
2
3
4
5
6
7
8
char* arr[] = {
"Jan",
"Feb",
"Mar",
};

printf("请输入月份:"); int month; scanf("%d", &month);
printf("%s\n", arr[month]);

I/O

scanf() 其实“破绽百出”
  1. 「空格、tab、回车」 都是 scanf 输入结束的标志,这就导致 scanf 其实只能读入“单词”
1
2
3
4
5
6
7
8
char str[20];
scanf("%s", str);

// 试试输入「hello world」

printf("%s\n", str);

// 其实输出的只有「hello」

注意:「空格、tab、回车」 只是输入结束的标志,不会被输到 str 中(在 Linux 上验证如下)

image-20220508163507995

关于上图中的 $ 符号,请看 => DOS 与 Linux 的断行字元

  1. 单论读取“单词”而言,由于 scanf 并不知道需要读入多少字符,当输入的字符串过长,就可能导致缓冲区溢出,所以 scanf 并不安全
1
2
3
// 安全的做法:在 %s 中间加一个数字,以此告诉 scanf 最多能读入几个字符
char str[10];
scanf("%9s", str);

那如果我们就是要读入一个英文句子,而不仅仅是一个单词

在 C 语言的官方文档中可查阅到曾有人设计了gets()函数,但该函数已被废除,因为文档描述了这样一段话:

image-20220508171150196

另外,如果你是一名 c++ 开发者,可以使用getline()函数,但我偏爱更简单纯粹的 C :)

(完)