# 引入 ## 实验 我想通过以下方式来打印字符数组`a`和`b` ```c #include <stdio.h> int main(int argc, char const *argv[]) { char a[3] = {'d','e','f'}; char b[3] = {"abc"}; printf("%s\n", b); printf("%s\n", a); return 0; } ``` ## 运行 ```sh zhoumengkang@OSX10111-3c15c2ba060a:~/Downloads$ gcc test4.c -o test4 zhoumengkang@OSX10111-3c15c2ba060a:~/Downloads$ ./test4 abcdef��6R� def��6R� zhoumengkang@OSX10111-3c15c2ba060a:~/Downloads$ ./test4 abcdefȪ R� defȪ R� zhoumengkang@OSX10111-3c15c2ba060a:~/Downloads$ ./test4 abcdef��R� def��R� zhoumengkang@OSX10111-3c15c2ba060a:~/Downloads$ ./test4 abcdef�*�^� def�*�^� ``` ## 规律 打印 b 的时候总是会把 a 也附带在后面; a 本身也会附带一些“诡异”的乱码。 ## 原因 因为我们在定义`a`和`b`的时候都没有在末尾加上`\0`,这样在打印字符串的时候,会一直从字符串首字符的指针地址向后,一直输出,直到遇到`\0`。 而 a 和 b 都是在栈上分配内存,所以是向下增长,所以 b 的地址比 a 的小; 当打印 b 的时候,末尾没有找到`\0`,而紧接着后面就是 a 了,所以继续打印。一直打印完 a 还是没有发现`\0`,继续向后输出,直到发现`\0`。 这样就解释了上面发现的规律。 ## 改进 ```c char a[4] = {'d','e','f','\0'}; char b[4] = {"abc"}; // 等同于 char b[4] = "abc"; ``` 定义的时候多申请一个字节的内存(也就是数组数加一),然后末尾加上`\0`;然后再打印就不会出现诡异的乱码了。 双引号初始化的时候,会申请的内存空间够用的情况下,末尾加上`\0`,详情见之前数组的笔记:https://mengkang.net/1008.html#blog-title-4 # 字符串 在 C 语言中,字符串实际上是使用 null 字符 `\0` 终止的一维字符数组。 **初始化字符串时,请务必使用`双引号`,java 里面也如此。** ```c #include <stdio.h> int main(int argc, char const *argv[]) { char *c = "abcdef"; printf("%s\n", c); return 0; } ``` 以指针的方式来定义的时候,则会默认在字符串的末尾加上一个`\0`,这样打印的时候就不会越界了。 ## 字符串初始化的原理 按照我们前面对指针的理解:这里的`c`是一个`char`类型的指针,应该是赋值一个`char`字符的地址才对呀。这么用才是前面说的基本数据类型和指针对应的思路 ```c char a = 'a'; char *c = &a; ``` 的确,上面的方式没问题。 **那么为什么字符串也可以赋值给字符指针变量呢?** ```c char *c = "abcdef"; ``` ### 双引号的秘密 双引号做了3件事: - 在常量区申请内存,存放字符串 - 在字符串尾加上了'/0' - 返回字符串的首地址 所以这里的赋值操作是将`abcdef\0`的首地址赋值给了`c`。 ### 字符串的生命周期 假如在一个函数里声明了一个常量字符串,那么在函数运行结束后,它的内存会回收吗? ```c #include <stdio.h> char * a(); int main(int argc, char const *argv[]) { a(); a(); return 0; } char * a() { char *b = "string"; printf("%p\n", b); return b; } ``` 运行结果 ``` $ ./test 0x1069bdfa6 0x1069bdfa6 ``` 可以看出,字符串的内存在函数运行结束后是没有被回收的。