1.2 C++11 语言特性介绍
- 1.2.1 类型推到(auto)
- 1.2.2 空指针集(nullptr)
- 1.2.3 容器的 for 循环遍历
- 1.2.4 匿名函数(Lambda)
- 1.2.5 统一的初始化语法
- 1.2.6 哈希容器
1.2.1 类型推到(auto)
如果要使用比较长的类型声明,最常见的就是 STL 中的枚举器(iterator
),就要写得很长,例如:
vector<int> vec;
vector<int>::iterator cit = vec.begin();
而在 C++11 中就可以这么写:
auto cit = vec.begin();
编译器遇到 auto
之后会根据右边的表达式自动推导出其具体类型。同时也支持引用类型的变量:
vector<int> vec = {1,2,3};
auto& v2 = vec[1];
v2 += 3; //vec 就变成{1, 5, 3}
1.2.2 空指针集(nullptr)
在之前的 C/C++ 代码中,如果要表示空指针,一般使用 “p=NULL;
”,实际上 NULL
只是一个定义为常整数 0 的宏,这样有时候就可能和整数类型混淆。
在 C++11 中,有专门的用来表示空指针的数据类型:nullpt
。nullptr
关键字代表值类型 std::nullptr_t
,在语义上可以被理解为空指针。
之前的写法:
char *p = NULL;
int i = NULL; //这里不会报错,因为 NULL 本质上就是 0
C++11 中的写法:
char *p = nullptr;
int i = nullptr; //这里会报错,因为 nullptr 不再是整数类型
if(p) //这里仍然可以转换为 bool 的 false
1.2.3 容器的 for 循环遍历
以前去便利一个 STL 中的集合(如 vector<int>
)时要写出非常烦琐的代码:
vector<int> vec;
for(vector<int>::iterator it = vec.begin(); it != vec.end(); it++) {
*it += 2;
cout<<*it<<endl;
}
到了 C++11 中,其实可以这么写:
for(const auto& p : vec) {
cout<<p<<endl;
}
或者如果要修改容器中的数据:
for(auto& p : vec) p += 2;
这个语法和 Java 中的遍历方式非常像。
其实不仅仅是 vector
,所有的标准容器,如 map
、string
、deque
、list
,甚至数组都可以这么便利,非常方便。
int arr[] = {1,2,3,4,5};
for(int& x : arr) x += 2;
1.2.4 匿名函数(Lambda)
匿名函数是最重要的改进,是函数式编程分割(Functional Programming Style)的基石。
简单的说,就是可以在需要的地方定义函数,而不是提前定义好才能用:
using namespace std;
#define _for(i,a,b) for( int i=(a); i<(b); ++i)
const int N = 10000000;
struct TS{
int a, b, c;
};
void genData(){
_for(i, 0, N) {
tss[i].a = rand();
tss[i].b = rand();
tss[i].b = rand();
}
}
int main()
{
genData();
sort(tss, tss+N, [](const TS& t1, const TS& t2)) {
if(t1.a != t2.a) return t1.a < t2.a;
if(t1.b != t2.b) return t1.b < t2.b;
return t1.c <= t2.c;
});
return 0;
}
以 C++98 的 STL 中 for_each(InputIterator first, InputIterator last, Function fn)
为例,第 3 个参数需要一个 functor
(函数对象。
所谓函数对象,其实是一个类,这个类重载了 operator
,于是这个对象可以像函数一样被使用。
以前 STL 中的很多算法都是需要传入 functor
的,写起来非常麻烦。C++11 中,就可以直接用 lambda
代替。
另外,利用 C++ 的 lamba
函数内部也可以对外围作用域的变量进行捕捉:
vector<int> list{1,2,3};
int total = 0;
for_each(list.begin(), list.end(), [&total](int x) { //匿名函数,捕捉 total
total += x;
});
cout << total << endl;
上述代码中的 lamba
函数内部要对 total
变量进行写操作,所以声明的 [&total]
部分对 total
进行按引用捕捉。
另外,还可以直接像声明一个变量一样声明一个函数:
//将 lambda 赋值给有类型的变量然后作为参数传递
total = 0;
std:function<void(int)> add = [&total](int x) { total += x; };
for_each(begin(list), end(list), add);
cout << total<<endl;
或者声明的类型部分也可以直接使用类型推导:
total = 0;
auto add2 = [&total](int x) { total += x; }; //类型推导 lambda 的类型
for_each(begin(list), end(list), add2);
cout << total<<endl;
1.2.5 统一的初始化语法
在 C++98 中,对于数组可以这样初始化其内容:
int arr[] = {1,2,3};
但是对于 STL 中的容器,就必须一个一个元素进行附加:
vector<int> vec;
vec.push_back(1); vec.push_back(2); vec.push_back(3);
在 C++11 中,可以使用像数组那样的初始化语法对 STL 容器进行初始化:
vector<string> vec{1,2,3};
map<string, string> dict{ {"ABC", "123"}, {"BCD", "234"}}; //map 也可以
1.2.6 哈希容器
比赛中,经常有用哈希容器存储数据的需要,而 C++98 标准的 STL 中并没有提供基于 hash 算法的容器,基于平衡二叉树实现的 map
可以起到类似的作用,但是在数据量较大时速度还是不够快(查询时间复杂度是 $\mathcal{O} (\log n)$ 的),有时就不得不自己手动编写 Hash 算法。
而在 C++11 中正式引入了几个基于 Hash 算法的容器:unordered_map
、unordered_set
、unordered_multimap
和 unordered_multiset
。
当不需要元素排序时,可以尽量使用这些容器来获得更好的查找性能。
unordered_map<string,int> um {
{"Dijkstra",1972}, {"Scott",1976},
{"Wilkes",1967}, {"Hamming",1968}
};
um["Ritchie"] = 1983;
for(auto x : um) cout << '{' << x.first << ',' << x.second << '}';
其他 Hash 容器的用法类似。
默认的 Hash 容器只是提供了内置数据类型的 Hash 算法,如果是自定义类型,就需要提供自定义的 Hash 函数。
自定义类型可能包含几种内置类型,可以分别算出其 Hash,然后对它们进行组合得到一个新的 Hash 值,一般直接采用移位加异或(XOR)便可得到基本够用的哈希值(碰撞不太频繁)。
容器处理碰撞时需判断两对象是否相等,所以必须提供判断相等的方法,建议重载 “==
” 操作符:
#include <unordered_map>
#include <string>
#include <iostream>
using namespace std;
struct Type
{
int x; string y;
bool operator==(const Type& a) const {
return x == a.x && y == a.y;
}
};
struct HashFunc
{
std::size_t operator() (const Type &o) const
{
return ((hash<int>()(o.x)
(hash<string>()(o.y) << 1)) >> 1);
}
};
int main() {
unordered_map<Type, string, HashFunc> testHash =
{
{ { 1, "1"}, "one" },
{ { 2, "2"}, "two" },
{ { 3, "3"}, "three"}
};
for(const auto& kv : testHash)
cout<<kv.first.x<<","<<kv.first.y<<" - "<<kv.second<<endl;
return 0;
}
/*
输出:
3,3 - three
2,2 - two
1,1 - one
*/