C++基础知识(七)--- 多态

一. 多态

同一个操作作用于不同的对象,可以有不同的解释,会产生不同的效果,这就是多态。

可以解决项目中的紧耦合问题,提供程序的可扩展性。

多态的实现原理:

① 编译器只要发现类中有虚函数,就会创建一个表,表里放着类中所有虚函数的入口地址;

② 编译器会在类对象中安插一个虚函数表指针,该指针指向本类的虚函数表;

③ 子类中的虚函数表指针是从父类继承下来的,该指针指向父类的虚函数表。编译器为了初始化从父类继承过来的虚函数表指针,在我们所有的构造函数中添加了初始化虚函数指针的代码,让从父类继承过来的虚函数表指针指向子类自己的虚函数表。

④ 当编译器发现子类重写了父类的虚函数,那么子类重写的函数就会覆盖掉虚函数表中对应的父类函数,即把子类重写的虚函数的地址放到了虚函数表中,并覆盖掉了原先虚函数表中父类的虚函数的地址;

⑤ 当通过父类指针去调用该虚函数时,编译器会根据该父类指针指向的内存空间中的虚函数指针找到对应的虚函数。

 二. 纯虚函数和抽象类

//抽象层
class rule
{
public:
	virtual int getnum(int a, int b)
	{
		return 0;
	}
};

//实现层
class plus_rule :public rule
{
public:
	virtual int getnum(int a, int b)  //重写父类虚函数,依赖抽象层
	{
		return a+b;
	}
};
//扩展新功能
class dec_rule :public rule
{
public:
	virtual int getnum(int a, int b)
	{
		return a - b;
	}
};

//业务层
int func(rule* cal)
{
	int a = 10;
	int b = 20;
	int ret = cal->getnum(a, b); //依赖抽象层
	return ret;
}

//应用层
void test()
{
	rule* r = NULL;
	r = new plus_rule;
	cout << func(r) << endl;
	delete r;
	//扩展功能新增的代码
	r = new dec_rule;
	cout << func(r) << endl;
	delete r;
}

观察代码发现,class rule中的虚函数似乎没有什么用,既然如此,那么就可以把它改成一个纯虚函数:

① 有纯虚函数的类叫做抽象类,不能实例化对象。

class rule
{
public:
	virtual int getnum(int a, int b) = 0;
};

void test()
{
    rule r; //err,抽象类不能实例化对象
}

② 如果子类继承抽象类,子类必须实现抽象类的所有纯虚函数,不然子类也变为抽象类。

//抽象类
class Maker
{
public:
	virtual void func1() = 0;  //接口的声明
	virtual void func2() = 0;
};

class Son :public Maker
{
public:
	virtual void func1()    //接口的实现
	{}
	virtual void func2()
	{}
};

void test()
{
    Son s;
}

三. 接口的定义

所谓的接口,即把内部实现的细节封装起来,外部用户通过预留的接口可以使用接口的功能,而不需要知晓内部具体细节。

C++中,通过类实现面向对象的编程,而在基类中只给出纯虚函数的声明,然后在派生类中实现纯虚函数的具体定义的方式实现接口。不同派生类实现接口的方式也不尽相同。

四. 模板方法模式

class Drink  //抽象制作饮品
{
public:
	//煮水
	virtual void Boil() = 0;
	//冲泡
	virtual void Brew() = 0;
	//倒入杯中
	virtual void Pour() = 0;
	//加佐料
	virtual void adds() = 0;
	//模板方法,把调用函数的顺序确定下来
	void func()
	{
		Boil();
		Brew();
		Pour();
		adds();
	}
};

class Coffee :public Drink  //制作咖啡
{
public:
	void Boil()
	{
		cout << "煮水" << endl;
	}
	void Brew()
	{
		cout << "冲泡咖啡" << endl;
	}
	void Pour()
	{
		cout << "倒入咖啡杯" << endl;
	}
	void adds()
	{
		cout << "加牛奶" << endl;
	}
};

class Tea :public Drink  //制作茶水
{
public:
	void Boil()
	{
		cout << "煮自来水" << endl;
	}
	void Brew()
	{
		cout << "冲泡茶叶" << endl;
	}
	void Pour()
	{
		cout << "倒入茶杯" << endl;
	}
	void adds()
	{
		cout << "加枸杞" << endl;
	}
};
//业务层
void doBuniess(Drink* drink)
{
	drink->func();
	delete drink;
	drink = NULL;
}

void test()
{
	doBuniess(new Coffee);
	doBuniess(new Tea);
}

五. 虚析构函数

1. 虚析构函数

是为了:当有一个父类指针指向子类对象时,调用父类指针释放子类对象。

class Animal
{
public:
	Animal()
	{
		cout << "Animal构造" << endl;
	}
	virtual ~Animal()   //虚析构函数,会调用到子类的析构函数
	{
		cout << "Animal析构" << endl;
	}
};

class Son :public Animal
{
public:
	Son()
	{
		cout << "Son构造" << endl;
		pName = new char[64];
		memset(pName, 0, 64);
		strcpy(pName,"孙");
	}
	~Son()
	{
		cout << "Son析构" << endl;
		if (pName != NULL)
		{
			delete[] pName;
			pName = NULL;
		}
	}
public:
	char* pName;
};

void test()
{
	Animal* an = new Son; //父类指针指向子类
	delete an;
}

2. 纯虚析构函数

有纯虚析构函数的类是抽象类,不能实例化对象,而且要在类外实现。

如上,如果不想让类Animal实例化出对象,则

class Animal
{
public:
	Animal()
	{
		cout << "Animal构造" << endl;
	}
	virtual ~Animal() = 0; //纯虚析构函数,需要在类外实现
};

