算法竞赛入门经典:习题与解答
上QQ阅读APP看书,第一时间看更新

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),就要写得很长,例如:

而在C++11中就可以这么写:

编译器遇到auto之后会根据右边的表达式自动推导出其具体类型。同时也支持引用类型的变量:

1.2.2 空指针值(nullptr)

在之前的C/C++代码中,如果要表示空指针,一般使用“p=NULL;”,实际上NULL只是一个定义为常整数0的宏,这样有时候就可能和整数类型混淆。

在C++11中,有专门的用来表示空指针的数据类型:nullptr。nullptr关键字代表值类型std::nullptr_t,在语义上可以被理解为空指针。

之前的写法:

C++11中的写法:

1.2.3 容器的for循环遍历

以前去遍历一个STL中的集合(如vector<int>)时要写出非常烦琐的代码:

到了C++11中,其实可以这么写:

或者如果要修改容器中的数据:

这个语法和Java中的遍历方式非常像。其实不仅仅是vector,所有的标准容器,如map、string、deque、list,甚至数组都可以这么遍历,非常方便。

1.2.4 匿名函数(Lambda)

匿名函数是笔者认为最重要的改进,是函数式编程(Funcitonal Programming Style)风格的基石。简单地说,就是可以在需要的地方定义函数,而不是提前定义好才能用:

以C++98的STL中for_each(InputIterator first,InputIterator last,Function fn)为例,第3个参数需要一个functor(函数对象)。所谓函数对象,其实是一个类,这个类重载了operator,于是这个对象可以像函数一样被使用。

以前STL中的很多算法都是需要传入functor的,写起来非常麻烦。C++11中,就可以直接用lambda代替。另外,利用C++的lambda函数内部也可以对外围作用域的变量进行捕捉:

上述代码中的lambda函数内部要对total变量进行写操作,所以声明的[&total]部分对total进行按引用捕捉。

另外,还可以直接像声明一个变量一样声明一个函数:

或者声明的类型部分也可以直接使用类型推导:

关于lambda的用法,有非常大的想象空间。建议读者参考以下资料仔细学习:https://msdn.microsoft.com/zh-cn/library/dd293608.aspx。

1.2.5 统一的初始化语法

在C++98中,对于数组可以这样初始化其内容:

但是对于STL中的容器,就必须一个一个元素进行附加:

在C++11中,可以使用像数组那样的初始化语法对STL容器进行初始化:

1.2.6 哈希容器

比赛中,经常有用哈希容器存储数据的需要,而C++98标准的STL中并没有提供基于hash算法的容器,基于平衡二叉树实现的map可以起到类似的作用,但是在数据量较大时速度还是不够快(查询时间复杂度是O(logn)的),有时就不得不自己手动编写Hash算法。而在C++11中正式引入了几个基于Hash算法的容器:unordered_map、unordered_set、unordered_multimap和unordered_multiset。

当不需要元素排序时,可以尽量使用这些容器来获得更好的查找性能。

其他Hash容器的用法类似。

默认的Hash容器只是提供了内置数据类型的Hash算法,如果是自定义类型,就需要提供自定义的Hash函数。自定义类型可能包含几种内置类型,可以分别算出其Hash,然后对它们进行组合得到一个新的Hash值,一般直接采用移位加异或(XOR)便可得到基本够用的哈希值(碰撞不太频繁)。容器处理碰撞时需判断两对象是否相等,所以必须提供判断相等的方法,建议重载“==”操作符: