首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【C++】继承—C++的秘密武器,get父类的智慧

【C++】继承—C++的秘密武器,get父类的智慧

作者头像
我不是呆头
发布2025-12-20 14:03:57
发布2025-12-20 14:03:57
00
举报

摘要

C++继承是通过派生类继承基类的成员,避免了代码冗余,提升了代码复用性。继承有public、protected和private三种方式,影响成员的访问权限。继承支持多态性,使得同一接口能表现出不同的行为。继承和组合各有优缺点,继承适用于“is-a”关系,而组合适用于“has-a”关系。在使用时需根据需求选择合适的方式,避免过度依赖继承,保持代码的可维护性。

一、继承的概念和定义

1. 继承的概念

继承机制是面向对象程序设计使代码可以复用的重要手段。它允许陈煦园在保持原有类特性的基础上进行扩展,增加功能,产生新的类,这个新的类就叫做派生类。 继承体现了面向对象程序设计的层次结构,体现了由简单到复杂的过程。以前我们所接触的复用都是函数层次的复用(函数调用,模板使用)。现在的继承是类层次设计的复用。

  1. 老师和学生共享“人”的属性,继承帮你避免重复劳动 在面向对象编程中,继承就像是你从父母那里继承了某些特征。比如,老师和学生都是人,都有姓名、年龄、身高、体重等共同属性,但如果每次都重复写这些属性岂不是太麻烦?所以,我们创建一个Person类,像是一个“通用模板”,包含所有人的共有属性,老师和学生就可以从这个类继承所有这些基本信息,轻松避免代码冗余。
  2. 差异化部分各自发挥,老师和学生各有专属功能 当然,老师和学生并不是完全相同的,他们有自己的特征和行为。比如,老师有职称,学生有学号;老师会授课,学生会学习。通过继承,Person类只负责提供基本的属性,而各自的特有属性和功能(如授课学习)则由老师和学生各自负责,这样既不重复也能满足不同需求。
代码语言:javascript
复制
#include <iostream>
#include <string>
using namespace std;

class Person
{
public:
    void Print()
    {
        cout << "姓名: " << _name << endl;
        cout << "年龄: " << _age << endl;
    }

protected:
    int _age = 20;
    string _name = "DaiTou";
};

class Student : public Person
{
public:
    void Study()
    {
        cout << _name << " 正在学习中..." << endl;
    }

protected:
    int _stuid = 2025101010;
};

class Teacher : public Person
{
public:
    void Teach()
    {
        cout << _name << " 正在授课中..." << endl;
    }

protected:
    string _title = "教授";
};

int main()
{
    Student s;
    s.Print();
    s.Study();

    Teacher t;
    t.Print();
    t.Teach();

    return 0;
}

我们来看这段代码,这其中属于老师和人的公共属性有年龄,和人名,属于他们各自的自己的属性有学生的学号和老师的职称。

在这里插入图片描述
在这里插入图片描述

2. 继承的定义
2.1 定义格式

上述中Person类就是父类,也叫做基类;Student类和Teacher类叫做子类,也叫做派生类。

继承方式: public继承,protected继承,private继承。 继承使用方法: class 子类名 :继承方式 父类名

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.2 继承基类成员访问方式的变化

类成员 / 继承方式

public继承

protected继承

private继承

基类的public成员

派生类的public成员

派生类的protected成员

派生类的private成员

基类的protected成员

派生类的protected成员

派生类的protected成员

派生类的private成员

基类的private成员

在派生类中不可见

在派生类中不可见

在派生类中不可见

  1. 基类的private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员虽然被继承到了派生类对象,但是语法限制派生类对象无论在类里面还是在类外面都不可以访问基类的私有成员。

这个基类的private成员和派生类中原有的private成员不同,派生类原有的private成员可以在类里面进行访问,但是在类外面不能访问。

  1. 如果我们不想基类的成员在派生类继承后直接在派生类的类外面直接访问,但在派生类的类里面进行访问,我们就可以直接把基类的成员定义成protected成员。

因为protected成员在基类中的里面可以访问,在基类的外面不可以访问(protected保护成员限定符是因为继承出现的)

  1. 基类的成员(除开private成员)在派生类的继承中,取权限大小的那一个

public > protected > private

  1. 如果没用继承方式的话,使用class就默认private继承;使用struct就默认public继承。

3. 继承类模板
代码语言:javascript
复制
#include <iostream>
#include <vector>
using namespace std;

//=========================
// 自定义命名空间 dh
// 防止与标准库或其他代码命名冲突
//=========================
namespace dh
{
    //=========================
    // 类模板:stack<T>
    // 模拟栈(stack)的实现
    // 继承自 std::vector<T>,利用其底层动态数组特性
    //=========================
    template<class T>
    class stack : public std::vector<T>  // 继承标准库 vector<T>
    {
    public:
        // 入栈操作:在 vector 尾部插入元素
        void push(const T& x)
        {
            vector<T>::push_back(x);
        }

        // 出栈操作:删除 vector 尾部元素
        void pop()
        {
            vector<T>::pop_back();
        }

        // 获取栈顶元素:返回 vector 的最后一个元素的引用
        T& top()
        {
            return vector<T>::back();
        }

        // 判断栈是否为空
        bool empty()
        {
            return vector<T>::empty();
        }
    };
}

//=========================
// 主函数入口
//=========================
int main()
{
    dh::stack<int> st;  // 定义一个整型栈对象

    st.push(1);  // 压入元素 1
    st.push(2);  // 压入元素 2

    // 循环打印并弹出栈顶元素
    while (!st.empty())
    {
        cout << st.top() << " ";  // 输出当前栈顶元素
        st.pop();                 // 弹出栈顶元素
    }

    return 0;
}

二、 基类和派生类对象的赋值转换

  1. public继承的派生类对象可以赋值给基类的对象/基类的指针/积累的引用。

其中当派生类的对象赋值给基类的对象是,派生类中多出来的成员会被切掉,这叫做切片。派生类中基类的那一部分进行赋值。 但是当用基类指针或引用指向派生类对象时,并不会发生切片,只是“看不到”派生类新增的部分,但派生类对象仍然完整地存在内存中。这时通过虚函数可以实现多态访问。

代码语言:javascript
复制
class Base {
public:
    int a;
};

class Derived : public Base {
public:
    int b;
};

int main() {
    Derived d;
    d.a = 1;
    d.b = 2;

    Base b = d;  // ❌ 发生对象切片
    cout << b.a << endl;  // 输出 1
    // b 中没有 b.b 这个成员,派生部分被“切掉”了
}
代码语言:javascript
复制
Derived d;
Base* pb = &d;  // ✅ 没有切片
Base& rb = d;   // ✅ 没有切片

此时 pb 和 rb 只是“看不到”派生类新增的部分, 但派生类对象仍然完整地存在内存中。 这时通过虚函数可以实现多态访问。

  1. 基类对象不可以赋值给派生类对象
在这里插入图片描述
在这里插入图片描述

  1. 基类的指针或引用可以通过强制类型转换赋值给派生类的指针或引用。因为基类的指针或者引用是有可能指向派生类的父类那一部分。但是必须是基类的指针或者引用指向派生类的对象才是安全的。
在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
#include <iostream>
#include <string>
using namespace std;

//基类
class Person
{
public:
    void Print()
    {
        cout << "姓名: " << _name << endl;
        cout << "年龄: " << _age << endl;
    }

protected:
    int _age = 20;
    string _name = "DaiTou";
};

//派生类
class Student : public Person
{
public:
    void Study()
    {
        cout << _name << " 正在学习中..." << endl;
    }

protected:
    int _stuid = 2025101010;
};

class Teacher : public Person
{
public:
    void Teach()
    {
        cout << _name << " 正在授课中..." << endl;
    }

protected:
    string _title = "教授";
};

int main()
{
    Student s;
    Teacher t;
    // ======= 基类指针指向派生类对象 =======
    Person* p1 = &s;   // 基类指针指向 Student 对象
    Person* p2 = &t;   // 基类指针指向 Teacher 对象

    cout << "基类指针访问派生类对象(Student):" << endl;
    p1->Print();   // ✅ 可以访问基类成员函数
    // p1->Study(); // ❌ 错误:基类指针不能访问派生类独有成员

    cout << endl << "基类指针访问派生类对象(Teacher):" << endl;
    p2->Print();
    // p2->Teach(); // ❌ 同理,不可访问派生类特有函数

    cout << endl;

    // ======= 基类引用指向派生类对象 =======
    Person& rp1 = s;  // 基类引用绑定到 Student
    Person& rp2 = t;  // 基类引用绑定到 Teacher

    cout << "基类引用访问派生类对象(Student):" << endl;
    rp1.Print();  // ✅ 可以调用基类成员

    cout << endl << "基类引用访问派生类对象(Teacher):" << endl;
    rp2.Print();  // ✅ 依然可以访问基类成员
    return 0;
}
在这里插入图片描述
在这里插入图片描述

三、继承中的作用域

1. 隐藏规则
  1. 在继承体系中的基类和派生类都有其独立的作用域。
  2. 当派生类和基类中有同名成员时,派生类中的同名成员将屏蔽基类中的同名成员,直接对当前派生类的同名成员进行访问,这种情况叫做隐藏,也叫做重定义(在派生类的成员函数中,可以使用 基类::同名成员 的形式直接访问基类的同名成员)。
  3. 注意,如果是派生类和基类中的成员函数构成隐藏的话,只要函数名相同就构成隐藏。
  4. 但是注意,在实际的继承体系中,最好不要定义同名成员。
代码语言:javascript
复制
#include <iostream>
#include <string>
using namespace std;

// 基类
class Person
{
public:
    void Print()
    {
        cout << "基类:年龄:" << _age << endl;
        cout << "基类:姓名:" << _name << endl;
    }

protected:
    int _age = 20;
    string _name = "DaiTou";
};

// 派生类
class Student : public Person
{
public:
    void Print()
    {
        cout << "派生类:年龄:" << _age << endl;
        cout << "派生类:姓名:" << _name << endl;

        cout << "--- 调用基类 Print() ---" << endl;
        Person::Print();
    }

protected:
    int _age = 666;   // 派生类自己的年龄,隐藏基类 _age
    int _stuid = 2025101010;
};

int main()
{
    Student s;

    cout << "--- 调用派生类 Print() ---" << endl;
    s.Print();  // 调用 Student::Print

    cout << "--- 直接调用基类 Print() ---" << endl;
    s.Person::Print();  // 调用基类 Print

    return 0;
}
在这里插入图片描述
在这里插入图片描述

2. 考察继承作用域选择题
代码语言:javascript
复制
class A
{
public:
    void fun()
    {
        cout << "func()" << endl;
    }
};

class B : public A
{
public:
    void fun(int i)
    {
        cout << "func(int i)" << i << endl;
    }
};

int main()
{
    B b;
    b.fun(10);
    b.fun();   // ⚠️ 注意这里
    return 0;
}

  1. A 和 B 中两个 fun 的关系?

A::fun() 和 B::fun(int),B 类中定义了一个与基类同名的函数 fun。在 C++ 中,如果派生类中定义了一个与基类同名的函数,会隐藏基类所有同名函数(不管参数列表是否相同)。 注意:这不是重载(overload),因为重载是在同一个类中根据参数类型或个数区分函数。

  1. 程序编译运行结果?

