C++——6(多态&抽象类)
一、多态相关概念
多态:
一个接口,多种方法,在程序运行时才决定调用哪个函数,是面向对象编程的核心概念多态性:
将接口与实现进行分离,实现以共同的方法,但因个体差异不同,而采取不同的策略OOP(面向对象)的主要特征:
- 1.封装:wrap,实现了细节的隐藏,让代码模块化。类体{}的定义体现了封装
- 2.继承:Inherrit,扩展已存在的代码,实现了代码的复用。
- 3.多态:polymorphism,目的是为了接口的重用,不论传递的是哪个类的对象,都能通过同一接口,调用到适应各自对象的实现方法。
二、为何要用多态
- 1.有时候希望父类和子类,有相同的方法,但行为却有所不同。
三、多态如何实现
- 1.用虚函数实现多态性
四、虚函数
1.
概念:
用关键字
virtual
修饰的成员函数2.
虚函数的限制:
- (1)必须是类的
成员函数
,不能是静态的函数
。- (2)类的
构造函数
不能是虚函数,但析构函数
可以(虚析构函数)。- (3)基类函数用
virtual
关键字修饰,派生类的函数不需要修饰
。- (4)基类函数被声明为
virtual
虚函数,派生类中的同名函数(返回值、函数名、参数列表完全一致)自动成为虚函数
。3.
虚函数的作用:
- 实现多态(晚绑定,派生类对虚函数进行重写)
五、覆盖&重载&隐藏
1.
覆盖:override
派生类重新定义基类虚函数,也称为重写。
特点:
- (1)作用域不同:一个在
基类
,一个在派生类
。- (2)函数返回值和参数列表完全一致(
同名函数
)。- (3)访问
权限可以不同
。- (4)基类函数必须有
virtual
关键字修饰。// 基类:含虚函数 class Base { public: virtual void show() { // 虚函数 cout << "Base::show()" << endl; } }; // 派生类:覆盖基类虚函数 class Derived : public Base { public: void show(){ // 重写(覆盖)基类虚函数 cout << "Derived::show()" << endl; } };
2.
重载:overload
特点:
- (1)
作用域
必须相同。- (2)函数名相同,返回值不管。
- (3)参数列表不同。
- (4)无论有无
virtual
关键字修饰。class Demo { public: //示例函数 int func(int a) //作用域:Demo:: { return sum += a; } //重载func 参数列表必须不同 返回值可以不同 void func(int a , int b) //作用域:Demo:: { sum += a + b; } private: int sum; };
3.
隐藏(重定义):
特点:
- (1)
作用域
不同。- (2)
函数名
必须相同,- (3)
返回值
可以不同。- (4*)
参数不同时:
无论基类函数有无virtual
修饰,基类函数都会被派生类函数隐藏。- (5*)
参数相同时:
基类函数没有virtual
修饰,基类的函数会被派生类的函数隐藏。class Base { public: // 基类非虚函数(无virtual) void func() { cout << "Base::func()" << endl; } void func(int x) { // 基类重载版本 cout << "Base::func(int) " << x << endl; } }; class Derived : public Base { public: // 派生类同名函数(参数不同),隐藏基类所有func void func(double x) { cout << "Derived::func(double) " << x << endl; } // 派生类同名函数 (参数相同) 无virtual 隐藏 void func() { cout << "Derived::func(void)" << endl; } };
特性 覆盖(override) 重载(overload) 隐藏(重定义) 作用域 基类和派生类(不同作用域) 同一作用域 基类和派生类(不同作用域) 函数名 必须相同 必须相同 必须相同 参数列表 必须完全一致 必须不同(参数个数 / 类型 / 顺序) 参数不同时:基类函数直接被隐藏; 参数相同时:需基类函数无 virtual
才会隐藏返回值 必须完全一致 无要求(返回值可不同) 可以不同 基类函数 virtual
必须有 无要求(无论有无) 参数不同:无要求; 参数相同:基类函数没有 virtual
时,才会被隐藏核心规则 派生类重写基类虚函数 同一作用域内同名不同参的函数重载 派生类隐藏基类同名函数; 是否隐藏与参数、 virtual
修饰的组合有关访问权限 可以不同 -(同一作用域内,按声明权限) -
六、联编
1.
联编:
将程序代码需要的库或接口连接在一起
2.
分类:
静态联编:
静态
编译,称为早绑定
。在
编译阶段
将 函数的实现和使用 绑定在一起。会导致目标代码体积变大,但方便移植
动态联编:
动态
编译,称为晚绑定
。在
程序运行时
将函数的实现和使用 绑定在一起。目标代码体积不变,但不方便移植
七、虚函数表
C++引入虚函数表技术,来实现晚绑定,实现多态。
1.
虚函数表:
存放虚函数首地址的内存区域,虚函数表占对象头8个字节的空间(64位)
对象空间---代码示例:
/*=============================================== * 文件名称: * 创 建 者:青木莲华 * 创建日期:2025年09月02日 * 描 述: ================================================*/ #include <iostream> using namespace std; class Demo { public: Demo(){} virtual int getA(){ cout << __func__ << endl; return a; } //virtual 虚函数表占8字节 static int d; //静态成员不占对象空间,属于类 private: int a = 10; char b = 65; }; int Demo::d = 20; int main(int argc, char *argv[]) { cout << "Size of Demo class is " << sizeof(Demo) << endl; //==> 16 Demo obj; cout << *((int *)&obj + 2) << endl; //==> a cout << *((char *)&obj + 12) << endl; //==> b #if 0 int *out = (int *)&obj + 2; *out = 123; cout << *out << endl; cout << obj.getA() << endl; #endif long long ptr = *(long long *)&obj; //取对象头8字节(虚函数表首地址) long long *vtab = (long long *)ptr; //数值转为 虚函数表首地址 long long vfunc = *vtab; //得到虚函数表 存放的虚函数地址的数值 void (*func)() = (void (*)())vfunc; //转函数指针 func(); // ===> getA() return 0; }
运行结果:
八、抽象类
1.
抽象类:
含有纯虚函数的类,就是抽象类
2.
纯虚函数:
//纯虚函数声明时,末尾赋值为0,没有定义 virtual void func() = 0;
3.
抽象类的特点:
- (1)抽象类
没有完整
的信息,因此不能有实例
(对象)- (2)抽象类只能是派生类的
基类
,不能有static
成员- (3)派生类应该
实现抽象类的所有方法
练习
设计一个图形类,通过图形类计算三角形、圆、矩形的面积
/*=============================================== * 文件名称:4_abstract.cpp * 创 建 者:青木莲华 * 创建日期:2025年09月02日 * 描 述:抽象类 ================================================*/ #include <iostream> using namespace std; #define PI 3.1415926 class Graphic //抽象类 图形 { public: virtual ~Graphic() = 0; //纯虚析构函数 virtual float getArea() = 0; //纯虚函数 }; Graphic::~Graphic(){} //纯虚析构函数定义 class Tranangle : public Graphic //派生类 三角形 { public: Tranangle(){} Tranangle(int w ,int h):w(w) , h(h){} float getArea() { return w*h/2; } private: int w,h; }; class Circle : public Graphic //派生类 圆形 { public: Circle(){} Circle(int r):r(r){} float getArea() { return r*r*PI; } private: int r; }; class Rectangle : public Graphic { public: Rectangle(){} Rectangle(int l , int w):l(l) , w(w){} float getArea() { return l*w; } private: int l , w; }; int main() { Graphic *gh = new Tranangle(6,8); cout << "Tranangle's area : " << gh->getArea() << endl; gh = new Circle(10); cout << "Circle's area : " << gh->getArea() << endl; gh = new Rectangle(5,6); cout << "Rectangle's area : " << gh->getArea() << endl; }
九、虚析构函数
1.
目的:
解决基类指针指向的派生类对象,delete基类指针的时候,导致派生类对象资源回收不完整的情况。
2.在基类析构函数声明前,用
virtual
修饰
纯虚析构函数
1.一个抽象类的虚构函数,不清楚回收什么资源,而且也没有什么资源可以回收。为了保证解决:delete基类指针的时候,导致派生类对象资源回收不完整的情况 , 引入了
纯虚析构函数
2.
用法声明:
class Demo { //声明在类体内 virtual ~Demo() = 0; }; //定义必须在类体外,不定义会报错 Demo::~Demo(){}
十、限制构造
1.
限制构造:
构造函数的权限为非
public
权限,不能通过该类直接定义对象,只能使用该类的特殊接口,或者通过该类派生子类来使用。2.
目的:
保护设计的类的安全性
3.
限制构造应用:
C++设计模式——单例模式
(1)
单例模式:
当前程序中,该类的对象只会被构造一次
(2)
分类:
- 懒汉式:在第一次调用静态接口时,创建对象实例,线程中不安全(需用互斥锁)
- 饿汉式:在程序运行的第一时间,就创建对象,而非调用静态接口,线程中安全,但是会造成资源浪费
示例代码:
/*=============================================== * 文件名称:6_singleton.cpp * 创 建 者:青木莲华 * 创建日期:2025年09月02日 * 描 述:单例类 ================================================*/ #include <iostream> using namespace std; /** * 懒汉式单例: * 1.构造函数私有化(限制构造) * 2.定义私有的静态的当前对象指针作为成员 * 3.类体外对该对象指针初始化为nullptr * 4.定义一个公有的 静态成员函数,返回该对象指针 */ class Singleton { static Singleton *instance; //静态指针 Singleton(){} public: static Singleton *getInstance() { if(instance == nullptr) //确保只初始化1次 instance = new Singleton(); return instance; } }; //类体外定义 Singleton* Singleton::instance = nullptr; /** * 饿汉式单例: * 1.构造函数私有化(限制构造) * 2.定义私有静态指针作为成员 * 3.类体外为指针初始化为new Singleton_hungry() * 4.定义一个公有静态成员函数接口,返回该成员(静态指针) */ class Singleton_hungry { static Singleton_hungry *instance_h; Singleton_hungry(){} public: static Singleton_hungry *getInstance_h() { return instance_h; } }; Singleton_hungry* Singleton_hungry::instance_h = new Singleton_hungry(); int main(int argc, char *argv[]) { Singleton *s1 = Singleton::getInstance(); Singleton *s2 = Singleton::getInstance(); cout << s1 << endl; cout << s2 << endl; Singleton_hungry *sh1 = Singleton_hungry::getInstance_h(); Singleton_hungry *sh2 = Singleton_hungry::getInstance_h(); cout << sh1 << endl; cout << sh2 << endl; return 0; }
运行截图: