关于C++ Primer中“通过基类调用被屏蔽的虚函数”

listenxu 2014-07-07 09:57:34
在C++ Primer 15.5.4中有一个例子,看了很久没有看懂,请教一下大家(分割线中是书中的原话);
===============================================================
class Base {
public:
virtual int fcn();
};
class D1 : public Base {
public:
// hides fcn in the base; this fcn is not virtual
int fcn(int); // parameter list differs from fcn in Base
// D1 inherits definition of Base::fcn()
};
class D2 : public D1 {
public:
int fcn(int); // nonvirtual function hides D1::fcn(int)
int fcn(); // redefines virtual fcn from Base
};
从 Base 继承的虚函数不能通过 D1 对象(或 D1 的引用或指针)调用,因为该函数被 fcn(int) 的定义屏蔽了。

通过基类类型的引用或指针调用函数时,编译器将在基类中查找该函数而忽略派生类:

Base bobj; D1 d1obj; D2 d2obj;
Base *bp1 = &bobj, *bp2 = &d1obj, *bp3 = &d2obj;
bp1->fcn(); // ok: virtual call, will call Base::fcnat run time
bp2->fcn(); // ok: virtual call, will call Base::fcnat run time
bp3->fcn(); // ok: virtual call, will call D2::fcnat run time
===============================================================

对于bp1和bp3,都没有问题,但是对于bp2 ,为什么是可以调用的,存在如下疑问:

1) bp2 实际指向的是D1 类型的对象,但是D1 类 中定义了fcn,屏蔽了Base中的fcn,为什么会调用Base中的fcn而不出错?
2) 对于“通过基类类型的引用或指针调用函数时,编译器将在基类中查找该函数而忽略派生类” 这句话,无法理解;对于虚函数,不是应该动态绑定吗?动态绑定的后果就是很有可能调用派生类中重新定义的虚函数;为什么这里讲“忽略派生类”? 如果忽略了派生类,如何实现动态绑定?
...全文
439 17 打赏 收藏 转发到动态 举报
写回复
用AI写文章
17 条回复
切换为时间正序
请发表友善的回复…
发表回复
苏客达 2014-07-07
  • 打赏
  • 举报
回复
引用 14 楼 listenxu 的回复:
[quote=引用 11 楼 sukiida 的回复:] [quote=引用 6 楼 listenxu 的回复:] [quote=引用 1 楼 sukiida 的回复:] 1) D2中存在两个fcn,分别是基类继承来的,和自己定义的。因为参数列表不同,这是重载的两个不同函数。 ->我之前理解和你一样,但是在effective C++ 条款33中,明确讲了这种是属于遮掩,也就是如果D1中定义了和Base中同名的函数,调用函数的时候,不管是否是虚函数,形参是否相同,D1中会屏蔽Base中的同名函数,而无法调用到基类的函数 2)看上下文,肯定有具体情况,一句摘出来的话不能判断书里具体是要表达什么意思。
--> 书中就孤零零的这一句[/quote] 首先,在例子的前面一页有一个深色强调句子:如果派生类定义了重载成员,则通过派生类型只能访问派生类中重定义的那些成员。也就是D1 d1; d1.fcn(); 这样的调用是违法的。 然后,给的例子是为了说明“通过基类调用被屏蔽的虚函数”。虽然 d1.fcn(); 违法,但是通过指向基类的指针来动态调用 bp2->fcn(); 则是合法。 最后,“关键概念:名字查找与继承”说明什么这样的调用是合法的。前两条简单,直接略过。第三条,在基类找到了满足bp2->fcn(); 的原型,调用合法。第四条,普通函数的话,就调用基类的成员函数。虚函数则通过虚函数表查找。“如果函数是虚函数且通过引用或指针调用,则编译器生成代码以确定根据对象的动态类型运行那个函数版本”。我的理解是通过动态调用到D1中的 fcn() 但是此时 fcn() 作用域仍然在基类中(因为是通过基类指针调用的),没有被屏蔽,调用合法。 之前我也没有注意到这个细节,有什么错误欢迎后面的高手指正。[/quote] 如果把“通过基类调用被屏蔽的虚函数”。虽然 d1.fcn(); 违法,但是通过指向基类的指针来动态调用 bp2->fcn(); 则是合法”当成一个C++的定律的话,确实可以解释bp2->fcn() 可以成功的原因; 那么,关于effective C++中的遮掩,可能需要加一个限制条件,就是遮掩只发生在使用派生类的对象来调用成员函数的情况下,而不适合于用基类指针或引用(虽然指向派生类的对象)来调用成员函数的情况; [/quote] 查了下,虚函数调用编译时被处理成 pObj->_vptr->vtable[]。也就是对编译器来说,bp2->fcn() 调用的仍然是Base::fcn()而不是D1::fcn();所以屏蔽作用在这个时候是不存在的。程序运行期间会根据bp2绑定的实际类去vtable[]里面查实际应该调用的函数。
listenxu 2014-07-07
  • 打赏
  • 举报
