字符串 - string

罗大富 BigRich大约 11 分钟C/C++

这节课,我们终于来到了字符串的学习。在课程开始的时候,我们已经简单的介绍过字符串(string)了,字符串其实就是用双引号 "" 引起来的一串字符,像名字、爱好、地址等文本信息都需要用到字符串。但是 C 语言中没有字符串这种变量类型,在程序中,如果要存储字符串,可以通过以下 2 种方式:

  1. 通过 char 数组保存字符串,例如:char str[] = "abc_123"
  2. 通过 char* 指针保存字符串的首地址,例如:char* str = "abc_123"

通过 char 数组保存字符串

字符串由多个字符组成,可用 1 个 char 型的一维数组来保存 1 个字符串,注意 1 个一维数组只能保存 1 个字符串。保存字符串只是 char 数组的一种应用,但 char 型数组不一定都要保存字符串。

字符 \0 是字符串的结束标志,因此,如果用 char 数组保存字符串,数组中必须有 \0 元素,否则它只是一个数组而已,数组可以做它用,但是不能保存字符串。例如:

char array_1[] = {'a', 'b', '_', '1', '2'};
char array_2[] = {'a', 'b', '_', '1', '2', '\0'};
char array_3[6] = {'a', 'b', '_', '1', '2'};

printf("%s\n", array_1);
printf("%s\n", array_2);

运行上述代码,可以发现 array_1 无法正常显示,而 array_2 与 array_3 可以正常显示。

数组 array_1 有 5 个元素,由于其中没有 \0 元素,printf 函数会从数组的首地址开始逐个输出其中的字符,直到输出完 2 还没有遇到 \0 元素,所以会继续输出下去,而内存中元素 2 后面的内容不是我们程序中的内容,因此,打印的内容与长度都是随机的,直到碰巧在后面的内存中遇到了 \0 而终止输出,这就是字符串中没有 \0 的后果;

array_2 最后一个元素是 \0,因此,可以保存字符串;

尽管 array_3 没有 \0 元素,但是,array_3 的数组长度为 6,而初始值长度仅为 5,最后一个元素 array_3[5] 由于没有初始值,于是被自动补了 \0

注意

array_3 最后一位元素实际上被自动补充的是数字 0,但是 \0 的 ASCII 码是 0。

以上定义 char 型数组赋初值是一个一个地将字符串中的每个字符写出,C 语言还允许通过如下方式给 char 型数组赋初值,这是当 char 型数组保存字符串时的特殊用法:

char array_4[] = {"ab_12"};
char array_5[] = "ab_12";
char array_6[6] = "ab_12";
char array_7[100] = "ab_12";

printf("array_4: %s\n", array_4);
printf("array_5: %s\n", array_5);
printf("array_6: %s\n", array_6);

需要注意的是,双引号中字符的长度虽然是 5,但是末尾还有一个 \0,因此,数组长度应该为 6,array_6 与 array_7 都是因为初始值不足而自动补了 0,从而存储了字符串,因此,array_6 与 array_7 本质上没有区别,除了存储数组长度不同。

通过 char* 指针保存字符串的首地址

我们也可以将字符串的首地址赋值给一个 char* 类型的指针变量。

根据 char 数组保存字符串与数组指针的概念,我们可以让数组指针指向一个 char 数组,代码如下:

char array_5[] = "abc_123";         // 定义字符串数组
char* ptr = array_5;                // ptr 指向 array_5

printf("%s", ptr);

请牢记规定:双引号 "" 引起来的字符串常量可被看做是表达式,表达式的值就是字符串常量的首地址。我们可以来打印验证一下:

printf("%p", "abc_123");

运行该行代码,我们可以得到一个地址的值,因此,我们可以直接将该地址值传递给指针,而不需要先创建一个新的数组,再让指针指向该数组,代码如下:

char* ptr = "abc_123";

printf("%s", ptr);

两种存储方式的异同点

二者在实际存储时,程序都会把字符串转换成字符数组进行保存,并在末尾加上 \0

区别是通过 char 数组定义的字符串是可修改的;而通过 char* 指针定义的字符串不可修改,代码如下:

#include <stdio.h>

int main() {
    // char 数组定义的字符串可修改
    char str_array[] = "ab_12";
    printf("修改前 str_array:%s\n", str_array);
    str_array[1] = '0';
    *(str_array + 2) = '+';
    printf("修改后 str_array:%s\n", str_array);

    // char* 指针定义的字符串不可修改
    char* str_ptr = "ab_12";
    printf("修改前 str_ptr:%s\n", str_ptr);
    *(str_ptr + 2) = '+';
    printf("修改后 str_ptr:%s\n", str_ptr);

    return 0;
}

之所以指针定义的字符串不可修改,是因为该字符串被放在了只读常量区。只读常量区中的内容是不可修改的,并且其中定义的字符串是可以复用的。比如,我们使用指针创建两个相同的字符串,然后打印他们的地址,代码如下:

#include <stdio.h>


int main() {
    char* str_1 = "罗大富";
    char* str_2 = "罗大富";
    printf("str_1 的地址为:%p\n", str_1);
    printf("str_2 的地址为:%p\n", str_2);

    return 0;
}

运行这段代码后,我们可以发现 str_1 的地址与 str_2 的地址是一致的,这就表示 str_2 复用了 str_1 的地址。也就是说,通过指针创建字符串时,会检查只读常量区里面有没有相同的字符串,如果有则会进行复用,如果没有才会创建新的地址。

最后就是关于字符串输入需要注意的事项,因为通过指针创建的字符串无法修改,所以,我们在输入字符串时要先创建一个用来存放字符串的数组,代码如下:

#include <stdio.h>

int main() {
    char name[50];
    char province[50];

    printf("请输入你的名字:");
    scanf("%s", name);                  // 数组名就是首地址,因此,不需要使用取地址符
    printf("你是哪里人:");
    scanf("%s", &province[0]);          // 也可以对数组中

    printf("我叫%s,来自%s", name, province);
    return 0;
}

字符串数组

字符串本身就需要一个 char 型的一维数组保存,那么多个字符串就需要用到多个 char 类型的一维数组,而字符串数组则可以使用一个 char 类型的二维数组进行保存,char 型的二维数组每行保存一个字符串,例如:定义一个数组存储 4 个学生的名字,代码如下:

char student_array[4][20] = {"Alice", "Bob", "Cathy", "David"};

虽然,本质上字符串数组是一个 char 类型的二维数组,但是我们在遍历时,依然可以把他看作是一维字符串数组进行使用,代码如下:

#include <stdio.h>


int main() {
    char student_array[4][20] = {"Alice", "Bob", "Cathy", "David"};

    for (int i = 0; i < 4; ++i) {
        printf("%s\n", student_array[i]);
    }
    return 0;
}

字符串处理函数

C 语言提供了丰富的字符串处理函数,可以对字符串进行输入、输出、合并、修改、比较、转换、复制、搜索等操作,使用这些现成的函数可以大大减轻我们的编程负担。在使用字符串处理函数的时候需要包含头文件 <string.h>

string.h 是一个专门用来处理字符串的头文件,它包含了很多字符串处理函数,由于篇幅限制,本节只能讲解几个常用的,也可以点击此处open in new window查阅所有函数。

strlen - 获取字符串长度

strlenstring length 的缩写,意思是计算字符串的长度,语法格式为:

size_t strlen ( const char * str );

字符串以 ‘\0' 作为结束标志,strlen 函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0')。参数指向的字符串必须要以 '\0' 结束。注意函数的返回值类型为 size_t,对应的占位符为 %zu

示例如下:

#include <stdio.h>
#include <string.h>


int main()
{
    char str_1[] = "abcdef";
    printf("使用 strlen 获取 str_1 长度:%zu\n", strlen(str_1));

    return 0;
    
}

运行以上代码,我们可以获得数组长度为 6,如果使用 sizeof 来获取字符串的长度,得到的结果是 7。还有一点注意事项,当我们使用 char 数组定义字符串时,如果给数组设置了长度,无论字符串长度是多少,sizeof 都只会打印数组的长度代码如下:

#include <stdio.h>
#include <string.h>


int main()
{
    char str_1[] = "abcdef";
    printf("使用 strlen 获取 str_1 长度:%zu\n", strlen(str_1));
    printf("使用 sizeof 获取 str_1 长度:%zu\n", sizeof(str_1));

    char str_2[100] = "abcdef";
    printf("使用 strlen 获取 str_2 长度:%zu\n", strlen(str_2));
    printf("使用 sizeof 获取 str_2 长度:%zu\n", sizeof(str_2));

    return 0;
    
}

strcmp - 字符串比较函数

strcmpstring compare 的缩写,意思是字符串比较,语法格式为:

int strcmp ( const char * str1, const char * str2 );

str1 与 str2 是需要比较的两个字符串。字符本身没有大小之分,strcmp() 以各个字符对应的 ASCII 码值进行比较。strcmp() 从两个字符串的第 1 个字符开始比较,如果它们相等,就继续比较下一个字符,直到遇见不同的字符,或者到字符串的末尾。

返回值:若 str1 和 str2 相同,则返回 0;若 str1 大于 str2,则返回大于 0 的值;若 str1 小于 str2,则返回小于 0 的值。

#include <stdio.h>
#include <string.h>

int main() {
    char str_1[] = "apple";
    char str_2[] = "banana";
    int result = strcmp(str_1, str_2);

    if (result == 0) {
        printf("str1 == str2\n");
    }

    return 0;
}

strcat - 拼接字符串

strcatstring catenate 的缩写,意思是把两个字符串拼接在一起,语法格式为:

char * strcat( char * destination, const char * source );

该函数需要传递两个字符串 destinationsource,并将第 2 个字符串 source 的内容拼接在第 1 个字符串 destination 后面,并删除原来 destination 最后的结束标志 \0。这意味着,destination 必须足够长,要能够同时容纳 destinationsource,否则会越界(超出范围)。

strcat() 的返回值为 destination 的地址。 代码如下:

#include <stdio.h>
#include <string.h>


int main() {
    char last_name[100] = "罗";
    char first_name[] = "大富";

    char* ptr = strcat(last_name, first_name);

    printf("姓名:%s\n", last_name);
    printf("last_name 的地址:%p\n", last_name);
    printf("ptr:%s\n", ptr);
    printf("ptr 指向的地址:%p\n", ptr);

    return 0;
}

需要注意的是,strcat 函数的第 1 个参数 destination 必须是可以修改的字符串,如果是指针定义的字符串则无法实现拼接效果,示例如下:

#include <stdio.h>
#include <string.h>


int main() {
    char* last_name = "罗";
    char first_name[] = "大富";

    char* ptr = strcat(last_name, first_name);

    printf("姓名:%s\n", last_name);
    printf("last_name 的地址:%p\n", last_name);
    printf("ptr:%s\n", ptr);
    printf("ptr 指向的地址:%p\n", ptr);

    return 0;
}

strcpy - 字符串复制函数

strcpystring copy 的缩写,意思是字符串复制,将字符串从一个地方复制到另外一个地方,语法格式为:

char * strcpy ( char * destination, const char * source );

strcpy 函数与 strcat 函数类似,也需要传递两个字符串 destinationsource,并将第 2 个字符串 source 的内容转移到第 1 个字符串 destination 中,与 strcat 不同的是,strcat 是将第 2 个参数拼接在第 1 个参数的后面,而 strcpy 则会把第 2 个字符串的内容全部复制到第 1 个字符串中,并将第 1 个字符串原本的内容覆盖掉。

因此,destination 字符串空间的长度必须 >= source 字符串的长度,以确保能存放 source 字符串。同样地,destination 也必须是可以修改的字符串。返回值为第 1 个字符串 destination 的地址。

示例如下:

#include <stdio.h>
#include <string.h>


int main() {
    char str_6[20] = "Hello, ";
    char str_7[] = "world";

    char* ptr = strcpy(str_6, str_7);

    printf("str_6:%s\n", str_6);
    printf("str_6 的地址:%p\n", str_6);
    printf("r:%s\n", ptr);
    printf("r 的地址:%p\n", ptr);

    return 0;
}
上次编辑于:
贡献者: 罗大富BigRich