最近开始着手写slam代码,看一些常用库源码的时候发现各种力不从心,一些c++11的骚操作竟然没见过,是时候完整撸一发c++ primer祭天了。
iostream
- 标准输入:cin
- 标准输出:cout、cerr、clog
1
2
3
4
5
6
using namespace std;
int v1=0, v2=0;
cin >> v1 >> v2;
cout << v1+v2 << endl;
cerr << "This is nonsense." << endl;- << 和 >> 的方向表示了数据流的走向,也就是赋值的方向。cerr用来输出错误信息。
控制流
while:每次执行循环之前先检查循环条件
do while:先执行循环体后检查条件
1
2
3
4
5
6while (condition)
statement
do
statement
while (condition);for:每次执行循环之前先检查循环条件,执行循环之后执行表达式
1
2
3
4
5
6
7for (init-statement; condition; expression)
{
statemnt
}
// 范围for语句
for (declaration : expression)
statementswitch:
- case label:case标签必须是整形常量表达式
- 如果某个case标签匹配成功,会往后顺序执行所有case分支,直到结尾或者遇到break
- default标签
1
2
3
4
5
6switch(ch)
{
case 'a': case 'b': case 'c':
++cnt;
break;
}break:负责终止离他最近的while、do while、for或switch语句。
continue:负责终止离他最近的while、do while、for循环的当前迭代。
goto:无条件跳转到同一函数内的某个带标签语句。
labeled statement:
label: statement
异常
throw:引发异常,后面紧随一个异常类型,终止当前函数,将控制权转移给能够处理该异常的代码。
1
2
3
// runtime_error 标准库异常类型
throw runtime_error("Data must refer to same name");try:处理异常,后面紧随一套catch子句用来处理异常。
1
2
3
4
5
6
7try{
program statements
} catch (exception-declaration) {
handler-statements
} catch (exception-declaration) {
handler-statements
} ...try语句块内声明的变量在块外无法访问,即使是catch语句。
catch一旦完成,程序跳转到最后一个catch子句之后的语句。
类
类型 & 对象(实例),变量 & 行为(方法)。
存在类内默认初始化
类通常被定义在头文件中,头文件名字应与类的名字保持一致
头文件通常包含只能被定义一次的实体,如类、const等。
头文件保护符#ifndef系列,创建预处理变量,防止多次包含。
构造函数初始值列表:冒号以及冒号和花括号之间的代码
列表只说明用于初始化成员的值,而不限定初始化的具体顺序。
成员的初始化顺序与它们在类定义中的出现顺序一致。
1
2
3
4
5
6
7
8
9
10// 为类成员初始化
Sales_data(const string &s, unsigned n, double p)
: bookNo(s), units_sold(n), revenue(p*n) {}
// 区别于赋值
Sales_data(const string &s, unsigned n, double p)
{
bookNo = s;
...
}接口与封装:
- 定义在private说明符之后的成员只能被类内成员函数访问,封装了类的实现细节。
- 定义在public说明符之后的成员可以在整个程序内被访问,定义类的接口。
class和struct的区别:成员访问权限
- struct:定义在第一个说明符之前的成员是public
- class:定义在第一个说明符之前的成员是private
友元:允许其他类或函数访问它的非公有成员,在类内添加以friend关键字开始的友元声明。
友元的声明仅仅指定了访问权限,而非一个通常意义上的函数声明。
1
2
3
4
5
6
7
8class Sales_data {
// 友元声明
friend Sales_data add(const Sales_data&, const Sales_data&);
// 非公有成员
private:
string bookNo;
double revenue = 0.0;
};静态成员static:与类本身相关联,不属于任何一个对象,因此不是在创建类对象的时候被定义的,因此通常在类的外部定义和初始化,在类内部添加以static关键字开始的静态成员声明。
内置类型
- 内存中的一个地址对应一个字节
- unsigned类型表示大于等于0的数($[0, 2^{n}-1]$),被赋给一个超出表示范围的数时,自动取余,作为循环条件时当心进入无限循环
- signed类型正负值范围平衡($[-2^{n-1}, 2^{n-1}-1]$),被赋给一个超出表示范围的数时,结果未定义
- 字符型char,单引号,一个字节
- 字符串型,双引号,常量字符数组,结尾隐含空字符 ‘\0’
- nullptr = 0(传统NULL包含在cstdlib头文件内)
变量
列表初始化,花括号
1
2
3
4
5
6// 拷贝初始化
int x=0;
int x={0};
// 直接初始化
int x{0};
int x(0);变量声明extern,源于分离式编译机制,一个变量只能被定义一次,可以声明多次
作用域,嵌套作用域 & 内部重定义
复合类型
引用,
typename &declaration
,浅拷贝,绑定一个对象,引用不是对象指针,
typename *declaration
,存放对象地址1
2
3
4int a;
int *p, *q=a;
p = &a;
p = q;取地址符&
1
2
3int *p = a;
int *p = &a;
// a--->对象 &a--->地址解引用符*
1
2
3
4
5
6int a;
int *p;
*p ---> undefined
p = &a;
*p = 10;
// p--->指针 *p--->对象void* 指针,可以指向任意类型的对象,但是不能进行对象操作
const限定符
参与编译预处理
要实现多个文件共享,必须在const变量定义之前加上extern关键字
1
2
3
4// define
extern const int bufferSize = fcn();
// declare
extern const int bufferSize;允许任意表达式作为初始值(允许隐式类型转换)
常量引用,允许非常量赋值,实际引用一个内存中的“临时值”
指向常量的指针,允许非常量赋值,但是不能通过该指针修改对象
常量指针,指针始终指向同一个对象
常量表达式constexpr,表达式在编译过程中就能得到计算结果
处理类型
类型别名typedef & using
1
2
3
4
5
6
7// 传统
typedef double base;
typedef base *p; // p是double指针
base a;
p p1=&a;
// c++11
using base = double;auto类型说明符,让编译器分析表达式所属类型并为变量赋值
1
2// 一条类型声明语句中所有变量的类型必须保持一致
auto i=0, *p=&i;decltype类型指示符,仅分析表达式返回类型,不做赋值(因此不做实际计算)
1
decltype(f()) a=x;
string
读取,
>>
不读取空白,遇到空白符停止,getline
保留空白符,遇到换行符停止。字符串字面值不是string对象,而是C风格字符串,
c_str()
成员函数能够将string对象转化成C风格字符串遍历,范围for语句,每次迭代declare的变量会被初始化为expression的下一个元素
1
2
3
4
5
6
7
8
9for (declaration : expression)
statement
string str("some string");
// 赋值
for (auto c: str)
cout << c << endl;
// 引用
for (auto &c: str)
c = toupper(c);size()
返回的类型是string::size_type
,通常用auto
vector
- 类模版,相同类型对象的集合,声明时必须提供元素类型
vector<int>
- 添加元素
push_back()
- 类模版,相同类型对象的集合,声明时必须提供元素类型
迭代器
- 所有标准库容器都支持迭代器,只有少数支持下标访问
begin()
返回指向第一个元素的迭代器,end()
返回尾后元素的迭代器cbegin()
和cend()
操作类似,返回值是const_iterator,不能修改对象- 迭代器的类型是
container::iterator
和container::const_iterator
,通常用auto - 解引用迭代器得到对象
- 箭头运算符
->
,结合解引用+成员访问两个操作 - 迭代器失效:容器改变容量
数组
- 大小固定,编译的时候维度应该已知,因此必须是常量表达式
- 不能用做拷贝和赋值
表达式
左值和右值
C语言中,左值指的是既能出现在等号左边也能出现在等号右边的变量或表达式,通常来说就是有名字的变量,而右值只能出现在等号右侧,通常就是一些没有名字也取不到地址的中间结果。
继承到C++中归纳来讲就是:当一个对象被用作右值的时候,用的是对象的值(内容),当被用作左值的时候,用的是对象的身份(在内存中的位置)。
求值顺序
有四种运算符明确规定了求值顺序,逻辑与(&&)、逻辑或(||)、条件(?:)、逗号(,)运算符。
1
2int i = 0;
cout << i << ++i << endl;前置版本和后置版本的递增递减
用于复合运算中时,
前置版本首先修改对象,然后将对象本身作为左值返回。
后置版本将对象原始值的副本作为右值返回。
位运算
- 整形提升,char8->int32
- 添0,越界丢弃
逗号运算符:含有两个运算对象,首先对左表达式求值,然后将求值结果丢弃掉,最右边的表达式的值将作为整个逗号表达式的值。本质上,逗号的作用是导致一些列运算被顺序执行。
1
2// 分别对逗号表达式内对象赋值,然后返回最右cnt的值
var = (count=19, incr=10, cnt++)
函数
局部静态对象static:首次调用时被初始化,直到程序终止才被销毁。
1
2
3
4
5
6int f()
{
// 只初始化一次,函数调用结束以后这个值仍有效
static cnt = 0;
return ++cnt;
}参数传递:如果形参被声明为引用类型,它将绑定到对应的实参上(传引用调用),否则将实参的值拷贝后赋给形参(传值调用)。
含有可变形参的函数
所有实参类型相同,可以使用initializer_list模版类型的形参,列表中元素是const。
1
2initializer_list<T> lst;
initializer_list<T> lst{a, b, c, ...};编写可变参数模版
省略符形参:对应的实参无需类型检查
1
2
3
4// 带部分形参类型
void foo(parm_list, ...);
void foo(...);
内联函数incline:避免函数调用开销
调试帮助
- NDEBUG预处理变量:用于关闭调试状态,assert将跳过不执行。
- assert (expr) 预处理宏:如果表达式为假,assert输出信息并终止程序。
预处理名字由预处理而非编译器管理,因此可以直接使用名字而无须提供using声明。
- static_cast和dynamic_cast强制类型转换
- static_cast \
(expression):暴力类型转换,不运行类型检查。 - dynamic_cast\
(expression):运行类型检查,下行转换安全。
- static_cast \
- new & delete:new [] 要和 delete []对应上。
- c++的oop特性(private public…)只在编译时刻有意义。同一类的对象可以互相访问私有成员。
- firend:注意方向是give acess to,授权friend访问自己的private。编译时刻检查。
- composition:组合,用一系列对象构造对象。
- inheritance:继承,用一些类来构造新的类。
1 | class A; |
构造:子类构造的时候要先构造父类,析构的时候反过来,先析构子类。
重名:name hiding,special for c++。
- protected:designed for sub class。子类可以直接访问。其他类看不到。
- overload:参数表必须不同,否则编译器无法识别。
- default argument:defaults must be added from right to left。must be declared in .h files。发生在编译时刻。
- inline:不用真正调用函数,而是直接插入汇编代码段。tradeoff between space and time consuming。区别于宏,宏是没有类型检查的。
const
- declare a variable:是变量,而不是常数
- 变量的生存期限
- Automatic:local variables,存活在{}函数定义里面
- dynamic:controlled by special instructions,比如main里面实例化的变量/类/内存,最后delete/free它
- thread:
- static:程序运行时间内有效
- 存储
- stack:快,小,用于Automatic
- heap:慢,大,用于dynamic
- 指针
- 比引用灵活:可以在程序存活期间改指别的目标
- 用于支持底层的各类不连续存储:dynamic memory各种链表
- 智能指针
- std::unique_pointer
:每个object只能有一个unique pointer,pointer和target的生存时间相同 - std::shared_pointer
:每个object可以有多个,只要pointer存在target就存在 - std::weak_pointer
:同上 - c++11里面是shared_ptr、unique_ptr 以及 weak_ptr
- std::unique_pointer
- nullptr:可以当false用,free memory,所以不能解引用,*p会segmentation fault
- const
- T*:自由指针
- T const*:用read-only way指向value
- T* const:常数指针,不能改指
- T const* const:即不能改指,也不能改数
- this指针
- available inside member functions:类/函数等{}里面
- 返回object的地址
- this->能够访问成员
- *this表示object本身
- unique
- java里面有garbage collector,扫描那种没有指针的内存地址,然后销毁,但是不及时
- c++里面关键字类型可以自动回收
- unique_ptr保存在stack里面,但是它创建的对象在heap里面,unique_ptr失效的时候heap里的object也销毁掉
- move可以给object切换指针