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

news2024/11/26 5:34:08

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

📜 个人主页:@一伦明悦-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_A
	cout << "通过类名访问" << 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 Display
    d.Base::display(); // 通过作用域解析符访问基类成员,输出:Base Display
    return 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 Display
    delete 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 Display
    d.callBaseDisplay(); // 输出:Base Display
    return 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_A
	cout << "通过类名访问" << 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.coloradmin.cn/o/1671575.html

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

相关文章

python内置函数exec()和eval()区别

在Python中&#xff0c;eval() 和 exec() 都是内置函数&#xff0c;用于执行存储在字符串或对象中的Python代码&#xff0c;但它们之间也有一些区别。 eval() 语法&#xff1a;eval(expression, globalsNone, localsNone) expression&#xff1a;需要求值的字符串表达式。可…

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

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

自定义类型——结构体、枚举和联合

自定义类型——结构体、枚举和联合 结构体结构体的声明匿名结构体结构体的自引用结构体的初始化结构体的内存对齐修改默认对齐数结构体传参 位段枚举联合 结构体 结构是一些值的集合&#xff0c;这些值被称为成员变量&#xff0c;结构的每个成员可以是不同类型的变量。 数组是…

Python爬虫入门:网络世界的宝藏猎人

今天阿佑将带你踏上Python的肩膀&#xff0c;成为一名网络世界的宝藏猎人&#xff01; 文章目录 1. 引言1.1 简述Python在爬虫领域的地位1.2 阐明学习网络基础对爬虫的重要性 2. 背景介绍2.1 Python语言的流行与适用场景2.2 网络通信基础概念及其在数据抓取中的角色 3. Python基…

线性表(2)

第二章、线性表&#xff08;linear list&#xff09; 线性表是第一个数据结构&#xff0c;再提一遍&#xff0c;学习一个具体的数据结构需要关注它的逻辑结构&#xff0c;物理结构和数据的运算&#xff0c;即三要素。 2.1、线性表的定义和基本操作 线性表的定义 需要注意的是…

如文所示:

影响 ConnectWise 的 ScreenConnect 远程桌面访问产品的严重漏洞已被广泛利用来传播勒索软件和其他类型的恶意软件。 ConnectWise 于 2 月 19 日通知客户&#xff0c;它已发布针对关键身份验证绕过缺陷和高严重性路径遍历问题的补丁。该安全漏洞当时没有 CVE 标识符。第二天&am…

Windows2016系统禁止关闭系统自动更新教程

目录 1.输入cmd--适合系统2016版本2.输入sconfig&#xff0c;然后按回车键3.输入5&#xff0c;然后按回车键4.示例需要设置为手动更新&#xff0c;即输入M&#xff0c;然后按回车键 1.输入cmd–适合系统2016版本 2.输入sconfig&#xff0c;然后按回车键 3.输入5&#xff0c;然后…

前端铺子-uniapp移动端:跨平台开发新篇章

一、引言 在移动应用开发领域&#xff0c;随着技术的不断进步&#xff0c;用户对应用的需求也日益多样化。如何快速、高效地开发跨平台应用成为了前端开发者面临的一大挑战。uni-app作为一款使用Vue.js开发所有前端应用的框架&#xff0c;凭借其一次编写、多端运行的特性&…

栈和队列的基础知识,C语言实现及经典OJ题

基础知识 一.栈 1.栈的概念 定义&#xff1a;堆栈又名栈&#xff08;stack&#xff09;&#xff0c;它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶&#xff0c;相对地&#xff0c;把另一端称为栈底。 压栈&#xff1a;向一个栈插入新…

爆款小红书免费流量体系课程(两周变现),小红书电商教程

课程下载&#xff1a;小红书电商教程-课程网盘链接提取码下载.txt资源-CSDN文库 更多资源下载&#xff1a;关注我。 课程内容&#xff1a; 10-爆款标题(三段式取标题).mp3 11-爆款封面怎么作图.mp3 12-爆款内容的模板(三段式模板).mp3 13-小红书流量推荐背后的秘密(四大流…

