C++ · 代码笔记4 ·继承与派生

目录

  • 前言
  • 010继承与派生简单例程
  • 020多级继承
  • 030使用using关键词更改访问权限
  • 040隐藏
  • 050派生类与基类成员函数同名时不构成重载
  • 060使用多级继承展示成员变量在内存中的分布情况
  • 071派生类在函数头调用基类构造函数
  • 072构造函数调用顺序
  • 080构造函数与析构函数的调用顺序
  • 091多重继承与构造函数调用顺序
  • 092访问多重继承下多个基类具有相同名称的成员变量方法
  • 093多重继承下成员变量在内存中的排列
  • 100利用指针绕开访问限制
  • 110多重继承导致的二义性
  • 120虚继承解决多重继承下的二义性
  • 130虚继承下的调用构造函数与初始化基类
  • 140虚继承下的成员变量在内存中的排序
  • 151将派生类赋值给基类_对象实例赋值
  • 152将派生类赋值给基类_对象指针赋值
  • 153将派生类赋值给基类_引用赋值
  • 160解释将派生类指针赋值给基类指针时起始位置不一致的问题

前言

  本笔记所涉及到的编程环境与 《C++ · 代码笔记1 · 从C到C++》 中的编程环境一致,具体可参考此笔记。

010继承与派生简单例程

  相关代码:

#include <iostream>

/**
 * 使用继承的两种场景:
 * 1、基类需要扩展功能可使用继承,减少重复代码。
 * 2、多个类具有相似功能,提取出公共部分,而后再进行继承。
*/

// 基类声明
class Base
{
public:
    // 基类的构造函数
    Base();
    // 基类的析构函数
    ~Base();
    // 基类的show函数,用于输出信息
    void show(void);
};

// 基类构造函数的实现,输出基类构造信息
Base::Base()
{
    std::cout << "基类构造函数被调用。" << std::endl;
}

// 基类析构函数的实现,输出基类析构信息
Base::~Base()
{
    std::cout << "基类析构函数被调用。" << std::endl;
}

// 基类show函数的实现,输出基类show信息
void Base::show(void)
{
    std::cout << "基类show函数被调用。" << std::endl;
}

// 派生类声明,从基类Base公有继承
class Derived : public Base
{
public:
    // 派生类的构造函数
    Derived();
    // 派生类的析构函数
    ~Derived();
    // 派生类的show函数,用于输出信息
    void show(void);
};

// 派生类构造函数的实现,输出派生类构造信息
Derived::Derived()
{
    std::cout << "派生类构造函数被调用。" << std::endl;
}

// 派生类析构函数的实现,输出派生类析构信息
Derived::~Derived()
{
    std::cout << "派生类析构函数被调用。" << std::endl;
}

// 派生类show函数的实现,输出派生类show信息
void Derived::show(void)
{
    std::cout << "派生类show函数被调用。" << std::endl;
}

int main(int argc, char const *argv[])
{
    // 创建派生类对象
    Derived d_obj;
    // 调用派生类的show函数
    d_obj.show();

    return 0;
}

  运行结果:

在这里插入图片描述

020多级继承

  相关代码:

#include <iostream>
#include <string>

// 动物类,包含名称和年龄两个成员变量
class Animal
{
public:
    // 动物的名称
    std::string m_name;
    // 动物的年龄
    int m_age;
    // 动物的构造函数,初始化名称和年龄
    Animal(const std::string &animalName, int animalAge) : m_name(animalName), m_age(animalAge) {}
};

// 狗类,继承自动物类
class Dog : public Animal
{
public:
    // 狗的构造函数,调用动物类的构造函数初始化
    Dog(const std::string &dogName, int dogAge) : Animal(dogName, dogAge) {}
};

// 金毛犬类,继承自狗类
class GoldenRetriever : public Dog
{
public:
    // 金毛犬的构造函数,调用狗类的构造函数初始化
    GoldenRetriever(const std::string &goldenName, int goldenAge) : Dog(goldenName, goldenAge) {}
};

// 主函数
int main()
{
    // 创建金毛犬对象
    GoldenRetriever golden("金毛", 3);

    // 输出金毛犬的名称和年龄
    std::cout << "金毛的名字是:" << golden.m_name << std::endl
              << "金毛的年龄是:" << golden.m_age << "岁" << std::endl;

    return 0;
}

  运行结果:

在这里插入图片描述

030使用using关键词更改访问权限

  相关代码:

#include <iostream>

// 基类声明
class Base
{
public:
    int m_num; // 公有成员变量,用于存储数字

    // 基类的构造函数,初始化m_num为100
    Base() { this->m_num = 100; }

protected:
    // 基类的保护函数,输出信息
    void privateFunction()
    {
        std::cout << "这是基类的保护函数。" << std::endl;
    }
};

// 派生类声明,从基类Base公有继承
class Derived : public Base
{
public:
    // 使用using关键字改变基类保护函数的访问权限
    // 从protected更改为public
    using Base::privateFunction;

    // 派生类的show函数,用于输出m_num的值
    void show(void)
    {
        std::cout << "m_num = " << this->m_num << std::endl;
    }

private:
    // 使用using关键字将基类的公有成员变量m_num的访问权限更改为私有
    using Base::m_num;
};

int main()
{
    Derived d;           // 创建派生类对象
    d.privateFunction(); // 调用基类的保护函数,由于using关键字,这里可以公有访问
    // d.m_num = 10; // 不能这样做,因为m_num权限已经被更改成私有权限了。
    d.show(); // 调用派生类的show函数,输出m_num的值
    return 0;
}

  运行结果:

在这里插入图片描述

040隐藏

  相关代码:

#include <iostream>

/**
 * 如果派生类中的成员(包括成员变量和成员函数)和基类中的成员重名,
 * 那么就会隐藏从基类继承过来的成员。
 * 
 * 基类中的成员仍然可以访问,不过要加上类名和域解析符
*/

class Base
{
public:
    int m_num; // 基类的公有成员变量

    // 使用构造函数初始化基类的m_num值为0
    Base() : m_num(0) {}

    void show()
    { // 基类的公有成员函数
        std::cout << "基类的m_num: " << m_num << std::endl;
    }
};

// 派生类
class Derived : public Base
{
public:
    int m_num; // 派生类的公有成员变量,隐藏了基类的m_num

    // 使用构造函数初始化派生类的m_num值为0
    Derived() : m_num{100} {}

    void show()
    {
        // 派生类的公有成员函数,隐藏了基类的show函数
        std::cout << "派生类的m_num: " << m_num << std::endl;
    }
};

int main()
{
    Derived d; // 创建派生类对象

    // 输出派生类的m_num
    std::cout << "派生类的m_num: " << d.m_num << std::endl;
    // 输出基类的m_num,需要显式指定
    std::cout << "基类的m_num: " << d.Base::m_num << std::endl;

    // 调用派生类的show函数
    d.show();
    // 调用基类的show函数,需要显式指定
    d.Base::show();

    return 0;
}

  运行结果:

在这里插入图片描述

050派生类与基类成员函数同名时不构成重载

  相关代码:

#include <iostream>
using namespace std;

/**
 * 一个作用域内的同名函数才具有重载关系,不同作用域内的同名函数是会造成隐藏,使得外层函数无效
 */

// 基类 Base
class Base
{
public:
    // 基类的函数,输出基类信息
    void show()
    {
        cout << "这是基类的 show 函数。" << endl;
    }
};

// 派生类 Derived,从基类 Base 公有继承
class Derived : public Base
{
public:
    // 派生类中同名函数,但参数不同,不构成重载
    // 输出派生类信息
    void show(int num)
    {
        cout << "这是派生类的 show 函数,参数为:" << num << endl;
    }
};

int main()
{
    Derived d; // 创建派生类对象

    // 调用派生类的 show 函数,传递参数0
    d.show(0); // 输出:这是派生类的 show 函数,参数为:0。

    // 显式调用基类的 show 函数
    d.Base::show(); // 输出:这是基类的 show 函数。

    // 注意:如果去掉注释,下面的调用将会产生编译错误
    // 因为派生类的 show 函数隐藏了基类的 show 函数
    // 并且派生类的 show 函数需要一个整数参数
    // d.show();  // compile error
    // d.show();  // compile error
    return 0;
}

  运行结果:

在这里插入图片描述

060使用多级继承展示成员变量在内存中的分布情况

  相关代码:

#include <iostream>

class Base
{
public:
    int base1;
    int base2;

    Base() : base1(1), base2(2)
    {
        std::cout << "基类构造函数" << std::endl;
    }
};

class Derived : public Base
{
public:
    int derived1;
    int derived2;

    Derived() : Base(), derived1(3), derived2(4)
    {
        std::cout << "派生类构造函数" << std::endl;
    }
};

class MoreDerived : public Derived
{
public:
    int moreDerived1;
    int moreDerived2;

    MoreDerived() : Derived(), moreDerived1(5), moreDerived2(6)
    {
        std::cout << "更多派生类构造函数" << std::endl;
    }
};

int main()
{
    MoreDerived obj;

    // 打印对象地址和成员变量的地址
    std::cout << "对象obj的地址: " << &obj << std::endl;
    std::cout << "成员变量obj.base1的地址: " << &obj.base1 << std::endl;
    std::cout << "成员变量obj.base2的地址: " << &obj.base2 << std::endl;
    std::cout << "成员变量obj.derived1的地址: " << &obj.derived1 << std::endl;
    std::cout << "成员变量obj.derived2的地址: " << &obj.derived2 << std::endl;
    std::cout << "成员变量obj.moreDerived1的地址: " << &obj.moreDerived1 << std::endl;
    std::cout << "成员变量obj.moreDerived2的地址: " << &obj.moreDerived2 << std::endl;

    return 0;
}

  运行结果:

在这里插入图片描述

  现象解释:可以发现,基类的成员变量排在前面,派生类的排在后面。成员变量按照派生的层级依次排列,新增成员变量始终在最后。

在这里插入图片描述

071派生类在函数头调用基类构造函数

  相关代码:

#include <iostream>

/* 只能将基类构造函数的调用放在函数头部,不能放在函数体中。 */

// 基类Base的定义
class Base
{
private:
    int baseData;

public:
    // Base类的构造函数
    Base(int data) : baseData(data)
    {
        std::cout << "基类构造函数被调用,数据为: " << data << std::endl;
    }

    // Base类的成员函数,用于展示基类的数据
    void showBaseData()
    {
        std::cout << "基类数据: " << baseData << std::endl;
    }
};

// 派生类Derived的定义
class Derived : public Base
{
private:
    int derivedData;

public:
    // Derived类的构造函数,调用基类Base的构造函数
    Derived(int baseData, int derivedData) : Base(baseData), derivedData(derivedData)
    {
        std::cout << "派生类构造函数被调用,派生类数据为: " << derivedData << std::endl;
    }

    // Derived类的成员函数,用于展示派生类的数据
    void showDerivedData()
    {
        std::cout << "派生类数据: " << derivedData << std::endl;
    }
};

int main()
{
    // 创建Derived类的对象
    Derived obj(10, 20);

    // 调用基类和派生类的成员函数
    obj.showBaseData();
    obj.showDerivedData();

    return 0;
}

  运行结果:
在这里插入图片描述

072构造函数调用顺序

  相关代码:

#include <iostream>

/**
 * 定义派生类构造函数时最好指明基类构造函数;
 * 如果不指明,就调用基类的默认构造函数(不带参数的构造函数);
 * 如果没有默认构造函数,那么编译失败。
 * */

