concersion function(转换函数)
举个例子:分数转换成小数
operator开头, 后接上想转换的类型,无参数,返回类型不用写,因为已经有目标转换类型了,防止出错。
转换不会改变对象属性值,故建议加上const。
对于
Fraction f(3,5);
double d = 4 + f;
编译器首先会找一个全局函数,operator+,第一个参数是int/double(int可以转换成double),第二个参数是fraction,但是没有这样的函数,
所以会试着把f转换成double,去找转换函数。
non-explicit-one-argument constructor
对于
Fraction f(3,5);
Fraction d2=f+4;
对于f在其类内找到了对应的operator+,但是右边的参数得是Fraction,这里是4,编译器会试着将4转换成Fraction,由于有绿色那一块的构造函数,故4可以转换成Fraction(4,1),然后就可以进行计算,这就是隐式转换。
同时有绿色和黄色代码时,对于下面这段代码编译器会报错
Fraction f(3,5);
Fraction d2=f+4;
因为会有二义性,既可以把f转换成double进行计算,也可以把4转换成Fraction进行计算
explicit-one-argument constructor
基本只有构造函数会用到,模板很细微的地方也会用到
对于
Fraction f(3,5);
Fraction d2=f+4;
不能将4隐式转换成Fraction,加法失败,或者将f转换成double,最后由double来赋值(其实也是将double隐式转换成Fraction,但也不被允许),故最终报错
pointer-like classes
智能指针
指针能做的,智能指针都要能做
对于operator*很简单,返回类内指针指向的对象即可,*px
对于operator->,直接返回px,对于sp->method();会将sp->转换成px,那么不就变成pxmethod();了吗?不会,箭头符号会持续作用下去,不会被消耗掉,最终px->method();就显得合理了
迭代器
迭代器可以看成另外一种智能指针,迭代器需要代表容器中的一个元素,可以当做智能指针,但是它不但要处理*和->,还要处理== != ++ --等符号
下面这个例子是用双向链表实现迭代器
function-like classes(仿函数)
如何让一个类像一个函数呢
如果一个东西能够接受()操作符,我们就把这个东西叫做函数或者说像函数的东西
标准库中的仿函数都会继承一种奇特的base class
namespace经验谈
用不同名字的namespace把函数、变量等包起来,防止重名起冲突
Template 模板
class template(类模板)
用T来泛指类型,使用时再具体指明是什么类型
function template(函数模板)
函数模板使用时不必指明具体类型,编译器会进行实参推导,得出传入参数是什么类型
Member Template(成员模板)
在构造pair时,允许使用U1、U2来构造,但是需要T1能够通过U1来构造,T2能够通过U2来构造,否则会报错,故图中例子可行,如果反过来用D2、D1来构造B1、B2就不行,D2不能用来构造T1,D2只继承了B2而没有继承B1故会报错
specialization模板特化
就是在泛化基础上对某些独特的类型,要做独特的设计
下面的例子表示hash可以接受很多类,但是对于char、int、long有自己独特的设计
对于cout<<hash<long>()(1000);
它表示创建了一个临时变量hash<long>(),它调用了重载的()函数,会使用特化中的代码而不是泛化中的
partial specialization 模板偏特化 -- 个数的偏
两个typename,只特化(绑定)其中一个,第二个参数还是泛化
不能跳跃绑定,只能从左到右一个一个特化
partial specialization 模板偏特化 -- 范围的偏
特化指针类型,当类型是指针时使用特化的代码
template template parameter 模板模板参数
假如需要模板传入一个模板就需要用到模板模板参数
当我们声明一个模板模板参数时,我们需要使用 template <class T>,而不是 template <typename T>。这是因为在模板模板参数中,我们需要明确指定参数是一个类模板,而不是一个单独的类型。
尖括号内typename和class相同
Container使用了typename T来当做自己模板的实际值,像例子中就表示list是一个模板,其中装的是前一个参数string,但是这样有问题,容器有第二模板参数,甚至有三、四,平时不写是因为有默认值,但是这里这样用语法过不了。
为了解决这个问题可以用Lst那一行(c++2.0语法)
有的智能指针只接受一个模板参数,所以可以这么写,打×不是参数原因,是指针特性原因
在使用中传入的参数已经不是模板了,所以这个不是模板模板参数
variadic templates(since C++11)数量不定的模板参数
...表示多个参数,递归调用自己,不断打印出来第一个参数
auto(since C++11)
让编译器自己推断是什么类型
ranged-base for(since C++11)
auto elem 是值传递,会把值拷贝到elem中,如果想要更改vec中的内容,传入引用。
Reference
引用必须初始化
引用就是别名,实际上用指针来实现,真实大小为指针的大小,但是sizeof(r)==sizeof(x),&r==&x,这是编译器制造的假象
引用不能重新再代表别的对象
const是函数签名的一部分
复合继承关系下的构造和析构
复合:一个类中含有别的类。
构造由内而外构造,析构由外而内析构(父子类继承也一样)
vptr和vtbl(虚指针和虚表)
继承函数继承的是函数的调用权
每个对象都有一个虚指针(存储在类对象开头的4个字节),指向自己所属类的虚表(虚表是同一个类的对象共用的)
虚表中存放了虚函数的地址
子类重写父类虚函数会改变子类虚表中该函数的地址
通过指针对象中找到虚指针,再找到虚表,再找到对应的虚函数(作用之一是当父类指针指向子类,要删除基类指针时,调用的仍然是子类的析构函数,就不会导致内存泄漏),这是动态绑定
而静态绑定是直接使用call函数地址。
用父类指针指向子类,调用draw函数,执行的还是子类的draw函数
符合动态绑定的条件
1.通过指针调用
2.指针向上转型(this指向子类)
3.调用虚函数
this指针
对象的地址就是this
通过对象来调用函数,实际上是将自己的地址传入函数,就是this指针(所有的成员函数都会有隐藏的this指针作为参数)
这个例子中myDoc是子类对象,想要调用OnFileOpen函数,由于子类中没有这样的函数,故会调用父类的函数(感觉是静态绑定,直接call这个函数的地址),并且将自己的地址传入该函数(也就是this)后续其他函数调用都是以this->的形式,而执行到Serialize()时(由于它是虚函数且子类中有重写),就会走虚指针、虚表那一套流程调用子类中重写的Serialize()
关于Dynamic Binding 动态绑定
a.vfun();是对象直接调用,不符合动态绑定条件,调用A类的函数
从call往前的几行汇编 用C表现是(*(p->vptr)[n])(p);
即通过p指针找到vptr,再找到虚表的第n个,再用函数指针的形式调用,传进去的p即为this pointer。
补充
const修饰函数一般是修饰成员函数。
成员函数的const 和 non-const 版本同时存在时
const 对象只能调用const版本
non-const 对象只能调用non-const版本
不同时存在时,non-const 对象无论哪种都能调用,但const 对象只能调用 const版本
new和delete重载
全局重载
类内重载
示例:
如果使用::new就会使用全局的new,不执行自己的重载
delete[]和new[]
本来5个对象应该是60,但是数组中多了一个记录元素个数的数据,所以是64
placement new/delete
对于Foo* pf = new(300,'c') Foo;总共有3个参数,有一个size_t自动传入
只有构造函数发出异常时才会执行对应的delete
编译器不同结果也不同,可能会调用也可能不会
basic_string 使用placement new(extra) 扩充申请量