不喜勿喷


C++ 新手教学:从 cout 到快读

一、最开始的输入输出:cin 和 cout

1.1 基本用法

刚学 C++ 时,我们一般这样写:

#include <iostream>
using namespace std;

int main() {
    int n;
    cin >> n;          // 输入一个整数
    cout << n << '\n'; // 输出这个整数并换行
    return 0;
}
  • cin 是标准输入流,>> 是流提取运算符。
  • cout 是标准输出流,<< 是流插入运算符。
  • 可以连续输入输出:
int a, b;
cin >> a >> b;
cout << a << " " << b << '\n';

cin 和 cout 的好处:

  • 不用记格式符,比如 %d、%lld。
  • 类型安全,输入输出会自动匹配类型。
  • 写起来简单,适合新手。

1.2 为什么说它慢?

cin / cout 默认会跟 C 语言的标准输入输出(scanf / printf)保持同步,还会在输入前刷新输出缓冲区。这些机制保证了混用时的正确性,但也带来了额外的开销。数据量很大时(比如读入 (10^6) 个整数),就可能超时。

1.3 小技巧:'\n' 和 endl 的区别

cout << n << '\n';   // 只是输出一个换行符
cout << n << endl;   // 输出换行符,并刷新缓冲区

endl 会强制刷新缓冲区,频繁刷新会导致程序变慢。
所以平时尽量用 '\n',少用 endl。


二、C 风格输入输出:printf 和 scanf

2.1 为什么需要它们?

scanf / printf 是 C 语言的输入输出函数,通常比默认的 cin / cout 快。它们直接和底层打交道,没有 C++ 流的那些复杂机制。

使用时需要包含头文件:

#include <cstdio>

2.2 scanf 的基本用法

int n;
scanf("%d", &n);
  • %d 表示读入一个 int。
  • &n 是取地址,scanf 需要知道变量存在哪里,才能把读到的值写进去。
  • 忘记写 & 是新手常见错误,会导致程序崩溃或乱码。

读多个变量:

int a, b;
scanf("%d%d", &a, &b);

读 long long:

long long x;
scanf("%lld", &x);

读 double:

double d;
scanf("%lf", &d);

读字符:

char ch;
scanf(" %c", &ch); // 前面的空格可以跳过空白字符

读字符串(不含空格):

char s[100];
scanf("%s", s); // 数组名本身就是地址,不用加 &

2.3 printf 的基本用法

int n = 10;
printf("%d\n", n);
  • %d 表示输出一个 int。
  • \n 是换行符。
  • 可以一次输出多个:
int a = 1, b = 2;
printf("%d %d\n", a, b);

输出 long long:

long long x = 1234567890123LL;
printf("%lld\n", x);

输出 double:

double d = 3.14159;
printf("%.2f\n", d); // 保留两位小数

输出字符和字符串:

char ch = 'A';
char s[] = "hello";
printf("%c\n", ch);
printf("%s\n", s);

2.4 常见格式符总结

类型 scanf 格式符 printf 格式符
int %d
long long %lld
char %c
double %lf %f
float %f
字符串 %s

注意:double 输入用 %lf,输出用 %f,不要弄混。

2.5 注意事项

  • scanf 的格式符必须和变量类型匹配,否则会读错。
  • printf 的格式符也必须匹配,否则输出可能乱码。
  • scanf 中除了 %c 以外的格式符会自动跳过前面的空白字符(空格、换行、制表符)。
  • printf 不会自动换行,需要手动加 \n。

三、让 cin/cout 变快:解绑同步流

如果你还是喜欢用 cin / cout,但又嫌它慢,可以在 main 函数开头加上这两句:

ios::sync_with_stdio(false);
cin.tie(nullptr);

完整示例:

#include <iostream>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;
    cout << n << '\n';
    return 0;
}

3.1 这两句是什么意思?

  • ios::sync_with_stdio(false);
    关闭 C++ 标准流和 C 标准流的同步。默认情况下,cin / cout 会和 scanf / printf 共享缓冲区,保证混用时顺序正确。关闭后,cin / cout 可以独立使用自己的缓冲区,速度会快很多。

  • cin.tie(nullptr);
    解除 cin 和 cout 的绑定。默认情况下,每次从 cin 读数据之前,都会自动刷新 cout 的缓冲区。解除绑定后,cin 不再自动刷新 cout,减少了不必要的刷新操作。

3.2 注意事项

  • 解绑之后,不要混用 cin / cout 和 scanf / printf。因为两者不再同步,输出顺序可能会乱。
  • 如果一定要混用,就不要解绑。
  • cout.tie(nullptr); 一般不用写,因为 cout 很少需要绑定到其他流。

3.3 速度对比

解绑后,cin / cout 的速度已经接近 scanf / printf,对于大多数题目足够了。但如果题目卡常数特别严,还是需要手写快读。


四、手写快读:getchar 版本

4.1 getchar 是什么?

getchar() 是 C 语言的一个函数,从标准输入读取一个字符,返回它的 ASCII 码(整数)。读到文件结尾返回 EOF。

#include <cstdio>

int main() {
    char ch = getchar(); // 读一个字符
    return 0;
}

4.2 快读的原理

