c++工程师面试题

去年3月初开始准备跳槽,面了一个多俩星期,最终定下来了现在这家。
面试时把问我的面试题做了整理和复盘,闲来无事分享一下,给各位提供参考吧~

1. strlen 和 sizeof 的区别?

sizeof 是运算符,strlen 是函数;
sizeof 编译时计算数值,strlen 运行时计算;
sizeof 参数可以为数组、类型、指针等,strlen 参数必须是 char 类型指针;
sizeof 计算占用空间,strlen 返回字符长度。

2. sizeof(“qwer”)的写法对吗?

可以这么写,这是计算常量字符串的空间。

3. 智能指针作用

智能指针用于管理堆上分配的内存,它是一个类,它将普通指针封装为一个栈对象,栈对
象的生命周期结束后,会在析构中释放申请的内存,从而防止内存泄漏。
常用智能指针 shared_ptr,采用引用计数方法,记录当前内存内存资源被多少个智能指针引
用。该引用计数的内存在堆上分配,新增一个时引用加一,过期时减一,为 0 时自动释放引
用的内存资源。

4.回调函数

回调函数就是一个被作为参数传递的函数。
函数 F1 调用函数 F2 的时候,函数 F1 通过参数给 函数 F2 传递了另外一个函数 F3 的指 针 , 在 函 数 F2 执 行 的 过 程 中 , 函 数 F2 调 用 了 函 数 F3 , 这 个 动 作 就 叫 做 回 调(Callback),而先被当做指针传入、后面又被回调的函数 F3 就是回调函数。

5. 内联函数

使用 inline 关键字修饰的函数,特点:

  1. 编译器会将函数调用直接展开为函数体代码,即在编译时,在函数中调用的内联函数会
    直接展开成内联函数中的代码(应该是拷贝到使用者函数中),类似宏替换。将会增大代
    码体积。
  2. 为什么要使用内联函数?
    直接调用函数时,将会开辟栈空间、回收栈空间,产生内存开销。若使用内联函数,在编译时将直接展开使用函数代码,就减少内存分配、回收操作,执行率将会变高,能够减少函数调用的开销。
  3. 什么情况下使用内联函数?
    1.被调用的函数代码体积小;
    2.被调用频繁的函数。
  4. 内联说明对于编译器来说只是一个建议,编译器可以选择忽略。递归函数、大函数不太可能在调用点内联展开。

6. 递归函数

一个函数在它的函数体内调用它自身,这种调用过程称为递归,这种函数称为递归函数。

7. 单例模式

单例 Singleton 是设计模式的一种,其特点是只提供唯一一个类的实例,具有全局变量的特
点,在任何位置都可以通过接口获取到那个唯一实例。

具体运用场景如:

  1. 设备管理器,系统中可能有多个设备,但是只有一个设备管理器,用于管理设备驱动;
  2. 数据池,用来缓存数据的数据结构,需要在一处写,多处读取或者多处写,多处读取;

特点:

  1. 类构造器私有;
  2. 持有自己类型的属性;
  3. 对外提供获取实例的静态方法(获取自身的指针)。
    这个类需要有:
  4. 构造函数私有;
  5. 静态的指向自己的指针(在全局初始化);
  6. 析构函数函数公有;
  7. 对外界提供一个静态的获取自己对象指针的方法(返回一个 new 好的自己本身的指针)。

8. 内存分配

  1. 静态存储区域分配
    内存在程序编译时分配好的,这块内存在程序整个运行期间都存在。如全局变量、static 静态变量。
  2. 栈上分配
    函数执行时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动释放。栈内存分配运算内置于处理器的指令集中,效率高,但分配的内存容量有限。
  3. 堆上分配(动态分配内存)
    程序在运行的时候用 malloc 或 new 申请任意多少的内存,我们自己决定在什么时候用 free 或 delete 释放内存,其生存周期由我们决定,使用很灵活。

9. static 数据的作用范围

在静态存储区分配的,生命周期是程序的整个运行期间。

10. new 开辟空间是按照什么?

这个问题我没理解,我回答: new 是动态分配内存,分配在堆上,它分配的内存
大小应该是根据申请的数据类型的大小。

11. 栈空间可以定义多大?

进程的虚拟地址空间中,栈用于为函数开辟栈帧以及存放局部变量。
linux 下用 ulimit -a 可以查看栈空间大小,stack size 即栈空间大小。
linux 默认栈空间大小为8MB(好像是)。

12. 递归操作爆栈原因的理解

递归深度过大时,不断调用方法没有返回数据,每次调用都会将方法的临时变量封装为栈帧存入内存栈,等方法返回时才出栈,会出现一直入栈导致爆栈或内存溢出。
如果无法控制递归的深度,就要避免使用递归,可采用循环+栈结构代替递归。

13. 变量占几个字节和什么有关

跟计算机系统和编译器有关。
32 位和 64 位指的是计算机 cpu 中寄存器的长度,反映 cpu 一次可以处理的最大数据,它们
是决定 int 变量长度的一个重要因素。

14. linux 常用命令

略。

15. c++11 的特性

nullptr 关键字(不是智能指针,是空指针)用来区分空指针和 0
auto 关键字 进行类型推导,不能用于函数传参和推导数组类型
lambda 表达式
等等。。。

16. C++多态

通过派生类和虚函数实现。基类和派生类中使用同样的函数名,完成不同的操作。

延伸:
静态多态和动态多态的区别?
多态指的是派生类重写基类方法,然后让基类引用指向派生类对象,调用方法时候会进行动态绑定。多态可以分为静态多态和动态多态。

静态多态:在编译阶段就已经绑定了函数地址,主要体现是重载、模板。如:函数重载、模板函数。

  • 静态多态是通过函数重载或模板特化来实现的。
  • 在编译时确定调用哪个函数或模板的版本。
  • 编译器根据函数或模板的参数类型进行静态绑定(静态解析),决定使用哪个函数或模板。
  • 静态多态性在编译时确定,因此效率较高。

动态多态:利用虚函数实现,在运行期间绑定,主要体现是给父类指针传递不同的类型,调用的函数也会不同。如:虚函数、纯虚函数、虚函数重写(覆盖)

  • 动态多态是通过基类和派生类之间的继承关系以及虚函数的使用来实现的。
  • 在运行时确定调用哪个函数的版本。
  • 通过基类指针或引用调用虚函数,根据指针或引用指向的实际对象的类型,决定调用哪个派生类的方法。
  • 动态多态性在运行时确定,因此具有更大的灵活性。

17. 深拷贝与浅拷贝

若一个类拥有资源,当类的对象发生复制过程的时候,资源重新分配,这个过程是深拷贝;
没有重新分配资源就是浅拷贝。

18.进程间通信方式

管道(有名管道、无名管道。无名管道,亲缘可用。)、消息队列、信号量、共享内存

19.线程通信

c++11 的线程通信:条件变量、标志位

20. 线程的创建方式

当时使用qt开发,所以直接回答了 qt 里我知道两种,一种是继承 QThread 类,重写 run 函数处理;另一种定义一个 QThread对象,定义工作类对象,将工作类对象 线程类对象->movetoThread(工作类对象),使用信号槽的方式做处理。

21. 读写锁与互斥锁的区别?

互斥锁:mutex,用于保证在任何时刻,都只能有一个线程访问该对象。当获取锁操作失败
时,线程会进入睡眠,等待锁释放时被唤醒。
读写锁:rwlock,分为读锁和写锁。处于读操作时,可以允许多个线程同时获得读操作。但
是同一时刻只能有一个线程可以获得写锁。其它获取写锁失败的线程都会进入睡眠状态,直
到写锁释放时被唤醒。 注意:写锁会阻塞其它读写锁。当有一个线程获得写锁在写时,读
锁也不能被其它线程获取;写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优
先考虑写者)。适用于读取数据的频率远远大于写数据的频率的场合。
1)读写锁区分读者和写者,而互斥锁不区分
2)互斥锁同一时间只允许一个线程访问该对象,无论读写;读写锁同一时间内只允许一个
写者,但是允许多个读者同时读对象。
读写锁特点:
1)多个读者可以同时进行读
2)写者必须互斥(只允许一个写者写,也不能读者写者同时进行)
3)写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)
互斥锁特点:
一次只能一个线程拥有互斥锁,其他线程只有等待。

22. UDP 组播创建使用

略。

22. TCP 与 UDP 的区别

  1. TCP 面向连接(先建立连接 三次握手);UDP 是无连接的,送数据之前不需要建立连接。
  2. TCP 提供可靠的服务。也就是说,通过 TCP 连接传送的数据,无差错,不丢失,不重复,
    且按序到达;UDP 尽最大努力交付,即不保证可靠交付。
  3. TCP 面向字节流,实际上是 TCP 把数据看成一连串无结构的字节流;UDP 是面向报文的,UDP 没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如 IP 电话,实时视频会议等)。
  4. 每一条 TCP 连接只能是点到点的;UDP 支持一对一,一对多,多对一和多对多的交互通信。
  5. TCP 首部开销 20 字节;UDP 的首部开销小,只有 8 个字节。
  6. TCP 的逻辑通信信道是全双工的可靠信道,UDP 则是不可靠信道。

23. TCP 与 UDP 的优缺点

TCP 的优点:
可靠,稳定 TCP 的可靠体现在 TCP 在传递数据之前,会有三次握手来建立连
接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连
接用来节约系统资源。
TCP 的缺点:
慢,效率低,占用系统资源高,易被攻击 。TCP 在传递数据之前,要先建连
接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞控制机制等都会消耗大
量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的 CPU
、内存等硬件资源。 而且,因为 TCP 有确认机制、三次握手机制,这些也导致 TCP 容易被
人利用,实现 DOS、DDOS、CC 等攻击。

UDP 的优点:
快,比 TCP 稍安全,UDP 没有 TCP 的握手、确认、窗口、重传、拥塞控制等
机制,UDP 是一个无状态的传输协议,所以它在传递数据时非常快。没有 TCP 的这些机制,
UDP 较 TCP 被攻击者利用的漏洞就要少一些。但 UDP 也是无法避免攻击的,比如: UDP Flood
攻击……
UDP 的缺点:
不可靠,不稳定。因为 UDP 没有 TCP 那些可靠的机制,在数据传递时,如果
网络质量不好,就会很容易丢包。

24. TCP 与 UDP 的应用场景

什么时候应该使用 TCP:当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用,比如 HTTP、HTTPS、FTP 等传输文件的协议,POP、SMTP 等邮件传输的协议。 在日常生活中,常见使用 TCP 协议的应用如下: 浏览器,用的 HTTP FlashFXP,用的 FTP Outlook,用的 POP、SMTP Putty,用的 Telnet、SSH QQ 文件传输等。

什么时候应该使用 UDP: 当对网络通讯质量要求不高的时候,要求网络通讯速度能尽量的快,这时就可以使用 UDP。比如,日常生活中,常见使用 UDP 协议的应用如下: QQ 语音QQ 视频 TFTP ……有些应用场景对可靠性要求不高会用到 UPD,比如长视频,要求速率。

25. 什么是 tcp 粘包与 udp 丢包?

TCP 是面向流的, 流要说明就像河水一样, 只要有水, 就会一直流向低处, 不会间断。TCP 为了
提高传输效率, 发送数据的时候, 并不是直接发送数据到网路, 而是先暂存到系统缓冲, 超过时间或者缓冲满了, 才把缓冲区的内容发送出去, 这样就可以有效提高发送效率。所以会造成
所谓的粘包, 即前一份 Send 的数据跟后一份 Send 的数据可能会暂存到缓冲当中, 然后一起
发送。

UDP 就不同了, 面向报文形式, 系统是不会缓冲的, 也不会做优化的, Send 的时候就会直接
Send 到网络上, 对方收不收到也不管, 所以这块数据总是能够能一包一包的形式接收到, 而
不会出现前一个包跟后一个包都写到缓冲然后一起 Send。

26. STL 有哪些?

容器:vector、list、map、deque、string。。。
算法:find、swap。。。
迭代器:iterator。。。

27.常用的容器有哪些,底层实现是什么?

Vector 底层数据结构是数组(动态数组)支持快速随机访问
List 底层数据结构是环状双向链表,即存即取没效率
Map 底层数据结构是红黑树,有序 不重复
deque是一个双向队列(double-ended queue),可以在队列的两端进行元素的插入和删除操作

28. 容器的应用场景

vector 是可以快速地在最后添加删除元素,并可以快速地访问任意元素;
list 是可以快速地在所有地方添加删除元素,但是只能快速地访问最开始与最后的元素;
deque 在开始和最后添加元素都一样快,并提供了随机访问方法,像 vector 一样使用 [] 访问任意元素,但是随机访问速度比不上 vector 快,因为它要内部处理堆跳转。deque的特点是可以在队列的两端进行元素的操作,并且可以高效地在队列的任意位置进行元素的插入和删除操作。

  1. 如果你需要高效的随即存取,而不在乎插入和删除的效率,使用 vector;
  2. 如果你需要大量的插入和删除,而不关心随即存取,则应使用 list;
  3. 如果你需要随即存取,而且关心两端数据的插入和删除,则应使用 deque。

29.vector、list、map、deque 的区别

vector 底层是数组,list 是链表,map 是红黑树,deque是队列;
vector、deque支持随机访问,list 和 map 不支持;
vector、deque是顺序内存,list 和 map 不是;
由于内存空间,vector 在插入删除时会发生内存块拷贝,插入和删除没有 list 快,list 是通过指针访问的数据,能够高效插入和删除,但随机存取效率不高。
*vector、list 是在栈上分配还是在堆中分配?在堆上。
拓展:STL 算法有哪些

30. 数据库

基本的增删改查语句。

31. git 的基本操作

我理解应该大部分人都会。。。

33. 写 cmake、makefile

cmake 的编写格式

笔试部分

//写一个双向链表

struct node {
public:
	int value;
	node* prev;
	node* next;
	node(){}
	node(int val, node* p, node* n) {
		this->value = val;
		this->prev = p;
		this->next = n;
	}
};
class doublelink {
public:
	doublelink();
	~ doublelink();
	node* find(int index);
	void insert(int index, int val);
	void append(int val);
	void delete(int index);
	int count;
	node* phead;
}
doublelink::doublelink() {
	count = 0;
	phead = new node();
	phead->prev = phead->next = phead;
}
doublelink::~doublelink() {
	node* tmp = NULL;
	node* pnode = phead->next;
	while(pnode != phead) {
		tmp = pnode;
		pnode = phead->next;
		delete phone;
	}
	delete phead;
}
node* doublelink::find(int index) {
	if (index < 0 || index > count)
		return;
	if (index <= count/2) {
		int i = 0;
		node* pindex =phead->next;
		while (pindex != phead) {
			if(i++ < index)
				pindex = phead->next;
		}
		return pindex;
	}
	int i = 0;
	node* pindex = phead->prev;
	while (pindex != phead) {
		if (index <= count - index - 1)
			pindex = phead->prev;
	}
	return pindex;
}
void doublelink::insert(int index, int val) {
	node* pindex = find(index);
	node* pnode = new node(val, pindex->prev, pindex);
	pindex->prev->next = pnode;
	pindex->prev = pnode;
	count++;
}
void doublelink::append(int val) {
	node* pnode = new node(val, phead, phead->next);
	phead->next->prev = pnode;
	phead->next = pnode;
	count++;
}
void doublelink::delete(int index) {
	node* pindex = find(index);
	pindex->prev->next = pindex->next;
	pindex->next->prev = pindex->prev;
	delete pindex;
	count- -;
}
/*
移动数组
输入:有数值=非负数整数
将数组元素依次向右移动非负整数的位数
如数组为 1234567 非负整数为 3 输出为 5671234

思考:
数组 arry 的元素为{1,2,3,4,5,6,7},将其右移 k=3 位后得到{5,6,7,1,2,3,4}。
移动过程为{1,2,3,4,5,6,7}向右移动一位得到{7,1,2,3,4,5,6,7},向右移动两位得到{6,7,1,2,3,4,5},则向右移动三位得到{5,6,7,1,2,3,4}。
可以看做数组逆序来处理。数组长度为 n=7,循环右移 k 位,则数组中前 n-k 个元素会被移动,同时数组中最后 k 位的元素将会被移动到数组开始的位置。
所以可将原始数组分为两部分进行处理,先将数组中前 n-k 个元素进行逆序,之后再将最后的 k 个元素进行逆序,最后将整个数组再进行一次逆序即可。
*/

Widget::Widget(QWidget *parent):QWidget(parent) {
	int arry[] = {1,2,3,4,5,6,7};
	int len = sizeof(arry) / sizeof(arry[0]);
	for(int i = 0; i < len; i++)
		qDebug()<<"原数组:"<<arry[i];
	int k = 5;
	r_move(arry,0,len-k-1);//将数组中前 n-k 个元素进行逆序
	r_move(arry,len-k,len-1);//将最后的 k 个元素进行逆序
	r_move(arry,0,len-1);//将整个数组再进行一次逆序
	for(int i = 0; i < len; i++)
		qDebug()<<"移动后:"<<arry[i];
}
void Widget::r_move(int arry[], int n, int k) {
	while(n < k) {
		int temp = arry[n];
		arry[n] = arry[k];
		arry[k] = temp;n++;
		k--;
	}
}

笔试题可以多刷点力扣。。。
不一定全部都正确,可能有不足的地方,仅供参考哈~

你可能感兴趣的:(面试,c++,面试)