本blog只是记录C++学习以来掉过的一些坑,写下来防止自己下一次再犯,顺便分享
持续更新,到死为止
- 菜品特套1:vector.size() '~' 的使用
- 菜品特套2:if-else的缩进
- 菜品特套3:int范围边界的1ll使用
- 菜品特套4:内存计算
- 菜品特套5:全局变量和局部变量的重复
- 菜品特套6:顺序结构&& ||前后顺序
- 菜品特套7:scanf("%s")与换行符的爱恨情仇
- 菜品特套8:break
- 菜品特套9:,;的差距
- 菜品特套10:STL的map/set
- 菜品特套11:C++的执行顺序
- 菜品特套12:顺次处理字符读入
- 菜品特套13:传地址的动态开点
- 菜品特套14:异或判断相等的偷懒
菜品特套1:vector.size() ‘~’ 的使用
✔✔✔
for( int i = G[u].size() - 2;i >= 0;i -- )
❌❌❌
for( int i = G[u].size() - 2;~ i;i -- )
食用说明书食用说明书食用说明书
~:是用来判断是否为-1的简便写法
常见用法👇
跟前向星套用
memset( head, -1, sizeof( head) );
...
for( int i = head[u];~ i;i = nxt[i] )
还有无限输入的判断
此时与EOF产生的作用一样
while( ~ scanf( "%d", &n ) )
//也可写作
while( scanf( "%d", &n ) != EOF )
以上操作一但出现符合=−1=-1=−1就会结束程序,跳出循环……
但是倒回去仔细看错误的写法,看似正确实则暗流涌动,一句话就可以直击要害
G[u].size()==0G[u].size()==0G[u].size()==0的时候怎么办?!! 减完后变成−2-2−2
此时是判断不出来的,就会一直死循环−2-2−2下去!
PS:此问题来自哲学的微笑——老刘,疯狂嘲笑哈哈哈哈
菜品特套2:if-else的缩进
✔✔✔
if( ... ) {for( int i = 1;i <= n;i ++ )if( ... )
}
else {...
}
❌❌❌
if( ... )for( int i = 1;i <= n;i ++ )if( ... )
else...
食用说明书食用说明书食用说明书
如果if−elseif-elseif−else和forforfor是这么操作,那么运行就会很正常
满足ififif条件就执行iii的循环
否则执行jjj的循环
if( ... )for( int i = 1;i <= n;i ++ )...
elsefor( int j = 1;j <= n;j ++ )...
但是当我们在循环里面再次嵌套条件语句的时候,此时就问题大大滴了!!
if−else用法if-else用法if−else用法
我们要了解if−elseif-elseif−else的运行原则,elseelseelse在不加{}\{\}{}强制区分的时候,是默认否定离它最近的ififif情况
也就是说,在错误写法中,elseelseelse否定的是forforfor循环里面的ififif条件
if−elseif-elseif−else是算一条语句的,嵌套在循环里面时,就可以不用打括号
for( int i = 1;i <= n;i ++ )if( ... ) ...else ...
//上面写法等价于下面的写法
//如果if,else里面有多条语句的时候
//注意要打括号,不然if-else会被中断,出现CE
for( int i = 1;i <= n;i ++ ) {if( ... ) ...else ...
}
if−elseif-elseif−else是一条语句,所以中间是不能被其它非if−elseif-elseif−else语句打断
for( int i = 1;i <= n;i ++ )if( ... ) ...n = n + 1;else ...
//这个时候会if-else编译报错,并且提醒我们缺少大括号
往往题目的情况不止简简单单的两种,这个时候我们一样处理
for( int i = 1;i <= n;i ++ ) if( ... ) ...else if( ... ) ...else if( ... ) ...else ...
//两种写法是等价的
for( int i = 1;i <= n;i ++ )if( ... )else if( ... ) ...else if( ... ) ...else ...
综上,总结一下:
我们的缩进只是为了代码可观,逻辑清晰易懂
但是程序运行有自己严格的规则,并不是智能AI
程序并不会按照我们的缩进,智能分类匹配if−elseif-elseif−else
PS:此问题出自蒟蒻博主,例题“zamjena”,当时调了好几天,自己也发现了加括号和不加括号会有答案的区别,但是当时没有意识到本质的原因是什么
博主自我反思:
有的时候觉得if-else很简洁,而且我自己不太喜欢打大括号,从而可以缩减代码行数
现在细想来觉得还是应该从严谨逻辑的角度出发,还是打个大括号,避免这种分歧
菜品特套3:int范围边界的1ll使用
✔✔✔
long long n = 1ll << 31 - 1;
//运行结果:2147483647
❌❌❌
long long n = 1 << 31 - 1;
食用说明书食用说明书食用说明书
有符号整型int:232int:2^{32}int:232,占用4字节,32bit
然而取值范围[−231,231−1][-2^{31},2^{31}-1][−231,231−1]
无符号整数unsigned_intunsigned\_intunsigned_int取值范围就可以达到[0,232−1][0,2^{32}-1][0,232−1]
C++计算的时候是默认intintint类型,也就是说如果我们不强调1ll1ll1ll,111就只申请了intintint类型
我们可能会以为:我左边储存答案的变量开了longlonglong\ longlong long啊,这不存的下嘛
其实不然,可以理解为是右边每个计算部分存好后再统一进行操作
然后放到左边变量里面,并不是直接在左边变量里面操作
延伸拓展👇
请看接下来的两种操作写法
int A, B;
long long n = A + B;
long long A;
int B;
long long n = A + B;
将A,BA,BA,B赋值214748364721474836472147483647
会发现第一种写法溢出了,而第二种写法算出了正确答案
这是因为A+BA+BA+B暂时将结果存在了AAA里面,然后再像赋值一样赋给nnn
可以试试A:int,B:longlongA:int,B:long\ longA:int,B:long long也是一样的
而第一种写法AAA是intintint类型的,显然存不下,所以就爆出去了
解决方法有两种
第一个就是像第二种写法一样直接把变量开成longlonglong\ longlong long
第二种就是计算时乘以1ll1ll1ll,一般习惯在第一个变量前面加,这样右边整个答案只要在longlonglong\ longlong long范围内都可以存储
int A, B;
long long n = 1ll * A + B;
泼水不收
long long n = ( 1 << 30 ) + ( 1 << 30 ) - 1;
long long n = ( 1 << 30 ) + 3 - ( 1 << 30 ) - 4;
...
long long n = ( 1 << 30 ) + x - ( 1 << 30 ) - ( x + 1 );
//可以试试这种写法
//x不要带得太大
//发现这种写法一样可以
这是为什么呢??留给dalao们,蒟蒻不懂
蒟蒻bb:
初学C++,学了很多算法
但是对这种知识真的是了解很少,不懂计算机的运算法则
所以经常会在各种歪歪扭扭的角(ka)角(ka)落(guo)落(guo)到处爆掉
PS:
Upd–>2020-06-11
老刘好SB哈哈哈哈,写FWT板题(f[k]+f[k+p]+mod)(f[k]+f[k+p]+mod)%mod(f[k]+f[k+p]+mod)
还不知道为什么错了,要是f[k]=f[k+p]=mod−1f[k]=f[k+p]=mod-1f[k]=f[k+p]=mod−1
三个加一起就爆intintint了呀,我看了一眼就发现了哈哈哈哈哈
菜品特套4:内存计算
做FMTFMTFMT州区划分,给爷调崩了,一直RERERE,我都以为是被针对了!!
✔✔✔
long long dp[23][1 << 22], g[23][1 << 22], inv[1 << 22];
❌❌❌
long long dp[23][1 << 21], g[23][1 << 21], inv[1 << 21];
食用说明书食用说明书食用说明书
1GB=1024MB=1024∗1024KB=1024∗1024∗1024B(byte字节)1GB=1024MB=1024*1024KB=1024*1024*1024B(byte 字节)1GB=1024MB=1024∗1024KB=1024∗1024∗1024B(byte字节)
在 C++ 中
intintint类型每个空间是444个字节
longlonglong\ longlong long是888个字节
boolboolbool类型是111个字节
数组占用内存的计算
①:a[x][y]a[x][y]a[x][y]的空间大小=x∗y=x*y=x∗y(数组大小)∗4*4∗4(转化为ByteByteByte)/1024/1024/1024(转化为KBKBKB)/1024/1024/1024(转化为MBMBMB)
②:直接用sizeof(a)sizeof(a)sizeof(a),这样算出来的空间占存的单位是byte
看到这里的时候就已经明白自己是如何死得死翘翘的了
就以RERERE的数组大小来举栗计算
23∗(1<<22)=96468992∗4(B)/1024=376832(KB)/1024=368MB23*(1<<22)=96468992*4(B)/1024=376832(KB)/1024=368MB23∗(1<<22)=96468992∗4(B)/1024=376832(KB)/1024=368MB
按照intintint的字节数来计算就已经如此之大,更何况开的是longlonglong\ longlong long,内存算出来就要×2×2×2
涉及内存的一般都是卡你数据结构啊,或者状压dpdpdp…
我认为学习C++还是有必要掌握这么基本的内存计算方法,因为编译器的不同对数组的最大忍受也不同,可能编译能过,但是交上去跑出来是RE,这个时候调起来就会很崩溃...
菜品特套5:全局变量和局部变量的重复
✔✔✔
#include <cstdio>
int n;void calc() {for( int i = 1;i <= n;i ++ )...
}int main() {n = ...return 0;
}
❌❌❌
#include <cstdio>
int n;void calc() {for( int i = 1;i <= n;i ++ )...
}int main() {int n = ...return 0;
}
食用说明书食用说明书食用说明书
太智障了,这个坑,一点都不想说fa
做的FMT遗失的答案掉的坑,凸(艹皿艹 )
局部变量只在局部内有值
全局变量适用于全局
局部内可以正确调用局部变量
局部外的其他版块一旦涉及到该变量,默认调用全局
一言以蔽之:局部内优先调用局部内的变量,次调用全局变量
所以错误写法中calccalccalc版块的nnn其实是等于0的,该forforfor循环根本未被执行
报告完毕!
菜品特套6:顺序结构&& ||前后顺序
✔✔✔
bool vis[n + 5];
while( x <= n && vis[x] ) x += y;
❌❌❌
bool vis[n + 5];
while( vis[x] && x <= n ) x += y;
食用说明书食用说明书食用说明书
顺序结构大师上线
在我初学C++的时候就曾经犯过这个问题,没想到时隔多年再次煞笔
由&&∣∣\&\&\ ||&& ∣∣连接的顺序结构
一连串的条件限制,程序默认从左往右顺次判定
所以在错误写法中,极有可能vis[x]vis[x]vis[x]就已经炸出visvisvis的范围了,看都没看后面的条件限制
最终导致程序死亡
菜品特套7:scanf(“%s”)与换行符的爱恨情仇
爷爷/奶奶你追的博客,关注的大大更新了
因为真的被玄学到了,所以单独开了一篇blog来写
这里直接上链接吧
五星菜品⑦
菜品特套8:break
原料来自于此blog题解的第二题
这里不谈正解,只考虑用mapmapmap暴力搞的写法 因为我考场就是纯map
✔✔✔
for( int i = 1, x;i <= m;i ++ ) {scanf( "%d", &x );long long ans = 0;bool no = 0;for( int j = 1;j <= x;j ++ ) {cin >> s;if( mp[s] ) ans += mp[s];else no = 1;}if( no ) printf( "-1\n" );else printf( "%lld\n", ans );
}
❌❌❌
for( int i = 1, x;i <= m;i ++ ) {scanf( "%d", &x );long long ans = 0;bool no = 0;for( int j = 1;j <= x;j ++ ) {cin >> s;if( mp[s] ) ans += mp[s];else { no = 1; break; }}if( no ) printf( "-1\n" );else printf( "%lld\n", ans );
}
食用说明书食用说明书食用说明书
想必dalao看一眼就知道咋回事了
没错
breakbreakbreak:结束当前循环
continue:continue:continue:结束当前情况,并不结束当前循环
举个栗子
for( int i = 1;i <= n;i ++ )if( i & 1 ) break;
for( int i = 1;i <= n;i ++ )if( i & 1 ) continue;
breakbreakbreak,i=1i=1i=1就直接结束循环了,所以时间复杂度只有111
continuecontinuecontinue,iii还是会把nnn以内的数都遍历一遍,时间复杂度仍为O(n)O(n)O(n)
再多说几句,break,continue...break,continue...break,continue...这种语句,跟其他语句混用必须打大括号,不认逗号
如果每一条语句之间都是逗号
计算机会一直读,默认到最后分号出现的位置为一整条语句
所以就不用打大括号
但套上这种专有语句后,就不行了 我也不造为什么
举个栗子
✔
for( int i = 1;i <= n;i ++ )if( i & 1 ) ans ++, p[++ cnt] = i;
✔
for( int i = 1;i <= n;i ++ )if( i & 1 ) { ans ++, p[++ cnt] = i; continue; }
❌
for( int i = 1;i <= n;i ++ )if( i & 1 ) ans ++, p[++ cnt] = i, continue;
转回正题
因为我边读入边线性求解,发现是穷人后就直接breakbreakbreak,导致当前组数据压根没读完
再加之这道题是多组数据,被我中间插断的数据就被我的计算机读入成为下一组数据了
菜品特套9:,;的差距
我以为许久不更新以后就不会写sb锅了
✔✔✔
#include <cstdio>
void solve( int x ) {printf( "%d\n", x );
}
int main() {int cnt = 1;int pre = ++ cnt; solve( cnt );printf( "%d", pre );return 0;
}
/*
输出:
2
2
*/
❌❌❌
#include <cstdio>
void solve( int x ) {printf( "%d\n", x );
}
int main() {int cnt = 1;int pre = ++ cnt, solve( cnt );printf( "%d", pre );return 0;
}/*
输出:
2
*/
食用说明书食用说明书食用说明书
可能似个人就看出来了,错误写法中intintint变量名的定义和函数调用中间用的逗号连接
之前也不是没犯过因为逗号死掉的问题,这次又中了
逗号连接的在计算机里的默认是一句话
所以计算机将我的函数调用理解为了int solve(cnt),然后。。。。p都没干
菜品特套10:STL的map/set
setsetset:自动去重——解决方法,重载排序
mapmapmap:本质也是一个排序的,不然为什么会有unorderd_map??
所以如果传入的下标是多个元素,必须重载排序,才能编译成功
并且每一个元素都要参与排序
比如(1,2,3)(1,2,3)(1,2,3),(1,2,4)(1,2,4)(1,2,4)
只比较前两位参与排序,那么setsetset自动去重了只留下一个,mapmapmap则判断两者为一样的
灰常感谢香香mm的倾情赞助,真是让吾辈受益匪浅呢!!%%%%
菜品特套11:C++的执行顺序
前言:是在最近做的线段树优化建图时碰到的
addedge( u, ++ cnt );
addedge( cnt, ++ cnt );
addedge( v, cnt );
这三条语句写者的想法是,uuu向cntcntcnt点连边,然后cntcntcnt向++cnt++cnt++cnt(cntcntcnt先自加111)连边,vvv向cnt+1cnt+1cnt+1连边
e.g.
cnt=1cnt=1cnt=1,uuu向111连边,111向222连边,vvv向222连边
你就可以发现Dev-c++被卡RE了
因为
c++一条语句其实是从右往左读的
也就是说在第二条加边语句中,是先cnt++cnt++cnt++再cntcntcnt与cntcntcnt连边
这就连出了自环的无限循环了
所以必须这么写
addedge( u, ++ cnt );
addedge( cnt, cnt + 1 );
addedge( v, ++ cnt );
这也能说明,有的时候我们会像下面这么连续赋值
int a, b, c, d;
a = b = c = d = 419;
先执行d=419
,然后c=d
,接着b=c
,最后a=b
,成功赋值四个变量
菜品特套12:顺次处理字符读入
最近都有碰到字符串读入的处理
众所周知,只有空格和换行是非常头疼的
而且现在随着版本的更新,gets()
已经不允许使用了(但是它的功能真的很舒服啊,可以读入空格和换行,哎),可以试试fgets()
,但建议还是别了吧
一般读入字符串的方法如下
- 一个一个字符地读,
ch=getchar()
,scanf("%c",&ch)
好处自然是可以通过条件语句判断空格和换行,做出相应举措 - 一个字符串一个字符串地读,
scanf("%s",s)
,cin>>s
但都是遇到空格或者换行就直接结束了,便于我们直接处理不要空格和换行的题目
但是博主最近的问题是,需要空格和换行,那么就选择了一个一个字符地读
结果总是忘记最后一个字符串是留下来未操作的,因为后面是空,不能触发处理一个字符串的结束条件
经常最后一个字符串被遗忘,导致出错丢分
菜品特套13:传地址的动态开点
最近做了两道题,是需要可持久化字典树的。
而可持久化标准需要动态开点,所以大家都习惯于直接把数组的地址传过去,这样就直接改了数组。
但是字典树的可持久化就不要传了。
void insert( int lst, int &now, char s ) {now = ++ cnt; t[now] = t[lst];for( int i = 1;i <= n;i ++ ) {int c = s[i] - 'a';t[now].son[c] = ++ cnt;now = t[now].son[c];lst = t[lst].son[c];t[now] = t[lst];}
}
insert( root[i - 1], root[i], s[i] );
受到可持久化字典树的影响实在太大了。
上面的写法完全是错误的,因为 nownownow 从始至终都带的是地址,所以看似我们走了 nownownow 的儿子边,但是其实一直都是 root[i]root[i]root[i] 的编号在自加。
只有 lstlstlst 版本的字典树一直在正确的走。
所以字典树的就不要传地址了,直接改原数组得了。
void insert( int lst, int now, char s ) {root[now] = ++ cnt; now = root[now];lst = root[lst];t[now] = t[lst];for( int i = 1;i <= n;i ++ ) {int c = s[i] - 'a';t[now].son[c] = ++ cnt;now = t[now].son[c];lst = t[lst].son[c];t[now] = t[lst];}
}
insert( i - 1, i, s[i] );
菜品特套14:异或判断相等的偷懒
因为本人的码风和个人喜好问题,比起来 !=
我更喜欢 ^
而且少占一个字符。
众所周知 x^x=0
,所以我很喜欢,if(a^b) ...
这种写法。
这倒是没什么问题,因为 if-else
这种判断句又是 bool\text{bool}bool 类的,只认 0/10/10/1。
但是写多了就以为本身运算就是个 0/10/10/1 的返回。
比如我的本意是如果 x≠yx\ne yx=y 就 +1+1+1,否则 +0+0+0。
写代码就容易写成 ans+=(x^y)
。
这简直就是大错特错!现在的 x^y
明显是个运算了,而不是 [x^y]
的条件判断。
不如老老实实写 !=
,因为这个真的很难调,思维进入一个误区跳出来很困难的欸。