抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

CodingStudio

努力进步

引言

侯捷老师C++标准11-14学习笔记


1 演进,环境与资源

  • C++1.0为C++98
  • C++2.0为C++11
  • C++2.0新特性包括语言标准库两个层面(标准库以header files形式呈现)
    • C++标准库的header files不带副档名(.h)
    • 新式C header files不带副名称
    • 旧式C header files(带有副名称.h)仍可用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include<type_traits>
#include<unordered_set>
#include<forward_list>
#include<array>
#include<tuple>
#include<regex>
#include<thread>

using namespace std;

// 第一步,确认支持C++11
// 如果C++版本支持C++11则__cplusplus为201103L
#define __cplusplus 201103L
// 如果C++版本不支持C++11则__cplusplus为199711L
#define __cplusplus 199711L

// 使用程序打印出C++的版本
#include"stdafx.h"
#include<iostream>
using namespace std;

int main(){
cout<<__cplusplus<<endl;
return 0;
}

// 需要设置编译器属性来支持C++14

2 variadic Templates(数量不定的模板参数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 为处理最后的空参数的情况,添加该函数,否则会报错
void print(){
// 空函数
}

// 版本二
template <typename T, typename... Types>
void print(const T& firstArg, const Types&... args){
const << firstArg << endl;
print(args...); // 帮助实现递归
}

// ...表示接受可变数量的参数
// ...为所谓的pack(包)
// 用于模板参数则为模板参数包;
// 用于函数参数类型则为函数参数类型包;
// 用于函数参数则为函数参数包

// 用于知道args参数有多少个
sizeof...(args);

// 第三个版本
template<typename... Types>
void print(const Types&... args){
/* ... */
}
// 该版本可以与版本二并存
// 版本二更加特化,版本三更加特化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 更加方便地完成函数调用
class CustomerHash{
public:
std::size_t operator()(const Customer& c) const{
return hash_val(c.fname, c.lname, c.no);
}
}

// hash_val的版本一
template<typename... Types>
inline size_t hash_val(const Types... args){
size_t seed = 0;
hash_val(seed, args...);
return seed;
}

// hash_val的版本二
template<typename T, typename... Types>
inline void hash_val(size_t& seed, const T& val, const Types... args){
hash_combine(seed, val);
hash_val(seed, args...);
}

// hash_val的版本三,可作为终止递归的函数
template<typename T>
inline void hash_val(size_t& seed, const T& val){
hash_combine(seed, val);
}

// hash_combine函数
#include<functional>
template<typename T>
inline void hash_combine(size_t& seed, const T& val){
seed ^= std::hash<T>()(val) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}

// hash_val不能调用版本二(第一个参数不符合),版本三(参数不符合),只能调用版本一
// 随后调用版本二
// 递归调用版本二
// 最后调用版本三终止递归调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template<typename... Values> class tuple;
template<> class tuple<>{};

template<typename Head, typename... Tail>
class tuple<Head, Tail...>
:private tuple<Tail...> //私有继承一包参数
{
typedef tuple<Tail...> inherited;
public:
tuple(){}
tuple(Head v, Tail... vtail)
:m_head(v), inherited(vtail...){}

typename Head::type head {return}
inherited& tail { return *this;}
protected:
Head m_head; //第一个参数定义为一个变量
}

// 例:tuple<int, float, string> t(41, 6.3, "nico");
  • tuple可以放任意数量的任意类型的元组

3 模板表达式的空格, nullptr与std::nullptr_t, auto

3.1 模板表达式的空格

  • 在之前版本的C++98中,模板中模板参数为一个模板时,两个>>之间必须包含一个空格

vector<list<int> >

  • 在C++2.0中,空格可以省略

vector<list<int>>

3.2 nullptr与std::nullptr_t

  • C++11使用nullptr代替NULL或者0给指针赋值为空指针
  • nullptr的类型为std::nullptr_t, 定义在cstddef中
1
2
3
4
5
6
void f(int);
void f(void*);

f(0); //调用上面的函数
f(NULL); //可调用上面的函数,也可以调用下面的函数,编译器不知道该调用哪一个
f(nullptr); //调用下面的函数

3.3 auto

  • 使用auto自动推断变量类型
    • 编译器可推断变量类型
    • 建议auto用于类型名太长或类型名过于复杂的情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
auto i = 42;
double f();
auto d = f(); //推断d为double类型

vector<string> v;
auto pos = v.begin(); //vector<string>::iterator
auto L = [](int x) -> bool{ //Lambda表达式的类型推断
...,
}
// L为一个对象

list<string> c;
...
list<string>::iterator ite;
ite = find(c.begin(), c.end(), target);
// 可简化为
list<string> c;
...
auto ite = find(c.begin(), c.end(), target);

// 在标准库中的使用示例
inline auto operator-(const reverse_iterator<_Iterator>& __x,
const reverse_iterator<_Iterator>& __y,)
-> decltype(__y.base() - __x.base())

4 一致性的初始化

  • 初始化可能发生在(),{}或者=赋值符号中
  • C++11引入一致性的初始化:任何初始化可以使用大括号{}进行
    • 编译器看到{t1,t1,…}便做出一个initializer_list<T>,initializer_list<T>关联到一个array<T,n>(n为个数)
    • 调用函数有参数为initializer_list<T>的版本,则调用该版本,否则将该array内的元素分解依次传给函数
      • 调用函数(构造函数)时,该array内的元素可被编译器分解依次传给函数
      • 若函数参数是initializer_list<T>,调用者却不能给数个T参数然后以为它们会被自动转为一个initializer_list<T>传入
1
2
3
4
5
6
// 使用示例
int values[]{1,2,3};
vector<int> v {2,3,4,5,6};
vector<string> cities {"Berlin","New York","Cairo"};
// C++标准库中的复数
complex<double> c{4.0,3.0}

5 initializer_list<T>

  • {}可以被用来设定初值
  • {}不允许窄化转换,不允许向低位数转换
    • 但是在开发平台上只是提出警告
  • 传给initializer_list的必须为initializer_list或者为{…}的形式
    • C++11提供std::initializer_list<>模板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// {}可以被用来设定初值
int i; // 初值未定义
int j{}; // 初值为0
int* p; // 初值未定义
int* q{}; // 初值为nullptr

int x1(5.3);
int x2 = 5.3;
int x3{5.0}; // 不允许窄化转换
int x4 = {5.3}; // 不允许窄化转换
char c1{7};
char c2{99999}; // 不允许窄化转换
std::vector<int> v1{1, 2, 3, 4};
std::vector<int> v1{1, 2.3, 3, 4}; // 不允许窄化转换

void print(std::initializer_list<int> vals){
for(auto p = vals.begin(); p!= vals.end(); ++p)
std::cout<<*p<<"\n";
}
print(12,3,5,7,11,13,17);
// 传给initializer_list的必须为initializer_list或者为{...}的形式

class P{
public:
// 版本1
P(int a, int b){
cout<<"P(int, int), a=" << a << ", b=" << b << endl;
}

// 版本2
P(initializer_list<int> initlist){
cout<<"P(initializer_list<int>), values=";
for(auto i : initlist)
cout << i << ' ';
cout << endl;
}
}

P p(77 ,5); // 调用版本1
P q{77 ,5}; // 调用版本2
P r{77 ,5, 42}; // 调用版本2
P s = {77 ,5, 42}; // 调用版本2

// 如果没有版本2只有版本1,不影响p,q和s的调用,当为q,s时会将q的{}拆解为两个参数后调用版本1,r为非法的,因为r有3个参数
// complex<T>没有版本2,故只能一个一个分解使用版本1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// initializer_list的源代码
template<class E>
class initializer_list{
public:
typedef _E value_type;
typedef const _E& reference;
typedef const _E& const_reference;
typedef size_t size_type;
typedef const _E* iterator;
typedef const _E* const_iterator;
private:
iterator _M_array;
size_type _M_len;

// the complier can call a private constructor
constexpr initializer_list(const_iterator __a, size_type __l)
: _M_array(__a),__M_len(__l){}
public:
constexpr initializer_list() noexcept
: _M_array(0), _M_len(0) {}

constexpr size_type
size() const noexcept {return _M_len;}

constexpr const_iterator
begin() const noexcept {return _M_array;}

constexpr const_iterator
end() const noexcept {return begin() - end();}
}
  • array就是C++中数组的另一种形式
    • 可以用到标准库中的begin(),end()等接口
    • array提供迭代器,故可以被其他算法接受
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// array
// TR1版本
template<typename _Tp, std::size_t _Nm>
struct array{
typedef _Tp value_type;
typedef _Tp* pointer;
typedef value_type* iterator;

// support for zerp_sized arrays mandatory
value_type _M_instance[ _Nm ? _Nm : 1];

iterator begin()
{return iterator(&_M_instance[0]);}

iterator end()
{return iterator(&_M_instance[_Nm]);}
...
}

array<int, 10> myArray;
auto ite = myArray.begin();
itr+=3;
cout<<*ite;
  • initializer_list对象只是指向背后的array而不是包含array
    • 详见源代码中的构造函数
  • 拷贝initializer_list时为浅拷贝,是指针拷贝(指针指向的东西相同,而不是值拷贝)
  • 使用initializer_list可以对算法进行扩充,可以传进任意数量的参数

6 explicit参数

  • explicit参数会使函数不自动进行隐式转换,只有明确调用构造函数时进行转换
    • 只有一个实参的非explicit构造函数可以进行隐式转换
    • C++11之后,explicit支持两个及以上实参的构造函数声明为explicit,该构造函数不进行隐式转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// explicit用在一个参数的构造函数上
struct Complex{
int real,imag;

// 该形式为单一实参,第二个参数有默认实参
Complex(int re, int im=0):real(re),imag(im){}

Complex operator+(const Complex& x){
return Complex((real + x.real), (imag + x.image);)
}
}

Complex c1(12,5);
Complex c2 = c1+5; //会将5转换为Complex

struct Complex{
int real,imag;

explicit Complex(int re, int im=0):real(re),imag(im){}

Complex operator+(const Complex& x){
return Complex((real + x.real), (imag + x.image);)
}
}
Complex c1(12,5);
Complex c2 = c1+5; // 由于explicit,不会进行转换

// -----
// 小测试
// -----
class P{
public:
// 版本1
P(int a, int b){
cout<<"P(int, int), a=" << a << ", b=" << b << endl;
}

// 版本2
P(initializer_list<int> initlist){
cout<<"P(initializer_list<int>), values=";
for(auto i : initlist)
cout << i << ' ';
cout << endl;
}

// 版本3
explicit P(int a, int b, int c){
cout<<"P(int, int), a=" << a << ", b=" << b << endl;
}
};
void fp(const P&){};

P p1 (77,5);
P p2 {77,5};
P p3 {77,5,42};
P p4 = {77,5};
// P p5 = {77,5,42};
// 错误原因:版本3声明为explicit,故不能隐式转换后拷贝给p4
P p6 (77,5,42);

fp({42,11});
// fp({42,11,3}); //错误
fp(P{42,11});
fp(P{42,11,3});

7 范围for循环

  • 范围for循环拿出元素时,若需要进行转换,则在其构造函数中不能声明为explicit(不能进行隐式转换)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
for(decl:coll){
statement
}

// 例子
for(int i:{2,3,4,5,6,7,13,17,19})
cout << i << endl;

vector<double> vec;
...
for(auto elem : vec){
cout << elem << endl;
}
for(auto& elem : vec){
elem *= 3;
}

for(decl:coll){
statement
}
// 编译器行为代码化
for(auto _pos = coll.begin(),_end=coll.end();_pos!=_end;++_pos){
decl = *_pos;
statement
}


for(const auto& elem :coll){
cout << elem << endl;
}
// 编译器行为代码化
for(auto _pos = coll.begin(),_end=coll.end();_pos!=_end;++_pos){
const auto& elem = *_pos;
cout << elem << endl;
}

8 =default,=delete

  • 如果自定义了构造函数,则编译器不会合成默认构造函数
    • 强制加上=default,可以重新获取默认构造函数
  • 对于big-five(C++2.0之后)之外的函数使用=default会报错,因为没有默认版本
  • =delete可以使用在任何函数上
    • =0只可以使用在virtual函数上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Zoo{
public:
Zoo(int i1, int i2) : d1(i1),d2(i2){} //构造函数
Zoo(const Zoo&) = delete; //拷贝构造函数copy
Zoo(Zoo&&) = default; //右值引用move
Zoo& operator=(const Zoo&) =default; //拷贝赋值函数copy
Zoo& operator=(const Zoo&&) =delete; //右值赋值函数move
virtual ~Zoo(){}
private:
int d1,d2;
}

class Foo{
public:
Foo(int i) : _i(i) {}
Foo() = default; //可以与构造函数并存

Foo(const Foo& x) : _i(x._i) {}
// !Foo(const Foo& x) =default; // 拷贝构造函数不可以重载
// !Foo(const Foo& x) =delete;

Foo& operator=(const Foo& x) {_i = x._i;return *this;}
// !Foo& operator=(const Foo& x) =default;
// !Foo& operator=(const Foo& x) =delete;

// !void func1() = default; //一般函数没有默认版本

// ! ~Foo() = delete; //会造成无析构函数
~Foo() = default;

private:
int _i;
}
  • 声明一个空类,该类并不是空的,编译器会为其声明一个拷贝构造函数、拷贝赋值函数和析构函数(big-three)。若没有声明任何构造函数,编译器会声明一个默认构造函数
    • 该函数都是pubic和inline的
    • 这些函数只有使用时才会被编译器合成
    • 默认构造函数和析构函数是为了给编译器一个地方存放藏身幕后的代码
      • 子类的构造函数调用父类的构造函数
  • 一个类只要含有指针成员,则该类需要自己定义big-three;若不含有绝大多数不需要自定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// NoCopy and Private-Copy
struct NoCopy{
NoCopy() = default;
NoCopy(const NoCopy&) = delete;
NoCopy& operator=(const NoCopy&) = delete;
~NoCopy() = default;
// other members
}

struct NoDtor{
NoDtor() = default;
~NoDtor() = delete;
}
NoDtor nd; //error
NoDtor *p = new NoDtor();
delete p; //error

class PrivateCopy{
private:
PrivateCopy(const PrivateCopy&);
PrivateCopy& operator=(const PrivateCopy&);
public:
PrivateCopy() = default;
~PrivateCopy();
}
// 这个类不能被一般的代码调用,但可以被友元friend或成员member复制
  • =delete告诉编译器不要定义它,必须出现在声明式
    • 用于析构函数时后果自负
  • 将拷贝构造函数和拷贝赋值函数声明为private时,只能被友元或成员复制
    • 这种类用于被继承,性质为不能被复制
  • boost社群:为C++标准库的先前版本
    • boost::noncopyable实现了上述的PrivateCopy类

9 模板化名(template typedef)

  • 不能使用化名对模板进行特化
    • 特化时还需要使用模板本身
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
template <typename T>
using Vec = std::vector<T,MyAlloc<T>>; //Vec为=后面的别名

// 使用
Vec<int> coll;
// 等效于
std::vector<int,MyAlloc<int>> coll;

// ---------------------------
// 使用#define无法达到相同的效果
#define Vec<T> template<typename T> std::vector<T,MyAlloc<T>>

Vec<int> coll;
// 等效于
template<typename int> std::vector<int,MyAlloc<int>> coll;

// 使用typedef亦 无法达到相同的效果
typedef std::vector<int, MyAlloc<int>> vec; //不是想要的结果

// ---------------------------
// 使用模板可以更好的实现代码复用
void test_moveable(Container cntr, T elem){
Container<T> c;

for(long i = 0; i<SIZE;++i)
c.insert(c.end(),T());

output_static_data(T());
Container<T> c1(c);
Container<T> c2(std::move(c));
c1.swap(c2);
}

test_moveable(list,MyString()); //list写法错误需要尖括号
test_moveable(list,MyStrNoMove()); //list写法错误需要尖括号
// 天方夜谭,调用函数传递的参数为Object,但拿参数的type做文章

// Container is not a template
template<typename Container, typename T>
void test_moveable(Container cntr, T elem){
Container<T> c;

for(long i = 0; i<SIZE;++i)
c.insert(c.end(),T());

output_static_data(T());
Container<T> c1(c);
Container<T> c2(std::move(c));
c1.swap(c2);
}

test_moveable(list(),MyString()); //list()为建立一个临时对象,但写法错误需要尖括号
test_moveable(list(),MyStrNoMove()); //list()为建立一个临时对象,但写法错误需要尖括号
// typename后面加小括号为创建一个临时对象

// 最终解决方案
// 容器都包含iterator迭代器,通过iterator_traits获得value_type,即为每个元素的类型
template<typename Container>
void test_moveable(Container c){
typedef typename iterator_traits<typename Container::iterator>::value_type Valtype;
for(long i = 0; i<SIZE ;++i)
c.insert(c.end(),Valtype());

output_static_data(*(c.begin()));
Container c1(c);
Container c2(std::move(c));
c1.swap(c2);
}

test_moveable(list<MyString>());
test_moveable(list<MyStrString>());

// 另向思考:在没有iterator和traits的情况下
// template语法能够在模板接受一个template参数Container时,当Container本身又是个class template,能去除Container的template参数
// 例如一个vector<string>,能够取出其元素类型string
// 使用模板模板参数template template parameter

10 模板模板参数template template parameter

  • 在推导模板模板参数时,不会通过模板实参推导模板化名
    • 模板模板参数的使用需要配合模板化名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 单独模板编译通过
template<typename T, template<class> class Container>
class XCls{
private:
Container<T> c;
public:
XCls(){
for(long i = 0; i<SIZE;++i)
c.insert(c.end(),T());

output_static_data(T());
Container<T> c1(c);
Container<T> c2(std::move(c));
c1.swap(c);
}
};

// 相当于
template<typename T, template<class T> class Container>

// 添加参数后,编译不通过
XCls<MyString, vector> c1;
// 由于在推导模板模板参数时,不会通过模板实参推导模板化名

// 解决方法,传递模板化名
// 不能再function body之内声明
template<typename T>
using Vec=vector<T,allocator<T>>;

XCls<Mystring, Vec> c1;

11 类型别名、noexcept、override、final

11.1 类型别名

  • 类型别名(Type Alias)类似于typedef,两者之间没有任何差别
    • 标准库中<string>和<string_fwd.h>中都typedef basic_string<char> string;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// -----------
// 类型别名(Type Alias)
// typedef void (*func)(int, int);
using func = void(*)(int, int);

// the name 'func' now denotes a pointer to function
void example(int, int){}
func fn = example;

// type Alias can introuce a member typedef name
template<typename T>
struct Container{
using value_type = T;
}

// Alias template
template<class CharT> using mystring = std::basic_string<CharT,std::char_traits<CharT>>;

mystring<char> str;
  • using的用途
    • using引用命名空间或命名空间中的成员
      • using namespace std
      • using std::count
    • using声明类成员
      • using _Base::_M_allocate;
    • using用来声明类型别名或模板别名

11.2 noexcept

  • noexcept保证函数不抛出异常
    • 可跟上小括号表明条件
    • ==如果异常没有被处理,程序将调用std::terminate(),其中调用std::abort()终止程序
  • 必须通知C++移动构造函数和析构函数不抛出异常,然后当vector成长时移动构造函数将被调用
    • 若移动构造函数不是noexcept的就无法使用它,因为不能保证异常能被正确的处理
    • 函数只要有move function,必须保证函数不会抛出异常且声明noexcept
  • 成长容器(会发生内存重新分配)只有两种:vector和deque
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void foo() noexcept()
// 相当于
void foo() noexcept(true)

void swap(Type& x, Type& y) noexcept(noexcept(x.swap(y))){
x.swap(y);
}

// 通知C++移动构造函数和析构函数不抛出异常
class MyString{
private:
char* _data;
size_t _len;
...
public:
MyString(MyString&& str) noexcept
: _data(str,_data),_len(str,_len){...}

MyString& operator=(MyString&& str) noexcept
{...return *this;}
...
}

11.3 override

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Base{
virtual void vfunc(float){}
};
struct Derived1 : Base{
virtual void vfunc(int){}
// 创建了一个新的函数
}

// 使用override可以使编译器进行侦错
struct Derived2 : Base{
virtual void vfunc(int) override{}
// 错误:并没有override
virtual void vfunc(float) override{}
// 正确
}

11.4 final

  • 被final关键字修饰的类不能继承,为继承体系中的最后一个版本
    • 被final关键字修饰的虚函数不能重写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Base1 final{};

// final类不能被继承
struct Derived1 : Base1{};

struct Base2{
virtual void f() final;
}

// final虚函数不能被重写
struct Derived2{
void f()
// 错误
};

12 decltype

  • 使用decltype可以使编译器找出表达式的类型
    • 然而存在的typeof的实现并不完整,故C++11导入了decltype
    • 在C++11之前,使用typeof可以得到对象的类型,但不能使用其创建对象,只能做打印输出等操作;而decltype完善了该操作
  • decltype的用处
    • 用于定义一个表达式类型的
    • 用于声明返回类型
    • 用于元编程(模板中的各种操作)或传递lambda表达式的类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// decltype使用范例
map<string, float> coll;
decltype(coll) :: value_type elem;

// 用来声明返回类型
template<typename T1, typename T2>
decltype(x+y) add(T1 x, T2 y);

// 在C++11之后,返回类型可以声明在函数参数列表之后
template<typename T1, typename T2>
auto add(T1 x, T2 y) -> decltype(x+y);

// lambda表达式
[...](...)mutable throwSpec -> retType{...}

// ---------
// 第二种用法,用于元编程
template<typename T>
void test18_decltype(T obj){
map<string, float>::value_type elem1;
// 当手上有type,可以取其inner typedef

map<string, float> coll;
decltype(coll)::value_type elem2;
// 面对obj取其class type的inner typedef
// 因为如今可以使用decltype

// 有了decltype可以这样
typedef typename decltype(obj)::iterator iType;
typedef typename T::iterator iType;

decltype(obj) anotherObj(obj);
}

// 传递,编译失败
test18_decltype(complex<int>()); // 编译失败
// complex没有迭代器

// --------------------------
// 第三种用法,用于lambda表达式
auto cmp = [](const Person& p1, const Person& p2){
return p1.lastname()<p2.lastname() ||
(p1.lastname() == p2.lastname() &&
p1.firstname()<p2.firstname());
};
...
std::set<Person, decltype(cmp)> coll(cmp);
// 在lambda表达式中,常常只有object,没有type,可以使用decltype

13 lambda表达式

  • C++11允许使用lambda定义inline函数,可以被用来一个参数或一个本地对象
    • lambda改变了C++标准库的使用方式
    • lambda为一个函数定义,可以被定义在表达式内部作为一个内联函数使用
  • lambda的类型为一个匿名的函数对象,每个Lambda表达式是独一无二的
    • 声明一个这种类型的对象,需要用模板或者auto关键字
    • 如果需要Lambda表达式的类型,使用decltype,比如需要传递Lambda表达式作为散列准则或排序准则,分类准则
  • decltype的第三种用途跳转至decltype关键字
  • Lambda没有默认的构造函数,没有赋值操作
    • 所以需要一个排序准则时,一个定义函数对象的类会更加明显
  • 函数对象是对于自定义STL算法和其函数是一种重要的方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// lambda表达式
[...](...)mutable throwSpec -> retType{...}
// []为导入器,为取用外部的变量,可传值或传引用,加上&为传引用
// [=,&y]表明接受外界所有的变量(不常用,不易被理解)
// ()为参数;* 为可选选项,若下面三个都不写则()可不写
// * mutable表示[]中的变量可被改写
// * throwSpec为抛出的异常
// * retType为返回类型
// {}中可以声明静态变量,可以声明变量,可以指定返回变量

[]{
std::cout << "hello lambda" << std::endl;
}() //加上()表示直接调用

// 或者
auto I = []{
std::cout << "hello lambda" << std::endl;
};
...
I(); //调用lambda表达式

// ----
// 例子
int id = 0;
auto f = [id]() mutable {
std::cout << "id: " << id << std::endl;
++id;
// 不写mutable则id不可以++
}

id = 42;
f(); //0
f(); //1
f(); //2
std::cout << id << std::endl; //42
// 该例子中id传递为id=0时的id,并不是外部id=42

// 版本2
int id = 0;
auto f = [&id](int param){
std::cout << "id: " << id << std::endl;
++id;++param; //OK
// 不写mutable则id不可以++
}
id = 42;
f(7); //id 42;param 7
f(7); //id 43;param 8
f(7); //id 44;param 9
std::cout << id << std::endl; //42

// 版本3
int id = 0;
auto f = [id](){
std::cout << "id: " << id << std::endl;
++id;
// !报错:不写mutable则id不可以++
}

// ----
// 例子
int tobefound = 5;
auto lambda1 = [tobefound](int val) {return val == tobefound;};

// --------------------------
// decltype的第三种用法,用于lambda表达式
auto cmp = [](const Person& p1, const Person& p2){
return p1.lastname()<p2.lastname() ||
(p1.lastname() == p2.lastname() &&
p1.firstname()<p2.firstname());
};
...
std::set<Person, decltype(cmp)> coll(cmp);

// --------------------
// 例子(自定义标准库算法)
vector<int> vi {5,28,50,83,70,590,245,59,24};
int x = 30;
int y = 100;
vi.erase(remove_if(vi.begin(),
vi.end(),
[x,y](int n) {return x<n && n<y;}
),
vi.end()
);
for(auto i:vi)
cout << i<< ' ';
cout << endl;

14 可变模板(Variadic Templates)

  • 谈的是template,包括function template和class template
  • 变化的是模板参数
    • 参数个数:利用参数个数的逐一递减,实现递归函数调用
    • 参数类型:利用参数个数的逐一递减导致参数类型也逐一递减的特性,实现递归继承或递归组合,以class template完成
  • 参数类型相同但个数不限可以使用initializer_list<T>,不必要使用可变模板
  • 递归调用处理的都是参数,使用函数模板
    • 递归继承处理的都是类型,使用类模板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
void func(){/* ... */}
template<typename T, typename... Type>
void func(const T& firstArg, const Type&... args)
{
func(args...);
}
// 注意...的位置

// ---
// 例1
void printX(){
}

// 1
template<typename T, typename... Types>
void printX(const T& firstArg, const Type&...args){
cout << firstArg << endl;
// 使用sizeof...(args)知道args参数有几个
printX(args...);
}

// 3
template<typename... Type>
void printX(const Type&... args){/* ...*/}

print(7.5, "hello",bitset<16>(377),42);
// 1和3可以并存不产生歧义,1更加特化,3更加泛化,3一般永远不会被调用

// ---
// 例2
// 使用variadic templates重写printf()
int* pi = new int;
printf("%d %s %p %f\n",
15,
"this is Ace",
pi,
3.1415926);

template<typename T, typename... Args>
void printf(const char* s, T value, Args... args){
while(*s){
if(*s=='%' && *(++s)!='%'){
std::cout << value;
printf(++s, args...);
return;
}
std::cout << *s++;
}
throw std::logic_error("extra argments provided to print");
}

// 最后一个没有其他参数的版本
void printf(const char* s){
while(*s){
if(*s=='%' && *(++s)!='%')
throw std::runtime_error("invalid format string: missing argment");
std::cout << *s++;
}
}

// ---
// 例3
// 使用initializer_list实现类型相同个数不同的参数传递

// ---
// 例4
// 使用variadic template实现maximum
int maximum(int n){
return n;
}

template<typename... Args>
int maximum(int n, Args... args){
return std::max(n, maximum(args...));
}

// ---
// 例5
// 以易于一般的方式处理first元素和last元素(头尾元素)
cout << make_tuple(7.5,string("hello"),bitset<16>(377),42)
// 想要输出[7.5,hello,000000010111001,42]

// 需要指导处理的元素的index,使用sizeof...()获得元素个数,以get<index>(t)取出元素并将其index++

// 首先操作符<<重载
// output operator for tuple
template<typename... Args>
ostream& operator<<(ostream& os, const tuple<Args...>& t){
os<<"[";
PRINT_TUPLE<0,sizeof...(Args),Args...>::print(os,t);
return os << "]";
}

template<int IDX, int MAX, typename... Args>
struct PRINT_TUPLE{
static void print(ostream& os, const tuple<Args...>& t){
os << get<IDX>(t) << (IDX+1==MAX?"":",");
PRINT_TUPLE<IDX,MAX,Args...>::print(os,t);
}
}
template<int MAX, typename... Args>
struct PRINT_TUPLE<IDX,MAX,Args...>{
static void print (std::ostream& os, const tuple<Args...>& t){ };
}

// ---
// 例6
// 用于递归继承
template<typename... Values> class tuple;
template<> class tuple(){ };

template<typename Head, typename... Tail>
class tuple<Head,Tail...>
: private tuple<Tail...>{
typedef tuple<Tail...> inherited;
public:
tuple(){}
tuple(Head v, Tail... vtail)
: m_head(v), inherited(vtail...) { /*这里是initialization_list*/ }

typename Head::type head() {return m_head;} // 获得Head的类型使用Head::type并加上typename
inherited& tail() {return *this;} // tail()转型为inherited返回
protected:
Head m_head;
}

tuple<int, float, string> t(41, 6.3, "nico");

// 该程序在typename Head::type head() {return m_head;}行报错,原因在于int等类型的::type处报错
// 考虑使用decltype()实现
template<typename... Values> class tuple;
template<> class tuple(){ };

template<typename Head, typename... Tail>
class tuple<Head,Tail...>
: private tuple<Tail...>{
typedef tuple<Tail...> inherited;
protected:
Head m_head; // 若没有移动上去,编译器不认识m_head
public:
tuple(){}
tuple(Head v, Tail... vtail)
: m_head(v), inherited(vtail...) { /*这里是initialization_list*/ }

auto head() -> decltype(m_head) {return m_head;}
inherited& tail() {return *this;} // tail()转型为inherited返回
}

// 该类型返回的类型就是Head
template<typename... Values> class tuple;
template<> class tuple(){ };

template<typename Head, typename... Tail>
class tuple<Head,Tail...>
: private tuple<Tail...>{
typedef tuple<Tail...> inherited;
public:
tuple(){}
tuple(Head v, Tail... vtail)
: m_head(v), inherited(vtail...) { /*这里是initialization_list*/ }

Head head(){return m_head;}
inherited& tail() {return *this;} // tail()转型为inherited返回
protected:
Head m_head;
}

// ---
// 例7
// 递归组合
// 将不同的东西组合成一个type
template<typename... Values> class tup;
template<> class tup<> {};
template<typename Head, typename... Tail>
class tup<Head, Tail...>
{
typedef tup<Tail...> composited;
protected:
composited m_tail;
Head m_head;
public:
tup(){}
tup(Head v, Tail... vtail)
: m_tail(vtail...), m_head(v){}

Head head() {return m_head;}
composited& tail() {return m_tail;}
}

15 标准库源代码分布

  • visual C++中源代码分布在…\include和…\include\cliext

16 右值引用和move语义

  • 右值引用帮助解决没有必要的拷贝操作,并保证高效率
    • 当=右边是一个右值,左边的接收端可以偷(move)右值的资源而不需要重新分配资源
    • 左值:变量,可以出现在operator=左侧者
    • 右值:只能出现在operator=右侧者,不可以出现在=左侧
      • 临时对象为右值,临时对象没有名称故不能进行赋值为右值
      • 函数返回的为右值,不可以对右值取地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 以int试验
int a = 9;
int b = 4;

a = b;
b = a;
a = a+b;

a+b = 42; // a+b为右值,故报错

// 以string试验
string s1("Hello");
string s2("World");
s1+s2 = s2; // 编译通过
string() = "World" // 可以对临时对象赋值

// 以complex试验
complex<int> c1(3,8),c2(1,0);
c1+c2 = complex<int>(4,9); // 编译通过
complex<int>() = complex<int>(4,9); // 可以对临时对象赋值

// 右值只能出现在operator=右侧
int foo(){return 5;}
...
int x = foo(); // ok
int *p = &foo(); // !错误,不可对右值取地址
  • 移动构造函数只需要浅拷贝指针,不需要重新分配内存空间
    • 为了安全需要打断原来的指针,将原来的指针设置为nullptr
    • 被移动之后的原对象不能使用,可以使用临时对象(右值)
      • 若为左值,在确定左值不再使用的前提下,可以将左值转换为右值从而使用移动构造函数(std::move)

17 完美的转交

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 不完美的转交:右值属性被抛弃
void process(int& i){
cout << "provess(int& i):" << i << endl;
}
void process(int&& i){
cout << "provess(int&& i):" << i << endl;
}

int a = 0;
process(a); //provess(int& i):0
provess(1); //provess(int&& i):1
process(move(a)); //provess(int& i):0

void forward(int&& i){
cout << "forward(int&& i):" << i << endl;
process(i);
}

forward(2); //forward(int&& i): 0, provess(int& i):2
// 右值经forward()函数传给另一个函数变成了左值
// 原因是在传递过程中变成了另一个name object

forward(move(a));
// 右值经forward()函数传给另一个函数变成了左值

// ! forward(a); // 左值不能转换为一个右值
  • 完美的转交:完美转交让一个单一的接受n个任意参数的函数模板,并透明地将参数转发给另一个任意函数。参数的性质(可修改的,Const,左值或右值)在这个转发过程中被保留
    • 使用std::forward<>()保留参数的性质
1
2
3
4
5
6
// 完美的转交
template<typename T1, typename T2>
void functionA(T1&& t1, T2&& t2){
functionB(std::forward<T1>(t1),
std::forward<T2>(t2));
}

18 写一个Move-aware类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
class MyString{
public:
static size_t Dctor;
static size_t Ctor;
static size_t CCtor;
static size_t CAsgn;
static size_t MCtor;
static size_t MAsgn;
static size_t Dtor;
private:
char* _data;
size_t _len;
void _init_data(const char *s){
_data = new char[_len+1];
memcpy(_data, s, _len);
_data[_len] = '\0';
}
public:
MyString() : _data(NULL), _len(0) {++Dctor;}

// constructor
MyString(const char* p) : _len(strlen(p)){
++Ctor;
_init_data(p);
}

// copy constructor
MyString(const MyString& str) : _len(str){
++CCtor;
_inti_data(str._data);
}

// move constructor,with noexcept
MyString(MyString&& str) noexcept
: _data(str.data), _len(str._len){
++MCtor;
str._len = 0;
str._data = NULL; //重要
}

// copy assignment
MyString& operator=(const MyString& str){
++CAsgn;
if(this != &str){
if(_data) delete _data;
_len = str._len;
_init_data(str._data);
}else{
}
return *this;
}

// move assignment
MyString& operator=(MyString&& str) noexcept{
++MAsgn;
// 自我赋值检查
if(this != &str){
if(_data) delete _data;
_len = str._len;
_data = str._data; //move
str._len = 0;
str._data = NULL; //重要
}
return *this;
}

// dtor
virtual ~MyString(){
++Dctor;
if(_data){
delete _data;
}
}

bool
operator<(const MyString& rhs) const{ //为了set
return
string(this->data)<string(rhs);
// 借用现成是事实std::string能够比较大小
}

bool
operator==(const MyString& rhs) const{ //为了set
return
string(this->_data)==string(rhs._data);
// string能够判断相等与否
}

char* get() const {return _data;}
};

size_t MyString::Dctor = 0;
size_t MyString::Ctor = 0;
size_t MyString::CCtor = 0;
size_t MyString::CAsgn = 0;
size_t MyString::MCtor = 0;
size_t MyString::MAsgn = 0;
size_t MyString::Dtor = 0;

namespace std{ // 必须放在std内
template<>
struct hash<MyString>{ //为了unordered containers
size_t
operator()(const MyString& str){
return hash<string>() (str)
// 借用现成的hash<string>
}
}
}

19 对容器的效能测试

  • 在vector中,为什么实际构造的次数大于添加元素的个数
    • 因为vector会成长,当vector不够时会成长导致搬移次数或拷贝次数增加
  • 在list、deque、multiset和unordered_multiset中,构造函数调用的次数和添加元素的个数相同
    • 且在deque、multiset和unordered_multiset中性能差别不大
  • 是否具有move的函数对于vector影响较大
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include<typeinfo>
template<typename T>
void output_static_data(const T& myStr){
cout << "CCtor=" << T::CCtor
<< "MCtor=" << T::MCtor
<< "CAsgn=" << T::CAsgn
<< "MAsgn=" << T::MAsgn
<< "Dtor=" << T::Dtor
<< "Ctor=" << T::Ctor
<< "DCtor=" << T::DCtor
<< endl;
}

template<typename M, typename NM>
void test_moveable(M c1, NM c2, long& value){
char buf[10];

// 测试moveable
typedef typename iterator_traits<typename M::iterator>::value_type V1type;
clock_t timeStart = clock();
for(long i = 0; i< value; ++i){
snprintf(buf, 10, "%d", rand());
auto ite = c1.end();
c1.insert(ite, V1type(buf));
}
cout << "construction, milli-seconds:" << (clock()-timeStart);
cout << "size()=" << c1.size() << endl;
output_static_data(*(c1.begin()));

M cl1(c1);
M Cl2(std::move(c1));
cl1.swap(cl2);

// 测试none-test_moveable
...
}

20 容器-结构与分类

  • Sequence Container
    • Array
    • Vector
    • Deque
    • List
  • Forward-List
  • Associative Container
    • Set/Multiset
    • Map/Multimap
  • Unordered Container
    • Unordered Set/Multiset
    • Unordered Map/Multimap

21 容器hashtable

  • 在hashtable中,若元素的个数大于篮子的个数,则打断重新建立篮子(rehashing),篮子的个数为对应的最大素数
    • 元素落在哪个篮子的计算方法(取余数):MOD(元素/篮子个数)
    • 元素为对象,为hash function计算出的hash code

22 hash function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 使用内置的hash function
void* pi = (void*)(new int(100));
cout << hash<int>()(123) << endl;
cout << hash<long>()(123L) << endl;
cout << hash<string>()(string("Ace")) << endl;
cout << hash<const char*>()("Ace") << endl;
cout << hash<char>()('A') << endl;
cout << hash<float>()(3.1415926) << endl;
cout << hash<double>()(3.1415926) << endl;
cout << hash<void*>()(pi) << endl;

// G2.9
// 泛化
template<class Key> struct hash() {};
// 特化
// __STL_TEMPLATE_NULL等效于template<>
__STL_TEMPLATE_NULL struct hash<char>{
size_t operator() (char x) const {return x;}
};
__STL_TEMPLATE_NULL struct hash<short>{
size_t operator() (short x) const {return x;}
};
__STL_TEMPLATE_NULL struct hash<unsigned short>{
size_t operator() (unsigned short x) const {return x;}
};
__STL_TEMPLATE_NULL struct hash<int>{
size_t operator() (int x) const {return x;}
};
__STL_TEMPLATE_NULL struct hash<unsigned int>{
size_t operator() (unsigned int x) const {return x;}
};
__STL_TEMPLATE_NULL struct hash<long>{
size_t operator() (long x) const {return x;}
};
__STL_TEMPLATE_NULL struct hash<unsigned long>{
size_t operator() (unsigned long x) const {return x;}
};
// G4.9中提供string
inline size_t __stl_hash_string(const char* s){
unsigned long h = 0;
for(; *s; ++s)
h = 5*h + *s;
return size_t(h);
}
__STL_TEMPLATE_NULL struct hash<char*>{
size_t operator() (char* s) const {return __stl_hash_string(s);}
};
__STL_TEMPLATE_NULL struct hash<const char*>{
size_t operator() (const char* s) const {return __stl_hash_string(s);}
};

// G4.9
// function_hash.h中实现hash_function

// 字符串string的hash_function在basic_string.h文件中实现

23 tuple

  • tuple的大小为其中空间最大的元素的空间与元素个数的乘积(每个元素的空间为元素中占用空间最大的元素的占用空间)
  • meta programing(元编程):对类型进行编程
1
2
3
4
5
6
// 得到tuple的大小
typedef tuple<int, float, string> TupleType;
cout << tuple_size<TupleType>::value << endl; //3

// 使用tuple_element
tuple_element<1,TupleType>::type f1 = 1.0;

评论