2016-04-12
1、C++中的静态编译和动态编译分别基于什么机制实现?
首先,静态编译和动态编译都属于C++中的多态,即“一种接口,多种实现”。
静态编译(/联编) —— 也称为 静态多态 如:函数的重载和运算符的重载
动态编译(/联编 ) ——也称为运行时多态 如:虚函数
多态规则:
通过基类指针或引用调用成员函数时
如果函数是非虚函数,采用静态编译(即:编译时绑定)。
如果函数是虚函数,采用动态编译(即:运行时绑定)。
--- 动态联编
指的是被调函数的入口地址是在运行时才确定的。
** C++实现动态联编是基于 虚指针(vptr) 和 虚函数表(vtable)
具体为:
a. 为每一个包含虚函数的类建立一个虚函数表,表中存放的是各虚函数在内存中的入口地址。
b. 包含虚函数的类的每个对象都有一个指向虚函数表的 虚指针。
c. 调用虚函数时,利用虚指针找到虚函数表,进而确定虚函数的入口地址,最后调用之。
注:
一个包含虚函数的类只有一个虚函数表。
** 虚函数表的结构
a. 一个类中虚函数的来源包括:
继承自父类(需要改写函数) +
当前类中新声明的虚函数
b. 为所有虚函数排序:继承的虚函数在前,新声明的虚函数按照声明顺序排列在后。
** 虚函数表的位置
虚函数表的存放位置
在应用程序的常量区
2、进阶讨论 纯虚函数是否可以声明为private?
可以
但与声明为public的区别在于:虚函数不能在类外部调用,只能在内部调用 or 通过类所提供的public接口调用。
** 关于基类中函数声明为 private 和 virtual private 的一点讨论
--- 首先,声明为 private,表示基类不希望外部看到这个函数,子类当然也就无法覆盖或重载这个函数,
看起来这个函数的实现是无法修改的。但是如果我们在基类中定义一个公共接口,该接口对private函
数进行了调用,子类可以对公共接口进行修改达到"修改"private函数的假象。
--- 其次,声明为 virtual private,那么既希望子类对其重写实现多态,又不希望子类看到这个函数。看似
自相矛盾。这里实际的意义在于:
子类可以修改函数的实现,也可以继承基类中默认的实现。
--- 最后,声明为virtual protected,其意义在于
这是必须被子类修改的实现。
P.S: 最好不要把虚函数声明为public,而是用protected来替换。
结论:多态与虚函数前的访问限定符无关,限定符仅仅限制外部对类成员的访问权限。
恩 说了这么多 给个例子。
#include <iostream>
#include <string>
using namespace std;
// 父类/基类
class CMyBase
{
// 多态的实现只与是否是虚函数有关 与访问限定符public private protected无关
private:
//public:
// 可以看到 输出结果只与该函数是否为virtual有关 与访问限定符无关
//virtual string getObj() const
string getObj() const
{
return string("CMyBase");
}
protected:
virtual void DoSth() = 0; // 纯虚函数
public:
// 外部访问虚函数的接口(如果getObj()函数声明为private的话)
void DoSthAgain()
{
cout << "This is "<< getObj() <<endl;
DoSth();
}
// 虚析构函数
virtual ~CMyBase(){}
};
// 子类/派生类
class CMyDrived : public CMyBase
{
private:
string getObj() const
{
return string("CMyDrived");
}
protected:
void DoSth()
{
cout<<"This is CMyDrived"<<endl;
}
};
int main(void)
{
CMyBase *p = new CMyDrived();
p->DoSthAgain();
return 0;
}
getObj()声明为virtual时,输出第一行才是CMyDerived的信息,这时是动态联编。
否则无论访问限定符是什么(public private 抑或 protected),输出第一行都是CMyBase的信息。
分析一下:
函数并没有声明为virtual,那么就只是一个普通函数对不对,普通的成员函数是在编译期就决定的,属于静态联编。
因此根本就不关继承不继承什么事,对不对。这仅仅只是类内部普通成员函数的调用而已。
所以,输出信息肯定只是基类的输出,与子类无关。