字符串字面量和字符串变量

字符串字面量(又名字符串常量)是不能被修改的,但可以被读取。字符串变量是可以被修改的,还可以被访问。

1
2
3
4
5
6
7
8
9
10
11
12
int main(void) {

// 字符串变量
char str1[] = "hello";
str1[0] = 'A';

// 字符串字面量
char* str2 = "hello";
str2[0] = 'A'; // 报错

return 0;
}

字符串字面量

字符串字面量是用一对双引号括起来的字符序列。通常存储在只读数据区(只读内存区域),它的内容是不可修改的。尝试修改字符串字面量会导致未定义行为。编译器自动计算并在编译时确定其长度,通常包括末尾的 \0 终止符。

从本质而言,C语言把字符串字面量作为字符数组来处理。当C语言编译器在程序中遇到长度为的字符串字面量时,它会为字符串字面量分配长度为的内存空间。这块内存空间将用来存储字符串字面量中的字符,以及一个用来标志字符串末尾的额外字符(空字符)。空字符是一个所有位都为0的字节,因此用转义序列\0来表示

字符串字面值.png

字符串变量

存储在栈(或堆)内存中,可以被修改。字符串变量通常是一个字符数组或指向字符数组的指针。可以存储不同长度的字符串,长度由你在定义数组时指定,或者在运行时动态确定。

一些编程语言为声明字符串变量提供了专门的 string 类型。C 语言采取了不同的方式:只要保证字符串是以空字符结尾的,任何一维的字符数组都可以用来存储字符串。

1
char str1[] = "hello";

看起来好像是 字符串字面量,但其实不然。C编译器会把它看成是数组初始化式的缩写形式。实际上,我们可以写成:

1
char str[] = {'h','e','l','l','o','\0'};

如果指定数组大小没有被填充满,会自动补0。如果指定数组大小务必要能有一个位置给其添加\0,否则这不是一个合法的字符变量。

字符数组和字符指针

1
2
char date[] = "June 14";
char *date = "June 14";

前者声明 date 是一个数组,后者声明 date 是一个指针。正因为有了数组和指针之间的紧密关系,才使上面这两个声明中的 date 都可以用作字符串。尤其是,任何期望传递字符数组或字符指针的函数都能够接收这两种声明的 date 作为参数。

然而,需要注意,不能错误地认为上面这两种date可以互换。两者之间有很大的差异:

  • 在声明为数组时,就像任意数组元素一样,可以修改存储在date中的字符。在声明为指针时,date指向字符串字面量,前面我们已经看到字符串字面量是不可以修改的。
  • 在声明为数组时,date是数组名。在声明为指针时,date是变量,这个变量可以在程序执行期间指向其他字符串。

如果希望可以修改字符串,那么就要建立字符数组来存储字符串,声明指针变量就不够的。因为指针变量没有指向一个有效的地址,对其修改会产生未定义的行为。下面的声明使编译器为指针变量分配了足够的内存空间

1
char* p

可惜的是,它不能为字符串分配空间。(怎么会这样呢?因为我们没有指明字符串的长度。)在使用p作为字符串之前,必须把 p 指向字符数组。可以把p指向已经存在的字符串变量:

1
2
char str[STR_LEN+1], *p;
p = str;

现在 p 指向了 str 的第一个字符,所以可以把 p 作为字符串使用了。

1
2
3
char *p;
p[0] = 'a'; // 报错
p[1] = 'b'; // 报错

因为 p 没有被初始化,所以我们不知道它指向哪里。用指针 p 把字符 a、b 写入内存会导致未定义的行为。