整数在输入中是一串字符,比如 123 是 '1'、'2'、'3'。
我们可以逐个字符读取,跳过空格和换行,遇到数字就把它拼成整数:

x = x * 10 + (ch - '0');

比如读 123:

  • 初始 x = 0
  • 读 '1':x = 0 * 10 + 1 = 1
  • 读 '2':x = 1 * 10 + 2 = 12
  • 读 '3':x = 12 * 10 + 3 = 123

这就是快读的核心。

4.3 基础整数快读

#include <cstdio>

inline int read() {
    int x = 0, f = 1;
    char ch = getchar();

    // 跳过非数字字符,同时处理负号
    while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar();
    }

    // 读取数字
    while (ch >= '0' && ch <= '9') {
        x = x * 10 + (ch - '0');
        ch = getchar();
    }

    return x * f;
}

使用:

int n = read();
int m = read();

解释:

  • inline 建议编译器内联,减少函数调用开销。
  • f 是符号,遇到 '-' 就变成 -1。
  • 第一个 while 跳过所有非数字字符,包括空格、换行、负号。
  • 第二个 while 处理连续的数字字符。
  • 最后返回 x * f,如果是负数就乘以 -1。

4.4 处理负数

上面的代码已经处理了负数:当遇到 '-' 时,f = -1,最后 return x * f。

注意:如果输入中可能有 '-' 出现在数字前面,第一个 while 会先读到 '-',设置 f = -1,然后继续读下一个字符,直到读到数字。

4.5 支持 long long

int 的范围大约是 (-2 \times 10^9) 到 (2 \times 10^9)。如果数字更大,需要读 long long。

inline long long readLL() {
    long long x = 0;
    int f = 1;
    char ch = getchar();

    while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar();
    }

    while (ch >= '0' && ch <= '9') {
        x = x * 10 + (ch - '0');
        ch = getchar();
    }

    return x * f;
}

使用:

long long x = readLL();

4.6 模板版本

如果不想为每种类型写一个函数,可以用模板:

template <typename T>
inline T read() {
    T x = 0;
    int f = 1;
    char ch = getchar();

    while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar();
    }

    while (ch >= '0' && ch <= '9') {
        x = x * 10 + (ch - '0');
        ch = getchar();
    }

    return x * f;
}

使用:

int n = read<int>();
long long m = read<long long>();

这样就能自动适配不同类型。

4.7 更快的 fread 版本(进阶)

getchar() 本身还是函数调用,每次读一个字符。如果数据量特别大,可以用 fread 一次读一大块到缓冲区,再从缓冲区里取字符。

#include <bits/stdc++.h>
using namespace std;

namespace FastIO {
    const int BUF = 1 << 20; // 1MB 缓冲区
    char buf[BUF];
    int p = 0, len = 0;

    inline int gc() {
        if (p == len) {
            len = (int)fread(buf, 1, BUF, stdin);
            p = 0;
            if (len == 0) return EOF;
        }
        return buf[p++];
    }

    template <typename T>
    inline T read() {
        T x = 0;
        int f = 1;
        int c = gc();

        while (c != '-' && (c < '0' || c > '9')) c = gc();
        if (c == '-') {
            f = -1;
            c = gc();
        }

        while (c >= '0' && c <= '9') {
            x = x * 10 + (c - '0');
            c = gc();
        }
        return x * f;
    }
}

using FastIO::read;

int main() {
    int n = read<int>();
    long long m = read<long long>();
    printf("%d %lld\n", n, m);
    return 0;
}

这个版本比 getchar 更快,但代码复杂一些。新手可以先掌握 getchar 版本,以后再学 fread 版本。

4.8 使用示例

#include <cstdio>

inline int read() {
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = x * 10 + (ch - '0');
        ch = getchar();
    }
    return x * f;
}

int main() {
    int n = read();
    int m = read();
    printf("%d %d\n", n, m);
    return 0;
}

输入:

10 20

输出:

10 20

五、速度对比与选择建议

方法 速度 易用性 适用场景
cin / cout 默认 慢 最简单 数据量小,练习
scanf / printf 较快 较简单 一般竞赛
cin / cout 解绑 简单 喜欢 C++ 流
getchar 快读 快 中等 数据量大,卡常
fread 快读 最快 较难 极限卡常

建议:

  • 新手先熟练 cin / cout。
  • 然后学 scanf / printf,了解格式符。
  • 再学解绑同步流,让 cin / cout 变快。
  • 最后学 getchar 快读,应对大数据。
  • fread 快读可以以后再看。

六、完整代码模板(getchar 快读)

#include <cstdio>

// 快读:支持 int
inline int read() {
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = x * 10 + (ch - '0');
        ch = getchar();
    }
    return x * f;
}

// 快读:支持 long long
inline long long readLL() {
    long long x = 0;
    int f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = x * 10 + (ch - '0');
        ch = getchar();
    }
    return x * f;
}

int main() {
    int n = read();
    long long m = readLL();
    printf("%d %lld\n", n, m);
    return 0;
}

这样从 cout 到 printf,再到解绑同步流,最后到 getchar 快读,每一步都讲清楚了。你可以按这个顺序慢慢学,先理解原理,再动手写几遍,就能掌握快读了。

0 条评论

目前还没有评论...