Fork me on GitHub

C++ 基本的输入输出、缓冲机制、cerr引发的超长折磨、同步问题

菜鸟教程:基本的输入输出 

 

关于iostream等基础知识点,原汁原味的豆包问答

1、Person p;这里Person叫类,pPerson的实例,也叫对象

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 类从 istreamostream 派生,意味着它能拥有 istream 的输入功能与 ostream 的输出功能,实现代码复用和功能扩展

继承了原本就有的i/ostream使得引入iostream头文件后就可以用里面的iostream类里的cin/cout这些玩意,但更专业的说法是:

cinistream 类的全局对象,用于输入,coutostream 类的全局对象,用于输出,而 iostream 类继承 istreamostreamcincout 并非其成员,iostream 类自身较少直接使用。

至于很少用为啥还派生了这么个玩意豆包没说清楚。

 

cerrostream 类的一个全局对象,在包含 <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了

 

更加专业的解释:

①头文件与类的定义

<iostream> 是一个头文件,它的主要作用是提供输入输出流相关功能的声明和定义。在这个头文件中,定义了多个类,其中就包括 istreamostreamiostream 等类。
  • istream 类专门用于处理输入操作,它定义了一系列与输入相关的成员函数和操作符。
  • ostream 类专门用于处理输出操作,定义了与输出相关的成员函数和操作符。
  • iostream 类通过多重继承自 istreamostream,从理论上具备了输入和输出的双重能力。

② 对象的定义

cincout 是在 <iostream> 头文件中定义的全局对象:
  • cinistream 类的一个实例对象,关联到标准输入设备(通常是键盘),用于从标准输入读取数据。
  • coutostream 类的一个实例对象,关联到标准输出设备(通常是显示器),用于向标准输出写入数据。

③ 命名空间的作用

为了避免不同库或者用户自定义代码之间出现命名冲突,C++ 引入了命名空间的概念。标准库中的所有标识符(类、对象、函数等)都被放在了 std 命名空间里。所以 cincoutistreamostream 等都属于 std 命名空间。

 

using namespace std; 引入的 std 命名空间包含标准库众多内容,有 cincout 等输入输出对象,string 类、容器类(如 vectormap)、算法(如 sort)等;C++ 里还有用户自定义命名空间,如 namespace MyNamespace { ... } 可定义自己的类、函数等,此外部分第三方库也有自身命名空间。
 
 

这块我之前只是朦朦胧胧的知道,但没法用这样专业的话叙述出来,这下起码更加了解一点了

 

——————————————————————————————————————————————————

 

关于cerr例子,结合豆包和通义千问,豆包的UI界面真的无敌,爱不释手,有时候回答有问题,就用通义千问试试,找出问题所在,就把这个问题当作已知,去提供给豆包,让他顺着这个继续解答

涉及关闭同步的问题不去回忆之前刷题的博客了,重新问

知识点:

1、时钟周期:

简单理解。计算机程序执行过程中包含一系列操作,如数据读取、运算、存储等,这些操作的完成都需要时间。时钟周期就像是一把 “时间尺子”,作为一个基本度量单位来衡量程序执行的耗时。

CPU 以时钟周期为节奏来执行指令,每完成一个或几个基本操作就消耗一个或多个时钟周期。通过统计程序执行过程中消耗的时钟周期总数,结合时钟频率(时钟周期的倒数),就能计算出程序实际运行所花费的时间,从而评估程序的性能。

 

每秒的时钟周期数(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 中的 printfscanf)的同步,我一直以为是 cincout 之间、printfscanf 之间同步,但其实是 C++ 的 iostream 库(包含 std::cinstd::cout 等)和 C 的标准输入输出库(包含 printfscanf 等)之间是同步的

库内同步情况

  • iostream 库内std::cinstd::cout 是 C++ iostream 库中的输入输出对象,它们在库内部有自己的缓冲区管理和操作机制,本身会按照一定规则处理输入输出,例如 std::cout 会将输出内容先存到缓冲区,在满足一定条件(如缓冲区满、遇到 std::endl 等)时才将内容输出到终端。它们之间不存在为了保证与其他库同步而进行的额外操作。
  • C 标准输入输出库内printfscanf 是 C 语言标准输入输出库中的函数,它们也有自己的一套缓冲区管理方式。同样,在这个库内部,这些函数会按照既定规则工作,不需要为了彼此同步而做额外处理。

库间同步情况

在默认同步状态下,C++ 的 iostream 库和 C 的标准输入输出库之间需要保持一致,也就是要保证输入输出的顺序正确。这意味着当你在程序中混合使用 std::coutprintf 时,系统要确保它们输出的内容不会混乱。例如:
#include <iostream>
#include <cstdio>
int main() {
    std::ios::sync_with_stdio(false);
    std::cout << "cout ";//<<std::endl;
    printf("printf ");
}
为了让上述代码中 std::coutprintf 的输出能按代码顺序正确显示,系统在底层会进行一些同步操作,比如在每次使用 std::coutprintf 时可能会锁定共享资源、刷新缓冲区等,以避免数据交错或丢失。而使用 std::ios::sync_with_stdio(false); 关闭同步后,就不会进行这些跨库的同步操作了,但此时就不能再混合使用这两个库的输入输出函数了。
 
刷新缓冲区通常会把缓冲区里的数据输出到屏幕,但不局限于屏幕,也可能输出到文件、网络等其他目标设备。

Q:

我终于懂了,我一直纳闷,因为我觉得程序顺序执行,咋还有同步的事,现在懂了,大概是程序看上去顺序执行,但每条语句有不同的时钟周期,会出现上面那句话没跑完,就读到下面那句话了,然后又由于scanf、printf和cin、cout隶属于不同的库,所以可能互相争夺缓冲区或者某个计算机内部的底层管理资源机制啥的,所以需要他俩同步上?

A:

确实是因为 scanfprintf 所在的 C 标准输入输出库和 cincout 所在的 C++ iostream 库各自有独立的缓冲区管理机制,在默认情况下,程序为了保证混合使用这些输入输出函数时数据按代码顺序正确呈现,避免因不同库对缓冲区或底层资源的操作时序差异(如可能出现某个库还没处理完数据,另一个库就开始操作缓冲区导致数据混乱),所以需要进行同步。
 
那上面的代码,当关闭同步(std::ios::sync_with_stdio(false);)后,iostream 库和 C 标准输入输出库不再保持同步,各自按自己的缓冲区管理机制操作。std::cout 有自己的缓冲区,输出数据时先存于该缓冲区,未遇强制刷新条件(如 std::endl、缓冲区满等)不会立即输出;而 printf 是 C 标准库函数,它的缓冲区刷新规则不同,可能调用后就直接刷新输出了,所以在这个代码里会先看到 printf 的输出。
 
4、关于流,标准流
scanf和printf本质是函数,不是流
 
std::cout 是标准输出流对象,
std::cout 本质上是 std::ostream 类的一个全局对象,它关联到标准输出设备(通常是显示器)。std::ostream 类是 C++ 标准库中用于输出操作的流类,它提供了一系列的成员函数和重载的运算符,使得可以方便地将各种类型的数据输出到与之关联的设备上。
 
流是一种抽象的数据传输通道,具有自身的状态和管理机制;而 scanfprintf 是用于对标准输入输出流(stdinstdout)进行读写操作的函数,它们依赖于流来完成数据的输入输出,但本身并不具备流的特征,所以它们不是流。

 

 

在计算机编程里,流是一种抽象的概念,代表着数据的序列,就像数据在一个通道中流动。
这个 “管道” 可以连接不同的数据源和数据目的地,比如文件、内存、网络套接字或者标准输入输出设备(如键盘、显示器)等。

 

流提供了一种统一的方式来处理数据的输入和输出,而不需要关心数据的具体来源和去向。你可以把流想象成一个传送带,数据就像传送带上的物品,从一端(数据源)移动到另一端(数据目的地)。通过流,我们可以按照一定的顺序逐个处理数据元素,而不用考虑数据的存储细节。

它提供了一种统一的方式来处理数据的输入和输出,并且隐藏了数据传输的底层细节。流可以连接不同的数据源和数据目的地,如文件、网络、标准输入输出设备等。在 C 和 C++ 中,流具有状态(如是否打开、是否出错等),还能进行缓冲管理等操作。

 

标准流的概念

从输入角度理解

C语言stdin

C++std::cin

标准输入流(如 C++ 里的 std::cin)为程序提供了一种标准化的方式来获取外部数据,就好像给程序开了一扇 “数据入口”,

从输出角度理解

输出:

C语言:stdout

C++:std::cout

错误流:

C语言:stderr
C++: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值几乎一样

不过若取消这行注释,让代码真正进行标准输出,关闭同步通常会使程序运行更快,加同步就会增加开销,下面为你详细解释原因:

①. 标准流同步机制的工作原理

在 C++ 里,标准输入输出流(如 std::cinstd::cout)和 C 语言的标准输入输出流(像 stdinstdout)默认是同步的。这种同步机制确保了在混合使用 C 和 C++ 的输入输出函数时,输出结果的顺序符合预期。
例如,你可以在代码里交替使用 printfstd::cout,而不用担心输出会混乱。为了实现这一点,标准库需要做一些额外的工作。

②. 同步机制带来的性能开销

  • 频繁刷新缓冲区std::cout 为了和 stdout 保持同步,每次输出操作后都可能需要刷新缓冲区。刷新缓冲区意味着把缓冲区里的数据立刻发送到对应的外部设备(如显示器),这是一个相对耗时的操作。因为与外部设备的交互通常比内存操作慢很多。
  • 线程安全的锁开销:同步机制要保证线程安全,这通常会用到锁。在多线程环境下,线程需要获取锁才能访问标准输入输出流,获取和释放锁会带来一定的时间开销。

③. 关闭同步后的性能优势

当调用 std::ios::sync_with_stdio(false); 后,C++ 的标准输入输出流和 C 语言的标准输入输出流不再同步,这样就能避免上述的性能开销:
  • 减少刷新操作:关闭同步后,std::cout 可以更自主地管理自己的缓冲区。数据会在缓冲区里积累,直到缓冲区满或者显式调用 std::endlstd::flush 时才进行输出,减少了与外部设备的交互次数,从而提高了性能。
  • 避免锁开销:不再需要保证和 C 标准输入输出流的同步,也就不需要使用锁机制,避免了锁的获取和释放带来的时间消耗。
需要注意的是,一旦调用 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() 函数的精度不足以捕捉到循环执行所花费的时间,所以这段代码输出的startend值是一样的(基本上)

 

原因分析:

clock() 函数返回从程序启动到调用该函数时所使用的 CPU 时钟周期数。CLOCKS_PER_SEC 是一个常量,表示每秒的时钟周期数。在大多数系统中,clock() 函数的分辨率是有限的,通常为 10 毫秒(即 0.01 秒)。
当代码执行速度极快时,例如你代码中的循环只是简单地输出从 0 到 999 的整数,这个过程可能在不到一个时钟周期的时间内完成。因此,startend 所记录的时钟周期数是相同的。
 

解决办法:

#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;
}
 

非常完美了大概2k个时钟周期,但又tm出先个问题,我发现开同步后,居然等半天程序都不结束,难道开同步又多输出了啥东西?不应该啊

 

首先理解一个东西:(开同步后就可以捕获到有显而易见差值的时钟周期数,关同步性能提升这么多吗?)

原理

在 C++ 中,std::ios::sync_with_stdio(false); 这行代码的作用是解除 iostreamstdio 标准输入输出流的同步。在默认情况下,iostreamstdio 是同步的,这意味着它们会协调操作,以确保混合使用 coutiostream 库)和 printfstdio 库)等函数时不会出现输出混乱的情况。
然而,这种同步是有代价的,它会增加额外的开销。当你调用 std::ios::sync_with_stdio(false); 时,就关闭了这种同步机制,从而减少了额外的开销,使得 iostream 的操作变得更快。

时钟周期数捕获

当开启同步时,每次 std::cout 输出操作都会有额外的同步开销,这会使得整个循环的执行时间变长。由于 clock() 函数有一定的分辨率,当执行时间足够长,超过了其分辨率时,就能明显捕获到 startend 之间的时钟周期数差值。
而关闭同步后,代码执行速度大幅提升,当循环次数较少时,整个循环可能在 clock() 函数的分辨率时间内完成,就会出现 startend 记录的时钟周期数相同的情况。但当增加循环次数后,还是能记录到差值。

性能提升

关闭同步确实能带来性能提升,尤其是在大量使用 iostream 进行输入输出操作时。因为避免了同步的开销,cout 等操作可以更高效地执行。
 

 

那为啥输出不停止了?

要知道:同步是保证cout和printf同步,cin和scanf同步,尽管没有printf同步机制也会这样做

开启同步的情况

当开启同步时,每次 std::cout 输出都会进行同步操作,以保证与 stdio 库的兼容性。在循环 1000000 次输出的过程中,这种同步开销会不断累积。而且,iostream 为了保证线程安全和同步性,会有一些额外的锁机制和缓冲管理,这些都会增加每次输出的时间。因此,整个循环的执行时间会变得很长,你会感觉控制台很久都没有停止。实际确实如此,关同步会发现输出结果是,控制台右边那个滚轮可以从上拉到下占满整个,而开同步的时候看似一直在输出,只是右边滚轮跑的很慢,而关同步是“嗖”的一下,直接就下去了

关闭同步的情况

关闭同步后,iostream 不再进行那些额外的同步操作和锁机制,输出操作变得更加高效。虽然同样是循环 1000000 次输出,但每次输出的时间大大减少,所以整个循环的执行时间也会明显缩短,控制台会在较短的时间内停止。
以下是简单的代码示例,展示开启和关闭同步时的性能差异:注意:这段代码又引入一个新的问题,下面说)
#include <iostream>
#include <ctime>