b.fun(10); ✅ 可以正常调用 B::fun(int) 但是b.fun(); ❌因为编译器找不到匹配的函数因为基类 A::fun() 被 B::fun(int) 隐藏了。

代码语言:javascript
复制
class B : public A
{
public:
    using A::fun;  // 引入基类 fun() 到当前作用域
    void fun(int i)
    {
        cout << "func(int i)" << i << endl;
    }
};

此时就能够同时调用b.fun(10); b.fun();


四、派生类中的默认成员函数

在这里插入图片描述
在这里插入图片描述

6个默认成员函数,意思就是我们不写,编译器也会自动生成,那么他们分别是如何生成的呢?

  1. 派生类的构造函数在走初始化列表时会优先自动调用基类的默认构造函数完成基类那一部分成员的初始化,如果基类没有默认成员函数,那么在派生类的构造函数中必须显示调用基类的构造函数
代码语言:javascript
复制
// 基类
class Person
{
public:
    // 构造函数:如果没有传递 name 参数,则使用默认值 "peter"
    Person(const string& name = "peter")
        : _name(name)  // 使用初始化列表初始化 _name
    {
        cout << "Person()" << endl;  // 输出提示,表示基类构造函数被调用
    }

private:
    string _name;  // 姓名属性,用于存储人的名字
};

// 派生类
class Student : public Person
{
public:
    // 构造函数:接受 name 和 id 参数来初始化对象
    Student(const string& name, int id)
        : _id(id)  // 初始化派生类的成员 _id
    {
        cout << "Student()" << endl;  // 输出提示,表示派生类构造函数被调用
    }
private:
    int _id;  // 学号属性,用于存储学生的学号
};
  1. 基类: 这段代码具有一个带有默认参数的构造函数(如果没有传递 name 参数,则使用默认值 “peter” Person(const string& name = "peter");然后使用初始化列表来初始化成员变量_name,即_name会被赋予传入name参数值,如果没用传递参数,默认为peter。
  2. 派生类: 构造函数Student(const string& name, int id)会接受两个参数,name和id用来初始化对象,值得注意的是在这个构造函数里面,基类的构造函数没用显示调用,但是由于基类的构造函数有默认参数,编译器会自动调用基类构造函数来初始化基类的_name。
在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
//基类
class Person
{
public:
    // 构造函数
    // 如果没有传递 name 参数,则默认使用 "peter"
    Person(const string& name)
        : _name(name)  // 使用初始化列表初始化 _name
    {
        cout << "Person()" << endl;  // 构造函数的输出,表示对象创建
    }
private:
    string _name;  // 姓名属性,存储人的名字
};

//派生类
class Student : public Person
{
public:
	//构造函数
	Student(const string& name, int id)
		:Person(name) //调用基类的构造函数初始化基类的那一部分成员
		, _id(id) //初始化派生类的成员
	{
		cout << "Student()" << endl;
	}
private:
	int _id; //学号
};
  1. 基类: 这段代码的构造函数是没用默认参数的,要求我们再创建Person对象的时候必须提供一个name的参数,这个参数会在初始化列表赋值给_name。
  2. 派生类: 派生类中的构造函数同样需要接受两个参数,name和id用来初始化对象,因为基类中构造函数没有默认参数,我们必须显示调用基类构造函数,将name传递给Person的构造函数来初始化基类的_name。
在这里插入图片描述
在这里插入图片描述

  1. 派生类的拷贝构造函数必须显示调用基类的拷贝构造函数来完成基类的拷贝初始化。

拷贝构造函数也属于构造函数,如果我们在进行派生类的拷贝构造的时候没有进行显式调用,编译器就会去调用基类的默认构造函数去完成对基类部分的初始化,这时候基类中的数据就不是我们想要进行拷贝的数据,而是初始化的数据。

代码语言:javascript
复制
class Person
{
public:
    // 构造函数
    // 如果没有传递 name 参数,则默认使用 "peter"
    Person(const string& name)
        : _name(name)  // 使用初始化列表初始化 _name
    {
        cout << "Person()" << endl;  // 构造函数的输出,表示对象创建
    }

	//拷贝构造函数
	Person(const Person& p)
		:_name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}

private:
    string _name;  // 姓名属性,存储人的名字
};

