[关闭]
@dantangfan 2015-01-21T17:14:04.000000Z 字数 15769 阅读 1805

乱七八糟问题篇


里面大多数问题都是从学习过程中从书上或者网上收集的内容,但是并没有说明出处,麻烦不要散播。谢谢。

  1. 程序如下
  1. int i=1;
  2. main(){
  3. int i=i;
  4. }//能通过编译,但是i是一个未定义值。
  1. 程序如下
  1. main(){
  2. int arr[]={6,7,8,9};
  3. int *ptr=arr;
  4. *(ptr++) += 123;
  5. printf(“%d,%d”,*ptr,*(ptr++));
  6. }

第三句等价与*ptr += 123;prt++;这个时候ptr指向7,printf中从右往左先计算ptr++。所以最后答案是输出8,8.

  1. sizeof和strlen的区别
    (1)sizeof是运算符,strlen是函数
    (2)sizeof可以用类型做参数,strlen只能用char*做参数并且以'\0'结尾
    (3)数组作为sizeof参数不退化,但作为strlen参数退成指针
    (4)sizeof在编译阶段就计算,strlen在运行时候计算

  2. 指针和引用的区别

    • a非空区别。任何情况下都不能指向空引用,一个引用总是指向某个对象。
    • b合法性区别。使用引用前不需要检测合法性,而指针需要判断是否是空
    • c可修改区别。指针可以指向其他对象,引用只能是当前对象
    • d应用区别。使用指针:对象有可能指向空、可能改变指向的对象。其他地方都用引用。
      (其实就两个区别,是否可以为空,是否可以改变指向的对象)
  3. 程序输出
  1. #include <iostream>
  2. using namespace std;
  3. class A{
  4. public:
  5. int a;
  6. A():a(1){}
  7. void print(){cout<<a;}
  8. };
  9. class B:public A{
  10. public:
  11. int a;
  12. B():a(2){}
  13. };
  14. int main(int argc, const char *argv[])
  15. {
  16. B b;
  17. b.print();
  18. cout<<b.a;
  19. return 0;
  20. }

