C++中的派生类与基类继承关系详解

在C++中,派生类和基类之间的继承关系是非常重要的,本文将详细介绍派生类模型对象、派生类构造函数、派生类向基类的隐式类型转换以及父类子类之间的拷贝与赋值。

一、派生类模型对象简介

派生类模型对象包含多个组成部分,即派生类对象包含基类部分和派生类自身定义的成员函数。这使得我们可以用基类指针new一个子类对象,因为派生类对象含有基类部分,编译器帮我们做了一个隐式的类型转换。

    Men mymen;//子类(派生类对象),包含多个组成部分(也就是多个子对象)1)一个是含有派生类自己定义的成员函数,成员函数的子对象
	(2)一个是该派生类所继承的基类的子对象,这个子对象中包含的是基类中定义的成员变量、
	成员函数(派生类对象含有基类对应的组成部分)
	Human* phuman = new Men;//为什么基类指针可有new 派生类对象?
	因为派生类对象含有基类部分,所有我们可以把派生类对象当成基类指针用
	换句话说我们可以用基类指针new一个子类对象
	编译器帮我们做了一个隐式的类型转换。
	这种转换的好处就是有些需要基类引用的地方,可以用派生类对象的指针代替
	如果有些需要用基类指针的地方,也可以用派生类对象的指针来代替

二、派生类构造函数

派生类实际是使用基类构造函数来初始化它的基类部分,而派生类控制派生类部分成员的初始化。通过派生类的构造函数初始化列表,我们可以很方便地传递参数给基类构造函数。 你基类控制你基类部分的成员初始化,派生类控制派生类部分成员的初始化,各司其职

Men mymen;//即调用基类构造函数又调用派生类构造函数
	派生类实际是使用基类构造函数来初始化它的基类部分,
	你基类控制你基类部分的成员初始化,派生类控制派生类部分成员的初始化,各司其职

	如何传递参数给基类构造函数?
	  通过派生类的构造函数初始化列表;

	例如1//例1
class E {
public:
	E(int i): m_valueE(i){};//初始化列表
private:
	int m_valueE;
};
//不写public继承,默认私有继承,struct默认公有继承
class B : public E {
public:
	B(int i, int j, int k):E(i),m_valueB(k){};//通过子类的初始化列表给父类的构造函数传递参数
public:
	int m_valueB;
};

	B b(10, 20, 50);//先执行基类的构造函数,再执行派生类的构造函数。
	                先执行派生类的析构函数,再执行基类的析构函数。

三、既当父类又当子类

继承关系一直传递,构成了一种继承链;最终结果就是派生类会包含直接基类的成员和间接基类的成员。

    class gra {...};
	class fa : public gra {....};//gra是fa的直接基类
	class son:public fa {...};//gra是son的间接基类(爷爷类)
	继承关系一直传递,构成了一种继承链;最终结果就是派生类son会包含直接基类的成员和间接基类的成员

四、不想当基类的类

final:加在类名后面,有这个关键字的类就不能做基类了
使用final关键字可以防止类被当作其他类的基类,如果类E不想当类B的基类,可以使用final关键字

class E final {
public:
    E(int i) : m_valueE(i) {};//初始化列表
private:
    int m_valueE;
};

// 不写public继承,默认私有继承,struct默认公有继承
class B : public E {
public:
    B(int i, int j, int k) :E(i), m_valueB(k) {};//通过子类的初始化列表给父类的构造函数传递参数
public:
    int m_valueB;
};

五、静态类型与动态类型

在C++中,静态类型指的是变量声明时的类型,而动态类型指的是指针或引用所指向的内存中的对象的类型。当基类指针或引用指向派生类对象时,静态类型和动态类型可能不一致。

Human* phuman = new Men;//基类指针指向一个派生类对象
Human& q = *phuman;//基类引用绑定到派生类对象上

静态类型:变量声明时候的类型。编译的时候是已知的。
动态类型:指的是指针或者引用所表达(代表)的内存中的对象的类型。这里是Men动态类型。
    动态类型  是在运行的时候才能知道。
动态类型,静态类型这种概念,只有基类指针/引用,才存在静态类型和动态类型不一致的情况。
如果不是基类的指针或者引用,那么静态类型和动态类型都应该是一致的。

六、派生类向基类的隐式类型转换

编译器会隐式地执行派生类到基类的类型转换,因为每个派生类对象都包含一个基类对象部分,使得基类的引用或指针可以绑定到基类对象部分。然而,从基类到派生类的自动类型转换是不合法的,但可以通过dynamic_cast进行转换。

    Human * phuman = new Men;//基类指针指向一个派生类对象
	Human& q = *phuman;//基类引用绑定到派生类对象上
	编译器隐式的帮我们执行了派生类到基类的转换。
	转换成功的原因:是因为每个派生类对象都包含一个基类对象部分,所有基类的引用或指针可以绑到基类对象部分。
	基类对象 能独立存在,也能作为派生类对象的一部分存在。
	并不存在从基类到派生类的自动类型转换。如:Men *pen=new Human;
	Human human;
	Men& my = human;//非法,不能将基类转换成派生类。(派生类的引用不能绑定到基类的对象上去)
	Men* pmen = &human;//非法,不能将基类转换成派生类(派生类指针不能指向基类地址)

	Men men;
	Human* phuman = &men;//可以
	Men* pmen = phuman;//不可以,编译器,通过静态类型推断判断转换合法性。发现基类不能转成派生类
	如果基类中有虚函数的话,可以通过dynamic_cast可以转换。
	Men* pmen = dynamic_cast<Men*>(phuman);
	if (pmen != nullptr) {
		  ....
	}

七、父类子类之间的拷贝与赋值

用派生类对象为一个基类对象初始化或者赋值时,只有该派生类对象的基类部分会被拷贝或者赋值,而派生类部分将被忽略。

3
	Human(const Human& tephuman)//tephuman是得到了Men对象的引用。但是,Human作为基类,
		                        //只能处理自己的成员即便是一个派生类对象
	{
		int a;
		a = 1;
	}
	//例4
	Human& operator=(const Human& tmphuman)
	{
		//...
		return *this;
	}
	//例3
	Human human(men);//用派生类对象来定义并初始化基类对象。这个会导致拷贝构造函数的执行。
	Men men;
	Human human;
	//例4
	human = men;//调用的是赋值运算符
class Human {
public:
    virtual void speak() {
        cout << "I am a human" << endl;
    }
};

class Men : public Human {
public:
    void speak() override {
        cout << "I am a man" << endl;
    }
};

int main() {
    Human* phuman = new Men;
    Human& q = *phuman;

    Men men;
    Human* phuman2 = &men;
    Men* pmen = dynamic_cast<Men*>(phuman2);
    if (pmen != nullptr) {
        pmen->speak();
    }

    Men men1;
    Human human(men1);

    Men men2;
    Human human2;
    human2 = men2;

    return 0;
}

结论:
用派生类对象为一个基类对象初始化或者赋值时只有该派生类对象的基类部分会被拷贝或者赋值,派生类部分将被忽略掉,也就是基类只干自己的事,多余的部分不会去赋值

以上是C++中派生类与基类继承关系的一些重要概念和用法,希望对大家有所帮助。如果有任何疑问或建议,欢迎留言交流!

你可能感兴趣的:(c++,开发语言)