云计算百科
云计算领域专业知识百科平台

《从零理解C++继承:代码复用背后的设计思想》

C++继承详解:基类、派生类与访问权限

一、什么是继承?

1. 继承的基本概念

继承是 C++ 面向对象编程中的重要特性,
它允许一个类在已有类的基础上获得其可继承的成员,并在此基础上扩展新的属性和行为,从而实现代码复用

例如,设计一个动物管理系统,其中包含狗、猫等不同动物
狗和猫虽然具有不同的特点,但它们都具有一些共同的属性和行为,例如名字、年龄、进食等
如果分别定义狗类和猫类,就会出现重复代码
因此,可以将它们的共同特征提取出来,定义一个动物类,再让狗类和猫类继承动物类

#include<iostream>
using namespace std;

//基类
class Animal
{
public:
string name;
void eat()
{
cout << name << "正在进食" << endl;
}
};

//派生类
class Dog:public Animal
{
public:
void bark()
{
cout << name << "正在叫" << endl;
}
};

class Cat:public Animal {
public:
void meow()
{
cout << name << "正在叫" << endl;
}
};

int main()
{
Dog dog;
dog.name = "小狗";
dog.eat();
dog.bark();

Cat cat;
cat.name = "小猫";
cat.eat();
cat.meow();

return 0;
}

运行结果:
在这里插入图片描述

可以看到,Dog 和 Cat 并没有重新定义 name 和 eat(),却可以直接使用它们

这是因为它们继承了 Animal 类

继承的核心思想:将多个类共有的属性和行为提取到一个基类中,派生类在此基础上扩展自己的功能,避免重复编写代码。

2. 继承和派生的区别

继承和派生实际上描述的是同一种类关系,只是观察角度不同。

概念含义
继承(Inheritance) 强调派生类获得基类的可继承成员
派生(Derivation) 强调以已有类为基础定义一个新的类
基类(Base Class) 被继承的类,也称父类
派生类(Derived Class) 通过继承定义出来的类,也称子类

例如:

class Animal {};

class Dog : public Animal {};

从继承角度看:Dog 继承了 Animal

从派生角度看:Dog 是由 Animal 派生出来的

一句话记忆:继承强调获得,派生强调产生


二、C++ 继承的基本语法

1. 继承语法

C++ 使用冒号 : 实现继承

class 基类
{
//基类成员
};

class 派生类:继承方式 基类
{
//派生类新增的成员
};

例如:

class Animal
{
public:
void eat()
{
cout << "动物正在进食" << endl;
}
};

class Dog : public Animal
{
public:
void bark()
{
cout << "狗正在叫" << endl;
}
};

其中:

  • Dog:派生类
  • Animal:基类
  • public:继承方式
  • 冒号:继承语法中的分隔符

2. 派生类能够使用什么?

派生类可以使用基类中允许访问的成员,也可以定义自己的成员

class Animal
{
public:
string name;

void eat()
{
cout << "正在进食" << endl;
}
};

class Dog : public Animal
{
public:
int age;
void bark()
{
cout << "正在叫" << endl;
}
};

int main()
{
Dog d;

d.name = "小狗"; //基类成员
d.age = 2; //派生类成员

d.eat(); //基类成员函数
d.bark(); //派生类成员函数

return 0;
}

这里需要理解:

继承不是简单地将基类代码复制粘贴到派生类中

从对象模型来看,派生类对象包含一个基类子对象,同时还可以包含派生类自身新增的成员

例如:

Dog d;

可以将它理解为:

Dog 对象
│
├── Animal 基类子对象
│ └── name
│
└── Dog 自身部分
└── age

注意:成员函数的代码通常由同一个类的对象共享,并不是每个对象都存储一份成员函数代码


三、继承中的访问权限

这是继承中非常重要的知识点,也是面试高频考点

C++ 类成员主要有三种访问权限:

  • public:公有成员,可以在允许的范围内被外部访问
  • protected:保护成员,可以被本类成员以及符合访问规则的派生类成员访问,普通外部代码不能直接访问
  • private:私有成员,只能由本类成员函数、友元等符合访问规则的代码访问,派生类不能直接访问

1. 基类中的三种访问权限

class Animal
{
public:
int a = 10;

protected:
int b = 20;

private:
int c = 30;
};

class Dog : public Animal
{
public:
void show()
{
cout << a << endl; //正确:public
cout << b << endl; //正确:protected

//cout << c << endl; //错误:private
}
};

int main()
{
Dog d;

cout << d.a << endl; //正确

//cout << d.b << endl; //错误
//cout << d.c << endl; //错误

d.show();

return 0;
}

为什么 a 和 b 可以访问,而 c 不可以?

因为:

  • public 成员允许派生类访问
  • protected 成员允许派生类访问
  • private 成员不允许派生类直接访问

这里需要特别注意:

基类的 private 成员仍然存在于派生类对象中的基类子对象中,但派生类不能直接访问它

例如:

class Animal
{
private:
int weight = 50;
};

class Dog : public Animal
{
public:
void show()
{
//cout << weight; //错误
}
};

虽然 Dog 对象包含 Animal 基类子对象,而 weight 仍然属于这个基类子对象,但 weight 的私有访问权限并没有因为继承而改变

因此需要区分两个问题:

  • 成员是否存在:属于对象组成的问题
  • 成员能否访问:属于访问权限的问题
    两者不能混为一谈

2. 如何间接访问基类的 private 成员?

可以通过基类提供的 public 或 protected 成员函数间接访问

#include<iostream>
using namespace std;

class Animal
{
private:
int weight = 50;

public:
int getWeight()
{
return weight;
}

void setWeight(int w)
{
weight = w;
}
};

class Dog : public Animal
{
public:
void show()
{
cout << getWeight() << endl;
}
};

int main()
{
Dog d;

d.show();//50

d.setWeight(60);
cout << d.getWeight() << endl; //60

return 0;
}

