[C++核心编程-09]----C++类和对象之继承

🎩 欢迎来到技术探索的奇幻世界👨‍💻

📜 个人主页:@一伦明悦-CSDN博客

✍🏻 作者简介: C++软件开发、Python机器学习爱好者

🗣️ 互动与支持:💬评论      👍🏻点赞      📂收藏     👀关注+

如果文章有所帮助,欢迎留下您宝贵的评论,点赞加收藏支持我,点击关注,一起进步!​​​​​​​

目录

 前言       

正文       

01-继承简介        

02-继承的基本用法        

03-继承方式        

04-继承中的对象模型        

 05-继承中的构造和析构顺序       

06-继承中同名的成员处理       

07-继承中的同名静态成员处理       

总结            


前言       

        在面向对象编程中,继承是一种重要的概念,它允许一个类(称为子类或派生类)从另一个类(称为基类或父类)继承属性和行为。这意味着子类可以使用基类中已有的数据和行为,而无需重新编写,从而实现代码重用和扩展的目的。

        继承的基本原则包括:

         代码重用:继承允许子类继承父类的成员变量和成员函数。这意味着子类可以重用父类的功能,而不必重新实现相同的功能,从而减少了代码的重复性。

        代码扩展:子类可以在继承了父类的基础上添加新的成员变量和成员函数,以满足特定需求或扩展功能。这种灵活性使得程序的设计更加模块化和可扩展。        

        多态性:继承也为多态性提供了基础。子类可以重写(覆盖)父类的成员函数,从而在不同的上下文中表现出不同的行为。这种多态性使得代码更具灵活性和可维护性。

        继承链:在继承中可以形成类的层次结构,即继承链。子类可以进一步作为其他类的基类,从而形成更深层次的继承关系。这种继承链的存在使得代码组织更加清晰,也更容易理解和维护。 在 C++ 中,继承通过关键字 class 后面的 : 来实现,例如 class DerivedClass : public BaseClass,其中 DerivedClass 是派生类,BaseClass 是基类。C++ 中的继承支持多种类型的继承,包括公有继承、保护继承和私有继承,通过不同的访问说明符来控制派生类对基类成员的访问权限。

正文       

01-继承简介        

        在 C++ 中,继承是一种重要的概念,它允许一个类(称为子类或派生类)从另一个类(称为基类或父类)继承属性和行为。这种机制使得代码重用和扩展变得更加容易和灵活。

        继承的基本概念包括以下几点:

        派生类继承基类的成员:派生类可以继承基类的成员变量和成员函数。这意味着派生类可以使用基类中已有的数据和行为,而无需重新编写。

        访问控制:派生类可以选择性地改变从基类继承的成员的访问控制。C++ 中的访问控制有三种:public、protected 和 private。默认情况下,基类的成员是私有的,但是通过使用不同的访问说明符(public、protected、private),可以调整派生类对基类成员的访问权限。

        派生类可以添加新的成员:派生类可以添加新的成员变量和成员函数,以满足特定需求或扩展功能。

        下面是一个简单的 C++ 代码示例,演示了继承的基本用法:在这个例子中,我们有一个基类 Shape,其中包含 width 和 height 两个成员变量以及对它们进行设置的成员函数。然后,我们定义了一个派生类 Rectangle,它继承了 Shape 类。Rectangle 类添加了一个新的成员函数 getArea(),用于计算矩形的面积。在 main() 函数中,我们创建了一个 Rectangle 类的对象 rect,并使用基类的成员函数设置它的宽度和高度。最后,我们调用 getArea() 函数来计算矩形的面积并输出结果。

#include <iostream>
using namespace std;// 基类
class Shape {
public:void setWidth(int w) {width = w;}void setHeight(int h) {height = h;}
protected:int width;int height;
};// 派生类
class Rectangle: public Shape {
public:int getArea() {return (width * height);}
};int main() {Rectangle rect;rect.setWidth(5);rect.setHeight(7);// 访问基类的成员cout << "Total area: " << rect.getArea() << endl;return 0;
}

02-继承的基本用法        

        继承是面向对象编程中的核心概念之一,它允许一个类(称为子类或派生类)从另一个类(称为基类或父类)继承属性和行为。让我们来详细解释继承的基本用法,并给出一个具体的代码示例。

        基本用法解释:

        定义基类:首先,你需要定义一个基类。基类是包含通用属性和行为的类。这些属性和行为可以被其他类继承和重用。

        定义派生类:然后,你可以定义一个或多个派生类,它们从基类继承属性和行为。派生类可以添加新的属性和行为,或者重写基类的方法以实现特定功能。

        访问基类成员:派生类可以访问基类的公有成员和受保护的成员,但不能直接访问基类的私有成员。这种访问控制可以通过派生类对象来实现。

        重写基类方法:派生类可以重写基类的方法,以实现自己的功能。这种机制称为多态性,允许相同的方法在不同的派生类中表现出不同的行为。

        下面是一个简单的 C++ 代码示例,演示了继承的基本用法:在这个例子中,我们有一个基类 Animal,它有两个公有的成员函数 eat() 和 sleep()。然后,我们定义了一个派生类 Dog,它从 Animal 类继承了这两个成员函数。Dog 类添加了一个新的成员函数 bark(),用于描述狗叫的行为。

        在 main() 函数中,我们创建了一个 Dog 类的对象 myDog,并可以通过该对象调用 Animal 类的方法,例如 eat() 和 sleep()。同时,我们也可以调用派生类 Dog 自己的方法,例如 bark()

#include <iostream>
using namespace std;// 基类
class Animal {
public:void eat() {cout << "Animal is eating..." << endl;}void sleep() {cout << "Animal is sleeping..." << endl;}
};// 派生类
class Dog : public Animal {
public:void bark() {cout << "Dog is barking..." << endl;}
};int main() {Dog myDog;// 访问基类的成员myDog.eat();    // 输出:Animal is eating...myDog.sleep();  // 输出:Animal is sleeping...// 调用派生类的方法myDog.bark();   // 输出:Dog is barking...return 0;
}

        下面给出具体代码分析应用过程,这段代码演示了在继承中处理同名静态成员的方式。让我来解释一下:

        静态成员属性的处理:在 Base 类中定义了静态成员属性 m_A,并赋初值为 100。在 Son 类中也定义了同名的静态成员属性 m_A,并赋初值为 200。当我们创建 Son 类的对象 s 后,可以通过对象访问 Son 类和 Base 类中的 m_A,分别使用 s.m_A 和 s.Base::m_A。同样,也可以通过类名直接访问这两个属性,使用 Son::m_A 和 Son::Base::m_A

        静态成员函数的处理:在 Base 类中定义了静态成员函数 func(),输出 “Base-static void func()”。在 Son 类中也定义了同名的静态成员函数 func(),输出 “Son-static void func()”。通过对象访问时,调用的是相应对象所属类的函数,即 s.func() 调用 Son 类的 func()s.Base::func() 调用 Base 类的 func()。通过类名直接访问时,同样也是调用相应类的函数,即 Son::func() 调用 Son 类的 func()Son::Base::func() 调用 Base 类的 func()

#include<iostream>
using namespace std;// 继承中的同名静态成员处理方式class Base
{
public:// 静态成员属性  编译阶段分配内存、所有对象共享同一份数据、类内声明、类外初始化static int m_A;static void func(){cout << "Base-static void func()" << endl;}
};int Base::m_A = 100;class Son :public Base
{
public:static int m_A;static void func(){cout << "Son-static void func()" << endl;}};int Son::m_A = 200;// 同名静态成员属性
void test01()
{// 两种访问方式 // 1、建立了一个对象s,通过对象s进行访问cout << "通过建立对象访问" << endl;Son s;cout << "Son 下 m_A = " << s.m_A << endl;cout << "Base 下 m_A = " << s.Base::m_A << endl;// 2、不建立任何对象,通过类直接进行访问,也是可以实现输出功能// 其中输出父类中的对象时,第一个::指的是通过类名的方式访问,第二个::代表访问父类作用域下的m_Acout << "通过类名访问" << endl;cout << "Son 下 m_A = " << Son::m_A << endl;cout << "Base 下 m_A = " << Son::Base::m_A << endl;
}// 同名静态成员函数
void test02()
{// 1、通过对象访问cout << "通过建立对象访问" << endl;Son s;s.func();s.Base::func();// 2、通过类名访问cout << "通过类名访问" << endl;Son::func();Son::Base::func();
}int main()
{//test01();test02();system("pause");return 0;
}

        示例运行结果如下图所示: 

03-继承方式        

        在面向对象编程中,有三种常见的继承方式:公有继承、保护继承和私有继承。详细解释一如下,相应的代码示例:

        公有继承(public inheritance):

        公有继承是最常见的继承方式之一。在公有继承中,基类的公有成员和保护成员在派生类中仍然保持公有或保护的访问权限,私有成员仍然是私有的,不可访问。

