本专栏目的
- 更新C/C++的基础语法,包括C++的一些新特性
前言
- 通过前面几节课,我们学习了抽象、封装相关的概念,接下来我们将讲解继承;
- C语言后面也会继续更新知识点,如内联汇编;
- 本人现在正在写一个C语言的图书管理系统,1000多行代码,包含之前所学的所有知识点,包括链表和顺序表等数据结构,请大家耐心等待!!预计国庆前写完更新,现在基本功能已经完成。
文章目录
面向对象程序设计有4个主要特点:抽象、封装、继承和多态性。我们已经讲解了类和对象,了解了面向对象程序设计的两个重要特征一数据抽象与封装,接下来我们将讲解继承和多态,多态在下一节课讲解。
继承关系举例
在生活中,万事万物中皆有继承,是重要的现象,一层一层嵌套,就想我们常说的**”子从父,子子孙孙无穷尽也“**
这里本文找了一张植物继承图:
继承概念
官方概念:继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。
说直白一点,假设A继承B,那么B就可以使用A的一些API和变量。
例如:我们设计一个学生管理系统,学校中有老师,学生,工作人员等,我们要记录他们的信息比如学生有电话,姓名,地址,学号,各科成绩等信息。
struct Student
{
int _number;
string _tel;
string _name;
string _addr;
float _chineseScore;
float _mathScore;
float _englistScore;
};
比如教师有电话,姓名,地址,工号,工资等信息。
struct Teacher
{
int _number;
string _tel;
string _name;
string _addr;
float _sal;
};
这样设计后我们会发现会有很多的重复信息,那么我们可以把重复的信息提取出来,重新建立一个Person类。
struct Person
{
uint32_t _number;
string _tel;
string _name;
string _addr;
};
那么我们的学生类和教师类就可以复用Person类。
struct Student
{
Person _super; //复用Person类的成员(相当于父类)
float _chineseScore;
float _mathScore;
float _englistScore;
};
struct Teacher
{
Person _super; // 继承Person类
float _sal;
};
-
注意:这在C语言中可以这样实现继承,但是,这样在一个类里面组合一个类,访问其成员非常的不方便,(扩展go语言采用了以上的思想,这个我们后面介绍GO语言时候会再次详细解释)。
-
C++给我们在语法层面上直接提供了支持,上面的代码可修改如下:
struct Person
{
uint32_t _number;
std::string _tel;
std::string _name;
std::string _addr;
};
//Student继承自Person类,复用Person类的成员
//Person类可以称为** 基类或父类 **
//Student类可以称为** 派生类或子类 **
struct Student : public Person
{
float _chineseScore;
float _mathScore;
float _englistScore;
};
struct Teacher : public Person
{
float _sal;
};
在继承后父类Person的成员(成员函数与成员变量)都会变成子类的一部分,这里就体现出Student和Teacher复用了Person类,在vs2022中我们可以通过调试的监视窗口看到继承关系和调用父类成员。
继承使用
继承语法
一个类继承自另一个类语法:
class Derived(派生类名): AccessQualifier(访问限定符) Base(基类)
{
//成员变量和成员函数声明...
}
访问限定符(Access qualifier)
访问限定符指定了从基类继承的方式,继承方式不同,成员在派生类中的权限也不同。
类成员/继承方式 | public继承 | protected继承 | private继承 |
---|---|---|---|
基类的public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
基类的protected成员 | 派生类的protecte成员 | 派生类的protected成员 | 派生类的private成员 |
基类的private成员 | 在派生类不可见 | 在派生类不可见 | 在派生类不可见 |
C++的继承方式有三种,实际上最常使用的为public继承方式,而基类的成员访问限定符设置最多的为public和protected。
那如果我们想要在A中的某一个东西,禁止他继承呢?C++就提供了林外一个关键字,如下:
- 使用final关键字可以禁止继承
总结
- 基类private成员在派生类中无论以什么方式继承都是不可见的;
- 如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的;
- class的默认继承方式为private,而struct的默认继承方式为public;
- 派生类可以拥有基类没有的方法和属性,派生类可以对基类进行扩充。
以上条例,理解性记忆即可。
赋值兼容原则
我们来看这一段代码:
class Animal
{
public:
std::string category{"Animal"}; //所属类别(如:狗 猫 蛇...)
};
class Dog :public Animal
{
public:
std::string name{"Dog"}; //名字(如:大黄 小黑 发财 旺财...)
};
int main()
{
Animal animal;
Dog spike; //(spike:猫和老鼠中大狗的名字 它的儿子叫tyke哟~)
animal = spike; //1、子类对象赋值给父类对象
return 0;
}
以上代码并没有报错,为什么呢?子类赋值给父类?
因为我们可以把派生类的对象赋值给基类,派生类赋值给基类的过程我们称其为切片,但注意的是基类对象不能赋值给派生类对象,也就是子类可以赋值给父类,但是父类不能赋值给子类。
我们还可以用基类的指针指向子对象、用基类引用子对象
// 2、父类的指针可以指向子类的对象
Animal* pa = &dog; //注意这个是传递地址
// 3、父类可以引用子类对象
Animal & pra = dog;
//1中:子类赋值给父类,调用父类依然还是只能用父类
//2,3中:经常用作函数传参,可以用子类当作父类作为函数的参数
void print(Animal* pa){
pa->tiger();
}
//调用的时候可以传递子类作为参数
print(&cat) //指针,所以要加取地址符
总结:子类可以当作父类
赋值兼容原因:
- 从代码中来看,子类继承了父类,拥有了父类的全部成员,所以父类能干的事,子类也能干;反之,子类对父类进行了扩充,子类能干的,父类不一定干的了。
继承中的成员
父子类同名成员变量
如果子类A继承父类B,那如果父类B和子类A都有相同名字的成员呢?
为了解决这个问题,C++提出了重定义的概念,因为无论是父类和子类,他们都有自己的一块内存空间,重定义本质就是在不同的内存空间中定义不同成员。
来个代码辅助分析:
class Father
{
public:
int age = 45;
};
class Child : public Father
{
public:
int age = 18;
};
int main()
{
Child self;
std::cout <<"self age is:" << self.age << std::endl;
return 0;
}
运行结果:
self age is: 18
那么我们如果想要打印父类的成员num应该怎么办呢?那么我们应该指定类域:
std::cout <<"self age is:" << self.Father::age << std::endl; //很重要
输出:
self age is: 45
父子类同名成员函数
上面我们测试的是成员变量,那么成员函数呢?给父子类加个同名的成员函数,测试一下:
class Father
{
public:
void foo()
{
std::cout << __FUNCSIG__ << std::endl;
}
};
class Child : public Father
{
public:
void foo(int i)
{
std::cout << __FUNCSIG__ << " " << i << std::endl;
}
};
int main()
{
Child self;
self.foo(1);
//self.foo(); //访问不到
self.Father::foo();
return 0;
}
结果:
void __cdecl Child::foo(int) 1
void __cdecl Father::foo(void)
可以看出,想要调用父类的API,就需要指明范围。
继承和静态成员
继承和static关键字在一起会产生什么现象?
class A
{
public:
static int count;
};
int A::count = 222; //在外面赋值
class B :public A
{
public:
};
int main()
{
A::count++;
std::cout << A::count << " " << B::count << std::endl;
return 0;
}
结果:
223 223
小结:
- 基类定义的静态成员,将被派生类共享,因为静态成员定义在全局区,是共享的。
- 派生类中访问静态成员,用以下形式显式说明:
- 通过类名直接访问:类名 :: 成员
- 通过对象访问: 对象名 . 成员
继承和友元
- 友元是不能够继承的,也说明父类的友元无法访问子类的私有和保护成员。
多继承
C++中是支持多继承的,但是一直存在争议,及其不推荐使用,Java直接禁止多继承,有兴趣的可以多查找C++ Prime这本书籍。
下面是本人学习中的一段演示代码截图(vs2022):
拷贝继承
我们知道在C++中有浅拷贝和深拷贝之分,有指针,必须深拷贝,,想要实现深拷贝就必须自己自定义实现,因为C++默认是浅拷贝的,但是如果子类在进行赋值、钱拷贝等操作的时候,那父类应该怎么操作呢???,下面是一段代码演示,解决浅拷贝问题,深拷贝其实也一样道理,只是需要重新分配内存:
拷贝构造:
class DNode
{
public:
DNode(const DNode& other)
{
x = other.x;
y = other.y;
}
...
};
class DSprite : public DNode
{
public:
...
DSprite(const DSprite& other)
:DNode(other) // 父类拷贝构造
,texture(other.texture)
{
}
...
};
赋值构造:
class DNode
{
public:
DNode& operator=(const DNode& other)
{
if(other==*this){
return *this;
}
x = other.x;
y = other.y;
return *this;
}
...
};
class DSprite : public DNode
{
public:
DSprite& operator=(const DSprite& other)
{
DNode::operator=(other); //注意注意注意注意 :; 重点, 子类实现拷贝
texture = other.texture;
return *this;
}
...
};
标签:继承,子类,成员,C++,--,派生类,父类,public
From: https://blog.csdn.net/weixin_74085818/article/details/142624138