C++的运用方法十分巧妙,这得益于他的繁多的特性,同时也为我们的学习带来了压力。在本次博客中我将用一个示例来介绍多继承,虚继承,多态性以及友元函数的相关概念和我个人的理解。
设计类Person具有public, protected, private等不同属性的成员函数或变量,用于后续不同方式继承下权限访问参数的测试
class Person // A类
{
public:
virtual void returnCareer();
virtual ~Person();
Person();
string m_strName; //姓名
protected:
string m_strSex; //性别
private:
int m_nAge; //年龄
};
类Student通过public, protected, private等不同方式继承Person,在类B的成员函数中测试访问A的成员函数或变量;
class Student:public Person
{
public:
void setName(string strName = "")
{
m_strName = strName; //public变量在B成员函数中可以访问
}
void SetSex(string strSex="")
{
m_strSex = strSex;//public方式继承父类protect变量也可以访问
}//隐藏内联函数
void setAge(int age)
{
m_nAge = age; //以public方式继承的父类的private变量无法访问,该函数无法执行
}
inline string GetSex(); //显式内联函数
};
//在外部成员函数访问Student
void TestStudent()
{
Student stu;
stu.m_strName ="lll";
stu.m_strSex = "boy";//protect变量在外部无法被访问,该行代码无法运行
stu.m_nAge = 11;//private变量无法在外部函数中被访问
}
//使用protected来继承的话,原有自由度高于protected的变量都会被降级成protected
class Student:public Person
{
public:
void setName(string strName = "")
{
m_strName = strName; //public变量在B成员函数中可以访问
}
void SetSex(string strSex="")
{
m_strSex = strSex;//public方式继承父类protect变量也可以访问
}//隐藏内联函数
void setAge(int age)
{
m_nAge = age; //以public方式继承的父类的private变量无法访问,该函数无法执行
}
inline string GetSex(); //显式内联函数
};
//在外部成员函数访问Student
void TestStudent()
{
Student stu;
stu.m_strName ="lll"; //protect变量在外部无法被访问,该行代码无法运行
stu.m_strSex = "boy";//private变量无法在外部函数中被访问
stu.m_nAge = 11;//private变量无法在外部函数中被访问
}
//使用private来继承的话,原有自由度高于private的变量都会被降级成private
class Student:private Person
{
public:
void setName(string strName = "")
{
m_strName = strName;
}
void SetSex(string strSex="")
{
m_strSex = strSex;
}//隐藏内联函数
void setAge(int age)
{
m_nAge = age; //以private方式继承仍然无法访问父类的private变量
}
inline string GetSex(); //显式内联函数
};
//在外部成员函数访问Student
void TestStudent()
{
Student stu;
stu.m_strName ="lll"; //priavte变量在外部无法被访问,该行代码无法运行
stu.m_strSex = "boy";//private变量无法在外部函数中被访问
stu.m_nAge = 11;//private变量无法在外部函数中被访问
}
但在有时候,我们为了实现方法的多样性,我们不得不在子类访问父类的protected的变量(private的变量不能使用这个方法),这个时候我们就需要使用using方法了:
class Student:private Person
{
public:
using Person::m_strSex; //将基类的protected变量变为public访问级别
void setName(string strName = "")
{
m_strName = strName;
}
void SetSex(string strSex="")
{
m_strSex = strSex;
}//隐藏内联函数
void setAge(int age)
{
m_nAge = age;
}
inline string GetSex(); //显示内联函数
};
void TestStudent()
{
Student stu;
stu.m_strName ="lll";
stu.m_strSex = "boy"; //外部函数可以访问public公有变量
stu.m_nAge = 11;
}
但using方法有几个规则,就是:
为了测试友元类对基类的权限访问,我们这里新引入Teacher类作为Person类的友元类:
class Person // A类
{
public:
virtual void returnCareer();
virtual ~Person();
Person();
string m_strName; //姓名
protected:
string m_strSex; //性别
private:
int m_nAge; //年龄
//友原全局函数,友元类,友元成员函数
// friend void TestFriend();
friend class Teacher; //将Teacher 类设置为 Person类的友元类
};
class Teacher:protected Person //基类比protect高的级别都会被降低到protect的级别
{
public :
Teacher();
using Person::m_strName;//私有方法无法通过这个方法共有
void setAge(int age)
{
m_nAge = age; //友元函数可以访问从基类继承的私有变量
}
};
那么友元类可以访问基类的私有变量,这个特性对基类的派生类是不是也有效呢?这里我们使用上面继承于Person类的Student,来测试一下:
class Student:public Person //这里我们对B进行进一步完善,为后面的实验做准备
{
public:
~ Student();
void Person_Career(Person& p);
void Student_Career(Student& p);
void returnCareer();
using Person::m_strSex;
using Person::m_strName;
//使用这两行代码可以将因为继承方式采用private而导致sex和name两个属性改为public公开
// using Person::m_nAge;//派生类并没有继承基类的private方法,所以无法使用这行diamante
Student();
string m_nSubject;
void SetSex(string strSex="")
{
m_strSex = strSex;//内部函数可以访问prortect变量,外部不行
}//隐藏内联函数
private:
double m_nScore; //考试分数
protected:
string m_nNumber; //学生学号
inline string GetSex(); //显示内联函数
};
//测试在外部函数中访问派生类的私有变量
void TestTeacher()
{
Teacher tea;
tea.stu.m_nScore = 0; //无法访问派生类的私有变量m_nScore,该代码无法运行
}
//测试在成员函数中访问派生类的私有变量
Teacher::Teacher()
{
stu.m_nScore = 0; //无法访问派生类的私有变量m_nSccore,改代码无法运行
}
通过上述两个测试函数我们可以发现,基类的友元类可以访问基类的私有变量,但是却不能对基类的派生类产生这样的效果。
那么接下来我们来查看一下友元类的效果能不能代代传递给他的派生类,这里我们创建派生类Director继承Teacher:
class Director:public Teacher
{
public:
Director();
void testDirector();
Student stu;
};
//测试友元类的派生类的访问权限
Director::Director()
{
//经过测试这三行代码均运行失败
m_nAge = 0;//基类的私有变量无法访问
stu.m_nAge = 0;
stu.m_nScore =0; //基类的派生类的私有变量无法访问
}
通过上述实验,我们可以知道友元类的派生类并没有继承父类对于基类的访问权限,他对于基类的派生类仍然无法访问其私有变量。
在很多时候,我们在不确定到底需要构造什么类的时候,会提前定义基类在需要使用派生类的时候再将基类用派生类的构造器来构造。但这样会产生一个问题。那就是通过这样构造出来的类无法调用派生类继承并实现的函数,而是调用自己的函数。所以为了达到这样的效果,我们对父类的函数使用virtual修饰。
class Person // A类
{
public:
virtual void returnCareer(); //虚函数returnCareer
virtual ~Person(); //析构函数使用虚函数机制,方便实现多态性
Person();
string m_strName; //姓名
protected:
string m_strSex; //性别
private:
int m_nAge; //年龄
//友原全局函数,友元类,友元成员函数
// friend void TestFriend();
friend class Teacher; //将Teacher 类设置为 Person类的友元类
};
class Student:public Person //B类
{
public:
~ Student();
void Person_Career(Person& p);
void Student_Career(Student& p);
void returnCareer();
using Person::m_strSex;
using Person::m_strName;
//使用这两行代码可以将因为继承方式采用private而导致sex和name两个属性改为public公开
// using Person::m_nAge;//派生类并没有继承基类的private方法,所以无法使用这行diamante
Student();
string m_nSubject;
void SetSex(string strSex="")
{
m_strSex = strSex;//内部函数可以访问prortect变量,外部不行
}//隐藏内联函数
private:
double m_nScore; //考试分数
protected:
string m_nNumber; //学生学号
inline string GetSex(); //显示内联函数
};
//实现虚函数returnCareer
void Person::returnCareer(){
cout<<"I am a person."<<endl;
}
//实现从基类继承的虚函数
void Student::returnCareer(){
cout<<"I am a Student."<<endl;
}
int main()
{
cout << "Hello World!" << endl;
Person * stu = new Student(); //基类使用派生类的构造器
stu->returnCareer();//调用基类和派生类都实现的函数测试最终系统调用的是哪个
return 0;
}
最终输出结果为 I am a Student. 说明了当使用虚函数继承机制的时候,此时调用的函数是派生类的函数。
那么如果我们使得输入或输出参数在子类中是父类的指针或基类的引用,这样会发生什么呢?也就是我们将派生类输入形参类型是基类的函数,这个时候再调用又会发生什么呢?
void Student::Person_Career(Person* p) //传入的是基类数据
{
p->returnCareer();
}
void Student::Student_Career(Person &p) //测试引用和取地址符的区别
{
p.returnCareer();
}
int main()
{
cout << "Hello World!" << endl;
Person * stu = new Student();
Student * stu2 = new Student();
Student s;
Person & stu3 =s;
stu2->Person_Career(stu);
stu2->Student_Career(stu3);
return 0;
}
通过上述实验,我们发现无论引用还是取地址符,最终实现的结果都是调用派生类实现的方法,从而我们利用virtual虚函数继承方式真正的实现了该函数方法的多态性多态性。
在C++面向对象编程中还有一个非常重要的函数类–析构函数。析构函数用于当对象完成生命周期时触发,释放对象的空间。那么在多继承中实现析构函数的多态性也同样对系统的完备性有促进作用。
//实现基类和派生类的构造器,测试当类生命周期结束时调用的构造器
Person::~Person()
{
cout<<"Person Desuctor"<<endl;
}
Student::~Student()
{
cout<<"Student Desuctor"<<endl;
}
int main()
{
cout << "Hello World!" << endl;
Person * stu = new Student();
Student s;
Person & stu3 =s;//引用变量会自动在生命周期结束的时候调用析构函数
stu->~Person(); //指针变量不会自动调用析构函数,需要我们自己手动调用
return 0;
}
输出结果:
Student Desuctor
Person Desuctor
Student Desuctor
Person Desuctor
输出结果表明这两个变量即使都使用派生类方式创建,但最终既会调用派生类的构造器,也会调用基类的构造器。这实际上是C++特有的机制,通过这样会将原有变量所占的空间全部释放。
在本次多态性继承中,我们重点介绍关于虚继承的相关知识。为什么我们说虚继承是必要的呢,其实是因为C++与Java不同。拥有了多继承的特性。就是一个类别一次性可以继承多个类,这在一定程度增加了C++编程的灵活性,但也带来了更多的问题。我们以下面的例子来解释:
//这里我们让Director继承Person类的两个派生类
class Director:public Teacher ,public Student
{
public:
Director();
void testDirector();
Student stu;
};
//在构造器我们测试是否能够访问基类Person的公有变量
Director::Director()
{
m_strName = "";
}
//代码无法运行报错 error: member 'm_strName' found in multiple base classes of different types
那么这个是什么意思呢,我们用下面这张图来进行说明:
假设A拥有公有变量x,他的两个派生类B,C自然都继承了该变量x,那么这个时候如果D类同时继承B,C类,那么这个时候我们在D类中访问变量x。他到底是从B,还是C继承而来的呢?这就会引起冲突。这就需要我们使用虚继承修改继承方式来解决该问题。那么如何修改呢?如下图
也就是将A变为虚基类的,B,C使用虚继承来继承A。这样就不会出现上面D的变量冲突的事件:
class Student: virtual public Person //虚继承
{
public:
~ Student();
void Person_Career(Person* p);
void Student_Career(Person& p);
void returnCareer();
//使用这两行代码可以将因为继承方式采用private而导致sex和name两个属性改为public公开
// using Person::m_nAge;//派生类并没有继承基类的private方法,所以无法使用这行diamante
Student();
string m_nSubject;
void SetSex(string strSex="")
{
m_strSex = strSex;//内部函数可以访问prortect变量,外部不行
}//隐藏内联函数
private:
double m_nScore; //考试分数
protected:
string m_nNumber; //学生学号
inline string GetSex(); //显示内联函数
};
class Teacher: virtual public Person //基类比protect高的级别都会被降低到protect的级别
{
public :
Teacher();
void setAge(int age)
{
m_nAge = age;
}
Student stu;
};
class Director:public Teacher ,public Student
{
public:
Director();
void testDirector();
Student stu;
};
Director::Director()
{
m_strName = "";
}//代码成功运行
测试代码运行成功,说明使用虚继承可以解决上面的问题,这里其实还要说明一个问题。就是采用虚继承的话对原有派生类不会产生太大影响,是对最后同时继承多个虚继承派生类的派生类产生影响,所以加上virtual不会对原有函数产生过多影响。
通过本次实验,我掌握了多继承,多态性,虚继承,友元类的相关概念和具体如何使用虚函数来实现函数多态性。同时引用和指针变量的使用方式也需要重点区分,有时候会影响系统完备性和运行结果。