HDU1686-Oulipo
继续跟邝斌飞KMP ——(之前都是POJ多,现在KMP居然大部分都是HDOJ的)
HDU1686 来源:华东区大学生程序设计邀请赛_热身赛,AC数没啥参考价值,不知道是不是不是那场比赛的提交也算上了,AC人数最多,但AC率排行榜5/9
POJ3461 来源:BAPC 2006 Qualification
题意:
主串 和 子串 均为大写字母A~Z,
子串叫W,称之为单词,长度:1≤|W|≤10^4,
主串叫T,称之为文本,长度:W|≤|T|≤10^6
没空格之类的不可见字符,原文: And they never use spaces.
原文描述: So we want to quickly find out how often a word, i.e., a given string, occurs in a text ,i.e是 “即” 的意思
这题不同之处是,原文描述: Occurrences may overlap. 即可能会重叠,上一个题剪花布条 里的样例 “aaaaaa aa” 输出3,按照这个题的题意应该输出5
读完题立马起手写,感觉就是签到题啊,这不白送气球一样嘛
我靠不对啊,如果数据是:
位置:0 1 2 3 4 5 6 7 8
主串:a a a a a a a a a
子串:a a a a
这tm要朴素暴力啊子串匹配 0123 位置后,应该去匹配 1234,然后 2345,KMP 是把不匹配的做处理,所以 next 数组应运而生,那这种重复匹配的,咋感觉要发明新算法,新 next 数组了呢
好简单,只需要在上个题的基础上,j 不回退到0,回退到 next 的值就行了
用例子一想就出来了,子串为:ABABC,去你妈的想错了,因为 ABABC 到 C 这如果匹配了,该重新让主串退到能跟子串头匹配的位置去,可是这C的 next 是2,没用啊,这2表示的 子串的前两个字符 也没法保证跟主串后面的或者前面的哪里匹配啊。
妈的咋能让主串的每一位,都存一个数值,这个数值表示当前位置的前面,最往前可以在哪个位置,跟子串头的那个字符匹配,难道让老子发明KMP或者改进KMP吗
位置:0 1 2 3 4 5 6 7 8
主串:a a a a a a a a a
子串:a a a a
01234匹配成功后,主串 4 位置应该有个东西记录他的前面,最往前,是1位可以跟子串头匹配,那主串就退到1重新跟子串匹配。
那咋感觉也没啥用呢?如果单独匹配上头也没啥意义啊,仿佛应该也想子串一样找个 next
艹有了,那就对主串做一次 next ,匹配失败正常按照KMP来,如果匹配成功,子串一定是到了末尾,上个题是直接继续在这个位置匹配,但这题可以重复,那主串就退到 主串这个位置存的 next 那去。
那对于上面的例子,主串 next_a[4] 是 3,应该退到 1 的位置,咋能是 1 ,那我就构造位置 4 减去 next_a ,4-3==1
再试一个,
位置:0 1 2 3 4 5 6
主串:a b a b a b a
子串:a b a
这个匹配成功后,匹配函数里,主串应该是到 3 了,主串的 next_a 是 1 ,位置 3 减去 next_a[3] 的值 1 就是 2 。完美。
其实想想也成立,就是当前主串跟子串匹配成功后,主串会加 1 ,指针到主串的下一个位置,简称 P位置,这 P 位置正好记录了前面的最长前后缀长度,其实就是 子串的头 可以跟 P 位置前面哪个位置能匹配,并且能保证一直可以匹配到 P 位置之前。
但想两个事:
0、这只是头,那等到子串拉到主串后面的时候,还能适用了吗,因为你主串记录的是你主串的前后缀啊,可是等子串拉到后面的时候,跟主串前缀的那些字母就没关系了啊。
举个例子:
主串:a b a c m n a b a b a
子串:a b a
当匹配到
位置:0 1 2 3 4 5 6 7 8 9 A
主串:a b a c m n a b a b a
子串: a b a
的时候,显然匹配函数里,主串指针应该指向 9位,子串指针应该指向自己的头了,那次是主串指针应该去指向 8 位了啊,你主串 next_a 有个毛用处啊。
1、要的是P位置前面的最前,或者说距离 P位置 最远,可是这么搞主串只是记录了前面最近的啊,
随便搞个例子:
主串:a a a a a a a
子串:a a a
就可以发现问题,不过严格广义上来说这个问题是0、那个的,但其实狭义上这个角度也说得通,因为题目求有几个重复的,也就是最多能匹配上几个串,例子不好举,但描述来就是:
我子串跟主串匹配,子串头从主串的哪个位置开始能匹配上的,往后一直能匹配到子串末尾,就算匹配成功了一个,那主串通过匹配函数,也到了下一个位置,
好,现在我就应该去找这个主串最远的位置,如下图:
就是说,我子串跟主串的 23456 位匹配成功后,由于上面的0、已经说了主串的 next_a 没用,那就只求子串的,那我现在给子串 01234 这几个位后再来个虚拟位,比如叫 5 位,5位会记录 next_b ,假设 next_b[5] 是 2 ,表示子串的 01位 == 34位 , 我可以知道,主串的 23位 == 56位 ,
好了,现在就可以知道我接下来该怎么匹配了,(上一个题目正常的 KMP 不考虑重复或者叫 重叠 ,应该是主串 7位 跟子串的 0位 比较)显然是应该去尝试子串的 2位 和主串的 7位 比较,应为根据前面假设,子串 next_b[5] 是 2 ,子串 01 == 34 ,主串 23 == 56 ,推理出 子串01位 == 子串34位 ==主串23位 ==主串56位 ,那子串 01位 一定等于主串 56位 ,可是现在要说的就是 1、 里考虑的问题,
这个 next_b 只是记录了最长前后缀长度,前缀没问题,就是指的是子串头,但是后缀就有问题了啊,后缀指的是从最后一位往前去数位数,即最近的,可是我算重叠,不应该考虑最远的吗?即中间的某位如果也可以应该也要去尝试匹配啊
如上面图的例子,我 01 必然跟 56 相等,就直接 子2 去比较 主7,那我有没有可能 01 还跟 45 相等啊,这个是你 next_b 根本不会记录的啊,你只记录的是距离你尾巴最近的,即4、43、432、4321这些,倒过来说就是1234、234、34、4,即1234跟0123相等或者234跟012相等或者34跟01相等或者4跟0相等。但对于 01 是否能和 45 相等不清楚啊?如果 01 和 45 相等,那是不是就 01 跟主串 45 相等然后如果都成立,就 子01234 会匹配上 主45678 ,那重叠的 ans 不就多了一个吗?
仔细想想并不是这样,之前学KMP的博客里,去搜“数学理论严谨的证明”,跟那个思想差不多,如果 01234 会匹配上 45678 能匹配,一定有
那就表明自己的 012位 等于自己的 234位 ,next_b[5]就是 3 了,与next_b[5]假设的 2 矛盾,到这一下豁然开朗,也彻底明白了那篇博客里的“数学理论严谨的证明”
至此懂了,匹配成功后直接让子串的虚拟位的 next_b 来跟主串的 i 比较,如果你问 i 是啥,i 匹配到哪了,连这个你不知道的话说明连最基本的KMP匹配都不懂,看我剪花布条的 博客
写完TLE了,用的是上一个题的string直接cin写法,百度说getline也会耗时,换成数组试试 (本来想做个测试,cin、scanf、getline分别读取10^6个数据,是多少,但不好构造数据,先搁置)
想到 上个题 的这句话 “但这道题输入形式是两个串的中间没空格,那我如果用比较耗时的cin一次直接读取整个的string,会不会比,虽然读取速度很快,但要一个一个读然后赋值的scanf好一些”
中间插曲:
不知道为啥,freopen读文件的时候,如果输入数据最后没回车,按理说应该等待啊,但程序仿佛死循环或者哪里下标溢出了什么的,鼠标转圈,返回的是负数
注意我txt里鼠标位置,没回车
加上回车就没事
这个玄学问题之前食物链遇到过, 搁置吧,感觉研究不明白,网上也查不到,也暂时感觉没啥意义
感觉写了个完美的代码

1 #include<stdio.h> 2 #include<string.h> 3 #include<iostream> 4 using namespace std; 5 char b[10001+1];//子W 6 char a[1000001];//主T 7 //string W;//子 8 //string T;//主 9 int ne_b[10001+1]; 10 int main() 11 { 12 // freopen("zhishu.txt","r",stdin); 13 int Case; 14 int flag; 15 // cin>>Case; 16 scanf("%d",&Case); 17 flag=Case; 18 while(Case--){ 19 // cin>>W; 20 // cin>>T; 21 //为了防止混淆,重新处理下 22 23 // string a; 24 // string b; 25 // a=T; 26 // b=W;//方便理解,a主,b子 27 // int len_a=a.length(); 28 // int len_b=b.length(); 29 30 int len_a=0;//主 31 int len_b=0;//子 32 char str; 33 if(flag-1 == Case) 34 getchar();//吃Case后的回车,注意Case只有第一次的时候有输入 35 36 while(scanf("%c",&str)){//先输入的是子 37 if(str=='\n') 38 break; 39 b[len_b]=str; 40 len_b++; 41 } 42 // for(int i=0;i<len_a;i++) 43 // cout<<W[i]; 44 // cout<<endl; 45 46 while(scanf("%c",&str)){//后输入主 47 if(str=='\n') 48 break; 49 a[len_a]=str; 50 len_a++; 51 } 52 // for(int i=0;i<len_b;i++) 53 // cout<<T[i]; 54 // cout<<endl; 55 56 // 对子串 a 求 next 57 int k=-1; 58 int j=0; 59 ne_b[0]=-1; 60 while(j<len_b){ 61 62 if(k==-1 || b[j]==b[k]){ 63 j++; 64 k++; 65 if(b[j]==b[k]) 66 ne_b[j]=ne_b[k]; 67 else 68 ne_b[j]=k; 69 } 70 else 71 k=ne_b[k]; 72 } 73 74 // 匹配 75 int ans=0; 76 int i=0; 77 j=0; 78 while(i<len_a){ 79 if(j==len_b){ 80 j=ne_b[j]; 81 ans++; 82 // cout<<"#"<<i<<" "<<j<<endl; 83 } 84 if(j==-1 || a[i]==b[j]){ 85 i++; 86 j++; 87 // cout<<"@"<<i<<" "<<j<<endl; 88 } 89 else 90 j=ne_b[j]; 91 } 92 if(i==len_a && j==len_b) 93 ans++; 94 printf("%d\n",ans); 95 // cout<<ans<<endl; 96 97 } 98 }
但还是TLE
看了下vjudge的讨论,里面有句“strlen()提出循环就不tle了。。。”,感觉纯属放屁,就是要在循环内每次求长度的,不过strlen忘记是啥了,
插曲开始
先问了下百度关于求数组长度的方法,方法二的模版元没管他不想研究
又问了下文心GPT
学到几个知识,总结下就是:
#0、size_t作为无符号整型,一般用作存储数组长度啥的,溢出会绕回 0,其余说的一堆没看懂也不想去研究
#1、关于几个长度的东西
自己总结:
size() 在容器vector那用的,之前确实用过,博客搜size,vector存图那,遍历的时候用过.size。string获取长度可以用size和length一样的
sizeof() 是一个运算符,就memset那用过,用于返回类型大小,其他没用过没必要记
length() C++字符串string获取长度的
strlen() 用于C以为\0结尾的字符数组长度
GPT总结:
size() 用于STL容器,返回元素数量
sizeof() 是运算符,获取类型大小
legnth() 字符串string长度
strlen 是C风格字符串返回,只能是C里以为 '\0' 结尾的字符串(不包括 '\0' ),整型不行,整型理论上可以这么写
int aaa[5]={3,4,5}; int length = sizeof(aaa) / sizeof(aaa[0]); cout << length << endl;
注意:输出5
#1.1、针对求字符数组长度,又问了下GPT,单独拎出来再说下,有 strlen 和 sizeof
但 strlen 是从第一个字符到 \0 结束,不包括 \0
而 sizeof 是计算数组或者类型所占内存大小,单位是字节,但他返回的包括 \0,即
char aaa[]="sdf";
cout<<sizeof(aaa)<<endl;
输出4
且由于数组大小是固定的,在定义时就确定了,所以无法获得\0之前占多长,只能获得整个数组占多少内存,即
char aaa[12]="sdf"; cout<<sizeof(aaa)<<endl;
输出12
至于 size() 和 length,不是用于计算字符数组长度的函数,
string 里有 size 和 length 内置的成员函数。但 size 和 length 可不是内置的
#2、运算符:
算数运算符:+ - * /等等
关系运算符:用于比较两个值的大小是否相等:== != > <等等
逻辑运算符:
与 非等等
赋值运算符
= += *=等等
条件三目运算符
其他运算符:逗号运算符,指针运算符,地之大小运算符sizeof
插曲结束
麻痹的看了个博客,(但他文章里说:“分析:先来个模板题”??这是模板题???看了他的下面剪布条的题解发现好像正常KMP就是这个题的,上个题剪布条那种不重叠的才是与众不同的,好吧,反正都练习到了)
我居然忘记C语言可以有字符串%s这玩意直接输入。更改后直接AC
AC代码 —— POJ HDOJ两大平台均可AC 注意:cin cout 关同步,比scanf快好多好多,原因下面有解释
1 #include<stdio.h> 2 #include<string.h> 3 #include<iostream> 4 using namespace std; 5 char b[10001+1];//子W 6 char a[1000001];//主T 7 int ne_b[10001+1]; 8 int main() 9 { 10 int Case; 11 int flag; 12 scanf("%d",&Case); 13 flag=Case; 14 while(Case--){ 15 16 int len_a=0;//主 17 int len_b=0;//子 18 char str; 19 // if(flag-1 == Case) 20 // getchar();//吃Case后的回车,注意Case只有第一次的时候有输入 21 22 scanf("%s%s",b,a); 23 // cout<<a<<" "<<b<<endl; 24 // while(scanf("%c",&str)){//先输入的是子 25 // if(str=='\n') 26 // break; 27 // b[len_b]=str; 28 // len_b++; 29 // } 30 len_a=strlen(a); 31 len_b=strlen(b); 32 // cout<<len_a<<" "<<len_b<<endl; 33 // while(scanf("%c",&str)){//后输入主 34 // if(str=='\n') 35 // break; 36 // a[len_a]=str; 37 // len_a++; 38 // } 39 40 // 子串 a 求 next 41 int k=-1; 42 int j=0; 43 ne_b[0]=-1; 44 while(j<len_b){ 45 46 if(k==-1 || b[j]==b[k]){ 47 j++; 48 k++; 49 if(b[j]==b[k]) 50 ne_b[j]=ne_b[k]; 51 else 52 ne_b[j]=k; 53 } 54 else 55 k=ne_b[k]; 56 } 57 58 // 匹配 59 int ans=0; 60 int i=0; 61 j=0; 62 while(i<len_a){ 63 if(j==len_b){ 64 j=ne_b[j]; 65 ans++; 66 } 67 if(j==-1 || a[i]==b[j]){ 68 i++; 69 j++; 70 } 71 else 72 j=ne_b[j]; 73 } 74 if(i==len_a && j==len_b) 75 ans++; 76 printf("%d\n",ans); 77 } 78 }
总结来说,
0、string 类型用输入一个一个char拼接超级慢,必然TLE
1、两个 string 直接连 cin,依旧会TLE,但我凌晨3:46提交试了下,这段代码982ms,真的是人少就会快点,但终究是不可取,再也没A过

1 #include<stdio.h> 2 #include<string.h> 3 #include<iostream> 4 using namespace std; 5 string a; 6 string b; 7 //char b[10001+1];//子W 8 //char a[1000001];//主T 9 int ne_b[10001+1]; 10 int main() 11 { 12 int Case; 13 int flag; 14 scanf("%d",&Case); 15 while(Case--){ 16 17 int len_a=0;//主 18 int len_b=0;//子 19 // char str; 20 // if(flag-1 == Case) 21 // getchar();//吃Case后的回车,注意Case只有第一次的时候有输入 22 23 // scanf("%s%s",b,a); 24 cin>>b; 25 cin>>a; 26 // cout<<a<<" "<<b<<endl; 27 // while(scanf("%c",&str)){//先输入的是子 28 // if(str=='\n') 29 // break; 30 // b[len_b]=str; 31 // len_b++; 32 // } 33 len_a=a.length(); 34 len_b=b.length(); 35 // cout<<len_a<<" "<<len_b<<endl; 36 // while(scanf("%c",&str)){//后输入主 37 // if(str=='\n') 38 // break; 39 // a[len_a]=str; 40 // len_a++; 41 // } 42 43 // 子串 a 求 next 44 int k=-1; 45 int j=0; 46 ne_b[0]=-1; 47 while(j<len_b){ 48 49 if(k==-1 || b[j]==b[k]){ 50 j++; 51 k++; 52 if(b[j]==b[k]) 53 ne_b[j]=ne_b[k]; 54 else 55 ne_b[j]=k; 56 } 57 else 58 k=ne_b[k]; 59 } 60 61 // 匹配 62 int ans=0; 63 int i=0; 64 j=0; 65 while(i<len_a){ 66 if(j==len_b){ 67 j=ne_b[j]; 68 ans++; 69 } 70 if(j==-1 || a[i]==b[j]){ 71 i++; 72 j++; 73 } 74 else 75 j=ne_b[j]; 76 } 77 if(i==len_a && j==len_b) 78 ans++; 79 // printf("%d\n",ans); 80 cout<<ans<<endl; 81 } 82 }
2、两个字符数组,scanf一个char一个char的读取,依据tmd会TLE (我不知道C语言有读%s这玩意,忘记了,以前是知道的)改成%s直接AC了
3、另外之前听说过关同步,但我加 std::ios::sync_with_stdio(false); HDOJ 依旧TLE,POJ RE了(忘了,他用的万能头文件,POJ不支持)
vjudge的讨论里有句:“tle的取消cin和cout的同步性就可以了。加上std::ios::sync_with_stdio(false);”,关联博客。
查了下关同步,我试了下代码加了也AC了

