C++总结(4):继承和多态
最佳答案 问答题库658位专家为你答疑解惑
面向对象编程的主要目的之一是提供可重用的代码,即实现代码的模块化。C++提供了类(class)的方法将数据表示和类方法组合在一起。然而不同的产品可能有不同的需求,这意味着需要对原先的类进行修改,而C++提供了比修改代码更好的方法来扩展和修改类,它就是继承。
文章目录
- 1 继承
- 1.1 派生类
- 1.2 派生类的特性和使用
- 1.3 protected关键字
- 1.4 友元的继承
- 2 多态
1 继承
1.1 派生类
继承能够从已有的类派生出新的类,而这个派生类继承了原有类(基类)的特征,包括方法。这里以一个乒乓球俱乐部跟踪乒乓球会员为例,首先需要设计一个简单的TableTennisPlayer
类。
class TableTennisPlayer{
private:string firstname;string lastname;bool hasTable;
public:TableTennisPlayer(const string & fn = "none", const string & ln = "none", bool ht = false);void Name() const;bool HasTable() const { return hasTable; }void ResetTable(bool v) {hasTable = v;}
};TableTennisPlayer::TableTennisPlayer(const string & fn, const string & ln, bool ht) : firstname(fn), lastname(ln), hasTable(ht){}void TableTennisPlayer::Name() const{std::cout << lastname << ", " << firstname;
}
现在需要一个类来记录成员在比赛中的比分,所以可以将RatedPlayer
类从TableTennisPlayer
类派生而来,语法如下:
class RatedPlayer : public TableTennisPlayer{...
};
这里的public
表示TableTennisPlayer
是一个公有基类,这被称为公有派生,它有以下特性:
- 派生类对象存储了基类的数据成员,基类的公有成员将成为派生类的公有成员
- 基类的私有部分也将成为派生类的一部分,但只能通过基类的public或protected的方法访问
- 派生类对象可以使用基类的方法
在这个例子中,派生类需要一个变量rating
来存储比分,声明如下:
class RatedPlayer : public TableTennisPlayer{
private:unsigned int rating;
public:RatedPlayer (unsigned int r = 0, const string & fn = "none", const string & ln = "none", bool ht = false);RatedPlayer (unsigned int r, const TableTennisPlayer & tp);unsigned int Rating () const {return rating;} // add a methodvoid ResetRating (unsigned int r ) { rating = r; } // add a method
}
如上所示的两个RatedPlayer
所示,派生类的构造函数必须给新成员(如果有)和继承的成员提供数据。
RatedPlayer::RatedPlayer(unsigned int r, const string & fn, const string & ln, bool ht):TableTennisPlayer(fn, ln, ht)
{rating = r;
}
或
RatedPlayer::RatedPlayer(unsigned int r, const string & fn, const string & ln, bool ht):TableTennisPlayer(fn, ln, ht), rating(r){}
- 如果不写
TableTennisPlayer(fn, ln, ht)
,就将调用默认的构造函数。
1.2 派生类的特性和使用
- 基类对象应当在程序进入派生类构造函数之前被创建。
- 创建派生类对象时,程序首先调用基类构造函数,然后再调用派生类构造函数。
- 派生类对象销毁时,程序将首先调用派生类析构函数,然后再调用基类析构函数。
- 派生类可以使用基类的公有方法
- 基类指针可以在不进行显式类型转换的情况下指向派生类对象;基类引用可以在不进行显式类型转换的情况下引用派生类对象。但这样只能调用基类的方法。
RatedPlayer rplayer1(1140, "Mallory", "Duck", true);
TableTennisPlayer & rt = rplayer;
TableTennisPlayer * pt = & rplayer;
rt.Name(); // invoke Name() with reference
pt->Name(); // invoke Name() with pointer
- 同理,基类作为函数参数的时候,可以传派生类作为参数:
void Show(const TableTennisPlayer & rt) {using std::cout;cout << "Name: " <<rt.Name();
}TableTennisPlayer player1("Tara", "Boomdea", false);
RatedPlayer rplayer1(1140, "Mallory", "Duck" ,true);
Show(player1); // works with TableTennisPlayer argument
Show(rplayer1); // works with RatedPlayer argument
- 同理,也可以将派生对象赋给基类对象
RatedPlayer olaf1(1840, "Olaf", 'Loaf", true);
TableTenisPlayer winner;
winner = olaf1; // assign derived to base object
1.3 protected关键字
在C++中,protected
是一种访问控制修饰符,用于类的成员变量和成员函数,具有介于 public
和 private
之间的访问级别。protected
成员对于派生类是可见的,但对于外部类或对象不可见。
- 继承和派生类访问:
protected
成员在派生类中可访问,这意味着派生类可以继承和使用基类中的protected
成员,就像它们是自己的成员一样。 - 外部访问受限:对于外部类或对象,
protected
成员是受限制的,不能直接访问它们。只有在派生类内部才能访问。 - 类的封装性:
protected
成员的使用有助于实现类的封装性,因为它们不会对外部类或对象公开。只有继承了基类的派生类才能访问这些成员。
下面看一个例子来理解:
#include <iostream>class Base {
protected:int protectedVar;public:Base(int val) : protectedVar(val) {}
};class Derived : public Base {
public:Derived(int val) : Base(val) {}void printProtectedVar() {std::cout << "Value of protectedVar in Derived: " << protectedVar << std::endl;}
};int main() {Base baseObj(10);Derived derivedObj(20);// 在派生类内部可以访问基类的 protected 成员derivedObj.printProtectedVar();// 无法在外部访问基类的 protected 成员// baseObj.protectedVar; // 编译错误return 0;
}
1.4 友元的继承
有时候我们希望在派生类中调用基类的友元函数,这要怎么实现呢?下面来看一个例子,我们将创建一个基类 Shape
和一个派生类 Circle
,它们都有 <<
运算符重载函数。然后,我们将演示如何在派生类中调用基类的 <<
运算符重载函数。
class Shape {
private:int id;public:Shape(int i) : id(i) {}friend std::ostream& operator<<(std::ostream& os, const Shape& shape) {os << "Shape ID: " << shape.id;return os;}
};class Circle : public Shape {
private:double radius;public:Circle(int i, double r) : Shape(i), radius(r) {}friend std::ostream& operator<<(std::ostream& os, const Circle& circle) {os << static_cast<const Shape&>(circle); // 强制类型转换为基类os << ", Radius: " << circle.radius;return os;}
};int main() {Circle circle(1, 5.0);std::cout << circle << std::endl;return 0;
}
在这个示例中,Shape
类和 Circle
类都定义了 <<
运算符重载函数作为友元函数,以便在输出对象时自定义输出格式。在 Circle
类的运算符重载函数中,我们使用强制类型转换 static_cast
来将 Circle
对象转换为基类 Shape
,以便调用基类的 <<
运算符重载函数。这允许我们在输出 Circle
对象的同时输出基类的信息。
2 多态
有时我们希望同一个方法在派生类和基类中的行为是不同的,也就是说方法的行为取决于调用该方法的对象。这种行为称为多态,即一个方法具有多种形态。
下面来看一个例子,假设有一个基类Shape
,包含一个虚函数getArea()
,以及一个虚析构函数:
class Shape {
public:virtual double getArea() const {return 0.0;}virtual ~Shape() {std::cout << "Base class destructor" << std::endl;}
};
虚函数是C++中的一种特殊函数,它允许在派生类中重写基类中的同名函数,从而实现多态性。要将一个函数定义为虚函数,您需要在基类中使用virtual
关键字进行声明。这里的getArea()
函数也可以写成virtual double getArea() const = 0;
,表示这是一个纯虚函数。纯虚函数没有默认的实现,它要求派生类必须提供具体的实现。
Circle
和Rectangle
都是Shape
的派生类,它们重写了getArea()
函数,都具有计算面积的功能,并定义了各自的析构函数。
class Circle : public Shape {
private:double radius;public:Circle(double r) : radius(r) {}double getArea() const override {return 3.141592 * radius * radius;}~Circle() {std::cout << "Circle class destructor" << std::endl;}
};class Rectangle : public Shape {
private:double width;double height;public:Rectangle(double w, double h) : width(w), height(h) {}double getArea() const override {return width * height;}~Rectangle() {std::cout << "Rectangle class destructor" << std::endl;}
};
override
关键字用于明确指示正在重写基类中的虚函数,可提醒编译器进行检查是否存在虚函数,这个关键字可以省略,但推荐写上。
在main
函数中,我们创建了一个Circle
对象和一个Rectangle
对象,并将它们的指针存储在Shape
指针中。通过这种方式,我们可以使用基类指针来调用不同类型对象的相同函数,实现了多态性。
int main() {Shape* shape1 = new Circle(5.0);Shape* shape2 = new Rectangle(4.0, 6.0);// 使用多态性,调用不同类型对象的相同函数std::cout << "Area of shape1: " << shape1->getArea() << std::endl;std::cout << "Area of shape2: " << shape2->getArea() << std::endl;// 释放内存,注意虚析构函数的作用delete shape1;delete shape2;return 0;
}
程序的输出如下:
Area of shape1: 78.5398
Area of shape2: 24
Circle class destructor
Base class destructor
Rectangle class destructor
Base class destructor
因为基类Shape
的析构函数是虚函数,所以在使用基类指针删除派生类对象时,会先调用派生类析构函数,然后再调用基类的析构函数。
99%的人还看了
相似问题
猜你感兴趣
版权申明
本文"C++总结(4):继承和多态":http://eshow365.cn/6-28438-0.html 内容来自互联网,请自行判断内容的正确性。如有侵权请联系我们,立即删除!
- 上一篇: UE5 日记(人物连招:蒙太奇动画通知(含视频链接))
- 下一篇: 快速了解ClickHouse!