OxFAN

::Just For Fun::

   :: 首页 :: 联系 :: 聚合  :: 管理
  3 Posts :: 1 Stories :: 1 Comments :: 0 Trackbacks

常用链接

留言簿(4)

我参与的团队

搜索

  •  

最新评论

阅读排行榜

评论排行榜

2009年4月30日 #

[转自TopLanguages里的 一篇帖子,并非原译文,《常见逻辑谬误》译文地址:http://www.yeeyan.com/articles/view/65452/28581]

当你与别人讨论,尝试获得答案或解释时,你可能会遇到一些人犯上逻辑谬误。这样的讨论是无意义的。你可能尝试向对手要求证据或提供其他假设,令你获得更好或更简 单的解释。如果都失败,可以尝试指出你讨论对手的问题。你可辨认他的逻辑问题以免深究,以及可告知讨论对手关於他的谬误。以下是简单介绍其中最常见的谬误: 

人身攻击(ad hominem): 
拉丁语「向着人」的意思。辩者用人身攻击来攻击对手,而不是在讨论议题。当辩者不能用证据、事实或理由去维护他的立场,他可能透过标签、稻草人、骂人、挑衅及愤 怒的人身攻击方式来攻击对手。 

诉诸无知(appeal to ignorance / argumentum ex silentio): 
以诉诸无知作为某些证据。(例如:我们没有证据说神不存在,所以祂一定存在。又例如:由於我们没有关於外星人的知识,这表示他们并不存在。)对某些东西的无知, 是与它的存在与否无关。 

全知论据(argument from omniscience): 
(例如:所有人都相信某些东西,每个人都知道的。) 
辩者需要有全知能力以清楚每个人的信仰、怀疑或他们的知识。小心如「所有」、「每个人」、「每种东西」、「绝对」等词语。 

诉诸信心(appeal to faith): 
(例如:如果你不相信,是不能清楚明白的。)如果辩者倚仗信心作为他论据的根基,那麽你在以後的讨论所能得到的将不多。根据定义,「信心」是倚靠相信,并非靠逻 辑或证据支持。信心倚赖非理性的思想,并会产生不妥协。 

诉诸传统(appeal to tradition): 
(类似主流思想谬误)(例如:占星、宗教、奴隶)只因为人们以此为传统,与它本身的存活能力无关。 

诉诸权威(argument from authority / argumentum ad verecundiam): 
以「专家」或权威的说话作论据的根基,而不是用逻辑或证据来支持该论据。(例如:某某教授相信创造科学。)只由於某个权威的声称,不足以代表他已令这声称正确。 假如辩者展示某专家的论据,那麽看看它有否伴随着原因,以及它背後证据的来源。 

不良後果论据(argument from adverse consequences): 
(例如:我们应判被告有罪,否则其他人会仿效而犯上类似的罪行。)只因为讨厌的罪行或行为出现,并不足以代表被告犯了该罪,或代表我们应判他有罪。(又例如:灾 难的出现是因为神惩罚不信者,所以我们都应该信神。)只因灾害或惨剧发生,与神是否存在、或我们该信甚麽并无关系。 

恐吓论据(argumentum ad baculum): 
论据根基於恐惧或威胁。(例如:如果你不信神,你将会下地狱被火烧。) 

无知论据(argumentum ad ignorantiam): 
误导性的论据,倚仗於人们的无知。 

群众论据(argumentum ad populum): 
论据诉诸感性的弱点,而非事实和原因,旨在煽动群众的支持。 

主流思想谬误(bandwagon fallacy): 
只因为很多人相信或实践,便认为一个思想有价值。(例如:大多数人相信神,所以它一定是真的。)只因为很多人相信某些东西,与那是事实与否并无关系。如很多人在 黑死病时期都相信疫症是由於魔鬼引起,有多少人相信跟疫症的起因全无关系。 

窃取论点(begging the question): 
(例如:我们必须鼓励年青人去崇拜神,以灌输道德行为。)可是宗教与崇拜真的产生道德行为吗? 

循环论证(circular reasoning): 
陈述某命题,而其实那正是需要被证实的。(例如:神存在是因为圣经有记载,圣经存在是因为神所默示的。) 

构成谬误(composition fallacy): 
当某论据的结论,是倚靠由某东西从部份至整体、或从整体至部份的错误特性。(例如:人类有意识,而人体和人脑都是由原子组成,所以原子都有意识。又例如:文书处 理软件由佷多原位组(byte)组成,所以一个原位组是组成文书处理软件的一部份。) 

确认性偏见(confirmation bias): 
(类似监视下的选择)这是指一种选择性的思想,集中於支持相信的人已相信的证据,而忽略反驳他们信念的证据。确认性偏见常见於人们以信心、传统及成见为根据的信 念。例如,如果有些人相信祈祷的力量,相信的人只会注意到少量「有回应」的祈祷,而忽略大多数无回应的祈祷。(这表示祈祷的价值最差只是随机,最好也只有心理上 的安慰作用。) 

混淆相关及起因(confusion of correlation and causation): 
(例如:玩象棋的人男性比女性多,所以男性棋艺也比女性高。又例如:儿童观看电视的暴力场面,成长後会有暴力倾向。)但是,那是由於电视节目引致暴力,还是有暴 力倾向的儿童喜欢观看暴力节目?真正引致暴力的原因可能是完全与电视无关。Stephen 
Jay Gould 把相关引致的无效假设称为「可能是人类推理上两三种最严重和最普遍的错误」。 

错误二分法/排中(excluded middle / false dichotomy): 
只考虑极端。很多人用亚理士多德式(Aristotelian)的「非此即彼」的逻辑去解释上下、黑白、对错、爱恶等。(例如:你若非喜欢它,就是不喜欢它。他 如不是有罪,就是无罪。)很多时人们没有看到在两个极端之间出现的连续,这个宇宙也包含很多「可能」的。 

隐藏证据(half truths / suppressed evidence): 
故意欺骗的陈述,通常隐藏一些事实,而那是构成准确描述所必需的。 

暗示/诱导性问题(loaded questions): 
问题加入假设,一旦回答便显示了一个暗示性的同意。(例如:你停止了打你的妻子吗?) 

无意义的问题(meaningless question): 
(例如「上面有多高?」「一切皆可能吗?」)「上面」描述方向,不是可衡量的单位。假如一切都证实可能,那麽「不可能」都可能出现,矛盾便出现。尽管一切不一定 证实可能,亦可以有无数的可能和无数的不可能。很多无意思的问题都包含了空废的词语,如 
"is," "are," "were," "was," "am," "be," 或 "been." 

统计性质的误解(misunderstanding the nature of statistics): 
(例如:大多数美国人都死在医院内,所以应尽量远离医院。)「统计显示,通常染上进食习惯的人,很少能生存。」-- Wallace Irwin 

不当结论(non sequitur): 
拉丁语「它没有跟随」的意思。推断或结论没有跟随已建立的前提或证据。(例如:在月圆时出生的人较多。结论:月圆引致出生率上升。)可是,是月圆引致较多出生, 还是由於其他原因(可能是统计上的期望差异)? 

监视下的选择(observational selection): 
(类似确认性偏见)指出有利的,却忽略不利的事实。谁去过拉斯维加斯(Las 
Vegas)赌场会见到人们在赌桌上和老虎机上赢钱,赌场经理会响钟及鸣笛以公告胜利者,却永不会提及失败者。这可令人觉得胜出的机会看来颇大,但是事实却刚刚 相反。 

错误因果(post hoc, ergo propter hoc): 
拉丁语「它发生在之後,所以它是结果。」与不当结论类似,不过与时间有关。(例如:她去了中国之後病了,所以中国有些东西令到她病。)可能她的病是由於其他原因 ,与中国完全无关。 

证明不存在(proving non-existence): 
当辩者无法为他的声称提供证据,他可能会挑战他的对手,叫对手证明他的声称不存在。(例如:证明神不存在;证明不明飞行物体未曾到过地球;等等)尽管有人可以在 特定的限制中证明不存在,如在盒中没有某些东西,可是却无法证明普遍性、绝对性或认知性的不存在。无人能证明一些不存在的东西。提出声称的人必需自己证明那声称 的存在。 

扯开话题(red herring): 
辩者改变话题,以分散注意力。 

实体化谬误(reification fallacy): 
当人们把抽象的信念或假设性的构想,当作是实在的事物。如以IQ题作为真实衡量智慧的方法;由抽象的社会构想而来的种族概念(尽管基因属性的存在),源自经拣选 的属性组合,或者标签某一组人;占星;耶稣;圣诞老人;等等。 

滑坡谬误(slippery slope): 
一个步骤、法律、或行动的改变,可引致不良的後果。(例如:如果我们容许医生帮助安乐死,那麽去到最後,政府会控制我们如何死。)不一定只因为我们的改变,出现 了滑坡,便会使预计的後果实现。 

片面辩护(special pleading): 
以新鲜或特别的声称,抗衡对手的陈述;展示论据时只着重主题中有利或单一的范畴。(例如:神为何在世上创造这麽多苦难?答案是:你必须明白,神自有祂神奇的安排 ,我们没有特权去知道的。又例如:星座是准确的,但你必须先了解背後的理论。) 

小众统计(statistics of small numbers): 
类似监视下的选择。(例如:我的父母吸了一世烟,但他们从未患过癌症。又例如:我不管其他人如何讲 Toyota,我的 Toyota 
却从未发生过问题。)只指出少量有利数据,与整体机会并无关系。〔译注:把 Yugo 改成 Toyota 使更易明白〕 

稻草人谬误(straw man): 
创造一个虚假的情况,然後去攻击它。(例如:进化论者认为所有事物都是随机的。)大部份进化论者认为,在自然选择的解释下,可能包括偶发的成份,但并非全然依靠 随机。抹黑你的对手只会令讨论的功能偏离。 

你我皆错(two wrongs make a right): 
指控其他人跟我们所做的同样事情,为我们所作所为辩护。(例如:你有甚麽资格批评我?你也跟我做着一模一样的事情!)控方的所犯的罪与讨论本身并无关连。 

分散注意力的谬误(Fallacies of Distraction) 

     * 两难推理(False Dilemma) 
错谬:为多於一个答案的问题提供不足(通常两个)的选择,即是隐藏了一些选择,最典型的表现是非黑即白观点。 
     * 例子:萨达姆是邪恶的,所以美军是正义之师。 
     * 解释:除正邪之争外,还有邪邪之争及许多难分正邪的纷争,所以不能单以萨达姆邪恶便认定美军正义。 

     * 诉诸无知(From Ignorance) 错谬:因为不能否定,所以必然肯定,反之亦然。 
     * 例子:没有人能证明鬼不存在,那麽鬼肯定存在。 
     * 解释:总有些事是既不能否定,亦不能肯定的。除了肯定和否定,我们还可以存疑吧! 

     * 滑坡谬误(Slippery Slope) 错谬:不合理使用连串因果关系。 
     * 
例子:迟到的学生要判死刑。因为迟到是不用功的表现;将来工作也不勤力;不勤力导致公司损失;公司损失就会倒闭;公司倒闭会使人失业;失业造成家庭问题;家庭问 题导致自杀率上升,为了防止自杀率上升,我们应判迟到的学生死刑。 
     * 
解释:滑坡谬误中假定了连串「可能性」为「必然性」。比方说,迟到是否「必然」是不用功的表现?将来工作又是否「必然」不勤力?答案可想而知。例子虽然夸张,但 其实许多时候大家亦会犯相同错误而不自知。 

     * 复合问题(Complex Question) 错谬:一条问题内包含两个无关的重点。 
     * 例子:你还有没有干那非法勾当?(你有干非法勾当吗?是否还有继续?) 
     * 解释:简单的一句提问,其实隐藏了两个问题。你给予其中一条问题的答案,并不一定和另外一条的一样。例如你有干非法勾当,但未必等於你还有继续。 

诉诸其他支持(Appeals to Motives in Place of Support) 

     * 诉诸势力(Appeal to Force) 错谬:以势力服人。 
     * 例子:若你不想被解雇,你必须认同公司的制度。 
     * 解释:这是以工作机会强迫员工认同制度,员工不是依据制度好坏来决定认同与否。 

     * 诉诸怜悯(Appeal to Pity) 错谬:以别人的同情心服人。 
     * 例子:希望你接受我这个多月来天天通宵撰写的建议书。 
     * 解释:建议书的好坏,不在乎花了多少时间,而是取决於其内容,提出「多月来天天通宵撰写」只为搏取同情。 

     * 诉诸结果(Consequences) 错谬:以讨好或不讨好的结果服人。 
     * 例子:你若不听我的话,我便打你,不准你外出,扣起你的零用。 

     * 诉诸不中肯字词(Prejudicial Language) 错谬:以不中肯的字词修饰论点。 
     * 例子:凡是爱国的人都会认同订立国家安全法的必要。 

     * 诉诸大众(Popularity) 错谬:以被广泛接纳为理由服人。 
     * 例子:看!人人都这样说,还会错吗? 

     * 一厢情愿(Wishful Thinking) 错谬:以自己单方面想法作为论证根据。 
     * 例子:因为我希望明天在户外打球,所以明天一定天晴。 

改变话题(Changing the Subject) 

     * 人身攻击(Attacking the Person) 错谬〔一〕:以攻击发言人代替攻击其论点(因人废言)。 
     * 例子    :张厂长反对陈主任增加成本会计部的建议:「你当然说成本会计十分重要,因为你是会计主任。」 
     * 错谬〔二〕:由回应论点改变为攻击论点发起人的处境。 
     * 例子    :你竟相信那些草根阶层的说话? 
     * 错谬〔三〕:提出「你也是!」的不恰当反问作论据。 
     * 例子    :父:吸烟对健康不好!儿:为什麽你也吸? 

     * 诉诸权威(Appeal to Authority) 错谬〔一〕:诉诸讨论的范畴以外的权威人士。 
     * 例子    :经济学家都认为爱因斯坦的相对论是不可能的。 
     * 错谬〔二〕:诉诸权威人士的个人意见。 
     * 例子    :罗局长说:「学生是政府的政策下最大得益者,所以学生无权批评领导人」 
     * 解释    :学生是政府的政策下最大得益者只是罗局长的说话,事实上学生是否政府的政策下最大得益者,却没有一个客观答案。 
     * 错谬〔三〕:该范畴的权威人士不是认真的回应。(例如:只是在开玩笑/喝醉。) 
     * 例子    :「有香车自然有美人,BENZ的总公司董事长都这样说啦!」 

     * 匿名权威(Anonymous Authority) 错谬:匿名的权威人士使人不能确定其权威性。 
     * 例子:有位心理学家曾经说过,每人都有犯罪倾向。 

     * 作风盖过本体(Style Over Substance) 错谬:讨论者以作风盖过事件本身使人认为其论点正确。 
     * 例子:以他一向的对人的态度,他一定不会对你好的。 

归纳的谬误(Inductive Fallacies) 

     * 轻率的归纳(Hasty Generalization) 错谬:用作归纳总体的样本太少。 
     * 例子:我问了十个人,有九个说反对民主党。结论:原来九成香港人反对民主党。 
     * 解释:单凭十个人论断香港七百万人?未免太轻率吧。若说访问了数万人,得出来的结果便较有说服力。 

     * 不具代表性的例子(Unrepresentative Sample) 错谬:用作归纳的例子不能代表其总体。 
     * 例子:叶继欢持械行劫;林过云奸杀多女;欧阳炳强纸盒藏屍。香港人肯定有杀人倾向。 

     * 不当类比(Weak Analogy) 错谬:以两件不相似的事件/事物作类比。 
     * 例子:他对朋友这麽好,对女朋友一定很好呢。 

     * 懒散的归纳(Slothful Induction) 错谬:否定归纳得出来的恰当结论。 
     * 例子:即使有万多个实验证明化学物质影响我们的感觉,我就是不相信。 

     * 排除证据谬误(Fallacy of Exclusion) 错谬:故意把重要的证据隐藏,以得出不同的结论。 
     * 例子: 

统计三段论的谬误(Fallacies Involving Statistical Syllogisms) 

     * 例外(Accident) 错谬:以概括情况加诸应有的例外情况。 
     * 例子:政府法例规定,行走此公路的汽车最高时速为七十公里。所以即使载着快要生产的产妇,亦不可开得快过七十公里。 

     * 相反的例外(Converse Accident) 错谬:以例外情况加诸应有的概括情况。 
     * 例子:我们准许濒死的病人注射海洛英,基於人人平等,也应让其他人注射海洛英。 

因果的谬误(Causal Fallacies) 

     * 巧合谬误(Coincidental Correlation) 错谬:以个别情况肯定某种因果关系。 
     * 例子:希希吃了一种药,出现过敏反应。因此,希希认为这种药必然导致过敏反应。 
     * 解释:希希遇到的只是个别例子,不能因此论断该药必然导致过敏反应。 

     * 复合结果(Joint Effect) 错谬:当两件事都为某原因的结果时,以一事为另一事的原因。 
     * 例子:记者报导离乡背井的战争难民中的一家人:「他们因为房子被炮火所毁而逃到这里。」 
     * 解释:炮火导致这家人的房子被毁及离乡逃难;房子被毁并不导致这家人离开原居地。 

     * 无足轻重(Genuine but Insignificant Cause) 错谬:举出无足轻重的次要原因论证,遗漏真正的主因。 
     * 例子:吸烟使香港空气质素每况愈下。 
     * 解释:导致香港空气质素差的主因是交通公具的废气和天气情况。 

     * 倒果为因(Wrong Direction) 错谬:颠倒事件的因果关系。 
     * 例子:癌症导致吸烟 
     * 解释:吸烟才是癌症的原因。 

     * 复合原因(Complex Cause) 错谬:只指出多个原因中的其中一个为事件主因。 
     * 例子:你一日到晚都只是玩游戏机而不温习,难怪你考试成绩那麽差。 
     * 解释:除了玩游戏机而不温习外,还有其他原因,例如考试期间一时大意或者试题太难,但它们和玩游戏机一样,不一定是主因。 

论点缺失谬误(Missing the Point) 

     * 乞求/窃取论点(Begging the Question) 错谬:以假定正确的论点得出结论。 
     * 例子:我知道有上帝,因为《圣经》是这样说,而《圣经》是不会错,因为它是上帝写的。 

     * 不恰当结论(Irrelevant Conclusion) 错谬:提出作支持的论据主要支持其他结论。 
     * 例子: 

     * 稻草人谬误(Straw Man) 错谬:扭曲对方论据以攻击之。 
     * 例子:进化论说人是由猩猩演化而来。 
     * 解释:进化论只是说人和猩猩有共同祖先。 

含糊不清谬误(Fallacies of Ambiguity) 

     * 含糊其辞(Equivocation) 错谬:使用有多於一个含义的字眼。 
     * 例子:甲:喇叭中学又发生学生殴斗事件。乙:噢!是九龙那所吗?甲:&%^%$&%$#... 
     * 解释:甲这里没有表明是新界喇叭,使乙误会成九龙的喇叭书院。 

     * 模棱两可(Amphiboly) 错谬:句子结构含多种解释方法。 
     * 例子: 

     * 重音谬误(Accent) 错谬:以重音强调某字眼或字句,达致其他意思。 
     * 例子: 

类目错误(Category Errors) 

     * 构成谬误(Composition) 错谬:以总体的某部份符合某条件推断总体均符合某条件。 
     * 例子: 

     * 分割谬误(Division) 错谬:以总体符合某条件推断总体的所有部份均符合某条件。 
     * 例子: 

不根据前题的推理(Non Sequitur) 

     * 肯定後件(Affirming the Consequent) 错谬:所有依此结构的推论:若A则必定B;B,所以便A。 
     * 例子:如果他在中环,他一定在港岛。因此如果他现在在港岛,他一定在中环。 
     * 解释:在港岛不一定要在中环,可以在金钟、湾仔、铜锣湾等。因港岛包含了以上各项。 

     * 否定前件(Denying the Antecedent) 错谬:所有依此结构的推论:若A则必定B; 非A,所以非B。 
     * 例子:如果他在中环,他一定在港岛。因此如果他现在不在中环,那麽他一定不在港岛。 
     * 解释:不在中环,也可以在金钟、湾仔、铜锣湾等。因港岛包含了以上各项。 

     * 前後矛盾(Inconsistency) 错谬:断言两件矛盾的事件都正确 

posted @ 2009-04-30 13:02 OxFAN 阅读(430) | 评论 (0)编辑 收藏

(注:个人觉得这篇文章不错故转载了)

     C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。

关于虚函数的使用方法,我在这里不做过多的阐述。大家可以看看相关的C++的书籍。在这篇文章中,我只想从虚函数的实现机制上面为大家 一个清晰的剖析。

当然,相同的文章在网上也出现过一些了,但我总感觉这些文章不是很容易阅读,大段大段的代码,没有图片,没有详细的说明,没有比较,没有举一反三。不利于学习和阅读,所以这是我想写下这篇文章的原因。也希望大家多给我提意见。

言归正传,让我们一起进入虚函数的世界。

虚函数表

对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。 在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了 这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

这里我们着重看一下这张虚函数表。在C++的标准规格说明书中说到,编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证正确取到虚函数的偏移量)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

