经典C++笔试题解析3
Sailor_forever sailing_9806@163.com 转载请注明
http://blog.csdn.net/sailor_8318/archive/2008/10/31/3188958.aspx
31、尽量避免类的值传递,用什么方法可以阻止用户的类值传递?
只要声明一个私有的拷贝构造函数即可,其不能被编译器调用,因此无法编译通过
class X{
X(X& x) {
cout<<"hehe"<<endl;
}
public:
X(){}
};
void main(){
X xx;
//! X x2=xx;
}
32、定义一个函数指针,指向一个类的非静态成员函数,并调用:
class X;
typedef void (X::*FPT) (); // 定义一个指向X中任意一个无参返回值为void类型的成员函数的指针
class X{
public:
int xi;
FPT pp;
X(int j){
xi=j;
pp=X::f;
(this->*pp)();
}
void f(){
cout<<"f(), xi = "<<xi<<endl;
}
void g(){
cout<<"g(), xi = "<<xi<<endl;
}
};
int main(){
X x1(1),x2(2);
FPT p1,p2,p3;
p1=X::f;
p2=x1.f;
p3=X::g;
(x1.*p1)();
(x1.*p2)();
(x1.*p3)();
(x2.*p1)();
(x2.*p2)();
(x2.*p3)();
return 1;
}
f(), xi = 1
f(), xi = 2
f(), xi = 1
f(), xi = 1
g(), xi = 1
f(), xi = 2
f(), xi = 2
g(), xi = 2
指针必须指向一个实际的地址,在对象的成员尚未创建之前,对象的成员函数并没有地址,p1=X::f只是确定了p1指向了X的f成员函数,也就是确定了一个偏移地址。当X创建了对象后,p1的地址就确定了,p1必须和对象结合使用才有实际意义。P3的道理相同。
P2 呢,按道理p2 的地址就是x1.f阿,可是为什么(x2.*p2)()结果是2呢?本质上指向成员函数的指针只是确定了该指针在类中的偏移量而已,具体指向的地址由调用时关联的具体对象确定。
注意,非静态的成员始终要跟this指针结合使用,因此要用一个对象来使用它而不是一个类型。即使是在类定义内部也要显式用this来强调这种关系,以便和全局函数区分开来
33、下例发生了几次拷贝构造函数调用?一次
class X{
public:
X(){}
X(X&){
cout<<"haha..."<<endl;
}
};
void main(){
X x1;
X x2=x1; //定义的时候同时拷贝一个已经存在的对象才会调用拷贝构造函数,构造且拷贝
x2=x1; //此为赋值操作,而非构造,因为x2对象已经存在
}
34、避免使用运算符重载!!!
35、类成员运算符重载与全局运算符重载的区别? 参数的个数
36、在继承链上,子类的构造函数需要显式的调用基类的构造函数,而析构函数呢?
因为构造函数可能有多个,需要明确指定,否则会调用默认的无参构造函数,当未定义无参构造函数而定义了其他构造函数时,编译器不会为其生成默认的构造函数,此时将无法编译通过。每个类只有一个析构函数,因此不需要指定,它们会被自动调用。
调用构造函数的语法:
foo::foo(int i):base_foo(i){...}
37、什么是构造函数初始化列表?
构造函数的调用顺序是:先基类的构造函数,然后是成员对象的构造函数,构造顺序不受初始化列表中的排列顺序影响。同等级成员的初始化列表由其在类声明中的顺序决定。因为析构顺序与构造顺序相反,而析构顺序是自动且唯一的,因此构造顺序唯一,但初始化列表的顺序是可变的,这就证明了构造顺序取决于类声明中的固定顺序而与列表顺序无关。
38、举例说明为什么有时候必须要初始化列表?
class Z{
public:
Z(int i) {
cout<<"huhu..."<<endl;
}
};
class X{
public:
X(int i) {
cout<<"hehe..."<<endl;
}
};
class Y :public X{
public:
Z z1;
Y(int i):X(i),z1(6) { //初始化列表,“,”分开各个初始化项。Compostion模式应采用成员对象而非类名称
cout<<"haha..."<<endl;
}
};
void main(){
Y y(5);
}
试试将上面例子的初始化列表移到函数体内部,会发生什么?
C++必须保证每个一对象都初始化了,而对于基类私有成员,派生类无法操作,而对象在定义的时候必须初始化,这样只能在派生类的构造列表中显示的调用基类的构造函数了。
39、基类中对函数foo进行了多次重载,子类中重定义函数foo,能否在子类中调用foo的基类版本?
不能,子类重定义将屏蔽所有基类版本。
class X{
public:
f(int i,int j) {cout<<"haha"<<endl;}
f(int i){cout<<"haha"<<endl;}
};
class Y:public X{
public:
f(int i){cout<<"hehe"<<endl;}
};
void main(){
Y y;
//! y.f(3,4);
}
40、谈谈public, private, protect关键字的区别
41、谈谈私有继承的性质
去掉public或者显式指定为private的都可以将基类作为私有的。此时新类具备所有基类的成员和功能,但其是隐藏的。新的对象不能作为基类的一个实例。通常为了避免混淆,避免用私有继承。
私有继承会使得基类中的所有public成员变为private的,若希望部分可见,可在派生类的public域进行声明,无须任何参数或返回值。因此当你想隐藏基类的部分功能时,私有继承就派上用场了。
42、如何在子类中开放私有基类的接口?
class X{
public:
void f(){cout<<"haha"<<endl;}
void ff(){cout<<"hehe"<<endl;}
};
class Y: X{ //私有继承
public:
X::f; //将f重新申明为public的
};
void main(){
Y y;
y.f();
//! y.ff();
}
43、判断下列例子的输出:
class X{
public:
void foo() {cout<<"X::foo"<<endl;}
};
class Y: public X{
public:
void foo() {cout<<"Y::foo"<<endl;} //覆盖基类foo
};
void bar(X* p){
p->foo();
}
void barbar(X& x){
x.foo();
}
void main(){
Y y;
X* p;
Y* q;
X& m=y;
p=&y;
q=&y;
p->foo();
bar(p);
barbar(m);
q->foo();
bar(q);
}
X::foo
X::foo
X::foo
Y::foo
X::foo
Y中的foo覆盖了X中的的foo,因此q->foo()输出Y::foo,但其他都输出X,是因为编译器不知道调用哪一个foo,所以就固定为X的了,这样不会受到派生类的影响
要想通过p调用Y::foo()该怎么做?利用virtual 实现多态,告诉其该调用哪一个版本
44、晚捆绑late binding。解释编译器是如何来做到晚捆绑的?
a. 为每个包含虚函数的类创建一个表(VTABLE)
b. 在该表中放置所有虚函数的地址
c. 为每个有虚函数的类的对象的初始位置放置一个指针:VPTR,指向相应的VTABLE
45、class no_virtual{
int a;
void f(){};
};
class one_virtual{
int a;
virtual void f(){};
};
class only_virtual{
virtual void f(){};
};
class two_virtual{
virtual void f(){};
virtual void g(){};
};
class null{};
void main(){
cout<< sizeof(no_virtual)
<< sizeof(one_virtual)
<< sizeof(only_virtual)
<< sizeof(two_virtual)
<< sizeof(null)
<<endl;
}
OUTPUT:48441说明为什么是这个结果?
谈谈在one_virtual 中VPTR与 int a 的位置关系是怎么样的?
4,为成员a所占据的内存
8,成员a及VPTR占据内存
4,VPTR占据内存
4,尽管有两个虚函数,但只有一个VPTR,实际指向的VTABLE大些,但类本身并没有虚函数的个数增长
1,尽管类null没有任何成员,但对于该类的每个对象都需要有一个唯一的地址,不占据内存就没有地址,因此必须至少分配一个字节内存
若唯一的VPTR在a之后,那么再加一个成员变量b后,按照前面的类比关系,VPTR可以在b之后,n个成员变量呢??VPTR的位置会随着成员变量的增减而变化,这种变化编译器在upcasting时如何知道呢?无法知道,因此必须让VPTR处于含有虚函数的类的第一个内存位置