// 基类A
class A
{
public:
    A()
    {
        m_name = "?";
        std::cout << "调用A的默认构造函数" << std::endl;
    }
    A(std::string name) : m_name(name)
    {
        std::cout << "调用A的有参构造函数,姓名:" << name << std::endl;
    }

protected:
    std::string m_name;
};

// 派生类B,继承自A
class B : public A
{
public:
    B()
    {
        m_age = 0;
        std::cout << "调用B的默认构造函数" << std::endl;
    }
    B(std::string name, int age) : A(name), m_age(age)
    {
        std::cout << "调用B的有参构造函数,年龄:" << age << std::endl;
    }

protected:
    int m_age;
};

// 派生类C,继承自B
class C : public B
{
public:
    C()
    {
        m_score = 0.0;
        std::cout << "调用C的默认构造函数" << std::endl;
    }
    C(std::string name, int age, float score) : B(name, age), m_score(score)
    {
        std::cout << "调用C的有参构造函数,成绩:" << score << std::endl;
    }

public:
    void display()
    {
        std::cout << m_name << "的年龄是" << m_age << ",成绩是" << m_score << "。" << std::endl;
    }

private:
    float m_score;
};

int main()
{
    // 创建C类的对象,使用默认构造函数
    C obj1;
    obj1.display();

    // 创建C类的对象,使用有参构造函数
    C obj2(std::string("小明"), 16, 90.5);
    obj2.display();

    return 0;
}

  运行结果:

在这里插入图片描述

080构造函数与析构函数的调用顺序

  相关代码:

#include <iostream>
using namespace std;

class A
{
public:
    A() { cout << "A构造函数" << endl; }
    ~A() { cout << "A析构函数" << endl; }
};

class B : public A
{
public:
    B() { cout << "B构造函数" << endl; }
    ~B() { cout << "B析构函数" << endl; }
};

class C : public B
{
public:
    C() { cout << "C构造函数" << endl; }
    ~C() { cout << "C析构函数" << endl; }
};

int main(int argc, char const *argv[])
{
    C test;
    return 0;
}

  运行结果:

在这里插入图片描述

091多重继承与构造函数调用顺序

  相关代码:

#include <iostream>
using namespace std;

/** 基类构造函数的调用顺序和和它们在派生类构造函数中出现的顺序无关
 * 而是和[声明]派生类时基类出现的顺序相同。 
 * */

// 基类BaseB的定义
class BaseB
{
public:
    // BaseB的构造函数,接受两个整型参数
    BaseB(int c, int d);
    // BaseB的析构函数
    ~BaseB();

protected:
    // BaseB的 保护成员变量
    int m_c;
    int m_d;
};
// BaseB的构造函数实现,初始化成员变量
BaseB::BaseB(int c, int d) : m_c(c), m_d(d)
{
    cout << "BaseB构造函数" << endl;
}
// BaseB的析构函数实现
BaseB::~BaseB()
{
    cout << "BaseB析构函数" << endl;
}

// 基类BaseA的定义
class BaseA
{
public:
    // BaseA的构造函数,接受两个整型参数
    BaseA(int a, int b);
    // BaseA的析构函数
    ~BaseA();

protected:
    // BaseA的 保护成员变量
    int m_a;
    int m_b;
};
// BaseA的构造函数实现,初始化成员变量
BaseA::BaseA(int a, int b) : m_a(a), m_b(b)
{
    cout << "BaseA构造函数" << endl;
}
// BaseA的析构函数实现
BaseA::~BaseA()
{
    cout << "BaseA析构函数" << endl;
}

// 派生类Derived的定义,继承自BaseA和BaseB
class Derived : public BaseB, public BaseA
{
public:
    // Derived的构造函数,接受五个整型参数
    Derived(int a, int b, int c, int d, int e);
    // Derived的析构函数
    ~Derived();

public:
    // Derived的公共成员函数,用于显示成员变量的值
    void show();

private:
    // Derived的 私有成员变量
    int m_e;
};
// Derived的构造函数实现,初始化基类和派生类的成员变量
Derived::Derived(int a, int b, int c, int d, int e) : BaseB(c, d), BaseA(a, b), m_e(e)
{
    cout << "Derived构造函数" << endl;
}
// Derived的析构函数实现
Derived::~Derived()
{
    cout << "Derived析构函数" << endl;
}
// Derived的show成员函数实现,输出成员变量的值
void Derived::show()
{
    cout << m_a << ", " << m_b << ", " << m_c << ", " << m_d << ", " << m_e << endl;
}

// 程序的入口点
int main()
{
    // 创建Derived类的对象,并传入初始化参数
    Derived obj(1, 2, 3, 4, 5);
    // 调用obj的show函数,输出成员变量的值
    obj.show();
    // 程序结束,返回0
    return 0;
}

  运行结果:

在这里插入图片描述

092访问多重继承下多个基类具有相同名称的成员变量方法

  相关代码:

#include <iostream>
using namespace std;

/** 当两个或多个基类中有同名的成员时
 * 要在成员名字前面加上类名和域解析符::
 * 以显式地指明到底使用哪个类的成员,消除二义性。
 */

// 基类A
class A
{
public:
    void show()
    {
        cout << "A::show() 被调用" << endl;
    }
};

// 基类B
class B
{
public:
    void show()
    {
        cout << "B::show() 被调用" << endl;
    }
};

// 派生类C,继承自A和B
class C : public A, public B
{
};

int main()
{
    // 创建C类的对象
    C obj;

    // 调用A类的show函数
    obj.A::show(); // 通过类名和域解析符显式指定调用A类的show函数

    // 调用B类的show函数
    obj.B::show(); // 通过类名和域解析符显式指定调用B类的show函数

    return 0;
}

  运行结果:

