C++继承与派生(二)

C++继承时的名字遮蔽问题

如果派生类中的成员(包含成员变量和成员函数)和基类中的成员重名,那么派生类中的成员就会遮蔽从基类继承过来的成员。所谓遮蔽,就是在派生类中使用该成员(包括在定义派生类时使用,也包括通过派生类对象访问该成员)时,实际上使用的是派生类新增的成员,而不是从基类继承来的。

#include <iostream>

using namespace std;

//基类People
class People{
public:
    void show();//基类的show
protected:
    char *m_name;
    int m_age;
};
void People::show()
{
    cout<<"嗨,大家好,我叫"<<m_name<<",今年"<<m_age<<"岁"<<endl;
}
//派生类Student
class Student: public People
{
public:
	Student(char *name, int age, float score);
public:
	void show();//被遮蔽类的show
private:
	float m_score;
};
Student::Student(char *name,int age, float score)
{
	m_name = name;
	m_age = age;
	m_score = score;
}
void Student::show()
{
    cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
}
int main()
{
	Student stu("小明", 16, 90.5);
	//使用派生类
	 stu.show();
    //使用的是从基类继承来的成员函数
    stu.People::show();
	return 0;
}

程序输出的结果

小明的年龄是16,成绩是90.5
嗨,大家好,我叫小明,今年16岁
请按任意键继续. ..

以上代码中派生类show函数与基类函数名一样,造成遮蔽。但是基类的show函数仍可访问,如42行代码所示。
除了以上需要注意的,还有 基类成员函数和派生类成员函数不构成重载
基类成员和派生类成员的名字一样时会造成遮蔽,这句话对于成员变量很好理解,对于成员函数要引起注意,不管函数的参数如何,只要名字一样就会造成遮蔽。换句话说,基类成员函数和派生类成员函数不会构成重载,如果派生类有同名函数,那么就会遮蔽基类中的所有同名函数,不管它们的参数是否一样。

#include<iostream>
using namespace std;
//基类Base
class Base{
public:
    void func();
    void func(int);
};
void Base::func(){ cout<<"Base::func()"<<endl; }
void Base::func(int a){ cout<<"Base::func(int)"<<endl; }
//派生类Derived
class Derived: public Base{
public:
    void func(char *);
    void func(bool);
};
void Derived::func(char *str){ cout<<"Derived::func(char *)"<<endl; }
void Derived::func(bool is){ cout<<"Derived::func(bool)"<<endl; }
int main(){
    Derived d;
    d.func("c.biancheng.net");
    d.func(true);
    d.func();  //compile error
    d.func(10);  //compile error
    d.Base::func();
    d.Base::func(100);
    return 0;
}

运行结果

Derived: : func(char *)
Derived: :func (bool)
Base: :func
Base: :func(int)

类其实也是一种作用域,基类与派生类作用域的关系是基类包含派生类,派生类的作用域位于基类作用域之内有点出人意料,不过正是因为类作用域有这种继承嵌套的关系,派生类才能使用自己成员一样来使用基类成员。
只有一个作用域内的同名函数才具有重载关系,不同作用域内的同名函数是会造成遮蔽,使得外层函数无效。派生类和基类拥有不同的作用域,所以它们的同名函数不具有重载关系。

C++继承时的对象内存模型

在没有继承时对象的内存分布情况很简单,成员变量和成员函数会分开存储:

  • 对象的内存中只包含成员变量,存储在栈区或堆区(使用 new 创建对象);
  • 成员函数与对象内存分离,存储在代码区。

继承时的内存模型

当有继承关系时,有继承关系时派生类的内存模型可以看成是基类成员变量和新增成员变量的总和,而所有成员函数仍然存储在另外一个区域——代码区,由所有对象共享。

#include <cstdio>
using namespace std;
//基类A
class A{
public:
    A(int a, int b);
public:
    void display();
protected:
    int m_a;
    int m_b;
};
A::A(int a, int b): m_a(a), m_b(b){}
void A::display(){
    printf("m_a=%d, m_b=%d\n", m_a, m_b);
}
//派生类B
class B: public A{
public:
    B(int a, int b, int c);
    void display();
private:
    int m_c;
};
B::B(int a, int b, int c): A(a, b), m_c(c){ }
void B::display(){
    printf("m_a=%d, m_b=%d, m_c=%d\n", m_a, m_b, m_c);
}
//声明并定义派生类C
class C: public B{
public:
    C(char a, int b, int c, int d);
public:
    void display();
private:
    int m_d;
};
C::C(char a, int b, int c, int d): B(a, b, c), m_d(d){ }
void C::display(){
    printf("m_a=%d, m_b=%d, m_c=%d, m_d=%d\n", m_a, m_b, m_c, m_d);
}
int main(){
    A obj_a(99, 10);
    B obj_b(84, 23, 95);
    obj_a.display();
    obj_b.display();
    return 0;
}

运行结果

m _a=99,m_b=10
m_a=84,m_b=23,m_c=95

内存分布如下图
在这里插入图片描述
在这里插入图片描述
有成员遮蔽时内存分布

#include <cstdio>
using namespace std;
//基类A
class A {
public:
	A(int a, int b);
public:
	void display();
protected:
	int m_a;
	int m_b;
};
A::A(int a, int b) : m_a(a), m_b(b) {}
void A::display() {
	printf("m_a=%d, m_b=%d\n", m_a, m_b);
}
//派生类B
class B : public A {
public:
	B(int a, int b, int c);
	void display();
protected:
	int m_c;
};
B::B(int a, int b, int c) : A(a, b), m_c(c) { }
void B::display() {
	printf("m_a=%d, m_b=%d, m_c=%d\n", m_a, m_b, m_c);
}
//声明并定义派生类C
class C : public B {
public:
	C(char a, int b, int c, int d);
public:
	void display();
private:
	int m_b;  //遮蔽A类的成员变量
	int m_c;  //遮蔽B类的成员变量
	int m_d;  //新增成员变量
};
C::C(char a, int b, int c, int d) : B(a, b, c), m_b(b), m_c(c), m_d(d) { }
void C::display() {
	printf("A::m_a=%d, A::m_b=%d, B::m_c=%d\n", m_a, A::m_b, B::m_c);
	printf("C::m_b=%d, C::m_c=%d, C::m_d=%d\n", m_b, m_c, m_d);
}
int main() {
	A obj_a(99, 10);
	B obj_b(84, 23, 95);
	C obj_c(84, 23, 95, 60);
	
	obj_a.display();
	obj_b.display();
	obj_c.display();
	return 0;
}

运行结果

im _a=99,m_b=10
m a=84,_m_b=23,m_c=95
A::m_a=84,A: :m_b=23,B::m_c=95
C::m_b=23,C::m_c=95,C::m_d=60

假设obj_c的起止地址为0x1300,那么他的内存分布如下图
在这里插入图片描述
当基类 A、B 的成员变量被遮蔽时,仍然会留在派生类对象 obj_c 的内存中,C 类新增的成员变量始终排在基类 A、B 的后面。
在派生类的对象模型中,会包含所有基类的成员变量。这种设计方案的优点是访问效率高,能够在派生类对象中直接访问基类变量,无需经过好几层间接计算。


版权声明:本文为qq_43509546原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。