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输入框问题
这个平平无奇的回答,可能是全网最靠谱的解决方案。 这里我用的是vue3 setup .vue文件的方式 <view> <web-view :fullscreen"false" :webview-styles"{top: statusBarHeight40,height:height,progress: {color: green,height:1px } }"…...

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

万字详解,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…...

挂耳式蓝牙耳机哪家的好用?购买耳机前必须了解的几大要点
随着健康意识的提升,越来越多的人开始热衷于运动。运动不仅能够增强体质,对于我们这些忙碌的上班族而言,它也是一种极佳的减压方式。经过一天的辛勤工作,能够在户外跑步,让汗水带走压力,实在是一种享受。在…...

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

MySQL篇—执行计划之覆盖索引Using index和条件过滤Using where介绍(第三篇,总共三篇)
☘️博主介绍☘️: ✨又是一天没白过,我是奈斯,DBA一名✨ ✌✌️擅长Oracle、MySQL、SQLserver、Linux,也在积极的扩展IT方向的其他知识面✌✌️ ❣️❣️❣️大佬们都喜欢静静的看文章,并且也会默默的点赞收藏加关注❣…...

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

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

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

JavaWeb Tomcat启动、部署、配置、集成IDEA
web服务器软件 服务器是安装了服务器软件的计算机,在web服务器软件中,可以部署web项目,让用户通过浏览器来访问这些项目。 Web服务器是一个应用程序(软件),对HTTP协议的操作进行封装,使得程序…...

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

外贸常用的出口认证 | 全球外贸数据服务平台 | 箱讯科技
出口认证是一种贸易信任背书,对许多外贸从业者而言,产品的出口认证和当前的国际贸易环境一样复杂多变,不同的目标市场、不同的产品类别,所需要的认证及标准也不同。 国际认证 01 IECEE-CB IECEE-CB体系的中文含义是“关于电工产品测试证书的相互认可体…...

C++ 标准库类型string
C/C总述: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按钮 图标按钮通常适用于切换按钮,允许选择或选择单个选项 取消选择,例如在项目中添加或删除星号。 <IconButton aria-lab…...

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

influxdb2.0插入数据字段类型出现冲突问题解决
一、问题出现 一个学校换热站自控系统,会定时从换热站获取测点数据,并插入到influxdb数据库中。influxdb插入数据时,报错提示: com.influxdb.exceptions.UnprocessableEntityException: failure writing points to database: par…...

[C++]类和对象,explicit,static,友元,构造函数——喵喵要吃C嘎嘎4
希望你开心,希望你健康,希望你幸福,希望你点赞! 最后的最后,关注喵,关注喵,关注喵,大大会看到更多有趣的博客哦!!! 喵喵喵,你对我真的…...

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

智能指针基础知识【C++】【RAII思想 || unique_ptr || shared_ptrweak_ptr || 循环引用问题】
目录 一,为什么需要智能指针 二,内存泄露的基本认识 1. 内存泄露分类 2. 常见的内存检测工具 3,如何避免内存泄露 三,智能指针的使用与原理 1. RAII思想 2. 智能指针 (1. unique_ptr (2. shared_…...

leetcode:反转链表II 和k个一组反转链表的C++实现
反转链表II 问题描述 给你单链表的头指针 head 和两个整数 left 和 right ,其中 left < right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。 ListNode* reverseBetween(ListNode* head, int left, int right) {ListNode *…...

ERD Online 快速启动指南:代码下载到首次运行的全流程攻略 ️
🚀 一、代码下载 ERD online前端代码正常拉取即可👌 后端代码含有子模块,拉取命令如下: git clone --recurse-submodules https://github.com/www-zerocode-net-cn/martin-framework.git 🛠️ 二、代码构建 dz…...

c++ 11 新特性 不同数据类型之间转换函数之const_cast
一.不同数据类型之间转换函数const_cast介绍 const_cast是C11中引入的一种类型转换操作符,用于修改类型的const或volatile属性。const_cast的主要用途是移除对象的常量性,它是唯一具有此能力的C风格的转型操作符。在C11中,const_cast可以完成…...

C++从零开始的打怪升级之路(day45)
这是关于一个普通双非本科大一学生的C的学习记录贴 在此前,我学了一点点C语言还有简单的数据结构,如果有小伙伴想和我一起学习的,可以私信我交流分享学习资料 那么开启正题 今天分享的是关于二叉树的题目 1.根据二叉树创建字符串 606. 根…...

小鹅通前端实习一面
总时长35分钟,自我介绍开始 1.js和c特点上的差异; 2.js数组去重 3.js的数据类型 4.js的引用类型和值类型的差别 5.讲一下js的网络请求 6.对前端三件套和框架的理解 7.一个html文档的结构是怎样的 8.head和body的区别 9.一个页面的加载顺序(ht…...

ArrayList常用API
常见方法 add 增remove 删set 改get 查clear 清空元素size 长度isEmpty 为空判断 用法 // String就是泛型 这种使用方法对于限制类型很有用 ArrayList<String> arrayList new ArrayList<>();// add 添加元素 返回的是boolean 代表是否添加成功 arrayList.add(&qu…...

Chrome安装Axure插件
打开原型目录/resources/chrome,重命名axure-chrome-extension.crx,修改后缀为rar,axure-chrome-extension.rar 解压到axure-chrome-extension目录打开Chrome,更多工具->扩展程序,打开开发者模式,选择加…...

【AI+应用】模仿爆款视频二次创作短视频操作步骤
本来不想水这篇的, 剪辑软件估计很多人用的比我还6。 今天自己遇到1个需求,我看到一篇公众号文章的视频觉得有意思,但视频有点长,我没带耳机看视频的习惯,就想着能不能下载下来, 提取视频的音频转为文字&am…...

HTML使用
文章目录 一、简介二、HTML快速入门三、基础标签四、图片、音频、视频标签五、超链接标签六、列表标签七、表格标签八、布局标签九、表单标签十、表单向标签 一、简介 二、HTML快速入门 <html><head><title>你好</title></head><body>再…...