# 引入
## 實驗
我想通過以下方式來打印字符數組`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
```
可以看出,字符串的內存在函數運行結束后是沒有被回收的。