面试例题(21-30)

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++的了解深入不少。

  1. 对于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。

  2. 对于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.

  3. 下面是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++设计新思维》一书中对多重继承和模板有极为精彩的运用。

  1. 多重继承本身并没有问题,如果运用得当可以收到事半功倍的效果。不过大多数系统的类层次往往有一个公共的基类,就像MFC中的Cobject,Java中的object。而这样的结构如果使用多重继承,稍有不慎,将会出现一个严重现象——菱形继承,这样的继承方式会使得类的访问结构非常复杂。但并非不可处理。可以用virtual继承(并非唯一方法)及Loki库中的多继承框架来掩盖这些复杂性。
  2. 从哲学上来说,C++多重继承必须要存在,这个世界本来就不是单根的。从实际用途上来说,多重继承不是必需的,但这个世界上有多少东西是必需的呢?对象不过是一组有意义的数据集合及其上的一组有意义的操作,虚函数也不过是一堆函数入口表,重载也不过是函数名扩展,这些东西都不是必需的,而且它们的不当使用都会带来问题。但是没有这些东西行吗?很显然。不行、
  3. 多重继承在面向对象理论中并非是必要的——因为它不提供新的语意,可以通过单继承与复合结构来取代。而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
{
privateint i;
public:
	base(int x) { i = x;}
};
class derived : public base
{
privateint 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
{
privateint i;
public:
	derived (int x, int y) : base(x)
	{
		 i = x;
	}
	void printTotal() { int total = i + base::i;}
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值