C++小工修炼之路XXI(多态上)

什么是多态?你对多态的认识?
简单来说就是同一事物在不同的场景下表现出的多种形态。
栗子:网上的例子,见人说人或话,见鬼说鬼话。
一个人见到不同身份的人说话的态度不一样。
在这里插入图片描述
多态的实现条件:

在继承的体系下
基类中必须有虚函数,被virtual修饰的成员函数(注意一定是成员函数),友元函数不是虚函数
在派生类中必须要对基类中的虚函数进行重写
对于虚函数的调用:必须使用基类的指针或者引用调用多态。多态的发生就是在基类的指针或者引用指向子类的对象的时候。

体现多态性:在代码运行时,基类指针指向哪个类的对象,就调用哪个类的虚函数

多态的实现:
汉语命名好玩,但是感觉好乱

class 人类
{
public:
	人类(string 名字,string 性别,int 年龄)
		:_名字(名字)
		, _性别(性别)
		, _年龄(年龄)
	{}
	virtual void 买票()
	{
		cout << "全价票" << endl;
	}
protected:
	string _名字;  //姓名
	string _性别;   //性别
	int _年龄;  //年龄
};

class 学生类 : public 人类
{
public:
	学生类(string 名字, string 性别, int 年龄, int 学号)
		:人类(名字, 性别, 年龄)
		,_学号(学号)
	{}
	virtual void 买票()
	{
		cout << "半价票" << endl;
	}
protected:
	int _学号;  //学号
};
class 军人类 : public 人类
{
public:
	军人类(string 名字, string 性别, int 年龄, string 军衔)
		:人类(名字, 性别, 年龄)
		, _军衔(军衔)
	{}
	virtual void 买票()
	{
		cout << "免票" << endl;
	}
protected:
	string _军衔;  //学号
};
int main()
{
//必须通过基类的指针或引用来调用
//多态的体现就是:代码编译的时候不能确定到底调用哪个类的虚函数,要在代码运行时才可以确定下来
	军人类 张超伦("张超伦", "男", 28,"大佐");
	学生类 小董("小董", "男", 20, 1740);
	人类& 九一刘先生 = 张超伦;  //发生多态
	//根据基类指向的派生类的对象,才能确定调用哪一个类的虚函数
	人类& 小王 = 小董;   //发生多态
	九一刘先生.买票();
	小王.买票();
	return 0;
}

发生多态后打印结果:
在这里插入图片描述
重写:派生类重写基类的某个虚函数,派生类虚函数必须要和基类中虚函数的原型完全一致,原型完全一致指的是,返回值类型,函数名,参数类型,完全一致。
重写:一定是派生类重写基类的虚函数
1.一个函数在基类中,一个函数在子类中
2.基类中的成员函数必须是虚函数:基类的成员函数前必须增加virtual的关键字,
3.子类同名成员函数前virtual关键字是否田间都可以。子类加不加都可以,在子类中就算不加virtual关键字,但是实际上它还是虚函数,所以说建议最好加上。
4.基类虚函数必须要与派生类虚函数的原型完全一致(返回值类型,函数名,参数列表)。

关于原型完全一致也是存在**两个例外(协变,析构函数)**的:
协变:
基类中虚函数返回的是基类的引用或者指针,子类的虚函数返回子类的引用或者指针
此时就算在基类和派生类中虚函数原型不一致,这照样可以形成重写,也可以发生多态。

利用协变发生多态:

class Base
{
public:
	virtual Base* test()    在基类中返回基类的指针或者引用
	{
		cout << "base()" << endl;
		return this;
	}
};
class Dri : public Base
{
public:
	virtual Dri* test()   //在子类中返回子类的指针或者引用
	{
		cout << "Dri()" << endl;
		return this;
	}
};
void test(Base* p)
{
	p->test();
}
int main()
{
	Base b;
	Dri d;
	test(&b);
	test(&d);
	return 0;
}

关于协变的用法:

在这里插入图片描述

举例说名:

class A
{
public:
	virtual A* test()
	{
		cout << "A" << endl;
		return this;
	}
};
class B : public A
{
public:
	virtual A* test()  //返回值是基类的指针
	{
		cout << "B" << endl;
		return this;
	}
};
class C : public B
{
public:
	virtual B* test()   //返回的是基类的指针
	{
		cout << "C" << endl;
		return this;
	}

};
void test(A* p)
{
	p->test();
}
int main()
{
	A a;
	B b;
	C c;
	test(&a);
	test(&b);
	test(&c);
	return 0;
}

上面代码的关系:
在这里插入图片描述

2.析构函数:

class A
{
public:
	A()
	{
		cout << "A" << endl;
	}
	virtual ~A()  //注意这里将析构函数给成了虚函数
	{
		cout << "~A()" << endl;
	}
};
class B : public A
{
public:
	B() :p(new int[10])
	{
		cout << "B" << endl;
	}
	~B()
	{
		delete[] p;
		cout << "~B()" << endl;
	}
	int* p;
};
void test()
{
	A* p = new B;
	delete p;
}
int main()
{
	test();
	return 0;
}

打印结果:
在这里插入图片描述
假如不把A类的析构函数给成虚函数的话。
在这里插入图片描述

打印结果就变成了:
在这里插入图片描述
说明并没有调用子类的析构 ,也就是没有释放子类申请的空间

所以析构函数也是一个例外,虽然基类和派生类析构函数名不一样,但是照样发生了重写。

析构函数和构造函数都没有办法被继承,
构造函数可以被重载,析构函数不可以被重载
析构函数能作为虚函数,构造函数不可以作为虚函数

添加:
析构函数不可以声明为const
C++11auto是在编译时器进行推演,所以不可以推演函数参数和数组类型,
this指针可以为空,也就是说,自定义类型的指针指向空之后,照样存在有this指针。

析构函数不能被重载,构造函数可以被重载
析构函数和构造函数都不可以被继承
析构函数可以是虚函数,构造函数不可以是虚函数

static成员可以被继承
static成员不能被作为虚函数
static成员大小不包含在类的成员大小中
友元函数不可以被继承

一个类不想被继承可以使用final,
一个类不想实例化出对象,可以在类中的虚函数后面 添加=0,则此函数为纯虚函数,包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象
派生类不充写抽象类中的纯虚函数的话也无法实例化对象
基类对象使用同一张虚表,虚表地址相同
派生类和基类永远不会使用同一张虚表,哪怕派生类并没有重写积累的虚函数,只是照搬下来,此时基类的派生类虚表地址不同(说明不是同一张虚表),但是内容一致

构造函数和析构函数可以抛出异常么?
首先是析构函数。

一. 析构函数

 参照《Effective C++》中条款08:别让异常逃离析构函数。

总结如下:

 1. 不要在析构函数中抛出异常!虽然C++并不禁止析构函数抛出异常,但这样会导致程序过早结束或出现不明确的行为。

 2. 如果某个操作可能会抛出异常,class应提供一个普通函数(而非析构函数),来执行该操作。目的是给客户一个处理错误的机会。

 3. 如果析构函数中异常非抛不可,那就用try catch来将异常吞下,但这样方法并不好,我们提倡有错早些报出来。

二. 构造函数

 总结如下:

 1. 构造函数中抛出异常,会导致析构函数不能被调用,但对象本身已申请到的内存资源会被系统释放(已申请到资源的内部成员变量会被系统依次逆序调用其析构函数)。

 2. 因为析构函数不能被调用,所以可能会造成内存泄露或系统资源未被释放。

 3. 构造函数中可以抛出异常,但必须保证在构造函数抛出异常之前,把系统资源释放掉,防止内存泄露。(如何保证???使用auto_ptr???)

最后总结如下:

  1. 构造函数中尽量不要抛出异常,能避免的就避免,如果必须,要考虑不要内存泄露!

  2. 不要在析构函数中抛出异常!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值