地平线后端日常实习 地平线 C++之解答
项目2
5.为什么选epoll
主要是epoll相对于select,poll更高效。
epoll为什么高效:
(1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。
(2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把当前进程往设备等待队列中挂一次,而epoll只要一次拷贝,而且把当前进程往等待队列上挂也只挂一次,这也能节省不少的开销。
6.epoll是最快的么,什么场景下
不一定。epoll的一个缺点,当事件触发比较频繁时,回调函数也会被频繁触发,此时效率就未必比select 或 poll高了。所以epoll的最佳使用情景是:连接数量多,但活跃的连接数量少。
7.用的水平还是垂直触发,有什么区别
LT模式(水平触发)下,只要这个fd还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作;
而在ET(边缘触发)模式中,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无论fd中是否还有数据可读。
8.epoll是如何操作fd的
epoll提供了三个函数,epoll_create、epoll_ctl和epoll_wait。 首先创建一个epoll对象,然后使用epoll_ctl对这个对象进行操作(添加、删除、修改),把需要监控的描述符加进去,这些描述符将会以epoll_event结构体的形式组成一颗红黑树,接着阻塞在epoll_wait,进入大循环,当某个fd上有事件发生时,内核将会把其对应的结构体放入一个链表中,返回有事件发生的链表。
C++
9.c++生成可执行文件过程
C++和C语言类似,一个C++程序从源码到执行文件,有四个过程,预编译、编译、汇编、链接。
预编译:这个过程主要的处理操作如下:
(1) 将所有的#define删除,并且展开所有的宏定义
(2) 处理所有的条件预编译指令,如#if、#ifdef
(3) 处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。
(4) 过滤所有的注释,如//、/**/
(5) 添加行号和文件名标识。
编译:这个过程主要的处理操作如下:
(1) 词法分析:将源代码的字符序列分割成一系列的记号。
(2) 语法分析:对记号进行语法分析,产生语法树。
(3) 语义分析:判断表达式是否有意义。
(4) 代码优化:
(5) 目标代码生成:生成汇编代码。
(6) 目标代码优化:
汇编:这个过程主要是将汇编代码转变成机器可以执行的指令。
链接:将不同的源文件产生的目标文件进行链接,从而形成一个可以执行的程序。
10.多态有哪些
编译期间如何实现多态:重载
执行期间如何实现多态:多态
11.怎么实现虚函数(虚表、虚指针)
C++实现虚函数的原理是虚函数表+虚表指针。
当一个类里存在虚函数时,编译器会为类创建一个虚函数表,虚函数表是一个数组,数组的元素存放的是类中虚函数的地址。
同时为每个类的对象添加一个隐藏成员,该隐藏成员保存了指向该虚函数表的指针。该隐藏成员占据该对象的内存布局的最前端。
所以虚函数表只有一份,而有多少个对象,就对应多少个虚函数表指针。
12.模板展开在哪个阶段
编译阶段
13.用模板写过哪些功能
略
14.private、public、protected三种修饰符作用于继承,哪些可见哪些不可见
类中成员访问属性有三种:
(1)私有成员(变量和函数)只限于类成员访问,由private限定;
(2)公有成员(变量和函数)允许类成员和类外的任何访问,由public限定;
(3)受保护成员(变量和函数)允许类成员和派生类成员访问,不允许类外的任何访问。所以protected对外封闭,对派生类开放。
15.裸socket连接流程
(1)服务器根据地址类型( ipv4, ipv6 )、 socket 类型、协议创建 socket。
(2)服务器为 socket 绑定 IP 地址和端口号。
(3)服务器 socket 监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket 并没有被打开 。
(4)客户端创建 socket。
(5)客户端打开 socket,根据服务器 IP 地址和端口号试图连接服务器 socket。
(6)服务器 socket 接收到客户端 socket 请求,被动打开,开始接收客户端请求,直到客户端返回连接信息 。这时候 socket 进入阻塞状态,所谓阻塞即accept()方法一直到客户端返回连接信息后才返回,开始接收下一个客户端连接请求 。
(7)客户端连接成功,向服务器发送连接状态信息 。
(8)服务器 accept 方法返回,连接成功 。
(9)客户端向 socket 写入信息 。
(10)服务器读取信息 。
(11)客户端关闭 。
(12)服务器端关闭 。
16.新特性有哪些
C++后续版本更是发展了不少新特性,如C++11中引入了nullptr、auto变量、Lambda匿名函数、右值引用、智能指针。
17.为什么用智能指针
使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等。
正是因为指针存在这样的问题,C++便引入了智能指针来更好的管理堆内存。智能指针是利用了一种叫做RAII(资源获取即初始化)的技术对普通的指针进行封装,这使得智能指针实质是一个对象,行为表现的却像一个指针。
因为智能指针就是一个类,当超出了类的作用域时,类会自动调用析构函数,自动释放资源。这样程序员就不用再担心内存泄露的问题了。
C++里面有四个指针:auto_ptr、unique_ptr、shared_ptr、weak_ptr,后面三个是C++11支持的,第一个被C++11弃用。
18.怎么设计share_ptr,引用计数存在哪里
实现原理:有一个引用计数的指针类型变量,专门用于引用计数,使用拷贝构造函数和赋值拷贝构造函数时,引用计数加1,当引用计数为0时,释放资源。
19.全用share_ptr就能解决内存泄漏了么
会出现内存泄露问题。
共享指针的循环引用计数问题:当两个类中相互定义shared_ptr成员变量,同时对象相互赋值时,就会产生循环引用计数问题,最后引用计数无法清零,资源得不到释放。
可以使用weak_ptr,weak_ptr是弱引用,weak_ptr的构造和析构不会引起引用计数的增加或减少。我们可以将其中一个改为weak_ptr指针就可以了。比如我们将class B里shared_ptr换成weak_ptr。
20.weak_ptr的lock()函数怎么知道share_ptr是否存在
lock()方法的功能是:判断weak_ptr所指向的shared_ptr对象是否存在。若存在,则这个lock方法会返回一个指向该对象的shared_ptr指针;若它所指向的这个shared_ptr对象不存在,则lock()函数会返回一个空的shared_ptr。
21.静态变量初始化顺序
对于C语言的全局和静态变量,初始化发生在任何代码执行之前,属于编译期初始化。
而C++标准规定:全局或静态对象当且仅当对象首次用到时才进行构造。
编程
22.写一个线程池
设计思路:
实现线程池有以下几个步骤: (1)设置一个生产者消费者队列,作为临界资源。
(2)初始化n个线程,并让其运行起来,加锁去队列里取任务运行
(3)当任务队列为空时,所有线程阻塞。
(4)当生产者队列来了一个任务后,先对队列加锁,把任务挂到队列上,然后使用条件变量去通知阻塞中的一个线程来处理。
代码略
2023.1.10 (40分钟)
1.malloc种brk和mmp的区别(没看过。。。)
问题有点迷糊,不清楚面试官的点是什么
2.实现一个单例模式
单例实现原理是,将能够创建对象的函数都设置为private,通过静态成员返回一个实例。
有两种方式,一个是懒汉式,一个是饿汉式。懒汉式需要考虑加锁。
实现代码如下:
#include <iostream> #include <pthread.h> using namespace std; class singleInstance{ public: static singleInstance* GetsingleInstance(){ if (instance == NULL){ pthread_mutex_lock(&mutex);//mlock.lock(); if (instance == NULL){ instance = new singleInstance(); } pthread_mutex_unlock(&mutex);//mlock.unlock(); } return instance; }; ~singleInstance(){}; static pthread_mutex_t mutex;//mutex mlock; 加锁互斥 private:// 涉及创建对象的函数都设置为private singleInstance(){}; singleInstance(const singleInstance& other){}; singleInstance& operator=(const singleInstance& other){ return *this; }; static singleInstance* instance; }; //懒汉式,静态变量需要定义 singleInstance* singleInstance::instance = nullptr; pthread_mutex_t singleInstance::mutex; int main(){ // 因为没有办法创建对象,就得采用静态成员函数的方法返回静态成员变量 singleInstance *s = singleInstance::GetsingleInstance(); //singleInstance *s1 = new singleInstance(); // 报错 cout << "Hello World"; delete s; // 防止内存泄露 return 0; }
下面是饿汉式:
#include <iostream> #include <pthread.h> using namespace std; class singleInstance{ public: static singleInstance* GetsingleInstance(){ // 饿汉式,直接创建一个对象,不需要加锁 static singleInstance instance; return &instance; }; ~singleInstance(){}; private:// 涉及创建对象的函数都设置为private singleInstance(){}; singleInstance(const singleInstance& other){}; singleInstance& operator=(const singleInstance& other){ return *this; }; }; int main(){ // 因为没有办法创建对象,就得采用静态成员函数的方法返回 singleInstance *s = singleInstance::GetsingleInstance(); //singleInstance *s1 = new singleInstance(); // 报错 cout << "Hello World"; return 0; }
3.写一个字符串转数字(要求:1.负数;2.十六进制;3.非法字符)
略
以上答案均来自本人专栏:校招面试考点全解析——C++软件与嵌入式篇(蒋豆芽的秋招打怪之旅)
欢迎大家围观:https://blog.nowcoder.net/jiangwenbo
这个专栏专门用于为牛友解答面经,希望能帮助到大家。