11.1覆盖
面试例题21:以下代码的输出结果是什么?
#include<iostream>
using namespace std;
class Parent
{
public:
virtual void foo()
{
cout << “foo from Parent”;
}
void fool()
{
cout << “fool from Parent”;
}
};
class Son: public Parent
{
void foo()
{
cout << “foo from Son”;
}
void fool()
{
cout << “fool from Son”;
}
};
int main()
{
parent* p = new Son();
P->foo();
P->fool();
return 0;
}
A. foo from Parent fool from Son
B. foo from Son fool from Parent
C. foo from Son fool from Son
D. Cannot complie
解析:这是一个典型的覆盖问题。Parent类里的foo函数是函数,虚函数是被子类同名函数所覆盖的。而Parent类里的foo函数是l一个普通函数,不会被子类同名函数所覆盖。P是一个指针,指向的是对象。由于Parent类里的foo函数被Son类里的foo函数所覆盖,所以结果是foo from Son。
答案:B。
面试例题22:以下代码的输出结果是什么?
这题没看懂
#include <iostream>
using namespace std;
class A
{
public:
void virtual f()
{
cout << “A” << endl;
}
};
class B : public A
{
public:
void virtual f()
{
cout << “B” << endl;
}
};
int main()
{
A* pa = new A();
pa->f();
B* pb = (B*)Pa;
pb->f();
delete pa,pb;
pa = new B();
pa->f();
pb = (B*)pa;
pb->f();
};
A. AABA B. AABB C. AAAB D. ABBA
解析:这是一个虚函数覆盖函数的问题。A类里的f函数是一个虚函数,虚函数是被子类同名函数所覆盖的。而B类里的f函数也是一个虚函数
,它覆盖A类f函数的同时,也会被它的子类覆盖。但是在B* pb = (B*)pa里面,该语句的意思是 转化pa为B类型并新建一个指针pb,将pa复制到pb。但这里有一点请注意,就是pa的指针始终没有发生变化,所以pb也指向pa的f函数。这里并不存在覆盖的问题。
delete pa,pb;删除了pa和pb所指向的地址,但pa、pb指针并没有删除,也就是我们通常说的悬浮指针。现在重新给pa指向新地址,所指向的位置是B类的,而pa指针类型是A类的,所以就产生了一个覆盖。Pa->f();的值就是B。
Pb = (B*)pa;转化pa为B类指针给pb赋值,但pa所指向的f函数是B类的f函数,所以pb所指向的f函数是B类的f函数。Pb->f();的值是B。
答案:B。
私有继承
面试例题23:公有继承和私有继承的区别是什么?
A. 没有区别。
B. 私有继承使父类中所有元素变成私有。
C. 私有继承使父类中的函数转化成私有。
D. 私有继承使父类中所有元素无法与子类联系。
解析:A肯定错。
因为子类只能继承父类的protected和public,所以B也是错的。
C的叙述不全面,而且父类可能有自己的私有方法成员,所以也是错误的。
答案:D。
面试例题24:请考虑标记为A到J的语句再编译时可能出现的情况。如果能够成功编译,请记为”RIGHT”,否则记为”ERROR”
#include <iostream>
#include<stdio.h>
class Parent
{
public:
Parent(int var = -1)
{
m_nPub = var;
m_nPtd = var;
m_nPrt = var;
}
public:
int m_nPub;
protected:
int m_nPtd;
private:
int m_nPrt;
};
class Child1 : public Parent
{
public:
int GetPub() {return m_nPub};
int GetPtd() {return m_nPtd};
int GetPrt() {return m_nPrt}; //A
};
class Child2 : protected Parent
{
public:
int GetPub() {return m_nPub};
int GetPtd() {return m_nPtd};
int GetPrt() {return m_nPrt}; //B
};
class Child3 : private Parent
{
public:
int GetPub() {return m_nPub};
int GetPtd() {return m_nPtd};
int GetPrt() {return m_nPrt}; //C
};
Int main()
{
Child1 cd1;
Child2 cd2;
Child3 cd3;
int nVar = 0;
//public inherited
cd1.m_nPub = nVar; //D 。
ca1.m_nPtd = nVar; //E
nVar = cd1.GetPtd(); //F .
//protected inherited
ca2.m_nPub = nVar; //G
nVar = cd2.GetPtd(); //H .
//private inherited
ca3.m_nPub = nVar; //I
nVar = cd3.GetPtd; //J .
return 0;
}
解析:
A、B、C都是错误的,因为m_nPrt时父类Parent的私有变量,所以不能被子类访问。
D正确。Cd1时公有继承,可以访问并改变父类的公有变量。
E错误。M_nPtd是父类Parent的保护变量,可以被公有继承的cd1访问,但不可以修改。
F正确。可以通过函数访问父类的保护变量。
G错误。Cd2是保护继承的,不可以直接修改父类的公有变量。
H正确。可以通过函数访问父类的保护变量。
I错误。Cd3是私有继承的,不可以直接修改父类的公有变量。
J正确。可以通过函数访问父类的保护变量。
答案:
A、B、C、E、G、I是错误的。
D、F、H、J是正确的。
虚函数继承和虚继承
虚派生只影响从指定了虚基类的派生类中进一步派生出来的类,它不会影响派生类本身。
面试例题25:下面程序的结果是什么?
#include<iostream>
#include<memory.h>
#include<assert.h>
using namespace std;
class A
{
char k[3];
public:
virtual void aa(){};
};
class B : public virtual A
{
char j[3];
//加入一个变量是为了看清楚class中的vfptr放在什么位置
public:
virtual void bb(){};
};
class C : public virtual B
{
char i[3];
public:
virtual void cc() {};
};
int main(int argc, char* argv[])
{
cout << “Sizeof(A):” << sizeof(A) << endl; //8
cout << “Sizeof(B):” << sizeof(B) << endl; //16
cout << “Sizeof(C):” << sizeof(C) << endl; //24
return 0;
}
解析:C++2.0以后全面支持虚函数继承。这个特性的引入为C++增强了不少功能,也引入了不少烦恼。如果能够了解编译器是如何实现虚函数继承,它们在类的内存空间中又是如何布局的,就可以对C++的了解深入不少。
-
对于class A,由于有一个虚函数,那么必须得有一个对应的虚函数表记录对应的函数入口地址。每个地址需要标有一个虚指针,指针的大小为4。类中还有一个char k[3],每一个char值所占位置是1,所以char k[3]所占大小是3.做一次数据对齐后(编译器里一般以4的倍数为对齐单位),char k[3]所占大小变为4。Sizeof(A)的结果就是char k[3]所占大小4和虚指针所占大小4,三者之和等于8。
-
对于class B,由于class B虚继承了class A,同时还拥有自己的虚函数,那么class B中首先拥有一个vfptr_B,指向自己的虚函数表。还有char j[3],大小为4。可虚继承该如何实现?首先要通过加入一个虚类指针来指向其父类,然后还要包含父类的所有内容。有些复杂了,不过还不难想象。Sizeof(B)的结果就是char k[3]所占大小4和虚指针vfptr_B所占大小4家sizeof(A)所占大小8,三者之和等于16.
-
下面是class C了,class C首先也得有个vfptr_C,然后是char i[3],然后是sizeof(B),所以sizeof( C )的结果就是char i[3]所占大
小4和虚指针vfptr_C所占大小4加sizeof(B)所占大小16,三者之和等于24。
答案:再gcc中打印上面几个类的大小,结果为8、16、24。
面试例题26:什么是虚继承?它与一般的继承有什么不同?它有什么用?写出一段虚继承的C++代码。
☆☆☆☆☆
答案:虚拟继承是多重继承中特有的概念。
虚拟基类是为解决多重继承而出现的。
代码如下:
class A;
class B : public virtual A; //virtual inheritance.
class C : public virtual A;
class D : public B, public C;
注意:虚函数继承和虚继承是完全不同的两个概念,请不要在面试中混淆。
面试例题27:如果一个圆角矩形有直边和圆角,那么圆角矩形也就多重继承了圆形和矩形,而圆形和矩形又都是从shape类里继承的。请问:
☆☆☆☆☆
当你创建一个圆角矩形类时,共创建了多少个shape?
答案:如果圆形类和矩形类都不是用关键字virtual继承shape类,那么生成两个shape:一个为圆形类,一个为矩形类。如果圆形类和矩形类都是用关键字virtual继承shape类,那么生成一个共享的shape。
多重继承
面试例题28:请评价多重继承的优点和缺陷。
答案:
多重继承在语言上并没有什么很严重的问题,但是标准本身只对语义做了规定,而对编译器的细节没有做规定。所以在使用时(即使是继承),最好不要对内存布局等有什么假设。此类的问题还有虚析构函数等。为了避免由此带来的复杂性,通常推荐使用复合。但是,在《C++设计新思维》一书中对多重继承和模板有极为精彩的运用。
- 多重继承本身并没有问题,如果运用得当可以收到事半功倍的效果。不过大多数系统的类层次往往有一个公共的基类,就像MFC中的Cobject,Java中的object。而这样的结构如果使用多重继承,稍有不慎,将会出现一个严重现象——菱形继承,这样的继承方式会使得类的访问结构非常复杂。但并非不可处理。可以用virtual继承(并非唯一方法)及Loki库中的多继承框架来掩盖这些复杂性。
- 从哲学上来说,C++多重继承必须要存在,这个世界本来就不是单根的。从实际用途上来说,多重继承不是必需的,但这个世界上有多少东西是必需的呢?对象不过是一组有意义的数据集合及其上的一组有意义的操作,虚函数也不过是一堆函数入口表,重载也不过是函数名扩展,这些东西都不是必需的,而且它们的不当使用都会带来问题。但是没有这些东西行吗?很显然。不行、
- 多重继承在面向对象理论中并非是必要的——因为它不提供新的语意,可以通过单继承与复合结构来取代。而Java则放弃了多重继承,使用简单的interface取代。多重继承是把双刃剑,应该正确地对待。况且,它不像goto,不破坏面向对象语义。跟其他任何威力强大的东西一样,用好了会带来代码的极大精简,用坏了那就不用说了。
C++是为实用而设计的,在语言里有很多东西存在着各种各样的“缺陷”。所以,对于这种有“缺陷”的东西,它的优劣就要看使用它的人。C++不回避问题,它只是把问题留给使用者,从而给大家更多的自由。像Ada、Pascal这类定义严格的语言,从语法上回避了问题,但并不是真正的解决了问题,而使人做很多事时束手束脚。
(4)多重继承本身并不复杂,对象布局也不混乱,语言中都有明确的定义。真正复杂的是用了运行时多态的多重继承。为什么非要说多重继承不好呢?如果这样的话,指针不是更容易出错,运行时多态不是更不好理解吗?
因为C++中没有interface这个关键字,所以不存在所谓的“接口”技术。但是C++可以很轻松地做到这样的模拟,因为C++中的不定义属性的抽象类就是接口。
面试例题29:找出下面程序的错误,并解释它为什么是错的。
#include<iostream>
using namespace std;
class Base
{
public:
int val;
Base() { val = 1; };
};
class Derive : Base //默认为私有继承方式,子类不能访问父类的对象
{
public:
int val;
Derive(int i) { val = Base::val + i; };
};
int main(int, char**, char**)
{
Derive d(10);
cout << d.Base::val << endl << d.val << endl;
return 0;
}
答案:把class Derive:Base改成class Derive : public Base
解析:这是个类继承问题。如果不指定public,C++默认的是私有继承。私有继承是无法继承并使用父类函数中的公有变量。
面试例题30:找出下列程序的错误,并解释它为什么是错的。
class base
{
private:
int i;
public:
base(int x) { i = x;}
};
class derived : public base
{
private:
int i;
public:
derived (int x, int y) { i = x;}
boid printTotal() { int total = i + base::i;}
};
解析:要在子类中设定初始成员变量,把derived(int x, int y)
改成derived(int x, int y) : base(x)
。
class base
{
protected: //这里的访问属性从私有改成受保护的
int i;
public:
base(int x) { i = x;}
};
class derived : public base
{
private:
int i;
public:
derived (int x, int y) : base(x)
{
i = x;
}
void printTotal() { int total = i + base::i;}
};