IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++校招常见问题汇总 -> 正文阅读

[C++知识库]C++校招常见问题汇总

文章目录

1. this指针干什么用的?

一个class可以定义很多对象,每个对象都有自己的成员变量以及成员方法,this指针在编译后作为成员方法的第一个参数

在成员方法里访问成员变量的时候,this指针用于区分访问哪个对象的成员变量

2. new和delete,什么时候用new[]申请,可以用delete释放?

new和delete是重载的运算符,实际上调用的是operator newoperator delete

如果是自定义类型,而且提供了析构函数,用new[]申请的内存空间就一定要用delete[]释放,因为new[]除了开辟用户需要使用的内存空间,还多开辟了4字节存放对象的个数。因为编译器知道自己总共分配了多少空间,记录对象的个数后,可以在内存中准确划分出每个对象的首地址。

如果没有提供析构函数, new[]开辟空间的起始地址和返回给用户的地址相同,也就是没有存放对象个数的4字节空间。

胡乱猜测:没有提供析构函数,delete不执行析构,分配多少空间就回收多少空间,不用4字节记录对象个数。

可参考:C++运算符重载

3. static关键字的作用(从elf结构、链接过程回答)?

从面向过程角度来说:

符号表中被static修饰的全局变量、函数就从global变成了local,也即从整个项目可见变成当前文件可见

static修饰的局部变量放在数据段(.data或.bss),由于是在数据段,程序一开始就要开辟空间,在执行到相应语句的时候初始化变量。局部变量本身不产生符号,通过ebp-偏移量进行访问,但是被static修饰后由于放在数据段,这时候就需要产生符号。

从面向对象角度来说:

static可以修饰成员变量,修饰成员方法,不再产生this指针,变成归类所有,通过类作用域调用

4. C++的继承有什么好处?

继承处于类和类之间的关系,好处如下:

  1. 代码复用
  2. 通过继承,在基类里面给所有派生类保留统一的纯虚函数接口,派生类进行重写。通过基类指针或者引用指向派生类对象实现多态(指针指向谁就访问谁的方法),也更符合软件开发的 “开闭原则”(只要是派生类,基类指针都可指向,不用修改)

可参考:C++的继承和多态

5. 讲一下C++的多态

分为静多态(编译时期)和动多态(运行时期)。

静多态的表现形式包括函数重载和类模板,在编译时期就要确定下来。

继承结构中,Base类指针(引用)指向Derive类对象,通过该指针(引用)调用同名覆盖方法(虚函数),该指针指向哪个Derive对象的覆盖对象,就调用哪个Derive类方法

可参考:C++的继承和多态

6. 空间配置器allocator?

allocator主要是给容器使用,作用是将开辟空间和构造对象分开,以及将回收空间和析构对象分开
对于容器而言,如果首先就要开辟空间,而不是放入对象,这个时候需要用到空间配置器。容器删除元素的时候,只是析构对象,而不是像delete一样析构对象且回收空间。

7. vector和list的区别?

vector底层是数组,内存连续。插入删除为O(n),随机访问O(1),尾部的插入删除都是O(1)

list底层是双向链表,内存不连续。中间增加删除O(n),首尾增加删除O(1),访问O(n)

可参考:C++STL总结

8. map和multimap的区别

map是一个映射表[key-value],不允许key重复,底层实现是红黑树(一种二叉排序树),主要是对key进行查找比较,快速找到key,时间复杂度为O(log2n)

multimap允许key重复,底层实现是红黑树

红黑树的5个性质:

  1. 每个节点要么是黑色,要么是红色。
  2. 根节点是黑色。
  3. 叶子节点是黑色。
  4. 每个红色节点的两个子节点一定都是黑色, 不能有两个红色节点相连。
  5. 任意一节点到每个叶子节点的路径都包含数量相同的黑色结点。

性质5又可以推出: 如果一个节点存在黑色子节点,那么该结点肯定有两个子节点,不然走另一条路就会少一层黑色结点。

红黑树插入有3种情况(最多旋转2次),删除有4种情况(最多旋转3次)

9. C++如何防止内存泄漏?智能指针详述

内存泄漏:分配的堆内存(没有名字,只能用指针指向)没有释放

智能指针体现在把裸指针进行了面向对象的封装,在构造函数中初始化资源地址,在析构函数中负责释放资源

利用栈上的对象出作用域自动析构这个特点,在智能指针的析构函数中保证释放资源。所以,智能指针一般都是定义在栈上

可参考:C++智能指针

10. C++如何调用C语言函数接口?

由于C和C++生成符号的方式不同,C和C++直接的API接口无法直接调用,C语言的函数声明必须在extern "C"

// 使用C语言的方式生成函数符号
extern "C" {
	// 函数符号function_C,而不是function_C_int
	int function_C();
}

可参考:C和C++的区别

11. 那些情况下可能出现访问越界

  1. 数组访问越界
  2. STL容器访问越界
  3. 字符串访问越界
  4. 字符串没有添加\0,导致访问越界
  5. 使用类型强转,使得大类型(派生类)的指针指向小类型(基类)的对象,指针解引用直接越界

12. C++中类的初始化列表

可以指定数据成员的初始化方式,尤其是指定成员对象的初始化(成员变量的初始化和定义的顺序有关,和初始化列表的顺序无关)

13. C和C++的区别以及内存分布

引用、函数重载、运算符重载、new/delete/malloc/free、const、inline、模板、OOP语言可使用设计模式、STL、异常处理、智能指针

在这里插入图片描述

可参考:C和C++的区别

14. int* const pconst int* p的区别

int* const p表示不可改变p指针的指向,const int* p表示不可修改p指针指向的值

可参考:C和C++的区别

15. malloc和new的区别

  1. malloc是按字节开辟空间的,new开辟内存时需要指定类型(new int()),malloc开辟内存返回的都是void*,而new返回的是对应类型的指针

  2. malloc负责开辟空间,new不仅有malloc的功能,还可以进行数据初始化,比如:new int(10)。new有开辟空间和构造的功能。

  3. malloc开辟内存失败返回nullptr,而new则会抛出bad_alloc异常

  4. 我们调用new实际上是调用的operator new

可参考:C++运算符重载

16. map和set的实现原理

set是集合,里面只存放key。map是映射表,存储[key,value]键值对。

底层数据结构都是红黑树,然后需要答一些红黑树的性质,相关算法

17. shared_ptr的引用计数在哪存放

存放在堆内存

可参考:C++智能指针的enable_shared_from_this和shared_from_this机制

18. STL各个容器底层数据结构?

顺序容器: vector(数组)、deque(二维数组)、list(双向循环链表)
容器适配器: stack(deque)、queue(deque)、priority_queue(vector)
关联容器: set系列(红黑树)、map系列(红黑树)

可参考:C++ STL总结

19. vector里empty()和size()的区别?

 _NODISCARD bool empty() const noexcept {
    auto& _My_data = _Mypair._Myval2;
    return _My_data._Myfirst == _My_data._Mylast;
}

_NODISCARD size_type size() const noexcept {
    auto& _My_data = _Mypair._Myval2;
    return static_cast<size_type>(_My_data._Mylast - _My_data._Myfirst);
}

20. 迭代器的实效问题

迭代器不允许一边读一边修改

当容器调用erase方法后,当前位置到容器末尾元素的所有迭代器全部失效(首元素到插入点的迭代器有效)

当容器调用insert方法后,当前位置到容器末尾元素的所有迭代器全部失效(首元素到插入点的迭代器有效)

insert来说,如果引起容器内存扩容,原来容器的所有的迭代器就全部失效

当通过迭代器更新容器元素以后,要及时对迭代器进行更新,insert/erase都会返回新位置的迭代器

可参考:C++运算符重载

21. C++中struct和class的区别

  1. 定义类的时候,struct的访问限定默认是public,class的访问限定默认是private
  2. 继承时,class Derive:Base表示私有继承,struct Derive:Base表示公有继承
  3. C语言里struct定义的空结构体是0字节,C++里struct定义的空类是1字节
  4. 为了兼容C语言里的struct,C++里struct定义的类对象初始化也可以用C语言中结构体的初始化方式,如:Data data = {10,20}
  5. C++支持template<class T>,不支持template<struct T>

22. 初始化的全局变量和未初始化的全局变量的区别

.data:存放初始化且初始化值不为0的数据
.bss:存放未初始化和初始化为0的数据

23. 堆和栈的区别

  1. 堆内存的大小 >> 栈内存的大小
  2. 堆内存需要手动开辟、释放,栈区开辟函数栈帧,自动清退
  3. 堆内存从低地址到高地址分配,栈从高地址(栈底)到低地址(栈顶)

24. 构造函数和析构函数可不可以是虚函数

构造函数不能是虚函数,析构函数可以是虚函数

构造函数不能实现成virtual,构造函数构造完成,对象才产生,才有vfptr,才能访问虚函数表。

如果构造一个派生类对象,派生类构造需要先构造基类,基类的构造是一个虚函数,此时发生动态绑定,需要访问派生类的前4个字节vfptr,然后这个时候派生类对象还没产生,访问出错。

