wink

有的鸟是不会被关住的, 因为它们的羽毛太耀眼了.

  • main

wink

有的鸟是不会被关住的, 因为它们的羽毛太耀眼了.

  • main

c++11 笔记

2016-04-06

C++ 11 笔记

constexpr 表达式

常量表达式是指值不会改变并且在编译过程就能得到计算结果的表达式.

1
2
3
constexpr int mf = 20;
constexpr int limit = mf + 1;
constexpr int sz = size(); // 只有当size是一个constexpr函数时, 才是一条正确的声明语句

using 别名声明

C++ 11规定了一种新的方法, 使用别名声明来定义类型的别名:

1
2
using SI = Sales_item;
// 等价于: typedef Sales_item SI;

auto 类型说明符

auto能让编译器去推断表达式所属的类型, 使用auto能在一条语句中声明多个变量, 但是该语句中所有变量的初十基本数据类型都必须一样:

1
2
auto i = 0, *p = &i; // 正确: i是整数, p是整型指针
auto sz = 0, pi = 3.14; // 错误: sz和pi的类型不一致

decltype 类型指示符

decltype, 它的作用是选择并返回操作数的数据类型. 在此过程中, 编译器分析表达式并得到它的类型, 却不实际计算表达式的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
decltype(f()) sum = x; // sum 的类型就是函数f的返回类型
const int ci = 0, &cj = ci;
decltype(ci) x = 0; // x的类型是const int
decltype(cj) y = x; // y的类型是const int&, y绑定到变量x
decltype(cj) z; // 错误: z是一个引用, 必须初始化
int i = 42, *p = &i, &r = i;
decltype(r + 0) b; // 正确, 表达式的结果是一个具体值而非引用
decltype(*p) c; // 错误, 解引用将得到引用类型, 必须初始化
// decltype的表达式如果是加上了括号的变量, 结果将是引用
decltype((i)) d; // 错误, d是int&, 必须初始化
decltype(i) e; // 正确, e是一个int

注意: decltype(*p) 解引用操作符会返回一个引用类型.

for range

1
2
3
std::string str("some string");
for (auto c : str)
std::cout << c << std::endl;

如果要改变循环的值:

1
2
3
std::string s("Hello World!!!");
for (auto &c : s)
c = toupper(c);

赋值运算符

C++ 11 允许使用花括号括起来的初始值列表, 作为赋值语句的右侧运算对象:

1
2
vector<int> vi;
vi = {0, 1, 2, 3, 4};

initializer_list 形参

如果函数的实参数量未知, 但是全部实参的类型都相同, 可以使用initializer_list类型的形参.

1
2
3
4
5
6
7
8
9
10
11
void error_msg(initializer_list<string> il) {
for (const auto &e : il) {
cout << e << " ";
}
cout << endl;
}
if (expected != actual)
error_msg({"functionX", expected, actual});
else
error_msg({"functionX", "okay"});

列表初始化返回值

C++ 11 规定, 函数可以返回花括号包围的值的列表.

1
2
3
4
5
6
7
8
9
vector<string> process() {
...
if (expected.empty())
return {};
else if (expected == actual)
return {"functionX", "okay"};
else
return {"functionX", expected, actual};
}

= default

c++ 11中, 如果我们需要默认的构造函数行为, 那么可以通过在参数列表后面写上= default 来要求编译器生成构造函数.其中= default既可以和声明一起出现在类的内部, 也可以作为定义出现在类的外部. 如果出现= default在内部, 那么默认构造函数是内联的, 如果在类外部, 那么该成员默认情况下不内联.

1
2
3
4
5
6
7
8
class SalesData{
public:
SalesData() = default;
SalesData(const string &s): bookNo_(s) {}
...
private:
string bookNo_;
}

委托构造函数

委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程, 或者说它把它自己的一些(或者全部)职责委托给了其他构造函数.

1
2
3
4
5
6
7
class SalesData {
public:
SalesData(string s, unsigned cnt, double price): bookNo(s), units_sold(cnt), revenue(cnt*price) {}
// 其余构造函数全都委托给第一个构造函数
SalesData(): SalesData("", 0, 0) {}
SalesData(string s): SalesData(s, 0, 0) {}
};

容器emplace操作

新标准引入了三个心成员— emplace_front, emplace和emplace_back, 这些操作构造而不是拷贝元素, 这些操作分别对应push_front, insert和push_back, 允许我们将元素放置在容器头部, 一个指定位置之前或容器尾部.(省去了一次拷贝)

1
2
3
c.emplace_back("978-0590353203", 25, 15.99); // 正确
c.push_back("978-0590353203", 25, 15.99); // 错误
c.push_back(SalesData("978-0590353203", 25, 15.99)); // 正确, 等价于emplace_back

string 和 数值之间的转换

1
2
3
4
5
6
7
8
9
10
11
to_string(val) 将数值val转换成string, val可以是任何算术类型(包括浮点数)
stoi(s, p, b) 返回s的起始子串(表示正数内容)的数值
stol(s, p, b)
stoul(s, p, b)
stoll(s, p, b)
stoull(s, p, b)
stof(s, p) 返回s的起始子串(表示呢浮点数内容)的数值
stod(s, p) // double
stold(s, p) // long double

