Skip to content

字符串读入 #19

@lexiaox

Description

@lexiaox

好的,C语言中字符串的读入是一个基础但容易混淆的知识点。不同的函数有不同的特性,适用于不同的场景。我来为你系统地梳理一下,方便你做笔记。

核心概念:字符串在C语言中的存储

C语言没有真正的字符串类型,而是用字符数组来存储字符串,以空字符 '\0' 作为结束标志。任何读入操作都必须确保数组末尾有这个 '\0'


1. scanf 函数

scanf 是最常用的输入函数,但与字符串一起使用时需要特别注意。

a) 使用 %s 格式

  • 特性

    • 从第一个非空白字符开始读取,直到遇到空白字符(空格、制表符、换行符)为止。
    • 会自动在末尾添加 '\0'
    • 不安全:如果输入的单词长度超过字符数组的长度,会导致缓冲区溢出,这是严重的漏洞。
  • 语法

    char str[100];
    scanf("%s", str); // 注意:数组名本身就是地址,所以不需要 &str
  • 示例

    #include <stdio.h>
    
    int main() {
        char name[20];
        printf("请输入你的名字(不能有空格): ");
        scanf("%s", name);
        printf("你好, %s!\n", name);
        return 0;
    }
    • 输入:Alice
    • 输出:你好, Alice!
    • 输入:Alice Smith
    • 输出:你好, Alice! (后面的 Smith 会留在输入缓冲区中)

b) 使用 %[^\n] 格式 (读取带空格的字符串)

  • 特性

    • %[^\n] 表示读取所有字符,直到遇到换行符 \n 为止
    • 这允许你读取包含空格的整行字符串。
    • 同样有缓冲区溢出的风险。
  • 语法

    char str[100];
    scanf("%[^\n]", str); // 读取一行,直到遇到换行符
  • 示例

    #include <stdio.h>
    
    int main() {
        char sentence[100];
        printf("请输入一句话: ");
        scanf("%[^\n]", sentence); // 可以读取带空格的输入
        printf("你输入的是: %s\n", sentence);
        return 0;
    }
    • 输入:I love C programming.
    • 输出:你输入的是: I love C programming.

c) 限制读取长度 (安全的方法)

  • 方法:在 %s%[^\n] 中间加入数字,指定最多读取的字符数(这个数量需要为 '\0' 预留一个位置)。
  • 语法
    char str[10]; // 最多能存储9个有效字符 + 1个 '\0'
    scanf("%9s", str); // 最多读取9个字符,确保不会溢出

2. gets 函数 (已废弃,绝对不要使用!)

  • 特性
    • 读取一整行,直到遇到换行符,然后丢弃换行符并添加 '\0'
    • 极其危险:它不知道目标数组的大小,会无限制地读取,极易导致缓冲区溢出。
  • 在现代编译器中,使用 gets 通常会收到警告或错误。

3. fgets 函数 (推荐的读行方法)

这是读取整行字符串最安全、最标准的方法。

  • 特性

    • 会指定读取的最大字符数和目标流(通常是标准输入 stdin)。
    • 会读取并存储换行符 \n(如果数组空间足够容纳它)。
    • 绝对保证不会发生缓冲区溢出。
  • 语法

    char *fgets(char *str, int n, FILE *stream);
    // str: 目标字符数组
    // n: 最大读取字符数(包括最后的 '\0')
    // stream: 输入流,使用 stdin 表示从标准输入读取
  • 示例

    #include <stdio.h>
    
    int main() {
        char line[100];
        printf("请输入一行文字: ");
        fgets(line, sizeof(line), stdin); // 安全地读取最多99个字符
        printf("你输入的是: %s", line); // 注意,line中可能包含换行符
        return 0;
    }
    • 输入:Hello, how are you?
    • 输出:你输入的是: Hello, how are you? (后面自带一个换行)
  • 处理 fgets 的换行符
    很多时候我们不希望字符串末尾有换行符,可以手动移除它。

    #include <stdio.h>
    #include <string.h>
    
    int main() {
        char line[100];
        printf("请输入: ");
        fgets(line, sizeof(line), stdin);
        
        // 移除换行符:找到换行符并将其替换为 '\0'
        size_t len = strlen(line);
        if (len > 0 && line[len-1] == '\n') {
            line[len-1] = '\0';
        }
        
        printf("处理后的字符串: \"%s\"\n", line);
        return 0;
    }

4. getchar 函数 (逐个字符读取)

用于需要精细控制输入流程的场景,例如自己实现一个读行函数。

  • 示例:用 getchar 模拟 fgets
    #include <stdio.h>
    
    void my_gets(char *str, int max_len) {
        int i = 0;
        char ch;
        while (i < max_len - 1 && (ch = getchar()) != '\n' && ch != EOF) {
            str[i++] = ch;
        }
        str[i] = '\0'; // 不要忘记添加字符串结束符
    }
    
    int main() {
        char str[100];
        printf("请输入: ");
        my_gets(str, sizeof(str));
        printf("你输入的是: %s\n", str);
        return 0;
    }

总结与对比表

方法 能否读空格? 是否安全? 是否读取换行符? 适用场景
scanf("%s") 不能 不安全 不读取 读取单个单词
scanf("%[^\n]") 不安全 不读取 读取一行(不包含换行符)
gets 极不安全 读取但丢弃 绝对不要使用
fgets 安全 读取并存储 读取一行的首选方法
getchar 安全(取决于实现) 可控制 需要逐个字符处理的复杂输入

最佳实践建议

  1. 读取单个无空格的单词:使用 scanf限制长度,如 scanf("%9s", str)
  2. 读取一行包含空格的字符串总是使用 fgets
  3. 永远不要使用 gets
  4. 使用 fgets 后,如果不需要换行符,记得手动移除。
  5. 在调用 fgets 读取一行后,如果想再用 scanf,要注意清理输入缓冲区,因为 fgetsscanf 对换行符的处理方式不同,可能会互相干扰。

希望这份详细的笔记对你有帮助!

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions