一、继承的概念及定义
1.继承的概念
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,简称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都使函数复用,继承是类设计层次的复用。
先上手看一看。
#include<iostream>
using namespace std;
class Person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "peter";//姓名
int _age = 18;//年龄
};
class Student :public Person {
protected:
int _stuid;//学号
};
class Teacher :public Person
{
protected:
int _jobid;//工号
};
int main()
{
Student s;
Teacher t;
return 0;
}
定义了一个Person的类,以及两个继承了Person的子类(Student和Teacher)。
能看到这里t和s都继承了Person类里的对象。
int main()
{
Student s;
Teacher t;
s.Print();
t.Print();
return 0;
}
s,t都调用了父类的Print()函数。
1.2继承定义
1.2.1定义格式
在这之中,Student为派生类,public为继承方式,Person为基类
类成员/继承方式 | public继承 | protected继承 | private继承 |
---|---|---|---|
基类的public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
基类的protected成员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 |
基类的private成员 | 在派生类中不可见 | 在派生类中不可见 | 在派生类中不可见 |
总结:
- 基类的private成员在派生类中无论以什么方式继承都是不可见的,这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
- 基类private成员在派生类中是不能被访问,如果是基类成员不想在类外面直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
- 实际上面的表格我们总结一下就会发现,基类的私有成员在子类都是不可见的。基类的其他成员在子类的访问方式==Min(成员在基类的访问限定符,继承方式),public>protected>private。
- 使用关键字class时默认的继承方式是private,使用struct时默认继承方式是pulic,不过最好显示的写出继承方式。
- 在实际运用中一般使用都是public集成,几乎很少使用private,protected也不提倡使用,因为protected/private继承下来的成员都只在派生类里面使用,实际中维护扩展性不强。
赋值兼容规则
在任何需要基类对象的地方都可以用公有派生类对象来代替。(把子给父)
is-a 规则
在public继承的条件下,父类和子类是一个is-a关系
子类对象赋值给父类对象/父类指针/父类的引用
我们认为是天然,中间不产生临时对象,这个叫做父子类赋值兼容规则(切割/切片)
由此可知,父类对象一般不能给子类。
继承中的作用域
- 在继承体系中基类和派生类都有独立的作用域。
- 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用基类::基类成员 显示访问)
- 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
- 注意在实际中在继承体系里最好不要定义同名的成员。
父类和子类可以有同名成员,因为他们是独立作用域。
class A
{
public:
void fun()
{
cout << "fun()" << endl;
}
};
class B : public A {
public:
void fun(int i) {
cout << "fun()" << i << endl;
}
};
练习 以下哪个选项正确(B)
A、两个fun构成函数重载——两个不在同一个作用域,不构成重载
B、两个fun构成隐藏
C、编译错误
D、运行报错
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Person
{
public:
Person(const char* name = "peter")
:_name(name)
{
cout << "Person()" << endl;
}
Person(const Person& p)
:_name(p._name)
{
cout << "Person(const Person& p)" << endl;
}
Person& operator=(const Person& p)
{
cout << "Person& operator=(const Person& p)" << endl;
}
~Person()
{
cout << "~Person()" << endl;
}
protected:
string _name;//姓名
};
class Student :public Person
{
protected:
int _id;
};
int main() {
Student s;
return 0;
}
运行结果:
可以看到,这里Student调用了父类的构造函数和析构函数。
如果我们为Student写了构造函数。
class Student :public Person
{
public:
Student(const char* name,int id)
:_name(name)
,_id(id)
{}
protected:
int _id;
};
会发现编译时报错,
诶?我不是将Person类继承给Student类了吗,为什么Student类不能识别_name?
因为这里没有初始化父类,因此我们需要显示化的调用父类。
Student(const char* name,int id)
:Person(name)
,_id(id)
{
cout << "Student(const char* name,int id)" << endl;
}
这时就能正常运行了。
拷贝构造
Student(const Student& s)
:Person(s)
, _id(s._id)
{
cout << "Student(const Student& s)" << endl;
}
赋值
Student& operator=(const Student& s)
{
if (&s != this)
{
Person::operator=(s);
_id=s._id;
}
return *this;
}
**注意:**这里必须调用父类的operator=函数,否则构成隐藏关系,会一直调用Student的operator=()而不会结束。
析构函数
~Student()
{
~Person();
cout << "~Person()" << endl;
}
这里会发现调不动析构函数。
父子类的析构函数构成隐藏。
由于多台的原因,析构函数会被同意处理成destructor。
~Student()
{
Person::~Person();
cout << "~Person()" << endl;
}
构造顺序:先父后子
析构顺序:先子后父
如果析构先子后父,会存在风险。
因此,为了保证析构安全,父类析构函数不需要显示调用,子类析构函数调用结束时会自动调用父类析构,以保证先子后父