一、子类的概念
很多类之间有一种层次关系,有父亲类(简称父类/基类/超类),有孩子类(简称子类/派生类)。如卡车和轿车,它们都是车,它们有一些共性,比如说都烧油、都有轮子、都在机动车道上行驶。
细想一下,可以定义一个车的类,把这个车的类当成父类,从这个父类派生出卡车、轿车等,那么,卡车类、轿车类就属于子类。
有父类,有子类,这种层次关系就叫“继承”(也叫继承性),也就是说子类能够从父类那里继承很多东西,“继承”这种概念是面向对象程序设计的核心思想之一。
这种继承需要先定义一个父类,父类中主要是定义一些公共的成员变量和成员函数,然后通过继承这个父类来构建新的类,这个新的类型称之为子类,通过继承,父类中的很多成员变量和成员函数就自动继承到了子类中,那么,在书写子类的时候就减少了很多编码工作量--只写子类中独有的一些内容即可。
所以,通常子类会比父类有更多的成员变量以及成员函数,创建子类对象时占用的内存空间更多。
Human.h头文件内容如下:
#ifndef __HUMAIN_H__ #define __HUMAIN_H__ #include#include class Human { public: Human(); Human(int); public: int age; char name[50]; }; #endif // !__MAIN_H__
Human.cpp文件中父类的定义如下:
#include"Human.h"
using namespace std;
Human::Human()
{
cout << "执行了Human::Human()构造函数" << endl;
}
Human::Human(int age)
{
cout << "执行了Human::Human(int age)构造函数" << endl;
}
下面定义一个子类,父类名称为Men,在.h头文件中声明子类,在.cpp文件中定义子类。
在Men.h头文件声明子类如下:
#pragma once #ifndef __MEN_H__ #define __MEN_H__ #include#include #include"Human.h" class Men :public Human { public: Men(); }; #endif // !__MEN_H__
在Men.cpp文件定义子类如下:
#include "Men.h"
using namespace std;
Men::Men()
{
cout << "执行了Men::Men()构造函数" << endl;
}
int main()
{
Men men;
return 0;
}
运行结果如下所示:
我们可以看到:
程序是先执行了父类构造函数体,再执行的子类构造函数体。
上面的子类Men,派生自父类Human。定义子类的一般形式:
class 子类名:继承方式 父类名
继承方式(访问等级/访问权限):public、protected、private之一。
父类名:已经定义了的一个类名。一个子类可以继承自多个父类,但比较少见。一般的继承关系都是子类只继承一个父类。
子类能够继承父类全部的成员变量和成员函数,但是不能继承父类的构造方法。
如果子类想调用父类的带参数的构造方法,需要用初始化父类成员对象的方式:
方式一:
class Men :public Human
{
public:
//Men();
Men():Human(100) {}
Men(int age);
};
方式二:直接在main函数中创建子类对象后,子类对象这样调用父类的带参构造函数:
Men men; men:Human(80); 或者 Men men(20); men:Human(80);
方式一的初始化,还常用来对类中的常量(const)成员进行初始化,如下面的代码所示:
class point
{
public:
point() :x(0), y(0)
private:
const int x;
const int y;
};
二、访问等级(public、protected与private)
public表示“公共”的意思,private表示“私有”的意思。protected是“保护”的意思。访问等级介于public和private之间,一般有父子关系的类谈protected才有意义。
现对这三种访问权限修饰符进行总结:
public:可以被任意实体所访问。
protected:只允许本类或者子类的成员函数来访问。
private:只允许本类的成员函数访问。
现在来看一下类继承时的继承方式(专用于子类继承父类),也依然是这三种:
public继承;
protected继承;
private继承。
表1 访问权限及继承方式总结
| 三种访问权限 | 三种继承方式 | |
| public:可以被任意实体访问 | public继承 | |
| protected:只允许本类或者子类的成员函数来访问 | protected继承 | |
| private:只允许本类的成员函数访问 | private继承 | |
| 父类中的访问权限 | 子类继承父类的继承方式 | 子类得到的访问权限 |
| public | public | public |
| protected | public | protected |
| private | public | 子类无权访问 |
| public | protected | protected |
| protected | protected | protected |
| private | protected | 子类无权访问 |
| public | private | private |
| protected | private | private |
| private | private | 子类无权访问 |
总结:
(1)子类public继承父类,则父类所有成员在子类中的访问权限都不发生改变;
(2)protected继承将父类中public成员变为子类的protected成员;
(3)private继承使得父类所有成员在子类中的访问权限变为private;
(4)父类中的private成员不受继承方式的影响,子类永远无权访问;
(5)对于父类来讲,尤其是父类的成员函数,如果你不想让外接访问,就设置为private;
如果你想让自己的子类能够访问,就设置为protected,如果你想公开,就设置为public。
对照上面的表,修改Human.h文件以定义一些protected和private修饰的成员变量和成员函数,修改后的Human.h文件如下:
#include#include class Human { public: Human(); Human(int); public: int age; char name[50]; void funcpub() {}; protected: int h_pro; void funcpro() {}; private: int h_priv; void funcpriv() {}; }; #endif // !__MAIN_H__
可以将Men类对Human类的继承方式分别由原来的public修改为protected、private,并在main主函数中书写一些代码,测试一下对类中各种成员变量、成员函数的访问权限。
先将Men类继承Human类的继承方式是protected,如下:
class Men :protected Human
{
public:
Men();
Men(int age);
};
然后在main主函数中书写如下测试代码,是否会有问题:
int main()
{
Men men;
men.age = 18; //有问题
men.h_priv = 10;//有问题
return 0;
}
经过测试,上面的代码对age成员变量的访问和针对h_priv成员变量的访问都有问题:
(1)age 在Human中被定义为public类型,而Men类是用protected继承方式来继承Human类的。根据上面的表格看到,父类的访问权限为public,子类的继承方式为protected,从而得到子类访问权限是protected,再从上面表格看到,protected访问权限只允许在本类或者子类中的成员函数中访问,不允许在main主函数中直接访问,所以men.age = 18;有问题。
(2)h_priv在Human中被定义为private类型,而Men类时用protected继承方式来继承Human类的。查上面的表可以看到,父类的访问权限为private,子类的继承方式为protected,从而得到子类无权访问,这就意味着h_priv就好像完全不存在与子类Men中(无法用Men类对象访问h_priv)。所以,在main主函数中出现以下代码是不可以的。
men.h_priv = 10;//有问题
子类可以继承父类所有的成员变量和成员方法,但不继承父类的构造方法。
三、函数遮蔽
正常情况下,父类中的成员函数只有是用public或者protected修饰的,子类只要不采用private继承方式来继承父类,那么子类中都可以调用。
但是,在C++的类继承中,子类会遮蔽父类中的同名函数,不论此函数的返回值、参数。
也就是说,父类和子类中的函数只要名字相同,子类中的函数就会遮蔽掉父类中的同名函数。
在父类Human.h的Human类定义中,增加两个public修饰的成员函数声明:
public: void sameName(); void sameName(int);
在Human.cpp中,增加这两个成员函数的实现代码:
void Human::sameName()
{
cout << "调用了父类的sameName()函数" << endl;
}
void Human::sameName(int name)
{
cout << "调用了父类的sameName(int name)函数" << endl;
}
在子类Men.h的Men类定义中,增加一个public修饰的成员函数的声明:
public: void sameName(int);
在Man.cpp中,增加这个成员函数的实现代码:
void Men::sameName(int)
{
cout << "调用了子类的sameName(int)函数" << endl;
}
在main主函数中,增加如下代码:
Men men; men.sameName();//报错,只能调用子类不带参数的函数 men.sameName(2);//不报错,但是调用的是子类的sameName函数,而不是父类的sameName函数
通过上面的例子可以看到,
只有子类中有一个和父类同名的成员函数,通过子类无法调用(访问)父类中的同名函数。但是如果确实想调用父类中的同名函数,可以调用吗。这个是可以的,可以借助子类的成员函数sameName来调用父类的成员函数sameName,只要在子类的成员函数sameName中使用“父类::成员函数名(...)”的方式就可以调用父类的sameName函数,代码如下所示:
void Men::sameName(int)
{
Human::sameName();
Human::sameName(15);
cout << "调用了子类的sameName(int)函数" << endl;
}
当然,如果Men是以public继承方式来继承Human父类,那么也可以在main主函数中使用“子类对象名.父类名::成员函数名(...)”的方式来调用父类的同名成员函数,代码如下:
int main()
{
Men men;
//men.sameName();
men.sameName(2);
men.Human::sameName();
men.Human::sameName(15);
return 0;
}
using namespace 一般用于声明(使用)命名空间。在C++中,还可以通过using这个关键字让父类同名函数在子类中以重载方式使用。
现在,在Men.h头文件中的类内书写如下代码行:
using Human::sameName;
现在,在main主函数中写如下代码:
Men men; men.sameName();//可以调用父类不带参数的sameName函数 men.sameName(2);//执行子类的sameName函数,虽然父类也带有一个参数的该函数,但子类函数还是覆盖了父类同名函数
总结说明:
(1)using 声明只能指定函数名,不能带形参列表,并且父类中的这些函数必须都是public或者protected(只能在子类中的成员函数里面调用),不能有private的,否则编译会出错。
(2)using声明这种方法引入的主要摸底是实现可以在子类实例中调用到父类的重载版本的函数。
如果子类中的成员函数和父类中的同名成员函数参数个数、参数类型完全相同,那么是无法调用到父类中的该函数。像上面的例子,men.sameName(2);无法调用到父类的一个带参的成员函数sameName。这时在main主函数中这样调用父类的一个参数的成员函数sameName,就可以正常调用:
men.Human::sameName(2);
如果是在Men子类的成员函数中调用父类的带一个参数的成员函数sameName,就应该这样调用:
Human::sameName(18);
2022.07.09结。



