首页 > 编程笔记

C++继承(三种方式)

继承是面向对象程序设计的重要特性之一,本节将针对继承的概念、继承的权限、继承过程中的类型兼容进行详细讲解。

继承的概念

所谓继承,就是从“先辈”处获得特性,它是客观世界事物之间的一种重要关系。

例如,脊椎动物和无脊椎动物都属于动物,在程序中便可以描述为:脊椎动物和无脊椎动物继承自动物;同时,哺乳动物和两栖动物继承自脊椎动物,而节肢动物和软体动物继承自无脊椎动物。这些动物之间会形成一个继承体系,如图 1 所示。

在 C++中,继承就是在原有类的基础上产生出新类,新类会继承原有类的所有属性和方法。

原有的类称为基类或父类,新类称为派生类或子类。派生类同样可以作为基类派生出新类。

在多层次继承结构中,派生类上一层的基类称为直接基类,隔层次的基类称为间接基类。例如在图 1 中,脊椎动物是哺乳动物的直接基类,动物是哺乳动物的间接基类。

图1 动物之间的继承体系
图1 动物之间的继承体系

在 C++ 中,声明一个类继承另一个类的格式如下所示:

class 派生类名称:继承方式  基类名称
{
派生类成员声明
};

从上述格式可以看出,派生类的定义方法与普通类基本相同,只是在派生类名称后添加冒号:、继承方式和基类名称。

在类的继承中,有以下几点需要注意:
  1. 基类的构造函数与析构函数不能被继承。
  2. 派生类对基类成员的继承没有选择权,不能选择继承或不继承某些成员。
  3. 派生类中可以增加新的成员,用于实现新功能,保证派生类的功能在基类基础上有所扩展。
  4. 一个基类可以派生出多个派生类;一个派生类也可以继承自多个基类。

通过继承,基类中的所有成员(构造函数和析构函数除外)被派生类继承,成为派生类成员。在此基础上,派生类还可以增加新的成员。


图2 基类与派生类之间的关系  

【示例1】为了让读者更好地理解和掌握继承的概念,下面通过案例演示派生类的定义与调用,C++代码如下:
#include<iostream>
using namespace std;
class Animal    //定义动物类Animal
{
public:
    void move();    //声明表示动物行为的成员函数move()
};
void Animal::move()   //类外实现成员函数move()
{
    cout<<"动物行为"<<endl;
}
class Cat:public Animal  //定义猫类Cat,公有继承动物类Animal
{
public:
    Cat(string name);   //声明有参构造函数
    void walk();    //声明表示动物行为的普通成员函数walk()
private:
    string _name;    //成员变量:表示名字
};
Cat::Cat(string name)   //类外实现构造函数
{
    _name=name;
}
void Cat::walk()   //类外实现普通成员函数walk()
{
    cout<<_name<<"会走"<<endl;
}
int main()
{
    Cat cat("猫");    //定义猫类对象cat
    cat.move();    //通过派生类对象调用基类成员函数
    cat.walk();    //通过派生类对象调用新增的成员函数
    return 0;
}
运行结果:

动物行为
猫会走

示例分析:
本例 Cat 类中并没有定义 move() 函数,但是 Cat 类继承了 Animal 类,它会继承 Animal 类的 move() 函数,因此 Cat 类对象能够调用 move() 函数。

图3 Cat类与Animal类的继承关系
图3 Cat类与Animal类的继承关系

需要注意的是,在图 3 中,空心箭头表示继承关系;+符号表示成员访问权限为public(公有继承),符号表示成员访问权限为 private(私有继承)。

如果成员访问权限为 protected(保护继承)或友元,则用#符号表示。

继承方式

在继承中,派生类会继承基类除构造函数、析构函数之外的全部成员。从基类继承的成员,其访问属性除了成员自身的访问属性,还受继承方式的影响。

类的继承方式主要有三种:public(公有继承)、protected(保护继承)和 private(私有继承)。

不同的继承方式会影响基类成员在派生类中的访问权限。下面分别介绍这三种继承方式。

1) public(公有继承)

采用公有继承方式时,基类的公有成员和保护成员在派生类中仍然是公有成员和保护成员,其访问属性不变,可以使用派生类的对象访问基类公有成员。

但是,基类的私有成员在派生类中变成了不可访问成员。

如果基类中有从上层基类继承过来的不可访问成员,则基类的不可访问成员在它的派生类中同样是不可访问的。

