• 《C++ primer plus》精炼(OOP部分)——对象和类(8)


    学习是一项持续的投资,永远不白费——本杰明·富兰克林

    第13章:类继承

    一个基类和派生类

    从一个类派生出另一个类时,原始类称为基类,继承类称为派生类。以下是一个简单的基类:

    class Person
    {
    private:
    	string name;
    public:
    	Person(){name="none";};
    	Person(string s)
    	{
    		name=s;
    	}
    	~Person(){};
    	void show()
    	{
    		cout<<name;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这是一个表示人的简约的类,接下来我们用这个人来派生一个学生类:

    class Student:public Person
    {
    private:
    	int grade;
    public:
    	Student():Person()
    	{
    		grade=0;
    	}
    	Student(string s,int g):Person(s)
    	{
    		grade=g;
    	}
    	void s_show()
    	{
    		show();
    		cout<<grade;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    1. 要使用类继承,在类声明中需要使用如下格式:

    class 派生类:public/protected/private 基类

    其中public表示公有继承,protected表示保护继承,private表示私有继承,在本章我们只考虑公有继承。

    1. 在继承中,派生类自动包含基类的所有属性和方法,但派生类中的方法不能直接访问基类的私有成员,而要通过基类提供的方法来访问,例如:
    Student():Person()
    {
    	grade=0;
    }
    
    • 1
    • 2
    • 3
    • 4

    这是Student类的默认构造函数。对于派生类和基类的逻辑关系,可以用下面这张图来表示:
    在这里插入图片描述
    所以当要构造一个Student类的时候,从概念上要先构造出其中的Person类,所以要在构造函数执行到函数体之前将Person类初始化,即必须选用列表初始化语法。而且因为派生类不能直接访问基类的私有属性,因此下面的代码是错误的:

    Student():name("none")//不能直接访问name属性
    {
    	grade=0;
    }
    
    • 1
    • 2
    • 3
    • 4
    1. 使用时,可以用基类指针来指向派生类:
    int main(void)
    {
    	Person* p[2];
    	p[0]=Person();//指向一个Person类对象
    	p[1]=Student();//也可以指向一个Student类对象
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    注意,这一规则是单向的,也就是说不能把派生类指针指向基类,这是因为编译器看到Person类指针后,会认为这个指针指向的对象拥有Person类声明的方法和属性,下面的代码显然是错误的:

    int main(void)
    {
    	Student* s[2];
    	s[0]=Student();//可以通过编译
    	s[1]=Person();//不能通过编译
    	s[0]->s_show();//因为s[0]指向的是一个Student类对象,自然可以调用这个方法
    	s[1]->s_show();//但s[1]指向一个Person对象,因此显然是不行的
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    另外,如果用Person类指针指向了Student类对象,也只能用对象中从Person类继承下来的方法:

    int main(void)
    {
    	Person* p[2];
    	p[0]=Person();//指向一个Person类对象
    	p[1]=Student();//也可以指向一个Student类对象
    	p[1]->s_show();//错误,不能通过编译,即使指向一个Studnet类对象
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    对于形参为指向基类的指针的函数,也可以用派生类的地址作为实参,因此也可以用派生类对象来初始化基类对象(实际上是使用了复制构造函数),实际效果为将派生类对象中的基类部分拷贝到新的基类对象上。

    公有继承的逻辑关系:is-a

    当派生类is a基类时,适合使用公有继承,这就是公有继承的逻辑关系,例如在上面的例子中,Student is a Person.
    在这里插入图片描述

    多态公有继承

    在弄清Person类和Student类的关系后,我们可以把这种关系扩展到其他职业:
    在这里插入图片描述
    同样的,因为teacher is a person,headmaster is a person,也可以使用公有继承。可以试着自己写一写代码,笔者的代码如下:

    class Person
    {
    private:
    	string name;
    public:
    	Person(){name="none";};
    	Person(string s)
    	{
    		name=s;
    	}
    	virtual ~Person(){};
    	virtual void show()
    	{
    		cout<<name;
    	}
    }
    
    class Student:public Person
    {
    private:
    	int grade;
    public:
    	Student():Person()
    	{
    		grade=0;
    	}
    	Student(string s,int g):Person(s)
    	{
    		grade=g;
    	}
    	virtual void show()
    	{
    		Person::show();
    		cout<<grade;
    	}
    }
    
    class Teacher:public Person
    {
    private:
    	int number;//负责哪个班级的班号,简单即可
    public:
    	Teacher():Person()
    	{
    		number=0;
    	}
    	Teacher(string s,int n):Person(s)
    	{
    		number=n;
    	}
    	virtual void show()
    	{
    		Person::show();
    		cout<<number;
    	}
    }
    
    class HeadTeacher:public Person
    {
    private:
    	int s_number;//负责哪个学校的代号
    public:
    	HeadTeacher():Person()
    	{
    		number=0;
    	}
    	HeadTeacher(string s,int n):Person(s)
    	{
    		s_number=n;
    	}
    	virtual void show()
    	{
    		Person::show();
    		cout<<s_number;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    这里需要注意的就是virtual关键字。被virtual关键字当要将基类的方法在派生类中重写时,可以使用这个关键字。当使用这个关键字时,如果一个对象由指针引用,那么这个对象调用函数时,所调用的函数由这个对象的实际类决定。另外,因为进行了函数重写,因此在调用基类的方法时需要使用::运算符。下面是这四个类的测试代码:

    Person* p[4];
    p[0]=Person();
    p[1]=Student();
    p[2]=Teacher();
    p[3]=HeadTeacher();
    
    p[0]->show();//调用Person类中的show函数
    p[1]->show();//调用Student类中的show函数
    p[2]->show();//调用Teacher类中的show函数
    p[3]->show();//调用HeadTeacher类中的show函数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    另外,如果将函数的声明和定义分开,那么virtual关键字只在类声明的时候加上即可。
    用一个基类指针的数组指向不同派生类的对象,当执行同样的代码时,实际行为会随被执行的对象类型而变化,这就是多态。多态保证了代码的重用性,使得程序员不需要频繁修改测试代码,所以多态也是继封装和继承后OOP中第三个重要的思想。
    最后,在Person类中的构造函数也被声明为虚函数,这是因为如果不这样声明,那么编译器会按照指针类型而非实际对象类型来析构数组p所指向的对象,这会导致内存泄漏,所以即使Person类析构函数不进行任何操作,也要声明为虚函数。
    请添加图片描述
    我是霜_哀,在算法之路上努力前行的一位萌新,感谢你的阅读!如果觉得好的话,可以关注一下,我会在将来带来更多更全面的知识讲解!

  • 相关阅读:
    SpringBoot电商项目进阶Day5
    数商云SRM系统询比价有何优势?供应商平台助力汽车零部件企业快速查找供应商
    线程池执行的用户任务抛出异常会怎样
    mysql死锁的排查和解决
    技术分享 | 常见接口协议解析
    cvx matlab 求不出解可尝试方法 Failed Infeasible Unbounded
    CANoe-Model Editor无法修改ARXML文件的问题、E2E在SOME/IP通信中的使用问题
    蓝桥杯 选择排序
    [PAT-Advanced] A1015 Reversible Primes (20)
    界面开发的优化与设计
  • 原文地址:https://blog.csdn.net/m0_72987309/article/details/133385520