C++ 语言特性杂谈与常数优化
起因是发现很多同学对常数优化和语句的执行效率有误解(我肯定也有,但是我尽量把我知道的正确的写出来),因此写一篇杂谈做一个说明,同时介绍一些比较新的好用的语言特性(C++14)
template
模板关键字
函数
template
顾名思义是用来做模板的,那是做什么的模板呢? 举个例子,一般来说我们写 max
函数替代 std::max
来优化的时候,写出来的大概长下面这样:
1 | int max(int a, int b) { |
在这三行代码中,第一行定义了 max
的返回值和参数 a
b
的类型为 int
,显然意味着这个函数只能比较 int
类型的量。
那么如果既想要比较 int
又想要比较 long long
还想比较 char
等等,如果按传统的写法,就是 Ctrl+C Ctrl+V 写好几个比较函数,还得重新命名,甚是麻烦。
观察写出来的几个函数,肉眼可见他们的本质区别只有返回值与参数的类型。那么有没有一种类似模板的东西,写一个函数的主要部分作为模板,返回值、变量的类型由编译器决定呢?
这就是 template
的一大作用,也是 OI 中最常用的方面。
使用起来也很简单,在函数声明前面加上 template <typename T>
,此处 T 可以是随便一个字母或者单词,后面写函数的时候把 int
替换成 T
即可。
1 | template <typename T> |
此时的 T
相当于一个变量类型,如果这个函数被调用,编译器会给你生成一个符合传入参数的类型的 max
函数。
当然了,如果是一个没有传参的函数,我们也能指定其类型。
这样写会报错:
1 | template <typename T> |
原因是没有传参进去,编译器无法推断模板函数的类型。
类比 STL 容器 中的使用方法,能不能用尖括号带一个类型名称进去呢?可以的。
1 | template <typename T> |
这样写就不会报错了
模板类
和模板函数类似,我们可以泛化结构体中的类型。
1 | struct qwq { |
存紫妈的年龄可能会爆 int
,存阿求的年龄可能浪费 long long
的空间,于是可以:
1 | template <class T> |
这样,akyuu.age
的类型即是 int8_t
,yukari.age
的类型即是 __int128
。
TODO: ...
关键字
基于范围的循环
不过多介绍了,主要在 STL 里用
1 | for (auto i : container) { |
container
为容器的名称,auto
是 C++11 以后的类型名称,编译时由编译器推断。
此处 auto
的效果就是把 i
的类型设置为 container
的元素的类型。如果 container
是 vector<int>
,则上面语句的效果就是遍历 container
的所有元素并输出。
此外,如果想遍历的同时修改容器中的元素,可以在 i
前加取址符 &
表示引用,如下
1 | for (auto &i : container) { |
该段代码的效果即为遍历容器 container
,同时把其中所有元素赋值为 0
;
函数相关
Lambda 表达式
实际上就是定义了一个函数。
基本语法:
1 | [捕获](参数){ |
捕获一般没啥用,留空就好。
Lambda 表达式在 C++ 中是一个类型,但是不能用类型名定义,所以用 auto
声明。
举个例子,编写 max
函数:
原来是这样的
1 | int max(int a, int b) { |
用 Lambda 表达式长这样:
1 | auto max = [](int a, int b) { // 捕获部分留空,传参和内部实现不变 |
看起来这两种方式没有什么区别,确实是这样,但是 Lambda 还有一个重要性质——匿名。
显然,原来写函数的时候必须要声明,声明这个函数的名称,返回值类型,传参类型,内部实现。
但是观察到上面声明 Lambda 表达式 max
的时候,使用的是类似变量赋值的方法。那是不是只留下等式右边的部分也可以当函数使用?实际上是可以的,这就是 Lambda 函数的匿名性质。即引入 Lambda 表达式之后,就可以随时随地定义函数而不给它起名字。
听起来有点抽象,再举个例子:std::sort
原来我们想要自定义比较函数,只能这样:
1 | bool cmp(int a, int b) { |
引入 Lambda 表达式之后:
1 | std::sort(a.begin(), a.end(), [](int a, int b){return a > b;}); |
在这里,我们并没有给比较函数起名字,就直接扔到了 std::sort
里面,这样做方便很多~
读者可能有疑问:这样做效率如何?事实上,Lambda 表达式和下面的函数对象的效率都是比较高的,比第一种方式中 cmp
这种函数指针效率高。
函数对象
假定你已经学会了类/结构体的运算符重载。
观察函数组成 function()
,它是不是非常像一个类型 function
搭配上了 ()
这个运算符?太像了!
那我们是不是可以这样做:开一个对象,重载 ()
这个运算符再当函数用?当然可以。
还是用 max
举例:
1 | struct qwq { |
非常自然,非常易懂。
效率
懒得测了。可以参考下 这个博客
利用如上链接内方式测试的结果是:差不多。