B类的a覆盖了A类的a,但是在构造的时候要先构造A类的a,所以输出是12。重点需要说明的是构造函数从最初开始构造,各个类的同名变量没有形成覆盖,都是独立的变量。所以在b.print的时候,b中没有print函数,只有从A中找,找到了print就使用A中的a。
16. 什么是多态
一个接口,多种实现。在运行的过程中才决定调用的函数。允许将父对象设置称为和他的一个或更多的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给他的子对象的特性以不同的方式运作。简单的说就是允许将子类的指针赋值给父类指针。多态是由虚函数实现的。
17. 共有继承和私有继承
c++中继承只能继承public和protected中的属性和方法。如果是私有继承,那么继承下来的属性或者方法都只能在类的内部使用,是子类的private方法或者属性。
公有继承:对派生类来说,基类的public都被继承下来并且是public的,protected也被继承下来,但是是private的。
私有继承:对派生类来说,基类的public和protected都被继承下来,但是是private的。
受保护继承:对派生类来说,基类的public和protected都被继承下来,但是都是protected的

  1. static的作用
    • a函数体内static变量的作用范围是函数体,不同于auto,该变量的内存只能被分配一次,下次调用时依旧维持上次值。
    • b模块内的static全局变量可以被模块内所有函数访问,但不能被模块外的函数访问
    • c模块内的static函数只能被模块内的其他函数调用,这个函数的使用范围被限制在声明他的模块内
    • d类中的static成员变量属于整个类,类的所有对象只有一个拷贝
    • e类的static成员函数属于整个类,这个函数不接收this指针,所以只能访问static变量。
  2. 解释操作系统中作业、进程、线程、管程的定义
    作业:用户在一次解题或者一个事物处理过程中要求计算机系统所工作的集合。它包括用户程序、所需要的数据以及控制命令等。作业是由一系列有序的步骤组成的。
    进程:一个程序在一个数据集合上的一次运行过程。所以一个程序在不同数据集合上运行,乃至一个程序在同样的数据集合上运行多次都是不同的进程。
    线程:是进程中的一个实体,是被系统独立调度和执行的基本单位
    管程:光成实际上是定义了一个数据结构和在该数据结构上的能为并发进程所执行的一组操作,这组操作能同步进程和改变管程中的数据。
  3. 死锁的解除与预防方法
    a采用静态分配策略,破坏部分分配条件(占有并等待)
    b允许进程剥夺使用其他进程占有的资源,破坏不可抢占条件
    c采用资源有序分配法,破坏循环等待条件
    注意:互斥条件不能被破坏!!
  4. 描述进程的三种基本状态
    a就绪(ready):当进程已被分配到除CPU之外的所有必要资源,只要获得处理器就可以执行的状态
    b执行(running):进程已经获得处理器,起程序正在处理器上运行
    c阻塞(blocked):正在执行的进程,由于某个事件的发生而无法执行,便放弃处理器出于阻塞状态,引起阻塞的原因有等待I/O、缓冲区不足、等待信号等
  5. 进程和线程的区别
    进程是程序的一次执行,线程可以理解为进程执行的一段程序片,在多任务环境中下面的概念可以帮我们理解两者的区别
    进程间是独立的,这表现在内存空间、上下文环境上;线程运行在进程空间内。一般来讲,进程无法突破进程边界存取其他进程内的存储空间;而线程出于进程空间内,所以同一进程产生的线程共享同一内存空间。
    同一进程的两段代码不能同时执行,除非引入线程
    线程是属于进程的,当进程退出该进程锁产生的线程都会被强制退出并清除。线程占用的资源要少于进程占用的资源。进程和线程都可以有优先级。
  6. 简述windows内存管理的几种方式的优缺点
    主要方式有:页式管理,段式管理,段页式管理
    • 页式管理:基本原理是将各进程的虚拟空间划分成若干个长度相等的page;页式管理把内存空间按页的大小划分成片或者页面,然后把页式虚拟地址与内存地址建立一一对应的页表;并用相应的硬件地址变换机构来解决离散地址变换问题。采用请求调页或者预调页技术来实现内外存存储器的统一管理。优点是没有外部碎片,内部碎片不超过页大小。缺点:程序全部装入内存,要有相应的硬件支持。例如地址变换机构却也中断的产生和选择淘汰页面等都要求有相应的硬件支持。这增加了成本和系统开销
    • 段式管理:把程序按内容霍过程函数关系分段,每段有自己的名字。一个用户作业霍进程锁包含的段对应一个二维线形虚拟空间,也就是一个二维虚拟存储器。短时管理程序是以段为单位分配内存,然后通过地址映射机构把段式虚拟地址转换为实际内存物理地址。优点是可以分别编写和编译,可以针对不同类型的段采用不同的保护,可以按段为单位来进行共享,包括通过动态链接来进行代码共享。缺点是会产生碎片
    • 段页式管理:系统必须为每个作业或进程简历一张段表以管理内存分配与释放、缺段处理等。另外由于每个段又被分成了若干页。每个段又必须简历一张页表以把段中的虚页变换成内存中的实际页面。显然与页式管理时相同,页表中也要有相应的实现缺页中断处理和页面保护等功能的表项。段页式管理是段式管理和页式管理方案的结合而成的,所以具有他们两个的优点。但反过来说,由于管理软件的增加,复杂性和开销也就随之增加了。另外需要的硬件以及占用的内存也有增加。使得执行速度下降。
  7. new/delete、malloc/free的区别
    delete会调用对象的析构函数,free只会释放内存,new调用构造函数。malloc和free是标准库函数,new/delete是c++运算符。都可以用于动态分配内存。对于非内部数据对象而言,malloc/free不能满足动态对象的要求。对象在创建时需要自动调用构造函数,在消亡的时候调用析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能把构造函数和析构函数的任务强加到上面。因此c++需要一个能完成动态分配内存和初始化工作的运算符new,以及一个能完成释放内存工作的delete。
  8. 面向对象的三个特点:封装,继承,多态
    封装:将客观事物抽象成类,每个类对自身数据和方法实行protection(public等)
    继承:广义的继承有三种形式:实现继承(使用基类的属性和方法而无需格外编码)、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承(只使用属性和方法,实现滞后到子类实现)。前两种(类继承)和后一种(对象组合==>接口继承以及纯虚函数)构成了功能复用的两种方式
    多态:是将父类对象设置成和一个或多个他的子类相等的技术,赋值之后,父类对象就可以根据当前赋值给他的子对象的特性以不同的方式运作。简单的说,就是:允许将子类类型的指针赋值给父类类型的指针。
  9. 多态的作用:
    a隐藏实现细节,使得代码模块化;扩展代码模块,实现代码重用
    b接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性的正确调用

  10. 代码输出

  1. main(){
  2. printf(“%f”,5);
  3. printf(“%d”,5.0);
  4. }

首先5是int型的,在stack中分配4个字节存放,%f是double(printf中float会自动转换成double),因此在stack中读取8个字节,所以内存访问越界了,会发生什么情况不可预测。而第二个5.0占8字节,由于小段规则,%d只能读取低4字节。就输出0.
值得注意的是,现代编译器gcc等都不会通过编译。

  1. this指针
    • this指针的本质是一个函数参数,知识编译器隐藏起形式的,语法层面上的参数。this只能在成员函数中调用,全局函数、静态函数都不能用this。实际上,成员函数默认的第一个参数都是T* const this。
      Class A{public: int func(int p){}};
      其中func的原型在编译器看来是这样的int func(A* const this, int p);
    • this在成员函数的开始前构造,在成员函数结束后清除。这个声明周期同任何一个函数的参数是一样的,没有任何区别。当调用一个类的成员函数时,编译器将类的指针作为函数的this指针传递进去,如:
  1. A a;
  2. a.func(10);
  3. //将会被编译成
  4. A::func(&a,10);

