c++求两个数的最大值运行错误

本次是接着前面的例子使用模板函数来更方便的计算三个数的最大值。

template的意思是模板,typename是类型名,后面的T是一个类型参数,当然也可以是其他的字母,这里使用T的目标是为了表示类型type的意思。

2.编译系统会根据函数名 max与模板函数max进行匹配,将实参类型取代模板当中的T,这样就实现了后面的使用int代表T使用double代表T或者使用long代表T。

3.模板函数适用于参数数量相同但是类型不相同的情况。如果参数数量不相同就不可以使用模板函数了。

  • 技术交流QQ群:,欢迎你的加入! 1.Cpp中的模板template 模板是泛型编程的基础,泛...

  • C++ 模板简介 一、模板 使用模板的目的就是能够让程序员编写与类型无关的代码。 模板是一种对类型进行参数化的工具...

  • 教师的《年度统计报表》中有一表是统计各岗位各薪级人数的,如果用传统的自动筛选方法来统计比较麻烦: 其实我们可以用E...

  • 信任这个话题在我们生活中无处不在,生活中与伴侣的信任,工作中与同事领导的信任,甚至在路上偶遇的陌生人,信任也是对彼...

  • 问题空间由类组成,对象是类在解空间的映射; 计算机语言的发证过程:机器码->汇编语言->c语言->c++-...

  • 现在的手机有什么用,一般都不打电话了,只用QQ和微信就能聊天,而且是和不认识的人聊! 为什么不和熟人聊天呢?因为都...

我秋招就是背的这篇面经,因此分享出来,很感谢大神的面经html


    是个声明,此变量/函数是在别处定义的,要在此处引用 暗示这个函数可能在别的源文件里定义,没有其它做用。 告诉编译器在编译函数名时按着C的规则去翻译相应的函数名而不是C++的(C++要支持多态,C没有)
    被修饰的变量成为静态变量,存储在静态区。存储在静态区的数据生命周期与程序相同,在main函数以前初始化,在程序退出时销毁。(不管是局部静态仍是全局静态)
    局部静态变量使得该变量在退出函数后,不会被销毁,所以再次调用该函数时,该变量的值与上次退出函数时值相同。值得注意的是,生命周期并不表明其能够一直被访问,由于变量的访问还受到其做用域的限制。 全局变量原本就存储在静态区,所以static并不能改变其存储位置。可是,static限制了其连接属性。被static修饰的全局变量只能被该包含该定义的文件访问。
  1. static修饰函数使得函数只能在包含该函数定义的文件中被调用。static修饰的变量先于对象存在,因此static修饰的变量要在类外初始化。由于static是全部对象共享的东西嘛,必需要比对象先存在的。

static在C++中对于静态成员变量和静态成员函数。全部的对象都只维持同一个实例。至关于类的属性

  1. 因为static修饰的类成员属于类,不属于对象,所以static类成员函数是没有this指针的,this指针是指向本对象的指针。正由于没有this指针,因此static类成员函数不能访问非static的类成员,只能访问 static修饰的类成员。
  • 出如今类体外的函数不能指定关键字static;
  • 静态成员之间能够互相访问,包括静态成员函数访问静态数据成员和访问静态成员函数;
  • 非静态成员函数能够任意地访问静态成员函数和静态数据成员;
  • 静态成员函数不能访问非静态成员函数和非静态数据成员;
  • 因为没有this指针的额外开销,所以静态成员函数与类的全局函数相比,速度上会有少量的增加;
  • 调用静态成员函数,能够用成员访问操做符(.)和(->)为一个类的对象或指向类对象的指调用静态成员函数。
  1. 访问寄存器要比访问内存要块,所以CPU会优先访问该数据在寄存器中的存储结果,可是内存中的数据可能已经发生了改变,而寄存器中还保留着原来的结果。为了不这种状况的发生将该变量声明为volatile,告诉CPU每次都从内存去读取数据。
  2. 一个参数能够便是const又是volatile的吗?能够,一个例子是只读状态寄存器,是volatile是由于它可能被意想不到的被改变,是const告诉程序不该该试图去修改他。

某个枚举变量的值默认为前一个变量值加一
第一个枚举变量默认值为0
枚举变量值是能够重复的java

修饰变量,局部变量,全局变量,成员变量(必须初始值列表)
修饰引用做为函数参数,保护值不会改变,同时也针对常量参数
修饰成员函数,类的成员函数加上const限定能够声明此函数不会更改类对象的内容(并不牢靠)
修饰返回值,代表返回的数据是不可修改的
修饰指针,左定值,右定向,const在*左侧表示所指内容是常量,在*右侧表示指针自己是常量不可变node

  1. new分配内存按照数据类型进行分配,malloc分配内存按照大小分配;
  2. new不只分配一段内存,并且会调用构造函数,可是malloc则不会。new的实现原理?可是还须要注意的是,以前看到过一个题说int p = new int与int p = new int()的区别,由于int属于C++内置对象,不会默认初始化,必须显示调用默认构造函数,可是对于自定义对象都会默认调用构造函数初始化。翻阅资料后,在C++11中二者没有区别了,本身测试的结构也都是为0;
  3. new返回的是指定对象的指针,而malloc返回的是void*,所以malloc的返回值通常都须要进行类型转化;
  4. new是一个操做符能够重载,malloc是一个库函数;
  5. new分配的内存要用delete销毁,malloc要用free来销毁;delete销毁的时候会调用对象的析构函数,而free则不会;
  6. malloc分配的内存不够的时候,能够用realloc扩容。扩容的原理?new没用这样操做;
  7. new若是分配失败了会抛出bad_malloc的异常,而malloc失败了会返回NULL。所以对于new,正确的姿式是采用try...catch语法,而malloc则应该判断指针的返回值。为了兼容不少c程序员的习惯,C++也能够采用new nothrow的方法禁止抛出异常而返回NULL;
  8. new和new[]的区别,new[]一次分配全部内存,屡次调用构造函数,分别搭配使用delete和delete[],同理,delete[]屡次调用析构函数,销毁数组中的每一个对象。而malloc则只能sizeof(int) * n;
  9. 若是不够能够继续谈new和malloc的实现,空闲链表,分配方法(首次适配原则,最佳适配原则,最差适配原则,快速适配原则)。delete和free的实现原理,free为何直到销毁多大的空间?
    多态分为静态多态和动态多态。静态多态是经过重载和模板技术实现,在编译的时候肯定。动态多态经过虚函数和继承关系来实现,执行动态绑定,在运行的时候肯定。
    动态多态实现有几个条件:
    (2) 一个基类的指针或引用指向派生类的对象;
    基类指针在调用成员函数(虚函数)时,就会去查找该对象的虚函数表。虚函数表的地址在每一个对象的首地址。查找该虚函数表中该函数的指针进行调用。
    每一个对象中保存的只是一个虚函数表的指针,C++内部为每个类维持一个虚函数表,该类的对象的都指向这同一个虚函数表。
    虚函数表中为何就能准确查找相应的函数指针呢?由于在类设计的时候,虚函数表直接从基类也继承过来,若是覆盖了其中的某个虚函数,那么虚函数表的指针就会被替换,所以能够根据指针准确找到该调用哪一个函数。
  • 在设计上还具备封装和抽象的做用。好比抽象工厂模式。
    静态多态是指经过模板技术或者函数重载技术实现的多态,其在编译器肯定行为。动态多态是指经过虚函数技术实如今运行期动态绑定的技术。

  1. 编译器为每个类维护一个虚函数表,每一个对象的首地址保存着该虚函数表的指针,同一个类的不一样对象实际上指向同一张虚函数表。
  2. 为何对于存在虚函数的类中析构函数要定义成虚函数
    为了实现多态进行动态绑定,将派生类对象指针绑定到基类指针上,对象销毁时,若是析构函数没有定义为析构函数,则会调用基类的析构函数,显然只能销毁部分数据。若是要调用对象的析构函数,就须要将该对象的析构函数定义为虚函数,销毁时经过虚函数表找到对应的析构函数。
  3. 析构函数能抛出异常吗?
    确定是不能。 C++标准指明析构函数不能、也不该该抛出异常。C++异常处理模型最大的特色和优点就是对C++中的面向对象提供了最强大的无缝支持。那么若是对象在运行期间出现了异常,C++异常处理模型有责任清除那些因为出现异常所致使的已经失效了的对象(也即对象超出了它原来的做用域),并释放对象原来所分配的资源, 这就是调用这些对象的析构函数来完成释放资源的任务,因此从这个意义上说,析构函数已经变成了异常处理的一部分。
  • 若是析构函数抛出异常,则异常点以后的程序不会执行,若是析构函数在异常点以后执行了某些必要的动做好比释放某些资源,则这些动做不会执行,会形成诸如资源泄漏的问题。
  • 一般异常发生时,c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数自己也抛出异常,则前一个异常还没有处理,又有新的异常,会形成程序崩溃的问题。
  1. 构造函数能够调用虚函数吗?
    不能够,构造是按继承顺序的,先基类,后派生类,若是构造期间调用了一个子类的虚函数,可是子类还未构造出来,出现未定义行为。析构函数同理。

C++空类默认的成员函数

C++中空类默认会产生如下6个函数:linux

  • const取地址符重载函数
  1. 指针保存的是所指对象的地址,引用是所指对象的别名,指针须要经过解引用间接访问,而引用是直接访问;
  2. 指针能够改变地址,从而改变所指的对象,而引用必须从一而终;
  3. 引用在定义的时候必须初始化,而指针则不须要;
  4. 指针有指向常量的指针和指针常量,而引用没有常量引用;
  5. 指针更灵活,用的好威力无比,用的很差到处是坑,而引用用起来则安全多了,可是比较死板。
  1. 一个一维int数组的数组名其实是一个int* const 类型;
  2. 数组名作参数会退化为指针,除了sizeof

C++中异常的处理方法

使用try{} catch(){}来捕获异常,若是本级没有带适当类型参数的catch块,则不能捕获异常,异常就会向上一级传递,若是一直没有捕获,C++会使用默认的异常处理函数c++

回调函数是经过函数指针调用的函数,把函数的指针(地址)做为参数传递给另外一个函数
回调函数与应用程序接口(API)很是接近,都是跨层调用的函数,区别是API是低层给高层的调用,回调函数则相反,是高层提供给低层的调用,必须由高层来安装git

所谓内存泄漏是指因为疏忽或错误致使程序未能释放已经再也不使用的内存的状况,通常指堆内存的泄露,失去对该段内存的控制,于是形成了内存的浪费,会致使CPU资源耗尽的严重后果程序员

缓冲区是成勋运行的时候机器内存的一个连续块
当向缓冲区内填充数据位数超过了缓冲区自身的容量限制,发生溢出的数据覆盖在合法数据(数据,下一条指令的指针,函数返回地址等),解决办法是检查数据长度github

  • 野指针是指向不可用内存的指针,没有被正确的初始化,指向随机地址
  • 空悬指针:曾经指向有效地址,但原来内存被释放了,如今再也不有效
1. 构造函数中计数初始化为1;
2. 拷贝构造函数中计数值加1;
3. 赋值运算符中,左边的对象引用计数减一,右边的对象引用计数加一;
4. 析构函数中引用计数减一;
5. 在赋值运算符和析构函数中,若是减一后为0,则调用delete释放对象。
 
share_ptr可能出现循环引用,从而致使内存泄露redis

weak_ptr是一种弱引用指针,其存在不会影响引用计数,从而解决循环引用的问题

  1. static_cast用的最多,对于各类隐式转换,非const转const,void*转指针等, static_cast能用于多态想上转化,若是向下转能成功可是不安全,结果未知;
  2. dynamic_cast用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。向下转化时,若是是非法的对于指针返回NULL,对于引用抛异常。要深刻了解内部转换的原理。
  3. reinterpret_cast几乎什么均可以转,好比将int转指针,可能会出问题,尽可能少用;
  4. 为何不使用C的强制转换?C的强制转换表面上看起来功能强大什么都能转,可是转化不够明确,不能进行错误检查,容易出错。
  1. 变量存储的起始位置是该变量大小的整数倍;
  2. 结构体总的大小是其最大元素的整数倍,不足的后面要补齐;
  3. 结构体中包含结构体,从结构体中最大元素的整数倍开始存;
  4. 若是加入pragma pack(n) ,取n和变量自身大小较小的一个。

