1. 导读
我们的目标
- 在先前基础课程所培养的正规、大气的编程素养上,继续探讨更多技术。
- 泛型编程(Generic Programming)和面向对象编程(Object-Oriented Programming)虽然分属不同思维,但它们正是 C++的技术主线,所以本课程也讨论template(模板)。
- 深入探索面向对象的继承关系(inheritance)所形成的对象模型(Object Model),包括隐藏于底层的 this 指针,vptr(虚指针),vtbl(虚表),virtual mechanism(虚机制),以及虚函数(virtual functions)造成的 polymorphism(多态)效果。
2. 类型转换
转换有两种,一种是转出去,一种是转过来。
2.1 Conversion Function 转换函数
转换函数的作用是转出去,把这个类的值转换成其他的类型。
operator double() const {……;}
定义了一个转换为 double 的函数,
没有参数,转换时不会带有参数。
不写返回类型,返回类型就是名称里这个double
- 如果不改变值,就该加 const,否则后面可能会出错。
- 只要认为合理,可以设计好几个转换函数。
模板的偏特化?
操作符重载
2.2 只有一个参数的构造函数
只有一个参数的构造函数可以将一个 int 或 float 值转换为该类对象。
2.2.1 non-explicit-on-argument ctor
Fraction (int num, int den=1): m_numerator(num), m_denominator(den) { }
分母默认是1,这样一个值构造为对象时,实际值不变。
Fraction operator+(const Fraction& f) { }
只能分数加分数
Fraction d2=f+4;
4可以转换为 Fraction,因为有一个构造函数只有一个int 参数。
2.2.2 conversion function vs. non-explicit-one argument ctor
当转换函数与非显式声明的一个参数的构造函数同时存在时,就会出现歧义。因为两种方式都可以编译。
多于一条路径可以编译,就会出现歧义,编译器就会报错。
[Error] ambiguous
2.2.3 explicit-one-argument ctor
显式声明就可以避免这个问题。
explicit Fraction(int num, int den=1):……
此时就会调用构造函数,而不会调用 double 转换函数。
3. 模仿的类
3.1 pointer-like classes
设计一个类,模拟 pointer
3.1.1 关于智能指针
->
用掉后,还有一个->
,所以
sp->method();
px->method(); //转换完依旧有->
3.1.2 关于迭代器
注意操作符重载
++
--
适用于指针移动return (*node).data; //取的是node 指向的块的数据
return &(operator()); //返回迭代器中内容的指针,而不是指向迭代器块的 node 的指针。
3.2 function-like classes,所谓仿函数
unary_function 一个操作数
binary_function 两个操作数
4. namespace 经验谈
namespace jj01
{
}//namespace
5. 模板 template
5.1 class template
template<typename T>
5.2. Function Template
template <class T>
5.3 Member Template
template <class T1, class T2>
struct pair {
……
template <class U1, class U2>
……
}
Base1* ptr = new Derived1; //up-cast
shared_ptr<Base1>sptr(new Derived1); // 模拟 up-cast
5.4 specialization,模板特化
template<class Key>
struct hash { } ;
template<>
struct hash<char> { };
泛化对应特化,共性中的个性,用来应对特例。
5.4.1 partial specialization,模板偏特化
又称为局部特化
5.4.1.1 个数的偏
< >
尖括号内叫做模板参数
class vector<bool, Alloc> { }; // Alloc没改变,而 bool 设定了。
一定要从左到右,不能跳,不能135固定,24改变。
5.4.1.2 范围的偏
指针指向的偏
class c<T*> { }; // 限定为指针
如果 T 是指针,使用偏特化模板。
5.5 template template parameter,模板模板参数
5.5.1 容器需要参数
using Lst = list<T, allocator<>>;
模板需要好几个参数,必须替换
5.5.2 智能指针
对应 SmartPtr,可以是……,不可以是……
5.5.3 这不是模板模板参数
已经绑定了,必须是这个,所以就没有模糊地带了。
6. 关于 C++ 标准库
所有的容易算法都要用一遍。
编译器需要设定到 C++ 11
6.1 variadic templates (since C++11)
6.2 auto (since C++11)
用 auto 时一定要让编译器能推出来。
auto ite = find(……)
find 的类型就是 auto 给 ite 的类型。
auto 不能乱用,太长了、写不出来可以用。
我们一定要知道每个变量的定义是什么。
ranged-bas for (since C++11)
for ( decl : coll ) //左边是一个变量,右边必须是一个collector,容器
for ( int i : { 2, 3, 5, 7 } ){ }
vector<double> vec;
……
for ( auto elem : vec ) { cout << elem << endl ;} //传值
for ( auto& elem : vec ) { elem *= 3; } //传引用,尽量传引用
15. reference(引用)
声明时一定要有初值,设完之后就不能再变了。
object 和其 reference 的大小相同,地址也相同(全都是假象)
???那么新建一个 reference 会让程序新占用多少内存呢?
常见用途
函数传参
void func3(Cls& obj) { obj.xxx(); }
func3(obj);
reference 通常不用于声明变量,而用于参数类型和返回类型的描述。
相同声明结构,仅仅传递引用和变量,会导致模糊,签名相同,所以不能同时存在。
是否加const,
???有什么区别?
加 const 会改变签名,是可以定义的。
7. 复合&继承关系下的构造和析构
7.1 Composition(复合)关系下的构造和析构
Container 在外 Component 在内
7.2 Inheritance(继承)关系下的构造和析构
Derive在外,Base 在内
由内而外构建
由外而内析构
子类析构函数会自动调用父类析构函数
Inheritance + Composition 关系下的构造和析构
不同编译器可能不同
观察到的结论是先调用 Base,后调用 Component