在完成了C++面向对象高级编程(下)第一周的学习之后,有一些总结和心得在这里通过学习笔记的方式分享出来,供也在学习C++的小伙伴用作学习交流,如有理解不到位的地方,欢迎批评指正。
本周学习的(下)与之前学习的(上)的不同之处在于,泛型编程与面向对象编程之间的差异。
一.转换函数(Conversion Function)
下图的代码,蓝色部分是Fraction类的构造函数;黄色部分是转换函数;创建了一个Fraction类型的对象,调用了构造函数。当执行下一条语句的时候,由于‘+’号的两边并不是同一个类型,因此编译器先会去寻找有没有‘+’号的重载;但是并没有发现类中有‘+’号重载的成员函数,但是看到了黄色的这部分,所以将f转换成了double类型的对象再相加。
当然,也可以使用操作符重载(重新定义“+”)完成上述运算,如下图代码所示:
但是,当主程序执行
Fraction f (3, 5);
Fraction d = f + 4;
操作时,由于“+”重载是作用在右边的操作数(也就是4),并且f和4不是同一类型,4不是一个Fraction类,此时编译器调用non-explicit ctor将4转换为Fraction(4,1)再进行操作符“+”重载。我们可以发现这种情况下的构造函数接受的两个参数中的一个参数有默认值,也就是说,在实际创建对象的时候,我们可以只设置一个参数的初值,另一个可以初始化也可以不用初始化,即“non-explicit-one-argument ctor”。
但是,如果我们将两种操作放在一起时,编译器便会报错,因为当我们给编译器提供了两种选择,编译器会认为有歧义:
此时,我们可以使用explict关键字,告诉编译器不要自动使用构造函数来进行对象的类型转换,explict关键字通常用在构造函数之前:
此时,编译器依然会先去调用“+”重载,但是由于4不是Fraction类型,因此“+”重载失败,因此编译器会通过转换函数将f转换成double类型与4相加,但是d2是一个Fraction类型,无法接收这个double类型的结果,导致编译器报错。
注意:如果转换过后值不变,最好在函数定义之前加上关键字“const”;另外,只要合理,可以转换成任何类型,转换规则由程序员来决定。
二.智能指针(Pointer-like classes)
智能指针与普通指针不同之处在于,智能指针是用类来实现的。所以,这种智能指针类中,必然会包含普通指针,实现基本功能。因为智能指针通常指向未知的类型对象,所以我们在设计的时候采用模板的形式。
如下代码所示,创建了一个智能指针sp指向Foo类型的对象;随后,我们可以像普通指针的操作那样“*”解除引用,取得值,“->”取得结构体中的成员。
这样,我们就可以直接创建某个类的指针了,如果希望使用类中的某个函数,也可以通过指针来直接调用。
三.仿函数(Function-like class)
如果在class中存在operator(),即这个类就是仿函数,这种类产生的对象就是函数对象。
四.类模板(class template)
之前的课程也提到过类模板,使用类模板可以省去大量重复的代码。
五.函数模板(function template)
函数模板是一种不说明某些参数的数据类型的函数;函数模板被调用时,编译器根据实际参数的类型确定模板参数T的类型,并自动生成一个对应的函数;定义函数模板时也可以使用多个类型参数,这时,每个类型参数前面都要加上关键字class或typename,其间用逗号分隔。
编译器可以根据调用函数中的参数来进行推导,这里推导出来T为stone类型;推导出stone类之后,编译器回去找“<”有没有被重构,这里编译器找到了“<”的重构函数。跟之前的类模板相似,只需要把传入的参数类型,函数的返回类型都设置为T,调用时编译器会自动推导出参数类型。
六.成员模板(member template)
任意类(模板或非模板)可以拥有本身为类模板或函数模板的成员,这种成员称为成员函数模板。
黄色部分是模板中的一个成员,而它本身又是一个模板。也就是当T1,T2确定下来了,U1,U2仍需确定。
在下面的例子中,创建了四个类,继承关系如下图所示,而pair类的每一个对象是由两个其他类的对象所组成的;也就是说,类中有类。
到这里,我们知道了模板有三种:类模板、函数模板和成员模板。
七.模板特化(specialization)和模板偏特化(partial specialization)
模板特化很好理解,就是根据独特类型做相应的特殊设计,这种特殊设计可能会提高效率,也可能会节约内存等等。
以上代码是泛化的情况,把类型固定下来再做特殊处理,即为特化:
模板偏特化分为个数的偏和范围的偏。
两个模板参数绑定其中一个,即将其中一个特化。注意:一定要从左至右依次绑定,不能跳跃绑定模板参数。
将泛化的任意类型特化为指针指向的类型,即为范围上的偏,也就是将范围缩小到指针指向类型。
八.C++11新增
1.auto(since C++11)
在变量前面加上auto,可以让编译器去推导类型,但是需要注意的是,一定要用在定义的语句前,如果是在声明的语句之前加上auto,那么编译器是无法推导的。
2.ranged-base for(since C++11)
:的左边声明一个变量,右边是一个容器。
Pass by reference传递的引用也就是指针,elem*=3并不会影响容器中元素的值,若想改变容器中的元素值,则应该pass by value
九.reference
上述代码中,p是指向x的指针,r代表了x,那么r就不能够代表其他物体,一旦r的值改变,它所代表的x的值也将改变。执行上述代码,最后的结果将是r2=r=x2=x=5,r2代表r,就相当于代表了x。
注意,r与其代表的x大小、地址都相同是编译器制造的假象。
Reference通常不用于声明变量,而用于参数类型(parameters
type)和返回类型(return type)的描述。使用reference的好处在于函数被调用端与调用端接口与按值传递相同,如果传递的是指针,写法就不同了。