文中标明【11】的为C++11新增标准。
第零部分
第一章 开始
- C++是静态(编译时检查变量类型)、弱类型(会自动做隐式类型转换);
cin >>
和cout <<
运算顺序均为从左至右,运算结果为一个istream/ostream对象;
while (cin >> a)
在读到EOF
时跳出循环;
- 由于
/* */
注释的判定为遇到第一个*/
结束,因此该注释不能嵌套。一般只用它来写注释,需要注释掉代码时使用多行//
;
cerr
不可重定向,不通过缓冲区;endl
会刷新缓冲区;
- Windows下文件结束符为
Ctrl+Z
后Enter
,Linux下为Ctrl+D
;
- 用户自定义的标识符不能连续出现两个下划线,也不能以下划线紧连大写字母开头,定义在函数体外的标识符不能以下划线开头;
第一部分 C++基础
第二章 变量和基本类型
- 不同编译器字长和实现方式均不同,不要混用有符号和无符号变量;
- 赋给带符号类型一个超出其表示范围的值是ub(未定义行为);
- 字面值常量:编译期可以直接得到结果的常量,如整数
1
、字符’a’
、字符串”a”
、布尔true
、指针nullptr
等;
- 【11】初始化和赋值不一样;C++11支持列表初始化,可以初始化类或数组等,在可能造成数据丢失时会警告;
- 全局变量会被默认初始化,而函数体内置类型变量不会;
- 作为静态类型语言,且为了支持分离式编译,C++区分了声明和定义;允许多次声明(当然声明必须一样),但定义只能有一次;
- 复合类型(引用、指针)由基本数据类型(如int)和声明符(*和&)列表组成;
int *a,b
此时b为整型,因此声明时建议将声明符列表紧挨变量名;
- 【11】C++11用
nullptr
(字面值)带出原先的预处理变量NULL
,以防止函数重载后无法分清参数是0还是NULL;
void *
指针没有对象类型信息,不能解引用;
- 常量引用
const &
必须在声明时初始化;
- 顶层const表示指针本身是常量(const
pointer):
int * const
;底层const表示指针所指对象为常量(pointer
to
const):const int *
;此处国内叫法(常量指针)似乎不一致,建议直接使用英文;
- 读声明时可以从右向左读,左边是右边的定语;
- 【11】
constexpr
表示编译时常量,即可以直接由字面值简单运算得到;相应地有运行时常量,如用一个变量初始化常量;
- 【11】C++11规定了新方法:别名声明
using new_type_name = old_type_name;
- 不应将
typedef
后的类型简单带入到新声明中,如typedef char * pstring; const pstring cstr;
中cstr
的基本数据类型为指针,此为const
pointer,而简单带入成const char * cstr
后基本数据类型变为const char
,*
成为声明符的一部分,变为pointer
to const;
- 【11】定义
auto
类型时必须初始化,否则编译期无法推导类型;auto
一般会忽略顶层const,若需要应写明const auto
;
- 【11】
decltype(f()) sum = x;
用f()
的返回值类型作为sum
的推导类型,但不实际计算f()
,sum
由x
初始化;
- 设
int i = 42, *p = &i, &r = i;
则decltype(*p)
的结果是int&
而非int
,decltype(r)
结果为int&
,decltype(r + 0)
的结果为int
;decltype
的表达式若是加上括号的变量,结果将是引用,如decltype((i))
会得到引用;其余情况均会将引用理解为变量别名,除了decltype
的时候;
- 头文件保护符:在代码首尾分别加上
#ifndef A_H #define A_H
和#endif
;
typedef int i1, *i2;
定义了一个int
类型和一个int*
类型;
第三章 字符串、向量和数组
- 头文件不应包含using声明;
- 若用=号初始化则为拷贝初始化,否则为直接初始化;
getline(cin, s)
会读到一个'\n'
为止(包括此'\n'
但s
中不存);
- 【11】范围(range)for语句:
for (declaration: expression)
如for (auto &c: str)
;
- 有些老式编译器要求
vector<vector<int> >
此处必须有空格;
const_iterator
是pointer to
const;auto it = v.cbegin();
会得到一个const_iterator
;
- 但凡使用了迭代器的循环体都不要向所属容器添加元素;
- 迭代器可以和整数相加减;两个同容器的迭代器也可以相减,得到二者的距离,结果为有符号整型
difference_type
;两个指向同一数组中元素的指针相减的结果为有符号整型ptrdiff_t
;
- 引用不是对象,故不存在引用的数组;
- 读数组的声明:由内而外,优先右结合,其次左结合;
- 数组下标为无符号整型
size_t
;数组定义的维度必须是编译期常量;
- 当使用数组名时往往会被编译器转换为数组首地址(
decltype
时不会),如string *p = arr;
等价于string *p = &arr[0];
- 多维数组使用范围for语句时需要所有外层都使用引用
for (auto &i: arr)
,否则会被编译器理解为数组首地址;
- 指针也是迭代器;尾后迭代器
end()
没有实际含义,不能被递增或解引用;
- 【11】C++11支持数组的
begin(arr)
和end(arr)
函数;
- 指向数组元素的指针也可以当数组用:
int *p = &arr[2]; p[-1];
strlen(str)
会一直找到空字符为止,所以可能产生缓冲区错误;
string::c_str()
返回一个const char *
,不保证一直有效;
- 用数组初始化vector:
vector<int> ivec(begin(arr), end(arr));
- 优先使用string和vector而减少使用C风格的字符串和数组;
第四章 表达式
- 【11】C++11允许直接使用初始化列表赋值、传参、作函数返回值;
- 新版C++正负均向零舍入;
- 赋值运算符具有右结合律;非必要不使用后置递增递减算符,因为会对迭代器产生较大不必要运算;
,
运算符从左至右运算,并只返回最后一项;
- 无符号类型与带符号类型的运算会进行依赖于编译器的算数转换,故不要使用;
static_cast
为不报警告的强制类型转换,如void* p = &d; double *dp = static_cast<double*>(p);
const_cast
改变运算对象的底层const,转换本身是常量的对象是ub;示例用法:const char *pc; char *p = const_cast<char*>(pc);
reinterpret_cast
重新解释位模式,示例:int *ip; char *pc = reinterpret_cast<char*>(ip);
尽量不要使用此强制转换;
- 尽量使用C++风格的类型转换而非C风格的(type)var;
- 提倡使用
*p++
,递增运算符优先级高于解引用;
第五章 语句
else
默认匹配最近的没有else
的if
;
case
的值必须是整型常量表达式(字符算整型);case
语句会一直执行直至遇到break
;
- 异常类只有一个成员函数
what()
,返回const char *
,提供异常文本信息;
第六章 函数
- 函数调用的这对括号叫调用运算符;局部变量、形参等离开作用域自动销毁的变量称为自动对象;
- 函数最外层作用域中的局部变量不能与形参重名;
- 最佳实践:定义函数的源文件应包含声明函数的头文件;尽量使用常量引用作为形参;
- C++允许用字面值初始化常量引用;
- 【11】实参数量未知但类型都相同,C++11支持
initializer_list
类型,内存常量值,用法类似vector
:initializer_list<T> lst{a, b, c};
如:void msg(initializer_list<string> il){ … }
,调用:msg({"a", "b"}
;
- 省略符形参
...
只能用于形参列表的最后一个,且仅用于C和C++通用的类型;
- 不使用
typedef
让函数返回数组指针:Type (*function(parameter_list))[dimension]
,或使用decltype(arr) *arrPtr(int i)
,*
表示返回数组指针(这点函数指针同理)。
- 【11】尾置返回类型:
auto func(int i) -> int(*)[10];
- 默认实参必须是全局定义,其值取决于调用时对应实参的值;
constexpr
函数不一定返回常量表达式,只要编译期能得到值即可;
assert
预处理宏依赖于NDEBUG
预处理变量,编译参数加入NDEBUG等价于#define NDEBUG
- 预处理器定义的5个程序调试用的名字:
__func__
、__FILE__
、__LINE__
、__TIME__
、__DATE__
- 函数指针:用指针替换函数名即可,如
bool (*pf)(const int &);
,函数指针可以作为形参;
- 当把函数名作为值使用时,自动转换成函数指针(即
pf = func
等价于pf = &func
),调用时也会自动解引用(即bool b = pf(1);
等价于bool b = (*pf)(1);
)
- 函数类型的形参会被自动转换为指针:
void func(bool pf());
等价于void func(bool (*pf)());
;使用decltype(func) *
定义函数指针;
- 相反,函数返回值不会做自动转换,必须指明返回一个函数指针;
- 局部静态变量一般拥有和全局变量同等地位和处理方式;
- 内联函数、函数重载部分略;
第七章 类
constexpr
函数和定义在类内的函数都是隐式inline
函数;
this
指针是一个Type * const
,若需要对常量对象执行成员函数,可以在函数参数列表的最后加上const Type * const this
;仅在使用整体而非访问部分成员的时候使用this
;
- 类内函数定义顺序不影响,编译期先处理成员声明,再处理函数体(类外有影响);但是类内声明之间有顺序,如当一个函数使用类型
Type
时,必须之前已经定义此Type
类型。
- 只有当类没有声明任何构造函数时才会自动生成默认构造函数(且若有其它成员类且该类没有默认构造函数或其它特殊情况,则无法自动生成);
- 【11】可以
= default
使用默认构造函数,若此定义在内部则为内联,否则不是;
struct
和class
的唯一区别是默认访问控制,前者是private
后者是public
;
- 一个可变数据成员
mutable
即使是const
对象成员也不是const
;
- 友元声明:
friend
后接声明即可(并非真正的声明);外类的友元函数要写对应类classtype::
;若有函数重载则每种均应分别声明;
- 若类中已使用外层作用域定义的类型,则类内不可再定义此类型覆盖外层定义;
- 构造函数初始化列表为初始化,但函数体内为赋值;若一个成员变量同时在定义时被初始化和在初始化列表中,则以初始化列表为准(不推荐);
const
或引用类型必须初始化;
- 委托构造函数,即直接在初始化列表调用其它构造函数:
Type():Type(...){...}
,此处Type()
委托了Type(...)
;
- 函数传参遵循最佳匹配原则,不匹配时会做一次(且仅一次)类类型转换;
- 用初始化列表初始化类时需要所有成员均为
public
;
- 字面值常量类必须定义至少一个
constexpr
的构造函数,而普通类不能定义const
的构造函数;
- 应该在类外部定义静态成员,但需要在类内声明
static
;类外定义可以访问类的私有成员;
static constexpr int period = 30;
进行了声明和初始化,但没有进行定义,最好在类外再定义一下;
- 前向声明暂时不进行定义,可以用来定义指针或引用,或声明以它为参数或返回类型的函数;声明之后定义之前的类型叫作不完全类型;
- 静态成员类型可以是不完全类型,也可以就是它所属的类类型,而非静态成员只能声明成它所属类的指针或引用;
第二部分 C++标准库
第八章 IO库
ifstream
(头文件fstream
)和istringstream
(头文件sstream
)继承自istream
(头文件iostream
),输出同理;故使用cin
的地方均可以使用自定义的ifstream
和istringstream
对象代替;
- IO对象无拷贝和赋值;使用
<< flush
可以刷新缓冲区;当fstream
对象被销毁时会自动调用close
;
- 高级IO操作略;
第九章 顺序容器
- 【11】
array<type, size>
可以灵活指定大小,支持赋值和复制(因此可以直接作为函数参数或返回值),也支持迭代器、内置方法,提供更好的类型安全检查(std::out_of_range
异常);
- 顺序容器提供
arr.assign(begin, end)
进行赋值,但传入的迭代器不能指向调用者本身;
- 除
array
外的swap()
函数都是只交换指针;array
交换整体,但可以用std::swap()
实现交换指针;建议统一使用非成员版本的std::swap()
;
- 【11】C++11中接受元素个数或范围的
insert
返回指向第一个新加入元素的迭代器(旧版本返回void
),erase()
返回被删元素之后元素的迭代器;同样insert
的参数不能指向调用者容器;insert()
不能使用初始化列表;
emplace_front()
、emplace()
和emplace_back()
分别是push_front()
、insert()
和push_back()
的构造函数而非拷贝构造函数版本;
- 访问成员函数
front()
、back()
、下标[]
和at()
返回的都是引用;下标不做安全检查,超出范围为ub,at(n)
越界返回std::out_of_range()
;
- 【11】C++11实现了高效简单的
forward_list
单向链表,仅支持before_begin()
、insert_after()
、emplace_after()
和erase_after()
;
- 不要保存
end()
,因为在添加删除元素时原先end()
会失效,end()
操作很快;
resize()
和reserve()
不会减少容器占用的内存空间,而C++11的shrink_to_fit()
可以(但不保证退回内存);
- 容器适配器
stack
和queue
默认基于deque
实现,可以指明使用除array
外任何容器构造stack
,以及用list
或deque
(不能用vector
)构造queue
,如stack<string, vector<string>>
;
第十章 泛型算法
- 迭代器令算法不依赖于容器而是依赖于元素类型的操作,泛型算法永远不会执行容器的操作,只会运行与迭代器之上;
- 【11】lambda表达式:
[capture list](parameter list) -> return type {...}
参数列表和返回类型可省略(代表指定空参数列表和自动推断返回类型);捕获值在lambda创建时而非调用时拷贝,引用捕获需要保证对应变量存在;
[]
不捕获变量;[names]
规定捕获列表,默认为值拷贝,加&
表示引用捕获;[&]
和[=]
表示自动隐式捕获;[&, identifier_list]
和[=, identifier_list]
,后者变量前必须加&;
- 若函数体包含
return
外任何语句,编译器假定此lambda
返回void
,可使用尾置->
指定返回类型;
- 【11】参数绑定:
auto newCallable = bind(callable, arg_list);
其中使用_n表示newCallable
的第n个参数(需要using namespace std::placeholders
);使用ref
函数和cref
函数(#include <functional>
)返回的对象实现引用参数绑定,如:auto g = bind(f, a, ref(b), _2, c, _1);
(注:此特性新版本已被弃用,建议直接使用lambda);
- 插入迭代器
back_inserter it = vec
在it = t
时会push_back(t)
,front_inserter
会push_front(t)
(插入多个时会倒序插入),inserter
在*it = val
时等价于it = c.insert(it, val); ++ it;
- 流迭代器:注意
istream_iterator
允许懒惰求值,知道使用迭代器时才真正读取;
1 2 3 4 5
| istream_iterator<int> in_iter(cin), eof; vector<int> vec(in_iter, eof);
while (in_iter != eof) vec.push_back(*in_iter++);
|
1 2 3
| ostream_iterator<int> out_iter(cout, " "); for (auto e: vec) *out_iter++ = e;
|
- 反向迭代器
rptr.base()
实际为rptr
的后一个,以统一左闭右开区间;
- 泛型算法可能要求的五类迭代器:输入、输出、前向、双向、随机访问;能力更强的迭代器可以传给能力更弱的形参,反之报错;标准库提供的泛型算法见附录A;
- 对于
list
和forward_list
,应优先使用成员函数版本算法而非通用泛型算法;一般成员函数版本会改变容器及其迭代器,而通用函数不会;
第十一章 关联容器
- 【11】可以使用比较函数定义关联容器:
multiset<T, decltype(compareT)*>
,第二个为函数指针;
1 2 3 4 5
| set<string>::value_type; set<string> key_type; map<string, int>::key_type; map<string, int>::mapped_type; map<string, int>::value_type;
|
- 一般不对关联容器使用泛型算法(键值是const也意味着不能修改);
- 面向迭代器的查找遍历:
lower_bound(
)、upper_bound()
和equal_range()
;
- 【11】无序关联容器:
unordered_map
、unordered_set
、unordered_multimap
、unordered_multiset
;支持一系列桶接口、桶迭代和哈希策略函数;
第十二章 动态内存
- 【11】C++11新特性支持智能指针
shared_ptr
、unique_ptr
和前者的伴随类weak_ptr
;
make_shared<T>(args)
和shared_ptr<T>p(q)
定义,编译期使用引用计数智能判定是否销毁指针指向的值并返还内存;当前指针设为nullptr
将递减原对象引用计数,可以使用reset()
销毁对象(注意别的指向此对象的指针);
- 空悬指针是
delete
之后仍然指向原对象地址的指针,相当于野指针;不要混用智能指针和普通指针;
- 用
make_unique<T>(args)
(11不支持)或unique_ptr<T> p(new int(42));
定义unique_ptr
,不能拷贝和赋值(但可以作为函数参数和返回值);用unique_ptr<int> p2(p1.release())
或p2 = move(p1)
或p2.reset(p1.release())
转移对象所有权(p1、p2均交出当前所有权,并将p1所有权交给p2);
weak_ptr<T> p(sp)
定义weak_ptr
,不增加对象的引用计数,不阻止管理对象的销毁,用p.use_count()
返回共享对象数量,p.expired()
返回use_count()
是否为0,用lock()
在expired
时返回空shared_ptr
,否则返回指向p的对象的shared_ptr
;
- 不应使用旧规范的动态数组,而应使用
vector
;使用vector<int>().swap(vec);
释放vec
空间;
allocator
类分离了内存分配和对象构造:
1 2 3 4 5
| allocator<T> a; p = a.allocate(n); a.deallocate(p, n) a.construct(p, args); a.destroy(p);
|
- 【11】
construct
在旧标准中args
必须传入一个元素类型值,C++11中可以使用多个构造函数参数初始化,如a.construct(q++, 3, 'c')
令*q
为"ccc"
;
- 对为构造部分进行初始化,copy函数返回初始化范围的后一个尾置指针;
1 2 3 4
| uninitialized_copy(b, e, b2) uninitialized_copy_n(b, n, b2) uninitialized_fill(b, e, t) uninitialized_fill_n(b, n, t)
|
第三部分 类设计者的工具
第十三章 拷贝控制
- 三/五法则:五种拷贝控制操作特殊成员函数:拷贝构造、拷贝赋值、析构、移动构造、移动赋值,前三个可以控制类的拷贝操作;常常是否需要自定义拷贝构造和拷贝赋值就看是否需要析构函数;
- 拷贝构造函数
T(const T&)
;默认合成拷贝构造函数将参数成员逐个拷贝到当前对象中;
- 直接初始化选取最符合的构造函数,可能调用拷贝构造函数;拷贝初始化可能进行类型转换;拷贝构造函数可以布置一个参数,但必须带默认参数;
- 拷贝初始化发生:使用
=
定义变量时;函数传递值参、返回
值类型时;使用C++11的花括号列表时的部分类类型;emplace
都进行直接初始化;
- 重载拷贝赋值运算符:
T& operator =(const T &)
;合成析构函数不会delete
它的指针成员,重载析构函数:T::~T()
;
- 给函数传递类类型对象时,除了常规作用域查找外还会查找实参类所属的命名空间;当自定义和
std::
有命名冲突时,默认使用自定义函数;不提倡使用using
而应该在每个使用标准库函数时均添加std::
;
- 标准库容器、string和shared_ptr既支持移动又支持拷贝,IO类和unique_ptr类可以移动但不能拷贝;
- 【11】右值引用可以被绑定到要求转换的表达式、字面常量或返回右值的表达式;头文件
utility
中move()
返回给定对象的右值引用,即承诺除了赋值和销毁外不会再使用原左值,定义移动构造函数:T (T&& other)
;定义移动赋值函数:T& operator=(T&& other)
forward
和move
不可以using
,必须带std::
;
第十四章 重载运算与类型转换
- 使用含有状态的函数对象类:可以被作为参数传入泛型算法,如
for_each(vs.begin(), vs.end(), PrintString(cerr,'\n'))
;lambda是函数对象;
1 2 3 4 5 6 7 8
| class PrintString{ public PrintString(ostream &o = cout, char c = ' '):os(o), sep(c){} void operator()(const string &s)const{ os << s << sep; } private: ostream &os; char sep; };
|
- 【11】C++11支持标准库
function
类型;
- 类型转换运算符:
[explicit] operator int() const;
- 表示运算符的模板对象类:
greater<int>()
等;
第十五章 面向对象程序设计
- 【11】C++11允许在参数列表后使用
override
关键字显式注明覆盖了继承的虚函数;
- 静态成员即使被继承也只存在唯一实例;
- 【11】在类名后使用
final
关键字防止继承;
- 不存在从基类向派生类的隐式类型转换;派生类向基类的转换只对指针和引用有效;
- 可以使用作用域运算符指定使用的虚函数;
- 名字查找先于类型检查;在构造函数和析构函数中使用的虚函数就是此函数所在的类的虚函数,而非动态类型的虚函数;
- 如果一个类会被派生,应该将其析构函数定义为虚函数;
第十六章 模板与泛型编程
- 有关模板、实例化、包扩展、转发、特例化、
std::move
等内容;
- 推荐阅读 Effective Modern C++,缩略版;
- 关于移动语义;
第四部分 高级主题
第十七章 标准库特殊设施
- 【11】
tuple
类似pair
但成员数量任意(固定),定义为tuple<T1, T2, ..., Tn> t(v1, v2, ..., vn)
或make_tuple(v1, v2, ..., vn)
;get<i>(t)
返回t
的第i个成员的引用(t
为左值则返回左值引用,右值则右值引用);拆包:std::tie(gpa, grade, name) = make_tuple(3.8, 'A', "张三");
- 两个辅助类模板:
1 2 3
| typedef decltype(item) T; size_t sz = tuple_size<T>::value; tuple_element<1, T>::type cnt = get<1>(item);
|
bitset<n> b(u)
定义bitset
;
- 【11】
regex
类定义在regex
头文件中,表示一个正则表达式;使用的是ECMAScript正则表达式语言,具体使用略;
- 【11】随机数引擎类和随机数分布类用法:
default_random_engine e; uniform_int_distribution<unsigned> u(0, 9);
e
是引擎类,u
是分布类,用u(e)
返回一个随机数;具体使用略;
- 【11】C++风格IO格式控制略;C++11新增了十六进制浮点数等格式操作;
第十八章 用于大型程序的工具
- 异常处理之栈展开:沿函数嵌套调用链查找对应
catch
子句,若为找到调用标准库函数terminate
,沿着调用链创建的对象将被销毁;
- 【11】紧跟函数参数列表之后的
noexcept
标识该函数不会抛出异常,与同样位置写throw()
等价;catch(...)
捕获所有异常,常常做部分处理后重新抛出throw空语句(会沿调用链向上传递);
- 命名空间可以不连续;旧C++使用static表示文件级变量,文件外不可访问,新C++应使用未命名名字空间;
- 多重继承,使用虚继承解决菱形继承问题;
第十九章 特殊工具与技术
1 2 3 4
| void *operator new(size_t); void *operator new[](size_t) void *operator delete(void*) noexcept; void *operator delete[](void*) noexcept;
|
- 运行时类型识别(Run-Time Type
Identification,RTTI):使用基类对象指针或引用执行派生类非虚函数时使用;
dynamic_cast<type*/type&/type&&>(e)
在转换失败时返回空指针或抛出bad_cast
异常;typeid(e)
返回运行时类型判断;
- 析构函数销毁对象但不释放内存;
- 枚举成员是
const
,可用enum class
或enum struct
限定作用域;限定作用域的枚举必须加上作用域限定符访问,且不会进行隐式转换;
- 【11】C++11中可以指定enum的大小:
enum big: unsigned long long
,且允许前置声明;
- 成员指针:
1 2 3 4
| auto pdata = &Screen::contents; Screen myScreen, *pScreen = &myScreen; auto s = myScreen.*pdata; s = pScreen->*pdata;
|
union
:节省空间的类,一次只有一个成员有效;匿名union
的成员在union
定义所在作用域可以被直接访问;
个人注记
现代C++教程:快速上手C++
11/14/17/20
- 现代C++不再允许将字符串字面值常量赋值给
char *
,应该使用const char *
;
unexpected_handler
、set_unexpected()
被弃用,应使用noexcept
;
auto_ptr
被弃用,应使用unique_ptr
;
register
被弃用,若一个类有析构函数,不再自动生成拷贝构造函数和拷贝赋值运算符;
- C++17弃用了
<ccomplex>
;
- 使用
extern "C"
分离代码中的C代码和C++代码,再用clang++链接.o文件;(见此)
- C++14之后实现了泛型函数版本的
begin()
、end()
等,建议使用;