Animal::Animal()
{
    cout << "Animal纯虚析构" << endl;
}

六. 重写、重载、重定义

1. 重写(覆盖)

  • 有继承;
  • 子类重写父类的 virtual 函数;
  • 函数返回值、函数名、函数参数,必须和父类中的虚函数一致。

2. 重载(同一个作用域的同名函数)

  • 同一个作用域;
  • 参数个数、参数类型、参数顺序不同;
  • 和函数返回值没有关系;
  • const也可以作为重载条件 //do(const Maker& m){}  do(Maker& m){} 

3. 重定义(隐藏)

  • 有继承;
  • 子类重新定义父类的同名成员(非 virtual 函数)

七. 父类引用子类对象

 

八.  动物园案例

//1.创建动物的基类
class Animal
{
public:
	virtual void speak() = 0;
};
//创建动物
class Dog :public Animal
{
public:
	Dog(string name)
	{
		this->name = name;
	}
	virtual void speak()  //重写基类的虚函数
	{
		cout << "小狗" << name << "汪汪汪" << endl;
	}
private:
	string name;
};

class Tiger :public Animal
{
public:
	Tiger(string name, int age)
	{
		this->name = name;
		this->age = age;
	}
	virtual void speak()  //重写基类的虚函数
	{
		cout << age << "岁" << "老虎" << name << "嗷嗷嗷" << endl;
	}
private:
	string name;
	int age;
};
//创建动物园
class Zoo
{
public:
	Zoo()
	{
		mCapacity = 1024;
		mSize = 0;
		//申请空间,储存animal*的空间,指针数组
		this->p = new Animal * [mCapacity];
	}
	//增加动物
	int AddAnimal(Animal* animal)
	{
		if (mCapacity == mSize)
		{
			return -1;
		}
		//把指针存到指针数组
		this->p[mSize] = animal;
		mSize++;

		return 0;
	}
	void StartSpeak()
	{
		for (int i = 0; i < mSize; i++)
		{
			p[i]->speak();
		}
	}
	//析构函数
	~Zoo()
	{
		//先释放指针数组中指针指向的堆区空间
		for (int i = 0; i < mSize; i++)
		{
			if (p[i] != NULL)
			{
				delete p[i];
				p[i] = NULL;
			}
		}
		//然后释放指针数组
		delete[] p;
		p = NULL;
	}
private:
	Animal** p;
	int mCapacity;
	int mSize;
};

void test()
{
	Zoo* zoo = new Zoo;
	//添加动物
	zoo->AddAnimal(new Dog("狗子"));
	zoo->AddAnimal(new Tiger("虎子",18));

	zoo->StartSpeak();

	delete zoo;
}
小狗狗子汪汪汪
18岁老虎虎子嗷嗷嗷

然后,如果要新增动物,则只需要新增类即可,并且不需要修改原先的代码。如下:

class Cat :public Animal
{
public:
	Cat(string name)
	{
		this->name = name;
	}
	virtual void speak()
	{
		cout << "小猫" << name << "喵喵喵" << endl;
	}
private:
	string name;
};

九. 案例二 - 班级

class Maker
{
public:
	virtual void show() = 0;
};
class Person :public Maker
{
public:
	Person(string name, int age, string sex, string Skill)
	{
		this->name = name;
		this->age = age;
		this->sex = sex;
		this->Skill = Skill;
	}
	virtual void show()
	{
		cout << name << "准备开始" << Skill << endl;
	}
private:
	string name;
	int age;
	string sex;
	string Skill;
};

class MyClass
{
public:
	MyClass()
	{
		mCapacity = 50;
		mSize = 0;
		this->p = new Maker * [mCapacity];
	}
	//增加成员
	void AddMaker(Maker* ma)
	{
		if (mSize == mCapacity)
		{
			return;
		}
		this->p[mSize] = ma;
		mSize++;
	}
	//秀技能
	void StartShow()
	{
		for (int i = 0; i < mSize; i++)
		{
			p[i]->show();
		}
	}
	~MyClass()
	{
		for (int i = 0; i < mSize; i++)
		{
			if (p[i] != NULL)
			{
				delete p[i];
				p = NULL;
			}
		}
	}
private:
	Maker** p;
	int mCapacity;
	int mSize;
};


void test()
{
	MyClass* my = new MyClass;
	my->AddMaker(new Person("Sun", 18, "男", "打篮球"));
	my->StartShow();
	delete my;
}

解释下为什么使用 Maker** p,这里采用double 和 double* 来代替Maker 和 Maker*,便于理解。

首先来看 double p; 和 double* p; 的区别:

double 表示8个字节,double* 表示4个字节;

如果定义了 double* p; 则 p+1 表示跨过一个 double。

new double * [size]; 和 new double [size]; 两者开辟的空间是不一样大的,前者空间大小是 4×size,后者空间大小是 8×size。如果用 double* p来接这个空间,如下所示:

p+1 表示跨过一个 double,是8个字节,
如果再加一个*,即double* *p,则p+1跨过4个字节。
等号右边每个元素的大小是4个字节,
所以这里不能使用double* p,而是double** p; 

double* p;  double* *p;

this->p = new double*[size];

十. 案例三 - 加减计算

class rule
{
public:
	virtual double getNum(double a, double b) = 0;
};

class rule_add :public rule
{
	virtual double getNum(double a, double b)
	{
		return a + b;
	}
};
class rule_minux :public rule
{
	virtual double getNum(double a, double b)
	{
		return a - b;
	}
};

void test()
{
	rule* r = NULL;
	r = new rule_add;
	cout << r->getNum(1, 2) << endl;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值