- 同class 的 obj 互为friend
- 传参和返回尽量引用
- 返回时如果是临时变量不能引用
- 加减乘除负号不能返回引用,正号可以
- 传递者(return, 实参) 无需知道 接受者(函数返回类型,形参)是不是接受的引用
- 尽量const(类方法,传参等)
- 构造函数尽量初始化赋值不要通过{}赋值
c3+=c2+=c1
从右往左,operator +=
如果返回值不是对象或其引用则不能连续- 操作符都是作用于左边
T pc = new T(a)
的操作
void* mem = operator new(sizeof(T))
(实际内部调用malloc(n))pc = static_cast<T*>(mem);
//转型pc->T:T(a)
//调用构造函数(定义成员,赋值)
delete pc
的操作
T::~T(pc)
//析构函数operator delete(pc)
//(实际内部调用free(pc)
new []
要和delete []
对应用
- 内存分配会16字节的倍数 (32位和64位都是?)
- 如果没有对应,比如
T *p = new T[3]; delete p
- 调用
delete p
会释放申请的动态内存,但是只会调用一次析构函数(图是有问题的应该是从下往上析构)- 调用
delete[] p
才会在释放动态内存后,正确的调用三次析构函数- 如上图,如果不是
new
的数组,会少了从上往下的第二个3内存
- 内存依次记录的是(16进制):
- 分配的内存大小+1(1代表分配出去的意思,因为分配的大小会是16的倍数,可以用这一位)
- 如果是数组会记录数组的大小
- 实际数据
- 同第一个
class Account{ public: static double m_rate; // 声明,同时需要在外部定义(应该是编译器怕导致重复链接 ) static const int x = 2.1; // 常量可以,也可以用非常量的方式,但是不能在内外都赋予初始值 static constexpr double d = 1.0; // 编译器-O0时对于非int char等需要这么操作 static Account ac; //可以不在外部定义(在外部定义也OK) Account ac; // 错误类类型成员只能是static};double Account::m_rate; //定义(必须要有这一步)复制代码
- 模板导致代码膨胀
- 继承函数继承的是调用权
- 委托模式共用引用修改时可以 copy-on-write (string的实现也是cow)
- 成员函数同时存在const 和 non-const版本时,const obj 只能调用const的,non-const的只能调用non-const的
- 继承和组合构造从内而外,析构由外而内,继承的父类析构必须是
virtual
- 继承空心三角箭头头
- 组合实心菱形箭头尾
- 委托空心菱形箭头尾
- T转换函数 ex:
operator double() const {return xxx;}
把T->double- 同时可以使用no-explict的构造函数 ex:
T(double x){}
double->T
Fraction f(3,5);double d = 4.0+f; // 会先找operator(double, Fration),看看能不能把Fraction转换成double, 找 operator double()或者non-explict构造函数,但是共存可能会导致二义性复制代码
pointer-like class
- 特殊的重载符号
operator ->
,重载后不会消耗掉->
;- 迭代器也是一种智能指针
function-like class
- 重载
operator ()
- 如果类型是依赖于模板参数的限定名,那么在它之前必须加
typename
,避免编译器在实例化时才知道,explain(T::c
不加的话并不知道这个是个嵌套类型还是静态成员或者静态函数) 比如:
templatevoid foo() { T::iterator * iter; // 不加typename, 这一行可能是个乘法运算可能是个定义语句 // ...}复制代码
链接器相关: 我们需要注意从库中导入文件的粒度问题:如果某个特定符号的定义是必须的,那么包含该符号定义的整个目标文件(一个库可能会有多个目标文件)都要被导入
有默认值的会编译不通过(list模板参数有默认值)
虚函数实例内存多一个虚指针的大小