【深度学习】探秘PSD:合成到真实去雾框架的实例解析

揭秘PSD&#xff1a;合成到真实去雾框架的革新 一、PSD框架的提出背景二、PSD框架的原理与网络结构三、PSD框架的实现与代码示例四、结论与展望 在图像处理领域&#xff0c;去雾技术一直是一个备受关注的研究热点。然而&#xff0c;传统的去雾方法在面对真实世界的模糊图像时&a…

UDP和TCP协议比较,TOE技术

如今在某些方面TCP超越UDP的主要原因如下 在硬件层面的TOE(TCP Offload Engine)功能&#xff0c;将越来越多的TCP功能卸载到网卡上。它极大地提升了TCP的性能&#xff0c;使其在高吞吐量场景下的表现更为出色。近年TCP的拥塞控制算法实现了显著进步。这些新算法显著提高了TCP在…

设计模式3——简单工厂模式

简单工厂模式 简单工厂模式是工厂方法模式的衍生&#xff0c;实现起来较容易&#xff0c;也是一种创建型模式。 目录 一、简短概述 二、优缺点 三、使用过程 四、举例 一、简短概述 当有一堆相似的对象需要被创建时&#xff0c;可以使用一个简单工厂去管理如何创建它们&…

到底考不考CISP?纠结的看过来

专业认证如CISP&#xff08;注册信息安全专业人员&#xff09;成为了衡量专业水平的重要标准。 CISP的含金量懂的都懂&#xff0c;然而&#xff0c;是否要投入时间、精力和金钱去追求这样一个认证&#xff0c;对于许多人来说&#xff0c;依然是一个值得深思的问题。 那么到底…

Spirng-IOC零碎知识点

Spirng IOC 依赖注入 根据名称注入 <?xml version"1.0" encoding"UTF-8"?> <beansxmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xmlns:util"http://w…

鸿蒙ArkUI开发:常用布局【弹性布局方向图】

弹性布局方向图 Flex({ direction: FlexDirection.Row }) FlexDirection.Row&#xff08;默认值&#xff09;&#xff1a;主轴为水平方向&#xff0c;子组件从起始端沿着水平方向开始排布FlexDirection.RowReverse&#xff1a;主轴为水平方向&#xff0c;子组件从终点端沿着F…

Hystrix服务熔断

服务熔断 熔断机制是应对雪崩效应的一种微服务链路保护机制。当某个微服务不可用或者响应时间太长时&#xff0c; 会进行服务降级&#xff0c;进而熔断该节点微服务的调用&#xff0c;快速返回“错误”的响应信息。当检测到该节点微 服务调用响应正常后恢复调用链路。 在Spri…

NCL绘制WRF domain区域并添加气象站点

读取文件 根据官网例子Using gsn_csm_contour_map to plot WRF-ARW data绘制&#xff1a; ; It shows how to use gsn_csm_xxxx scripts to do the plotting. ; ; You can use the map projection settings on the WRF file, or you ; can use your own map projection. See …

在excel的内置瀑布图模板中,能在数据标签里同时显示数值和百分比吗?

瀑布图是由麦肯锡顾问公司所创的图表类型&#xff0c;因为形似瀑布流水而称之为瀑布图( Waterfall Plot)。这种图表常用于表达数个特定数值之间的数量增减变化关系。 在Excel中&#xff0c;瀑布图是可以通过簇状柱形图来完成创建。从excel2016版起&#xff0c;excel添加了内置…

【UE5 C++】基础学习笔记——01 UObject的创建与使用

目录 步骤 一、创建UObject 二、创建基于UObject的蓝图类 三、在UObject中使用变量和函数 步骤 一、创建UObject 在内容浏览器中新建一个C类 父类选择“Object” 类的类型设置为公有&#xff0c;这里就命名为“MyObject”&#xff0c;点击“创建类”来创建头文件和源文…