目录
一、继承的数据权限变化之后的权限突破
继承之后,父类数据到子类之后的权限变化之后,对数据权限的突破,
主要有三种方法:
公开外部接口
友元函数
友元类
二、友元类
在哪个类中声明友元类,就获得对这个类的私有成员的访问权力.
看程序举例:
#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的