语法,语法糖,标准库函数
介绍一些有用常用但鲜为人知的 C++ 语法,语法糖。
运算顺序
记好表,记不住打括号,最好打括号。
标准库
函数
std::swap
memcpy
memcpy(dst, src, size)
dst:目标数组起始位置
src:源数组起始位置
size:需要复制的字节数
lower_bound,upper_bound
看下实现吧:
upper_bound
如果对类使用,那么类的比较函数需要定义为友元函数 。
lower_bound
返回第一个大于等于它的位置,upper_bound
返回第一个大于它的位置,comp 为默认小于号。
如果要对不增序列操作,那么 comp
为大于号,lower_bound
返回第一个小于等于它的位置,upper_bound
返回第一个小于它的位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 template <class ForwardIt, class T, class Compare>ForwardIt lower_bound (ForwardIt first, ForwardIt last, const T& value, Compare comp) { ForwardIt it; typename std::iterator_traits<ForwardIt>::difference_type count, step; count = std::distance (first, last); while (count > 0 ) { it = first; step = count / 2 ; std::advance (it, step); if (comp (*it, value)) { first = ++it; count -= step + 1 ; } else count = step; } return first; } template <class ForwardIt, class T, class Compare>ForwardIt upper_bound (ForwardIt first, ForwardIt last, const T& value, Compare comp) { ForwardIt it; typename std::iterator_traits<ForwardIt>::difference_type count, step; count = std::distance (first,last); while (count > 0 ) { it = first; step = count / 2 ; std::advance (it, step); if (!comp (value, *it)) { first = ++it; count -= step + 1 ; } else count = step; } return first; }
atan2
1 2 3 4 double atan2 (double x,double y) { }
for_each(begin,end,fun)
对 begin 到 end 区间的每一个元素 x,执行 fun(x)。
如果需要传参,可以利用类的构造函数。
即给类重载一个 () 运算,然后构造输出。
accumulate(begin,end,start)
对 begin end 区间运用 + 操作,起始为
start。
效率不开 \(O2\)
时略高于循环,开了没差别。
count(begin,end,val)
返回 begin end 中 ==val 的数的个数。
开了 \(O2\) 效率和循环没差别。
结构体
一般来说 class 和 struct
竞赛上差别不大,struct 是默认 public 的
class。
重载括号
重载小括号运算符可以让你把结构体当函数用,其实本质上少写了一个
.{function name}。
它和构造函数不冲突。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 struct barrett1 { long long m,im; int operator () (int a,int b) { ULL z=(ULL)a*b; int v=z-((__int128)z*im>>64 )*m; return v<0 ?v+m:v; } }bt1; int a=1 ,b=1 ;int c=bt1 (a,b);struct barrett2 { long long m,im; int foo (int a,int b) { ULL z=(ULL)a*b; int v=z-((__int128)z*im>>64 )*m; return v<0 ?v+m:v; } }bt2; c=bt2.f oo(a,b);
两者没有本质区别。
中括号可以重载,一般来说,重载中括号返回的是一个引用,中扩号只接受一个参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct array_2d { int a[10005 ],b[10005 ]; int n,m; void init (int nn,int mm) { n=nn,m=mm; } int & operator [](int x){ return a[x]; } }a; a.init (4 ,5 ); a[3 ]=1 ;a.b[4 ]=2 ; a[4 ]=4 ; cout<<a[4 ]<<' ' <<a[3 ]<<' ' <<a[1 ]<<endl;
构造
结构体的构造函数可以返回一个结构体实例,也可以允许在声明结构体的时候同时构造。
举个例子
1 2 3 4 5 6 7 8 9 10 struct st { int a,b;string str; st (int aa,int bb){ a=aa; b=bb; } }; st t1=st (2 ,3 ); st t2 (2 ,3 ) ;
这两种写法都行,注意不能变量重名 ,不会
CE,但是函数参数里会那个名字会覆盖掉全局的。
注意写了构造函数,所有的构造都必须带参数。
定义结构体的时候还可以给变量赋初值。
1 2 3 4 5 6 7 8 9 10 11 12 struct st { int a,b=1 ;string str="str" ; st (int aa,int bb){ a=aa; b=bb; } }; st t1=st (2 ,3 ); st t2 (2 ,3 ) ;
但是如果构造函数里写了,就会被覆盖。声明的局部变量写了的初值会固定,没写的初值就随机。
如果没写构造函数,那么会有一个默认的列表构造函数,按照结构体内声明变量的顺序将列表中的每一个值依次赋给对应变量。
1 2 3 4 5 6 struct st { int a,b=1 ;string str="str" ; }; st t1=st{2 ,3 ,'huan_yp' }; st t2{2 ,3 ,"huan_yp" };
其实构造函数还有另一种写法
1 2 3 4 5 6 struct st { int a,b;string str; st (int aa,int bb): a (aa), b (bb) {} }; st t1=st (2 ,3 ); st t2 (2 ,3 ) ;
和最开始的写法是等效的。
注意,如果写了构造函数,默认的列表构造函数会调用它,所以如果你想不同参数个数构造,需要填默认参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct st { int a,b;string str; st () :a (), b (), str (){} st (int aa, int bb,string cc) :a (aa), b (bb), str (cc){} }; st t1; t1={2 ,3 ,"str" }; t1={2 ,3 };
列表构造式还可以自推导。
1 2 3 4 5 6 7 8 9 10 11 struct st { int a,b=1 ;string str="str" ; st (int aa,int bb){ a=aa; b=bb; } }; st t1={2 ,3 }; t1={3 ,4 };
语法概念
运算优先级
容易出问题的是几个位运算。
永远记住,位运算除了左右移位外,优先级低于比较操作。
比较操作之间,等于和不等的优先级低于大于小于。
引用
引用可以认为是一个隐式指针 ,不需要 *
的修饰便可以直接访问内容。它既然是一个指针,自然会占用一个
long 的空间。
定义,返回一个引用
可以在函数中返回一个引用,只需要在函数名前面后加一个
&,定义引用也在变量名加一个
&,和指针类似。
1 2 3 4 double &setValues (int i) { double &ref = vals[i]; return ref; }
定义引用的示例:
1 2 3 4 5 6 7 8 int n=0 ,m=0 ;int & nn=n,mm=m;n=10 ,m=20 ; printf ("n,m:%d,%d\n" ,n,m);printf ("nn,mm:%d,%d\n" ,nn,mm);
在重载结构体 +=,= 运算符号的时候比较常用。
引用传参应该已经很熟悉了吧。
左值引用和右值引用
C++11 新概念。参考阅读
一般的,使用引用传参时,如果接受的参数为右值,那么不能修改右值,参数必须声明为
const 类型。
算了,先咕咕咕了吧。
模板
如果用的不好,容易出现找不到实现的问题。
一个原则:所有东西的类型必须在使用前就得知。
类模板
继承的时候需要写明父类的类型参数,如果子类也是模板类,可以用子类的类型模板参数作为父类的类型模板参数。
函数模板
函数模板是可以自动推导类型的,如果出现类型冲突,那么会报
CE。
常见例子是 std::max(1,1ll) 报错。
注意字面量的类型。
指定模板参数可以仅指定一段前缀,剩下的采用自动推导。
多文件的一些问题
按惯例编写的问题
通常情况下,你会在 .h
文件中声明函数和类,而将它们的定义放置在一个单独的 .cpp
文件中。但是在使用模板时,这种习惯性做法将变得不再有用,因为当实例化一个模板时,编译器必须看到模板确切的定义,而不仅仅是它的声明。
定义和声明一起写
可以将模板的声明和定义都放置在同一个 .h 文件中。这就是为什么所有的
STL 头文件都包含模板定义的原因。
习惯来说,一般把这种包含定义的头文件后缀名写为
.hpp。
使用 export
咕咕咕
Static 关键字
声明一个静态的东西,可以用来避免命名冲突,也可以用来更好的实现一个类。
Static
关键字声明的东西生命周期是整个程序的生命周期,但作用域仅限定为声明处的作用域。
举个例子,某个函数的执行过程和它被调用的次数有关,这个可以弄个全局变量记一个次数,但是我们也可以在它的内部写个
static 变量记录,这样就不会与外部空间变量名冲突。
所有静态变量的初始值为 0
1 2 3 4 5 6 7 8 int cnt=0 ;void fun () { static int cnt; cout<<++cnt<<endl; } fun ();cnt=10 ;cout<<cnt<<endl; fun ();fun ();cnt++;fun ();
也可以用静态成员来维护一个类,比如写链表的时候,我们通常要先写个 node
类,然后才能实现 list 类,但我们可以直接将 head
指针作为一个静态变量放入结构体中,这样,这个静态变量就可以被所有结构体的实例访问修改。
注意,如果需要两个链表,那种这种方式是不可取的,因为静态变量
head 对所有该结构体的实例来说都只有一个。
我们当然也可以声明静态成员函数。
对于结构体的静态资源,我们可以用 {结构体名}.{成员名} 来访问。
函数,匿名函数
C++
的函数名,本质上是一个指向某个函数的指针,函数在二进制层面和数据没有差异。
functor (一般译为算子),也可以调用,但它是一个对象。
函数类
C++
的函数可以当作一个对象处理的,函数名是指向该对象的指针 ,比如说:
1 2 3 4 5 6 7 void add (int &x) {x++;}void del (int &x) {x--;}void doit (int &x,function<void (int &)> f) {(x);}n=10 ;doit (n,del); printf ("%d\n" ,n);n=20 ;doit (n,add); printf ("%d\n" ,n);
匿名函数
格式:["捕获列表"]("参数列表")->"返回类型"{"函数体"}
捕获列表指的捕获局部变量,在函数中可以使用和修改。
参数列表无参数时可以省略,返回值如果不指定则会自动推导。
捕获列表不可省略,[] 为强制不捕获,[&] 为引用捕获全部,[=]
为取值捕获全部,也可以用变量名 [x,&y] 取值捕获 x,引用捕获 y。
1 sort (v.begin (),v.end (),[](const int &x,const int &y){return x>y;});
匿名函数也可以作为一个对象处理。
虚函数
这个东西自己写的时候用的比较少,毕竟不太注重面向对象,
这玩意和 Python 的 abstract_method
类似,我也不知道怎么解释,反正会用就够了。
竞赛中应用的话,可以用来重写 pd_ds
里面的平衡树,但是真的用的很少。
语法糖
函数相关
返回值为 void 的函数
你是否碰到过这样的情况,函数返回值为
void,但是返回又是有条件,并且还需要执行一些简单的操作,用
{} 括起来显得代码冗长,不妨试试:
1 2 3 4 5 6 7 void do_cool_thing (int arg) { if (some_condition) return void (a_simple_expression); do_cool_thing (arg); } void do_cool_thing (int arg) { if (some_condition) return void (ans++); }
有返回值的函数
, 可以用在 return
语句,会返回最后一个值,当然也可以用在其它 Case。
1 2 3 4 5 6 7 int get_v () { return 3 ,5 ; } int new_node (int x) { return a[++t]=node{mt_rand (), 1 , x, 0 , 0 }, t; }
列表初始化器
这东西说白了就是 {1,2,3,4,5}
这种,注意区分类成员初始化器,后者可以多类型,前者只能单类型。
循环
1 2 3 for (int v:{0 ,1 }){ do_cool_thing (); }
分支更少,效率更高,代码更优美!