int main() {
    // 关闭同步
    std::ios::sync_with_stdio(false);
    clock_t start1 = clock();
    for (int i = 0; i < 1000000; ++i) {
        std::cout << i % 10;
    }
    clock_t end1 = clock();
    std::cout << "\n关闭同步用时: " << (double)(end1 - start1) / CLOCKS_PER_SEC << " 秒" << std::endl;

    // 开启同步
    std::ios::sync_with_stdio(true);
    clock_t start2 = clock();
    for (int i = 0; i < 1000000; ++i) {
        std::cout << i % 10;
    }
    clock_t end2 = clock();
    std::cout << "\n开启同步用时: " << (double)(end2 - start2) / CLOCKS_PER_SEC << " 秒" << std::endl;

    return 0;
}
运行这个代码,你会看到开启同步和关闭同步时的执行时间有明显差异,关闭同步时性能会更好。
 
 
关同步后, std::cout << i%10;这句话和cout<<"\n\n"<<start<<" "<<end<<endl;具体是怎样的?我的意思是,他顺序执行?打印循环内的数,再输出start和end吗?但关同步不是要等到endl后才刷新打印吗?怎么1000000循环内部就打印了
 
 
开启同步后,iostream 要和 stdio 库保持同步,这会带来额外的开销。为了保证这种同步性,系统可能会更频繁地刷新缓冲区,从而让输出看起来像是立即进行的。也有些是行刷新。开启同步时除缓冲区满或遇 endl 会输出,系统可能因协调 iostreamstdio 流在特定条件下主动刷新缓冲区使内容输出
 
虽然关闭同步后 std::cout 的刷新机制有所改变,但并不意味着只有遇到 endl 才会刷新。std::cout 有一个输出缓冲区,用于暂存要输出的内容,在以下几种情况下会刷新缓冲区:
  • 遇到 endlendl 不仅会插入一个换行符,还会强制刷新输出缓冲区。
  • 缓冲区满:当输出缓冲区达到其容量上限时,会自动刷新缓冲区,将内容输出到控制台。
  • 程序正常结束:在程序正常终止时,会刷新所有打开的输出流的缓冲区。
  • 显式调用 flush() 函数:可以使用 std::cout.flush(); 来用flush手动刷新缓冲区。
 
在你的循环中,由于循环次数较多(1000000 次),输出内容不断填充缓冲区,很可能在循环过程中就达到了缓冲区的容量上限,从而触发了自动刷新,所以你会看到在循环内部就有内容打印到控制台。可以理解为:即循环了1000000次,但可能1000次就满了,先输出了一波,又继续循环大概这样,然后最后遇到endl才直接不管满不满就输出
 
 
不同系统和编译器默认大小不同,可通过 std::streambuf::pubsetbuf 成员函数自定义确定,也可查看流的底层实现细节来了解默认大小。
 
 
endl 是一个操纵符,它的作用有两个:
  • 插入一个换行符到输出流中,这和 \n 的作用类似。
  • 强制刷新输出缓冲区,将缓冲区中的内容立即输出到控制台。
\n 是一个转义字符,表示换行符。它只是简单地在输出流中插入一个换行符,不会刷新输出缓冲区。
 
 
 

至此就剩最后一个问题,上面说的(注意:这段代码又引入一个新的问题,下面说)

那段代码为何不输出关闭同步的用时,

我换做用VS却更tm诡异,

调试模式默认开启、编译器优化策略保守导致1000次循环,关同步,codeblock是0 0,而VS却跑很久,0 100

继续思考codeblock里的,当我改为%2的时候,只有0/1,发现显示输出一片子0/1,然后输出0~9,而再回去找,发现0/1没了,怀疑控制台会清空东西

豆包回答:

输出了好几页,占据了整个控制台,但等全部输出完,前面的输出却没有了

这是因为控制台有缓冲区行数限制,当输出内容超过该限制,早期输出就会被滚动移除不可见,可在控制台属性中调整缓冲区大小来保留更多历史输出。

 
本身是50 4,我更改为更大也不行,不管了
 
但我又有一个问题,

 为何这段代码

#include <iostream>
#include <ctime>
using namespace std;
int main() {
    clock_t start = clock();
    std::ios::sync_with_stdio(false);
    for (int i = 0; i < 1000000; ++i) {
        std::cout << 1;
    }
    clock_t end_1 = clock();
    cout << "\n\n关同步" << start << " " << end_1 << endl;

    std::ios::sync_with_stdio(true);
    for (int i = 0; i < 1000000; ++i) {
        std::cout <<2;
    }
    clock_t end_2 = clock();
    cout << "\n\n开同步" << start << " " << " " << end_2 << endl;
}