看起来和静态函数没有差别,不过,还是有区别的。编译器通常会对this指针做一些优化,因此this指针的传递效率比较高
- this指针并不占用对象的空间。this相当于非静态函数的一个隐匿参数,不占用对象空间。他跟对象之间没有包含关系,只是当前调用函数的对象被它指向而已。所有成员函数的参数,不管是不是隐含的,都不会占用对象空间,只是会占用参数传递时的栈空间或者直接占用一个寄存器。
- this指针是什么时候创建的。this在成员函数的开始前构造,执行结束之后清除。
- this指针存放在何处。他会因编译器的不同而放在不同的地方,可能是堆,栈或者寄存器。
- 我们只有获得一个对象后,才能通过对象获得this指针。this指针只有在成员函数中才有定义,因此你获得一个对象后也不能通过对象使用this指针。所以我们无法知道一个对象的this指针的位置。当然,在成员函数里是可以知道的,直接&this

  1. 静态路由和动态路由
    静态路由是管理员手动配置的,当网络拓扑结构发生变化的时候需要手动修改信息。在缺省状态下是私有的,不会传播给其他路由器。一般适用于比较简单的网络环境,这种环境中,网络管理员易清楚了解拓扑结构,便于设置。
    动态路由是指路由器能自动的建立路由表,并且能根据实际情况自动调整。主要依赖于对路由表的维护和路由器之间的信息交换,适用于大网络
    策略路由可以说是一种高级的静态路由,但是又与静态路由不同。普通的静态路由(也包括动态路由)是按照数据包的目的地址来进行路由,而策略路由是按照数据包的源地址来进行路由。在同一台路由器上如果配置了策略、静态、动态三种路由,路由器接口首先对入站的数据包源地址进行判断有没有匹配在此接口上配置的策略路由的数据流,如果有,则按照策略路由的配置转发数据包。如果没有,则按普通数据包情况路由。具体是静态路由协议优先还是动态路由协议优先(去往同一个目的地址根据路由协议的不同有多条路径),要看你在此路由器上定义的管理具体大小,管理距离越小则此种路由协议的可信度越高,则优先选用该种路由协议。而管理距离的默认值又根据各厂家路由器的不同而不同。

  2. 网络层和数据链路层
    网络层传输的是IP包, 数据链路层传输的是帧。
    数据链路层值负责传输数据,不理会格式、内容、编码等。保证传输的可靠性
    网络层要负责内容,格式和编码
    网络层就像秘书,而链路层就像邮递员。
    交换机工作在链路层,路由器工作在网络层,集线器工作在物理层,路由器设备=路由器+交换机

  3. ip地址分类
    IP被分为5类(A,B,C)还有D,E他们是留给IAB委员使用的就不说了.
    A的范围1.0.0.0~126.255.255.555
    B的范围128.1.0.0~191.254.255.255
    C的范围192.0.1.0~233.255.254.0

    • 1.A类IP地址
      一个A类IP地址是指,在IP地址的四段号码中,第一段号码为网络号码,剩下的三段号码为本地计算机的号码。如果用二进制表示IP地址的话,A类IP地址就由1字节的网络地址和3字节主机地址组成,网络地址的最高位必须是“0”。A类IP地址中网络的标识长度为7位,主机标识的长度为24位,A类网络地址数量较少,可以用于主机数达1600多万台的大型网络。
    • 2.B类IP地址
      一个B类IP地址是指,在IP地址的四段号码中,前两段号码为网络号码,剩下的两段号码为本地计算机的号码。如果用二进制表示IP地址的话,B类IP地址就由2字节的网络地址和2字节主机地址组成,网络地址的最高位必须是“10”。B类IP地址中网络的标识长度为14位,主机标识的长度为16位,B类网络地址适用于中等规模规模的网络,每个网络所能容纳的计算机数为6万多台。
    • 3.C类IP地址
      一个C类IP地址是指,在IP地址的四段号码中,前三段号码为网络号码,剩下的一段号码为本地计算机的号码。如果用二进制表示IP地址的话,C类IP地址就由3字节的网络地址和1字节主机地址组成,网络地址的最高位必须是“110”。C类IP地址中网络的标识长度为21位,主机标识的长度为8位,C类网络地址数量较多,适用于小规模的局域网络,每个网络最多只能包含254台计算机。
    • 除了上面三种类型的IP地址外,还有几种特殊类型的IP地址,TCP/IP协议规定,凡IP地址中的第一个字节以“lll0”开始的地址都叫多点广播地址。因此,任何第一个字节大于223小于240的IP地址是多点广播地址;IP地址中的每一个字节都为0的地址(“0.0.0.0”)对应于当前主机;IP地址中的每一个字节都为1的IP地址(“255.255.255.255”)是当前子网的广播地址;IP地址中凡是以“llll0”的地址都留着将来作为特殊用途使用;IP地址中不能以十进制“127”作为开头,27.1.1.1用于回路测试,同时网络ID的第一个6位组也不能全置为“0”,全“0”表示本地网络。
      9.常用设计模式
      singleton(单例模式):有些时候,允许自由创建某个类的实例是没有意义的,还可能造成系统性能的下降。如果一个类自始至终只能创建一个实例,则这个类被称为单例类,这种设计模式被称为单例模式。
      一般建议将单例模式的方法命名为getInstance(),这个方法的返回值类型肯定是类的类型了,该方法可以有参数,这些参数可能是类实例需要的,当然大多数情况下不需要参数。
  1. Class Singleton{
  2. private:
  3. Singleton(){}
  4. static Singleton *instance;
  5. public:
  6. static Singleton *getInstance(){
  7. if(instance==NULL)
  8. instance = new Singleton;
  9. return instance;
  10. }
  11. };
  12. Singleton *S = Singleton::getInstance();

