类体系的函数重载
- 在一个类中声明重载;
- 派生类定义和基类同名,同参数的函数,即在派生类中重载。
类指针的关系
- 基类指针指向基类对象;
- 派生类指针指向派生类对象;
- 基类指针指向派生类对象;
- 派生类指针指向基类对象;
- 基类指针可以直接指向派生类对象,但是使用的成员都是自己的版本,只有强制类型转换为派生类指针才能使用派生类特有成员;派生类指针不能直接指向基类对象,只有强制类型转换为基类指针才能指向。
基类指针指向派生类对象
- 基类指针只能引用基类成员;
- 如果试图引用派生类中特有的成员,必须通过强制类型转换;
- 任何引用同名的成员,都是基类的版本。
#include<iostream>
using namespace std;
class Base{
public:
int ba;
Base(int x){ba = x;cout<<"Base build!"<<endl;}
void show(){cout<<"Base.show()"<<endl;}
};
class Detrived:public Base{
public:
int da;
int db;
Detrived(int m,int n,int x):Base(x){da = m;db = n;cout<<"Detrived build!"<<endl;}
void show(){cout<<"Detrived.show()"<<endl;}
void print(){cout<<"Detrived.print()"<<endl;}
};
int main(){
Base* pb = new Detrived(1,2,3);
pb->show();
cout<<pb->ba<<endl;
// cout<<pb->da<<endl; // 错误
cout<<((Detrived*)pb)->da<<endl; // 正确,强制类型转换
}
结果:
Base build!
Detrived build!
Base.show()
3
1
派生类指针指向基类对象
- 只有通过强制类型转换之后,才能引用基类对象
Detrived* p = new Base(); // 错误
Base b;
Detrived* p = &b; // 错误
(Base*)p = &b; // 正确
虚函数
背景
基类指针可以指向派生类对象,但是通过基类指针访问的只能是基类的成员。C++ 中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。使基类指针依赖运行时的地址值调用不同版本的成员函数。
定义
在基类中想要说明为虚函数的成员函数前面加上
virtual
关键字,就将该成员函数说明为虚函数,之后,派生类中同名的函数也默认具有虚特性而可以省略此关键字。
- 一个函数被说明为虚函数,不论经历多少派生层次,所有同界面(同名同参数同返回类型)的重载函数都保持虚特性,因为派生类也是基类;
- 虚函数必须是类的成员函数,不能将虚函数声明为全局函数或静态成员函数,因为虚函数的动态联编依靠 this 指针实现;
- 不能将友元说明为虚函数,但是虚函数可以是另一个类的友元;
- 析构函数可以是虚函数,但是构造函数不能是。
- 虚函数是函数重载的一种特殊形式,不同于一般的函数重载。一般的函数重载仅仅要求函数名相同,重载虚函数要求函数界面完全一致(同名同参数同返回类型)。
- 虚函数在基类中说明,派生类中自动定义。
实例
#include<iostream>
using namespace std;
class Base{
public:
virtual void show(){cout<<"Base"<<endl;}
};
class DetrivedA:public Base{
public:
void show(){cout<<"DetrivedA"<<endl;} // 不用声明 virtual,默认具有
};
class DetrivedB:public DetrivedA{
public:
void show(){cout<<"DetrivedB"<<endl;} // 不用声明 virtual,默认具有
};
int main(){
Base* p = new Base();
p->show();
p = new DetrivedA();
p->show();
p = new DetrivedB();
p->show();
}
结果:
Base
DetrivedA
DetrivedB
虚函数的重载特性
类层次的各个虚函数,表面上它们界面相同,但是隐含的 this 指针是不同的,其关联类型分别是重载它们的派生类。虚函数是仅由 this 指针类型区分接口的函数。C++ 的“虚”特性仅负责在程序运行时把基类 this 指针的关联类型转换成当前指向对象的类类型,而不能改变函数其他参数的性质。
虚析构函数
在继承下,用基类指针指向派生类对象时,然后 delete 基类指针,只会调用基类的析构函数,派生类的析构函数不调用,其资源难以释放,此时可以将派生类声明为虚函数,将基类和派生类的资源一起释放。
#include<iostream>
using namespace std;
class Base{
public:
~Base(){cout<<"delete Base"<<endl;}
};
class DetrivedA:public Base{
public:
~DetrivedA(){cout<<"delete DetrivedA"<<endl;}
};
int main(){
Base* p = new DetrivedA();
delete p;
}
结果:
delete Base
将析构函数声明为虚函数之后:
#include<iostream>
using namespace std;
class Base{
public:
virtual ~Base(){cout<<"delete Base"<<endl;}
};
class DetrivedA:public Base{
public:
~DetrivedA(){cout<<"delete DetrivedA"<<endl;}
};
int main(){
Base* p = new DetrivedA();
delete p;
}
结果:
delete DetrivedA
delete Base
纯虚函数
定义是:
virtual 类型 函数名(参数表) = 0;
- 纯虚函数在基类中定义;
- 比较虚函数后面多了一个 = 0;
- 纯虚函数只需要声明,不需要定义也不能定义;
- 所有的派生类都必须实现这个纯虚函数。
抽象类
- 一个具有纯虚函数的基类叫作抽象类;
- 抽象类至少要有一个纯虚函数;
- 如果抽象类的一个派生类没有为继承的纯虚函数实现自己的版本,那么它依旧是抽象类
- 抽象类只能作基类;
- 抽象类不能建立对象;
- 抽象类不能用作参数类型、函数返回类型或显式转换类型;
- 抽象类可以说明指针和引用,它的指针不受上面一条的限制。
#include<iostream>
using namespace std;
class Animals{
string name;
public:
Animals(string n){name = n;}
virtual void makeSound() = 0;
};
class Dog:public Animals{
public:
Dog(string n):Animals(n) {}
void makeSound(){
cout<<"Dog:WangWang!"<<endl;
}
};
class Cat:public Animals{
public:
Cat(string n):Animals(n) {}
void makeSound(){
cout<<"Cat:MiaoMiao!"<<endl;
}
};
int main(){
// Animals animals[3]; 错误,抽象类不能创建数组
Animals* animal[3] = {new Dog("A"),new Cat("b"),new Cat("c")};
for(int i=0;i<3;i++)
animal[i]->makeSound();
}
结果:
Dog:WangWang!
Cat:MiaoMiao!
Cat:MiaoMiao!