【C语言】字符串 | 回顾C语言005
字符串,就是一长串的字符类型,每一个位上都是一个单独的char字符,连起来组合成了字符串。
你在各个客户端/网页里面看到的文字,在编程语言中都可以认为是一个字符串。
在C语言中,字符串对应的是const char*
和char*
类型;其本质上是一个char字符数组。
1.常量字符串
常量字符串的类型是const char*
,我们直接使用的"字符串"
,就是常量字符串。
1 | const char* str = "hello world"; // 常量字符串 |
常量字符串不能被修改,但可以像数组一样通过下标和指针的方式访问字符串的单个字符
1 | const char* str = "hello world"; // 常量字符串 |
而没有const修饰的char*
类型,是不能直接接受常量字符串赋值的
1 | char* str = "hello world"; // 无法接受常量字符串,这行代码有误 |
虽然这行代码在VS2019中没有报错,在Linux下的g++也仅仅是报了warning警告,但其本质是有问题的!
下面的文字就是Linux下g++编译时的警告,翻译过来是禁止将常量字符串转换为char *
类型。
1 | $ g++ test.cpp -o test |
2.普通字符串
前面提到,字符串就是一个字符的数组。但我们想定义一个字符串数组,也不能用char*
,而需要用另外一种方式。
下面这种在变量名后带[]
的写法,就是定义数组的语法,在前文已经介绍过了。
1 | char str[] = "hello world"; // 普通字符串的基本定义方式 |
上方是声明时赋值的写法,编译器在识别到这一行代码后,会自动帮我们创建字符串对应大小的空间,并让str指向这片空间的起始地址。
需要注意的是,只有初始化的时候能这么写。后续如果想修改这个字符串,需要使用strcpy函数,而不能用=直接赋值。strcpy函数会在后文介绍。
1 | char str[] = "hello world"; |
这里的str变量本质上是
char*
类型,是一个字符指针。所有数组变量都是对应类型的指针。
如果想在声明时不赋初值,可以用下面的办法来定义一个字符串。
1 | char str1[15]; // 定义一个长度为15的字符串数组 |
运行结果如下,成功打印出hello字符串。
2.1 字符串结束标志\0
请注意,所有字符串数组最后都会带上一个隐藏的'\0'
作为字符串的结束标志,该结束标志也需要占用一个字节的空间。
如果缺少了这个结束标志,那么程序就无法确定字符串什么时候结束,字符串就不完整了。
1 | char str4[] = "hello world"; // 该字符串末尾会自动带上一个\0 |
可以看到,原本应该打印完毕 hello worlda
就停止的程序,却继续往后打印了非常“经典”的乱码字符烫烫
🤣。这也是初学者经常遇到的错误之一。这就是缺少\0
结束标志的后果。
可能又有初学者在这里会犯第二个错误:越界访问。
在数组章节中,我们已经介绍过越界访问的概念。访问了不属于用户的数组空间就是越界访问。
1 | char str4[] = "hello world"; // 该字符串末尾会自动带上一个\0 |
但实际上,我们的str4字符串,在定义的时候赋值了"hello world"
,这个字符串的长度是12。分别是可见的hello world
一共11个字符(空格也算入字符中),和末尾隐藏的\0
字符。
编译器会自动为我们开长度12的char数组,即等价于char str4[12] = "hello world";
数组[]
里面的数字是通过下标访问的,下标是从0开始的,数组中的第一位的下标是0。所以,str4[11]
访问的实际上是第十二个位置的\0
,而str4[12]
访问的数组的第十三个位置,该位置的空间实际上并不属于我们,此时的访问就是越界访问了!
在windows下,这份代码会因为错误提前退出;在linux下,运行会报段错误;
下图中就是windows下异常退出的情况,可以看到进程退出代码是负数,而且最后一行的printf
也并没有打印出结果,这是因为代码在下图第24行中的str4[12] = '\0';
就已经因为越界访问提前异常退出程序了。
正常程序中,如果运行正常,程序终止后的退出代码应该是0;非零值都代表运行出错!
2.2 strcpy函数
除了一个一个通过下标访问的方式来赋值外,还可以用库函数strcpy,点我查看文档
strcpy函数的使用比较简单,只有两个参数,分别是源字符串和目标字符串。
1 | char * strcpy ( char * destination, const char * source ); |
该函数有几点说明:
- 拷贝的时候,会把源字符串末尾的
\0
也拷贝过去; - 目标字符串所能容纳的长度必须大于源字符串(长度要包括
\0
),否则会有越界访问; - 返回值是目标字符串的指针,即返回
destination
;
在当前场景中,我们可以用下面的方式使用strcpy,只要保证我们自己设定的字符数组空间大于源字符串即可。
1 | char str5[15]; // 定义一个长度为15的字符串数组 |
2.3 为什么会打印乱码字符?
前文中的这份代码,我们将原有的'\0'
截止字符替换掉后,因为没有结束标志,printf函数就会一直往后打印,于是就出现了一些常见越界访问和未定义空间才会出现的生僻汉字。
你可能会疑惑,理论上后续的空间是没有人使用的空间,那为什么会打印出这些不知道哪里冒出来的字符呢?那最后又为什么停止了?
其实这个问题的答案很简单:在一个程序中,所有未使用的内存空间,内部都存放的是随机值。这些随机值组合起来,和GBK/UTF-8这类文字编码的部分编码对应,就会打印出这些生僻字,也就是网络上俗称的“乱码字符”。
所谓“文字编码”,就是将语言里面的文字转为机器可识别的二进制的一个转换表。ASCII码就是一个文字编码表,但其内部只包含了英语字母和部分符号的转换,于是就会有各类的文字编码,将各国语言以规定的格式转换成机器可以识别的二进制。比较常用的文字编码是UTF-8;
至于为什么会停止?回到ASCII码表上,查表可知,'\0'
字符对应的十进制是0。
也就是说,只要我们原本定义的字符串,后续的内存空间中出现了一个随机值为0的位置,那么printf打印到这里就会把他当作截止标志,停止打印了。
结语
对字符串的介绍到这里就over了。总结一下,字符串中最需要关注的两个问题:
- 字符串(字符数组)的空间长度,注意不要越界访问;
- 末尾的这个
'\0'
一定不能漏,否则没有结束标志了。
在OJ刷题中,经常会有拼接字符串相关的题目,如果字符串结束标志控制不到位,就容易把自己弄进一个很难找到的混乱BUG中。说多了都是泪,后续等你开始OJ刷题了,就知道慕雪所言何物了。