Linux C++岗位笔试题之一
- 简答
(1) 简述面向对象的三个基本特征,各自是如何实现的;
封装、继承、多态
封装:
通过C++提供的关键这public/private/protected,用于声明哪些函数和数据是可以公开访问的、私用的或者受保护的
继承:
通过继承(泛化),组合(聚合)来实现
多态:
通过覆盖(虚函数、接口)、重载来实现
(2) 简述重载和重写的区别,并对应用举例;
重载是同名函数,不同参数,比如
class Shape{
public:
void draw(void);
void draw(int len);
};
重写是不同类中,比如:
class Shape{
public:
void draw(void);
};
class Rectangle:public Shape{
public:
void draw(void);
}
(3) 简述指针、引用以及相应的应用场景;
指针就是一个存放内存地址的整数,这个整数表示的是指向变量的地址;
引用就是变量的别名;
区别:
指针声明的可以暂时不初始化,每次做检查,防止出现空指针异常;
引用不用检查,因为引用永远不会为空,它一定有本体,一定得代表某个对象,引用在创建的同时必须被初始化。
引用的主要功能就是作为函数的参数和返回值,指向一个东西,一定专一,绝不会让其指向其它东西,用引用;其它情况都要用指针
(4) 多线程、多进程如何实现,各自优缺点,简述同步和互斥、进程间通信方式,
各自适用的场景;
多线程通过:
#include<iostream>
#include<thread>
using namespace std;
void proc(int &a)
{
cout << "我是子线程,传入参数为" << a << endl;
cout << "子线程中显示子线程id为" << this_thread::get_id()<< endl;
}
int main()
{
cout << "我是主线程" << endl;
int a = 9;
thread th2(proc,ref(a));//第一个参数为函数名,第二个参数为该函数的第一个参数,如果该函数接收多个参数就依次写在后面。此时线程开始执行。
cout << "主线程中显示子线程id为" << th2.get_id() << endl;
th2.join();//此时主线程被阻塞直至子线程执行结束。
return 0;
}
多进程通过:
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork();
//根据pid是否大于0判断是父进程还是子进程
if (pid > 0) {
std::cout << "Parent process" << '\n';
return 0;
}
std::cout << "Child process" << '\n';
return 0;
}
多进程的优点:
1、进程资源相互独立,互不干扰,子程序崩溃,不影响主程序稳定性,进程比较稳定健壮
2、减少线程加锁、解锁的影响,极大提高性能
3、每个进程可以获得本地全部地址空间各相关资源,性能上限大
多进程缺点:
1、逻辑控制复杂,需要与主程序进行交互
2、需要跨进程边界,对大量数据传输不友好
多线程的优点:
1、无需跨进程边界进行数据传输,通信更便捷
2、程序逻辑和控制方式简单
3、所有线程直接共享同一内存地址和资源
多线程缺点:
1、每一个线程与主程序共享地址空间,受限于本地内存空间大小
2、线程间由于共享同一资源,线程间同步和加锁控制操作麻烦
3、一个线程的崩溃可能影响整个程序的稳定性
多线程的场景:
数据需要修改,不同任务需要大量共享数据或频繁通信的情况下,比如IO密集性的任务
多进程场景:
不同任务间无需大量交互,上下文切换不频繁,可以使用多进程
比如守护进程,无需与主任务进行交互
互斥:
一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。
同步:两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序进行,同步就是互斥基础上有顺序
进程间通信:
管道通信
消除队列通信
信号量通信
共享内存通信
套接字通信
(5) 简述对容器的了解,并举例说明应用场景;
vector向量:
底层实现:动态数组
适用场景:不频繁删除插入,经常查看的操作
deque双端队列:
底层实现:双向链表
适用场景:比如排队购票系统,头部的快速移除,尾部的快速添加
list队列:
底层实现:链表
适用场景:频繁删除插入的操作
set集合:
底层实现:红黑树
适用场景:查询某一个范围的值,比如学生的成绩,按照顺序排列
map映射:
底层实现:红黑树
适用场景:负责查询某个内容,具体到某一个单位,查询效率高,范围比set更宽一些。
二叉树的查找效率,在这里就体现出来了
(6) 如何预防和查找内存泄漏;
1、良好的编程习惯,只要有malloc/new,就得有free/delete
2、尽可能用智能指针
3、log记录
4、谁申请,谁释放
用valgrind:
比如:
gcc -o memleak main.cc -g
valgrind --tool=memcheck --leak-check=full ./memleak
(7) 简述实现线程池的基本思路;
(一)功能
添加任务到线程池
获取任务执行结果
维护线程池中线程状态
(二)设计思路
生产者&消费者模式
任务队列: 生产者与消费者公用的资源
生产者 : 将请求分解为多个小任务,并将其放入任务队列
消费者 : 线程池中的工作线程从任务队列中取任务进行处理
等待任务执行完毕
1)如何获得任务执行的结果?
将任务执行结果结构体的指针作为参数传给任务队列,在任务执行后直接将结果写到对应位置。
2)将任务A拆分为A1、A2、A3、A4、A5放入任务队列,如何知道任务已执行完毕?
对每个请求维护任务计数,当新增任务时计数++,当任务执行完毕时计数--;在每个请求后条件等待直到计数为0,表明该请求的任务执行完毕。
确定线程池 线程数&任务队列size
1)根据目前服务请求量计算线程个数
2)任务队列尽量设置够大
3)根据实际情况调整线程数