第2章 保证稳定性和兼容性
2.1 保持与C99兼容
1、__func__:返回所在函数的名字
2、_Pragma操作符:
#pragma是一条预处理指令。C++11定义了与预处理指令#pragma功能相同的操作符_Pragma。格式如下:_Pragma(字符串字面量)。
#pragma不能在宏中展开,而_Pragma具有更大的灵活性。
3、变长参数的宏定义以及__VA_ARGS__:
#define PR(...) printf(__VA_ARGS__)
4、在C++11标准中,窄字符串可以转换成宽字符串。但是之前标准中,将窄字符串转换成宽字符串是未定义的行为。
2.2 long long整型
1、使用LL后缀表示一个long long类型的字面量,ULL表示一个unsigned long long类型的字面量。
2.3 扩展的整型
标准的有符号整型:signed char、short int、int、long int、long long int
2.4 宏__cplusplus:C与C++混合编写
在C++03标准中,__cplusplus的值为199711L,在C++11标准中,__cplusplus为201103L。
#error:
2.5 静态断言
assert:只在程序运行时才能起作用。
static_assert:断言表达式的结果必须是在编译时期可以计算的表达式,即必须是常量表达式。
2.6 noexcept修饰符与noexcept操作符
noexcept:表示其修饰的函数不会抛出异常。如果noexcept修饰的函数抛出了异常,编译器直接调用std::terminate()函数来终止程序。比throw()在效率上高。
void func() noexcept;
void func() noexcept(常量表达式); //常量表达式的结果会被转换成一个bool类型的值。该值为true,表示函数不会抛出异常,反之,则有可能抛出异常。
2.7 快速初始化成员变量
2.8 非静态成员的sizeof
在C++08标准中,对非静态成员变量使用sizeof是不能通过编译的,但是C++11可以。
2.9 扩展的friend语法
1、在C++11中,声明一个类为另外一个类的友元时,不再需要使用class关键字。
2.10 final/override控制
1、final关键字阻止函数继续重写。
2、如果派生类在虚函数声明时使用了override描述符,那么该函数必须重载其基类中的同名函数,否则代码将无法通过编译。
2.11 模板函数的默认模块参数
2.12 外部模板
template void fun
extern template void fun
2.13 局部和匿名类型作模板实例
1、局部的类型和匿名的类型在C++98中不能做模板类的实参。
2、不能将匿名的结构体直接声明在模板实参的位置。
2.14 本章小结
第3章 通用为本,专用为末
3.1 继承构造函数
struct B : A {
using A::A; //继承构造函数
}
3.2 委派构造函数
struct A {
A(){}
A(int i):A(){}
}
3.3 右值引用:移动语义和完美转发
1、指针成员与拷贝构造
2、移动语义:TODO:p87
3、
左值:可以取地址的、有名字的
右值:不能取地址的、没有名字的
4、std::move:强制转化为右值
#include
5、尽量编写不抛出异常的移动构造函数,可以使用noexcept关键字。可以用std::move_if_noexcept代替move函数。
RVO/NRVO优化(返回值优化):
6、完美转发:在函数模板中,完全按照模板的参数类型,将参数传递给函数模板中调用的另外一个函数。
template
void PerfectForwart(T && t) { RunCode(forward
3.4 显示转换操作符
1、explicit:声明为explicit的构造函数不能在隐式转换中使用。
3.5 列表初始化
1、初始化列表:{}
2、在C++11中,列表初始化是唯一一种可以防止类型收窄的初始化方式。
3.6 POD类型
1、好处:
字节赋值,可以安全使用memset和memcpy等操作;
提供对C内存布局兼容;
保证静态初始化的安全有效
2、什么样的是POD类型:(可以通过 std::is_pod 判断)
平凡:(可以通过 std::is_trivial 判断)
拥有平凡的默认构造函数和析构函数;(不包含参数,函数体没有任何代码)
拥有平凡的拷贝构造函数;
拥有平凡的拷贝赋值运算符和移动赋值运算符;
不能包含虚函数以及虚基类;
标准布局:(可以通过 std::is_standard_layout 判断)
所有非静态成员有相同的访问权限;
在类或结构体继承时,派生类中有非静态成员且只有一个仅包含静态成员的基类 或 基类有非静态成员而派生类没有非静态成员;
类中的第一个非静态成员的类型与其基类不同;
没有虚函数和虚基类;
所有非静态数据成员均符合标准布局类型,其基类也符合标准布局
3.7 非受限联合体
在C++11中取消了联合体对于数据成员类型的限制,任何非引用类型都可以成为联合体的数据成员。
3.8 用户自定义字面量
规则:
如果字面量为整型数,那么字面量操作符函数只能接受unsigned long long 或者 const char* 为其参数
如果字面量为浮点型数,则字面量操作符函数只能接受long double 或者 const char* 为参数
如果字面量为字符串,则字面量操作符函数只能接受const char*,size_t为参数
如果字面量为字符,则字面量操作符函数只能接受char为参数
注意:
在字面量操作符函数的生命中,operator""与用户自定义后缀之间必须有空格
后缀建议以下划线开始
示例:
struct Watt{ unsigned int v; };
Watt operator "" _w(unsigned long long v) { return {(unsigned int)v}; }
int main() { Watt capacity = 1024_w; }
3.9 内联名字空间
C++98不允许在不同的名字空间中对模板进行特化。
C++11引入内敛命名空间,可以解决该问题。(inline namespace)
3.10 模板的别名(using)
可以使用 std::is_same 判断两个类型是否一致。
3.11 一般化的SFINEA规则(匹配失败不是错误:Substitution failure is not an error)
3.12 本章小结
第4章 新手易用,老兵易用
4.1 右尖括号>的改进
C++98会将>>优先解析为右移。在C++11中要求编译器智能地判断。
4.2 auto类型推导
1、优势:
简化代码(在拥有初始化表达式的复杂类型变量声明时)
避免类型声明时的错误
在一定程度上支持泛型编程
2、auto可以与cv限制符一起用,不过声明为auto的变量不能从其初始化表达式中“带走”cv限制符。cv限制符:volatile(易失的)和const(常量)
3、以下情况不能使用auto:
不能做形参类型
对于结构体来说,非静态成员变量的类型不能为auto
不能声明auto数组
不能在实例化模板的时候使用auto昨模板参数,如vector
4.3 decltype
1、C完全不支持动态类型,C++98对动态类型支持即C++中的运行时类型识别(RTTI)
2、typeid:返回类型为type_info,包含nameHe hash_code。除typeid外,RTTI还包括C++中的dynamic_cast等特性,由于RTTI会带来一些运行时的开销,所以建议关闭该特性。
3、decltype(e)推导规则:
如果 e 是一个没有带括号的标记符表达式或者类成员访问表达式,那么decltype(e)就是e所命名的实体的类型。如果e是一个被重载的函数,则会导致编译时错误。
否则,假设e的类型是T,如果e是一个将亡值(xvalue),那么decltype(e)为T&&。
否则,假设e的类型是T,如果e是一个左值,则decltype(e)为T&。
否则,假设e的类型是T,则decltype(e)为T。
标记符表达式:所有除去关键字、字面量等编译器需要使用的标记之外的程序员自定义的标记。
4、decltype与auto不同,能“带走”表达式的cv限制符。不过,如果对象的定义中有const或volatile限制符,使用decltype进行推导时,其成员不会继承const或volatile限制符。
4.4 追踪返回类型
示例:
template
auto Sum(T1& t1, T2& t2) -> decltype(t1 + t2) { return t1 + t2; }
4.5 基于范围的for循环
4.6 本章小结
第5章 提高类型安全
5.1 强类型枚举
1、匿名枚举:enum { Male, Female };
2、枚举类缺点:非强类型作用域,允许隐式转换为整型,占用存储空间及符号性不确定
3、枚举类(强类型枚举):enum class Type {};
优势:
强作用域,强类型枚举成员的名称不会被输出到其父作用域空间
转换限制,强类型枚举成员的值不可以与整型隐式地互相转换
可以指定底层类型。enum class Type : char {};
5.2 堆内存管理:智能指针与垃圾回收
1、显示堆内存管理问题: 野指针、重复释放、内存泄漏。
2、
unique_ptr:与所指对象的内存绑定紧密,不能与其他unique_ptr类型的指针对象共享所指对象的内存。
shared_ptr:允许多个智能指针共享地“拥有”同一堆分配对象的内存。
weak_ptr:可以指向shared_ptr指针指向的对象内存,却并不拥有该内存。而使用wak_ptr成员lock,则可返回其指向内存的一个shared_ptr对象,且在所指对象内存已经无效时,返回指针空值。
3、垃圾回收
基于引用计数的垃圾回收器
基于跟踪处理的垃圾回收器
4、安全派生指针的操作:
在解引用基础上的引用,如:&*p
定义明确的指针操作,如:p+1
定义明确的指针转换,如:static_cast
指针和整型之间的转换,如reinterpret_cast
5.3 本章小结
第6章 提高性能及操作硬件的能力
6.1 常量表达式
1、
const:运行时常量
constexpr:编译时常量。可以在函数返回类型前加入constexpr使其成为常量表达式函数
常量表达式函数要求:
函数体只有单一的return返回语句
函数必须返回值
在使用前必须已有定义
return返回语句表达式中不能使用非常量表达式的函数、全局数据,且必须是一个常量表达式
2、常量表达式值
const int i = 1; // 如果i在全局名字空间中,编译器一定会为i产生数据
constexpr int j = 1; // 如果不是有代码显示地使用了j的地址,编译器可以不选择为它生成数据,而仅将其当做编译时期的值
3、constexpr不能修饰自定义类型,但是通过定义自定义常量构造函数可以。
实例:
struct MyType { constexpr MyType(int x) : i(x){} int i;};
constexpr MyType mt = {0};
4、常量表达式可以用于模板函数。由于模板中类型的不确定性,所以模板函数是否会被实例化为一个能够满足编译时常量性的版本通常是未知的。C++11标准规定,当声明为常量表达式的模板函数后,而某个该模板函数的实例化结果不满足常量表达式的需求的话,constexpr会被自动忽略。
6.2 变长模板
template
6.3 原子类型与原子操作
内存模型:
memory_order_relaxed 不对执行顺序做任何保证
memory_order_acquire 本线程中,所有后续的读操作必须在本条原子操作完成后执行
memory_order_release 本线程中,所有之前的写操作完成后才能执行本条原子操作
memory_order_acq_rel 同时包含memory_order_acquire好memory_order_release标记
memory_order_consume 本线程中,所有后续的有关本原子类型的操作,必须在本条原子操作完成之后执行
memory_order_seq_cst 全部存取都按顺序执行
原子存储操作(store):可以使用memory_order_relaxed、memory_order_release、memory_order_seq_cst
原子读取操作(load):可以使用memory_order_relaxed、memory_order_consume、memory_order_acquire、memory_order_seq_cst
6.4 线程局部存储
线程局部存储变量:拥有线程生命期及线程可见性的变量。
int thread_local errcode; // 一旦声明一个变量为thread_local,其值将在线程开始时被初始化,而在线程结束时,该值也将不再有效。
6.5 快速退出:quick_exit与at_quick_exit
terminate:终止程序。在默认情况下会调用abort函数。可以通过set_terminate函数来改变默认行为。
abort:不会调用任何的析构函数,默认情况下会抛出一个信号:SIGABRT。
exit:正常退出。会正常调用自动变量的析构函数,还会调用atexit注册的函数。
quick_exit:不执行析构函数。
at_quick_exit:注册函数。
6.6 本章小结
第7章 为改变思考方式而改变
7.1 指针空值-nullptr
nullptr是有类型的(nullptr_t),且仅可以被隐式转化为指针类型。
nullptr是编译时期的关键字。
7.2 默认函数的控制
1、默认函数:构造、拷贝构造、拷贝赋值、移动构造、移动拷贝、析构
2、=default 修饰的函数为显示缺省函数,=delete 修饰的函数为删除函数。
7.3 lambda函数
1、[捕捉列表](参数列表) 修饰符 ->返回类型{ 函数体 }
参数列表和返还类型是可选部分,捕捉列表和函数体可能为空。最简略声明为:[]{}
2、捕捉列表
[var]:值传递方式捕捉变量var
[=]:值传递方式捕捉所有父作用域的变量
[&var]:引用传递捕捉变量var
[&]:引用传递捕捉所有父作用域的变量
[this]:值传递方式捕捉当前的this指针
[=, a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量
[&, a, this]:以值传递的方式捕捉变量a和this,引用传递方式捕捉其他所有变量
但是:捕捉列表不允许变量重复传递。
7.4 本章小结
第8章 融入实际应用
8.1 对齐支持
1、offset:查看类成员的偏移。
alignof:操作符,查看数据的对齐方式
alignas:描述符,设定数据的对齐方式。可以接受常量表达式,也可以接受类型表达式做参数
std::align:库函数
aligned_storage:模板
aligned_union:模板
2、保证数据对齐是保证正确有效读写数据的一个基本条件。对齐的数据在读写上会有性能上的优势。
8.2 通用属性
1、属性是通过GNU的关键字__attribute__来声明的。如:__attribute__((attribute-list))。
windows评估,使用__declspec来扩展属性。
2、C++11通用属性:[[attribute-list]]。可以作用于类型、变量、名称、代码块等。既可以写在声明的起始处,也可以写在声明的标识符之后。
3、C++11预定义的通用属性包括[[noreturn]]和[[carries_dependency]]两种。
[[noreturn]]:用于标识不会返回的函数。没有返回值的void函数在调用完成后,调用者会接着执行函数后的代码;而不会返回的函数在被调用完成后,后续代码不会被执行。
[[carries_dependency]]:为了解决弱内存模型平台上使用memory_order_consume内存顺序枚举问题。
8.3 Unicode支持
1、C++98为了支持Unicode,定义了wchar_t,但是不同操作系统定义不一样,不可移植。
C++11定义了:char16_t(存储UTF-16编码的Unicode数据)和char32_t(存储UTF-32编码的Unicode数据)
在声明常量字符串时,定义了3种前缀:
u8:UTF-8
u:UTF-16
U:UTF-32
2、C11编码转换:mbrtoc16、c16rtomb、mbrtoc32、c32rtomb
C++11编码转换:codecvt
8.4 原生字符串字面量
在字符串前加入前缀(R),并在引号中使用括号左右标识即可。如 R"(hello,
world)"
8.5 本章小结
附录A C++11对其他标准的不兼容项目
1、在C++11中R、u8、u8R、u、uR、U、UR和LR是新的字符串修饰符,当用它们来修饰字符串时,即使是宏名,也将作为修饰符来解释。
2、C++11要求数组初始化时,不能将数据的类型收窄。如下面代码非法:int arr[]={1.0}
附录B 弃用的特性
附录C 编译器支持
附录D 相关资源