lambda (详见 p346页)

一个lambda表达式表示一个可调用的代码单元, 我们可以将其理解为一个未命名的内联函数.与任何函数类似, 一个lambda具有一个返回类型, 一个参数列表和一个函数体. 但与函数体不同, lambda可能定义在函数内部, 一个lambda表达式具有如下形式:

1
[capture list](parameter list) -> return type { function body }

其中capture list 是一个lambda所在函数中定义的局部变量列表, 通常为空, return type, parameter list 和 function body与任何普通函数一样, 分表表示返回类型, 参数列表和函数体. 但是与普通函数不同, lambda必须使用尾置返回来指定返回类型

1
2
3
auto f = [] { return 42; }
cout << f() << endl;

如果忽略返回类型, lambda根据函数体中的代码推断出返回类型.

bind 函数

可以将bind函数看做一个通用的函数适配器, 它接受一个可调用的对象, 生成一个新的可调用对象来适应原对象的参数列表.调用bind的一般形式为:

1
auto newCallable = bind(callable, arg_list)

其中, newCallable本身是一个可调用对象, arg_list是一个逗号分隔的参数列表, 对应给定的callable的参数. 即, 当我们调用newCallable时, newCallable会调用callable, 并传递给它arg_list中的参数.
arg_list中的参数可能包含形如_n的名字, 其中n是一个正数, 这些参数是占位符, 表示newCallable的参数, 它们占据了传递给newCallable的参数的”位置”. 数值n表示生成的可调用对象中参数的位置: _1为newCallable的第一个参数, _2为第二个参数, 依此类推(详见P354)

std::move

详见P469

右值引用

为了支持移动操作, 新标准引入了一种新的引用类型—右值引用. 所谓右值引用就是必须绑定到右值的引用. 我们通过&&而不是&来获得右值引用. 右值引用有一个重要的性质—只能绑定到一个将要销毁的对象.因此, 我们可以自由的将一个右值引用的资源”移动” 到另一个对象中.

1
2
3
4
5
6
int i = 42;
int &r = i; // 正确: r引用i
int &&rr = i; // 错误: 不能把一个右值引用绑定到一个左值上
int &r2 = i * 42; // 错误: i * 42是一个右值
const int &r3 = i * 42; // 正确: 我们可以将一个const引用绑定到一个右值上
int &&rr2 = i * 42; // 正确: 将rr2绑定到乘法结果上

详见P471

移动构造函数和移动赋值运算符

类似拷贝构造函数, 移动构造函数的第一个参数是该类类型的一个引用. 不同于拷贝构造函数的是, 这个引用参数在移动构造函数中是一个右值引用. 与拷贝构造函数一样, 任何额外的参数都必须有默认实参.

1
2
3
4
5
6
StrVec::StrVec(StrVec &&s) noexcept // 移动操作不应该抛出任何异常
// 成员初始化器接管s中的资源
:elements(s.elements), first_free(s.first_free), cap(s.cap) {
// 清理s的状态, 令其安全析构
s.elements = s.first_free = s.cap = nullptr;
}

编译器会合成移动构造函数和移动赋值运算符, 但是如果一个类定义了自己的拷贝构造函数, 拷贝赋值运算符或者析构函数, 编译器就不会为它生成移动构造函数和移动赋值运算符了.(在需要移动构造函数的时候, 会调用拷贝构造函数来代替)

引用限定符

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
#include<iostream>
#include<string>
using namespace std;
class Animal{
public:
Animal& operator=(const Animal& animal) &;//&引用限定符指定该赋值运算符只能用于左值 ;&&限定符指定赋值运算符只能用于右值
private:
string name;
int weight;
};
Animal& Animal::operator=(const Animal& animal) &{
if(this!=&animal){
name=animal.name;
weight=animal.weight;
}
cout<<"Animal::operator=(const Animal& animal)&"<<endl;
return *this;
}
int main(){
Animal bird1,bird2,bird3;
move(bird1)=bird3;//error
bird2=bird3;//ok
}

防止继承的发生

C++11定义了一种防止继承发生的方法, 即在类名后跟一个关键字final:

1
2
3
class Noderived final{}; // Noderived不能作为基类
class Base {};
class Last final : Base{}; // Last不能作为基类

override 说明符

新标准中, 可以使用override关键字来说明派生类中的虚函数. 如果我们使用override标记了某函数, 但该函数并没有覆盖已存在的虚函数, 此时编译器会报错:

1
2
3
4
5
6
7
8
9
10
11
12
struct B {
virtual void f1(int) const;
virtual void f2();
void f3();
};
struct D1 : B {
void f1(int) const override; // 正确
void f2(int) override; // 错误, B中没有形如f2(int)的函数
void f3() override; //错误, f3不是虚函数
void f4() override; // 错误: B没有名为f4的函数
};

赏

thanks

  • c++11
  • tech

扫一扫,分享到微信

微信分享二维码
tidb笔记1
© 2019 wink