C++第三大特性:多态(1)

目录

   一.多态的含义

1.普通调用:

2.多态调用 

        重写函数:

实现多态调用的三个条件:(缺一不可)

        情况1:当只有父类中存在虚函数,两个子类都没有virtual形成的虚函数时,也能形成多态!

        特殊情况2:协变——也可以形成多态


   一.多态的含义

        多态!通俗来说,就是多种形态,具体上就是去完成某个行为,即当不同的对象去完成某个行为时会产生出不同的状态。而想要体现多态的特性,前提就需要用到继承特性!


        举个栗子:比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人买票时是优先买票。

        想要实现各类对象做某件事时产生不同的状态,有两种方式,一种是普通调用,另一种是多态调用。

1.普通调用:

class Person {
public:
	 void Func() {
		cout << "Person买票——全价" << endl;
	}
};

//子类1:
class Student :public Person{
public:
	 void Func() {
		cout << "Student买票——半价" << endl;
	}
};

//子类2:
class Soldier :public Person {
public:
	 void Func() {
		cout << "Soldier买票——免费" << endl;
	}
};

int main() {
	Person p1;
	Student st1;
	Soldier so1;

	//下面为普通调用,都是各类调用各类的函数
	p1.Func();
	st1.Func();
	so1.Func();
	return 0;
}

运行结果: 

2.多态调用 

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

class Student :public Person {
public:
	void virtual  Func() {
		cout << "Student买票——半价" << endl;
	}
};

class Soldier :public Person {
public:
	void virtual  Func() {
		cout << "Soldier买票——免费" << endl;
	}
};

void Fun1(Person& p) {
	p.Func();
}

void Fun2(Person* p) {
	p->Func();
}

int main() {
	Person p1;
	Student st1;
	Soldier so1;

	//多态调用
	Fun1(p1);
	Fun1(st1);
	Fun1(so1);

	//多态调用
	Fun2(&p1);
	Fun2(&st1);
	Fun2(&so1);

	return 0;
}

        我们发现在上方代码中,父类与子类的同名成员函数上都加上了virtual关键字,之前我们学习多继承的过程中,了解到virtual关键字的用法是用于解决菱形继承的二义性问题。而今天见到的virtual它又有了新的作用:虚函数,只要在类中加入virtual就代表了该函数是虚函数。 

        而虚函数的作用就是用于实现多态特性。

