C++(二)

目录

一、继承访问权限测试

第一步:设计类A

第二步:设计类B、类C、类D(继承于类A)

类B通过public的方式继承类A:

类C通过protected的方式继承类A:

类D通过private的方式继承类A:

把原本A中的部分public成员重新提升为public:

二、友元类继承测试

设计类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

显然这并不是我们想要的结果。

实现期望结果,方法有两种:

  1. 将传入参数改为基类的引用
    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;
    }
  2. 将传入参数改为基类的指针
    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;
};

 纯虚函数的特点:

  1. 纯虚函数没有函数体,即不需要具体实现;
  2. 最后面的“=0”并不表示函数的返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”;
  3. 纯虚函数不具备函数的功能,不能被调用;
  4. 含有纯虚拟函数的类称为抽象类,它不能生成对象;
  5. 抽象类的派生类中必须实现抽象方法。

纯虚函数后面常跟const,这只是为了说明该函数为“只读函数”,同时保证了代码的健壮性。

纯虚函数引入的意义:比如哺乳动物这个抽象类底下有有老虎、海豚等派生类,这些具体动物类都可以实际生成对象,但哺乳动物本身却不应该生成具体的对象;很多情况下,基类本身生成对象是不合逻辑的纯虚函数保证了逻辑的合理性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值