c++面试题1

一、哪些成员函数不能被继承。

  C++中,并不是所有的成员函数都能被子类继承,有三类成员函数不能被子类继承,分别是:构造函数(包括拷贝构造)、析构函数、赋值运算符重载函数。

1. 构造函数

  构造方法用来初始化类的对象,与父类的其它成员不同,它不能被子类继承(子类可以继承父类所有的成员变量和成员方法,但不继承父类的构造方法)。因此,在创建子类对象时,为了初始化从父类继承来的数据成员,系统需要调用其父类的构造方法。 
  如果没有显式的构造函数,编译器会给一个默认的构造函数,并且该默认的构造函数仅仅在没有显式地声明构造函数情况下创建。

构造原则如下:

1. 如果子类没有定义构造方法,则调用父类的无参数的构造方法。

2. 如果子类定义了构造方法,不论是无参数还是带参数,在创建子类的对象的时候,首先执行父类无参数的构造方法,然后执行自己的构造方法。

3. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数,则会调用父类的默认无参构造函数。

4. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类自己提供了无参构造函数,则会调用父类自己的无参构造函数。

5. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类只定义了自己的有参构造函数,则会出错(如果父类只有有参数的构造方法,则子类必须显示调用此带参构造方法)。

6. 如果子类调用父类带参数的构造方法,需要用初始化父类成员对象的方式
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

2. 析构函数

  析构函数也不会被子类继承,只是在子类的析构函数中会调用父类的析构函数。

3. 赋值运算符重载函数

  赋值运算符重载函数也不会被子类继承,只是在子类的赋值运算符重载函数中会调用父类的赋值运算符重载函数。


二、哪些函数不能声明成虚函数

  在C++,有五种函数不能被声明成虚函数,分别是:非成员函数、构造函数、静态成员函数、内联成员函数、友元函数这五种,下面分别解释为什么这五种函数不能被声明成虚函数。

1. 非成员函数

  非成员函数只能被重载(overload),不能被继承(override),而虚函数主要的作用是在继承中实现动态多态,非成员函数早在编译期间就已经绑定函数了,无法实现动态多态,那声明成虚函数还有什么意义呢?

2. 构造函数

  要想调用虚函数必须要通过“虚函数表”来进行的,但虚函数表是要在对象实例化之后才能够进行调用。而在构造函数运行期间,还没有为虚函数表分配空间,自然就没法调用虚函数了。

3. 静态成员函数

  静态成员函数对于每个类来说只有一份,所有的对象都共享这一份代码,它是属于类的而不是属于对象。虚函数必须根据对象类型才能知道调用哪一个虚函数,故虚函数是一定要在对象的基础上才可以的,两者一个是与实例相关,一个是与类相关。

4. 内联成员函数

  内联函数是为了在代码中直接展开,减少函数调用花费的代价,虚函数是为了在继承后对象能够准确的执行自己的动作,并且inline函数在编译时被展开,虚函数在运行时才能动态地绑定函数。

5. 友元函数

  因为C++不支持友元函数的继承,对于没有继承特性的函数没有虚函数的说法。友元函数不属于类的成员函数,不能被继承。


三、为什么析构函数和构造函数内不能调用虚函数

  在构造派生类对象时,首先调用基类构造函数初始化对象的基类部分,再调用派生类构造函数。在执行基类构造函数时,对象的派生类部分是未初始化的。实际上,此时的对象还不是一个派生类对象(不完整)。

  析构派生类对象时,首先调用的是派生类析构函数,一旦开始执行派生类析构函数,对象内派生类的成员变量便呈现未定义值,此时对象便不完整。 
   
  为了适应这种不完整,编译器将对象的类型视为在调用构造/析构函数时发生了变换,即:视对象的类型为当前构造函数/析构函数所在的类的类型。由此造成的结果是:在基类构造函数或者析构函数中,会将派生类对象当做基类类型对象对待。而这样一个结果,会对构造函数、析构函数调用期间调用的虚函数类型的动态绑定对象产生影响,最终的结果是:如果在构造函数或者析构函数中调用虚函数,运行的都将是为构造函数或者析构函数自身类类型定义的虚函数版本。


四、为什么析构函数最好声明成虚函数

class A{
public:
    A(){cout << "A()" << endl;}

    ~A(){cout << "~A()" << endl;}
};

class B : public A{
public:
    B(){cout << "B()" << endl;}
    ~B(){cout << "~B()" << endl;}
};

int main()
{
    A* p = new B;
    delete p;
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

  上述代码,最后只调用了父类的析构函数,没有调用子类的析构函数,这在一定程度上会造成内存泄漏,就像你拆房子只拆了地基其他的不拆一样。要解决这种问题可以把析构函数定义成虚函数。虚函数的调用会根据指针指向的类型决定,此时p指向B类型,所以会调用B的析构函数,这样就不会造成内存泄漏。 
  所以最好把析构函数,建议是最好定义成虚函数。


五、实现一个不能被继承的类

  将构造函数定义为私有的,因为子类创建对象需要先调用父类的构造函数,如果父类中的构造函数被定义为私有的,那么子类就无法调用父类构造函数。

class A
{
private:
    A(){}
    int _a;
};

class B :public A
{
private:
    int _b;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

六、实现一个类定义出来的对象都在堆上面

class A
{
public:
    static A* fun()
    {
        return new A;
    }
private:
    A(){}
    int _a;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

  上面代码能保证构造函数生成的对象都在堆上,但并不能保证拷贝构造出来的对象在堆上。 
  这个代码有缺陷,虽然能保证构造的对象都是堆上的,但是不能防止拷贝构造的对象在栈上。

A* a = A::fun(); //在堆上 
A a1(*pa);       //在栈上 
  • 1
  • 2

此时要再优化一下代码: 
1. 只声明拷贝构造不实现 。 
2. 将拷贝构造声明成私有的(防止在类外实现拷贝构造)。

class A
{
public:
    static A* fun()
    {
        return new A;
    }

private:
    A(){}
    //将拷贝构造函数声明成私有的
    A(const A& other);
    A& operator=(const A& other);
    int _a;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

七、实现一个类定义出来的对象都在栈上面

class A
{
public:
    static A& fun()
    {
        return A();
    }

private:
    A()
    {}
    int _a;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

八、实现一个类,定义出的对象不能在堆上

  将动态分配空间的操作符声明为私有的,即可防止构造类对象时在堆上开空间的行为。

class A
{
private:
    void* operator new(size_t size);
    void operator delete(void *p);
    int _a;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

九、用C语言实现继承和多态

要求如下: 
C 实现一个 struct A 和 stuct B 包含一个 int 成员 _a 和 _b,要求达到B 继承 A 的效果,也就是 B里面包含一个 A,并且能达到多态的效果,也就是基类指针,能根据它指向的对象类型调用不同的函数。

//函数指针
typedef void(*FUNC) ();

struct A
{
    // 函数指针模拟实现成员函数
    FUNC _func;
    int _a;
};

struct B
{
    // 结构体嵌套定义模拟实现继承
    struct A a;
    int _b;
};

void fa()
{
    printf("A::fa()\n");
}
void fb()
{
    printf("B::fb()\n");
}

int main()
{
    struct A a;
    struct B b;
    a._func = fa;
    b.a._func = fb;
    struct A* p = &a; 

    //指向基类对象,调用基类的函数
    p->_func();

    //指向派生类对象,调用派生类函数
    p = (struct A*)&b;
    p->_func();

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

测试结果:

[tian@localhost Test]$ ./a.out 
A::fa()
B::fb()
  • 1
  • 2
  • 3

欢迎各位大佬斧正!

转载出处 https://blog.csdn.net/Tianzez/article/details/80057455

猜你喜欢

转载自blog.csdn.net/usstmiracle/article/details/81285274