【C++】面试官:你小子,继承与多态的题你都会(下)

简介: 【C++】面试官:你小子,继承与多态的题你都会(下)

二、编程题选择类



1.下面代码输出结果:( D)

class A
{
public:
  void f(){ cout<<"A::f()"<<endl; }
  int a;   
};
class B : public A
{
public:
  void f(int a){cout<<"B::f()"<<endl;}
  int a;
};
int main()
{
  B b;
  b.f();
  return 0;
}


A.打印A::f()

B.打印B::f()

C.不能通过编译,因为基类和派生类中a的类型以及名称完全相同

D.以上说法都不对


首先看main函数,定义了一个对象b,既然是对象先去B类中找f()函数如果没有才去父类中寻找,而子类中由于f()函数与父类函数同名构成隐藏,隐藏了父类的函数实现方法所以只能调用子类的f(int a)函数,而由于缺乏参数所以编译报错。


2.下面哪项结果是正确的(C )

class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 
{ 
  public: int _d; 
};
int main(){
Derive d;
Base1* p1 = &d;
Base2* p2 = &d;
Derive* p3 = &d;
return 0;
}


A.p1 == p2 == p3

B.p1 < p2 < p3

C.p1 == p3 != p2

D.p1 != p2 != p3


首先d继承了base1和base2,有一个base1的指针存储d对象,base2的指针也存储d对象,派生类指针存储子类对象,下面我们画图看看他们的关系:

d77189d9953f4676a609162857cb55fb.png



由于d先继承的base1所以只有base1的首地址与d相同,而base2的首地址在base1类的下一个所以和d不相同。


3.下列代码中f函数执行结束后输出(C )

class A
{
public:
  A() { cout<<"A::A()"<<endl; }
  ~A() { cout<<"A::~A()"<<endl; }
  int a;
};
class B : public A
{
public:
  B() { cout<<"B::B()"<<endl; }
  ~B() {cout<<"B::~B()"<<endl; }
  int b;
};
void f()
{
  B b;
}


A.B::B() B::~B()

B.B::B() A::A() A::~A() B::B()

C.A::A() B::B() B::~B() A::~A()

D.以上都不对


首先从f()函数开始看起,有一个子类对象b,这时候调用B的构造函数,进入B的构造函数初始化列表调用父类A的构造函数,所以先打印A(),然后进入B构造函数的函数体打印B(),然后函数结束开始析构,先析构子类对象打印~B(),然后子类析构结束后自动调用父类析构~A()。


4.以下哪项说法时正确的(D )

class A
{
public:
  void f1(){cout<<"A::f1()"<<endl;}
  virtual void f2(){cout<<"A::f2()"<<endl;}
  virtual void f3(){cout<<"A::f3()"<<endl;}
};
class B : public A
{
public:
  virtual void f1(){cout<<"B::f1()"<<endl;}
  virtual void f2(){cout<<"B::f2()"<<endl;}
  void f3(){cout<<"B::f3()"<<endl;}
};


A.基类和子类的f1函数构成重写

B.基类和子类的f3函数没有构成重写,因为子类f3前没有增加virtual关键字

C.如果基类指针引用子类对象后,通过基类对象调用f2时,调用的是子类的f2

D.f2和f3都是重写,f1是重定义


f1由于在基类中没有加virtual关键字,所以只能构成隐藏。f2和f3满足重写的条件。

5.以下程序输出结果是( C)

class A
{
public:
  A ():m_iVal(0){test();}
  virtual void func() { std::cout<<m_iVal<<‘ ’;}
  void test(){func();}
public:
  int m_iVal;
};
class B : public A
{
public:
  B(){test();}
  virtual void func()
  {
    ++m_iVal;
    std::cout<<m_iVal<<‘ ’;
  }
};
int main(int argc ,char* argv[])
{
  A*p = new B;
  p->test();
  return 0;
}

A.1 0

B.0 1

C.0 1 2

D.2 1 0

E.不可预期

F. 以上都不对


首先从main函数看起,父类指针存放子类是多态的信号,先自动调用B类的构造函数,在B的构造函数的初始化列表调用A的构造函数,然后将mval初始化为0,调用A类中的test函数,在test函数中又调用了func函数,这个时候由于派生类的构造函数初始化列表还没走完,所以没有虚表指针不构成多态,只能调用A类中的func打印0,然后进入B类的构造函数的函数体中调用test函数,由于B中无test函数只能去父类A中调用,在A类中的test函数体中调用func函数,这个时候因为派生类的初始化列表已经走完了虚表指针形成了,并且func被子类重写由this指针也就是A*父类指针调用func满足多态所以在B类中的func中先让mval++变成1然后打印1,接下来由父类指针P主动调用test函数,同样满足多态调用B类中的func函数,mval++变成2然后打印2,所以答案是0 1 2.


6.下面函数输出结果是( A)

class A
{
public: 
  virtual void f()
  {
    cout<<"A::f()"<<endl;
  }
};
class B : public A
{
private:
   virtual void f()
  {
    cout<<"B::f()"<<endl;
  }
};
int main()
{
A* pa = (A*)new B;
pa->f();
}


