【前言】
- 本文转载自Andy Liu。
1. 转换函数conversion function
- 转换函数,对象的类型之间进行转换。
- 黄色部分即为转换函数,要以operator开头,函数名称为需要转成的类型,不可以有参数。前面不需要写返回类型,因为c++会自动返回函数名称这个类型。
- 转换函数通常后面有const,即不需要改变数据则要加const。
- 写好之后,在将Fraction对象转成double的时候,会调用我们写好的转换函数。
2. non-explicit-one-argument ctor
one-argument表示只要一个实参就够了。
non-explicit
- 这里没有写转换函数,二是重载了+操作符。
- 重载之后的+是分数+分数,编译器处理d2 = f+4的时候,发现右边不是分数,则看4能否转换成分数。
- 因为是只需要一个实参的构造函数,因此4可以转为分数,则可以调用重载之后的+。
- 因此non-explicit-one-argument ctor可以把其他类型转换为该类型。
- 如果这两个并存了,编译器就不知道该调用哪个了。(不知道把分数转为double还是把int转为分数)
- 构造函数加上explicit之后,表示这个构造函数只能在构造的时候使用,不会在转换类型时使用了。
- 这个explicit关键字主要就出现在这里。
3. pointer-like classes
关于智能指针
- 设计的class,像指针。智能指针,完成比指针更多的工作。一般都是包着一层普通指针。
- 指针允许的动作,这个类也需要允许操作。
*操作符和->操作符
都需要重载。- 这样调用sp->的时候,实际上内部重载操作符,将内部的普通指针px返回出来,然后px可以继续使用->来完成。相当于这个->符号用了两次。
迭代器
- 迭代器这种智能指针还需要处理++,--等符号。
- 这里面node用 * 号,则是取得data。
4. function-like classes
设计一个class,行为像一个函数,即仿函数。
- 即可以使用小括号来调用。
- 对小括号()操作符进行重载。
5. namespace
- 使用namespace将不同的函数包在里面,这样可以避免混淆。
6. 类模板
类模板
- 设计class的时候,如果数据的类型可以指定,那么就可以使用类模板。
函数模板
- 与上面的类模板一致,在设计函数的时候,如果传入的参数可以指定,那么就使用模板。
- 在函数定义前面,写template
- 或者,template
成员模板
- 黄色部分是成员模板,它即是模板的一部分(在pair类中),自己又是模板,则称为成员模板。
- 右上角设计了四种class。
- 右下角的最后三行,设计了一个pair的构造函数,可以使用<U1,U2>这种pair对象p作为初值来构造一个pair,将p的first和second作为构造的pair的first和second。
- 例子就是,可以使用<鲫鱼,麻雀>对象来构造一个<鱼类,鸟类>的pair。如左下角所示。
- new一个子类,这个指针类型是指向父类,是可以的,叫做up-cast。
- 智能指针也必须可以这样。
7. specialization特化
- 特化是泛化模板的反面。
- 在使用模板之后,可以针对不同的类型,来设计不同的东西。
- 使用template<>,后面指定类型,比如struct hash进行特定的设计。
偏特化
- 偏特化,即局部特化。
- 第一种是个数的偏,比如上面的模板有两个,特化其中一个为bool类型。
- 第二种是范围的偏
- 可以把参数的范围缩小,比如上面,如果只要传进来的是指针,就使用下面这种。而指针指向的是什么,不需要考虑。
8. 模板模板参数
模板中的一个模板参数也为模板。
- 只有模板的尖括号中<>,typename和class写哪个都行,互通。
- 要使用最后一行代码来使用,第一个参数为string,第二个模板参数本身为模板,引入Lst,来作为第二参数。
9. 关于C++标准库
- 容器、迭代器、算法。
10. C++11新特性
variadic templates 数量不定的模板参数
- 模板的参数可以变化,使用...即可,表示任意个数。
- 示例中,将模板参数分为一个和一包参数,后面的一包参数数量任意。
- 这个示例中,使用了递归,不断地将一包参数里的每一个print...,直到最后一个,调用了没有参数的print,结束。
- 使用sizeof...(args)可以直到现在这个参数包中有多少个参数。
auto关键字
- 编译器自动匹配返回类型。
range-base for
- 使用单冒号来进行for循环遍历。
11. reference引用
- x是整数,p是指向x的指针,r是x的引用。
- x是整数,占4字节;p是指针,32位机器上占4字节;r代表x,那么r也是整数,占4字节。
- 逻辑上r是这样,但底部的实现也是指针,即r也是指向x的指针。(即使底部是这样,但是引用占用的大小也需要与代表的物体一样,编译器创建出的假象)引用与指针不同,不可以改变,代表一个变量之后,就不能改变。
- 这里r和x都是8字节,且地址也相同,实际是假象。
- reference通常用在参数传递上。
12. vptr和vtbl
- 三个类,B继承了A,C继承了B。因此三个类所占的内存如左边所示。
- 子类对象中,有父类的成分。
- 当类中有虚函数的时候,对象就会多一个指针。(无论多少个虚函数,都是多一个指针,即vptr),因此占用的内存,会多一个指针的空间(4字节)。
- 继承的时候,会继承父类的函数的调用权。
- vptr只会关联到虚函数上,与一般函数无关。vptr指向一个表格vtbl,里面是虚函数的位置。
- 调用函数则是动态绑定,通过指针p找到vptr,找到vtbl,再找到调用的函数。
(*p->vptr[n])(p)
,则调用第n个虚函数。
13. 关于this pointer
通过对象来调用函数,这个对象的地址即是this pointer
- 父类中其他可以通用,读文件这个函数Serialize设置为虚函数,需要override。
- 我们定义一个读文档的类,那么serialize函数就要override成读文档的函数。
- 调用serialize时,通过隐藏的this pointer来调用,因为myDoc.OnFileOpen,因此this就是myDoc(这里就是上面说的动态绑定,this指向的serialize,是重载过的虚函数),因此调用的是我们override之后的serialize函数。
- 这就是设计模式,template method
动态绑定
- a.vfunc1()这是通过对象来调用(将B转成A类对象),是静态的调用。可以看到右边的汇编代码,调用call来执行(固定地址)。
- 这里是使用动态绑定调用。
- 首先**向上转型,new B的指针是A*,**下面用指针调用函数,是动态的。
- 右边的汇编中可以看到,调用函数的时候,call的是dword ptr[edx],即是vtbl中对应虚函数的位置。
14. 浅谈const
- const放在成员函数定义小括号的后面,表示修饰这个成员函数。表示我这个成员函数不准备改变class的data。
- **当两个版本同时存在的时候,**const object只能调用const版本,non-const object只能调用non-const版本。
- 常量对象是不可以调用非常量函数的,非常量对象可以调用常量函数。
15. 关于new、delete
- 这里面的new和delete是表达式,之后具体执行几步,完成操作(详细可见上门课程)。
- 这里面的new和delete表达式无法重载,但是分解之后的几步操作是可以重载的。
- 全局重载operator,new、delete、new[]、delete[]。
- 这几个函数我们不会调用,是编译器会调用,我们new、delete的时候,编译器会找这几个操作符函数有没有重载。
- 重载了之后,全局的new和delete都调用这个版本。
在class中成员重载new、delete
- 重载之后,new这个类的时候,使用重载之后的操作符函数。
下面是接口示例:
- 在class Foo定义中,重载了这四种操作符函数。
- 使用者进行调用。
- 如果要跳过我们设计的这四种,强制使用全局的,那么使用::new Foo;::delete pf即可。即前面加上::。
- 这里面Foo的数据大小为12字节(int4字节,long4字节,string大小为指针大小,4字节)
- 如果加上虚函数之后,会产生vptr,因此有虚函数的大小为16字节。
- 主要看第三步和第四步,new一个Foo[5],发现大小是64,不是60(12 * 5)。**多出来的一个4字节,记录了这个数组的长度。**从上到下进行构造,从下到上进行析构。
- 同理,下面的大小是84,也不是80。
- 加上::之后会绕过我们重载过的函数。调用全局的。
- 可以重载类的成员操作符函数,new()、delete()。
- 第一个参数必须是size_t。
- 重载delete(),不会被delete调用,只当new调用的构造函数抛出异常的时候,才调用来归还占用的内存。
- 故意写错第一个参数,编译器会报错。
举例:改写new()(placement new)
- 创建create的时候,会使用extra来存放string内容。