23.面向对象-多态-虚函数
23.1 知识点
知识回顾
封装:用程序语言来形容对象
类和对象相关
class Player
{
// 访问修饰符(3p):
// 特征——成员变量
// 行为——成员方法
// 初始化调用——构造函数
// 释放时调用——析构函数
};
修饰类成员的关键字
- 静态成员
- const成员
- 友元函数
- 友元类
- 友元成员函数
其他封装机制
- 嵌套类
- 命名空间
- 运算符重载
继承:复用封装对象的代码;儿子继承父亲,复用现成代码
继承的基本规则
继承后的构造函数和析构函数
多重继承
继承中的友元和运算符重载
里氏替换原则
多态的概念
- 多态按字面意思就是“多种形态”
- 让继承同一父类的子类们在执行相同方法时有不同的表现(状态)
- 主要目的:同一父类的对象执行相同行为(方法)有不同的表现
- 解决的问题:让同一个对象有唯一行为的特征
解决的问题
如果不使用多态:
- new出子类用父类装,执行的还是父类的方法
- 除非用
dynamic_cast
强转,同时强转时还需保证父类有虚函数
希望的效果是 new 出子类用父类装,执行的直接是子类的方法
#pragma once
#include <iostream>
using namespace std;
class Father
{
public:
virtual void Test()
{
}
void SpeakName()
{
cout << "Father的方法" << endl;
}
};
#pragma once
#include "Father.h"
class Son : public Father
{
public:
void SpeakName()
{
cout << "Son的方法" << endl;
}
};
Father* f = new Son();
f->SpeakName(); // Father的方法
Son* s = dynamic_cast<Son*>(f);
s->SpeakName(); // Son的方法
多态的实现
已学的多态:编译时多态(函数重载、运算符重载)
将学的:运行时多态
实现运行时多态的关键词:
- v:virtual(虚函数)
- o:override(推翻重写)
v 和 o 一定是结合使用的来实现多态
#pragma once
#include <iostream>
using namespace std;
class GameObject
{
public:
string name;
GameObject(string name)
{
this->name = name;
}
virtual void Atk()
{
cout << "游戏对象进行攻击" << endl;
}
};
#pragma once
#include "GameObject.h"
class Player : public GameObject
{
public:
Player(string name) : GameObject(name) {}
void Atk() override
{
cout << "玩家" << name << "进行攻击" << endl;
}
};
#pragma once
#include "GameObject.h"
class Monster : public GameObject
{
public:
Monster(string name) : GameObject(name) {}
void Atk() override
{
cout << "怪物" << name << "进行攻击" << endl;
}
};
GameObject* player = new Player("韬老狮");
GameObject* monster = new Monster("小申怪物");
player->Atk(); // 玩家韬老狮进行攻击
monster->Atk(); // 怪物小申怪物进行攻击
// 栈上分配的对象,存在对象切片时就不会执行子类中重写的方法,所以一般我们不会这样使用
GameObject player2 = Player("韬老狮2");
player2.Atk(); // 游戏对象进行攻击
// 栈上分配,但用引用父类容器装载,不会对象切片,可执行子类中重写的方法
Player p2 = Player("韬老狮3");
GameObject& g = p2;
g.Atk(); // 玩家韬老狮3进行攻击
如何保留父类逻辑
- 如果想在重写函数中保留父类逻辑:通过
父类名::方法名
显式调用父类逻辑 - 如果想在外部调用,同理
#pragma once
#include "GameObject.h"
class Boss : public GameObject
{
public:
Boss(string name) : GameObject(name) {}
void Atk() override
{
GameObject::Atk();
cout << "Boss" << name << "进行攻击" << endl;
}
};
GameObject* boss = new Boss("哈莫雷特");
boss->Atk(); // Boss哈莫雷特进行攻击
player->GameObject::Atk(); // 游戏对象进行攻击
override 可以省略
virtual
不可以省略,override
是可以省略的但极其不建议省略
原因:
- 拼写错误:拼错重写方法名会有报错提示
- 结构错误:如多写参数会有报错提示
总结
多态:让同一类型的对象,执行相同行为时有不同的表现
解决的问题:让同一对象有唯一的行为特征
vo:
- v:virtual 虚函数
- o:override 推翻重写
v 和 o 一定是配合使用的来实现多态
如果想保留父类逻辑,可以显式调用父类方法
父类名::方法名
override
是可以省略的,但极不建议,因为可能出现不必要的问题
23.2 知识点代码
Father.h
#pragma once
#include <iostream>
using namespace std;
class Father
{
public:
virtual void Test()
{
}
void SpeakName()
{
cout << "Father的方法" << endl;
}
};
Son.h
#pragma once
#include "Father.h"
class Son : public Father
{
public:
void SpeakName()
{
cout << "Son的方法" << endl;
}
};
GameObject.h
#pragma once
#include <iostream>
using namespace std;
class GameObject
{
public:
string name;
GameObject(string name)
{
this->name = name;
}
virtual void Atk()
{
cout << "游戏对象进行攻击" << endl;
}
};
Player.h
#pragma once
#include "GameObject.h"
class Player : public GameObject
{
public:
Player(string name) : GameObject(name)
{
}
void Atk() override
{
//GameObject::Atk();
cout << "玩家" << name << "进行攻击" << endl;
}
};
Monster.h
#pragma once
#include "GameObject.h"
class Monster :
public GameObject
{
public:
Monster(string name) : GameObject(name)
{
}
void Atk() override
{
cout << "怪物" << name << "进行攻击" << endl;
}
};
Boss.h
#pragma once
#include "GameObject.h"
class Boss : public GameObject
{
public:
Boss(string name) : GameObject(name)
{
}
void Atk() override
{
GameObject::Atk();
cout << "Boss" << name << "进行攻击" << endl;
}
};
Lesson23_面向对象_多态_虚函数.cpp
#include <iostream>
#include "Son.h"
#include "Player.h"
#include "Monster.h"
#include "Boss.h"
using namespace std;
int main()
{
#pragma region 知识回顾
#pragma region 封装:用程序语言来形容对象
#pragma region 类和对象相关
/*class Player
访问修饰符(3p):
特征——成员变量
行为——成员方法
初始化调用——构造函数
释放时调用——析构函数
};*/
#pragma endregion
#pragma region 修饰类成员的关键字
/*静态成员
const成员
友元函数
友元类
友元成员函数*/
#pragma endregion
#pragma region 其他封装机制
//嵌套类
//命名空间
//运算符重载
#pragma endregion
#pragma endregion
#pragma region 继承:复用封装对象的代码;儿子继承父亲,复用现成代码
#pragma region 继承的基本规则
#pragma endregion
#pragma region 继承后的构造函数和析构函数
#pragma endregion
#pragma region 多重继承
#pragma endregion
#pragma region 继承中的友元和运算符重载
#pragma endregion
#pragma region 里氏替换原则
#pragma endregion
#pragma endregion
#pragma endregion
#pragma region 知识点一 多态的概念
// 多态按字面的意思就是“多种形态”
// 让继承同一父类的子类们 在执行相同方法时有不同的表现(状态)
// 主要目的
// 同一父类的对象 执行相同行为(方法)有不同的表现
// 解决的问题
// 让同一个对象有唯一行为的特征
#pragma endregion
#pragma region 知识点二 解决的问题
// 现在如果不使用多态
// new出子类用父类装 执行的还是父类的方法
// 除非用dynamic_cast强转 同时强转的时候还需要保证父类有虚函数
// 希望的效果是new出子类用父类装 执行的直接是子类的方法
Father* f = new Son();
f->SpeakName();//Father的方法
Son* s = dynamic_cast<Son*>(f);
s->SpeakName();//Son的方法
#pragma endregion
#pragma region 知识点三 多态的实现
//我们目前已经学过的多态
//编译时多态——函数重载,运算符重载,一开始就写好的
//我们将学习的:
//运行时多态
//实现运行时多态的关键词是:
//v:virtual (虚函数)
//o:override (推翻重写)
//v和o一定是结合使用的 来实现多态
GameObject* player = new Player("韬老狮");
GameObject* monster = new Monster("小申怪物");
player->Atk();//玩家韬老狮进行攻击
monster->Atk();//怪物小申怪物进行攻击
//栈上分配的这种对象 存在对象切片时 就不会执行子类中重写的方法 所以一般我们不会这样使用
GameObject player2 = Player("韬老狮2");
player2.Atk();//游戏对象进行攻击
//栈上分配,但是用引用父类容器装载,是不会对象切片的 是可以执行子类中重写的方法的
Player p2 = Player("韬老狮3");
GameObject& g = p2;
g.Atk();//玩家韬老狮3进行攻击
#pragma endregion
#pragma region 知识点四 如何保留父类逻辑
//1.如果想要在重写函数中保留父类逻辑
// 只需要通过 父类名::方法名 显示调用父类逻辑即可
//2.如果想要在外部调用,同理
GameObject* boss = new Boss("哈莫雷特");
boss->Atk();//Boss哈莫雷特进行攻击
player->GameObject::Atk();//游戏对象进行攻击
#pragma endregion
#pragma region 知识点五 override可以省略
//virtual不可以省略 override是可以省略的
//但是极其不建议省略
//因为省略可能由于拼写错误或者结构错误而出现问题
//拼写错误:拼错重写方法名会有报错提示
//结构错误:比如多写了些参数会有报错
#pragma endregion
//总结
//多态:让同一类型的对象,执行相同行为时有不同的表现
//解决的问题:让同一对象有唯一的行为特征
//vo:
//v:virtual 虚函数
//o:override 推翻重写
//v和o一定是配合使用的 来实现我们的多态
//但是想要保留父类逻辑的话,我们可以显示的去调用父类方法即可 父类名::方法名
//override是可以省略的,但是极其不建议大家省略 因为可能出现不必要的问题
}
23.3 练习题
真的鸭子嘎嘎叫,木头鸭子吱吱叫,橡皮鸭子唧唧叫
这是一个典型的继承与多态应用示例,通过基类指针调用子类重写的 Speak
方法,模拟不同鸭子的叫声行为。
#include "Duck.h"
#include <iostream>
using namespace std;
void Duck::Speak()
{
cout << "嘎嘎" << endl; // 真实的鸭子发出“嘎嘎”的叫声
}
#include "WoodDuck.h"
#include <iostream>
using namespace std;
void WoodDuck::Speak()
{
cout << "吱吱" << endl; // 木头鸭子发出“吱吱”的叫声
}
#include "RubberDuck.h"
#include <iostream>
using namespace std;
void RubberDuck::Speak()
{
cout << "唧唧" << endl; // 橡皮鸭子发出“唧唧”的叫声
}
调用示例:
Duck* duck = new Duck(); // 创建真实鸭子对象
Duck* woodDuck = new WoodDuck(); // 创建木头鸭子对象
Duck* rubberDuck = new RubberDuck(); // 创建橡皮鸭子对象
duck->Speak(); // 输出:嘎嘎
woodDuck->Speak(); // 输出:吱吱
rubberDuck->Speak(); // 输出:唧唧
delete duck;
duck = nullptr;
delete woodDuck;
woodDuck = nullptr;
delete rubberDuck;
rubberDuck = nullptr;
多态解释: 父类指针指向子类对象,通过虚函数机制,正确地调用到子类重写的方法,实现行为多样性。
所有员工9点打卡,但经理十一点打卡,程序员不打卡
这是一个模拟不同职位员工行为差异的继承结构与重写方法的示例。
#include "Staff.h"
#include <iostream>
using namespace std;
void Staff::PunchTheClock()
{
cout << "9点打卡" << endl; // 所有普通员工默认9点打卡
}
#include "Manager.h"
#include <iostream>
using namespace std;
void Manager::PunchTheClock()
{
cout << "11点打卡" << endl; // 经理特殊,11点打卡
}
#include "Programmer.h"
#include <iostream>
using namespace std;
void Programmer::PunchTheClock()
{
cout << "老子不打卡" << endl; // 程序员个性张扬,不打卡
}
继承结构说明:
Manager
与Programmer
都继承自Staff
- 重写了
PunchTheClock()
方法实现不同的打卡逻辑 - 体现了子类可根据自身特点覆盖父类方法的特点
创建一个图形类,有求面积和周长两个方法,创建矩形类、正方形类、圆形类继承图形类,实例化对象并求面积和周长
该示例展示了面向对象编程中多态和继承的结合使用,通过基类 Graph
的指针,调用不同子类对面积和周长的实现。
矩形类实现:
#include "Rect.h"
Rect::Rect(float w, float h)
{
this->w = w;
this->h = h;
}
float Rect::GetLength()
{
return (w + h) * 2; // 周长公式:2 × (宽 + 高)
}
float Rect::GetArea()
{
return w * h; // 面积公式:宽 × 高
}
正方形类实现:
#include "Square.h"
Square::Square(float l)
{
this->l = l;
}
float Square::GetLength()
{
return l * 4; // 周长公式:边长 × 4
}
float Square::GetArea()
{
return l * l; // 面积公式:边长²
}
圆形类实现:
#include "Circular.h"
Circular::Circular(float r)
{
this->r = r;
}
float Circular::GetLength()
{
return 2 * 3.14f * r; // 周长公式:2πr
}
float Circular::GetArea()
{
return 3.14f * r * r; // 面积公式:πr²
}
调用示例:
Graph* r = new Rect(4, 5);
cout << r->GetLength() << endl; // 输出:18
cout << r->GetArea() << endl; // 输出:20
delete r;
r = nullptr;
r = new Square(5);
cout << r->GetLength() << endl; // 输出:20
cout << r->GetArea() << endl; // 输出:25
delete r;
r = nullptr;
r = new Circular(3);
cout << r->GetLength() << endl; // 输出:18.84
cout << r->GetArea() << endl; // 输出:28.26
delete r;
r = nullptr;
输出结果:
18
20
20
25
18.84
28.26
设计总结:
Graph
作为抽象基类(或通用接口)定义公共行为Rect
、Square
、Circular
分别继承并实现不同的逻辑- 多态机制使得通过统一接口操作不同图形对象成为可能
- 实现了统一调用,多种实现的典型面向对象思想
23.4 练习题代码
Duck.h
#pragma once
class Duck
{
public:
virtual void Speak();
};
Duck.cpp
#include "Duck.h"
#include <iostream>
using namespace std;
void Duck::Speak()
{
cout << "嘎嘎" << endl;
}
WoodDuck.h
#pragma once
#include "Duck.h"
class WoodDuck :
public Duck
{
public:
void Speak() override;
};
WoodDuck.cpp
#include "WoodDuck.h"
#include <iostream>
using namespace std;
void WoodDuck::Speak()
{
cout << "֨֨" << endl;
}
RubberDuck.h
#pragma once
#include "Duck.h"
class RubberDuck :
public Duck
{
public:
void Speak() override;
};
RubberDuck.cpp
#include "RubberDuck.h"
#include <iostream>
using namespace std;
void RubberDuck::Speak()
{
cout << "唧唧" << endl;
}
Staff.h
#pragma once
class Staff
{
public:
virtual void PunchTheClock();
};
Staff.cpp
#include "Staff.h"
#include <iostream>
using namespace std;
void Staff::PunchTheClock()
{
cout << "9点打卡" << endl;
}
Manager.h
#pragma once
#include "Staff.h"
class Manager :
public Staff
{
public:
void PunchTheClock() override;
};
Manager.cpp
#include "Manager.h"
#include <iostream>
using namespace std;
void Manager::PunchTheClock()
{
cout << "11点打卡" << endl;
}
Programmer.h
#pragma once
#include "Staff.h"
class Programmer :
public Staff
{
public:
void PunchTheClock() override;
};
Programmer.cpp
#include "Programmer.h"
#include <iostream>
using namespace std;
void Programmer::PunchTheClock()
{
cout << "老子不打卡" << endl;
}
Graph.h
#pragma once
class Graph
{
public:
virtual float GetLength()
{
return 0;
}
virtual float GetArea()
{
return 0;
}
};
Rect.h
#pragma once
#include "Graph.h"
class Rect :
public Graph
{
private:
float w;
float h;
public:
Rect(float w, float h);
float GetLength() override;
float GetArea() override;
};
Rect.cpp
#include "Rect.h"
Rect::Rect(float w, float h)
{
this->w = w;
this->h = h;
}
float Rect::GetLength()
{
return (w + h) * 2;
}
float Rect::GetArea()
{
return w * h;
}
Square.h
#pragma once
#include "Graph.h"
class Square :
public Graph
{
private:
float l;
public:
Square(float l);
float GetLength() override;
float GetArea() override;
};
Square.cpp
#include "Square.h"
Square::Square(float l)
{
this->l = l;
}
float Square::GetLength()
{
return l * 4;
}
float Square::GetArea()
{
return l * l;
}
Circular.h
#pragma once
#include "Graph.h"
class Circular :
public Graph
{
private:
float r;
public:
Circular(float r);
float GetLength() override;
float GetArea() override;
};
Circular.cpp
#include "Circular.h"
Circular::Circular(float r)
{
this->r = r;
}
float Circular::GetLength()
{
//2πr
return 2 * 3.14f * r;
}
float Circular::GetArea()
{
//πr²
return 3.14f * r * r;
}
Lesson23_练习题.cpp
#include <iostream>
using namespace std;
#include "Duck.h"
#include "WoodDuck.h"
#include "RubberDuck.h"
#include "Manager.h"
#include "Programmer.h"
#include "Rect.h"
#include "Square.h"
#include "Circular.h"
int main()
{
#pragma region 练习题一
//真的鸭子嘎嘎叫,木头鸭子吱吱叫,橡皮鸭子唧唧叫
Duck* duck = new Duck();
Duck* woodDuck = new WoodDuck();
Duck* rubberDuck = new RubberDuck();
duck->Speak();
woodDuck->Speak();
rubberDuck->Speak();
//嘎嘎
//吱吱
//唧唧
delete duck;
duck = nullptr;
delete woodDuck;
woodDuck = nullptr;
delete rubberDuck;
rubberDuck = nullptr;
#pragma endregion
#pragma region 练习题二
//所有员工9点打卡
//但经理十一点打卡,程序员不打卡
Staff* staff = new Staff();
staff->PunchTheClock();
delete staff;
staff = nullptr;
Staff* m = new Manager();
m->PunchTheClock();
delete m;
m = nullptr;
Staff* p = new Programmer();
p->PunchTheClock();
delete p;
p = nullptr;
//9点打卡
//11点打卡
//老子不打卡
#pragma endregion
#pragma region 练习题三
//创建一个图形类,有求面积和周长两个方法
//创建矩形类,正方形类,圆形类继承图形类
//实例化矩形、正方形、圆形对象求面积和周长
Graph* r = new Rect(4, 5);
cout << r->GetLength() << endl;
cout << r->GetArea() << endl;
delete r;
r = nullptr;
r = new Square(5);
cout << r->GetLength() << endl;
cout << r->GetArea() << endl;
delete r;
r = nullptr;
r = new Circular(3);
cout << r->GetLength() << endl;
cout << r->GetArea() << endl;
delete r;
r = nullptr;
//18
//20
//20
//25
//18.84
//28.26
#pragma endregion
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com