CC++ & Algorithm

C++输入输出:从cin/cout的“懒人福音”到scanf/printf的“精准手术刀”

前两天有个学生发来一段代码,满脸困惑:“老师,我用cin输入两个整数,本地跑得好好的,怎么一提交到OJ就超时?”我瞄了一眼题目——经典的A+B问题,输入规模只有两个数。那肯定不是性能问题。但当我看到他的循环里用cin读100万个int时,一切就明白了。

cin/cout和scanf/printf,看似都能完成“输入输出”这件小事,背后却是两套完全不同的哲学:一个像自动售货机,投币即得;一个像填表格,格子画好了往里塞。选错工具,轻则代码臃肿,重则性能崩盘。

cin/cout:程序员的“耳朵和嘴巴”

cin是C++程序里接收键盘输入的“耳朵”,cout是“嘴巴”。它们的使用极其自然:

int age;
cin >> age;          // 程序停住,等你输入数字
cout << "你今年" << age << "岁啦!" << endl;

从自动售货机投币口的比喻来看,>>好比一个滑梯,数据从键盘滑进变量这个“口袋”。但注意:口袋的形状(变量类型)必须和硬币(输入数据)匹配。你扔进一个字母,口袋可能撑破(程序崩溃)。

更妙的是,cin/cout支持链式输入输出:

cin >> a >> b;       // 空格或回车分隔
cout << a << " + " << b << " = " << a + b << endl;

这种“懒人福音”式的写法,让初学者几乎零门槛上手。但代价呢?性能损耗。cin/cout为了兼容C的输入输出流,与stdio的缓冲区同步。每次读写都要检查同步标志,大量数据时就成了性能杀手。

scanf/printf:上古神器,依然锋利

在C++的世界里,scanf和printf像两件古物,却至今活跃在算法竞赛和底层开发的战场上。它们的本质是格式化函数,利用占位符精确控制数据的“长相”。

int a, b;
scanf("%d %d", &a, &b);
printf("%d\n", a + b);

%d是整数占位符,&a表示取变量地址——这是新手最容易踩的坑。忘了加&,程序不会抱怨,但会把你输入的值存到奇怪的地方,轻则输出乱码,重则段错误。

生活中的比喻:printf就像你填一张表格,表格上预留了格子:姓名:%s,年龄:%d。你只需要按格子类型填写实际数据。而%.2f可以指定小数点后两位,像在表格里画好小数位数。

关键区别:scanf/printf没有cin/cout的同步开销,速度可以快5到10倍。在百万级数据的输入输出中,这个差距就是AC与TLE的分界线。

一道A+B问题,揭示选择的分水岭

来看这道经典的A+B问题:输入两个整数,输出它们的和。样例:

输入:3 5
输出:8

问题似乎简单到不值得讨论。但仔细想想:如果你用cin/cout,代码写起来当然舒服:

int a, b;
cin >> a >> b;
cout << a + b << endl;

但如果你面对的是100万个这样的“A+B”呢?或者竞赛中你只有1秒时间?cin/cout的同步开销就会暴露。解决方案有两个:要么在程序开头加一句 ios::sync_with_stdio(false); cin.tie(0); 关闭同步;要么直接上scanf/printf。我见过太多学生在没有关闭同步的情况下用cin读大数据,结果超时后找半天bug。

所以这道A+B题其实在考察:你是否清楚工具的特性,并能在简单场景中做出最合理的选择。单次读两个数,cin/cout和scanf/printf差别微乎其微,但养成好的选择习惯,能为后续复杂题目省下大量时间。

乘法竖式:printf的“格式魔法”现场

另一道题更有意思:输入两个100以内的正整数,以竖式输出乘法运算。比如输入89 13,输出类似:

   89
×  13
-----
  267
  890
-----
 1157

这题考察什么?格式化输出。用cout当然也能做,但要控制每行的宽度、对齐、前缀空格,写起来累死人。而printf用占位符的宽度修饰符可以优雅搞定:

printf("%4d\n", a);
printf("×%3d\n", b);
printf("-----\n");
printf("%4d\n", a * (b % 10));
printf("%3d\n", a * (b / 10));  // 注意缩进一位
printf("-----\n");
printf("%4d\n", a * b);

注意这里的%4d表示输出宽度4,右对齐。如果位数不足4,左边补空格。这正是printf的“模板填表”优势——你可以精确控制每个数字的位置,而cout的setw()虽然也能做到,但需要额外包含 <iomanip>,写法也更繁琐。

这道题完美展示了:当输出格式有复杂要求时,printf的声明式风格比cin/cout的过程式拼凑更直接。就像你写春联,用模板印字和一笔一画描,效率天差地别。

cin vs scanf:别忘了陷阱

那什么场景用cin,什么场景用scanf?我的建议是:

  • 日常练习、小数据、快速验证逻辑:cin/cout。代码简洁,大脑负担小。
  • 竞赛或处理大量数据(10^5以上):关闭同步的cin,或者直接scanf/printf。
  • 需要精确格式化输出:printf,省心。

但有些坑要注意:scanf读字符串时遇到空格会停止,而cin用>>则跳过空白字符后读入。cin如果读入类型不匹配,输入流会进入错误状态,后续所有输入都会失效。比如你期望整数,输入了字母,cin >> a 会失败,但不会崩溃,你需要手动处理。而scanf遇到匹配失败,会直接返回0(成功匹配项数),更透明。

一个选择题和填空题的启发

统计学生的反馈,我发现很多人分不清头文件和地址符。比如这道常见题:

在C++中,使用cin进行输入时,需要包含哪个头文件?

答案是 <iostream>。但总有人选 <cstdio> 或者 <string>。这暴露了一个认知断层:很多人只复制代码,从不关心头文件的角色。实际上,cin/cout的定义就在 <iostream> 里,它们不是语言内置关键字,而是标准库的对象。理解这点,你就能明白为什么 std::cin 前必须加上 std:: 或写 using namespace std;

另一道填空题:执行int a; double b; cin >> a >> b;后输入10 3.14,a和b的值分别是?答案是10和3.14。但有人误以为cin会像scanf那样需要占位符格式。其实cin会自动根据变量类型解析输入,遇到空格或回车分隔。这既是方便(不用记占位符),也是隐患(类型不匹配时行为复杂)。

回到最初的问题

那位学生为什么超时?因为他用cin在大循环里读了100万个int,却没有关闭同步。我给他加了ios::sync_with_stdio(false);,再跑就通过了。他恍然大悟:原来工具不仅关乎写代码的舒适度,还关乎运行效率。

如果你现在问我该学哪个,我的答案是:两个都要会。cin/cout是你的日常伙伴,scanf/printf是你的关键时刻利器。就像你既会用筷子吃饭,也会用刀叉切牛排——在不同的桌子前,选择不同的工具。

掌握它们,你就在输入输出这个看似简单的环节,拥有了真正的选择权。


关于作者

我是赵老师,持有 NOI 信息学奥赛教练证书,拥有 15 年以上的软件开发经验,从事信息学少儿编程教学已有 8 年时间。

这些年累计帮助 多名 学生通过编程特长升入自己心仪的目标学校。

如果你在编程学习上有任何疑问,欢迎联系我:18620372957(微信同号)

这篇文章对你有帮助吗?

成为第一个评价的人

想系统学习这个知识点?查看完整知识点 →