struct的对其系数和如下几个关系有关

  1. struct中成员在内存中按顺序排列,在没有#pargma pack(n)的状况下,各个成员的对齐系数为本身的长度
  2. 在有#pargma pack(n)的状况下,各个成员的对齐系数为min(本身的长度,n)
  3. struct总体的对齐系数为对齐系数中最大的
  4. 依次排列时要知足地址对对齐系数取模为0

内联函数有什么优势?与宏定义的区别?

  1. 宏定义在预处理阶段进行替换
  2. 内联函数在编译阶段,在调用内联函数的地方进行替换,减小了函数的调用过程,可是使得编译文件变大。所以,内联函数适合简单函数,对于复杂函数,即便定义了内联编译器可能也不会按照内联的方式进行编译。
  3. 内联函数相比宏定义更安全,内联函数能够检查参数,而宏定义只是简单的文本替换。所以推荐使用内联函数,而不是宏定义。
    在C++中,内存分红5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。
  • 栈,在执行函数时,函数内局部变量的存储单元均可以在栈上建立,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,可是分配的内存容量有限。
  • 堆,就是那些由new分配的内存块,他们的释放编译器不去管,由咱们的应用程序去控制,通常一个new就要对应一个delete。若是程序员没有释放掉,那么在程序结束后,操做系统会自动回收。
  • 自由存储区,就是那些由malloc等分配的内存块,他和堆是十分类似的,不过它是用free来结束本身的生命的。
  • 全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在之前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。
  • 常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不容许修改

在此注意自由存储区和堆

  • 自由存储是C++中经过new与delete动态分配和释放对象的抽象概念,而堆(heap)是C语言和操做系统的术语,是操做系统维护的一块动态分配内存。
  • new所申请的内存区域在C++中称为自由存储区。借由堆实现的自由存储,能够说new所申请的内存区域在堆上。
  • 堆与自由存储区仍是有区别的,它们并不是等价。
  • 学会迁移,能够说到malloc,从malloc说到操做系统的内存管理,说道内核态和用户态,而后就什么高端内存,slab层,伙伴算法,VMA能够巴拉巴拉了,接着能够迁移到fork()。

STL内存分配分为一级分配器和二级分配器,一级分配器就是采用malloc分配内存,二级分配器采用内存池。

二级分配器设计的很是巧妙,分别给8k,16k,..., 128k等比较小的内存片都维持一个空闲链表,每一个链表的头节点由一个数组来维护。须要分配内存时从合适大小的链表中取一块下来。假设须要分配一块10K的内存,那么就找到最小的大于等于10k的块,也就是16K,从16K的空闲链表里取出一个用于分配。释放该块内存时,将内存节点归还给链表。
若是要分配的内存大于128K则直接调用一级分配器。
为了节省维持链表的开销,采用了一个union结构体,分配器使用union里的next指针来指向下一个节点,而用户则使用union的空指针来表示该节点的地址。

STL里set和map是基于什么实现的?红黑树的特色?

STL的set和map都是基于红黑树实现的

AVL是一种高度平衡的二叉树,因此一般的结果是,维护这种高度平衡所付出的代价比从中得到的效率收益还大,故而实际的应用很少,更多的地方是用追求局部而不是很是严格总体平衡的红黑树。固然,若是场景中对插入删除不频繁,只是对查找特别有要求,AVL仍是优于红黑的。

红黑树的应用:STL,epoll在内核中的实现

  1. 每一个结点或者为黑色或者为红色。
  2. 每一个叶结点(实际上就是NULL指针)都是黑色的。
  3. 若是一个结点是红色的,那么它的两个子节点都是黑色的(也就是说,不能有两个相邻的红色结点)。
  4. 对于每一个结点,从该结点到其全部子孙叶结点的路径中所包含的黑色结点数量必须相同。

红黑树可以以O(log2 n) 的时间复杂度进行搜索、插入、删除操做。此外,因为它的设计,任何不平衡都会在三次旋转以内解决。固然,还有一些更好的,但实现起来更复杂的数据结构,可以作到一步旋转以内达到平衡,但红黑树可以给咱们一个比较“便宜”的解决方案。红黑树的算法时间复杂度和AVL相同,但统计性能比AVL树更高。

若是插入一个node引发了树的不平衡,AVL和RB-Tree都是最多只须要2次旋转操做,即二者都是O(1);可是在删除node引发树的不平衡时,最坏状况下,AVL须要维护从被删node到root这条路径上全部node的平衡性,所以须要旋转的量级O(logN),而RB-Tree最多只需3次旋转,只须要O(1)的复杂度。

必须在构造函数初始化列表里进行初始化的数据成员有哪些

  1. 常量成员,由于常量只能初始化不能赋值,因此必须放在初始化列表里面
  2. 引用类型,引用必须在定义的时候初始化,而且不能从新赋值,因此也要写在初始化列表里面
  3. 没有默认构造函数的类类型,由于使用初始化列表能够没必要调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化
  4. 要注意,对非内置类型成员,确定仍是列表的性能好,由于省略了一次复制的过程

模板分为类模板与函数模板,特化分为全特化与偏特化。全特化就是限定死模板实现的具体类型,偏特化就是若是这个模板有多个类型,那么只限定其中的一部分。
模板特化的目的就是对于某一种变量类型具备不一样的实现,所以须要特化版本。例如,在STL里迭代器为了适应原生指针就将原生指针进行特化。

  1. 根据关键码值(key value)直接访问的数据结构,实现有拉链法(链表),以及开放地址法解决冲突
  1. (1) 建立一个新桶,该桶是原来桶两倍大最接近的质数(判断n是否是质数的方法:用n除2到$sqrt(n)$范围内的数) ;
    (2) 将原来桶里的数经过指针的转换,插入到新桶中(注意STL这里作的很精细,没有直接将数据从旧桶遍历拷贝数据插入到新桶,而是经过指针转换)
    (3) 经过swap函数将新桶和旧桶交换,销毁新桶。

  2. 容量扩张是一次完成的,期间要花很长时间一次转移hash表中的全部元素。
    redis中的dict.c中的设计思路是用两个hash表来进行进行扩容和转移的工做,把第一个hash表全部元素的转移分摊为屡次转移,并且每次转移的指望时间复杂度为O(1)。这样就不会出现某一次往字典中插入元素要等候很长时间的状况了。

  1. 二叉树结构,二叉搜索树在二叉树的基础上加上了这样的一个性质:对于树中的每个节点来讲,若是有左儿子的话,它的左儿子的值必定小于它自己的值,若是有右儿子的话,它的右儿子的值必定大于它自己的值。
  2. 递归是解决二叉树相关问题的神级方法
  • 每一个叶节点(NIL节点,空节点)是黑色的
  • 每一个红色节点的两个子节点都是黑色的(也就是说不存在两个连续的红色节点)
  • 从任一节点到其每一个叶节点的全部路径都包含相同数目的黑色节点

红黑树与AVL树的区别

  • 红黑树与AVL树都是平衡树,可是AVL是彻底平衡的(平衡就是值树中任意节点的左子树和右子树高度差不超过1);
  • 红黑树插入效率更高,由于AVL为了保证其彻底平衡,插入和删除的时候在最坏的状况下要旋转logN次,而红黑树插入和删除的旋转次数要比AVL少,牺牲了严格的高度平衡的优越条件使得三次旋转便可平衡。
  • 链表的插入和删除,单向双向链表
  • 链表的问题考虑多个指针和递归
  1. 打印倒数第K个节点(先后指针)
  2. 链表是否有环(快慢指针)

栈(Stack)和队列(Queue)是两种操做受限的线性表。
2. 插入操做都是限定在表尾进行。
3. 均可以经过顺序结构和链式结构实现。、
4. 插入与删除的时间复杂度都是O(1),在空间复杂度上二者也同样。
5. 多链栈和多链队列的管理模式能够相同。

  1. 删除数据元素的位置不一样,栈的删除操做在表尾进行,队列的删除操做在表头进行。栈的应用场景包括括号问题的求解,表达式的转换和求值,函数调用和递归实现,深度优先搜索遍历等
    队列的应用场景包括计算机系统中各类资源的管理,消息缓冲器的管理和广度优先搜索遍历等。
  2. 顺序栈可以实现多栈空间共享,而顺序队列不能。

相似问题的解决方法思路:首先哈希将数据分红N个文件,而后对每一个文件创建K个元素最小/大堆(根据要求来选择)。最后将文件中剩余的数插入堆中,并维持K个元素的堆。最后将N个堆中的元素合起来分析。能够采用归并的方式来合并。在归并的时候为了提升效率还须要建一个N个元素构成的最大堆,先用N个堆中的最大值填充这个堆,而后就是弹出最大值,指针后移的操做了。固然这种问题在如今的互联网技术中,通常就用map-reduce框架来作了。
大数据排序相同的思路:先哈希(哈希是好处是分布均匀,相同的数在同一个文件中),而后小文件装入内存快排,排序结果输出到文件。最后建堆归并。

使用hash_map或者位图,再利用归并的思想

  • 必须至少能快速写出,快排,建堆,和归并
  • 种算法的时间空间复杂度,最好最差平均状况
  • 不用临时变量交换两个整数(屡次异或)
  • 不用加减乘除完成整数相加

由一个很长的二进制向量和一系列随机映射函数组成,布隆过滤器能够用于检索一个元素是否在一个集合中。
优势是空间效率和查询时间都远远超过通常的算法,缺点是有必定的误识别率(可是不会漏报)

    • TCP基于有链接,UDP基于无链接
    • TCP能保证可靠传输,UDP不能保证可靠传输TCP
    • TCP结构复杂,消耗资源多,创建过程较慢较复杂。UDP结构简单,消耗资源少,创建过程较快
    • TCP基于流模式,UDP是数据报模式
    • TCP链接只能是点到点,而UDP能够一对一,一对多或者多对多
    • TCP有确认,重传,拥赛控制机制,UDP在没有创建链接或者对方已经退出的状况下任然会继续发送数据,致使通讯流量的浪费。
  • TCP:用于实现可靠传输的状况,文件很是重要,对网络拥堵有较高要求的状况。
  • UDP:用于高速传输和实时性较高的场合(即时通讯),广播通讯
  1. 实现UDP的可靠传输,如RTP协议,RUDP协议
    检测包的顺序,请求重传,请求者发起或接收者发起
  1. 客户端发送请求包,告诉服务器:“我想和你通讯?”数据包中SYN位置为1,假设其序列号为x,客户端状态变成SYN_SENT;
  2. 服务器端接受到请求包后也发送一个请求包,告诉客户端:“如今能够创建链接”。数据包中SYN位置位1,假设其序列号为y,注意客户端序列号和服务器端序列号并无关系,他们是由各自的内核按照必定的规则生成的。可是这个应答包的32位应答号,必须是x+1,之因此加1是由于客户端发过来的包SYN位被认为占一个数据。所以,告诉下一包从x+1开始发。发送后,服务器从监听状态变成SYN_RCVD状态。
  3. 客户端发送应答数据包,告诉服务器:“那咱们开始发送数据吧”。数据包应答号为y+1。客户端变成ESTABLISHED状态,便可以传输状态。
  4. 服务器端接受到应答数据包后,变成ESTABLISHED状态。
  1. 客户端发送一个一个字节的数据,所以序列号为x+1;
  2. 服务端发送一个应答包,应答号为x+2,告诉客户端下次从x+2开始发;
  1. 客户端发送请求断开的数据包,告诉服务器:“数据传完了,我要断开了”。发送一个FIN包,序列号x+2。客户端转移到FIN_WAIT_1状态。
  2. 服务器端发送应答包,告诉客户端:“行,我知道了,你断开吧!”。应答号为x+3,服务器进入CLOSE_WAIT状态。客户端收到应答后,转移到FIN_WAIT_2状态。
  3. 服务器发送一个断开数据包,告诉客户端:“既然传完了,那我这边的开关也准备关了”。序列号为y+1,发送完后服务器进入LAST_ACK状态。
  4. 客户端发送一个应答包,告诉服务器:“好的,我知道你要断开了。”应答号为y+2。客户端进入TIME_WAIT状态。