听我扯了那么多,我可以感觉出来你现在可能比以前更加晕头转向了。 没关系,下面就是实际的例子,相信聪明的你一看就明白了。

假设我们有这样的一个类:

class Base {

public:

virtual void f() { cout << "Base::f" << endl; }

virtual void g() { cout << "Base::g" << endl; }

virtual void h() { cout << "Base::h" << endl; }

};

按照上面的说法,我们可以通过Base的实例来得到虚函数表。 下面是实际例程:

typedef void(*Fun)(void);

Base b;

Fun pFun = NULL;

cout << "虚函数表地址:" << (int*)(&b) << endl;

cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl;

// Invoke the first virtual function

pFun = (Fun)*((int*)*(int*)(&b));

pFun();

实际运行经果如下:(Windows XP+VS2003, Linux 2.6.22 + GCC 4.1.3)

虚函数表地址:0012FED4

虚函数表 — 第一个函数地址:0044F148

Base::f

通过这个示例,我们可以看到,我们可以通过强行把&b转成int *,取得虚函数表的地址,然后,再次取址就可以得到第一个虚函数的地址了,也就是Base::f(),这在上面的程序中得到了验证(把int* 强制转成了函数指针)。通过这个示例,我们就可以知道如果要调用Base::g()和Base::h(),其代码如下:

(Fun)*((int*)*(int*)(&b)+0); // Base::f()

(Fun)*((int*)*(int*)(&b)+1); // Base::g()

(Fun)*((int*)*(int*)(&b)+2); // Base::h()

这个时候你应该懂了吧。什么?还是有点晕。也是,这样的代码看着太乱了。没问题,让我画个图解释一下。如下所示:

注意:在上面这个图中,我在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“\0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下,这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。

下面,我将分别说明“无覆盖”和“有覆盖”时的虚函数表的样子。没有覆盖父类的虚函数是毫无意义的。我之所以要讲述没有覆盖的情况,主要目的是为了给一个对比。在比较之下,我们可以更加清楚地知道其内部的具体实现。

一般继承(无虚函数覆盖)

下面,再让我们来看看继承时的虚函数表是什么样的。假设有如下所示的一个继承关系:

请注意,在这个继承关系中,子类没有重载任何父类的函数。那么,在派生类的实例中,其虚函数表如下所示:

对于实例:Derive d; 的虚函数表如下:

我们可以看到下面几点:

1)虚函数按照其声明顺序放于表中。

2)父类的虚函数在子类的虚函数前面。

我相信聪明的你一定可以参考前面的那个程序,来编写一段程序来验证。

一般继承(有虚函数覆盖)