1 #include<stdio.h> 2 #include<string.h> 3 #include<iostream> 4 using namespace std; 5 string a; 6 string b; 7 //char b[10001+1];//子W 8 //char a[1000001];//主T 9 int ne_b[10001+1]; 10 int main() 11 { 12 std::ios::sync_with_stdio(false); 13 // cout<<"a"; 14 // printf("3\n"); 15 // cout<<"4"<<endl; 16 int Case; 17 int flag; 18 cin>>Case; 19 // scanf("%d",&Case); 20 while(Case--){ 21 22 int len_a=0;//主 23 int len_b=0;//子 24 // char str; 25 // if(flag-1 == Case) 26 // getchar();//吃Case后的回车,注意Case只有第一次的时候有输入 27 28 // scanf("%s%s",b,a); 29 cin>>b; 30 cin>>a; 31 // cout<<a<<" "<<b<<endl; 32 // while(scanf("%c",&str)){//先输入的是子 33 // if(str=='\n') 34 // break; 35 // b[len_b]=str; 36 // len_b++; 37 // } 38 len_a=a.length(); 39 len_b=b.length(); 40 // cout<<len_a<<" "<<len_b<<endl; 41 // while(scanf("%c",&str)){//后输入主 42 // if(str=='\n') 43 // break; 44 // a[len_a]=str; 45 // len_a++; 46 // } 47 48 // 子串 a 求 next 49 int k=-1; 50 int j=0; 51 ne_b[0]=-1; 52 while(j<len_b){ 53 54 if(k==-1 || b[j]==b[k]){ 55 j++; 56 k++; 57 if(b[j]==b[k]) 58 ne_b[j]=ne_b[k]; 59 else 60 ne_b[j]=k; 61 } 62 else 63 k=ne_b[k]; 64 } 65 66 // 匹配 67 int ans=0; 68 int i=0; 69 j=0; 70 while(i<len_a){ 71 if(j==len_b){ 72 j=ne_b[j]; 73 ans++; 74 } 75 if(j==-1 || a[i]==b[j]){ 76 i++; 77 j++; 78 } 79 else 80 j=ne_b[j]; 81 } 82 if(i==len_a && j==len_b) 83 ans++; 84 // printf("%d\n",ans); 85 cout<<ans<<endl; 86 } 87 }
发现几个事,问了GPT后学到了很多:(对画图见下面)
3.1、HDOJ 62ms,但scanf 两个%s却是400ms左右,好神奇啊,cincout关闭同步,比scanf还快。
GPT解释:
的确如此,原因是:关闭同步类似于异步,当然比一个接一个排队等待输入的scanf快了,你可以理解成异步,但绝对不是异步!异步需要涉及到我没学过的比如多线程、事件驱动、异步I/O库等,关闭了同步重点在于可以独立的做事,所以互相独立做事这个词更贴切,scanf和正常写cincout都是开同步的你一句执行完,再执行下一句
为了更好理解,GPT举的例子是买奶茶:
正常写代码都是默认开启同步,比如你排队等待就相当于scanf或者cin,不能做其他事,前面的结束你才能干自己的事
关闭同步就相当于你网上定奶茶,你可以在等待期间导管子,那你能在排队的时候导管子吗?显然不能,效率就很慢啊
所以关闭同步的cincout比正常顶点scanf还快,而scanf并不涉及关同步提高效率的事,关同步是C++的I/O流的事,所以 关同步cincout 远优于 scanf printf 远优于 cincout
3.2、而且上面view里的AC代码如果Case那个变量用scanf输入也会TLE,必须全都用cin才行。
GPT给的理由是:
确实如此。我本着求其上得其中,求其中的其下的原则,不说自己的理解,因为第一次看的理解肯定很肤浅,日后读多少遍永远都不会超过自己的理解,所直接贴GPT的解释原文:
将cin>>Case;
替换为scanf("%d",&Case);
导致程序超时,这个问题可能涉及到多个方面。首先,我们来探讨一下C++的I/O流(cin
)和C风格I/O(scanf
)在性能上的差异。
性能差异
-
C++的I/O流(
cin
):- C++的I/O流是面向对象的,提供了更多的功能和灵活性。
- 但是,在处理大量数据时,C++的I/O流可能会比C风格I/O更慢。
-
C风格I/O(
scanf
):- C风格I/O更加底层,通常更快。
- 它牺牲了灵活性和易用性,以换取更高的性能
关闭同步的影响
此外,如果之前关闭了C++的I/O流和C风格I/O之间的同步(std::ios::sync_with_stdio(false);
),那么这两种I/O方式将独立地进行I/O操作。这可能会导致以下情况:
- 当使用
cin
时,它可能使用了C++的缓冲区,并且与C风格的I/O没有同步。 - 当使用
scanf
时,它直接操作底层的I/O,可能与C++的I/O流产生冲突或竞争,导致性能下降。
可能的解决方案
-
保持同步:
- 如果你的程序需要同时使用
cin
和scanf
,并且希望避免性能问题,可以考虑保持它们之间的同步(即不调用std::ios::sync_with_stdio(false);
)。
- 如果你的程序需要同时使用
-
选择一种I/O方式:
- 为了优化性能,你可以选择只使用
cin
或只使用scanf
,而不是混合使用它们。 - 根据你的具体需求和测试结果,选择性能更好的那种方式。
- 为了优化性能,你可以选择只使用
-
优化输入处理:
- 如果你的程序需要处理大量的输入,可以考虑使用更高效的输入方法,如从文件读取或使用专门的输入库。
-
性能测试:
- 在进行任何优化之前,最好先对程序进行性能测试,以确定性能瓶颈所在。
- 使用合适的性能测试工具和方法,准确地测量程序的运行时间和资源使用情况。
GPT进一步用例子说明了为啥关闭同步会更快:这个例子里面很多东西我看不懂,但大概可以理解啥意思了,类似考研操作系统讲解的线程阻塞分时异步那些玩意吧

