前面的基本上不太需要进行记录
从 C ++ Primer Plus 第十二章开始有些东西还是得记一下,毕竟类编程算是 C ++ 最特色也最难的一部分了
重载运算符
有些运算符是不可以被重载的
.:成员访问运算符
.*, ->*:成员指针访问运算符
:::域运算符
sizeof:长度运算符
?::条件运算符
#:预处理符号
重载 << 和 >> 的时候要返回对象的可变引用,这是因为要考虑 A<<B<<C
的情况
要对运算符反着使用的时候提供重载,需要使用友元函数
重载赋值运算符
自赋值检查:避免操作冲突。
释放旧资源:防止内存泄漏。
深拷贝:复制数据而非指针。
异常安全:确保操作失败时对象有效。
返回引用:支持链式操作。
特殊成员函数
C++ 会自动添加的成员函数有
-
默认的构造析构函数
-
赋值与地址运算符
-
复制构造函数
new 和 delete
new 的对象一定要手动 delete
new[] 要和 delete []一起用,因此分配字符数组时,当只分配一个也要 buf[1], 这样才能在析构中统一处理
使用定位 new:
char * buf = new char[100];
Obj* obj1;
obj1 = new (buf) Obj;
定位 new 不能直接 delete 分配的对象,要先调用 buf 中对象的析构函数,然后直接 delete[]整个 buf
派生
公有派生时无法直接访问父类私有成员,要使用成员初始化列表初始化父类对象,如果不使用成员初始化列表则会调用父类默认构造函数
派生类对象过期时,程序将首先调用派生类析构函数,然后再调用基类析构函数
静态联编和动态联编
基类中没有将 ViewAcct()声明为虚的,则 bp->ViewAcct()将根据指针类型(Brass *)调用 Brass::ViewAcct()。指针类型在编译时已知,因此编译器在编译时,可以将 ViewAcct()关联到 Brass::ViewAcct()。总之,编译器对非虚方法使用静态联编。
然而,如果在基类中将 ViewAcct()声明为虚的,则 bp->ViewAcct()根据对象类型(BrassPlus)调用 BrassPlus::ViewAcct()。
虚函数的工作原理
给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针。这种数组称为虚函数表(virtual function table,vtbl)。虚函数表中存储了为类对象进行声明的虚函数的地址。
虚函数特殊情况
构造函数不能是虚函数(还需要调用基类的构造函数,不覆盖)
如果要继承,析构函数需要是虚函数(需要释放子类的内存,不应该使用父类的。通常应给基类提供一个虚析构函数,即使它并不需要析构函数。)
如果在派生类中重新定义函数,将不是使用相同的函数特征标覆盖基类声明,而是隐藏同名的基类方法,不管参数特征标如何。
特殊情况:返回类型协变: 对于返回类型是本身类引用或是指针的情况,可以在子类虚方法中修改返回类型为子类的引用或是指针(返回对象本身不可以),且同样能被视为和原函数签名相同
如果基类声明被重载了,则应在派生类中重新定义所有的基类版本。否则其他重载函数会被隐藏
使用 = 0 来创建纯虚函数,包含纯虚函数的类不能被实例化
动态内存分配与类继承
对于要使用动态内存分配的类,需要遵循 rule of 3
添加显示的析构函数,对于子类来说,只要 free 自己子类另外分配的内存即可,父类的构造函数会释放父类的内存
添加复制构造函数。与构造器相同,子类在定义复制构造函数的时候要使用成员初始化列表,使用父类的构造函数来构造父类的部分(不使用则同样会使用父类的复制构造函数)
添加赋值运算符。除了管理子类动态分配的内存之外还需要显式调用父类的赋值运算符。Base::operator=(rs),其效果等同于 *this=rs
私有继承
私有继承用于表示 has- a 关系,继承之后父类会成为子类的 private 属性。如果需要调用父类的方法,要通过类名和域解析运算符来调用,访问父类对象的时候也需要用强制类型转换转换成父类才行
类模板
使用类模板时,模板类中可以直接使用类名,而外部则需要显式指定 Class\
友元类
使用 friend class
友元类中使用 Class& 作为参数来访问另一个类的属性
可以使一个成员函数成为友元而不是整个类,但是需要此类定义在此友元成员函数之前。
举例,例如 Remote 类的函数 A 是 Tv 类友元成员函数,Tv 中的函数声明 A 会提到 Remote 类,因此 Remote 类的函数声明应该在 Tv 前面。但是 Remote 类中用到了 Tv。正确做法是,先 class Tv; 进行占位再定义 Remote,最后给出 Tv 的完整定义
异常
noexcept
在函数定义最后加上 noexcept 可以告诉编译器此函数不会引发异常,帮助编译器优化编译结果
使用 catch
catch 异常类对象的时候,要使用对象的引用,即使 catch 得到的是异常类对象的拷贝。因为使用引用可以使用派生特性,即 catch 基类异常类可以 catch 到子类异常类
std::exception
需要 include exception
之后继承 std::exception。需要实现 what 方法用来说明异常信息。
stdexcept
头文件 stdexcept 中定义了一些 C ++ 提供的异常类
常用的:
invalid_argument
length_error
domain_error
out_of_bounds
runtime_error:range_error,overflow_error,underflow_error
bad_alloc
通过 include new 可以使用 bad_alloc 异常,可以处理 new 分配内存失败的问题
RTTI
RunTime Type Identification
只适用于包含虚函数的类
dynamic_cast
对于指针 cast
dynamic_cast<Type*>(pt)
如果不能够安全转换,返回空指针
对于对象引用 cast
dynamic_cast<Type &>(ref)
由于引用不能是空的,所以如果转换失败会抛出 bad_cast 异常
typeid 和 type_info 类
typeid 运算符返回一个对于 type_info 对象的引用,这个对象重载了 == != 运算符,可以用于比较
类型转换运算符
dynamic_cast
const_cast
用于加上或去除 const volatile. 注意,转换前后的类型必须完全一样
static_cast
static_cast<typename>(expression)
仅当 type 可以被隐式转换为 expression 的时候才是合法的。允许 upcast 和 downcast
reinterpret_cast
用于执行一些危险的转换操作,例如直接把指针转换为对象,转换为 struct
限制:这种转换不允许删除 const
这种转换也不能把指针转换为更小的整形或浮点型,且不支持函数指针和数据指针的相互转换。
string 类
string 实际上是 basic_string 的使用 char 的具体化的一个 typedef
因此,也存在使用别的更宽单字符的 typedef
wstring 是使用的 wchar_t
u16string 使用 char16_t
u32string 使用 char32_t
智能指针
是基于模板的类对象,需要 include memory
只有使用 new 的时候才能使用 auto_ptr 和 shared_ptr
new[]需要用 unique_ptr
auto_ptr
C++98 的智能指针。已被废弃
当两个 auto_ptr 指向同一个内存的时候会出现 double free
shared_ptr
类似于 Rc,引用计数,当计数归零的时候就会释放内存
当需要多个指向同一个对象的指针时使用
unique_ptr
类似于 auto_ptr,但是会有所有权检查,比较安全
容器类只接受 unique_ptr
如果函数需要返回 new 出来的内存,推荐使用 unique_ptr
STL
Vector
v.end()返回的是最后一个位置的后一个位置,因为 vector 的遍历是不包含右端点的,因此需要在端点后一个位置
functor
使用 operator()来重载可以做到像函数一样调用对象。对于不同类型支持比较好