继承时的名字遮蔽
如果派生类中的成员变量和基类中的成员变量重名,那么就会遮蔽从基类继承过来的成员变量。所谓遮蔽,就是使用新增的成员变量,而不使用继承来的。
成员函数也一样,如果函数名和参数签名都相同,就会造成遮蔽。如果仅仅是函数名相同,而参数签名不同,那么会构成重载。
派生类的构造函数
基类的构造函数不能被继承,在声明派生类时,对继承过来的成员变量的初始化工作也要由派生类的构造函数来完成。所以在设计派生类的构造函数时,不仅要考虑派生类新增的成员变量,还要考虑基类的成员变量,要让它们都被初始化。
解决这个问题的思路是:在执行派生类的构造函数时,调用基类的构造函数。
下面的例子展示了如何在派生类的构造函数中调用基类的构造函数。
#include<iostream>
using namespace std;
//基类
class People
{
protected:
char *name;
int age;
public:
People(char*, int);//基类的接口函数
};
People::People(char *name, int age): name(name), age(age){}
//派生类
class Student: public People{
private:
float score;//派生类新增成员变量
public:
Student(char*, int, float);//派生类新增成员函数
void display();
};
//调用基类的构造函数
Student::Student(char *name, int age, float score): People(name, age)//派生类Student构造函数的写法
//冒号前边是Student类构造函数的头部,而冒号后边是基类构造函数的调用。
{
this->score = score;
}
void Student::display(){
cout<<name<<"的年龄是"<<age<<",成绩是"<<score<<endl;
}
int main(){
Student stu("小明", 16, 90.5);
stu.display();
return 0;
}
多继承
在前面的例子中,派生类都只有一个基类,称为单继承。除此之外,C++也支持多继承,即一个派生类可以有两个或多个基类。
基类和派生类的赋值
类也是一种数据类型,也可以发生数据类型转换。不过这种转换只有在基类和派生类之间才有意义。
由于派生类包含从基类继承的成员,因此可以将派生类的对象赋值给基类对象,如下所示:
class A{
public:
int m;
};
class B: public A{
public:
int n;
};
A a;
B b;
a = b;
a.m = 2;
对象之间的赋值是成员变量之间的赋值,当进行赋值时,当用派生类给基类赋值时,会丢弃派生类自己的成员变量,即赋值的过程中基类不会得到派生类新增的成员变量。
可以发现,即使将派生类对象赋值给基类对象,基类对象也不会包含派生类的成员,所以依然不同通过基类对象来访问派生类的成员。对于上面的例子,a.m 是正确的,但 a.n 就是错误的,因为 a 不包含成员 n。
虚函数
虚函数的作用是:允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。
如果希望通过 p 指针访问 Student 类的成员函数,可以将该成员函数声明为虚函数
如果希望通过 p 指针访问 Student 类的成员函数,可以将该成员函数声明为虚函数
虚函数的使用方法:
1. 在基类用virtual声明成员函数为虚函数。这样就可以在派生类中重新定义此函数,为它赋予新的功能,并能方便地被调用。在类外定义虚函数时,不必再加virtual。
2. 在派生类中重新定义此函数,要求函数名、函数类型、函数参数个数和类型全部与基类的虚函数相同,并根据派生类的需要重新定义函数体。C++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。
3. 定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象 。
4. 通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。 ——zhenhuaqin的博客园
借助虚函数,基类指针既可以使用基类的成员函数,也可以使用派生类的成员函数,它有多种形态,或多种表现方式,这就是多态(Polymorphism)。
多态
一般在面向对象的程序设计中,是这样表述多态的:向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为(方法),也可以说,多态性是“一个接口,多种方法”
多态是面向对象的主要特征之一。在C++中,虚函数的唯一用处就是构成多态。
C++提供多态的目的是:可以通过基类指针对所有派生类(包括直接派生和间接派生)的成员变量和成员函数进行“全方位”的访问,尤其是成员函数。如果没有多态,我们只能访问成员变量。
多态存在的三个条件:
- 必须存在继承关系
- 继承关系中须有同名的虚函数,且其为覆盖关系,而不是重载。
- 存在基类的指针,通过该指针调用虚函数。
派生类中的虚函数必须覆盖(不是重载)基类中的虚函数,才能通过基类指针访问。
使用多态的一个例子:
#include <iostream>
using namespace std;
//军队
class Troops{
public:
virtual void fight(){ cout<<"Strike back!"<<endl; }
};
//陆军
class Army: public Troops{
public:
void fight(){ cout<<"--Army is fighting!"<<endl; }
};
//99A主战坦克
class _99A: public Army{
public:
void fight(){ cout<<"----99A(Tank) is fighting!"<<endl; }
};
//武直10武装直升机
class WZ_10: public Army{
public:
void fight(){ cout<<"----WZ-10(Helicopter) is fighting!"<<endl; }
};
//长剑10巡航导弹
class CJ_10: public Army{
public:
void fight(){ cout<<"----CJ-10(Missile) is fighting!"<<endl; }
};
//空军
class AirForce: public Troops{
public:
void fight(){ cout<<"--AirForce is fighting!"<<endl; }
};
//J-20隐形歼击机
class J_20: public AirForce{
public:
void fight(){ cout<<"----J-20(Fighter Plane) is fighting!"<<endl; }
};
//CH5无人机
class CH_5: public AirForce{
public:
void fight(){ cout<<"----CH-5(UAV) is fighting!"<<endl; }
};
//轰6K轰炸机
class H_6K: public AirForce{
public:
void fight(){ cout<<"----H-6K(Bomber) is fighting!"<<endl; }
};
int main(){
Troops *p = new Troops;
p ->fight();
//陆军
p = new Army;
p ->fight();
p = new _99A;
p -> fight();
p = new WZ_10;
p -> fight();
p = new CJ_10;
p -> fight();
//空军
p = new AirForce;
p -> fight();
p = new J_20;
p -> fight();
p = new CH_5;
p -> fight();
p = new H_6K;
p -> fight();
return 0;
}
程序运行结果:
Strike back!
--Army is fighting!
----99A(Tank) is fighting!
----WZ-10(Helicopter) is fighting!
----CJ-10(Missile) is fighting!
--AirForce is fighting!
----J-20(Fighter Plane) is fighting!
----CH-5(UAV) is fighting!
----H-6K(Bomber) is fighting!
该程序中,需要注意一下两点:
- virtual 关键字仅用于函数声明,如果函数是在类外定义,则不需要再加上virtual关键字。
- 为了方便,你可以只将基类中的函数声明为虚函数,所有派生类中具有覆盖关系的同名函数都将自动成为虚函数。
虚析构函数
引入虚析构函数的目的是为了解决基类指针不能调用派生类析构函数从而造成内存泄露的问题。
使用方法:
在基类的析构函数前加virtual将基类析构函数定义成虚析构函数,此时派生类中的析构函数自动成为虚析构函数。