TIME_WAIT又称为2MSL等待状态,MSL是系统中定义的最大报文生存时间,任何TCP报文在网络中生存时间超过这个值就必须被丢弃。
等待MSL的缘由是防止最后一个ACK丢失后能够进行重发,若是ACK丢失后,服务器会重发FIN。

    当一个包被发送后,就开启一个定时器,若是定时时间到了,还未收到能确认该发送包的应答包,就重传一份数据。注意收到的应答包多是该包也多是后面包的,可是只要能确认该包被收到就行。另外若是,是由于网络延时形成重传,则接受端收到重复数据包后丢弃该包。 当若是发送端收到一个包的三次应答包后,当即重传,比超时重传更高效。
  1. 核心思想为任意时刻,最多只能有一个未被确认的小段。 所谓“小段”,指的是小于MSS尺寸的数据块,所谓“未被确认”,是指一个数据块发送出去后,没有收到对方发送的ACK确认该数据已收到。

  2. Nagle算法简单讲就是,等待服务器应答包到达后,再发送下一个数据包。数据在发送端被缓存,若是缓存到达指定大小就将其发送,或者上一个数据的应答包到达,将缓存区一次性所有发送。

Nagle算法是从发送端角度考虑减小了数据包的个数,时延应答从接收端角度考虑减小了数据包的个数。

目的:若是发送方把数据发送得过快,接收方可能会来不及接收,这就会形成数据的丢失。
因此流量控制是点对点通讯的控制,而拥塞控制是对整个网络内流量负载的控制
TCP的流量控制是利用滑动窗口机制实现的,接收方在返回的ACK中会包含本身的接收窗口的大小,以控制发送方的数据发送。



如上图所示A向B发送数据。在链接创建时,B告诉A接收窗口rwnd(receiver window)= 400,单位字节,所以发送方A的发送窗口不能超过400。

(能够看出,B向A发送的三个报文段都设置了 ACK = 1以保证字段有效,后面的rwnd值就是接收方对发送方的三次流量控制。)

第一次把窗口设置为300 ,第二次100 ,最后一次为 0,即不容许发送方再发送数据的状态。

可是当某个ACK报文丢失了,就会出现A等待B确认,而且B等待A发送数据的死锁状态。为了解决这种问题,TCP引入了持续计时器(Persistence timer),当A收到rwnd=0时,就启用该计时器,时间到了则发送一个1字节的探测报文,询问B是很忙仍是上个ACK丢失了,而后B回应自身的接收窗口大小,返回仍为0(A重设持续计时器继续等待)或者会重发rwnd=x。

窗口是TCP中为了解决应答机制等待时间过长而引入的方法,若是没有窗口,则TCP每发送一次数据就必须等待应答,收到应答后继续发送,若是没有收到则等待一段时间后重发,若是很长时间都没法收到应答则判断为网络断开。而使用窗口后,窗口的大小指无需等待应答能够连续发送多个数据包。
TCP窗口在每一个传输方向都有两个窗口,发送端窗口和接收端窗口,又由于TCP是全双工通讯,所以有四个窗口。
引入窗口后,TCP的应答包若是部分丢失,无需重传,由后面的应答包保证。TCP为了提升效率,采用延时再确认应答,和选择性确认应答,即收到数据包后不当即发送应答包,而是等待收到下一个或多个包后发一个应答。

TCP的拥塞控制算法和过程

网络中的链路容量、交换结点中的缓存、处理机等等都有着工做的极限,当网络的需求超过它们的工做极限时,就出现了拥塞。拥塞控制就是防止过多的数据注入到网络中,这样可使网络中的路由器或链路不致过载。

慢开始算法是指开始发送数据时,并不清楚网络的负荷状况,会先发送一个1字节的试探报文,当收到确认后,就发送2个字节的报文,继而4个,8个以此指数类推。

须要注意的是,慢开始的“慢”并非指拥塞窗口的增加速率慢,而是指在TCP开始发送报文时先设置拥塞窗口=1。
拥塞避免算法是让拥塞窗口缓慢地增大,即cwnd加1,而不是如慢开始算法同样加倍。、

根据上图的实例进行分析,一开始的慢开始算法的指数增加是很恐怖的,因此为了防止拥塞窗口cwnd增加过快须要设置一个门限ssthresh,这里是16。

  1. 当 cwnd > ssthresh 时,中止使用慢开始算法而改用拥塞避免算法。
  2. 当 cwnd = ssthresh 时,既可以使用慢开始算法,也可以使用拥塞控制避免算法。

在上述对拥塞窗口的描述中,咱们只是说在链接开始的时候,以指数级的速率增长,直到第一个丢失事件发生。但实际中TCP对因超时而检测到的丢包事件做出的反应与对因收到3个冗余ACK而检测到的丢包事件作出的反应是不一样的。

  • 收到3个冗余ACK后:CongWin减半、窗口再线性增长。
  • 检测超时事件后:CongWin值设置为1MSS、窗口再指数增加、到达一个阈值(Threshold,初始化时被设置为一个很大的值,以使它没有初始效应。每发生一个丢包事件,Threshold就会被设置为当前CongWin值的一半)后,再线性增加。

缘由:3个冗余ACK指示网络还具备某些传送报文段的能力;3个冗余ACK之前的超时,则更为 “严重”。

快重传是指,若是发送端接收到3个以上的重复ACK,不须要等到重传定时器溢出就从新传递,因此叫作快速重传,而快速重传之后,由于走的不是慢启动而是拥塞避免算法,因此这又叫作快速恢复算法。

若是没有快速重传和快速恢复,TCP将会使用定时器来要求传输暂停。在暂停这段时间内,没有新的数据包被发送。因此快速重传和快速恢复旨在快速恢复丢失的数据包。

与快重传配合使用的还有快恢复算法,结合下图的实例来分析,其过程有如下两个要点:

  1. 当发送方在cwnd=24时连续收到三个重复确认,就把慢开始门限ssthresh减半(就是上图中的24修改成12)。
  2. 接下来不执行慢开始算法,而是把cwnd值设置为门限ssthresh减半后的数值(即cwnd不是设置为1而是设置为12),而后开始执行的是拥塞避免算法,使拥塞窗口缓慢地线性增大。

这里为何替换掉了慢开始算法呢?

这是由于收到重复的ACK不只仅告诉咱们一个分组丢失了,因为接收方只有在收到另外一个报文段时才会产生重复的ACK,因此还告诉咱们该报文段已经进入了接收方的缓存。也就是说,在收发两端之间仍然有流动的数据,而咱们不想执行慢启动来忽然减小数据流。

  1. sendto函数,发送数据给指定地址
    sendto函数比send函数多出两个参数,一个是目的地址,一个是地址长度。告诉客户端发送给哪一个IP地址和哪一个端口号。recvfrom函数比recv函数多出两个参数,至关于TCP的accept函数,告诉咱们是谁发送了数据过来。

ARP(地址解析协议)

ARP协议是辅助链路层传输的,在已经知道下一站路由器的IP地址后,要将以太网包发送给目的地址,可是以太网须要的是目的mac地址不是IP地址,而经过ARP请求包就能够得到目的IP地址的mac地址。

ARP请求的过程:源主机以广播的形式,发送一个ARP请求包,全部与源主机在直连的主机都会收到一个请求包,以下图所示,请求包询问目的IP地址的mac地址,目的IP地址的主机收到这个请求后,发送一个ARP应答,告诉源主机本身的mac地址。

RARP以与ARP相反的方式工做。RARP发出要反向解析的物理地址并但愿返回其对应的IP地址,应答包括由可以提供所需信息的RARP服务器发出的IP地址。

  1. Tracert 命令用 IP 生存时间 (TTL) 字段和 ICMP 错误消息来肯定从一个主机到网络上其余主机的路由。
  • 首先,tracert送出一个TTL是1的IP 数据包到目的地,当路径上的第一个路由器收到这个数据包时,它将TTL减1。此时,TTL变为0,因此该路由器会将此数据包丢掉,并送回一个「ICMP time exceeded」消息(包括发IP包的源地址,IP包的全部内容及路由器的IP地址),tracert 收到这个消息后,便知道这个路由器存在于这个路径上,接着tracert 再送出另外一个TTL是2 的数据包,发现第2 个路由器...... tracert 每次将送出的数据包的TTL 加1来发现另外一个路由器,这个重复的动做一直持续到某个数据包 抵达目的地。当数据包到达目的地后,该主机则不会送回ICMP time exceeded消息,一旦到达目的地,因为tracert经过UDP数据包向不常见端口(30000以上)发送数据包,所以会收到「ICMP port unreachable」消息,故可判断到达目的地。
  • tracert 有一个固定的时间等待响应(ICMP TTL到期消息)。若是这个时间过了,它将打印出一系列的*号代表:在这个路径上,这个设备不能在给定的时间内发出ICMP TTL到期消息的响应。而后,Tracert给TTL记数器加1,继续进行。
  1. 简单快速:当客户端向服务器端发送请求时,只是简单的填写请求路径和请求方法便可
  2. 灵活:HTTP 协议容许客户端和服务器端传输任意类型任意格式的数据对象
  3. 无链接:无链接的含义是限制每次链接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开链接,采用这种方式能够节省传输时间。(当今多数服务器支持Keep-Alive功能,使用服务器支持长链接,解决无链接的问题)
  4. 无状态:无状态是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。即客户端发送HTTP请求后,服务器根据请求,会给咱们发送数据,发送完后,不会记录信息。(使用 cookie 机制能够保持 session,解决无状态的问题)
  1. 默认持久链接节省通讯量,只要客户端服务端任意一端没有明确提出断开TCP链接,就一直保持链接,能够发送屡次HTTP请求
  2. 管线化,客户端能够同时发出多个HTTP请求,而不用一个个等待响应
  • HTTP/2采用二进制格式而非文本格式
  • HTTP/2是彻底多路复用的,而非有序并阻塞的——只需一个HTTP链接就能够实现多个请求响应
  • 使用报头压缩,HTTP/2下降了开销
  • HTTP/2让服务器能够将响应主动“推送”到客户端缓存中
  • GET是向服务器索取数据的请求
  • POST是向服务器提交数据的请求
  • GET是等幂的,POST不是等幂的,等幂就是一次执行和屡次执行效果同样!DELETE,PUT,HEAD,也是等幂的,因为网络是不可靠的,安全性和等幂性就特别重要,若是POST两次相同的,会产生两个资源。
  • get传输数据是经过URL请求,以field(字段)= value的形式,置于URL后,并用"?"链接,多个请求数据间用"&"链接,如,这个过程用户是可见的;post传输数据经过Http的post机制,将字段与对应值封存在请求实体中发送给服务器,这个过程对用户是不可见的;
  • GET安全性低,用户可见,POST安全性高些
  • GET效率高些,只发送一次数据包,POST会发送两次TCP数据包(先header,再data)
  • 数据量大小:URL不存在参数上限,取决于特定的浏览器或服务器限制,POST数据理论上也没有限制

100:请求者应当继续提出请求。服务器返回此代码则意味着,服务器已收到了请求的第一部分,现正在等
101(切换协议)请求者已要求服务器切换协议,服务器已确认并准备进行切换。

200:请求被正常处理
204:请求被受理但没有资源能够返回
206:客户端只是请求资源的一部分,服务器只对请求的部分资源执行GET方法,相应报文中经过Content-Range指定范围的资源。
303:与302状态码有类似功能,只是它但愿客户端在请求一个URI的时候,能经过GET方法重定向到另外一个URI上
304:发送附带条件的请求时,条件不知足时返回,与重定向无关
307:临时重定向,与302相似,只是强制要求使用POST方法
400:请求报文语法有误,服务器没法识别
403:请求的对应资源禁止被访问
404:服务器没法找到对应资源
500:服务器内部错误

http数据由请求行,首部字段,空行,报文主体四个部分组成
首部字段分为:通用首部字段,请求首部字段,响应首部字段,实体首部字段

https与http的区别?如何实现加密传输?

  • https就是在http与传输层之间加上了一个SSL
  • 对称加密与非对称加密:非对称加密和解密使用的是两个不一样的密钥,公钥和私钥,不须要交换密钥

浏览器中输入一个URL发生什么,用到哪些协议?

