栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

[C++]多态(上)

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

[C++]多态(上)

目录
  • 一、多态的概念
  • 二、多态的定义及实现
    • 2.1 多态构成条件
    • 2.2 虚函数
    • 2.3 虚函数的重写
      • 2.3.1 虚函数重写的两个意外
    • 2.4 C++11 override和final
    • 2.5 重载、覆盖(重写)、隐藏(重定义)的对比


一、多态的概念

多态:通俗来说就是多种形态
多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态
静态的多态:函数重载(同一个函数可以实现多种不同的形态,看起来调用同一个函数有不同的行为,实际上调用的都是不同的函数)这里的静指的是编译时实现的(原理)
动态的多态:一个父类的引用或是指针去调用同一个函数,如果传递不同的对象,会调用不同的函数。动态:运行时实现(原理)
本质:不同人做同一件事,结果不同

class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
	 
};
void Func(Person& p) // 父类的引用
{
	p.BuyTicket(); // 多态
}
int main()
{
	Person ps;
	Student st;
	Func(ps);
	Func(st); // 子类对象也可以给父类对象
	return 0;
}

swap函数模板实际上不是多态,模板只是实例化成了多个函数,函数构成了重载;而是函数模板实例化生成了多份函数,这时候才是多态


二、多态的定义及实现 2.1 多态构成条件

在继承中要构成多态需要两个条件:

  1. 必须通过基类的指针或者引用调用虚函数(只有这样子才可以接受父类对象或是子类对象)
  2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
// 传对象呢?——这时候就不构成多态了
void Func(Person p) // 原本是void Func(Person& p)
{
	p.BuyTicket(); // 多态
}

构成多态:传的是哪一个类型的对象,调用的就是这个类型的虚函数——与对象有关
不构成多态:调用的就是p类型的函数——与类型有关

void Func(Person* p) // 父类的引用
{
	p->BuyTicket(); // 多态
}

传指针也是可以的

2.2 虚函数

虚函数:即被virtual修饰的类成员函数称为虚函数。

class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
};

静态成员函数不能加virtual,只能是类的非静态成员函数才能是虚函数

2.3 虚函数的重写

虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。

子类中满足虚函数的重写要求:首先是虚函数,而且要求三同(函数名、参数、返回值)
虚函数是重写的条件之一

class Person {
public:
	void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
	void BuyTicket() { cout << "买票-半价" << endl; }
};
void Func(Person& p)
{
	p.BuyTicket(); // 这里构成隐藏,为什么还能调用这个函数?
}
int main()
{
	Person ps;
	Student st;
	Func(ps);
	Func(st); // 子类对象也可以给父类对象
	return 0;
}

为什么这里构成了隐藏,还是能够调用全票函数?

隐藏与父类无关,隐藏是影响的子类
子类中有两个BuyTicket()函数,想让子类去调用父类的需要加域限定符


若参数不同,则不会实现多态

class Person {
public:
	virtual void BuyTicket(char ch) { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
	virtual void BuyTicket(int i) { cout << "买票-半价" << endl; }
};
void Func(Person& p) // 父类的引用
{
	p.BuyTicket(10); 
	// 这里与隐藏不隐藏无关,因为隐藏是子类对象需要考虑的,这里只考虑是否会构成多态
}

这种情况会调用哪一个?——还是父类的(因为不构成多态了)
不构成多态,调用的就是p的类型——与类型有关系
构成多态后,与p的类型就无关了,传哪一个类型的对象,就会去调用哪一个类型的虚函数

2.3.1 虚函数重写的两个意外

重写要求返回值相同有一个例外:协变
协变:要求返回值是父子关系的指针或引用

// 当返回值是父子关系的对象
class A
{};
class B : public A
{};
class Person {
public:
	virtual A BuyTicket() { cout << "买票-全价" << endl; return A(); }
};
class Student : public Person {
public:
	virtual B BuyTicket() { cout << "买票-半价" << endl; return B(); }
};
void Func(Person& p) // 父类的引用
{
	p.BuyTicket(); // 多态
}
int main()
{
	Person ps;
	Student st;
	Func(ps);
	Func(st); 
	return 0;
}


匿名对象不能用引用返回

// 当返回的是指针或是引用就是多态了
class A
{};
class B : public A
{};
class Person {
public:
	virtual A* BuyTicket() { cout << "买票-全价" << endl; return nullptr; }
};
class Student : public Person {
public:
	virtual B* BuyTicket() { cout << "买票-半价" << endl; return nullptr; }
};


因此遇到虚函数重写必须要求返回值相同这句话是要看有没有更适合的选项,如果没有就认为它是错的

class A
{};
class B : public A
{};
class Person {
public:
	virtual Person* BuyTicket() { cout << "买票-全价" << endl; return nullptr; }
};
class Student : public Person {
public:
	virtual Student* BuyTicket() { cout << "买票-半价" << endl; return nullptr; }
};

自己的父亲关系指针也可以

析构函数的重写(基类与派生类析构函数名字不同)
析构函数是虚函数构成重写吗?——构成

class Person {
public:
	virtual ~Person() { cout << "~Person()" << endl; }
};
class Student : public Person {
public:
	virtual ~Student() { cout << "~Student()" << endl; }
};

他们俩如果不是虚函数构成隐藏——析构函数名被特殊处理成了destructor

他们有完成正常的析构,s对象有两个析构,先析构子,再析构父

普通对象:析构函数是否是虚函数,是否完全重写都正确调用了,因此普通对象不需要写为虚函数,下面的情况才要写为虚函数

class Person {
public:
	~Person() { cout << "~Person()" << endl; }
};
class Student : public Person {
public:
	~Student() { cout << "~Student()" << endl; }
};
// 只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函数,才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数。
int main()
{
	Person* p1 = new Person; // new是由operator new 开空间  +   构造函数
	Person* p2 = new Student;

	// delete是由 析构函数 + operator delete构成
	//            p1->destructor()
	delete p1; // 父类对象调用父类析构函数
	//            p2->destructor()
	delete p2; // 子类对象调用父类析构函数(子类有子类和父类两部分,这时候只销毁了父类)
	return 0; 
}


因为他不符合多态,所以没去调用子类的(都调用父类的析构是因为p1、p2类型都是Person),想要让它符合多态,就需要去加上虚函数

class Person {
public:
	virtual ~Person() { cout << "~Person()" << endl; }
};
class Student : public Person {
public:
	virtual ~Student() { cout << "~Student()" << endl; }
};
int main()
{
	Person* p1 = new Person; 
	Person* p2 = new Student;
	delete p1; 
	delete p2; 
	return 0; 
}


这时候因为构成了多态,p2指向的student,就可以去调用student了

int main()
{
	Person* p1 = new Person;
	Student* p2 = new Student; // 这里就不需要实现虚函数,因为没有给父类指针管理,而且这里也不是多态
	delete p1; 
	delete p2;
	return 0; 
}

动态申请的父子对象,如果给了父类指针管理,那么需要析构函数是虚函数,完成重写,构成多态,才能正确的调用析构函数,在其他场景析构函数是不是虚函数都可以正确调用析构函数

class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
	virtual ~Person() { cout << "~Person()" << endl; }
};
class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
	virtual ~Student() { cout << "~Student()" << endl; }
};
void Func(Person& p)
{
	p.BuyTicket();
}
int main()
{
	Person* p1 = new Person; 
	Student* p2 = new Student;

	delete p1; 
	delete p2;

	Person ps;
	Student st;
	Func(ps);
	Func(st);
	return 0; 
}


