找工作知识点总结

  1. 自我介绍以及询问问题的基本准备;
  2. 复习项目,项目实现细节,项目背景等;
  3. 复习基本算法:排序(尤其是堆排序和快排的实现),常见动态规划(最长公共子序列,最长递增子序列,编辑距离,第k大的数,Leetcode相关题目,位操作优化等);二分搜索;

双指针,hash表。

  1. C++语言基础:volatile, const, static, 内存泄漏,智能指针,malloc与new,指针与引用,类型转换,c++11的移动语义完美转发;C++内存布局(一般的内存布局,有虚函数的内存布局<如何实现>, 虚继承的布局);C++面向对象(封装多态抽象,构造函数<成员初始化列表>,复制构造函数<NRV优化>,赋值构造函数,重载函数,Inline函数,友元,动态联编,重载操作符,模板,设计String类),接口与抽象类。

Volatile: 对于一段代码中的变量,如果没有加volatile关键字申明,那么编译器将会进行优化,假定该变量首次和第二次出现之间,没有修改,直接预读取之前的值。而加入volatile,则告诉编译器不要进行这样的优化,避免出现寄存器修改,使得每次都从地址中去读取这个值。

  1. Stl相关知识:迭代器实现与Traits技法;内存管理(一级与二级);各种容器的内部实现<vector, list, set, map>, hash_table<hash基础,冲突解决,拉链法>;
  2. 设计模式:单例模式,简单工厂,模板模式,解释器模式,迭代器模式,代理模式,策略模式,装饰模式,观察者模式,适配器模式。
  3. 数据库:索引实现,不同索引比较;事务及事务例子;sql语句执行顺序,sql基本语句;不同引擎对比(myisam,Innodb),优化
  4. 网络:tcp状态转换的图(2MSL时间,time_wait状态,close_wait状态,半关闭状态的出现原因危害及其解决方案;三次握手四次挥手过程及其原因),tcp数据可靠性保证(超时重传发生条件,拥塞处理机制<慢启动,拥塞避免,快速重传,快速恢复>,窗口滑动协议),tcp与udp区别,各层的协议类型;
  5. 网络编程:socket连接各过程以及各个函数<listen的队列,connect的非阻塞模式,关闭的两种方式>;socket的选项(oob,地址重用SO_RESUADDR,SO_LINGER优雅关闭),Io多路复用三种的区别于实现<select,poll,epoll>;epoll的et和Lt模式比较,同步异步,阻塞非阻塞,读写操作(read,write,readv,writev,sendfile)
  6. 多线程与多进程:线程的同步与互斥方法(互斥锁,自旋锁,条件变量,join,信号量,屏障,读写锁区别与使用场景);进程的通信方式(管道,FIFO,信号,消息队列,共享内存<实现原理与进程中地址>,socket,Unix域套接字);协程(setjmp,longjmp),多进程与多线程区别。生产者消费者问题,哲学家问题以及读写问题。
  7. Linux: 进程相关(fork,vfork,wait,cow,进程创建fork,执行exec与退出exit的方法,进程状态图,僵尸进程,孤儿进程,init进程,守护进程<实现原理与方法>, 进程通信,进程关系<组长进程,会话,控制终端>,动态库静态库,进程存储空间布局,进程调度算法),文件系统(inode---文件Io实现,硬链接,软连接,以及文件相关的linux命令及其实现),内存管理(malloc,slab,伙伴系统),信号基本知识,sendfile,fcntl;线程池实现。
  8. 网络模型:reactor,proactor;
  9. 操作系统:死锁《原因,银行家算法》,磁盘调度算法(电梯扫描,最短寻道时间,先来先服务);缺页中断选取(FIFO,LRU,LFU)
  10. Linux命令:性能命令(top,free….),调试相关命令(gdb,strace,core dump,valgrind),makefile书写,gdb常见命令(断点,进入断点,跳过finish,多线程调试如何搞),磁盘命令(iostat,df,du),进程(ps) 网络(netstat) ---(sar, uptime, pmap, ipcs, watch)
  11. 额外:大小端;内存大小(union, struct, class)--字节对齐; c实现封装,继承等如何做。函数调用过程,中断与系统调用,堆栈
  12. 设计:栈及队列(栈实现队列,队列实现栈,最小栈设计; 爬虫系统,微博。
  13. 几种树:b,b+, 二叉树,bst,红黑树,平衡树,234树。

 

数据结构知识点总结

#数组和链表的区别

数组可以处理一组数据类型相同的数据,但不允许动态定义数组的大小,在使用之前必须确定数组的长度,而链表可以动态分配。

逻辑结构上看,数组必须固定长度,不能适应数据动态增减的情况,容易溢出或内存浪费;链表可适应数据动态增减,且可以方便插入、删除。

内存角度来看,数组从栈中分配空间(用new则在堆上创建),对程序员方便快速,但是自由度小;链表从堆中分配空间,自由度大但是申请管理比较麻烦。

从访问方式类看,数组在内存中是连续的存储,因此可以利用下标索引进行访问;链表是链式存储结构,在访问元素时候只能够通过线性方式由前到后顺序的访问,所以访问效率比数组要低。

#Struct和class的区别

在C++中,Struct和class一样能包含成员函数,能够继承多态。区别在于

  1. Struct默认继承访问权限是public而class是private。

默认是public继承还是private继承取决于子类而不是基类

struct可以继承class,同样class也可以继承struct,那么默认的继承访问权限是看子类到底是用的struct还是class。如下:

 struct A{};class B : A{}; //private继承
struct C : B{}; //public继承

  1. struct作为数据结构的实现体,它默认的数据访问控制是public的,而class作为对象的实现体,它默认的成员变量访问控制是private的
  2. “class”这个关键字还用于定义模板参数,就像“typename”。但关键字“struct”不用于定义模板参数。

#进程和线程的区别

进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.线程是进程的一个实体,CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.

进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

进程间通信方式

  1. 管道(pipe,流管道(s_pipe)和有名管道(FIFO):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。有名管道允许无亲缘关系进程间的通信。
  2. 信号(signal):信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
  3. 消息队列:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  4. 共享内存:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信
  5. 信号量(semophore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  6. 套接字(socket):套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信

http://blog.csdn.net/gatieme/article/details/50908749

线程间通信方式

  1. 锁机制:包括互斥锁、条件变量、读写锁
       *互斥锁提供了以排他方式防止数据结构被并发修改的方法。
       *读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
       *条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
  2. 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
  3. 信号机制(Signal):类似进程间的信号处理
        线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。

 

#TCP/UDP

Tcp与udp区别

TCP提供可靠的通信传输,而UDP则常被用于让广播和细节控制交给应用的通信传输。

TCP充分实现数据传输时各种控制功能,可以进行丢包的重发控制,还可以对次序乱掉的分包进行顺序控制。而这些在UDP中都没有。此外,TCP作为一种面向有连接的协议,只有在确认通信对端存在时才会发送数据,从而可以控制通信流量的浪费。TCP通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输。

TCP用于在传输层有必要实现可靠性传输的情况。由于它是面向有连接并具备顺序控制、重发控制等机制的。所以它可以为应用提供可靠传输。另一方面,UDP主要用于那些对高速传输和实时性有较高要求的通信或广播通信。

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则是不可靠信道

 

UDP实现可靠传输

1. RUDP(Reliable UDP)

可靠用户数据报协议(RUDP)是一种基于可靠数据协议(RDP)的简单分组传输协议,用于传输 IP 网络间的电话信号。RUDP 提供一组数据服务质量增强机制,如拥塞控制的改进、重发机制及淡化服务器算法等,从而在包丢失和网络拥塞的情况下, RTP 客户机(实时位置)面前呈现的就是一个高质量的 RTP 流。在不干扰协议的实时特性的同时,可靠 UDP 的拥塞控制机制允许 TCP 方式下的流控制行为。为了与网络 TCP 通信量同时工作, RUDP 使用类似于 TCP 的重发机制和拥塞控制算法

2. RTPReal Time Protocol

RTP,实时协议被用来为应用程序如音频,视频等的实时数据的传输提供端到端(end to end)的网络传输功能。传输的模型可以是单点传送或是多点传送。数据传输被一个姐妹协议——实时控制协议(RTCP)来监控,后者允许在一个大的多点传送网络上监视数据传送,并且提供最小限度的控制和识别功能。

3. UDT (UDP-based Data Transfer Protocol,简称UDT)

基于UDP的数据传输协议是一种互联网数据传输协议。UDT的主要目的是支持高速广域网上的海量数据传输,而互联网上的标准数据传输协议TCP在高带宽长距离网络上性能很差。 顾名思义,UDT建于UDP之上,并引入新的拥塞控制和数据可靠性控制机制。UDT是面向连接的双向的应用层协议。它同时支持可靠的数据流传输和部分可靠的数据报传输。 由于UDT完全在UDP上实现,它也可以应用在除了高速数据传输之外的其它应用领域,例如点到点技术(P2P),防火墙穿透,多媒体数据传输等等。

#琐碎知识点

用户态切换到内核态的方法

系统调用、异常、中断。

Ps:用户态的程序可以通过三种方式访问内核态的资源:系统调用、库函数、shell脚本。

 

局部变量能否和全局变量重名?

答:能,局部会屏蔽全局。要用全局变量,需要使用"::" 。局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。对于有些编译器而言,在同一个函数内可以定义多个同名的局部变量,比如在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那个循环体内

如何引用一个已经定义过的全局变量?

答:extern 。可以用引用头文件的方式,也可以用extern关键字,如果用引用头文件方式来引用某个在头文件中声明的全局变理,假定你将那个变写错了,那么在编译期间会报错,如果你用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在连接期间报错。

 

算法知识点总结

链表反转

使用3个指针遍历单链表,逐个链接点进行反转


操作系统

#死锁

  1. 什么是死锁

所谓死锁,是指多个进程循环等待它方占有的资源而无限期地僵持下去的局面。eg: 进程A占有资源R1,等待进程B占有的资源Rr;进程B占有资源Rr,等待进程A占有的资源R1。而且资源R1和Rr只允许一个进程占用,即:不允许两个进程同时占用。结果,两个进程都不能继续执行,若不采取其它措施,这种循环等待状况会无限期持续下去,就发生了进程死锁。

  1. 死锁产生的原因

计算机系统产生死锁的根本原因就是资源有限且操作不当。即一种原因是系统提供的资源太少了,远不能满足并发进程对资源的需求。另一种原因是由于进程推进顺序不合适引发的死锁。

  1. 产生死锁的必要条件

计算机系统只有同时具备以下四个条件才会发生死锁。

〈1〉互斥条件。即某个资源在一段时间内只能由一个进程占有,不能同时被两个或两个以上的进程占有。这种独占资源如CD-ROM驱动器,打印机等等,必须在占有该资源的进程主动释放它之后,其它进程才能占有该资源。这是由资源本身的属性所决定的。如独木桥就是一种独占资源,两方的人不能同时过桥。

  〈2〉不可抢占条件。进程所获得的资源在未使用完毕之前,资源申请者不能强行地从资源占有者手中夺取资源,而只能由该资源的占有者进程自行释放。如过独木桥的人不能强迫对方后退,也不能非法地将对方推下桥,必须是桥上的人自己过桥后空出桥面(即主动释放占有资源),对方的人才能过桥。

  〈3〉占有且申请条件。进程至少已经占有一个资源,但又申请新的资源;由于该资源已被另外进程占有,此时该进程阻塞;但是,它在等待新资源之时,仍继续占用已占有的资源。还以过独木桥为例,甲乙两人在桥上相遇。甲走过一段桥面(即占有了一些资源),还需要走其余的桥面(申请新的资源),但那部分桥面被乙占有(乙走过一段桥面)。甲过不去,前进不能,又不后退;乙也处于同样的状况。

  〈4〉循环等待条件。存在一个进程等待序列{P1,P2,...,Pn},其中P1等待P2所占有的某一资源,P2等待P3所占有的某一源,......,而Pn等待P1所占有的的某一资源,形成一个进程循环等待环。就像前面的过独木桥问题,甲等待乙占有的桥面,而乙又等待甲占有的桥面,从而彼此循环等待。

  1. 死锁的预防

1)打破互斥条件。允许进程同时访问某些资源,不可取。

2)打破不可抢占条件。允许进程强行从占有者那里夺取某些资源,实现困难降低性能。

3)打破占有且申请条件。可以实行资源预先分配策略。即进程在运行前一次性地向系统申请它所需要的全部资源。如果某个进程所需的全部资源得不到满足,则不分配任何资源,此进程暂不运行。只有当系统能够满足当前进程的全部资源需求时,才一次性地将所申请的资源全部分配给该进程。由于运行的进程已占有了它所需的全部资源,所以不会发生占有资源又申请资源的现象,因此不会发生死锁。但是,这种策略也有如下缺点:

l  在许多情况下,一个进程在执行之前不可能知道它所需要的全部资源。这是由于进程在执行时是动态的,不可预测的;

l  资源利用率低。无论所分资源何时用到,一个进程只有在占有所需的全部资源后才能执行。即使有些资源最后才被该进程用到一次,但该进程在生存期间却一直占有它们,造成长期占着不用的状况。这显然是一种极大的资源浪费;

l  降低了进程的并发性。因为资源有限,又加上存在浪费,能分配到所需全部资源的进程个数就必然少了。    

 

#逻辑地址

物理地址:用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。

逻辑地址:逻辑地址指的是机器语言指令中,用来指定一个操作数或者是一条指令的地址。

线性地址:跟逻辑地址类似,它也是一个不真实的地址,如果逻辑地址是对应的硬件平台段式管理转换前地址的话,那么线性地址则对应了硬件页式内存的转换前地址。

 

C++

#虚函数

virtual在函数中的使用限制

  • 普通函数不能是虚函数,也就是说这个函数必须是某一个类的成员函数,不可以是一个全局函数,否则会导致编译错误。
  • 静态成员函数不能是虚函数, static成员函数是和类同生共处的,他不属于任何对象,使用virtual也将导致错误。
  • 内联函数不能是虚函数 如果修饰内联函数 如果内联函数被virtual修饰,计算机会忽略inline使它变成存粹的虚函数。
  • 构造函数不能是虚函数,否则会出现编译错误。

虚函数表指针

类中除了定义的函数成员,还有一个成员是虚函数表指针(占四个基本内存单元),这个指针指向一个虚函数表的起始位置,这个表会与类的定义同时出现,这个表存放着该类的虚函数指针,调用的时候可以找到该类的虚函数表指针,通过虚函数表指针找到虚函数表,通过虚函数表的偏移找到函数的入口地址,从而找到要使用的虚函数。

父类和子类出现同名函数称为隐藏。父类和子类出现同名虚函数称为覆盖。

虚析构函数

特点:当我们在父类中通过virtual修饰析构函数之后,通过父类指针指向子类对象,通过delete接父类指针就可以释放掉子类对象。

解决问题:通过父类指针操作子类对象的成员函数的时候是没有问题的,可是在销毁对象内存的时候则只是执行了父类的析构函数,子类的析构函数却没有执行,这会导致内存泄漏,即多态中的内存泄漏问题。

原理:如果父类当中定义了虚析构函数,那么父类的虚函数表当中就会有一个父类的虚析构函数的入口指针,指向的是父类的虚析构函数,子类虚函数表当中也会产生一个子类的虚析构函数的入口指针,指向的是子类的虚析构函数,这个时候使用父类的指针指向子类的对象,delete接父类指针,就会通过指向的子类的对象找到子类的虚函数表指针,从而找到虚函数表,再虚函数表中找到子类的虚析构函数,从而使得子类的析构函数得以执行,子类的析构函数执行之后系统会自动执行父类的虚析构函数。这个是虚析构函数的实现原理。