在这里插入图片描述

093多重继承下成员变量在内存中的排列

  相关代码:

#include <iostream>

// 基类A
class A
{
public:
    int a1;
    int a2;

    A() : a1(1), a2(2)
    {
        std::cout << "A类构造函数" << std::endl;
    }
};

// 基类B
class B
{
public:
    int b1;
    int b2;

    B() : b1(3), b2(4)
    {
        std::cout << "B类构造函数" << std::endl;
    }
};

// 基类C
class C
{
public:
    int c1;
    int c2;

    C() : c1(5), c2(6)
    {
        std::cout << "C类构造函数" << std::endl;
    }
};

// 派生类D,继承自A、B和C
class D : public A, public B, public C
{
public:
    D()
    {
        std::cout << "D类构造函数" << std::endl;
    }
};

int main()
{
    // 创建D类的对象
    D obj;

    // 输出成员变量的地址
    std::cout << "地址 of obj.a1: " << &obj.a1 << std::endl;
    std::cout << "地址 of obj.a2: " << &obj.a2 << std::endl;
    std::cout << "地址 of obj.b1: " << &obj.b1 << std::endl;
    std::cout << "地址 of obj.b2: " << &obj.b2 << std::endl;
    std::cout << "地址 of obj.c1: " << &obj.c1 << std::endl;
    std::cout << "地址 of obj.c2: " << &obj.c2 << std::endl;

    return 0;
}

  运行结果:

在这里插入图片描述

  现象解释:事实上,多重继承的成员变量也是按照派生类的继承声明顺序来排列的。

在这里插入图片描述

100利用指针绕开访问限制

  相关代码:

#include <iostream>

class MyClass
{
private:
    int privateData;

protected:
    int protectedData;

public:
    // 初始化私有和保护成员变量
    MyClass() : privateData(10), protectedData(20) {}
};

int main()
{
    MyClass obj;

    // 获取对象的地址
    void *objPtr = &obj;

    // 计算私有成员变量的偏移量
    // 假设int类型的大小为4字节
    size_t privateDataOffset = 0; // 假设私有成员变量在类中的第一个位置

    // 计算保护成员变量的偏移量
    // 假设保护成员变量在私有成员变量之后
    size_t protectedDataOffset = sizeof(int); // 私有成员变量之后

    // 创建指向私有成员变量的指针
    int *privateDataPtr = (int *)((char *)objPtr + privateDataOffset);

    // 创建指向保护成员变量的指针
    int *protectedDataPtr = (int *)((char *)objPtr + protectedDataOffset);

    // 通过指针访问私有成员变量
    std::cout << "私有成员变量的值: " << *privateDataPtr << std::endl;

    // 通过指针访问保护成员变量
    std::cout << "保护成员变量的值: " << *protectedDataPtr << std::endl;

    return 0;
}

  运行结果:

在这里插入图片描述

110多重继承导致的二义性

  相关代码:

#include <iostream>

// 间接基类A
class A
{
protected:
    int m_a;
};

// 直接基类B
class B : public A
{
protected:
    int m_b;
};

// 直接基类C
class C : public A
{
protected:
    int m_c;
};

// 派生类D
class D : public B, public C
{
public:
    // 命名冲突,程序不知道是哪条继承路径下的m_a
    void seta(int a) { m_a = a; }
    void setb(int b) { m_b = b; }
    void setc(int c) { m_c = c; }
    void setd(int d) { m_d = d; }

private:
    int m_d;
};

int main()
{
    D d;
    return 0;
}

  运行结果:

在这里插入图片描述
  解释:由于代码有二义性,编译器直接不给通过编译。

120虚继承解决多重继承下的二义性

  相关代码:

#include <iostream>

// 间接基类A
class A
{
protected:
    int m_a;
};

// 直接基类B,虚继承
class B : virtual public A
{
protected:
    int m_b;
};

// 直接基类C,虚继承
class C : virtual public A
{
protected:
    int m_c;
};

// 派生类D
class D : public B, public C
{
public:
    void seta(int a) { m_a = a; }
    void setb(int b) { m_b = b; }
    void setc(int c) { m_c = c; }
    void setd(int d) { m_d = d; }

private:
    int m_d;
};

int main()
{
    D d;
    return 0;
}

  运行结果:

在这里插入图片描述

130虚继承下的调用构造函数与初始化基类

  相关代码:

#include <iostream>

// 虚基类A的声明和定义
/**
 * @brief 虚基类A,提供基本的属性m_a
 */
class A
{
public:
    // A类的构造函数
    /**
     * @brief 构造函数,初始化m_a
     * @param a 初始化m_a的值
     */
    A(int a);

protected:
    // A类的保护成员,存储构造函数中传入的值
    int m_a;
};
// A类的构造函数定义
A::A(int a) : m_a(a) {}

// 直接派生类B的声明和定义
/**
 * @brief 直接派生类B,从虚基类A派生,增加新的属性m_b
 */
class B : virtual public A
{
public:
    // B类的构造函数
    /**
     * @brief 构造函数,初始化m_a和m_b
     * @param a 初始化虚基类A的m_a
     * @param b 初始化自己的m_b
     */
    B(int a, int b);

public:
    // B类的成员函数,用于显示成员变量的值
    /**
     * @brief 显示m_a和m_b的值
     */
    void display();

protected:
    // B类的保护成员,存储构造函数中传入的值
    int m_b;
};
// B类的构造函数定义
B::B(int a, int b) : A(a), m_b(b) {}
// B类的成员函数定义
void B::display()
{
    std::cout << "m_a=" << m_a << ", m_b=" << m_b << std::endl;
}

// 直接派生类C的声明和定义
/**
 * @brief 直接派生类C,从虚基类A派生,增加新的属性m_c
 */
