目录
1.继承的引入
现实中的继承很好理解,例如回家继承家产。而C++中的继承则是继承代码。如下举个例子,这段代码较长,但非常好理解。(本文代码均在win10系统上的vs2019上验证)
代码一:这段代码定义了两个类,一个狗的类,一个猫的类。但可以发现里面的代码高度相似,狗和猫的很多行为都很像,只有个别行为不一样,比如狗喜欢吃骨头,猫喜欢吃鱼。这样就感觉代码复用性很低,有大量代码冗余。而继承就可以解决这种问题。
代码一:
//代码一
#include "iostream"
using namespace std;
class Dog {
public:
Dog(string name, int age)
{
_name = name;
_age = age;
Prin();
}
void Prin() {
cout << _name << "的年龄是" << _age << "岁" << endl;
}
void Eat() {
cout << _name << "吃饭" << endl;
}
void Sleep() {
cout << _name << "睡觉" << endl;
}
//该种动物特有的行为
void Act() {
cout << _name << "爱吃骨头" << endl;
}
private:
string _name;//姓名
int _age;//年龄
};
class Cat {
public:
Cat(string name, int age)
{
_name = name;
_age = age;
Prin();
}
void Prin() {
cout << _name << "的年龄是" << _age << "岁" << endl;
}
void Eat() {
cout << _name << "吃饭" << endl;
}
void Sleep() {
cout << _name << "睡觉" << endl;
}
//该种动物特有的行为
void Act() {
cout << _name << "爱吃鱼" << endl;
}
private:
string _name;//姓名
int _age;//年龄
};
int main() {
Dog d("大黄", 6);
Cat c("小花", 5);
}
2.继承的概念和定义
(1)继承的概念
继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。
简单来说,继承就是把猫类和狗类共同包含的成员提取出来组成一个基础的类,而猫类和狗类可以直接从基础的类里面把共同成员继承下来,自己只需要实现自己独有的功能即可。
(2)继承的定义
在这里统一下名称,派生类称为子类,基础类称为父类,子类从父类中衍生出来。
继承语法:class 子类名 :访问限定符 父类名 {}
为了更好理解继承的概念,我们把代码一的代码按照继承的概念进行修改。猫和狗都是动物,所以我们把基础类命名为动物类,共同具有姓名、年龄、吃饭、睡觉的方法,那么就把这些成员都放在动物类中。
代码二:
//代码二
#include "iostream"
using namespace std;
class Animal {
public:
void Prin() {
cout << _name << "的年龄是" << _age << "岁" << endl;
}
void Eat() {
cout << _name << "吃饭" << endl;
}
void Sleep() {
cout << _name << "睡觉" << endl;
}
//该种动物特有的行为
void Act() {
cout << _name << "爱吃骨头" << endl;
}
public:
string _name;//姓名
int _age;//年龄
};
class Dog : public Animal{
public:
//设置动物信息
void Set(string name, int age) {
_name = name;
_age = age;
cout << _name << "是一只狗" << endl;
}
//该种动物特有的行为
void Act() {
cout << _name <<"爱吃骨头" << endl;
}
};
class Cat : public Animal {
public:
//设置动物信息
void Set(string name,int age) {
_name = name;
_age = age;
cout << _name << "是一只猫" << endl;
}
//该种动物特有的行为
void Act() {
cout << _name <<"爱吃鱼" << endl;
}
};
int main() {
Dog d;
d.Set("大黄", 5);
d.Sleep();
Cat c;
c.Set("小花", 4);
c.Sleep();
}
3.继承的访问限定符
(1)类中成员访问限定符回顾
这是在类与对象初阶的时候给出的访问限定符作用,当时只给出了很简单的性质:
public成员可以在类内类外直接访问,protected和private成员只能在类内直接访问,不能在类外直接访问。
接下来将扩展它在继承中的性质。
(2)继承中的访问限定符及其验证
这里给出继承中的访问限定符性质:比如第二列的含义是:当子类public继承基类后,基类中的public成员在子类中也是public属性,基类中的protected成员在子类中也是protected属性,基类中的private成员在子类中不可见。
下面将会证明这三种继承的性质。首先要证明子类是否会继承父类中的成员。
基类成员及其属性 | public继承后基类成员在子类中的属性 | protected继承后基类成员在子类中的属性 | private继承后基类成员在子类中的属性 |
基类的public成员 | 子类的public成员 | 子类的protected成员 | 子类的private成员 |
基类的protected成员 | 子类的protected成员 | 子类的protected成员 | 子类的private成员 |
基类的private成员 | 派生类中不可见 | 派生类中不可见 | 派生类中不可见 |
[1]验证子类是否继承了父类成员
通过计算子类和父类的大小来判断子类是否继承了父类的成员变量。
代码三:计算得到子类与父类大小相等,证明子类完全继承父类成员变量。
//代码三
#include "iostream"
using namespace std;
class Base {
public:
void Set(int a, int b, int c) {
_a = a;
_b = b;
_c = c;
}
public:
int _a;
protected:
int _b;
private:
int _c;
};
class Son : public Base {};
int main() {
cout << sizeof(Base) << endl;//12
cout << sizeof(Son) << endl;//12
}
[2]public继承
代码四:代码四中有两次继承,第一次是Base类派生Son类,Son类派生孙子类。
验证原理如下:Base类的public和protected成员可以在Base的子类Son类中访问,说明这两种成员在Son类中的属性可能是public 或者 protected 或者 private。但是Base类的private成员不可以在Son中访问。
那么对Son类再继承一次,发现Son类的public成员和protected成员在孙子类中依然可以访问,说明它们在Son类中的属性绝对是public 或者 protected,而不是private属性。
因为如果在Son类中是private,孙子类中就不可以访问了。
然后对Son类进行实例化,发现Son中的_a可以在类外访问,说明在Son类中是public,_b不可以在类外访问,说明是在Son类中是protected。_c在类外依然不可以访问。
说明在public继承中,父类的public成员在子类中还是public成员,protected成员在子类中还是protected成员,但private成员在子类中不可见!
//代码四
#include "iostream"
using namespace std;
class Base {
public:
void Set(int a,int b,int c) {
_a = a;
_b = b;
_c = c;
}
public:
int _a;
protected:
int _b;
private:
int _c;
};
class Son : public Base {
void Set(int a = 10,int b = 11,int c = 12) {
_a = a;//_a可以在子类中访问 说明在子类中是public 或者 protected 或者 private
_b = b;//_b可以在子类中访问 说明在子类中是public 或者 protected 或者 private
//_c = c; _c不可以在子类中访问
}
};
class Grandson : public Son {
void Set(int a, int b, int c) {
_a = 10;//_a在孙子类中可以访问,说明在Son类中是public 或者 protected成员
_b = b;//_b可以在孙子类中访问,说明在Son类中是public 或者 protected成员
//_a和_b绝对不是 private成员,因为基类的private成员在Son类中不可访问
//那么子类的private成员自然也在孙子类中不可访问
//_c = 19;_c在孙子类中依然不可访问
}
};
int main() {
Son s;
s._a = 10;//_a可以在类外访问 说明在Son类中是public成员
//s._b = 1; _b不可以在类外访问 说明在Son类中是protected成员
//s._c = 2; _c不可以在子类和孙子类外或类中访问,说明在Son类中是不可见成员
}
[3]protected继承
代码五:
验证原理如下,Base类的public和protected成员可以在Base的子类Son类中访问,说明这两种成员在Son类中的属性可能是public 或者 protected 或者 private。
对Son类再继承一次,Son类的public成员和protected成员在孙子类中依然可以访问,说明在Son类中的属性绝对是public 或者 protected。
对Son类进行实例化,发现Son中的_a和_b不可以在类外访问,说明在Son类中是protected。_c在类外依然不可以访问。
说明在protected继承中,父类的protected成员在子类中是protected成员,protected成员在子类中还是protected成员,但private成员在子类中不可见!
//代码五
#include "iostream"
using namespace std;
class Base {
public:
void Set(int a,int b,int c) {
_a = a;
_b = b;
_c = c;
}
public:
int _a;
protected:
int _b;
private:
int _c;
};
class Son : protected Base {
void Set(int a = 10,int b = 11,int c = 12) {
_a = a;//_a可以在子类中访问 说明在子类中是public 或者 protected 或者 private
_b = b;//_b可以在子类中访问 说明在子类中是public 或者 protected 或者 private
//_c = c; _c不可以在子类中访问
}
};
class Grandson : public Son {
void Set(int a, int b, int c) {
_a = 10;//_a在孙子类中可以访问,说明在Son类中是public 或者 protected成员
_b = b;//_b可以在孙子类中访问,说明在Son类中是public 或者 protected成员
//_a和_b绝对不是 private成员,因为基类的private成员在子类中不可访问
//_c = 19;_c在孙子类中依然不可访问
}
};
int main() {
Son s;
//s._a = 10;_a不可以在类外访问 说明在Son类中是protected成员
//s._b = 1; _b不可以在类外访问 说明在Son类中是protected成员
//s._c = 2; _c不可以在类外或类中访问,说明在Son类中是不可见成员
}
[4]private继承
代码六:
验证原理如下:Base类的public和protected成员可以在Base的子类son类中访问,说明这两种成员在Son类中的属性可能是public 或者 protected 或者 private。
对Son类再继承一次,发现Son类的public成员和protected成员在孙子类中不可以访问,说明在Son类中的属性绝对是private。
然后对Son类进行实例化,发现_c在类外依然不可以访问。
说明在private继承中,父类的public和protected成员在子类中都是private,而父类的private成员依然在子类中是不可见。
//代码六
#include "iostream"
using namespace std;
class Base {
public:
void Set(int a, int b, int c) {
_a = a;
_b = b;
_c = c;
}
public:
int _a;
protected:
int _b;
private:
int _c;
};
class Son : private Base {
void Set(int a = 10, int b = 11, int c = 12) {
_a = a;//_a可以在子类中访问 说明在子类中是public 或者 protected 或者 private
_b = b;//_b可以在子类中访问 说明在子类中是public 或者 protected 或者 private
//_c = c; _c不可以在子类中访问
}
};
class Grandson : public Son {
void Set(int a, int b, int c) {
//_a = 10;//_a在孙子类中不可以访问,说明在Son类中是private
//_b = b;//_b不可以在孙子类中访问,说明在Son类中是private
//_c = 19;_c在孙子类中依然不可访问
}
};
int main() {
Son s;
//s._a = 10;_a 在Son类中是private
//s._b = 1; _b 在Son类中是private
//s._c = 2; _c不可以在类外或类中访问,说明在Son类中是不可见成员
}
(3)默认继承访问权限
[1]struct关键字定义类
struct关键字定义的类,其默认的继承限定符是public。
代码七:
//代码七
#include "iostream"
using namespace std;
class Farther {
public:
int a;
protected:
int b;
private:
int c;
};
struct Son : Farther {
void Set(int aa, int bb, int cc) {
a = aa;
b = bb;
//c = cc; 无法访问
}
};
struct GrandSon : Son {
void Set(int aa, int bb, int cc) {
a = aa;//无法访问
b = bb;//无法访问
//c = cc; 无法访问
}
};
int main() {
Son s;
s.a = 10;
//s.b = 10; 不可访问
//s.c = 10; 无法访问
}
[2]class关键字定义类
class关键字定义的类,其默认的继承限定符是private。
代码八:
//代码八
#include "iostream"
using namespace std;
class Farther {
public:
int a;
protected:
int b;
private:
int c;
};
class Son : Farther {
void Set(int aa, int bb, int cc) {
a = aa;
b = bb;
//c = cc; 无法访问
}
};
class GrandSon : public Son {
void Set(int aa, int bb, int cc) {
//a = aa;无法访问
//b = bb;无法访问
//c = cc; 无法访问
}
};
int main() {
Son s;
//s.a = 10; 无法访问
//s.b = 10; 无法访问
//s.c = 10; 无法访问
}
(4)访问限定符总结
1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。
3. 基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。
4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,最好显示的写出继承方式。
5. 在实际运用中一般使用都是public继承。
4.基类和派生类对象赋值转换
赋值转换前提:必须是public继承。
(1)赋值转换规则
1.派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
2.基类对象不能赋值给派生类对象
3.基类的指针可以通过强制类型转换赋值给派生类的指针。但是必须是基类的指针是指向派生类对象时才是安全的。
(2)规则理解
[1]规则1和2理解
Animal类派生出Dog类,如下是animal对象和dog对象的对象模型。将dog赋值给animal对象,只需要将继承来的元素赋值给animal对应的变量即可。
但如果用animal给dog对象赋值,dog对象中有三个变量,animal对象只有两个变量,那么_color变量拿什么赋值?
[2]规则3理解
类的设置和上文相同。当用Animal类型的指针指向dog对象时,因为指针类型是Animal,所以指针指向的空间只包括从Animal中继承来的变量。
但如果试图用Dog类的指针指向animal对象,因为指针类型是Dog,那么指针可能就会去访问_color变量,可是animal对象跟本没有这个变量,怎么访问呢?
5.继承中的作用域
(1)作用域规则
1. 在继承体系中基类和派生类都有独立的作用域。子类和基类隶属于不同的作用域,所以子类不可以访问基类中的私有成员。
2. 同名隐藏/重定义:子类和父类中有同名成员变量或成员函数,子类成员将屏蔽对父类同名成员的直接访问。(在子类成员函数中,可以使用 基类::基类成员 显示访问父类的同名成员)
3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
4. 注意在实际中在继承体系里面最好不要定义同名的成员
(2)验证作用域规则
[1]规则1
代码九:子类中无法访问父类私有成员
//代码九
#include "iostream"
using namespace std;
class Father {
private:
int a;
};
class Son : public Father {
void Set() {
//a = 10; 报错
}
};
int main() {
}
[2]规则2和3
代码十:
//代码十
//代码八
#include "iostream"
using namespace std;
class Father {
public:
int a;
int b;
void Prin() {
cout << "基类" << endl;
}
};
class Son : public Father {
public:
void Set() {
Father::a = 10;
}
int a;
void Prin() {
cout << "子类" << endl;
}
};
int main() {
Son s;
s.a = 10;
s.Father::a = 12;
s.Prin();// 子类
s.Father::Prin();// 基类
}