Review cpp day09
Posted 达少Rising
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Review cpp day09相关的知识,希望对你有一定的参考价值。
回顾:
Review cpp day01
Review cpp day02
Review cpp day03
Review cpp day04
Review cpp day05
Review cpp day06
Review cpp day07
Review cpp day08
二十二、多态(Polymorphic)
1、虚函数覆盖(函数重写)、多态概念
- 如果将基类中的某个成员函数声明为虚函数,那么其子类中与该函数具有相同原型的成员函数也就是虚函数,并且对基类中的版本形成覆盖。这时,通过指向子类对象的基类指针,或者通过引用子类对象的基类引用,去调用虚函数,实际被执行的将是子类中的覆盖版本,而不是基类中的原始版本,这种语法现象被称为多态。
class A{
public:
virtual void func(void){}//虚函数
};
class B:public A{
public:
void func(void){}//自动变成虚函数
};
int main(void){
B b;
A* pa = &b;//pa指向子类对象的基类指针
A& ra = b;//ra引用子类对象的基类引用
pa->func();//子类中的func
ra.func();//子类中的func
}
eg:实现图形库,用于绘制各种图形
图形(位置、绘制)
/ \\
矩形(宽和高、绘制) 圆形(半径、绘制)
05shape.cpp
#include <iostream>
using namespace std;
class Shape{
public:
Shape(int x, int y):m_x(x), m_y(y){}
virtual void draw(void){//虚函数******
cout << "绘制图形:" << m_x << "," << m_y << endl;
}
protected:
int m_x;//坐标
int m_y;
};
class Rect:public Shape{
public:
Rect(int x, int y, int w, int h):Shape(x, y), m_w(w), m_h(h){}
void draw(void){
cout << "绘制矩形:" << m_x << ',' << m_y << ',' <<m_w
<< ',' << m_h << endl;
}
private:
int m_w;
int m_h;
};
class Circle:public Shape{
public:
Circle(int x, int y, int r):Shape(x, y), m_r(r){}
void draw(void){
cout << "绘制圆形:" << m_x << ',' << m_y << ',' << m_r << endl;
}
private:
int m_r;
};
void render(Shape* buffer[]){
for(int i=0; buffer[i] != NULL; i++){
/*
*调用虚函数时,不再由指针本身类型决定调用哪个版本,
*而是由实际的目标对象类型决定
*/
buffer[i]->draw();
}
}
int main(void){
Shape* buffer[1024] = {NULL};
buffer[0] = new Rect(1, 2, 3, 4);
buffer[1] = new Circle(5, 6, 7);
buffer[2] = new Rect(1, 2, 3, 4);
buffer[3] = new Rect(1, 2, 3, 4);
buffer[4] = new Circle(5, 6, 7);
buffer[5] = new Circle(5, 6, 7);
//...
render(buffer);
return 0;
}
2、虚函数覆盖的条件
- 1)只有类中成员函数才能声明为虚函数,全局函数、静态成员函数、构造函数都不能声明为虚函数。
注: 析构函数可以虚(特殊情况后面讲) - 2)只有基类中以virtual关键字修饰的成员函数才能作为虚函数被子类覆盖,而与子类中的virtual关键字无关。
- 3)虚函数在子类中的版本必须和该函数在基类中的版本拥有相同的函数签名,即函数名、形参表和常属性一致。
- 4)如果基类中的虚函数返回基本类型的数据,那么该函数在子类中的覆盖版本必须返回相同的类型的数据。
- 5)如果基类中的虚函数返回类类型的指针(A*)或引用(&A), 那么允许子类的覆盖版本返回其子类类型的指针(B*)或引用(&B)。
class A{}; class B:public A{};
01poly.cpp
#include <iostream>
using namespace std;
class Base{
public:
void foo(void){
cout << "Base::foo" << endl;
}
virtual void bar(double d){
cout << "Base::bar" << endl;
}
virtual void hum(void){
cout << "Base::hum" << endl;
}
};
class Derived:public Base{
public:
//如果基类中该函数不加上virtual关键字,这个函数尝试覆盖,覆盖失败
void foo(void){
cout << "Derived::foo" << endl;
}
void bar(int d){
cout << "Derived::bar" << endl;
}
int hum(void){
cout << "Derived::hum" << endl;
}
};
int main(void){
Derived d;
Base* pb = &d;
pb->foo();//Base::foo
pb->bar();//不能构成多态,形参表不一样
pb->hum();//产生冲突,返回类型不一样
return 0;
}
3、多态的条件
- 1)多态特性除了需要满足虚函数的覆盖,还必须通过指针或引用去调用才能表现出来。
- 2)调用虚函数的指针也可能是this指针,当通过一个子类对象调用基类中的成员函数时,该函数里面的this指针就是一个指向子类对象的基类指针,再通过它去调用虚函数,同样可以表现多态的语法特性。
02poly.cpp
#include <iostream>
using namespace std;
class Base{
public:
virtual int cal(int x, int y){
return x + y;
}
//void func(Base* this = &d)
void func(void){
cout << this->cal(200, 300) << endl;
cout << cal(200, 300) << endl;
}
};
class Derived:public Base{
public:
int cal(int x, int y){
return x * y;
}
};
int main(void){
Derived d;
//Base b = d;
//cout << b.cal(100, 200) << endl;//300,没有产生多态
d.func();
return 0;
}
4、纯虚函数、抽象类、纯抽象类
- 1)纯虚函数:virtual 返回类型 函数名(形参表)[const] = 0;
- 2)抽象类:如果一个类中包含了纯虚函数,那么这类就是抽象类。
注: 不能使用抽象类创建对象。 - 3)纯抽象类(有名接口类):如果一个类中成员函数(不包含构造函数)都是纯虚函数,那么在该类就是纯抽象类。
03shape.cpp
#include <iostream>
using namespace std;
class Shape{//抽象类、纯抽象类
public:
Shape(int x, int y):m_x(x), m_y(y){}
virtual void draw(void) = 0;//纯虚函数
protected:
int m_x;//坐标
int m_y;
};
class Rect:public Shape{
public:
Rect(int x, int y, int w, int h):Shape(x, y), m_w(w), m_h(h){}
void draw(void){
cout << "绘制矩形:" << m_x << ',' << m_y << ',' <<m_w
<< ',' << m_h << endl;
}
private:
int m_w;
int m_h;
};
class Circle:public Shape{
public:
Circle(int x, int y, int r):Shape(x, y), m_r(r){}
void draw(void){
cout << "绘制圆形:" << m_x << ',' << m_y << ',' << m_r << endl;
}
private:
int m_r;
};
void render(Shape* buffer[]){
for(int i=0; buffer[i] != NULL; i++){
/*
*调用虚函数时,不再由指针本身类型决定调用哪个版本,
*而是由实际的目标对象类型决定
*/
buffer[i]->draw();
}
}
int main(void){
Shape* buffer[1024] = {NULL};
buffer[0] = new Rect(1, 2, 3, 4);
buffer[1] = new Circle(5, 6, 7);
buffer[2] = new Rect(1, 2, 3, 4);
buffer[3] = new Rect(1, 2, 3, 4);
buffer[4] = new Circle(5, 6, 7);
buffer[5] = new Circle(5, 6, 7);
//...
render(buffer);
//抽象类不能创建对象
//Shape s(10,50);//error
return 0;
}
04poly.cpp
//工厂方法模式
#include <iostream>
using namespace std;
class PDFParser{
public:
void parse(const char* pdffile){
cout << "解析出一个矩形" << endl;
onRect();
cout << "解析出了一个圆形" << endl;
onCircle();
cout << "解析出了一行文本" << endl;
onText();
cout << "解析出了一张图片" << endl;
onImag();
}
private:
virtual void onRect(void) = 0;
virtual void onCircle(void) = 0;
virtual void onText(void) = 0;
virtual void onImage(void) = 0;
};
class PDFRender:public PDFParse{
private:
void onRect(void){
cout << "绘制一个矩形" << endl;
}
void onCircle(void){
cout << "绘制一个圆形"<< endl;
}
void onText(void){
cout << "显式一行文本" << endl;
}
void onImage(void){
cout << "显式一张图片" << endl;
}
};
int main(void){
PDFRender render;
render.parse("xx.pdf");
return 0;
}
5、多态实现原理//了解
- 通过虚函数表和动态绑定来是实现
- 1)虚函数的动态绑定会增加时间的开销
- 2)虚函数表的生成会增加内存的开销
注: 如果没有多态的语法要求,最好不要使用虚函数
class A{
public:
virtual void foo(void){}
virtual void bar(void){}
};
class B:public A{
public:
void foo(void){}
};
6、虚析构函数
- 1)基类的析构函数不会自动调用子类的析构函数。如果对一个指向子类对象的基类指针使用delete操作符,实际被执行的仅是基类的析构函数,子类的析构函数执行不到,有内存泄漏的风险。
class A{...};
class B:public A{...};
A* pa = new B;//pa: 指向子类的基类指针
delete pa;//内存泄漏
- 2)虚析构来解决
可以将基类的析构函数声明为虚函数,那么子类的析构函数就也是一个虚函数,并且对基类的虚构函数构成有效的覆盖,可以表现多态的语法特性。 这时delete一个指向子类对象的基类指针,实际被调用的将是子类的析构函数,子类的析构函数在执行结束以后又会自动调用基类的析构函数,避免了内存泄漏。
05poly.cpp
#include <iostream>
using namespace std;
class Base{
public:
Base(void){
cout << "Base的构造" << endl;
}
virtual ~Base(void){//虚析构函数
cout << "Base的析构" << endl;
}
};
class Derived:public Base{
public:
Derived(void){
cout << "Derived的构造" << endl;
}
~Derived(void){
cout << "Derived的析构" << endl;
}
};
int main(void){
Base* pb = new Derived;
//...
//1) pb->析构函数
//2) 释放内存
delete pb;
return 0;
}
Base的构造
Derived的构造
Derived的析构
Base的析构
扩展练习V7.0:实现企业员工管理系统
- 需求:
- 增加技术主管类,同时具有技术员和经理属性
- 绩效工资=(技术员绩效+经理绩效)/ 2
员工(Employee)
/ \\
技术员 经理
\\ /
技术主管
- 提示:
void printInfo(){
printBasic();//打印公有信息
printExtra();//打印特有信息
}
void calSalary(){
//总工资 = 基本工资 + 绩效工资
calBasic() + calMerit();
}
Employee.h
#ifndef __EMPLOYEE_H__
#define __EMPLOYEE_H__
#include <iostream>
#include <cstdio>
using namespace std;
class Employee{
public:
Employee(const string& name, double salary);
~Employee(void);
void printInfo(void)const;
protected:
void printInfo(void)const;//公有信息
void printExtra(void)const;//特有信息
public:
//计算工资
void claSalary(void);
protected:
double calBasic(void);//基本工资
double calMerit(void);//绩效工资
public:
void setId(int id);
void setName(const string& name);
void setSalary(double salary);
void saveInfo(void)const;
//根据员工对象的id和参数id比较,是否相等
bool operator==(int idReview cpp day10