class C : virtual public A
{
public:
    // C类的构造函数
    /**
     * @brief 构造函数,初始化m_a和m_c
     * @param a 初始化虚基类A的m_a
     * @param c 初始化自己的m_c
     */
    C(int a, int c);

public:
    // C类的成员函数,用于显示成员变量的值
    /**
     * @brief 显示m_a和m_c的值
     */
    void display();

protected:
    // C类的保护成员,存储构造函数中传入的值
    int m_c;
};
// C类的构造函数定义
C::C(int a, int c) : A(a), m_c(c) {}
// C类的成员函数定义
void C::display()
{
    std::cout << "m_a=" << m_a << ", m_c=" << m_c << std::endl;
}

// 间接派生类D的声明和定义
/**
 * @brief 间接派生类D,从B和C派生,增加新的属性m_d
 */
class D : public B, public C
{
public:
    // D类的构造函数
    /**
     * @brief 构造函数,初始化m_a、m_b、m_c和m_d
     * @param a 初始化虚基类A的m_a
     * @param b 初始化B类的m_b
     * @param c 初始化C类的m_c
     * @param d 初始化自己的m_d
     */
    D(int a, int b, int c, int d);

public:
    // D类的成员函数,用于显示成员变量的值
    /**
     * @brief 显示m_a、m_b、m_c和m_d的值
     */
    void display();

private:
    // D类的私有成员,存储构造函数中传入的值
    int m_d;
};
// D类的构造函数定义
/* 最终的派生类 D 来初始化虚基类 A,直接派生类 B 和 C 对 A 的构造函数的调用是无效的。 */
D::D(int a, int b, int c, int d) : A(a), B(90, b), C(100, c), m_d(d) {}
// D类的成员函数定义
void D::display()
{
    std::cout << "m_a=" << m_a << ", m_b=" << m_b << ", m_c=" << m_c << ", m_d=" << m_d << std::endl;
}

// 主函数
/**
 * @brief 程序的入口点
 * @return int 返回状态码
 */
int main()
{
    // 创建B类对象b,并调用display函数
    B b(10, 20);
    b.display();

    // 创建C类对象c,并调用display函数
    C c(30, 40);
    c.display();

    // 创建D类对象d,并调用display函数
    D d(50, 60, 70, 80);
    d.display();
    return 0;
}

  运行结果:

在这里插入图片描述

140虚继承下的成员变量在内存中的排序

  相关代码:

#include <iostream>

class A
{
public:
    int a_num1;
    int a_num2;
};

class B : virtual public A
{
public:
    int b_num1;
    int b_num2;
};

class C : virtual public A
{
public:
    int c_num1;
    int c_num2;
};

class D : public B, public C
{
public:
    int d_num1;
    int d_num2;
};

int main(int argc, char const *argv[])
{
    D d;

    // 打印D对象中各个成员变量的地址
    std::cout << "d.a_num1 的地址: " << &d.a_num1 << std::endl;
    std::cout << "d.a_num2 的地址: " << &d.a_num2 << std::endl;
    std::cout << "d.b_num1 的地址: " << &d.b_num1 << std::endl;
    std::cout << "d.b_num2 的地址: " << &d.b_num2 << std::endl;
    std::cout << "d.c_num1 的地址: " << &d.c_num1 << std::endl;
    std::cout << "d.c_num2 的地址: " << &d.c_num2 << std::endl;
    std::cout << "d.d_num1 的地址: " << &d.d_num1 << std::endl;
    std::cout << "d.d_num2 的地址: " << &d.d_num2 << std::endl;

    return 0;
}

  运行结果:

在这里插入图片描述

  对于虚继承,将派生类分为固定部分和共享部分,并把共享部分(虚继承的基类)放在最后。

在这里插入图片描述

151将派生类赋值给基类_对象实例赋值

  相关代码:

#include <iostream>

/**
 * 对象之间的赋值是成员变量的赋值,成员函数不存在赋值问题。
 * 对象之间的赋值不会影响成员函数,也不会影响 this 指针。
 */

/**
 * @class A
 * @brief 定义了一个简单的类A,具有一个成员变量m_a和一个display函数。
 */
class A
{
public:
    /**
     * @brief A类的构造函数,用于初始化成员变量m_a。
     * @param a 初始化m_a的值。
     */
    A(int a);

public:
    /**
     * @brief 打印类A的成员变量m_a的值。
     */
    void display();

public:
    int m_a; ///< 类A的成员变量,存储整数类型的数据。
};
// A类的构造函数定义,使用成员初始化列表初始化m_a。
A::A(int a) : m_a(a) {}
// display函数定义,输出m_a的值。
void A::display()
{
    std::cout << "Class A: m_a=" << m_a << std::endl;
}

/**
 * @class B
 * @brief 定义了一个继承自A的类B,添加了成员变量m_b和一个重写的display函数。
 */
class B : public A
{
public:
    /**
     * @brief B类的构造函数,调用基类A的构造函数初始化m_a,并初始化成员变量m_b。
     * @param a 初始化基类A的成员变量m_a的值。
     * @param b 初始化成员变量m_b的值。
     */
    B(int a, int b);

public:
    /**
     * @brief 重写基类A的display函数,打印类B的成员变量m_a和m_b的值。
     */
    void display();

public:
    int m_b; ///< 类B的成员变量,存储整数类型的数据。
};
// B类的构造函数定义,首先调用基类A的构造函数,然后使用成员初始化列表初始化m_b。
B::B(int a, int b) : A(a), m_b(b) {}
// display函数定义,输出m_a和m_b的值。
void B::display()
{
    std::cout << "Class B: m_a=" << m_a << ", m_b=" << m_b << std::endl;
}

/**
 * @brief 主函数,程序入口。
 * @param argc 命令行参数的数量。
 * @param argv 命令行参数的字符串数组。
 * @return 程序执行状态码。
 */