当父子都不是虚函数的时候,不构成多态

当父类不是虚函数,子类是虚函数也不构成多态(上图)

当父是虚函数,子不是虚函数构成多态,虽然子类没写virtual,但是他是先继承父类虚函数的属性、再完成的重写,那么他也算是虚函数

虚函数的重写允许两个都是虚函数或者父类是虚函数再满足三同就构成了重写
这里是C++不是很规范的地方,建议都要写上virtual

当修改访问限定符后呢?

class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
	virtual ~Person() { cout << "~Person()" << endl; }
};


class Student : public Person {
	void BuyTicket() { cout << "买票-半价" << endl; }

public:
	virtual ~Student() { cout << "~Student()" << endl; }
};
void Func(Person& p)
{
	p.BuyTicket();
}

这里还构成多态吗?

虽然是私有,因为多态,所以这里还是能够调用到,因为继承下来了属性

为什么子类可以不去写virtual,本质上是因为析构函数,设计初衷是父类析构函数+virtual就不构成多态,没有调用到子类析构函数,内存泄漏的场景

建议我们自己写的时候都加上virtual,这样子肯定没问题

2.4 C++11 override和final

若要设计一个不能被继承的类如何做?
C++98是将构造函数弄为私有

class A
{
private:
	A(int a = 0)
		:_a(a)
	{}

protected:
	int _a;
};
class B : public A
{

};
int main()
{
	B bb; // 子类要初始化父类,需要去调用父类的构造函数——私有是不可见的所以子类就没办法去构造了
	A aa; // A也构造不了了——这时候是有方法(单例方法,之后会讲)
	return 0;
}

这样子就可以创建A类的对象

class A
{
private:
	A(int a = 0)
		:_a(a)
	{}

public:
	static A CreateObj(int a = 0)
	{
		// new A;
		return A(a);
	}

protected:
	int _a;
};
class B : public A
{

};
int main()
{
	// B bb; // 子类要初始化父类,需要去调用父类的构造函数
	A aa = A::CreateObj(10); // 如果不是静态的就调用不到了,需要用对象来调用这个函数,但是调用这个函数就需要是对象,不需要对象就可以去调用静态的函数
	return 0;
}

C++11后就解决了这种问题
使用final表明这个类为最终类,这样子就不能去继承了

class A final
{
protected:
	int _a;
};
class B : public A
{

};


final还可以去限制重写

class C
{
public:
	virtual void f() final
	{
		cout << "C::f()" << endl;
	}
};
class D : public C
{
public:
	virtual void f()
	{
		cout << "D::f()" << endl; // 这里就会报错,无法完成重写
	}
};

override可以帮助检查是否完成了重写,如果没有完成重写就会报错
override放在子类重写的虚函数的后面

class Car {
public:
	virtual void Drive() {}
};

class Benz :public Car {
public:
	virtual void Drive() override 
	{ cout << "Benz-舒适" << endl; }
};
2.5 重载、覆盖(重写)、隐藏(重定义)的对比

重载

  1. 要求两个函数在同一个作用域内
  2. 函数名相同,参数不同,对返回值没有要求

重写(覆盖)

  1. 两个函数分别在基类和派生类的作用域
  2. 函数名、参数、返回值都必须要求相同(协变例外)
  3. 两个函数都必须是虚函数

重定义(隐藏)

  1. 两个函数分别在基类和派生类的作用域
  2. 函数名相同
  3. 两个基类和派生类的同名函数不构成重写就是重定义
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1029316.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号