C++面试(1)
目录
1.请你说说 delete 和 free 的区别
2.简述一下什么是面向对象
3.简述一下 C++ 的重载和重写
4.简述一下面向对象的三大特征
5.简述一下浅拷贝和深拷贝
6.简述一下 C++ 中的多态
7.简述一下虚函数的实现原理
8.什么是纯虚函数,有什么作用
9.虚析构函数有什么作用
10.请你说说重载,复写,隐藏的区别
1.请你说说 delete 和 free 的区别
标准回答 delete 和 free 的区别:
1. delete 是操作符,而 free 是函数;
2. delete 用于释放 new 分配的空间,free 有用释放 malloc 分配的空间;
3. free 会不会调用对象的析构函数,而 delete 会调用对象的析构函数;
4. 调用 free 之前需要检查要释放的指针是否为 NULL,使用 delete 释放内存则不需要检查指针是否为 NULL;
2.简述一下什么是面向对象
解题思路
得分点 面向过程的思想、面向对象的思想、面向对象三大特征:封装、继承、多态 标准回答 面向对象思想是基于面向过程思想的,要说面向对象思想,先说说面向过程思想。 1. 面向过程思想 完成一个需求的步骤:首先是搞清楚要做什么,然后再分析怎么做,最后再通过代码体现。一步一步去实现,而具体的每一步都需要我们去实现和操作。这些步骤相互调用和协作,从而完成需求。在上面的每一个具体步骤中我们都是参与者,并且需要面对具体的每一个步骤和过程,这就是面向过程最直接的体现。 面向过程编程,其实就是面向着具体的每一个步骤和过程,把每一个步骤和过程完成,然后由这些功能函数相互调用,完成需求。 2. 面向对象思想 面向对象的思想是尽可能模拟人类的思维方式,使得软件的开发方法与过程尽可能接近人类认识世界、解决现实问题的方法和过程,把客观世界中的实体抽象为问题域中的对象。面向对象以对象为核心,该思想认为程序由一系列对象组成。 面向对象思想的特点: - 是一种更符合人类思维习惯的思想 - 可以将复杂的问题简单化 - 将我们从执行者变成了指挥者 3. 面向对象的三大特征:封装、继承、多态 - 封装:将事物属性和行为封装到一起,也就是 C++ 中的类,便于管理,提高代码的复用性。事物的属性和行为分别对应类中的成员变量和成员方法。 - 继承:继承使类与类之间产生关系,能够提高代码的复用性以及可维护性。 - 多态:多态意味着调用成员函数时,会根据调用方法的对象的类型来执行不同的函数。 加分回答 面向过程和面向对象解决问题举例:以洗衣服为例。 1. 面向过程:接水到盆中 —— 放入衣服 —— 加入洗衣粉 —— 浸泡 —— 搓衣服 —— 过水 —— 拧干 —— 晾干 2. 面向对象:将衣服放入洗衣机 —— 加入洗衣粉 —— 开启 —— 晾干 通过例子可以发现面向对象的方式解决问题更加简单一些,但是面向对象还是基于面向过程的。
3.简述一下 C++ 的重载和重写
解题思路
得分点:概念、应用、区别
标准回答:
1. 重载a. 重载是指不同的函数使用相同的函数名,但是函数的参数个数或类型不同(参数列表不同)。调用的时候根据函数的参数来区别不同的函数,函数重载跟返回值无关。
b. 重载的规则 - 函数名相同 - 必须具有不同的参数列表 - 可以有不同的访问修饰符
c. 重载用来实现静态多态(函数名相同,功能不一样)。
d. 重载是多个函数或者同一个类中方法之间的关系,是平行关系。
2. 重写a. 重写(也叫覆盖)是指在派生类中重新对基类中的虚函数重新实现。即函数名和参数都一样,只是函数的实现体不一样。
b. 重写的规则: - 方法声明必须完全与父类中被重写的方法相同 - 访问修饰符的权限要大于或者等于父类中被重写的方法的访问修饰符 - 子类重写的方法可以加virtual,也可以不加
c. 重写用来实现动态多态(根据调用方法的对象的类型来执行不同的函数)。
d. 重写是父类和子类之间的关系,是垂直关系。
加分回答
1. C++中重载的实现 采用命名倾轧(name mangling)技术,编译时会将同名的函数或方法根据某种规则生成不同的函数或方法名(因为函数或方法的特征标不一样)。
2. C++中重写的实现 C++中重写可以用来实现动态多态,父类中需要重写的方法要加上 virtual 关键字。 虚函数实现的原理是采用虚函数表,多态中每个对象内存中都有一个指针,被称为虚函数指针,这个指针指向虚函数表,表中记录的是该类的所有虚函数的入口地址,所以对象能够根据它自身的类型调用不同的函数。
4.简述一下面向对象的三大特征
得分点 封装、继承、多态 标准回答 面向对象的三大特征是:封装、继承、多态。 1. 封装 将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。封装本质上是一种管理:比如景区,如果人人都能随意进来,那么很容易造成问题,比如损坏公物。所以我们需要建一堵围墙将景区围起来,但是我们的目的不是不让别人进去,所以开放了售票通道,可以买票在合理的监管机制下进去游玩。 C++通过 private、protected、public 三个关键字来控制成员变量和成员函数的访问权限,它们分别表示公有的、受保护的、私有的,被称为成员访问限定符。 - private 修饰的成员只能在本类中访问 - protected 表示受保护的权限,修饰的成员只能在本类或者子类中访问 - public 修饰的成员是公共的,哪儿都可用访问。 封装的好处:隐藏实现细节,提供公共的访问方式;提高了代码的复用性;提高了安全性。 2. 继承 C++最重要的特征是代码重用,通过继承机制可以利用已有的数据类型来定义新的数据类型,新的类不仅拥有旧类的成员,还拥有新定义的成员。一个 B 类继承于 A 类,或称从类 A 派生类 B。这样的话,类 A 成为基类(父类), 类 B 成为派生类(子类)。派生类中的成员,包含两大部分:一类是从基类继承过来的,一类是自己增加的成员。从基类继承过过来的表现其共性,而新增的成员体现了其个性。 继承的好处:提高代码的复用性;提高代码的拓展性;是多态的前提。 3. 多态 在面向对象中,多态是指通过基类的指针或者引用,在运行时动态调用实际绑定对象函数的行为。多态是在程序运行时根据基类的引用(指针)指向的对象来确定自己具体该调用哪一个类的虚函数。当父类指针(引用)指向 父类对象时,就调用父类中定义的虚函数;即当父类指针(引用)指向 子类对象时,就调用子类中定义的虚函数。多态性改善了代码的可读性和组织性,同时也使创建的程序具有可扩展性。
5.简述一下浅拷贝和深拷贝
得分点 浅拷贝的问题、深拷贝的实现 标准回答 浅拷贝和深拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是“引用”。浅拷贝和深拷贝一般在拷贝构造函数和赋值运算符重载函数中涉及到。
1. 浅拷贝 浅拷贝又称为值拷贝,将源对象的值拷贝到目标对象中,如果对象中有某个成员是指针类型数据,并且是在堆区创建,则使用浅拷贝仅仅拷贝的是这个指针变量的值,也就是在目标对象中该指针类型数据和源对象中的该成员指向的是同一块堆空间。这样会带来一个问题,就是在析构函数中释放该堆区数据,会被释放多次。默认的拷贝构造函数和默认的赋值运算符重载函数都是浅拷贝。
2. 深拷贝 深拷贝在拷贝的时候先开辟出和源对象大小一样的空间,然后将源对象里的内容拷贝到目标对象中去,这样指针成员就指向了不同的内存位置。并且里面的内容是一样的,这样不但达到了拷贝的目的,还不会出现问题,两个对象先后去调用析构函数,分别释放自己指针成员所指向的内存。即为每次增加一个指针,便申请一块新的内存,并让这个指针指向新的内存,深拷贝情况下,不会出现重复释放同一块内存的错误。
6.简述一下 C++ 中的多态
得分点 静态多态、动态多态、多态的实现原理、虚函数、虚函数表 标准回答 在现实生活中,多态是同一个事物在不同场景下的多种形态。在面向对象中,多态是指通过基类的指针或者引用,在运行时动态调用实际绑定对象函数的行为,与之相对应的编译时绑定函数称为静态绑定。所以多态分为静态多态和动态多态。 1. 静态多态 静态多态是编译器在编译期间完成的,编译器会根据实参类型来选择调用合适的函数,如果有合适的函数就调用,没有的话就会发出警告或者报错。静态多态有函数重载、运算符重载、泛型编程等。 2. 动态多态 动态多态是在程序运行时根据基类的引用(指针)指向的对象来确定自己具体该调用哪一个类的虚函数。当父类指针(引用)指向 父类对象时,就调用父类中定义的虚函数;即当父类指针(引用)指向 子类对象时,就调用子类中定义的虚函数。 加分回答 1. 动态多态行为的表现效果为:同样的调用语句在实际运行时有多种不同的表现形态。 2. 实现动态多态的条件: - 要有继承关系 - 要有虚函数重写(被 virtual 声明的函数叫虚函数) - 要有父类指针(父类引用)指向子类对象 3. 动态多态的实现原理 当类中声明虚函数时,编译器会在类中生成一个虚函数表,虚函数表是一个存储类虚函数指针的数据结构, 虚函数表是由编译器自动生成与维护的。virtual 成员函数会被编译器放入虚函数表中,存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr 指针)。在多态调用时, vptr 指针就会根据这个对象在对应类的虚函数表中查找被调用的函数,从而找到函数的入口地址。
7.简述一下虚函数的实现原理
得分点 多态、虚函数表、虚函数表指针 标准回答 1. 虚函数的作用 C++ 中的虚函数的作用主要是实现了动态多态的机制。动态多态,简单的说就是用父类型的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。 2. 虚函数实现原理 编译器处理虚函数时,给每个对象添加一个隐藏的成员。隐藏的成员是一个指针类型的数据,指向的是函数地址数组,这个数组被称为虚函数表(virtual function table,vtbl)。虚函数表中存储的是类中的虚函数的地址。如果派生类重写了基类中的虚函数,则派生类对象的虚函数表中保存的是派生类的虚函数地址,如果派生类没有重写基类中的虚函数,则派生类对象的虚函数表中保存的是父类的虚函数地址。 加分回答 使用虚函数时,对于内存和执行速度方面会有一定的成本: 1. 每个对象都会变大,变大的量为存储虚函数表指针; 2. 对于每个类,编译器都会创建一个虚函数表; 3. 对于每次调用虚函数,都需要额外执行一个操作,就是到表中查找虚函数地址。
8.什么是纯虚函数,有什么作用
得分点 概念、格式、特点、抽象类 标准回答 1. 概念、格式 纯虚函数是一种特殊的虚函数,它的格式是:虚函数不给出具体的实现,也就是后面没有大括号实现体,而在后面加上 "=0" class 类名 { virtual 返回值类型 函数名(参数列表) = 0; } 2. 作用 很多情况下,在基类中不能对虚函数给出具体的有意义的实现,就可以把它声明为纯虚函数,它的实现留给该基类的派生类去做。例如猫类和狗类的基类是动物类,动物类中有一个吃饭的函数 eat(),那这个 eat() 函数可以是纯虚函数,因为并不能够确定动物吃的东西是什么,具体吃的内容由不同的派生类去实现。 3. 特点 如果一个类中有纯虚函数,那么这个类也被称为抽象类。这种类不能实例化对象,也就是不能创建该类的对象。除非在派生类中完全实现基类中所有的纯虚函数,否则派生类也是抽象类,不能实例化对象。
9.虚析构函数有什么作用
得分点 概念、防止内存泄露 标准回答 1. 概念 虚析构函数,是将基类的析构函数声明为 virtual class Base { public: Base() { } // 虚析构函数 virtual ~Base() { } } 2. 作用 虚析构函数的主要作用是为了防止遗漏资源的释放,防止内存泄露。如果基类中的析构函数没有声明为虚函数,基类指针指向派生类对象时,则当基类指针释放时不会调用派生类对象的析构函数,而是调用基类的析构函数,如果派生类析构函数中做了某些释放资源的操作,则这时就会造成内存泄露。
10.请你说说重载,复写,隐藏的区别
得分点
定义、作用域、有无 virtual、函数名、形参列表、返回值类型
标准回答
重载、重写、隐藏在定义、作用域、有无 virtual、函数名、形参列表、返回值类型等方面有区别。
1. 重载:在同一作用域中,同名函数的形式参数(参数个数、类型或者顺序)不同时,构成函数重载,与返回值类型无关。
2. 重写:指不同作用域中定义的同名函数构成隐藏(不要求函数返回值和函数参数类型相同)。比如派生类成员函数隐藏与其同名的基类成员函数、类成员函数隐藏全局外部函数。
隐藏的实质是:在函数查找时,名字查找先于类型检查。如果派生类中成员和基类中的成员同名,就隐藏掉。编译器首先在相应作用域中查找函数,如果找到名字一样的则停止查找。3. 派生类中与基类同返回值类型、同名和同参数的虚函数重定义,构成虚函数覆盖,也叫虚函数重写。