最困难的
讲了线材的
再讲个
讲了魔方的
项目中线程间是怎么通讯的
QT信号槽
你感觉最有收获的项目
讲了魔方的
这两个项目有共同点,你有没有从中学到什么通用方法
讲了面向对象编程的思想
如果允许变量名以数字开头,语法分析器每读取下一个字符的时候需要回溯来确定是否是数字、变量名还是词法错误(#等),直到符号出现非数字再转成变量名,很显然这是一种极大的浪费。而且如果当变量名全为数字时,这时候语法分析器就不知道是数字还是变量名了。
get 方法一般用于请求,比如你在浏览器地址栏输入 www.cxuanblog.com 其实就是发送了一个 get
请求,它的主要特征是请求服务器返回资源, 而 post 方法一般用于表单的提交,相当于是把信息提交给服务器,等待服务器作出响应,get
相当于一个是 pull/拉的操作,而 post 相当于是一个 push/推的操作。 get
方法是不安全的,因为你在发送请求的过程中,你的请求参数会拼在 URL 后面,从而导致容易被攻击者窃取,对你的信息造成破坏和伪造;
1)进程是cpu资源分配的最小单位,线程是cpu调度的最小单位。
2)进程有独立的系统资源,而同一进程内的线程共享进程的大部分系统资源,包括堆、代码段、数据段,每个线程只拥有一些在运行中必不可少的私有属性,比如tcb,线程Id,栈、寄存器。
3)一个进程崩溃,不会对其他进程产生影响;而一个线程崩溃,会让同一进程内的其他线程也死掉。
4)进程在创建、切换和销毁时开销比较大,而线程比较小。进程创建的时候需要分配系统资源,而销毁的的时候需要释放系统资源。进程切换需要分两步:切换页目录、刷新TLB以使用新的地址空间;切换内核栈和硬件上下文(寄存器);而同一进程的线程间逻辑地址空间是一样的,不需要切换页目录、刷新TLB。
5)进程间通信比较复杂,而同一进程的线程由于共享代码段和数据段,所以通信比较容易。
首先,map和set的底层是红黑树,而其find查找也是查找红黑树的方法,时间复杂度为O(logn)。而STL的方法用来查找连续容器,比如vector时,用的方法都是遍历,时间复杂度O(n)。
编译过程中(链接阶段)出现了undefined,函数或变量未定义的错误提示。
编译的所有过程:预编译、编译(生成汇编.s文件)、汇编(生产二进制.obj文件)、链接。
编译的过程:
合并所有“.obj”文件的段并调整段偏移和段长度
符号的重定位(链接核心):将符号分配的虚拟地址写回原先未分配正确地址的地方
对于数据符号会存准确地址,对于函数符号,相对于存下一行指令的偏移量(从PC寄存器取地址,并且PC中下一行指令的地址)
链接有分为静态链接和动态链接。
在链接过程中,静态链接和动态链接就出现了区别。静态链接的过程就已经把要链接的内容已经链接到了生成的可执行文件中,就算你在去把静态库删除也不会影响可执行程序的执行;而动态链接这个过程却没有把内容链接进去,而是在执行的过程中,再去找要链接的内容,生成的可执行文件中并没有要链接的内容,所以当你删除动态库时,可执行程序就不能运行。
各自的优缺点: 1、静态链接库执行速度比动态链接库快。(执行过程不需要找链接的内容) 2、动态链接库更节省内存。(未写入要链接的内容)
答了写脚本,确定编译方式和顺序,说了下之前linux下怎么用的。
gdb调试 gdb main.out或者gdb (换行) file main.out
内存泄漏检测工具用过吗
数据库深分页问题
fork的底层实现
fork函数底层由clone()系统调用实现。执行fork函数后,成功:父进程返回子进程id,子进程返回0;
C++调用C语言代码是加的,正确实现调用C代码,指示编译器这部分代码按C语言(而不是C++)的方式进行编译。
临界区(critical
section)、事件(event)、互斥锁(mutex)、信号量(semaphores)、条件变量。临界区是效率最高的,因为不需要其他开销。
上来差点没想起来,共享存储器、消息传递、管道通信。
管道与有名管道 所谓“管道”,是指用于连接一个读进程和一个写进程以实现它们之间通信的一个共享文件,又名pipe文件。
管道可用于具有亲缘关系的父子进程间通信,有名管道除了具有管道的特点外,还允许无亲缘关系的进程进行通信。 信号
信号是软件层次上对中断机制的一种模拟,它是一种复杂的通信方式,用于通知进程某事件的发生,一个进程收到一个信号与处理器收到一个中断请求的效果上是一致的。
消息队列
消息队列是消息的链接表,它克服了以上两种方式中信号量有限的缺点,具有写权限的进程可以按照一定规则在消息队列中添加消息,具有读权限的进程可以按照一定规则从其中读取消息。共享内存
共享内存是最有用的进程通信方式,同一块物理内存被映射到多个进程,它使得多进程可以访问同一块内存空间,不同进程可以看到对方进程中对共享数据的更新。这种方式需要某种同步操作,比如互斥锁和信号量。信号量 信号量主要作为进程之间以及不同线程之间同步和互斥的手段。
套接字(socket) 套接字是更通用的进程通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。
答了bind accept connect write read
socket通讯的时候,write如果写了1000个字节过来,read的时候会会出现小于1000个字节的情况吗?
虚析构
将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。
1.指针有自己的一块空间,而引用只是一个别名
2.使用sizeof看一个指针的大小是4,而引用则是被引用对象的大小
3.指针可以被初始化为NULL,而引用必须被初始化且必须是一个已有对象 的引用
回答了用队列实现的方法
先是语言描述了遍dp方法。面试官突然问,还能怎么优化吗?我懵了,想了一会儿不知道怎么说,开始扯,说用哈希来存什么的,后来突然想到,时间复杂度应该是没得优化了,问优化的话应该只能优化空间复杂度了,突然就想到了原地算法,不用数组存,就三个变量来回存。
技术栈
服务端开发,微信发现接口,京喜。
项目经历很丰富,各方面都很好,没什么问题。
收到了哪些offer,哪些在流程里。
总体几乎所有问题都答出来了,主要问的刚好都是我会的,看了昨天我同学跟我说的,发现没几个能答出来的,今天的虽然很偏,但刚好是我知道的,一问一答像对答案一样,也是走了狗屎运,但愿能过吧。
京东这边是个声音超好听的姐姐,特别温柔,说话也非常礼貌。
现场面试,在学校旁边的金万丽酒店的会议厅。是直接在大会议室里分成小桌,没有隔间,两位面试官。进去就像银行一样,扫码签到,然后等待叫号,到对应桌去面试。
面试我的是两位,一位男技术,一位女姐姐,估计是HR。都是非常好的人,很有礼貌,氛围也很好。一来就您好您好,专心听你讲话。
首先自我介绍,完了问学校的课程成绩,我正讲着,忽然想起来有成绩单,就对着看成绩单。然后讲一下在校的项目经历,我本来准备了三个,讲到第三个的时候,他们对第二个非常感兴趣,因为我是主负责人嘛,就针对第二个开始问问题。
首先项目的背景,是继承之前的项目吗,作为主负责人怎么协调各个人员之间的关系,团队的构成,你是做什么的。
我依照项目讲了一下,然后又问能画出项目的框图吗,给了我一张白纸,我就在上面画出来之前博客写过的差不多的框图,并且主动讲了每个人的分工和我负责的部分,具体要做的事。之后又让我画出做的程序的主界面,在电脑上看起来的样子,画到纸上。我画的时候顺便跟他讲解,刚画完开始执行,他就问道点了开始执行后台会发生什么过程呢。我就讲了算法的分线程实现,以及和主界面的交互。之后HR姐姐问我这个项目中遇到的最大的困难是什么,我讲了工厂里的事,和技术上的,关于代码规范和代码管理的感悟。技术又问我后来有对这部分坏味道的代码进行重构吗,我只能说没有(这部分之后需要再思考一下这个问题)。另外还问我项目中有用到什么设计模式吗,我说了用到的单例模式,解释了下为什么。又问我这整个项目都是用什么开发的,我说了C++,和算法部分本来用python和MATLAB写出来,然后转化成C++融入到主系统里。
HR姐姐又问了我后面参赛的情况,我说了背景,她问我具体做了什么,怎么做的,我就又详细讲了下用到的技术,和比赛规则。之后她又问论文的事,是全英文的吗,这个会议是哪里开的,我都如实回答了,并说具体的日期和地点。她问是全英语汇报吗,那你英语一定不错,我心里一紧,不会要来英语对话吧,我说都是事先准备过的,只需要按照演讲稿汇报即可,她点了点头。
反问环节,我问了中兴的技术栈,C++进去能干什么,技术有点想问我愿不愿意转语言和做测试。我有点懵,感觉中兴应该需要C++的啊,他说网络编程和后台那边是JAVA。然后进去需要等待部门根据简历匹配个人,也就是部门选择人,不是人选部门。这就有点难受了。
然后我希望他给我提点建议和评价,他就说了我项目经历很丰富,但之前的代码坏的味道,可能需要更多的思考,还有代码程序落地之前的各种测试测试方案也需要多多思考。 感觉很对,这确实都是我之前所没有想到的。
这里做个总结和我自己感觉自己没做好的地方:两个人面试记得询问另外一个人,最后反问环节我只听了技术小哥的意见,HR姐姐并没有说,我也没有问,我应该再主动问一下她,尤其是这种两人面试的时候应该照顾一下另一位的感受,不应该全程我跟技术小哥讨论技术,忽略了HR姐姐。另外应该主动表现的更礼貌,结束的时候,他们两位都站了起来,我应该主动上前去握手,可是稍微说了一下转身就走了,应该有诚意的都握一下手,并表示感谢。虽然不是很关键,这也是人细节的一部分。
还有一点,说话有的太快了,记得面试官曾两次提醒我,而已放慢点语速,我想想可能也是因为太紧张了,一激动就会语无伦次,说的很快,这也是个要注意的点,慢慢说,注意语速。保持兴奋,但不要太影响语速。
还是老地方现场排号面。两位看起像领导的面试官。整体感觉就像HR面。
最后,一个面试官刚准备说结束,另一个面试官说等一下,还有个环节,这个面试官还奇怪,还有什么吗。第二个面试官还让我猜,我说不知道,他狡猾一笑说没听之前的同学回去传授经验吗,我说我同学刚好都是今天上午的。那好吧,是英语展示,能说点英语展示一下自己吗?
我说了之前准备的自我介绍,有点太紧张了,断断续续的。还是那个面试官,狡黠一笑,能给你提个建议吗?我说当然可以,以后说自己学校名字的时候可千万不能说错哦,我虽然毕业二十多年了,SCUT这几个单词全拼还是不会说反的。我羞愧了,哈哈,原来是老校友啊。刚太紧张了,SCUT估计拼成SCTU了…
反问,我问了对我有什么建议吗,校友开始滔滔不绝,老实说中兴工作压力挺大的,需要你有别的兴趣爱好,还有属于自己的减压的方法,毕竟人不可能二十四小时工作,都是需要宣泄自己的,有压力一定要有方法宣泄出来,不然可能会转嫁给别人,甚至会给家人或者身边的人带来伤害。所以我的建议就是,作为年轻人来说,培养工作技能的同时,也要培养舒缓自己,放松和减压的手段,这点是年轻人需要注意的,希望你也能注意培养。
好的。
深信服这次问的也太深了!
默认构造函数
析构函数
拷贝构造函数
赋值运算符(operator=)
取址运算符(operator&)(一对,一个非const的,一个const的)
当然,所有这些只有当被需要才会产生。比如你定义了一个类,但从来定义过该类的对象,也没使用过该类型的函数参数,那么基本啥也不会产生。在比如你从来没有进行过该类型对象之间的赋值,那么operator=不会被产生。
最后那一对取址运算符是用争议的, 我记得以前是有一个贴讨论这个的,
据说跟具体的编译器相关, 有的生成, 有的不生成, 只有前四个
原来也是看了《Effective C++》上面的总结,没有细想想,也没有查阅一下资料,刚刚查了C++98年标准(ISO/IEC 14882),至少在“Special Member Function”一节中并未提到operator&。而仅仅提到了其它四个
注意变量命名的规范,参照之前的博文《C++ 编程风格及代码规范》说
基本数据类型最大的特点:基本数据类型之间可以进行隐式转换。 基本数据类型之间进行隐式转换的特点:
把表示范围小的给表示范围大的需要进行扩充。扩充的时候跟目标没关,跟自身有关。如果自身是有符号的类型,扩充的时候给符号位。如果自身类型是无符号的,扩充的时候添0
把表示范围大的给表示范围小的要进行截取。截取权位小的。
- 结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻,
联合中只存放了一个被选中的成员, 而结构的所有成员都存在。- 对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存 在了, 而对于结构的不同成员赋值是互不影响的。
静态多态和动态多态,静态是重载,动态是虚函数
1)普通函数
普通函数不属于成员函数,是不能被继承的。普通函数只能被重载,不能被重写,因此声明为虚函数没有意义。因为编译器会在编译时绑定函数。
而多态体现在运行时绑定。通常通过基类指针指向子类对象实现多态。
2)友元函数
友元函数不属于类的成员函数,不能被继承。对于没有继承特性的函数没有虚函数的说法。
3)构造函数
首先说下什么是构造函数,构造函数是用来初始化对象的。假如子类可以继承基类构造函数,那么子类对象的构造将使用基类的构造函数,而基类构造函数并不知道子类的有什么成员,显然是不符合语义的。从另外一个角度来讲,多态是通过基类指针指向子类对象来实现多态的,在对象构造之前并没有对象产生,因此无法使用多态特性,这是矛盾的。因此构造函数不允许继承。
4)内联成员函数
我们需要知道内联函数就是为了在代码中直接展开,减少函数调用花费的代价。也就是说内联函数是在编译时展开的。而虚函数是为了实现多态,是在运行时绑定的。因此显然内联函数和多态的特性相违背。
5)静态成员函数
首先静态成员函数理论是可继承的。但是静态成员函数是编译时确定的,无法动态绑定,不支持多态,因此不能被重写,也就不能被声明为虚函数。
能否在析构函数里面调用虚函数:同理,编译不会报错,但是最好不要这样做!在调用析构的时候类里面的变量可能已经被析构,
虚函数这时可能会访问内存中未知的区域,比较容易出错,所以最好不要在析构函数里调用虚函数。
派生类对虚函数重写时
/*
* 虚函数的三种调用
* 1: 指针
* 2: 引用
* 3: 对象(不能实现多态)
*/
#include
//继承,默认情况下class是私有继承 struct默认是公有继承
//虚函数可以调用成员函数
//多态调用依赖于指针 或 引用调用
//对象的调用有副本机制,会调用拷贝构造 拷贝一个父类 无法实现多态
//p->go(); //多态调用
//p->myclass::go(); //原生调用
//虚函数重载和返回值无关 和参数的类型 个数 顺序有关
//虚函数被继承下来了还是虚函数
//如果要使用被继承的虚函数 不允许出现虚函数重载和覆盖
//多态可以跨类 爷爷辈的指针 可以存储孙子辈的地址 父辈拔针的地址
class myclass
{
public:
int i;
virtual void go()
{
i = 0;
show();
}
void show()
{
std::cout << "myclass->show()" << std::endl;
}
virtual ~myclass()
{
// 不加virtual 会造成内存泄漏
}
};
class newmyclass: public myclass
{
public:
void go()
{
std::cout << "newmyclass->go()" << std::endl;
}
void show()
{
std::cout << "newmyclass->show()" << std::endl;
}
void put()
{
std::cout << "newmyclass->put()" << std::endl;
}
};
// 1: 指针
void test1(myclass *p)
{
p->go();
}
// 2: 引用
void test2(myclass &p)
{
p.go();
}
// 3: 对象
void test3(myclass my)
{
my.go();
}
int main()
{
myclass my;
my.go(); // 正常调用父类的go函数
newmyclass newmy;
newmy.go(); // 正常调用子类的go函数
myclass *pmy(nullptr);
pmy->show(); // 空指针可以调用show操作
//pmy->go(); // 空指针无法调用go函数,因为myclass类,没有实例化为对象,go函数内部操作了对象的内部变量,此时该内部变量并没有构造出来
myclass *p = new newmyclass;
p->go(); // 调用子类的go函数
p->myclass::go(); // 指定调用父类的go函数
std::cout << typeid(p).name() << std::endl; // p的类型为父类类型指针 class myclass *
std::cout << typeid(*p).name() << std::endl; // *p的类型为子类类型 class newmyclass
test1(p); // 调用子类的go函数
test1(&my); // 调用父类的go函数
test1(&newmy); // 调用子类的go函数
// 引用在语言内部用指针实现,引用是操作受限了的指针(仅容许取内容操作)
test2(*p); // 调用子类的go函数
test2(my); // 调用父类的go函数
test2(newmy); // 调用子类的go函数
// 对象作为参数,会使用拷贝构造函数,形成对象的副本
test3(*p); // 调用父类的go函数
test3(my); // 调用父类的go函数
test3(newmy); // 调用父类的go函数
//std::cout << "mytest" << std::endl;
system("pause");
return 0;
}
无继承时:
1、分配内存
2、初始化列表之前赋值虚表指针
3、列表初始化
4、执行构造函数体
有继承时:
1、分配内存
2、基类构造过程(按照无继承来)
3、初始化子类虚表指针
4、子类列表初始化
5、执行子类构造函数体
拷贝一份父进程资源独立使用
子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。
注意,子进程持有的是上述存储空间的“副本”,
这意味着父子进程间不共享这些存储空间,
它们之间共享的存储空间只有代码段。
实际上,fork后子进程和父进程共享的资源还包括:
打开的文件
实际用户ID、实际组ID、有效用户ID、有效组ID
添加组ID
进程组ID
会话期ID
控制终端
设置-用户-ID标志和设置-组-ID标志
当前工作目录
根目录
文件方式创建屏蔽字
信号屏蔽和排列
对任一打开文件描述符的在执行时关闭标志
环境
连接的共享存储段(共享内存)
资源限制
父子进程之间的区别是:
fork的返回值
进程ID
不同的父进程ID
子进程的tms_utime,tms_stime,tms-cutime以及tms_ustime设置为0
父进程设置的锁,子进程不继承
子进程的未决告警被清除
子进程的未决信号集设置为空集
保留父进程打开文件的副本
读共享写复制
1、管道与有名管道
2、信号
3、消息队列
4、共享内存
5、信号量
6、套接字(socket)
信号是软件层次上对中断机制的一种模拟,它是一种复杂的通信方式,用于通知进程某事件的发生,一个进程收到一个信号与处理器收到一个中断请求的效果上是一致的。
SIGINT终止进程,通常我们的Ctrl+C就发送的这个消息。
SIGQUIT和SIGINT类似, 但由QUIT字符(通常是Ctrl- / )来控制. 进程收到该消息退出时会产生core文件。
SIGKILL消息编号为9,我们经常用kill -9来杀死进程发送的就是这个消息,程序收到这个消息立即终止,这个消息不能被捕获,封锁或这忽略,所以是杀死进程的终极武器。
SIGTERM是不带参数时kill默认发送的信号,默认是杀死进程。
SIGSTOP停止进程的执行,同SIGKILL一样不可以被应用程序所处理,注意它和terminate以及interrupt的区别:该进程还未结束, 只是暂停执行。
SIGCONT当SIGSTOP发送到一个进程时,通常的行为是暂停该进程的当前状态。如果发送SIGCONT信号,该进程将仅恢复执行。除了其他目的,SIGSTOP和SIGCONT用于Unix shell中的作业控制,无法捕获或忽略SIGCONT信号。
SIGCHLD子进程结束时,父进程会收到这个信号。默认动作为忽略这个信号。
SIGCHLD子进程结束时,父进程会收到这个信号。默认动作为忽略这个信号。
64按说内存对齐应该8,网上也有说4的,可以问下面试官再算。
BSS段和数据段
栈区、常量区
函数返回类型和变量不一致
好像没有
存在潜在越界问题
当dest的长度 < src的长度的时候,由于无法根据指针判定其所指指针的长度,故数组内存边界不可知的。因此会导致内存越界,尤其是当数组是分配在栈空间的,其越界会进入你的程序代码区,将使你的程序出现非常隐晦的异常。
不能处理内存覆盖问题 不能处理dest和src内存重叠的情况。
new关键字
当开辟的空间小于128k时,底层调用系统的brk()函数
大于128K时,mmap()系统调用函数在虚拟地址空间中(堆和栈中间,文件映射区域)找一块空间来开辟
Windows下32位程序如果单纯看地址空间能有4G左右的内存可用,不过实际上系统会把其中2G的地址留给内核使用,所以你的程序最大能用2G的内存。除去其他开销,你能用malloc申请到的内存只有1.9G左右。
新增元素:Vector通过一个连续的数组存放元素,如果集合已满,在新增数据的时候,就要分配一块更大的内存,将原来的数据复制过来,释放之前的内存,在插入新增的元素;
不同的编译器实现的扩容方式不一样,VS2015中以1.5倍扩容,GCC以2倍扩容。
vector在push_back以成倍增长可以在均摊后达到O(1)的事件复杂度,相对于增长指定大小的O(n)时间复杂度更好。
当空间不够装下数据 时, 会自动申请另一片更大的空间( 1 . 5 倍或者2 倍) , 然后把原来的数据拷贝到新的内存空间, 接着释放原来的那片空间
红黑树
红黑树的特点:
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
保证TCP连接的可靠释放
避免本次的连接失效报文会对之后的连接造成影响
在高并发短连接的TCP服务器上,当服务器处理完请求后立刻主动正常关闭连接。这个场景下会出现大量socket处于TIME_WAIT状态。如果客户端的并发量持续很高,此时部分客户端就会显示连接不上。
我来解释下这个场景。主动正常关闭TCP连接,都会出现TIMEWAIT。
让TIME_WAIT状态可以重用,这样即使TIME_WAIT占满了所有端口,也不会拒绝新的请求造成障碍
阻塞与非阻塞recv返回值没有区别,都是:
<0 出错
=0 对方调用了close API来关闭连接
0 接收到的数据大小,
send也是>0表示成功,返回实际发送或接受的字节数,=0表示超时,对方主动关闭了连接过程,<0出错
select 和 poll 监听文件描述符list,进行一个线性的查找 O(n)epoll: 使用了内核文件级别的回调机制O(1)
三者都由timeout参数指定超时时间,直到一个或多个文件描述符上有时间发生时返回,返回值就是文件描述符的数量。返回0表示没有事件发生。
1、select没有文件描述符与事件绑定,它仅仅是一个文件描述符的几何,因此select需要提供三个此类参数来区分传入的可读,可写,异常事件。一方面使得其不能处理更多事件,另一方面内核对fd_set集合在线修改,应用程序下次需要重置此三个fd_set集合。
3、epoll在内核维护一个事件表,并提供epoll_ctl来控制向其中添加、删除、修改事件。这样,epoll调用直接从该内核事件表中取得用户注册事件,从而无需反复从用户空间读入这些事件。epoll_wait系统调用的events参数仅用来返回就绪事件,使得应用程序索引就绪事件描述符的时间复杂度为O(1)
4、poll/epoll分别采用nfds和maxevents参数指定最多监听的文件描述符合事件。这两个数值都能达到系统允许的最大最大文件描述符数,65536。而select允许最大监听最大文件描述符数量通常有限制。
5、select/poll都支持相对低效的LT模式,epoll可在ET模式下工作,epoll还支持EPOLLONESHOT事件
6、活动连接比较多是,epoll效率未必比select、poll高,因为此时回调函数被触发过于频繁。epoll适用于连接数量较多,但活动连接相对较少的情况。
7、select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。
水平触发 (level-trggered)
只要文件描述符关联的读内核缓冲区非空,有数据可以读取,就一直发出可读信号进行通知,
当文件描述符关联的内核写缓冲区不满,有空间可以写入,就一直发出可写信号进行通知
LT 模式支持阻塞和非阻塞两种方式。epoll 默认的模式是 LT。
边缘触发 (edge-triggered)
当文件描述符关联的读内核缓冲区由空转化为非空的时候,则发出可读信号进行通知,
当文件描述符关联的内核写缓冲区由满转化为不满的时候,则发出可写信号进行通知
select,poll 就属于水平触发。epoll 既可以采用水平触发,也可以采用边缘触发。
边沿触发
优点:每次内核只会通知一次,大大减少了内核资源的浪费,提高效率。
缺点:不能保证数据的完整。不能及时的取出所有的数据。
应用场景:处理大数据。使用non-block模式的socket。
关于读事件,如果业务可以保证每次都可以读完,那就可以使用ET,否则使用LT。
对于写事件,如果一次性可以写完那就可以使用LT,写完删除写事件就可以了;但是如果写的数据很大也不在意延迟,那么就可以使用ET,因为ET可以保证在发送缓冲区变为空时才再次通知就绪。
遍历 O(n),10次
初始时候算法是利用buildMaxHeap将数组A[1…n]建成最大堆,因为数组中最大元素总在根节点A[1]处,通过把他与A[n]进行互换,我们可以让该元素放到正确位置。这时候,如果我们从堆中去掉结点n,剩余结点仍然是最大堆,而新的结点可能会违背最大堆的性质。为了维护最大堆的性质,我们调用maxHeap(A, 1),从而在A[1…n-1]上构造一个新的最大堆。堆排序要做的是不断重重复这样的操作,直到堆的大小从n-1降到2.
关键的性质
将堆的内容填入一个一维数组,这样通过下标就能计算出每个结点的父子节点,编号顺序从0开始,从左往右,从上至下层次遍历。a[10] = {2,8,5,10,9,12,7,14,15,13}
若一个结点的下标为k,那么它的父结点为(k-1)/2,其子节点为2k+1和2k+2
例:数字为10的节点的下标为3,父结点为1号:8,子节点为7号和8号:14,15
sort完了遍历
哈希表存
df,命令"df -a"是全部的文件系统的使用情况
部门是智能驾驶BU下的MDC产品部。
很年轻的面试官,但知识广度非常大,跟我从ROS、QT、OPENCV聊了个遍,感觉基本项目里用到的他都懂。人又非常好,非常的宽容,最后了我感觉自己可能要凉的时候,还鼓励我说,表现的不错,基础很好,我会给你通过的,准备下一面吧。
看起来很成熟稳重,像个部门主管的样子,也是人很好,很有趣,耐心引导。面试体验非常好。
一看就是个大佬的样子, 用的应该是华为笔记本,摄像头在下面,看起就是全程俯视我,哈哈哈。最主要的是我介绍个什么项目或者问题,他立马就能我了解了,然后提炼出这段话的关键点,问我是这样吗,我一听,确实啊。就是那种能一眼把人看穿的感觉,抓重点和概括能力极强。
问的问题都毕竟常见,也都答出来了,和之前的差不多,就不再记录了。有几个是让直接想的开放问题,能想多少想多少,差点没想起来几个,也记一下吧,以后复习用。
主要记录一下问到的关于项目的问题和给我的建议,非常中肯,受益匪浅。
项目里用到的IMU惯性传感器是几轴的?
9轴
分别是哪9轴呢?
xyz三轴陀螺仪、三轴加速度计、三轴磁力计(对于偏航角度,由于偏航角和重力方向正交,无法用加速度计测量得到,因此还需要采用其他设备来校准测量偏航角度的陀螺仪的漂移值。校准的设备可以使用磁罗盘计,因此引入磁力计,来找到正确的方向进行校正。)。
QT视频播放是什么格式的?
MP4
MP4直接能播放吗?底层转化了解吗?
C++音视频解码转换?
最后的建议阶段,针对我之前的项目和说自己的优缺点,提了很有建设性的意见:
说我的项目经历其实足够了,设计面很广,包括ROS/QT/OPENCV等等,这随便一个都能有很多东西能学习,比如ROS的整体架构和底层,QT的信号槽是怎么设计和实现的,OPENCV3里就有许多神经网络相关的应用是怎么实现的。有时候不需要很丰富的项目,针对一个项目能够学精,一个小领域能学出深度也是一样可以的。比如之后工作了,遇到IMU需要优化,如果你刚好之前把IMU相关的底层原理都学的很精,自然就能提出一些可行的方法,便能在相关的工作中展露头角,这也是一方面。
把已有的项目研究透,即使不是自己工作的部分,有这个平台,有没有也去学习和掌握一下,转化为自己的一部分。针对项目提出自己的疑问,有自己的思考和理解。
我很认同,这确实是技术继续往上走必须的品质。其实这可以理解为对技术的深度的追求,在小细节的理解。这很关键,但我觉得当前阶段对技术广度的追求才是当务之急,先要了解有哪些主流的技术需要掌握,然后知道怎么用,然后才是进一步深挖底层实现原理。如果广度没做好,某方面深度很深也收效甚微。
现在面试的时候遇到这样注重细节和深度的面试官已经很难得了,大部分时候都是会先关注项目是不是高大上,很少想去了解即使是小项目,是不是在已有基础上学习了足够的深度。这就跟许多人找对象类似,上来就会先看些表面的东西,长得好不好看,开什么车,有没有房。之后才会想去了解你的精神内核,是不是在已有条件下尽可能有深度。人之常情,无可厚非。
所以我才觉得,只有在广度的基础上,再慢慢的去提升某一方面的深度才有意义。表面功夫做足,在大局观做好的基础上去再去注重小细节,才能让自己达到新的高度。
总之,这次面试受益匪浅,面试官是敏视研发的技术负责人,有点听君一席话胜读十年书的感觉了。感激,愿意在面试中跟我做这样的分享。
PS:查了下 QT播放视频底层相关的原理
1、SDL:(以下仅针对图像刷新)
优点:后台渲染,只需要传入窗口句柄,与Qt是不同的图形显示框架;完全可以在非GUI线程中传入句柄和图像数据,完全不影响GUI线程的刷新,因为它连Qt的事件循环都没有进入,更新显示是使用的传入句柄后台显示的,SDL自已的刷新策略。可以显示的视频格式多。GPU转换图像格式
缺点:由于没有进入Qt的事件循环,所以在widget这种GUI程序update()的时候,如果没有禁用updatesEnable()那么Qt对这个widget也会重绘刷新一次,而这上widget的句柄SDL也在用,如此就会造成闪屏。另外,如果在视频上放一些Qt的一些widget控件,也会造成闪屏(或者直接没有),这也是因为视频的刷新是SDL,而视频上面的刷新是Qt。SDL里面的窗口叠加和Qt的窗口叠加应该不一样导致的吧,直接没显示,可能是因为你禁用了updatesEnable(),视频上面的widgets都不会刷新了。所以要在视频上面画图案就只有用SDL在视频上面画了,另外如果禁用updatesEnable(),在多分屏窗口如果窗口从大的窗口变成小的多个窗口时,就会花屏了,因为大的窗口的区域没有刷新。
2、 QOpenGLWidget:
优点 : 使用GUI渲染视频,widget原生控件,可以在视频上面放其它widget控件,不会闪屏之类 其它问题,可以自定义shader显示yuv、nv12等视频格式。GPU转换图像格式
缺点 :进入了Qt的事件循环, 刷新多路视频的时候,某一时刻每一路视频的每帧图像依然是在一个openglwidget中传入数据给opengl渲染后,再接着下一个,这里的每一个openglwidget都相当于一个opengl场景。由于以上原因,我的电脑i5CPU、显卡是英伟达的(型号忘了,反正够显示了),在显示20个每秒25帧的视频画面时,有一点卡滞但不严重。由于是给一个openglwidget传递数据等它渲染好了,再到下一个,所以主循环耗掉了一些时间,但opengl渲染给力,每个耗时不长,所以卡滞不严重。
3 、自定义qml视频显示控件
优点 : 使用GPU渲染;如果是QQuick应用,那么还有多线程渲染。可以在视频上面放置其它qml控件,所有的qml控件在同一个图像渲染场景中,本身就是一个状态机,可以一次把多路视频都渲染了,因为qml场景中的渲染,就是渲染场景中有变化的都一次性渲染了,当外部把数据传进来时,并不是立即刷新的,而是把数据给到了渲染场景中,再下一次刷新时,会一个结点一个节点的取数据,一次性渲染,所以效果较好。显示100路25帧的视频时,界面依然很流畅
缺点 : 进入了事件循环,开发windows应用周期长,一些复杂控件,像QTreeWidget、QDateTime、QTableView这种复杂控件没有传统的widget功能齐全。我实现的是需要外部转换图像格式,不能直接显示Yuv和Nv12这种视频格式,qt封装的类中我暂没看到可以用来解码的,可能也有像材质、纹理之类的可以处理,暂不清楚。
4 、 QMediaPlayer
优点 : 官方控件,支持格式多,用directshow做底层,不用处理视频、音频解码,功能齐全。界面流畅效果和使用SDL一样,非常流畅,rtsp和http视频都可以
缺点 : 需要安装K-Lite插件(里面包含directshow),我电脑测试显示最多8路视频同时播放,再多程序就挂了。直接以QVideoWidget为父对象放Qt控件时,直接不显示。以QVideoWidget的父对象为父对象放置控件,并把位置调整到视频上,背景不能透明,应该是放置上去的控件在栈区中的分层在QVideoWidget的下面,而其中的视频可能是在类似3d场景中渲染的,3d场景中默认的背景是黑色(猜测)。不能自字义使用多张显示(解码),directshow的GUI配置里面是选择其中一种
我们用的是第四种,直接用了QMediaPlayer,用directshow做底层,不用处理视频、音频解码。
上来两道算法题:
1、编码:写一个c/c++函数,实现将16进制表现形式的字符串转化为整数。例如,输入”1a”,返回26,输入“FE”,返回254。
2、编码:有两个字符串s1和s2,判断s2是否包括s1的排列(即s1的排列是否s2的子串)
例如,输入s1=mn,s2=abnmxy, 输出True; 输入s1=mn,s2=abnomy, 输出False
1.
#include
#include
using namespace std;
int main(){
string str;
cin>>str;
int len = str.size();
int sum=0;
for(int i=0;i<len;++i){
int tmp=0;
if(str[i]>='0'&&str[i]<='9')
tmp=str[i]-'0';
else if(str[i]>='a'&&str[i]<='f')
tmp=str[i]-'a'+10;
sum=sum*16+tmp;
}
cout<<sum<<endl;
return 0;
}
2.
#include
#include
#include
using namespace std;
int main(){
string str1,str2;
int left=0,right=0;
int len1=str1.size(),len2=str2.size();
unordered_map<char,int>ump;
for(auto c:str1)ump[c]++;
int sum=0;
while(right<len2){
if(ump.count(str2[right]) && ump[str2[right]]>0){
sum++;ump[str2[right]]--;
}
right++;
while(sum==len1 && left<right){
if(right-left==len1)return true;
if(ump.count(str2[left]) && ump[str2[left]]<=0){
sum--;ump[str2[left]]++;
}
left++;
}
}
return false;
}
解完开始交流。
第一题提了3个潜在问题:
1.没有异常和边界检测
2.没有判断大写字母
3.可能存在溢出
第一个是他提的,后面两个是我说的,然后问了有什么解决方法。
本来讲完以为差不多要结束了,话锋一转,下面开始问些别的问题。十点五十开始的,写题给了五十分钟,这讲完一个多小时了,还开始接着问题,也是懵了。
上来一道概率题和脑筋急转弯题:
一个国家生孩子,生到男孩就停止,生到女孩就接着生,直到生到的孩子是男孩为止,请问最后这个国家的男女比例是多少?
我第一感觉是二分之一啊,生男孩二分之一,用数学期望算,剩下的二分之一概率里女孩再直到男孩为止。后来想想不对啊,现在中国男女比例失衡,不就这种生男孩导致的么,然后又想了十分钟,纠结的不知道分布。
面试官让我给个结果,凭感觉,我只能说女孩比男孩多,中国不就这种情况。他突然笑了起来。我讲了讲推测,就是给不出计算式子,因为按刚写的计算式算的话,结果是二分之一了,太不符合常理。
后面开始专业问题
InnoDB存储引擎的最小存储单元为16k(就像操作系统的最小单元为4k 即1页),在这即B+树的一个节点的大小为16k
假设数据库一条数据的大小为1k,则一个节点可以存储16条数据
而非叶子节点,key一般为主键假设8字节,指针在InnoDB中是6字节,一共为14字节,一个节点可以存储 16384/14 =
1170个索引指针可以算出一颗高度为2的树(即根节点为存储索引指针节点,还有1170个叶子节点存储数据),每个节点可以存储16条数据,一共1170*16条数据
= 18720条高度为3的树,可以存放 1170 * 1170 * 16 = 21902400条记录
两千多万条数据,我们只需要B+树为3层的数据结构就可以完成,通过主键查询只需要3次IO操作就能查到对应记录。
补:
1、网页上输入url的整个过程(从协议入手描述)
2、TCP四次握手(每个状态机都要描述)
3、操作系统(malloc过程,讲到了虚拟内存、物理内存、缺页置换等)
auto addr = (type) malloc(size); // line 1
strcpy(addr, “apple”); // line 2
面试官是给了上面两行代码,让你根据这个去描述细节。
4、数据库
①staff_info表,有20个字段,按nick_name、gender、school查询
有这样一个表,有什么办法保证查询的高效性
②事务:
事务A和事务B
一个空表,事务A先slecte id = 2,结果肯定是0
然后此时事务B insert id = 2的一个字段,然后commit
然后现在事务A再slecte id = 2,结果会是怎样(我答的是这是幻读问题,此时事务A select的结果应该还是0)
面试官追问:为什么会有这样的结果,底层是怎么是实现的,你具体描述一下
我记不太清楚了,就答了个通过版本号实现的?
面试官:这部分你后面再看看吧。
面试官继续问:如果此时事务A在一行sql操作后面加了for update,结果是怎样?
我答:能成功
面试官追问:那这又是为什么,底层是怎样实现的,描述一下
我答:for upadte应该是加了个写锁,然后balabala
面试官:这部分你后面再看看吧。
5、一道算法题
给定一个数组,不含重复元素,返回该数组所有可能的子集,如:
input:[1, 2, 3]
output:[ [], [1], [2], [3], [1,2], [1,3], [2,3], [1,2,3] ]
面试官步步引导,最后甚至直接点明考点:回溯
然后就让我先描述一下自己的思路,后续让我在纸上写下代码拍照发他,我在剪枝的处理上不是特别优,面试官说:这样剪枝有点绕了,但是应该也还行。
最后,面试官:你有什么问题吗?
面试官介绍了下富途的主要业务(证券业务)流程,他是华工的(本科),然后现在是资产组的负责人。等等等等
#include
using namespace std;
struct Fraction {
int numerator;//分子
int denominator;//分母
int integer;//整数部分
int decimal[100] = { 0 };//小数部分
};
int main() {
Fraction fra;
cin >> fra.numerator >> fra.denominator;
int r[101] = { 0 }; //存储余数
fra.integer = fra.numerator / fra.denominator; //求出整数部分
int cnt = 0, flag = -1; //cnt小数位数 flag记录循环开始的数字
r[cnt] = fra.numerator%fra.denominator; //得到余数
bool f = fra.numerator%fra.denominator == 0 ? true : false; //结果就是整数无余数
while (r[cnt]) {
fra.decimal[cnt] = r[cnt] * 10 / fra.denominator; //得到第cnt位小数
for (int j = 0; j<cnt; j++)
{
if (r[j] == r[cnt]) //若有此余数与之前余数相等 则发生循环
{
flag = j;
break;
}
}
if (flag != -1)
break;
cnt++;
r[cnt] = r[cnt - 1] * 10 % fra.denominator;
}
//开始输出结果
cout << fra.integer; //输出整数部分
if(!f)
cout << "."; //小数点
for (int j = 0; j<cnt; j++) {
if (j == flag) cout << "(" << fra.decimal[j];
else cout << fra.decimal[j];
}
if (flag != -1) //flag=-1表明无循环节
cout << ")" << endl;
return 0;
}
面试官看完表示思路没问题,就是中间用的数据结构有点low,他还问我数据C++标准库吗…
这个之前做过,就直接说这个题用“单调栈”的思路可以解,然后面试官就直接说,我不要你用单调栈,你就给我说下暴力的思路吧。我就说主要是维护一个left左边界数组和一个right右边界数组等等。
然后面试官见状就给我再换了个题:只让我答了下第一问。
最后时间也差不多了,再问点基础问题吧:
MYSQL事务了解吗? 答:了解
MYSQL怎么显示加一个排他锁 答:SQL语句后面加for update
我现在对一个事务加了排他锁,那么其他事务还能对他再加X或者S锁吗? 答:不能
那我现在另外一个事务select能成功是什么原理 答:可能是读的他的一个快照(版本)
在MYSQL里面这个叫什么 答:MVCC多版本并发控制
系统调度的最小单位 答:线程
线程有自己的栈吗 答:有
栈里面一般存储什么东西 答:函数形参、临时变量之类的
还有吗? 答:线程切换的时的状态、递归函数的递归状态?(面试官表示否定说不是的哦)
最后很幸运基本都offer了,秋招也告一段落。总体感觉越是大厂,流程越规范,面试官也越亲切有礼貌。另外还有对应的笔试题,不是很方便公布出来,有需要的可以私信要,看到会回,免费分享。