目录
继承的介绍
一、单一继承
继承就是在一个已经存在的类的基础上创建一个新的类,并拥有其特性。
- 新建立的类被称为“派生类”或“子类”。
- 已经存在的类被称为“基类”或“父类”。
继承是面向对象三大特性之一,主要体现的是代码复用的思想。
如果一个派生类只是继承一个基类,两个类的内容基本相同,这样的继承是没有意义的。通过派生类可以对继承来的成员做出必要修改和增加。(如果基类的属性是private,派生类无法直接修改或访问,但是确实继承了)
- 对于继承的属性,通常可以修改值
#include <iostream>
using namespace std;
class Father{
public:
string first_name="王";
void work(){
cout<<"我是一名理发师"<<endl;
}
void show(){
cout<<"姓氏:"<<first_name<<endl;
}
};
class Son:public Father{
};
int main()
{
Son s;
s.work();
s.show();
}
- 对于继承的函数,可以通过函数隐藏“屏蔽”基类的同名函数
如果子类中给出了与父类同名的函数,会把父类的同名函数隐藏
#include <iostream>
using namespace std;
class Father{
public:
string first_name="王";
void work(){
cout<<"我是一名理发师"<<endl;
}
void show(){
cout<<"姓氏:"<<first_name<<endl;
}
};
class Son:public Father{
public:
int age=20;
//子类中给出了与父类同名的函数,会把父类的同名函数隐藏
void work(){
cout<<"我是一名老师"<<endl;
}
void play_game(){
cout<<"玩游戏"<<endl;
}
};
int main()
{
Son s;
s.work(); // //我是一名老师
s.show();
s.play_game();
cout<<s.first_name<<" "<<s.age<<endl;
}
需要注意的是:
- 基类和派生类是相对的,一个基类可以派生出多个派生类,每一个派生类又可以派生出新的派生类。
- 间接继承的类之间也具有继承关系,也可以称之为间接基类和间接派生类。直接继承的类之间称为直接基类和直接派生类。
- 派生类是基类的具体化,而基类是派生类的抽象化。
- 构造函数和析构函数不能被继承
1. 继承中的构造函数
1.1 函数基础特性
构造函数和析构函数不能被继承。
#include <iostream>
using namespace std;
/**
* @brief 基类
*/
class Father
{
public:
string first_name;
Father(string first_name)
{
this->first_name = first_name;
}
string get_first_name()
{
return first_name;
}
};
/**
* @brief 派生类
*/
class Son:public Father
{
};
int main()
{
// Son s("张"); 构造函数不能被继承
// Son s1; 找不到构造函数
return 0;
}
在继承中,任何一个派生类的任意一个构造函数都必须直接或间接调用基类的任意一个构造函数。因为在创建派生类对象时,需要调用基类的代码,使用基类的逻辑去开辟部分继承的空间。
在上一节的代码中,没写构造函数,编译器会为Father类和Son类增加无参构造函数,与此同时,还在Son的构造函数中尝试调用Father类的无参构造函数。
实际上,只要一个派生类没有手动调用基类的构造函数,编译器都会尝试调用基类的无参构造。
上面的代码有两种解决方案:
- 给Father构造函数增加无参调用的接口:
1. Father类增加一个重载的无参构造函数
Father()
{
first_name = "张";
}
2. 给Father已有的构造函数增加一个默认值,使其支持无参调用。
Father(string first_name="张")
{
this->first_name = first_name;
}
- 给Son类增加调用Father类构造函数的代码(派生类构造函数调用基类构造函数)
注意:
父类的构造函数不会继承下来
子类一定要直接或者间接的调用父类的构造函数。来完成从父类继承过来数据的初始化。如果子类不写明如何调用父类的构造函数,这时会调用父类无参的构造函数,如果父类中没有无参的构造函数,这时就会报错
1.2 派生类构造函数调用基类构造函数
派生类的构造函数中调用基类的构造函数一共有三种方式:
- 透传构造(直接调用)
- 委托构造()
- 继承构造
默认情况下,如果程序员在一个派生类中不写构造函数,编译器会自动给这个类添加一个无参构造函数且尝试调用街垒的无参构造函数。但是基类没有无参构造时,程序员必须手写派生类的构造函数并调用基类的构造函数。
1.2.1 透传构造(掌握)
透传构造指的是在派生类的构造函数中直接调用基类的构造函数
#include <iostream>
using namespace std;
class Father{
private:
string first_name;
int age;
public:
Father(string first_name,int age){
this->first_name=first_name;
this->age=age;
}
void getFirstName(){
cout<<"姓氏:"<<first_name<<endl;
}
void show(){
cout<<"姓氏:"<<first_name<<" 年龄"<<age<<endl;
}
};
class Son:public Father{
private:
int height; //子类新增的
public:
Son(string name,int age):Father(name,age){}
Son(string name,int age,int height):Father(name,age){
this->height=height; //子类新增的属性,自己完成初始化
}
void son_show(){
Father::show();
cout<<"身高是:"<<height<<endl;
}
};
int main()
{
Son s("小明",20);
s.show();
Son s2("小红",30,160);
s2.son_show();
}
1.2.2 委托构造(熟悉)
间接的调用父类的构造函数。子类的构造函数调用子类另一个构造函数,另一个构造函数调用了父类的构造函数,相当于间接调用父类的构造函数
#include <iostream>
using namespace std;
class Father{
private:
string first_name;
int age;
public:
Father(string first_name,int age){
this->first_name=first_name;
this->age=age;
}
void getFirstName(){
cout<<"姓氏:"<<first_name<<endl;
}
void show(){
cout<<"姓氏:"<<first_name<<" 年龄"<<age<<endl;
}
};
class Son:public Father{
private:
int height; //子类新增的
public:
Son(string name):Son(name,18){}
Son(string name,int age):Father(name,age){}
Son(string name,int age,int height):Father(name,age){
this->height=height; //子类新增的属性,自己完成初始化
}
void son_show(){
Father::show();
cout<<"身高是:"<<height<<endl;
}
};
int main()
{
Son s("小明",20);
s.show();
Son s3("张三");
s3.show();
}
1.2.3 继承构造
C++11新增的写法,实际上就是让编译器自动生成结构与基类的构造函数相同的派生类构造函数,并逐一透传构造。
#include <iostream>
using namespace std;
/**
* @brief 基类
*/
class Father
{
public:
string first_name;
Father(string first_name)
{
this->first_name = first_name;
}
string get_first_name()
{
return first_name;
}
Father():Father("张"){}
};
/**
* @brief 派生类
*/
class Son:public Father
{
public:
using Father::Father;
};
int main()
{
Son s1;
cout << s1.get_first_name() << endl; //王
Son s2("张");
cout << s2.get_first_name() << endl; //张
return 0;
}
1.3 对象的创建与销毁的过程
类中有成员对象时构造和析构顺序
类中的数据成员也可以是个对象,称为成员对象
当类中有成员对象时,实例化对象先调用成员对象的构造函数,再调用对象自己的构造函数
#include <iostream>
using namespace std;
//作为其他类的变量
class Value
{
private:
string name;
public:
Value(string name):name(name)
{
cout << name << "成员变量构建了" << endl;
}
~Value()
{
cout << name << "成员变量销毁了" << endl;
}
};
class Father
{
public:
Value value = Value("Father的");
static Value s_value;
Father():value(value)
{
cout << "Father类的构造函数" << endl;
}
~Father()
{
cout << "Father类的析构函数" << endl;
}
};
Value Father::s_value = Value("Father的静态");
class Son:public Father
{
public:
Value value = Value("Son的");
static Value s_value;
Son():Father():value(value)
{
cout << "Son类的构造函数" << endl;
}
~Son()
{
cout << "Son类的析构函数" << endl;
}
};
Value Son::s_value = Value("Son的静态");
int main()
{
cout <<"主函数开始执行" << endl;
//局部代码块
{
Son s; //栈内存对象
cout <<"-----对象使用中----" << endl;
}
cout <<"主函数结束执行" << endl;
return 0;
}
以上运行结果符合以下规律:
- 构建和销毁呈现对称关系,基类的内容先构建,派生类的内容后构建;派生类的内容先销毁,基类的内容后销毁。
- 静态成员从程序开始就创建,程序执行结束再销毁,不与任何对象绑定。
二、多重继承
1. 多重继承概念
C++语言支持多重继承,即一个派生类有多个直接继承的基类,是单一继承的扩展,每一个基类和派生类之间的关系都可以看做是一个单继承。
#include <iostream>
using namespace std;
class Sofa
{
public:
void sit()
{
cout << "沙发可以坐着" << endl;
}
};
class Bed
{
public:
void lay()
{
cout << "床可以躺着" << endl;
}
};
class SofaBed:public Sofa,public Bed
{
public:
};
int main()
{
SofaBed sb;
sb.lay();
sb.sit();
return 0;
}
2. 多重继承的二义性
2.1 作用域限定符
多重继承的二义性有两类:
- 当两个直接基类出现重名成员函数是时
解决方法:使用作用域限定符进行区分
#include <iostream>
using namespace std;
class Sofa
{
public:
void sit()
{
cout << "沙发可以坐着" << endl;
}
void position()
{
cout << "沙发放在客厅" << endl;
}
};
class Bed
{
public:
void lay()
{
cout << "床可以躺着" << endl;
}
void position()
{
cout << "床放在卧室" << endl;
}
};
class SofaBed:public Sofa,public Bed
{
public:
};
int main()
{
SofaBed sb;
sb.lay();
sb.sit();
sb.Bed::position();
sb.Sofa::position();
return 0;
}
2.2 菱形继承
菱形继承通过virtual虚继承的方式,从公共基类中只得到一份成员,避免了二义性
解决方法:
- 作用域限定符
- 使用虚继承
#include <iostream>
using namespace std;
class Furniture
{
public:
void function()
{
cout << "家具是家里的东西" << endl;
}
};
class Sofa:virtual public Furniture{};
class Bed:virtual public Furniture{};
class SofaBed:public Sofa,public Bed
{
public:
};
int main()
{
SofaBed sb;
//第二种:使用虚继承
sb.function();
//第一种:作用域限定符
// sb.Bed::function();
// sb.Sofa::function();
return 0;
}