回复
引用 11 楼 sukiida 的回复:
[quote=引用 6 楼 listenxu 的回复:] [quote=引用 1 楼 sukiida 的回复:] 1) D2中存在两个fcn,分别是基类继承来的,和自己定义的。因为参数列表不同,这是重载的两个不同函数。 ->我之前理解和你一样,但是在effective C++ 条款33中,明确讲了这种是属于遮掩,也就是如果D1中定义了和Base中同名的函数,调用函数的时候,不管是否是虚函数,形参是否相同,D1中会屏蔽Base中的同名函数,而无法调用到基类的函数 2)看上下文,肯定有具体情况,一句摘出来的话不能判断书里具体是要表达什么意思。
--> 书中就孤零零的这一句[/quote] 首先,在例子的前面一页有一个深色强调句子:如果派生类定义了重载成员,则通过派生类型只能访问派生类中重定义的那些成员。也就是D1 d1; d1.fcn(); 这样的调用是违法的。 然后,给的例子是为了说明“通过基类调用被屏蔽的虚函数”。虽然 d1.fcn(); 违法,但是通过指向基类的指针来动态调用 bp2->fcn(); 则是合法。 最后,“关键概念:名字查找与继承”说明什么这样的调用是合法的。前两条简单,直接略过。第三条,在基类找到了满足bp2->fcn(); 的原型,调用合法。第四条,普通函数的话,就调用基类的成员函数。虚函数则通过虚函数表查找。“如果函数是虚函数且通过引用或指针调用,则编译器生成代码以确定根据对象的动态类型运行那个函数版本”。我的理解是通过动态调用到D1中的 fcn() 但是此时 fcn() 作用域仍然在基类中(因为是通过基类指针调用的),没有被屏蔽,调用合法。 之前我也没有注意到这个细节,有什么错误欢迎后面的高手指正。[/quote] 如果把“通过基类调用被屏蔽的虚函数”。虽然 d1.fcn(); 违法,但是通过指向基类的指针来动态调用 bp2->fcn(); 则是合法”当成一个C++的定律的话,确实可以解释bp2->fcn() 可以成功的原因; 那么,关于effective C++中的遮掩,可能需要加一个限制条件,就是遮掩只发生在使用派生类的对象来调用成员函数的情况下,而不适合于用基类指针或引用(虽然指向派生类的对象)来调用成员函数的情况;
苏客达 2014-07-07
  • 打赏
  • 举报
回复
不好意思,更正下, 然后,给的例子是为了说明“通过基类调用被屏蔽的虚函数”。虽然 d1.fcn(); 违法,但是通过指向基类的指针来通过指向派生类的基类静态类型指针 bp2 动态调用 bp2->fcn(); 则是合法。
listenxu 2014-07-07
  • 打赏
  • 举报
回复
引用 9 楼 victor1960 的回复:
你需要弄清楚 虚函数的实现原理,没有你想象的那么神奇
effective C++中,关于遮掩有这样一句话,“此例内含→组混合了public 和private 名称,以及一组成员变量和成员函数名称。 这些成员函数包括pure virtual, impure virtual 和non-virtual 三种,这是为了强调我们 谈的是名称,和其他无关。” 这个说明遮掩和是否虚函数无关啊
苏客达 2014-07-07
  • 打赏
  • 举报
回复
引用 6 楼 listenxu 的回复:
[quote=引用 1 楼 sukiida 的回复:] 1) D2中存在两个fcn,分别是基类继承来的,和自己定义的。因为参数列表不同,这是重载的两个不同函数。 ->我之前理解和你一样,但是在effective C++ 条款33中,明确讲了这种是属于遮掩,也就是如果D1中定义了和Base中同名的函数,调用函数的时候,不管是否是虚函数,形参是否相同,D1中会屏蔽Base中的同名函数,而无法调用到基类的函数 2)看上下文,肯定有具体情况,一句摘出来的话不能判断书里具体是要表达什么意思。
--> 书中就孤零零的这一句[/quote] 首先,在例子的前面一页有一个深色强调句子:如果派生类定义了重载成员,则通过派生类型只能访问派生类中重定义的那些成员。也就是D1 d1; d1.fcn(); 这样的调用是违法的。 然后,给的例子是为了说明“通过基类调用被屏蔽的虚函数”。虽然 d1.fcn(); 违法,但是通过指向基类的指针来动态调用 bp2->fcn(); 则是合法。 最后,“关键概念:名字查找与继承”说明什么这样的调用是合法的。前两条简单,直接略过。第三条,在基类找到了满足bp2->fcn(); 的原型,调用合法。第四条,普通函数的话,就调用基类的成员函数。虚函数则通过虚函数表查找。“如果函数是虚函数且通过引用或指针调用,则编译器生成代码以确定根据对象的动态类型运行那个函数版本”。我的理解是通过动态调用到D1中的 fcn() 但是此时 fcn() 作用域仍然在基类中(因为是通过基类指针调用的),没有被屏蔽,调用合法。 之前我也没有注意到这个细节,有什么错误欢迎后面的高手指正。
listenxu 2014-07-07
  • 打赏
  • 举报
回复
引用 8 楼 victor1960 的回复:
撸主啊 这个估计没人能帮助你了 悟性。 不过也不急,你还年轻,我也是反反复复纠结了很多遍之后才懂了。 同样是看这本书,effective C++ ,你不要乱黑我们的 meyers 哦,遮掩也要在子类的作用域。 简单的 你需要理解 作用域、静态类型、动态类型。 复杂的,深入的,彻底的,请看:《深度探索C++对象模型》。
怎么理解“遮掩也要在子类的作用域”? 是说只适合于使用子类来调用函数吗? 但是,这里bp2所指向的对象确实是子类D1,为什不适用于遮掩的概念呢? 这段真是让人纠结的茶饭不思啊;
victor1960 2014-07-07
  • 打赏
  • 举报
回复
你需要弄清楚 虚函数的实现原理,没有你想象的那么神奇
victor1960 2014-07-07
  • 打赏
  • 举报
回复
撸主啊 这个估计没人能帮助你了 悟性。 不过也不急,你还年轻,我也是反反复复纠结了很多遍之后才懂了。 同样是看这本书,effective C++ ,你不要乱黑我们的 meyers 哦,遮掩也要在子类的作用域。 简单的 你需要理解 作用域、静态类型、动态类型。 复杂的,深入的,彻底的,请看:《深度探索C++对象模型》。
sdghchj 2014-07-07
  • 打赏
  • 举报
回复
单看那一句,是错的。 但要联系上下文。 “通过基类类型的引用或指针调用(虚)函数时,(如果子类中没有重写该虚函数且定义了一个同名函数),编译器将在基类中查找该(虚)函数而忽略派生类(的同名函数)”
listenxu 2014-07-07
  • 打赏
  • 举报
回复
引用 1 楼 sukiida 的回复:
1) D2中存在两个fcn,分别是基类继承来的,和自己定义的。因为参数列表不同,这是重载的两个不同函数。 ->我之前理解和你一样,但是在effective C++ 条款33中,明确讲了这种是属于遮掩,也就是如果D1中定义了和Base中同名的函数,调用函数的时候,不管是否是虚函数,形参是否相同,D1中会屏蔽Base中的同名函数,而无法调用到基类的函数 2)看上下文,肯定有具体情况,一句摘出来的话不能判断书里具体是要表达什么意思。
--> 书中就孤零零的这一句
listenxu 2014-07-07
  • 打赏
  • 举报
回复
引用 4 楼 u013470052 的回复:
前面的说错了,应该是这样 1>D1中的fcn也是虚函数,你不声明编译器帮你完成,而且是继承来的。D1中也有两个fcn函数,一个是继承来的,一个是自己定义的,也就是函数的重载,你在调用的时候没有传参,所以自动调用的是基类的fcn。 2>"通过基类类型的引用或指针调用函数时,编译器将在基类中查找该函数而忽略派生类"这句话是没有问题的,动态绑定我不懂,我就说我对这句话的理解吧。 这句话的意思是当你在基类中定义了一个虚函数的时候,通过基类的指针或者引用调用该函数的时候会直接调用基类的这个函数,而忽略了子类。原因呢是因为所有的子类都会继承基类的虚函数,而如果在子函数中定义了同名同参的函数的时候,基类的虚函数会自动隐藏,也就是说此时使用基类类型的指针调用该函数的时候调用的就是子类中的函数。
关于1,我之前理解和你一样,但是在effective C++ 条款33中,明确讲了这种是属于遮掩,也就是如果D1中定义了和Base中同名的函数,调用函数的时候,不管是否是虚函数,形参是否相同,D1中会屏蔽Base中的同名函数,而无法调用到基类的函数 关于2,我认为你说的和这句话矛盾,这句话说忽略派生类,但是你说的却会“调用子类(派生类)中的函数”,如果忽略了派生类,还如何调用派生类函数呢?
初見的畫面 2014-07-07
  • 打赏
  • 举报
回复
前面的说错了,应该是这样 1>D1中的fcn也是虚函数,你不声明编译器帮你完成,而且是继承来的。D1中也有两个fcn函数,一个是继承来的,一个是自己定义的,也就是函数的重载,你在调用的时候没有传参,所以自动调用的是基类的fcn。 2>"通过基类类型的引用或指针调用函数时,编译器将在基类中查找该函数而忽略派生类"这句话是没有问题的,动态绑定我不懂,我就说我对这句话的理解吧。 这句话的意思是当你在基类中定义了一个虚函数的时候,通过基类的指针或者引用调用该函数的时候会直接调用基类的这个函数,而忽略了子类。原因呢是因为所有的子类都会继承基类的虚函数,而如果在子函数中定义了同名同参的函数的时候,基类的虚函数会自动隐藏,也就是说此时使用基类类型的指针调用该函数的时候调用的就是子类中的函数。
初見的畫面 2014-07-07
  • 打赏
  • 举报
回复
首先你得明白虚函数的定义以及作用。 被声明为虚函数的函数在被子类继承时,无论子类中继承来的函数是否声明为virtual,编译器都自动认为它是虚函数。 而如果在子类中声明或者定义了同名函数(但是参数不同),那么基类的虚函数将会隐藏。使用基类类型的指针指向子类对象时是无法访问的。 1>所以说D1中的fcn也是虚函数,你不声明编译器帮你完成,而且是继承来的。D1中也有两个fcn函数,一个是继承来的,一个是自己定义的,也就是函数的重载,你在调用的时候没有传参,所以自动调用的是基类的fcn。 2>"通过基类类型的引用或指针调用函数时,编译器将在基类中查找该函数而忽略派生类"这句话是没有问题的,动态绑定我不懂,我就说我对这句话的理解吧。 这句话的意思是当你在基类中定义了一个虚函数的时候,通过基类的指针或者引用调用该函数的时候会直接调用基类的这个函数,而忽略了子类。原因呢是因为所有的子类都会继承基类的虚函数,而如果在子函数中定义了同名同参的函数的时候,基类的虚函数会自动隐藏,也就是说此时使用基类类型的指针调用该函数的时候调用的就是子类中的函数。
熠de 2014-07-07
  • 打赏
  • 举报
回复
覆盖(也叫做多态)是指派生类重新实现或者改写了基类的成员函数,其特征是:1、不同的作用域(分别位于派生类和基类中)。2、函数名称相同搜索。3、函数的参数也完全相同。4、基类必须有virtual关键字。 注意第三点,class D1 中的int fcn(int) 并不满足多态的条件,也无法动态绑定,因此只能调用基类的函数
苏客达 2014-07-07
  • 打赏
  • 举报
回复
1) D2中存在两个fcn,分别是基类继承来的,和自己定义的。因为参数列表不同,这是重载的两个不同函数。 2)看上下文,肯定有具体情况,一句摘出来的话不能判断书里具体是要表达什么意思。
初見的畫面 2014-07-07
  • 打赏
  • 举报
回复
引用 5 楼 listenxu 的回复:
[quote=引用 4 楼 u013470052 的回复:] 前面的说错了,应该是这样 1>D1中的fcn也是虚函数,你不声明编译器帮你完成,而且是继承来的。D1中也有两个fcn函数,一个是继承来的,一个是自己定义的,也就是函数的重载,你在调用的时候没有传参,所以自动调用的是基类的fcn。 2>"通过基类类型的引用或指针调用函数时,编译器将在基类中查找该函数而忽略派生类"这句话是没有问题的,动态绑定我不懂,我就说我对这句话的理解吧。 这句话的意思是当你在基类中定义了一个虚函数的时候,通过基类的指针或者引用调用该函数的时候会直接调用基类的这个函数,而忽略了子类。原因呢是因为所有的子类都会继承基类的虚函数,而如果在子函数中定义了同名同参的函数的时候,基类的虚函数会自动隐藏,也就是说此时使用基类类型的指针调用该函数的时候调用的就是子类中的函数。
关于1,我之前理解和你一样,但是在effective C++ 条款33中,明确讲了这种是属于遮掩,也就是如果D1中定义了和Base中同名的函数,调用函数的时候,不管是否是虚函数,形参是否相同,D1中会屏蔽Base中的同名函数,而无法调用到基类的函数 关于2,我认为你说的和这句话矛盾,这句话说忽略派生类,但是你说的却会“调用子类(派生类)中的函数”,如果忽略了派生类,还如何调用派生类函数呢?[/quote] 关于1: 事实上 你可以用代码试一下 而不是一味的讨论 毕竟实践才是真理 关于2:我说的是另一种情况,是与前一句话相对立的情况。前面是对前一句话的解释。
listenxu 2014-07-07
  • 打赏
  • 举报