表1 公有继承对派生类继承成员的访问控制权限影响
基类成员访问属性 public protected private 不可访问
在派生类中的访问属性 public protected 不可访问 不可访问

注意:不可访问成员是指无论在类内还是在类外均不可访问的成员。它与私有成员的区别是,私有成员在类外不可访问,只能通过类的成员进行访问。不可访问成员完全是由类的派生形成的。对于顶层类,不存在不可访问成员,但是通过继承,基类的私有成员在派生类内就成为不可访问成员。

【示例2】下面通过案例演示类的公有继承,C++ 代码如下:
#include<iostream>
using namespace std;
class Student       //定义学生类Student
{
public:
void setGrade(string grade);    //设置年级的成员函数
string getGrade();      //获取年级的成员函数
void setName(string name);    //设置姓名的成员函数
string getName();      //获取姓名的成员函数
protected:
    string _grade;      //保护成员:表示年级
private:
    string _name;       //私有成员:表示姓名
};
void Student::setGrade(string grade)   //类外实现setGrade()函数
{
    _grade=grade;
}
string Student::getGrade()    //类外实现getGrade()函数
{
    return _grade;
}
void  Student::setName(string name)  //类外实现setName()函数
{
    _name=name;
}
string Student::getName()    //类外实现getName()函数
{
    return _name;
}
class Undergraduate:public Student  //大学生类公有继承学生类
{
public:
    Undergraduate(string major);    //声明构造函数
    void show();       //声明显示大学生信息的成员函数
private:
    string _major;      //私有成员:表示专业
};
//类外实现构造函数
Undergraduate::Undergraduate(string major)
{
    _major=major;
}
void Undergraduate::show()    //类外实现show()函数
{
    cout<<"姓名:"<<getName()<<endl;   //派生类调用基类成员函数
    cout<<"年级:"<<_grade<<endl;   //派生类访问继承的基类成员变量
    cout<<"专业:"<<_major<<endl;   //派生类访问新增成员
}
int main()
{
    //创建大学生类对象stu
    Undergraduate stu("计算机信息工程"); 
    stu.setGrade("大三");    //派生类对象调用基类成员函数设置年级
    stu.setName("www.weixueyuan.net");    //派生类对象调用基类成员函数设置姓名
    stu.show();      //派生类对象调用新增成员函数显示学生信息
    return 0;
}
运行结果:

姓名:www.weixueyuan.net
年级:大三
专业:计算机信息工程

示例分析:
注意:本例第 46~47 行代码,在 Undergraduate 类的 show() 函数内部直接访问了从基类继承过来的保护成员 _grade,因为 Undergraduate 类是公有继承 Student 类,_grade 在派生类 Undergraduate 中也是保护成员,所以可以通过成员函数 show() 访问。

但是,show() 函数无法直接访问从基类继承过来的 _name 成员,因为 _name 是基类的私有成员,在派生类中,_name 变成了派生类的不可访问成员。所以在 show() 函数中只能通过基类的公有成员函数 getName() 访问 _name 成员。

如果在 show() 函数中直接访问从基类继承过来的 _name 成员,程序会报错。

例如,若在 show() 函数中添加如下代码:

cout<<_name<<endl;

再次运行程序,编译器会报错,如下所示:

"Student::_name":无法访问 private 成员(在"Student"类中声明)
成员"Student::name"(已声明所在行数:13)不可访问


Undergraduate类与Student类之间的公有继承关系可以用图 4 表示。

图4-7 Undergraduate类公有继承Student类
图4 Undergraduate类公有继承Student类

2) protected(保护继承)

采用保护继承方式时,基类的公有成员和保护成员在派生类中全部变成保护成员,派生类的其他成员可以直接访问它们,在派生类外无法访问。

基类的私有成员和不可访问成员在派生类中的访问属性是不可访问。

表2 保护继承对派生类继承成员的访问控制权限影响
基类成员访问属性 public protected private 不可访问
在派生类中的访问属性 protected protected 不可访问 不可访问

若将【示例2】中第 31 行代码的继承方式改为 protected,再次运行程序,此时编译器会报错,如下所示:

"Student::setGrade"不可访问,因为"UnderGraduate"使用"protected"从"Student"继承
"Student::setName"不可访问,因为"UnderGraduate"使用"protected"从"Student"继承 
函数"Student::setGrade"(已声明所在行数:15)不可访问
函数"Student::setName"(已声明所在行数:23)不可访问


