C++ 虚函数机制-(转载)

来源:互联网 发布:苹果电脑平面设计软件 编辑:程序博客网 时间:2024/05/20 09:22

2:对虚函数的理解(virtual)
2.1:只有申明为virtual的函数才能动态绑定(late binding),缺省情况下函数不具有该特性。
这说明一个问题:静态绑定和动态绑定的区别
静态绑定是在程序编译时期就一定分配好了地址,绑定完毕。
动态绑定就是只有等到程序运行时刻执行到这一条语句的时候,才确定调用的哪一个函数。
而实现动态绑定,只有在有virtual的情形下才有意义。所以可以这么理解:virtual函数可以用来实现动态绑定;
动态绑定也只有在virtual存在的情况下才具有意义。

2.2:多态:允许子类的指针赋值给父类的指针。一种常见的情形就是父类的指针指向了派生类的对象。
所谓多态:必须用virtual来实现,这个是前提条件。而虚函数的作用,就是在执行期实现动态绑定。
多态----虚函数----动态绑定:三者的关系。

2.3:若基类定义了虚函数,则基类会生成一张虚函数表,这张表里保持着类中虚函数的地址。
当然,要操作这张虚函数表,就必须有一个虚函数指针来遍历这张表。而这虚函数表指针什么时候创建?
又是属于类,还是对象的呢?
虚函数表,是属于类的,因为只要有虚函数,类就会创建虚函数表。
而指向虚函数表的指针则是属于对象的,只有当一个对象创建时刻,在调用默认构造函数的时刻,才创建这个虚函数表
指针,这就解释了为什么虚函数可以用来实现多态。vptr是在运行时刻创建的(对象创建时)。
类对象包含有的东西:非静态的数据成员,以及(有虚函数时候)一个vptr。就这些东西。
那些类中的成员函数,都是在代码段,这些函数是借口,供外界调用。
类中可以有静态的数据成员,以及静态的方法:静态的数据成员,是在静态区(全部变量,static变量都放在那里,
还有字面值常量也在那里),静态方法只能调用静态的数据成员。这个在单件模式中,应用到了。
这里会有很多sizeof的问题,仔细分析应该没有任何问题。

2.4:基类和派生类的关系
首先是对基类和派生类都求sizeof()的问题

(a):测试代码
#include
<iostream>
using namespace std;

class A
{
public:
int m;
private:
int n;
};

class B : public A
{
public:
int k;
};

int main()
{
A a;
cout
<<sizeof(a)<<endl; //8
B b;
cout
<<sizeof(b)<<endl; //12
return 0;
}
sizeof(a)=8,很容易知道。
sizeof(b)=12,这就说明了基类的private成员变量也被继承到派生类中,只是派生类无法直接去访问
这些基类的private成员。但,仍然可以通过派生类的对象去调用从基类继承下来的成员函数,去访问这些
基类的私有成员变量。
(b):测试代码:说明上面这个问题
---派生类对象去调用基类方法访问基类私有成员变量。
#include
<iostream>
using namespace std;

class A
{
public:
int m;
A()
{
n
=10;
}
void Count()
{
n
++;
}
void Print()
{
cout
<<n<<endl;
}
private:
int n;
};

class B : public A
{
public:
int k;

};

int main()
{
A a;
cout
<<sizeof(a)<<endl; //8
B b;
cout
<<sizeof(b)<<endl; //12
b.Count();
b.Print();
//11
return 0;
}
 

2.5:再谈虚函数问题<派生类的虚函数表>
基类的虚函数,会派生到派生类的虚函数表中,而且在派生类虚函数表中的位置和这个虚函数在基类表中的
位置是一样的。若是派生类重写了基类的某个虚函数,则在派生类虚函数表中把重写的虚函数的地址放进去,
覆盖掉原来基类那个虚函数的地址,这就是当基类的指针指向派生类的对象的时候,会根据这个对象的虚函数表
指针找到这张虚函数表,去调用这个虚函数:若派生类改写,则调用的是派生类的虚函数,若未改写,则调用的
还是基类的虚函数。这就是多态的具体实现哦......(咳,不知道说清楚没有...)

 

2.6:纯虚函数
这个问题困扰了很久,主要是搞不清楚里面的实现机制是怎么回事!
纯虚函数,一般是在基类中才有纯虚函数,也只有这样才具有意义。
声明一个虚函数的时候把它设置为0,就是纯虚函数了。具有纯虚函数的基类,就是虚基类,这个类不能实例化,
只提供接口。为什么不能实例化呢?主要是纯虚函数的地址在虚函数表中无法确定,所以不能实例化。
一个经常性的用法:把基类的析构函数设置为纯虚函数。
这样的好处:可以防止内存泄露。当基类的指针指向派生类对象时,在程序结束时,delete p(p为基类指针),
接下来发生的事情是:首先会调用派生类的析构函数,清理派生类的这块内存,接着删除掉这块内存;
再调用基类的析构函数,清理基类的这块内存,在删除掉这块内存。这就保证了不发生内存泄露。
若:基类的析构函数未声明为纯虚函数,则在delete p的时候,就不会去调用派生类的析构函数去完成派生类
内存清理工作了,发生内存泄露。
当基类的析构函数声明为纯虚函数时,那派生类的析构函数也是虚函数。
构造函数不可以被声明为虚的,为什么呢?
举个例子:基类A和派生类B,B继承自A,A的构造函数声明为虚函数,则在A类的虚函数表中就登记了这个虚构造函数
的地址。好,现在A创建一个对象a,则要去调用构造函数完成初始化工作,问题出来了,这个构造函数已经登记在虚函
数表中,我没办法找打它,为什么呢?因为我要通过一个vptr去找遍历这个虚函数表,而这个时候vptr没有。为什么没有呢?
因为,vptr是在创建对象的时候,调用默认构造函数的时候才创建的。
这是一个死循环:我要调用构造函数,而构造函数在虚函数表中;那就需要一个vptr去访问这个虚函数表;而这个
vptr却是在默认构造函数中才创建的,而此刻这个构造函数却在虚函数表中。结果就是找都找不到这个构造函数。

原创粉丝点击