6.3.3 字符串和字符串结束标记
这一小节内容是重点中的重点,请务必好好学。
刚才举了如下这个例子,先回顾一下:如果提供的初值个数和预定的数组长度相同,定义时可以省略数组长度,系统会自动根据初值个数确定数组长度:

跟踪一下不难发现,上面这行代码其实等于定义了一个包含10个元素的数组(char c[10];),能够引用的元素是c[0]~c[9]。
现在,要补充一个对字符数组初始化的方法,也就是用字符串常量来初始化字符数组。看看下面这行代码:

如果设置断点跟踪调试,会赫然发现,上面这种初始化居然是定义了一个形如“char c[11];”的数组,也就是该数组的长度是11,意味着能够引用的元素是c[0]~c[10],并且c[10]里面被系统自动填充进去一个'\0'字符。前面讲过,在计算机内存中保存的是字符的ASCII码,查询一下ASCII码表就可以知道'\0'的ASCII码是0,所以'\0'其实就是0。跟踪截图如图6.10所示。

图6.10 代码“charc[]={"Iam happy"};”跟踪截图
现在正式介绍这个'\0'。'\0'称为字符串结束标志。这个字符串结束标记,用来标记一个字符串的结束。为了测定字符串的实际长度,C语言规定了一个字符串结束标记,用'\0'代表,如果一个字符串的第10个字符为'\0',则该字符串中的有效字符为9个。也就是说,在遇到字符'\0'时,代表字符串结束,由'\0'前面的字符构成整个字符串。
第2章中看到过字符串常量,例如"I am happy"就是字符串常量。实际上,C语言对字符串常量也会自动在其末尾增加一个'\0'作为字符串结束标记,例如"I am happy"一共有10个可见字符(空格也算可见字符),每个可见字符占1字节内存,但实际上该字符串在内存中是占11字节,最后1字节存放的正是'\0'。
看如下代码,这段代码涉及字符指针p,该字符指针在这里用于指向字符串"Iam happy"所在的一段内存,指针的概念后面章节会详细讲解:

现在主要目的是观察一下"Iam happy"这个字符串在内存中究竟是什么样子,可以利用第2章学习过的知识来给代码设置断点,并观察内存,如图6.11所示。

图6.11 字符串"Iam happy"在内存中存储示意图
在图6.11中不难发现,在"Iam happy"字符串的最后一个可见字符'y'的后面有一个00,这其实就是一个数字0,也就是字符串结束标记'\0',是由系统自动加上去的。
有了字符串结束标记'\0'之后,字符数组的长度就很容易确定了,在程序中往往依靠检测'\0'来判断字符串(也就是字符数组中保存的内容)是否结束,当然,在定义字符数组时,必须要估计实际要保存的字符串长度,定义字符数组时指定的长度必须要不小于字符串的长度。如果在一个字符数组中先后存放多个不同长度的字符串,则定义字符数组时的长度应该不小于最长的字符串长度。看如下代码:

上面的代码是错误的,为什么?因为数组c大小是10,无法保存下"Iam happy"这个字符串,刚刚说过,C语言对字符串常量会自动在字符串末尾加一个'\0'作为字符串结束标记(该标记也要占一个位置),而上述初始化数组c的方式显然也会把字符串常量里面的\'0'放入字符数组c中去,所以在数组c中必须要为'\0'字符留有位置,因此,必须至少要把字符数组c的大小设置为11。看如下代码:

所以,如下范例:

所以请想一想下面两行代码的区别:

上面两行代码是不等价的,因为后一种写法系统会自动在字符串末尾增加个'\0'。但是,下面这种写法:

就会等价于:

也就是说,它们的长度相等,内容也相同。
再次强调,对于字符串(双引号包含起来),系统会自动往末尾增加一个'\0',当然,自己手工往末尾增加一个'\0'也是可以的。
这里还有一点要强调,字符数组并不要求它最后一个字符为'\0',甚至整个字符数组可以没有'\0'。例如:

是否加个'\0'取决于需要,但是,只要用字符串来初始化字符数组,系统就会自动在字符串末尾加一个'\0'。例如如下代码:

所以如果要使用字符数组并对其进行初始化,建议和字符串保持一致,也就是人为地增加一个'\0',这样做主要是为了确定字符串的实际长度(因为字符串实际长度是靠找到末尾的'\0'来确定的)。

不过上面这种写法很罕见,一般都不会给字符数组中每个元素分别赋值,而是下面这种写法。笔者建议,重点掌握下面这种写法即可:

为了加深对字符串结束标记的记忆,现在再仔细地看一个范例。如下代码:

现在执行这段代码,结果如图6.12所示,可以看到出现了垃圾信息。
但是,下面这段代码:

执行这段代码,结果如图6.13所示。

图6.12 没有字符串结束标记导致输出时出现了垃圾信息

图6.13 含有字符串结束标记,输出的字符串结果很正确
这里看到了正常的输出,没有任何多余的垃圾信息,这是因为,在图6.12中,向屏幕输出字符串内容,一直遇到'\0'才会停止输出,而因为内存中一直没有遇到'\0',就会一直向屏幕输出内容,所以看到了很多垃圾信息,终于,偶然之间(无意中)遇到了一个'\0',于是停止了输出,这种写法的程序代码肯定是有问题的。但反观图6.13,因为系统自动给字符串末尾增加了一个'\0',所以向屏幕输出字符串内容时,遇到'\0'刚好停止输出,所以结果非常正确。
本节内容即将结束,读者的主要任务是理解好字符串结束标记,一定不能忘记,面试的时候,这很可能是考点,若因回答不好而丢掉工作机会将非常可惜。