C++之继承

C++中的继承是类与类之间的关系,是一个很简单很直观的概念,例如:儿子继承爸爸的财产。

继承是一个类获取另一个类的成员函数和变量的过程,分为三种

类继承格式

class 派生类 : [ public | protected | private ] 基类
{
    ...
}

public继承(公有继承)

1. 基类的private、public和 protected 成员的访问属性在派生类中保持不变;
2. 派生类中继承的成员函数可以直接访问基类中的所有成员,派生类中新增的成员函数只能访问基类的 public和protected 成员,不能访问基类的 private 成员;
3. 通过派生类的对象只能访问基类的 public 成员。

protected继承(受保护继承)

1. 基类的 public 和 protected 成员都以protected 身份出现在派生类中;
2. 派生类中新增的成员函数可以直接访问基类中的 public 和 protected 成员,但不能访问基类的 private 成员;
3. 通过派生类的对象不能访问基类中的任何成员。

privated继承(私有继承)

1. 基类的 public 和 protected 成员都以 private 身份出现在派生类中;
2. 派生类中新增的成员函数可以直接访问基类中的 public和 protected 成员,但不能访问基类的private成员;
3. 通过派生类的对象不能访问基类中的任何成员。

测试代码

#include<iostream>
using namespace std;
//基类
class Person
{
    int m_age;
protected:
    char m_name[20];
public:
    Person(int age = 0, const char* name = nullptr)
    {
        m_age = age;
        strcpy_s(m_name, name);
    }
    ~Person() {}
    void set_age(int age) { m_age = age; }
    void set_name(char* name) { strcpy_s(m_name, name); }
};
//派生类
class Student :public Person
{
    int m_no;
public:
    Student(int age = 0, const char* name = nullptr, int no = 0): Person(age, name), m_no(no) {}
    void set_no(int no) { m_no = no; }
    void get_name() { cout << m_name << endl; }//新增的成员函数可以访问public和protected
    //void get_age() { cout << m_age << endl; }    //不可访问
};
//派生类
class Teacher :protected Person
{
    string m_addr;
public:
    //void show() { cout << m_name << m_age << endl; }    //报错
    void set_add(string addr) { m_addr = addr; }
};
class Boss :private Person
{
    string m_job;
public:
    void set_job(string job) { m_job = job; }
    //void show() { cout << m_name << m_age << endl; }    //报错
};
int main()
{

    return 0;
}

继承的对象模型

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。

void* operator new(size_t size)   // 重载new运算符。
{
    void* ptr = malloc(size);        // 申请内存。
    cout << "申请到的内存的地址是:" << ptr << ",大小是:" << size << endl;
    return ptr;
}

void operator delete(void* ptr)   // 重载delete运算符。
{
    if (ptr == 0) return;       // 对空指针delete是安全的。
    free(ptr);      // 释放内存。
    cout << "释放了内存。\n";
}

class A {        // 基类
public:
    int m_a = 10;
protected:
    int m_b = 20;
private:
    int m_c = 30;
public:
    A() {
        cout << "A中this指针是: " << this << endl;
        cout << "A中m_a的地址是:" << &m_a << endl;
        cout << "A中m_b的地址是:" << &m_b << endl;
        cout << "A中m_c的地址是:" << &m_c << endl;
    }
    void func() { cout << "m_a=" << m_a << ",m_b=" << m_b << ",m_c=" << m_c << endl; }
};

class B :public A        // 派生类
{
public:
    int m_d = 40;
    B() {
        cout << "B中this指针是: " << this << endl;
        cout << "B中m_a的地址是:" << &m_a << endl;
        cout << "B中m_b的地址是:" << &m_b << endl;
        //cout << "B中m_c的地址是:" << &m_c << endl;
        cout << "B中m_d的地址是:" << &m_d << endl;
    }
    void func1() { cout << "m_d=" << m_d << endl; }
};

int main()
{
    cout << "基类占用内存的大小是:" << sizeof(A) << endl;
    cout << "派生类占用内存的大小是:" << sizeof(B) << endl;

    B* p = new B;
    p->func(); p->func1();
    // memset(p, 0, sizeof(B));
    delete p;
}

我们可以发现派生类中private成员虽然不可见,但是仍占有内存空间,并且可以得出以下结论

1. 创建派生类对象时只会申请一次内存,派生类对象包含了基类对象的内存空间,this指针相同的。
2. 创建派生类对象时,先初始化基类对象,再初始化派生类对象
3. 对派生类对象用sizeof得到的是基类所有成员(包括私有成员)+派生类对象所有成员的大小。
4. 用指针可以访问到基类中的私有成员(内存对齐)
5. 在C++中,不同继承方式的访问权限只是语法上的处理

在讲第五点时,首先要讲一下在VS中查看类的内存模型的方法

打开VS开发者命令行后,我们输入

cl 文件名 /d1 reportSingleClassLayout类名

例如我们需要查继承详解.cpp中的类A的内存模型

cl 继承详解.cpp /d1 reportSingleClassLayoutA

我们通过内存模型可以看出来成员变量的相对路径是连续的,通过运行结果也可以看出来,这里存在内存对齐问题,感兴趣的话可以去我的另一篇文章C语言之深入理解内存对齐简单了解内存对齐。

接下来讲《在C++中,不同继承方式的访问权限只是语法上的处理

