继承
面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行效率的效果。
当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。在 Qt 里大量的使用了这种特性,当 Qt 里的类不满足自己的要求时,我们可以重写这个类,就是通过继承需要重写
的类,来实现自己的类的功能。
一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数。定义一个派生类,我们使用一个类派生列表来指定基类。类派生列表以一个或多个基类命名,形式如下:
class derived-class: access-specifier base-class
与类的访问修饰限定符一样,继承的方式也有几种。其中,访问修饰符 access-specifier 是 public、protected 或 private 其中的一个,base-class 是之前定义过的某个类的名称。如果未使用访问修饰符 access-specifier,则默认为 private。
下面来捋一捋继承的方式,例子都是以公有成员和公有继承来说明,其他访问修饰符和其他继承方式,大家可以在教程外自己捋一捋。这个公有成员和继承方式也没有什么特别的,无非就是不同的访问权限而已,可以这样简单的理解。
1. 公有继承(public):当一个类派生继承公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
2. 保护继承(protected): 当一个类派生继承保护基类时,基类的公有和保护成员将成为派生类的保护成员。
3. 私有继承(private):当一个类派生继承私有基类时,基类的公有和保护成员将成为派生类的私有成员。
下面我们还是以狗类为例,在 2.2.1 小节里我们定义的狗类,已经定义了 name,age 和 run()方法。假设我们不想重写这个狗类,而是新建一个 Animal 类,让狗类去继承这个 Animal 类。
假设是公有继承,那么我们是不是可以在狗类实例的对象里去使用继承 Animal 类里的成员呢?带着这个疑问,我们使用下面的例子来说明。
新建一个目录 06_inherit_example,编辑一个 06_inherit_example.cpp 内容如下。
1 #include <iostream>
2 #include <string>
3 using namespace std;
4
5 /*动物类,抽象出下面两种属性,
6 *颜色和体重,是每种动物都具有的属性
7 */
8 class Animal
9 {
10 public:
11 /* 颜色成员变量 */
12 string color;
13 /* 体重成员变量 */
14 int weight;
15 };
16
17 /*让狗类继承这个动物类,并在狗类里写自己的属性。
18 *狗类拥有自己的属性 name,age,run()方法,同时也继承了
19 *动物类的 color 和 weight 的属性
20 */
21 class Dog : public Animal
22 {
23 public:
24 string name;
25 int age;
26 void run();
27 };
28
29 int main()
30 {
31 Dog dog;
32 dog.name = "旺财";
33 dog.age = 2;
34 dog.color = "黑色";
35 dog.weight = 120;
36 cout<<"狗的名字叫:"<<dog.name<<endl;
37 cout<<"狗的年龄是:"<<dog.age<<endl;
38 cout<<"狗的毛发颜色是:"<<dog.color<<endl;
39 cout<<"狗的体重是:"<<dog.weight<<endl;
40 return 0;
41 }
第 21 行,Animal 作为基类,Dog 作为派生类。Dog 继承了 Animal 类。访问修饰符为 public(公有继承)。
执行下面的指令开始编译。
g++ 06_inherit_example.cpp -o 06_inherit_example
编译完成执行的结果为如下。
重载
C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载。
重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列表和定义(实现)不相同。
当您调用一个重载函数或重载运算符时,编译器通过把您所使用的参数类型与定义中的参数类型进行比较,决定选用最合适的定义。选择最合适的重载函数或重载运算符的过程,称为重载决策。
函数重载
在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。我们不能仅通过返回类型的不同来重载函数。在 Qt源码里,运用了大量的函数重载,所以我们是有必要学习一下什么是函数重载。不仅在 C++,在其他语言的里,都能看见函数重载。因为需要不同,所以有重载各种各样的函数。
下面通过一个小实例来简单说明一下函数重载的用法。我们还是以狗类为说明,现在假设有个需求。我们需要打印狗的体重,分别以整数记录旺财的体重和小数记录旺财的体重,同时以整数打印和小数打印旺财的体重。那么我们可以通过函数重载的方法实现这个简单的功能。
新建一个目录 07_func_overloading,编辑一个 07_func_overloading.cpp 内容如下。
1 #include <iostream>
2 #include <string>
3 using namespace std;
4
5 class Dog
6 {
7 public:
8 string name;
9 void getWeight(int weight) {
10 cout<<name<<"的体重是:"<<weight<<"kG"<<endl;
11 }
12
13 void getWeight(double weight) {
14 cout<<name<<"的体重是:"<<weight<<"kG"<<endl;
15 }
16 };
17
18 int main()
19 {
20 Dog dog;
21 dog.name = "旺财";
22 dog.getWeight(10);
23 dog.getWeight(10.5);
24 return 0;
25 }
第 9 行,写了一个方法 getWeight(int weight),以 int 类型作为参数。
第 13 行,以相同的函数名 getWeight,不同的参数类型 double weight,这样就构成了函数重载。
第 22 行与第 23 行,分别传进参数不同的参数,程序就会匹配不同的重载函数。
执行下面的指令编译。
g++ 07_func_overloading.cpp -o 07_func_overloading
程序执后的结果如下。
通过上面的例子我们可以知道重载函数的使用方法,避免用户传入的参数类型,有可能用户传入的参数类型不在我们写的重载函数里,假若用户传入了一个字符串类型,这样编译器就会匹配不到相应的重载函数,编译时就会报错。其实我们还可以多写几个重载函数,设置多几
种类型,如 string 类型,char 类型,float 类型等。
运算符重载
运算符重载的实质就是函数重载或函数多态。运算符重载是一种形式的 C++多态。目的在于让人能够用同名的函数来完成不同的基本操作。要重载运算符,需要使用被称为运算符函数的特殊函数形式,运算符函数形式:operatorp(argument-list),operator 后面的’p’为要重载的运
算符符号。重载运算符的格式如下:
<返回类型说明符> operator <运算符符号>(<参数表>)
{
<函数体>
}
下面是可重载的运算符列表:
下面是不可重载的运算符列表:
根据上表我们知道可以重载的运算符有很多,我们以重载“+”运算符为例,实际上用重载运算符我们在实际应用上用的比较少,我们只需要了解和学习这种思想即可。
下面的实例使用成员函数演示了运算符重载的概念。在这里,对象作为参数进行传递,对象的属性使用 this 运算符进行访问。下面还是以我们熟悉的狗类为例。声明加法运算符用于把两个 Dog 对象相加的体重相加,返回最终的 Dog 对象然后得到第三个 Dog 对象的体重。
新建一个目录 08_operator_example,编辑一个 08_operator_example.cpp 内容如下。
1 #include <iostream>
2 #include <string>
3 using namespace std;
4
5 class Dog
6 {
7 public:
8 int weight;
9 Dog operator+(const Dog &d) {
10 Dog dog;
11 dog.weight = this->weight + d.weight;
12 return dog;
13 }
14
15 };
16
17 int main()
18 {
19 Dog dog1;
20 Dog dog2;
21 Dog dog3;
22
23 dog1.weight = 10;
24 dog2.weight = 20;
25 dog3 = dog1 + dog2;
26 cout<<"第三只狗的体重是:"<<dog3.weight<<endl;
27 return 0;
28 }
第 9 至 13 行,重载“+”运算符,注意函数必须与类名同名,把 Dog 对象作为传递,使用 this 运算符进行访问。然后返回一个 dog 对象。
执行下面指令进行编译。
g++ 08_operator_example.cpp -o 08_operator_example
编译完成后运行的结果如下。
结果可以预知的,重载运算符“+”,可以把两个对象进行相加。在普通的算术运算符“+”是不能将两个对象进行相加的,所以我们重载运算符的意义可以体现在这里。
多态
C++多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数;
形成多态必须具备三个条件:
1. 必须存在继承关系;
2. 继承关系必须有同名虚函数(其中虚函数是在基类中使用关键字 virtual 声明的函数,在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数);
3. 存在基类类型的指针或者引用,通过该指针或引用调用虚函数。
这里我们还需要理解两个概念:
虚函数:
是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。虚函数声明如下:virtual
ReturnType FunctionName(Parameter) 虚函数必须实现,如果不实现,编译器将报错
纯虚函数:
若在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。纯虚函数声明如下:
virtual void funtion1()=0; 纯虚函数一定没有定义,纯虚函数用来规范派生类的行为,即接口。
包含纯虚函数的类是抽象类,抽象类不能定义实例,但可以声明指向实现该抽象类的具体类的指针或引用。
上面那些概念大家可以捋一捋,毕竟 C++概念还是挺多的。为什么说到多态要与虚函数和纯虚函数扯上关系?光说概念没有实例确实难理解。下面我们还是以我们熟悉的狗类和动物类,另外加一个猫类进行多态的讲解。