现在的位置: 首页 > 综合 > 正文

C/C++笔试系列--经典C++笔试题解析3

2013年10月03日 ⁄ 综合 ⁄ 共 4309字 ⁄ 字号 评论关闭

 

经典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指向了Xf成员函数,也就是确定了一个偏移地址。当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

 

45class 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;

}

OUTPUT48441说明为什么是这个结果?

谈谈在one_virtual VPTR int a 的位置关系是怎么样的?

4,为成员a所占据的内存

8,成员aVPTR占据内存

4VPTR占据内存

4,尽管有两个虚函数,但只有一个VPTR,实际指向的VTABLE大些,但类本身并没有虚函数的个数增长

1,尽管类null没有任何成员,但对于该类的每个对象都需要有一个唯一的地址,不占据内存就没有地址,因此必须至少分配一个字节内存

若唯一的VPTRa之后,那么再加一个成员变量b后,按照前面的类比关系VPTR可以在b之后,n个成员变量呢??VPTR的位置会随着成员变量的增减而变化,这种变化编译器在upcasting时如何知道呢?无法知道,因此必须让VPTR处于含有虚函数的类的第一个内存位置

 

 

抱歉!评论已关闭.