首页 > 编程语言 >详解c++多态中的析构与构造函数

详解c++多态中的析构与构造函数

时间:2024-03-13 20:58:23浏览次数:14  
标签:析构 子类 函数 多态 Cat Animal 派生类 指针 构造函数

首先简单介绍一下多态。

多态是面向对象编程中的概念,它允许我们使用基类类型的指针或引用来调用派生类对象的方法。C++中实现多态主要依靠虚函数动态绑定

那怎么使用多态呢?

基类指针或引用指向派生类对象。

在我学习过程中,这些概念耳熟能详,但是为什么要有多态呢,先看下面这段代码

class Animal
{
public:
	void speak()
	{
		cout << "动物在说话" << endl;
	}
};

class Cat :public Animal
{
public:
	void speak()
	{
		cout << "小猫在说话" << endl;
	}
};

void doSpeak(Animal &animal)
{
	animal.speak();
}

void test()
{
	Cat cat;
	doSpeak(cat);
}

int main()
{
	test();
}

Cat是Animal的派生类,在doSpeak函数中传入派生类对象的引用,最终的输出如下:

那么为什么结果是这个呢,刚学完继承的时候觉得子类重写的方法会覆盖父类,现在又搞不懂了。

当调用 doSpeak(cat) 时,参数 cat 实际上是一个 Cat 类型的对象,但编译器会进行切片操作,将  Cat 对象切割成一个 Animal 对象传递给 doSpeak() 函数。这意味着在 doSpeak() 函数中,实际上只能访问到 Animal 对象的部分,即使传入的是 Cat 对象。

那么切片是什么呢?

将一个派生类对象(如 Cat 对象)赋值给一个基类对象(如 Animal 对象)时,会发生切片。

也就是用基类类型的指针或引用来调用派生类对象实,会发生切片。

在 C++ 中,当通过基类指针指向派生类对象时,这个指针只能访问到基类中定义的成员变量和成员函数,而无法直接访问派生类特有的成员变量和成员函数。这就是所谓的“切片”(slicing)问题。
这意味着只有基类的部分成员和方法会被保留,而派生类特有的成员和方法会被丢弃

在调用 doSpeak(cat) 函数时,实际上是将Cat 对象传递给了一个接受 Animal​​​​​​​ 引用的函数。由于 doSpeak() 函数的参数类型是 Animal &,因此 Cat 对象会被切片为一个 Animal 对象,只有 Animal 类的部分内容被传递给了函数,导致无法访问到 Cat 类特有的内容。

因此,在没有使用虚函数的情况下,如果直接将派生类对象赋值给基类对象或传递给基类引用/指针,可能会导致切片问题,使得只有基类的部分功能可用,而派生类特有的功能则无法访问。

那我们之所以将基类指针指向派生类对象,目的是为了实现派生类中独有的方法,于是乎就有了多态。

---------------------------------------------------------

关于如何使用多态,网上有很多优秀的帖子,本文不过多赘述,直接切入主题——多态中的构造和析构函数。

先看代码

class Animal
{
public:
	Animal()
	{
		cout << "Animal构造函数调用" << endl;
	}
	
	virtual void speak() = 0;

    ~Animal()
	{
		cout << "Animal析构函数调用" << endl;
	}
};

class Cat : public Animal
{
public:
	Cat(string name)
	{
		cout << "Cat构造函数调用" << endl;
		m_Name = new string(name);
	}

	virtual void speak()
	{
		cout << *m_Name << "小猫在说话" << endl;
	}

	string* m_Name;

	~Cat()
	{
		if (m_Name != NULL)
		{
			cout << "Cat析构函数调用" << endl;
			delete m_Name;
			m_Name = NULL;
		}
	}
};

void test01()
{
	// 使用 new 运算符创建对象时,会分配内存并自动调用对象的构造函数来初始化对象,确保对象在创建时被正确初始化。
	Animal* animal = new Cat("tom");
	animal->speak();
	delete animal;// 删除父类指针的时候并不会走子类的析构函数
}

int main()
{
	test01();
}

结果如下

只有4行输出,从继承的角度来说应该有5行输出。
子类创建对象时有五个步骤:

  1. 先调用父类的构造函数
  2. 再调用子类的构造函数
  3. 实现子类重写的成员方法(如果有的话)
  4. 子类对象销毁时调用子类的析构函数
  5. 最后调用父类的析构函数

图中的输出结果少了一个第4步,我们来分析一下输出的每一步的由来。

首先在test01()中new Cat("Tom"),此时Cat对象在堆区被创建,自动执行1和2步,得到“Animal构造函数调用”和“Cat构造函数调用”。