纯虚函数

class Shape

{

public:

    virtual  double calcArea()//虚函数

    {....}

    virtual  double calcPerimeter()=0;//纯虚函数

    ....

};

纯虚函数没有函数体,同时在定义的时候函数名后面要加“=0”。

纯虚函数的实现原理:

      在虚函数原理的基础上,虚函数表中,虚函数的地址是一个有意义的值,如果是纯虚函数就实实在在的写一个0。

含有纯虚函数的类被称为抽象类,纯虚函数没有函数体,所以抽象类不允许实例化对象,而对于一些具体的类来说,我们要求必须实现那些要求(纯虚函数),使之成为有具体动作的类。抽象类的子类也可以是一个抽象类。抽象类子类只有把抽象类当中的所有的纯虚函数都做了实现才可以实例化对象。

如果在抽象类当中仅含有纯虚函数而不含其他任何东西,我们称之为接口类

  1. 没有任何数据成员
  2. 仅有成员函数
  3. 成员函数都是纯虚函数

虚函数和纯虚函数的区别

l  类里声明为虚函数的话,这个函数是实现的,哪怕是空实现,它的作用就是为了能让这个函数在它的子类里面可以被重载,这样的话,这样编译器就可以使用后期绑定来达到多态了;纯虚函数只是一个接口,是个函数的声明而已,它要留到子类里去实现。

l  虚函数在子类里面也可以不重载的;但纯虚必须在子类去实现,这就像Java的接口一样。通常我们把很多函数加上virtual,是一个好的习惯,虽然牺牲了一些性能,但是增加了面向对象的多态性,因为你很难预料到父类里面的这个函数不在子类里面不去修改它的实现。

l  虚函数的类用于“实作继承”,继承接口的同时也继承了父类的实现。当然我们也可以完成自己的实现。纯虚函数的类用于“介面继承”,主要用于通信协议方面。关注的是接口的统一性,实现由子类完成。一般来说,介面类中只有纯虚函数的。

#类的内存计算(sizeof)

 

posted @ 2017-10-14 10:04  thatdor  阅读(273)  评论(0编辑  收藏  举报