🎩 欢迎来到技术探索的奇幻世界👨💻
📜 个人主页:@一伦明悦-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++ 中实现代码重用和层次化设计的重要方式之一,合理的继承关系可以使代码结构更加清晰,便于维护和扩展。