虚函数和多态性
派生类中与基类重名的成员
如果派生类的成员与基类的成员重名,则派生类的成员将隐藏同名的基类成员。
/*正方形类,基类*/
class Square
{
protected:
double L;
public:
Square() :L{ 4 } {};
Square(double a) :L{ a } {}
double GetArea() const /*矩形面积*/
{
return L * L;
}
double GetGirth() const /*矩形周长*/
{
return 4 * L;
}
};
/*矩形类,派生类*/
class Rectangle : public Square
{
protected:
double W = 0;
public:
Rectangle() = default;
Rectangle(double a, double b) : Square{ a }, W{ b } {}
double GetArea() const /*矩形面积*/
{//重定义从基类继承的成员函数
return L * W;
}
double GetGirth() const /*矩形周长*/
{//重定义从基类继承的成员函数
return 2 * (L + W);
}
};
int main(void)
{
Rectangle Re{ 7,3 };
cout << Re.GetArea() << endl; //调用派生类自定义的GetArea()成员函数
cout << Re.GetGirth() << endl;//调用派生类自定义的GetGirth()成员函数
return 0;
}
我们可以通过作用域运算符来使用一个被隐藏的基类成员。
double GetArea() const /*矩形面积*/
{
if (L == W)
return Square::GetArea(); //调用基类版本的GetArea()函数
return L * W;
}
虚函数
如果基类希望其派生类覆盖从基类继承的函数,则基类应当将该函数声明为虚函数。
- 任何构造函数之外的非静态函数都可以是虚函数。
- 关键字 virtual 只能出现在类内部的声明语句之前而不能用于类外部的函数定义。
- 如果基类把一个函数声明成虚函数,则该函数在派生类中隐式地也是虚函数。
/*正方形类,基类*/
class Square
{
protected:
double L;
public:
Square() :L{ 4 } {};
Square(double a) :L{ a } {}
virtual double GetArea() const /*矩形面积*/
{//虚函数
return L * L;
}
virtual double GetGirth() const /*矩形周长*/
{//虚函数
return 4 * L;
}
};
C++11新标准允许派生类显式地注明它使用某个成员函数覆盖了它继承的虚函数。
具体做法是在形参列表后面、或者在 const 成员函数的 const 关键字后面、或者在引用成员函数的引用限定符后面添加一个关键字 override。
- 在派生类中重新定义的虚函数,其函数名,函数类型,函数参数个数和类型必须与基类的虚函数相同。
- 如果虚函数使用默认实参,则基类和派生类中定义的默认实参最好一致。
/*矩形类*/
class Rectangle : public Square
{
protected:
double W = 0;
public:
Rectangle() = default;
Rectangle(double a, double b) : Square{ a }, W{ b } {}
double GetArea() const override /*矩形面积*/
{
return L * W;
}
double GetGirth() const override /*矩形周长*/
{
return 2 * (L + W);
}
};
可以通过基类的指针或引用来访问派生类中与基类同名的成员函数。
int main(void)
{
Cube cube{2};
cout << cube.GetVolume()<< endl;
Rectangle Re{ 3,3 };
cout << "矩形面积:" << Re.GetArea() << endl;
cout << "矩形周长:" << Re.GetGirth() << endl;
Square * S = ℜ
cout << S->GetArea() << endl; //调用派生类的GetArea()版本
cout << S->GetGirth() << endl;//调用派生类的GetGirth()版本
return 0;
}
虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。
需要说明的是:当一个类带有虚函数时,编译系统会为该类构造一个虚函数表,它是一个指针数组,存放每个虚函数的入口地址。
虚析构函数
最好把基类的析构函数声明为虚函数。
将基类的析构函数声明为虚函数时,由该基类所派生的所有派生类的析构函数也都自动成为虚函数,即使派生类的析构函数与基类的析构函数名字不同。
构造函数不能声明为虚函数。
这是因为在执行构造函数时类对象还未完全建立过程,也无法把函数与类对象绑定。
多态性
在C++中,多态性表现形式之一是:具有不同功能的函数可以用同一个函数名,这样就可以实现用一个函数名调用不同内容的函数。
从系统实现的角度来看,多态性分为两类:
- 静态多态性(编译时的多态性):通过函数重载和运算符重载实现。
- 动态多态性(运行时的多态性):通过虚函数实现。
静态关联与动态关联
确定调用的具体对象的过程称为关联(binding),即把一个函数名与一个类对象捆绑在一起,建立关联。
静态关联:在编译阶段将函数名与类名“绑定”。
动态关联:在运行阶段将函数名与类名“绑定”。
纯虚函数和抽象基类
纯虚函数
纯虚函数(pure virtual function)纯虚函数是在声明虚函数时被“初始化”为 0 的函数。
声明纯虚函数的一般形式:
virtual 函数类型 函数名(参数列表)= 0;
纯虚函数只有函数名字而不具备功能。
纯虚函数的作用是:在基类中为其派生类保留一个函数名字,以便派生类根据需要对它进行定义。
抽象基类
含有(或者未经覆盖直接继承)纯虚函数的类是抽象基类(abstract base class)。
抽象基类负责定义接口,而后续的其他类可以覆盖该接口。
值得注意:不能定义一个抽象基类的对象。
一个简单的抽象基类
/*形状类*/
class Shape
{
public:
virtual double GetArea() = 0; /*求面积*/
virtual double GetGirth() = 0; /*求周长*/
};
派生类
/*圆形类*/
class Circular : public Shape
{
private:
const static double PI;
double R = 0;
public:
Circular() = default;
Circular(double r);
double GetArea() const override; /*圆面积*/
double GetGirth() const override; /*圆周长*/
};
const double Circular::PI = 3.14;
Circular::Circular(double r) : R{ r } {}
/*圆面积*/
double Circular::GetArea() const
{
double S = PI * R * R;
return S;
}
/*圆周长*/
double Circular::GetGirth() const
{
double L = 2 * PI * R;
return L;
}
通过基类的指针或引用来访问虚函数。
int main(void)
{
//Shape Re; 不能定义一个抽象基类的对象。
Circular Cir{ 10 };
Shape * S = &Cir;
cout << "圆面积:" << S->GetArea() << endl;
cout << "圆周长:" << S->GetGirth() << endl;
return 0;
}