首页 > 编程语言 >二、C++面向对象面试题

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

时间:2022-12-08 21:01:15浏览次数:56  
标签:面试题 函数 基类 C++ 面向对象 析构 派生类 public 指针


二、面向对象

1. 多态

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

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

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

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

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

二、C++面向对象面试题_析构函数

(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)多继承存在什么问题?如何消除多继承中的二义性?

二、C++面向对象面试题_派生类_02

在继承时,基类之间发生成员同名时,将出现对成员访问的不确定性,即同名二义性;

消除同名二义性的方法:
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(私有的):只能被类成员函数及友元访问,不能被其他任何访问,本身的类对象也不行。


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

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

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

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

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

二、C++面向对象面试题_析构函数_03

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

二、C++面向对象面试题_派生类_04

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++面向对象面试题_析构函数_05

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

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

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

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

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

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

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


标签:面试题,函数,基类,C++,面向对象,析构,派生类,public,指针
From: https://blog.51cto.com/u_15902866/5923471

相关文章

  • VC++ ScrollBar控件的使用方法
    1、在对话框中拖入CScrollBar控件,并修改ID,2、示例一:voidCTestScrollBarDlg::OnHScroll(UINTnSBCode,UINTnPos,CScrollBar*pScrollBar){ //TODO:在此添加消息处......
  • C++ pair
    1.头文件pair函数存在于C++标准库#include<utility>中。2.用处他可以创建一个有2个成员的结构体。3.使用方法它的使用方法是:pair<数据类型1,数据类型2>结构体变量......
  • C++图谱
    最新的培训机构培训图谱,可以用于自身知识体系图谱的构建。......
  • 继承与组合 C++(五)
     1.继承如果A是基类,B是A的派生类,那么B将继承A的数据和函数。 C++的“继承”特性可以提高程序的可复用性。正因为“继承”太有用、太容易用,才要防止乱用“继承”。我们应当......
  • C++面试题(四)
     ​待续#include<QCoreApplication>#include<QTextStream>#include<iostream>usingnamespacestd;classString{public:String(constchar*str=NULL);......
  • c++ stl总结
    前言本篇是c++总结系列的stl篇,重点讲解容器,及effectivestl的总结stl原理stl提供六大部件,这六大部件可以彼此搭配工作,这六大部件是:容器。各种数据结构迭代器。扮演容......
  • windows下c++设置防火墙
     /*Copyright(c)MicrosoftCorporationSYNOPSISSamplecodefortheWindowsFirewallCOMinterface.*/#include<windows.h>#include<cr......
  • (黑马)C++提高编程笔记
    文章目录​​1模板​​​​1.1模板的概念​​​​1.2函数模板​​​​1.2.1函数模板语法​​​​1.2.2函数模板注意事项​​​​1.2.3函数模板案例​​​​1.2.4普通......
  • python面向对象(类的成员及类方法)
    类的普通成员字段方法属性类的高级成员静态字段静态方法属性方法 类方法类成员修饰符类的成员类的成员可以分为三大类:字段、方法和属性注:所有成员中,只有普通字段的内容保存......
  • C++的语法 学习笔记1
    C++的语法学习笔记1  C++各种数据类型的默认值数值类型int/double/float/long0char'\0'string"\0"bool0,也就是false  数......