二、C++面向对象面试题

简介: 二、C++面向对象面试题

二、面向对象


1. 多态


(1)多态的实现有哪几种?


黑马程序员C++核心编程第68页


静态多态和动态多态。
静态多态:是通过重载和模板技术实现的,在编译期间确定函数地址;
动态多态:是通过虚函数和继承关系实现的,执行动态绑定,在运行期间确定函数地址。


(2)动态绑定是如何实现的?


当编译器发现类中有虚函数时,会创建一张虚函数表,把虚函数的函数入口地址放在虚函数表中,并且在对象中增加一个指针vptr,用于指向类的虚函数表。当派生类覆盖基类的虚函数时,会将虚函数表中对应的指针进行替换,从而调用派生类中覆盖后的虚函数,从而实现动态绑定。



(3)动态多态有什么作用?有哪些必要条件?


动态多态的作用:
1. 隐藏实现细节,使代码模块化,提高代码的可复用性。
2. 接口重用,使派生类的功能可以被基类的指针/引用所调用,即向后兼容,提高代码的可扩充性和可维护性。
动态多态的必要条件:
1. 需要有继承关系
2. 需要有子类重写父类的虚函数
3. 需要有基(父)类指针/引用指向子类对象


//类中只有一个纯虚函数就称为抽象类
//抽象类无法实例化对象
//子类必须重写父类中的纯虚函数
class Animal{
public:
    virtual void speak() = 0;  //纯虚函数
    /*
    virtual void sepak(){   //虚函数
      cout << "动物在说话" << endl;
    }
    */
};
class Cat : public Animal{
public:
    void speak(){
        cout << "小猫在说话" << endl;
    }
};
class Dog : public Animal{
public:
    void speak(){
        cout << "小狗在说话" << endl;
    }
};
void DoSpeak(Animal& animal){
  animal.speak();
}
int main(){
    Cat cat;
    DoSpeak(cat);
    Dog dog;
    DoSpeak(dog);
    return 0;
}


(4)多继承存在什么问题?如何消除多继承中的二义性?



在继承时,基类之间发生成员同名时,将出现对成员访问的不确定性,即同名二义性;
消除同名二义性的方法:
    1. 利用作用域运算符::,用于限定派生类使用的是哪个基类的成员;
    2. 在派生类中定义同名成员,覆盖基类中的相关成员;
当派生类从多个基类派生,而这些基类又从同一个基类派生,则在访问此共同基类的成员时,将产生另一种不确定性,即路径二义性;
消除路径二义性的方法:
    1. 消除同名二义性的两种方法都可以;
    2. 使用虚继承,使得不同路径继承来的同名成员在内存中只有一份拷贝。


class Animal {
public:
  int m_Age;
};
//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal称为虚基类
class Sheep : virtual public Animal {};
class Tuo : virtual public Animal {};
class SheepTuo : public Sheep, public Tuo {};
void test01(){
    SheepTuo st;
    st.Sheep::m_Age = 100;
    st.Tuo::m_Age = 200;
    cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;  //200
    cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;      //200
    cout << "st.m_Age = " << st.m_Age << endl;                //200
}
int main() {
    test01();
    return 0;
}


2. 虚函数


(1)纯虚函数有什么作用?如何实现?


定义纯虚函数是为了实现一个接口,起到规范的作用,想要继承这个类就必须重写该函数。
实现方式是在虚函数声明的结尾加上 = 0;


(2)虚函数表是针对类的还是针对对象的?同一个类的两个对象的虚函数表是怎么维护的?


虚函数表是针对类的,类的所有对象共享这个类的虚函数表,因为每个对象内部都保存了一个指向该虚函数表的指针vptr,每个对象的vptr的存放地址都不同,但都指向同一虚函数表。


(3)为什么基类的构造函数不能定义为虚函数?


虚函数的调用依赖于虚函数表,而指向虚函数表的指针vptr需要在构造函数中进行初始化,所以无法调用定义为虚函数的构造函数。


(4)虚析构和纯虚析构


多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码。
解决方法:将父类中的析构函数改为虚析构或者纯虚析构
虚析构:virtual ~类名(){}
纯虚析构:virtual ~类名() = 0;


(5)为什么基类的析构函数需要定义为虚函数?


为什么析构函数一般写成虚函数-帅地玩编程 (iamshuaidi.com)


由于类的多态性,基类指针可以指向派生类的对象,如果删除该基类的指针,就会调用该指针指向的派生类析构函数,而派生类的析构函数又自动调用基类的析构函数,这样整个派生类的对象完全被释放。
如果析构函数不被声明成虚函数,则编译器实施静态绑定,在删除基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会造成派生类对象析构不完全,造成内存泄漏。


class Parent{
public:
    Parent(){
    cout << "调用父类构造函数" << endl;
    }
    ~Parent(){
        cout << "调用父类析构函数" << endl;
    }
};
class Son : public Parent{
public:
    Son(){
        cout << "调用子类构造函数" << endl;
    }
    ~Son(){
        cout << "调用子类析构函数" << endl;
    }
};
int main(){
    Parent* p = new Son();
    delete p;
    p = NULL;
    system("pause");
    return 0;
}
/*
输出:
调用父类构造函数
调用子类构造函数
调用父类析构函数
*/


将父类析构函数声明为虚函数:


class Parent{
public:
    Parent(){
    cout << "调用父类构造函数" << endl;
    }
    ~Parent(){
        cout << "调用父类析构函数" << endl;
    }
};
class Son : public Parent{
public:
    Son(){
        cout << "调用子类构造函数" << endl;
    }
    virtual ~Son(){
        cout << "调用子类析构函数" << endl;
    }
};
int main(){
    Parent* p = new Son();
    delete p;
    p = NULL;
    system("pause");
    return 0;
}
/*
输出:
调用父类构造函数
调用子类构造函数
调用子类析构函数
调用父类析构函数
*/


(6)如何让一个类不能实例化?


将类定义为抽象类(也就是存在纯虚函数),或者将构造函数声明为private


构造函数和析构函数能抛出异常吗?


* 从语法的角度来说,构造函数可以抛出异常,但从逻辑和风险控制的角度来说,尽量不要抛出异常,否则可能导致内存泄漏。
* 析构函数不可以抛出异常,如果析构函数抛出异常,则异常点之后的程序,比如释放内存等操作,就不会被执行,从而造成内存泄漏的问题;而且当异常发生时,C++通常会调用对象的析构函数来释放资源,如果此时析构函数也抛出异常,即前一个异常未处理又出现了新的异常,从而造成程序崩溃的问题。


3. 重写、重载


(1)覆盖和重载之间有什么区别?


覆盖是指派生类中重新定义函数,其函数名、参数列表、返回类型与父类完全相同,只是函数体存在区别;覆盖只发生在类的成员函数中。
重载是指两个函数具有相同的函数名,不同的参数列表,不关心返回值;当调用函数时,根据传递的参数列表来判断调用哪个函数;重载可以是类的成员函数(构造函数重载),也可以是普通函数。


(2)简述类成员函数的重写、重载和隐藏的区别


1. 重写是指子类重写父类的方法,发生在两个类里面;重载发生在一个类里面(如构造函数的重载)。
2. 重写函数的参数列表相同;重载函数的参数列表不同。
3. 虽然重载和重写都是实现多态的基础,但是重载是静态绑定的多态,重写(覆盖)是动态绑定。


4. 其他


(1)面向对象三大特性?


1. 封装:将客观事物封装成抽象的类,而类可以把自己的数据和方法暴露给可信的类或者对象,对不可信的类或对象则进行信息隐藏。
2. 继承:可以使用现有类的所有功能,并且无需重新编写原来的类即可对功能进行扩展
3. 多态:一个类实际的相同方法在不同情形下有不同的表现形式,使不同内部结构的对象可以共享相同的外部接口。


(2)C++中类成员的访问权限


public(共有的):能被类成员函数、子类函数、友元访问,也能被类的对象访问。
protected(受保护的):只能被类成员函数、子类函数及友元访问,不能被其他任何访问,本身的类对象也不行。
private(私有的):只能被类成员函数及友元访问,不能被其他任何访问,本身的类对象也不行。


