![算法竞赛入门经典:习题与解答](https://wfqqreader-1252317822.image.myqcloud.com/cover/371/26793371/b_26793371.jpg)
1.2 C++11语言特性介绍
笔者写作本书时主流的算法比赛以及在线OJ平台均已支持最新的C++11语言标准。从开发者的角度来看,新标准中提供了不少能提高开发效率的新特性。本节选择了一些在算法比赛中常用特性进行介绍,希望读者通过练习掌握这些语言特性,提高在比赛中的编码速度和正确率。本书后文中的题解代码也有较多使用C++11的案例,请读者参考。
需要注意的是,如果是使用g++编译,编译器需要加命令行参数-std=c++11。如果用Visual Studio,则至少需要2013版本。在各大OJ在线提交时,也要选择C++11。
1.2.1 类型推导(auto)
如果要使用比较长的类型声明,最常见的就是STL中的枚举器(iterator),就要写得很长,例如:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P17_1.jpg?sign=1739904095-RSRDrPz7YLaAlAhq9HapG9hyu7l97lpF-0-59b91c6ef2dbb1001d8e1f366b9b719c)
而在C++11中就可以这么写:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P17_2.jpg?sign=1739904095-SLtuOHW5sJXttjUZflrxh1gCi0efCDp0-0-55e3b91b588c375772f3dcd772312163)
编译器遇到auto之后会根据右边的表达式自动推导出其具体类型。同时也支持引用类型的变量:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P17_3.jpg?sign=1739904095-p2ftF2wEHJa1FmSwP5ssWw0t7UB8KbYi-0-27f8a0a89db36a96d905f9b8eab26855)
1.2.2 空指针值(nullptr)
在之前的C/C++代码中,如果要表示空指针,一般使用“p=NULL;”,实际上NULL只是一个定义为常整数0的宏,这样有时候就可能和整数类型混淆。
在C++11中,有专门的用来表示空指针的数据类型:nullptr。nullptr关键字代表值类型std::nullptr_t,在语义上可以被理解为空指针。
之前的写法:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P17_4.jpg?sign=1739904095-ZGo689mtFiV1B2xNLAMwKQWBnpYhtvvW-0-1a69fb0a767aa6b4f2e572ccc26c7c3e)
C++11中的写法:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P17_5.jpg?sign=1739904095-i9TBXO6hFWknuNcfbzsQVmM920QXpAQc-0-343d12ecd939ae3679587840eabf02a6)
1.2.3 容器的for循环遍历
以前去遍历一个STL中的集合(如vector<int>)时要写出非常烦琐的代码:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P17_6.jpg?sign=1739904095-jnLIZAm32biMyKWTyjnQcssRuWkonbNT-0-000d227edd6bb3b1f04d21472e9eda7c)
到了C++11中,其实可以这么写:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P18_1.jpg?sign=1739904095-YRLanJnoEqIL2Kz6LkcUxKLK1urLPRr9-0-bbefc9e8179efb62abb73b5245a3b7fa)
或者如果要修改容器中的数据:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P18_2.jpg?sign=1739904095-X96UeXaiQp75VSAy2Xs1Z0iEEIhxFoIs-0-2c8a0143e7da07ba5ff8fd52e1a4ea6f)
这个语法和Java中的遍历方式非常像。其实不仅仅是vector,所有的标准容器,如map、string、deque、list,甚至数组都可以这么遍历,非常方便。
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P18_3.jpg?sign=1739904095-xU2jlnWIvnrPw7IICHyHJkVn3XwA9D4A-0-72d19930ea3a3c895257335a802b1f97)
1.2.4 匿名函数(Lambda)
匿名函数是笔者认为最重要的改进,是函数式编程(Funcitonal Programming Style)风格的基石。简单地说,就是可以在需要的地方定义函数,而不是提前定义好才能用:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P18_4.jpg?sign=1739904095-1FOssyI0F2399GCt5j1kof4FzC4rSXXQ-0-dad5037ce4f99f4586686a6aca9339fb)
以C++98的STL中for_each(InputIterator first,InputIterator last,Function fn)为例,第3个参数需要一个functor(函数对象)。所谓函数对象,其实是一个类,这个类重载了operator,于是这个对象可以像函数一样被使用。
以前STL中的很多算法都是需要传入functor的,写起来非常麻烦。C++11中,就可以直接用lambda代替。另外,利用C++的lambda函数内部也可以对外围作用域的变量进行捕捉:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P19_2.jpg?sign=1739904095-Qx3dD9PVt33GWQoWjAWRWPAsLqJm2DKw-0-2b40af05f34167aa205a5bb7c31d54dc)
上述代码中的lambda函数内部要对total变量进行写操作,所以声明的[&total]部分对total进行按引用捕捉。
另外,还可以直接像声明一个变量一样声明一个函数:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P19_3.jpg?sign=1739904095-YZxJB4LPb4po8jbBh0fvg9LFJNeT08yy-0-8b55c9ac07fe8cfce0805fa875a7f24d)
或者声明的类型部分也可以直接使用类型推导:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P19_4.jpg?sign=1739904095-XE5306uhwciETumly8vLz8xFhocvqztk-0-68170ac8a3215f9fafccfba9f96bdd93)
关于lambda的用法,有非常大的想象空间。建议读者参考以下资料仔细学习:https://msdn.microsoft.com/zh-cn/library/dd293608.aspx。
1.2.5 统一的初始化语法
在C++98中,对于数组可以这样初始化其内容:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P19_5.jpg?sign=1739904095-LiwXXcqQy0Qa82caLcucVUTb2bZ5AVcf-0-049bc97b0f1860088e30444644409d98)
但是对于STL中的容器,就必须一个一个元素进行附加:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P20_1.jpg?sign=1739904095-Ktx3VOW0kWgrYX5feZ8HkiKa9R6vtdOm-0-781bf71edc8a35cc01e1060e358ba905)
在C++11中,可以使用像数组那样的初始化语法对STL容器进行初始化:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P20_2.jpg?sign=1739904095-1Ea5SLFbK22iItNjeVTF0nuIrUKtTcQu-0-f1c4001aa817bf18dfa8a3c6d25f4809)
1.2.6 哈希容器
比赛中,经常有用哈希容器存储数据的需要,而C++98标准的STL中并没有提供基于hash算法的容器,基于平衡二叉树实现的map可以起到类似的作用,但是在数据量较大时速度还是不够快(查询时间复杂度是O(logn)的),有时就不得不自己手动编写Hash算法。而在C++11中正式引入了几个基于Hash算法的容器:unordered_map、unordered_set、unordered_multimap和unordered_multiset。
当不需要元素排序时,可以尽量使用这些容器来获得更好的查找性能。
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P20_3.jpg?sign=1739904095-04NIUZE5FLQjrtUQ2jEjYbxUV8OQeFVL-0-fdf10bbce57b373e5627328c1d819445)
其他Hash容器的用法类似。
默认的Hash容器只是提供了内置数据类型的Hash算法,如果是自定义类型,就需要提供自定义的Hash函数。自定义类型可能包含几种内置类型,可以分别算出其Hash,然后对它们进行组合得到一个新的Hash值,一般直接采用移位加异或(XOR)便可得到基本够用的哈希值(碰撞不太频繁)。容器处理碰撞时需判断两对象是否相等,所以必须提供判断相等的方法,建议重载“==”操作符:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P21_1.jpg?sign=1739904095-z21SAyJVyt7l00aaushUehNZ3a6IWRpQ-0-0aa7643a1a0c61a931c8ccf931f17c62)