class Student : public Person
{
public:
	//构造函数
	Student(const string& name, int id)
		:Person(name)
		,_id(id) //初始化派生类的成员
	{
		cout << "Student()" << endl;
	}

	//拷贝构造函数
	Student(const Student& s)
		:Person(s) //显示调用基类的拷贝构造函数完成基类成员的拷贝构造
		, _id(s._id) //拷贝构造派生类的成员
	{
		cout << "Student(const Student& s)" << endl;
	}

private:
	int _id; //学号
};

int main()
{
	Student s("DaiTou", 20231010);
	Student s1(s);

	return 0;
}
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 派生类的 赋值运算符(operator=) 需要显式地调用基类的赋值运算符,以确保基类部分的成员正确地被复制。

如果你在派生类的 operator= 中不显示调用基类的 operator=,基类的 _name 就不会被正确赋值,导致 s2 的基类部分依然保持原来的值(可能是默认值或不正确的值)。值得注意的是派生类的赋值运算符隐藏了基类的赋值运算符,所以显示调用基类的operator=,需要指定基类作用域

代码语言:javascript
复制
//基类
class Person
{
public:
    // 构造函数
    // 如果没有传递 name 参数,则默认使用 "peter"
    Person(const string& name = "Peter")
        : _name(name)  // 使用初始化列表初始化 _name
    {
        cout << "Person()" << endl;  // 构造函数的输出,表示对象创建
    }

	//拷贝构造函数
	Person(const Person& p)
		:_name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}

	//赋值运算符重载函数
	Person& operator=(const Person& p)
	{
		cout << "Person& operator=(const Person& p)" << endl;
		if (this != &p)
		{
			_name = p._name;
		}
		return *this;
	}

private:
    string _name;  // 姓名属性,存储人的名字
};

//派生类
class Student : public Person
{
public:
	//构造函数
	Student(const string& name, int id)
		:Person(name)
		,_id(id) //初始化派生类的成员
	{
		cout << "Student()" << endl;
	}

	//拷贝构造函数
	Student(const Student& s)
		:Person(s) //显示调用基类的拷贝构造函数完成基类成员的拷贝构造
		, _id(s._id) //拷贝构造派生类的成员
	{
		cout << "Student(const Student& s)" << endl;
	}

	//赋值运算符重载函数
	Student& operator=(const Student& s)
	{
		cout << "Student& operator=(const Student& s)" << endl;
		if (this != &s)
		{
			Person::operator=(s); //显示调用基类的operator=完成基类成员的赋值
			_id = s._id; //完成派生类成员的赋值
		}
		return *this;
	}

private:
	int _id; //学号
};

int main()
{
	Student s("DaiTou", 20231010);
	Student s1(s);
	Student s2("BuDai", 20231212);
	s1 = s2;
	return 0;
}
在这里插入图片描述
在这里插入图片描述

  1. 派生类的析构函数会在被调用完之后自动调用基类的析构函数完成对基类成员的清理。

因为派生类对象可能管理一些资源或成员,在基类析构之前,派生类需要完成自己的资源清理。如果先调用基类的析构函数,派生类的析构函数可能会尝试访问已经销毁的基类成员,这样会导致未定义的行为或错误。C++ 会自动处理析构函数的调用顺序,基类的析构函数不需要在派生类析构函数中显式调用。

代码语言:javascript
复制
//基类
class Person
{
public:
    // 构造函数
    // 如果没有传递 name 参数,则默认使用 "peter"
    Person(const string& name = "Peter")
        : _name(name)  // 使用初始化列表初始化 _name
    {
        cout << "Person()" << endl;  // 构造函数的输出,表示对象创建
    }

	//拷贝构造函数
	Person(const Person& p)
		:_name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}

	//赋值运算符重载函数
	Person& operator=(const Person& p)
	{
		cout << "Person& operator=(const Person& p)" << endl;
		if (this != &p)
		{
			_name = p._name;
		}
		return *this;
	}

	//析构函数
	~Person()
	{
		cout << "~Person()" << endl;
	}

private:
    string _name;  // 姓名属性,存储人的名字
};

//派生类
class Student : public Person
{
public:
	//构造函数
	Student(const string& name, int id)
		:Person(name)
		,_id(id) //初始化派生类的成员
	{
		cout << "Student()" << endl;
	}

	//拷贝构造函数
	Student(const Student& s)
		:Person(s) //显示调用基类的拷贝构造函数完成基类成员的拷贝构造
		, _id(s._id) //拷贝构造派生类的成员
	{
		cout << "Student(const Student& s)" << endl;
	}

	//赋值运算符重载函数
	Student& operator=(const Student& s)
	{
		cout << "Student& operator=(const Student& s)" << endl;
		if (this != &s)
		{
			Person::operator=(s); //显示调用基类的operator=完成基类成员的赋值
			_id = s._id; //完成派生类成员的赋值
		}
		return *this;
	}

	//析构函数
	~Student()
	{
		cout << "~Student()" << endl;
		//派生类的析构函数会在被调用完成后自动调用基类的析构函数
	}

private:
	int _id; //学号
};

int main()
{
	Student s("DaiTou", 20231010);
	Student s1(s);
	Student s2("BuDai", 20231212);
	s1 = s2;
	return 0;
}
在这里插入图片描述
在这里插入图片描述

  1. 派生类的析构函数会在被调用完成之后自动调用基类的析构函数这种方式有效的保证了在栈上先定义后析构,后定义先析构的顺序。

定义派生类成员的时候,先调用派生类中基类的那一部分进行初始化,然后在生命周期结束后,由于派生类自己那部分的成员是后定义的,所以先调用派生类的析构函数进行析构,派生类中的在基类先定义那一部分就会调用基类的析构函数后析构。

  1. 因为多态中的一些析构函数会进行重写,重写的条件之一是函数名相同(放在多态中进行学习所以那么编译器会对析构函数的函数名进行重写,那么会统一修饰为destructor(),所以基类析构函数不加virtual的情况下,派生类的析构函数和基类析构函数构成隐藏)

五、 继承与友元

  • 友元关系不能继承,也就是说明基类的友元可以访问基类的私有成员和保护成员,但是不能访问派生类的私有成员和保护成员。
  • 但是基类的友元函数想访问基类的派生类的私有和保护成员,应该声明为派生类的友元函数。
代码语言:javascript
复制
// 友元函数示例
// 基类 Person
class Person
{
    // 友元函数声明:Print 允许访问 Person 类的私有成员
    friend void Print(const Person& p); // 友元函数 Print 被声明为 Person 类的友元函数
public:

protected:
    // 保护成员:年龄(_age),默认为 20
    int _age = 20;

private:
    // 私有成员:name 是私有的,只有友元函数或者该类内部可以访问
    // 私有成员无法直接通过外部对象访问
    string _name = "DaiTou";
};

// 友元函数定义:该函数可以访问 Person 类的私有成员
// 注意:Print 函数是一个全局函数,可以访问 Person 的私有成员(因为它是 Person 的友元)
void Print(const Person& p)
{
    cout << p._age << endl;  // 访问 Person 类的 _age 成员
    cout << p._name << endl; // 访问 Person 类的 _name 成员
}

// 派生类 Student
class Student : public Person
{
    // 友元函数声明:Print 也可以访问 Student 类及其基类 Person 的私有成员
    friend void Print(const Person& p); // 友元函数 Print 被声明为 Student 类的友元函数

public:
    // 公共成员:学号(_stuid),默认为 20251010
    int _stuid = 20251010;

protected:
    // 保护成员:暂无
};

// 主函数:创建 Person 和 Student 类型的对象,调用 Print 函数
int main()
{
    Student s;  // 创建一个 Student 类型的对象
    Person p;   // 创建一个 Person 类型的对象

    Print(p);  // 调用 Print 函数打印 Person 类型对象 p 的成员
    Print(s);  // 调用 Print 函数打印 Student 类型对象 s 的成员

    return 0;
}
在这里插入图片描述
在这里插入图片描述

六、继承与静态成员

基类定义了static的静态成员,则在整个继承体系中都只有一个这样的成员。无论派生出多少个派生类整个继承体系都只有一个static静态成员。 当我们定义一个_count基类的静态成员变量的时候,我们让它在基类的构造函数和拷贝构造函数进行自增,这样我们就能够在该时刻实时获取已经实例化的基类和派生类的总个数(因为派生类在进行构造的时候会优先调用基类的默认构造完成派生类中在基类的那一部分成员的初始化)

代码语言:javascript
复制
#include <iostream>
using namespace std;
// 基类 Person
class Person
{
public:
    // 构造函数,每次创建一个 Person 对象时,_count 自增
    Person()
    {
        _count++;
    }

    // 静态成员变量:计数器,用于统计创建了多少个 Person 或其派生类对象
    static int _count;
};

// 静态成员变量的定义与初始化
int Person::_count = 0;  // 该静态变量仅在 Person 类中定义和初始化

// 派生类 Student
class Student : public Person
{
public:
    // 默认构造函数
    Student()
    {
        // 每当创建一个 Student 对象时,_count 会在基类 Person 中自增
    }
};

int main()
{
    Person p;  // 创建一个 Person 对象,_count 会自增至 1

    Student s1;  // 创建一个 Student 对象,_count 会自增至 2
    Student s2;  // 创建另一个 Student 对象,_count 会自增至 3
    Student s3;  // 再创建一个 Student 对象,_count 会自增至 4

    // 输出 _count,显示创建的 Person 和 Student 对象的总数
    cout << "Total number of objects created: " << Person::_count << endl;

    return 0;
}

七、继承和组合

特性

继承(is-a)

组合(has-a)

关系类型

is-a 关系,即派生类是基类的一种类型

has-a 关系,即对象包含另一个对象

复用类型

白箱复用:派生类可以访问基类的实现细节

黑箱复用:对象细节不可见,只通过接口访问

耦合度

高,基类变化会影响派生类

低,改变一个对象不会影响另一个对象

优点

代码复用,继承可以拓展基类功能

类之间的关系松散,低耦合,易于维护

缺点

破坏封装,依赖性强,基类变动可能影响派生类

需要定义清晰的接口

使用场景

类之间是 is-a 关系,且需要多态支持

类之间是 has-a 关系,且不需要多态

总结

C++中的继承是面向对象编程的核心机制之一,它允许通过父类复用代码并扩展子类的功能。通过继承,子类可以继承父类的属性和方法,并根据需要进行扩展或重写。继承分为三种方式:public、protected和private继承,不同方式会影响访问权限。继承不仅可以带来代码复用,还能够实现多态和重写,使得程序更加灵活和可扩展。然而,继承也有其缺点,如破坏封装性、耦合度较高等,因此在实际设计时需要合理权衡,选择继承还是组合。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-12-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 摘要
    • 一、继承的概念和定义
      • 1. 继承的概念
      • 2. 继承的定义
    • 二、 基类和派生类对象的赋值转换
    • 三、继承中的作用域
      • 1. 隐藏规则
      • 2. 考察继承作用域选择题
    • 四、派生类中的默认成员函数
    • 五、 继承与友元
    • 六、继承与静态成员
    • 七、继承和组合
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档