GeekBand by 494631002
C++面向高级编程(下)与(上)不同,重点在于泛型编程和面向对象编程;
通过这个星期的课程,再次让我体会到了在C++中操作符重载的重要性;并且有很多特性的关键位置都由操作符的重载函数来实现;
一、转换函数(Conversion Function)
在上图中,蓝色的部分是Fraction类的构造函数;黄色部分是转换函数;在方框底下,创建了一个Fraction类型的对象,调用了构造函数。当执行下一条语句的时候,由于‘+’号的两边并不是同一个类型,因此编译器率先会去寻找有没有‘+’号的重载;但是并没有发现类中有‘+’号重载的成员函数,但是看到了黄色的这部分,所以将f转换成了double类型的对象再相加;
注意,我刚才说的是转换成double类型的对象。在这个例子中只是把f转换成基本类型double(基本类型包括int、double、char...),其实还可以转换成其他自定义的类型,那就要看你自己怎么实现自定义的类和如何转换了;
当然,要实现分数的加法也可以这样写,通过操作符重载,告诉编译器'+'号两边类型不一致时该怎么处理:
从这种方法中,我们可以看到有‘+’号的重载;的确,编译器也会执行重载函数,只不过‘+’号的重载是作用在‘+’号右边的操作数,并且这个对象的类型必须是分数,而在实际的操作中,4并不是分数类型;这个时候,编译器会执行构造函数,把4转换为4/1;
我们可以发现,蓝色部分的构造函数有些特殊;它接受两个参数,但是其中一个参数已经有了自己的默认值,也就是说,在实际创建对象的时候,我们可以只设置一个参数的初值,另一个可以初始化也可以不用初始化;
显然,当我们把两者写在一起的时候,编译器会报错,因为编译器发现有两种情况可以选择:
在这样的情形下,我们使用explict关键字,告诉编译器不要用构造函数来进行对象的类型转换;通常explict用在构造函数之前。
这里,编译器依然去先寻找'+'号的重载,可是这里'+'号的重载并不符合要求(+号右边需要一个分数类型),因此f会通过转换函数转换成了double类型和4相加,可最终,无法将这个结果(double类型的对象)转换成分数类型给d2,编译器报错。
二、智能指针(Pointer-like classes)
智能指针,其实是用类来实现,这种类产生的对象和普通指针在功能上相比,多了一些新的功能;
还是先考虑使用者会如何去使用,需求就是要求:
也就是说,这种智能指针类中,必定会包含普通的指针,实现基本的功能;
因为我们是把智能指针用于未知的类型的对象,因此我们在设计的时候应该采用模板;结合这个具体的实例来说,右边使用者先创建一个指向Foo类型对象的指针的智能指针sp; 这里我觉得是智能指针的对象sp和Foo对象(临时对象)一同被创建出来。sp指向这个临时对象;
随后将使用指针的基本用法,解引用'*',用于取得sp所指的内容;至于图中的f,并不是清楚是干什么的;总之,我们得先设计'*'操作符的重载了;另外还有'->'的重载,这个的设计需要花点时间来思考;我们知道'->'是用来取一个结构体中的成员的,'->'其实是对它左边的对象进行操作,返回的是指针类型,'->'的左边必须是一个指针;既然sp已经指向一个对象了,所以这里使用'->'就是获取这个对象中的一个成员,这里就是调用成员中的一个方法;显然,'->'的设计如图所示;
到这里,我们成功的实现了智能指针的基本功能;即以后,我们可以直接创建某个类的指针了;以后希望使用类中的某个函数,也可以通过指针来直接调用;还有更多的功能,比方把指针当做参数来传递等等。。。只要基本指针都能做的,这个智能指针也可以做。
三、仿函数(Function-like class)
如果在一个class中看到有关于'( )'的重载,那么这种类产生的对象就是函数对象;
图中的左半部分,我们可以看到了这三个类都有对'( )'进行重载,因此这三个类产生的对象都可以当做函数来使用;select1st在暗示需要传入Pair类型的对象(Pair类在图的的右边,里面有两个其他类的对象的成员变量),里面的构造函数如图所示;'( )'重载就是去取出Pair类中的第一个对象;
标准库中有很多仿函数,其实这些仿函数都是一个个的类。
四、类模板
类模版中可以指定类中函数参数或者成员变量的类型为模版类型:
类模板是这样一种通用的类:在定义类时不说明某些数据成员、成员函数的参数及返回值的数据类型。类是对对象的抽象,而类模板是对类的抽象,及更高层次上的抽象。类模板称为带参数的类,也称为类工厂(class factory),它可用来生成多个成员相同而某些数据成员、成员函数的参数及返回值的数据类型不同的类型参数。
这样做的原因是因为你可以省去了大部分重复的代码。而且事先我们并不能确定所创建的对象中,每个成员的类型是什么,所以最好就是当你创建一个对象的时候再去使用模板就行,这时编译器会自动把所有的T替换为需要的类型。
五、函数模板
1.函数模板是一种不说明某些参数的数据类型的函数。
2.函数模板被调用时,编译器根据实际参数的类型确定模板参数T的类型,并自动生成一个对应的函数。
3.定义函数模板时也可以使用多个类型参数,这时,每个类型参数前面都要加上关键字class 或typename,其间用逗号分隔。
和之前的类模板类似,只需要把传入的参数类型,函数的返回类型都设置为T,等到时候调用的时候编译器会自动推导出参数类型。
补充:
函数模板和函数模板的区别:请参考CSDN《函数模板与类模板的区别》
函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化必须由程序员在程序中显式地指定。即函数模板允许隐式调用和显式调用而类模板只能显示调用。
注意:模板类的函数声明和实现必须都在头文件中完成,不能像普通类那样声明在.h文件中实现在.cpp文件中:
六、成员模板
定义:任意类(模板或非模板)可以拥有本身为类模板或函数模板的成员,这种成员称为成员函数模板。
也就是说,当T1、T2确定下来之后,U1、U2还可以继续当模板;
这里比较抽象,这里我们需要结合实例来说明:
这个例子中,我们创建了四个类;其中的继承关系如右上角所示;
而pair类的每一个对象是由两个其他类的对象所组成的;也就是说,类中有类;
我先把所有的T1,T2进行替换:
现在,代码变得非常清晰了:
第5行是新建一个Derived1和Derived2的临时变量,并把临时变量的值作为first和second的初值;第6行是另外一种构造函数,只不过参数不是默认值;
但是,为了实现用p初始化p2,又要设计新的构造函数了;
总结:模板有三种:类模板、函数模板、成员模板;
七、模板的特化和偏特化
特化是泛化的反义词,偏特化是局部特化的意思;偏特化有两种情形:
八、零碎的一些概念
auto:
在变量前面加上auto,可以让编译器去推导类型,但是需要注意的是,一定要用在定义的语句前,如果是在声明的语句之前加上auto,那么编译器是无法推导的;
ranged-base for :