前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >常见c和cpp面试题目汇总(一)

常见c和cpp面试题目汇总(一)

作者头像
用户6280468
发布2022-03-21 18:50:48
1.2K0
发布2022-03-21 18:50:48
举报
文章被收录于专栏:txp玩Linuxtxp玩Linux

前言:

大家好,我是小涂,今天给大家分享一些常见面试题目!

一、C和C++的区别:

  • 1、C是面向过程的语言,是一个结构化的语言,考虑如何通过一个过程对输入进行处理得到输出;C++是面向对象的语言,主要特征是“封装、继承和多态”。封装隐藏了实现细节,使得代码模块化;派生类可以继承父类的数据和方法,扩展了已经存在的模块,实现了代码重用;多态则是“一个接口,多种实现”,通过派生类重写父类的虚函数,实现了接口的重用。
  • 2、C和C++动态管理内存的方法不一样,C是使用malloc/free,而C++除此之外还有new/delete关键字。
  • 3、C++支持函数重载,C不支持函数重载
  • 4、C++中有引用,C中不存在引用的概念

二、C++中指针和引用的区别:

  • 1、 指针是一个新的变量,存储了另一个变量的地址,我们可以通过访问这个地址来修改另一个变量;引用只是一个别名,还是变量本身,对引用的任何操作就是对变量本身进行操作,以达到修改变量的目的
  • 2、引用只有一级,而指针可以有多级
  • 3、指针传参的时候,还是值传递,指针本身的值不可以修改,需要通过解引用才能对指向的对象进行操作,引用传参的时候,传进来的就是变量本身,因此变量可以被修改

三、结构体struct和共同体union(联合)的区别:

  • 结构体:将不同类型的数据组合成一个整体,是自定义类型
  • 共同体:不同类型的几个变量共同占用一段内存
  • 结构体中的每个成员都有自己独立的地址,它们是同时存在的;共同体中的所有成员占用同一段内存,它们不能同时存在。
  • sizeof(struct)是内存对齐后所有成员长度的总和,sizeof(union)是内存对齐后最长数据成员的长度。

四、#define和const的区别:

  • 1、#define定义的常量没有类型,所给出的是一个立即数;const定义的常量有类型名字,存放在静态区域
  • 2、处理阶段不同,#define定义的宏变量在预处理时进行替换,可能有多个拷贝,const所定义的变量在编译时确定其值,只有一个拷贝。
  • 3、#define定义的常量是不可以用指针去指向,const定义的常量可以用指针去指向该常量的地址
  • 4、#define可以定义简单的函数,const不可以定义函数

五、重载overload,覆盖override,重写overwrite,这三者之间的区别:

  • overload,将语义相近的几个函数用同一个名字表示,但是参数和返回值不同,这就是函数重载;特征:相同范围(同一个类中)、函数名字相同、参数不同、virtual关键字可有可无
  • override,派生类覆盖基类的虚函数,实现接口的重用;特征:不同范围(基类和派生类)、函数名字相同、参数相同、基类中必须有virtual关键字(必须是虚函数)
  • overwrite,派生类屏蔽了其同名的基类函数;特征:不同范围(基类和派生类)、函数名字相同、参数不同或者参数相同且无virtual关键字

六、new、delete、malloc、free之间的关系:

new/delete,malloc/free都是动态分配内存的方式

  • 1、malloc对开辟的空间大小严格指定,而new只需要对象名
  • 2、new为对象分配空间时,调用对象的构造函数,delete调用对象的析构函数
  • 3、 既然有了malloc/free,C++中为什么还需要new/delete呢?因为malloc/free是库函数而不是运算符,不能把执行构造函数和析构函数的功能强加于malloc/free

七、delete和delete[]的区别:

  • delete只会调用一次析构函数,而delete[]会调用每个成员的析构函数
  • 用new分配的内存用delete释放,用new[]分配的内存用delete[]释放

八、STL库用过吗?常见的STL容器有哪些?算法用过几个?

STL包括两部分内容:容器和算法;容器即存放数据的地方,比如array, vector,分为两类,序列式容器和关联式容器:

  • 序列式容器,其中的元素不一定有序,但是都可以被排序,比如vector,list,queue,stack,heap, priority-queue, slist
  • 关联式容器,内部结构是一个平衡二叉树,每个元素都有一个键值和一个实值,比如map, set, hashtable, hash_set

算法有排序,复制等,以及各个容器特定的算法;迭代器是STL的精髓,迭代器提供了一种方法,使得它能够按照顺序访问某个容器所含的各个元素,但无需暴露该容器的内部结构,它将容器和算法分开,让二者独立设计。

九、虚函数是怎么实现的:

每一个含有虚函数的类都至少有有一个与之对应的虚函数表,其中存放着该类所有虚函数对应的函数指针(地址);类的示例对象不包含虚函数表,只有虚指针;派生类会生成一个兼容基类的虚函数表。

十、STL中的vector的实现,是怎么扩容的?

vector使用的注意点及其原因,频繁对vector调用push_back()对性能的影响和原因。

vector就是一个动态增长的数组,里面有一个指针指向一片连续的空间,当空间装不下的时候,会申请一片更大的空间,将原来的数据拷贝过去,并释放原来的旧空间。当删除的时候空间并不会被释放,只是清空了里面的数据。对比array是静态空间一旦配置了就不能改变大小。

vector的动态增加大小的时候,并不是在原有的空间上持续新的空间(无法保证原空间的后面还有可供配置的空间),而是以原大小的两倍另外配置一块较大的空间,然后将原内容拷贝过来,并释放原空间。在VS下是1.5倍扩容,在GCC下是2倍扩容。

在原来空间不够存储新值时,每次调用push_back方法都会重新分配新的空间以满足新数据的添加操作。如果在程序中频繁进行这种操作,还是比较消耗性能的。

十一、STL中map和set的原理(关联式容器):

map和set的底层实现主要通过红黑树来实现

红黑树是一种特殊的二叉查找树:

  • 每个节点或者是黑色,或者是红色
  • 根节点是黑色
  • 每个叶子节点(NIL)是黑色。[注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
  • 如果一个节点是红色的,则它的子节点必须是黑色的
  • 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

十二、STL中unordered_map和map的区别:

map是STL中的一个关联容器,提供键值对的数据管理。底层通过红黑树来实现,实际上是二叉排序树和非严格意义上的二叉平衡树。所以在map内部所有的数据都是有序的,且map的查询、插入、删除操作的时间复杂度都是O(logN)。

unordered_map和map类似,都是存储key-value对,可以通过key快速索引到value,不同的是unordered_map不会根据key进行排序。unordered_map底层是一个防冗余的哈希表,存储时根据key的hash值判断元素是否相同,即unoredered_map内部是无序的。

十三、 构造函数为什么一般不定义为虚函数?而析构函数一般写成虚函数的原因 ?

1、构造函数不能声明为虚函数

  • 1)因为创建一个对象时需要确定对象的类型,而虚函数是在运行时确定其类型的。而在构造一个对象时,由于对象还未创建成功,编译器无法知道对象的实际类型,是类本身还是类的派生类等等
  • 2)虚函数的调用需要虚函数表指针,而该指针存放在对象的内存空间中;若构造函数声明为虚函数,那么由于对象还未创建,还没有内存空间,更没有虚函数表地址用来调用虚函数即构造函数了

2、析构函数最好声明为虚函数

  • 首先析构函数可以为虚函数,当析构一个指向派生类的基类指针时,最好将基类的析构函数声明为虚函数,否则可以存在内存泄露的问题。
  • 如果析构函数不被声明成虚函数,则编译器实施静态绑定,在删除指向派生类的基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会造成派生类对象析构不完全。

十四、静态绑定和动态绑定的介绍:

静态绑定和动态绑定是C++多态性的一种特性

1)对象的静态类型和动态类型

静态类型:对象在声明时采用的类型,在编译时确定

动态类型:当前对象所指的类型,在运行期决定,对象的动态类型可变,静态类型无法更改

2)静态绑定和动态绑定

静态绑定:绑定的是对象的静态类型,函数依赖于对象的静态类型,在编译期确定

动态绑定:绑定的是对象的动态类型,函数依赖于对象的动态类型,在运行期确定

只有虚函数才使用的是动态绑定,其他的全部是静态绑定

十五、引用是否能实现动态绑定,为什么引用可以实现:

可以。因为引用(或指针)既可以指向基类对象也可以指向派生类对象,这一事实是动态绑定的关键。用引用(或指针)调用的虚函数在运行时确定,被调用的函数是引用(或指针)所指的对象的实际类型所定义的。

十六、深拷贝和浅拷贝的区别:

深拷贝和浅拷贝可以简单的理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,如果资源重新分配了就是深拷贝;反之没有重新分配资源,就是浅拷贝。

十七、 什么情况下会调用拷贝构造函数(三种情况) :

系统自动生成的构造函数:普通构造函数和拷贝构造函数 (在没有定义对应的构造函数的时候)

生成一个实例化的对象会调用一次普通构造函数,而用一个对象去实例化一个新的对象所调用的就是拷贝构造函数

调用拷贝构造函数的情形:

  • 1)用类的一个对象去初始化另一个对象的时候
  • 2)当函数的参数是类的对象时,就是值传递的时候,如果是引用传递则不会调用
  • 3)当函数的返回值是类的对象或者引用的时候

举例:

代码语言:javascript
复制
#include <iostream>
#include <string>
 
using namespace std;
 
class A{
 private:
  int data;
 public:
  A(int i){ data = i;}  //自定义的构造函数
  A(A && a);     //拷贝构造函数 
  int getdata(){return data;} 
};
//拷贝构造函数 
A::A(A && a){
 data = a.data;
 cout <<"拷贝构造函数执行完毕"<<endl;
}
//参数是对象,值传递,调用拷贝构造函数
int getdata1(A a){
 return a.getdata();
}
//参数是引用,引用传递,不调用拷贝构造函数 
int getdata2(A &a){
 return a.getdata();
} 
//返回值是对象类型,会调用拷贝构造函数
 A getA1(){
  A a(0);
  return a;
 } 
 //返回值是引用类型,会调用拷贝构造函数,因为函数体内生成的对象是临时的,离开函数就消失
 A& getA2(){
  A a(0);
  return a;
 } 
 
 int main(){
    A a1(1);  
    A b1(a1);             //用a1初始化b1,调用拷贝构造函数  
    A c1=a1;              //用a1初始化c1,调用拷贝构造函数  
  
    int i=getdata1(a1);         //函数形参是类的对象,调用拷贝构造函数  
    int j=getdata2(a1);       //函数形参类型是引用,不调用拷贝构造函数  
  
    A d1=getA1();         //调用拷贝构造函数  
    A e1=getA2();        //调用拷贝构造函数  
  
    return 0;  
}  

十八、 C++的四种强制转换 :

类型转化机制可以分为隐式类型转换和显示类型转化(强制类型转换)

(new-type) expression new-type (expression) 隐式类型转换比较常见,在混合类型表达式中经常发生;四种强制类型转换操作符:

static_cast、dynamic_cast、const_cast、reinterpret_cast

  • 1)static_cast :编译时期的静态类型检查

static_cast < type-id > ( expression )

该运算符把expression转换成type-id类型,在编译时使用类型信息执行转换,在转换时执行必要的检测(指针越界、类型检查),其操作数相对是安全的

  • 2)dynamic_cast:运行时的检查

用于在集成体系中进行安全的向下转换downcast,即基类指针/引用->派生类指针/引用

dynamic_cast是4个转换中唯一的RTTI操作符,提供运行时类型检查。

dynamic_cast如果不能转换返回NULL

源类中必须要有虚函数,保证多态,才能使用dynamic_cast(expression)

  • 3)const_cast

去除const常量属性,使其可以修改 ; volatile属性的转换

  • 4)reinterpret_cast

通常为了将一种数据类型转换成另一种数据类型

十九、引用作为函数参数以及返回值的好处:

对比值传递,引用传参的好处:

  • 1)在函数内部可以对此参数进行修改
  • 2)提高函数调用和运行的效率(所以没有了传值和生成副本的时间和空间消耗)

如果函数的参数实质就是形参,不过这个形参的作用域只是在函数体内部,也就是说实参和形参是两个不同的东西,要想形参代替实参,肯定有一个值的传递。函数调用时,值的传递机制是通过“形参=实参”来对形参赋值达到传值目的,产生了一个实参的副本。即使函数内部有对参数的修改,也只是针对形参,也就是那个副本,实参不会有任何更改。函数一旦结束,形参生命也宣告终结,做出的修改一样没对任何变量产生影响。

用引用作为返回值最大的好处就是在内存中不产生被返回值的副本。

但是有以下的限制:

  • 1)不能返回局部变量的引用。因为函数返回以后局部变量就会被销毁
  • 2)不能返回函数内部new分配的内存的引用。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一 个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak
  • 3)可以返回类成员的引用,但是最好是const。因为如果其他对象可以获得该属性的非常量的引用,那么对该属性的单纯赋值就会破坏业务规则的完整性。

二十、纯虚函数:

纯虚函数是只有声明没有实现的虚函数,是对子类的约束,是接口继承

包含纯虚函数的类是抽象类,它不能被实例化,只有实现了这个纯虚函数的子类才能生成对象

普通函数是静态编译的,没有运行时多态

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-12-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 txp玩Linux 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言:
  • 一、C和C++的区别:
  • 二、C++中指针和引用的区别:
  • 三、结构体struct和共同体union(联合)的区别:
  • 四、#define和const的区别:
  • 五、重载overload,覆盖override,重写overwrite,这三者之间的区别:
  • 六、new、delete、malloc、free之间的关系:
  • 七、delete和delete[]的区别:
  • 八、STL库用过吗?常见的STL容器有哪些?算法用过几个?
  • 九、虚函数是怎么实现的:
  • 十、STL中的vector的实现,是怎么扩容的?
  • 十一、STL中map和set的原理(关联式容器):
  • 十二、STL中unordered_map和map的区别:
  • 十三、 构造函数为什么一般不定义为虚函数?而析构函数一般写成虚函数的原因 ?
  • 十四、静态绑定和动态绑定的介绍:
  • 十五、引用是否能实现动态绑定,为什么引用可以实现:
  • 十六、深拷贝和浅拷贝的区别:
  • 十七、 什么情况下会调用拷贝构造函数(三种情况) :
  • 十八、 C++的四种强制转换 :
  • 十九、引用作为函数参数以及返回值的好处:
  • 二十、纯虚函数:
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档