继承—虚基类-虚函数-多态19.5.14-5.22

/****************************************/

C++支持多继承:

  1)即一个子类也可以有多个基类

  2)表达形式如下:

class 派生类名: 访问控制符 基类名1,访问控制符 基类名2 

数据成员和成员函数声明; 

}

class A: public B,public c

{

}

多个直接基类构造函数执行顺序取决于定义派生类时指定的各个继承基类的顺序

 

派生类继承的方式

公有继承:

2)可以像访问自身公有和保护成员一样,访问基类成员;

即             public -public ;            protected-pretected;    private-private;

3)对于基类的保护成员,无法访问;

 

私有继承:public-protected;       protected-pretected;     private-private;

 

保护继承:public-private;            protected-private;         private-private;

4)赋值兼容原则:在公有派生的前提下,派生类对象可作为基类对象来使用

     1)派生类对象可以赋予基类的对象;

        A a;//A是B的派生类

        B b;

        a=b;

     2)派生类对象可以初始化基类的引用;

     3)基类指针可以指向公有派生类的对象;//利用虚函数可以访问派生类的特定成员和方法

 

派生类继承基类成员和方法

 

派生类构造函数:用于初始化派生类新增成员和方法

基类构造函数:用于初始化基类继承的成员和方法

构造函数执行顺序:先调用基类构造函数初始化继承成员和变量,再调用派生类构造函数,初始化新增成员和方法;

析构函数则相反

 

#include
#include
#include
#include
using namespace std;                       
class B1                                              
{   
public:
       B1(int a){
           cout<<"B1"<

基类与派生类的特殊关系:

  1. 派生类可以访问基类中不是私有的成员变量和方法,如果是私有,则需要通过基类的公有方法来访问;

  1. 不能调用派生类的方法和成员;(父母无私奉献给孩子,孩子却有所保留);

  1. 基类指针可以在不进行显式类型转换的情况下指向派生类对象,基类引用可以在不进行显式类型转换的情况下指向派生类对象;(如下面的程序)

 

下面程序还要注意一个知识点:就是继承怎么写

 

 

虚基类

虚基类的成员由所有的派生类共同拥有。

从基类派生新类时,使用关键字virtual可以将基类说明成虚基类,

特点:虚基类只初始化一次

虚基类对象是由最直接派生类的构造函数通过调用虚基类进行初始化的,虚基类优先于非虚基类的构造函数执行;

/**************************************************/

继承、派生类、虚基类的练习题:

1)虚函数使用:

 

//基于VS2017
//未加虚基类的但可以实现完整输出的程序
#include "pch.h"
#include 
#include 
#include 
using namespace std;
class Student
{
public:
       Student(int, string, string, string, string);  //声明构造函数
       void display();      
protected:
       int Regnum;
       string name;
       string gender;
       string school;
       string Admission_Date;
};
//Student类成员函数的实现
Student::Student(int n, string nam, string gen,string sch,string time)//定义构造函数
{
       Regnum = n;
       name = nam;
       gender = gen;
       school = sch;
       Admission_Date = time;
}
void Student::display()//定义输出函数
{
       cout << "在校生Regnum:" << Regnum << "\nname:" << name << "\ngender:" << gender << "\nschool:"<< school << "\nAdmission_Date:"<< Admission_Date<<"\n\n";
}
//声明公用派生类Graduate
class Graduate :public Student
{
public:
       Graduate(int, string, string, string, string, string, string);//声明构造函数
       void display();//声明输出函数
private://派生类新增成员
       string Graduate_Date;
       string Graduate_degree;
};
// Graduate类成员函数的实现
void Graduate::display()//定义输出函数
{
       cout << "毕业生Regnum:" << Regnum << "\nname:" << name << "\ngender:" << gender << "\nschool:" << school << "\nAdmission_Date:" << Admission_Date << "\nGraduate_Date:" << Graduate_Date << "\nGraduate_degree:"<< Graduate_degree<<"\n\n";
}
Graduate::Graduate(int n, string nam, string gen, string sch, string time, string Gratime, string Degree) : Student(n, nam, gen, sch, time), Graduate_Date(Gratime), Graduate_degree(Degree)
{
       //Graduate_Date = Gratime;
       //Graduate_degree = Degree;
}
int main()
{
       Student stud1(1170230039, "xu", "male","浙江科技学院","2017.9.1");//定义Student类对象stud1
       Graduate grad1(115000001, "zhou","female" ,"浙江科技学院","2015.9.1","2019.6","研究生" );//定义Graduate类对象grad1
       stud1.display();
       grad1.display();
       system("pause");
       return 0;
}

//未使用虚基类也可以输出的程序

//缺点是:如果一个继承的层级很深,有多重继承,那就需要定义多个指针,使得程序冗杂,所以虚基类就很有用了

//基于VS2017
#include "pch.h"
#include 
#include 
#include 
using namespace std;
class Student
{
public:
       Student(int, string, string, string, string);  //声明构造函数
       void display();      
protected:
       int Regnum;
       string name;
       string gender;
       string school;
       string Admission_Date;
};
//Student类成员函数的实现
Student::Student(int n, string nam, string gen,string sch,string time)//定义构造函数
{
       Regnum = n;
       name = nam;
       gender = gen;
       school = sch;
       Admission_Date = time;
}
void Student::display()//定义输出函数
{
       cout << "在校生Regnum:" << Regnum << "\nname:" << name << "\ngender:" << gender << "\nschool:"<< school << "\nAdmission_Date:"<< Admission_Date<<"\n\n";
}
//声明公用派生类Graduate
class Graduate :public Student
{
public:
       Graduate(int, string, string, string, string, string, string);//声明构造函数
       void display();//声明输出函数
private://派生类新增成员
       string Graduate_Date;
       string Graduate_degree;
};
// Graduate类成员函数的实现
void Graduate::display()//定义输出函数
{
       cout << "毕业生Regnum:" << Regnum << "\nname:" << name << "\ngender:" << gender << "\nschool:" << school << "\nAdmission_Date:" << Admission_Date << "\nGraduate_Date:" << Graduate_Date << "\nGraduate_degree:"<< Graduate_degree<<"\n\n";
}
Graduate::Graduate(int n, string nam, string gen, string sch, string time, string Gratime, string Degree) : Student(n, nam, gen, sch, time), Graduate_Date(Gratime), Graduate_degree(Degree)
{
       //Graduate_Date = Gratime;
       //Graduate_degree = Degree;
}
int main()
{
       Student stud1(1170230039, "xu", "male","浙江科技学院","2017.9.1");//定义Student类对象stud1
       Graduate grad1(115000001, "zhou","female" ,"浙江科技学院","2015.9.1","2019.6","研究生" );//定义Graduate类对象grad1
       //第二种方式访问:但要注意要使用虚函数方法才能显示派生类数据
       Student *pt = &stud1;//定义指向基类对象的指针变量pt
         Graduate *p = &grad1;//定义指向派生类对象的指针变量pt
       pt->display();
       p->display();   
       system("pause");
       return 0;
}

//使用虚函数来简化

//基于VS2017
#include "pch.h"
#include 
#include 
#include 
using namespace std;
class Student
{
public:
       Student(int, string, string, string, string);  //声明构造函数
       virtual void display();
protected:
       int Regnum;
       string name;
       string gender;
       string school;
       string Admission_Date;
};
//Student类成员函数的实现
Student::Student(int n, string nam, string gen,string sch,string time)//定义构造函数
{
       Regnum = n;
       name = nam;
       gender = gen;
       school = sch;
       Admission_Date = time;
}
void Student::display()//定义输出函数
{
       cout << "在校生Regnum:" << Regnum << "\nname:" << name << "\ngender:" << gender << "\nschool:"<< school << "\nAdmission_Date:"<< Admission_Date<<"\n\n";
}
//声明公用派生类Graduate
class Graduate :public Student
{
public:
       Graduate(int, string, string, string, string, string, string);//声明构造函数
       void display();//声明输出函数
private://派生类新增成员
       string Graduate_Date;
       string Graduate_degree;
};
// Graduate类成员函数的实现
void Graduate::display()//定义输出函数
{
       cout << "毕业生Regnum:" << Regnum << "\nname:" << name << "\ngender:" << gender << "\nschool:" << school << "\nAdmission_Date:" << Admission_Date << "\nGraduate_Date:" << Graduate_Date << "\nGraduate_degree:"<< Graduate_degree<<"\n\n";
}
Graduate::Graduate(int n, string nam, string gen, string sch, string time, string Gratime, string Degree) : Student(n, nam, gen, sch, time), Graduate_Date(Gratime), Graduate_degree(Degree)
{
       //Graduate_Date = Gratime;
       //Graduate_degree = Degree;
}
int main()
{
       Student stud1(1170230039, "xu", "male","浙江科技学院","2017.9.1");//定义Student类对象stud1
       Graduate grad1(115000001, "zhou","female" ,"浙江科技学院","2015.9.1","2019.6","研究生" );//定义Graduate类对象grad1
       //第二种方式访问:但要注意在基类方法中要使用虚函数方法才能显示派生类数据,即最左边加virtual
       Student *pt = &stud1;//定义指向基类对象的指针变量pt——>基类指针就可实现所有的显示
       pt->display();
       pt = &grad1;//指向派生类的首地址
       pt->display();
       system("pause");
       return 0;
}

虚函数的使用方法是:

1)在基类用virtual在最左边声明成员函数为虚函数。

这样就可以在派生类中重新定义此函数,为它赋予新的功能,并能方便地被调用。在类外定义虚函数时,可以不加virtual

 

2)在派生类中重新定义此函数,要求函数名、函数类型、函数参数个数和类型全部与基类的虚函数相同,并根据派生类的需要重新定义函数体。

 

3)C++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此在派生类重新声明该虚函数时可以加virtual,也可以不加,但习惯上一般在每一层声明该函数时都加virtual,使程序更加清晰。如果在派生类中没有对基类的虚函数重新定义,则派生类简单地继承其直接基类的虚函数。

 

4)为什么构造函数(一种特殊的成员函数)不能声明为虚基类?

   1)虚函数是用来表现基类和子类之间关系的方法;

   2)虚函数作用:通过基类指针来访问公有派生类中不是基类继承的成员;

   3)而构造函数——对象创建以后,编译器自动调用,不需要指针访问;

C++之父的解释:Stroustrup: C++ Style and Technique FAQ

 

 

5)为什么析构函数可以声明为虚函数,而且也最好声明为虚函数?为什么构造函数不可以声明为虚函数?

暂时先记住,这个问题先留着✘

解答:

【为什么构造函数不能为虚函数,而析构函数可以为虚函数 - 李艳21 - 博客园】博客园】https://www.cnblogs.com/lirong21/p/4035031.html

 

 

 

6)定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象。

 

7)通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。

通过虚函数与指向基类对象的指针变量的配合使用,就能方便地调用同一类族中不同类的同名函数,只要先用基类指针指向即可。如果指针不断地指向同一类族中不同类的对象,就能不断地调用这些对象中的同名函数。这就如同前面说的,不断地告诉出租车司机要去的目的地,然后司机把你送到你要去的地方。

虚函数与重载函数的关系:

1. 重载函数:要求函数有相同的函数名称,不同的参数列表;

    虚函数:要求函数有相同的函数名称,相同的参数列表。

2. 重载函数可以是成员函数也可以是友元函数;而虚函数只能是成员函数。

3. 重载函数的调用是以所传递参数序列的差别作为调用不同函数的依据,而虚函数则是根据传入的对象的不同去调用不同类的虚函数的;

4.  虚函数在运行时表现出多态,而重载在编译时表现出多态性;

 

对于第3点的解释与拓展:

1)首先友元函数并不属于类的成员函数,没有this指针,只能用对象名访问;

2)在华为、中兴等企业招聘笔试面试中曾经出现过这样一道题目:什么函数不能声明为虚函数?该题答案为:普通函数(非成员函数)、静态成员函数、内联成员函数、构造函数、友元函数

解释如下:

         1)虚函数通过继承方式来体现出多态作用,它必须是基类的非静态成员函数,其访问权限可以是protected或public,在基类的类定义中定义虚函数的一般形式是:

virtual 函数返回值类型虚函数名(形参表){ 函数体 }

常见的不能声明为虚函数的有:普通函数(非成员函数)、静态成员函数、内联成员函数、构造函数、友元函数。

          2) 普通函数不能声明为虚函数。普通函数(非成员函数)只能被重载(overload),不能被重写(override),声明为虚函数也没有什么意思,因此编译器会在编译时绑定函数。

          3) 构造函数不能声明为虚函数。构造函数一般用来初始化对象,只有在一个对象生成之后,才能发挥多态作用。如果将构造函数声明为虚函数,则表现为在对象还没有生成的时候来定义它的多态,这两点是不统一的。另外,构造函数不能被继承,因而不能声明为虚函数。

          4) 静态成员函数不能声明为虚函数。静态成员函数对于每个类来说只有一份代码,所有的对象都共享这份代码,它不归某个对象所有,所以也没有动态绑定的必要性。

          5) 内联(inline)成员函数不能声明为虚函数。内联函数就是为了在代码中直接展开,减少函数调用开销的代价。虚函数是为了在继承后对象能够准确的执行自己的动作,这是不可能统一的。另外,内联函数在编译时被展开,虚函数在运行时才能动态的绑定函数。

          6) 友元函数不能声明为虚函数。友元函数不属于类的成员函数,不能被继承

设置虚函数时须注意以下几点:

只有类的成员函数才能说明为虚函数;

静态成员函数不能是虚函数;

内联函数不能为虚函数;

构造函数不能是虚函数;

析构函数是虚函数,而且通常声明为虚函数。

继承,虚函数练习题2:

设计一个animal基类和它的派生类老虎和羊,要求用虚函数实现基类指针对派生类的调用;

//基于VS2017

// Test6_1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include 
#include 
#include 
using namespace std;
//基类
class Animal
{
protected:
       string gender;
public :
       Animal(){}
       Animal(string);
       virtual void soar()=0;
       void eat();
};
Animal::Animal(string gen)
{
       gender = gen;
}
void Animal::soar()
{}
void Animal::eat()
{}
//子类老虎
class Tiger :public Animal
{
public:
       Tiger(string);
       virtual void soar();
       void eat();
};
Tiger::Tiger(string gender)
{
       cout << "tiger is " << gender << endl;
}
void Tiger::soar()
{
       cout << "tiger's soar is 嗷嗷嗷嗷……" << endl;
}
void Tiger::eat()
{
       cout << "tiger eats meat mainly" << endl;
}
//子类羊
class Sheep :public Animal
{
public:
       Sheep(string);
       virtual void soar();
       void eat();
};
Sheep::Sheep(string gender)
{
       cout << "sheep is " << gender << endl;
}
void Sheep::soar()
{
       cout << "sheep's soar is 咩咩咩咩……" << endl;
}
void Sheep::eat()
{
       cout << "sheep eats grass mainly" << endl;
}
//主函数
int main()
{
       /* 程序说明:
       *  因无法做到将两个对象存入一个数组里,但能通过基类指针访问虚函数
       *  又因两个函数一个是虚函数,一个不是, 所以无法都用基类指针访问
       *  故只能实现一个用基类指针访问,一个用子类指针访问
       */
       vector animal;//使用vector基类指针实现
       vector ::const_iterator iter;
       Tiger tiger[2] = {Tiger( "male"),Tiger("female")};
       Sheep sheep[2] = {Sheep("female"),Sheep("male")};
       //将地址给基类指针实现访问
       animal.push_back(&tiger[0]);
       animal.push_back(&sheep[1]);
       animal.push_back(&tiger[1]);
       animal.push_back(&sheep[0]);
       Tiger *ptr1 = &tiger[0];
       Sheep *ptr2 = &sheep[1];
       Tiger *ptr3 = &tiger[1];
       Sheep *ptr4 = &sheep[0];
       int i = 0;
       for (iter = animal.begin(); iter != animal.end(); iter++)
       {
              i++;          
        (*iter)->soar();//访问虚函数soar()
              //访问普通函数eat()
              switch (i)
              {
              case 1:
                     ptr1->eat();
                     break;
              case 2:
                     ptr2->eat();
                     break;
              case 3:
                     ptr3->eat();
                     break;
              case 4:
                     ptr4->eat();
                     break;
              }
       }
       system("pause");
       return 0;
}

多重继承的构造函数和析构函数:

1)如果基类中无虚基类:

按照派生类定义从左到右调用,析构则相反;

2)如果有基类中有虚基类,有以下原则:

     1)先执行虚基类构造函数,再执行非虚基类

     2)有多个虚基类,则按定义顺序调用

     3)虚基类由非虚基类派生,则先调用基类的构造函数

 

3)一个类可以有多个构造函数,但只能有一个析构函数(且析构函数无参)

     1)动态对象被删除,使用delete,编译系统会调用析构函数;

     2)程序结束;

     3)一个编译器生成的临时对象不再需要, 自动调用;

 

类的聚集——对象成员

       类对象作为数据成员,先调用对象成员的构造函数,再调用本身的构造函数;

 

类成员

1)类外无需static修饰,类内需要,类内定义限制作用域;

2)静态成员一旦被初始化,以后再创建新对象,都无需再初始化;

3)静态成员和成员函数无this指针,需要通过类名或者对象类调用,一般的this指针指向自身,可以直接访问非静态的数据成员;

 

 

纯虚函数:

1)作用:在基类中没有具体实现的虚函数,留给派生类去实现

2)定义方式

class <类名>

{

virtual <类型><函数名>(<参数表>)=0;

};

3)凡是含有纯虚函数的类叫做抽象类

4)一般而言纯虚函数的函数体是缺省的,但是也可以给出纯虚函数的函数体(此时纯虚函数变为虚函数),这一点经常被人们忽视,调用纯虚函数的方法为baseclass::virtual function.

如练习题2

 

虚析构函数:析构函数前面加Virtual

 

在基类析构函数前面加Virtual,则派生类的析构函数都自动为虚析构函数——>与普通成员函数一致

 

纯虚函数——>类似接口的概念

 

使用virtual关键字的是虚函数,函数声明的时候=0 没有方法实体的是纯虚函数

virtual int area(){a=0 return 0};虚函数

virtual int area() = 0;纯虚函数

即virtual <类型>(函数名)(参数表) =0;

注意:构造函数和析构函数不允许调用纯虚析构函数,运行出错

包含纯虚函数的类是抽象类,不能实例化对象,但是抽象类的子类可以实例化对象,实现纯虚函数这个方法;

基类的析构函数被修饰为虚函数,则基类的派生类的析构函数无论是否有virtual,都被修饰为虚函数;

你可能感兴趣的:(C++)