A.B::f()

B.A::f(),因为子类的f()函数是私有的

C.A::f(),因为强制类型转化后,生成一个基类的临时对象,pa实际指向的是一个基类的临时对象

D.编译错误,私有的成员函数不能在类外调用


先从main函数看起,父类指针存放子类,先调用子类的构造函数,无构造我们就直接往下讲了,由于继承中天生的赋值类型转换,所以子类到父类并不需要强转,所以这一步没有作用,父类指针调用f函数,进入A类中发现f是虚函数子类重写了这个虚函数所以调用B类中的f()函数,虽然这个时候B类中的f()函数是私有的,但是多态仅仅是用子类函数的地址覆盖虚表,最终调用的位置不变只是执行函数发生了变化,所以还是打印B()


7.下面 C++ 程序的运行结果是(C)

class parent {
int i;
protected:
int x;
public:
parent() { x = 0; i = 0; }
void change() { x++; i++; }
void display();
};
class son :public parent {
public:
void modify();
};
void parent::display() {
cout << "x=" << x << endl;
}
void son::modify() {
x++;
}
int main() 
{
son A;
parent B;
A.display();
A.change();
A.modify();
A.display();
B.change();
B.display();
return 0;
}


A:x=1  x=0   x=2

B :x=2    x=0     x=1

C :x=0    x=2    x=1

D: x=0    x=1    x=2


先进入main函数,有一个子类对象A,有一个父类对象B,都经过构造函数初始化x=0,A调用A中的display打印x = 0,然后A调用父类中的change函数(因为子类无change函数),x变成1,然后A调用A中的modify函数x++变成2然后打印2,由于A和B是两个不同的类,所以B调用B中的change函数后x从0变成1,然后打印1。


8.分析一下这段程序的输出(A)

class B
{
public:
B()
{
cout << "default constructor" << " ";
}
~
B()
{
cout << "destructed" << " ";
}
B(int i): data(i)
{
cout << "constructed by parameter" << data << " ";
}
private: int data;
};
B Play( B b)
{
return b;
}
int main(int argc, char *argv[])
{
B temp = Play(5);
return 0;
}


A constructed by parameter5 destructed destructed

B constructed by parameter5 destructed

C default constructor" constructed by parameter5 destructed

D default constructor" constructed by parameter5 destructed destructed


首先进入main函数,调用play函数,而play函数的参数是B类对象,所以这里生成一个B类的临时对象将5拿来构造B然后调用B的构造函数打印constructed by parameter 5,然后调用拷贝构造将这个对象给play函数中的形参,返回的时候本来要创建一个B类的临时对象调用拷贝构造将b给临时对象后再释放原来形参中的那个b变量,但是由于编译器的优化会直接将play的返回值给temp,然后析构掉刚刚那个返回值对象,所以返回值析构打印destructed,函数结束后temp对象析构打印destructed.


9.以下程序的输出是(C)

class Base {
public:
Base(int j): i(j) {}
virtual~Base() {}
void func1() {
i *= 10;
func2();
}
int getValue() {
return i;
}
protected:
virtual void func2() {
i++;
}
protected:
int i;
};
class Child: public Base {
public:
Child(int j): Base(j) {}
void func1() {
i *= 100;
func2();
}
protected:
void func2() {
i += 2;
}
};
int main() {
Base * pb = new Child(1);
pb->func1();
cout << pb->getValue() << endl; delete pb;
}


A 11

B 101

C 12

D 102


首先基类指针存放子类对象是多态的信号,child调用自己的构造函数,然后在初始化列表中用1初始化基类对象,然后去基类的构造函数中用1初始化i,然后调用func1函数,由于func1不是虚函数所以调用基类的func1i*10==10,然后调用func2函数,发现func2是虚函数并且this指针是父类指针所以调用子类中的func2函数i变成12.


10.下面 C++ 代码的运行结果为(A)

class B0 {
public:
virtual void display() {
cout << "B0::display0" << endl;
}
};
class B1 :public B0 {
public:
void display() { cout << "B1::display0" << endl; }
};
class D1 : public B1 {
public:
void display() {
cout << "D1::display0" << endl;
}
};
void fun(B0 ptr) {
ptr.display();
}
int main() {
B0 b0;
B1 b1;
D1 d1;
fun(b0);
fun(b1);
fun(d1);
}


A B0::display0 B0::display0 B0::display0

B B0::display0 B0::display0 D1::display0

C B0::display0 B1::display0 D1::display0

D B0::display0 B1::display0 B1::display0


首先main函数有3个对象,然后都去调用fun函数,而fun函数参数是基类对象,由对象调用display只能调用父类B0自己的函数,所以打印三次B0::display0


11.下面 C++ 程序的运行结果为(A)

class A {
public: A(const char* s) { cout << s << endl; } ~A() {}
};
class B : virtual public A {
public: B(const char* s1, const char* s2) :A(s1) { cout << s2 << endl; }
};
class C : virtual public A {
public: C(const char* s1, const char* s2) :A(s1) { cout << s2 << endl; }
};
class D : public B, public C {
public: D(const char* s1, const char* s2, const char* s3, const char* s4) :B(s1, s2), C(s1, s3), A(s1)
{ cout << s4 << endl; }
};
int main()
{ 
  D* p = new D("class A", "class B", "class C", "class D"); delete p; return 0;
}


A class A    class B     class C     class D

B class D    class B     class C     class A

C class D    class C     class B     class A

D class A     class C     class B     class D


首先这是个多继承问题,我们可以看到D类先继承B,再继承C,而B类中先继承了A,所以D中构造函数的初始化顺序为A B C D,A用s1初始化进入s1的构造函数打印class A,然后用s1和s2初始化B,由于B虚继承A只有一份A所以刚开始在A初始化一次后后面在其他类的初始化列表就不再初始化A了,所以打印S2也就是classB,然后用s1和s3初始化C,与B同理A不初始化打印S3也就是class C,最后进入D的构造函数函数体打印classD


12.有如下C++代码(A)

struct A{
void foo(){printf("foo");}
virtual void bar(){printf("bar");}
A(){bar();}
};
struct B:A{
void foo(){printf("b_foo");}
void bar(){printf("b_bar");}
};
A *p = new B;
p->foo();
p->bar();


A barfoob_bar

B foobarb_bar

C barfoob_foo

D foobarb_fpp

首先A和B是struct,struct默认访问权限和继承权限都是公有,new一个B对象,调用B的构造函数在B的构造函数的初始化列表调用A的构造函数,在A的构造函数中调用bar函数,由于初始化列表没有结束只能调用A的bar函数所以打印bar,然后父类指针调用foo函数foo不是虚函数所以调用A中的foo打印foo,然后父类指针调用bar函数,bar是虚函数满足多态条件所以打印b_bar.


13.以下程序输出结果是(B)

class A
{
public:
virtual void func(int val = 1)
{ std::cout<<"A->"<<val <<std::endl;}
virtual void test()
{ func();}
};
class B : public A
{
public:
void func(int val=0) {std::cout<<"B->"<<val <<std::endl;}
};
int main(int argc ,char* argv[])
{
B*p = new B;
p->test();
return 0;
}


A :A->0

B :B->1

C :A->1

D: B->0


首先子类指针存放子类对象,然后调用test函数,进入B类中发现没有test函数只能去父类中找,然后调用调用父类中的test函数,函数体中调用func函数,这时的this指针为A*,func为虚函数去B类中调用func函数,由于缺省参数是编译期间就完成的,而虚函数是运行期间完成的,所以当调用func函数的时候缺省参数还是父类的int val = 1,所以这时候打印B->1.


14.下面程序的输出是(B)

class A
{
public:
void foo()
{
printf("1");
}
virtual void fun()
{
printf("2");
}
};
class B: public A
{
public:
void foo()
{
printf("3");
}
void fun()
{
printf("4");
}
};
int main(void)
{
A a;
B b;
A *p = &a;
p->foo();
p->fun();
p = &b;
p->foo();
p->fun();
A *ptr = (A *)&b;
ptr->foo();
ptr->fun();
return 0;
}


A 121434

B 121414

C 121232

D 123434


首先父类指针p存放父类对象,所以调用的函数都是父类的,打印1 2,然后p又存放子类对象,由于fun是虚函数所以打印1 4,因为子类对象本来就可以直接给父类,所以强转无任何意义与p = &b一模一样,还是打印1 4.


15.下面这段代码运行时会出现什么问题(B)

class A
{
public:
void f()
{
printf("A\n");
}
};
class B: public A
{
public:
virtual void f()
{
printf("B\n");
}
};
int main()
{
A *a = new B;
a->f();
delete a;
return 0;
}


A 没有问题,输出B

B 不符合预期的输出A

C 程序不正确

D 以上答案都不正确


因为父类指针保存的子类对象,f不是虚函数正常调用A的f函数,但是delete的时候只会调用父类的析构函数这里是会崩溃的,正确的做法是将父类的析构函数定义为虚函数,就可以正常释放子类的资源了。


目录
相关文章
|
1月前
|
编译器 C++
C++入门12——详解多态1
C++入门12——详解多态1
40 2
C++入门12——详解多态1
|
1月前
|
安全 程序员 编译器
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
84 11
|
1月前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
54 1
|
1月前
|
C++
C++入门13——详解多态2
C++入门13——详解多态2
81 1
|
1月前
|
C++
C++番外篇——虚拟继承解决数据冗余和二义性的原理
C++番外篇——虚拟继承解决数据冗余和二义性的原理
39 1
|
1月前
|
安全 编译器 程序员
C++的忠实粉丝-继承的热情(1)
C++的忠实粉丝-继承的热情(1)
20 0
|
1月前
|
编译器 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
31 0
|
1月前
|
程序员 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
33 0
|
3月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
15天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
下一篇
无影云桌面