factory(工厂模式):
看不懂,不看了
10. 数据库范式
1NF:字段不可分
2NF:有主键,非主键字段依赖主键
3NF:非主键字段不能相互依赖
例题:
设有关系模式R(U,F),其中: U={A,B,C,D,E},F={A→D,E→D,D→B,BC→D,DC→A } (1) 出R的候选关键字 (2) 判断R最高为几级范式? (3) 若R不是3NF,将R分解为3NF
a在F中我们不难看出C,E没有谁决定它,所以CE一定是候选关键字,可见CE就能决定ABD了,所以主键就是CE
b首先肯定是1NF,简单理解就是F中ABCDE都有;其次也是2NF因为F中不存在部分依赖,也就是没有AB->C,B->C这种形式出现;但是不是3NF,因为F中存在传递依赖。
11. 编译原理和正则表达式
(1)词法分析:词法分析器根据词法规则识别出源程序中各个记号token,每个token代表一类单词lexeme。源程序中常见的记号可分为几类:关键字、标识符、字面量、特殊符号。词法分析输入的是源程序,输出的是识别的记号流。词法分析的任务是把源文件的字符流转换成记号流。
(2)语法分析:根据词法分析识别出记号流中的结构(短语、句子),并构造一颗能反映该结构的语法树。
(3)语义分析:根据语义规则对语法分析树中的语法单元进行静态语义检查,类型检查和转换等。其目的在于保证语法正确的的结构在语义上也是合法的。
(4)中间代码生成:根据语义分析的输出生成中间代码。可以有若干形式,他们的共同特征是与具体机器无关。最常用的就是三地址码,它的一种实现方式是四元式。优点是便于阅读,便于优化
(5)中间代码优化:由于编译器将源程序翻译成中间代码的工作是机械的、按固定模式进行的,因此,生成的中间代码往往在时间和空间上都有极大的浪费。
(6)目标代码生成:是编译器的最后一个阶段,需要考虑以下几个问题:计算机的系统结构、指令系统、寄存器的分配以及内存的组织等。目标代码可以有多种形式:汇编语言、二进制代码、内存形式
(7)符号表管理:符号表的作用是源程序中符号的必要信息,并加以合理组织,从而在编译器的各个阶段能对他们进行快速准确的查找和操作。
(8)出错处理:可以分为静态错误和动态错误。所谓动态错误,是指源程序中的逻辑错误,往往发生在运行时,也被称作动态语义错误,如被除数为0,下标越界等。静态错误分为语法错误和静态语义错误。语法错误是指有关语言结构上的错误,如单词拼写、表达式缺少操作数、begin和end不匹配。静态语义错误是指分析源程序时可发现的语义上的错误,如加法的两个变量一个是整数、一个是数组。

  1. 抢占式调度和轮询调度

    • 轮询调度是每次把用户的请求轮流分配给服务器,从1到n,然后重新开始循环。只有在当前任务主动放弃cpu的情况下才允许其他任务使用cpu。优点是简洁,无需记录当前所有链接的状态,是无状态调度,但不利于后面请求得到及时处理
    • 抢占式允许高优先级打断当前任务,抢占cpu。有利于后面的请求的及时响应,但实现复杂,而且可能使低优先级长时间的不到处理
  2. linux僵尸进程如何产生,有何危害,如何避免
    一个进程在调用exit结束自己生命的时候其实并没有真正销毁,而是留下一个称为僵尸进程的数据结构,在linux进程状态中,僵尸进程是特殊的一种,它放弃了所有的内存空间,没有代码也不能调度,只在进程列表中保存一个位置。它需要父进程来为他收尸,如果父进程没有使用wait等函数等待子进程结束,它就会一直保存僵尸状态,如果父进程结束,init进程会自动接手这个进程,还是可以为它收尸的。如果父进程是一个不会结束的循环,那子进程就会一直僵尸。我们可以用wait等函数等待子进程返回来避免僵尸

  3. 面向对象的三个基本要素和五种主要的设计原则
    基本要素:继承、封装、多态
    基本原则:

    • 单一职责原则,一个类最好值做一件事,只有一个引起它的变化。这可以看作是低耦合、高内聚在面向对象的引
    • 开放封闭原则,软件实体应该是可以拓展但不能修改的,也就是拓展对外开放,修改对内封闭。
    • liskow原则:子类必须能替换基类
    • 依赖倒置原则:依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者都依赖于抽象;抽象不依赖于具体,而具体依赖于抽象
    • 接口隔离原则:使用多个小的专门的接口,而不要使用一个大的总接口。
  4. 简述DNS解析过程
    首先,客户端发出DNS请求翻译ip地址或者主机名,DNS收到请求后:

    • 检查DNS服务器的缓存,如果查到,即向客户端发起应答
    • 若没查到,则在数据库中查询,如果查到,即向客户端相应
    • 若没查到,则将请求发给根dns服务器,并依次从根查到顶级域,由顶级域查到二级,二级查到三级,,,直到找到为止,向客户机所在的dns服务器发起应答,服务器收到后在缓存中储存,然后将解析结果发给客户机
    • 如果没找到,返回错误信息
  5. 对三次握手和四次挥手的理解

    • 为什么要三次握手:避免过时的重复连接再次建立时造成的混乱。比如,客户在某个时刻向服务端发起了一个请求,即一个SYN包。该包由于某种原因未能在链接建立超时之前到达服务器,这个时候客户端就会主动放弃链接,并且释放与该链接有关的所有数据结构。需要注意的是,上面的那个SYN虽然没能准时到达,但是它并没有消失,并且能在其生存周期内到达服务器,这个时候服务器就发一个ACK到客户端,客户端收到这个ACK之后找不到相应的数据结构,于是就会发一个RESET给服务端,这个时候服务器就知道这是一个超时的链接。
    • 为什么要有四次挥手:假设主动断开链接的客户端(TCP是全双工,服务器主动断开也适合下面讨论)。服务器收到客户端发来的FIN后,不能马上就断开链接,因为他可能还没有把之前收到的数据交给应用程序处理。因此服务器端就需要等一会儿再通知客户端断开链接,这一段等待时间就确保了之前的所有数据都交给应用程序处理了。也正是由于这段等待的时间,使得服务器端需要主动发一个FIN给客户端来告知链接可以断开了。由于在TCP中收到一个一个数据包(不带数据的ACK除外)都需要确认,所以服务器在收到客户端发送的FIN后需要马上发送一个ACK,不然客户端就会认为它发送的FIN丢失了,就会重发。因此,断开比建立多的一个数据包就是服务器为了保证收到的所有数据都已经交给应用程序处理而发出的那个FIN包。
    • 在四次挥手的过程中,主动断开链接的一方在收到服务器端发来的FIN之后,要进入一个TIME_WAIT状态,时间为两倍数据在网络中传输的时间(2MSL),之后才会放开与链接相关的所有数据结果。原因有二:首先是主动断开方在收到服务器发送的FIN之后要发送ACK,但是这个ACK有可能会丢失,这个时候服务器就会重新发送一个FIN,如果这个之前已经把数据结构丢失了,自然就无法处理这个FIN。第二个原因是如果直接释放相关数据结构,那么就意味着该IP和端口可以复用来建立一个新的链接了。这个时候就可能有个问题,由于旧的数据包还在网络中传输并且能在生存周期中达到服务器,这个时候服务器就不知道数据包是旧的还是新的。
  6. Epoll机制相关概念(Epoll与Select机制区别),这个概念许多面试官都会问起. epoll模型及优缺点?
    主要有3点,对应于select的3个缺点:1连接数受限 2 查找配对速度慢 3数据由内核拷贝到用户态。
    后台开发没能必考!!

  7. 各种STL容器的实现方式
    vector的容量增长的题目,vector a; push_back八次对象,求总共调用多少次拷贝构造函数。另外问到vecter和set的底层实现
    vector初始大小是0,之后可能按照2倍数增长g++/gun下测试,但实际上可能不是这样的,v.capacity()
    vector是动态数组,在堆上分配内存,元素连续存放,有保留内存,如果大小减小后,内存不会自动释放。
    list双向链表,用链表节点实现
    deque双向队列=vector+list
    map平衡二叉树/红黑树实现
    set红黑树实现,为什么不用hash?首先set不像map的key-value对,它的key和value是相同的。关于set有两种说法,一个是STL中的set用红黑树实现,一个是hash_set用hash table实现。红黑树和hash table最大的不同是树是有结构的。如果单单判断set中的元素是否存在,那么当然hash table显然更合适,因为查找时间是log1。但是STL中的set被强调成集合,涉及到了集合的并set_intersection(),交set_union(),差set_difference()等操作,都需要进行大量的操作,于是树有优势

  8. 虚函数问题,析构函数为什么经常被声明为虚函数?析构函数里面能调用虚函数么?
    答:为了防止通过父类指针析构子类对象时能正确的调用虚函数。析构函数调用虚函数语法上是没有问题的,但是标准应该是不建议的

  9. tcp状态变迁图
    艹,记不住

  10. tcp怎么实现流控制和拥塞控制机制
    ack=1代表确认字段。链接建立的时候接收方会告诉发送方接收窗口大小,协商好窗口大小后,发送符合尺寸的字节流并等待对方回应,发送方根据回应信息改变窗口尺寸大小,增加或者减小发送未得到确认的字节流的字节数。调整过程包括:如果发生拥塞,发送窗口缩小为原来的一半(慢启动),同时将超时重传的时间变为原来的两倍。tcp的窗口机制保证了数据传输的可靠性和流量控制。
    差错控制:校验和(跟udp一样),确认(ack报文不需要确认,当收到一个序列号比期望要大时马上发ack让发送端重传,收到重复的序列号马上ack解决ack丢失问题,丢失的报文到达的时候马上ack表示已经收到丢失的),重传(超时、发送端收到连续的三个ACK)。

  11. 16进制字串转10进制字符串
    先把16进制转换成2进制,再把2进制转换成10进制(需要不能2进制反转)。
    16转2很简单,2进制转10

  1. //思路是每次向右一位的时候先整体乘以2,在看这一位是不是1.
  2. a[n]={0},b[4*n]=”0011111111111111111111111111101”;
  3. len=1;
  4. int j,i;
  5. for(i=0;i<n;i++){
  6. for(j=0;j<len;j++);
  7. a[j]*=2;
  8. j=0;
  9. while(a[j]!=0||j<len){
  10. if(a[j]>=10){
  11. a[j] = a[j]/10;
  12. a[j+1]+=1;
  13. }
  14. j++;
  15. }
  16. len = j;
  17. if(b[i]==1)
  18. a[0]+=1;
  19. }//得到的a是倒序的,个位是a0
  1. 红黑树和AVL树的实现原理和性能、优缺点比较
    红黑树定义,红黑树也是一种二叉查找树
    a。每个节点要么是红的,要么是黑的
    b。跟节点是黑的
    c。每个叶节点,即空节点是黑的
    d。如果一个节点是红的,那么他的字节点都是黑的
    e。对每个节点,从该节点到子孙节点的所有路径包含相同数量的黑节点。
    alv树:任何一个节点的左右子树高度查不超过1
    红黑树:只要求部分平衡,降低了旋转,提高了性能。能够以logn进行插入/删除/查找操作。任何不平衡的树都会在三次旋转内解决;
    AVL树:删除一个节点平衡的时候需要logn
  2. vector的实现,string的实现,queue的实现
    vector实现注意使用模板
  1. template<class T>
  2. class vector{
  3. pirvate:
  4. T *array;
  5. int size;
  6. int capacity;
  7. public:
  8. vector();
  9. vector(vector &);
  10. ~vector();
  11. bool push_back(T);
  12. bool pop_back();
  13. bool empty();
  14. T & operate[](T);
  15. };
  16. template<class T>
  17. vector<T>::vector(const vector<T> v);
  18. class string{
  19. private:
  20. char *data;
  21. public:
  22. string():data(new char[1]){}
  23. string(const char *str):data(new char[strlen(str)+1]){
  24. strcpy(data,str);
  25. }
  26. string(const string &str):data(new char[str.length()+1]){
  27. strcpy(data,str.c_str());
  28. }
  29. ~string(){
  30. delete [] data;
  31. }
  32. string &operate=(const string &src){
  33. swap(src);
  34. return *this;
  35. }
  36. size_t size(){
  37. return strlen(data);
  38. }
  39. const char *c_str(){
  40. return data;
  41. }
  42. void swap(string &str){
  43. std::swap(data,str.data);
  44. }
  45. char & operate[](int i){
  46. return data[i];
  47. }
  48. };

