C

C 知识量:16 - 74 - 317

11.1 定义字符串><

字符串字面量- 11.1.1 -

字符串是以空字符(\0)结尾的char类型数组。可以使用以下方法来定义字符串:

  • 字符串字面量(字符串常量)

  • 字符串数组(char类型数组)

  • 指向char的指针

用双引号括起来的内容称为字符串字面量,即字符串常量。双引号中的字符和编译器自动加入末尾的\0字符,都会作为字符串存储在内存中,以下都是字符串:

"hello world."
"hello
 world!"

如果字符串字面量之间没有间隔或用空白字符分隔,C会将其视为串联起来的字符串字面量:

char str[20] = "hell""o wor" "ld.";
char str[20] = "hello world.";

以上两个字符串是完全相同的。如果要在字符串内使用双引号,需要使用反斜杠进行转义:

char str[20] = "He is the \"best\"!";

如果打印以上字符串,那么将会显示为:

He is the "best"!

字符串常量属于静态存储类别,如果在函数中使用字符串常量,该字符串只会被存储一次,在整个程序的生命周期内存在。

用双引号括起来的内容被视为指向该字符串储存位置的指针,类似于把数组名作为指向该数组位置的指针,例如以下示例:

#include <stdio.h>

int main(void) {
    printf("%s,%p,%c\n", "We", "are", *"family");
    system("pause");
    return 0;
}

运行结果为:

We,0040b064,f

以上代码中:

  • %s表示打印We。

  • %p表示打印are的储存地址,即其首字符(a)的地址。在这里,are被视为指针。

  • *"family"表示该字符串所指向地址上存储的值,即family本身,而%c表示打印其第1个字符f。

字符串数组- 11.1.2 -

在定义字符串数组时,必须让编译器知道数组的大小,即需要多少空间。

一种方法是用足够空间的数组储存字符串,例如:

char array[10] = "hello";

数组可以容纳10个元素,而字符串hello加上末尾的空字符(\0)总共需要6个元素空间。如果使用标准的数组赋值方法来初始化字符串数组就会比较麻烦,以下代码与以上代码效果相同:

char array[10] = {'h', 'e', 'l', 'l', 'o', '\0'};

注意末尾的\0是不能省略的,否则就不是字符串数组而是字符数组了。

在初始化字符串数组时,多余的存储空间会被自动初始化为0,这里的0指的是char形式的空字符(即\0),而不是数字字符0。

另一种告知编译器数组大小的方法是让编译器自己确定数组的大小,即创建数组时不在方括号内填写数值,例如:

char array[] = "hello";

这种方法只能用在初始化数组的时候,如果要创建一个稍后再填充的数组,就必须事先指定数组大小了。而且声明数组时,除变长数组外,数组的大小必须是可求值的整数(整型常量或由整型常量组成的表达式)。例如以下数组的创建都是可以的:

char array[5 + 5] = "hello";
char array[2 * sizeof (int)] = "hello";

字符串与指针- 11.1.3 -

与其他数组一样,字符串数组名是该数组首元素的地址,例如:

char array[] = "hello";

对于数组array来说,array就等于&array[0],而*array就等于h。

可以使用指针表示法来创建字符串,例如:

char array[] = "hello";
char *p = "hello";

对于以上代码,array和p都代表字符串"hello",但是它们是有区别的:

  • 对于array来说,编译器会把数组名array识别为该数组首元素(&array[0])的地址的别名,array是地址常量,因此不能更改array,不能进行++array的操作,但可以进行array+1操作。也就是说不能更改array指向的地址,但可以标识数组的下一个元素。

  • 对于p来说,它是一个指向字符串的指针变量,因此可以进行++p操作,也可以进行p+1操作。也就是说可以改变p指向的地址,使它不再指向字符串,同时,也可以用它标识字符串数组的下一个元素。

以下是一个字符串数组与指针的示例:

#include <stdio.h>
#define SS "hello"

int main(void) {
    char array[] = SS;
    char *p = SS;
    printf("address of \"hello\" : %p\n", "hello");
    printf("address of array : %p\n", array);
    printf("address of p : %p\n", p);
    printf("address of SS : %p\n", SS);
    system("pause");
    return 0;
}