基类的指针指向堆上的派生类对象时,delete ptr_base调用析构函数的时候,由于必须要调用到派生类对象的析构函数,所以必须是动态绑定,此时需要把Base的析构函数实现成virtual。

若是静态绑定,则直接根据指针的类型,调用析构函数,无法调用派生类的析构函数。

可参考:C++中的继承和多态总结

25. 构造函数和析构函数是否可以抛异常

主要考虑内存泄漏、资源释放的问题

构造函数不能抛出异常,若抛出异常则表示对象创建失败,不能调用析构函数,资源无法释放

析构函数只能在释放完所有资源后抛出异常

26. 宏和内联函数的区别

  1. #define在预编译时期处理,inline在编译时期处理
  2. #define就是字符串替换,release版本中inline在函数调用点把函数代码展开,节省函数的调用开销
  3. #define可以定义很多,比如常量、代码段、函数等,inline只能用于修饰函数
  4. #define不能调试,inline在Debug版本下可以调试,这时产生函数调用开销

27. 局部变量存放在哪

局部变量通过 栈底指针ebp偏移访问,存放在栈区

局部变量不产生符号,不属于数据,属于指令的一部分(若是在VS Debug版本下查看反汇编会发现依然有符号,这是VS优化后展示给用户的结果,实际上应该是ebp-偏移量

28. 拷贝构造函数为什么传引用&,不传值

class Test{
	// 会产生编译错误
	Test(const Test t){
		...
	}
};

int main(){
	Test t1;
	Test t2(t1);
	return 0;
}

这时用t1拷贝构造t2,如果传值,则需要用t1初始化形参t,这个时候也需要调用Test的拷贝构造函数(t1.Test(t)),而调用拷贝构造函数的时候,仍然需要实参初始化形参,再次调用拷贝构造函数,陷入了死循环。

而实际上,编译器也会检查,直接发生编译错误,无法运行

29. 内联函数和普通函数的区别(反汇编角度)

inline函数

  1. 编译期间在代码调用的地方展开(release版本),有逻辑性的进行文本替换,因此不产生函数符号
  2. 能够调试,在debug版本(需要调试)inline函数和普通的函数表现一致,只有在release版本才会真正在调用点展开
  3. 因为需要在编译期间展开,而编译期间针对的是单文件。所以inline函数的作用域只在本文件,debug版本生成local符号
  4. 有类型检测,安全
  5. 类体内实现的成员方法直接是inline

核心: 函数调用开销

开辟栈帧
在这里插入图片描述
栈帧清退
在这里插入图片描述

普通函数

  1. 不展开
  2. 能调试
  3. 作用域在全局,生成global符号
  4. 有类型检测,安全

可参考:C和C++的区别

30. 如何实现一个不可继承的类

派生类构造的过程:先是执行基类构造函数,然后执行派生类构造函数。

我们将基类的构造函数私有化,此时基类的构造函数对派生类是不可见的,所以无法继承。

31. 什么是纯虚函数?为什么要有纯虚函数?虚函数表放在哪?

virtual void fun() = 0;

用纯虚函数定义的类是抽象类,无法实例化对象,可以定义指针和引用

纯虚函数定义在基类里面,基类不代表实体,它的主要任务之一就是 给所有的派生类保留统一的纯虚函数接口,让派生类进行重写。重写以后就 可以使用多态,基类指针指向派生类对象,指向哪个对象就调用哪个对象的方法

虚函数表是在编译阶段产生,运行时加载到.rodata

可参考:C++继承和多态总结

32. 说一下C++中的const

编译时期值替换

const定义的叫常量,它的编译方式是把出现常量名字的地方,用常量的值进行替换

	const int a = 10;
	int* p = (int*)&a;
	*p = 100;
	cout<<a<<" "<<*p<<endl; // 10 100

常变量

若用立即数初始化,则编译阶段可以直接用立即数进行替换。
若用变量初始化,则退化为和C语言中一模一样的常变量(只是不能作为左值,编译方式同普通变量)

定义常成员方法
this指针由Test* this变成const Test* this,普通对象和常对象都可以调用

可参考:C和C++的区别

33. const与static的区别

面向过程

const修饰:全局变量、局部变量、形参变量
static修饰:全局变量、局部变量、函数(本文件可见)

面向过程

const
常方法(普通对象和常对象都可以调用,只能对成员进行读不能写)、常成员变量(不能被修改,必须在构造函数的初始化列表初始化)

static
静态方法:该方法没有this指针,不依赖于对象,通过类作用域访问
静态成员变量:静态成员变量属于类,不属于某个具体的对象,实现多个对象共享数据的目的。static 成员变量的内存既不是在声明类时分配,也不是在创建对象时分配,而是在(类外)初始化时分配。即没有在类外初始化的 static 成员变量不能使用。

34. 四种强制类型转换

const_cast:去掉常量属性

static_cast:类型安全的转换

reinterpret_cast:C风格的类型转换,没有安全可言,随意转换

dynamic_cast:支持RTTI识别的类型转换。基类指针转成相应的派生类对象指针的时候,dynamic_cast会识别该指针是否能够进行转换

35. deque底层数据结构

deque是双端队列容器,底层数据是动态扩容的二维数组。一维数组从2开始,以2倍的方式进行扩容,每次扩容后原来第二维的数组从新的第一维数组的下标oldsize/2开始存放,上下留出相同的空间,方便deque首尾入队。

由于是双端队列,所以最开始的时候,firstlast其实是指向相同地方的
在这里插入图片描述
扩容后:
在这里插入图片描述

36. 虚函数

一个类如果有虚函数表,那在编译阶段就会产生vfptr,指向虚函数表,该表运行时加载到.rodata

用指针或引用调用虚函数的时候,首先访问对象前4个字节vfptr,然后找到虚函数表取出函数入口地址,进行动态绑定

37. 多态

使用基类的指针或引用指向不同的派生类对象,指向哪个对象就调用哪个对象的方法。

增加新功能或删除已有功能的时候,函数接口不需要改变。不管基类有多少个派生类,接口一套就够了,不需要对每一个对象都设计一套接口。

OOP里面的设计模式离不开多态,达到高内聚、低耦合的目标

38. C++异常处理原理

try{
	可能抛出异常的代码
}catch(const string& error){
	捕获相应异常类型的对象,进行处理,处理完成后继续向下执行
}

C++中如果在当前函数栈帧上没有找到相应的catch块处理,就会把异常抛给调用方函数。处理了就继续运行,没处理就继续向调用方抛出,直到main函数还没有处理,就向系统抛出,系统发现进程有异常未处理,就直接终止进程。

39. 早绑定(静态)和晚绑定(动态)

早绑定:编译时期完成(汇编代码是call 函数名),用对象调用虚函数。
晚绑定:运行时确定(汇编代码是call 寄存器),用指针或引用调用虚函数。确定指针类中被调用函数的类型,是虚函数则找到对象前4个字节的vfptr,从vftable拿到函数入口地址

可参考:C++继承和多态总结

40. 指针和引用的区别(汇编角度)

lea是“load effective address”的缩写,简单的说,lea指令可以用来将一个内存地址直接赋给目的操作数,例如:lea eax,[ebx+8]就是将ebx+8这个值直接赋给eax,而不是把ebx+8处的内存地址里的数据赋给eax。而mov指令则恰恰相反,例如:mov eax,[ebx+8]则是把内存地址为ebx+8处的数据赋给eax。

可参考:C和C++的区别

41. 交叉引用的问题如何解决

交叉引用问题:无论是定义对象还是使用对象都用shared_ptr,导致交叉引用问题

定义对象时候使用强智能指针shared_ptr,引用的时候使用弱智能指针weak_ptr。当通过weak_ptr访问对象成员时,需要调用weak_ptr的lock提升方法,把weak_ptr提升成shared_ptr,然后再进行对象成员调用。

可参考:C++智能指针

42. C++函数重载的原理

C++生成函数符号是依赖函数名+参数列表

43. 编写一个C++程序需要注意什么

  1. 首先要分析需求,然后进行概要设计、详细设计。
  2. 考虑设计的函数、接口、类是否运行在多线程环境、考虑线程安全。
  3. 考虑代码的可移植性
  4. 使用现有的代码框架、设计模式等

44. 设计模式知道哪些?具体讲一下

可参考:C++设计模式总结

45. 构造函数抛出异常可能会导致内存泄漏,如何解决?

class Test{
	public:
		Test(){
			p1 = new int();
			p2 = new int();
			throw "xxxx"
		}
		~Test(){
			delete p1;
			delete p2;
		}
	private:
		int* p1;
		int* p2;
}

改写成:

class Test{
	public:
		Test(){
			p1 = new int();
			p2 = new int();
			throw "xxxx"
		}
		~Test(){ }
	private:
		unique_ptr<int> p1;
		unique_ptr<int> p2;
}
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-09-11 18:37:21  更:2021-09-11 18:37:24 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 20:33:40-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码