浏览器中输入URL,首先浏览器要将URL解析为IP地址,解析域名就要用到DNS协议,首先主机会查询DNS的缓存,若是没有就给本地DNS发送查询请求。DNS查询分为两种方式,一种是递归查询,一种是迭代查询。若是是迭代查询,本地的DNS服务器,向根域名服务器发送查询请求,根域名服务器告知该域名的一级域名服务器,而后本地服务器给该一级域名服务器发送查询请求,而后依次类推直到查询到该域名的IP地址。DNS服务器是基于UDP的,所以会用到UDP协议

获得IP地址后,浏览器就要与服务器创建一个http链接。所以要用到http协议,http协议报文格式上面已经提到。http生成一个get请求报文,将该报文传给TCP层处理。若是采用https还会先对http数据进行加密。TCP层若是有须要先将HTTP数据包分片,分片依据路径MTU和MSS。TCP的数据包而后会发送给IP层,用到IP协议。IP层经过路由选路,一跳一跳发送到目的地址。固然在一个网段内的寻址是经过以太网协议实现(也能够是其余物理层协议,好比PPP,SLIP),以太网协议须要直到目的IP地址的物理地址,有须要ARP协议。

SQL语言(内外链接,子查询,分组,汇集,嵌套,逻辑)

MySQL索引方法?索引的优化?

查询优化(从索引上优化,从SQL语言上优化)

MySQL的联合索引(又称多列索引)是什么?生效的条件?

    1. 进程是资源分配的基本单位,线程是cpu调度,或者说是程序执行的最小单位。可是并非说CPU不在以进程为单位进行调度,虽然在某些操做系统中是这样。同一个进程中并行运行多个线程,就是对在同一台计算机上运行多个进程的模拟。
    2. 进程有独立的地址空间,而同一进程中的线程共享该进程的地址空间。好比在linux下面启动一个新的进程,系统必须分配给它独立的地址空间,创建众多的数据表来维护它的代码段、堆栈段和数据段,这是一种很是昂贵的多任务工做方式。而运行一个进程中的线程,它们之间共享大部分数据,使用相同的地址空间,所以启动一个线程,切换一个线程远比进程操做要快,花费也要小得多。
    3. 线程之间的通讯比较方便。统一进程下的线程共享数据(好比全局变量,静态变量,打开的文件,子进程)
    4. 多进程比多线程程序要健壮。一个线程死掉整个进程就死掉了,可是在保护模式下,一个进程死掉对另外一个进程没有直接影响。
    5. 线程的执行与进程是有区别的。每一个独立的线程有有本身的一个程序入口,顺序执行序列和程序的出口,可是线程不能独立执行,必须依附与程序之中,由应用程序提供多个线程的并发控制。
    6. linux中进程具备父子关系,造成进程树,可是线程是平等的没有父子关系
    1. 多进程程序,一个进程崩溃不会影响其余进程,可是进程之间的切换和通讯代价较大;
    2. 多线程程序,一个线程崩溃会致使整个进程死掉,其余线程也不能正常工做,可是线程以前数据共享和通讯更加方便。
    3. 进程须要开辟独立的地址空间,多进程对资源的消耗很大,而线程则是“轻量级进程”,对资源的消耗更小,对于大并发的状况,只有线程加上IO复用技术才能适应。

    所以,对于须要频繁交互数据的,频繁的对同一个对象进行不一样的处理,选择多线程合适,对于一些并发编程,不须要不少数据交互的采用多进程。

    1. 一个任务能够分红多个子任务并行执行,他们是对一个对象在操做。
    2. 线程不须要像进程同样维护那么多信息,所以建立和销毁速度更快,拥有同一个地址空间,访问很容易
    3. 任务有CPU密集和IO等待的过程,使用线程能够最大化利用CPU

进程的内存布局在结构上是有规律的,具体来讲对于linux系统上的进程,其内存空间通常能够粗略地分为如下几大段,从高内存到低内存排列:

  1. 内核态内存空间,其大小通常比较固定(能够编译时调整),但 32 位系统和 64 位系统的值不同。
  2. 用户态的栈,大小不固定,能够用 ulimit -s 进行调整,默认通常为 8M,从高地址向低地址增加。
  3. 共享内存区,包括动态共享库以及mmap内存映射的区域
  4. 数据段,bss 未初始化 以及 data 已初始化的全局静态变量等
  • 信号量:信号量用于实现进程间的互斥与同步,而不是用于存储进程间通讯数据。

  • 管道( pipe ):管道是一种半双工的通讯方式,数据只能单向流动,并且只能在具备亲缘关系的进程间使用。进程的亲缘关系一般是指父子进程关系。

  • 命名管道 (named pipe) : 有名管道也是半双工的通讯方式,可是它容许无亲缘关系进程间的通讯。

  • 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

  • 信号 ( sinal ) : 信号是一种比较复杂的通讯方式,用于通知接收进程某个事件已经发生。

  • 共享内存( shared memory ) :共享内存就是映射一段能被其余进程所访问的内存,这段共享内存由一个进程建立,但多个进程均可以访问。共享内存是最快的 IPC 方式,它是针对其余进程间通讯方式运行效率低而专门设计的。它每每与其余通讯机制,如信号两,配合使用,来实现进程间的同步和通讯。

  • 套接字( socket ) : 套解口也是一种进程间通讯机制,与其余通讯机制不一样的是,它可用于不一样及其间的进程通讯。

匿名管道与命名管道的区别:匿名管道只能在具备公共祖先的两个进程间使用

mmap创建进程空间到文件的映射,在创建的时候并不直接将文件拷贝到物理内存,一样采用缺页终端。mmap映射一个具体的文件能够实现任意进程间共享内存,映射一个匿名文件,能够实现父子进程间共享内存。

  • SIGINT 终止进程,一般咱们的Ctrl+C就发送的这个消息。
  • SIGKILL 消息编号为9,咱们常常用kill -9来杀死进程发送的就是这个消息,程序收到这个消息当即终止,这个消息不能被捕获,封锁或这忽略,因此是杀死进程的终极武器。
  • SIGTERM 是不带参数时kill默认发送的信号,默认是杀死进程。(能够被捕获)
  • SIGSEGV 就是SegmentFault 试图访问未分配给本身的内存, 或试图往没有写权限的内存地址写数据
  • SIGCHLD 一个子进程中止或终止,默认行为忽略
  • SIGALRM 来自alarm函数的定时信号,默认行为 终止
  1. 下次适配next fit从上次找到的空闲区接着找
  2. 最佳适配best fit查找整个空闲区表,能知足要求的最小空闲区
  3. 最差适配worst fit老是分配最大空闲区
  • 在C和C++语言中,常常须要动态分配内存,咱们会用到new,delete,malloc,free。可是当咱们分配不少小块的内存时,会形成不少的内存碎片,大大下降了内存的使用效率。为了减小内存碎片的出现,采用了内存池技术。
  • 内存池的先调用malloc函数申请一大块内存,而后维护一个空闲链表,该链表是一个个小的空闲内存片,每当须要内存时就从空闲链表上拿过来一个小片内存使用。若是空闲链表为空了,就从以前分配的大块内存去取几个插入到空闲链表上。若是分配的大块内存也用光了,就继续用malloc申请一大块。

进程空间和内核空间对内存的管理不一样?

slab是Linux操做系统的一种内存分配机制。其工做是针对一些常常分配并释放的对象,如进程描述符等,这些对象的大小通常比较小,若是直接采用伙伴系统来进行分配和释放,不只会形成大量的内存碎片,并且处理速度也太慢。而slab分配器是基于对象进行管理的,相同类型的对象归为一类(如进程描述符就是一类),每当要申请这样一个对象,slab分配器就从一个slab列表中分配一个这样大小的单元出去,而当要释放时,将其从新保存在该列表中,而不是直接返回给伙伴系统,从而避免这些内碎片。slab分配器并不丢弃已分配的对象,而是释放并把它们保存在内存中。当之后又要请求新的对象时,就能够从内存直接获取而不用重复初始化。

解决问题:频繁地请求和释放不一样大小的一组连续页框,必然致使在已分配页框的块内分散了许多小块的空闲页面,由此带来的问题是,即便有足够的空闲页框能够知足请求,但要分配一个大块的连续页框可能没法知足请求。

伴算法虽然可以彻底避免外部碎片的产生,但这偏偏是以产生内部碎片为代价的。

  • 当须要分配若干个内存页面时,用于DMA的内存页面必须连续,伙伴算法很好的知足了这个要求
  • 只要请求的块不超过512个页面(2K),内核就尽可能分配连续的页面。
  • 合并的要求太过严格,只能是知足伙伴关系的块才能合并,好比第1块和第2块就不能合并。
  • 碎片问题:一个连续的内存中仅仅一个页面被占用,致使整块内存区都不具有合并的条件
  • 浪费问题:伙伴算法只能分配2的幂次方内存区,当须要8K(2页)时,好说,当须要9K时,那就须要分配16K(4页)的内存空间,可是实际只用到9K空间,多余的7K空间就被浪费掉。
  • 算法的效率问题: 伙伴算法涉及了比较多的计算还有链表和位图的操做,开销仍是比较大的,若是每次2n大小的伙伴块就会合并到2(n+1)的链表队列中,那么2n大小链表中的块就会由于合并操做而减小,但系统随后当即有可能又有对该大小块的需求,为此必须再从2(n+1)大小的链表中拆分,这样的合并又当即拆分的过程是无效率的。

全部的空闲页框分组为 11 块链表,每一块链表分别包含大小为1,2,4,8,16,32,64,128,256,512 和 1024 个连续的页框。

  • 假设要请求一个256(129~256)个页框的块。算法先在256个页框的链表中检查是否有一个空闲块。若是没有这样的块,算法会查找下一个更大的页块,也就是,在512个页框的链表中找一个空闲块。若是存在这样的块,内核就把512的页框分红两等分,通常用做知足需求,另外一半则插入到256个页框的链表中。若是在512个页框的块链表中也没找到空闲块,就继续找更大的块——1024个页框的块。若是这样的块存在,内核就把1024个页框块的256个页框用做请求,而后剩余的768个页框中拿512个插入到512个页框的链表中,再把最后的256个插入到256个页框的链表中。若是1024个页框的链表仍是空的,算法就放弃并发出错误信号。

Linux是如何避免内存碎片的

  1. 伙伴算法,用于管理物理内存,避免内存碎片;
  2. 高速缓存Slab层用于管理内核分配内存,避免碎片。

共享内存实现分为两种方式一种是采用mmap,另外一种是采用XSI机制中的共享内存方法。mmap是内存文件映射,将一个文件映射到进程的地址空间,用户进程的地址空间的管理是经过vm_area_struct结构体进行管理的。mmap经过映射一个相同的文件到两个不一样的进程,就能实现这两个进程的通讯,采用该方法能够实现任意进程之间的通讯。mmap也能够采用匿名映射,不指定映射的文件,可是只能在父子进程间通讯。XSI的内存共享实际上也是经过映射文件实现,只是其映射的是一种特殊文件系统下的文件,该文件是不能经过read和write访问的。

  1. 互斥锁,自旋锁,信号量,读写锁,屏障
  2. 互斥锁与自旋锁的区别:互斥锁得不到资源的时候阻塞,不占用cpu资源。自旋锁得不到资源的时候,不停的查询,而然占用cpu资源。

明显不是,++i主要有三个步骤,把数据从内存放在寄存器上,在寄存器上进行自增,把数据从寄存器拷贝会内存,每一个步骤均可能被中断。

大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿相似于把数据看成字符串顺序处理:地址由小向大增长,而数据从高位往低位放;这和咱们的阅读习惯一致。

小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。

如何判断操做系统是32位仍是64位?

  • 机器位数不一样,表示的数字最大值也不一样
  • 对0值取反,判断是否是大于32位下所能表示的最大数
  1. 单例模式线程安全写法,参考
  2. STL里的迭代器使用了迭代器模式

温馨提示:答案为网友推荐,仅供参考

用链表试试。不定长的。for中比较最大最小值就ok。 追问

我是初学者 希望能讲的清楚点 谢谢啦

下面那个代码不错。。。比我这个方法强。

我要回帖

更多关于 求两个整数中的较大者c语言代码 的文章

 

随机推荐