int main(int argc, char const *argv[])
{
    A a(10);     // 创建A类对象a,并初始化m_a为10。
    B b(66, 99); // 创建B类对象b,并初始化m_a为66,m_b为99。

    std::cout << "赋值前的数据:" << std::endl;
    a.display(); // 调用a的display函数。
    b.display(); // 调用b的display函数。

    std::cout << std::endl;

    std::cout << "赋值后的数据:" << std::endl;
    a = b;       // 将b的值赋给a,这里会发生切片,只复制基类A的部分。
    a.display(); // 调用a的display函数,此时a的m_a被b的m_a覆盖。
    b.display(); // 调用b的display函数,b的值未改变。

    return 0; // 程序执行成功,返回0。
}

  运行结果:

在这里插入图片描述

152将派生类赋值给基类_对象指针赋值

  相关代码:

#include <iostream>

/**
 * 编译器通过指针来访问成员变量,指针指向哪个对象就使用哪个对象的数据;
 * 编译器通过指针的类型来访问成员函数,指针属于哪个类的类型就使用哪个类的函数。
 */

// 基类A的定义,包含一个int类型的成员变量m_a
class A
{
public:
    // A类的构造函数,初始化m_a
    A(int a);

public:
    // 显示m_a的值的成员函数
    void display();

protected:
    // 保护成员,用于存储A类的数据
    int m_a;
};
// A类构造函数的实现,初始化m_a
A::A(int a) : m_a(a) {}
// display函数的实现,打印m_a的值
void A::display()
{
    std::cout << "Class A: m_a=" << m_a << std::endl;
}

// 派生类B的定义,从A类公有继承,并增加一个int类型的成员变量m_b
class B : public A
{
public:
    // B类的构造函数,初始化m_a和m_b
    B(int a, int b);

public:
    // 重写的display函数,打印m_a和m_b的值
    void display();

protected:
    // 保护成员,用于存储B类特有的数据
    int m_b;
};
// B类构造函数的实现,调用基类A的构造函数,并初始化m_b
B::B(int a, int b) : A(a), m_b(b) {}
// display函数的实现,打印m_a和m_b的值
void B::display()
{
    std::cout << "Class B: m_a=" << m_a << ", m_b=" << m_b << std::endl;
}

// 类C的定义,包含一个int类型的成员变量m_c
class C
{
public:
    // C类的构造函数,初始化m_c
    C(int c);

public:
    // 显示m_c的值的成员函数
    void display();

protected:
    // 保护成员,用于存储C类的数据
    int m_c;
};
// C类构造函数的实现,初始化m_c
C::C(int c) : m_c(c) {}
// display函数的实现,打印m_c的值
void C::display()
{
    std::cout << "Class C: m_c=" << m_c << std::endl;
}

// 派生类D的定义,公有继承自B和C类,并增加一个int类型的成员变量m_d
class D : public B, public C
{
public:
    // D类的构造函数,初始化m_a, m_b, m_c和m_d
    D(int a, int b, int c, int d);

public:
    // 重写的display函数,打印m_a, m_b, m_c和m_d的值
    void display();

private:
    // 私有成员,用于存储D类特有的数据
    int m_d;
};
// D类构造函数的实现,调用基类B和C的构造函数,并初始化m_d
D::D(int a, int b, int c, int d) : B(a, b), C(c), m_d(d) {}
// display函数的实现,打印m_a, m_b, m_c和m_d的值
void D::display()
{
    std::cout << "Class D: m_a=" << m_a << ", m_b=" << m_b << ", m_c=" << m_c << ", m_d=" << m_d << std::endl;
}
// 主函数
int main()
{
    // 创建A类的对象
    A *pa = new A(1);
    // 创建B类的对象
    B *pb = new B(2, 20);
    // 创建C类的对象
    C *pc = new C(3);
    // 创建D类的对象
    D *pd = new D(4, 40, 400, 4000);
    // 将pd地址赋给pa,pa此时指向D类的对象
    pa = pd;
    // 调用pa指向对象的display函数
    pa->display();
    // 将pd地址赋给pb,pb此时指向D类的对象
    pb = pd;
    // 调用pb指向对象的display函数
    pb->display();
    // 将pd地址赋给pc,pc此时指向D类的对象
    pc = pd;
    // 调用pc指向对象的display函数
    pc->display();
    // 打印各个指针的地址
    std::cout << "-----------------------" << std::endl;
    std::cout << "pa=" << pa << std::endl;
    std::cout << "pb=" << pb << std::endl;
    std::cout << "pc=" << pc << std::endl;
    std::cout << "pd=" << pd << std::endl;
    // 删除pd指向的对象
    delete pd;
    // 程序结束
    return 0;
}

  运行结果:

在这里插入图片描述
  pc的起始地址从这里看出地址与其他对象不一致,这在后面再解释,但可以看出将派生类指针赋值给基类指针。与对象变量之间的赋值不同的是,对象指针之间的赋值并没有拷贝对象的成员,也没有修改对象本身的数据,仅仅是改变了指针的指向。

153将派生类赋值给基类_引用赋值

  相关代码:

#include <iostream>

/**
 * 编译器通过指针来访问成员变量,指针指向哪个对象就使用哪个对象的数据;
 * 编译器通过指针的类型来访问成员函数,指针属于哪个类的类型就使用哪个类的函数。
 * 
 * 引用的底层也是指针,所以行为跟指针相似
 */

// 基类A的定义,包含一个int类型的成员变量m_a
class A
{
public:
    // A类的构造函数,初始化m_a
    A(int a);

public:
    // 显示m_a的值的成员函数
    void display();

protected:
    // 保护成员,用于存储A类的数据
    int m_a;
};
// A类构造函数的实现,初始化m_a
A::A(int a) : m_a(a) {}
// display函数的实现,打印m_a的值
void A::display()
{
    std::cout << "Class A: m_a=" << m_a << std::endl;
}
// 派生类B的定义,从A类公有继承,并增加一个int类型的成员变量m_b
class B : public A
{
public:
    // B类的构造函数,初始化m_a和m_b
    B(int a, int b);

public:
    // 重写的display函数,打印m_a和m_b的值
    void display();

protected:
    // 保护成员,用于存储B类特有的数据
    int m_b;
};
// B类构造函数的实现,调用基类A的构造函数,并初始化m_b
B::B(int a, int b) : A(a), m_b(b) {}
// display函数的实现,打印m_a和m_b的值
void B::display()
{
    std::cout << "Class B: m_a=" << m_a << ", m_b=" << m_b << std::endl;
}
// 类C的定义,包含一个int类型的成员变量m_c
class C
{
public:
    // C类的构造函数,初始化m_c
    C(int c);

public:
    // 显示m_c的值的成员函数
    void display();

protected:
    // 保护成员,用于存储C类的数据
    int m_c;
};
// C类构造函数的实现,初始化m_c
C::C(int c) : m_c(c) {}
// display函数的实现,打印m_c的值
void C::display()
{
    std::cout << "Class C: m_c=" << m_c << std::endl;
}
// 派生类D的定义,公有继承自B和C类,并增加一个int类型的成员变量m_d
class D : public B, public C
{
public:
    // D类的构造函数,初始化m_a, m_b, m_c和m_d
    D(int a, int b, int c, int d);

public:
    // 重写的display函数,打印m_a, m_b, m_c和m_d的值
    void display();

private:
    // 私有成员,用于存储D类特有的数据
    int m_d;
};
// D类构造函数的实现,调用基类B和C的构造函数,并初始化m_d
D::D(int a, int b, int c, int d) : B(a, b), C(c), m_d(d) {}
// display函数的实现,打印m_a, m_b, m_c和m_d的值
void D::display()
{
    std::cout << "Class D: m_a=" << m_a << ", m_b=" << m_b << ", m_c=" << m_c << ", m_d=" << m_d << std::endl;
}

int main(int argc, char const *argv[])
{
    D d(4, 40, 400, 4000);

    A &ra = d;
    B &rb = d;
    C &rc = d;

    ra.display();
    rb.display();
    rc.display();

    return 0;
}

  运行结果:

在这里插入图片描述

160解释将派生类指针赋值给基类指针时起始位置不一致的问题

  相关代码:

#include <iostream>

class A
{
public:
    int a;
};

class B : public A
{
public:
    int b;
};

class C
{
public:
    int c;
};

class D : public B, public C
{
public:
    int d;
};

class E : public C, public B
{
public:
    int e;
};

int main(int argc, char const *argv[])
{
    D *pd = new D;

    std::cout << "派生类 D 指针赋值给各基类情况:" << std::endl;

    A *pa = pd;
    std::cout << "pa = " << pa << std::endl;

    B *pb = pd;
    std::cout << "pb = " << pb << std::endl;

    C *pc = pd;
    std::cout << "pc = " << pc << std::endl;
    std::cout << "pd = " << pd << std::endl;


    std::cout << std::endl;

    E *pe = new E;

    std::cout << "派生类 E 指针赋值给各基类情况:" << std::endl;

    pa = pe;
    std::cout << "pa = " << pa << std::endl;

    pb = pe;
    std::cout << "pb = " << pb << std::endl;

    pc = pe;
    std::cout << "pc = " << pc << std::endl;
    std::cout << "pe = " << pe << std::endl;

    delete pd;

    return 0;
}

  运行结果:

在这里插入图片描述
  现象解释:事实上,派生类D和派生类E的继承结构如下所示:

在这里插入图片描述

  类D和类E不一样的地方在于声明继承顺序的时候不一样。

  对于类D:

class D : public B, public C

  对于类E:

class E : public C, public B

  从上可输出结果可看出,继承顺序声明不一样,这导致成员变量在内存中的排序也不一样,如下如图所示:

在这里插入图片描述

在这里插入图片描述

  从上可看出,成员变量内存地址并没有顺序递增这个情况,是受到了继承顺序和编译器实现的影响。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/440714.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【常见集合】Java 常见集合重点解析

Java 常见集合重点解析 1. 什么是算法时间复杂度&#xff1f; 时间复杂度表示了算法的 执行时间 和 数据规模 之间的增长关系&#xff1b; 什么是算法的空间复杂度&#xff1f; 表示了算法占用的额外 存储空间 与 数据规模 之间的增长关系&#xff1b; 常见的复杂度&#x…

超实用的公众号搭建教程分享,纯干货

微信公众号已经成为了企业、个人和品牌进行宣传和互动的重要平台。在这个拥有海量公众号的时代&#xff0c;如何让你的公众号脱颖而出&#xff0c;吸引更多的关注者&#xff0c;实现有效传播呢&#xff1f;接下来&#xff0c;伯乐网络传媒将为你详细解析公众号搭建教程&#xf…

便捷在线导入:完整Axure元件库集合,让你的设计更高效!

Axure元件库包含基本的工具组件&#xff0c;可以使原型绘制节省大量的重复工作&#xff0c;保持整个设计页面的一致性和标准化&#xff0c;同时显得专业。Axure元件库就像我们日常生活中的门把手、自行车踏板和桌子上的螺丝钉&#xff0c;需要组装才能使用。作为一名成熟的产品…

信息安全管理与评估DCST-6000B-Pro神州数码堡垒机沙箱连接教程

信息安全管理与评估DCST-6000B-Pro神州数码堡垒机沙箱连接教程 一、前言 在全国职业院校技能大赛-信息安全管理与评估赛项中&#xff0c;我们会用到DCST-6000B-Pro神州数码堡垒机沙箱&#xff0c;简称堡垒机&#xff0c; 很多院校并没有购买该设备&#xff0c;导致备赛学生可…

