1. 字符串数组与字符串的区别
在定义字符串的时候,比如通过如下方式
//方式1
char str1[] = "Hello world!";
编译器实际上是做了两件事:
- 定义了一个字符串字面量"Hello world!",并将他放在静态存储区,并且在这个字符串末尾添加一个null ('\0')字符,表示这字符串结束。
- 定义一个字符数组,在动态内存中为该数组开辟一块空间,将静态存储区的"Hello world!\0"(包含'\0')复制到字符数组。开辟空间的大小就是字符串长度加上末尾的null字符。这些都是编译器完成的。
关于什么是静态内存区,什么是动态内存区,请参考其他文档。
而定义字符串数组,则没有这样的过程。不存在静态内存中存储字符串常量和添加'\0'的过程。比如下边的程序:
#include <stdio.h>
#define MSG "Hello world!"
int main(void)
{
//方式1
char str1[] = MSG;
char str2[] = {'H','e','l','l','o',' ', 'w', 'o','r','l','d','!'};
printf("str1 is: %s, str1 size is: %zd\n",str1,sizeof(str1));
printf("str2 is: %s, str2 size is: %zd\n",str2,sizeof(str2));
}
他的输出是:
# ./a.out
str1 is: Hello world!, str1 size is: 13
str2 is: Hello world!, str2 size is: 12
需要注意的是,即使是字符数组没有以'\0'结尾,仍然可以在printf中以%s的格式打印。这也体现了c语言的灵活性。
Q:为什么编译器要将字符串字面量保存到静态存储区?
A:假设一个大型程序,有多处需要执行命令打印某一个字符串,比如下边这样,不管在printf中是直接使用字面量字符串参数,还是通过指针的方式,如果编译器为每一处字面量的出现或者字符串指针的定义都开辟一块空间保存字符串"balabala",就会存在存储空间浪费。所以编译器在编译的时候,当遇到第一次出现字符串字面量时,就在静态存储区开辟一块儿空间保存他,而这之后的程序中再次使用同样的字面量字符串,都会将静态存储区的字面量字符串地址传过去。
printf("%s","balabala");
...
printf("%s","balabala");
...
constant char * str1 = "balabala";
printf("%s", str1);
...
constant char * str2 = "balabala";
printf("%s", str2);
2. 字符串的定义主要有以下两种方式:
//方式1
char str1[] = "Hello world!";
//方式2
const char * str2 = "Hello world!";
方式1:
程序编译后,执行程序,首先完成加载程序,加载完成后,会在静态内存中开辟一块空间保存字符串常量"Hello world!"。
当执行char str1[] = "Hello world!"; 语句后,会开辟一块新的内存空间,将静态内存区的"Hello world!"复制到新开辟的空间,并将新开辟空间的第一个字节的地址赋给str1变量。
方式2:
接着上边的执行流程,当const char * str2 = "Hello world!"; 执行后,保存字符串常量"Hello world!"的静态内存空间的首字节地址赋给str2。至于为什么该语句中一般要求使用const,就是因为一般我们不希望修改静态内存空间的字符串的值。比如下边的程序,会打印Segmentation fault 错误。
#include <stdio.h>
#define MSG "Hello world!"
int main(void)
{
//方式2,但不适用const
char * str2 = MSG;
*(str2+1) = 'E';
//方式1
char str1[] = MSG;
printf("%s",str1);
}
而下边的程序,就没有错误。会输出"HEllo world!"
#include <stdio.h>
#define MSG "Hello world!"
int main(void)
{
//方式1
char str1[] = MSG;
//方式2,但不适用const
char * str2 = str1;
//printf("%c",*(str2+1));
*(str2+1) = 'E';
printf("%s",str2);
}