MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C 语言getchar()和putchar()深入解析

2023-12-094.6k 阅读

C 语言中的字符输入输出基础

在 C 语言中,getchar()putchar() 是用于字符输入输出的基础函数。它们提供了一种简单且高效的方式来处理单个字符的读取和写入操作。这两个函数在标准输入输出库 <stdio.h> 中定义,是 C 语言编程中处理字符流的重要工具。

getchar() 函数用于从标准输入(通常是键盘)读取一个字符。它的原型如下:

int getchar(void);

该函数从输入流 stdin 中读取下一个字符,并将其作为 int 类型返回。如果到达文件末尾或发生读取错误,getchar() 将返回 EOF(End - Of - File,这是一个在 <stdio.h> 中定义的常量,通常为 -1)。

例如,以下代码使用 getchar() 读取单个字符并输出:

#include <stdio.h>
int main() {
    int ch;
    printf("请输入一个字符:");
    ch = getchar();
    printf("你输入的字符是:%c\n", ch);
    return 0;
}

在上述代码中,getchar() 从标准输入读取一个字符,并将其存储在 ch 变量中。注意,ch 被定义为 int 类型,而不是 char 类型,这是因为 getchar() 返回 int 类型,以便能够处理 EOF。如果将 ch 定义为 char 类型,可能无法正确表示 EOF

putchar() 函数用于将一个字符输出到标准输出(通常是显示器)。它的原型如下:

int putchar(int c);

putchar() 接受一个 int 类型的参数 c,并将其低 8 位(即相当于一个字符)输出到输出流 stdout。函数返回输出的字符,如果发生错误,则返回 EOF

以下是使用 putchar() 的示例代码:

#include <stdio.h>
int main() {
    char ch = 'A';
    putchar(ch);
    putchar('\n');
    return 0;
}

在这段代码中,putchar(ch) 将字符 'A' 输出到标准输出,putchar('\n') 则输出一个换行符,使后续输出从新的一行开始。

getchar() 深入解析

缓冲区机制

getchar() 操作涉及到输入缓冲区的概念。在大多数系统中,标准输入是行缓冲的。这意味着,当你在键盘上输入字符时,这些字符不会立即被 getchar() 读取,而是先存储在输入缓冲区中。只有当你按下回车键时,缓冲区中的内容才会被 getchar() 等输入函数处理。

例如,考虑以下代码:

#include <stdio.h>
int main() {
    int ch;
    printf("请输入一些字符然后按回车:");
    while ((ch = getchar()) != EOF) {
        putchar(ch);
    }
    return 0;
}

在这个程序中,你可以输入多个字符,然后按回车键。程序会逐个读取缓冲区中的字符并输出,直到遇到文件结束标志(在 Windows 系统中,通常通过按下 Ctrl + Z 然后按回车键来模拟 EOF;在 Unix/Linux 系统中,通过按下 Ctrl + D 来模拟 EOF)。

处理多字符输入

由于缓冲区的存在,getchar() 在处理连续输入时需要特别注意。例如,假设你有如下代码:

#include <stdio.h>
int main() {
    int ch1, ch2;
    printf("请输入两个字符:");
    ch1 = getchar();
    ch2 = getchar();
    printf("ch1: %c, ch2: %c\n", ch1, ch2);
    return 0;
}

当你输入两个字符并按回车键时,你可能期望 ch1 存储第一个字符,ch2 存储第二个字符。然而,实际情况是,ch1 存储第一个字符,而 ch2 存储回车键产生的换行符 '\n'。这是因为按下回车键后,输入缓冲区中包含了你输入的两个字符以及换行符,getchar() 依次从缓冲区中读取字符。

为了避免这种情况,你可以在读取第二个字符之前清除缓冲区中的换行符。一种常见的方法是在读取第一个字符后,使用循环读取并丢弃缓冲区中的剩余字符,直到遇到换行符:

#include <stdio.h>
int main() {
    int ch1, ch2;
    char c;
    printf("请输入两个字符:");
    ch1 = getchar();
    while ((c = getchar()) != '\n' && c != EOF);
    ch2 = getchar();
    printf("ch1: %c, ch2: %c\n", ch1, ch2);
    return 0;
}

在上述代码中,while ((c = getchar()) != '\n' && c != EOF); 这个循环会读取并丢弃缓冲区中直到换行符之前的所有字符,这样 ch2 就能正确读取下一个输入的字符。

错误处理

getchar() 返回 EOF 不仅表示到达文件末尾,还可能表示发生了读取错误。在一些情况下,区分这两种情况是很重要的。例如,在读取文件时,如果因为文件损坏等原因导致读取错误,而程序误将其当作文件结束处理,可能会导致数据丢失或程序逻辑错误。

在 C 标准库中,可以使用 ferror() 函数来检查是否发生了输入错误。ferror() 函数的原型如下:

int ferror(FILE *stream);

它接受一个指向 FILE 类型对象的指针,该对象代表一个流(对于 getchar(),对应的流是 stdin)。如果在该流上发生了错误,ferror() 返回一个非零值,否则返回 0。

以下是一个示例代码,展示如何使用 ferror()getchar() 一起处理错误:

#include <stdio.h>
int main() {
    int ch;
    while ((ch = getchar()) != EOF) {
        if (ferror(stdin)) {
            perror("读取错误");
            break;
        }
        putchar(ch);
    }
    if (feof(stdin)) {
        printf("到达文件末尾\n");
    }
    return 0;
}

在这个程序中,每次调用 getchar() 后,通过 ferror(stdin) 检查是否发生读取错误。如果发生错误,使用 perror() 函数输出错误信息并终止循环。如果 getchar() 返回 EOFferror(stdin) 返回 0,则表示到达文件末尾。

putchar() 深入解析

输出缓冲区与刷新

putchar() 函数也与输出缓冲区密切相关。标准输出通常也是缓冲的,这意味着 putchar() 输出的字符不会立即显示在屏幕上,而是先存储在输出缓冲区中。只有当缓冲区已满、程序调用 fflush() 函数或程序正常结束时,缓冲区中的内容才会被实际输出到显示器。

fflush() 函数用于刷新输出缓冲区,其原型如下:

int fflush(FILE *stream);

对于标准输出 stdout,调用 fflush(stdout) 会将缓冲区中的所有字符输出到显示器。例如,以下代码展示了 fflush() 的使用:

#include <stdio.h>
#include <unistd.h>
int main() {
    char ch = 'A';
    putchar(ch);
    sleep(2);
    fflush(stdout);
    return 0;
}

在上述代码中,putchar(ch) 将字符 'A' 放入输出缓冲区,然后 sleep(2) 使程序暂停 2 秒。如果没有 fflush(stdout),字符 'A' 不会立即显示,而是在程序结束时才会显示。添加 fflush(stdout) 后,字符 'A' 会在 fflush() 调用时立即显示在屏幕上。

错误处理

getchar() 类似,putchar() 也可能发生错误。当 putchar() 无法将字符输出到标准输出时,它会返回 EOF。可以通过 ferror() 函数来检查 stdout 流上是否发生了错误。例如:

#include <stdio.h>
int main() {
    int result;
    result = putchar('A');
    if (result == EOF) {
        if (ferror(stdout)) {
            perror("输出错误");
        }
    }
    return 0;
}

在这个程序中,putchar('A') 的返回值被检查。如果返回 EOF,则通过 ferror(stdout) 检查是否发生了输出错误。如果发生错误,使用 perror() 输出错误信息。

在实际编程中的应用

简单文本处理

getchar()putchar() 在简单文本处理任务中非常有用。例如,编写一个程序将输入的文本中的大写字母转换为小写字母并输出。

#include <stdio.h>
int main() {
    int ch;
    while ((ch = getchar()) != EOF) {
        if (ch >= 'A' && ch <= 'Z') {
            ch = ch + ('a' - 'A');
        }
        putchar(ch);
    }
    return 0;
}

在这个程序中,getchar() 逐字符读取输入文本,对于每个读取的字符,如果它是大写字母,则将其转换为小写字母,然后通过 putchar() 输出。

密码输入模拟

虽然 C 语言本身没有内置的密码输入隐藏功能,但可以使用 getchar() 来模拟密码输入,通过不回显输入的字符。

