字符输入输出:getchar、putchar、getc、putc 的使用与区别

说到 C 语言的文件操作,字符级的输入输出是最基础、也是最容易被忽视的一块。我刚开始学 C 的时候,总觉得 printfscanf 就够用了,干嘛还要学这些单个字符的函数?后来在项目中踩了坑才明白——有些场景,还真就得用它们。

说白了,getcharputchargetcputc 这四位,就是 C 语言里处理单个字符的「四大金刚」。它们长得像,用法也像,但细节上各有各的脾气。

先看 getchar 和 putchar

getchar 从标准输入(通常是键盘)读一个字符。putchar 把一个字符写到标准输出(通常是屏幕)。用法极其简单:

#include <stdio.h>

int main() {
    char ch;
    printf("请输入一个字符:");
    ch = getchar();      // 从键盘读一个字符
    printf("你输入的是:");
    putchar(ch);         // 输出到屏幕
    putchar('\n');
    return 0;
}

注意看,getchar 返回的是 int 而不是 char。为什么?因为除了正常字符,它还可能返回 EOF(文件结束标志),而 EOF 通常定义为 -1,char 装不下。嗯,这里要注意——如果你用 char 去接返回值,在某些编译器上会出问题。

重要提醒:永远用 int 类型接收 getchar 的返回值,不要用 char。否则 EOF 可能被截断,导致死循环。

再看 getc 和 putc

getcputc 跟上面两位功能类似,但多了一个参数——文件指针。它们可以从任意文件流中读写字符,不限于标准输入输出。

#include <stdio.h>

int main() {
    FILE *fp = fopen("test.txt", "r");
    if (fp == NULL) {
        printf("文件打开失败\n");
        return 1;
    }

    int ch;
    while ((ch = getc(fp)) != EOF) {
        putc(ch, stdout);   // 输出到屏幕
    }

    fclose(fp);
    return 0;
}

这段代码把文件内容一个字符一个字符地读出来,再打印到屏幕上。效率不高,但胜在简单直观。我在项目中曾经用它来逐字符解析配置文件,虽然慢了点,但逻辑特别清晰,调试起来也方便。

它们到底有什么区别?

其实 getchar 就是 getc(stdin) 的简写,putchar(c) 就是 putc(c, stdout) 的简写。标准库就是这么定义的:

#define getchar()  getc(stdin)
#define putchar(c) putc(c, stdout)

所以从功能上讲,它们是一家人。但有一个细微差别:getcputc 可能是宏实现,也可能是函数实现,具体看编译器。而 getcharputchar 通常也是宏。这意味着什么?

  • 宏实现的版本:速度快,但参数可能被多次求值。比如 putc(getc(fp), stdout) 这种写法,如果 putc 是宏,getc(fp) 可能被调用两次——一次取字符,一次取参数。嗯,这是个坑。
  • 函数实现的版本:安全,但多一次函数调用开销。
我的建议:如果你不确定当前环境是宏还是函数,就老老实实分两步写。先 ch = getc(fp),再 putc(ch, stdout)。别图省事写在一行里。

用一张图理清关系

下面这张 SVG 图展示了这四个函数的关系和典型用法:

字符输入输出函数关系图 标准输入输出(stdin/stdout) getchar() / putchar() 任意文件流(FILE *fp) getc(fp) / putc(c, fp) 本质等价 关键区别: • getchar() = getc(stdin),putchar(c) = putc(c, stdout) • getc/putc 可能是宏实现,参数可能被多次求值 • 所有函数返回 int,不是 char,为了容纳 EOF

实际项目中的选择

我个人习惯这样选:

  • 只跟键盘和屏幕打交道:用 getcharputchar,代码更短,意图更明确。
  • 读写文件:用 getcputc,因为需要传文件指针。
  • 追求极致性能:用 getc_unlockedputc_unlocked(线程不安全版本),但一般用不到。

我曾经在做一个嵌入式日志系统时,需要逐字符把日志写到串口。当时用了 putc 配合自定义的 FILE 流,效果很好。但有一次因为没注意 putc 是宏实现,在循环里写了 putc(getc(fp), stdout),结果字符被吞了一半……排查了半天才发现是参数多次求值的问题。从那以后,我写这类代码都格外小心。

一个完整的例子

下面这个程序演示了从键盘读取字符,直到遇到换行符为止,然后原样输出:

#include <stdio.h>

int main() {
    int ch;
    printf("请输入一行文字(回车结束):\n");

    // 逐字符读取,直到遇到换行或 EOF
    while ((ch = getchar()) != '\n' && ch != EOF) {
        putchar(ch);  // 边读边输出
    }
    putchar('\n');

    return 0;
}

你想想看,如果这里用 scanf("%s"),遇到空格就停了。而用 getchar 逐字符处理,空格、制表符都能原样保留。这就是字符级函数的优势——精细控制。

核心要点:
  • 四个函数都处理单个字符,返回 int 类型
  • getchar/putchar 专用于标准输入输出
  • getc/putc 可用于任意文件流
  • 注意宏实现的参数多次求值问题
  • 始终检查返回值是否为 EOF

好了,这一节的内容就这些。字符输入输出虽然基础,但用好了能解决很多实际问题。下一节我们会聊到字符串的输入输出函数,到时候再对比一下它们的适用场景。

公众号:蓝海资料掘金营,微信deep3321