C++之多态与类型转换

目录

多态的引入

多态的限制

类型转换

隐式转换

显式转换


多态的引入

先看以下例子,人类用手吃饭是本能,英国人用刀叉恰饭,而我们用筷子恰饭,而我们问这个人怎么吃饭,应该根据其国别来回答,而不是简单地说"用手吃"

#include <iostream>
#include <string.h>
#include <unistd.h>

using namespace std;

class Human {
public:
	void eating(void) { cout<<"use hand to eat"<<endl; }
};

class Englishman : public Human {
public:
	void eating(void) { cout<<"use knife to eat"<<endl; }
};


class Chinese : public Human {
public:
	void eating(void) { cout<<"use chopsticks to eat"<<endl; }
};

void test_eating(Human& h)
{
	h.eating();
}

int main(int argc, char **argv)
{
	Human h;
	Englishman e;
	Chinese c;

	h.eating();
	e.eating();
	c.eating();
	test_eating(h);
	test_eating(e);
	test_eating(c);

	return 0;
}

执行结果如下,可以看到,一开始是覆写实现的,后面通过函数引用就不能自动的识别出是哪个国家的,本能的都是用手吃,里面的机制是静态联编,后面解释,因此我们需要以多态来实现自动识别

use hand to eat
use knife to eat
use chopsticks to eat
use hand to eat
use hand to eat
use hand to eat

怎么实现,在基类中加入虚拟函数,在eating函数前加上virtual,修改程序如下,派生类继承了基类的eating函数,因此也变成了virtual函数,对于派生类的eating函数可以不写virtual

...

class Human {
private:
	int a;
public:
	virtual void eating(void) { cout<<"use hand to eat"<<endl; }
};

...

int main(int argc, char **argv)
{
	Human h;
	Englishman e;
	Chinese c;

	test_eating(h);
	test_eating(e);
	test_eating(c);

	cout<<"sizeof(Human) = "<<sizeof(h)<<endl;
	cout<<"sizeof(Englishman) = "<<sizeof(e)<<endl;
	cout<<"sizeof(Chinese) = "<<sizeof(c)<<endl;

	return 0;
}

执行结果如下,可以看到自动的识别出各国的吃饭方法,而不是简单的用手吃,上述程序中,实现的机制是所谓的动态联编

use hand to eat
use knife to eat
use chopsticks to eat
sizeof(Human) = 16
sizeof(Englishman) = 16
sizeof(Chinese) = 16

对于非虚函数,采用的是静态联编,编译时就确定调用哪个函数,因此不能自动识别出哪国人,只是简单的用手吃;对于虚函数,采用动态联编,有虚函数的对象里含有一个指针如下图,指向虚函数表,调用虚函数表时,会根据对象里的指针找到表,从表中取出函数来执行,对于静态联编效率高,而动态联编支持多态,可以看到上述原本对象4字节变量大小,多了12字节的指针执行虚函数表

 

 

多态的限制

1、使用指针或者引用来使用对象才会有多态,修改程序如下

...

void test_eating(Human h)
{
	h.eating();
}

...

执行结果如下,对于test_eating函数中传值的时候没有指针,因此属于静态联编,例如"test_eating(e);",e会先变成human类,e中有a(基类)和指针,转换为human类型就只剩基类部分,因此只能是静态联编,调用的只能是Human的eating函数

use hand to eat
use hand to eat
use hand to eat
sizeof(Human) = 16
sizeof(Englishman) = 16
sizeof(Chinese) = 16

2、只有类的成员函数才能声明为虚函数

3、静态成员函数不能是虚函数

4、内联函数不能是虚函数

5、构造函数不能是虚函数

6、析构函数一般都声明为虚函数,修改程序如下

#include <iostream>
#include <string.h>
#include <unistd.h>

using namespace std;

class Human {
private:
	int a;
public:
	virtual void eating(void) { cout<<"use hand to eat"<<endl; }
	virtual ~Human() { cout<<"~Human()"<<endl; }
};

class Englishman : public Human {
public:
	void eating(void) { cout<<"use knife to eat"<<endl; }
	virtual ~Englishman() { cout<<"~Englishman()"<<endl; }
};


class Chinese : public Human {
public:
	void eating(void) { cout<<"use chopsticks to eat"<<endl; }
	virtual ~Chinese() { cout<<"~Chinese()"<<endl; }
};

void test_eating(Human h)
{
	h.eating();
}

int main(int argc, char **argv)
{
	Human* h = new Human;
	Englishman* e = new Englishman;
	Chinese* c = new Chinese;

	Human *p[3] = {h, e, c};
	int i;

	for (i = 0; i < 3; i++)
	{
		p[i]->eating(); 
		delete p[i];    
	}
	return 0;
}

执行结果如下,如果基类中的析构函数不设置为virtual,在main函数中三者都调用基类的析构函数,而不是各自的析构函数;而基类Human中的析构函数设置为virtual,对于Englishman会先调用自己的析构函数~Englishman(),再调用基类的析构函数~Human()

use hand to eat
~Human()
use knife to eat
~Englishman()
~Human()
use chopsticks to eat
~Chinese()
~Human()

7、重载不可设为虚函数,对于覆盖:函数参数、返回值相同,可以设为虚函数

8、返回值例外:函数参数相同,但返回值是当前对象的指针或者引用时,也可以设置为虚函数

如下程序,返回值不是对象的指针或者引用,因此这样编译会报错

class Human {
private:
	int a;
public:
	virtual void eating(void) { cout<<"use hand to eat"<<endl; }
	virtual ~Human() { cout<<"~Human()"<<endl; }
	virtual void test(void) {cout<<"Human's test"<<endl; }
};

class Englishman : public Human {
public:
	void eating(void) { cout<<"use knife to eat"<<endl; }
	virtual ~Englishman() { cout<<"~Englishman()"<<endl; }
	virtual int test(void) {cout<<"Englishman's test"<<endl; return 1; }
};


class Chinese : public Human {
public:
	void eating(void) { cout<<"use chopsticks to eat"<<endl; }
	virtual ~Chinese() { cout<<"~Chinese()"<<endl; }
	virtual int test(void) {cout<<"Chinese's test"<<endl; return 1; }
};

修改程序

...

class Human {
private:
	int a;
public:
	virtual void eating(void) { cout<<"use hand to eat"<<endl; }
	virtual ~Human() { cout<<"~Human()"<<endl; }
	virtual Human* test(void) {cout<<"Human's test"<<endl; return this; } 
};

class Englishman : public Human {
public:
	void eating(void) { cout<<"use knife to eat"<<endl; }
	virtual ~Englishman() { cout<<"~Englishman()"<<endl; }
	virtual Englishman* test(void) {cout<<"Englishman's test"<<endl; return this; }
};


class Chinese : public Human {
public:
	void eating(void) { cout<<"use chopsticks to eat"<<endl; }
	virtual ~Chinese() { cout<<"~Chinese()"<<endl; }
	virtual Chinese* test(void) {cout<<"Chinese's test"<<endl; return this; }
};

void test_return(Human& h)
{
	h.test();
}

int main(int argc, char **argv)
{
	Human h;
	Englishman e;
	Chinese c;

	test_return(h);
	test_return(e);
	test_return(c);


	return 0;
}

编译不会报错,执行结果如下

Human's test
Englishman's test
Chinese's test
~Chinese()
~Human()
~Englishman()
~Human()
~Human()

 

类型转换

分为两种,一种是隐式转换,另外一种是显式转换

 

隐式转换

如下C代码,其中"int i = d",double隐式转换转为int,"int *p = str",char*隐式转换转为int* 

	double d = 100.1;
	int i = d;
	char *str = "xiaoma";
	int *p = str;

显式转换

其中"int *p = (int *)str",char*显式强制转换转为int* ,打印语句在在64位系统下会有警告,指针是8字节,而unsigned int类型是4个字节

	char *str = "xiaoma";
	int *p = (int *)str;

	printf("i = %d, str = 0x%x, p = 0x%x\n", i, (unsigned int)str, (unsigned int)p); 

在C++中用以下四个函数进行转换(模板函数)

reinterpret_cast

格式:reinterpret_cast<type-id>(expression)

相当于C风格的用小括号"(type-id)"实现的强制类型转换

  • type-id必须是一个指针、引用、算术类型、函数指针或者成员指针
  • 它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针
  • 跟C风格的强制转换类似,没有安全性检查
	char *str = "xiaoma";
	int *p = reinterpret_cast<int *>(str);

const_cast

格式:const_cast<type_id>(expression)

该运算符用来去除原来类型的const或者volatile属性,除了const或者volatile修饰之外,type_id和expression的类型是一样的

看如下代码,reinterpret_cast不能直接强制转换str类型,不能去掉const属性,因此通过const_cast先把const属性去掉,再利用reinterpret_cast强制转换为int*类型

	const char *str = "100ask.taobao.com";  
	char *str2 = const_cast<char *>(str);   
	int *p = reinterpret_cast<int *>(str2);

dynamic

格式:dynamic_cast<type-id>(expression)

该运算符把expression转换为type-id类型的对象,type-is必须是类的指针、类的引用或者void *;如果type-id是类指针类型,那么expression也必须是一个指针;如果type-id是一个引用,那么expression也必须是一个引用。

  • 用于多态场合,即:必须有虚函数
  • 主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换
  • 在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。

在多态的例子中,想在test_eating函数中实现分辨人是英国人还是中国人

void test_eating(Human& h)
{
	Englishman *pe;
	Chinese    *pc;
	
	h.eating();

	/* 想分辨这个"人"是英国人还是中国人? */
	if (pe = dynamic_cast<Englishman *>(&h)) 
		cout<<"This human is Englishman"<<endl;

	if (pc = dynamic_cast<Chinese *>(&h))
		cout<<"This human is Chinese"<<endl;
}

int main(int argc, char **argv)
{
	Human h;
	Englishman e;
	Chinese c;

	test_eating(h);
	test_eating(e);
	test_eating(c);
	return 0;
}

执行结果如下,实现的机制,对于动态类型转换,在对象总通过指针找到虚函数表,在虚函数表中不仅含有虚函数信息,同时还含有继承信息,根据继承信息来判断

use hand to eat
use knife to eat
This human is Englishman
use chopsticks to eat
This human is Chinese
~Chinese()
~Human()
~Englishman()
~Human()
~Human()

在中国人类中派生出广西人,修改程序如下

class Guangximan : public Chinese {
public:
	void eating(void) { cout<<"use chopsticks to eat, I come from guangxi"<<endl; }
};

void test_eating(Human& h)
{
	Guangximan *pg;
	
	h.eating();

	/* 想分辨这个"人"是英国人还是中国人? */ 
	if (pe = dynamic_cast<Englishman *>(&h))
		cout<<"This human is Englishman"<<endl;

	if (pc = dynamic_cast<Chinese *>(&h)) 
		cout<<"This human is Chinese"<<endl;
	
	if (pg = dynamic_cast<Guangximan *>(&h))
		cout<<"This human is Guangximan"<<endl;
	
}

int main(int argc, char **argv)
{
	Guangximan g;

	test_eating(g); //广西人还是中国人,在类信息中还含有那些继承信息
	return 0;
}

执行结果如下,因为虚函数表中含有继承信息,因此既能转换为中国人,也能转换为广西人

use chopsticks to eat, I come from guangxi
This human is Chinese
This human is Guangximan
~Chinese()
~Human()

把指针改为引用,修改程序如下,如果是广西人转换为英国人,这里就会出现异常,转换不成功,程序直接崩溃因此程序在"Englishman& pe = dynamic_cast<Englishman&>(h)"就崩溃了,其中基类转换为派生类实现下行转换,对于引用的动态转换,转换过程有可能成功,也有可能失败,比强制类型转换更安全

void test_eating(Human& h)
{
	Englishman& pe = dynamic_cast<Englishman&>(h); 
	Chinese&    pc = dynamic_cast<Chinese&>(h);    //下行转换   
	Guangximan& pg = dynamic_cast<Guangximan&>(h);   
	h.eating();
}

int main(int argc, char **argv)
{
	Guangximan g;

	test_eating(g);

	return 0;
}

修改为强制类型转换,对于广西人强制转换为英国人就有问题,如果涉及英国人的操作就会有问题,但是编译能通过,因此不安全

void test_eating(Human& h)
{
	Englishman& pe = reinterpret_cast<Englishman&>(h); 
	Chinese&	pc = reinterpret_cast<Chinese&>(h); 
	Guangximan& pg = reinterpret_cast<Guangximan&>(h);

	h.eating();
}

static_cast

格式:static_cast<type-id>(expression)

该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。

  • 用于类层次结构中基类和子类之间指针或引用的转换。
  • 进行上行转换(把子类的指针或引用转换成基类表示)是安全的;
  • 进行下行转换(把基类指针或引用转换成子类指针或引用)时,由于没有动态类型检查,所以是不安全的。
  • 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum:这种转换的安全性也要开发人员来保证。
  • 把void指针转换成目标类型的指针(不安全!!)
  • 把任何类型的表达式转换成void类型。

注意:static_cast不能转换掉expression的const、volitale、或者__unaligned属性。

修改main函数,对于static_cast来说上行转换是安全的,下行转换检查不出来,有安全隐患

int main(int argc, char **argv)
{
	Human h;
	Guangximan g;
	Englishman *pe;

	pe = static_cast<Englishman *>(&h); //下行转换,但Human不一定是英国人,不安全,有安全隐患,static_cast检查不出来

	//Englishman *pe2 = static_cast<Englishman *>(&g); 上行转换,编译器会检查出错误,广西人不能够转换为英国人

	Chinese *pc = static_cast<Chinese *>(&g);  //上行转换是安全的

	return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值