快速读入

快速读入

我们平时写代码基本都使用 cin 进行读入,但众所周知,cin 的读入速度很慢,无法胜任输入数据量很大的题(如 洛谷 P5745 【深基附B例】区间最大和 ),所以算法竞赛中常用速度更快的读入方法。接下来就要介绍到常用的快速读入算法。


0. 前置知识

下面比较几种常用输入函数的速度。

cin < scanf < getchar < getchar_unlocked(Linux)/_getchar_nolock(Windows) < fread

1. cin 优化(关闭同步 和 解除绑定)

cin 读入速度慢的两个主要原因是与 stdio 同步和与 cout 绑定,所以 cin 优化的瓶颈就在于解除 stdio 同步和 cout 绑定,其弊端有:

  1. 不能混用 cin 和 scanf ,也不能混用 cout 和 printf ;
  2. 如果要在输入之前输出,则输出后需要 cout.flush(); 或 cout << flush; 。

实现方法

只需要在主函数最前面加上这两句代码即可:

ios::sync_with_stdio(0);
cin.tie(0);

这样 cin 的速度就接近 scanf 了。


2. getchar(最常用)

getchar 函数是用来读取 1 字节的输入数据并将其转化为 char 类型(但返回值为 int ),且速度比 scanf 快。所以我们可以使用 getchar 来实现整数的读入。

基本思路

逐位输入这个整数,用变量 x 来存储。如果第一位是 - ,则将变量 k 标记为 \(-1\),否则标记为 \(1\)。如果输入的是数字,则将变量 x 乘 \(10\) 之后再加上输入字符 \(-\) ' \(0\) ' ,直到输入空格或回车为止。

如何判断输入字符是不是数字?有一下两种方法:

  1. 如果 ch >= '0' && ch <= '9' ,则输入的是数字;如果 ch < '0' || ch > '9' ,则输入的不是数字。
  2. 还可以使用 isdigit 来判断:如果 isdigit(ch) ,则输入的是数字,反之则不是。这种方法的速度其实比第 1 种还要快。(需要 #include <cctype> )

代码实现

#include <cstdio>
#include <cctype>
inline int read() { // 使用内联函数提升效率
    int x = 0, k = 1, ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-') k = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * k;
}
int main() {
    int a = read(); // 使用方法
}

3. getchar_unlocked (Linux) / _getchar_nolock (Windows)

普通的 getchar 函数带有线程锁,但算法竞赛中用不到,所以可以关闭线程锁以提高效率。

具体实现

将上面代码中的所有 getchar 替换为 getchar_unlocked (Linux) / _getchar_nolock (Windows) 即可。

(p.s. NOI 系列竞赛评测机和洛谷评测机均为 Linux 系统,所以打 NOI 系列竞赛或在洛谷上提交评测都要用 getchar_unlocked )

#include <cstdio>
#include <cctype>
inline int read() { // 使用内联函数提升效率
    int x = 0, k = 1, ch = getchar_unlocked();
    while (!isdigit(ch)) {
        if (ch == '-') k = -1;
        ch = getchar_unlocked();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - '0';
        ch = getchar_unlocked();
    }
    return x * k;
}
int main() {
    int a = read(); // 使用方法
}

4. fread (不常用)

函数原型:

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

fread 的作用是从 stream 中读入 nmemb 个 size 字节大小的数据,并存储到 ptr 中,并返回读入的字节数。

例如:fread(buf, 1, 1 << 20, stdin); 可以从标准输入流中读入 \(2^{20}\) 个 \(1\) 字节的字符,并存储到 buf 数组中。

根据 fread 函数,我们可以重新写一个 getchar 函数。

使用 fread 实现 getchar

#include <cstdio>
char buf[1 << 20], *p1, *p2; /* buf 用来存储输入数据,p1 是 buf 的首指针,p2 是 buf 的尾指针 */
char gc() {
    if (p1 == p2) { // 如果 buf 中的数据全部读入完毕,则重新读入
        p1 = buf;
        p2 = buf + fread(buf, 1, 1 << 20, stdin); // 再读入 2^20 个字符
        if (p1 == p2) return EOF; /* 如果 p1==p2,则说明没有读入成功,即读入到文件末尾,返回 EOF */
    }
    return *p1++; // 返回读入的字符,并指向下一个字符
}

事实上,多次调用函数很浪费时间,甚至没有 getchar 快,所以实际使用时会写成宏定义形式。

#include <cstdio>
#include <cctype>
char buf[1 << 20], *p1, *p2;
#define gc() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 20, stdin)), *p1++)
inline int read() { // 使用内联函数提升效率
    int x = 0, k = 1, ch = gc();
    while (!isdigit(ch)) {
        if (ch == '-') k = -1;
        ch = gc();
    }
    while (isdigit(ch)) {
        x = x * 10 + ch - '0';
        ch = gc();
    }
    return x * k;
}
int main() {
    int a = read(); // 使用方法
}

注意

  1. 使用 fread 需要在输入完毕之后手动打 EOF (Linux 为 Ctrl+D ,Windows 为 Ctrl+Z) 。
  2. 使用此快读就不要使用 cin 、scanf 或 getchar ,否则会因为缓存问题出错。

至此,常规的快速读入方法都已经介绍完毕。更多非常规的快读方法(如:mmap 和打表优化)在这里不再赘述。本文参考 洛谷专栏 【输入 / 输出优化】浅谈 C++ IO优化(快读快写) 撰写,鸣谢此专栏作者 @Famishin。

posted @ 2026-08-26 10:24  zzx321  阅读(27)  评论(0)    收藏  举报