讨论:关于C++中的多态性!不是很理解,请各位高手不吝赐教!

scantity 2004-12-08 03:09:28
大家都知道C++中的一个很重要的特点:多态性
我只知道实现方法:就是在基类的函数在它的前面加上virtual,在继承时重写这个函数,运行时可以通过实际对象的地址调用基类还是派生类中的函数
但是我不是很明白原理。

各位高手请把你们的理解写下来好吗?帮帮我!
比如说派生类的对象的内存结构到底是怎样的/
为什么要引入多态性?
这种运行时的多态性到底是如何实现的.....

请大家畅所欲言!
...全文
220 18 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
18 条回复
切换为时间正序
请发表友善的回复…
发表回复
hongzm 2004-12-09
  • 打赏
  • 举报
回复
书上的确写 内联函数不能是函数。但我又找到如下文章


C++如何处理内联虚函数

当一个函数是内联和虚函数时,会发生代码替换或使用虚表调用吗?

赵湘宁
为了弄清楚内联和虚函数,让我们将它们分开来考虑。通常,一个内联函数是被展开的。

class CFoo {
private:
int val;
public:
int GetVal() { return val; }
int SetVal(int v) { return val=v; }
};

这里,如果使用下列代码:

CFoo x;
x.SetVal(17);
int y = x.GetVal();

那么编译器产生的目标代码将与下面的代码段一样:

CFoo x;
x.val = 17;
int y = x.val;

你当然不能这么做,因为val是个私有变量。内联函数的优点是不用函数调用就能隐藏数据,仅此而已。

虚函数有多态性,意味着派生的类能实现相同的函数,不同的功能。假设GetVal被声明为虚函数,并且你有第二个以不同方法实现的类 CFoo2:

class CFoo2 : public CFoo {
public:
// virtual in base class too!
virtual int CFoo2::GetVal() { return someOtherVal; }
};

如果pFoo是一个CFoo或CFoo2指针,那么,无论pFoo指向哪个类CFoo或CFoo2,成员函数pFoo->GetVal都能调用成功。

如果一个函数既是虚拟函数,又是内联函数,会是什么情况呢?记住,有两种方式建立内联函数,

第一种是在函数定义中使用关键字 inline,如:

inline CFoo::GetVal() { return val; }

第二种是在类的声明中编写函数体,就象前面的CFoo2::GetVal一样。所以如果将虚函数体包含在类的声明中,如:

class CFoo {
public:
virtual int GetVal() { return val; }
};

编译器便认为这个函数GetVal是内联的,同时也是虚拟的。那么,多态性和内联特性如何同时工作呢?

编译器遵循的第一个规则是无论发生什么事情,多态性必须起作用。如果有一个指向CFoo对象的指针,pFoo->GetVal被保证去调用正确的函数。一般情况下,这就是说函数GetVal将被实例化为非内联函数,并有vtable(虚表)入口指向它们。但这并不意味着这个函数不能被扩展!再看看下面的代码:

CFoo x;
x.SetVal(17);
int y = x.GetVal();

编译器知道x是CFoo,而不是CFoo2,因为这个堆对象是被显式声明的。x肯定不会是CFoo2。所以展开SetVal/GetVal内联是安全的。如果要写更多的复杂代码:

CFoo x;
CFoo* pfoo=&x;
pfoo->SetVal(17);
int y = pfoo->GetVal();
...
CFoo2 x2;
pfoo = &x2;
pfoo->SetVal(17); //etc.

编译器知道pfoo第一次指向x,第二次指向x2,所以展开虚拟函数也是安全的。

你还可以编写更复杂的代码,其中,pfoo所指的对象类型总是透明的,但是大多数编译器不会做任何更多的分析。即使在前面的例子中,某些编译器将会安全运行,实例化并通过一个虚表来调用。实际上,编译器总是忽略内联需要并总是使用虚表。唯一绝对的规则是代码必须工作;也就是说,虚函数必须有多态行为。

通常,无论是显式还是隐式内联,它只是一个提示而已,并非是必须的,就象寄存器一样。编译器完全能拒绝展开一个非虚内联函数,C++编译器常常首先会报错:“内联中断-函数太大”。如果内联函数调用自身,或者你在某处传递其地址,编译器必须产生一个正常(外联?)函数。内联函数在DEBUG BUILDS中不被展开,可设置编译选项来预防。

要想知道编译器正在做什么,唯一的方法是看它产生的代码。对于微软的编译器来说,你可以用-FA编译选项产生汇编清单。你不必知道汇编程序如何做。我鼓励你完成这个实验;这对于了解机器实际所做的事情机器有益,同时你可学习许多汇编列表中的内容。

有关内联函数的东西比你第一次接触它时要复杂得多。有许多种情况强迫编译器产生正常函数:递归,获取函数地址,太大的那些函数和虚函数。但是如果编译器决定实例化你的内联函数,就要考虑把函数放在什么地方?它进入哪个模块?

通常类在头文件中声明,所以如果某个cpp包含foo.h,并且编译器决定实例化CFoo::GetVal,则在cpp文件中将它实例化成一个静态函数。如果十个模块包含foo.h,编译器产生的虚函数拷贝就有十个。实际上,可以用虚表指向不同类型的GetVal拷贝,从而是相同类型的对象只产生拷贝。一些链接器能巧妙地在链接时排除冗余,但一般你是不能指望他来保证的。

