器→工具, 编程语言

C语言学习之字符串

钱魏Way · · 52 次浏览

在C语言中,字符串是以空字符’\0’结尾的字符数组。在C语言学习之数组中有提到过。在C语言编程中,我们经常需要对字符串进行操作,如定义、赋值、输入输出、连接、比较等。

定义与初始化

定义一个字符串,我们需要声明一个字符数组,并以空字符’\0’结束。例如:

char str[] = "Hello, World!";

这条语句中,字符数组str1就是一个包含了 “Hello, World!” 的字符串。值得注意的是,虽然 “Hello, World!” 只有13个字符,但是字符串str1的实际长度是14,因为它在末尾添加了一个空字符。

我们也可以先定义一个字符数组,然后再对它进行赋值。但需要注意,这种情况下,我们不能直接将整个字符串赋值给字符数组。例如下面的代码是错误的:

char str[10];
str = "Hello";  // 错误!

这是因为数组名实际上是一个指向数组第一个元素的常量指针,它不能改变所指向的地址。

如果我们想要在定义后对字符串进行赋值,我们需要使用 strcpy 函数,或者逐个字符地进行赋值。例如:

char str[10];
strcpy(str, "Hello");

// 或

char str[10];
str[0] = 'H';
str[1] = 'e';
str[2] = 'l';
str[3] = 'l';
str[4] = 'o';
str[5] = '\0';

字符串常量与字符串变量

在 C 语言中,字符串常量(String Constants)和字符串变量(String Variables)都与字符串有关,但是它们的定义和使用方式有所不同。

字符串常量(String Constants):

字符串常量是由一个或多个字符组成的常量,它们被双引号 (” “) 包围。例如:

"Hello, World!"

在这个例子中,”Hello, World!” 是一个字符串常量。注意,C 语言会自动在字符串的末尾添加一个空字符 (‘\0′),所以实际上这个字符串的长度为 13(’Hello, World!’ 的字符数) + 1(空字符) = 14。

字符串常量本身是不能被修改的。例如,下面的代码是错误的:

"Hello, World!"[1] = 'a'; // 错误,不能修改字符串常量

字符串常量会被编译器变成一个字符数组放在某处。相邻的两个字符串常量会被自动连接起来,不需要加号。

字符串变量(String Variables):

在 C 语言中,并没有专门的字符串类型。但我们可以使用字符数组(char array)或字符指针(char pointer)来创建和操作字符串。

例如,我们可以像这样声明一个字符串变量:

char str1[20] = "Hello, World!"; // 字符数组来存储字符串
char *str2 = "Hello, World!"; // 字符指针来存储字符串

在这个例子中,str1 是一个足够大的字符数组,可以存储 “Hello, World!” 及其后的空字符;str2 是一个字符指针,它指向一个字符串常量。

注意,通过字符数组创建的字符串变量是可以被修改的,而通过字符指针创建的字符串变量则不能修改原始的字符串常量,但指针本身可以修改,使其指向其他的字符串常量。例如:

str1[1] = 'a'; // 正确
str2 = "Hello, C!"; // 正确
str2[1] = 'a'; // 错误,不能修改字符串常量

以上就是字符串常量和字符串变量的基本介绍,希望能对你有所帮助。

输入与输出

在 C 语言中,我们可以使用多种方式进行字符串的输入和输出。常见的有使用printf和scanf函数,以及gets和puts函数。

使用printf和scanf函数

printf用于格式化输出字符串,scanf用于读取输入的字符串。例如:

char str[50];
printf("Please enter a string: ");
scanf("%s", str);  // 注意,这里不需要使用 & 符号
printf("You entered: %s\n", str);

但是,scanf函数在读取字符串时,遇到空格或者换行符就会停止读取。所以,如果你想读取一行中包含空格的字符串,scanf就不能满足需求。

printf 函数的基本使用

C语言中的 printf 函数是一个用于格式化输出的函数,它的全名是 “print formatted”,属于标准输入输出库 <stdio.h>。这个函数可以使我们按照所需的格式输出各种类型的数据,包括整型、浮点型、字符型、字符串等。

基本语法:

int printf(const char *format, ...);
  • format:这是一个字符串,包含了文本以及格式参数(如:%d、%s、%f 等)。文本会原封不动地被打印出来,而格式参数会被后面对应位置的参数替换。
  • …: 这里可以是任意数量、任意类型的参数,数量和类型需要与 format 中的格式参数相匹配。

