hello大家好,今天又给大家带了c++的冷知识,本期我们要讲的是基本语法。
1 #define宏定义:解放肝帝!
有时候我们经常会重复写一些代码,尤其是类型比较长,简直痛不欲生……
这时候我们就有了一个解放双手的方式:#define宏定义。
define有三种方式:
1,定义常量
这个就是基本的,跟const是一样的效果,不一样的是它能自动识别你该用int还是用long,只是常量的值是不能变得。
2,替换语句
那些非常长的语句,比如快读,ios::sync_with_stdio……一大堆,这时候可以定义一个宏,直接替换。
3,带参数换函数
对于函数定义长的,就用这个,省时省力。也是能自动判断参数类型的,只是要注意括号。
这里给大家一些大神常用的:
1,cerr<<__LINE__<<' '<<__FUNCTION__<<endl;这个是输出他在哪一行,用来调试
2,ios::sync_with_stdio(false);cin.tie(0);cout.tie(0) 关闭同步指令,调试用
3,rt<<1(左子节点)、rt<<1|1(右子节点) 线段树问题的常客,记住!
4,freopen(FILENAME".in","r",stdin),freopen(FILENAME".out","w",stdout) 输入输出文件,非常实用的小技巧,OI竞赛必须加他!
5,((x)&(-(x))) 这个在树状数组常用。
注意:以上写法是前面加#define 宏名 之后的语句的写法,程序中不能这么写!
这只是加快速度的小技巧,根据个人喜好用就行。
来个小题:
A+B 肝帝版
输出n组a+b的结果,n不超过5000000.
一般人估计看见数据量就蒙圈了:五百万组!!
众所周知,读取最快的就是scanf和printf,还有getchar,putchar,这题数据量这么大,显然用getchar那一组。但是!getchar只能对单个字符进行处理。
怎么办?搞个小知识:把数字看成数字的字符串,再用getchar不就快了,只需要输入后在转换就可以了。
这里注意,你不用cin和cout就别关同步!要不然就运行不了了。
还有,快读一般是用不到,而且他本质是字符串,可以被空格卡崩。所以大家在数据量没那么大时别用快读。
2 template:解放参数类型!
他也可以自动判断类型,基本写法为
template<typename T>
(函数本体)
这样用T表示类型,就不用再在类型上纠结了。
这个可以优化快读,但是卡服解决不了。
3 运算符重载:自定义加法?
这个适用于结构体,可以在结构体内外重载。
基本写法:
类型 operator运算符 (形参列表)
注意形参前面要加const 类型&。结构体外面是要加形参的那种,结构体里面可以不加。。
常用于结构体排序和STL。因为这样可以直接sort结构体。注意set<node>存结构体时必须有序且必须在参数前面加const&。
4 对拍调试码:肝帝的快乐!
对拍常用于竞赛或者自己在家写。比如你写了一个优化算法,这时候用暴力方法当做对拍代码再做一遍。因为暴力方法十有八九是对的,所以只需要答案相比较就行。
这里注意,对拍只能保证结果是对的,他不管过程,像程序员的两个噩梦TLE和MLE他就不会管,遇上橙题或红题只能一行一行看。
注意这里要有一个数据生成器来帮忙,具体模板如下:
(gen.cpp数据生成器)
int main()
{
srand(time(0));
int n,m,S;
n=rand()%1000+1;
m=rand()%2000+1;
S=1;
printf("%d%d%d\n",n,m,S);
for(int i=1;i<=m;i++)
{
int u,v,w;
u=rand()%n+1;
v=rand()%n+1;
w=rand()%1000000000;
printf("%d%d%d\n",u,v,w);
}
}
下面是对拍程序模板:
int main()
{
int t=10;
while(t)
{
t--;
system("gen.exe>data.in");
system("brute.exe<data.in>brute.out");
system("std.exe<data.in>std.out");
if(system("fc brute.out std.out")) break;
}
if(t==0) cout<<"yes";
else cout<<"error";
}
注意,这里data.in是之前gen.cpp生成的文件,按照输入格式来;这里std.cpp指的是你最初写的需要调试的代码,brute.cpp指的是用来对比的暴力代码。这里的数据生成器采用了C++随机生成,具体方法以后再讲。再解释一下,rand()函数是取随机数的,system(fc a b)是比较a和b的文本是否一样。而system(a>b)是把a给b,(b<a)是把b给a
我这里着重说下rand(),基本格式是rand()%N,代表范围0到N-1;%N加1就是1到N的范围。前面加的srand()是种子(简单理解成mc那个种子,用来生成对应区间),time(N)是时间戳。
要取范围【n,m】得数,是rand()%(m-n+1)+n。
还有一种“真随机数”——mt19937。
基本写法有点长,耐心看完:
unsigned seed=std::chrono::system_clock::now().time_since_epoch().count();
mt19937 rand_num(seed);
没有十年码龄学不来啊!
接下来给大家一个常用代码:
生成1——n随机排列
for(int i=1;i<=10;i++) a[i]=i;
unsigned seed=std::chrono::system_clock::now().time_since_epoch().count();
shuffle(a+1,a+11,default_random_engine(seed));
此时a[]数组里存了一个随机排列。shuffle函数用来打乱区间内的元素。
我们还能用并查集加rand生成随机树,进而生成随机图。
5,STL标准库:C++人性化的一面
众所周知,C++有个标准库叫STL,我之前也讲过。
复习一下,我列几个常用的STL关键字:
算法函数:<algorithm>
栈:stack
队列:queue
优先队列:priority_queue,队尾压进去的自动排序,出队可以是升序也可以是降序,升序的是priority_queue<int,vector<int>,greater<int>>q;
降序的则是priority_queue<int,vector<int>,less<int>>q;
注意优先队列可以插入运算符重载后的结构体和pair数对。
双端队列:deque 这个比较特殊,两边都可以进或者出,所以多了push_back、push_front、pop_back、pop_front四个操作,分别是在队首队尾入队和出队。单调队列用的就是他。
向量:vector 本质可以理解为大小能改的数组,啥都能放。可以直接下标访问,也可以push_back,还有insert(it,x)是迭代器指向的位置插入x,erase(it)是把迭代器指的删了。
集合:set 不重复的数据,自动去重
无序集合:unordered_set 无序去重
多重集合:multiset 有序不去重,能二分查找,查到的是迭代器的类型
映射:map 映射是一种关联方式,比如map<int,int>a是两个int型的映射,他也可以用下标访问。但是当建立映射时,map中的键是唯一的,像变量一样。另外函数clear是清空,counnt(x)是查找键为x的数的数量。map的底层是红黑树(别问我红黑树是啥,因为我也不知道!)还有,map的迭代器要用->first取第一关键字,->second取第二关键字。
无序映射:unordered_map ,它的底层实现是哈希表,不是自己有序的,增删改查操作比map快。注意insert时必须插入pair形式。
多重映射:multimap 按照插入的顺序排序,先排第一关键字再排时间,而且键值能重复用,形成一对多的数据结构。注意erase会把所有带这个键值的全删掉。
数对:pair 包含两个数据值,两个数据的类型可以不一样。三个值能嵌套pair。它能够直接排序,后面会见他的排序规则。注意尾部“ > >”两个尖括号一定要加空格,要不就跟右移混了。有一个函数是make_pair,对已有的元素构造新的数对。注意不是int会按照字典序排序。
STL库把一切都隐藏,只留了调用接口。标红的是重点,必须记住!
STL可以理解为C++创始时候人性化的一面——于是,就不用手写栈和队列了。好吧,开个玩笑而已。
上面讲了一个迭代器,后面讲。
接下来依然是重点,是algorithm库的几个常用函数:
1,unique(起始下标,结束下标) 对应区间中的重复数据挑出来放在区间末尾,注意这个区间内的数据必须是有序的。
2,lower_bound/upper_bound(起始下标,结束下标,num(要找的数据))
lower是第一个大于等于num的位置,upper则是第一个大于num的位置。注意这个区间依然是必须有序的。
3,next_permutation/prev_permutation(起始下标,结束下标)是找全排列,next是求下一个全排列,prev是求上一个全排列。
4,swap(a,b)交换a和b的值,再也不用写三行还多个变量了。
接下来讲迭代器,它可以看作是一个指针,作为容器和算法间的媒介,他可以指向一个元素。迭代器的定义方式是::iterator it;
因为它类似于指针存地址,所以赋值要加*号。