1.转换函数
整个标准库都是用模板做出来的,里面有类class,但是继承关系很少,虚函数也几乎没有,所以是模板,泛型编程思维,不是面向对象思维,
operator后面加空格,要转换为什么类型,就作为函数名称的一部分,这个函数的意思是fraction可以被转换为double,编译器在碰到任何需要把fraction转换为double的时候就来调用黄色的函数,不可以有参数,转化没有参数,转换函数没有return type 返回类型,返回类型就是函数名称里面的类型,转换不可能改变class里的data,所以转换函数通常会加上const在()后{}前,
double d=4+f编译器去找有没有写一个全局的函数operator+,如果写了且第一个参数是整数或者浮点数,第二个参数是fraction,如果能找到这个参数,4+f就可以通过编译,此次找不到,就会看看能不能把f转化为double,double加double还是double,找到了所以转换f为double
one argument指只要一个实参就够了,non-explicit-one-argument ctor的作用是可以把别的东西转化为这种东西,之前是把这种东西转变为别种东西,方向相反,
编译器通过绿色把4转化为fraction,然后fraction和fraction相加也有函数,加完后是fraction,而黄色的是把fraction转换为double0.6,加4后4.6,4.6再转换为fraction,所以产生二义性,报错,
explicit是明确的,加上explicit告诉编译器不要自动做事情,不可以把3变成3/1,因为没有明确的说要这么做, 4不会被变成4/1,加法失败,因为要求左右两边都是fraction不满足,右边转换不过去,
explicit只用在构造函数前面,
用a代表b,则a应该有个转换函数转换为b,
2 智能指针
class做出来后所产生的对象像一个指针,或是像一个函数,所以pointer like classes,因为想要比指针能多做一些事情,所以比指针更聪明是智能指针,是一个class,c++在2.0之前有一个智能指针auto pointer,2.0之后有好几个智能指针,
大的圈是智能指针,里面一定会有一个真正的c++的指针,智能指针里有一个变量 pointer to T ,指向T,指针所允许的动作,这个class做出来的对象也都要允许,*和->作用于一般的指针上,一个class 叫Foo,new Foo后得到的天然的指针作为初值包装到智能指针里,智能指针都会有一个构造函数,接收天然的指针,此次调用起智能指针的构造函数,把天然指针作为初值放入智能指针里,p赋值到px上,sp就是大圈,使用者面对sp就要把它当成像一般的指针一样,sp为解参考dereference,右边是使用者想要的东西,左边是智能指针对它的呼应,所以智能指针的 星号都是左边的写法,星号作用在sp身上,星号就消耗掉,得到的值是px,
c++语法规定,箭头符号有个特殊的行为,作用下去得到的结果,箭头符号会继续作用下去,因为sp->,箭头作用于sp后会返回px指针,此时箭头符号已经被消耗掉,但是下面还有px->,又多出了箭头,正常不应该有箭头了,得到的箭头符号要继续作用上去,
智能指针里一定带着一般的指针,而智能指针一定要写箭头和星号,写法一定就是如图的写法
迭代器作为另一种智能指针,指向容器内的一个元素,因此也像一个指针,迭代器之外的智能指针可能不用处理++或–操作符重载,
链表的迭代器是图里圆形,里面必然有一个真正的指针node,指向一个节点,一般的智能指针与迭代器这种智能指针实现星号和箭头不同,
3 仿函数
让一个class所创建出来的对象像函数,函数的小括号操作符叫做function call operator函数调用操作符,所以任何一个东西能接收小括号这个操作符,就把这个东西叫做一个函数或者像函数的东西function like,三个类都对小括号重载,创建出来的对象都能接收小括号,template 表示接收的是一个任意的类型,暗示要给一个pair,
select1st().(),第一个小括号是创建一个临时对象,后面小括号是调用重载的小括号函数,把收到的pair取出第一个,
class里面重载小括号,就是为了变成一个function,这种class所创建出来的对象叫做函数对象,或者叫仿函数,
unary_function是一个操作数,binary_function是两个操作数,都继承了这两个类,这两个类大小都是0,理论上是0,实际受限是1,
标准库里有很多的仿函数,这些仿函数都是一些class,里面重载了小括号,这样就成为仿函数,这些仿函数都继承了一些父类,这些父类没有大小,没有函数,只有一些typedef,
4 namespace
namespace就是把一些东西区隔开,
5函数模板
关键字class可以改成typename,写出函数模板使用的时候比类模板更简单,不用指明函数模板里的T的type,因为函数模板在使用的时候一定是去调用,调用的时候里面放参数,于是编译器进行实参推导,就能通过调用时的参数推出T是什么,编译器在编译模板函数的时候,不知道真正的a和b的类型T是什么,所以模板本身可以编译通过,但是使用的时候会再编译一次,后续可能会编译出错,
6 成员模板
黄色的部分就是成员模板,黄色部分是模板里面的一个member,而它自己本身又是一个template,成员模板会出现在标准库,很多类模板的构造函数会设计为这样(作为初值),为了让构造函数更有弹性,
左下角问题:可以,反正不可以,鲫鱼是一种鱼类,所以first(p.first)可以转型通过,
指针可以使得父类的指针指向子类,那么智能指针里的指针也可以指向子类,一个指针的类型是指向父类,将来去指向一个子类也可以,
7 模板特化
泛化就是模板,特化的反面,泛化就是有一个类型在用的时候再指定,特化就是设计者很可能会面对某些独特的类型要做特殊的设计,特化有任意版本,
class key被绑定了,所以没有了,写为template<> ,上面框框是泛化,下面三个是特化,hash()是临时对象,
泛化又叫全泛化full specialization,对应偏特化,即局部特化
最上面一行里面的是模板参数,
c++里最小的元素单元的类型是char,即character,8bit位即1字节,让一个字节代表bool值,为了减少浪费bit位,就直接绑定bool,绑定其中一个模板参数,
范围缩小就是把类型的范围缩小,比如任意范围变成指针,至于指向什么都可以,如果使用者用的是指针,就用特化的那一套代码,特化与泛化的T不是同一个,
黄色部分本身是一个模板参数,而它本身又是一个模板,只有在模板template尖括号的位置上typename和class共通, 其他地方都不可以,因为历史原因,早先并没有typename关键字,模板是后来发展出来的,c++就先暂时先用class,后来用typename,希望使用者使用XCLs<string ,list>mylst1,表示传入一个容器list,并且传入容器的元素类型位string,所以使用者就很有弹性,传入的list是一个模板,此处的模板参数写法是正确的,container是list,把T当成元素类型即string, 容器有第二模板参数,有的还有第三模板参数,有默认值,此处上面语法过不了,再加上下面的两行,(c++2.0增加的语法)
smartPtr是智能指针,使用传进来的一个智能指针,并且以第一模板参数T为智能指针里的参数,
8 三个主题:数量不定的模板参数,auto,ranged-base for
这种语法把调用者所放进去的模板参数分为1个和一包,递归调用,或者怎么处理都可以,设计函数或者类都可以用此语法,sizeof…(args)可以得出一包有几个,
c++2.0的语法糖有auto,
c++2.0的语法糖有 for循环,冒号左边是一个变量,右边是一个容器collection,容器就是一个数据结构,编译器就会找出容器里面的每一个元素,把每一个元素赋值设定到左边的变量,然后进行statement,
c++2.0的语法糖有大括号自然就形成一个容器,{}
传引用的时候改变就会改变原来的东西,
9 reference
p是一个变量,类型是pointer to integer,&的位置不同造成意义不同,int& r=x表示变量r的类型是reference to integer,x,p,r都是变量,都占用了某个大小的内存,x是整数,所以是4个字节,p是指针,在32位电脑上指针也是4个字节,r代表x,所以x是整数,r也是整数,其实所有的编译器对待r都是拿指针把它实现出来的,但是从逻辑的角度要把r看成是一个整数,
reference一定要有初值,因为声明一个reference的时候要告诉它它要代表谁,所以一定要有初值,并且之后reference不可以改变,r再也不能代表其他人,指针可以改变,
reference用于参数传递,底部是一个指针,东西不管多大,传的都是指针, 所以比较快,reference引用很少用于声明对象或者声明变量,主要用于参数传递,
下面两行签名部分相同,签名部分不含前面的return type,这两个同名的函数,如果像上面调用,编译器不知道想调用谁,会产生二义性,小括号()后面大括号{}签名的const算函数签名的一部分,两个函数完全一样,一个有const,一个没有const,是可以并存的,
10 复合 继承 (类和类的关系)
从内存来看,子类对象里面有父类的成分part,猪是一种哺乳动物,猪里面有哺乳动物的特性在里面,写猪猪这种class时不用管上面几层,写它的构造函数时编译器会加代码,在函数执行{}之前调用父类的默认构造函数,这样完成由内而外的构造,从外而内的析构,先析构外部,执行完了调用内部父类的析构函数,编译器会加父类析构函数,
左边拥有右边的,所以由内而外构造,由外而内析构,右边是内部的,
次序属于实现手法的不同,
11 对象模型:虚指针,虚表
B继承了A,所以b也有a的data1和data2,再加上自己的data3,所以可以看出,子类的对象里有父类的成分part,只要类里有一个虚函数,这个对象里就会多一个指针virtual pointer虚指针,有一万个虚函数也是这一个指针,大小4个字节,b也有一个指针,因为b也有虚函数,用黑框框起来的部分就是父类的part,所以子类的对象里有父类的成分part这个概念在多了一个指针后仍然不变,没有可能a有虚函数,b没有虚函数,继承会把数据和函数都继承下来,继承函数指的是继承函数的调用权,不是继承它的内存的大小,所以A这个class有虚函数,B会全部的继承下来,继承的是调用权,所以b不可能没有虚函数,所以父类有虚函数,子类一定有,
A,B,C总共有8个函数,非虚函数有4个,虚函数有4个,虚指针会关联到虚函数,与一般函数无关系,
a有虚函数,所以有虚指针,这个指针指向虚表,table里面放的都是函数指针,指向虚函数所在的位置,A的虚表里的两个指针指向A的两个虚函数,b有两个虚函数,继承了a所以有两个,但是推翻了第一个,覆写,自己有一个定义,即b有自己的vfunc1,同时又继承了a的vfunc2,c同理,
有一个指针p指向c,new C 就可以得到这个指针p,通过这个指针要调用vfunc1,编译器看到一个调用的动作,c语言会变成一个特定的语法,call xxxx,xxxx是一个地址,要调用哪一个函数,编译器就会把它解析出来,跳到那个地方去,将来再return回来,是一种静态绑定,而通过指针去调用虚函数,编译器就知道不能做静态绑定,而做动态绑定,动态绑定的逻辑意义就是通过指针p,找到virtual pointer,再找到virtual table,然后再从里面看看走向哪一个函数,这一条路线走过来,解析成c的形式是下面两行,上面或下面都对,n就是虚函数在这个表格中第几个,从0开始,编译器在编代码的时候看当初放在第几个就是第几个
设计ppt的时候,为了让一个容器可以容纳各种形状,必须在这个容器指定的时候放的是指针,因为每个形状占用的内存大小不一,没有一种容器可以放大小不同的元素,所以用指针,而且这个指针必须指向父类,有一个指针声明的时候指向动物,未来运行的时候可以指向猴子,灵长类,指针都是4字节,遍历这个容器,调用每一个指针所指的形状的draw函数,指针指向什么形状,调用得到就是哪个形状的draw,此处的draw就是之前的vfunc1,这个draw必须是虚函数,才能有这样的效果,如果不是虚函数,则还需要判断指针指向什么类型,是c语言风格if else,
c++编译器看到一个函数调用,有两个考量,是要把它静态绑定还是动态绑定,call是汇编语言,符合三个条件就会动态绑定,1.必须是通过指针p来调用,2.这个指针是向上转型up-cast,声明的时候是动物,new的时候是一只猪,3.调用的是虚函数,满足这三个条件,编译器就把调用动作编译成下面两行,虚机制,即动态绑定的形式,调用的函数不一定,要看p指的是谁,静态绑定一定调用到某个地址,
虚函数的这一种用法,叫做多态,因为声明同样是pointer to A,但实际上却指向不同的东西,
12 对象模型:this
通过对象调用一个函数,那个对象的地址就是this,
图中用法是template method 模板方法,是一种设计模式的名称,
虚函数在使用的时候,一种是此图用法模板方法,一种是上一个图的用法多态,此图中子类的对象调用父类的函数,mydoc.onfileopen函数调用,谁调用谁的地址就是this,这一行解析为下面的代码,红色mydoc的地址就是this,this指针所指的object是this object,所以这个指针this就传入左边父类写的函数onfileopen,父类的函数onfileopen里有个隐藏的参数因为在c++里所有的成员函数一定有一个隐藏的this pointer作为参数,传进来后onfileopen里所有的调用动作都被编译器看成左边的代码,都是通过this来调用,
编译器看到三个条件满足就会动态绑定,1.必须是通过指针来调用,此处是this指针,2.这个指针是向上转型up-cast,this指的是子类的对象,符合向上转型,声明的时候是动物,new的时候是一只猪,3.调用的是虚函数,此处调用serialize虚函数,满足这三个条件,编译器就把调用动作编译成下面的形式,虚机制,即动态绑定的形式,编译器会看this指向谁,此处this指向子类,因此调用到的是子类的虚函数,而不是父类的虚函数,
汇编代码: a.vfunc1是通过对象来绑定,不是通过指针来绑定,是静态绑定,
A* pa = new B是向上转型,pa->vfunc1是动态绑定,(*(p->vptr)[n])§通过指针p找到它的虚指针,再找到虚表取出其中的第n个当成函数指针去调用,由于是通过p来调用,小括号里的p就是this pointer
13 const
const放在函数小括号即参数列的后面,大括号即函数本体的前面,为了告诉编译器要修饰这个成员函数,const放在这个位置只能放在成员函数的后面,一般的全局函数不能在这个位置放const,告诉编译器这个成员函数不打算改变class的data数据,让编译器把关,看看有没有违反意图,
常量对象调用非常量成员函数,会报错,
标准库的basic_string会被typedef换成string,图右边表示里面有两个函数中括号是重载的,函数重载不用管return type返回类型,只需要看后面的函数签名,加和不加const算签名的一部分,所以两个函数不同,可以共存,
使得的标准库的字符串是reference counting引用计数的技巧做出来的,也就是是可以被共享,一个字符串被拷贝多份,这几个字符串是共享同一个内容,此时如果有一个要改动内容,其余也会改动,所以在共享的时候必须要做cow :copy on write 的动作,
常量字符串不可能会被改内容,所以常量字符串调用中括号绝对不必做cow,常量字符串不管是几个人共享一个常量字符串都不必考虑cow,为了区分中括号是给常量字符串调用还是非常量字符串调用,必须写出两个版本,函数加const就是常量成员函数,不加就是非常量成员函数,常量对象只能调用常量成员函数,不能调用非常量成员函数,但是常量成员函数仍然不知道谁调用它,于是在表格规则之外,上面三行写明c++还有规则,
14 new,delete
使用的new和delete都叫expression表达式,分解后的都是operator ,表达式的行为不能变,不能重载,即new分解为1,2,3和delete分解为1,2的事实无法改变,但是分解后所调用的函数可以重载
placement new