1 #include <iostream> 2 #include <cstdio> 3 #include <thread> 4 #include <chrono> 5 6 // 模拟一个耗时的I/O操作,比如读取文件或写入文件 7 void simulateIO(const char* name) { 8 std::this_thread::sleep_for(std::chrono::seconds(1)); 9 std::cout << name << " finished IO operation" << std::endl; 10 } 11 12 int main() { 13 // 关闭C++的I/O流和C风格I/O之间的同步 14 std::ios::sync_with_stdio(false); 15 16 // 启动两个线程,分别进行模拟的I/O操作 17 std::thread t1(simulateIO, "Thread 1"); 18 std::thread t2(simulateIO_c, "Thread 2"); // 注意:这里simulateIO_c是一个用printf的模拟IO函数,需要你自己定义,类似simulateIO但用printf输出 19 20 // 等待两个线程完成 21 t1.join(); 22 t2.join(); 23 24 // 如果不关闭同步,下面的代码可能需要等待上面两个线程都完成IO操作后才能输出 25 std::cout << "Main thread finished" << std::endl; 26 27 // 注意:这里的代码是为了展示关闭同步的效果,并没有真正地实现异步IO。 28 // 在实际应用中,要实现异步IO,可能需要使用更复杂的机制,比如多线程、事件驱动等。 29 30 // 但是,即使在这个简单的例子中,关闭同步也可以让我们看到两个线程似乎是在并行执行IO操作, 31 // 因为它们不再需要等待对方完成输出。这在一定程度上提高了效率,因为减少了等待时间。 32 33 // 强调:这段代码不可用作商业盈利!若私自用于商业用途,出现bug导致经济损失可是要你自己承担的! 34 35 return 0; 36 } 37 38 // 注意:这里需要补充一个使用printf的模拟IO函数simulateIO_c 39 void simulateIO_c(const char* name) { 40 std::this_thread::sleep_for(std::chrono::seconds(1)); 41 printf("%s finished IO operation with printf\n", name); 42 }
注意:上面的代码中,simulateIO_c
函数是用 printf
来模拟IO操作的,你需要自己定义这个函数,类似 simulateIO
但使用 printf
输出。
在这个例子中,我们关闭了C++的I/O流和C风格I/O之间的同步,并启动了两个线程来并行执行模拟的I/O操作。由于关闭了同步,这两个线程可以独立地进行I/O操作,而不需要等待对方完成输出。这在一定程度上提高了效率,因为减少了等待时间。
然而,需要注意的是,这个例子并没有真正地实现异步I/O操作。在实际应用中,要实现异步I/O,可能需要使用更复杂的机制,比如多线程、事件驱动、异步I/O库等。
3.3、
std::ios::sync_with_stdio(false); cout<<"a";
printf("3\n"); cout<<"4"<<endl;
输出的是3回车a4。
GPT的解释
cout << "a";
这行代码会将字符'a'
放入C++的I/O流缓冲区中,但此时并不会立即输出到屏幕,因为C++的I/O流默认是带缓冲的。
printf("3\n");
这行代码则直接将字符'3'
和换行符输出到屏幕上,因为C风格的I/O(如printf
)在默认情况下是不带缓冲的(或者说,它的缓冲是行缓冲的,遇到换行符就会立即刷新缓冲区,刷新缓冲区意思就是刷新到输出设备比如屏幕) ———— 这里我有疑问,后面说
cout << "4" << endl;
这行代码首先将字符'4'
放入C++的I/O流缓冲区中,然后由于endl
的作用,会刷新C++的I/O流缓冲区,将'4'
和之前的'a'
一起输出到屏幕上。因此,最终的输出顺序是
“3回车a4”
那如果 printf 那去掉 \n 呢?插一句知识点:
C风格I/O(如
printf
)的缓冲有三种类型:无缓冲、行缓冲和完全缓冲。具体使用哪种缓冲方式,取决于输出的目标。无缓冲:通常用于终端(比如控制台),这意味着输出会立即被显示,不会存储在缓冲区中。不过,实际上,对于大多数现代终端,
printf
通常会使用行缓冲而不是无缓冲。行缓冲:输出会先存储在缓冲区中,直到遇到换行符(
\n
)或缓冲区满时,才会被刷新到输出设备(如屏幕)。对于控制台输出,printf
默认使用行缓冲完全缓冲:输出会先存储在缓冲区中,直到缓冲区满或显式调用
fflush
函数时,才会被刷新到输出设备。这通常用于文件输出。
好回归问题,printf去掉 '\n' ,首先,我们要明白printf
确实是 行缓冲 的,这意味着它通常会在遇到换行符或缓冲区满时刷新输出。但是,这里有个重要的细节要注意:缓冲行为可能会受到输出目标(如控制台、文件等)和操作系统的影响。
具体分析:
由于std::ios::sync_with_stdio(false);
的设置,C++的I/O流(cout
)和C风格的I/O(printf
)之间的同步被关闭了。这意味着它们可以独立地缓冲和输出数据。
现在,让我们一步步分析输出:
-
cout << "a";
这行代码会将字符'a'
放入C++的I/O流缓冲区中,但此时并不会立即输出到屏幕,因为C++的I/O流默认是带缓冲的。 -
printf("3");
这行代码会将字符'3'
放入printf
的缓冲区中。由于printf
是行缓冲的,并且此时缓冲区没有满(也没有遇到换行符),所以'3'
可能不会立即被输出到屏幕上。但是,在某些情况下(特别是输出到控制台时),操作系统或C标准库的实现可能会选择立即刷新缓冲区,或者由于其他因素(如内部优化、多线程竞争等)导致缓冲区被提前刷新。这就是为什么你可能会看到'3'
首先被输出的原因。然而,需要注意的是,这种行为并不是C标准所保证的,它可能因编译器、操作系统或环境的不同而有所变化。
-
cout << "4" << endl;
这行代码会将字符'4'
放入C++的I/O流缓冲区中,然后由于endl
的作用,会刷新C++的I/O流缓冲区,将'4'
和之前的'a'
一起输出到屏幕上。
因此,尽管printf
是行缓冲的,并且后面没有换行符,但在某些情况下,你可能会看到它的输出首先出现在屏幕上。这主要是由于缓冲行为的不确定性以及操作系统或C标准库实现的具体细节所导致的。
不过,从严格意义上讲,我们不能保证'3'
一定会首先被输出。实际的输出顺序可能会因编译器、操作系统或环境的不同而有所变化
// 同步的情况 std::ios::sync_with_stdio(true); cout << "Hello, "; printf("world!\n"); // 输出: Hello, world! // 不同步的情况 std::ios::sync_with_stdio(false); cout << "Hello, ";//后输出 printf("world!\n");//先输出 // 输出顺序可能是不确定的,取决于哪个系统的缓冲区先被刷新
对画原文图:
写代码遇到的问题
###:

