C++笔记六(多态)

1.多态

1.1多态、动态绑定的一些概念
多态简单说就是一个接口,多种方法。分为两类:静态多态和动态多态
静态多态:函数重载(运算符重载实质就是函数重载)和模板属于静态多态
动态多态:派生类和虚函数实现运行时多态(C++一般多指动态多态)

静态多态和动态多态的区别:
静态多态的函数地址早绑定,即编译阶段确定函数地址
动态多态的函数地址晚绑定,即运行阶段确定函数地址

绑定:把一个消息和一个对象的方法相结合的过程。根据绑定进行的阶段分为静态绑定和动态绑定。
静态绑定:程序执行前就能解析出调用哪个函数。
动态绑定:究竟调用哪个派生类的函数要到运行时才进行解析。比如函数指针

实例:

class Animal
{
    
    
public:
	virtual void speak()
	{
    
    
		cout << "动物在叫!" << endl;
	}
};
class Cat:public Animal
{
    
    
public:
	void speak()
	{
    
    
		cout << "猫在叫!" << endl;
	}
};
void test(Animal & animal)
{
    
    
	animal.speak();
}
class Dog :public Animal
{
    
    
public:
	void speak()
	{
    
    
		cout << "狗在叫!" << endl;
	}
};
int main()
{
    
    
	Cat cat;
	//第一,当speak函数不是虚函数时返回“动物在叫”
	//第二,test函数参数列表中是Animal对象的引用,可见父类的指针或者引用可以直接指向子类,也就是说子类可以初始化父类的指针与引用
	test(cat);	//猫在叫!

	Dog dog;
	//第一,当在Animal类中的speak函数前加virtual关键字后,speak函数为虚函数,返回“狗在叫”
	//第二。这里便是动态多态
	test(dog);	//狗在叫!
	return 0;
}

1.2动态多态(通过virtual实现动态绑定)的实现
1>产生动态多态的条件:其一,要有继承;其二,子类要重写父类的虚函数。
2>动态多态失败的可能原因:其一:在基类的构造函数和析构函数内是不会发生动态多态的;其二:使用的是父类对象而不是父类的指针或者引用。
3>重写:重写指除了函数体不一样外,其他都一样有一种特例,当父类返回的是父类指针或者引用,而且子类返回的是子类的指针或者引用时可以,返回类型不同也行)。
4>动态多态的使用:父类的引用或者指针指向子类
5>多看看上例!!!

1.3多态的原理
首先,使用函数指针也可以实现动态绑定,比如使用函数指针去调用那些返回类型与参数符合函数,一般也是要等运行时才知道调用的是哪个。所以,也可以尝试使用函数指针实现虚函数的动态绑定,只要把每个虚函数设置一个函数指针,在类对象进行构造时如果重写了哪个虚函数就替代哪个虚函数,最后调用比如void test(Base &){} 时,根据函数指针调用相应函数即可。(指针只保存地址,指针的类型与所指内容无关,只是一种对内容的解释,所以父类指针指向子类对象或者父类对象时,就是这个指针保存了父类或者某个子类的对象,所以根据这个对象便可知是哪个函数指针,便可知是哪个函数了)。但缺点是占用额外空间太大,每一个对象都要保存一份自己所包含的所有虚函数的指针,比如创建两个父类对象,那么两个对象保存的虚函数指针都是一摸一样的,但是一人一份。所以,有了第二种方法:将所有的虚函数的函数指针保存到虚函数表中(每个类都有一份虚函数表,由类的所有对象共享),而且相同的只保存一份,父类虚函数的函数指针放在前面,子类的放在后面,由编译器安排。所以所有的对象都不保存虚函数的函数指针了,而是保存一个虚函数表的指针,该指针在构造函数中就初始化。所以在最后通过基类的指针或者引用调用虚函数时,就能通过虚函数指针找到相应的虚函数表,再找到对应的虚函数的函数指针,最后找到对应的虚函数。

1.4多态的好处
其一:代码组织结构清晰
其二:可读性强
其三:利于前期和后期的扩展以及维护

//不用多态写法
class MyCalculator
{
    
    
public:
	int calculator(string symbol);
	int m_A;
	int m_B;
};
//在此方法中如果需要再添加或者修改功能那么需要从整个函数里找,并且违背开闭原则
int MyCalculator::calculator(string symbol)	
{
    
    
	if (symbol == "+")
	{
    
    
		return m_A + m_B;
	}
	else if(symbol == "-")
	{
    
    
		return m_A - m_B;
	}
	else if (symbol == "*")
	{
    
    
		return m_A * m_B;
	}
}
void test01()
{
    
    
	MyCalculator mycalculator;
	mycalculator.m_A = 10;
	mycalculator.m_B = 20;
	cout << mycalculator.m_A << "+" << mycalculator.m_B << "=" << mycalculator.calculator("+") << endl;
	mycalculator.m_A = 10;
	mycalculator.m_B = 20;
	cout << mycalculator.m_A << "-" << mycalculator.m_B << "=" << mycalculator.calculator("-") << endl;
	mycalculator.m_A = 10;
	mycalculator.m_B = 20;
	cout << mycalculator.m_A << "*" << mycalculator.m_B << "=" << mycalculator.calculator("*") << endl;
}

