C++ 快读快写详解:读入整数与读入字符串(全网最详细)

C++ 快读快写详解:读入整数与读入字符串

整数读入输出

众所周知,快读快写是众多 OI 选手必须掌握的一项卡常小技巧,优秀的读入输出可以让你的代码跑得飞快,有时甚至比别人多得一些分数,那么如何写快读快写呢,那就引入下面这道题:题目跳转:P10815 【模板】快速读入。

在此注明,这不是一篇关于P10815 【模板】快速读入的题解(当然也算是吧),只是借用它来展现各种读入输出方法的速度对比。

引入

读入 \(n\) 个数,求它们的和。每个测试点时限 \(2.5s\)。

非常简单,对吧?但是 \(n\leq 1\times 10^8\),怎么读入这么多数成了难题。

直接这样写:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,x,sum;
int main(){
    cin>>n;
    for(ll i=1;i<=n;i++){
        cin>>x;
        sum+=x;
    }
    cout<<sum;
	return 0;
}

读入 \(10^8\) 的数据需要惊人的 \(67.526s\),这谁能接受?

先在此声明,本文在本地跑出来的时间均为在 Windows 下使用 DEV-C++ 编译后跑出来的时间,是只跑一组 \(10^8\) 的数据得出来的;在洛谷上的时间为所有洛谷提供的数据评测出来的时间。

先看一组对比数据:

  • 读入对比(以 \(1 MiB\) fread 为 \(1\))
方法 相对耗时 说明
fread 大块读 \(1.0\) 通常最快或接近最快;块 IO,库/系统调用开销被摊薄
cin.read 解绑 \(1.05\sim 1.6\) 块 IO,接近 fread;需自行缓冲并处理 gcount()
getchar_unlocked \(4\sim 10\) 逐字符无锁;常配合手写整数解析,通常很快
getchar \(8\sim 20\) 逐字符有锁和 stdio 开销;比 getchar_unlocked 慢
关流的 cin>> \(20\sim 60\) 关闭同步并 tie(nullptr) 后明显变快,但仍是流式 token 格式化提取
普通 scanf \(20\sim 70\) 有 stdio 锁和格式串解析;读整数通常比普通 cin>> 快
普通 cin>> \(50\sim 150\) 未关同步,含同步/刷新等开销,通常最慢之一
  • 写出对比(以 \(1 MiB\) fwrite 大块写为 \(1\))
方法 相对耗时 说明
fwrite 大块输出 \(1.0\) 通常最快或接近最快;需先构造缓冲区
cout.write 解绑 \(1.05\sim 1.8\) 块 IO,接近 fwrite;需自己管理输出缓冲区
putchar \(10\sim 30\) 逐字符有锁;写整数需手写转字符串
关流的 cout<< \(20\sim 50\) 关闭同步并 tie(nullptr) 后较快,但仍有格式化/类型处理开销
普通 printf \(20\sim 50\) 每次格式化有解析开销和 stdio 锁;大块 %s 会更快
普通 cout<< \(50\sim 150\) 未关同步,通常明显慢于关流 cout<< 和普通 printf

这里解释一下 cin.read 和 cin.get 到底是啥,其实你可以理解为:

  • cin.read 类似 fread,是块读;

  • cout.write 类似 fwrite,是块写。

以下按"从常见、易写到较快"的顺序介绍。不同平台、编译器、IO 环境下排名可能不同,表中相对耗时只表示数量级参考,不代表严格排名:

一、关闭同步流后的 cin、cout

这应该是许多 OI 选手在考场上最喜欢用的快读方式,因为它够用且非常好写。

关闭同步流后,cin/cout 与 scanf/printf 不再保证同步,千万不要混用。若必须混用,应重新打开同步并注意刷新,或全程只使用一套 IO。

但是它通过不了此题 ,本地实际读入 \(10^8\) 的数据需要 \(10.951s\),在洛谷上花了 \(6.11s\),点此查看,是无法接受的。

代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,x,sum;
int main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
    cin>>n;
    for(ll i=1;i<=n;i++){
        cin>>x;
        sum+=x;
    }
    cout<<sum;
	return 0;
}

二、C 风格输入的优化之 getchar

这就是非常实用的 getchar 读入方式,它是通过先读入字符,然后再转成数字的形式进行读入加速的。

代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll rd(){
	ll x=0,f=1;
	char ch=getchar();
	while(ch!=EOF&&(ch<'0'||ch>'9')){
		if(ch=='-'){
			f=-1;
		}
		ch=getchar();
	}
	if(ch==EOF){
		return x;
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch-'0');
		ch=getchar();
	}
	return x*f;
}
void write(ll x){
	if(x<0){
		x*=-x;
		putchar('-');
	}
	if(x>9){
		write(x/10);
	}
	putchar(x%10+'0');
}
ll n,sum;
int main(){
    n=rd();
    for(ll i=1;i<=n;i++){
        sum+=rd();
    }
    write(sum);
	return 0;
}

非常出乎意料,本地读入 \(10^8\) 的数据,getchar 居然用了 \(17.476s\)!比关同步流还慢!在洛谷上花了 \(4.20s\)(比关流稍快),点此查看,所以使用 getchar 的方式通过不了此题。

三、C 风格输入的优化之 getchar_unlocked

getchar_unlocked 是 getchar 的非线程安全版本,少了加锁开销,因此通常更快。正式比赛能否使用要看评测环境。

Linux 下一般用 getchar_unlocked;Windows/MinGW 下通常没有 getchar_unlocked,可用 _getchar_nolock 替代,写的时候要注意,下面的代码使用了宏定义的一些技巧,在 Windows 和 Linux 两种环境下都不会 CE。

代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#ifdef __unix__
#define gc getchar_unlocked
#else
#define gc _getchar_nolock
#endif
typedef long long ll;
ll rd(){
	ll x=0,f=1;
	char ch=gc();
	while(ch!=EOF&&(ch<'0'||ch>'9')){
		if(ch=='-'){
			f=-1;
		}
		ch=gc();
	}
	if(ch==EOF){
		return x;
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch-'0');
		ch=gc();
	}
	return x*f;
}
void write(ll x){
	if(x<0){
		x*=-x;
		putchar('-');
	}
	if(x>9){
		write(x/10);
	}
	putchar(x%10+'0');
}
ll n,sum;
int main(){
    n=rd();
    for(ll i=1;i<=n;i++){
        sum+=rd();
    }
    write(sum);
	return 0;
}

此方法在本地跑 \(10^8\) 的数据花费 \(4.499s\),在洛谷上跑出了 \(3.21s\) 的成绩,点此查看,已足够通过此题(但个人觉得还是慢!)。

四、fread 快读

fread 能将需要的文件部分读入内存缓冲区。由于 fread 是整段整段读取,所以比 getchar_unlocked 要快得多。

使用时一般把它写成宏定义的形式,既简洁又快速。

在Windows环境下,在 DEV-C++ 或 Visual Studio Code 里本地运行的时候,输入完了记得加 Ctrl+Z,才会执行程序。

在Linux环境下,在本地运行的时候,输入完了记得加 Ctrl+D,才会执行程序。

当然使用文件读入读出就不用执行 Ctrl+Z 或 Ctrl+D 操作。

使用时不能同时存在 cin/scanf/getchar 等(都用这么快的了还用啥 cin、scanf)。

代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
uint8_t buf[1<<20],*p1,*p2;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
ll rd(){
	ll x=0,f=1,ch=gc();
	while(ch!=EOF&&(ch<'0'||ch>'9')){
		if(ch=='-'){
            f=-1;
        }
        ch=gc();
	}
	if(ch==EOF){
		return x;
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch-'0');
		ch=gc();
	}
	return x*f;
}
void write(ll x){
	if(x<0){
		x=-x;
		putchar('-');
	}
	if(x>9){
		write(x/10);
	}
	putchar(x%10+'0');
}
ll n,sum;
int main(){
    n=rd();
    for(ll i=1;i<=n;i++){
        sum+=rd();
    }
    write(sum);
	return 0;
}

这种方法在本地读入 \(10^8\) 的数据只需 \(2.905s\),在洛谷上跑出了 \(2.33s\) 的成绩点此查看,是不是快了很多。

五、cin.read 读入

此方法需要关闭同步流,写法和 fread 方法类似,只有些许不同。

在Windows环境下,在 DEV-C++ 或 Visual Studio Code 里本地运行的时候,输入完了记得加 Ctrl+Z,才会执行程序。

在Linux环境下,在本地运行的时候,输入完了记得加 Ctrl+D,才会执行程序。

使用时不要同时用 cin>>、scanf、getchar 等其他读入方式,也不要和 fread 混用。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
uint8_t buf[1<<20],*p1,*p2;
#define gc() (p1==p2&&(cin.read((char*)buf,1<<20),p2=(p1=buf)+cin.gcount(),p1==p2)?EOF:*p1++)
ll rd(){
	ll x=0,f=1,ch=gc();
	while(ch!=EOF&&(ch<'0'||ch>'9')){
		if(ch=='-'){
            f=-1;
        }
        ch=gc();
	}
	if(ch==EOF){
		return x;
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch-'0');
		ch=gc();
	}
	return x*f;
}
void write(ll x){
	if(x<0){
		x=-x;
		putchar('-');
	}
	if(x>9){
		write(x/10);
	}
	putchar(x%10+'0');
}
ll n,sum;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
	n=rd();
    for(ll i=1;i<=n;i++){
        sum+=rd();
    }
    write(sum);
	return 0;
}

本地读入 \(10^8\) 的数据实测下来 \(2.812s\),非常惊人!在洛谷上跑出 \(2.33s\) 的成绩点此查看,也是非常不错。


以上就是个人认为比较快且好写实用的读入方法,当然还有其他的一些奇奇怪怪的读入方法,代码都非常多,很难记,而且都不太建议在正式 OI 比赛中使用,所以这里就不一一介绍了。


六、关于输出

因为一般题目输出都比较小,而且在输出方面优化的速度不是很多(相比于优化读入),所以这里就给出几种好写且快速的输出方法(以下几种输出方法均是将整数转化为字符输出,速度还可以,想要详细速度对比请往上看,慢的输出咱就不给了):

1. 递归

非常常见的一种写法,可能比后面的方法慢。

cpp 复制代码
void write(ll x){
	if(x<0){
		x=-x;
		putchar('-');
	}
	if(x>9){
		write(x/10);
	}
	putchar(x%10+'0');
}

2. 非递归

可能会比递归的方法快一点点,但也比后面的方法慢一点点。

这里字符数组 ch 的大小要按最大位数开够。以 long long 为例,最多 \(19\) 位数字,开 char ch[20]; 足够。

cpp 复制代码
void write(ll x){
	if(x<0){
		x=-x;
		putchar('-');
	}
    if(x==0){
	    putchar('0');
	    return;
	}
	ll top=0;
	char ch[20];
	while(x){
		ch[++top]=x%10+'0';
		x/=10;
	}
	for(ll i=top;i>=1;i--){
		putchar(ch[i]);
	}
}

3. fwrite 快写(递归版)

与 fread 对应,用于实现更快的快写。

使用时一定不要再出现 cout。

注意此写法要在主函数最后,即 return 0; 前写上:fls(),以便一次性输出并清空缓冲区。

配合 fread 使用(fread 的注意事项见上):

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
uint8_t buf[1<<20],*p1,*p2;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
ll rd(){
    ll x=0,f=1,ch=gc();
    while(ch!=EOF&&(ch<'0'||ch>'9')){
        if(ch=='-'){
        	f=-1;
		}
        ch=gc();
    }
	if(ch==EOF){
		return x;
	}
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch-'0');
        ch=gc();
    }
    return x*f;
}
uint8_t obuf[1<<20],*op=obuf;
#define fls() (fwrite(obuf,1,op-obuf,stdout),op=obuf,0)
#define pc(ch) (op==obuf+(1<<20)?fls():0,*op++=(ch))
void write(ll x){
    if(x<0){
		x=-x;
		pc('-');
	}
    if(x>9){
    	write(x/10);
	}
    pc(x%10+'0');
}
ll n;
int main(){
	n=rd();
	write(n);
    fls();
    return 0;
}

4. fwrite 快写(非递归版)

与 fwrite 的递归版相似,这里与普通的非递归的 write 函数几乎一样。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
uint8_t buf[1<<20],*p1,*p2;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
ll rd(){
    ll x=0,f=1,ch=gc();
    while(ch!=EOF&&(ch<'0'||ch>'9')){
        if(ch=='-'){
        	f=-1;
		}
        ch=gc();
    }
	if(ch==EOF){
		return x;
	}
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch-'0');
        ch=gc();
    }
    return x*f;
}
uint8_t obuf[1<<20],*op=obuf;
#define fls() (fwrite(obuf,1,op-obuf,stdout),op=obuf,0)
#define pc(ch) (op==obuf+(1<<20)?fls():0,*op++=(ch))
void write(ll x){
	if(x<0){
		x=-x;
		pc('-');
	}
    if(x==0){
	    pc('0');
	    return;
	}
	ll top=0;
	char ch[20];
	while(x){
		ch[++top]=x%10+'0';
		x/=10;
	}
	for(ll i=top;i>=1;i--){
		pc(ch[i]);
	}
}
ll n;
int main(){
	n=rd();
	write(n);
	fls();
	return 0;
}

5. cout.write 快写(递归版)

和 fwrite 方法差不多,这里和 cin.read 读入方法搭配(cin.read 注意事项见上)。

注意此写法要在主函数最后,即 return 0; 前写上:fls(),以便一次性输出并清空缓冲区。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
uint8_t buf[1<<20],*p1,*p2;
#define gc() (p1==p2&&(cin.read((char*)buf,1<<20),p2=(p1=buf)+cin.gcount(),p1==p2)?EOF:*p1++)
ll rd(){
	ll x=0,f=1,ch=gc();
	while(ch!=EOF&&(ch<'0'||ch>'9')){
		if(ch=='-'){
            f=-1;
        }
        ch=gc();
	}
	if(ch==EOF){
		return x;
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch-'0');
		ch=gc();
	}
	return x*f;
}
uint8_t obuf[1<<20],*op=obuf;
#define fls() (cout.write((char*)obuf,op-obuf),op=obuf,0)
#define pc(ch) (op==obuf+(1<<20)?fls():0,*op++=(ch))
void write(ll x){
    if(x<0){
		x=-x;
		pc('-');
	}
    if(x>9){
    	write(x/10);
	}
    pc(x%10+'0');
}
ll n;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
	n=rd();
	write(n);
    fls();
    return 0;
}

6. cout.write 快写(非递归版)

和 cout.write 递归版差不多,参照 fwrite 非递归版。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
uint8_t buf[1<<20],*p1,*p2;
#define gc() (p1==p2&&(cin.read((char*)buf,1<<20),p2=(p1=buf)+cin.gcount(),p1==p2)?EOF:*p1++)
ll rd(){
	ll x=0,f=1,ch=gc();
	while(ch!=EOF&&(ch<'0'||ch>'9')){
		if(ch=='-'){
            f=-1;
        }
        ch=gc();
	}
	if(ch==EOF){
		return x;
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch-'0');
		ch=gc();
	}
	return x*f;
}
uint8_t obuf[1<<20],*op=obuf;
#define fls() (cout.write((char*)obuf,op-obuf),op=obuf,0)
#define pc(ch) (op==obuf+(1<<20)?fls():0,*op++=(ch))
void write(ll x){
	if(x<0){
		x=-x;
		pc('-');
	}
    if(x==0){
	    pc('0');
	    return;
	}
	ll top=0;
	char ch[20];
	while(x){
		ch[++top]=x%10+'0';
		x/=10;
	}
	for(ll i=top;i>=1;i--){
		pc(ch[i]);
	}
}
ll n;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
	n=rd();
	write(n);
    fls();
    return 0;
}

总结

以上就是读入输出整数的非常实用的方法了,选择一个自己喜欢的并记住它,对你有很大好处。

一些东西我都封装成了函数或宏定义,这些东西都可以直接套的。比如既要读入输出整数,又要读入输出字符串,那么就可以把两个的封装好的函数都写上。

但一定要记牢哪些不可以混用,一般同类的输入输出就当 CP 记,下面给出一般几类 "CP":

读入 输出
cin>> cout<<
getchar putchar
getchar_unlocked putchar
fread fwrite
cin.read cout.write
cin.get cout.put
scanf printf

更准确地说:带自定义缓冲的 fread/cin.read 不要和 cin/scanf/getchar 混用;关闭同步后的 cin/cout 不要和 scanf/printf 混用;表中只是推荐搭配,不是唯一合法搭配。

一般有关 cin 和 cout 的快读快写方法都搭配关同步流操作。

如果还想看读入输出字符串的方法请往下看。


字符串读入输出

读入输出字符串的这几种方法之间还是有一定速度差距的:

  • 读入,以 \(1 MiB\) fread 为 \(1\):
方法 相对耗时 说明
fread 大块读 \(1.0\) 通常最快或接近最快
cin.read 解绑 \(1.05\sim1.6\) 块 IO,接近 fread
getline(cin,string) 解绑 \(2\sim5\) 有按行扫描和 string 复用开销
getchar_unlocked \(4\sim10\) 逐字符,但去掉锁
getchar \(8\sim20\) 逐字符,有锁和 stdio 开销
cin.get 解绑 \(8\sim25\) 逐字符,有 stream sentry 等开销
scanf("%c") \(20\sim50\) 逐字符格式化,最慢之一
cin>> 解绑 \(20\sim60\) 逐 token 格式化提取,含类型解析与空白跳过
  • 写出,以 \(1 MiB\) fwrite 为 \(1\):
方法 相对耗时 说明
fwrite 大块输出 \(1.0\) 通常最快或接近最快
cout.write 解绑 \(1.05\sim1.8\) 块 IO,接近 fwrite
printf("%s") 大块 \(1.2\sim2.5\) 一次格式化,通常可接受
putchar_unlocked \(5\sim15\) 逐字符无锁
putchar \(10\sim30\) 逐字符有锁
cout.put 解绑 \(10\sim30\) 逐字符流操作
printf("%c") \(20\sim50\) 逐字符格式化,非常慢
cout<< 解绑 \(20\sim50\) 格式化输出,有类型/格式标志处理开销

这里解释一下 cin.read 和 cin.get 到底是啥,其实你可以理解为:

  • cin.read 类似 fread,是块读;

  • cin.get 类似 getchar,是逐字符读;

  • cout.write 类似 fwrite,是块写;

  • cout.put 类似 putchar,是逐字符写。

下面按主观认为的速度由慢到快给出各类方法(速度和实测排名差不多),为了节省时间,读入和输出的方法我就不分开了,常见的搭配就放一起了:

一、普通/关流的 cin>>、cout<< 组合

注释掉 int main(){} 里的前三行就是普通的,不注释就是关流的。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
string s;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
	cin>>s;
	cout<<s;
	return 0;
}

二、scanf 与 printf 组合

这种方法适合给了上限的读入输出字符串。

string 不是 char 数组,&s 的类型是 string*,不是 char*。用 %s 读写 string 属于未定义行为。所以要用 char 数组读入。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn=1e6+5;//大小自行修改
char s[maxn];
int main(){
    scanf("%1000000s",s);//这里的 1000000 只是长度上限,而不是实际读入多少
    printf("%s",s);
	return 0;
}

三、getchar 与 putchar 组合

分为两种,一种是知道字符串长度的,一种是不知道的,"三"这里都给出,到后面的"四、五、六"就只给出"不知道字符串"的写法(其实知道长度的场景也可以改用"不知道长度"的写法,读完后按需截取;反之则不行),知道字符串长度的写法与"三"这里同理。

这里给出的是支持读入"大/小写字母"、"数字"的写法,如果读入字符串里有其他字符,可以加些特判(下面的"四、五、六"同理)。

1. 知道字符串长度

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll rd(){
	ll x=0,f=1,ch=getchar();
	while(ch!=EOF&&(ch<'0'||ch>'9')){
		if(ch=='-'){
            f=-1;
        }
        ch=getchar();
	}
	if(ch==EOF){
		return x;
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch-'0');
		ch=getchar();
	}
	return x*f;
}
string rds(ll len){
	string x="";
	ll ch=getchar();
	while(ch!=EOF&&(ch<'a'||ch>'z')&&(ch<'0'||ch>'9')&&(ch<'A'||ch>'Z')){
		ch=getchar();
	}
	if(ch==EOF){
		return x;
	}
	for(ll i=1;i<=len;i++){
		x+=ch;
		if(i<len){
			ch=getchar();
		}
	}
	return x;
}
void writes(const string &x){
	ll len=x.size();
	for(ll i=0;i<len;i++){
		putchar(x[i]);
	}
}
ll n;
string s;
int main(){
    n=rd();
    s=rds(n);
	writes(s);
	return 0;
}

2. 不知道字符串长度

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
string rds(){
	string x="";
	ll ch=getchar();
	while(ch!=EOF&&(ch<'a'||ch>'z')&&(ch<'0'||ch>'9')&&(ch<'A'||ch>'Z')){
		ch=getchar();
	}
	if(ch==EOF){
		return x;
	}
	while((ch>='a'&&ch<='z')||(ch>='0'&&ch<='9')||(ch>='A'&&ch<='Z')){
		x+=ch;
		ch=getchar();
	}
	return x;
}
void writes(const string &x){
	ll len=x.size();
	for(ll i=0;i<len;i++){
		putchar(x[i]);
	}
}
string s;
int main(){
    s=rds();
	writes(s);
	return 0;
}

四、getchar_unlocked 与 putchar 组合

这里只给出不知道字符串长度的写法且支持读入"大/小写字母"、"数字",如果读入字符串里有其他字符,可以加些特判。

下面的代码在 Windows 和 Linux 中均不会 CE。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#ifdef __unix__
#define gc getchar_unlocked
#else
#define gc _getchar_nolock
#endif
typedef long long ll;
string rds(){
	string x="";
	ll ch=gc();
	while(ch!=EOF&&(ch<'a'||ch>'z')&&(ch<'0'||ch>'9')&&(ch<'A'||ch>'Z')){
		ch=gc();
	}
	if(ch==EOF){
		return x;
	}
	while((ch>='a'&&ch<='z')||(ch>='0'&&ch<='9')||(ch>='A'&&ch<='Z')){
		x+=ch;
		ch=gc();
	}
	return x;
}
void writes(const string &x){
	ll len=x.size();
	for(ll i=0;i<len;i++){
		putchar(x[i]);
	}
}
string s;
int main(){
    s=rds();
	writes(s);
	return 0;
}

五、fread 与 fwrite 组合

这里只给出不知道字符串长度的写法且支持读入"大/小写字母"、"数字",如果读入字符串里有其他字符,可以加些特判。

使用时一定不要再出现 cin 或 cout,记得在 return 0; 前加 fls();。

在Windows环境下,在 DEV-C++ 或 Visual Studio Code 里本地运行的时候,输入完了记得加 Ctrl+Z,才会执行程序。

在Linux环境下,在本地运行的时候,输入完了记得加 Ctrl+D,才会执行程序。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
uint8_t buf[1<<20],*p1,*p2;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
string rds(){
	string x="";
	ll ch=gc();
	while(ch!=EOF&&(ch<'a'||ch>'z')&&(ch<'0'||ch>'9')&&(ch<'A'||ch>'Z')){
		ch=gc();
	}
	if(ch==EOF){
		return x;
	}
	while((ch>='a'&&ch<='z')||(ch>='0'&&ch<='9')||(ch>='A'&&ch<='Z')){
		x+=ch;
		ch=gc();
	}
	return x;
}
uint8_t obuf[1<<20],*op=obuf;
#define fls() (fwrite(obuf,1,op-obuf,stdout),op=obuf,0)
#define pc(ch) (op==obuf+(1<<20)?fls():0,*op++=(ch))
void writes(const string &x){
	ll len=x.size();
	for(ll i=0;i<len;i++){
		pc(x[i]);
	}
}
string s;
int main(){
    s=rds();
	writes(s);
	fls();
	return 0;
}

六、cin.read 与 cout.write 组合

这里只给出不知道字符串长度的写法且支持读入"大/小写字母"、"数字",如果读入字符串里有其他字符,可以加些特判。

记得在 return 0; 前加 fls();。

在Windows环境下,在 DEV-C++ 或 Visual Studio Code 里本地运行的时候,输入完了记得加 Ctrl+Z,才会执行程序。

在Linux环境下,在本地运行的时候,输入完了记得加 Ctrl+D,才会执行程序。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
uint8_t buf[1<<20],*p1,*p2;
#define gc() (p1==p2&&(cin.read((char*)buf,1<<20),p2=(p1=buf)+cin.gcount(),p1==p2)?EOF:*p1++)
string rds(){
	string x="";
	ll ch=gc();
	while(ch!=EOF&&(ch<'a'||ch>'z')&&(ch<'0'||ch>'9')&&(ch<'A'||ch>'Z')){
		ch=gc();
	}
	if(ch==EOF){
		return x;
	}
	while((ch>='a'&&ch<='z')||(ch>='0'&&ch<='9')||(ch>='A'&&ch<='Z')){
		x+=ch;
		ch=gc();
	}
	return x;
}
uint8_t obuf[1<<20],*op=obuf;
#define fls() (cout.write((char*)obuf,op-obuf),op=obuf,0)
#define pc(ch) (op==obuf+(1<<20)?fls():0,*op++=(ch))
void writes(const string &x){
	ll len=x.size();
	for(ll i=0;i<len;i++){
		pc(x[i]);
	}
}
string s;
int main(){
    s=rds();
	writes(s);
	fls();
	return 0;
}

七、读入整行(包含空格)的方法

使用 getline(cin,s) 的方法

搭配 cout<< 使用。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
string s;
int main(){
    getline(cin,s);
	cout<<s;
	return 0;
}

getchar、getchar_unlocked、fread、cin.read 通用版

这里就以 fread 为例,我相信你已经会变通了,其他三种方法同理,这里就不给出了。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
uint8_t buf[1<<20],*p1,*p2;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
string rds(){
	string x="";
	ll ch=gc();
	while(ch=='\n'){//跳过前导换行
		ch=gc();
	}
	while(ch!='\n'&&ch!=EOF&&ch!='\r'){
		x+=ch;
		ch=gc();
	}
	return x;
}
uint8_t obuf[1<<20],*op=obuf;
#define fls() (fwrite(obuf,1,op-obuf,stdout),op=obuf,0)
#define pc(ch) (op==obuf+(1<<20)?fls():0,*op++=(ch))
void writes(const string &x){
	ll len=x.size();
	for(ll i=0;i<len;i++){
		pc(x[i]);
	}
}
string s;
int main(){
    s=rds();
	writes(s);
	fls();
	return 0;
}

总结

以上就是读入输出字符串的非常实用的方法了,希望你能选择一个自己喜欢的并记住它。

其实并没有那么难,很多都是相通的,希望你在今后的 OI 生涯中,不会因读入输出卡常而烦恼。


声明

本文由作者费时费力 \(+\) 纯手工制作,均为原创,转载请跟我说一声,并注明出处。
如果您觉得有不正确或不妥的地方,欢迎在评论区指正,感谢!
如果觉得对你有帮助,就帮忙点个赞吧!(❁´◡`❁)