我们得出的结论是:最好不要使用内联虚函数,因为它们几乎不会被展开,即便你的函数只有一行,你最好还是将它与其它的类函数一起放在模块(cpp文件)中。当然,开发者常常将简短的虚函数放在类声明中-不是因为他们希望这个函数被展开为内联,而是因为这样做更方便和可读性更

不解~
dabears 2004-12-09
  • 打赏
  • 举报
回复
我也来复习一下:
多态可以实现子类成员函数重载基类的成员函数
我们在函数前面加VIRTUAL成为虚函数 实现多态
但是虚函数是有限制的:
1.只有类成员函数才能说明为虚函数
2.静态成员函数不能是虚函数
3.内联函数不能是虚函数
4.构造函数不能是虚函数,因为现在对象还是未定性的空间。
5.析构函数可以是虚函数,而且通常声明为虚函数
pomelowu 2004-12-09
  • 打赏
  • 举报
回复
《精通MFC》,刘晓华等著,电子工业出版社出版。
第一章有讲到C++对象的内存布局,剖析得不错。
不过整本书没看过,我一个同事有,偶尔当作工具书查查,不知道该不该建议购买;到书店翻翻就行了。
oyljerry 2004-12-08
  • 打赏
  • 举报
回复
多态主要是为了让基对象指针可以访问继承类的虚函数
scantity 2004-12-08
  • 打赏
  • 举报
回复
多态就是实现了“一种接口,多种方法”这个概念?

多谢各位,对多态性又加深了一些理解。

我觉得,学C++,概念很重要。
要想掌握好C++,一定要透彻的理解C++中的概念。
不过,C++确实很博大精深啊!
scantity 2004-12-08
  • 打赏
  • 举报
回复
《精通MFC》

这本书是谁写的啊?
yanw0212 2004-12-08
  • 打赏
  • 举报
回复
class A
{
...
};

class B public A
{
.....
};

class C public A
{
...
};

在定义某个函数时:
void function(A a)
{
....
}

在实际调用function()时。可以给函数传进B b,或C c;
C c;
function(c);

B b;
function(b);
powerbamboo 2004-12-08
  • 打赏
  • 举报
回复
再废话几句:
多态是从基于对象到面向对象的基础,观察者模式也用多态。
如果基类只有纯虚函数,就是COM的接口了
showjancn 2004-12-08
  • 打赏
  • 举报
回复
在C++中多态是通过虚函数来实现,建议查阅C++编程思想有一章专门介绍,相当年我反复看了很多遍。
powerbamboo 2004-12-08
  • 打赏
  • 举报
回复
《C++编程思想》中有很好的阐述,简单的大意:
基类定义virtual函数 Fun(),不同的派生类有各自不同的实现。
客户程序定义派生类对象,和基类指针,使用基类指针调用被virtual修饰的函数,程序会根据基类指针所指向的派生类的对象,调用派生类的实现。这样,同一个消息pBase->Fun(),有不同的响应。
lzzqqq 2004-12-08
  • 打赏
  • 举报
回复
每个对象里有一个默认的vtable
各种语言的实现机制好象是类似的。
beyondtkl 2004-12-08
  • 打赏
  • 举报
回复
INSIDE C++ OBJECT MODEL... 說的很清楚...

樓上的大致說了一些.
redex 2004-12-08
  • 打赏
  • 举报
回复
vptr->vtbl->addr_of_func1
addr_of_func2
... ...
hellopower 2004-12-08
  • 打赏
  • 举报
回复
多态,就是基类只是定义某些函数,但不一定在基类中实现,派生类根据自己的需要写具体的实现,如果派生类没有自己的实现,就用基类的函数
老夏Max 2004-12-08
  • 打赏
  • 举报
回复
虚函数的存在主要是为了使派生类的指针可以调用基类的函数,呵呵,例如:
class A
{
virtual FuncA();
}
class B:public A
{
//FunA()
}
定义一个B的指针B*pB = new B,这么使用pB->FuncA(),这时候如果B实现了和A同名的FuncA,那么调用的就是B的FuncA,如果B没有实现FuncA,那么就可以调用A的FuncA了,呵呵
指针调用什么函数主要看指针的原始类型。呵呵
《深入浅出MFC》中说的很好了!
longsheng2 2004-12-08
  • 打赏
  • 举报
回复
对啊,看看vtable的东西
快乐鹦鹉 2004-12-08
  • 打赏
  • 举报
回复
只要类中用到了virtual申明,那么这个类就自动会增加一个隐藏的成员变量来维护这个虚拟函数表。
pomelowu 2004-12-08
  • 打赏
  • 举报
回复
《精通MFC》(如果我没有把书名写错的话)一开始就有OO、虚函数表、智能指针这部分很不错的描述,建议楼主去看看

16,548

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC相关问题讨论
社区管理员
  • 基础类社区
  • AIGC Browser
  • encoderlee
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

        VC/MFC社区版块或许是CSDN最“古老”的版块了,记忆之中,与CSDN的年龄几乎差不多。随着时间的推移,MFC技术渐渐的偏离了开发主流,若干年之后的今天,当我们面对着微软的这个经典之笔,内心充满着敬意,那些曾经的记忆,可以说代表着二十年前曾经的辉煌……
        向经典致敬,或许是老一代程序员内心里面难以释怀的感受。互联网大行其道的今天,我们期待着MFC技术能够恢复其曾经的辉煌,或许这个期待会永远成为一种“梦想”,或许一切皆有可能……
        我们希望这个版块可以很好的适配Web时代,期待更好的互联网技术能够使得MFC技术框架得以重现活力,……

试试用AI创作助手写篇文章吧