//利用多态
//创建一个抽象计算器类,只有两个整形,进行运算的成员方法设置为虚函数,并由加计算类、减计算类等重写
class AbstractCalculator	
{
    
    
public:
	virtual int getResult();
	int m_AA;
	int m_AB;
};
int AbstractCalculator::getResult()
{
    
    
	return 0;
}

class AddCalculator :public AbstractCalculator
{
    
    
public:
	int getResult()
	{
    
    
		return m_AA + m_AB;
	}
};
class SubCalculator :public AbstractCalculator
{
    
    
public:
	int getResult()
	{
    
    
		return m_AA - m_AB;
	}
};
class MulCalculator :public AbstractCalculator
{
    
    
public:
	int getResult()
	{
    
    
		return m_AA * m_AB;
	}
};
void test02()
{
    
    
	AbstractCalculator *ac = new AddCalculator;	//子类对象初始化父类指针
	ac->m_AA = 30;
	ac->m_AB = 20;
	cout << ac->m_AA << "+" << ac->m_AB << "=" << ac->getResult() << endl;
	delete ac;	//由于是在堆区开辟的,所以使用完后就释放

	ac = new SubCalculator;
	ac->m_AA = 30;
	ac->m_AB = 20;
	cout << ac->m_AA << "-" << ac->m_AB << "=" << ac->getResult() << endl;
	delete ac;

	ac = new MulCalculator;
	ac->m_AA = 30;
	ac->m_AB = 20;
	cout << ac->m_AA << "*" << ac->m_AB << "=" << ac->getResult() << endl;
	delete ac;
	ac = NULL;
}
//使用多态后 结构清晰,一目了然!可读性强,也便于修改,如果想增加除法那就再写个类继承就行,
//不需要像之前一样不管干什么都要盯着算术函数,而且还要看整个函数。
int main()
{
    
    
	test01();
	cout << "-----------------------" << endl;
	test02();
	return 0;
}

注意:多看看上面的示例,多态的优点很多,多使用多态

1.5纯虚函数和抽象类
来由:从上面实例可知,父类中的虚函数其实根本就没怎么用,特别是函数体里面的代码,因为已经被子类重写了,由此,我们可以将其写成纯虚函数。

纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0;不需要写函数实现。

拥有纯虚函数的类叫抽象类,抽象类无法实例化对象。而且子类必须重写纯虚函数,不然子类也是抽象类。

如果父类有虚函数,父类的析构函数最好加上virtual(原因在1.6)
如果是个抽象类,无静态成员,由于无法实例化,所以最好变成procted:

class AbstractMakeDrink
{
    
    
public:
	virtual void Boil() = 0;	//煮水
	virtual void PushSomething() = 0;	//加入材料
	virtual void Brew() = 0;	//冲泡
	void MakeDrink()		//制作
	{
    
    
		Boil();
		PushSomething();
		Brew();
	}
};

class Tea :public AbstractMakeDrink
{
    
    
public:
	void Boil()
	{
    
    
		cout << "烧泉水!" << endl;
	}
	void PushSomething()
	{
    
    
		cout << "放入人参、枸杞!" << endl;
	}
	void Brew()
	{
    
    
		cout << "开始泡茶!" << endl;
	}
};

class Coffee :public AbstractMakeDrink
{
    
    
public:
	void Boil()
	{
    
    
		cout << "烧自来水!" << endl;
	}
	void PushSomething()
	{
    
    
		cout << "放入咖啡!" << endl;
	}
	void Brew()
	{
    
    
		cout << "开始冲咖啡!" << endl;
	}
};
//这里参数列表相当于AbstractMakeDrink * amd = new Coffee;
void Do(AbstractMakeDrink * amd)	
{
    
    
	amd->MakeDrink();
	delete amd;	//因为amd指向堆空间,使用完要释放
	amd = NULL;
}

void test()
{
    
    
	Do(new Coffee);
	cout << "-----------------" << endl;
	Do(new Tea);
}

int main()
{
    
    
	test();
	return 0;
}

1.6 当子类中有属性开辟到堆空间,那么父类指针释放时子类的析构无法被调用,从而造成内存泄漏!
错误示例:

class Animal
{
    
    
public:
	Animal()
	{
    
    
		cout << "动物构造!" << endl;
	}
	~Animal()
	{
    
    
		cout << "动物析构!" << endl;
	}
	virtual void speak() = 0;
};

