目标:
书籍推荐:
示例:Fraction 转换为 double
- double 函数不可以有参数;没有返回类型;
- 函数名称一定是operator type();
- 只要合理,一个类可以设计多个转换函数。
编译器在编译上面的代码时,在处理这行代码:
double d = 4 + f;
的时候,会先查看是否有写函数Fraction operator+(double, Fraction&),目前的代码中没有这个函数,于是编译器就另寻他路,看是否能将 f 转换为 double,就会到代码中查看是否设计了转换函数,然后就发现了operator double() 这个转换函数。于是编译器就将f 转换为 3 / 5 = 0.6 3/5 = 0.6 3/5=0.6,所以最终的结果 d = 4.6 d = 4.6 d=4.6。
正确的代码应该如下:
#includeusing namespace std; class Fraction { public: Fraction(int num, int den = 1) : m_numerator(num), m_denominator(den) {} operator double() const { cout << "double function is called" << endl; return ((double)m_numerator / m_denominator); } private: int m_numerator; int m_denominator; }; int main(){ Fraction f(3, 5); double d = 4 + f; cout << "f = " << f << endl; //0.6 cout << "d = " << d << endl; //4.6 return 0; }
标准库中的应用:
reference operator[] 函数要返回一个 bool 值,则 reference 类型中一定有个转换函数将结果转换为bool 值类型,果然,在__bit_reference 中有个转换函数 operator bool() const。
2.2 non-explicit-one-argument ctor此处的one-argument是指至少有一个实参,若有多个实参也可以。
示例:将double转换为Fraction
2.3 conversion function vs. non-explicit-one-argument ctor绿色的部分可以将 4 转换为 Fraction,黄色部分可以将 f 转换为 double。这两条路径都可行,产生了歧义,所以编译器不知道应该选择哪个路径。
2.4 explicit-one-argument ctorexplicit 关键字的意思是告诉编译器,不要将 4 自动转换为 4/1。
此处的+ 要将4 转换为 Fraction 类型,但是因为构造函数加了 explicit关键字,所以 4 无法转换为 Fraction,因此出现错误。
百分之九十的情况,explicit 关键字出现在构造函数前。
三、智能指针 3.1 pointer-like classes,关于智能指针将一个 class 设计出来像一个指针或者函数。
- sp->method()这行代码,-> 作用于 sp,即调用了 T * operator->() 函数,得到了 px,而-> 得到的结果如果要继续执行下去就要继续调用 ->,这是 C++ 中 -> 的特性;
- 智能指针类中一定有指针类型的变量,且一定要写操作符 * 和 -> 的函数,写法一般都是如上所示。
标准库中的链表:
- 迭代器也是智能指针,但是相比较于 shared_ptr 中的操作符重载的写法,有所变化;
- 迭代器不仅要实现操作符*和 ->,还要实现其他的操作符。
设计一个class,让它像一个函数。
4.2 标准库中的仿函数的奇特模样 4.3 标准库中,仿函数所使用的奇特的 base classes 五、namespace经验谈 六、模板 6.1 class template,类模板- 类模板使用的时候需要指定类型
- 此处的template
中的 class,可以修改为 typename; - 使用函数模板的时候不需要指定类型;
- 模板会编译一次,实际使用的时候会再编译一次;
问:把一个由鲫鱼和麻雀构成的 pairt,放进(拷贝到)一个由鱼类和鸟类构成的 pair 中,可以吗?
答:可以。
问:反之,可以吗?
答:不可以。
6.4 specialization,模板特化 6.5 partial specialization,模板偏特化—— 个数的偏 6.6 partial specialization,模板偏特化—— 范围的偏- 所谓的”范围的偏“,指的是之前的泛化版本是任意类型,而偏特化版本是指针类型;
- C
obj1; 使用的是泛化版本; - C
obj2; 使用的是偏特化版本,因为是指针类型。
- 容器中的类型未定,容器本身就是个模板;
- 容器还有其他模板参数,平时没有写是因为有默认值;
- 此处的两个×不是模板模板参数有错,而是智能指针unique_ptr和 weak_ptr 的特性导致此处的错误。
这不是 template template parameter
- 使用第二模板参数的时候,必须写成list
,因为已经绑定了int,不再是模糊的,所以它不是模板模板参数。
#include9.3 三大主题 9.3.1 variadic templates(since C++11)using namespace std; int main() { cout << __cplusplus << endl; //201103 return 0; }
数量不定的模板参数
sizeof...(args) 可以获得pack里的参数个数。
完整代码:
#include9.3.2 auto(since C++11)using namespace std; void print() { } template void print(const T& firstArg, const Types&... args) { //cout << "pack numbers: " << sizeof...(args) << endl; cout << firstArg << endl; print(args...); } int main() { print(7.5, "hello", bitset<16>(377), 42); return 0; }
auto ite;没有赋值的时候,编译器是无法推导出 ite 的类型的,所以使用错误。
9.3.3 ranged-base for (since C++11)如果要改变原来的值就使用引用。
9.4 reference- reference一定要设初值,说明它代表什么东西;自此之后,reference就不能代表其它了;
- reference底层有个指针指向它所代表的东西;
示例:
9.5 reference的常见用途- 引用的底层是用指针实现的,所以当传的数据很大的时候,通过引用传递速度更快;
- 签名不包含 return type。
回顾:
- Composition(复合)关系下的构造和析构
- Inheritance(继承)关系下的构造和析构
- Inheritance+Composition关系下的析构和构造
虚指针和虚表。
- 子类中有父类的成分;子类不仅会继承父类的数据,还会继承函数的调用权利;
- 如果类中有虚函数,类就会多一个指针的大小,即虚指针;
- 不管类中有多少个虚函数,都只有一个虚指针vptr,指向虚函数表vtbl;vtbl 中放置的是函数指针;
- 如果父类有虚函数,子类也一定有虚函数;
- B 继承了 A,所以B 继承了A 的虚函数,B 中也有两个虚函数vfunc1和 vfunc2,只是B 又重写了方法vfunc1;
- “从 p 到 ptr 到 vtbl 到 某个方法的流程” 编译器解析出来就是 (*(p->vptr)[n])(p); 或者 (* p->vptr[n])(p);
- 为了放置不同大小的形状,所以要放入指针,指向父类;
- 希望做到:遍历容器,调用每个指针所指向的draw;所以draw必须是虚函数,才能实现这种功能;
- 虚函数的这种用法叫做多态(polymorphism);
总结:C++编译器处理函数调用的时候,要考虑是静态绑定还是动态绑定。
- 静态绑定被编译成call xxx,其中的xxx 是函数地址;
- 如果符合以下三个条件就会进行动态绑定:① 必须通过指针调用;②指针是向上转型的up-cast;③调用的是虚函数。编译器会将其编译成 (*(p->vptr)[n])(p); 或者 (* p->vptr[n])(p);,具体调用的是哪个函数要看p 指向的是什么。
多态、虚函数和动态绑定其实都是一回事。
10.2 对象模型(Object Model):关于 this-
通过某个对象调用函数,则该对象的地址就是this;
-
上述的是模板方法(Template Method);
-
this->Serialize(); 函数满足动态绑定的三个条件,所以编译器会进行动态绑定;
- 此处的a.vfunc1(),通过对象调用函数,所以是静态绑定;
- 此处的虚函数调用,满足动态绑定过的三个条件,是动态绑定。
- const修饰成员函数是为了告诉编译器这个成员函数不打算修改类中的成员数据的意图,请编译器帮忙把关是否违反了这个意图。
- const 属于签名;
- 如果涉及到引用,可能就会有共享,为了防止被修改,就要拷贝一份进行修改,即涉及到共享,就要考虑Copy On Write;
- 常量字符串不必考虑Copy On Write;
- 类中的new 和 delete 都可以进行重载,可用于内存池的设计;
- 重载的函数会接管new/delete 分解后的步骤中的相同函数名的函数
说明:
Foo* pf = new Foo; delete pf;
如果没有重载运算符new和delete的成员函数,就调用全局的operator new;
Foo* pf = ::new Foo; ::delete pf;
无论是否有重载运算符new和delete的成员函数,都调用全局的operator new。
- Foo with virtual dtor 的 size 比 Foo without virtual dtor 的 size 大 4 的原因是,类中如果有虚函数,就会多一个虚指针,在32bit机器上占 4 个字节;
- Foo* pArray = new Foo[5] 的大小为 64, 其中多出来的 4 是计数counter,表示有5个元素,将 “array 的整包大小 + 一个计数的counter” 进行分配;
- 无论是否调用的是重载的new和delete 函数,在数组的前面加一个 counter 的逻辑是不会变的
标准库中 placement new 的例子:



