一、c++基础知识
1、进程和线程的同步方式
进程:1)管道,是内核里的一串缓存
2)消息队列
3)共享内存
4)信号量机制
5)信号
6)socket
线程:1)等待通知机制
2)共享内存
3)管道
5)并发工具
信号量、读写锁、互斥锁和条件变量
线程的死锁概念 :线程间相互等待临界资源而造成彼此无法继续执行
方式一
1)创建一个线程类的子对象,继承QThread:
2)重写父类的run()方法,在该函数内部编写子线程要处理的具体业务流程
3)在主线程中创建子线程对象,new一个
4)启动子线程,调用start()方法
方式二
1)创建一个新的类,QObject派生
2)类中添加一个公有的自定义成员函数,函数体就是子线程中执行的业务逻辑
3)主线程中创建一个 QThread 对象,就是子线程对象
4)在主线程中创建工作的类对象,不要给创建的对象指定父对象
5)Mywork对象移动到创建的子线程对象中,需要调用QObject类提供的 moveToThread() 方法
注意事项:
业务对象, 构造的时候不能指定父对象
子线程中不能处理ui窗口(ui相关的类)
子线程中只能处理一些数据相关的操作, 不能涉及窗口
2、什么是堆栈
栈区(stack)
堆区(heap)抽象数据结构:后进先出
全局区(静态区)(static)
文字常量区
程序代码区
栈是自动分配释放,一级缓存,类似数组的结构。
堆是由程序员分配释放,二级缓存,速度慢些,先进后出。
3、常用的排序
插入排序、希尔排序、选择排序、冒泡排序、堆排序、快速排序
冒泡排序:
时间复杂度:最好O(n),最坏O(n的2次方)。
空间复杂度:O(1);
4、数组和链表的区别
1)数组连续储存、固定长度、增删需要移动其他元素。链表动态分配,不连续,需要malloc或
new申请内存,不用时要free或delete.
2)数组访问效率高,链表删插效率高
3)数组利用下标定位,查找的时间复杂度是O(1),链表通过遍历定位元素,查找的时间复杂度是O(N)。
4)数组插入和删除需要移动其他元素,时间复杂度是O(N),链表的插入或删除不需要移动其他元素,时间复杂度是O(1)。
5、回调函数的三种典型使用场景:
1)实现函数功能重定义
2)扩展函数功能
3)实现程序分层设计
6、区分static和const
static :
(1) 修饰全局变量:变量只在本模块内可见,在定义不需要与其他文件共享的全局变量时,加上 static 关键字能够有效地降低程序模块之间的耦合,避免不同文件同名变量的冲突,且不会误使用。
(2) 修饰局部变量:变量在全局数据区分配内存空间,编译器自动对其初始化,其作用域为局部作用域,当定义它的函数结束时,其作用域随之结束。
(3) 修饰函数:函数的使用方式与全局变量类似,在函数的返回类型前加上 static,就是静态函数,静态函数只能在声明它的文件中可见,其他文件不能引用该函数,不同的文件可以使用相同名字的静态函数,互不影响。
const :
被 const 修饰的变量是只读变量,本质还是变量,有人称其为常变量,和普通变量的区别在于常变量不能用于左值,其余的用法和普通常量一样,变量名不可以改变。
7、工作中有没有使用过动态库和静态库?能不能简单说下两者的区别?
答:静态库:在链接阶段将汇编生成的目标文件.o与引用库一起链接打包到可执行文件中,可简单看成(.o或者.obj文件的集合)。(1)对函数库的链接是放在编译时期完成的(2)程序在运行时与函数库没有瓜葛,移植方便(3)浪费空间和资源
动态库:(1)将库函数的链接载入推迟到程序运行时期(2)可以实现进程间的资源共享(因此也称为共享库)(3)将一些程序升级变得简单(4)可以真正的做到链接载入完全由程序员在程序代码中控制(显示调用)
8、虚函数:
父类型的指针指向其子类的实例
存在虚函数的类都有一个一维的虚函数表叫做虚表,
类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。
默认构造函数、初始化构造函数、拷贝构造函数、移动构造函数
构造函数为什么不能被声明为虚函数?
虚函数对应一个vtale,这个表的地址是存储在对象的内存空间的。如果将构造函数设置为虚函数,
就需要到vtable 中调用,可是对象还没有实例化,没有内存空间分配,如何调用
9、STL相关知识
STL由6部分组成:容器(Container)、算法(Algorithm)、 迭代器(Iterator)、
仿函数(Function object)、适配器(Adaptor)、空间配制器(Allocator)
容器、算法、迭代器、仿函数、适配器、空间配置器
容器:数组(array) , 链表(list), tree(树),栈(stack), 队列(queue), 集合(set),映射表(map)
string容器
Vector容器是单向开口的连续内存空间,deque则是一种双向开口的连续线性空间
stack是一种先进后出
Queue是一种先进先出
list容器链表是一种物理存储单元上非连续、非顺序的存储结构
set/multiset二叉树
map/multimap二叉树
交换,查找,遍历,复制,修改,反转,排序,合并等
10、智能指针:auto_ptr(17后已遗弃)、unique_ptr、shared_ptr 和 weak_ptr
智能指针本质就是一个类模板,当智能指针对象使用完后,对象就会自动调用析构函数去释放该指针所指向的空间。
shared_ptr采用的是引用计数原理来实现多个shared_ptr对象之间共享资源:
shared_ptr在内部会维护着一份引用计数,用来记录该份资源被几个对象共享。
当一个shared_ptr对象被销毁时(调用析构函数),析构函数内就会将该计数减1。
如果引用计数减为0后,则表示自己是最后一个使用该资源的shared_ptr对象,必须释放资源。
如果引用计数不是0,就说明自己还有其他对象在使用,则不能释放该资源,否则其他对象就成为野指针。
11、指针和引用的区别
指针是一个实体,而引用仅是个别名。
引用只能在定义时被初始化一次,之后不可变,但指针可以改变。
指针可以有多级,但引用只有一级。
引用不能为空,指针可以为空。
sizeof 引用"得到的是所指向的变量(对象)的大小,而"sizeof 指针"得到的是指针本身的大小
指针和引用的自增(++)运算意义不一样
引用是类型安全的,而指针不是 ,引用比指针多了类型检查。
引用没有const,指针有const,const的指针不可变。
访问实体方式不同,指针需要显式解引用,引用编译器自己处理
二、网络编程
1、描述QT的TCP通讯流程
服务端:(QTcpServer)
①创建QTcpServer对象
②监听list需要的参数是地址和端口号
③当有新的客户端连接成功回发送newConnect信号
④在newConnection信号槽函数中,调用nextPendingConnection函数获取新连接QTcpSocket对象
⑤连接QTcpSocket对象的readRead信号
⑥在readRead信号的槽函数使用read接收数据
⑦调用write成员函数发送数据
客户端:(QTcpSocket)
①创建QTcpSocket对象
②当对象与Server连接成功时会发送connected 信号
③调用成员函数connectToHost连接服务器,需要的参数是地址和端口号
④connected信号的槽函数开启发送数据
⑤使用write发送数据,read接收数据
1)长连接的保活问题
标准TCP层协议里把对方超时设为2小时,若服务器端超过了2小时还没收到客户的信息,它就发送探测报文段,若发送了10个探测报文段(每一个相隔75S)还没有收到响应,就假定客户出了故障,并终止这个连接。因此应对tcp长连接进行保活。
2)TCP提供了强制数据立即传送的操作指令push,TCP软件收到该操作指令后,就立即将本段数据发送出去,而不必等待发送缓冲区满;
解决方法二:发送固定长度的消息
解决方法三:把消息的尺寸与消息一块发送
解决方法四:双方约定每次传送的大小
解决方法五:双方约定使用特殊标记来区分消息间隔
解决方法六:标准协议按协议规则处理,如Sip协议
3)粘包问题
TCP产生粘包问题的主要原因是:TCP是面向连接的,所以在TCP看来,并没有把消息看成一条条的,而是全部消息在TCP眼里都是字节流,
因此A、B消息混在一起后,TCP就分不清了。
粘包问题的最本质原因在与接收对等方无法分辨消息与消息之间的边界在哪。我们通过使用某种方案给出边界,例如:
包头加上包体长度。包头是定长的4个字节,说明了包体的长度。接收对先接收包体长度,依据包体长度来接收包体。
解决方法一:TCP提供了强制数据立即传送的操作指令push,TCP软件收到该操作指令后,就立即将本段数据发送出去,而不必等待发送缓冲区满;
解决方法二:发送固定长度的消息
解决方法三:把消息的尺寸与消息一块发送
解决方法四:双方约定每次传送的大小
解决方法五:双方约定使用特殊标记来区分消息间隔
解决方法六:标准协议按协议规则处理,如Sip协议
发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。
指定数据长度的解决方法,主要思路:
我们在数据结构中有个成员代表了长度(消息头),我们准备一个足够大的消息缓冲区(程序中是1024000个字节),循环使用socket中的recv每次读取最多102400个字节,然后把循环接收的消息拼接到消息缓冲区中,直到接收到的消息大于消息头指示的长度,则接收到了一个完整的消息(所以我们的消息缓冲区要比完整的消息还要大才行),进行消息处理。
4)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则是不可靠信道
7、三次握手和四次挥手具体流程
SYN:请求建立连接,FIN:请求断开连接,ACK:确认是否有效, seq:序列号, ack:确认号
1)三次握手
1.客户端向服务端发送⼀个SYN=1(请求建立连接),并生成一个序列号seq=j。
2.服务端接收到SYN=1后,给客户端发送⼀个SYN=1与ACK=1;并将ack置为j+1;同时生成一个序列号seq=k。
3.客户端接收到会检查ack是否为j+1与ACK是否为1,如果是,则会给服务端发送一个ACK=1与ack=k+1,以及自己的序列号seq=j=1; 服务端接收到会检查ACK是否为1与ack是否为k+1,如果是则代表连接建立成功,两者间可以传递数据。
2)四次挥手
1.客户端向服务端发送FIN=1(请求关闭连接),并生成一个序列号seq=x。
2.服务端接收FIN后,向客户端发送ACK=1,ack=x+1,并生成序列号seq=y(客户端无数据发送,但服务器端需发送完最后的数据)。
3.服务端处理完所有数据后,向客户端发送FIN=1与ACK=1,ack=x+1,并生成序列号z,表示服务端现在可以断开连接。
4.客户端收到服务端的数据包后,会向服务端发送ACK=1,seq=x=1,ack=z+1(需要等待2MSL后才可断开连接)。
8、指针和引用的区别
指针是一个实体,而引用仅是个别名。
引用只能在定义时被初始化一次,之后不可变,但指针可以改变。
指针可以有多级,但引用只有一级。
引用不能为空,指针可以为空。
sizeof 引用"得到的是所指向的变量(对象)的大小,而"sizeof 指针"得到的是指针本身的大小
指针和引用的自增(++)运算意义不一样
引用是类型安全的,而指针不是 ,引用比指针多了类型检查。
引用没有const,指针有const,const的指针不可变。
访问实体方式不同,指针需要显式解引用,引用编译器自己处理
9、HTTP 是一种 超文本传输协议
因特网的协议栈由五个部分组成:物理层、链路层、网络层、运输层和应用层
URL(即网址)
浏览器会向DNS(域名服务器,后面会说)提供网址
HTML 称为超文本标记语言
http协议是应用层协议,主要是解决如何包装数据。而tcp协议是传输层协议,主要解决数据如何在网络中传输。
通俗点说,http的任务是与服务器交换信息,它不管怎么连到服务器和保证数据正确的事情。而tcp的任务是保证连接的可靠,它只管连接,它不管连接后要传什么数据。
http协议是建立在tcp之上的,http是无状态的短链接,而tcp是有状态的长链接。
HTTPS:是以安全为目标的 HTTP 通道,是 HTTP 的安全版。HTTPS 的安全基础是 SSL。
SSL 协议位于 TCP/IP 协议与各种应用层协议之间,为数据通讯提供安全支持。
二、HTTP 与 HTTPS 的区别
1、HTTPS 协议需要到 CA (Certificate Authority,证书颁发机构)申请证书,一般免费证书较少,因而需要一定费用。
(以前的网易官网是http,而网易邮箱是 https 。)
2、HTTP 是超文本传输协议,信息是明文传输,HTTPS 则是具有安全性的 SSL 加密传输协议。
3、HTTP 和 HTTPS 使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
4、HTTP 的连接很简单,是无状态的。HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,
比 HTTP 协议安全。(无状态的意思是其数据包的发送、传输和接收都是相互独立的。
无连接的意思是指通信双方都不长久的维持对方的任何信息。)
三、QT相关知识
1、什么是元对象系统
元对象系统是一个基于标准C++的扩展,为QT提供了信号与槽机制、实时类型信息、动态属性系统。
元对象系统的三个基本条件:类必须继承自QObject、类声明Q_OBJECT宏(默认私有)、元对象编译器moc。
信号与槽类似观察者模式;
回调函数的本质是基于"你想让别人的代码执行你的代码,而别人的代码你又不能动"这种产生的;
对象树;
信号与槽的实现是借助了Qt 的元对象系统,元对象系统有一个元对象编译器,
程序编译之前会有一个预处理过程,预处理将一个类/对象中的信号,槽的字符串值分别保存在一个容器中,可能是字符串或者其他的有序容器
第5个参数跟线程相关Qt::AutoConnection
1)Qt信号槽机制的优势
(1)类型安全。需要关联的信号和槽的签名必须是等同的,
(2)松散耦合。
(3)信号和槽机制增强了对象间通信的灵活性。一个信号可以关联多个槽,也可以多个信号关联一个槽。
2)Qt信号槽机制的不足
同回调函数相比,信号和槽机制运行速度有些慢。通过传递一个信号来调用槽函数将会比直接调用非虚函数运行速度慢10倍。原因如下:
(1)需要定位接收信号的对象;
(2)安全地遍历所有的关联(如一个信号关联多个槽的情况);
(3)编组/解组传递的参数;
(4)多线程的时候,信号可能需要排队等待。
然而,与创建对象的new操作及删除对象的delete操作相比,信号和槽的运行代价只是他们很少的一部分。信号和槽机制导致的这点性能损耗,对实时应用程序是可以忽略的。
知道QT事件机制有几种级别的事件过滤吗?能大致描述下吗?
答:根据对Qt事件机制的分析, 我们可以得到5种级别的事件过滤,处理办法. 以功能从弱到强, 排列如下:
1)重载特定事件处理函数.
最常见的事件处理办法就是重载象mousePressEvent(), keyPressEvent(), paintEvent() 这样的特定事件处理函数.
2)重载event()函数.
通过重载event()函数,我们可以在事件被特定的事件处理函数处理之前(象keyPressEvent())处理它. 比如, 当我们想改变tab键的默认动作时,一般要重载这个函数. 在处理一些不常见的事件(比如:LayoutDirectionChange)时,evnet()也很有用,因为这些函数没有相应的特定事件处理函数. 当我们重载event()函数时, 需要调用父类的event()函数来处理我们不需要处理或是不清楚如何处理的事件.
3) 在Qt对象上安装事件过滤器.
安装事件过滤器有两个步骤: (假设要用A来监视过滤B的事件)
首先调用B的installEventFilter( const QOject *obj ), 以A的指针作为参数. 这样所有发往B的事件都将先由A的eventFilter()处理.
然后, A要重载QObject::eventFilter()函数, 在eventFilter() 中书写对事件进行处理的代码.
4) 给QAppliction对象安装事件过滤器.
一旦我们给qApp(每个程序中唯一的QApplication对象)装上过滤器,那么所有的事件在发往任何其他的过滤器时,都要先经过当前这个 eventFilter(). 在debug的时候,这个办法就非常有用, 也常常被用来处理失效了的widget的鼠标事件,通常这些事件会被QApplication::notify()丢掉. ( 在QApplication::notify() 中, 是先调用qApp的过滤器, 再对事件进行分析, 以决定是否合并或丢弃)
5) 继承QApplication类,并重载notify()函数.
Qt 是用QApplication::notify()函数来分发事件的.想要在任何事件过滤器查看任何事件之前先得到这些事件,重载这个函数是唯一的办法. 通常来说事件过滤器更好用一些, 因为不需要去继承QApplication类. 而且可以给QApplication对象安装任意个数的事件。
自定义界面
UI设计器集成了Qt样式表的编辑功能
前景色color
背景色background-color
选中后颜色selection-color
背景图片background-image
选择器:QPushButton 、QDialog QPushButton、QPushButton#btnOk
子控件:drop-down、up-button、down-button
伪状态:hover(鼠标移动到条目上方时),active(处于活动的窗体)
属性:min_width、padding-top、border-width、
1、使用Qt Designer
2、qApp->setStyleSheet("QLineEdit{ background-color: gray}");
自定义QT控件
1、自定义Widget子类QmyBattery
paintEvent()事件
QmyBattery继承于QWidget
Q_UNUSED(event)
QPainter/QRect/QColor
提升法
2、自定义Qt Designer插件
编译器用MSVC2015 32bit
Q_INTERFACES声明了实现接口
Q_PLUGIN_METADATA声明了元数据名称
要把dll和lib放到插件目录下
D:\Qt\Qt5.9.1\Tools\QtCreator\bin\plugins\designer
D:\Qt\Qt5.9.1\5.9.1\msvc2015\plugins\designer
QWT,是一个基于LGPL版权协议的开源项目, 可生成各种统计图
QT和MFC消息机制比较:
mfc的消息机制其实就是消息映射机制,程序员需要将自定义消息和对应的处理函数添加到消息映射表中。通过PostMessage和SendMessage来实现异步和同步消息。
QT的信号槽机制是信号和槽函数通过QObject::connect动态链接上后存储到元对象系统中,通过emit发送信号,对应的槽函数执行。
比较
Qt的信号槽是动态链接的,而MFC的消息映射是静态的
Qt的信号支持自定义参数,且类型安全
在多线程中,MFC需要向已知线程对象发布消息,而Qt可以不考虑多线程之间的信号槽关系
总结
Qt相比较MFC的消息机制,使用起来更方便,最大的优势是Qt支持动态链接信号槽。
四、项目相关
1、定位问题:
请问下,如果软件除了问题(Bug),如何快速定位?主要方法有哪些?
答:打印输出/代码调试/日志记录/分析工具/找同事讨论。
1)二分法定位技巧
无论是有多复杂的代码,利用二分法定位技巧一般都是可以定位到问题所在。
从二分法定位技巧可以延伸出一些具体的处理bug的方法,比如:对输入数据二分、对代码版本二分、注释掉部分代码、在不同位置插入试探性代码、对运行环境二分。
2)IDE调试
IDE的VS debug的功能简直就是立竿见影。它可以加断点,单步调试。
单步调试可以让我们对代码逻辑,执行顺序,以及各种中间结果更加清晰。
至于本身容易出错的BUG,用IDE调试简直是再合适不过了。
3)重新读一遍程序
相对新手程序员来说,如果代码出现bug,可以重新读一遍程序。这种方法是最有效、最快速的 Debug 方式。
4)必杀,重写一遍
如果你发现无论如何也找不到BUG,而且代码只是复杂,本身不是很长,直接重写代码吧!
5)小黄鸭调试法
小黄鸭调试法是程序员们经常使用的调试代码方法之一。
小黄鸭不懂程序,所以我们可以向他解释每一行程序的作用,以此来激发灵感。
内存泄露及解决办法:
什么是内存泄露?
简单地说就是申请了一块内存空间,使用完毕后没有释放掉。(1)new和malloc申请资源使用后,没有用delete和free释放;(2)子类继承父类时,父类析构函数不是虚函数。(3)Windows句柄资源使用后没有释放。
怎么检测?
第一:良好的编码习惯,使用了内存分配的函数,一旦使用完毕,要记得使用其相应的函数释放掉。
第二:将分配的内存的指针以链表的形式自行管理,使用完毕之后从链表中删除,程序结束时可检查改链表。
第三:使用智能指针。
第四:一些常见的工具插件,如ccmalloc、Dmalloc、Leaky、Valgrind等等。
数据库相关
什么是事务
并发控制
原子性、一致性、隔离性、持续性
valueChanged(int)
QMutex mutex;
QMutexLocker locker(&mutex);
waitForReadyRead()
常用的设计结构:
工厂方法 Factory Method 定义了创建对象的接口,让子类决定实例化哪个类
单例 Singleton 确保一个类只有一个实例,并提供一个访问它的全局访问点
原型 Prototype 通过拷贝原型对象创建新的对象。
适配器 Adapter 将一个类的接口转换成希望的另外一个接口,使得原本不兼容的接口可以协同工作。
观察者 Observer 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
单例:懒汉模式和饿汉模式
饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了;线程安全。
而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例;
线程不安全,就只有在实例化之前调用的时候加锁,后面不加锁。
C++新特性主要包括包含语法改进和标准库扩充两个方面,主要包括以下11点:
语法的改进
(1)统一的初始化方法,允许构造函数或其他函数像参数一样使用初始化列表
(2)成员变量默认初始化
(3)类型推导 auto关键字 用于定义变量,编译器可以自动判断的类型(前提:定义一个变量时对其进行初始化)
(4)decltype 求表达式的类型
(5)智能指针 shared_ptr
(6)空指针 nullptr(原来NULL)
nullptr表示空指针,是一个关键词
NULL是老版本的,是一个0的宏
(7)基于范围的for循环
(8)右值引用和move语义 让程序员有意识减少进行深拷贝操作
标准库扩充(往STL里新加进一些模板类,比较好用)
(9)无序容器(哈希表) 用法和功能同map一模一样,区别在于哈希表的效率更高
(10)正则表达式 可以认为正则表达式实质上是一个字符串,该字符串描述了一种特定模式的字符串
(11)Lambda表达式