阿珊详解Vue Router的守卫机制

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

安全防御第七次实验

需求&#xff1a;在FW7和FW8之间建立一条IPSEC通道保证10.0.2.0/24网段可以正常访问到192.168.1.0/24 一、NAT配置 FW4&#xff1a; FW6&#xff1a; 二、在FW4上做服务器映射 三、配置IPSEC FW5&#xff1a; FW6&#xff1a; 四、防火墙上的安全策略 FW4&#xff1a; FW5:…

spring cloud 之 Netflix Eureka

1、Eureka 简介 Eureka是Spring Cloud Netflix 微服务套件中的一个服务发现组件&#xff0c;本质上是一个基于REST的服务&#xff0c;主要用于AWS云来定位服务以实现中间层服务的负载均衡和故障转移,它的设计理念就是“注册中心”。 你可以认为它是一个存储服务地址信息的大本…

Androidstudio实现登录按钮按下变色

在activity_main.xml中&#xff0c;写如下代码&#xff1a; <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"androi…

禁止使用搜索引擎,你了解吗?

员工A&#xff1a;“我今天想搜索的时候&#xff0c;用不了浏览器了&#xff0c;你能用么&#xff1f;” 员工B&#xff1a;“不知道啊我试一下啊” “也不行” 员工C&#xff1a;“为什么啊&#xff1f;” 针对上述对话&#xff0c;我们不禁思考&#xff1a; 公司为什么禁…

2023最新群智能优化算法:巨型犰狳优化算法(Giant Armadillo Optimization,GAO)求解23个基准函数(提供MATLAB代码)

一、巨型犰狳优化算法 巨型犰狳优化算法&#xff08;Giant Armadillo Optimization&#xff0c;GAO&#xff09;由Omar Alsayyed等人于2023年提出&#xff0c;该算法模仿了巨型犰狳在野外的自然行为。GAO设计的基本灵感来自巨型犰狳向猎物位置移动和挖掘白蚁丘的狩猎策略。GAO…

Spring Boot中SQL语句报错

报错原因&#xff1a; You have an error in your SQL syntax 你的SQL语句出现错误 报错位置&#xff1a; check the manual that corresponds to your MySQL server version for the right syntax to use near :/sql/schema.sql.t_film at line 1 在:/sql/schema.sql附近使用…

前端使用Ant Design中的Modal框实现长按顶部拖动弹框需求

需求&#xff1a;需要Ant Design中的一个Modal弹框&#xff0c;并且可以让用户按住顶部实现拖动操作 实现&#xff1a;在Ant Design的Modal框的基础上&#xff0c;在title中添加一个onMouseDown去记录拖拽的坐标&#xff0c;然后将其赋值给Modal的style属性 代码部分&#xff…

20 卷积层里的填充和步幅【李沐动手学深度学习v2课程笔记】

1. 填充和步幅 在上下左右分别填充一些0 2. 代码实现 2.1 填充 我们创建一个高度和宽度为3的二维卷积层&#xff0c;并在所有侧边填充1个像素。给定高度和宽度为8的输入&#xff0c;则输出的高度和宽度也是8。 import torch from torch import nn# 为了方便起见&#xff0c;…

smplx pkl格式可视化

smplx pkl格式可视化 import glob import os import pickleimport torch import numpy as npfrom smplpytorch.pytorch.smpl_layer import SMPL_Layer from display_utils import display_model, display_model_continuousfrom matplotlib import pyplot as plt from matplotl…

详解float函数类型转换

函数描述 float([x]) 函数将数字或数字的字符串表示形式转换为与它等效的有符号浮点数。如果参数x是一个字符串&#xff08;十进制表示的数字串&#xff09;&#xff0c;数字前面可以添加符号来表示正数&#xff0c;或负数。符号和数字之间不能出现空格&#xff0c;但是符号前…

Spring学习 基础(二)Bean和AOP

3、Spring Bean Bean 代指的就是那些被 IoC 容器所管理的对象&#xff0c;我们需要告诉 IoC 容器帮助我们管理哪些对象&#xff0c;这个是通过配置元数据来定义的。配置元数据可以是 XML 文件、注解或者 Java 配置类。 Bean的创建方式 1. XML 配置文件&#xff1a; 传统上&am…

python 截取字符串string.split

目录 作用语法只要第一个值获得第3个值遍历 作用 根据某个符号对数据进行截取 从而获得自己想要的内容 语法 使用’string.split’ 方法 对字符串’123/abc/BPYC’ 以 ‘/’ 进行截取 string "123/abc/BPYC" substring string.split("/") print(subs…

3、proxy、for...of、iterator遍历器以及原理

一、proxy&#xff1a; 1、proxy的作用&#xff1a;&#xff08;重点&#xff09; 代理解决跨域 2、proxy代理格式&#xff1a; // target:要代理的对象 property:对象里的方法 let proxy new Proxy(target, property);3、代理里面的方法 get(target,property) 创建代…

对simplex算法的时间复杂度进行分析

对于simplex算法,如果每进行一次pivot变换,目标函数所得到的结果都会有可能出现增加的情况,所以得到的结论中,可以肯定它的值是一定不会出现减少的情况的,每次从目标函数中找到一个系数大于0的变量,然后再在约束条件中选取能够让它的增值最少的那个来继续进行pivot变换。…

代码随想录day16 栈与队列:前 K 个高频元素(leetcode347)

题目要求&#xff1a;给定一个非空的整数数组&#xff0c;返回其中出现频率前 k 高的元素。 思路&#xff1a;我们需要使用map来统计整个数组中元素出现的频率&#xff0c;然后再根据统计好的频率去排序&#xff0c;取得频率前K高的元素。我们不必使用快排实际上我们使用优先级…