C++三大特性
1.继承
一个对象直接使用另一个对象的属性和方法,减少重复的代码,增加了类的耦合性。但父类通常定义了子类的部分行为,父类的改变可能影响子类的行为。
2. 多态
C++中有静多态(编译时多态)和动多态(运行时多态)两种。静多态主要通过模板来实现,宏也是实现静多态的一种途径,所有调用在编译期就可确定,因此它是静态的;动多态在C++中是通过虚函数实现的,即在基类中存在一些接口(一般为纯虚函数),子类必须重载这些接口,通过使用基类的指针或者引用指向子类的对象,通过基类指针实现调用子类对应的函数的功能,函数调用在执行期才能进行确定,所以它是动态的。
/*****静多态*****/
#include <iostream>
using namespace std;
class line
{
public:
void draw()
{
cout << "line is drawing!" << endl;
}
};
class circle
{
public:
void draw()
{
cout << "circle is drawing!" << endl;
}
};
template<typename T>
void drawShape(T & shape)
{
shape.draw();
}
int main()
{
line lining;
circle circling;
drawShape( lining );
drawShape( circling );
return 0;
}
/*****动多态*****/
#include <iostream>
using namespace std;
class shape
{
public:
virtual void draw() = 0;
};
class line : public shape
{
public:
void draw()
{
cout << "line is drawing!" << endl;
}
};
class circle : public shape
{
public:
void draw()
{
cout << "circle is drawing!" << endl;
}
};
int main()
{
/*基类的指针指向子类的对象*/
shape* pLine = new line;
shape* pCircle = new circle;
pLine->draw();
pCircle->draw();
if( pCircle ) delete pCircle ;
if( pLine ) delete pLine ;
/*基类的引用指向子类的对象*/
//line line;
//circle circl;
//shape& referenceLine = line; //
//shape& referenceCircl = circl;
//referenceLine.draw();
//referenceCircl.draw();
return 0;
}
动多态代码运行结果:
3. 封装
隐藏对象的属性和实现细节,仅仅对外提供接口和方法。
重载:写一个与已有函数同名但是参数表不同的函数
覆盖:派生类中改写基类中的虚函数
虚函数与纯虚函数
虚函数使用的其核心目的是通过基类访问派生类定义的函数(virtual),实现了多态的机制。在使用虚函数的过程中存在两个常见错误:无意的重写(具有相同的签名的成员函数)、虚函数签名不匹配(函数名、参数列表 或 const 属性不一样)。针对上述情况,C++ 11 增加了两个继承控制关键字:override 和 final。override:防止虚函数的覆盖,保证基类的虚函数在派生类重载;final:防止基类的虚函数在派生类重载,保证虚函数的覆盖。
/*override:防止虚函数覆盖*/
class Base {
public:
virtual void display(int x); // 虚函数
};
class Derived : public Base {
public:
virtual void display(int x) const override; // const 属性不一样,新的虚函数
}
/*final:防止虚函数重载*/
class Base {
public:
virtual void display(int x) final; // 虚函数
};
class Derived : public Base {
public:
virtual void display(int x) override; // 重写提示错误
};
当想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数(virtual 返回类型 函数名() = 0;)。
虚继承
在上图菱形继承中,类 A 中的成员变量和成员函数继承到类 D 中变成了两份,在一个派生类中保留间接基类的多份同名成员,虽然可以在不同的成员变量中分别存放不同的数据,但大多数情况下这是多余的:因为保留多份成员变量不仅占用较多的存储空间,还容易产生命名冲突。为了解决多继承时的命名冲突和冗余数据问题,C++ 提出了虚继承(继承方式前面加上 virtual 关键字),使得在派生类中只保留一份间接基类的成员。虚继承机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员。
虚指针与虚表
C++实现虚函数的方法是:为每个类对象添加一个隐藏成员,隐藏成员保存了一个指针,这个指针叫虚表指针(vptr),它指向一个虚函数表(vtbl),位于该类的首地址(系统为32位时地址为前4个字节,系统为64位时地址为前8个字节)。
基类对象包含一个虚表指针,指向基类的虚函数表,派生类对象也将包含一个虚表指针,指向派生类虚函数表,如果派生类重写了基类的虚方法,该派生类虚函数表将保存重写的虚函数的地址,而不是基类的虚函数地址,如果基类中的虚方法没有在派生类中重写,那么派生类将继承基类中的虚方法,而且派生类中虚函数表将保存基类中未被重写的虚函数的地址,但如果派生类中定义了新的虚方法,则该虚函数的地址也将被添加到派生类虚函数表中。
例子:
#include <iostream>
int main()
{
class A
{
public:
virtual void vfunc() { std::cout << "A::vfunc" << std::endl; }
virtual void vfuncA() { std::cout << "A::vfuncA" << std::endl; }
public:
double m_data = 1.57;
};
class B:public A
{
public:
virtual void vfunc() { std::cout << "B::vfunc" << std::endl; }
virtual void vfuncB() { std::cout << "B::vfuncB" << std::endl; }
public:
double m_data = 3.14;
};
typedef void(*Fun)(void);//声明一个函数指针
A aObj;
B bObj;
A *abObj = new B;
Fun pFun = NULL;//指向void* pf(void)类的函数的指针pFun
/*_WIN64--只有64位程序才有,_WIN32--32位和64位程序都有*/
/*_WIN64 用来判断编译环境是 x86 还是 x64,_WIN32 可以用来判断是否 Windows 系统*/
#if _WIN64
std::cout << "aObj实例对象的数据地址:" << &(aObj.m_data) << std::endl;
std::cout << "A虚函数表的地址:" << (long long int*) * (long long int*)(&aObj) << std::endl;
std::cout << "A虚函数表的第一个函数地址:" << (long long int*) * (long long int*) * (long long int*)(&aObj) << std::endl;
std::cout << "A虚函数表的第二个函数地址:" << (long long int*) * ((long long int*) * (long long int*)(&aObj) + 1) << std::endl;
std::cout << std::endl;
std::cout << "bObj实例对象的数据地址:" << &(bObj.m_data) << std::endl;
std::cout << "B虚函数表的地址:" << (long long int*) * (long long int*)(&bObj) << std::endl;
std::cout << "B虚函数表的第一个函数地址:" << (long long int*) * (long long int*) * (long long int*)(&bObj) << std::endl;
std::cout << "B虚函数表的第二个函数地址:" << (long long int*) * ((long long int*) * (long long int*)(&bObj) + 1) << std::endl;
std::cout << "B虚函数表的第三个函数地址:" << (long long int*) * ((long long int*) * (long long int*)(&bObj) + 2) << std::endl;
std::cout << std::endl;
//再次取址得到第一个虚函数的地址
//A第一个虚函数
pFun = (Fun) * (long long int*) * (long long int*)(&aObj);
pFun();
//A第二个虚函数
pFun = (Fun) * ((long long int*) * (long long int*)(&aObj) + 1);
pFun();
std::cout << std::endl;
//B第一个虚函数
pFun = (Fun) * (long long int*) * (long long int*)(&bObj);
pFun();
//B第二个虚函数
pFun = (Fun) * ((long long int*) * (long long int*)(&bObj) + 1);
pFun();
//B第三个虚函数
pFun = (Fun) * ((long long int*) * (long long int*)(&bObj) + 2);
pFun();
std::cout << std::endl;
//基类指针指向派生类,调用派生类函数vfunc()
aObj.vfunc();
abObj->vfunc();
#else
std::cout << "aObj实例对象的数据地址:" << &(aObj.m_data) << std::endl;
std::cout << "A虚函数表的地址:" << (int*)*(int*)(&aObj) << std::endl;
std::cout << "A虚函数表的第一个函数地址:" << (int*)*(int*)*(int*)(&aObj) << std::endl;
std::cout << "A虚函数表的第二个函数地址:" << (int*)*((int*)*(int*)(&aObj) + 1) << std::endl;
std::cout << std::endl;
std::cout << "bObj实例对象的数据地址:" << &(bObj.m_data) << std::endl;
std::cout << "B虚函数表的地址:" << (int*)*(int*)(&bObj) << std::endl;
std::cout << "B虚函数表的第一个函数地址:" << (int*)*(int*)*(int*)(&bObj) << std::endl;
std::cout << "B虚函数表的第二个函数地址:" << (int*)*((int*)*(int*)(&bObj) + 1) << std::endl;
std::cout << "B虚函数表的第三个函数地址:" << (int*)*((int*)*(int*)(&bObj) + 2) << std::endl;
std::cout << std::endl;
//再次取址得到第一个虚函数的地址
//A第一个虚函数
pFun = (Fun) * (int*)*(int*)(&aObj);
pFun();
//A第二个虚函数
pFun = (Fun) * ((int*)*(int*)(&aObj) + 1);
pFun();
std::cout << std::endl;
//B第一个虚函数
pFun = (Fun) * (int*)*(int*)(&bObj);
pFun();
//B第二个虚函数
pFun = (Fun) * ((int*)*(int*)(&bObj) + 1);
pFun();
//B第三个虚函数
pFun = (Fun) * ((int*)*(int*)(&bObj) + 2);
pFun();
std::cout << std::endl;
//基类指针指向派生类,调用派生类函数vfunc()
aObj.vfunc();
abObj->vfunc();
#endif
delete abObj;
return 0;
}
运行结果:
上例虚函数存储方式如图所示: