【C++】day07 - 【继承的权限突破】【友元类】【继承中的构造、析构、拷贝构造、赋值运算符函数】【名字隐藏机制】【多继承】【钻石继承】【虚继承】【函数重写(覆盖)】【多态】

一、继承的数据权限变化之后的权限突破

继承之后,父类数据到子类之后的权限变化之后,对数据权限的突破,
主要有三种方法:
	公开外部接口
	友元函数
	友元类

二、友元类

在哪个类中声明友元类,就获得对这个类的私有成员的访问权力.
看程序举例:
#include <iostream>
using namespace std;
class A{
    
    
	int x;
	int y;
	public:
	A():x(10),y(123){
    
    
		
	}
	friend class B;
	friend class C;
};
class B :public A{
    
    
	public:
	void show(){
    
    
		cout << x << ":" << y << endl;
	}
};
class C{
    
    
	public:
	void show(){
    
    
		A a;
		cout << a.x << ":" << a.y << endl;
	}
};
int main(){
    
    
	B b;
	b.show();
	c.show();
}

三、继承中构造函数和析构函数的调用顺序

构造函数和析构函数是不能被继承的,但可以调用。
子类一定会调用父类的构造函数。
子类默认调用父类的无参构造,也可以指定调用构造函数。
析构函数的调用和构造的调用顺序相反。
程序举例:
#include <iostream>
using namespace std;
class A{
    
    
	int x;
	public:
	A(){
    
    
		cout << "A()" << endl;
	}
	A(int x){
    
    cout << "A(int)" << endl;}
	~A(){
    
    
		cout << "~A()" << endl;
	}
};
class B:public A{
    
    
	public:
	B():A(100){
    
    /*
	在初始化参数列表中可以指定调用父类的构造函数。
	即利用A的有参构造函数,充当A私有成员的外部接口作用。*/
		cout << "B()" << endl;
	}
	~B(){
    
    
		cout << "~B()" << endl;
	}
};
	
int main(){
    
    
	B b;
	/*结果
		A(int)说明:这是因为在有参构造函数时,首先调用有参构造函数
		B()
		~B()
		~A()
	*/
}

四、继承中的拷贝构造函数 和 赋值运算符函数

拷贝构造函数和赋值运算符也不能被继承
但子类不提供拷贝构造 和 赋值运算符时,子类默认调用父类的拷贝构
	造函数。
但子类一旦提供拷贝构造函数 和 赋值运算符函数,则不再调用父类的
	拷贝构造函数 和 赋值运算符。
假如我们子类提供了拷贝构造函数和赋值运算符函数:
	拷贝构造函数在初始化参数列表中调用即可
	赋值运算符函数需要使用类名::
程序举例:
#include <iostream>
using namespace std;
class A{
    
    
	public:
	A(){
    
    
		
	}
	A(const A& a){
    
    
		cout << "A(const A&)" << endl;
	}
	A& operator=(const A& a){
    
    
		cout << "operator=(const A&)" << endl;
	}
};
/*如果B提供了拷贝构造函数,会带来哪些问题?如何解决?*/
class B:public A{
    
    
	int *pi;
	public:
	B(){
    
    }
	B(const B& b):A(b){
    
    /*B子类有了拷贝构造,同时又想调用父类的拷贝
	构造怎么办?答:在初始化参数列表调用A的有参构造函数。
	即既调父类的拷贝构造函数,又调子类的拷贝构造函数*/
		cout << "B(B&)" << endl;
	}
	B& operator=(const B& b){
    
    
		A::operator=(b);/*调用A的赋值运算符函数,简介实现同时使用
					父类和子类赋值运算符函数的使用*/
		cout << "operator=(B&)" << endl;
	}
};
int main(){
    
    
	B a;
	B b=a;//则调用拷贝构造
	/*B b;
	b=a;调用赋值运算符函数*/
}

五、名字隐藏机制(name hide)

4.1概念

	子类中如果定义了和父类型同名的数据,则会把父类中的数
		据隐藏掉。
		名字隐藏时对函数的返回值和参数列表没有限制,但是函数
			设计了参数,调用会有歧义,因此调用时应该传参。
		需要调用父类的数据,需要在数据前加类型::

4.2举例

#include <iostream>
using namespace std;
class Animal{
    
    
	public:
	int x;
	static int acount;
	Animal(int x=100):x(x){
    
    }
	void show(){
    
    
		cout << "this is Animal show()" << endl;
	}
};
int Animal::acount=99;
class Dog:public Animal{
    
    
	public:
	int x;//则父类的x被隐藏了
	static int acount;
	void show(){
    
    
		Animal::show();//调父类的show()
		cout << "this is a dog" << endl;
	}
};
int Dog::acount=100;
int main(){
    
    
	Dog dog;
	cout << dog.x << endl;
	cout << dog.Animal::x << endl;/*虽然父子类的两个x相同,导致
	父类x被隐藏了,但是我们可以使用dog.Animal::x的方式来调用。
	注意:同样对于同名的函数,我们的做法也是这样*/
	dog.show();//调子类的show()
	dog.Animal::show();//调父类的show()
	cout << Animal::acount << endl;//99,来源于父
	cout << Dog::acount << endl;//100。来源于子类
	cout << Dog::Animal::acount << endl;//99,来源于父类
}

六、多继承

6.1概念

	一个类可以有多个直接父类。
	构造函数的调用顺序和继承的顺序有关,和其他任何情况都无关。
		析构函数调用的顺序和构造函数顺序相反。
	子类可以调用父类不重名的数据,重名的数据可以通过类名作用域
		区分,也可以使用名字隐藏机制。

6.2举例

#include <iostream>
using namespace std;
class Phone{
    
    
	private:
	double price;
	public:
	Phone(double price):price(price){
    
    
		cout << "Phone()" << endl;
	}
	~Phone(){
    
    cout << "~Phone()" << endl;}
	double getPrice()const{
    
    return price;}
	void call(){
    
    
		cout << "使用Phone打电话" << endl;
	}
};
class Mp3{
    
    
	private:
	double price;
	public:
	Mp3(double price):price(price){
    
    
		cout << "Mp3()" << endl;
	}
	~Mp3(){
    
    cout << "~Mp3()" << endl;}
	double getPrice()const{
    
    return price;}
	void play(){
    
    
		cout << "使用Mp3听音乐" << endl;
	}
};
class Camera{
    
    
	private:
	double price;
	public:
	Camera(double price):price(price){
    
    
		cout << "Mp3()" << endl;
	}
	~Camera(){
    
    cout << "~Mp3()" << endl;}
	double getPrice()const{
    
    return price;}
	void camera(){
    
    
		cout << "使用Camera拍照片" << endl;
	}
};
class IPhone:public Phone,public Mp3,public Camera{
    
    
	public:
	double getPrice(){
    
    
		return Phone::getPrice()+
				Mp3::getPrice()+
				Camera::getPrice();
	}
};
int main(){
    
    
	IPhone ipone6;
	/*构造函数调用顺序:Phone、Mp3、Camera
	析构函数调用顺序:Camera、Mp3、Phone*/
	ipone6.call();
	ipone6.play();
	ipone6.camera();
	ipone6.getPrice();//可以,子类中的getPrice函数
	ipone6.Mp3::getPrice();//可以
}

6.3把父类中公共的数据部分抽象到更高层的类中

	(5.3和5.2不一样,5.2是让IPhone继承Phone、Mp3、Camera
	而5.3是让Phone、Mp3、Camera继承Product。
	你看,5.2中的Phone、Mp3、Camera各有一个getPrice(),现在
	我们在5.3中把getPrice集成到三者父类Product中)
	程序举例:
#include <iostream>
using namespace std;
class Product{
    
    
	double price;
	public:
	double getPrice()const{
    
    return price};
	Product(double price=0):price(price){
    
    }
	
};
class Phone:public Product{
    
    
	public:
	Phone(double price):Product(price){
    
    
		cout << "Phone()" << endl;
	}
	~Phone(){
    
    cout << "~Phone()" << endl;}
	void call(){
    
    
		cout << "使用Phone打电话" << endl;
	}
};
class Mp3:public Product{
    
    
	public:
	Mp3(double price):Product(price){
    
    
		cout << "Mp3()" << endl;
	}
	~Mp3(){
    
    cout << "~Mp3()" << endl;}
	void play(){
    
    
		cout << "使用Mp3听音乐" << endl;
	}
};
class Camera:public Product{
    
    
	public:
	Camera(double price):Product(price){
    
    
		cout << "Mp3()" << endl;
	}
	~Camera(){
    
    cout << "~Mp3()" << endl;}
	void camera(){
    
    
		cout << "使用Camera拍照片" << endl;
	}
};
class IPhone:public Phone,public Mp3,public Camera{
    
    
	public:
	double getPrice(){
    
    
		return Phone::getPrice()+
				Mp3::getPrice()+
				Camera::getPrice();
	}
};
int main(){
    
    
	IPhone ipone6;
	/*构造函数调用顺序:Phone、Mp3、Camera
	析构函数调用顺序:Camera、Mp3、Phone*/
	
	cout << sizeof(ipone6) << endl;//24
	ipone6.call();
	ipone6.play();
	ipone6.camera();
	ipone6.getPrice();//可以,子类中的getPrice函数
	ipone6.Mp3::getPrice();//可以
}

6.4钻石继承(菱形继承)、虚继承、 virtual关键字

	上面5.3的继承其实是钻石继承,如下图所示。其容易引起冲突。

在这里插入图片描述

	我们可以用虚继承解决。
	钻石继承其实就是一个类有多个子类,而这多个子类又有共同的
		共同的子类。
	钻石继承会让底层的类 复制 顶层类多份数据,从而引起冲突
		为了解决这个问题,我们使用虚继承(virtual)。即孙子类
		直接绕过父类,越级直接去访问爷爷类。所达到的效果是:
			孙子IPone这个孙子和三个爸爸具有同样的权力去访问
			爷爷,也就是说孙子可以直接访问爷爷了。原理如下图:

在这里插入图片描述

	程序举例:
#include <iostream>
using namespace std;
class Product{
    
    
	double price;
	public:
	double getPrice()const{
    
    return price};
	Product(double price=0):price(price){
    
    }
};
class Phone:virtual public Product{
    
    
	public:
	Phone(double price):Product(price){
    
    
		cout << "Phone()" << endl;
	}
	~Phone(){
    
    cout << "~Phone()" << endl;}
	void call(){
    
    
		cout << "使用Phone打电话" << endl;
	}
};
class Mp3:virtual public Product{
    
    
	public:
	Mp3(double price):Product(price){
    
    
		cout << "Mp3()" << endl;
	}
	~Mp3(){
    
    cout << "~Mp3()" << endl;}
	void play(){
    
    
		cout << "使用Mp3听音乐" << endl;
	}
};
class Camera:virtual public Product{
    
    
	public:
	Camera(double price):Product(price){
    
    
		cout << "Mp3()" << endl;
	}
	~Camera(){
    
    cout << "~Mp3()" << endl;}
	void camera(){
    
    
		cout << "使用Camera拍照片" << endl;
	}
};
class IPhone:virtual public Phone,public Mp3,public Camera{
    
    
	/*孙子只从爷爷那里拿了一份数据,因此孙子可以直接
			访问爷爷*/
	IPhone(double m=0,double p=0,double c=0)
	:Product(m+p+c){
    
    //把m+p+c直接给爷爷
	}	
};
int main(){
    
    
	IPhone iphone6(50,1000,2500);
	cout << siizeof(Mp3) << endl;/*12字节。如果MP3不加virtual
							则为8字节(即Product的大小),现在
							多出的4字节是加了virtual所付出的代价*/
	cout << iphone6.getPrice() << endl;/*3500
	由于使用了virture关键字,因此孙子只从爷爷那里拿了一份数
	据,因此孙子可以直接调用爷爷的detPrice()。这就是虚继承。*/
	
	iphone6.call();
	cout << sizeof(iphone6) << endl;/*20字节。8字节为double price
				12个字节是因为孙子的三个爸爸使用了virturl*/
}

七、虚函数

7.1概念

	如果在一个成员函数上加了virtual修饰之后,则这个函数成为虚函数。
	一个类型只要出现虚函数,则会出现一个指针维护这些虚函数。

7.2举例

#include <iostream>
using namespace std;
class A{
    
    
	int x;
	public:
	virtual void showa(){
    
    }
	virtual void showb(){
    
    }
};
int main(){
    
    
	cout << sizeof(A) << endl;/*8字节,因为有show()函数有virtuual。其实
	增加的4字节是一个指针,其管理着左右的有virtual修饰的函数
	*/
}

八、函数重写(over write)

8.1概念

函数重写也叫做函数覆盖。即父类和子类的普通函数重名的话,那么子类的这个函数就
	会把父类的同名函数覆盖掉,即执行的是子类的这个函数。
在父类中出现一个虚函数,如果在子类中提供和父类同名的虚函数,这叫函数
	重写。注意,如果父类的函数有virtual,则子类不管你写不写virtual都
	会默认为有virtual修饰。
函数重写要求必须有相同的函数名,相同的参数列表,相同的返回值。

8.2程序举例

#include <iostream>
using namespace std;
class Animal{
    
    
	public:
	virtual void run(){
    
    
		cout << "动物的跑" << endl;
	}
	void show(){
    
    
		cout << "动物的show()" << endl;
	}
};
class Dog:public Animal{
    
    
	public:
	/*函数重写*/
	virtual void run(){
    
    //virtual加不加都是虚函数
		cout << "狗用四条腿跑" << endl;
	}
	/*名字隐藏*/
	void show(){
    
    
		
	}
	
};
int main(){
    
    
	/*编译期就可以确定调用的函数*/
	Dog dog;
	dog.run();
	dog.show();
	
	int x=0;
	cin >> x;
	Animal *animal;
	if(0==x){
    
    
		animal=new Animal();
	}
	else if(1==x){
    
    
		animal=new Dog();
	}
	/*run函数的调用不是在编译期决定的,而是在程序运行过程中确定的,这种
	称为动态绑定*/
	animal->run;//调哪个run(),取决于你输入的0/1
	animal->show();//调Animal的show()
}

8.3 函数重载、函数重写、函数隐藏的区别

	over load	同一作用域函数名相同,参数列表不同
	over write  父子类之间,子类提供了和父类重名的虚函数
	over hide	父子类之间,子类提供了和父类同名的数据

总结一下:

所谓函数重载,就是系统已经默认提供了相关函数,而你想要自己定义此函数,一旦你自己定义了这个函数,那么系统就会使用你写的函数而丢弃系统原本默认的。

所谓函数重写,一般父类和子类的某个函数都会有virtual修饰,且此二者函数重名。n个重名的函数共用一个指针,此指针指向哪一个函数,则你就会调用那一个函数。至于使用哪个取决于:一般和多态一块用,就是说:如Animal *pdog = new Dog();new的是Dog,那么就调用的是Dog中的重名函数,new的是Animal那么就调用的是Animal中的重名函数(注:Animal是父类,Dog是子类)。
Animal *pdog = new Dog();这种写法即new一个子类对象给父类指针就是多态的特征写法。
所谓函数隐藏,就是你的父类和子类某函数重名了。至于使用哪个取决于:你的对象是什么类型的,就使用那个类型中的函数(重名的函数)。

九、多态

9.1概念

一个父类型的对象的指针或者引用,指向(引用)一个子类对象时,调用父类
	型中的虚函数,如果子类覆盖了这个虚函数,则调用的表现是子类覆盖之后
	的。
多态的三个条件,缺一不可:
	继承是构成多态的基础
	调用父类的虚函数构成多态的关键
	函数覆盖(也叫函数重写)是必备条件 

9.2多态的应用

	①函数的参数
	②函数的返回值
	程序举例
#include <iostream>
using namespace std;
class Animal{
    
    
	public:
	virtual void run(){
    
    
		cout << "动物的跑" << endl;
	}
	void show(){
    
    
		cout << "动物show()" << endl;
	}
};
class Dog:public Animal{
    
    
	public:
	void run(){
    
    
		cout << "狗用四条腿跑" << endl;
	}
	void show(){
    
    
		cout << "狗show()" << endl;
	}
};
class Cat:public Animal{
    
    
	public:
	void run(){
    
    
		cout << "猫走猫步" << endl;
	}
};
void showAnimal(Animal *animal){
    
    /*可以传Animal、Dog、Cat类型。注意参数
									必须是指针或引用类型*/
	animal->show();//show()不是虚函数,所以不满足多态
	animal->run();
}
Animal* getAnimal(int x){
    
    /*多态用于函数的返回值,可以返
										回Animal、Dog、Cat类型*/
	if(1==x){
    
    
		return new Dog();
	}
	if(2==x){
    
    
		return new Cat();
	}
}
int main(){
    
    
	Cat cat;
	showAnimal(&cat);
	Dog dog;
	showAnimal(&dog);
	/*运行结果:
		动物show
		猫走猫步
		动物show
		狗用四条腿跑
	*/
	dog.show();//调子类的,因为父类的被隐藏;并且show()根本不满足多态啊
}

9.3多态的原理

	先搞清楚三个概念:
		虚函数			成员函数加了virtual
		虚函数表指针		一个类型有虚函数,则对这个类型提供一个指针,这
						指针放在生成对象的前四个字节。
						同类型的对象共享一种虚函数表,不同类型的虚函数
						表是不一样的。
		虚函数表 		虚函数表中的每个元素都是虚函数的地址。
	原理看下图:
	
	我们写一个例子:

在这里插入图片描述

#include <iostream>
using namespace std;
class Animal{
    
    
	public:
	virtual void fun(){
    
    
		cout << "动物的作用" << endl;
	}
	virtual void  run(){
    
    
		cout << "动物的跑" << endl;
	}
	void show(){
    
    
		cout << "动物show()" << endl;
	}
};
class Dog:public Animal{
    
    
	public:
	virtual void fun(){
    
    
		cout << "狗看家" << endl;
	}
	virtual  void run(){
    
    
		cout << "狗用四条腿跑" << endl;
	}
};
class Cat:public Animal{
    
    
	public:
	void fun(){
    
    
		cout << "抓老鼠" << endl;
	}
};
int main(){
    
    
	Animal a;
	Animal b;
	int *pi=(int*)&a;
	cout << showbase << hex << *pi << endl;//C语言强制类型转换
	/*输出a对象的前4字节,发现是一个指针(虚函数指针)*/
	pi = reinterpret_cast<int*>&b;//c++类型转换
	cout << showbase << hex << *pi << endl;//发现和a的虚函数指针一样
	Dog dog;
	pi = reinterpret_cast<int*>&dog;
	cout << showbase << hex << *pi << endl;//发现和a的虚函数指针不一样
	
	Animal *pcat = new Cat();
	pcat->fun();//Cat的
	pcat->run();//Animal的
	
	Animal *pdog = new Dog();
	pdog-fun();//狗的
	pdog->run();//狗的
	memcpy(pdog,pcat,4);//把Cat的前四个字节(虚函数表指针)移动到pdog对象
	pdog-fun();//猫的
	pdog->run();//猫的
}
	我们对上例画原理图示意:
	下图为pcat的

在这里插入图片描述
下图为pdog的
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_45519751/article/details/108380777
今日推荐