几个要点:
a。只在构造函数中用new,析构函数中用delete
b。每个函数都只有一两行代码, 没有条件判断
c。析构函数不必建厂data是否是NULL
e。拷贝构造函数没有检查str的合法性。这里在初始化列表中就用了str,因此在函数内部用assert没有意义

  1. 你觉得const是怎么实现的
    一段代码如下
  1. const int a=1;
  2. int *b = (int *)&a;
  3. *b = 3;
  4. printf(“%d,%d”,a,*b);

使用gcc的时候输出3,3;使用g++的时候输出1,3;
区别在于:c语言把const int当成只读的变量,既然是变量,那么在内存中就会有储存空间,并且可以通过指针改变他的值。而c++当成常量,编译器会使用常数直接替换掉他比如cout<

  1. 交换机的工作原理
  2. tcp/ip工作原理(5层)
    应用层(数据段)、传输层(数据包)、网络层(帧)、主机到网络层(bit流)
    OSI7层协议(应用层、表示层、会话层、传输层、网络层、链路层、物理层)
  3. 进程的堆和栈的最大值
    栈(属于线程,一个进程有多少个线程就有多少栈)是1M,堆理论上内存多大就可以多大
  4. 如何提高cache的命中率

  5. 智能指针的设计(垃圾回收机制)
    首先new和delete要成对出现,而且delete要自动调用,那么就只能考class中的构造函数和析构函数
    但是多个指针可以指向同一个数据,于是我们就使用引用计数的方式来解决多个指针指向同一数据的问题。
    定义一个类来解决引用指针的问题(这个数据是要被多个smart指针共享的数据),所以需要设置成友员,由于不会有除了智能指针之外的其他调用,所以他的所有成员和方法都可以是私有的。

  1. class _counter{
  2. template<class T> friend class SmartPointer;
  3. _counter(int u):use(u){}
  4. ~_counter(){};
  5. int use;
  6. };
  7. smartpointer类中保留counter的指针
  8. template<class T>
  9. class SmartPointer{
  10. private:
  11. T *pt;
  12. _counter *pc;
  13. public:
  14. SmartPointer(T *t):pc(new _count(1)),pt(t){};
  15. SmartPointer(SmartPointer<T> &rhs){
  16. pc = rhs->pc;
  17. pt = rhs->pt;
  18. pc->use +=1;
  19. }
  20. ~SmartPointer(){
  21. pc->use -=1;
  22. if(pc->use==0){
  23. delete pc;
  24. delete pt;
  25. }
  26. }
  27. SmartPointer & operator=(SmartPointer<T> &rhs){
  28. if(rhs==*this)
  29. return *this;
  30. pc = rhs.pc;
  31. pt = rhs.pt;
  32. pc->use += 1;
  33. return *this;
  34. }
  35. T &operator *(){
  36. return *pt;
  37. }
  38. T *operator ->(){
  39. return pt;
  40. }
  41. }
  42. //比如说我们有一个有指针的类
  43. class HasPtr{
  44. int *p;
  45. public:
  46. HasPtr(int n):p(new int[n]){}
  47. ~HasPtr(){delete []p;}
  48. };
  49. //使用的时候
  50. SmartPointer<HasPtr> psp(new HasPtr(3));
  51. SmartPointer<HasPtr> npsp(p);
  52. //就可以得到如下内存结构