我记得上面10^6在开同步情况下,是很久都不停止的,但这里为何,前面加上了关同步,开同步这部分就会输出的很快??

豆包解答:

1. 缓冲区状态

关闭同步时的缓冲区设置

当调用 std::ios::sync_with_stdio(false) 关闭同步后,std::cout 会使用自己独立的缓冲区。这个缓冲区通常更大,而且输出操作可以更高效地批量处理数据。当进行一系列输出操作(如循环输出)时,数据会被快速地填充到这个缓冲区中。

开启同步后的缓冲区延续性

之后再调用 std::ios::sync_with_stdio(true) 开启同步,虽然理论上恢复了与 C 标准 I/O 的同步,但 C++ 流的缓冲区并不会立即重置或改变其状态。之前关闭同步时设置好的高效缓冲区依然可以继续使用,输出操作依然可以利用这个缓冲区进行批量处理,减少与底层设备(如控制台)的交互次数,从而提高效率。

2. 系统调用开销

关闭同步减少系统调用

在关闭同步状态下,std::cout 的输出操作尽量避免频繁的系统调用。系统调用是一种比较昂贵的操作,涉及到用户态和内核态的切换,会消耗较多的时间和资源。当使用独立缓冲区时,只有在缓冲区满或者遇到显式刷新操作时才会进行系统调用将数据输出到控制台。

开启同步时系统调用的影响

直接开启同步时,每次 std::cout 输出都需要与 C 标准 I/O 保持同步,这意味着每次输出都可能会触发系统调用以确保数据的一致性。而先关闭再开启同步,由于缓冲区的存在,不会立即频繁触发这些系统调用,减少了额外的开销。

3. 代码示例验证

下面是一个代码示例,通过多次循环测试来更明显地体现这种差异:
#include <iostream>
#include <ctime>

// 先关闭同步再开启同步的测试函数
void testWithSyncToggle() {
    std::ios::sync_with_stdio(false);
    clock_t start = clock();
    for (int i = 0; i < 100000; ++i) {
        std::cout << 1;
    }
    std::ios::sync_with_stdio(true);
    for (int i = 0; i < 100000; ++i) {
        std::cout << 2;
    }
    clock_t end = clock();
    std::cout << "\n先关同步再开同步执行时间: " << static_cast<double>(end - start) / CLOCKS_PER_SEC << " 秒" << std::endl;
}

// 直接开启同步的测试函数
void testWithSyncOn() {
    std::ios::sync_with_stdio(true);
    clock_t start = clock();
    for (int i = 0; i < 100000; ++i) {
        std::cout << 1;
    }
    for (int i = 0; i < 100000; ++i) {
        std::cout << 2;
    }
    clock_t end = clock();
    std::cout << "直接开同步执行时间: " << static_cast<double>(end - start) / CLOCKS_PER_SEC << " 秒" << std::endl;
}

int main() {
    testWithSyncToggle();
    testWithSyncOn();
    return 0;
}

代码解释

  • testWithSyncToggle 函数先关闭同步进行输出,然后再开启同步继续输出,并记录整个过程的执行时间。
  • testWithSyncOn 函数直接开启同步进行相同的输出操作,并记录执行时间。
运行这个程序,你会发现 testWithSyncToggle 的执行时间通常会明显小于 testWithSyncOn,这就验证了先关闭同步再开启同步会比直接开启同步执行得更快。

 

但事实上:

VS连关同步都tm执行的很慢

codeblock的话上述代码数量级太小看不出差别,而数量级变到10^6,他妈的又会缓冲区把前面的输出都清理掉,根本看不到想看的输出

不管了

 

至此知识点铺垫结束,那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看着贼吓人,一群搞臭行业的傻逼垃圾

不知道为啥,对鱼皮,吴师兄莫名的好感
View Code

###:不知道为啥,记录总感觉没不记录深刻

###:

收获:

1、刷题收获可以看懂别人算法代码

2、问各种大模型收获,灵活了,会的逐渐变多,可以不用强迫症全部复制粘贴QA问答也不用上翻强迫记录问答,而是有清晰的思路,知道哪句是重点,而其他回答就不用去看

3、哪怕大模型解释的有问题但基本可以理解,不用追问太多了

4、那些用阳寿烂玩Deepseek的狗逼死一死

posted @ 2025-02-11 18:38  GerJCS  阅读(117)  评论(0)    收藏  举报