..

1.2 C++11 语言特性介绍

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 中,有专门的用来表示空指针的数据类型:nullptnullptr 关键字代表值类型 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,所有的标准容器,如 mapstringdequelist,甚至数组都可以这么便利,非常方便。

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_mapunordered_setunordered_multimapunordered_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
*/