1.C++特性
抽象、封装、继承、多态
2.多态怎么实现的
多态:分为静态多态和动态多态:
静态多态:编译器在编译期间完成的,编译器会根据实参类型来推断该调用哪个函数,如果有对应的函数,就调用,没有则在编译时报错。
动态多态:其实要实现动态多态,需要几个条件——即动态绑定条件:
1、虚函数。基类中必须有虚函数,在派生类中必须重写虚函数。
2、通过基类类型的指针或引用来调用虚函数。
C++的多态性用一句话概括就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数
1:用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数。
2:存在虚函数的类都有一个一维的虚函数表叫做虚表,类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。
3:多态性是一个接口多种实现,是面向对象的核心,分为类的多态性和函数的多态性。
4:多态用虚函数来实现,结合动态绑定.
5:纯虚函数是虚函数再加上 = 0;
6:抽象类是指包括至少一个纯虚函数的类。
纯虚函数:virtual void fun()=0;即抽象类!必须在子类实现这个函数,即先有名称,没有内容,在派生类实现内容。
3.可继承的类的实现需要注意什么问题(构造函数、析构函数)
如果想要子类自动调用父类的构造函数,父类则必须提供无参构造函数 。
1、默认构造函数(没有参数,或者参数有默认值),如果派生类的成员初始化列表没有显式的调用基类的构造函数,编译器会调用基类的默认构造函数。如果定义了构造函数,那么编译器将不会定义默认构造函数,如果需要默认构造函数,需要自己定义。
2、复制构造函数(拷贝构造函数),复制构造函数接受其所属类的对象作为参数
什么时候使用复制构造函数:
将新对象初始化为一个同类对象
按值将对象传递给函数
函数按值返回对象
编译器生成临时对象
使用公有继承要考虑的因素:
1、什么不能被继承?
构造函数,析构函数,赋值运算符,友元函数
2、如果派生类希望使用基类的友元函数,如何解决?
2.1 使用强制转换,将派生类指针或引用强制转换为基类指针或引用,然后使用转换后的指针或yy调用
2.2 使用运算符 dynamic_cast<>来进行强制类型转换
3、析构函数,通常情况下应该是虚的。
4、对于派生类而言,保护的成员类似公有成员,对于外部而言,保护成员类似于私有成员。 派生类可以直接访问基类的保护成员
4.引用和指针区别
★ 相同点:
1. 都是地址的概念;
指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。
★ 区别:
1. 指针是一个实体,而引用仅是个别名;
2. 引用使用时无需解引用(*),指针需要解引用;
3. 引用只能在定义时被初始化一次,之后不可变;指针可变;
4. 引用没有 const,指针有 const;
5. 引用不能为空,指针可以为空;
6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;
7. 指针和引用的自增(++)运算意义不一样;
8.从内存分配上看:程序为指针变量分配内存区域,而引用不需要分配内存区域。
5.const用法
一、const修饰普通类型的变量
被编译器认为是一个常量,其值不允许修改。
二、const 修饰指针变量有以下三种情况。
A:const 修饰指针指向的内容,则内容为不可变量。
B:const 修饰指针,则指针为不可变量。
C:const 修饰指针和指针指向的内容,则指针和指针指向的内容都为不可变量。
三、对于const修饰函数参数可以分为三种情况。
A:值传递的const修饰传递,一般这种情况不需要const修饰,因为函数会自动产生临时变量复制实参值。
B:当const参数为指针时,可以防止指针被意外篡改。
C:自定义类型的参数传递,需要临时对象复制参数,对于临时对象的构造,需要调用构造函数,比较浪费时间,因此我们采取const外加引用传递的方法。并且对于一般的int ,double等内置类型,我们不采用引用的传递方式。
四、Const修饰返回值分三种情况。
A:const修饰内置类型的返回值,修饰与不修饰返回值作用一样。
B:const 修饰自定义类型的作为返回值,此时返回的值不能作为左值使用,既不能被赋值,也不能被修改。
C: const 修饰返回的指针或者引用,是否返回一个指向const的指针,取决于我们想让用户干什么。
五、const 修饰类成员函数,其目的是防止成员函数修改被调用对象的值,如果我们不想修改一个调用对象的值,所有的成员函数都应当声明为const成员函数。注意:const关键字不能与static关键字同时使用,因为static关键字修饰静态成员函数,静态成员函数不含有this指针,即不能实例化,const成员函数必须具体到某一实例。
6.RAII
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
RAII 的一般做法是这样的:在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
不需要显式地释放资源。
采用这种方式,对象所需的资源在其生命期内始终保持有效。
2.实战应用
2.1 scope lock (局部锁技术) (参见 ACE ACE_Gaurd 模板类的实现)
在很多时候,为了实现多线程之间的数据同步,我们会使用到 mutex,critical section,event,singal 等技术。但在使用过程中,由于各种原因,有时候,我们会遇到一个问题:由于忘记释放(Unlock)锁,产生死锁现象。采用RAII 就可以很好的解决这个问题,使用着不必担心释放锁的问题.
7.函数传值、传引用、传指针区别
传值:是把实参的值赋值给行参,那么对行参的修改,不会影响实参的值
传地址:是传值的一种特殊方式,只是他传递的是地址,不是普通的如int,那么传地址以后,实参和行参都指向同一个对象
传引用:真正的以地址的方式传递参数,传递以后,行参和实参都是同一个对象,只是他们名字不同而已, 对行参的修改将影响实参的值。
传递引用与传指针、传值的区别?
(1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
(2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
(3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。
从本质上来说,传值和传指针都是传值方式,除了传引用之外。
8.STL迭代器
1 头文件
所有容器有含有其各自的迭代器型别(iterator types),所以当你使用一般的容器迭代器时,并不需要含入专门的头文件。不过有几种特别的迭代器,例如逆向迭代器,被定义于<iterator>中。
2 迭代器类型
迭代器共分为五种,分别为:
Input iterator、只能一次一个向前读取元素,按此顺序一个个传回元素值,支持++,不能--
Output iterator、将元素值一个个写入,支持++,不能--
Forward iterator、是Input迭代器与Output迭代器的结合,具有Input迭代器的全部功能和Output迭代器的大部分功能
Bidirectional iterator、在Forward迭代器的基础上增加了回头遍历的能力。换言之,它支持递减操作符,
Random access iterator、在Bidirectional迭代器的基础上再增加随机存取能力。
提供“迭代器算数运算”(和一般指针“指针算术运算”相当)。也就是说,它能加减某个偏移量、能处理距离(differences)问题,并运用诸如<和>的相互关系操作符进行比较。以下对象和型别支持Random Access迭代器:
可随机存取的容器(vector, deque)
strings(字符串,string,wstring)
一般array(指针)
迭代器iterator 提供了一种一般化的方法对顺序或关联容器类型中的每个元素进行连续访问
例如,假设iter为任意容器类型的一个iterator,则++iter 表示向前移动迭代器使其指向容器的下一个元素,而*iter 返回iterator 指向元素的值,每种容器类型都提供一个begin()和一个end()成员函数。
begin()返回一个iterator 它指向容器的第一个元素
end()返回一个iterator 它指向容器的末元素的下一个位置
9.拷贝构造函数什么时候需要重写
当构造函数涉及到动态存储分配空间时,要自己写拷贝构造函数,并且要深拷贝。
当没有定义拷贝构造函数时,对象值传递时是位拷贝,但是通常情况下,位拷贝已经能满足我们的要求,是我们不必自己定义拷贝构造函数。
如果你需要定义一个非空的析构函数,那么,通常情况下你也需要定义一个拷贝构造函数。
拷贝构造函数的参数类型一般都是const ,是因为复制构造函数是用引用方式传递复制对象,引用方式传递的是地址,因此在构造函数内对该引用的修改会影响源对象。而用对象a1构造a2时,自然不希望复制构造函数会改变a1的内容,因此要防止复制构造函数内部修改该引用,所以用const声明。
10.placement new
不常用的new操作placement new,在使用时需要我们传入一个指针,此时会在该指针指向的内存空间构造该对象,该指针指向的地址可以使堆空间,栈空间,也可以是静态存储区。
通过placement new和显式调用析构函数的方式我们就可以在已有的空间上动态地创建和销毁对象。
new运算符会在堆中动态分配内存,并在成功分配的内存空间上调用相应对象的构造函数,而delete运算符会先在该内存空间上调用相应对象的析构函数,然后释放该内存空间。
11.对象池
什么是对象池(模式):
对象池(模式)是一种创建型设计模式
它持有一个初始化好的对象的集合,将对象提供给调用者。
对象池的目的:
减少频繁创建和销毁对象带来的成本,实现对象的缓存和复用
什么条件下使用对象池:
创建对象的成本比较大,并且创建比较频繁。比如线程的创建代价比较大,于是就有了常用的线程池。
对象池无可用的对象时,再次对象请求,可能的表现行为:
如果池的大小可以增长,创建新的对象并返回给client
阻塞client调用,直到有可用的对象回收并返回
抛出异常,通知client
返回null给client
同步处理:
在多线程的环境下,我们也会使用对象池。因此做好必要的同步是必须的。
要进行同步处理的通常是这两个方法
aquire或obtain 负责返回对象
release或recycle 负责回收对象
对象池好处
提升了client获取对象的响应速度,比如单个线程和资源连接的创建成本都比较大。
一定程度上减少了GC的压力。
对于实时性要求较高的程序有很大的帮助
对象池弊端
脏对象的问题
所谓的脏对象就是指的是当对象被放回对象池后,还保留着刚刚被客户端调用时生成的数据。
脏对象可能带来两个问题
脏对象持有上次使用的引用,导致内存泄漏等问题。
脏对象如果下一次使用时没有做清理,可能影响程序的处理数据。
12.函数模板、类模板区别
类模板的重点是模板。表示的是一个模板,专门用于产生类的模子。例子:
1 template <typename T>
2 class Vector
3 {
4 ...
5 };
使用这个Vector模板就可以产生很多的class(类),Vector<int>、Vector<char>、 Vector< Vector<int> >、Vector<Shape*>……。
模板类的重点是类。表示的是由一个模板生成而来的类。例子:
上面的Vector<int>、Vector<char>、……全是模板类。
函数模板的重点是模板。表示的是一个模板,专门用来生产函数。
模板函数的重点是函数。表示的是由一个模板生成而来的函数。例子:
上面生成的fun<int>、fun<Shape*>……都是模板函数。
13.Linux内存管理
代码段:代码段是用来存放可执行文件的操作指令,也就是说是它是可执行程序在内存中的镜像。
数据段:数据段用来存放可执行文件中已初始化全局变量,换句话说就是存放程序静态分配的变量和全局变量。
BSS段[2]:BSS段包含了程序中未初始化的全局变量,在内存中 bss段全部置零。
堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减,向上。
栈:栈是用户存放程序临时创建的局部变量。向下
数据段、BSS和堆通常是被连续存储的——内存位置上是连续的,而代码段和栈往往会被独立存放。
14.Linux父进程怎么知道子进程结束了
一、父进程创建子进程,使用fork(), 一次fork() 两次返回,一次返回的是父进程自己,一次返回的是子进程的运行空间。
如果fork调用成功,返回pid > 0 给父进程,返回pid == 0给子进程。
如果fork调用失败,返回pid == -1给父进程,并且设置errno。
二、父进程必须监控子进程的运行状态,等待子进程退出后,使用wait()进行关闭,否则,如果父进程先退出,子进程就没有父亲了,就变成了僵尸进程,此时退出时,就会被系统进程init监控到,然后由系统进程init进行关闭和释放资源。
父进程在执行完自己的所有操作后,会以阻塞或者轮询的方式等待子进程的退出,收到子进程的退出数据之后,通过一定的手段,对子进程的PCB进行资源的释放。wait(只会进行阻塞式等待子进程; )、waitpid(需要等待的进程id号)是父进程对子进程的等待函数,成功的返回值都是子进程的pid,直到收到这个pid,系统就会对这个pid对应的PCB进行回收,资源的释放的等操作,保证系统的效率。15.进程状态有哪几个
16.守护进程创建
1、fork()创建子进程,父进程exit()退出
这是创建守护进程的第一步。由于守护进程是脱离控制终端的,因此,完成第一步后就会在Shell终端里造成程序已经运行完毕的假象。之后的所有工作都在子进程中完成,而用户在Shell终端里则可以执行其他命令,从而在形式上做到了与控制终端的脱离,在后台工作。
2、在子进程中调用 setsid() 函数创建新的会话
在调用了 fork() 函数后,子进程全盘拷贝了父进程的会话期、进程组、控制终端等,虽然父进程退出了,但会话期、进程组、控制终端等并没有改变,因此,这还不是真正意义上的独立开来,而 setsid() 函数能够使进程完全独立出来。
3、再次 fork() 一个子进程并让父进程退出。
现在,进程已经成为无终端的会话组长,但它可以重新申请打开一个控制终端,可以通过 fork() 一个子进程,该子进程不是会话首进程,该进程将不能重新打开控制终端。退出父进程。
4、在子进程中调用 chdir() 函数,让根目录 ”/” 成为子进程的工作目录
这一步也是必要的步骤。使用fork创建的子进程继承了父进程的当前工作目录。由于在进程运行中,当前目录所在的文件系统(如“/mnt/usb”)是不能卸载的,这对以后的使用会造成诸多的麻烦(比如系统由于某种原因要进入单用户模式)。因此,通常的做法是让"/"作为守护进程的当前工作目录,这样就可以避免上述的问题,当然,如有特殊需要,也可以把当前工作目录换成其他的路径,如/tmp。改变工作目录的常见函数是chdir。
5、在子进程中调用 umask() 函数,设置进程的文件权限掩码为0
文件权限掩码是指屏蔽掉文件权限中的对应位。比如,有个文件权限掩码是050,它就屏蔽了文件组拥有者的可读与可执行权限。由于使用fork函数新建的子进程继承了父进程的文件权限掩码,这就给该子进程使用文件带来了诸多的麻烦。因此,把文件权限掩码设置为0,可以大大增强该守护进程的灵活性。设置文件权限掩码的函数是umask。在这里,通常的使用方法为umask(0)。
6、在子进程中关闭任何不需要的文件描述符
同文件权限码一样,用fork函数新建的子进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法卸下。
在上面的第二步之后,守护进程已经与所属的控制终端失去了联系。因此从终端输入的字符不可能达到守护进程,守护进程中用常规方法(如printf)输出的字符也不可能在终端上显示出来。所以,文件描述符为0、1和2 的3个文件(常说的输入、输出和报错)已经失去了存在的价值,也应被关闭。
7、守护进程退出处理
当用户需要外部停止守护进程运行时,往往会使用 kill 命令停止该守护进程。所以,守护进程中需要编码来实现 kill 发出的signal信号处理,达到进程的正常退出。
17.怎么查看进程
查进程
ps命令查找与进程相关的PID号:
ps a 显示现行终端机下的所有程序,包括其他用户的程序。
ps -A 显示所有程序。
ps c 列出程序时,显示每个程序真正的指令名称,而不包含路径,参数或常驻服务的标示。
ps -e 此参数的效果和指定"A"参数相同。
ps e 列出程序时,显示每个程序所使用的环境变量。
ps f 用ASCII字符显示树状结构,表达程序间的相互关系。
ps -H 显示树状结构,表示程序间的相互关系。
ps -N 显示所有的程序,除了执行ps指令终端机下的程序之外。
ps s 采用程序信号的格式显示程序状况。
ps S 列出程序时,包括已中断的子程序资料。
ps -t<终端机编号> 指定终端机编号,并列出属于该终端机的程序的状况。
ps u 以用户为主的格式来显示程序状况。
ps x 显示所有程序,不以终端机来区分。
最常用的方法是ps aux,然后再通过管道使用grep命令过滤查找特定的进程,然后再对特定的进程进行操作。
ps aux | grep program_filter_word,ps -ef |grep tomcat
ps -ef|grep java|grep -v grep 显示出所有的java进程,去处掉当前的grep进程。
18.进程和线程区别
进程和线程的区别:一个程序至少一个进程,一个进程至少一个线程。
进程:是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位。
线程:是进程的一个执行单元,是进程内科调度实体。比进程更小的独立运行的基本单位。线程也被称为轻量级进程。
进程线程的区别:
地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的。
一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程
执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
线程是处理器调度的基本单位,但是进程不是。
两者均可并发执行。
优缺点:
线程执行开销小,但是不利于资源的管理和保护。线程适合在SMP机器(双CPU系统)上运行。
进程执行开销大,但是能够很好的进行资源管理和保护。进程可以跨机器前移。
何时使用多进程,何时使用多线程?
对资源的管理和保护要求高,不限制开销和效率时,使用多进程。
要求效率高,频繁切换时,资源的保护管理要求不是很高时,使用多线程。
19.进程间通信方式
一、管道
管道,通常指无名管道,是 UNIX 系统IPC最古老的形式。
1、特点:
它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。
它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
二、FIFO
FIFO,也称为命名管道,它是一种文件类型。
1、特点
FIFO可以在无关的进程之间交换数据,与无名管道不同。
FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
三、消息队列
消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
1、特点
消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
四、信号量
信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
1、特点
信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
支持信号量组。
五、共享内存
共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。
1、特点
共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
因为多个进程可以同时操作,所以需要进行同步。
信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。
20.STL中各种容器的底层实现
STL中基本容器有: vector、list、deque、set、map
1.vector 底层数据结构为数组 ,支持快速随机访问
2.list 底层数据结构为双向链表,支持快速增删
3.deque:双端队列 底层数据结构为一个中央控制器和多个缓冲区,
4.stack 底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时
5.queue 底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时
6.priority_queue 的底层数据结构一般为vector为底层容器,堆heap为处理规则来管理底层容器实现
7.set 底层数据结构为红黑树,有序,不重复
8.multiset 底层数据结构为红黑树,有序,可重复
9.map 底层数据结构为红黑树,有序,不重复
10.multimap 底层数据结构为红黑树,有序,可重复
11.hash_set 底层数据结构为hash表,无序,不重复
12.hash_multiset 底层数据结构为hash表,无序,可重复
13.hash_map 底层数据结构为hash表,无序,不重复
14.hash_multimap 底层数据结构为hash表,无序,可重复
21.B+树、数据库内容(我不会,所以面试官就没问)
22.TCP分层
OSI分层(7层)
物理层、数据链路层、网络层、运输层、会话层、表示层、应用层
TCP/IP分层(4层)
网络接口层、网络层、运输层、应用层
五层协议(5层)
物理层、数据链路层、网络层、运输层、应用层
五层结构的概述
应用层:通过应用进程间的交互来完成特定网络应用
数据:报文
协议:HTTP, SMTP(邮件), FTP(文件传送)
运输层:向两个主机进程之间的通信提供通用的数据传输服务。
数据:TCP:报文段,UDP:用户数据报
协议:TCP, UDP
网络层:为分组交换网上的不同主机提供通信服务
数据:包或IP数据报
协议:IP
数据链路层:
数据:帧
协议:PPP、FR、HDLC、VLAN、MAC (网
物理层:
数据:比特
协议:RJ45、CLOCK、IEEE802.3 (中继器,集线器)
23.epoll和select区别
24.三次握手、四次挥手
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,
第一步,当主机A的应用程序通知TCP数据已经发送完毕时,TCP向主机B发送一个带有FIN附加标记的报文段(FIN表示英文finish)。
第二步,主机B收到这个FIN报文段之后,并不立即用FIN报文段回复主机A,而是先向主机A发送一个确认序号ACK,同时通知自己相应的应用程序:对方要求关闭连接(先发送ACK的目的是为了防止在这段时间内,对方重传FIN报文段)。
第三步,主机B的应用程序告诉TCP:我要彻底的关闭连接,TCP向主机A送一个FIN报文段。
第四步,主机A收到这个FIN报文段后,向主机B发送一个ACK表示连接彻底释放
25.定义全局变量需要注意哪些
1.在文件中定义了全局变量,需要在前面使用extern关键字对其进行声明。
2.在一个cpp文件中定义了全局变量,需要在其他文件中使用:这时需要在其他文件中需要使用的地方之前用extern声明下
3.在一个cpp文件中定义了全局变量,但是仅仅需要在本文件中使用该变量:这是需要在定义的时候加上static关键字。
一、全局变量定义要写在cpp文件中
二、头文件中不能定义变量,只能声明变量
1、声明变量使用extern关键字
三、自定义类型的定义,写在头文件中
全局变量在定义或者声明的时候最好给变量进行初始化。
26.部署过动态库、静态库吗?
27.怎么调试程序的?gdb怎么调试?gdb调试怎么传参的
gdb test 进入调试
下面可以使用两种方法输入命令行参数
1)run 命令行参数
2)set args 命令行参数
28. 单例模式
注意:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
应用实例: 1、一个党只能有一个主席。 2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
优点: 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。 2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景: 1、要求生产唯一序列号。 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
29. HTTP1.1和1.0区别
1.HTTP 1.0需要使用keep-alive参数来告知服务器端要建立一个长连接,而HTTP1.1默认支持长连接。
2.HTTP 1.1支持只发送header信息(不带任何body信息),如果服务器认为客户端有权限请求服务器,则返回100,否则返回401。客户端如果接受到100,才开始把请求body发送到服务器。
3.HTTP1.0是没有host域的,HTTP1.1才支持这个参数。
4.HTTP2.0使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1大了好几个数量级。
当然HTTP1.1也可以多建立几个TCP连接,来支持处理更多并发的请求,但是创建TCP连接本身也是有开销的。
30.TCP、UDP区别
TCP与UDP区别总结:
1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
Tcp通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输。如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。
3、UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。
4.每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
5、TCP对系统资源要求较多,UDP对系统资源要求较少。
2、为什么UDP有时比TCP更有优势?
UDP以其简单、传输快的优势,在越来越多场景下取代了TCP,如实时游戏。
(1)网速的提升给UDP的稳定性提供可靠网络保障,丢包率很低,如果使用应用层重传,能够确保传输的可靠性。
(2)TCP为了实现网络通信的可靠性,使用了复杂的拥塞控制算法,建立了繁琐的握手过程,由于TCP内置的系统协议栈中,极难对其进行改进。
采用TCP,一旦发生丢包,TCP会将后续的包缓存起来,等前面的包重传并接收到后再继续发送,延时会越来越大,基于UDP对实时性要求较为严格的情况下,采用自定义重传机制,能够把丢包产生的延迟降到最低,尽量减少网络问题对游戏性造成影响。
31.TCP流量控制
流量控制概述
那就是如果发送方把数据发送得过快,接收方可能会来不及接收,这就会造成数据的丢失。
TCP的流量控制是利用滑动窗口机制实现的,接收方在返回的ACK中会包含自己的接收窗口的大小,以控制发送方的数据发送。
TCP拥塞控制
1. 在TCP数据传输过程中可能会出现很多数据一下子涌入网络中,会使整个网络拥塞不堪,导致整个网络资源供应不足,整个性能下降,出现丢失数据严重的情况,从而使网络吞吐量极速下降;而如果采用一次性只传输一小段报文段数目,一直维持这种低速数据传输的话整个网络传输效率也是一个严重的问题,而TCP的拥塞控制就是在这两者之间进行一种权衡的方案,既能保证网络传输过程中数据的完整性、可靠性,又能稳定的提升网络传输效率,提高网络吞吐量,从而平衡整个网络性能。
2. 常用的拥塞控制方法
包括慢启动(slow-start)、拥塞避免(congestion avoidance)、快速重传(fast retransmit)和快恢复(fast recorver)。
32. 生产者消费者的类怎么实现?
33. 从高级语言到二进制的程序经过了哪些过程
预处理
首先是源代码文件test.c和相关的头文件,如stdio.h等被预处理器cpp预处理成一个.i文件。
编译:编译器将hello.i文件翻译成文本文件*hello.s,
编译过程就是把预处理完的文件进行一系列的词法分析、语法分析、语义分析以及优化后产生相应的汇编代码文件,-》.s/asm (汇编程序)
汇编:汇编器将hello.s翻译成机器语言指令
汇编器是将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。.o/obj 目标程序(二进制文件)
链接
即需要将一大堆文件链接起来才可以得到“a.out”。-》.exe可执行程序 (二进制文件)
34. GET 和 POST 的区别
get参数通过url传递,post放在request body中。
get请求在url中传递的参数是有长度限制的,而post没有。
get比post更不安全,因为参数直接暴露在url中,所以不能用来传递敏感信息。
get请求只能进行url编码,而post支持多种编码方式
get请求会浏览器主动cache,而post支持多种编码方式。
get请求参数会被完整保留在浏览历史记录里,而post中的参数不会被保留。
GET和POST本质上就是TCP链接,并无差别。但是由于HTTP的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同。
GET产生一个TCP数据包;POST产生两个TCP数据包。
长的说:
对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。