Essential C++ note

记录本书(对我来说)的一些重点内容

1 C++编程基础

  1. 初始化方法:构造函数法(constructor syntax)
1
2
int var(66);
int var2{66}; // ok
  1. srand()随机数种子;rand()则产生一个介于0和int所能表示的最大整数;需包含头文件cstdlib
  2. cerr(standard error)代表标准错误设备,与cout唯一区别就是不带缓冲,立即显示于用户终端

2 面向过程的编程风格

  1. 使用模板,则声明与定义要放在一起
  2. 函数指针
1
2
3
4
5
6
7
// 给函数指针赋初值
const vector<int>* (*seq_str)(int) = 0;
// seq_array是个数组,内放函数指针
const vector<int>* (*seq_array[])(int) = {
fibon_seq, lucas_seq, pell_seq,
triang_seq, square_seq, pent_seq
}
  1. inline函数的声明和定义都需要放在头文件,声明和定义有一个标明inline即可(在类定义中实现则默认是inline);其他的函数,则必须是“定义放在程序代码文件”、“声明放在头文件”
  2. 多文件共享变量
1
2
3
4
5
6
7
8
9
/*
变量只能定义一次,但是可以声明多次
假定main.cpp func.cpp func.h三个文件
1. 在func.cpp有一个int a=1;如果要在main.cpp中使用,要在func.h或main.cpp中写extern int a;
2. const object和inline函数一样,是“一次定义”规则下的例外。const object定义只要一出文件外就
不可见(意味着可以在多个程序代码中加以定义)。因此如果const int a=1想多文件共享,可以直接写
在func.h中,其他的非const object则不可。
可参考:https://www.icode9.com/content-1-915590.html
*/

3 泛型编程风格

  1. list不支持iterator的偏移运算(+、-)但是有++和--
  2. 对于标准容器,不确保目标空间大小,可使用iterator inserter用插入操作替代赋值操作
  3. <这章挺多东西,需要结合书本去看>

4 基于对象的编程风格

  1. 如果有必要为某个class编写[拷贝构造],则同样有必要为它编写[赋值操作]
  2. 没有一个const reference class参数可以调用公开接口中的non-const成分
  3. 类中的变量声明为mutable,标明其不会破坏对象的常量性(constness),即可在const函数中可以修改它
  4. 当定义class的static function时,不可加上关键字static(同理静态成员变量)
  5. 当类中有静态成员变量时,需要及时在类外进行初始化,否则报错:无法解析的外部命令
  6. 通常情况:**operator*无参表示解引用;有参表示乘法**
  7. friend声明可以出现在类定义任意位置,不受public或private影响
  8. 嵌套类型(Nested Type),类中可以定义嵌套类型,再用域解析符(例如每个STL容器类都有自己的iterator)
  9. 重载iostream(看书)
  10. 类的函数指针:
1
2
3
4
5
6
7
8
9
void (num_sequence::*pm)(int) = 0;
// 如果决定上面一行复杂可以写成:
typedef void (num_sequence::*PtrType)(int);
PtrType pm = 0;
// 成员函数取址要加上class scope限定符和&取地址运算符(不同于一般函数,都不可以省!)
PtrType pm = &num_sequence::fibonaci;
// 调用,由此要引出pointer to member selection运算符“.*”和"->*"
(ns.*pm)(pos)
(pns->*pm)(pos)

5 面向对象编程风格

  1. 纯虚函数:将虚函数赋值为0
1
virtual void gen_elems(int pos) = 0;
  1. 任何类声明了纯虚函数,那么由于其接口不完整(无定义),程序无法为其产生实体。这种类只能作为派生类的子对象使用,而且前提是这些派生类为所有虚函数提供确切的定义。
  2. 一般规则:凡基类定义了虚函数,则destructor也要声明为virtual
  3. 一般而言,对象的拷贝构造函数开发者如果未提供,则编译器会自动生成默认的拷贝构造函数。然而以下情况默认的拷贝构造函数会被删除:
1
2
3
4
5
6
7
/*
1、存在非静态的const成员变量
2、存在非静态的引用成员变量
3、存在不能拷贝的成员变量
4、存在不能拷贝的基类
5、存在用户定义的移动构造函数或移动赋值函数
*/
  1. 在基类的constructor中,派生类的虚函数不允许被调用
  2. static_cast和dynamic_cast,前者无条件转换,后者判断(比如基类的指针是否真的指向了该派生类)是否可以转换为目的类型再进行转换

6 以template进行编程

  1. 函数传入一个指针,只能改变指针所指对象的内容,要想改变指针本身(指向)就要传递reference to pointer
1
BTnode*& prev // 可以看成BTnode* &prev 或 ((BTnode*)&) prev
  1. 模板类声明友元的问题:
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
template <typename T>
class A {
public:
A(T t):dy(t) {}
T dy;

friend ostream& operator<<(ostream& os, const A<T>& b);

private:
void func(ostream& os) const{
os << dy << endl;
}
};

template <typename T>
ostream& operator<<(ostream& os, const A<T>& b) {
b.func(os);
return os;
}

int main() {
A<double> a(4.2);
cout << a << endl;
return 0;
}
1
2
3
4
5
6
7
8
9
/*
原因:
因为"operator<<"这个函数的参数T不应该依赖于class的模板参数,友元本来是可以访问类的所有数据成员的(该模板类的int、double、string对象),你这样依赖的话,放到外部表名该参数T是属于类内部本身,解决方案是为了更好的支持友元,我们一般单独给友元一个模板参数
1、类中友元声明改为(意义不明不过能用,但是其下绿波浪线提示函数未定义)(不推荐)
friend ostream& operator<< <T>(ostream& os, const A<T>& b);
2、类中友元声明改为(S不同于T)(推荐)
template <typename S>
friend ostream& operator<<(ostream& os, const A<S>& b);
*/
  1. 非类型参数
1
2
3
4
5
/*
1、这类参数在模板内部都是常量值
2、只允许传入整形、指针和引用这三类
3、调用非类型参数的实参必须为常量表达式(必须在编译时能计算出结果的)
*/

7 异常处理

  1. 重新抛出时,只需写下关键字throw即可。它只能出现于catch子句中
  2. 局部资源管理,在易发生异常后释放资源是一件风险很大的事情。虽然可以通过try catch处理,但是释放资源的代码要出现两次,不好。这就引出了resource acquisition is initialization(RAII)策略,即初始化阶段进行资源请求。说人话就是,在构造函数中请求所有资源,在析构函数中释放所有资源:C++保证,在异常处理机制终结某个函数之前,所有局部对象的destructor都会被调用。或者使用智能指针例如:unique_ptr、shared_ptr等。
  3. ptext = new vector; 经过以下几个步骤(异常出现,余下不会执行,会沿着调用链抛出)
1
2
3
4
5
6
/*
// 可能会reorder哦~
1、分配足够的空间
2、将vector<string> default constructor应用于heap对象之上
3、将对象地址设置给ptext
*/