C++ 之继承

C++ 之继承



0.利用 cl.exe 工具查看某个类的对象内存模型

Visual Studio 自带了一个很有用的工具cl.exe。使用该工具可以查看 【该类对象的内存模型】
该工具的使用方法:

  • 找到 VS 20XX的开发人员命令提示符,单击打开
  • 使用 cd 命令,跳转至我们要查看的类所在目录
  • 使用命令:cl /d1 reportSingleClassLayout类名 "文件名" 即可查看【指定类对象的内存模型】
    例如:cl /d1 reportSingleClassLayoutDerive "DeriveDemo.cpp"
    找到VS20XX的开发人员命令提示符

1.继承的基本语法

在为某个基类定义派生类对象时,可以指定继承的方式:public 继承、protected 继承、private 继承。

class Base{
public:
	Base(){}
	~Base(){}
};

// : 之后指定继承的方式,此处选择的 public 继承
class Derive1:public Base{
public:
	Derive1(){}
	~Derive1(){}	
};
// : 之后指定继承的方式,此处选择的 protected继承
class Derive2:protected Base{
public:
	Derive2(){}
	~Derive2(){}	
};
// : 之后指定继承的方式,此处选择的 private继承
class Derive3:private Base{
public:
	Derive3(){}
	~Derive3(){}	
};


2.不同继承方式区别

  • public 继承
class Base{
public:
	int m_BaseA;
protected:
	int m_BaseB;
private:
	int m_BaseC;
};
class Derive public Base{
public:
	int m_DeriveD;
};

此时,派生类Derive的实例化对象中包含:m_BaseAm_BaseBm_BaseCm_Derive四个成员,其中前三个继承自基类Base【基类的私有成员变量m_BaseC被编译器隐藏,子类中有这个变量,但是没有权限访问该变量】
m_BaseA在派生类中的访问权限是 【public】m_BaseB在派生类中的访问权限是 【protected】

  • protected 继承
class Base{
public:
	int m_BaseA;
protected:
	int m_BaseB;
private:
	int m_BaseC;
};
class Derive protected Base{
public:
	int m_DeriveD;
};

此时,派生类Derive的实例化对象中包含:m_BaseAm_BaseBm_BaseCm_Derive四个成员,其中前三个继承自基类Base【基类的私有成员变量m_BaseC被编译器隐藏,子类中有这个变量,但是没有权限访问该变量】
m_BaseA在派生类中的访问权限是 【protected】m_BaseB在派生类中的访问权限是 【protected】

  • private 继承
class Base{
public:
	int m_BaseA;
protected:
	int m_BaseB;
private:
	int m_BaseC;
};
class Derive private Base{
public:
	int m_DeriveD;
};

此时,派生类Derive的实例化对象中包含:m_BaseAm_BaseBm_BaseCm_Derive四个成员,其中前三个继承自基类Base【基类的私有成员变量m_BaseC被编译器隐藏,子类中有这个变量,但是没有权限访问该变量】
m_BaseA在派生类中的访问权限是 【private】m_BaseB在派生类中的访问权限是 【private】

因此,我们可以得出一个结论:继承方式只会提升访问权限低于该继承方式的成员变量的访问权限。例如:继承方式是public,则基类中所有权限在派生类中不变;继承方式是protected,则基类中public 成员会提升至protected,其余不变;继承方式是private,则public,protected成员会被提升至private。


3.继承中的对象模型

使用,cl.exe工具查看,上述例子中 Derive 类对像的内存模型。我们可以得知,在 Derive 类的内存模型中,基类 Base 的成员在内存模型的上边,Derive 自己定义的成员在内存模型的下边。 同时,【基类的私有成员是包含在派生类中的,只是被编译器隐藏,派生类无法访问】
在这里插入图片描述


4.构造函数与析构

#include <iostream>
using namespace std;
class Base{
public:
	Base(){
		cout << "Base constructor run" << endl;
	}
	~Base(){
		cout << "~Base destructor run" << endl;
	}
};

class Derive:public Base{
public:
	Derive(){
		cout << "Deriveconstructor run" << endl;
	}
	~Derive(){
		cout << "~Derivedestructor run" << endl;
	}	
};
void test(){
	Derive d1;
}
int main(int argc, char* argv[])
{
	test();	
	// Base constructor run
	// Deriveconstructor run
	// ~Derivedestructor run
	// ~Base destructor run
	return 0;
}

由本例我们可以得知:定义派生类对象时,会首先调用基类构造函数,然后调用派生类构造函数,析构函数与构造函数调用顺序相反。


5.继承中基类与派生类同名成员

5.1 派生类含有与基类同名的成员变量

#include <iostream>
using namespace std;
class Base{
public:
    Base(){
        m_number = 1;
        m_age = 25;
    }
    int m_number;
protected:
    int m_age;
};

class Derive:public Base{
public:
    Derive(){
        m_number = 10;
    }
    int m_number;
    int getNumber(){
        return m_number;
    }
};

int main(int argc, char const *argv[])
{
    Derive d;
    cout << d.m_number << endl;	// 10
    cout << d.Base::m_number << endl;	// 1
    return 0;
}

通过cl.exe工具,我们查看派生类 Derive 对象内存分布如下:
在这里插入图片描述
派生类中同时拥有来自基类的 m_number 成员变量与自身定义的m_number成员,直接访问m_number访问的时派生类自己定义的m_number,此时,如果想要访问基类来自基类的m_number,只需要添加基类作用域即可,如d.Base::m_number 通过作用域Base::明确指定,我们要访问的时基类的m_number

5.2 派生类含有与基类同名的成员函数

5.2.1 同名且同类型
#include <iostream>
using namespace std;
class Base{
public:
    void fun(){
        cout << "Base::fun..." << endl;
    }
};
class Derive:public Base{
public:
    void fun(){
        cout << "Derive::func..." << endl;
    }
};
int main(int argc, char const *argv[])
{
    Derive d;
    d.fun();	// Derive::fun...
    return 0;
}

派生类出现 同名函数隐藏基类中所有同名成员函数
此处,基类的 fun 函数被派生类同名函数fun所隐藏,因此此处调用的是派生类的 fun函数

5.2.2 同名不同参
#include <iostream>
using namespace std;
class Base{
public:
    void fun(){
        cout << "Base::fun..." << endl;
    }
};
class Derive:public Base{
public:
    void fun(int){
        cout << "Derive::func(int)..." << endl;
    }
};
int main(int argc, char const *argv[])
{
    Derive d;
    d.fun();	// Error
    d.fun(1);	// Derive::func(int)...
    return 0;
}

派生类中如果声明了基类的同名函数即使参数列表不同,新声明的同名函数也会将**【基类中的同名函数隐藏,在派生类中无法访问】**。
本例,d.fun()调用出错,因为 派生类声明了 void fun(int) 同名函数,虽然参数列表不同,也会将基类中的同名函数隐藏,导致无法访问,因此此处报错
而,派生类声明了void fun(int) ,故而d.fun(1)调用不会出错。
如果想要调用基类中的同名成员函数,可以添加基类作用域,这样即使基类成员函数已被隐藏,也可以调用,如:d.Base::fun()


6.继承中基类与派生类同名静态成员处理

#include <iostream>
using namespace std;
class Base{
public:
    static void fun(){
        cout << "static Base::fun..." << endl;
    }
    static int s_num;
    static int s_age;
};
int Base::s_num = 1;
int Base::s_age = 2;

class Derive:public Base{
public:
    static void fun(int){
        cout << "static Derive::func..." << endl;
    }
    int s_num = 100;
    static int s_age;
};
int Derive::s_age = 200;

int main(int argc, char const *argv[])
{
    Derive d;
    d.Base::fun();  // static Base::fun...
    // d.fun();     // Error
    d.fun(1);       // static Derive::func...
    cout << d.Base::s_num << endl;          // 1
    cout << Derive::Base::s_num << endl;    // 1
    cout << d.s_num << endl;                // 100
    cout << d.s_age << endl;                // 200
    cout << Derive::s_age << endl;          // 200
    
    return 0;
}

在本例中,我们定义了三种情况的同名情况:

  1. 派生类普通成员变量 与基类 静态变量同名

访问基类静态成员同样可以通过添加基类作用域实现,只不过访问静态成员变量时也可以直接使用派生类::基类::成员的方式访问,如:Derive::Base::s_num

  1. 派生类静态变量与基类静态变量同名

同非静态变量性质类似,访问基类需要添加基类作用域限定。

  1. 派生类静态函数与基类静态函数同名不同参

静态成员函数,同非静态成员函数性质类似,同名同样会发生**派生类同名成员函数隐藏基类同名成员函数。**访问基类的静态成员函数,添加基类作用域限定符即可。

Tips:

类内含静态成员变量时,其初始化需要在类外完成。
例如,Base 类内定义了static int s_age;
则,类外初始化需要 去掉 static 关键字 添加类作用域再赋值,如:int Base::s_age = 100;


7.菱形继承问题

C++ 允许派生类继承多个父类,此即多继承,语法如下:
class C:public B1,public B2{};
那什么时菱形继承呢?
下图可以形象的说明菱形继承问题,B1、B2 继承自 A,当 C 把 B1、B2 同时当作父类时,C 类中会保存两份来自 A 类的数据。
在这里插入图片描述

#include <iostream>
using namespace std;
class A{
public:
    int m_A;
};
class B1:public A{
public:
    int m_B1;
};
class B2:public A{
public:
    int m_B2;
};
class C:public B1, public B2{
public:
    int m_C;
};
int main(int argc, char const *argv[])
{
    C c;
    return 0;
}

此时,使用 cl.exe 工具,查看类 C 的内存模型,我们发现 类 C 中 保存了两份来自 A 的成员变量 m_A,此时在使用 c.m_A 使用该变量时会出现歧义,同时保存两份数据也完全没有必要。
在这里插入图片描述

为了解决菱形继承的问题,C++ 引入了 虚继承 的概念,即将中间层的类 B1、B2 继承方式前添加 virtual 关键字,这时 B1、B2 变成了虚基类,C 类继承 B1、B2 时指挥保存一份来自 A 的数据。

#include <iostream>
using namespace std;
class A{
public:
    int m_A;
};
class B1:virtual public A{
public:
    int m_B1;
};
class B2:virtual public A{
public:
    int m_B2;
};
class C:public B1, public B2{
public:
    int m_C;
};
int main(int argc, char const *argv[])
{
    C c;
    return 0;
}

此时,类 C 的内存模型如下图所示:可以发现,类 C 中只包含一份 m_A 的数据了,解决了上述的问题。
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值