😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
⏰发布时间⏰:
本文未经允许,不得转发!!!
目录
- 🎄一、概述
- 🎄二、继承的定义
- 🎄三、派生类(子类)继承了什么
- ✨3.1 派生类从基类继承了哪些东西
- ✨3.2 继承的东西怎么使用
- 🎄四、派生类对象的创建、销毁
- 🎄五、基类和派生类的类型关系
- 🎄六、继承中的类作用域
🎄一、概述
继承的概念:
- 继承(inheritance)机制是面向对象程序设计中使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。
- 继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用
通过继承可以完成的一些工作:
- 1、可以在己有类的基础上添加功能。 例如, 对于数组类, 可以添加数学运算。
- 2、可以给类添加数据。 例如, 对于字符串类, 可以派生出一个类, 并添加指定字符串显示颜色的数据成员。
- 3、可以修改类方法的行为。
按照继承权限区分,有公开继承、保护继承、私有继承三种方式。
按照继承的直接基类个数区分,有单继承、多重继承两种方式。
🎄二、继承的定义
通过继承 ( inheritance ) 联系在一起的类构成一种层次关系。通常在层次关系的根部有一个基类(base class),其他类则直接或间接地从基类继承而来,这些继承得到的类称为派生类(derived class)。基类负责定义在层次关系中所有类共同拥有的成员, 而每个派生类定义各自特有的成员。
派生类必须通过使用 类派生列表(class derivation list) 明确指出它是从哪个(哪些)基类继承而来的。
类派生列表的形式是:首先是一个冒号,后面紧跟以逗号分隔的基类列表,其中每个基类前面可以有访问说明符。
访问说明符有三种:pulbic(公有继承)、protect(保护继承)、private(私有继承)。
类派生列表也可以有多个基类,如:class CDog : public CAnimal, public CFriend
下面用代码演示一下继承的过程:
class CAnimal{
public:
CAnimal(){
sprintf(m_name, "Ainmal");
}
void run(){
cout << "my name is "<< m_name <<", Animal run" << endl;
}
private:
char m_name[64];
};
class CDog : public CAnimal{
};
is-a
:我们这里使用的是 公有继承,它需要遵循is-a
的关系,就是派生类 is a 基类
,可以表示成派生类 是 基类
,例如“狗 是 动物”。按照这个关系,我们不能在 动物 和 苹果 之间使用公有继承。派生类对象也是一个基类对象,可以对基类对象执行的任何操作,也可以对派生类对象执行。
🎄三、派生类(子类)继承了什么
继承完之后,那么派生类继承了哪些东西?这些东西怎样使用呢?这个小节,我们来学习这两个问题的答案。
✨3.1 派生类从基类继承了哪些东西
派生类继承了基类的 数据成员 和 大部分的函数成员,而基类的 构造函数、析构函数、赋值运算符函数
不会被派生类所继承。
下面修改一下上面的代码,演示派生类继承了父类的数据成员。
// 22_Inheritance.cpp
// g++ 22_Inheritance.cpp
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
class CAnimal{
enum{NAME_LEN=64};
public:
CAnimal(){
sprintf(m_name, "Ainmal");
}
void run(){
cout << "my name is "<< m_name <<", Animal run" << endl;
}
private:
char m_name[NAME_LEN];
};
class CDog : public CAnimal{
private:
int m_hairColor; // 毛发颜色
};
int main ()
{
CAnimal animal;
cout << "sizeof(animal)=" << sizeof(animal) << endl;
CDog dog;
cout << "sizeof(dog)=" << sizeof(dog) << endl;
return 0;
}
运行结果如下:
从结果来看,派生类CDog 比基类CAnimal 多了 4 个字节。这4个字节刚好是一个int型,也就是派生类多的 m_hairColor 成员。而成员函数也是被继承的,但成员函数并不会放在类对象的内存中,而是放在程序的代码段。打印类对象大小只会打印该对象的数据成员总和。
✨3.2 继承的东西怎么使用
派生类继承的成员并不是可以随意地访问,跟继承时使用的 访问说明符 以及该成员在 基类中的访问权限 有关。
按照访问说明符的不同,可以分为public继承、protected继承、private继承
,它们从基类继承到的成员访问权限如下表:
类成员\继承方式 | public继承 | protected继承 | private继承 |
---|---|---|---|
基类的public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
基类的protected成员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 |
基类的private成员 | 在派生类中不可见 | 在派生类中不可见 | 在派生类中不可见 |
从表格可以得出下面几个结论:
1、基类的私有成员,在派生类无法访问,只能通过基类的公有成员函数去访问。
2、基类的公有成员、保护成员,可以在派生类成员函数直接访问。
3、公有继承中,基类的公有成员、保护成员在派生类中也分别是公有、保护。
4、保护继承中,基类的公有成员、保护成员在派生类中都是保护的。
5、私有继承中,基类的公有成员、保护成员在派生类中都是私有的。
综上所述,派生类继承到的东西,并不是可以随意使用,而是按照上面表格和结论去使用。下面用代码演示怎样使用。代码使用了公有继承,基类的公有成员将成为派生类的公有成员;基类的私有部分也将成为派生类的一部分,但只能通过基类的公有和保护方法访问。
// 22_Inheritance1.cpp
// g++ 22_Inheritance1.cpp
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
class CAnimal{
enum{NAME_LEN=64};
public:
CAnimal(){
sprintf(m_name, "Ainmal");
}
void run(){
cout << "my name is "<< m_name <<", Animal run" << endl;
}
void SetName(const char *name)// 修改私有成员 m_name
{
int len = strlen(name) > NAME_LEN ? NAME_LEN : strlen(name);
memset(m_name, 0, sizeof(m_name));
strncpy(m_name, name, len);
}
const char *GetName()
{
return m_name;
}
private:
char m_name[NAME_LEN];
};
class CDog : public CAnimal{
public:
enum{HAIR_BLACK, HAIR_WHITE};
CDog()
{
m_hairColor = HAIR_BLACK;
//sprintf(m_name, "Dog"); // 报错,基类私有成员在派生类无法访问。
}
private:
int m_hairColor; // 毛发颜色
};
int main ()
{
CAnimal animal;
animal.run();
CDog dog;
dog.run();
CDog dog1;
dog1.SetName("dog1");
dog1.run();
return 0;
}
运行结果如下,代码演示了公有继承,派生类无法访问基类的m_name,但可以通过基类的公有函数SetName去修改m_name:
🎄四、派生类对象的创建、销毁
我们定义了一个派生类,大概率是需要用到这个派生类的对象的,那么派生类对象是怎样创建、销毁的呢?
创建派生类对象的几个要点:
1、派生类没有继承基类的构造函数。
2、创建派生类对象时,程序会先创建派生类对象的基类部分,所以,会先调用基类的构造函数,再调用派生类构造函数。
3、如果没有显式地调用基类构造函数,那么程序会自动调用基类的 无参构造函数 来创建派生类对象的基类部分。
4、如果要显式调用基类构造函数,为了保证先创建基类部分,需要在派生类构造函数的 成员初始化列表 去调用。
5、一般情况下,让基类构造函数初始化基类部分,派生类构造函数初始化派生类新增的数据成员。
派生类对象的销毁
派生类对象的销毁时,会先调用派生类的析构函数,再调用基类的析构函数。这个顺序与创建时相反。
🌰举例子:
// 22_Inheritance2.cpp
// g++ 22_Inheritance2.cpp
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
class CAnimal{
enum{NAME_LEN=64};
public:
CAnimal() // 无参构造
{
sprintf(m_name, "Ainmal");
cout << "Calling CAnimal(): this=" << this << endl;
}
CAnimal(char *name)
{
int len = strlen(name) > NAME_LEN ? NAME_LEN : strlen(name);
memset(m_name, 0, sizeof(m_name));
strncpy(m_name, name, len);
cout << "Calling CAnimal(char*): this=" << this << endl;
}
~CAnimal() // 无参构造
{
cout << "Calling ~CAnimal(): this=" << this << endl;
}
void run(){
cout << "my name is "<< m_name <<", Animal run" << endl;
}
private:
char m_name[NAME_LEN];
};
class CDog : public CAnimal{
public:
enum{HAIR_BLACK, HAIR_WHITE};
CDog()
{
cout << "Calling CDog(): this=" << this << endl;
m_hairColor = HAIR_BLACK;
}
CDog(int color, char* name) : CAnimal(name)
{
cout << "Calling CDog(int, char*): this=" << this << endl;
m_hairColor = color;
}
~CDog()
{
cout << "Calling ~CDog(): this=" << this << endl;
}
private:
int m_hairColor; // 毛发颜色
};
int main ()
{
CDog dog;
dog.run();
cout << endl;
CDog dog1(CDog::HAIR_BLACK, (char*)"dog1");
dog1.run();
cout << endl;
return 0;
}
运行结果:
🎄五、基类和派生类的类型关系
这个小节介绍基类和派生类的一些特殊关系,主要有以下几点:
1、派生类对象可以直接使用基类的公有成员函数。
2、基类指针可以在不进行显式类型转换的情况下指向派生类对象。
3、基类引用可以在不进行显式类型转换的情况下引用派生类对象。
4、派生类对象,可以直接赋值给基类对象,而不需要强制转换(不建议这样做)。
5、派生类指针需要在显式类型转换的情况下指向基类对象,一般不这样做;
6、派生类引用需要在显式类型转换的情况下引用基类对象,一般不这样做;
7、派生类对象不能用来初始化或赋值给基类对象。
-
第1点没什么好解释的,基类的公有成员被派生类继承后仍旧是公有成员,所以可以直接通过派生类对象访问。
-
而第2、3点是跟类型相关的。通常,C++不允许将一种类型的地址赋给另一种类型的指针,也不允许一种类型的引用指向另一种类型,那为什么基类指针指向派生类对象时不需要进行显式转换呢?这个规则是
is-a
关系的一部分,派生类对象是基类对象(狗是动物),狗(CDog)继承了动物(CAnimal)的所有数据成员、成员函数,所以CAnimal的所有操作都使用于CDog对象。派生类中有基类部分,当使用基类的指针、引用去指向派生类对象时,会指向派生类的基类部分,如下图:
-
第4点,C++允许用派生类对象初始化(或赋值给)基类对象。但一般不这么做,因为基类的数据成员一般比派生类少,这样的初始化或赋值会导致派生类对象的一些数据成员被“切割”。可能会产生一些意外的结果。
当我们用一个派生类对象为一个基类对象初始化或赋值时,只有该派生类对象中的基类部分会被拷贝、移动或赋值,它的派生类部分将被忽略掉:
-
第5、6点,让派生类指针或引用指向基类对象,需要使用强制转换。因为派生类CDog的数据成员比基类CAnimal多,所以这样的指向会导致派生类指针或引用只能使用基类部分的成员,如果使用派生类自身的成员可能有问题,因为实际所指向的基类对象没有这些成员。
-
第7点,不允许用基类对象来初始化或赋值给派生类对象,因为基类的成员会被派生类少,用基类对象初始化或赋值给派生类对象将导致派生类的一些成员没有被赋值到,这是不允许的。
下面用代码演示一下基类、派生类的关系:
// 22_Inheritance2.cpp
// g++ 22_Inheritance2.cpp
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
class CAnimal{
enum{NAME_LEN=64};
public:
CAnimal() // 无参构造
{
sprintf(m_name, "Ainmal");
cout << "Calling CAnimal(): this=" << this << endl;
}
CAnimal(char *name)
{
int len = strlen(name) > NAME_LEN ? NAME_LEN : strlen(name);
memset(m_name, 0, sizeof(m_name));
strncpy(m_name, name, len);
cout << "Calling CAnimal(char*): this=" << this << endl;
}
~CAnimal() // 无参构造
{
cout << "Calling ~CAnimal(): this=" << this << endl;
}
void run(){
cout << "my name is "<< m_name <<", Animal run" << endl;
}
private:
char m_name[NAME_LEN];
};
class CDog : public CAnimal{
public:
enum{HAIR_BLACK, HAIR_WHITE};
CDog()
{
cout << "Calling CDog(): this=" << this << endl;
m_hairColor = HAIR_BLACK;
}
CDog(int color, char* name) : CAnimal(name)
{
cout << "Calling CDog(int, char*): this=" << this << endl;
m_hairColor = color;
}
~CDog()
{
cout << "Calling ~CDog(): this=" << this << endl;
}
int DogColor()
{
cout << "DogColor is " << (HAIR_BLACK==m_hairColor?"black":"white") << endl;
return m_hairColor;
}
private:
int m_hairColor; // 毛发颜色
};
int main ()
{
CDog dog(CDog::HAIR_BLACK, (char*)"dog1");
dog.run();
cout << endl;
// 派生类类型 ==》基类类型
CAnimal animal = dog;
animal.run();
cout << endl;
CAnimal &r_animal = dog;
r_animal.run();
cout << endl;
CAnimal *p_animal = &dog;
p_animal->run();
cout << endl;
// 基类类型 ==》派生类类型,需要强制转换
//CDog Dog1 = (CDog)animal; 默认不允许
CDog *pDog = (CDog*)&animal;
pDog->DogColor();
CDog &rDog = (CDog&)animal;
rDog.DogColor();
return 0;
}
运行结果:
🎄六、继承中的类作用域
每个类定义自己的作用域,在这个作用域内我们定义类的成员。
当存在继承关系时,派生类的作用域嵌套在其基类的作用域之内。
- 名字查找
派生类对象的名字查找:当出现下面代码时,编译器是怎样进行名称查找的?
编译器会先到派生类中找run成员函数;CDog dog(CDog::HAIR_BLACK, (char*)"dog1"); dog.run();
如果找不到,因为CDog是CAnimal的派生类,所以会在CAnimal类去找run函数,最终,run被解析为CAnimal的run。 - 名字隐藏
当派生类重用定义在其直接基类或间接基类中的名字,此时派生类的成员将隐藏同名的基类成员。class CAnimal{ public: ... void run(){ cout << "my name is "<< m_name <<", Animal run" << endl; } }; class CDog : public CAnimal{ public: ... void run(){ // 隐藏了 CAnimal 的 run cout << "Dog run" << endl; } };
- 通过作用域运算符来使用隐藏的成员
如果基类名称被派生类同名成员给隐藏了,可以使用基类作用域运算符来使用基类的隐藏成员。
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁
参考:
《C++ primer plus》
C++继承(详细)