运行结果为:

address of "hello" : 0040b064
address of array : 0061ff26
address of p : 0040b064
address of SS : 0040b064

从结果可以看出:

  • 常量SS、指针p和字符串"hello"的地址相同,但数组array与它们的地址不同。

  • 虽然字符串字面量"hello"在程序中多次使用,但是编译器只使用了一个存储位置,而且与常量SS的地址相同。编译器可以将多次使用的相同的字面量储存在同一个地方,但这也取决于编译器的设定。

  • 字符串存储在静态存储区,在程序运行时会为字符串数组再分配动态内存,并将字符串拷贝到数组,所以它们是不同的地址。

数组与指针的区别- 11.1.4 -

下面继续探讨一下字符串数组和指向字符串的指针有什么区别。其中,最主要的区别就是:数组名是常量,而指针名是变量。以下是一个示例:

#include <stdio.h>
#define SS "hello pnotes!"

int main(void) {
    char array[] = SS;
    char *p = SS;
    int i;
    //使用数组表示法
    for (i = 0; i < 5; i++)
        putchar(array[i]);
    putchar('\n');
    for (i = 0; i < 5; i++)
        putchar(p[i]);
    putchar('\n');
    //使用指针表示法
    for (i = 0; i < 5; i++)
        putchar(*(array + i));
    putchar('\n');
    for (i = 0; i < 5; i++)
        putchar(*(p + i));
    putchar('\n');
    //递增操作
    while (*(p) != '\0')
        putchar(*(p++));
    system("pause");
    return 0;
}

运行结果为:

hello
hello
hello
hello
hello pnotes!

由以上代码可以看出,它们都可以使用数组表示法和指针表示法(指针加法),但是只有指针才可以进行递增操作。

我们可以使用数组名改变某个数组元素的值,但是能否使用指针的数组表示法修改数组元素的值是不确定的,因为编译器可以使用内存中的一个副本来表示所有完全相同的字符串字面量,如果通过指针改变了其中的元素,可能会影响所有使用该字符串字面量的指针。因此,建议把指针初始化为字符串字面量时使用const限定符,即不可以修改字符串的值。

注意:数组不存在以上问题,因为数组存储的是字符串字面量的一个副本,它的元素只属于它自己。

字符串的复杂表示- 11.1.5 -

可以使用二维数组来建立更复杂的字符串数组,即char类型数组的数组。对应的指针就是指向字符串的指针数组。下面是一个示例:

#include <stdio.h>
#define X 3
#define Y 30

int main(void) {
    const char *p[X] = {
        "hello pnotes.cn!",
        "I am Jeff.",
        "I like programming."
    };
    char array[X][Y] = {
        "hello pnotes.cn!",
        "I am Sophy.",
        "I like programming too."
    };
    int i;
    printf("%-30s %-30s\n", "Pointer", "Array");
    for (i = 0; i < X; i++)
        printf("%-30s %-30s\n", p[i], array[i]);
    printf("\nsizeof p: %zd, sizeof array: %zd\n", sizeof (p), sizeof (array));
    system("pause");
    return 0;
}

运行结果为:

Pointer                        Array
hello pnotes.cn!               hello pnotes.cn!
I am Jeff.                     I am Sophy.
I like programming.            I like programming too.

sizeof p: 12, sizeof array: 90

从以上代码看,指向字符串的指针数组p和char类型数组的数组array都代表3个字符串,它们的初始化方法也一样。但是,数组p是一个内含3个指针的数组,在系统内占用了12个字节。而数组array是一个内含3个数组的数组,每个数组内含30个char类型的值,共占用了90个字节。因此,虽然p[0]和array[0]都表示一个字符串,但是它们的类型并不相同。

p中的指针指向初始化时所用的字符串字面量的位置,这些字面量储存在静态内存中;array中的数组储存的是初始化时所用的字符串字面量的副本(拷贝),所以每个字符串都被储存了两次。另外,array中每个元素的大小都是30字节,就存在实际用不到的空间,造成浪费。

综上所述,如果要用数组表示一系列待显示的字符串,应当使用指针数组,它的效率更高;而如果要改变字符串或为字符串输入预留空间,应当使用char类型二维数组。