[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.rhkb.cn/news/326655.html

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

相关文章

迅睿CMS中实现关键词搜索高亮

在迅睿CMS系统中实现关键词搜索高亮是提升用户体验和搜索效果的重要手段。当用户搜索某个关键词时&#xff0c;将搜索结果中的关键词高亮显示&#xff0c;可以帮助用户更快速地定位到所需信息。 关键词高亮的实现 在迅睿CMS中&#xff0c;你可以使用内置的dr_keyword_highlig…

使用npm script运行webpack

npm&#xff08;Node Package Manager&#xff09;是 Node.js 的包管理工具&#xff0c;用于安装、发布和管理 Node.js 包。 在安装node的时候&#xff0c;就默认安装了npm。 Npm Script 则是 Npm 内置的一个功能&#xff0c;允许在 package.json 文件里面使用 scripts 字段定…

C#之partial关键字

在C#中&#xff0c;partial关键字用于声明一个类、结构体、接口或方法的分部定义。这意味着可以将一个类或其他类型的定义分成多个部分&#xff0c;这些部分可以在同一个命名空间或程序集中的多个源文件中进行定义。当编译器编译这些部分时&#xff0c;会将它们合并成一个单独的…

机器学习面试篇

如何理解机器学习数据集的概念 数据集是机器学习的基础&#xff0c;它包括了用于训练和测试模型所需的数据。数据集通常以矩阵的形式存在&#xff0c;其中每一行代表一个样本&#xff08;或实例&#xff09;&#xff0c;每一列代表一个特征&#xff08;或属性&#xff09;。…

宝塔Linux面板5.9版本升级新版失败解决方法

下载地址&#xff1a;宝塔Linux面板5.9升级教程 宝塔5.9版本升级最新版宝塔失败&#xff0c;可以参考这份详细教程&#xff08;不断更新中&#xff09; 安装要求&#xff1a; Python版本&#xff1a; 2.6/2.7&#xff08;安装宝塔时会自动安装&#xff09; 内存&#xff1a;1…

k8s 使用Docker和Containerd对比分析

目录 k8s 使用Docker和Containerd对比分析 互动1&#xff1a;docker build构建的镜像和containerd镜像通用吗&#xff1f; 互动2&#xff1a;k8s1.24之前版本和1.24及1.24之后版本区别&#xff1f; k8s 使用Docker和Containerd对比分析 如果你使用Docker作为K8S容器运行时的…

Spring框架核心:揭秘Java厨房的智能烹饪艺术

前情回顾&#xff1a;Spring框架深度解析&#xff1a;打造你的Java应用梦工厂 六. 实现控制反转 6.1 描述如何在Spring中实现IoC 在Spring Town的厨房里&#xff0c;实现控制反转就像是将食材的采购和准备过程外包给了一个智能系统。这个系统知道每种食材的特性&#xff0c;也…

RabbitMQ(四种使用模式)

文章目录 1.Fanout&#xff08;广播模式&#xff09;1.基本介绍2.需求分析3.具体实现1.编写配置类 RabbitMQConfig.java2.编写生产者&#xff0c;发送消息到交换机 MQSender.java3.编写消费者&#xff0c;接受消息 MQReceiver.java4.控制层调用方法&#xff0c;发送信息到交换机…

Python中进程类Process的方法与属性的使用示例

一、示例代码&#xff1a; from multiprocessing import Process import time import osdef child_1(interval):print(子进程&#xff08;%s&#xff09;开始执行&#xff0c;父进程为&#xff08;%s&#xff09; % (os.getpid(), os.getppid()))t_start time.time()time.sle…

2025考研 | 北京师范大学计算机考研考情分析

北京师范大学&#xff08;Beijing Normal University&#xff09;简称“北师大”&#xff0c;由中华人民共和国教育部直属&#xff0c;中央直管副部级建制&#xff0c;位列“211工程”、“985工程”&#xff0c;入选国家“双一流”、“珠峰计划”、“2011计划”、“111计划”、…

BFS Ekoparty 2022 -- Linux Kernel Exploitation Challenge

前言 昨天一个师傅给了我一道 linux kernel pwn 题目&#xff0c;然后我看了感觉非常有意思&#xff0c;题目也不算难&#xff08;在看了作者的提示下&#xff09;&#xff0c;所以就花时间做了做&#xff0c;在这里简单记录一下。这个题是 BFS Lab 2022 年的一道招聘题&#…

泰迪智能科技携手新乡学院开展“泰迪智能双创工作室”共建交流会

为深化校企合作&#xff0c;实现应用型人才培养目标。5月8日&#xff0c;广东泰迪智能科技股份有限公司河南分公司市场总监张京瑞到访新乡学院数学与统计学院参观交流&#xff0c;数学与统计学院院长赵国喜、副院长皮磊、张秦&#xff0c;教研室主任许寿方、姚广出席本次交流会…

如何防止WordPress网站内容被抓取

最近在检查网站服务器的访问日志的时候&#xff0c;发现了大量来自同一个IP地址的的请求&#xff0c;用站长工具分析确认了我的网站内容确实是被他人的网站抓取了&#xff0c;我第一时间联系了对方网站的服务器提供商投诉了该网站&#xff0c;要求对方停止侵权行为&#xff0c;…

Git 如何管理标签命令(tag)

1.查看本地仓库tag --1.查看本地仓库tag UserDESKTOP-2NRT2ST MINGW64 /e/GITROOT/STARiBOSS/STARiBOSS-5GCA (gw_frontend_master) $ git tag 1stBossUpgrade V10.0.1_20220224_test V10.0.1_20220301_test tag-gwfrontend-V1.0.12-230625 tag-gw_frontend-23.08.29 tag-gw_f…

基于51单片机的冰箱控制系统设计( proteus仿真+程序+设计报告+原理图+讲解视频)

基于51单片机冰箱控制系统设计( proteus仿真程序设计报告原理图讲解视频&#xff09; 基于51单片机冰箱控制系统设计 1. 主要功能&#xff1a;2. 讲解视频&#xff1a;3. 仿真4. 程序代码5. 设计报告6. 原理图7. 设计资料内容清单&&下载链接资料下载链接&#xff1a; …

PCIe总线-PCIe简介

一、前言 PCIe总线是由PCI/PCI-X发展而来&#xff0c;但是两者之间有很大的不同。PCI/PCI-X采用的是并行总线&#xff0c;最大支持的频率为PCI-X2.0 的133MHz&#xff0c;传输速率最大仅为4262MB/s。同时使用并行总线&#xff0c;在PCB上也会造成布线资源紧张&#xff0c;线与…

SpringCloud 集成 RocketMQ 及配置解析

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

idea 使用 git

可以看见项目地址&#xff0c; git clone 地址 就可以拉新项目了 命令 git remote -v

宁夏银川市起名专家的老师颜廷利:死神(死亡)并不可怕,可怕的是...

在中国优秀传统文化之中&#xff0c;汉语‘巳’字与‘四’同音&#xff0c;在阿拉伯数字里面&#xff0c;通常用‘4’来表示&#xff1b; 湖南长沙、四川成都、重庆、宁夏银川最靠谱最厉害的起名大师的老师颜廷利教授指出&#xff0c;作为汉语‘九’字&#xff0c;倘若是换一个…

八、e2studio VS STM32CubeIDE之内存使用情况窗口

目录 一、概述/目的 二、STM32CubeIDE Build Analyzer 三、e2studio Memory Usage 八、e2studio VS STM32CubeIDE之内存使用情况窗口 一、概述/目的 1、嵌入开发最大特点之一就是资源受限&#xff0c;关注芯片资源使用详情是优秀工程师的技能之一 2、Keil和IAR都不支持内存…