c++学习笔记(13) 继承和多态
继承:从已有的类派生出新类
基类和派生类
不同的类之间可能有很多共通的属性和行为,这些属性和行为允许在一个类中通用化并被其他类所共享。类C1从类C2扩展而来,则C1称为派生类,C2称为基类,基类也称为父类或者超类,派生类称为子类,一个子类继承了其父类所有可访问的数据域和函数。同时可以增加新的数据域和函数
例如:
定义基类geometric,在此基础上生成circle类:
基类Geometric:
geometric.h文件
#ifndef GEOMETRIC_H #define GEOMETRIC_H #include <string> using namespace std; class Geometric { private: string color; bool filled; public: Geometric(); Geometric(const string& color, bool filled); string getColor() const; void setColor(const string& color); bool isFilled() const; void setFilled(bool state); string toString() const; }; #endif
geometric.cpp文件
#include <string> #include <iostream> #include "E:\back_up\code\c_plus_code\chapter15\external_file\geometric.h" using namespace std; Geometric::Geometric() { color = "white"; filled = true; } Geometric::Geometric(const string& color, bool filled) { this->color = color; this->filled = filled; } string Geometric::getColor() const { return color; } void Geometric::setColor(const string& color) { this->color = color; } bool Geometric::isFilled() const { return filled; } void Geometric::setFilled(bool state) { filled = state; } string Geometric::toString() const { return "Geometric object"; }
派生类Circle
circle.h文件:
#ifndef CIRCLE_H #define CIRCLE_H #include "E:\back_up\code\c_plus_code\chapter15\external_file\geometric.h" #include <string> class Circle: public Geometric // 表明circle类都是从Geomerric类中派生出来的 Circle继承了所有的公有成员 { private: double radius; public: Circle(); Circle(double radius); Circle(double radius, const string& color, bool filled); double getRadius() const; void setRadius(double radius); double getArea() const; // 获取面积 double getPerimeter() const; // 获取周长 double getDiameter() const; // 直径 string toString() const; }; #endif
circle.cpp文件
#include <string> #include <iostream> #include "E:\back_up\code\c_plus_code\chapter15\external_file\circle.h" using namespace std; Circle::Circle() { radius = 1; } Circle::Circle(double radius) { this->radius = radius; } Circle::Circle(double radius, const string& color, bool filled) { //this->radius = radius; //this->color = color; // 这种写法是错误的 //this->filled = filled; // 这种写法是错误的 // 除了在Geomertic类中,它的私有成员color和filled无法在其他任何类中被访问。 // 唯一的方法是通过调用get()和set()进行访问 setRadius(radius); setColor(color); // 继承的成员函数 setFilled(filled); // 继承的成员函数 } double Circle::getRadius() const { //return radius; return (radius>0)?radius:0; } void Circle::setRadius(double radius) { this->radius = radius; } double Circle::getArea() const { return 3.14*radius*radius; } double Circle::getDiameter() const { return 2*radius; } double Circle::getPerimeter() const { return 2*3.14*radius; } string Circle::toString() const { return "Circle object"; }
main.cpp文件
int main(int argc, char *argv[]) { Circle circle1(4.5, "black", true); cout << "Circle1 radius is " << circle1.getRadius() << " and color is " << circle1.getColor() << " and area is " << circle1.getArea() << endl; if(circle1.isFilled()) { circle1.setColor(false); } return 0; }
总结:
1.基类中的私有数据域不能在基类以外被访问,因此在派生类中不能直接使用,但可以通过基类中定义的accessor和mutator来访问和修改它们。
2.继承用来建模is-a关系:
3.c++允许同时扩展多个类来得到一个派生类,该功能被称为多重继承。
泛型程序设计
当程序中需要一个基类对象时,向其提供一个这个基类的派生类对象是允许的。这种特性使得一个函数可以适用于较大范围的对象实参,变得更加通用,称之为泛型程序设计
例如:
void displayGeometric(const Geometric& shape)
{
cout << shape.getColor() << endl;
}
函数displayGeometric()的参数类型是Geometric,但是我们可以向它传递Geometric类的派生类作为参数也可以。
例如:
displayGeometric(Circle(5));
派生类的构造函数和析构函数:
派生类的构造函数在执行自身代码之前,首先调用它的基类的构造函数(派生类不继承基类ide构造函数,仅仅是调用而已)。派生类的析构函数首先执行其自身的代码,然后自动调用其基类的析构函数(不继承,同样是调用)。
1.调用基类构造函数
派生类中的构造函数总是显式或者隐式的调用基类中的构造函数。如果没有被显式调用,则会默认调用基类中无参的构造函数
例如:
Circle::Circle(double radius, const string& color, bool filled):Geometric(color, filled) { setRadius(radius); }
构造函数链和析构函数链:
这里用a->b表示b继承a:
假设a->b->c->d, 则在实例化d对象时,d的构造函数执行前,会调用a的构造函数,而a的构造函数执行前,会调用b的构造函数,以此类推,就会按照继承关系形成一个链,称为构造函数链,而析构函数链与构造函数链正好相反。
tips: 如果一个类可能被继承,则最好为它设计一个无参的构造函数
tips: 如果基类中有自定义的拷贝构造函数和赋值操作,因该在派生类中自定义这些来保证基类中的数据域被正确拷贝。
函数重定义
在基类中定义的函数能够在派生类中可以被重新定义。需要在派生类的头文件中添加函数原型,并在派生类的实现文件中提供函数新的实现。
例如:toStrng()函数
如果在子类中任然需要调用父类中的函数,则需要基类名和作用域解析运算符
例如:
cout << circle1.Geometric::toString(); // 在circle对象中调用基类中的toString()函数
函数重载和函数重定义的区别:
函数重载提供多个名字相同,但签名不同的函数。
函数重定义:必须在派生类中定义一个与基类中函数具有相同签名和返回类型的函数。
多态
多态意味着一个超类型的变量可以引用一个子类型的对象!(对比泛型编程的概念)
OOP三支柱: 封装, 继承性, 多态
一个类定义一种类型:基类称为超类型(subtype),派生类称为子类型(supertype)。
函数dislpayGeometric()的参数类型为Geometric, 但是在调用的函数的时候可以传入circle类型的对象。 这就是多态的概念。
#include <iostream> #include <string> //#include "E:\back_up\code\c_plus_code\chapter15\external_file\geometric.h" #include "E:\back_up\code\c_plus_code\chapter15\external_file\circle.h" using namespace std; void displayGeometric(const Geometric& g) { cout << g.toString() << endl; } int main(int argc, char *argv[]) { Circle circle1(4.5, "black", true); displayGeometric(circle1); // 超类型的变量引用子类型的对象 return 0; }
虚函数和动态绑定
一个函数可以在沿着继承关系链的多个类中实现,虚函数使得系统能够基于对象的实际类型决定在运行时调用哪一个函数。
#include <iostream> #include <string> //#include "E:\back_up\code\c_plus_code\chapter15\external_file\geometric.h" #include "E:\back_up\code\c_plus_code\chapter15\external_file\circle.h" using namespace std; void displayGeometric(const Geometric& g) { cout << g.toString() << endl; } int main(int argc, char *argv[]) { Circle circle1(4.5, "black", true); Geometric g1; displayGeometric(g1); displayGeometric(circle1); // 超类型的变量引用子类型的对象 return 0; }
上面的代码的输出结果如图所示:
在Geometric类和circle类总都定义了toString()函数,但是在语句:
displayGeometric(g1); displayGeometric(circle1); // 超类型的变量引用子类型的对象
被执行时,调用的都是Geometric类中的toString()函数
如何使得在传入相应类型的参数时,使得相应的类中的toString()函数被调用,而不是都调用Geometric类中的toString()函数?
虚拟函数就可以实现这一需求:
virtual string toString() const;
在基类中,函数toString()被定义成 virtual 类型,c++在运行时动态决定调用哪一个函数。这种功能叫做动态绑定。
实例:
如果不使用虚函数,调用Geometric, circle, Rectangle三个类对象的toString()方法,输出的都是“Geometric object”
将基类的toString()函数定义为虚函数后,实现了动态绑定,输出的结果为:
总结:
1.要实现动态绑定,函数在基类中要声明为虚函数。而在派生类中不必再声明为虚函数
2. 引用对象的变量必须以引用或者指针的形式传递
3.动态绑定与匹配函数签名是两个独立的问题:编译器根据参数类型,参数个数以及参数顺序在编译时寻找匹配的函数,这是静态绑定,用于匹配函数签名。 动态绑定是由变量所引用的对象的实际类型所决定的,这是动态绑定,一个虚函数可以在多个派生类中实现,C++在运行时动态绑定这些函数的实现。
-------------------------------------------分割线------------------------------------------------------
关键字protected
基类中定义的数据域和函数经常需要允许派生类访问而不允许非派生类访问,为此可以使用关键字protected。
抽象类和纯虚函数
在类的继承层次中,基类到派生类,类的内容越来越具体和明确,从派生类到基类,则越来越一般化和不具体。
在设计类时应该确保基类包含其派生类的共同特性。
例如:
Gemometric类定义几何图形的基类,描述几何对象的公共属性,例如派生类 circle,rectangle类都会包含getArea()函数和getPerimeter()函数,因为所有的几何对象都可以计算面积和周长,因此按照类的设计原则应该在基类Gemometric中声明getArea()和getPerimeter()函数。但是这个函数不能在Gemometric类中实现,因为其实现和具体的几何对象有关。
这样的函数就成为抽象函数,基类Gemometric称为抽象类
纯虚函数的声明方法:
virtual double getArea() const = 0; // 纯虚函数 virtual double getPerimeter() const = 0; // 纯虚函数
注(一个需要注意的地方)
当定义了基类中toString()函数为纯虚函数后,此时的Geometric类为一个抽象类,不能再声明Geometric对象!!!!!
在基类中定义了纯虚函数,就可以实现两个不同派生类之间的一些操作了
例如:
需要比较circle和rectangle之间的面积大小:
定义如下函数即可:
bool equalArea(const Geometric& g1, const Geometric& g2) { return g1.getArea()==g2.getArea(); }
关于纯虚函数的实现部分,这里代码没有报错,但是执行结果总是不对,贴上来代码:
基类:Geometric
Geometric.h文件
#ifndef GEOMETRIC_H #define GEOMETRIC_H #include <string> using namespace std; class Geometric { private: string color; bool filled; public: Geometric(); Geometric(const string& color, bool filled); string getColor() const; void setColor(const string& color); bool isFilled() const; void setFilled(bool state); virtual string toString() const; // 虚函数,实现动态绑定 virtual double getArea() const = 0; // 纯虚函数 virtual double getPerimeter() const = 0; // 纯虚函数 }; #endif
Geometric.cpp文件
#include <string> #include <iostream> #include "E:\back_up\code\c_plus_code\chapter15\external_file\geometric.h" using namespace std; Geometric::Geometric() { color = "white"; filled = true; } Geometric::Geometric(const string& color, bool filled) { this->color = color; this->filled = filled; } string Geometric::getColor() const { return color; } void Geometric::setColor(const string& color) { this->color = color; } bool Geometric::isFilled() const { return filled; } void Geometric::setFilled(bool state) { filled = state; } string Geometric::toString() const { return "Geometric object"; }
派生类Circle
circle.h
#ifndef CIRCLE_H #define CIRCLE_H #include "E:\back_up\code\c_plus_code\chapter15\external_file\geometric.h" #include <string> using namespace std; class Circle: public Geometric // 表明circle类都是从Geomerric类中派生出来的 Circle继承了所有的公有成员 { private: double radius; public: Circle(); Circle(double radius); Circle(double radius, const string& color, bool filled); double getRadius() const; void setRadius(double radius); double getArea() const; // 获取面积 double getPerimeter() const; // 获取周长 double getDiameter() const; // 直径 string toString() const; // 将派生类中的函数重定义 }; #endif
circle.cpp
#include <string> #include <iostream> #include "E:\back_up\code\c_plus_code\chapter15\external_file\circle.h" using namespace std; Circle::Circle() { radius = 1; } Circle::Circle(double radius) { this->radius = radius; } Circle::Circle(double radius, const string& color, bool filled) { //this->radius = radius; //this->color = color; // 这种写法是错误的 //this->filled = filled; // 这种写法是错误的 // 除了在Geomertic类中,它的私有成员color和filled无法在其他任何类中被访问。 // 唯一的方法是通过调用get()和set()进行访问 setRadius(radius); setColor(color); // 继承的成员函数 setFilled(filled); // 继承的成员函数 } double Circle::getRadius() const { //return radius; return (radius>0)?radius:0; } void Circle::setRadius(double radius) { this->radius = radius; } double Circle::getArea() const { return 3.14*radius*radius; } double Circle::getDiameter() const { return 2*radius; } double Circle::getPerimeter() const { return 2*3.14*radius; } string Circle::toString() const // 提供重定义函数的实现 { return "Circle object"; }
派生类rectangle
rectangle.h
#ifndef RECTANGLE_H #define RECTANGLE_H #include <iostream> #include <string> #include "E:\back_up\code\c_plus_code\chapter15\external_file\geometric.h" using namespace std; class Rectangle: public Geometric { private: double width; double height; public: Rectangle(); //无参构造函数 Rectangle(double width, double height); Rectangle(double width, double height, const string& color, bool filled); // accessor double getWidth() const; double getHeight() const; double getArea() const; double getPerimeter() const; // mutator void setWidth(double width); void setHeight(double height); string toString() const; //void setFilled(bool filled); //void setColor(const string& color); }; #endif
rectangle.cpp
#include <iostream> #include <string> #include "E:\back_up\code\c_plus_code\chapter15\external_file\rectangle.h" using namespace std; Rectangle::Rectangle() { width = 1; height = 1; } Rectangle::Rectangle(double width, double height) { setWidth(width); setHeight(height); } Rectangle::Rectangle(double width, double height, const string& color, bool filled) { setWidth(width); setHeight(height); setColor(color); setFilled(filled); } double Rectangle::getWidth() const { return width; } double Rectangle::getHeight() const { return height; } double Rectangle::getArea() const { return width*height; } double Rectangle::getPerimeter() const { return 2*(width+height); } void Rectangle::setWidth(double width) { this->width = width; } void Rectangle::setHeight(double height) { this->height = height; } string Rectangle::toString() const { return "Rectangle object"; }
主函数
main.cpp
#include <iostream> #include <string> #include "E:\back_up\code\c_plus_code\chapter15\external_file\geometric.h" #include "E:\back_up\code\c_plus_code\chapter15\external_file\circle.h" #include "E:\back_up\code\c_plus_code\chapter15\external_file\rectangle.h" using namespace std; void displayGeometric(const Geometric& g) { cout << g.toString() << endl; } bool equalArea(const Geometric& g1, const Geometric& g2) { return g1.getArea()==g2.getArea(); } int main(int argc, char *argv[]) { Circle circle1(4.5, "black", true); //Geometric g1; Rectangle rec1; //displayGeometric(g1); displayGeometric(circle1); // 超类型的变量引用子类型的对象 displayGeometric(rec1); cout << "rec1 area is " << rec1.getArea() << endl; cout << equalArea(circle1, rec1); cout << "circle area is " << circle1.getArea() << endl; //cout << "The circle and rectangle area is equal? " << ((equalArea(circle1, rec1))?"Yes":"No") << endl; return 0; }
遇到的问题是代码不能执行
cout << equalArea(circle1, rec1);
这一句之后的代码都执行不了,到这一句直接显示“按任意键结束”???
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)