# 引入
## 实验
我想通过以下方式来打印字符数组`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
```
可以看出,字符串的内存在函数运行结束后是没有被回收的。