class Cat :public Animal
{
    
    
public:
	Cat(string name)
	{
    
    
		cout << "小猫构造!" << endl;
		m_Name = new string(name);
	}
	~Cat()
	{
    
    
		cout << "小猫析构!" << endl;
		delete m_Name;
		m_Name = NULL;
	}
	void speak()
	{
    
    
		cout << *m_Name << "猫在叫!" << endl;
	}
	string * m_Name;
};

void test()
{
    
    
	Animal * animal = new Cat("Tom");
	animal->speak();
	delete animal;
	animal = NULL;
}

int main()
{
    
    
	test();
	return 0;
}
//输出结果为
//动物构造!
//小猫构造!
//Tom猫在叫!
//动物析构!

我们可以看到在test函数中,先构造父类对象,然后子类构造,最后父类指针释放时却只调用了父类析构,子类析构并没有调用!

解决方法:将父类的析构函数改为虚析构或者纯虚析构
虚析构与纯虚析构的共性:
其一,可以解决父类指针释放子类对象
其二,都需要具体的函数实现
虚析构与纯虚析构的区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象

使用虚析构解决:上面代码中的父类析构函数加关键字virtual变成虚析构!

virtual ~Animal()
{
    
    
	cout << "动物析构!" << endl;
}

结果如下:
在这里插入图片描述

使用纯虚析构解决:把析构变成纯虚析构并且纯虚析构要有具体函数实现

class Animal
{
    
    
public:
	Animal()
	{
    
    
		cout << "动物构造!" << endl;
	}
	//virtual ~Animal()
	//{
    
    
	//	cout << "动物析构!" << endl;
	//}
	virtual ~Animal() = 0;
	virtual void speak() = 0;
};
Animal::~Animal()
{
    
    
	cout << "动物析构!" << endl;
}

结果如下:
在这里插入图片描述
注意:纯虚析构与虚析构是专门为了解决子类有属性在堆区开辟空间父类指针释放时无法调用子列析构这个问题的

1.7运行时的类型鉴定机制
1> typeid运算符,头文件为typeinfo
const char * = typeod(*this).name(); //返回类名
2>dynamic_cast
dynamic_cast< Base >(base);
如果类型一致则转换,不一致则返回0

1.8多态实例
电脑组装:电脑主要构成部件有CPU、显卡、内存等,现在将这三个零件封装成抽象类。并且由不同厂商提供不同零件,例如可以是英伟达的显卡,因特尔的CPU等。创建一个电脑类,并且调用这几个部件的接口,测试组装几台不同电脑进行工作。

//CPU、显卡、内存的抽象类
class CPU
{
    
    
public:
	virtual void calculate() = 0;
};
class VideoCard
{
    
    
public:
	virtual void dispaly() = 0;
};
class Memory
{
    
    
public:
	virtual void storage() = 0;
};
class InterCPU :public CPU
{
    
    
public:
	void calculate()
	{
    
    
		cout << "Inter的CPU!" << endl;
	}
};
class AMDCPU :public CPU
{
    
    
public:
	void calculate()
	{
    
    
		cout << "AMD的CPU!" << endl;
	}
};
class AMDVideoCard :public VideoCard
{
    
    
public:
	void dispaly()
	{
    
    
		cout << "AMD的显卡!" << endl;
	}
};
class NVIDIAVideoCard :public VideoCard
{
    
    
public:
	void dispaly()
	{
    
    
		cout << "NVIDIA的显卡!" << endl;
	}
};
class SAMSUNGMeMory :public Memory
{
    
    
public:
	void storage()
	{
    
    
		cout << "三星的内存!" << endl;
	}
};
class KingstonMeMory :public Memory
{
    
    
public:
	void storage()
	{
    
    
		cout << "金士顿的内存!" << endl;
	}
};
//电脑类
class Computer
{
    
    
public:
	Computer(CPU * cpu, VideoCard * videocard, Memory * memory)
	{
    
    
		m_cpu = cpu;
		m_videocard = videocard;
		m_memory = memory;
	}
	~Computer()
	{
    
    
		delete m_cpu;
		m_cpu = NULL;
		delete m_memory;
		m_memory = NULL;
		delete m_videocard;
		m_videocard = NULL;
	}
	void Do()
	{
    
    
		m_cpu->calculate();
		m_videocard->dispaly();
		m_memory->storage();
	}
	CPU * m_cpu;
	VideoCard * m_videocard;
	Memory * m_memory;
};

void test()
{
    
    
	Computer computer(new AMDCPU, new AMDVideoCard, new SAMSUNGMeMory);
	computer.Do();
}

int main()
{
    
    
	test();
	return 0;
}      

猜你喜欢

转载自blog.csdn.net/weixin_45884870/article/details/110052882