覆盖父类的虚函数是很显然的事情,不然,虚函数就变得毫无意义。下面,我们来看一下,如果子类中有虚函数重载了父类的虚函数,会是一个什么样子?假设,我们有下面这样的一个继承关系。

为了让大家看到被继承过后的效果,在这个类的设计中,我只覆盖了父类的一个函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子:

我们从表中可以看到下面几点,

1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。

2)没有被覆盖的函数依旧。

这样,我们就可以看到对于下面这样的程序,

Base *b = new Derive();

b->f();

由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。

多重继承(无虚函数覆盖)

下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。

对于子类实例中的虚函数表,是下面这个样子:

我们可以看到:

1) 每个父类都有自己的虚表。

2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

多重继承(有虚函数覆盖)

下面我们再来看看,如果发生虚函数覆盖的情况。

下图中,我们在子类中覆盖了父类的f()函数。

下面是对于子类实例中的虚函数表的图:

我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:

Derive d;

Base1 *b1 = &d;

Base2 *b2 = &d;

Base3 *b3 = &d;

b1->f(); //Derive::f()

b2->f(); //Derive::f()

b3->f(); //Derive::f()

b1->g(); //Base1::g()

b2->g(); //Base2::g()

b3->g(); //Base3::g()

安全性

每次写C++的文章,总免不了要批判一下C++。这篇文章也不例外。通过上面的讲述,相信我们对虚函数表有一个比较细致的了解了。水可载舟,亦可覆舟。下面,让我们来看看我们可以用虚函数表来干点什么坏事吧。

一、通过父类型的指针访问子类自己的虚函数

我们知道,子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数,但我们根本不可能使用下面的语句来调用子类的自有虚函数:

Base1 *b1 = new Derive();

b1->f1(); //编译出错

任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。(关于这方面的尝试,通过阅读后面附录的代码,相信你可以做到这一点)

二、访问non-public的虚函数

另外,如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的。

如:

class Base {

private:

virtual void f() { cout << "Base::f" << endl; }

};

class Derive : public Base{

};

typedef void(*Fun)(void);

void main() {

Derive d;

Fun pFun = (Fun)*((int*)*(int*)(&d)+0);

pFun();

}

结束语

C++这门语言是一门Magic的语言,对于程序员来说,我们似乎永远摸不清楚这门语言背着我们在干了什么。需要熟悉这门语言,我们就必需要了解C++里面的那些东西,需要去了解C++中那些危险的东西。不然,这是一种搬起石头砸自己脚的编程语言。

posted @ 2009-04-30 11:02 OxFAN 阅读(165) | 评论 (1)编辑 收藏

2009年4月29日 #

刚刚落户cnblog,又来到了cppblog,这样cnblog上的空间可能就要荒废了,怪只能怪我没有早发现cppblog。
posted @ 2009-04-29 13:02 OxFAN 阅读(138) | 评论 (0)编辑 收藏