目录
设计类A含有私有变量a_pri_pro,在类A中友元给类B:
若只想给类TestA中“某个函数”基类A中的私有变量a_pri_pro的访问权限:
利用友元可以赋予某个类或某个函数访问基类的私有变量的权限,有三种模式:
测试友元类TestA的继承类Pro_TestA是否能访问基类A以及基类的继承类B的私有变量:
一、继承访问权限测试
第一步:设计类A
设计类A具有public, protected, private等不同属性的成员函数或变量
外部只能访问A类的public成员,如下图:
第二步:设计类B、类C、类D(继承于类A)
类B通过public的方式继承类A:
class B :public A
{
void setPuPro(string a_pro_pro = "")
{
a_pu_pro = "";//ok!
a_pro_pro = a_pro_pro;//ok!
//a_pri_pro = 0;//no!
}
};
从A类继承的成员变量或函数权限不变,类B不能访问A的private成员函数或变量,只能访问proteced以上的成员函数或变量,如下图:
在外部同样只能访问A类的public成员:
类C通过protected的方式继承类A:
class C :protected A
{
public:
void SetProPro(string a_pro_pro = "")
{
a_pu_pro = "";//ok!
a_pro_pro = a_pro_pro;//ok!
//a_pri_pro = 0;//no!
}
};
从A类继承的成员变量或函数父类中public权限降级为protected,类C不能访问A的private成员函数或变量,只能访问proteced以上的成员函数或变量
在外部不能访问A类的public及以下成员:
类D通过private的方式继承类A:
class D :private A
{
public:
void SetProPro(string pro_pro = "")
{
a_pu_pro = "";//ok!
a_pro_pro = pro_pro;//ok!
//a_pri_pro = 0;//no!
}
};
从A类继承的成员变量或函数父类中所有权限降级为private,类D不能访问A的private成员函数或变量,只能访问proteced以上的成员函数或变量
在外部同样不能访问A类的public及以下成员:
把原本A中的部分public成员重新提升为public:
方法一:在外部用get函数的方式访问protected及以下成员
class D :private A
{
public:
void SetProPro(string pro_pro = "")
{
a_pu_pro = "";//ok!
a_pro_pro = pro_pro;//ok!
//a_pri_pro = 0;//no!
}
string & GetPuPro()//这个函数保证了在外部可以访问A类的public成员
{
return a_pu_pro;
}
};
方法二:用using的方式访问protected及以下成员
class D :private A
{
public:
using A::a_pu_pro;
using A::a_pro_pro;//用using的方式访问protected及以下成员
void SetProPro(string pro_pro = "")
{
a_pu_pro = "";//ok!
a_pro_pro = pro_pro;//ok!
//a_pri_pro = 0;//no!
}
string & GetPuPro()//这个函数保证了在外部可以访问A类的public成员
{
return a_pu_pro;
}
};
main.cpp里的TestA()函数
void TestA()
{
A aObj;
aObj.a_pu_pro = "A PuPro is ok!";
//aObj.a_pro_pro = "A ProPro isn't ok!";//外部只能访问A的public成员
B bObj;
string proPro = bObj.GetProPro();//在外部用get函数的方式访问protected成员
bObj.a_pu_pro = "B PuPro is ok!";
//bObj.a_pro_pro = "B ProPro isn't ok!";//从A类以public继承下来可以访问public成员
C cObj;
//cObj.a_pu_pro = "C PuPro isn't ok!";//从A类以protected继承下来不可以访问public成员
cObj.SetProPro("");
//A aCObj = cObj; //按理论基类可以转为派生类,而派生类不能转为基类
D dObj;
//dObj.a_pu_pro = "D PuPro isn't ok!";//从A类以private继承下来不可以访问public成员
dObj.SetProPro("");
dObj.GetPuPro();
dObj.a_pu_pro = "";
dObj.a_pro_pro = "";
}
二、友元类继承测试
设计类A含有私有变量a_pri_pro,在类A中友元给类B:
class A {
public:
A();
string a_pu_pro;
void SetProPro(string pro_pro)
{
a_pro_pro = pro_pro;
}
protected:
string a_pro_pro;
private:
int a_pri_pro;
friend class B;//友元函数使得继承于classA的B可以访问A的私有变量a_pri_pro
};
若只想给类TestA中“某个函数”基类A中的私有变量a_pri_pro的访问权限:
class A;
class TestA {
public:
void TestFriend(A &a);//可以访问私有变量
void TestFriendA(A& a);//不能访问私有变量
};
class A {
public:
A();
string a_pu_pro;
void SetProPro(string pro_pro)
{
a_pro_pro = pro_pro;
}
protected:
string a_pro_pro;
private:
int a_pri_pro;
friend class B;//友元函数使得继承于classA的B可以访问A的私有变量a_pri_pro
//利用友元函数允许函数TestFriend(A& a)访问A的私有变量a_pri_pro
friend void TestA::TestFriend(A &a);
};
利用友元可以赋予某个类或某个函数访问基类的私有变量的权限,有三种模式:
private:
int a_pri_pro;
//友元使得继承于classA的B可以访问A的私有变量a_pri_pro
friend class B;//友元类
friend void TestFriendFun(A& a)//全集函数
{
a.a_pri_pro = 5;
}
//利用友元函数允许函数TestFriend(A& a)访问A的私有变量a_pri_pro
friend void TestA::TestFriend(A &a);//友元函数
类B是基类A的友元类,但B的继承类LittleB不是A的友元类,所以不能访问A的私有变量,类似于朋友的儿子不是我的朋友:
测试友元类是否能访问继承类的私有变量:
class A;
class B;
class TestA {
public:
void TestFriend(A &a);
//void TestFriendA(A& a);
void TestFriend(B& a);
};
class A {
public:
A();
string a_pu_pro;
void SetProPro(string pro_pro)
{
a_pro_pro = pro_pro;
}
protected:
string a_pro_pro;
private:
int a_pri_pro;
friend void TestFriendFun(A& a)//全集函数
{
a.a_pri_pro = 5;
}
//利用友元函数允许函数TestFriend(A& a)访问A的私有变量a_pri_pro
//friend void TestA::TestFriend(A &a);//友元函数
//友元使得继承于classA的B可以访问A的私有变量a_pri_pro
friend class B;//友元类
friend class TestA;//友元类可以访问基类继承类B继承于A的私有变量a_pri_pro
};
class B : public A
{
public:
void SetProPro(string pro_pro = "")
{
a_pu_pro = "";//ok!
a_pro_pro = pro_pro;//ok!
//a_pri_pro = 0;//no!
a_pri_pro = 0;//now ok!利用友元函数访问基类的私有变量
}
string GetProPro()
{
return a_pro_pro;
}
int GetPriPro()
{
return 0;
a_pri_pro = 10;
}
private:
int b_pri_pro;
};
class LettleB : public B
{
public:
//int GetPriPro()
//{
// a_pri_pro = 10;
//}
};
友元类可以访问基类继承类B继承于A的私有变量a_pri_pro:
但不能访问基类继承类B新定义的私有变量b_pri_pro:
类似于他是我的朋友不是我儿子的朋友,所以不能访问儿子独有的隐私
测试友元类TestA的继承类Pro_TestA是否能访问基类A以及基类的继承类B的私有变量:
class Pro_TestA {
public:
void TestFriend(A& a);
void TestFriend(B& a);
};
都没有权限访问,原因类似于朋友的儿子不是我和我儿子的朋友 。
三、多态性综合运用
梳理:
多态性的便利:比如有十只猫,十只狗,十只鸡,我想要他们动起来,原本是需要分别对猫狗鸡进行for遍历,但如果利用了多态的性质,只需要制作一个动物列表,把猫狗鸡放入动物列表,再对动物类进行for循环,后续如果有新的动物需要同样操作,只需将其加入动物列表。
#ifndef CANIMAL_H
#define CANIMAL_H
#include <string>
using namespace std;
class CAnimal
{
public:
CAnimal(int nLegs);
CAnimal();
~CAnimal();
void Move();
protected:
int m_nLeg;
};
class CCat : public CAnimal
{
public:
CCat();
CCat(int nLegs);
~CCat();
void Move();
};
class CEagle : public CAnimal
{
public:
CEagle();
CEagle(int nLegs);
~CEagle();
void Move();
};
class COwl : public CCat,public CEagle
{
public:
COwl();
COwl(int nLegs);
~COwl();
~COwl();
void Move();
};
#endif // !CANIMAL_H
遇到的问题:不存在默认构造函数
解决:类里面需要有无参构造器,添加一个无参构造器即可。
多态性可以简单的概括为“1个接口,多种方法”,在程序运行的过程中才决定调用的机制
程序实现上是这样,通过父类指针调用子类的函数,可以让父类指针有多种形态。
虚函数
一般多态性函数:
输入输出参数完全一样,在父类中添加virtual;
为了实现通过父类指针调用子类的函数,我们需要在基类中声明函数时使用virtual关键字,这样的函数我们称为虚函数。一旦某个函数在基类中声明为virtual,那么在所有的派生类中该函数都是virtual,而不需要再显式地声明为virtual,下面来看一下有无virtual声明的效果对比:
CAnimal.h
class CAnimal
{
public:
CAnimal(int nLegs);
CAnimal();
~CAnimal();
void Move();
void Sleep();
void Breathe();
protected:
int m_nLeg;
};
class CFish :public CAnimal
{
public:
CFish();
void Breathe();
};
CAnimal.cpp
void CAnimal::Sleep()
{
cout << "animal sleep" << endl;
}
void CAnimal::Breathe()
{
cout << "animal breathe" << endl;
}
void CFish::Breathe()
{
cout << "fish bubble" << endl;
}
mian.cpp
int main()
{
CFish fh;
CAnimal* pAn = &fh;
pAn->Breathe();
return 0;
}
输出结果:
我们可以发现输出的结果还是沿用了基类的Breathe函数,从指针角度来看,函数调用的地址是固定的,因而始终调用一个函数;从内存模型的角度来看,这是因为我们在构造CFish类的对象时是先去调用CAnimal类的构造函数,再去调用CFish类的构造函数,再将两者拼成该对象,对于CAnimal中已存在的部分默认保存不做更改,所以当我们利用类型转换后的对象指针去调用它的方法时,输出的时animal breathe。
所以我们在基类CAnimal里对Breathe函数进行virtual声明:
class CAnimal
{
public:
CAnimal(int nLegs);
CAnimal();
~CAnimal();
void Move();
void Sleep();
virtual void Breathe();
protected:
int m_nLeg;
};
运行结果:
这次我们得到了期望的结果,基类中被重写的成员函数被设置为虚函数,指针指向对象所属类的虚表,在程序运行时,将根据指针指向对象的类型来确定想要调用的函数,从而在调用虚函数时,就能够找到正确的函数。
特殊多态性函数:
不仅在函数的实际调用过程中可以体现多态,函数的传参过程中也可以体现多态:输入或输出参数在父类中是父类的指针或基类的引用,在子类中对于的是子类的指针或子类的引用。
举个例子,写一个传入参数为基类Canimal的函数,若mian函数里传入的是派生类,我们期望的结果是函数依然调用派生类中对应的方法:
void Lived(CAnimal animal)
{
animal.Breathe();
}
int main()
{
CAnimal a1;
CFish fish2;
//CFish fh;
//CAnimal* pAn = &fh;
//pAn->Breathe();
Lived(a1);
Lived(fish2);
return 0;
}
输出:
animal breathe
animal breathe
显然这并不是我们想要的结果。
实现期望结果,方法有两种:
- 将传入参数改为基类的引用
void Lived(CAnimal *animal) { animal->Breathe(); } int main() { CAnimal a1; CFish fish2; //CFish fh; //CAnimal* pAn = &fh; //pAn->Breathe(); Lived(&a1); Lived(&fish2); return 0; }
- 将传入参数改为基类的指针
void Lived(CAnimal &animal) { animal.Breathe(); } int main() { CAnimal a1; CFish fish2; //CFish fh; //CAnimal* pAn = &fh; //pAn->Breathe(); Lived(a1); Lived(fish2); return 0; }
两种方法都能得到想要的结果:
animal breathe
fish bubble
析构函数的多态性:
一个类中不可缺少的还有析构函数,目的是防止内存泄漏,当我们用基类的指针创建一个子类的对象,子类虽然继承了基类,但是没有重写基类的析构函数,因此同样会有内存泄漏的危险:
class CAnimal
{
public:
CAnimal(int nLegs);
CAnimal();
void Move();
void Sleep();
virtual void Breathe();
~CAnimal()
{
cout << "Destroy CAnimal" << endl;
}
protected:
int m_nLeg;
};
class CFish :public CAnimal
{
public:
void Breathe();
~CFish()
{
cout << "Destroy CFish" << endl;
}
};
int main()
{
//CAnimal a1;
//CFish fish2;
//CFish fh;
//CAnimal* pAn = &fh;
//pAn->Breathe();
//Lived(a1);
//Lived(fish2);
CAnimal* fish = new CFish;
fish->Breathe();
delete fish;
return 0;
}
fish bubble
Destroy CAnimal
派生类CFish有自己的析构函数,但是调用的确实基类的。
这时我们只要把基类的析构函数设置为virtual虚函数:
virtual ~CAnimal()
{
cout << "Destroy CAnimal" << endl;
}
输出:
fish bubble
Destroy CFish
Destroy CAnimal
这就是我们想要的结果。
纯虚函数:
虚构函数里还有一个特殊的成员——纯虚函数;其实就是在成员变量后面加上=0:
class Mammals//哺乳动物类
{
public:
int legs = 0;
virtual void breathe() const = 0;
};
纯虚函数的特点:
- 纯虚函数没有函数体,即不需要具体实现;
- 最后面的“=0”并不表示函数的返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”;
- 纯虚函数不具备函数的功能,不能被调用;
- 含有纯虚拟函数的类称为抽象类,它不能生成对象;
- 抽象类的派生类中必须实现抽象方法。
纯虚函数后面常跟const,这只是为了说明该函数为“只读函数”,同时保证了代码的健壮性。
纯虚函数引入的意义:比如哺乳动物这个抽象类底下有有老虎、海豚等派生类,这些具体动物类都可以实际生成对象,但哺乳动物本身却不应该生成具体的对象;很多情况下,基类本身生成对象是不合逻辑的,纯虚函数保证了逻辑的合理性。