内存池

  1. template<class T>
  2. class CmemoryPool{
  3. public:
  4. CmemoryPool(unsigned int nIterCount = 32){
  5. ExpandFreeList(nIterCount);
  6. }
  7. ~CmemoryPool(){
  8. CmemoryPool<T> *pNesxt = NULL
  9. for(pNext = m_pFreeList;pNext!=NULL;pNext = m_pFreeList){
  10. m_pFreeList = m_pFreeList->next;
  11. delete [](char *)pNext;
  12. }
  13. }
  14. void *Alloc(){
  15. if(m_pFreeList==NULL)
  16. ExpendFreeList();
  17. //从空表头获取空间
  18. CmemoryPool<T> *pHead = m_pFreeList;
  19. m_pFreeList = m_pFreeList->next;
  20. return pHead;
  21. }
  22. void Free(void *p){
  23. CmemoryPool<T> *pHead = static_cast<CmemoryPool<T>*>(p);
  24. pHead->m_pFreeList = m_pFreeList;
  25. m_pFreeList = pHead;
  26. }
  27. protected:
  28. //分配内存到list中
  29. void ExpandFreeList(unsigned nItemCount = EXPANSION_SIZE)
  30. {
  31. unsigned int nSize = sizeof(T) > sizeof(CMemoryPool<T>*) ? sizeof(T) :
  32. sizeof(CMemoryPool<T>*);
  33. CMemoryPool<T>* pLastItem = static_cast<CMemoryPool<T>*>(static_cast<void*>(new
  34. char[nSize]));
  35. m_pFreeList = pLastItem;
  36. for(int i=0; i<nItemCount-1; ++i)
  37. {
  38. pLastItem->m_pFreeList = static_cast<CMemoryPool<T>*>(static_cast<void*>(new
  39. char[nSize]));
  40. pLastItem = pLastItem->m_pFreeList;
  41. }
  42. pLastItem->m_pFreeList = NULL;
  43. }
  44. private:
  45. CMemoryPool<T>* m_pFreeList;
  46. };
  1. 一个排序好的数组生成平衡二叉树AVL树
    实现很简单,直接从中间断开,中间的数字就是根,左右继续划分。
  1. Struct Node{
  2. int data;
  3. Node *left;
  4. Node *right;
  5. };
  6. Node * ArrayToAVL(int arr[], int n){
  7. return helper(arr,0,n-1);
  8. }
  9. Node *helper(int arr[], int start, int end){
  10. if(start>end) return NULL;
  11. int mid=start+(end-start)/2;
  12. Node *node = new Node(arr[mid]);
  13. node->left=helper(arr,start,mid-1);
  14. node->right=helper(arr,mid+1,end);
  15. return node;
  16. }
  1. 实现线程安全的队列

  2. 旋转数组二分查找。方法一:第一次二分找到关键值,第二次再二分。方法二,判断出数组的单调性,根据第一个和最后一个元素大小判断要查找的数在那个部分,直接从中间二分。

  3. 快排最坏情况优化到nlogn

    • 随机选择povit
    • 插入排序在数组小的时候很高效,在数组小于某个筏值(比如10个元素,标准的是7)的时候采用插入排序。
    • 选择povit,三数取中值和九数取中值(分三次每次去三个求中值,最终结果是中值的中值,取的时候要从左、中、右三个部分都取),在数组大于40的时候9数字取中值,10-40的时候3数取中值,小于10的时候插入排序。
    • 最排除相同数字,每次递归的前,判断左右的相同数字不进入下次递归
      c+d就是最终方案
  4. memcpy实现

  1. int memcmp(const void *s, const void *t, unsigned int count){
  2. assert((s!=NULL)&&(t!=NULL));
  3. while( *(char *)s && *(char *)t &&*(char *)s==*(char *)t&&count--){
  4. s = (char *)s +1;
  5. t = (char *)t+1;
  6. }
  7. return (*(char *)s-*(char*)t);
  8. }
  9. void *memcpy(void *dest, const void *src, unsigned int count){
  10. assert((s!=NULL)&&(t!=NULL));
  11. void *addr = dest;
  12. while(count--){
  13. *(char *)dest = *(char *)src;
  14. dest = (char *)dest +1;
  15. src = (char *)src+1;
  16. }
  17. return addr;
  18. }
  1. i++和++i哪个效率搞,怎么实现
    在内置数据类型的情况下都一样,在自定义数据的情况下++i效率高。
    自定义数据类型的情况下,++i返回对象的引用,i++总是要创建一个临时对象,在退出的时候还要销毁它,而且返回临时对象还要调用其拷贝构造函数

  2. malloc的实现机制
    它将一个可用的内存块连接成一个长长的列表所谓空闲链表。调用malloc函数的时候,它沿着链接表找到一个大到足以满足用户请求的内存块。然后将内存块一分为二(其中一块与用户请求的大小相同,另一块就是剩下的)。接下来将内存传给用户,并将剩下的(如果有的话)返回到链表上。调用free的时候他将用户释放的内存连接到空闲链表上。最后,链表会被切成很多的小内存碎片,如果用户申请一个较大的内存,那么可能没有能满足要求的片段。于是malloc函数请求延时,并开始在空闲链表上翻箱倒柜查各内存片,对他们进行整理,将相邻的小空闲快合并成较大的。

  3. 共享内存的使用和实现原理
    共享内存定义:共享内存是最快的IPC(进程通信)形式,他允许多个不相关的进程去访问同一部分逻辑内存。共享内存是由IPC为一个进程创建的一个特殊的地址范围,它将出现的进程的地址空间中。其他进程可以将同一段共享内存‘连接’到他们自己的内存地址空间上。所有进程都可以访问共享内存中的地址。如果一个进程对这段共享内存写入了数据,所做的改动会立刻被访问同一段内存的其他进程看到。因此它对数据传输是非常高效的。
    共享内存原理:两个不同的进程AB共享内存的意思是,同一块物理内存被映射到进程AB各自的地址空间,进程A可以及时看到进程B对共享内存数据中的改变,反之亦然。
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注