C++语法说,public继承时,派生类访问基类私有成员的方法只有通过基类的公有成员函数访问,假如我们使用memset(p, 0, sizeof(B));我们发现private成员被修改了,看来也是可以修改的啊!!那么就简单了,memset是直接操作内存的,那我们能否通过指针直接修改基类private的成员,

下面再上面代码中加入以下代码

*((int*)p + 2) = 31;        // 把基类私有成员m_c的值修改成31。
//根据类B的内存模型,m_c是分配在相对的第12-16个内存单元,所以我们不妨把p指针强制转换成int*,然后往后偏移两个内存单元就指向m_c,然后再进行修改。
p->func(); p->func1();

我们奇迹的发现基类的private成员竟然被修改了!!!!

创建派生类时,系统自动分配一片基类+派生类大小的连续的空间,虽然private成员在派生类中不可见,但是也占有一定的空间;创建派生类对象时,先调用基类构造函数,再调用派生类构造函数;销毁派生类对象是,先调用派生类的析构函数,再调用基类的析构函数。

但是这种方法再实际开发中不建议。

如何构造基类

1)创建派生类对象时,程序首先调用基类构造函数,然后再调用派生类构造函数。
2)如果没以指定基类构造函数,将使用基类的默认构造函数。
3)可以用初始化列表指明要使用的基类构造函数。
4)基类构造函数负责初始化被继承的数据成员;派生类构造函数主要用于初始化新增的数据成员。
5)派生类的构造函数总是调用一个基类构造函数,包括拷贝构造函数。
#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。
               
class A {        // 基类
public:
    int m_a;
private:
    int m_b;
public:
    A() : m_a(0) , m_b(0)                     // 基类的默认构造函数。
    { 
        cout << "调用了基类的默认构造函数A()。\n";  
    }
    A(int a,int b) : m_a(a) , m_b(b)     // 基类有两个参数的构造函数。
    { 
        cout << "调用了基类的构造函数A(int a,int b)。\n";  
    }
    A(const A &a) : m_a(a.m_a+1) , m_b(a.m_b+1)   // 基类的拷贝构造函数。
    {
        cout << "调用了基类的拷贝构造函数A(const A &a)。\n";
    }
               
    // 显示基类A全部的成员。
    void showA() { cout << "m_a=" << m_a << ",m_b=" << m_b << endl; }
};
               
class B :public A        // 派生类
{        
public:
    int m_c;
    B() : m_c(0) , A()             // 派生类的默认构造函数,指明用基类的默认构造函数(不指明也无所谓)。
    {
        cout << "调用了派生类的默认构造函数B()。\n";
    }
    B(int a, int b, int c) : A(a, b), m_c(c)           // 指明用基类的有两个参数的构造函数。
    {
        cout << "调用了派生类的构造函数B(int a,int b,int c)。\n";
    }
    B(const A& a, int c) :A(a), m_c(c)              // 指明用基类的拷贝构造函数。
    {
        cout << "调用了派生类的构造函数B(const A &a,int c) 。\n";
    }
           
    // 显示派生类B全部的成员。
    void showB() { cout << "m_c=" << m_c << endl << endl; }
};          
             
int main()
{
    B b1;                 // 将调用基类默认的构造函数。
    b1.showA();     b1.showB();
       
    B b2(1, 2, 3);      // 将调用基类有两个参数的构造函数。
    b2.showA();     b2.showB();
            
    A a(10, 20);      // 创建基类对象。
    B b3(a, 30);      // 将调用基类的拷贝造函数。
    b3.showA();     b3.showB();
}     

名字遮蔽与类作用域

名字遮蔽

注意:基类的成员函数和派生类的成员函数不会构成重载,如果派生类有同名函数,那么就会遮蔽基类中的所有同名函数。

类作用域

当存在继承关系时,基类的作用域嵌套在派生类的作用域中。如果成员在派生类的作用域中已经找到,就不会在基类作用域中继续查找;如果没有找到,则继续在基类作用域中查找。
#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。

class A {        // 基类
public:
    int m_a = 10;
    void func() { cout << "调用了A的func()函数。\n"; }
};

class B :public A {       // 子类
public:
    int m_a = 20;
    void func() { cout << "调用了B的func()函数。\n"; }
};

class C :public B {       // 孙类
public:
    int m_a = 30;
    void func() { cout << "调用了C的func()函数。\n"; }
    void show() {
        cout << "C::m_a的值是:" << C::m_a << endl;
        cout << "B::m_a的值是:" << B::m_a << endl;
        cout << "A::m_a的值是:" << B::A::m_a << endl;
    }
};

int main()
{
    C c;
    cout << "C::m_a的值是:" << c.C::m_a << endl;
    cout << "B::m_a的值是:" << c.B::m_a << endl;
    cout << "A::m_a的值是:" << c.B::A::m_a << endl;
    c.C::func();
    c.B::func();
    c.B::A::func();
}

继承的特殊关系

1)如果继承方式是公有的,派生类对象可以使用基类成员。
2)可以把派生类对象赋值给基类对象(包括私有成员),但是,会舍弃非基类的成员。
3)基类指针可以在不进行显式转换的情况下指向派生类对象。
4)基类引用可以在不进行显式转换的情况下引用派生类对象。
注意:
1) 基类指针或引用只能调用基类的方法,不能调用派生类的方法
2)可以用派生类构造基类。
3)如果函数的形参是基类,实参可以用派生类。
4)C++要求指针和引用类型与赋给的类型匹配,这一规则对继承来说是例外。
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小谢%同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值