C++ 基本的输入输出、缓冲机制、cerr引发的超长折磨、同步问题
菜鸟教程:基本的输入输出
关于iostream等基础知识点,原汁原味的豆包问答。
1、Person p;这里Person叫类,p叫Person的实例,也叫对象
2、我们一般用cin/cout是因为有iostream头文件,首先要知道的是,比如#include<cmath>就是在引入头文件,然后主文件.cpp里就可以用这个头文件里的一些函数
头文件传统是.h,现代写法是.hpp,但我实际没咋用过,后来为与 C 语言区分及支持命名空间等新特性,C++ 标准库采用无后缀形式,cmath 就对应原来 C 的 math.h,它本质也是头文件,只是在新 C++ 标准下省略了 .h 后缀。
.cpp 主文件里通过在文件开头写 #include <iostream> 语句就能引入它,编译器会自动到标准库目录查找该头文件。所以iostream其实是个头文件,#include<iostream>,这个头文件里又有一个跟文件同名的iostream 类,这个类准确说是派生类
(
在 C++ 中 “派生” 指一个新类(子类、派生类)可以继承另一个或多个已有类(父类、基类)的属性和方法,iostream类从istream和ostream派生,意味着它能拥有istream的输入功能与ostream的输出功能,实现代码复用和功能扩展
)
继承了原本就有的i/ostream使得引入iostream头文件后就可以用里面的iostream类里的cin/cout这些玩意,但更专业的说法是:
cin 是 istream 类的全局对象,用于输入,cout 是 ostream 类的全局对象,用于输出,而 iostream 类继承 istream 和 ostream 但 cin、cout 并非其成员,iostream 类自身较少直接使用。至于很少用为啥还派生了这么个玩意豆包没说清楚。
而cerr 是 ostream 类的一个全局对象,在包含 <iostream> 头文件后就能使用,它关联到标准错误输出设备(一般是屏幕),主要用于输出错误信息,与 cout 不同的是它不经过缓冲区,会立即输出内容,以确保错误信息能及时显示。
但,引入 <iostream> 头文件仅声明了 cin 等对象,不能直接用,C++为避免命名冲突,把标准库内容放在 std 命名空间中,若不指定命名空间(如 std::cin)或开启对该命名空间的使用(如 using namespace std;),编译器就不知道去哪里找 cin,所以不能直接用。
综上,我自己的理解且经过豆包肯定:意思是引入iostream头文件,也就包涵头文件里定义的了istream类和ostream类,这里分别又包涵cin/cou不算完,不知道出于什么考虑又有个跟头文件名iostream一样的派生类,整合了istream类和ostream类里的cin和cout,但没啥具体用处,接下来又把他们统一放到了std命名空间里。这样就可以在cpp文件里用cin/cout了
更加专业的解释:
using namespace std; 引入的 std 命名空间包含标准库众多内容,有 cin、cout 等输入输出对象,string 类、容器类(如 vector、map)、算法(如 sort)等;C++ 里还有用户自定义命名空间,如 namespace MyNamespace { ... } 可定义自己的类、函数等,此外部分第三方库也有自身命名空间。这块我之前只是朦朦胧胧的知道,但没法用这样专业的话叙述出来,这下起码更加了解一点了
——————————————————————————————————————————————————
关于cerr例子,结合豆包和通义千问,豆包的UI界面真的无敌,爱不释手,有时候回答有问题,就用通义千问试试,找出问题所在,就把这个问题当作已知,去提供给豆包,让他顺着这个继续解答
涉及关闭同步的问题不去回忆之前刷题的博客了,重新问
每秒的时钟周期数(CLOCKS_PER_SEC)
clock() 函数返回从程序启动到调用该函数时所使用的 CPU 时钟周期数
clock_t:这是一个数据类型,在<ctime>头文件中定义。它用于表示 CPU 时钟时间,本质上是一个整数类型,用于存储从程序启动开始到当前时刻所经过的时钟周期数。不同系统上clock_t的具体类型可能不同,但都能满足存储时钟周期数的需求。clock():这是<ctime>头文件中声明的一个函数,该函数会返回从程序启动到调用该函数时所消耗的 CPU 时钟周期总数。其原型为clock_t clock();,调用时不需要传入参数,直接返回一个clock_t类型的值。
这段代码有话说,见后面的5、,这段代码对于同步这个事,是有问题的
#include <iostream>
#include <ctime>
using namespace std;
int main() {
// std::ios::sync_with_stdio(false);
clock_t start = clock();
for (int i = 0; i < 10000000; ++i) {
// std::cout << i;
}
clock_t end = clock();
cout<<start<<" "<<end<<endl;
// std::cout << "\n" << (double)(end - start) / CLOCKS_PER_SEC << " seconds" << std::endl;
}
2、为何关闭同步start是0,开启同步start是1?
关闭同步情况:
当关闭同步(
std::ios::sync_with_stdio(false);)时,程序启动后的初始化步骤相对较少。clock()函数在调用时,可能因为程序还没来得及消耗一个完整的 CPU 时钟周期就完成了初始化并执行到记录start的代码处,所以返回值为 0。这意味着在这个时间点,从程序启动开始还未跨过一个时钟周期的边界。开启同步情况:
开启同步时,
iostream与 C 标准输入输出流会进行同步操作,这会带来额外的初始化开销。这些额外操作可能刚好消耗了一个 CPU 时钟周期,使得clock()函数在调用时返回 1,表示从程序启动到此刻已经经过了一个时钟周期。
3、所谓同步,都说是iostream 与 C 标准输入输出流(如 stdio.h 中的 printf、scanf)的同步,我一直以为是 cin 和 cout 之间、printf 和 scanf 之间同步,但其实是 C++ 的 iostream 库(包含 std::cin、std::cout 等)和 C 的标准输入输出库(包含 printf、scanf 等)之间是同步的
Q:
我终于懂了,我一直纳闷,因为我觉得程序顺序执行,咋还有同步的事,现在懂了,大概是程序看上去顺序执行,但每条语句有不同的时钟周期,会出现上面那句话没跑完,就读到下面那句话了,然后又由于scanf、printf和cin、cout隶属于不同的库,所以可能互相争夺缓冲区或者某个计算机内部的底层管理资源机制啥的,所以需要他俩同步上?
A:
从输入角度理解
C语言stdin
C++std::cin
标准输入流(如 C++ 里的 std::cin)为程序提供了一种标准化的方式来获取外部数据,就好像给程序开了一扇 “数据入口”,
从输出角度理解
输出:
C语言:stdout
C++:std::cout
错误流:
C语言:stderrC++:std::cerr
标准输出流(如 C++ 里的 std::cout)和标准错误流(如 std::cerr)为程序提供了将内部数据展示给外部的标准化途径,就如同给程序设置了 “数据出口”,并且这些出口也有各自的管理机制。
stdout 是标准输出流(Standard Output Stream)的标识符。在 C 语言里,它是一个指向 FILE 类型对象的指针,代表程序与标准输出设备(通常是显示器)之间的连接通道。printf 基于 stdout 工作:printf 函数实际上是对 stdout 流进行操作的一个高级封装。当你调用 printf 函数时,它会将格式化后的数据写入到 stdout 流,最终通过 stdout 流将数据显示在标准输出设备上。Q:一般用pirntf函数,不用stdout流,而C++ 用std::cout流是吗
A:
printf 常用的原因:它提供了强大的格式化输出功能,格式控制符丰富,能满足各种复杂的输出格式需求。例如,能方便地输出不同进制的整数、控制浮点数精度、实现字符串的对齐等。代码编写简单直观,易于上手。stdout 也有其用武之地:在某些特定场景下会直接使用 stdout。比如自定义输出函数时,或是需要更底层地控制输出操作,像使用 fputs 函数直接向 stdout 写入字符串。
#include <stdio.h>
int main() {
fputs("Hello from stdout!\n", stdout);
return 0;
}
5、回头说1、那段同步有问题的代码,由于 std::cout << i; 这行被注释掉了,实际上并没有进行标准输出操作,所以此时 std::ios::sync_with_stdio(false); 开启与否对性能并没有影响。
也就是说,注释掉std::cout会使得start和end值几乎一样
不过若取消这行注释,让代码真正进行标准输出,关闭同步通常会使程序运行更快,加同步就会增加开销,下面为你详细解释原因:
std::ios::sync_with_stdio(false);,就不应该再混合使用 C 和 C++ 的输入输出函数,否则可能会导致输出结果混乱。
这段原汁原味的解释非常棒,那又有个问题,
#include <iostream>
#include <ctime>
using namespace std;
int main() {
std::ios::sync_with_stdio(false);
clock_t start = clock();
for (int i = 0; i < 1000; ++i) {
std::cout << i;
}
clock_t end = clock();
cout<<"\n\n"<<start<<" "<<end<<endl;
// std::cout << "\n" << (double)(end - start) / CLOCKS_PER_SEC << " seconds" << std::endl;
}
关同步后start和end都是一样的值,我经过跟豆包来回请教,最后懂了很多东西
逐步剖析这玩意,唉我学东西咋这么费劲,别人看这一节估计很快,而我一节搞出这么多东西,那个林纳斯・托瓦兹为了学习Linux开发了Unix,我tm:
首先要知道,1000这个循环量级,比较小,代码的执行速度非常快,
clock()函数的精度不足以捕捉到循环执行所花费的时间,所以这段代码输出的start和end值是一样的(基本上)
原因分析:
#include <iostream> #include <ctime> using namespace std; int main() { std::ios::sync_with_stdio(false); // 记录开始时间 clock_t start = clock(); // 增加循环次数以延长执行时间 for (int i = 0; i < 1000000; ++i) { std::cout << i % 10; // 输出 i 对 10 取模的结果,避免输出过多内容 } // 记录结束时间 clock_t end = clock(); // 输出开始和结束的时钟周期数 cout << "\n\n" << start << " " << end << endl; // 计算并输出执行时间 std::cout << "\n" << (double)(end - start) / CLOCKS_PER_SEC << " seconds" << std::endl; }
至此知识点铺垫结束,那cerr跟cout差别是啥?
大模型依旧解释的混乱(没试deepseek,总崩溃,轻易不想用那玩意),但又一个成长进步是,哪怕大模型解释的有问题但基本可以理解,不用追问太多了
可是半天没搞出来,真的难受,不管了,以后实际用到再说吧
通义千问和豆包都举例子的是sleep的例子,
#include <iostream>
#include <chrono>
#include <thread>
int main() {
std::ios::sync_with_stdio(false); // 关闭与C标准I/O的同步
std::cout << "A";
std::cerr << "B";
// 让程序暂停一段时间,以便查看缓冲区内容
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout.flush();
// // 再次让程序暂停一段时间,确保所有输出都被显示
std::this_thread::sleep_for(std::chrono::seconds(2));
}
VS和codeblock都是输出AB,理应输出BA,增加输出循环也不行
豆包、通义千问,甚至问了总崩溃的Deepseek都没有能说清楚的,Deepseek讲了很多,看出来思考确实深,但也总是重复一样的话,放弃了
几个知识点:
1、
#include <iostream>
int main() {
std::cout << "A";
return 1;
}
return 1;并不是异常终止。程序执行完毕并正常退出,只不过它的退出状态码不是通常表示成功的 0,而是 1。在C++(以及许多其他语言)中,main 函数的返回值被用来指示程序的执行状态:返回 0 通常表示程序成功执行完毕,而非零值则常用于表示某种形式的错误或异常情况。
因此,虽然从退出状态码来看可能暗示有某种“异常”或特殊情况发生,但就程序运行过程而言,它确实正常完成了其预定操作,并没有出现未捕获的异常或其他导致程序崩溃的情况。
2、
#include <iostream>
int main() {
std::ios::sync_with_stdio(false);
for(int i=0;i<1000000;i++){
std::cout << "A" ;
}
for(int i=0;i<1000;i++){
std::cerr << "B";
}
}
也是先输出一堆A,再输出B,无解妈逼的!
行缓冲:遇到换行符、缓冲区满、手动刷新或程序结束时,缓冲区数据会输出,常用于标准输入输出流,便于按行处理数据;
全缓冲:缓冲区填满、手动刷新或程序结束时,输出数据,多用于文件操作以减少系统调用提升性能。
关联到控制台就是行缓冲
cerr始终无缓冲
cout有
printf豆包说也有缓存,举的例子是,
#include <iostream>
#include <chrono>
#include <thread>
#include<stdio.h>
#include <unistd.h>
using namespace std;
int main() {
//std::ios::sync_with_stdio(false);
printf("A");
sleep(5); // 暂停5秒
printf("\n"); // 输出换行符,刷新缓冲区
}
说5s后才会输出A,可是妈逼的也不对,~~~~(>_<)~~~~
—————————————————至此cerr结束————————————————————

