目录
一、多态的基本语法
多态分为两类:
-
静态多态: 函数重载和运算符重载属于静态多态,复用函数名
-
动态多态: 派生类和虚函数实现运行时多态
静态多态和动态多态区别:
-
静态多态的函数地址早绑定 - 编译阶段确定函数地址
-
动态多态的函数地址晚绑定 - 运行阶段确定函数地址
程序调用的结果是动物在说话,因为父类与子类的同名函数重载在c++中属于静态多态,函数的地址在编译阶段确定,所以只要是Animal类,都会调用Animal类的speak函数
但传入的是子类对象,要调用子类speak函数,所以不能在编译时确定Animal类的Speak函数地址,而要让子类对象传入是在确定函数地址,在Animal类的Speak成员前加上 virtual 关键字,使其成为虚函数,在程序运行时确定函数地址。
//动物类
class Animal
{
public:
void Speak()
{
cout << "动物在说话" << endl;
}
};
//猫类
class Cat : public Animal
{
public:
void Speak()
{
cout << "小猫在说话" << endl;
}
};
//狗类
class Dog : public Animal
{
public:
void Speak()
{
cout << "小狗在说话" << endl;
}
};
//执行说话的函数
void doSpeak(Animal& animal) //在c++中,子类可以直接赋值给父类的引用
{
animal.Speak();
//父类的函数与子类的同名函数重载,属于静态多态,在编译阶段确定地址
//所以这里只要是Animal类都会调用Animal类的speak函数
}
//动物类
class Animal
{
public:
virtual void Speak() //加上virtual关键字,使用动态多态,使函数地址在程序运行时确定
{
cout << "动物在说话" << endl;
}
};
//猫类
class Cat : public Animal
{
public:
void Speak()
{
cout << "小猫在说话" << endl;
}
};
//狗类
class Dog : public Animal
{
public:
void Speak()
{
cout << "小狗在说话" << endl;
}
};
//执行说话的函数
void doSpeak(Animal& animal)
{
animal.Speak();
}
多态满足条件:
-
有继承关系
-
子类重写父类中的虚函数
多态使用条件:
-
父类指针或引用指向子类对象
重写:函数返回值类型 函数名 参数列表 完全一致称为重写
二、多态的原理剖析
将父类函数前的 virtual 关键字去掉,父类的函数变成了一个普通的成员函数,不属于类的对象上,类的大小为 1。
class Animal
{
public:
void Speak()
{
cout << "动物在说话" << endl;
}
};
void test01()
{
cout << sizeof(Animal) << endl;
}
加上virtual关键词
运行程序得到输出结果为 4.因为原本的成员函数变成了一个vfpty指针(虚函数指针),指针的大小为4,指向一个虚函数表(vftable)在表的内部会记录虚函数的地址,也就是Animal类中Speak()函数的地址。
子类继承了父类,子类也会有一个虚函数指针指向一个虚函数表,但这个虚函数表中依然记录着父类Animal::Speak()的地址。
但在子类中重写Speak()函数时,虚函数表中的父类的函数地址就会被子类所替换,但父类记录的函数地址没有被替换。
这时候用父类的指针或者引用子类的对象时,就会发生多态,调用通过父类对象就可以通过虚函数表中的函数地址调用子类函数。
class Animal
{
public:
virtual void Speak()
{
cout << "动物在说话" << endl;
}
};
void test()
{
cout << sizeof(Animal) << endl;
}
三、纯虚函数和抽象类
纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;
抽象类特点:
-
无法实例化对象
-
子类必须重写抽象类中的纯虚函数,否则也属于抽象类
class Base
{
public:
//纯虚函数声明
virtual int func() = 0;
};
class Son : public Base
{
public:
int func() //继承抽象类的子类必须重写纯虚函数,否则也为抽象类,不能实例化对象
{
cout << "子类函数调用" << endl;
}
};
void test01()
{
// Base b 抽象类不能实例化对象
Base* base = new Son;
base->func();
}
四、虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
-
可以解决父类指针释放子类对象
-
都需要有具体的函数实现
虚析构和纯虚析构区别:
-
如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名() = 0;
类名::~类名(){}
Cat类在执行函数时会在堆区开辟空间。
将Cat对象创建在堆区,由于Animal是Cat的父类,会先调用Animal的构造函数,再调用Cat的构造函数,最后释放堆区空间,但是,由于用Animal指针释放堆区空间,所以这里不会执行Cat的析构函数,只会执行Animal的析构函数,没有释放堆区空间。
class Animal
{
public:
Animal()
{
cout << "父类构造函数调用" << endl;
}
virtual void Speak() = 0;
~Animal()
{
cout << "父类析构函数调用" << endl;
}
};
class Cat : public Animal
{
public:
Cat(string name)
{
cout << "子类构造函数调用" << endl;
m_Name = new string(name);
}
void Speak()
{
cout << *m_Name << "小猫在说话" << endl;
}
~Cat()
{
cout << "子类析构函数调用" << endl;
if(m_Name != NULL)
{
delete m_Name;
m_Name = NULL;
}
}
string *m_Name = NULL;
};
void test()
{
Animal *animal = new Cat("Tom"); //可以在new的同时给对象初始化
animal -> Speak();
delete animal;
}
int main()
{
test();
return 0;
}
将父类的析构函数变为虚函数,通过父类指针指向子类函数
class Animal
{
public:
Animal()
{
cout << "父类构造函数调用" << endl;
}
virtual void Speak() = 0;
virtual ~Animal()
{
cout << "父类析构函数调用" << endl;
}
};
class Cat : public Animal
{
public:
Cat(string name)
{
cout << "子类构造函数调用" << endl;
m_Name = new string(name);
}
void Speak()
{
cout << *m_Name << "小猫在说话" << endl;
}
~Cat()
{
cout << "子类析构函数调用" << endl;
if (m_Name != NULL)
{
delete m_Name;
m_Name = NULL;
}
}
string* m_Name = NULL;
};
void test01()
{
Animal* animal = new Cat("Tom");
animal->Speak();
delete animal;
}
五、多态的案例
案例一:计算机类
普通实现
class Calculator
{
public:
int getResult(string oper)
{
if (oper == "+")
{
return m_Num1 + m_Num2;
}
else if (oper == "-")
{
return m_Num1 - m_Num2;
}
else if (oper == "*")
{
return m_Num1 * m_Num2;
}
//如果想扩展新的功能,需求修改源码
//在真实开发中 提倡开闭原则
//开闭原则:对扩展进行开发,对修改进行关闭
}
public:
int m_Num1; //操作数1
int m_Num2; //操作数2
};
void test01()
{
//创建计算器对象
Calculator c;
c.m_Num1 = 10;
c.m_Num2 = 10;
cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult("+") << endl;
cout << c.m_Num1 << " - " << c.m_Num2 << " = " << c.getResult("-") << endl;
cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResult("*") << endl;
}
如果想扩展新的功能,需求修改源码
在真实开发中 提倡开闭原则
开闭原则:对扩展进行开发,对修改进行关闭
多态实现
//利用多态实现计算器
//多态优点:代码组织结构清晰,可读性强,利于前期和后期的扩展以及维护
class AbstractCalculator
{
public:
virtual int getResult()
{
return 0;
}
int m_Num1;
int m_Num2;
};
//加法计算器
class AddCalculator :public AbstractCalculator
{
public:
virtual int getResult()
{
return m_Num1 + m_Num2;
}
};
//减法计算器
class SubCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 - m_Num2;
}
};
//乘法计算器
class MulCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 * m_Num2;
}
};
void test02()
{
//多态使用条件
// 父类指针或者引用指向子类对象
//创建加法计算器
AbstractCalculator* abc = new AddCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc;
//创建减法计算器
abc = new SubCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc;
//创建乘法计算器
abc = new MulCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc;
}
案例二:制作饮品
利用多态实现制作咖啡和茶水
Coffee和Tea继承了抽象类AbstractDrinking,并重写了AbstractDrinking的抽象函数
class AbstractDrinking
{
public:
//煮水
virtual void Boil() = 0;
//冲泡
virtual void Brew() = 0;
//倒入杯中
virtual void PourInCup() = 0;
//加入辅料
virtual void PutSomething() = 0;
//规定流程
void MakeDrink() {
Boil();
Brew();
PourInCup();
PutSomething();
}
};
//制作咖啡
class Coffee : public AbstractDrinking
{
public:
//烧水
virtual void Boil()
{
cout << "煮农夫山泉!" << endl;
}
//冲泡
virtual void Brew()
{
cout << "冲泡咖啡!" << endl;
}
//倒入杯中
virtual void PourInCup()
{
cout << "将咖啡倒入杯中!" << endl;
}
//加入辅料
virtual void PutSomething()
{
cout << "加入糖和牛奶!" << endl;
}
};
//制作茶叶
class Tea : public AbstractDrinking
{
public:
//煮水
virtual void Boil()
{
cout << "煮矿泉水!" << endl;
}
//冲泡
virtual void Brew()
{
cout << "冲泡茶叶!" << endl;
}
//倒入杯中
virtual void PourInCup()
{
cout << "倒入杯中" << endl;
}
//加入辅料
virtual void PutSomething()
{
cout << "加入枸杞" << endl;
}
};
void doWork(AbstractDrinking* abs)
{
abs->MakeDrink();
delete abs;
}
void test01()
{
doWork(new Coffee);
doWork(new Tea);
}
总结
以上是C++多态的内容。