Conversion function 转换函数
侯捷老师使用分数 Fraction举例,分数理应可以被看作是小数
提供了Fraction类对象一个转换为double的方法,当碰到需要转换为double的情况下,会调用该方法。
黄色的就是转换函数,没有return type,返回类型就是名称double这一类型。
分析 double d = 4 + f;
编译器会找是否有全局函数 operator + ,第一个参数是整数或浮点数(整数是浮点数),第二个参数是Fraction,若有这么个函数,就会走得通。
但示例代码中没有这么一个全局函数,故会去找有没有将fraction转换为double的转换函数。
non-explicit / explicit -one-argument-ctor
Fraction f(3,5);
Fraction d2 = f + 4;
写的operator+ 代码作用左边f,传的参数是右边的4,但4是int型,而参数要求类型是Fraction,因为有绿色代码,会调用 non-explicit ctor 将4 转换为 Fraction (4,1), 然后调用operator+。可以看出这个non-explicit ctor可以把别的类型转换为该类型,与上一节转换函数把该类型转换为其它类型不同。
当黄绿色代码并存时,
绿色可以把4转换为fraction,operator+也有,看起来可以执行d2 = f + 4
黄色会把f转换为double 0.6,相加得到4.6,4.6再转换为fraction,这也可以走得通。
但当多余一条路线可走时,编译器就不知道怎么走了,就会出现歧义ambiguous,发生报错。
(conversion function中的图不会报错,是因为做的事情并没有出现二义现象)
转换函数,ctor,operator重载,都会影响
explict(基本只有构造函数会用到,模板很细微的地方也会用到)
加上explicit后,就不会自动的将4转换为Fraction
Fraction f(3,5);
Fraction d2 = f + 4; //[error] conversion from 'double' to 'Fraction' requested
则 d2 = f + 4会报错,因为operator+要求右边是Fraction参数
pointer-like classes
关于智能指针
指针允许的动作,该类都要允许。
sp->method(),智能指针要满足这一动作,所以重载了操作符,返回了px,但sp-> 会被转换为px,这还差一个->符号呢。
->符号有个特殊行为,作用下去得到的结果会继续用->符号作用下去,也因此可行。
关于迭代器
迭代器也可以看作是一种智能指针,但他除了要处理*,->等符号,还要去处理++,--等运算符号
function-like classes
所谓仿函数
对()重载,就能做到仿函数,所做出来的对象可以接受()。
实际上仿函数都继承了某一类
仿函数继承的类(详细作用在标准库课程中讲)
namespace经验谈
取一个namespace命名空间,用来将自己定义的变量等包起来,以免与其它人写的变量名起到冲突。
Template 模板
class template 类模板
先用T来泛指类型,以后再来指定类型。
member template 成员模板
在模板里头,自己又是模板。可以认为外头模板的T1,T2可以变化,里头的U1,U2又可以变化
图例可以拷贝,反之不可以,如图的pair的构造函数可以看出,允许使用U1,U2类型的pair来进行构造,但是要满足p的first(U1),second(U2)要可以当作T1,T2.
specialization 模板特化
作为一个设计者,可能面对某些独特的类型,要做独特的设计。
如图上方的框内是泛化,指定任意类型都会使用上框中的代码。
但如果指定如下框中的char,int,long的类型,则会使用下框代码
写出代码
cout<< hash<long>()(1000);
hash<long>()表示是个临时的变量,因为是long类型,会用特化的代码,接着就是调用重载的()
partial specialization 模板偏特化 -- 个数的偏
2个typename,当T是bool时,进行特化,只有一个T进行特化,个数的偏
partial specialization 模板偏特化 -- 范围的偏
特化指针类型,范围的偏
template template parameter 模板模板参数
尖括号内 typename和class共通。也可以写template<class T>。
list确实是模板模板参数,Container会把T,也就是string放进去,这样list<string>好像也没问题,
但是实际上容器有第二模板参数,甚至有三,平时不写是因为有默认值。在这里虽然有默认值,但是仍然不行,想要解决可以用Lst这样来实现(C++2.0中的语法)。
SmartPtr有的只接受一个模板参数,因此有的可以,打×不是参数原因,是指针特性原因。
Sequence也是个模板(deque<T>),为什么不是模板模板参数呢。
看用法中,第二个用法stack<int,list<int>> s2,第二个参数不再是模板了。
variadic templates(since C++11)数量不定的模板参数
...不是省略,是语法一部分
auto(since C++11)
auto要让编译器可以推理
ranged-base for(since C++11)
auto elem 是值传递,会把值copy到elem中,如果想要更改vec中的内容,用引用。
Reference
编译器对待r,当作指针实现,真实大小为指针大小,但逻辑上r就是x,所以sizeof(r) == sizeof(x)。(编译器制造的假象)
r不能重新代表其它物体,它就是x
const是函数签名的一部分
Object Model
前情提要C++ 组合 委托 继承 组合使用-CSDN博客
vptr和vtbl(虚指针和虚表)
图中可以看出,每个对象中存在一个虚指针,指向自己类的虚表(类对象共用一个虚表)
虚表中存放各虚函数的地址
子类同名虚函数会覆盖父类虚函数,覆盖虚表位置与父类相同
虚函数表的指针存在于对象实例中最前面的位置
通过指针调用虚函数,编译器不能使用静态绑定(call(函数地址)),而是动态绑定,动过指针p找到vptr,再找到虚表中的对应函数地址。
如图,父类指针指向子类对象,调用同名虚函数draw(),会调用子类的虚函数draw()
符合动态绑定的条件
1.通过指针调用
2.指针向上转型(上行转换,保证安全)
3.调用虚函数
关于this
通过对象调用函数,对象的地址就是this
子类对象调用父类函数,父类中OnFileOpen函数执行到Serialize()时,会调用子类的Serialize函数
这是因为动态绑定,myDoc.OnFileOpen(),是子类myDoc调用,因此this是&myDoc,编译器会把所有调用函数的动作视为类似this->Serialize()的动作。this是指针,且调用OnFileOpen父类的函数符合向上转型,调用Serialize是虚函数,因此满足动态绑定的三个条件
关于Dynamic Binding 动态绑定
如图a.vfunc1(),是通过对象来调用函数,而不是指针,因此是静态绑定。
从call往前的几行汇编 用C表现是(*(p->vptr)[n])(p);
即通过p指针找到vptr,再找到虚表中函数对应位置进行调用,传进去的p即为this pointer。
补充
const
const修饰函数一般是修饰成员函数。
成员函数的const 和 non-const 版本同时存在时
const object 只能调用const版本
non-const object 只能调用non-const版本
不同时存在时,non-const object可以调用const版本,但const object 只能调用 const版本
const是签名的一部分
如 string s = "hello world"
s[0] = 'p'; //更改了(s为非常量对象)
此时会调用返回是 reference的函数
重载 new delete
全局重载
类内重载成员函数
重载示例
::new Foo这样的用法会使用全局的new,绕过自己的重载
new[] 和 delete[]
上图中一个Foo占用12个字节
有虚函数多4字节(虚指针)
下图中new Foo[5],理论上应该是60字节,但却是64,
多4字节中存的值是5,表示有5个对象(存的是count),图中construct和destruct箭头表示构造和析构顺序
placement new/delete
Foo* pf = new(300,'c') Foo;
这里总共有3个参数,有一个size_t自动传入
placement delete不会被delete调用,只有当对应new的构造函数出现异常时调用
抛出异常却并没有调用对应的delete重载,为什么呢
侯捷测试老的编译器会调用,但新的不调用 。跟编译器有关。
basic_string 使用placement new(extra) 扩充申请量