继承基础概念
1.什么是继承?
继承是C++三大特性之一;继承是一个已经存在的类的基础上新建一个类,新建的类拥有已经存在的类的特性。主要提现的是代码复用的思想。新的类继承了基类的所有成员变量和成员函数,包括不显示的函数(构造函数,析构函数等)。
2.继承关系
被继承的类叫做基类,继承的类叫做派生类。
3.基本语法
class Farther {
// 基类成员
};
class Son : public Farther{ //private或者protected
// 派生类成员
};
简单的实现,Dog1继承了Dog类的成员函数和成员变量。
#include <iostream>
using namespace std;
class Dog{
private:
string name1 = "狗1";
public:
void print(){
cout << "我是" << name1 << endl;
}
};
class Dog1:public Dog{
};
int main (){
Dog d;
Dog1 d1;
d.print();
d1.print();
return 0;
}
函数隐藏
函数隐藏是面向对象编程中的一个重要概念,指派生类中定义的同名函数会隐藏基类中的同名函数。比如我Dog中定义的成员函数,我在派生类中可以再写定义一次,但是定义完之后的功能就不再是基类的函数的功能了,相当于新函数会吧旧函数隐藏。这样可以实现派生类和基类差异化。
#include <iostream>
using namespace std;
class Dog{
private:
string name1 = "狗1";
public:
void print(){
cout << "我是" << name1 << endl;
}
};
class Dog1:public Dog{
public:
void print(){
cout << "我是" << "狗2" << endl;
}
};
int main (){
Dog d;
Dog1 d1;
d.print();
d1.print();
return 0;
}
构造函数
1.继承中构造函数的限制
C++中规定,派生类无法继承基类的构造函数,派生类的任意一个构造函数都必须直接或间接调用基类的任意一个构造函数。
默认情况下,编译器会为每个类增加一个无参构造函数,同时会在派生类的无参构造函数中调用基类的无参构造函数
#include <iostream> using namespace std; class Dog{ private: string name1 = "狗1"; public: Dog(){} //不写的时候系统默认的构造函数 }; class Dog1:public Dog{ public: //Dog1():Dog() {} //编译器会自动生成一个默认构造函数 //这个默认构造函数会隐式调用基类的默认构造函数 //如果基类没有默认构造函数,则必须显式调用基类的某个构造函数 }; int main (){ Dog d; Dog1 d1; return 0; }
上面这种情况不会报错,但是如果这时候你想传参改变成员变量初始值,只改基类的构造函数可以吗?是不可以的,上面说了,构造函数是不继承的,是调用基类的构造函数,也就是我想初始化d1,但是我调用的是Dog(string name1):name1(name1){};构造函数,可我定义的是Dog1的对象,这个时候就会报错。
错误说的是没有与'Dog1(const char [4])'调用匹配的函数,也就是派生类里没有处理参数的构造函数,只有默认的Dog1():Dog(){}。
上面的内容看不懂没关系,因为因为包含了透传构造的内容,可以先简单看一下学完透传再开看。
2.透传构造
上面是基类中编译器默认生成的构造函数,这就是一个透传构造函数,只是没有传参,意思是调用的基类的构造函数Dog()。可以这么理解下面的图:派生类Dog1接收初始化传入的参数name1,在调用基类的构造函数,把name1透传给基类的构造函数实现初始化,派生类调用基类的构造函数实现初始化。
实现可传参的构造函数:
#include <iostream>
using namespace std;
class Dog{
private:
string name1;
public:
Dog(){}
Dog(string name1):name1(name1){}
string get_name1(){
return name1;
}
void print(){
cout << "我是" << name1 << endl;
}
};
class Dog1:public Dog{
public:
Dog1():Dog(){}
Dog1(string name1):Dog(name1){}
void print(){
cout << "我是" << this->get_name1() << endl;
}
};
int main (){
Dog d("狗1");
Dog1 d1("狗2");
d.print();
d1.print();
return 0;
}
3.委托构造函数
和透传类似,我当前派生类把参数委托给自己的构造函数,然后在调用基类的构造函数实现,其实比较多此一举,多一次调用,代码性能就越差,所有不推荐这么写。
#include <iostream>
using namespace std;
class Dog{
private:
string name1;
public:
Dog(){}
Dog(string name1):name1(name1){}
string get_name1(){
return name1;
}
void print(){
cout << "我是" << name1 << endl;
}
};
class Dog1:public Dog{
public:
Dog1():Dog1("狗2"){} //委托下面的构造函数
Dog1(string name1):Dog(name1){} //调用基类构造函数
void print(){
cout << "我是" << this->get_name1() << endl;
}
};
int main (){
Dog d("狗1");
Dog1 d1;
d.print();
d1.print();
return 0;
}
委托构造的时候要避免闭环,比如下面这种情况。
class Dog1:public Dog{
Dog1():Dog1("狗2"){} //调用下面的构造函数
Dog1(string name1):Dog1(){} //又去调用第一个构造函数
}
4.继承构造
这是C++11的新特性,继承构造不是构造函数能继承,而是表现出类似继承的特性,本质上还是透传构造。
#include <iostream>
using namespace std;
class Dog{
private:
string name1;
public:
Dog(){}
Dog(string name1):name1(name1){}
string get_name1(){
return name1;
}
void print(){
cout << "我是" << name1 << endl;
}
};
class Dog1:public Dog{
public:
using Dog::Dog; //加上这个语句,编译器会自动添加下面的语句
//Dog1():Dog(){} //委托下面的构造函数
//Dog1(string name1):Dog(name1){} //调用基类构造函数
void print(){
cout << "我是" << this->get_name1() << endl;
}
};
int main (){
Dog d("狗1");
Dog1 d1("狗2");
d.print();
d1.print();
return 0;
}
对象的创建与销毁流程
下面这个代码测试的是不同位置的对象创建和销毁先后顺序和流程。
1.流程:
(1).首先是主函数开始前(编译的时候)先给静态成员变量初始化
(2).主函数开始
(3)定义一个Son类型的变量:
基类成员变量初始化
基类构造函数
派生类成员变量初始化
派生类构造函数
(4)输出"正在使用s中:调用s的各种功能"。
(5)销毁一个Son类型的变量
派生类析构函数
派生类成员变量析构函数
基类析构函数
基类成员变量析构函数
(6)程序结束
(7)静态成员变量调用析构函数,越早初始化的最晚被释放
2.规律:
(1)对象创建和销毁流程相反
(2)静态成员变量的生命周期是程序的整个运行周期,在程序结束后才释放。
(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:
static Value s_value; // 静态成员变量
Value value = Value("Father的成员变量"); // 成员变量
Father()
{
cout << "Father的构造函数" << endl;
}
~Father()
{
cout << "Father的析构函数" << endl;
}
};
Value Father::s_value = Value("Father的静态成员变量");
class Son:public Father
{
public:
static Value s_value; // 静态成员变量
Value value = Value("Son的成员变量"); // 成员变量
Son()
{
cout << "Son的构造函数" << endl;
}
~Son()
{
cout << "Son的析构函数" << endl;
}
};
Value Son::s_value = Value("Son的静态成员变量");
int main()
{
cout << "主函数开始" << endl;
Son* s = new Son;
cout << "正在使用s中:调用s的各种功能" << endl;
delete s;
cout << "主函数结束" << endl;
return 0;
}
多重继承
1.基本使用:
一个派生类有多个基类,也就是继承多个基类,派生类继承所有的基类的成员变量和成员函数。
#include <iostream>
using namespace std;
class Name //定义名字类
{
private:
string name = "Lisa";
public:
string get_name(){
return name;
}
Name() {
cout << "name" << endl;
}
};
class Age //定义年龄类
{
private:
int age = 5;
public:
int get_age(){
return age;
}
Age() {
cout << "age" << endl;
}
};
class Cat : public Name,public Age //继承年龄和名字类
{
private:
string color = "red";
public:
Cat(){} //默认的透传构造函数
};
int main()
{
Cat c1;
cout << c1.get_age() << endl << c1.get_name() << endl;
return 0;
}
2.二义性:
2.1 重名成员
派生类继承的多个基类中存在相同的成员变量或者成员函数,这个时候直接调用编译器不知道你调用的是来自哪个基类的成员,就会出现二义性问题。
解决办法:在重名的成员前加上作用域限定符号——(基类名::)
#include <iostream>
using namespace std;
class Name //定义名字类
{
private:
string name = "Lisa";
public:
string get_name()const{ //不对数据改变可以使用const修饰
return name;
}
Name(){}
void print()const{
cout << "name:" << name << endl;
}
};
class Age //定义年龄类
{
private:
int age = 5;
public:
int get_age()const{
return age;
}
Age() {}
void print()const{
cout << "age:" << age << endl;
}
};
class Cat : public Name,public Age //继承年龄和名字类
{
private:
string color = "red";
public:
Cat(){} //默认的透传构造函数
};
int main()
{
Cat c1;
c1.Name::print(); //加上作用域限定符
c1.Age::print();
return 0;
}
2.2 菱形继承(钻石继承)
一个派生类有多个基类,这多个基类又继承同一个类,如下图类似与菱形(钻石)。
这个时候创建一个Cat类型的对象,我想调用Ainaml中的成员变量或者函数就会报错,因为Name和Age中都继承了Aniaml的成员,Name和Age中都有从Animal继承的成员,编译器就不知道你调用的成员来自于Name还是Age中,会出现二义性问题。
解决方法:
(1)方法一:
加上作用域限定符号,告诉编译器,调用的成员来自哪个基类。
#include <iostream>
using namespace std;
class Animal
{
private:
string breed = "银渐层"; //品种
public:
string get_breed(){
return breed;
}
void print(){
cout << breed << endl;
}
};
class Name:public Animal //定义名字类
{};
class Age:public Animal //定义年龄类
{};
class Cat : public Name,public Age //继承年龄和名字类
{};
int main()
{
Cat c1;
//c1.print(); //错误
c1.Age::print();
c1.Name::print();
return 0;
}
(2).方法二:
使用虚继承。
当一个类被虚继承的时候,虚拟机会为该基类生成虚机类表,一个类只有一个虚机类表。而虚继承的派生类内部会增加一个隐藏的指针成员(虚基类指针),这样菱形继承的时候派生类就会继承两个不同的虚机指针而不是直接继承成员变量和成员函数,通过这几个指针找到函数,就不存在二义性问题。
虚继承发生在Animal和Name与Age之间,Cat是直接继承Name 和Age类,同时把隐藏的指针继承,这样虚拟机就可以通过两个不同的指针都可以找到的是同一个虚机类表,调用成员函数和成员变量。
#include <iostream>
using namespace std;
class Animal
{
public:
void print(){
cout << "虚机类表" << endl;
}
};
class Name:virtual public Animal //定义名字类
{};
class Age:virtual public Animal //定义年龄类
{};
class Cat : public Name,public Age //继承年龄和名字类
{};
int main()
{
Animal a;
cout << sizeof(Animal) << endl;
cout << sizeof(a) << endl;
Name n;
cout << sizeof(Name) << endl;
cout << sizeof(n) << endl;
Cat c1;
cout << sizeof(Cat) << endl;
cout << sizeof(c1) << endl;
c1.print();
return 0;
}
继承类型与权限
了解了继承的基础知识后,最后再说三种不同的继承类型和它们的权限区别。
1.公有继承
(1)在类中可以被直接调用修改。
(2)被继承到派生类中,也可以被直接调用修改。
(3)在类外也可以通过对象直接调用查看修改。
2.保护继承
(1)在类中可以被直接调用查看修改。
(2)被继承到派生类中也可以被你直接调用修改。
(3)在类外不可以通过对象直接调用修改,必须借助get或者set成员函数接口调用。
3.私有继承
(1)在类中可以被直接调用查看修改。
(2)被继承到派生类中不可以直接被调用查看修改,必须借助get或者set成员函数接口调用。
(3)在类外也不可以通过对象直接调用修改。
总结:
在类中 |
在派生类中 |
在类外 |
|
public |
可以直接调用 |
可以直接调用 |
可以直接调用 |
protected |
可以直接调用 |
可以直接调用 |
不可以直接调用 |
private |
可以直接调用 |
不可以直接调用 |
不可以直接调用 |
#include <iostream>
using namespace std;
class Animal
{
private:
string name1 = "金渐层";
protected:
string name2 = "银渐层";
public:
string name3 = "缅因猫";
string get_name1()const{
return name1;
}
string get_name2()const{
return name2;
}
string get_name3()const{
return name3;
}
void print()const{
cout << name1 << endl; //在自己的类中都可以直接调用
cout << name2 << endl;
cout << name3 << endl;
}
};
class Cat : public Animal //继承年龄和名字类
{
public:
void print()const{
cout << get_name1() << endl; //派生类私有类不可直接调用
cout << name2 << endl;
cout << name3 << endl;
}
};
int main()
{
Animal a;
a.print();
cout << "#######################" << endl;
Cat c;
c.print();
cout << "#######################" << endl;
cout << c.get_name1() << endl; //私有和保护继承类外需要接口调用
cout << c.get_name2() << endl;
cout << c.name3 << endl; //公有继承可以直接调用
return 0;
}
函数覆盖
注意:函数也叫函数重写,有的人喜欢叫它函数重写
先了解后面还会讲
概念:
函数覆盖是实现运行时多态的核心,它允许派生类重新定义基类的虚函数,实现特定于派生类的行为,是继承体系中最重要的特性之一。被virtual修饰的成员函数就是虚函数,如果一个类有虚函数,就会给该类生成一个vtable(虚机类表)。
虚函数性质:
- 虚函数具有传递性,基类的虚函数可以把派生类新覆盖的函数编程虚函数。
- 成员函数可以设置为虚函数,静态成员函数不能设置为虚函数。
- 构造函数不能设置为虚函数,析构函数可以设置为虚函数。
- 如果函数声明与定义分离,只需要使用virtual修饰声明即可。
- 在C++11中,可以使用override关键字验证是否覆盖成功。
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void eat()
{
cout << "吃东西" << endl;
}
};
class Dog:public Animal
{
public:
void eat() override // 如果不报错,说明覆盖成功
{
cout << "吃狗粮" << endl;
}
// 错误:覆盖失败
// void eat2() override
// {
// cout << "吃肉" << endl;
// }
};
int main()
{
Dog d;
d.eat();
return 0;
}
练习:
定义学生类,有姓名,学号,性别,年龄等私有成员变量,有构造和析构函数,有打印信息的成员函数。
1.定义大学生类,继承自学生类,大学生有专业名、成绩的私有成员变量,还有是否获得奖学金的成员函数(成绩为判断依据)。隐藏基类打印信息的成员函数,新的打印信息的成员函数也要能打印姓名、学号、性别、年龄信息。
2.要求通过构造函数可以给属性赋予初始值。
3.再定义研究生类,继承自大学生类,有导师姓名和工资的私有成员变量,有打印工资这个成员函数。
4.要求通过构造函数可以给属性赋予初始值,要求通过构造函数可以给属性赋予初始值。
#include <iostream>
using namespace std;
class Student
{
private:
string name; //学生姓名
string code; //学生学号
char sex; //学生性别
int age; //学生年龄
public:
Student(string name,string code,char sex,int age):name(name),code(code),sex(sex),age(age) {}
string get_name()const{
return name;
}
string get_code()const{
return code;
}
char get_sex()const{
return sex;
}
int get_age()const{
return age;
}
void print()const{
cout << "名字:" << name << endl;
cout << "学号:" << code << endl;
cout << "性别:" << sex << endl;
cout << "年龄:" << age << endl;
}
~Student(){
cout << "析构函数" << endl;
}
};
class Ustudent:public Student{
private:
string professhion; //专业
double score; //成绩
char scholarship; //是否有奖学金
public:
Ustudent(string name,string code,char sex,int age,string profession,double score,char scholarship):Student(name,code,sex,age){
this->professhion = profession;
this->score = score;
this->scholarship = scholarship;
}
string get_professhion()const{
return professhion;
}
double get_score()const{
return score;
}
char get_scholarship()const{
return scholarship;
}
void print()const{
cout << "名字:" << this->get_name() << endl;
cout << "学号:" << this->get_code() << endl;
cout << "专业:" << professhion << endl;
cout << "性别:" << this->get_sex() << endl;
cout << "年龄:" << this->get_age() << endl;
cout << "成绩:" << score << endl;
cout << "是否有奖学金:" << scholarship << endl;
}
};
class Graduate:public Ustudent{
private:
string teachername; //导师名字
double salar; //薪水
public:
Graduate(string name,string code,char sex,int age,string profession,double score,char scholarship,string teachername,double salar):Ustudent(name,code,sex,age,profession,score,scholarship){
this->teachername = teachername;
this->salar = salar;
}
void print()const{
cout << "名字:" << this->get_name() << endl;
cout << "学号:" << this->get_code() << endl;
cout << "专业:" << this->get_professhion() << endl;
cout << "性别:" << this->get_sex() << endl;
cout << "年龄:" << this->get_age() << endl;
cout << "成绩:" << this->get_score() << endl;
cout << "是否有奖学金:" << this->get_scholarship() << endl;
cout << "导师姓名:" << teachername << endl;
cout << "工资:" << salar << endl;
}
};
int main (){
Student s1("张三","202111110132",'f',18);
Ustudent s2("王五","202111110133",'f',22,"嵌入式",88.8,'y');
Graduate s3("李四","202111110134",'m',23,"软件",98.8,'y',"吃个桃桃好凉凉",400);
cout << "########################" << endl << "学生1:" << endl;
s1.print();
cout << "########################" << endl << "学生2:" << endl;
s2.print();
cout << "########################" << endl << "学生3:" << endl;
s3.print();
cout << "########################" << endl;
return 0;
}