![算法竞赛入门经典:习题与解答](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=1739372323-BvPjPzjVVCnotxpKiSwp5JJHVGIT0kmf-0-be9438af72013e392b100e84c5f436ba)
而在C++11中就可以这么写:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P17_2.jpg?sign=1739372323-LJUHfP4EIdUXnw3VINAvFCphNtwPlIvu-0-1d52524aad6bd005ecdb31fbbe678d31)
编译器遇到auto之后会根据右边的表达式自动推导出其具体类型。同时也支持引用类型的变量:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P17_3.jpg?sign=1739372323-79TOwgxKb5PJ8UvTXquq0TPsZP8bDbfj-0-d1c26a33ffd1d97dd6cf6e4297b72d1e)
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=1739372323-J0Hafxa7fw9TEXtFOZPfDpnwr1l5jEaq-0-ed0c64f7bfda04175eaab5a6ea4eea26)
C++11中的写法:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P17_5.jpg?sign=1739372323-iYksYvSf4GVbhRkHKFcnBF4sKxeSagkI-0-d1d8a6b34d36cd20e070c6449e0566fc)
1.2.3 容器的for循环遍历
以前去遍历一个STL中的集合(如vector<int>)时要写出非常烦琐的代码:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P17_6.jpg?sign=1739372323-Mbk3U22lMZAlQSF18QUlZpJ066G7yVyj-0-55a1db5a689d8fc08e515afc01983433)
到了C++11中,其实可以这么写:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P18_1.jpg?sign=1739372323-ERUU3qTWEfygETyuwTs4I73fQihe0PLL-0-d63a91c7b433d69d470a5a9ad3fb6232)
或者如果要修改容器中的数据:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P18_2.jpg?sign=1739372323-OlteBMOv7GPYMCvy1tWnSK4EdbxPESMm-0-30e6f8fc2ec9d3af9b2826234e4b0e15)
这个语法和Java中的遍历方式非常像。其实不仅仅是vector,所有的标准容器,如map、string、deque、list,甚至数组都可以这么遍历,非常方便。
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P18_3.jpg?sign=1739372323-bMlM5wgnQw5Xxaps5JAg0prnWNO5FEka-0-2f10b184487b1f5e7bd327cae80ce3a6)
1.2.4 匿名函数(Lambda)
匿名函数是笔者认为最重要的改进,是函数式编程(Funcitonal Programming Style)风格的基石。简单地说,就是可以在需要的地方定义函数,而不是提前定义好才能用:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P18_4.jpg?sign=1739372323-NDL0LqCf4X5rp9IbWCSptIqzDGKrVjXa-0-7cfc87e9454bb263df301b42dc58d314)
以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=1739372323-MhdT23T1RVUxDO7kG7icNlS7qfAOOUqo-0-6f37ea109bcbb2269a126acea1f12acb)
上述代码中的lambda函数内部要对total变量进行写操作,所以声明的[&total]部分对total进行按引用捕捉。
另外,还可以直接像声明一个变量一样声明一个函数:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P19_3.jpg?sign=1739372323-Ih1v6dUUiyMUWRgJyKm4LxdOHp9IVixH-0-8325a6a481a66890ec99a6e19781c21c)
或者声明的类型部分也可以直接使用类型推导:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P19_4.jpg?sign=1739372323-tEOpbBo8D48zbldJW4FuPWPWLCvuIirp-0-f3e25e9da5c250311c28fd4ec7b9e5fd)
关于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=1739372323-8G2hh9dNAZMqfXzAPAOGKQFr0IbAziby-0-7cbb8140a66f00df62bdc352402120de)
但是对于STL中的容器,就必须一个一个元素进行附加:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P20_1.jpg?sign=1739372323-nqMAkYMN4VrDx39LD3sdMSAu7ZTTNQDr-0-79154bb45c102ad98a91e3204ff9ae61)
在C++11中,可以使用像数组那样的初始化语法对STL容器进行初始化:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P20_2.jpg?sign=1739372323-Ybh3Y9YomIZmQ9JFb0a28HrdkHtZrBig-0-0fb31ff3873a32de468be65eeb11f4dc)
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=1739372323-ixUjTtmtBqYCNynXKI3XoSnAMZvhMdhN-0-44289d71244193623c41d8f083960244)
其他Hash容器的用法类似。
默认的Hash容器只是提供了内置数据类型的Hash算法,如果是自定义类型,就需要提供自定义的Hash函数。自定义类型可能包含几种内置类型,可以分别算出其Hash,然后对它们进行组合得到一个新的Hash值,一般直接采用移位加异或(XOR)便可得到基本够用的哈希值(碰撞不太频繁)。容器处理碰撞时需判断两对象是否相等,所以必须提供判断相等的方法,建议重载“==”操作符:
![](https://epubservercos.yuewen.com/BD1DD6/15253382405216406/epubprivate/OEBPS/Images/Figure-P21_1.jpg?sign=1739372323-uGj0uEI0NRG3ohpSLYtgsXsMgaL8ZtJr-0-c197649bdf836aa55266a7c5f6cf2e20)