回复
引用 15 楼 sukiida 的回复:
[quote=引用 14 楼 listenxu 的回复:] [quote=引用 11 楼 sukiida 的回复:] [quote=引用 6 楼 listenxu 的回复:] [quote=引用 1 楼 sukiida 的回复:] 1) D2中存在两个fcn,分别是基类继承来的,和自己定义的。因为参数列表不同,这是重载的两个不同函数。 ->我之前理解和你一样,但是在effective C++ 条款33中,明确讲了这种是属于遮掩,也就是如果D1中定义了和Base中同名的函数,调用函数的时候,不管是否是虚函数,形参是否相同,D1中会屏蔽Base中的同名函数,而无法调用到基类的函数 2)看上下文,肯定有具体情况,一句摘出来的话不能判断书里具体是要表达什么意思。
--> 书中就孤零零的这一句[/quote] 首先,在例子的前面一页有一个深色强调句子:如果派生类定义了重载成员,则通过派生类型只能访问派生类中重定义的那些成员。也就是D1 d1; d1.fcn(); 这样的调用是违法的。 然后,给的例子是为了说明“通过基类调用被屏蔽的虚函数”。虽然 d1.fcn(); 违法,但是通过指向基类的指针来动态调用 bp2->fcn(); 则是合法。 最后,“关键概念:名字查找与继承”说明什么这样的调用是合法的。前两条简单,直接略过。第三条,在基类找到了满足bp2->fcn(); 的原型,调用合法。第四条,普通函数的话,就调用基类的成员函数。虚函数则通过虚函数表查找。“如果函数是虚函数且通过引用或指针调用,则编译器生成代码以确定根据对象的动态类型运行那个函数版本”。我的理解是通过动态调用到D1中的 fcn() 但是此时 fcn() 作用域仍然在基类中(因为是通过基类指针调用的),没有被屏蔽,调用合法。 之前我也没有注意到这个细节,有什么错误欢迎后面的高手指正。[/quote] 如果把“通过基类调用被屏蔽的虚函数”。虽然 d1.fcn(); 违法,但是通过指向基类的指针来动态调用 bp2->fcn(); 则是合法”当成一个C++的定律的话,确实可以解释bp2->fcn() 可以成功的原因; 那么,关于effective C++中的遮掩,可能需要加一个限制条件,就是遮掩只发生在使用派生类的对象来调用成员函数的情况下,而不适合于用基类指针或引用(虽然指向派生类的对象)来调用成员函数的情况; [/quote] 查了下,虚函数调用编译时被处理成 pObj->_vptr->vtable[]。也就是对编译器来说,bp2->fcn() 调用的仍然是Base::fcn()而不是D1::fcn();所以屏蔽作用在这个时候是不存在的。程序运行期间会根据bp2绑定的实际类去vtable[]里面查实际应该调用的函数。[/quote]
引用 15 楼 sukiida 的回复:
[quote=引用 14 楼 listenxu 的回复:] [quote=引用 11 楼 sukiida 的回复:] [quote=引用 6 楼 listenxu 的回复:] [quote=引用 1 楼 sukiida 的回复:] 1) D2中存在两个fcn,分别是基类继承来的,和自己定义的。因为参数列表不同,这是重载的两个不同函数。 ->我之前理解和你一样,但是在effective C++ 条款33中,明确讲了这种是属于遮掩,也就是如果D1中定义了和Base中同名的函数,调用函数的时候,不管是否是虚函数,形参是否相同,D1中会屏蔽Base中的同名函数,而无法调用到基类的函数 2)看上下文,肯定有具体情况,一句摘出来的话不能判断书里具体是要表达什么意思。
--> 书中就孤零零的这一句[/quote] 首先,在例子的前面一页有一个深色强调句子:如果派生类定义了重载成员,则通过派生类型只能访问派生类中重定义的那些成员。也就是D1 d1; d1.fcn(); 这样的调用是违法的。 然后,给的例子是为了说明“通过基类调用被屏蔽的虚函数”。虽然 d1.fcn(); 违法,但是通过指向基类的指针来动态调用 bp2->fcn(); 则是合法。 最后,“关键概念:名字查找与继承”说明什么这样的调用是合法的。前两条简单,直接略过。第三条,在基类找到了满足bp2->fcn(); 的原型,调用合法。第四条,普通函数的话,就调用基类的成员函数。虚函数则通过虚函数表查找。“如果函数是虚函数且通过引用或指针调用,则编译器生成代码以确定根据对象的动态类型运行那个函数版本”。我的理解是通过动态调用到D1中的 fcn() 但是此时 fcn() 作用域仍然在基类中(因为是通过基类指针调用的),没有被屏蔽,调用合法。 之前我也没有注意到这个细节,有什么错误欢迎后面的高手指正。[/quote] 如果把“通过基类调用被屏蔽的虚函数”。虽然 d1.fcn(); 违法,但是通过指向基类的指针来动态调用 bp2->fcn(); 则是合法”当成一个C++的定律的话,确实可以解释bp2->fcn() 可以成功的原因; 那么,关于effective C++中的遮掩,可能需要加一个限制条件,就是遮掩只发生在使用派生类的对象来调用成员函数的情况下,而不适合于用基类指针或引用(虽然指向派生类的对象)来调用成员函数的情况; [/quote] 查了下,虚函数调用编译时被处理成 pObj->_vptr->vtable[]。也就是对编译器来说,bp2->fcn() 调用的仍然是Base::fcn()而不是D1::fcn();所以屏蔽作用在这个时候是不存在的。程序运行期间会根据bp2绑定的实际类去vtable[]里面查实际应该调用的函数。[/quote]
引用 15 楼 sukiida 的回复:
[quote=引用 14 楼 listenxu 的回复:] [quote=引用 11 楼 sukiida 的回复:] [quote=引用 6 楼 listenxu 的回复:] [quote=引用 1 楼 sukiida 的回复:] 1) D2中存在两个fcn,分别是基类继承来的,和自己定义的。因为参数列表不同,这是重载的两个不同函数。 ->我之前理解和你一样,但是在effective C++ 条款33中,明确讲了这种是属于遮掩,也就是如果D1中定义了和Base中同名的函数,调用函数的时候,不管是否是虚函数,形参是否相同,D1中会屏蔽Base中的同名函数,而无法调用到基类的函数 2)看上下文,肯定有具体情况,一句摘出来的话不能判断书里具体是要表达什么意思。
--> 书中就孤零零的这一句[/quote] 首先,在例子的前面一页有一个深色强调句子:如果派生类定义了重载成员,则通过派生类型只能访问派生类中重定义的那些成员。也就是D1 d1; d1.fcn(); 这样的调用是违法的。 然后,给的例子是为了说明“通过基类调用被屏蔽的虚函数”。虽然 d1.fcn(); 违法,但是通过指向基类的指针来动态调用 bp2->fcn(); 则是合法。 最后,“关键概念:名字查找与继承”说明什么这样的调用是合法的。前两条简单,直接略过。第三条,在基类找到了满足bp2->fcn(); 的原型,调用合法。第四条,普通函数的话,就调用基类的成员函数。虚函数则通过虚函数表查找。“如果函数是虚函数且通过引用或指针调用,则编译器生成代码以确定根据对象的动态类型运行那个函数版本”。我的理解是通过动态调用到D1中的 fcn() 但是此时 fcn() 作用域仍然在基类中(因为是通过基类指针调用的),没有被屏蔽,调用合法。 之前我也没有注意到这个细节,有什么错误欢迎后面的高手指正。[/quote] 如果把“通过基类调用被屏蔽的虚函数”。虽然 d1.fcn(); 违法,但是通过指向基类的指针来动态调用 bp2->fcn(); 则是合法”当成一个C++的定律的话,确实可以解释bp2->fcn() 可以成功的原因; 那么,关于effective C++中的遮掩,可能需要加一个限制条件,就是遮掩只发生在使用派生类的对象来调用成员函数的情况下,而不适合于用基类指针或引用(虽然指向派生类的对象)来调用成员函数的情况; [/quote] 查了下,虚函数调用编译时被处理成 pObj->_vptr->vtable[]。也就是对编译器来说,bp2->fcn() 调用的仍然是Base::fcn()而不是D1::fcn();所以屏蔽作用在这个时候是不存在的。程序运行期间会根据bp2绑定的实际类去vtable[]里面查实际应该调用的函数。[/quote] 对,按照你这样解释就明白多了; 遮掩是发生在编译期,而在这个例子中,编译器只知道静态类型,即基类类型(因为是基类类型的指针),所以对编译器而言,他认为bp2->fcn() 调用的仍然是Base::fcn();到了运行期,就按照虚函数表的法则来进行调用,所以对于没有定义虚函数的重载版本的派生类而言,指向他的基类的指针在运行期就会使用基类自己定义的版本; 如果是直接用D1的对象来调用fcn(),如d1obj.fcn(),那么编译器知道的静态类型为D1,这时候,编译器认为需要调用D1::fcn(),因为D1中定义了fcn(int),遮掩了Base::fcn(), 所以,对于这个调用,会发生编译错误; 总结起来,是否发生遮掩,是根据编译器所知道的静态类型决定的,如果静态类型是基类,则派生类无法遮掩基类中定义的函数;如果静态类型是派生类(对象,指针,或者引用都一样),而派生类定义了新的同名函数,则会发生遮掩;
【原书名】 C++ Primer (4th Edition) 【原出版社】 Addison Wesley/Pearson 【作者】 (美)Stanley B.Lippman,Josée LaJoie,Barbara E.Moo 【译者】 李师贤 蒋爱军 梅晓勇 林瑛 【丛书名】 图灵计算机科学丛书 【出版社】 人民邮电出版社 【书号】 7-115-14554-7 【开本】 16开 【页码】 900 【出版日期】 2006-3-1 【版次】 4-1 【内容简介】 本书是久负盛名的C++经典教程,其内容是C++大师Stanley B. Lippman丰富的实践经验和C++标准委员会原负责人Josée Lajoie对C++标准深入理解的完美结合,已经帮助全球无数程序员学会了C++。本版对前一版进行了彻底的修订,内容经过了重新组织,更加入了C++ 先驱Barbara E. Moo在C++教学方面的真知灼见。既显著改善了可读性,又充分体现了C++语言的最新进展和当前的业界最佳实践。书不但新增大量教学辅助内容,用于强调重要的知识点,提醒常见的错误,推荐优秀的编程实践,给出使用提示,还包含大量来自实战的示例和习题。对C++本概念和技术全面而且权威的阐述,对现代C++编程风格的强调,使本书成为C++初学者的最佳指南;对于高级程序员,本书也是不可或缺的参考书。本书的前言阐述了 第4版和前一版的不同之处。 【目录信息】 第1章 快速入门 1 1.1 编写简单的C++程序 2 1.2 初窥输入/输出 5 1.2.1 标准输入与输出对象 5 1.2.2 一个使用IO库的程序 5 1.3 关于注释 8 1.4 控制结构 10 1.4.1 while语句 10 1.4.2 for语句 12 1.4.3 if语句 14 1.4.4 读入未知数目的输入 15 1.5 类的简介 17 1.5.1 Sales_item类 17 1.5.2 初窥成员函数 19 1.6 C++程序 21 小结 22 术语 22 第一部分 本语言 第2章 变量和本类型 29 2.1 本内置类型 30 2.1.1 整型 30 2.1.2 浮点型 32 2.2 字面值常量 34 2.3 变量 38 2.3.1 什么是变量 39 2.3.2 变量名 40 2.3.3 定义对象 42 2.3.4 变量初始化规则 44 2.3.5 声明和定义 45 2.3.6 名字的作用域 46 2.3.7 在变量使用处定义变量 48 2.4 const限定符 49 2.5 引用 50 2.6 typedef名字 53 2.7 枚举 53 2.8 类类型 54 2.9 编写自己的头文件 57 2.9.1 设计自己的头文件 58 2.9.2 预处理器的简单介绍 60 小结 62 术语 62 第3章 标准库类型 67 3.1 命名空间的using声明 68 3.2 标准库string类型 70 3.2.1 string对象的定义和初始化 70 3.2.2 String对象的读写 71 3.2.3 string对象的操作 72 3.2.4 string对象字符的处理 76 3.3 标准库vector类型 78 3.3.1 vector对象的定义和初始化 79 3.3.2 vector对象的操作 81 3.4 迭代器简介 83 3.5 标准库bitset类型 88 3.5.1 bitset对象的定义和初始化 88 3.5.2 bitset对象上的操作 90 小结 92 术语 92 第4章 数组和指针 95 4.1 数组 96 4.1.1 数组的定义和初始化 96 4.1.2 数组操作 99 4.2 指针的引入 100 4.2.1 什么是指针 100 4.2.2 指针的定义和初始化 101 4.2.3 指针操作 104 4.2.4 使用指针访问数组元素 106 4.2.5 指针和const限定符 110 4.3 C风格字符串 113 4.3.1 创建动态数组 117 4.3.2 新旧代码的兼容 120 4.4 多维数组 122 小结 124 术语 125 第5章 表达式 127 5.1 算术操作符 129 5.2 关系操作符和逻辑操作符 131 5.3 位操作符 134 5.3.1 bitset对象或整型值的使用 135 5.3.2 将移位操作符用于IO 137 5.4 赋值操作符 137 5.4.1 赋值操作的右结合性 138 5.4.2 赋值操作具有低优先级 138 5.4.3 复合赋值操作符 139 5.5 自增和自减操作符 140 5.6 箭头操作符 142 5.7 条件操作符 143 5.8 sizeof操作符 144 5.9 逗号操作符 145 5.10 复合表达式的求值 145 5.10.1 优先级 145 5.10.2 结合性 146 5.10.3 求值顺序 148 5.11 new和delete表达式 150 5.12 类型转换 154 5.12.1 何时发生隐式类型转换 154 5.12.2 算术转换 155 5.12.3 其他隐式转换 156 5.12.4 显式转换 158 5.12.5 何时需要强制类型转换 158 5.12.6 命名的强制类型转换 158 5.12.7 旧式强制类型转换 160 小结 161 术语 162 第6章 语句 165 6.1 简单语句 166 6.2 声明语句 167 6.3 复合语句(块) 167 6.4 语句作用域 168 6.5 if语句 169 6.6 switch语句 172 6.6.1 使用switch 173 6.6.2 switch的控制流 173 6.6.3 default标号 175 6.6.4 switch表达式与case标号 176 6.6.5 switch内部的变量定义 176 6.7 while语句 177 6.8 for循环语句 179 6.8.1 省略for语句头的某些部分 180 6.8.2 for语句头的多个定义 181 6.9 do while语句 182 6.10 break语句 183 6.11 continue语句 184 6.12 goto语句 185 6.13 try块和异常处理 186 6.13.1 throw表达式 186 6.13.2 try块 187 6.13.3 标准异常 189 6.14 使用预处理器进行调试 190 小结 192 术语 192 第7章 函数 195 7.1 函数的定义 196 7.1.1 函数返回类型 197 7.1.2 函数形参表 198 7.2 参数传递 199 7.2.1 非引用形参 199 7.2.2 引用形参 201 7.2.3 vector和其他容器类型的形参 206 7.2.4 数组形参 206 7.2.5 传递给函数的数组的处理 209 7.2.6 main:处理命令行选项 210 7.2.7 含有可变形参的函数 211 7.3 return语句 211 7.3.1 没有返回值的函数 212 7.3.2 具有返回值的函数 212 7.3.3 递归 216 7.4 函数声明 217 7.5 局部对象 220 7.5.1 自动对象 220 7.5.2 静态局部对象 220 7.6 内联函数 221 7.7 类的成员函数 222 7.7.1 定义成员函数函数体 223 7.7.2 在类外定义成员函数 225 7.7.3 编写Sales_item类的构造 函数 225 7.7.4 类代码文件的组织 227 7.8 重载函数 228 7.8.1 重载与作用域 230 7.8.2 函数匹配与实参转换 231 7.8.3 重载确定的三个步骤 232 7.8.4 实参类型转换 234 7.9 指向函数的指针 237 小结 239 术语 240 第8章 标准IO库 243 8.1 面向对象的标准库 244 8.2 条件状态 247 8.3 输出缓冲区的管理 249 8.4 文件的输入和输出 251 8.4.1 文件流对象的使用 251 8.4.2 文件模式 254 8.4.3 一个打开并检查输入文件的 程序 256 8.5 字符串流 257 小结 259 术语 259 第二部分 容器和算法 第9章 顺序容器 263 9.1 顺序容器的定义 264 9.1.1 容器元素的初始化 265 9.1.2 容器内元素的类型约束 267 9.2 迭代器和迭代器范围 268 9.2.1 迭代器范围 270 9.2.2 使迭代器失效的容器操作 271 9.3 顺序容器的操作 272 9.3.1 容器定义的类型别名 272 9.3.2 begin和end成员 273 9.3.3 在顺序容器添加元素 273 9.3.4 关系操作符 277 9.3.5 容器大小的操作 278 9.3.6 访问元素 279 9.3.7 删除元素 280 9.3.8 赋值与swap 282 9.4 vector容器的自增长 284 9.5 容器的选用 287 9.6 再谈string类型 289 9.6.1 构造string对象的其他方法 290 9.6.2 修改string对象的其他方法 292 9.6.3 只适用于string类型的操作 293 9.6.4 string类型的查找操作 295 9.6.5 string对象的比较 298 9.7 容器适配器 300 9.7.1 栈适配器 301 9.7.2 队列和优先级队列 302 小结 303 术语 303 第10章 关联容器 305 10.1 引言:pair类型 306 10.2 关联容器 308 10.3 map类型 309 10.3.1 map对象的定义 309 10.3.2 map定义的类型 310 10.3.3 给map添加元素 311 10.3.4 使用下标访问map对象 311 10.3.5 map::insert的使用 313 10.3.6 查找并读取map的元素 315 10.3.7 从map对象删除元素 316 10.3.8 map对象的迭代遍历 316 10.3.9 “单词转换”map对象 317 10.4 set类型 319 10.4.1 set容器的定义和使用 319 10.4.2 创建“单词排除”集 321 10.5 multimap和multiset类型 322 10.5.1 元素的添加和删除 322 10.5.2 在multimap和multiset 查找元素 323 10.6 容器的综合应用:文本查询程序 325 10.6.1 查询程序的设计 326 10.6.2 TextQuery类 327 10.6.3 TextQuery类的使用 328 10.6.4 编写成员函数 330 小结 332 术语 332 第11章 泛型算法 335 11.1 概述 336 11.2 初窥算法 339 11.2.1 只读算法 339 11.2.2 写容器元素的算法 341 11.2.3 对容器元素重新排序的算法 343 11.3 再谈迭代器 347 11.3.1 插入迭代器 348 11.3.2 iostream迭代器 349 11.3.3 反向迭代器 353 11.3.4 const迭代器 355 11.3.5 五种迭代器 356 11.4 泛型算法的结构 358 11.4.1 算法的形参模式 359 11.4.2 算法的命名规范 359 11.5 容器特有的算法 361 小结 362 术语 363 第三部分 类和数据抽象 第12章 类 367 12.1 类的定义和声明 368 12.1.1 类定义:扼要重述 368 12.1.2 数据抽象和封装 369 12.1.3 关于类定义的更多内容 372 12.1.4 类声明与类定义 374 12.1.5 类对象 375 12.2 隐含的this指针 376 12.3 类作用域 380 类作用域的名字查找 382 12.4 构造函数 385 12.4.1 构造函数初始化式 387 12.4.2 默认实参与构造函数 391 12.4.3 默认构造函数 392 12.4.4 隐式类类型转换 393 12.4.5 类成员的显式初始化 396 12.5 友元 396 12.6 static类成员 398 12.6.1 static成员函数 400 12.6.2 static数据成员 400 小结 403 术语 403 第13章 复制控制 405 13.1 复制构造函数 406 13.1.1 合成的复制构造函数 409 13.1.2 定义自己的复制构造函数 409 13.1.3 禁止复制 410 13.2 赋值操作符 411 13.3 析构函数 412 13.4 消息处理示例 415 13.5 管理指针成员 419 13.5.1 定义智能指针类 421 13.5.2 定义值型类 425 小结 427 术语 427 第14章 重载操作符与转换 429 14.1 重载操作符的定义 430 14.2 输入和输出操作符 435 14.2.1 输出操作符<>的重载 437 14.3 算术操作符和关系操作符 439 14.3.1 相等操作符 440 14.3.2 关系操作符 441 14.4 赋值操作符 441 14.5 下标操作符 442 14.6 成员访问操作符 443 14.7 自增操作符和自减操作符 446 14.8 调用操作符和函数对象 449 14.8.1 将函数对象用于标准库算法 450 14.8.2 标准库定义的函数对象 451 14.8.3 函数对象的函数适配器 453 14.9 转换与类类型 454 14.9.1 转换为什么有用 454 14.9.2 转换操作符 455 14.9.3 实参匹配和转换 458 14.9.4 重载确定和类的实参 461 14.9.5 重载、转换和操作符 464 小结 466 术语 467 第四部分 面向对象编程与泛型编程 第15章 面向对象编程 471 15.1 面向对象编程:概述 472 15.2 定义类和派生类 473 15.2.1 定义类 474 15.2.2 protected成员 475 15.2.3 派生类 476 15.2.4 virtual与其他成员函数 479 15.2.5 公用、私有和受保护的继承 482 15.2.6 友元关系与继承 486 15.2.7 继承与静态成员 486 15.3 转换与继承 487 15.3.1 派生类到类的转换 487 15.3.2 类到派生类的转换 489 15.4 构造函数和复制控制 490 15.4.1 类构造函数和复制控制 490 15.4.2 派生类构造函数 490 15.4.3 复制控制和继承 494 15.4.4 析构函数 495 15.4.5 构造函数和析构函数函数 497 15.5 继承情况下的类作用域 497 15.5.1 名字查找在编译时发生 498 15.5.2 名字冲突与继承 498 15.5.3 作用域与成员函数 499 15.5.4 函数与作用域 500 15.6 纯函数 502 15.7 容器与继承 503 15.8 句柄类与继承 504 15.8.1 指针型句柄 505 15.8.2 复制未知类型 507 15.8.3 句柄的使用 508 15.9 再谈文本查询示例 511 15.9.1 面向对象的解决方案 513 15.9.2 值型句柄 514 15.9.3 Query_base类 515 15.9.4 Query句柄类 516 15.9.5 派生类 518 15.9.6 eval函数 520 小结 522 术语 523 第16章 模板与泛型编程 525 16.1 模板定义 526 16.1.1 定义函数模板 526 16.1.2 定义类模板 528 16.1.3 模板形参 529 16.1.4 模板类型形参 531 16.1.5 非类型模板形参 533 16.1.6 编写泛型程序 534 16.2 实例化 535 16.2.1 模板实参推断 537 16.2.2 函数模板的显式实参 540 16.3 模板编译模型 542 16.4 类模板成员 545 16.4.1 类模板成员函数 548 16.4.2 非类型形参的模板实参 551 16.4.3 类模板的友元声明 552 16.4.4 Queue和QueueItem的友元 声明 554 16.4.5 成员模板 556 16.4.6 完整的Queue类 558 16.4.7 类模板的static成员 559 16.5 一个泛型句柄类 560 16.5.1 定义句柄类 561 16.5.2 使用句柄 562 16.6 模板特化 564 16.6.1 函数模板的特化 565 16.6.2 类模板的特化 567 16.6.3 特化成员而不特化类 569 16.6.4 类模板的部分特化 570 16.7 重载与函数模板 570 小结 573 术语 574 第五部分 高级主题 第17章 用于大型程序的工具 579 17.1 异常处理 580 17.1.1 抛出类类型的异常 581 17.1.2 栈展开 582 17.1.3 捕获异常 583 17.1.4 重新抛出 585 17.1.5 捕获所有异常的处理代码 586 17.1.6 函数测试块与构造函数 586 17.1.7 异常类层次 587 17.1.8 自动资源释放 589 17.1.9 auto_ptr类 591 17.1.10 异常说明 595 17.1.11 函数指针的异常说明 598 17.2 命名空间 599 17.2.1 命名空间的定义 599 17.2.2 嵌套命名空间 603 17.2.3 未命名的命名空间 604 17.2.4 命名空间成员的使用 606 17.2.5 类、命名空间和作用域 609 17.2.6 重载与命名空间 612 17.2.7 命名空间与模板 614 17.3 多重继承与继承 614 17.3.1 多重继承 615 17.3.2 转换与多个类 617 17.3.3 多重继承派生类的复制控制 619 17.3.4 多重继承下的类作用域 620 17.3.5 继承 622 17.3.6 类的声明 624 17.3.7 特殊的初始化语义 625 小结 628 术语 628 第18章 特殊工具与技术 631 18.1 优化内存分配 632 18.1.1 C++的内存分配 632 18.1.2 allocator类 633 18.1.3 operator new函数和 operator delete函数 636 18.1.4 定位new表达式 638 18.1.5 显式析构函数调用 639 18.1.6 类特定的new和delete 639 18.1.7 一个内存分配器类 641 18.2 运行时类型识别 646 18.2.1 dynamic_cast操作符 647 18.2.2 typeid操作符 649 18.2.3 RTTI的使用 650 18.2.4 type_info类 652 18.3 类成员的指针 653 18.3.1 声明成员指针 653 18.3.2 使用类成员的指针 655 18.4 嵌套类 658 18.4.1 嵌套类的实现 658 18.4.2 嵌套类作用域的名字查找 661 18.5 联合:节省空间的类 662 18.6 局部类 665 18.7 固有的不可移植的特征 666 18.7.1 位域 666 18.7.2 volatile限定符 668 18.7.3 链接指示:extern "C" 669 小结 672 术语 673 附录 标准库 675 索引 703

64,701

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

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