重写函数:

        重写函数得基于继承的前提下,父类子类都有相同名称、相同参数、相同函数返回值的虚函数叫做重写函数。子类中有一个跟父类完全相同的虚函数(即子类虚函数与父类虚函数的(返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了父类的虚函数。

        如上代码中,子类中的Func函数就是重写函数(重写了父类的Func函数)。重写,从字面意思上看就是重新编写这个从父类继承过来的Func函数,意味着子类的Func函数体中的内容与父类的Func函数是不一样的,这样是实现多态特性的一个重要因素。

        继续回到代码解析中,在上方测试代码中,创建了三类对象,然后对一个形参是父类引用的Fun1函数进行不同实参的调用, 通过调用同一个函数,调用参数不同,实现的结果也不同——这就是多态的特性。

        为什么调用同一个函数,而参数不同,最终实现的结果就不同呢?

        有两个原因:原因1在于Func函数的形参:Person& p  这是父类的引用!!! 

        之前在讲继承的时候讲过一个子类对象可以向上转换赋值父类对象,C++继承——子类对象对父类对象的赋值操作。子类对象之所以能够向上转换赋值父类对象是因为切片,子类对象中包含了父类继承过来的父类成员变量,子类对象在向上转换时切割出父类继承过来的成员变量,之后子类对象就可以将切割出来的成员变量赋值给父类对象——完成了子类对象切割转换赋值父类对象的操作因为person& p ,在main中既可以用Person类的对象做实参,也可以用子类Student/Soldier 的对象做实参,最终完成多态。

 参数的不同实现导致了不同形式的生成。

实现多态调用的三个条件:(缺一不可)


       1.使用virtual关键字修饰父子类的某一同名函数为虚函数
       2.虚函数需要实现三同(同名、同参数、同返回值)——重写函数
       3.多态调用需要使用父类引用/指针 

但在写代码的过程中我们发现:

        情况1:当只有父类中存在虚函数,两个子类都没有virtual形成的虚函数时,也能形成多态!
class Person {
public:
	void Func() {
		cout << "Person买票——全价" << endl;
	}
};

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

class Soldier :public Person {
public:
	void Func() {
		cout << "Soldier买票——免费" << endl;
	}
};

        对于子类中不加virtual的函数,运行测试案例时,也能够实现多态特性。 

         如上可知,只有父类Person中有虚函数时,最终调用Fun1也能够完成多态,这是因为子类Student/soldier继承了父类的成员,其中也包括了父类的成员函数,那么即使子类中不亲自写出相同名称类型返回值的虚函数也可以,有继承过来的成员函数。

          
        但是当父类中没有加virtual,而子类Student中虽然有virtual,但不能形成多态了,可以试试,结果就会变成3个句子一样,都是调用的Person的Func函数了。


        特殊情况2:协变——也可以形成多态

协变:三同函数中,返回值可以不同,但是要求返回值必须是一个父子类关系的指针或者引用。

代码如下:

class Person {
public:
	virtual Person*   Func() {
		cout << "Person买票——全价" << endl;
		return nullptr;
	}
};

class Student :public Person {
public:
	virtual Student*  Func() {
		cout << "Student买票——半价" << endl;
		return nullptr;
	}
};

class Soldier :public Person {
public:
	virtual Soldier*  Func() {
		cout << "Soldier买票——免费" << endl;
		return nullptr;
	}
};
  

其他代码不变,运行测试用例:

         由上图代码可知:父类的虚函数的返回值变成了Person* ,子类的虚函数返回值分别是Student* 和Soldier*类型的,仍然形成了多态。

        对于违反协变的,编译器都会报编译错误!如下:


 


二.父类析构函数要加virtual

class Person {
public:
	virtual Person*  Buy_Ticket() {
		cout << "普通人买票——全价" << endl;
		return nullptr;
	}

	~Person() {
		cout << "Person析构 " << endl;
		cout << endl;
		delete[] _i;
	}

private:
	int* _i = new int[10];
};


class Student :public Person {
public:
	 Student*  Buy_Ticket() {
		cout << "学生买票——半价" << endl;
		return nullptr;
	}

	 ~Student() {
		 cout << "Student析构 " << endl;
		 delete[] _s;

	 }
private:
	int* _s = new int[20];
};


class Soldier :public Person {
public:
	 Soldier*  Buy_Ticket() {
		cout << "军人买票——优先" << endl;
		return nullptr;
	}

	 ~Soldier() {
		 cout << "Soldier析构 " << endl;
		 delete[] _sd;
	 }
private:
	int* _sd = new int[20];
};


void Test3() {
	//Person p1;
	//Student st1;
	//Soldier sd1;
	//子类在作用域结束时,编译器会自动调用析构函数
	//先析构子类对象,再调用父类的析构

	cout << endl;


	//通过父类指针,去指向子类对象——创建子类的堆区空间
	Person* pp1 = new Person;
	Person* stt1 = new Student;
	Person* sdd1 = new Soldier;

	delete pp1;
	delete stt1;
	delete sdd1;
}

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

通过运行Test3()函数的代码可知:

 

        delete 这三个指针对象后,看结果,这三个指针只调用了父类的Person析构,造成了内存泄漏!!!
        stt1虽然是父类指针,但指向了子类对象,也需要调用子类的析构函数才行。
        所以以上代码造成了内存泄漏,改正方案是需要让其满足多态条件,调用子类的析构函数!

解决方案:

class Person {
public:
	virtual Person*  Buy_Ticket() {
		cout << "普通人买票——全价" << endl;
		return nullptr;
	}

	//~Person() {
	//	cout << "Person析构 " << endl;
	//	cout << endl;
	//	delete[] _i;
	//}

	//给父类的析构函数无脑+ virtual
	virtual ~Person() {

		cout << "Person析构 " << endl;
		cout << endl;
		delete[] _i;
	}

private:
	int* _i = new int[10];
};

只要父类的析构成为虚函数,那么子类在重写时就会产生多态的特性,那么父类指针在指向子类对象后析构,就会把子类的析构函数也调用上! 

        有人想问了:多态的满足条件不是还要子类的析构与父类的析构三同吗?

        

        是三同,子类的析构和父类的析构表面上函数参数相同、返回值类型相同(void);其实,深层上,它们的函数名称也相同,底层都是叫destructor——所以符合三同重写的条件

 改正结果:

总结:以后给父类的析构函数都无脑+ virtual,以防内存泄漏

猜你喜欢

转载自blog.csdn.net/weixin_69283129/article/details/132026085