https://blog.csdn.net/zhizhengguan/article/details/109818741


(3)如果类A是一个空类,那么sizeof(A)的值为多少?


sizeof(A)的值为1,因为编译器需要区分这个空类的不同实例,分配一个字节,可以使这个空类的不同实例拥有独一无二的地址。


(4)C++的空类有哪些成员函数?


缺省构造函数
缺省拷贝构造函数
省析构函数
赋值运算符
取址运算符
取址运算符const
「注意」:有些书上只是简单的介绍了前四个函数。没有提及后面这两个函数。但后面这两个函数也是空类的默认函数。另外需要注意的是,只有当实际使用这些函数的时候,编译器才会去定义它们。


(5)拷贝构造函数和赋值运算符重载之间有什么区别?



(6)说说C++的四种强制类型转换运算符



1. reinterpret_cast
reinterpret_cast<type>(expression)
type必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以用于类型之间进行强制转换。
2. const_cast
const_cast(expression)
该运算符用来修饰类型的const或volatile属性。除了const或volatile修饰之外, type_id和expression的类型是一样的。用法如下:    
* 常量指针被转化成非常量的指针,并且仍然指向原来的对象
* 常量引用被转换成非常量的引用,并且仍然指向原来的对象
* const_cast一般用于修改底层const。如const char *p形式    
3. static_cast
static<type>(expression)
该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:   * 用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换
  1. 进行上行转换(把派生类的指针或引用转换成基类表示)是安全的
  2. 进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的
* 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
* 把空指针转换成目标类型的空指针
* 把任何类型的表达式转换成void类型 
注意:static_cast不能转换掉expression的const、volatile、或者__unaligned属性。    
4. dynamic_cast
 dynamic_cast (expression)   
有类型检查,基类向派生类转换比较安全,但是派生类向基类转换则不太安全    



说说C++的四种强制类型转换运算符-帅地玩编程 (iamshuaidi.com)


类型转换分为哪几种?各自有什么样的特点?-帅地玩编程 (iamshuaidi.com)


说一说c++中四种cast转换-帅地玩编程 (iamshuaidi.com)


(7)RTTI是什么?其原理是什么?



(8)模板函数和模板类的特例化


模板函数和模板类的特例化-帅地玩编程 (iamshuaidi.com)

相关文章
|
1月前
|
存储 算法 C++
C/C++工程师面试题(STL篇)
C/C++工程师面试题(STL篇)
49 6
|
1月前
|
存储 缓存 数据库
C/C++工程师面试题(数据库篇)
C/C++工程师面试题(数据库篇)
51 9
|
2月前
|
C++
二叉树进阶面试题(精华总结)【C++版本】
二叉树进阶面试题(精华总结)【C++版本】
|
2月前
|
算法 测试技术 数据处理
【C/C++ 面试技巧】如何在简单的项目里突出自己的价值?
【C/C++ 面试技巧】如何在简单的项目里突出自己的价值?
55 1
|
2月前
|
存储 编译器 C++
C++:多态究竟是什么?为何能成为面向对象的重要手段之一?
C++:多态究竟是什么?为何能成为面向对象的重要手段之一?
52 0
|
1月前
|
算法 Java 程序员
【C++专栏】C++入门 | 类和对象 | 面向过程与面向对象的初步认识
【C++专栏】C++入门 | 类和对象 | 面向过程与面向对象的初步认识
23 0
|
20天前
|
C++
C++从入门到精通:2.1.2函数和类——深入学习面向对象的编程基础
C++从入门到精通:2.1.2函数和类——深入学习面向对象的编程基础
|
26天前
|
C++
面向对象的C++题目以及解法2
面向对象的C++题目以及解法2
31 1
|
26天前
|
C++
面向对象的C++题目以及解法
面向对象的C++题目以及解法
19 0
|
28天前
|
编译器 C语言 C++
【C++成长记】C++入门 | 类和对象(上) |面向过程和面向对象初步认识、类的引入、类的定义、类的访问限定符及封装
【C++成长记】C++入门 | 类和对象(上) |面向过程和面向对象初步认识、类的引入、类的定义、类的访问限定符及封装