当前位置: 首页 > news >正文

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为100Base() { this->m_num = 100; }protected:// 基类的保护函数,输出信息void privateFunction(){std::cout << "这是基类的保护函数。" << std::endl;}
};// 派生类声明,从基类Base公有继承
class Derived : public Base
{
public:// 使用using关键字改变基类保护函数的访问权限// 从protected更改为publicusing 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值为0Base() : 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值为0Derived() : m_num{100} {}void show(){// 派生类的公有成员函数,隐藏了基类的show函数std::cout << "派生类的m_num: " << m_num << std::endl;}
};int main()
{Derived d; // 创建派生类对象// 输出派生类的m_numstd::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 函数,传递参数0d.show(0); // 输出:这是派生类的 show 函数,参数为:0。// 显式调用基类的 show 函数d.Base::show(); // 输出:这是基类的 show 函数。// 注意:如果去掉注释,下面的调用将会产生编译错误// 因为派生类的 show 函数隐藏了基类的 show 函数// 并且派生类的 show 函数需要一个整数参数// d.show();  // compile error// d.show();  // compile errorreturn 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();// 程序结束,返回0return 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_avoid 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_aA(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_bB(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_cC(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_dD(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_aA(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_bB(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_cC(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_dD(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

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

在这里插入图片描述

在这里插入图片描述

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

相关文章:

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

目录 前言010继承与派生简单例程020多级继承030使用using关键词更改访问权限040隐藏050派生类与基类成员函数同名时不构成重载060使用多级继承展示成员变量在内存中的分布情况071派生类在函数头调用基类构造函数072构造函数调用顺序080构造函数与析构函数的调用顺序091多重继承…...

解决uni-app中使用webview键盘弹起遮挡input输入框问题

这个平平无奇的回答&#xff0c;可能是全网最靠谱的解决方案。 这里我用的是vue3 setup .vue文件的方式 <view> <web-view :fullscreen"false" :webview-styles"{top: statusBarHeight40,height:height,progress: {color: green,height:1px } }"…...

Java注解介绍

Java注解 注解介绍元注解RetentionTargetDocumentedInherited接口类测试结果 注解介绍 Java注解&#xff08;Annotation&#xff09;是一种元数据&#xff08;Metadata&#xff09;的形式&#xff0c;它可以被添加到Java代码中的类、方法、变量、参数等元素上&#xff0c;以提…...

万字详解,Java实现低配版线程池

文章目录 1.什么是线程池2.线程池的优势3.原理4.代码编写4.1 阻塞队列4.2 ThreadPool线程池4.3 Worker工作线程4.4 代码测试 5. 拒绝策略5.1 抽象Reject接口5.2 BlockingQueue新增tryPut方法5.3 修改ThreadPool的execute方法5.4 ThreadPool线程池构造函数修改5.5 拒绝策略实现1…...

挂耳式蓝牙耳机哪家的好用?购买耳机前必须了解的几大要点

随着健康意识的提升&#xff0c;越来越多的人开始热衷于运动。运动不仅能够增强体质&#xff0c;对于我们这些忙碌的上班族而言&#xff0c;它也是一种极佳的减压方式。经过一天的辛勤工作&#xff0c;能够在户外跑步&#xff0c;让汗水带走压力&#xff0c;实在是一种享受。在…...

CSS文本属性

CSS文本属性 1.文本颜色2.文本间距3. 文本修饰4 .文本缩进5.文本对齐_水平6.行高7. vertical-align 1.文本颜色 属性名&#xff1a;color作用&#xff1a;控制文字的颜色。可选值&#xff1a; 颜色名rgb或rgbaHEX或HEXA &#xff08;十六进制&#xff09;HSL或HSLA 开发中常用…...

MySQL篇—执行计划之覆盖索引Using index和条件过滤Using where介绍(第三篇,总共三篇)

☘️博主介绍☘️&#xff1a; ✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ ✌✌️擅长Oracle、MySQL、SQLserver、Linux&#xff0c;也在积极的扩展IT方向的其他知识面✌✌️ ❣️❣️❣️大佬们都喜欢静静的看文章&#xff0c;并且也会默默的点赞收藏加关注❣…...

最短路径(2.19)

目录 1.网络延迟时间 弗洛伊德算法 迪杰斯特拉算法 2. K 站中转内最便宜的航班 3.从第一个节点出发到最后一个节点的受限路径数 4.到达目的地的方案数 1.网络延迟时间 有 n 个网络节点&#xff0c;标记为 1 到 n。 给你一个列表 times&#xff0c;表示信号经过 有向 边的…...

vue 总结

1.vue 的生命周期 1. es6 2. vue 基本属性指令 <template><div><!--<h1>vue基本指令的使用方式</h1><a :href"url">v-bind使用链接</a><img :src"srcUrl" /><div>解决闪烁问题<p v-cloak>{{…...

深入理解TCP/IP协议:互联网通信的核心

深入理解TCP/IP协议&#xff1a;互联网通信的核心 在数字化时代&#xff0c;TCP/IP协议是支撑全球互联网通信的基石。它不仅负责数据的传输和路由&#xff0c;还确保了信息传递的准确性和完整性。本文将深入探讨TCP/IP协议的工作原理、结构以及它在网络编程中的应用。 TCP/IP…...

Python数据处理实战(4)-上万行log数据提取并作图进阶版

系列文章&#xff1a; 0、基本常用功能及其操作 1&#xff0c;20G文件&#xff0c;分类&#xff0c;放入不同文件&#xff0c;每个单独处理 2&#xff0c;数据的归类并处理 3&#xff0c;txt文件指定的数据处理并可视化作图 4&#xff0c;上万行log数据提取并作图进阶版&a…...

JavaWeb Tomcat启动、部署、配置、集成IDEA

web服务器软件 服务器是安装了服务器软件的计算机&#xff0c;在web服务器软件中&#xff0c;可以部署web项目&#xff0c;让用户通过浏览器来访问这些项目。 Web服务器是一个应用程序&#xff08;软件&#xff09;&#xff0c;对HTTP协议的操作进行封装&#xff0c;使得程序…...

关于Vue3的一些操作

1. 设置浏览器自动打开 在package.json 中设置 dev: vite --open 2.给src文件夹配置别名 在vite.config.ts配置文件中添加以下内容 3. 如果2中有红色波浪线的问题 ***安装一个文件包***npm install types/node3. 在tsconfig.json配置文件中&#xff0c;找到配置项compi…...

外贸常用的出口认证 | 全球外贸数据服务平台 | 箱讯科技

出口认证是一种贸易信任背书&#xff0c;对许多外贸从业者而言,产品的出口认证和当前的国际贸易环境一样复杂多变&#xff0c;不同的目标市场、不同的产品类别,所需要的认证及标准也不同。 国际认证 01 IECEE-CB IECEE-CB体系的中文含义是“关于电工产品测试证书的相互认可体…...

C++ 标准库类型string

C/C总述&#xff1a;Study C/C-CSDN博客 目录 定义和初始化string对象 string的增 使用push_back进行尾插 使用insert插入 使用append函数完成string的拼接 string的删 使用pop_back进行尾删 使用erase删除 string的查 使用find函数正向搜索第一个匹配项 使用rf…...

Material UI 5 学习02-其它按钮组件

Material UI 5 学习02-其它按钮组件 一、IconButton按钮二、 ButtonGroup按钮组1、最基本的实例2、垂直按钮组 一、IconButton按钮 图标按钮通常适用于切换按钮&#xff0c;允许选择或选择单个选项 取消选择&#xff0c;例如在项目中添加或删除星号。 <IconButton aria-lab…...

Express学习(三)

Express中间件 中间件的概念 什么是中间件 中间件&#xff0c;特指业务流程的中间处理环节。Express中间件的调用流程 当一个请求到达Express的服务器之后&#xff0c;可以连续调用多个中间件&#xff0c;从而对这次请求进行预处理。类似于下图所示 Express中间件的格式 Expr…...

influxdb2.0插入数据字段类型出现冲突问题解决

一、问题出现 一个学校换热站自控系统&#xff0c;会定时从换热站获取测点数据&#xff0c;并插入到influxdb数据库中。influxdb插入数据时&#xff0c;报错提示&#xff1a; com.influxdb.exceptions.UnprocessableEntityException: failure writing points to database: par…...

[C++]类和对象,explicit,static,友元,构造函数——喵喵要吃C嘎嘎4

希望你开心&#xff0c;希望你健康&#xff0c;希望你幸福&#xff0c;希望你点赞&#xff01; 最后的最后&#xff0c;关注喵&#xff0c;关注喵&#xff0c;关注喵&#xff0c;大大会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我真的…...

物联网的商业模式洞察

大约在十年前&#xff08;2014年11月&#xff09;&#xff0c;全球知名管理思想家、哈佛商学院教授迈克尔波特与PTC前首席执行官吉姆赫普尔曼&#xff0c;在《哈佛商业评论》上联合撰写了一篇备受赞誉的文章&#xff0c;题为《智能互联产品如何改变竞争》。在这篇文章中&#x…...

零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?

一、核心优势&#xff1a;专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发&#xff0c;是一款收费低廉但功能全面的Windows NAS工具&#xff0c;主打“无学习成本部署” 。与其他NAS软件相比&#xff0c;其优势在于&#xff1a; 无需硬件改造&#xff1a;将任意W…...

高等数学(下)题型笔记(八)空间解析几何与向量代数

目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍

文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结&#xff1a; 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析&#xff1a; 实际业务去理解体会统一注…...

c#开发AI模型对话

AI模型 前面已经介绍了一般AI模型本地部署&#xff0c;直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型&#xff0c;但是目前国内可能使用不多&#xff0c;至少实践例子很少看见。开发训练模型就不介绍了&am…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2

每日一言 今天的每一份坚持&#xff0c;都是在为未来积攒底气。 案例&#xff1a;OLED显示一个A 这边观察到一个点&#xff0c;怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 &#xff1a; 如果代码里信号切换太快&#xff08;比如 SDA 刚变&#xff0c;SCL 立刻变&#…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

深入浅出Diffusion模型:从原理到实践的全方位教程

I. 引言&#xff1a;生成式AI的黎明 – Diffusion模型是什么&#xff1f; 近年来&#xff0c;生成式人工智能&#xff08;Generative AI&#xff09;领域取得了爆炸性的进展&#xff0c;模型能够根据简单的文本提示创作出逼真的图像、连贯的文本&#xff0c;乃至更多令人惊叹的…...

Xela矩阵三轴触觉传感器的工作原理解析与应用场景

Xela矩阵三轴触觉传感器通过先进技术模拟人类触觉感知&#xff0c;帮助设备实现精确的力测量与位移监测。其核心功能基于磁性三维力测量与空间位移测量&#xff0c;能够捕捉多维触觉信息。该传感器的设计不仅提升了触觉感知的精度&#xff0c;还为机器人、医疗设备和制造业的智…...

计算机系统结构复习-名词解释2

1.定向&#xff1a;在某条指令产生计算结果之前&#xff0c;其他指令并不真正立即需要该计算结果&#xff0c;如果能够将该计算结果从其产生的地方直接送到其他指令中需要它的地方&#xff0c;那么就可以避免停顿。 2.多级存储层次&#xff1a;由若干个采用不同实现技术的存储…...

如何用 HTML 展示计算机代码

原文&#xff1a;如何用 HTML 展示计算机代码 | w3cschool笔记 &#xff08;请勿将文章标记为付费&#xff01;&#xff01;&#xff01;&#xff01;&#xff09; 在编程学习和文档编写过程中&#xff0c;清晰地展示代码是一项关键技能。HTML 作为网页开发的基础语言&#x…...