常见的格式参数:

  • %d 或 %i: 用于输出十进制的整数。
  • %f: 用于输出浮点数。
  • %c: 用于输出字符。
  • %s: 用于输出字符串。
  • %u: 用于输出无符号十进制整数。
  • %o: 用于输出八进制整数。
  • %x 或 %X: 用于输出十六进制整数。
  • %%: 用于输出百分号。

例子:

int main()
{
    int a = 10;
    float b = 10.5;
    char c = 'A';
    char *str = "Hello, World!";
    
    printf("Integer is %d\n", a);
    printf("Float is %f\n", b);
    printf("Character is %c\n", c);
    printf("String is %s\n", str);
    
    return 0;
}

// 输出:
// Integer is 10
// Float is 10.500000
// Character is A
// String is Hello, World!

以上就是 printf 函数的基本使用,它是 C 语言中非常重要的一个函数,通过它我们可以方便地向屏幕输出信息。

scanf 函数的基本使用

C语言中的 scanf 函数用于从标准输入设备(通常是键盘)读取格式化的输入。它属于标准输入输出库 <stdio.h>。

基本语法:

int scanf(const char *format, ...);
  • format: 这是一个字符串,包含了需要读取的数据类型的格式(如:%d、%s、%f等)。
  • …: 这里可以是任意数量的参数,但这些参数前面必须加上地址操作符(&),因为 scanf 需要知道数据存储的位置。参数的数量和类型需要与 format 中的格式参数相匹配。

例子:

int main()
{
    int a = 0;
    float b = 0.0;
    char c = '\0';
    char str[50];
    
    printf("Enter an integer: ");
    scanf("%d", &a);
    printf("Enter a float: ");
    scanf("%f", &b);
    printf("Enter a character: ");
    scanf(" %c", &c); // 注意在 %c 前放一个空格,用来跳过前面输入的换行符
    printf("Enter a string: ");
    scanf("%s", str); // 注意这里不需要 & 符号
    printf("\nYou entered: \n%d\n%f\n%c\n%s\n", a, b, c, str);
    
    return 0;
}

注意,当使用 %s 格式读取字符串时,我们不需要在变量前加 &,因为数组名本身就是一个指针,它指向数组的第一个元素。另外,scanf 在读取输入时,遇到空格或换行就会停止。所以,如果你想读取一个包含空格的字符串,你需要用其他的函数,如 fgets。

使用gets和puts函数

为了解决scanf不能读取含有空格的字符串的问题,我们可以使用gets和puts函数。gets可以读取一行中包含空格的字符串,puts则用于输出字符串并自动换行。

char str[50];
printf("Please enter a string: ");
gets(str);
printf("You entered: ");
puts(str);

但是需要注意的是,gets函数在获取输入时不会检查输入的长度,如果输入的字符串长度超过了定义的数组长度,就会造成缓冲区溢出的问题。因此,现在通常推荐使用fgets函数替代gets函数,它可以防止缓冲区溢出:

char str[50];
printf("Please enter a string: ");
fgets(str, sizeof(str), stdin);   // 获取输入,最多获取 sizeof(str)-1 个字符,余下的一个字符留给 '\0'
printf("You entered: ");
puts(str);

在以上代码中,fgets函数的第二个参数表示最多读取的字符数,包括末尾的 ‘\0’。第三个参数表示输入来源,stdin表示标准输入,也就是键盘输入。

gets 函数与fgets 函数的区别

gets 函数用于从标准输入设备(通常是键盘)读取一个字符串,直到遇到换行符或 EOF(文件结束)为止。它的基本语法如下:

char *gets(char *s);

这里,s 是你想要存储输入字符串的字符数组。gets 函数会自动在字符串的末尾添加一个空字符 ‘\0’。

然而,gets 函数有一个严重的问题,那就是它不检查是否会超出数组 s 的长度,这可能会导致缓冲溢出的问题。因此,现在一般不推荐使用 gets 函数。

fgets 函数用于从指定的流中读取一行,直到遇到换行符、EOF 或者已经读取了 num-1 个字符为止(num 是你指定的最大字符数),并把结果存储在字符串 s 中。它的基本语法如下:

char *fgets(char *s, int num, FILE *stream);

这里,s 是你想要存储输入的字符串,num 是读取的最大字符数,stream 是你想要读取的流。

相比于 gets,fgets 更安全,因为你可以指定最大的字符数,防止缓冲溢出。此外,当你想从文件中读取字符串时,fgets 也非常有用。如果文件中的一行超过了你指定的字符数,fgets 会在下一次调用时继续从上次读取的位置开始读取。

puts、gets、putchar、getchar的区别

  • puts 函数用于输出一个字符串并自动在字符串末尾添加换行符。
  • gets 函数用于从标准输入(通常是键盘)读取一个字符串,直到遇到换行符或 EOF(文件结束符)
  • putchar 函数用于向标准输出写入一个字符。
  • getchar 函数用于从标准输入读取一个字符。

字符串函数

C语言提供了一系列的字符串处理函数,这些函数定义在 <string.h> 库中。以下是一些常见的字符串处理函数:

  • strlen(s):返回字符串 s 的长度,不包括末尾的 ‘\0’。
  • strcpy(s1, s2):复制字符串 s2 到 s1。
  • strncpy(s1, s2, n):复制字符串 s2 的前 n 个字符到 s1。
  • strcat(s1, s2):将字符串 s2 连接到 s1 的末尾。
  • strncat(s1, s2, n):将字符串 s2 的前 n 个字符连接到 s1 的末尾。
  • strcmp(s1, s2):比较字符串 s1 和 s2。如果 s1 和 s2 相等,返回 0;如果 s1 小于 s2,返回负数;如果 s1 大于 s2,返回正数。
  • strncmp(s1, s2, n):比较字符串 s1 和 s2 的前 n 个字符。
  • strchr(s, c):在字符串 s 中查找第一次出现字符 c 的位置。
  • strrchr(s, c):在字符串 s 中查找最后一次出现字符 c 的位置。
  • strstr(s1, s2):在字符串 s1 中查找子串 s2 第一次出现的位置。
  • strtok(s1, s2):将字符串 s1 分割为一系列由 s2 中的字符分隔的标记(token)。

字符串与指针

在 C 语言中,字符串和指针之间有着紧密的关系。实际上,C 语言中的字符串就是通过指针和字符数组来实现的。

字符串和字符数组:

在 C 语言中,并没有直接的字符串数据类型,但我们可以使用字符数组来存储和操作字符串。例如,我们可以这样定义一个字符串:

char str[] = "Hello";

在这个例子中,str 是一个字符数组,它包含了字符 ‘H’、’e’、’l’、’l’、’o’ 和一个空字符 ‘\0’(C 语言使用空字符来标记字符串的结束)。

字符串和指针:

虽然我们可以使用字符数组来存储和操作字符串,但在很多情况下,我们更倾向于使用指针来处理字符串。这是因为指针提供了一种更灵活和强大的方式来操作和传递字符串。例如,我们可以这样定义一个字符串:

char *str = "Hello";

在这个例子中,str 是一个指针,它指向字符串 “Hello” 的第一个字符 ‘H’。我们可以通过 str 来访问和操作 “Hello”。

注意,str 指向的是一个字符串常量,这意味着我们不能修改它指向的字符串(例如,不能写 str[1] = ‘a’;)。如果你想创建一个可以修改的字符串,你应该使用字符数组,或者使用 malloc 或 calloc 函数动态分配一块内存。

总的来说,C 语言中的字符串就是通过字符数组和指针来实现的。通过理解字符数组和指针,你可以更好地理解和使用 C 语言中的字符串。

字符串的遍历

在 C 语言中,可以使用数组索引或者指针来遍历字符串,以下是两种遍历字符串的方法:

使用数组索引遍历字符串:

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

int main() {
    char str[] = "Hello, World!";
    int len = strlen(str);

    for (int i = 0; i < len; i++) {
        printf("%c", str[i]);
    }

    return 0;
}

在这个例子中,我们首先获取字符串的长度,然后使用数组索引 i 来逐个访问字符串中的每个字符。

使用指针遍历字符串:

#include <stdio.h>

int main() {
    char *str = "Hello, World!";

    while (*str != '\0') {
        printf("%c", *str);
        str++;
    }

    return 0;
}

