/****************************************/
派生类继承基类成员和方法
派生类构造函数:用于初始化派生类新增成员和方法
基类构造函数:用于初始化基类继承的成员和方法
构造函数执行顺序:先调用基类构造函数初始化继承成员和变量,再调用派生类构造函数,初始化新增成员和方法;
析构函数则相反
基类与派生类的特殊关系:
派生类可以访问基类中不是私有的成员变量和方法,如果是私有,则需要通过基类的公有方法来访问;
基类不能调用派生类的方法和成员;(父母无私奉献给孩子,孩子却有所保留);
基类指针可以在不进行显式类型转换的情况下指向派生类对象,基类引用可以在不进行显式类型转换的情况下指向派生类对象;(如下面的程序)
下面程序还要注意一个知识点:就是继承怎么写
虚基类:
从基类派生新类时,使用关键字virtual可以将基类说明成虚基类,
特点:虚基类只初始化一次
虚基类对象是由最直接派生类的构造函数通过调用虚基类进行初始化的,虚基类优先于非虚基类的构造函数执行;
/**************************************************/
基于Visual studio 2017
继承、派生类、虚基类的练习题:
//未加虚基类的但可以实现完整输出的程序
#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)
{}
int main()
{
Student stud1(1170230000, "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;
}
//未使用虚基类也可以输出的程序
//缺点是:如果一个继承的层级很深,有多重继承,那就需要定义多个指针,使得程序冗杂,所以虚基类就很有用了
#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)
{}
int main()
{
Student stud1(1170230001, "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;
}
//使用虚基类来简化
#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)
{}
int main()
{
Student stud1(1170230003, "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. 在派生类中重新定义此函数,要求函数名、函数类型、函数参数个数和类型全部与基类的虚函数相同,并根据派生类的需要重新定义函数体。
C++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此在派生类重新声明该虚函数时,可以加virtual,也可以不加,但习惯上一般在每一层声明该函数时都加virtual,使程序更加清晰。如果在派生类中没有对基类的虚函数重新定义,则派生类简单地继承其直接基类的虚函数。
3. 定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象。
通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。
通过虚函数与指向基类对象的指针变量的配合使用,就能方便地调用同一类族中不同类的同名函数,只要先用基类指针指向即可。如果指针不断地指向同一类族中不同类的对象,就能不断地调用这些对象中的同名函数。这就如同前面说的,不断地告诉出租车司机要去的目的地,然后司机把你送到你要去的地方。
虚函数与重载函数的关系:
1. 重载函数:要求函数有相同的函数名称,不同的参数列表;
虚函数:要求函数有相同的函数名称,相同的参数列表。
2. 重载函数可以是成员函数也可以是友元函数;而虚函数只能是成员函数。
3. 重载函数的调用是以所传递参数序列的差别作为调用不同函数的依据,而虚函数则是根据传入的对象的不同去调用不同类的虚函数的;
4. 虚函数在运行时表现出多态,二重载在编译时表现出多态性;
对于第3点的解释与拓展:
(
首先友元函数并不属于类的成员函数,没有this指针,只能用对象名访问;
在华为、中兴等企业招聘笔试面试中曾经出现过这样一道题目:什么函数不能声明为虚函数?该题答案为:普通函数(非成员函数)、静态成员函数、内联成员函数、构造函数、友元函数
解释如下:
虚函数通过继承方式来体现出多态作用,它必须是基类的非静态成员函数,其访问权限可以是protected或public,在基类的类定义中定义虚函数的一般形式是:
virtual 函数返回值类型虚函数名(形参表){ 函数体 }
常见的不能声明为虚函数的有:普通函数(非成员函数)、静态成员函数、内联成员函数、构造函数、友元函数。
(1)普通函数不能声明为虚函数。普通函数(非成员函数)只能被重载(overload),不能被重写(override),声明为虚函数也没有什么意思,因此编译器会在编译时绑定函数。
(2) 构造函数不能声明为虚函数。构造函数一般用来初始化对象,只有在一个对象生成之后,才能发挥多态作用。如果将构造函数声明为虚函数,则表现为在对象还没有生成的时候来定义它的多态,这两点是不统一的。另外,构造函数不能被继承,因而不能声明为虚函数。
(3) 静态成员函数不能声明为虚函数。静态成员函数对于每个类来说只有一份代码,所有的对象都共享这份代码,它不归某个对象所有,所以也没有动态绑定的必要性。
(4) 内联(inline)成员函数不能声明为虚函数。内联函数就是为了在代码中直接展开,减少函数调用开销的代价。虚函数是为了在继承后对象能够准确的执行自己的动作,这是不可能统一的。另外,内联函数在编译时被展开,虚函数在运行时才能动态的绑定函数。
(5) 友元函数不能声明为虚函数。友元函数不属于类的成员函数,不能被继承
只有类的成员函数才能说明为虚函数;
静态成员函数不能是虚函数;
内联函数不能为虚函数;
构造函数不能是虚函数;
析构函数可以是虚函数,而且通常声明为虚函数。)
这里再复习一下友元:
/**************************************************友元类**************************************************/
上述注意:1.首先,友元类B中的参数需要传入一个类A的对象,才能引用A中的各种成员;
2.也就是声明友元类时,友元类中所有的函数变为了友元函数。 而后面派生新加的函数则不为友元函数。
#include
using namespace std;
class A
{
protected:
int a,b;
public:
virtual void sum(int aa,int bb)
{
a=aa;
b=bb;
cout<<"a+b="<
关于友元类的注意事项:
(1) 友元关系不能被继承。
(2) 友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。
(3) 友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的申明。
//定义了类C是类B的友元类,但是当访问类A的成员时,报错:成员是受保护的
除非是在A中定义其为友元函数才可行。
#include
using namespace std;
class A
{
protected:
int a,b;
public:
virtual void sum(int aa,int bb)
{
a=aa;
b=bb;
cout<<"a+b="<
总结友元类:
类比理解:一个朋友B到朋友A那里去参观油画,恰巧朋友A不在家,但得到了朋友A的权限,打开了朋友家的门等候,B就有机会欣赏A家中的油画;(可以访问私有成员)
但每次都需要得到A的权限,且只有B本人一个人有效;这里A家就相当于是私有成员;同样,如果A想去B家中参观,也必须得到B的权限。而不是因为A给B权限,B就能让A随意进出自己家(友元不具有交换性)
1.这时如果B的朋友C也慕名想到A家去参观,但因为没有得到A的权限,所以他进不去参观;
2.如果基类是某类的友元,这种友元关系同样不被继承的,即其派生类不因继承也成为那个类的友元
(友元类不具有传递性)
/*******************************友元函数*************************************/
类在内部声明,在类外定义;
/*******************************************************************/
友元使用
优点:
可以灵活地实现需要访问若干类的私有或受保护的成员才能完成的任务;
便于与其他不支持类概念的语言(如C语言、汇编等)进行混合编程;
通过使用友元函数重载可以更自然地使用C++语言的IO流库。
缺点:
一个类将对其非公有成员的访问权限授予其他函数或者类,会破坏该类的封装性,降低该类的可靠性和可维护性。