然后调用子类重写的speak()方法,通过多态的方式得到“tom小猫在说话”。

最后删除父类指针时,为什么只调用了父类的析构函数,没调用子类的析构函数呢?
其实跟上文所说的切片有关。
​​​​​​​尽管是通过 Animal* animal = new Cat("tom"); 这样的语句创建了一个 Cat 对象,并将其地址赋给了一个 Animal 类型的指针 animal,但实际上这只是将 Cat 对象的地址存储在了一个 Animal 类型的指针中。由于切片作用,编译器在这种情况下只会关注指针的类型,即基类类型 Animal,而不会考虑指针所指向的实际对象的类型。
所以销毁animal指针时,只会调用父类的析构函数,因为父类指针指向子类,经过切片,这个指针只能访问到基类中定义的成员变量和成员函数,而无法直接访问派生类特有的成员变量和成员函数。

那么这个时候怎么才能调用子类的析构函数呢,没错,是virtual。

至于为什么加了析构就可以使用子类,相信看到这篇帖子的伙伴都知道虚函数表指针,很容易理解。

于是在delete animal时,调用了子类的析构函数,但是由于子类继承父类,子类销毁时在自动调用析构函数后,会自动调用父类的析构函数,也就出现了“Cat析构函数调用”之后输出“Animal析构函数调用”。

标签:析构,子类,函数,多态,Cat,Animal,派生类,指针,构造函数
From: https://blog.csdn.net/weixin_44115575/article/details/136689784

相关文章

  • C++多态和虚函数
    C++多态和虚函数#include<iostream>usingnamespacestd;//基类PeopleclassPeople{public:People(char*name,intage);voiddisplay();protected:char*m_name;intm_age;};People::People(char*name,intage):m_name(name),m_age(age){}......
  • C#构造函数
    C#中的构造函数是一种特殊的方法,用于创建和初始化类的对象。构造函数的名称与类的名称相同,并且没有返回类型。在C#中,构造函数有以下几种类型:默认构造函数:如果在类中没有定义构造函数,系统将自动提供一个默认构造函数。默认构造函数没有任何参数,并且什么都不做。带参数的构......
  • C++看程序写结果 虚函数、构造、析构、初始化列表
    虚函数、构造、析构、初始化列表#include<iostream>usingnamespacestd;classBase{public:Base(constchar*p_szName):m_szName(p_szName){cout<<"Base类构造:"<<m_szName<<","<<endl;}virtual~Base(){cout......
  • 多态性#java#面向对象
    多态性静态多态:也称为编译期间的多态,编译器在编译期间完成的,主要通过函数重载实现。编译器根据相同函数名的不同参数列表,可推断出要调用哪个函数。publicclassPerson{publicvoidsay(){System.out.println("我是第一个名为say的方法!");}publicvo......
  • 子类包含父类成员的构造与析构顺序
    子类包含父类成员的构造与析构顺序#include<iostream>usingnamespacestd;classF1{public:F1(){cout<<"F1构造函数"<<endl;}~F1(){cout<<"F1析构函数"<<endl;}};classF2{public:F2(){cout<<"......
  • 多态实际例子,接口实际例子
    //业务:订单需要支持多种支付方式。换句话:订单需要支持多种支付【方法】//订单里面需要通过【调用同一方法】但是要求有【不同表现形式】————怎么才能【调用同一方法】能够有【不同表现形式】?记住通过【不同的对象调用同一方法】就可以实现【不同表现形式】,这个对象是传进......
  • TS多态
    多态:【同一类型的对象具有不同的行为】或者说【同一类型的不同对象具有不同的行为】因为Stu与Teacher都实现了Person,所以他们的类型都可以设置为Person此时p1与p2都是同一类型的,但是p1,p2的调用相同方法,结果不一样多态:【同一类型的对象具有不同的行为】或者说【同一类型的不......
  • 带析构语义的类的C++异常处理
    C++异常处理#include<iostream>#include<string>usingnamespacestd;classMyException{public:MyException(conststring&message):message(message){}~MyException(){}conststring&getMessage()const{returnmessage;}pr......
  • Vue学习笔记36--VueComponent构造函数
    VueComponent构造函数<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width,initial-scale=1.0"><title>VueComponent&......
  • C++派生类构造函数
    实例#include<iostream>usingnamespacestd;classBase1{//基类Base1,构造函数有参数public:Base1(inti){cout<<"ConstructingBase1"<<i<<endl;}};classBase2{//基类Base2,构造函数有参数public:Base2(intj){cout<&l......