宁可让别人考试时能看到后台数据但不现场返回测试结果,我看不到后台数据但需要现场返回结果
题目刷多了比撸管子上瘾多了,而且读懂题意是一回事,能AC是另一回事,就是有些题目描述可能很不好理解,强迫症总想去搞清题目里叙述的东西,但根据刷题的经验,却可以很快知道题目表达的是啥意思,可以AC,AC后题目都不一定完全懂,claris克拉丽丝酒店给q神讲题,鏼神:这不是BZOJ原题吗?
“这道题我虽然不会,但是AC还是没有问题的”
###:string a赋值给string b直接b=a;
之前那个strcpy是C的,但它不会自动检查目标数组的大小,可能会导致缓冲区溢出等安全问题。C++的string可以避免缓冲区溢出等安全问题,以上文心GPT告诉我的
关于字符数组
我记得之前说不用管最后一个字符的,不管了就先这样吧
###:回忆对拍诡异
###:妈逼的算法有个JB用,不会开发也找不到工作,连面试简历都没法投,呵呵。好后悔入这坑,之前有人说算法就是热爱,哪怕找不到工作也不后悔打过算法竞赛,我深信不疑自我高潮,如今,呵呵你tm可真他娘的神圣高尚,如果有人再问我算法有啥用,我会说,acm狗屁用没有,纯网隐
###:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)