C++ <继承> 详解

目录

一、引言

二、基本继承语法与示例

[2.1 基本语法](#2.1 基本语法)

[2.2 示例代码](#2.2 示例代码)

三、访问控制与继承

[3.1 不同访问控制符的影响](#3.1 不同访问控制符的影响)

[3.2 代码示例](#3.2 代码示例)

四、子类的构造、拷贝构造、赋值重载和析构

[4.1 构造函数](#4.1 构造函数)

[4.2 拷贝构造函数](#4.2 拷贝构造函数)

[4.3 赋值重载函数](#4.3 赋值重载函数)

[4.4 析构函数](#4.4 析构函数)

五、隐藏(重定义)

5.1.示例代码

六、不能被继承的类

6.1示例代码

七、友元与继承

八、静态成员与继承

8.1代码示例

九、多继承与菱形继承

9.1继承模型

9.2代码示例

十、总结


一、引言

在面向对象编程中,继承是一个非常重要的概念,它允许我们创建一个新的类(子类),这个新类可以继承另一个已存在的类(父类)的属性和方法。C++ 作为一门强大的面向对象编程语言,提供了丰富的继承机制。本文将结合提供的代码,详细解析 C++ 继承的各种特性。

二、基本继承语法与示例

2.1 基本语法

在 C++ 中,使用 : 符号来实现继承,语法如下:

cpp 复制代码
class 子类名 : 继承方式 父类名 {
    // 子类的成员
};

其中,继承方式可以是 publicprotectedprivate,默认情况下是 private

2.2 示例代码

cpp 复制代码
class Person {
protected:
    string _name = "张三";
    string _address;
    string _tel;
private:
    int _age = 18;
};

class Student : public Person {
protected:
    int _stuid;
};

在这个例子中,Student 类继承自 Person 类,使用了 public 继承方式。

三、访问控制与继承

3.1 不同访问控制符的影响

  • public 继承 :父类的 public 成员在子类中仍然是 publicprotected 成员在子类中仍然是 protectedprivate 成员在子类中不可直接访问。
  • protected 继承 :父类的 publicprotected 成员在子类中都变为 protectedprivate 成员在子类中不可直接访问。
  • private 继承 :父类的 publicprotected 成员在子类中都变为 privateprivate 成员在子类中不可直接访问。


在实际运⽤中⼀般使⽤都是public继承,⼏乎很少使⽤protetced/private继承,也不提倡使⽤
protetced/private继承,因为protetced/private继承下来的成员都只能在派⽣类的类⾥⾯使⽤,实
际中扩展维护性不强。

3.2 代码示例

cpp 复制代码
class Person {
public:
    void identity() {
        cout << "void identity()" << _name << endl;
        cout << _age << endl;
    }
protected:
    string _name = "张三";
private:
    int _age = 18;
};

class Student : public Person {
    void study() {
        identity(); // 可以调用父类的 public 方法
        // cout << _age << endl; // 错误,不能直接访问父类的 private 成员
    }
};

四、子类的构造、拷贝构造、赋值重载和析构

4.1 构造函数

子类的构造函数必须调用父类的构造函数来初始化父类的那部分成员。如果父类没有默认构造函数,则必须在子类的初始化列表中显式调用父类的构造函数。

cpp 复制代码
class Person {
public:
    Person(const char* name = "xxx") : _name(name) {
        cout << "Person()" << endl;
    }
protected:
    string _name;
};

class Student : public Person {
public:
    Student(const char* name, int nums, const char* address)
        : Person(name)
        , _nums(nums)
        , _address(address) 
    {}
protected:
    int _nums;
    string _address;
};

4.2 拷贝构造函数

子类的拷贝构造函数需要调用父类的拷贝构造函数来复制父类的成员。

cpp 复制代码
class Student : public Person {
public:
    Student(const Student& s)
        : Person(s), _nums(s._nums), _address(s._address) {}
};

4.3 赋值重载函数

子类的赋值重载函数需要显式调用父类的赋值重载函数。

cpp 复制代码
class Student : public Person {
public:
    Student& operator=(const Student& s) {
        if (this != &s) {
            Person::operator=(s);
            _nums = s._nums;
            _address = s._address;
        }
        return *this;
    }
};

4.4 析构函数

子类的析构函数会在执行完自身的析构代码后,自动调用父类的析构函数,不需要显式调用。

cpp 复制代码
class Student : public Person {
public:
    ~Student() {
        // 不需要显式调用父类的析构函数
        //子类的析构与父类的虚构构成隐藏关系
        //规定:不需要显示调用父类的析构,子类析构之后会自动调用父类的析构
        //这样能保证析构顺序为先子后父
        //如果显示调用则取决于实现者的实现顺序,不能保证先子后父
        //Person::~Person();

    }
};

五、隐藏(重定义)

当子类和父类有同名的成员函数或成员变量时,子类的成员会隐藏父类的成员。
隐藏规则:

  1. 在继承体系中基类和派⽣类都有独⽴的作⽤域。
  2. 派⽣类和基类中有同名成员,派⽣类成员将屏蔽基类对同名成员的直接访问,这种情况叫隐藏。
    (在派⽣类成员函数中,可以使⽤ 基类::基类成员 显⽰访问)
  3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
  4. 注意在实际中在继承体系⾥⾯最好不要定义同名的成员

5.1.示例代码

cpp 复制代码
class A {
public:
    void func() {
        cout << "func()" << endl;
    }
};

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

int main() {
    B b;
    // b.func(); // 无法调用
    b.func(1);
    b.A::func(); // 要显示调用父类函数
    return 0;

六、不能被继承的类

在C++98中,想要实现不能被继承的类,可以把类的构造函数定义在private中设为私有,而在C++11中加入了新的关键字final,直接写在该类的后面。

6.1示例代码

cpp 复制代码
//不能被继承的类
//C++98
class Base final //C++11关键字final
{

private: //把构造函数私有
    Base()
    {}
};

class Derive : public Base
{
public:
    Derive()
    {}
protected:
    int nums;
};

七、友元与继承

友元关系不能继承,也就是说基类友元不能访问派⽣类私有和保护成员。但是,如果在子类中再次声明同一个友元函数,那么该函数也会成为子类的友元。

cpp 复制代码
class Student;
class Person {
    friend void Display(const Person& p, const Student& s);
protected:
    int _nums;
};

class Student : public Person {
    friend void Display(const Person& p, const Student& s);
protected:
    string _name;
};

void Display(const Person& p, const Student& s) {
    cout << p._nums << endl;
    cout << s._name << endl;
}

八、静态成员与继承

基类定义了static静态成员,则整个继承体系⾥⾯只有⼀个这样的成员。⽆论派⽣出多少个派⽣类,都只有⼀个static成员实例。

8.1代码示例

cpp 复制代码
class Person
{
public:
    string _name;
    static int _count;
};
int Person::_count = 0;

class Student : public Person
{
protected:
    int _stuNums;
};

int main()
{
    Person p;
    Student s;

    // 这里的运行结果可以看到非静态成员_name的地址是不一样的
    // 说明子类继承下来了,父子类对象各有一份
    cout << &p._name << endl;
    cout << &s._name << endl;
    // 这里的运行结果可以看到静态成员_count的地址是一样的
	// 说明子类和父类共用同一份静态成员
	cout << &p._count << endl;
	cout << &s._count << endl;

    // 公有的情况下,父子类指定类域都可以访问静态成员
	cout << Person::_count << endl;
	cout << Student::_count << endl;

	Person::_count++;

	cout << p._count << endl;
	cout << s._count << endl;

    return 0;
}

九、多继承与菱形继承

9.1继承模型

1.单继承:⼀个派⽣类只有⼀个直接基类时称这个继承关系为单继承
2.多继承:⼀个派⽣类有两个或以上直接基类时称这个继承关系为多继承,多继承对象在内存中的模型是,先继承的基类在前⾯,后⾯继承的基类在后⾯,派⽣类成员在放到最后⾯。
3.菱形继承:菱形继承是多继承的⼀种特殊情况。菱形继承的问题,从下⾯的对象成员模型构造,可以看出菱形继承有数据冗余和⼆义性的问题,在Assistant的对象中Person成员会有两份。⽀持多继承就⼀定会有菱形继承,像Java就直接不⽀持多继承,规避掉了这⾥的问题,所以实践中我们也是不建议设计出菱形继承这样的模型的。
我们可以设计出多继承,但是不建议设计出菱形继承,因为菱形虚拟继承以后,⽆论是使⽤还是底层都会复杂很多。当然有多继承语法⽀持,就⼀定存在会设计出菱形继承,像Java是不⽀持多继承的,就避开了菱形继承。

9.2代码示例

多继承:

cpp 复制代码
class 子类名 : 继承方式 父类名1, 继承方式 父类名2, ... {
    // 子类的成员
};

菱形继承:

cpp 复制代码
class Person
{
public:
    Person(const char* name)
        :_name(name)
    {}

    string _name;
};

//虚继承 关键词virtual
class Student : virtual public Person
{
public:
    Student(const char* name, int nums = 0)
        :Person(name)
        ,_nums(nums)
    {}
protected:
    int _nums; //学号
};

class Teacher : virtual public Person
{
public:
    Teacher(const char* name, int id = 1)
        :Person(name)
        ,_id(id)
    {}
protected:
    int _id; //职工号
};

//不要用菱形继承 用的时候加上virtual关键字 底层复杂
//多继承先继承的在前面,后继承的在后面
class Assistant : public Student , public Teacher
{
public:    
    Assistant(const char* name1, const char* name2, const char* name3)
        :Student(name1)
        ,Teacher(name2)
        ,Person(name3)
    {}

protected:
    string _majorCourse; //主修课程
};

十、总结

C++ 的继承机制提供了强大的功能,但也带来了一些复杂的问题,如访问控制、构造析构顺序、隐藏、友元、静态成员、多继承和菱形继承等。在使用继承时,需要仔细考虑这些问题,以确保代码的正确性和可维护性。

相关推荐
Kira Skyler20 分钟前
c++,从汇编角度看lambda
汇编·c++
love530love23 分钟前
使用 Conda 工具链创建 UV 本地虚拟环境全记录——基于《Python 多版本与开发环境治理架构设计》
开发语言·人工智能·windows·python·机器学习·conda
Algebraaaaa1 小时前
C++ 多线程中成员函数如何传参?拷贝、引用还是指针?
开发语言·c++
程序员编程指南1 小时前
Qt开发环境搭建全攻略(Windows+Linux+macOS)
linux·c语言·c++·windows·qt
安卓开发者1 小时前
Android KTX:让Kotlin开发更简洁高效的利器
android·开发语言·kotlin
WSSWWWSSW1 小时前
JSX(JavaScript XML)‌简介
xml·开发语言·javascript
Star在努力2 小时前
C语言:第11天笔记
c语言·开发语言·笔记
懂得节能嘛.2 小时前
【SpringAI实战】实现仿DeepSeek页面对话机器人
java·开发语言·spring boot
nightunderblackcat2 小时前
新手向:基于Python的桌面便签与待办事项管理工具
开发语言·python