在这个例子中,我们使用一个指针 str 来访问字符串中的每个字符。每次迭代,我们都把 str 指向下一个字符,直到遇到空字符 ‘\0’。

在遍历字符串时,需要注意的是,C 语言中的字符串是以空字符 ‘\0’ 结束的,所以在遍历时需要确保不会超过这个结束标志。如果超过这个结束标志,就可能会读取到不应该访问的内存区域,这可能会导致程序崩溃或其他未定义的行为。

字符串与数字的转换

在 C 语言中,有时我们需要将字符串转换为数字(例如,在处理用户输入时),或者将数字转换为字符串(例如,在生成输出时)。以下是如何进行这些转换的一些例子:

字符串转换为数字:

C 语言提供了一些函数来将字符串转换为不同类型的数字,这些函数包括 atoi()(转换为 int)、atol()(转换为 long)、atoll()(转换为 long long)以及 atof()(转换为 double)。下面是一个使用 atoi() 和 atof() 的例子:

#include <stdlib.h>
#include <stdio.h>

int main() {
    char str_int[] = "123";
    char str_double[] = "123.456";

    int num_int = atoi(str_int);
    double num_double = atof(str_double);

    printf("%d, %f\n", num_int, num_double);

    return 0;
}

在这个例子中,我们将字符串 “123” 转换为了整数 123,将字符串 “123.456” 转换为了浮点数 123.456。

需要注意的是,这些函数不会检查转换是否成功,如果字符串不能被解析为一个有效的数字,它们通常会返回 0。如果你需要更强大的错误检查,可以考虑使用 strtol()、strtoll() 或 strtod() 等函数,这些函数可以告诉你转换是否成功,以及发生错误的原因。

数字转换为字符串:

C 语言提供了 sprintf() 函数,可以将数字格式化为字符串。下面是一个例子:

#include <stdio.h>

int main() {
    int num_int = 123;
    double num_double = 123.456;

    char str_int[50];
    char str_double[50];

    sprintf(str_int, "%d", num_int);
    sprintf(str_double, "%f", num_double);

    printf("%s, %s\n", str_int, str_double);

    return 0;
}

在这个例子中,我们将整数 123 格式化为了字符串 “123”,将浮点数 123.456 格式化为了字符串 “123.456000”。注意,sprintf() 会自动在生成的字符串末尾添加空字符 ‘\0’。

需要注意的是,当使用 sprintf() 时,你需要确保目标字符串有足够的空间来存储生成的字符串,否则可能会导致缓冲区溢出。为了防止缓冲区溢出,你可以考虑使用 snprintf(),这个函数可以让你指定一个最大长度,以防止超过目标字符串的空间。

字符串安全问题

在 C 语言中,处理字符串时存在一些安全问题,主要是因为 C 语言并没有内置的字符串类型,并且对数组的边界检查也相对较弱。以下是一些需要注意的安全问题:

缓冲区溢出(Buffer Overflow)

缓冲区溢出通常发生在对字符串进行操作时没有正确地检查边界的情况下。例如,使用 gets() 函数读取输入时就可能发生这种情况,因为 gets() 函数会一直读取字符,直到遇到换行符或 EOF,而不管目标缓冲区是否有足够的空间。这可能会导致溢出目标缓冲区,并覆盖内存中的其他数据,可能会导致程序崩溃或其他未定义的行为。因此,通常建议使用其他能检查边界的函数,如 fgets()。

字符串未终止(String Not Terminated)

在 C 语言中,字符串是以空字符 (‘\0’) 结束的。如果一个字符串没有正确地终止,那么当试图读取或操作该字符串时,可能会读取或操作到不应该访问的内存区域,这可能会导致程序崩溃或其他未定义的行为。

格式化字符串攻击(Format String Attack)

如果使用用户提供的数据作为 printf() 或其他格式化函数的格式字符串,那么用户可能会提供一些特殊的格式说明符,这可能会导致读取或写入不应该访问的内存区域,或者执行其他未预期的操作。为了防止这种情况,应始终使用静态的、不可变的字符串作为格式字符串,或者确保用户提供的数据不会被直接用作格式字符串。

以上就是在处理字符串时需要注意的一些安全问题。在编写代码时,应始终谨慎处理字符串,避免以上提到的以及其他可能的安全问题。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注