class Base {
public:int publicMember;
protected:int protectedMember;
private:int privateMember;
};class Derived : public Base {// Base 类的所有成员在 Derived 中保持相同的访问权限
};int main() {Derived d;d.publicMember = 10;    // 合法,公有成员在派生类中仍然是公有的d.protectedMember = 20; // 合法,保护成员在派生类中仍然是保护的// d.privateMember = 30; // 非法,私有成员在派生类中不可访问return 0;
}

        保护继承(protected inheritance):

        保护继承使得基类的公有成员和保护成员在派生类中都变成保护成员,而私有成员仍然是私有的。

class Base {
public:int publicMember;
protected:int protectedMember;
private:int privateMember;
};class Derived : protected Base {// Base 类的所有公有成员和保护成员在 Derived 中都变成了保护成员
};int main() {Derived d;// d.publicMember = 10;    // 非法,公有成员在派生类中变成了保护成员// d.protectedMember = 20; // 非法,保护成员在派生类中是保护的// d.privateMember = 30;   // 非法,私有成员在派生类中不可访问return 0;
}

        私有继承(private inheritance):

        私有继承使得基类的所有成员在派生类中都变成私有成员。

class Base {
public:int publicMember;
protected:int protectedMember;
private:int privateMember;
};class Derived : private Base {// Base 类的所有成员在 Derived 中都变成了私有成员
};int main() {Derived d;// d.publicMember = 10;    // 非法,公有成员在派生类中变成了私有成员// d.protectedMember = 20; // 非法,保护成员在派生类中是私有的// d.privateMember = 30;   // 非法,私有成员在派生类中不可访问return 0;
}

下面给出具体代码分析应用过程,

// 公共继承   public
// 保护继承   protected
// 私有继承   private
/* 说明如下:
   1、在父类A中定义为私有的成员变量,在子类中无论继承于父类的哪种方法都无法访问
   2、在父类中公共和保护的成员,当子类继承方式为public公共继承时,则父类中的公共成员变量在子类中仍为公共成员变量
   3、当子类继承方式为protected,父类中的公共和保护成员在子类中都变为保护成员
   4、当子类中继承方式为private时,父类都公共和保护成员变量都变为私有成员
*/

