我们先了解一下下面这段代码:
#include<iostream>
using namespace std;
class Base
{
public:
Base(int a =10):ma(a){}
void show()
{
cout<<"ma="<<ma<<endl;
}
protected:
int ma;
};
class Derived:public Base
{
public:
Derived(int b ):mb(b),Base(b){}
void show()
{
cout<<mb<<endl;
}
private:
int mb;
};
int main()
{
Base* pb = new Derived (10);
std::cout << "Base size:" << sizeof(Base) << std::endl;
std::cout << "Derived size:" << sizeof(Derived) << std::endl;
std::cout << "pb type:" << typeid(pb).name() << std::endl;
std::cout << "*pb type:" << typeid(*pb).name() << std::endl;
return 0;
}
运行结果为:
如果我们给基类(Base)的show()函数中加入一个virtual,那么结果会改变的:
#include<iostream>
using namespace std;
class Base
{
public:
Base(int a =10):ma(a){}
virtual void show()
{
cout<<"ma="<<ma<<endl;
}
protected:
int ma;
};
class Derived:public Base
{
public:
Derived(int b ):mb(b),Base(b){}
void show()
{
cout<<mb<<endl;
}
private:
int mb;
};
int main()
{
Base* pb = new Derived (10);
std::cout << "Base size:" << sizeof(Base) << std::endl;
std::cout << "Derived size:" << sizeof(Derived) << std::endl;
std::cout << "pb type:" << typeid(pb).name() << std::endl;
std::cout << "*pb type:" << typeid(*pb).name() << std::endl;
return 0;
}
则运行结果为:
那么加了virtual后,其中改变了什么?
virtual
在了解多态之前,我们先了解重载,隐藏,覆盖
1 成员函数重载特征:
a 相同作用域(在同一个类中)
b 函数名字相同
c 参数不同
d virtual关键字可有可无
2 重写(覆盖)是指派生类函数覆盖基类函数,特征是:
a 不同作用域,分别位于基类和派生类中
b 函数的名字相同
c 参数相同
d 基类函数必须有virtual关键字
3 重定义(隐藏)是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
a 如果派生类的函数和基类的函数同名,但是参数不同,此时,不管有无virtual,基类的函数被隐藏。
b 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有vitual关键字,此时,基类的函数被隐藏。(如果父类的成员和子类的成员属性名称相同,我们可以通过作用域操作符来显式的使用父类的成员,如果我们不使用作用域操作符,默认使用的是子类的成员属性。)
C++的多态性 :在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数
在派生类中加入了虚函数指针,虚函数指针的优先级最高,虚函数指针指向了虚函数表
虚函数表:
那么,在生成派生类的时候,他们的存储形式就变为了:
因此我们就可以解释上面代码的运行结果了:
std::cout << "Base size:" << sizeof(Base) << std::endl;//多了一个虚函数指针,增加了4个字节大小
std::cout << "Derived size:" << sizeof(Derived) << std::endl;;//多了一个虚函数指针,增加了4个字节大小
std::cout << "pb type:" << typeid(pb).name() << std::endl;//基类的指针指向的是派生类,实际指向了上图红色区域的部分
std::cout << "*pb type:" << typeid(*pb).name() << std::endl;//对其解引用的收时候,vfptr指针优先级最高,所以就指向了派生类 的对象(对基类的show()进行覆盖)
动多态的发生条件
指针或引用调用虚函数 对象必须完整
静态绑定 早绑定
动态绑定 晚绑定
虚函数表在编译期间生成
成为虚函数条件
1.能取地址 2.依赖对象调用
哪些函数可以成为虚函数
基类指针指向派生类对象 基类设置虚析构
那些函数不可以成为虚函数
1.inLine(内联)函数,因为内联函数不能取地址
2.普通函数 不依赖对象调用
3.构造函数 不依赖对象调用
4.static 静态成员函数,不依赖对象调用
注意:
1.基类的虚构为虚析构,则派生类的析构为虚析构
#include<iostream>
#include<vld.h>
using namespace std;
class Base
{
public:
Base(int a =10):ma(a){}
void show()
{
cout<<"ma="<<ma<<endl;
}
virtual ~Base()
{
cout<<"基类的析构"<<endl;
}
protected:
int ma;
};
class Derived:public Base
{
public:
Derived(int b ):mb(b),Base(20){}
void show()
{
cout<<mb<<endl;
}
~Derived()
{
cout<<"派生类的析构"<<endl;
}
private:
int mb;
};
int main()
{
Base* pb = new Derived (50);
delete pb;
return 0;
}
运行结果为:
其中,指针如下图这样指向,先析构派生类的资源,再析构 基类的资源。
如果没有在基类的析构中加入在虚函数,则运行的结果为:
其中,指针指向为下图,因为pb只指向基类的内存,所以只调运了基类的虚构,没有调用派生类的析构函数
如果我们仅仅在派生类的一个函数中加入虚函数,那么在释放内存的时候会崩溃
#include<iostream>
#include<vld.h>
using namespace std;
class Base
{
public:
Base(int a =10):ma(a){}
void show()
{
cout<<"ma="<<ma<<endl;
}
~Base()
{
cout<<"基类的析构"<<endl;
}
protected:
int ma;
};
class Derived:public Base
{
public:
Derived(int b ):mb(b),Base(20){}
virtual void show()
{
cout<<mb<<endl;
}
~Derived()
{
cout<<"派生类的析构"<<endl;
}
private:
int mb;
};
int main()
{
Base* pb = new Derived (50);
delete pb;
return 0;
}
运行奔溃了:
原因很简单:如下图,在释放的内存的时候,指针指向派生类中的基类,这次指针指的不是派生类的内存的开始,所以释放的时 候,会崩溃。
2.基类指针指向派生类对象,基类设置虚析构
3.虚表的写入时机:构造函数的第一行代码执行那个执行之前
4.在派生类对象生成的时候,会有vfptr多次赋值
基类中,有一个函数为虚函数,那么就会有一个vfptr,则派生类中同名同参的函数也会变为虚函数,有一个vfptr,那么这两个虚表会合并,生成一个vfptr
5.基类的指针指向派生类对象:指向的是派生类对象中基类的起始位置
纯虚函数
下面通过一个例子来说明纯虚函数的定义方法:
class Animal
{
public:
Animal(string name):mname(name){}
virtual void bark() = 0;//纯虚函数
virtual void show()
{
cout<<"mname= "<<mname<<endl;
}
private:
string mname;
};
在这个类当中,我们定义了一个普通的虚函数,并且也定义了一个纯虚函数。那么,纯虚函数是什么呢??从上面的定义可以看到,纯虚函数就是没有函数体,同时在定义的时候,其函数名后面要加上“= 0”。
纯虚函数和普通虚函数的实现原理是基本差不多的,都有一个虚指针vfptr,虚指针指向一个虚表,保存函数的入口地址,那么下面我们通过一段代码,来了解纯虚函数;
#include<iostream>
#include<string>
using namespace std;
class Animal
{
public:
Animal(string name):mname(name){}
virtual void bark() = 0;//纯虚函数
virtual void show()
{
cout<<"mname= "<<mname<<endl;
}
public:
string mname;
};
class Dog:public Animal
{
public:
Dog(string name):Animal(name){}
void bark()
{
cout<<"wang wang wang"<<endl;
}
};
class Cat:public Animal
{
public:
Cat(string name):Animal (name){}
void bark()
{
cout<<"miao miao miao"<<endl;
}
};
int main()
{
Dog*dog = new Dog("dog");
dog->bark();
dog->show();
Cat*cat = new Cat(" cat");
cat->bark();
cat->show();
return 0;
}
运行结果为:
内存布局为:
Dog类的内存布局为:
vfptr指针所指向的虚函数表为:
Cat类的内存布局为:
vfptr指针所指向的虚函数表为:
注意:
#include<iostream>
#include<string>
using namespace std;
class Animal
{
public:
Animal(string name):mname(name){}
virtual void bark() = 0;//纯虚函数
virtual void show()
{
cout<<"mname= "<<mname<<endl;
}
public:
string mname;
};
class Dog:public Animal
{
public:
Dog(string name):Animal(name){}
void bark()
{
cout<<"wang wang wang"<<endl;
}
};
class Cat:public Animal
{
public:
Cat(string name):Animal (name){}
void bark()
{
cout<<"miao miao miao"<<endl;
}
};
int main()
{
Animal* Ani = new Animal("GG");
return 0;
}
程序错误:
交换两个虚指针
#include<iostream>
#include<string>
using namespace std;
class Animal
{
public:
Animal(string name):mname(name){}
virtual void bark() = 0;//纯虚函数
virtual void show()
{
cout<<"mname= "<<mname<<endl;
}
public:
string mname;
};
class Dog:public Animal
{
public:
Dog(string name):Animal(name){}
void bark()
{
cout<<"wang wang wang"<<endl;
}
};
class Cat:public Animal
{
public:
Cat(string name):Animal (name){}
void bark()
{
cout<<"miao miao miao"<<endl;
}
};
int main()
{
Dog*dog = new Dog("dog");
Cat*cat = new Cat(" cat");
int*d1 = (int *)dog;
int*c1 = (int *)cat;
int tmp = *d1;
*d1 = *c1;
*c1 = tmp;
dog->bark();
cat->bark();
delete cat;
delete dog;
return 0;
}
运行结果为: