去年3月初开始准备跳槽,面了一个多俩星期,最终定下来了现在这家。
面试时把问我的面试题做了整理和复盘,闲来无事分享一下,给各位提供参考吧~
sizeof 是运算符,strlen 是函数;
sizeof 编译时计算数值,strlen 运行时计算;
sizeof 参数可以为数组、类型、指针等,strlen 参数必须是 char 类型指针;
sizeof 计算占用空间,strlen 返回字符长度。
可以这么写,这是计算常量字符串的空间。
智能指针用于管理堆上分配的内存,它是一个类,它将普通指针封装为一个栈对象,栈对
象的生命周期结束后,会在析构中释放申请的内存,从而防止内存泄漏。
常用智能指针 shared_ptr,采用引用计数方法,记录当前内存内存资源被多少个智能指针引
用。该引用计数的内存在堆上分配,新增一个时引用加一,过期时减一,为 0 时自动释放引
用的内存资源。
回调函数就是一个被作为参数传递的函数。
函数 F1 调用函数 F2 的时候,函数 F1 通过参数给 函数 F2 传递了另外一个函数 F3 的指 针 , 在 函 数 F2 执 行 的 过 程 中 , 函 数 F2 调 用 了 函 数 F3 , 这 个 动 作 就 叫 做 回 调(Callback),而先被当做指针传入、后面又被回调的函数 F3 就是回调函数。
使用 inline 关键字修饰的函数,特点:
一个函数在它的函数体内调用它自身,这种调用过程称为递归,这种函数称为递归函数。
单例 Singleton 是设计模式的一种,其特点是只提供唯一一个类的实例,具有全局变量的特
点,在任何位置都可以通过接口获取到那个唯一实例。
具体运用场景如:
特点:
在静态存储区分配的,生命周期是程序的整个运行期间。
这个问题我没理解,我回答: new 是动态分配内存,分配在堆上,它分配的内存
大小应该是根据申请的数据类型的大小。
进程的虚拟地址空间中,栈用于为函数开辟栈帧以及存放局部变量。
linux 下用 ulimit -a 可以查看栈空间大小,stack size 即栈空间大小。
linux 默认栈空间大小为8MB(好像是)。
递归深度过大时,不断调用方法没有返回数据,每次调用都会将方法的临时变量封装为栈帧存入内存栈,等方法返回时才出栈,会出现一直入栈导致爆栈或内存溢出。
如果无法控制递归的深度,就要避免使用递归,可采用循环+栈结构代替递归。
跟计算机系统和编译器有关。
32 位和 64 位指的是计算机 cpu 中寄存器的长度,反映 cpu 一次可以处理的最大数据,它们
是决定 int 变量长度的一个重要因素。
略。
nullptr 关键字(不是智能指针,是空指针)用来区分空指针和 0
auto 关键字 进行类型推导,不能用于函数传参和推导数组类型
lambda 表达式
等等。。。
通过派生类和虚函数实现。基类和派生类中使用同样的函数名,完成不同的操作。
延伸:
静态多态和动态多态的区别?
多态指的是派生类重写基类方法,然后让基类引用指向派生类对象,调用方法时候会进行动态绑定。多态可以分为静态多态和动态多态。
静态多态:在编译阶段就已经绑定了函数地址,主要体现是重载、模板。如:函数重载、模板函数。
- 静态多态是通过函数重载或模板特化来实现的。
- 在编译时确定调用哪个函数或模板的版本。
- 编译器根据函数或模板的参数类型进行静态绑定(静态解析),决定使用哪个函数或模板。
- 静态多态性在编译时确定,因此效率较高。
动态多态:利用虚函数实现,在运行期间绑定,主要体现是给父类指针传递不同的类型,调用的函数也会不同。如:虚函数、纯虚函数、虚函数重写(覆盖)
- 动态多态是通过基类和派生类之间的继承关系以及虚函数的使用来实现的。
- 在运行时确定调用哪个函数的版本。
- 通过基类指针或引用调用虚函数,根据指针或引用指向的实际对象的类型,决定调用哪个派生类的方法。
- 动态多态性在运行时确定,因此具有更大的灵活性。
若一个类拥有资源,当类的对象发生复制过程的时候,资源重新分配,这个过程是深拷贝;
没有重新分配资源就是浅拷贝。
管道(有名管道、无名管道。无名管道,亲缘可用。)、消息队列、信号量、共享内存
c++11 的线程通信:条件变量、标志位
当时使用qt开发,所以直接回答了 qt 里我知道两种,一种是继承 QThread 类,重写 run 函数处理;另一种定义一个 QThread对象,定义工作类对象,将工作类对象 线程类对象->movetoThread(工作类对象),使用信号槽的方式做处理。
互斥锁:mutex,用于保证在任何时刻,都只能有一个线程访问该对象。当获取锁操作失败
时,线程会进入睡眠,等待锁释放时被唤醒。
读写锁:rwlock,分为读锁和写锁。处于读操作时,可以允许多个线程同时获得读操作。但
是同一时刻只能有一个线程可以获得写锁。其它获取写锁失败的线程都会进入睡眠状态,直
到写锁释放时被唤醒。 注意:写锁会阻塞其它读写锁。当有一个线程获得写锁在写时,读
锁也不能被其它线程获取;写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优
先考虑写者)。适用于读取数据的频率远远大于写数据的频率的场合。
1)读写锁区分读者和写者,而互斥锁不区分
2)互斥锁同一时间只允许一个线程访问该对象,无论读写;读写锁同一时间内只允许一个
写者,但是允许多个读者同时读对象。
读写锁特点:
1)多个读者可以同时进行读
2)写者必须互斥(只允许一个写者写,也不能读者写者同时进行)
3)写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)
互斥锁特点:
一次只能一个线程拥有互斥锁,其他线程只有等待。
略。
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 那些可靠的机制,在数据传递时,如果
网络质量不好,就会很容易丢包。
什么时候应该使用 TCP:当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用,比如 HTTP、HTTPS、FTP 等传输文件的协议,POP、SMTP 等邮件传输的协议。 在日常生活中,常见使用 TCP 协议的应用如下: 浏览器,用的 HTTP FlashFXP,用的 FTP Outlook,用的 POP、SMTP Putty,用的 Telnet、SSH QQ 文件传输等。
什么时候应该使用 UDP: 当对网络通讯质量要求不高的时候,要求网络通讯速度能尽量的快,这时就可以使用 UDP。比如,日常生活中,常见使用 UDP 协议的应用如下: QQ 语音QQ 视频 TFTP ……有些应用场景对可靠性要求不高会用到 UPD,比如长视频,要求速率。
TCP 是面向流的, 流要说明就像河水一样, 只要有水, 就会一直流向低处, 不会间断。TCP 为了
提高传输效率, 发送数据的时候, 并不是直接发送数据到网路, 而是先暂存到系统缓冲, 超过时间或者缓冲满了, 才把缓冲区的内容发送出去, 这样就可以有效提高发送效率。所以会造成
所谓的粘包, 即前一份 Send 的数据跟后一份 Send 的数据可能会暂存到缓冲当中, 然后一起
发送。
UDP 就不同了, 面向报文形式, 系统是不会缓冲的, 也不会做优化的, Send 的时候就会直接
Send 到网络上, 对方收不收到也不管, 所以这块数据总是能够能一包一包的形式接收到, 而
不会出现前一个包跟后一个包都写到缓冲然后一起 Send。
容器:vector、list、map、deque、string。。。
算法:find、swap。。。
迭代器:iterator。。。
Vector 底层数据结构是数组(动态数组)支持快速随机访问
List 底层数据结构是环状双向链表,即存即取没效率
Map 底层数据结构是红黑树,有序 不重复
deque是一个双向队列(double-ended queue),可以在队列的两端进行元素的插入和删除操作
vector 是可以快速地在最后添加删除元素,并可以快速地访问任意元素;
list 是可以快速地在所有地方添加删除元素,但是只能快速地访问最开始与最后的元素;
deque 在开始和最后添加元素都一样快,并提供了随机访问方法,像 vector 一样使用 [] 访问任意元素,但是随机访问速度比不上 vector 快,因为它要内部处理堆跳转。deque的特点是可以在队列的两端进行元素的操作,并且可以高效地在队列的任意位置进行元素的插入和删除操作。
vector 底层是数组,list 是链表,map 是红黑树,deque是队列;
vector、deque支持随机访问,list 和 map 不支持;
vector、deque是顺序内存,list 和 map 不是;
由于内存空间,vector 在插入删除时会发生内存块拷贝,插入和删除没有 list 快,list 是通过指针访问的数据,能够高效插入和删除,但随机存取效率不高。
*vector、list 是在栈上分配还是在堆中分配?在堆上。
拓展:STL 算法有哪些
基本的增删改查语句。
我理解应该大部分人都会。。。
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--;
}
}
笔试题可以多刷点力扣。。。
不一定全部都正确,可能有不足的地方,仅供参考哈~