通过这些小实例,我们无法区分 cout、cerr 和 clog 的差异,但在编写和执行大型程序时,它们之间的差异就变得非常明显。所以良好的编程实践告诉我们,使用 cerr 流来显示错误消息,而其他的日志消息则使用 clog 流来输出。
弄死我得了
我学一节东西可真JB费劲唉
~~~~(>_<)~~~~╮(╯▽╰)╭
###:
看了下代码随想录公众号,好像很多985研究生都向他问问他,呵呵研究生算个啥,总tm推送说讲算法,一看他的算法网站,真tm水,呵呵,一整还tm周末手撕一个操作系统,我去你妈的吧,你咋不开发个除了安卓、鸿蒙、IOS之外的操作系统,无非就是像我之前复现卡内基梅隆大学人体姿态识别项目一样,那些高大上的项目狗JB用没有,不如脚踏实地学点调试代码这些真东西,简历名字都tm看着贼吓人,一群搞臭行业的傻逼垃圾
不知道为啥,对鱼皮,吴师兄莫名的好感
###:不知道为啥,记录总感觉没不记录深刻
###:
收获:
1、刷题收获可以看懂别人算法代码
2、问各种大模型收获,灵活了,会的逐渐变多,可以不用强迫症全部复制粘贴QA问答也不用上翻强迫记录问答,而是有清晰的思路,知道哪句是重点,而其他回答就不用去看
3、哪怕大模型解释的有问题但基本可以理解,不用追问太多了
4、那些用阳寿烂玩Deepseek的狗逼死一死


浙公网安备 33010602011771号