c++面向对象三大特征封装、继承和多态知识总结
面向对象三大特征:封装,继承,多态;
一、封装:该公开的就公开话,该私有的就隐藏掉,主要是由public,private实现;作用是便于分工和分模块,防止不必要的扩展;
二、继承:就是一种传承,可以把父类型中的数据传承到子类中,子类除了传承了父类的数据之外,还可以对父类型进行扩展;
公开继承 public
保护继承 protected
私有继承 private
保护成员:在子类和本类中可以访问,其他不行;
1、公开继承:在公开继承下,父类型中的数据是公开的到子类型中权限是公开的;父类型中保护权限的数据到子类中是保护的;父类中私有的数据到子类中会隐藏掉(就是说看不见权限,但是实际上式在子类中的);
2、私有继承:在私有继承下,父类中的公开数据到子类中变成私有的,父类中的保护数据到子类中称为私有的,父类中的私有数据到子类中隐藏;
3、保护继承:保护继承下,父类中的公开数据和保护数据到了子类中都成为保护权限,父类中私有的数据到了子类中就变成了隐藏的;
4、注意:不管何种类型的继承关系,父类私有成员到子类中都成为了隐藏掉。
公开继承下的public成员和protected成员的权限变化:
- #include <iostream>
- using namespace std;
- class A{
- public:
- int a;
- int geta(){
- a = 300;
- return a;
- }
- /*保护类型成员在本类和子类中可以访问*/
- protected:
- int b;
- private:
- int c;
- };
- class B:public A{
- //int x;
- public:
- void getb(){
- b = 200;
- }
- void show(){
- cout << b << endl;
- }
- void showa(){
- cout<< a<<endl;
- }
- };
- /*关键在于如何设置接口,成功合理的访问到各种类型的数据*/
- int main(){
- B pex;
- /*公开继承public成员依旧是public,所以可以类外访问*/
- pex.a = 100;
- /*b是保护类型成员,可以通过设置public接口来访问*/
- pex.getb();
- pex.show();
- /*隐藏成员的问题,怎么访问到隐藏的成员*/
- pex.geta();
- pex.showa();
- //A a = pex;//子类类型赋给了父类类型
- //a.geta();
- //cout << a.a << endl;
- }
#include <iostream>
using namespace std;class A{
public:int a;int geta(){a = 300;return a;}/*保护类型成员在本类和子类中可以访问*/
protected:int b;
private:int c;
};class B:public A{//int x;
public:void getb(){b = 200;}void show(){cout << b << endl;}void showa(){cout<< a<<endl;}
};
/*关键在于如何设置接口,成功合理的访问到各种类型的数据*/
int main(){B pex;/*公开继承public成员依旧是public,所以可以类外访问*/pex.a = 100;/*b是保护类型成员,可以通过设置public接口来访问*/pex.getb();pex.show();/*隐藏成员的问题,怎么访问到隐藏的成员*/pex.geta();pex.showa();//A a = pex;//子类类型赋给了父类类型//a.geta();//cout << a.a << endl;
}
5、私有继承下的成员的权限变化关系,注意如何设置合理的 访问接口去访问 隐藏的数据成员。
- /*私有继承下的权限变化,关键是设置合理的接口访问
- 父类中的各种类型的数据成员*/
- #include <iostream>
- using namespace std;
- class A{
- private:
- void showa(){
- cout << "this is showa()" << endl;
- }
- protected:
- void showb(){
- cout << "this is showb" << endl;
- }
- public:
- void showc(){
- cout << "this is showc" << endl;
- }
- void geta(){//设置合理的接口访问A中的私有数据
- showa();
- }
- };
- class B:private A{
- public:
- void show(){
- showc();
- showb();
- geta();
- }
- };
- int main(){
- B b;
- //A a = b;对比公开继承,对比一下
- b.show();
- }
/*私有继承下的权限变化,关键是设置合理的接口访问
父类中的各种类型的数据成员*/
#include <iostream>
using namespace std;class A{
private:void showa(){cout << "this is showa()" << endl;}
protected:void showb(){cout << "this is showb" << endl;}
public:void showc(){cout << "this is showc" << endl;}void geta(){//设置合理的接口访问A中的私有数据showa();}
};class B:private A{
public:void show(){showc();showb();geta();}
};int main(){B b;//A a = b;对比公开继承,对比一下b.show();
}
6、突破成员访问权限,可以设置合理的访问接口,也可以使用友元类。
- /*友元类*/
- #include <iostream>
- using namespace std;
- class A{
- private:
- int x;
- int y;
- public:
- A():x(10),y(123){}
- /*B,C声明为A的友元类之后,可以访问到父类的所有类型成员*/
- 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 c;
- c.show();
- }
/*友元类*/
#include <iostream>
using namespace std;class A{
private:int x;int y;
public:A():x(10),y(123){}/*B,C声明为A的友元类之后,可以访问到父类的所有类型成员*/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 c;c.show();
}
7、继承中构造函数、析构函数、赋值运算符函数和拷贝构造函数
构造函数和析构函数是不能被继承的,但是可以被调用。并且子类一定会调用父类的构造函数;
子类默认调用父类的无参构造,也可以制定调用构造函数;
析构函数的调用和构造函数的调用顺序相反;
拷贝构造函数和赋值运算符函数也不能被继承:在子类不提供拷贝构造和赋值运算符时,子类默认调用父类的赋值运算符和拷贝构造函数。但子类一旦提供拷贝构造和赋值运算符函数则不再调用父类拷贝构造和赋值运算符函数。
- /*继承中构造函数和析构函数的调用:构造函数和析构函数不可以
- 被继承,但是可以被调用,而且子类肯定会调用父类的构造函数
- 和析构函数。这种机制可以很自然的用于访问父类的私有成员*/
- #include <iostream>
- using namespace std;
- class A{
- private:
- int x;
- public:
- //A(){cout << "A()" << endl;}
- A(int x = 0):x(x){
- cout <<"A()构造"<<endl;
- cout << x << endl;}
- ~A(){cout << "~A()" << endl;}
- int _get(){
- return x;
- }
- };
- class B:public A{
- public:
- /*在初始化参数列表中可以指定调用父类的构造函数,指定调用构造函数并且给
- 父类中的私有成员赋值*/
- /*注意:子类默认调用父类的无参构造,如果下面的代码没有:A(100),则会调用无参构造,但是父类无参构造
- 被注释掉,所以会出错*/
- B():A(100){
- //x = 200;
- //A(100);
- cout << "B()" << endl;
- }
- //访问有参构造的方式,理解这种方式的作用
- /*注意,这种机制下的构造函数所赋的值是赋到了子类中的数据x中,
- 而父类中的x仍然为0*/
- ~B(){cout << "~B()" << endl;}
- int getbx(){
- return _get();
- }
- };
- int main(){
- A a;//构建A对象,此时A类构造被调用,并打印出了值
- B b;//B类为无参构造,首先调用了A的构造,在调用B的构造
- //打印a对象中的x成员
- cout <<a._get()<<endl;//a对象中的x为0
- //打印b对象中的x
- cout << b.getbx()<<endl;//是100
- /*一层一层的退,先调用b的析构,在调用a的析构*/
- }
/*继承中构造函数和析构函数的调用:构造函数和析构函数不可以
被继承,但是可以被调用,而且子类肯定会调用父类的构造函数
和析构函数。这种机制可以很自然的用于访问父类的私有成员*/
#include <iostream>
using namespace std;class A{
private:int x;
public://A(){cout << "A()" << endl;}A(int x = 0):x(x){cout <<"A()构造"<<endl;cout << x << endl;}~A(){cout << "~A()" << endl;}int _get(){return x;}
};class B:public A{
public:/*在初始化参数列表中可以指定调用父类的构造函数,指定调用构造函数并且给父类中的私有成员赋值*//*注意:子类默认调用父类的无参构造,如果下面的代码没有:A(100),则会调用无参构造,但是父类无参构造被注释掉,所以会出错*/B():A(100){//x = 200;//A(100);cout << "B()" << endl;}//访问有参构造的方式,理解这种方式的作用/*注意,这种机制下的构造函数所赋的值是赋到了子类中的数据x中,而父类中的x仍然为0*/~B(){cout << "~B()" << endl;}int getbx(){return _get();}
};
int main(){A a;//构建A对象,此时A类构造被调用,并打印出了值B b;//B类为无参构造,首先调用了A的构造,在调用B的构造//打印a对象中的x成员cout <<a._get()<<endl;//a对象中的x为0//打印b对象中的xcout << b.getbx()<<endl;//是100/*一层一层的退,先调用b的析构,在调用a的析构*/
}
拷贝构造和赋值运算符的问题
- #include <iostream>
- using namespace std;
- /*系统一旦提供构造函数,系统默认的构造函数将被回收
- 记住,拷贝构造也是构造函数*/
- class A{
- int arr;
- public:
- A(){}
- //A(int x = 0):arr(x){}
- A(const A& a){
- cout << "父类拷贝构造" << endl;
- }
- void operator=(const A& a){
- cout << "父类赋值运算符函数" << endl;
- }
- };
- /*有指针类型的成员时,采用默认机制就麻烦了*/
- class B:public A{
- //int * pi;
- public:
- B(){}
- B(const B& b):A(b){
- //子类中提供了拷贝构造函数将不再调用父类的拷贝构造
- cout << "子类拷贝构造" << endl;
- }
- void operator=(const B& b){
- A::operator=(b); //调用父类的拷贝构造函数的机制
- cout << "子类赋值运算符函数"<< endl;
- }
- };
- int main(){
- B a;
- B b = a;
- B c;
- c = a;
- }
#include <iostream>
using namespace std;
/*系统一旦提供构造函数,系统默认的构造函数将被回收
记住,拷贝构造也是构造函数*/
class A{int arr;
public:A(){}//A(int x = 0):arr(x){}A(const A& a){cout << "父类拷贝构造" << endl;}void operator=(const A& a){cout << "父类赋值运算符函数" << endl;}
};
/*有指针类型的成员时,采用默认机制就麻烦了*/
class B:public A{//int * pi;
public:B(){}B(const B& b):A(b){ //子类中提供了拷贝构造函数将不再调用父类的拷贝构造cout << "子类拷贝构造" << endl;}void operator=(const B& b){A::operator=(b); //调用父类的拷贝构造函数的机制cout << "子类赋值运算符函数"<< endl;}
};int main(){B a;B b = a;B c;c = a;
}
8,名字隐藏
名字隐藏机制:子类中如果定义了和父类中同名的数据,这些数据包括成员变量和成员函数。则会把父类中的数据隐藏掉。
注意:只要名字相同,计算返回值或者形参列表不同,也会被隐藏。隐藏不代表就没有了,可以通过类名作用域::访问到被隐藏的成员。
- #include <iostream>
- using namespace std;
- class A{
- public:
- int x;
- int show(){
- cout << "show A" << endl;
- return 0;
- }
- A(){x=20;}
- A(int x):x(x){cout << "show A(int x)" << endl;}
- void shouu(){
- cout <<"shouu()"<<endl;
- }
- };
- class B:public A{
- public:
- int x;
- int y;
- B(){cout << "B()" << endl;}
- void show(){
- cout << "show B" << endl;
- //A::show();
- }
- };
- int main(){
- B b;
- b.shouu();
- //cout << b.x << endl;
- //cout << b.A::x << endl; //突破名字隐藏机制
- //int c = b.show();被隐藏,无法访问
- //b.A::show();
- }
#include <iostream>
using namespace std;class A{
public:int x;int show(){cout << "show A" << endl;return 0;}A(){x=20;}A(int x):x(x){cout << "show A(int x)" << endl;}void shouu(){cout <<"shouu()"<<endl;}
};class B:public A{
public:int x;int y;B(){cout << "B()" << endl;}void show(){cout << "show B" << endl;//A::show();}
};int main(){B b;b.shouu();//cout << b.x << endl;//cout << b.A::x << endl; //突破名字隐藏机制//int c = b.show();被隐藏,无法访问//b.A::show();
}
9、多继承和函数重写
多继承是c++特有的语法机制,表现为一个子类有多个直接的父类。
- #include <iostream>
- using namespace std;
- class phone{
- double price;
- public:
- //phone();
- phone(double price = 15):price(price){cout << "phone" << endl;}
- ~phone(){cout << "~phone" << endl;}
- void call(){
- cout << "use calling" << endl;
- }
- double getprice(){
- return price;
- }
- };
- class MP3{
- double price;
- public:
- MP3(double price = 20):price(price){cout << "MP3" << endl;}
- ~MP3(){cout << "~MP3" << endl;}
- void play(){
- cout << "use to listening music" << endl;
- }
- double getprice(){
- return price;
- }
- };
- class vedio{
- double price;
- public:
- vedio(double price = 0):price(price){cout << "vedio" << endl;}
- ~vedio(){cout << "~vedio" << endl;}
- void vcd(){
- cout << "watch vedio" << endl;
- }
- double getprice(){
- return price;
- }
- };
- /*多继承*/
- class iphone:public phone,public MP3,public vedio{
- public:
- double getprice(){
- return phone::getprice() + MP3::getprice() + vedio::getprice();
- }
- };
- int main(){
- iphone iphone6;
- //cout << sizeof(iphone) << endl;
- cout << iphone6.MP3::getprice() << endl;
- cout << iphone6.phone::getprice() << endl;
- cout << iphone6.getprice() << endl; //用名字隐藏机制解决多分数据同名冲突的问题
- }
#include <iostream>
using namespace std;class phone{double price;
public://phone();phone(double price = 15):price(price){cout << "phone" << endl;}~phone(){cout << "~phone" << endl;}void call(){cout << "use calling" << endl;}double getprice(){return price;}
};class MP3{double price;
public:MP3(double price = 20):price(price){cout << "MP3" << endl;}~MP3(){cout << "~MP3" << endl;}void play(){cout << "use to listening music" << endl;}double getprice(){return price;}
}; class vedio{double price;
public:vedio(double price = 0):price(price){cout << "vedio" << endl;}~vedio(){cout << "~vedio" << endl;}void vcd(){cout << "watch vedio" << endl;} double getprice(){return price;}
};
/*多继承*/
class iphone:public phone,public MP3,public vedio{
public:double getprice(){return phone::getprice() + MP3::getprice() + vedio::getprice();}
};int main(){iphone iphone6;//cout << sizeof(iphone) << endl;cout << iphone6.MP3::getprice() << endl;cout << iphone6.phone::getprice() << endl;cout << iphone6.getprice() << endl; //用名字隐藏机制解决多分数据同名冲突的问题
}
多继承遇到的问题:上面的代码用sizeof就可以看到,子类在多继承的时候会多次复制顶层数据,而我们期望的是price这个成员只需要复制一份就可以了,因为多余的复制是无意义的。首先采用顶层抽象的方式,将三个父类抽象到更高的层面上。
- #include <iostream>
- using namespace std;
- /*抽象到更高层的类中*/
- class product{
- double price;
- public:
- double getprice(){
- return price;
- }
- product(double price = 0):price(price){cout <<"product"<<endl;}
- };
- class phone:public product{
- public:
- //phone();
- phone(double price = 15):product(price){cout << "phone" << endl;}
- ~phone(){cout << "~phone" << endl;}
- void call(){
- cout << "use calling" << endl;
- }
- };
- class MP3:public product{
- public:
- MP3(double price = 20):product(price){cout << "MP3" << endl;}
- ~MP3(){cout << "~MP3" << endl;}
- void play(){
- cout << "use to listening music" << endl;
- }
- };
- class vedio:public product{
- public:
- vedio(double price = 0):product(price){cout << "vedio" << endl;}
- ~vedio(){cout << "~vedio" << endl;}
- void vcd(){
- cout << "watch vedio" << endl;
- }
- };
- class iphone:public phone,public MP3,public vedio{
- };
- int main(){
- iphone iphone6;
- //cout << iphone6.getprice() << endl;同样会产生冲突的问题
- //cout << sizeof(iphone) << endl;
- cout << iphone6.MP3::getprice() << endl;
- cout << iphone6.phone::getprice() << endl;
- //cout << iphone6.getprice() << endl;//直接调用产生冲突问题,编译器不知道该调用哪一个
- }
#include <iostream>
using namespace std;
/*抽象到更高层的类中*/
class product{double price;
public:double getprice(){return price;}product(double price = 0):price(price){cout <<"product"<<endl;}
};
class phone:public product{
public://phone();phone(double price = 15):product(price){cout << "phone" << endl;}~phone(){cout << "~phone" << endl;}void call(){cout << "use calling" << endl;}
};class MP3:public product{
public:MP3(double price = 20):product(price){cout << "MP3" << endl;}~MP3(){cout << "~MP3" << endl;}void play(){cout << "use to listening music" << endl;}
}; class vedio:public product{
public:vedio(double price = 0):product(price){cout << "vedio" << endl;}~vedio(){cout << "~vedio" << endl;}void vcd(){cout << "watch vedio" << endl;}
};
class iphone:public phone,public MP3,public vedio{};int main(){iphone iphone6;//cout << iphone6.getprice() << endl;同样会产生冲突的问题//cout << sizeof(iphone) << endl;cout << iphone6.MP3::getprice() << endl;cout << iphone6.phone::getprice() << endl;//cout << iphone6.getprice() << endl;//直接调用产生冲突问题,编译器不知道该调用哪一个
}
上面的代码中,product的构造函数 被调用了三次,因为这种继承是一级一级的来的,构造子类的时候找父类,发现父类还有父类,就去调用爷爷类的构造函数,三次继承,三次调用。
这种继承方式构成了一种菱形或者钻石型的继承,叫做菱形继承或者钻石继承,但钻石继承并没有实际解决数据多次复制的问题,为了解决菱形继承,c++提出了虚继承。虚继承就是在继承的时候加上virtual关键字修饰即可。虚继承对于共同的成员父亲类从爷爷类那里继承来的,这里为double price,子类直接越级访问,直接从爷爷类那里继承price。
- /*类中也会有对齐和补齐*/
- #include <iostream>
- using namespace std;
- /*抽象到更高层的类中*/
- class product{
- int price;
- public:
- int getprice(){
- return price;
- }
- product(double price = 0):price(price){cout << "product" << endl;}
- };
- class phone:virtual public product{
- public:
- //phone();
- phone(double price = 15):product(price){cout << "phone" << endl;}
- ~phone(){cout << "~phone" << endl;}
- void call(){
- cout << "use calling" << endl;
- }
- };
- class MP3:virtual public product{
- public:
- MP3(double price = 20):product(price){cout << "MP3" << endl;}
- ~MP3(){cout << "~MP3" << endl;}
- void play(){
- cout << "use to listening music" << endl;
- }
- };
- class vedio:virtual public product{
- public:
- vedio(double price = 0):product(price){cout << "vedio" << endl;}
- ~vedio(){cout << "~vedio" << endl;}
- void vcd(){
- cout << "watch vedio" << endl;
- }
- };
- class iphone:virtual public phone,virtual public MP3,virtual public vedio{
- public:
- iphone(int m = 0,int v = 0,int p = 0):product(m + p + v){}
- };
- /*虚函数之后,product的构造函数只被调用了一次,孙子类直接越级访问
- 了product类*/
- int main(){
- iphone iphone6(1000,2041,3201);
- //cout << iphone6.getprice() << endl;同样会产生冲突的问题
- //cout << sizeof(iphone) << endl;
- //cout << iphone6.MP3::getprice() << endl;
- //cout << iphone6.phone::getprice() << endl;
- //cout << iphone6.getprice() << endl;直接调用产生冲突问题,编译器不知道该调用哪一个
- cout << sizeof(iphone) << endl;
- cout << iphone6.getprice() << endl;
- }
/*类中也会有对齐和补齐*/
#include <iostream>
using namespace std;
/*抽象到更高层的类中*/
class product{int price;
public:int getprice(){return price;}product(double price = 0):price(price){cout << "product" << endl;}
};class phone:virtual public product{
public://phone();phone(double price = 15):product(price){cout << "phone" << endl;}~phone(){cout << "~phone" << endl;}void call(){cout << "use calling" << endl;}
};class MP3:virtual public product{
public:MP3(double price = 20):product(price){cout << "MP3" << endl;}~MP3(){cout << "~MP3" << endl;}void play(){cout << "use to listening music" << endl;}
}; class vedio:virtual public product{
public:vedio(double price = 0):product(price){cout << "vedio" << endl;}~vedio(){cout << "~vedio" << endl;}void vcd(){cout << "watch vedio" << endl;}
};
class iphone:virtual public phone,virtual public MP3,virtual public vedio{
public:iphone(int m = 0,int v = 0,int p = 0):product(m + p + v){}
};
/*虚函数之后,product的构造函数只被调用了一次,孙子类直接越级访问
了product类*/
int main(){iphone iphone6(1000,2041,3201);//cout << iphone6.getprice() << endl;同样会产生冲突的问题//cout << sizeof(iphone) << endl;//cout << iphone6.MP3::getprice() << endl;//cout << iphone6.phone::getprice() << endl;//cout << iphone6.getprice() << endl;直接调用产生冲突问题,编译器不知道该调用哪一个cout << sizeof(iphone) << endl;cout << iphone6.getprice() << endl;
}
这个代码中product的构造函数只调用了一次,说明子类直接越级访问了爷爷类的数据。而对于父类特有的子类照常继承,只是没有通过父类去继承爷爷类的数据成员,所以product的构造函数只被调用了一次。
虚函数:在函数前面加上virtual关键字修饰过的就是虚函数.
#include <iostream>
using namespace std;
class A{
int x;
public:
virtual void show(){}
virtual void showa(){}
};
int main(){
cout << sizeof(A) << endl;
}
虚函数的主要表现为会占用四个字节的空间,只要成员中出现虚函数,不管有多少个虚函数,都只用四个字节来维护这个虚关系。虚函数会影响对象的大小。维护虚关系使用一个指针来维护的,所以是四个字节。
函数重写:
在父类中出现一个虚函数,如果在子类中提供和父类同名的函数(注意区分名字隐藏),这就加函数重写。
函数重写要求必须有相同函数名,相同的参数列表,相同的返回值。
三、c++面向对象之多态
1、多态:一个父类型的对象的指针或者引用指向或者是引用一个子类对象时,调用父类型中的虚函数,如果子类覆盖了虚函数,则调用的表现是子类覆盖之后的。
继承是构成多态的基础;
虚函数是构成多态的关键;
函数覆盖是构成多态的必备条件;
多态的应用:函数参数,函数返回值。
多态的产生必须依靠上面的四点,缺一不可。
2、多态的应用,多态相对做到了通用类型编程,主要用在函数参数和函数的返回值上。
- /*多态的应用:
- 1、函数参数
- 2、函数返回值*/
- #include <iostream>
- using namespace std;
- class Animal{
- public:
- virtual void run(){
- cout <<"Ainimal run()"<<endl;
- }
- void show(){
- cout <<"Animal show()"<<endl;
- }
- };
- class Dog:public Animal{
- public:
- void run(){
- cout <<"Dog run()"<<endl;
- }
- void show(){
- cout <<"dog show()"<<endl;
- }
- };
- class Cat:public Animal{
- public:
- void run(){
- cout <<"cat run()"<<endl;
- }
- };
- /*多态用作函数参数*/
- void showAnimal(Animal * animal){
- animal->show();
- animal->run();
- }
- /*多态用作返回值*/
- Animal * getAnimal(int x){
- if (1 == x)
- return new Dog();
- if (2 == x)
- return new Cat();
- }
- int main(){
- Cat cat;
- showAnimal(&cat);
- Dog dog;
- showAnimal(&dog);
- }
/*多态的应用:
1、函数参数
2、函数返回值*/
#include <iostream>
using namespace std;
class Animal{
public:virtual void run(){cout <<"Ainimal run()"<<endl;}void show(){cout <<"Animal show()"<<endl;}
};class Dog:public Animal{
public:void run(){cout <<"Dog run()"<<endl;}void show(){cout <<"dog show()"<<endl;}
};class Cat:public Animal{
public:void run(){cout <<"cat run()"<<endl;}
};
/*多态用作函数参数*/
void showAnimal(Animal * animal){animal->show();animal->run();
}
/*多态用作返回值*/
Animal * getAnimal(int x){if (1 == x)return new Dog();if (2 == x)return new Cat();
}
int main(){Cat cat;showAnimal(&cat);Dog dog;showAnimal(&dog);
}
3、多态的实现原理
多态的实现主要依赖于下面的三个东西:
虚函数:成员函数加了virtual修饰
虚函数表指针:一个类型有虚函数,则对这个类型提供一个指针,这个指针放在生成对象的前四个字节。同类型的对象共享一张虚函数表。并且不同类型的虚函数表地址不同。
虚函数表:虚函数表中的每个元素都是虚函数的地址。
一个类型一旦出现虚函数,则会生成一张虚函数表,虚函数表中存放的就是虚函数的函数地址,通过这个函数地址可以到代码区中去执行对应的函数。虚函数表中只存放类型中的虚函数,不是虚函数的一概不管。在每个生成的类型对象的前四个字节中存放的是虚函数表指针,通过这个指针可以访问到虚函数表,从而访问其中的函数。同种类型共享虚函数表,不同类型有自己独立的虚函数表,继承关系中的子类和父类属于不同类型,所以有自己独立的函数表。
- /*多态的原理*/
- #include <iostream>
- #include <cstring>
- using namespace std;
- class Animal{
- int x;
- public:
- virtual void fun(){
- cout<< "Aniaml fun()"<<endl;
- }
- virtual void run(){
- cout <<"Animal run()"<<endl;
- }
- void show(){
- cout <<"Animal show()"<<endl;
- }
- };
- class Dog:public Animal{
- public:
- virtual void fun(){
- cout << "dog run"<<endl;
- }
- void run(){
- cout <<"dog run"<<endl;
- }
- };
- class Cat:public Animal{
- public:
- void fun(){
- cout <<"cat fun"<<endl;
- }
- };
- int main(){
- Animal a;
- Animal b;
- /*取出虚函数表的地址并打印*/
- int * pi = (int*)&a;
- cout <<showbase<< hex << *pi<<endl;
- pi = reinterpret_cast<int*>(&b);
- cout <<showbase<< hex << *pi<<endl;
- /*子类不会和父类共享虚函数表,地址不一样*/
- Dog dog;
- pi = reinterpret_cast<int*>(&dog);
- cout <<showbase<< hex << *pi<<endl;
- Animal * pcat = new Cat();
- pcat->run();
- pcat->fun();
- /*更改dog的虚表的值,我们把dog的虚表地址
- 改成cat的虚表地址*/
- Animal * pdog = new Dog();
- pdog->run();
- pdog->fun();
- /*更换dog的虚表地址,将cat的前四个字节
- 移动到dog的前四个字节*/
- memcpy(pdog,pcat,4);
- pdog->run();
- pdog->fun();
- /*上面的更改后,狗变成了猫的特性*/
- }
/*多态的原理*/
#include <iostream>
#include <cstring>
using namespace std;
class Animal{int x;
public:virtual void fun(){cout<< "Aniaml fun()"<<endl;}virtual void run(){cout <<"Animal run()"<<endl;}void show(){cout <<"Animal show()"<<endl;}
};class Dog:public Animal{
public:virtual void fun(){cout << "dog run"<<endl;}void run(){cout <<"dog run"<<endl;}
};class Cat:public Animal{
public:void fun(){cout <<"cat fun"<<endl;}
};int main(){Animal a;Animal b;/*取出虚函数表的地址并打印*/int * pi = (int*)&a;cout <<showbase<< hex << *pi<<endl;pi = reinterpret_cast<int*>(&b);cout <<showbase<< hex << *pi<<endl;/*子类不会和父类共享虚函数表,地址不一样*/Dog dog;pi = reinterpret_cast<int*>(&dog);cout <<showbase<< hex << *pi<<endl;Animal * pcat = new Cat();pcat->run();pcat->fun();/*更改dog的虚表的值,我们把dog的虚表地址改成cat的虚表地址*/Animal * pdog = new Dog();pdog->run();pdog->fun();/*更换dog的虚表地址,将cat的前四个字节移动到dog的前四个字节*/memcpy(pdog,pcat,4);pdog->run();pdog->fun();/*上面的更改后,狗变成了猫的特性*/
}
上述程序对应的内存图:
可以看出,一旦满足了多态的条件,程序自然按照上图的流程执行。
4、上面既然说了,虚函数表中存放的是函数的地址,那么能不能直接我们自己取出虚函数的地址,直接调用所需要的函数呢?
- #include <iostream>
- using namespace std;
- class Animal{
- public:
- virtual void run(int x){
- cout <<"run x="<<x<<endl;
- }
- virtual void fun(int x){
- cout <<"fun x="<<x<<endl;
- }
- void show(){
- cout <<"this is show()"<<endl;
- }
- };
- int main()
- {
- /*去掉函数名就是函数指针的类型,指针简化操作*/
- typedef void (*MFUN)(Animal* a,int x);/*MFUN就是虚表中的函数指针类型*/
- typedef MFUN* VTABLE;//MFUN*就是虚表类型
- Animal animal;
- VTABLE vt = *((VTABLE*)&animal);
- /*虚函数表表现为函数指针数组*/
- vt[0](&animal,100);
- vt[1](&animal,123);
- return 0;
- }
#include <iostream>
using namespace std;class Animal{
public:virtual void run(int x){cout <<"run x="<<x<<endl;}virtual void fun(int x){cout <<"fun x="<<x<<endl;}void show(){cout <<"this is show()"<<endl;}
};int main()
{/*去掉函数名就是函数指针的类型,指针简化操作*/typedef void (*MFUN)(Animal* a,int x);/*MFUN就是虚表中的函数指针类型*/typedef MFUN* VTABLE;//MFUN*就是虚表类型Animal animal;VTABLE vt = *((VTABLE*)&animal);/*虚函数表表现为函数指针数组*/vt[0](&animal,100);vt[1](&animal,123);return 0;
}
虚表中存放的就是虚函数的函数指针,可以理解为函数指针的数组,通过typedef将指针降级。
5,虚析构函数
virtual关键字只能修饰成员函数或者析构函数,其他的函数都不行。
当我们用new创建一个指向子类对象的父类指针时,例如Animal * animal = new Dog()时,其中Animal时父类,Dog是子类,并且delete animal时,其子类对象的析构函数不会被调用,只会调用父类的析构函数。所以就会遇到一个问题,如果子类对象有自己独立的堆内存时,这部分内存就无法释放。这时,我们只需要在父类的析构函数上用virtual修饰即可,子类析构函数就会被调用。
- #include <iostream>
- using namespace std;
- class Animal{
- public:
- Animal(){
- cout <<"Animal()"<<endl;
- }
- virtual ~Animal(){
- cout <<"~Animal()"<<endl;
- }
- };
- class Dog:public Animal{
- public:
- Dog(){
- cout <<"Dog()"<<endl;
- }
- ~Dog(){
- cout <<"~Dog()"<<endl;
- }
- };
- int main(){
- Animal * pa = new Dog();
- delete pa;
- /*子类析构函数的调用必然引发父类析构*/
- }
#include <iostream>
using namespace std;class Animal{
public:Animal(){cout <<"Animal()"<<endl;}virtual ~Animal(){cout <<"~Animal()"<<endl;}
};
class Dog:public Animal{
public:Dog(){cout <<"Dog()"<<endl;}~Dog(){cout <<"~Dog()"<<endl;}
};
int main(){Animal * pa = new Dog();delete pa;/*子类析构函数的调用必然引发父类析构*/
}
虚析构函数的应用场景:
当父类中有虚函数时,应该吧父类的析构函数定义成虚析构函数。
子类和父类中都有自己的堆内存分配时。
c++面向对象三大特征封装、继承和多态知识总结相关推荐
- c++局部对象是什么_面向对象三大特征: 封装
今日内容 面向对象概述 类和对象的关系 面向对象内存图 private.this关键字和封装 面向对象之构造方法 面向对象三大特征 封装 继承 多态 01. 面向过程和面向对象思想概述 什么是面向过程 ...
- python多态的三种表现形式_python小结----面向对象的三大特征(封装,继承,多态)
面向对象的三大特征: 封装,继承,多态 面向对象的编程思想核心:高类聚,低耦合–程序的设计模式范畴 封装 什么是封装: 在面向对象编程的思想中,对代码进行高度封装,封装又叫包装 封装就是指将数据或者函 ...
- 面向对象第七章,内存的管理,面向对象三大特征封装、继承、多态
###1.内存管理:由JVM来管理的------了解 1)堆: 1.1)存储new出来的对象(包括实例变量) 1.2)垃圾:没有任何引用指向的对象 垃圾回收器(GC)不定时到内存中清扫垃圾, 回收过程 ...
- 面向对象三大特征———封装、继承、多态
一.封装 封装就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏.封装是面向对象的特征之一,是对象和类概念的主要特性. 简单的说,一个类就是一个 ...
- Java面向对象三大特性(封装继承多态)解释及案例
文章目录 包 包基本语法 命名规则 命名规范 导入包实例 访问修饰符 面向对象编程-封装 面向对象编程-继承 super关键词 super和this的比较 方法重写/覆盖 (override) 注意事 ...
- Python攻城师的成长————面向对象的三大特征(继承、多态)
学习目标: 了解继承与多态的概念,重点是要学会运用继承去处理问题 学习内容: 继承 在面对对象程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类,而被继 ...
- Python学习笔记④——类、面向对象的三大特征 [封装+继承+多态]
✅ Blogger Learns Python is for learning the "Deep Learning". 文章目录 一.面向对象编程的简介 二.类与对象 -- 基础 ...
- OC面向对象的三大特征(封装 继承 多态)习题2 复合
复合:一个类中有一个成员变量是另外一个类的对象. 比如我现在要制作一台电脑,电脑需要CPU,显示器,鼠标和键盘等.这些东西的研发都是很复杂的过程.如果现在有成型的CPU等组件,就可以直接用这些组件攒一 ...
- 三大特征 封装 继承 多态
''' 1.面向对象与面向过程 面向过程:核心过程二字,过程即解决问题的步骤,就是先干什么后干什么 基于该思想写程序就好比在这是一条流水线,是一种机械式的思维方式 优点:复杂的过程流程化 缺点:扩展性 ...
最新文章
- [转载] Maven类包冲突终极三大解决技巧 mvn dependency:tree
- windows terminal 笔记
- datetimepicker控件怎么改变hover颜色_VBA入门课程,ActiveX控件系列知识,复合框的属性与常见VBA代码...
- Flutter:尝试撸一个具有惯性跟阻力的旋转控件或用传感器控制其旋转
- git两个账号切换_多个git账号之间的切换
- mysql 有索引 不被使用方法_MySQL教程100-索引在什么情况下不会被使用?
- 意境级讲解二分查找算法、python
- Qt For Android | QT安卓开发环境搭建
- 共识算法PBFT和Raft
- P2P软件UFX被指藏后门搜客户信息 融都科技否认
- cuda的Pinned Memory(分页锁定内存)
- 中科院广州电子CASAIM与东风日产在3D打印生产制造发动机检具及治具应用研究项目顺利落地
- 使用Qt获取系统版本
- 视差图转为深度图_纽劢研习社 | 深度图的非深度讲解
- 学了一阵子python pygame, 写一些总结,回头看看哪些地方不足
- 显示器IPS屏和TN屏的优缺点及差异
- python中scale的用法_在netCDF4和Python中使用scale_factor和add_offset的示例?
- 用pycharm创建第一个django项目
- electron隐藏默认菜单
- Java 使用jacob ppt文件转pptx,doc转docx;word 转html、pdf等
热门文章
- Mybatis映射文件!CDATA[[]] 转义问题
- 从零开始搭建系统3.2——微服务注册中心开发及部署
- MySQL Infobright 数据仓库快速安装笔记[转]
- 使用Java程序输出1~100之间 7的倍数的个数及总和,并打印输出
- HarmonyOS之AI能力·二维码的生成和使用
- 下列说法正确的是( )
- 2019第十届蓝桥杯C/C++ B组省赛 —— 第三题:数列求值
- The 3n + 1 problem UVA - 100
- Python学习笔记(五) Python高级特性
- Struts2 ognl表达式