#include <stdio.h>
#include <termios.h>
#include <unistd.h>
struct termios oldattr, newattr;
void disableRawMode() {
    tcsetattr(STDIN_FILENO, TCSANOW, &oldattr);
}
void enableRawMode() {
    tcgetattr(STDIN_FILENO, &oldattr);
    newattr = oldattr;
    newattr.c_lflag &= ~(ECHO | ICANON);
    tcsetattr(STDIN_FILENO, TCSANOW, &newattr);
    atexit(disableRawMode);
}
int main() {
    enableRawMode();
    char password[100];
    int i = 0;
    int ch;
    printf("请输入密码:");
    while ((ch = getchar()) != '\n' && i < 99) {
        password[i++] = ch;
    }
    password[i] = '\0';
    printf("\n你输入的密码是:%s\n", password);
    return 0;
}

在上述代码中,通过 termios 头文件中的函数来设置终端为原始模式(关闭回显和规范模式),这样 getchar() 读取的字符不会显示在屏幕上。用户输入的密码字符被存储在 password 数组中,输入完成后,程序切换回正常终端模式并输出密码。

文件操作中的应用

getchar()putchar() 也可以用于简单的文件操作。例如,将一个文件的内容复制到另一个文件中:

#include <stdio.h>
int main() {
    FILE *src, *dst;
    int ch;
    src = fopen("source.txt", "r");
    if (src == NULL) {
        perror("无法打开源文件");
        return 1;
    }
    dst = fopen("destination.txt", "w");
    if (dst == NULL) {
        perror("无法创建目标文件");
        fclose(src);
        return 1;
    }
    while ((ch = getc(src)) != EOF) {
        putc(ch, dst);
    }
    fclose(src);
    fclose(dst);
    return 0;
}

在这个程序中,getc() 类似于 getchar(),但它从指定的文件流 src 中读取字符,putc() 类似于 putchar(),但它将字符输出到指定的文件流 dst 中。程序从源文件 source.txt 中读取字符,并将其写入目标文件 destination.txt 中。

与其他输入输出函数的对比

scanf()printf() 的对比

scanf()printf() 是 C 语言中更通用的格式化输入输出函数。scanf() 可以按照指定的格式从标准输入读取各种类型的数据,而 printf() 可以按照指定的格式将各种类型的数据输出到标准输出。

getchar()putchar() 相比,scanf()printf() 功能更强大,但也更复杂。getchar()putchar() 专注于单个字符的输入输出,简单高效,适用于处理字符流的场景。而 scanf()printf() 在处理复杂数据格式和多种数据类型时更有优势。

例如,scanf("%d", &num) 可以从标准输入读取一个整数,而 printf("%f", num) 可以将一个浮点数以指定格式输出。但如果只需要处理单个字符,getchar()putchar() 更为简洁。

fgetc()fputc() 的对比

fgetc()fputc()getchar()putchar() 功能类似,但它们可以操作任意文件流,而不仅仅是标准输入输出流。getchar() 实际上等价于 fgetc(stdin)putchar(c) 等价于 fputc(c, stdout)

fgetc()fputc() 的原型如下:

int fgetc(FILE *stream);
int fputc(int c, FILE *stream);

这使得它们在文件操作中更加灵活。例如,当需要从自定义文件流中读取或写入单个字符时,fgetc()fputc() 是更好的选择。

总结

getchar()putchar() 是 C 语言中处理字符输入输出的基础且重要的函数。深入理解它们的工作原理,包括缓冲区机制、错误处理等方面,对于编写高效、健壮的 C 程序至关重要。在实际编程中,它们在简单文本处理、密码输入模拟、文件操作等多种场景中都有广泛应用。同时,与其他输入输出函数如 scanf()printf()fgetc()fputc() 进行对比,可以根据具体需求选择最合适的函数来实现程序功能。通过合理运用这些函数,开发者能够更好地控制字符流的输入输出,提升程序的质量和效率。无论是初学者还是有经验的程序员,熟练掌握 getchar()putchar() 都是进一步深入学习 C 语言输入输出操作的重要基石。

希望通过以上对 getchar()putchar() 的详细解析,能帮助你更好地理解和使用这两个函数,在 C 语言编程中更加得心应手。在实际应用中,不断实践和探索,结合不同的编程场景,充分发挥它们的优势,解决更多复杂的问题。同时,也要注意它们与其他输入输出函数的协同使用,以实现更丰富、更强大的程序功能。