执行过程:

  • Dog 对象包含 Animal 基类子对象
  • 基类子对象中存在私有成员 weight
  • Dog 不能直接访问 weight
  • Dog 可以调用继承来的 getWeight()
  • getWeight() 是 Animal 自己的成员函数,因此有权限访问 Animal 的私有成员
  • 最终通过函数返回值间接获取 weight
  • 这也是面向对象封装思想的体现:将数据隐藏起来,通过接口控制数据的访问和修改


    四、三种继承方式

    C++ 提供三种继承方式:

    • public:公有继承
    • protected:保护继承
    • private:私有继承

    它们决定基类中的 public 和 protected 成员在派生类中的访问级别

    1. 三种继承方式的区别

    基类成员public 继承protected 继承private 继承
    public public protected private
    protected protected protected private
    private 不可直接访问 不可直接访问 不可直接访问

    注意:基类 private 成员仍然存在于基类子对象中,只是派生类不能直接访问

    2. 代码理解

    class Animal
    {
    public:
    void eat() {}

    protected:
    void sleep() {}

    private:
    void breathe() {}
    };

    class Dog : public Animal {};
    class Cat : protected Animal {};
    class Tiger : private Animal {};

    对于 Dog:

    • eat() 仍然是 public
    • sleep() 仍然是 protected
    • breathe() 不能直接访问

    对于 Cat:

    • eat() 在 Cat 中变成 protected
    • sleep() 仍然是 protected

    对于 Tiger:

    • eat() 在 Tiger 中变成 private
    • sleep() 在 Tiger 中变成 private

    注意:三种继承方式都不能让派生类直接访问基类的 private 成员

    3. 默认继承方式

    class Animal {};

    class Dog : Animal {}; //默认 private 继承

    struct Cat : Animal {}; //默认 public 继承

    需要记住:

    • class 声明的派生类,默认采用 private 继承
    • struct 声明的派生类,默认采用 public 继承

    实际开发中建议显式写出继承方式,避免歧义

    一般情况下,使用 public 继承来表达合理的 is-a(是一种)关系

    例如:Dog 是一种 Animal

    class Dog : public Animal {};


    五、派生类的构造函数和析构函数

    理解了继承之后,还需要解决一个问题:

    创建派生类对象时,基类部分是如何初始化的?

    答案是:

    构造时先构造基类,再构造派生类;析构时先析构派生类,再析构基类

    1. 构造函数和析构函数的执行顺序

    #include<iostream>
    using namespace std;

    class Animal
    {
    public:
    Animal()
    {
    cout << "Animal 构造函数" << endl;
    }

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

    class Dog : public Animal
    {
    public:
    Dog()
    {
    cout << "Dog 构造函数" << endl;
    }

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

    int main()
    {
    Dog d;
    return 0;
    }

    运行结果:

    Animal 构造函数
    Dog 构造函数
    Dog 析构函数
    Animal 析构函数

    为什么是这个顺序?

    因为派生类对象包含基类子对象

    创建 Dog 对象时,需要先初始化 Animal 基类子对象,再初始化 Dog 自身部分

    销毁时则相反,先销毁 Dog 自身部分,再销毁 Animal 基类子对象

    可以记忆为:

    构造:基类 → 派生类
    析构:派生类 → 基类

    2. 基类有带参数的构造函数怎么办?

    例如:

    class Animal
    {
    public:
    Animal(string name)
    {
    cout << "动物名字:" << name << endl;
    }
    };

    class Dog : public Animal
    {
    public:
    Dog(string name) : Animal(name)
    {
    cout << "Dog 构造函数" << endl;
    }
    };

    重点在于:

    Dog(string name) : Animal(name)

    这里的 Animal(name) 是成员初始化列表中对基类构造函数的调用,用于初始化派生类对象中的基类子对象

    如果基类没有可用的默认构造函数,而派生类又没有显式调用合适的基类构造函数,就会出现编译错误


    六、面试考点

    1. 什么是继承?有什么作用?

    继承是 C++ 面向对象编程的重要机制,允许派生类以基类为基础获得其可继承的成员,并在此基础上扩展新的属性和行为

    主要作用是代码复用、扩展类的功能以及建立类之间的层次关系

    2. 继承和派生有什么区别?

    继承和派生描述的是同一种类关系,只是角度不同

    继承强调派生类获得基类的可继承成员,派生强调以已有类为基础定义新的类

    3. 基类的 private 成员能否被派生类访问?

    不能直接访问

    基类的 private 成员仍然存在于派生类对象中的基类子对象中,但由于访问权限限制,派生类不能直接访问

    可以通过基类提供的 public 或 protected 成员函数间接访问

    4. public、protected、private 三种继承有什么区别?

    它们主要决定基类的 public 和 protected 成员在派生类中的访问级别

    public 继承:public 仍然是 public,protected 仍然是 protected

    protected 继承:public 和 protected 都变为 protected

    private 继承:public 和 protected 都变为 private

    三种继承方式都不能让派生类直接访问基类的 private 成员

    5. 派生类对象创建和销毁的顺序是什么?

    构造时先构造基类,再构造派生类

    析构时先析构派生类,再析构基类

    原因是派生类对象包含基类子对象,需要先初始化基类部分

    6. 派生类可以继承基类的构造函数和析构函数吗?

    构造函数和析构函数不能像普通成员函数一样被继承

    派生类对象构造时会调用基类构造函数,销毁时会调用基类析构函数

    C++11 还允许使用 using Base::Base; 声明继承构造函数,但这并不意味着派生类继承了基类的析构函数

    七、总结

    C++ 继承是面向对象编程中的重要机制,其核心思想是通过已有类构建新的类,实现代码复用和功能扩展

    上面笔记内容简要概况为以下内容:

  • 基本概念:区分基类、派生类、继承和派生
  • 基本语法:掌握 class Derived : public Base 的写法
  • 对象组成:理解派生类对象包含基类子对象和派生类自身部分
  • 访问权限:掌握 public、protected、private 三种访问权限
  • 继承方式:理解三种继承方式对基类成员访问级别的影响
  • 构造与析构:掌握基类和派生类构造、析构函数的执行顺序
  • 核心记忆:继承解决的是类之间的代码复用和层次关系问题,访问权限决定成员能否被访问

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 《从零理解C++继承:代码复用背后的设计思想》
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!