#include <iostream>
using namespace std;class Base1
{
public:int m_A;
protected:int m_B;
private:int m_C;
};class Son1 : public Base1
{
public:void func(){m_A = 10;   // 父类公共 ,子类也是公共 ,在类外可以访问m_B = 10;   // 父类保护,子类保护,在类外访问不到// m_C=  10 父类私有无法访问}
};void test01()
{Son1 s1;s1.m_A = 10;
}int main()
{system("pause");return 0;
}

04-继承中的对象模型        

        在C++中,继承关系在内存中的布局可以通过对象模型来理解。对象模型描述了继承的类在内存中的排布方式,包括子类对象如何包含父类的成员以及虚拟函数表等。

        对象模型的基本概念:

        基类子对象(Base Subobject):派生类对象中包含了基类对象的部分,这部分称为基类子对象。基类子对象包含了基类的非静态成员变量和虚函数表指针。

        派生类新增成员(Derived Member):派生类中新增的成员变量。

        虚函数表指针(Virtual Function Table Pointer,vptr):每个包含虚函数的类对象都有一个虚函数表指针,指向虚函数表。虚函数表存储了虚函数的地址,通过该指针可以实现动态绑定。

        具体对象模型示例:对象模型解释:

        对于 Derived 类的对象 d,其内存布局包括了 Base 类对象和 Derived 类新增的成员变量。

   Base 类的部分称为基类子对象,包含了 m_BaseData 成员变量和一个虚函数表指针。

   Derived 类新增了 m_DerivedData 成员变量。

   d.func() 调用时,会根据虚函数表指针找到 Derived 类的虚函数表,进而调用 Derived::func()

        内存布局示意图:

|-------------------------------------------|
| Base::m_BaseData | Base::vptr | (Derived) |
|-------------------------------------------|↑              ↑|              |m_BaseData      func()        // 基类子对象(Base::func() or Derived::func())|m_DerivedData     // 派生类新增成员
#include <iostream>
using namespace std;class Base {
public:int m_BaseData;virtual void func() {cout << "Base::func()" << endl;}
};class Derived : public Base {
public:int m_DerivedData;virtual void func() {cout << "Derived::func()" << endl;}
};int main() {Derived d;d.m_BaseData = 10;d.m_DerivedData = 20;d.func();return 0;
}

        下面给出具体代码分析应用过程,这段代码演示了一个简单的继承关系,并探讨了继承中的对象模型。

        定义了一个基类 Base,其中包含了一个公有成员 m_A、一个保护成员 m_B 和一个私有成员 m_C

        定义了一个派生类 Son,它公有地继承自 Base。在 Son 类中,新增了一个公有成员 m_D。                

        在 test01() 函数中,我们调用 sizeof(Son) 来获取 Son 类的对象大小。根据输出结果 16,我们可以分析对象的内存布局:

  Son 对象中包含了 Base 类对象的部分,即基类子对象。这部分包括 m_A 和 m_B,因为它们在基类中是公有和保护的,所以在派生类中仍然保持相同的访问权限。

  Son 类新增了一个成员 m_D,因此它会占据对象的内存空间。

  Base 类中的私有成员 m_C 被编译器隐藏,虽然不能直接访问,但它仍然被继承到了派生类中。

        因此,Son 类的对象在内存中的布局包括了基类子对象和派生类新增的成员,而基类中的私有成员对于外部是不可见的,但在内存中确实被继承下去了。

#include <iostream>
using namespace std;// 继承中的对象模型class Base
{
public:int m_A;
protected:int m_B;
private:int m_C;
};class Son :public Base
{public:int m_D;
};
void test01()
{// 父类中所有非静态成员属性都会被子类继承下去// 父类中私有成员属性,被编译器隐藏,只是访问不到,但是确实继承下去了cout << "size of son = " << sizeof(Son) << endl;  // 16
}int main()
{test01();system("pause");return 0;
}

        示例运行结果如下图所示: 

 05-继承中的构造和析构顺序       

        在C++中,继承中的构造和析构顺序是非常重要的,因为它们影响着基类和派生类对象的初始化和清理顺序。构造顺序决定了对象成员的初始化顺序,而析构顺序则是对象成员的清理顺序。

        构造顺序:

        基类构造函数先于派生类构造函数执行:在创建派生类对象时,首先会调用基类的构造函数,然后再调用派生类的构造函数。

        基类构造函数按照继承关系的顺序执行:如果存在多层继承关系,会从最顶层的基类开始逐层向下执行构造函数。

        派生类构造函数中初始化派生类新增成员:在派生类构造函数中,可以初始化派生类新增的成员变量。

        析构顺序:

        派生类析构函数先于基类析构函数执行:在销毁派生类对象时,首先会调用派生类的析构函数,然后再调用基类的析构函数。

        析构函数按照继承关系的逆序执行:与构造顺序相反,析构函数会从最底层的派生类开始逐层向上执行。

#include <iostream>
using namespace std;class Base {
public:Base() { cout << "Base Constructor" << endl; }~Base() { cout << "Base Destructor" << endl; }
};class Derived : public Base {
public:Derived() { cout << "Derived Constructor" << endl; }~Derived() { cout << "Derived Destructor" << endl; }
};int main() {Derived d;return 0;
}

        下面给出具体代码分析应用过程,这段代码定义了一个基类 Base 和一个派生类 Son,并在 test01() 函数中创建了一个 Son 类对象 s

        在 test01() 函数中,我们可以看到:

        当创建 Son 类对象 s 时,首先会调用 Base 类的构造函数,然后调用 Son 类的构造函数。因此,在输出中会先打印出 “Base构造函数!”,然后是 “Son构造函数!”。

        在 test01() 函数执行结束时,对象 s 被销毁,按照析构的顺序,会先调用 Son 类的析构函数,然后调用 Base 类的析构函数。因此,在输出中会先打印出 “Son析构函数!”,然后是 “Base析构函数!”。

#include<iostream>
using namespace std;class Base
{public:Base(){cout << "Base构造函数!" << endl;}~Base(){cout << "Base析构函数!" << endl;}
};class Son : public Base
{
public:Son(){cout << "Son构造函数!" << endl;}~Son(){cout << "Son析构函数!" << endl;}};void test01()
{
//	Base b;// 先构造父类,在构造子类// 析构时相反,先析构子类,再析构父类Son s;
}int main()
{test01();system("pause");return 0;}

        示例运行结果如下图所示:

06-继承中同名的成员处理       

        在继承中如果基类和派生类拥有同名的成员(函数或变量),则涉及到隐藏、覆盖和访问这些同名成员的问题。让我们详细解释这些情况:

        成员隐藏(Member Hiding):

        如果派生类中定义了与基类同名的成员(变量或函数),那么基类的同名成员就会被派生类的成员隐藏。这意味着在派生类中无法直接访问被隐藏的基类成员。

        在派生类 Derived 中定义了与基类 Base 同名的 display() 函数。当使用 d.display() 调用时,调用的是派生类的版本。但是,通过作用域解析符 d.Base::display() 可以显式地访问基类的 display() 函数。

#include <iostream>
using namespace std;class Base {
public:void display() { cout << "Base Display" << endl; }
};class Derived : public Base {
public:void display() { cout << "Derived Display" << endl; }
};int main() {Derived d;d.display(); // 输出:Derived Displayd.Base::display(); // 通过作用域解析符访问基类成员,输出:Base Displayreturn 0;
}

        成员覆盖(Member Overriding):

        如果派生类中定义了与基类同名的虚函数,并且它们的签名也匹配,那么这个函数会覆盖基类中的同名虚函数。覆盖的效果是,在运行时会根据对象的类型调用相应的函数。

        在这个示例中,基类 Base 的 display() 函数被派生类 Derived 中的 display() 函数覆盖了。当通过指向派生类对象的基类指针调用 display() 函数时,会根据对象的实际类型调用派生类中的函数。

#include <iostream>
using namespace std;class Base {
public:virtual void display() { cout << "Base Display" << endl; }
};class Derived : public Base {
public:void display() override { cout << "Derived Display" << endl; }
};int main() {Base* b = new Derived();b->display(); // 输出:Derived Displaydelete b;return 0;
}

         访问同名成员:

        在派生类中如果需要访问被隐藏的基类同名成员,可以使用作用域解析符来指定基类的命名空间。这样可以显式地访问基类中的成员。

        在 Derived 类中,callBaseDisplay() 函数通过作用域解析符显式地调用了基类 Base 的 display() 函数。

#include <iostream>
using namespace std;class Base {
public:void display() { cout << "Base Display" << endl; }
};class Derived : public Base {
public:void display() { cout << "Derived Display" << endl; }void callBaseDisplay() { Base::display(); } // 显式调用基类的 display() 函数
};int main() {Derived d;d.display(); // 输出:Derived Displayd.callBaseDisplay(); // 输出:Base Displayreturn 0;
}

        下面给出具体代码分析应用过程,这段代码定义了一个基类 Base 和一个派生类 Son。它们都包含一个同名的成员变量 m_A 和一个同名的成员函数 func()

        在 test01() 函数中:

        创建了一个 Son 类对象 s

        输出 s.m_A,这里访问的是派生类 Son 中的成员变量 m_A,输出为 Son中m_A=200

        通过 s.Base::m_A 访问了基类 Base 中的成员变量 m_A,因此输出为 Base中m_A=100

        在 test02() 函数中:

        创建了一个 Son 类对象 s

        直接调用 s.func(),这里调用的是派生类 Son 中的成员函数 func(),输出为 Son-func调用

        通过 s.Base::func() 使用作用域解析符调用了基类 Base 中的成员函数 func(),输出为 Base-func调用

#include <iostream>
using namespace std;class Base
{
public:Base(){m_A = 100;}void func(){cout << "Base-func调用" << endl;}int m_A;
};
class Son : public Base
{
public:Son(){m_A = 200;}void func(){cout << "Son-func调用" << endl;}int m_A;
};// 同名成员属性处理
void test01()
{Son s;// 这里输出的是子类中定义的成员cout << "Son中m_A= " << s.m_A << endl;// 如果通过子类对象访问父类中的对象,需要加作用域cout << "Base中m_A=" << s.Base::m_A << endl;
}// 同名成员函数处理
void test02()
{Son s;s.func();  // 直接调用,调用的是子类中的成员函数s.Base::func();   // 加作用域就可以调用,无论是有参函数还是无参函数
}int main()
{
//	test01();test02();system("pause");return 0;
}

        示例运行结果如下图所示:

07-继承中的同名静态成员处理       

        在继承中,如果基类和派生类中存在同名的静态成员(静态变量或静态函数),则它们的处理方式与普通成员略有不同。静态成员是与类相关联的,而不是与类的实例相关联,因此在继承关系中,同名的静态成员会被分别存储,而不会发生隐藏或覆盖的情况。

        同名静态成员变量处理:

        在继承关系中,如果基类和派生类中存在同名的静态成员变量,它们会被分别存储,而不会发生隐藏。因此,通过基类或派生类访问同名的静态成员变量时,分别访问的是各自类中的静态成员变量。

        同名静态成员函数处理:

        对于静态成员函数,它们也不会发生覆盖的情况。基类和派生类中的同名静态成员函数会被分别存储,通过类名直接调用时,调用的是对应类中的静态成员函数。

        下面是具体的代码分析:在这个示例中,Base 类和 Derived 类分别定义了同名的静态成员变量 staticValue 和静态成员函数 staticFunc()。在 main() 函数中,通过类名直接访问静态成员变量和调用静态成员函数,可以看到它们分别访问了各自类中的静态成员。

#include <iostream>
using namespace std;class Base {
public:static int staticValue;static void staticFunc() {cout << "Base Static Func" << endl;}
};// 静态成员变量初始化
int Base::staticValue = 10;class Derived : public Base {
public:static int staticValue;static void staticFunc() {cout << "Derived Static Func" << endl;}
};// 静态成员变量初始化
int Derived::staticValue = 20;int main() {cout << "Base staticValue: " << Base::staticValue << endl; // 输出 Base 类的静态成员变量值cout << "Derived staticValue: " << Derived::staticValue << endl; // 输出 Derived 类的静态成员变量值Base::staticFunc(); // 调用 Base 类的静态成员函数Derived::staticFunc(); // 调用 Derived 类的静态成员函数return 0;
}

        下面给出具体代码分析应用过程,这段代码展示了在继承关系中处理同名静态成员属性和函数的方式。

        在 Base 类和 Son 类中,都定义了同名的静态成员属性 m_A 和静态成员函数 func()

        在 test01() 函数中:

        通过建立对象 s 进行访问,可以直接输出 Son 类中的静态成员属性 m_A,或者使用作用域解析符 s.Base::m_A 访问基类 Base 中的静态成员属性 m_A

        通过类名直接访问时,使用 Son::m_A 访问 Son 类中的静态成员属性,或者使用 Son::Base::m_A 访问基类 Base 中的静态成员属性。

        在 test02() 函数中:

        通过建立对象 s 进行访问静态成员函数时,直接调用 s.func(),这会调用 Son 类中的静态成员函数 func(),使用作用域解析符 s.Base::func() 可以访问基类 Base 中的静态成员函数 func()

        通过类名直接访问静态成员函数时,使用 Son::func() 可以调用 Son 类中的静态成员函数 func(),使用 Son::Base::func() 可以调用基类 Base 中的静态成员函数 func()

#include<iostream>
using namespace std;// 继承中的同名静态成员处理方式class Base
{
public:// 静态成员属性  编译阶段分配内存、所有对象共享同一份数据、类内声明、类外初始化static int m_A;static void func(){cout << "Base-static void func()" << endl;}
};int Base::m_A = 100;class Son :public Base
{
public:static int m_A;static void func(){cout << "Son-static void func()" << endl;}};int Son::m_A = 200;// 同名静态成员属性
void test01()
{// 两种访问方式 // 1、建立了一个对象s,通过对象s进行访问cout << "通过建立对象访问" << endl;Son s;cout << "Son 下 m_A = " << s.m_A << endl;cout << "Base 下 m_A = " << s.Base::m_A << endl;// 2、不建立任何对象,通过类直接进行访问,也是可以实现输出功能// 其中输出父类中的对象时,第一个::指的是通过类名的方式访问,第二个::代表访问父类作用域下的m_Acout << "通过类名访问" << endl;cout << "Son 下 m_A = " << Son::m_A << endl;cout << "Base 下 m_A = " << Son::Base::m_A << endl;
}// 同名静态成员函数
void test02()
{// 1、通过对象访问cout << "通过建立对象访问" << endl;Son s;s.func();s.Base::func();// 2、通过类名访问cout << "通过类名访问" << endl;Son::func();Son::Base::func();
}int main()
{//test01();test02();system("pause");return 0;
}

        示例运行结果如下图所示:

总结            

        继承是面向对象编程中的重要概念,允许一个类(称为派生类或子类)继承另一个类(称为基类、父类或超类)的属性和行为。这里是关于 C++ 中类和对象继承的总结:

        基本概念:

        基类(父类):定义了共性特征和行为的类。

        派生类(子类):继承了基类的特征和行为的类。

        继承:子类可以继承父类的属性和方法,使得代码重用和层次化设计成为可能。

        单继承:C++ 支持单继承,即一个类只能直接继承一个基类。

        多继承:C++ 通过接口类和虚继承等方式支持多继承。

        访问控制:

        public:派生类中的成员默认继承方式是 public,基类的 public 成员在派生类中仍然是 public。

        protected:基类的 protected 成员在派生类中也是 protected,不同于 public,不能通过派生类的对象直接访问。

        private:基类的 private 成员在派生类中是不可访问的。

        构造和析构函数:

        构造函数:派生类的构造函数可以调用基类的构造函数,但不会继承基类的构造函数。可以使用初始化列表调用基类构造函数。

        析构函数:派生类的析构函数可以调用基类的析构函数,并按照派生类构造函数的相反顺序调用它们。

        同名成员处理:

        同名成员变量:在继承关系中,如果基类和派生类中存在同名的成员变量,派生类会隐藏基类的同名成员变量。通过作用域解析符可以访问基类的同名成员。

        同名成员函数:派生类中的同名成员函数会覆盖基类的同名成员函数,但可以通过作用域解析符访问基类的同名函数。

        静态成员处理:

        同名静态成员:在继承关系中,基类和派生类中的同名静态成员会被分别存储,通过类名直接访问时,会访问各自类中的静态成员。

        静态成员函数:静态成员函数在继承中不会发生覆盖,可以通过类名直接调用,调用的是对应类中的静态成员函数。

        虚函数和多态:

        虚函数:通过在基类中声明虚函数,可以实现运行时多态性。在派生类中重写虚函数,实现基类指针或引用指向派生类对象时的多态行为。

        纯虚函数:声明为纯虚函数的虚函数没有函数体,在派生类中必须被重写。含有纯虚函数的类为抽象类,不能实例化对象。

        虚继承:

        虚继承:通过 virtual 关键字实现,用于解决多重继承时的菱形继承问题。虚继承使得最终派生类只包含一个基类的子对象。

        继承是 C++ 中实现代码重用和层次化设计的重要方式之一,合理的继承关系可以使代码结构更加清晰,便于维护和扩展。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++进阶:哈希(1)

目录 1. 简介unordered_set与unordered_map2. 哈希表&#xff08;散列&#xff09;2.1 哈希表的引入2.2 闭散列的除留余数法2.2.1 前置知识补充与描述2.2.2 闭散列哈希表实现 2.3 开散列的哈希桶2.3.1 结构描述2.3.2 开散列哈希桶实现2.3.3 哈希桶的迭代器与key值处理仿函数 3.…

7B2 PRO主题5.4.2 免授权开心版源码 | WordPress主题

简介&#xff1a; B2 PRO 5.4.2 最新免授权版不再需要改hosts&#xff0c;和正版一样上传安装就可以激活。 直接在WordPress上传安装即可 点击下载

信息化总体架构方法_1.信息化的一般概念

通常&#xff0c;信息化包含了七个主要平台&#xff1a;知识管理平台、日常办公平台、信息集成平台、信息发布平台、协同工作平台、公文流转平台和企业通信平台。 1.信息化的一般概念 1&#xff09;信息化 “信息化是指培育、发展以智能化工具为代表的新的生产力并使之造福于…

RSA非对称加密解密,前端公钥加密后端私钥解密

RSA非对称加密解密&#xff0c;前端公钥加密后端私钥解密&#xff0c;可以防止陌生人直接通过后端接口篡改数据。有数据泄露的风险。 前端&#xff1a;Vue框架 后端&#xff1a;sprintboot&#xff08;Java&#xff09; 工具类&#xff1a;hutool 前端Vue获取公钥&#xff1a…

巴奴火锅翻车,杜中兵后悔暗讽海底捞

曾经喊出“服务不过度&#xff0c;样样都讲究”、内涵海底捞的巴奴火锅&#xff0c;又改回了2012年的广告语&#xff0c;试图重回“产品主义”。 巴奴火锅于2001年创立于河南安阳&#xff0c;彼时被视作火锅界的黑马。巴奴火锅创始人的杜中兵&#xff0c;坚信“产品主义”一定…

基于SpringBoot + Vue的学生宿舍课管理系统设计与实现+毕业论文(15000字)+开题报告

系统介绍 本系统包含管理员、宿管员、学生三个角色。 管理员&#xff1a;管理宿管员、管理学生、修改密码、维护个人信息。 宿管员&#xff1a;管理公寓资产、管理缴费信息、管理公共场所清理信息、管理日常事务信息、审核学生床位安排信息。 学生&#xff1a;查看公共场所清理…

【C++】 string类:应用与实践

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

ASP.NET Web Api 如何使用 Swagger 管理 API

前言 Swagger 是一个开源的框架&#xff0c;支持 OpenAPI 规范&#xff0c;可以根据 API 规范自动生成美观的、易于浏览的 API 文档页面&#xff0c;包括请求参数、响应示例等信息&#xff0c;并且&#xff0c;Swagger UI 提供了一个交互式的界面&#xff0c;可以帮助我们快速…

Java——多线程

一.多线程 1.什么是多线程 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中&#xff0c;是进程的实际运作单位 简单理解多线程就是应用软件中相互独立&#xff0c;可以同时运行的功能(也可以理解为人体内相互独立&#xff0c;但可以同时运行的器官⌓‿⌓) 我们…

如何用时尚新姿讲好中国品牌故事?

品牌建设在推动高质量发展中扮演了双重角色&#xff0c;既是高质量发展的重要“承载者”&#xff0c;也是强有力的“助推器”。5月10日-11日&#xff0c;中国时尚品牌URBAN REVIVO&#xff08;以下简称UR&#xff09;以中国品牌日为起点&#xff0c;联合天猫超级品牌日&#xf…

Linux提权--SUDO(CVE-2021-3156)Polkit(CVE-2021-4034)

免责声明:本文仅做技术学习与交流... 目录 SUDO(CVE-2021-3156) 影响版本 -判断&#xff1a; -利用&#xff1a; Polkit(CVE-2021-4034&#xff09; ​ -判断&#xff1a; -利用: 添加用户 SUDO(CVE-2021-3156) another: SUDO权限配置不当. 影响版本 由系统的内核和发…

Redis 5.0 Stream数据结构深入分析

Redis 5.0 Stream数据结构深入分析 目录 Redis 5.0 Stream数据结构深入分析 一、Stream数据结构概述 二、核心概念解析 三、Stream的特性与用途 四、案例研究&#xff1a;实时消息系统 五、性能优化与最佳实践 六、总结与展望 一、Stream数据结构概述 Redis Stream是Red…

FastAPI - Tortoise ORM 数据库基础操作

文章目录 1. 安装 Tortoise ORM2. 定义模型3. 初始化数据库连接4. 数据库操作4.1 创建数据4.2 查询数据4.3 更新数据4.4 删除数据 5. 使用 Pydantic 模型6. 关闭数据库连接7. fields类相关操作1. fields.IntField2. fields.BigIntField3. fields.SmallIntField4. fields.CharFi…

【LAMMPS学习】八、基础知识(6.3)使用 LAMMPS GUI

8. 基础知识 此部分描述了如何使用 LAMMPS 为用户和开发人员执行各种任务。术语表页面还列出了 MD 术语,以及相应 LAMMPS 手册页的链接。 LAMMPS 源代码分发的 examples 目录中包含的示例输入脚本以及示例脚本页面上突出显示的示例输入脚本还展示了如何设置和运行各种模拟。 …

西门子博途WINCC动画之旋转运动

概述 本例将介绍在西门子 TIA Portal HMI 中旋转运动动画的一种实现方法。本例以风机、搅拌器和传送带为例&#xff0c;按下启动按钮开始转动&#xff0c;按下停止按钮停止转动。 第1步&#xff1a; 添加 PLC 设备。​博途TIA/WINCC社区VX群 ​博途TIA/WINCC社区VX群 选择西…

PyQt5中的QGraphicsView()

文章目录 1. 简介2. 一个简单的示例2. 加载一幅图片3. 常用方法示例 1. 简介 QGraphicsView是PyQt5中用于显示图形场景的小部件&#xff0c;它提供了许多常用的方法来控制视图的行为和属性。下面是一些常用的QGraphicsView方法&#xff1a; setScene(scene): 设置要显示的场景…

从零开始写 Docker(十四)---重构:实现容器间 rootfs 隔离

本文为从零开始写 Docker 系列第十四篇&#xff0c;实现容器间的 rootfs 隔离&#xff0c;使得多个容器间互不影响。 完整代码见&#xff1a;https://github.com/lixd/mydocker 欢迎 Star 推荐阅读以下文章对 docker 基本实现有一个大致认识&#xff1a; 核心原理&#xff1a;…

SpringCloud 集成 RocketMQ 及配置解析

文章目录 前言一、SpringCloud 集成 RocketMQ1. pom 依赖2. yml 配置3. 操作实体4. 生产消息4.1. 自动发送消息4.2. 手动发送消息 5. 消费消息 二、配置解析1. spring.cloud.stream.function.definition 前言 定义 Spring Cloud Stream 是一个用来为微服务应用构建消息驱动能力…

spacy微调BERT-NER模型

数据准备 加载数据集 from tqdm.notebook import tqdm import osdataset [] with open(train_file, r) as file:for line in tqdm(file.readlines()):data json.loads(line.strip())dataset.append(data)你可以按照 CLUENER 的格式准备训练数据&#xff0c; 例如&#xff1…

(done) Beam search

参考视频1&#xff1a;https://www.bilibili.com/video/BV1Gs421N7S1/?spm_id_from333.337.search-card.all.click&vd_source7a1a0bc74158c6993c7355c5490fc600 &#xff08;beam search 视频&#xff09; 参考博客1&#xff1a;https://jasonhhao.github.io/2020/06/19/…