由于 Undergraduate 保护继承 Student 类,Student 类的 setGrade() 函数和 setName() 函数就变成了 Undergraduate 类的保护成员,保护成员在类外不能访问,因此编译器会报错。

Undergraduate 类与 Student 类之间的保护继承关系可以用图 5 表示。

图5  Undergraduate类保护继承Student类
图5 Undergraduate类保护继承Student类

3) private(私有继承)

采用私有继承方式时,基类的公有成员和保护成员在派生类中全部变成私有成员,派生类的其他成员可以直接访问它们,在派生类外无法访问。

基类的私有成员和不可访问成员在派生类中的访问属性是不可访问。

表3 私有继承对派生类继承成员的访问控制权限影响
基类成员访问属性 public protected private 不可访问
在派生类中的访问属性 private private 不可访问 不可访问

与保护继承相比,在直接派生类中,私有继承与保护继承的作用实际上是相同的。

在派生类外,不可访问任何基类成员;在派生类内,可以通过其他成员访问继承的基类公有成员和保护成员。

但是,如果再以派生类为基类派生新类,对于保护继承方式,派生类中的保护成员在新类中仍然是保护成员,类内的其他成员可以访问。

对于私有继承方式,派生类中的私有成员在新类中变成了不可访问成员,实际上就终止了基类功能在派生类中的延伸。

类型兼容

不同类型的数据在一定条件下可以进行转换,比如 int n='a',是将字符 'a' 赋值给整型变量 n,在赋值过程中发生了隐式类型转换,字符类型的数据转换为整型数据。这种现象称为类型转换,也称为类型兼容。

在 C++ 中,基类与派生类之间也存在类型兼容。通过公有继承,派生类获得了基类除构造函数、析构函数之外的所有成员。公有派生类实际上就继承了基类所有公有成员。

因此,在语法上,公有派生类对象总是可以充当基类对象,即可以将公有派生类对象赋值给基类对象,在用到基类对象的地方可以用其公有派生类对象代替。

C++ 中的类型兼容情况主要有以下几种:

【示例3】为了让读者更深入地理解 C++ 类型兼容规则,下面通过案例演示基类与派生类之间的类型兼容,程序代码如下:
#include<iostream>
using namespace std;
class Base       //定义基类Base
{
public:
    Base();       //Base类构造函数
    void show();       //Base类普通成员函数show()
protected:
    string _name;       //Base类保护成员变量_name
};
Base::Base()       //类外实现基类构造函数
{
    _name="base";
}
void Base::show()      //类外实现show()函数
{
    cout<<_name<<" Base show()"<<endl;
}
class Derive:public Base     //Derive类公有继承Base类
{
public:
    Derive();        //Derive类构造函数
    void display();      //Derive类普通成员函数display()
};
Derive::Derive()      //类外实现派生类构造函数
{
    _name="derive";      //_name成员从Base类继承而来
}
void Derive::display()     //类外实现display()函数
{
    cout<<_name<<" Derive show()"<<endl;
}
void func(Base* pbase)     //定义普通函数func(),参数为基类指针
{
    pbase->show();
}
int main()
{
    Derive derive;   //创建Derive类对象derive
    Base base=derive;   //使用对象derive为Base类对象base赋值
    Base &qbase=derive;  //使用对象derive为Base类对象的引用qbase赋值
    Base *pbase=&derive;  //使用对象derive的地址为Base类指针pbase赋值
    base.show();    //通过Base类对象调用show()函数
    qbase.show();    //通过Base类对象的引用调用show()函数
    pbase->show();   //通过Base类指针调用show()函数
    func(&derive);   //取对象derive的地址作为func()函数的参数
    return 0;
}
运行结果:

derive Base show()
derive Base show()
derive Base show()
derive Base show()

示例分析:
由运行结果可知,使用对象 derive 代替 Base 类对象,程序成功调用了 show() 函数。Derive 类与 Base 类的继承关系如图 6 所示。

图6 Derive类与Base类的继承关系
图6 Derive类与Base类的继承关系

需要注意的是,虽然可以使用公有派生类对象代替基类对象,但是通过基类对象只能访问基类的成员,无法访问派生类的新增成员。

如果在【示例3】中,通过基类对象 base、基类对象的引用 qbase、基类指针 pbase 访问 display() 函数,示例代码如下:

base.display();
qbase.display();
pbase->display();

添加上述代码之后,再次运行【示例3】的程序,编译器会报错。

“display”:不是“Base”的成员
“display”:不是“Base”的成员
“display”:不是“Base”的成员

优秀文章