• C++面试经典题目汇总


    一:C++中指针和引用的区别

    1.指针是用来保存存储地址的,一般需要初始化NULL。

    引用是取别名(类似小名),定义的时候一定要初始化,不能为NULL。

    2.引用不占用字节,但引用不可以改变:引用和变量指向同一片内存。

    指针占用4个字节,指针可以指向不同的地址,可以改名称。

    3.引用比指针使用起来会更加简洁,安全。使用指针时要特别注意野指针

    4.在sizeof中含义不同:引用结果为引用类型的大小,指针始终是地址空间所占字节个数(32位:4字节 64位8字节)。

    5.引用自加即引用的实体增加1,指针自加即指针向后偏移一个数据类型的大小。

    6.有多级指针,但是没有多级引用。

    7.访问实体方式不同,指针需要显式解引用,引用则是编译器自己处理。

    二:C++中重写和重载的区别

    1.重载(overload):函数名相同,函数的参数个数,参数类型或者参数顺序三者中必须至少有一种不同。函数返回值的类型可以相同,也可以不同。发生在一个类内部,不能跨作用域。也就是说使用同一个函数去完成不同的功能。

    2.重写(override):也叫做覆盖,一般发生在子类和父类继承关系之间。子类重新定义父类中有相同名称和参数的虚函数,返回值可以不相同,但是必须是父子关系的指针或者引用。

    重写需要注意:

    a.被重写的函数不能是static,必须是virtual

    b.重写函数必须有相同的类型,名称和参数列表

    c.重写函数的访问修饰符(public/private/protected)可以不同

    三:什么是进程

    程序的执行实例被称为进程(process)。简单来说,进程是动态的概念,指的是程序的一次运行活动,通俗点来看就是程序运行起来了的时候,系统中就多了一个进程。进程相当于是程序的一次执行过程。进程包含线程。

    四:什么是程序

    程序(program)是存放在磁盘文件中的可执行文件。是静态的。

    五:程序和进程的区别

    1.程序是指令和数据的有序集合,是一个静态的概念,而进程是程序在处理机上的一个执行过程,它是一个动态的概念。

    2.程序可以作为一种软件资料长期存在,而进程是有一定生命周期的。程序是永久的,进程只是暂时的。

    3.进程是进程控制块,程序段,数据段三部分组成。

    4.进程具有创建其他进程的功能,而程序没有。

    5.同一程序同时运行于若干个数据集合上,它将属于若干个不同的进程,也就是说同一个程序可以对应多个进程。

    6.在传统的操作系统中,程序并不能独立运行,作为资源分配和独立运行的基本单元都是进程。

    六:为什么要使用进程

    如果程序在运行的过程中,频繁地请求IO操作,那么CPU就会等待该程序的IO操作完成后再为其服务,这就大大的浪费了CPU资源,降低CPU效率。因此采用一种“程序排队”机制来提高CPU利用效率,从而产生了进程。

    七:深拷贝和浅拷贝 区别 以及 使用场景

    1.拷贝构造函数:是一种特殊的构造函数,由编译器调用来完成一些基于同一类的其他对象的构件以及初始化。

    2.使用场景

    a.函数参数中以值传递的方式传入时,会将传入的实际参数拷贝一份。

    b.函数返回中返回了一个局部对象,会将其拷贝一份并且返回。

    c.在给一个对象初始化的时候(不是赋值),会将值拷贝一份。

    3.什么是深拷贝和浅拷贝

    浅拷贝:只是对指针的拷贝,拷贝后两个指针指向同一个内存空间。

    深拷贝:不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经过深拷贝后的指针是指向两个不同地址的指针,也就是程序员自己编写的拷贝构造函数。

    重点:系统默认生成的拷贝构造函数是浅拷贝,深拷贝是程序员自己编写的拷贝构造函数。

    4.浅拷贝造成的问题

    a.浅拷贝只是拷贝了指针,使两个对象的指针指向同一地址,在对象结束的时候,会造成同一块内存资源析构两次,造成程序崩溃。

    b.指针指向同一块内存,任何一方有改动都会影响另一方。

    c.在释放内存时,后者的内存先释放会导致前者的内存也释放,导致空间不能再被利用、

    以上的这些问题就可以使用到深拷贝来进行避免!

    八:C中的new和malloc的区别

    molloc开辟一块内存,不进行初始化(动态开辟内存)

    new用来开辟内存,需要进行初始化(动态内存管理)

    区别

    1.malloc以及free都是函数(C语言),而new delete都是运算符(C++),都开在堆区。

    2.malloc需要指定开辟空间大小,new不需要,知道数据类型即可。

    3.malloc返回void*,一般强制转换。new则不需要。

    4.malloc开free收,new开delete释放。

    5.new内存分配失败时,会抛出异常警告。malloc分配内存失败时会返回NULL。

    6.new不仅能开辟内存,还能进行初始化,malloc只能开辟内存,不能初始化。

    7.new开辟的内存叫做自由存储区,malloc开辟的内存叫做堆区。

    8.new可以重载,malloc不能重载。

    9.new开辟数组时,用[ ]传入数组的大小。

    10.new在创建类的时候,会默认调用构造函数,而malloc不会。

    11.delate会默认调用类的析构函数,而free不会。

    12.如果父类的析构函数有写虚函数的关键字,当子类被析构的时候,会默认调用父类的析构函数。

    九:简述通用链表的原理

    通用链表是一个链式结构,其中至少包含一个指针域和一个数据域。如果只包含一个,就表示指针域时指向下一个节点。如果是双向链表,其中包含一个数据域和两个指针域。一个指针域指向上一个节点,一个指针域指向下一个节点。通常情况下,链表的添加是尾部添加,当然也有中间插入的,主要是看是什么容器。删除节点:list容器可以从中间删除和尾部删除。队列只可以头部删除,尾部追加,不存在数据插入。

    十:模板类和类模板 区别和作用

    模板类是类,类模板是模板。

    类模板需要我们自己去写(将逻辑什么的写好放入),而模板类是将写好的类模板将参数传递进去。容器本来就是一个模板类。它的底层原理就是模板类。

    十一:为什么需要使用自定义通信协议

    所谓通讯协议就是指 通信双方对数据传送控制的一种约定。约定中包括有对数据格式,同步方式,传送速度,传送步骤,纠错方式以及控制字符定义等问题做出统一规定,通信双方必须同时遵守,倘若一方不遵守,就会直接导致数据不能被解析!

    更通俗来讲,它可以理解为两个节点之间为了协同工作实现信息交换,协商一定的规则和约定,例如规定字节序,各个字段类型等。我们最常见的可能就是TCP(传输控制协议)/IP(网络协议)、UDP(用户数据报协议)等。

    不过,上面提到的这些协议是操作系统已经设定好的,并且广泛应用在网络通信中。最重要的一点是我们不能更改这些协议。而用户自定义的通讯协议就不同了。它的实现需要用户自己设定数据发送的格式以及数据的封装形式。然后通过上面的网络传输协议发送给对端,对端再根据自己定义好的协议对数据进行解析,从而得到想要的数据。

    自定义通信协议组成:协议头 + 协议体 (定长包头 + 不定长包体)

    协议头里包含:服务器是什么业务

    协议体里包含:具体业务数据参数

    十二:进程和线程的区别

    进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

    线程是“一个进程内部的控制序列”。

    根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位。

    资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销。线程可以看作是轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己的独立的运行栈和程序计数器(PC),线程之间切换的开销小。

    包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的,线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

    内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的。

    影响关系:一个进程崩溃后,在保护模式下不会对其他的进程产生影响,但是一个线程崩了就会导致整个进程卡死,所以多进程要比多线程更健壮。

    执行过程:每个独立的进程有程序运行的入口,顺序执行序列和程序出口,但是线程不能独立运行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可以并发执行。

    十三:TCP和UDP的区别

    TCP和UP都是传输层协议

    1.TCP面向连接(如打电话要先拨号建立连接),UDP是无连接的,即发送数据之前不需要建立连接。

    2.TCP提供可靠的服务,也就是说,通过TCP连接传送的数据,无差别,不丢失,不重复,并且按序到达,UDP尽最大的努力去交付,即不保证可靠交付。

    TCP通过校验和,重传控制,序号标识,滑动窗口,确认应答实现可靠传输,如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。

    3.UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。

    4.每一条TCP连接只能是点到点的,UDP支持一对一,一对多,多对一,和多对多的交互通信。

    5.TCP对系统资源要求较多,UDP对系统资源要求较少。

    十四:通信协议的作用

    通信协议又称为通信规程,是指通信双方对数据传送控制的一种约定。约定中包括对数据格式,同步方式,传送速度,传送步骤,检纠错方式以及控制字符定义等问题做出统一规定,通信双方必须共同遵守,它也叫做链路控制规程。

    电脑和电脑之间的沟通必须讲述相同的语言,才能互相传输信息,自然资料在国际互联网上传递,每一份都要符合一定的规格(即是相同的语言)。

    这些规格(语言)的规定都是事先在会议上讲好的,一般我们称之为“协议”(英文称为protocol),而这种在网络上负责定义资料传输规格的协议,我们就统称为通信协议。

    十五:拷贝构造函数 为什么使用const&

    调用拷贝构造函数的三种情况

    1.用已知的对象拷贝生成新对象

    2.以值传递的方式传入函数

    3.函数的返回值 是以值传递的方式

    在值传递的时候,传参期间会产生一个临时变量,当我们实例化对象d1后,将d1拷贝给d2时调用拷贝构造函数,此时d1发生值传递,d1将值传递给临时对象dd1,因为是值传递,一旦调用又要产生临时变量ddd1,将d1的值传递给临时变量ddd1,那么问题就很明显了,值传递,会进行形参实例化,类 类型实例化,会再调用构造函数,就会一直调用,因此结果就是无穷递归,所以要使用引用传参。

    第三种参数的返回值是以值传递的方式,首先我们需要知道,返回一个局部变量是通过一个临时的变量返回,对象也不例外,这里也会产生一个临时的对象,而这个临时对象,具有常性,也就是const,不可被修改,赋值之后临时对象也就消亡了。临时对象,具有const常性。所以当我们把const加上,通过严格的参数匹配,编译器才能找到我们的const&版本的拷贝构造函数。

    以下问题需要重点掌握! 

    十六:线程池的设计以及作用

    线程池的作用:

    线程池是为了解决线程在程序中因频繁创建和销毁而消耗大量的时间而存在的,即在程序开始正式任务之前,先创建出一些线程,这些线程在程序不会被销毁,而且程序在运行中也不会再去创建线程。这样在程序的运行期间就提高了效率。

    线程池的设计思路:

    先创建出一组线程,当有新任务进来时就从线程池中取出空闲线程处理任务,任务完成之后又会重新放回去,当线程池中的所有线程都在任务时,只能等待有线程结束任务才能继续执行 。

    十七:线程和进程的区别

    根本区别:进程是操作系统资源分配的基本单位,而线程是CPU任务调度和执行的基本单位

    在开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有比较大的开销;线程可以看作是轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

    所处环境:在操作系统中同时运行多个进程(程序);而在同一进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)

    内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。

    包含关系:一个线程只能属于一个进程,但是一个进程可以拥有多个线程。多线程处理就是允许一个进程中在同一时刻执行多个任务。所以线程也被称为轻权进程或者轻量级进程

    十八:C++设计模式

    单例模式

    概念:是指在内存中只会创建且创建一次对象的设计模式

    优点

    1.保证一个类只有一个实例,并提供一个访问它的全局访问点,使得系统中只有一个唯一的一个对象实例。这样就防止其他对象对自己的实例化,确保所有的对象都访问一个实例

    2.由于在系统内存中只存在一个对象,因此可以节约系统的资源,提高系统的性能

    缺点

    就是不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态

    实现要点:

    在类中,要构造一个实例,就必须调用类的构造函数,并且为了保证全局只有一个实例,需要防止在外部调用类的构造函数而构造实例,需要将构造函数的访问权限标记为private,同时阻止拷贝创建对象时赋值拷贝对象,因此也将它们声明并权限标记为private;另外,需要提供一个全局访问点,就需要在类中定义一个static函数,返回在类内部唯一构造的实例

    观察者模式:

    观察者模式定义了对象间的一对多依赖关系,让一个或多个观察者对象观察一个主题对象。当主题对象的状态发生变化时,系统能通知所有的依赖于此对象的观察者对象,从而使得观察者对象能够自动更新

    实现方式:

    a.角色抽象类(提供对观察者的添加,删除和通知功能)

    b.角色具体类,实现a,维护一个c的集合(对角色抽象类的实现)

    c.观察者抽象类(被角色通知后实现的方法)

    d.观察者实现类,实现c(多个)

    工厂模式

    工厂模式包括三种:简单工厂模式、工厂方法模式、抽象工厂模式

    工厂模式的主要作用是封装对象的创建,分离对象的创建和操作过程,用于批量管理对象的创建过程,便于程序的维护和扩展

    简单工厂是工厂模式最简单的一种实现,对于不同产品的创建定义一个工厂类,将产品的类型作为参数传入到工厂的创建函数,根据类型分支选择不同的产品构造函数

    十九:进程间的通讯方式

    管道:数据只能单向流动,速度慢,容量有限,只有父子进程能通讯

    消息队列:就是一个消息的链表,是一系列保存在内核中消息的列表。用户进程可以向消息队列添加消息,也可以向消息队列读取消息

    共享内存:映射一段能被其他进程访问的内存,这段内存由一个进程创建,但是多个进程都可以访问

    信号量:是一个计数器,用于控制多个进程间对共享资源的访问

    套接字:用于不同计算机之间的不同进程间通信

    二十:引用和指针的区别

    本质:引用是别名,指针是地址

    相同点:都是地址的概念

    指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名

    不同点

    1.引用不可以为空,但指针可以为空,故定义一个引用的时候,必须初始化

    2.引用不可以改变指向,对一个对象“至死不渝”;但是指针可以改变指向,而指向其他对象,虽然引用不可以改变指向,但是可以改变初始化对象的内容

    3.引用的大小是所指的变量的大小,因为引用只是一个别名而已;指针是指针本身的大小,4个字节

    4.引用比指针更加安全,由于不存在空引用,并且引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用,因此引用很安全。对于指针来说,它可以随时指向别的对象,并且可以不被初始化,或为NULL,所以不安全

    5.引用仅在声明时带有引用运算符“&”,以后像普通变量一样使用,不能再带“&”,其他场合使用的"&"都是地址操作符

    二十一:构造函数和析构函数

    构造函数的作用:用于新建对象的初始化工作(一个类可以有多个构造函数,构造函数可以重载,不可以加虚函数)

    析构函数的作用:用于在撤销对象前,完成一些清理工作,比如:释放内存等(一个类只能有一个析构函数,不可以重载)

    拷贝构造函数:拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它的唯一的一个参数是本类型的一个引用变量,该参数是const类型,不可变的

    二十二:深拷贝和浅拷贝

    浅拷贝:浅拷贝是对指针进行拷贝,拷贝后两个指针指向同一块内存空间。C++中如果不定义类的赋值构造函数,就会调用类的默认赋值构造函数,而类的赋值构造函数是浅拷贝

    深拷贝:深拷贝是对指针进行拷贝而且还对内容进行拷贝,拷贝完成后,指针指向的地址不一样,但是值是一样的

    浅拷贝和深拷贝的区别:前者就是使用编译器提供的默认拷贝构造函数或者默认赋值构造函数,后者是自己显示实现的拷贝/赋值构造函数

    二十三:对多态的理解

    C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类类,别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数

    多态的实现主要分为静态多态和动态多态,静态多态主要是重载,在编译的时候就已经确定;动态多态是用虚函数机制实现的,在运行期间动态绑定

    多态的条件

    1.必须有继承

    2.要有虚函数重写

    3.用父类指针(引用)指向子类对象

    多态的基础理论

    联编:一个程序模块,代码之间互相关联的过程

    动态联编:把程序联编的过程,推迟到运行时进行

    静态联编

    多态的实现效果

    同样的调用语句,不同的表现形态

    多态的意义:设计模式的基础,编写框架的基础,函数指针做函数参数

    二十四:数据结构有哪些

    什么是数据结构

    数据结构是指相互之间存在着一种或者多种关系的数据元素的集合和该集合中数据元素之间的关系组成

    常用的数据结构有:数组、栈、链表、队列、树、图、堆、散列表等

    数组:数组是可以在内存中连续存储多个元素的结构,在内存中的分配也是连续的,数组中的元素通过数组下标进行访问

    :栈是一种特殊的线性表,仅能在线性表的一端操作,栈顶允许操作,栈底不允许操作。栈的特点是:先进先出,或者说是后进先出

    队列:队列与栈一样,也是一种线性表,不同的是,队列可以在一端添加元素,在另一端取出元素,也就是:先进先出

    链表:链表是物理存储单元上非连续的,非顺序的存储结构,数据元素的逻辑顺序是通过链表的指针地址实现,每个元素包含两个结点,一个是存储元素的数据域(内存空间),另一个是指向下一个节点地址的指针域。根据指针的指向,链表能形成不同的结构,例如单链表、双向链表、循环链表等

    :树是一种数据结构,它是由n(n>=1)个有限节点组成一个具有层次关系的集合

    二十五:socket包安全

    TCP是一个基于字节流的传输服务,“流”意味着TCP所传输的数据是没有边界的。这不同于UDP提供基于消息的传输服务,其传输的数据是有边界的。TCP的发送方无法保证对等方每次接收到的是一个完整的数据包。就会出现分包或粘包的问题

    分包:传输数据不完整,一条信息被分成多次发送。比如我们发送了一条信息:“你好”,我们可能只收到了“你”,却没有收到“好”,这样就会导致数据的不完整

    粘包:传输的多条数据粘在一起,比如我发送了“你好”和“我是韩寒”,我们可能会收到“你好我是韩寒”,也可能收到“你好我是”、“你好我”、“你好我是小”,后几种情况是分包粘包同时发生,我们肯定不希望出现这种现象,所以我们就有必要对我们发生的数据进行编辑

    Socket只是一种通信手段它本身没有任何额外的安全措施,所以要用到加密技术,不然通信的数据非常容易被攻击者获取到,一般情况下,我们会使用CRC进行冗余验证,看数据包是否传输完整,然后自定义自己的加密方式,将数据包加密以后再发出,有的项目还会对数据包进行压缩,所以我们这里给出一种通用的结构:数据头(长度)+冗余验证(CRC)+是否压缩+包体(加密后)

    二十六:new和malloc的区别

    区别New deletemalloc free
    属性C++编译器支持库函数、头文件支持C
    参数申请内存无需指定内存的大小显示指定大小
    返回值对象类型的指针泛型,void*类型,再转化为需要的类型
    内存区域自由存储区堆上动态分配内存

    二十七:epoll的LT和ET模式的理解

    epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式和ET模式的区别如下:

    LT模式:支持block和no-block socket。当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。效率会低于ET触发,尤其在大并发,大流量的情况下。但是LT对代码编写要求比较低,不容易出现问题。LT模式服务编写上的表现是:只要有数据没有被获取到,内核就不断通知你,因此不用担心事件丢失的情况

    ET模式:只支持no-block socket。当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。该模式效率非常高,尤其在高并发,大流量的情况下,会比LT少很多epoll的系统调用。但是对编程要求高,需要细致的处理每个请求,否则容易发生丢失事件的情况

    二十八:TCP/UDP的区别

    TCP是一种面向连接的,可靠的,基于字节流的传输层通信协议,是专门为了在不可靠的网络中提供一个可靠的端对端字节流而设计的,面向字节流

    UDP(用户数据报协议)是ISO参考模型中一种无连接的传输层协议,提供简单不可靠的非连接传输层服务,面向报文

    区别

    1.TCP是面向连接的,可靠性强;UDP是基于非连接的,可靠性低

    2.由于TCP是连接的通信,需要有三次握手、重新确认等连接过程,会有延时,实时性差,同时过程复杂,也使得其易于攻击;UDP没有建立连接的过程,因而实时性较强,也稍安全

    3.在传输相同大小的数据时,TCP首部开销20字节;UDP首部开销8字节,TCP报头比UDP复杂,故实际包含的用户数据较少。TCP在IP协议的基础上添加了序号机制、确认机制、超时重传机制等,保证了传输的可靠性,不会出现丢包或乱序,而UDP有丢包,故TCP开销大,UDP开销小 

    4.每条TCP连接只能是点到点的;UDP支持一对一、一对多、多对一、多对多的交互通信

    应用场景选择

    对实时性要求高和高速传输的场合下使用UDP,在可靠性要求低,追求效率的情况下使用UDP

    需要传输大量数据且对可靠性要求高的情况下使用TCP

    二十九:三次握手和四次挥手

    三次握手:客户端和服务端建立连接需要三次握手

    第一次:客户端向服务端发送报文,向服务器发送连接请求

    第二次:服务端向客户端返回ACK报文,通知客户端可以连接

    第三次:客户端收到服务端报文,正式连接服务端

    三次握手完成

    四次挥手:客户端要与服务器断开连接,需要四次挥手

    第一次:客户端向服务端发送FIN报文,向服务器发送中断连接请求

    第二次:服务器收到客户端中断请求,向客户端发送已得知中断请求,但服务器还有资源未处理,需要等待;

    第三次:服务器处理完数据后,再次向客户端发送报文,告诉客户端可以断开连接了

    第四次:客户端收到服务端断开连接的确认信息后,最后发送信息看是否真的断开连接了,如果服务器一段时间没有回应,则说明已经断开了,中断过程完成

    四次挥手完成

    三十:socket的概念和特点

    socket概念

    这是为了实现以上的通信过程而建立起来的通信管道,其真实的代表是客户端和服务器端的一个通信进程,双方进程通过socket进行通信,而通信的规则采用指定的协议

    socket只是一种连接模式,不是协议,socket是对TCP/IP协议的封装,socket本身并不是协议,而是一个调用接口(API),通过socket,我们才能使用TCP/IP协议。TCP、UDP,简单的说,是两个最基本的协议,很多其他协议都是基于这两个协议如,http就是基于TCP的,用socket可以创建TCP连接,也可以创建UDP连接

    Socket传输的特点

    优点

    1.传输数据为字节级,传输数据可自定义,数据量小(对于手机应用讲;费用低)

    2.传输数据时间短,性能高

    3.适合于客户端和服务端之间信息实时交互

    4.可以加密,数据安全性强

    缺点

    1.需对传输的数据进行解析,转化成应用级的数据

    2.对开发人员的开发水平要求高

    3.相对于http协议传输,增加了开发量

    三十一:GDB调试

    GDB是一个由GNU开源组织发布的,UNIX/LINUX操作系统下的,基于命令行的,功能强大的程序调试工具

    进入GDB后可以直接在(gdb)后输入相应命令进行调试操作

    1.启动gdb

    2.查看源码list

    3.运行程序 run

    4.设置断点 break

    5.单步执行continue、step、next

    6.查看变量 printf

    7.退出调试 quit

    三十二:面向对象的理解

    1.面向对象

    特点:被动的去实现,分解成一个个的对象

    由现实的世界建立的软件模型

    优点:效率高、易维护、易复用、易扩展

    缺点:类调用时需要实例化,开销比较大

    面向对象:面向对象编程就是把问题分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为

    面向对象就是高度实物抽象化(功能划分)、面向过程就是自顶向下的编程(步骤划分)

    三十三:虚函数的作用

    虚函数的作用

    用专业术语来解释就是实现多态性(Polymorphism),多态性说将接口与实现进行分离;用形象的语言来解释就是实现以共同的方法,但因个体差异而采用不同的策略

    虚函数的实现

    在有虚函数的类中,类的最开始部分是一个虚函数表的指针,这个指针指向一个虚函数表,表中放了虚函数的地址,实际的虚函数在代码段(.text)中,当子类继承了父类的时候也会继承其虚函数表,当子类重写父类中虚函数的时候,会将其继承到的虚函数表中的地址替换为重新写的函数地址。使用了虚函数,会增加访问内存开销,降低效率

    三十四:类和对象的关系

    类是对象的概括,对象是类的具体实现

    1.对象:对象是运行期的基本实体,它是一个封装了数据和操作这些数据的代码的逻辑实体

    2.:类是具有相同类型的对象的抽象。一个对象所包含的所有数据和代码可以通过类来构造

    三十五:IO复用,epoll和select的区别,epoll和select的特点

    I/O多路复用是为了解决进程或线程阻塞到某个I/O系统调用而出现的技术,使进程或线程不阻塞于某种特定的I/O系统调用

    select(),poll(),epoll()都是I/O多路复用的机制,I/O多路复用是通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者是写就绪,就是这个文件描述符进行读写操作之前),能够通知程序进行相应的读写操作

    epoll

    epoll支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就需态,并且只会通知一次。还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知

    epoll的优点

    1.没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口)

    2.效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数;即epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll

    3.内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销

    select

    select的核心功能是调用tcp文件系统的poll函数,不停的查询,如果没有想要的数据,主动执行一次调度(防止一直占用CPU),直到有一个连接有想要的消息为止。从这里可以看出select的执行方式基本就是不同的调用poll,直到有需要的消息为止

    缺点

    1.每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大

    2.同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大

    3.select支持的文件描述符数量太小了,默认是1024

    优点

    1.select的可移植性更好,在某些Unix系统上不支持poll()

    2.select对于超时值提供了更好的精度:微秒。而poll是毫秒

    三十六:客户端与服务器如何通信

    1.服务器先用socket()函数来建立一个套接字,用这个套接字完成通信的监听和数据的收发

    2.服务器用bind()函数来绑定一个端口号和IP地址,使套接字与指定的端口号和IP地址相关联

    3.用服务器调用listen()函数,使服务器的这个端口和IP处于监听状态,等待网络中某一客户机的请求发送

    4.客户机用socket()函数建立一个套接字,设定远程IP和端口

    5.客户机调用socket()函数连接远程计算机指定的端口

    6.服务器调用accept()函数来接受远程计算机的请求,建立与客户机之间的通信连接

    7.建立连接后,客户机用write()函数或者close()函数向socket中写入数据,也可以用read()函数读取服务器发来的数据

    8.服务器用read()函数读取客户机发来的数据,也可以用write()函数或者send()函数来发送数据

    9.通信完成以后,使用close()函数关闭socket连接

    三十七:堆和栈的区别

    栈由操作系统自动分配释放,用于存放函数的参数值、局部变量等,其操作方式类似于数据结构中的栈

    堆由开发人员分配和释放,若是开发人员不释放,程序结束时由OS回收,分配方式类似于链表

    堆与栈实际上是操作系统对进程占用的内存空间的两种管理方式,主要有如下几种区别

    1.管理方式不同、栈由操作系统自动分配释放,无需我们手动控制;堆的申请和释放工作由程序员控制,容易产生内存泄漏

    2.空间大小不同,每个进程拥有的栈的大小要远远小于堆的大小、理论上,程序员可申请的堆大小为虚拟内存的大小,进程栈的大小64bits的windows默认1MB,64bits的Linux默认10MB

    3.生长方向不同,堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低

    4.分配方式不同,堆都是动态分配的,没有静态分配的堆,栈有2种分配方式:静态分配和动态分配。静态分配是由操作系统完成的,比如局部变量的分配。动态分配由于malloc函数进行分配,但是栈的动态分配和堆是不同的,它的动态分配是由操作系统进行释放,无需我们手工实现

    5.分配效率不同。栈由操作系统自动分配,会在硬件层级对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高,堆则是由C/C++提供的库函数或运算符来完成申请和管理,实现机制较为复杂,频繁的内存申请容易产生内存碎片。显然,堆的效率比栈要低的多

    三十八:结构体和类的区别

    最本质的一个区别就是默认的访问控制:

    默认的继承访问权限

    struct是public的,class是private的

    三十九:STL中的vector是如何实现的

    STL(Standard Template Library),即标准模板库,是一个具有工业强度的,高效的C++程序库,它被容纳于C++标准程序库中,包括容器、算法、迭代器组件。vector内部使用动态数组的方法实现的。如果动态数组的内存不够用,就要动态地重新分配,一般是当前大小的两倍,然后把原数组的内容拷贝过去。所以,在一般情况下,其访问速度同一般数组,只有在重新分配发生时,其性能才会下降。注意vector的size()和capacity()是不同的,前者表示数组中元素的多少,后者表示数组有多大的容量。由上面的分析可以看出,使用vector的时候需要注意内存的使用,如果频繁地进行内存地重新分配,会导致效率低下。它的内部使用allocator类进行内存管理,程序员不需要自己操作内存

    vector其中一个特点:内存空间只会增长,不会减小,援引C++ Primer:为了支持快速的随机访问,vector容器的元素以连续的方式存放,每一个元素都紧挨着前一个元素存储。设想一下,当vector添加一个元素时,为了满足连续存放这个特性,都需要重新分配空间、拷贝元素、撤销旧空间 ,这样性能难以接受。因此STL实现者在对vector进行内存分配时,其实际分配的容量要比当前所需的空间多一些。就是说,vector容器预留了一些额外的存储区,用于存放新添加的元素,这样就不必为每个新元素重新分配整个容器的内存空间

    四十:如何保证线程的安全

    在大多数软件应用中,线程的数量都不止一个,多线程程序处在一个多变的环境中,可以访问的全局变量和堆数据随时都可能被其他的线程改变,这就将”线程安全“的问题提上了议程。那么,如何确保线程的安全呢?

    线程安全

    一般来说,确保线程安全的方法有这几个:竞争与原子操作、同步与锁、可重入、过度优化

    竞争与原子操作

    多个线程同时访问和修改一个数据,可能造成很严重的后果。出现严重后果的原因是很多操作被操作系统编译为汇编代码之后不止一条指令,因此在执行的时候可能执行了一半就被调度系统打断了而去执行别的代码了。一般将单指令的操作称为原子的(Atomic),因为不管怎样,单条指令的执行是不会被打断的

    因此,为了避免出现多线程操作数据的出现异常,Linux系统提供了一些常用操作的原子指令,确保了线程的安全。但是,它们只适用于比较简单的场合,在复杂的情况下就要选用其他的方法了

    同步与锁

    为了避免多个线程同时读写一个数据而产生不可预料的后果,开发人员要将各个线程对同一个数据的访问同步,也就是说,在一个线程访问数据未结束的时候,其他线程不得对同一个数据进行访问

    同步的最常用的方法是使用锁(Lock),它是一种非强制机制,每个线程在访问数据或资源之前首先试图获取锁,并在访问结束之后释放锁;在锁已经被占用的时候试图获取锁时,线程会等待,直到锁重新可用

    二元信号量是最简单的一种锁,它只有两种状态:占用与非占用,它适合只能被唯一一个线程独占访问的资源。对于允许多个线程并发访问的资源,要使用多元信号量(简称信号量)

    可重入

    一个函数被重入,表示这个函数没有执行完成,但由于外部因素或内部因素,又一次进入该函数执行。一个函数称为可重入的,表面该函数被重入之后不会产生任何不良后果。可重入是并发安全的强力保障,一个可重入的函数可以在多线程环境下放心使用

    过度优化

    在很多情况下,即使我们合理地使用了锁,也不一定能够保证线程安全,因此,我们可能对代码进行过度的优化以确保线程的安全

    四十一:数组和链表的区别

    数组

    数组的特点

    1.在内存中,数组是一块连续的区域

    2.数组需要预留空间

    在使用前需要提前申请所占内存的大小,这样不知道需要多大的空间,就预先申请可能会浪费内存空间,即数组空间利用率低

    ps:数组的空间在编译阶段就需要进行确定,所以需要提前给出数组空间的大小(在运行阶段是不允许改变的)

    3.在数组起始位置处,插入数据和删除数据效率低

    插入数据时,待插入位置的元素和它后面的所有元素都需要向后搬移

    删除数据时,待删除位置后面的所有元素都需要向前搬移

    4.随机访问效率很高,时间复杂度可以达到O(1)

    因为数组的内存是连续的,想要访问那个元素,直接从数组的首地址向后偏移就可以访问了

    5.数组开辟的空间,在不够使用的时候需要扩容,扩容的话,就会涉及到需要把旧数组中的所有元素向新数组中搬移

    6.数组的空间是从栈分配的

    数组的优点

    随机访问性强,查找速度快,时间复杂度为O(1)

    数组的缺点

    1.头插和头删的效率低,时间复杂度为O(N)

    2.空间利用率不高

    3.内存空间要求高,必须有足够的连续的内存空间

    4.数组空间的大小固定,不能动态拓展

    链表

    链表的特点

    1.在内存中,元素的空间可以在任意地方,空间是分散的,不需要连续

    2.链表中的元素都会两个属性,一个是元素的值,另一个是指针,此指针标记了下一个元素的地址

    每一个数据都会保存下一个数据的内存的地址,通过此地址可以找到下一个数据

    3.查找数据时效率低,时间复杂度为O(N)

    因为链表的空间是分散的,所以不具有随机访问性,如果需要访问某个位置的数据,需要从第一个数据开始找起,依次往后遍历,直到找到待查询的位置,故可能在查找某个元素时,时间复杂度达到O(N)

    4.空间不需要提前指定大小,是动态申请的,根据需求动态的申请和删除内存空间,扩展方便,故空间的利用率较高

    5.任意位置插入元素和删除元素效率较高,时间复杂度为O(1)

    6.链表的空间是从堆中分配的

    链表的优点

    1.任意位置插入元素和删除元素的速度快,时间复杂度为O(1)

    2.内存利用率高,不会浪费内存

    3.链表的空间大小不固定,可以动态拓展

    链表的缺点

    随机访问效率低,时间复杂度为O(N)

    四十二:boost库是否有了解

    boost库是一个优秀的,可移植,开源的C++库,它是由C++标准委员会库工作自成员发起,它是对STL的延续和扩充,设计理念和STL比较接近,都是利用泛型让复用达到最大化,其中有些内容经常成为下一代C++标准库内容,在C++社区影响很大,是不折不扣的“准”标准库

    四十三:socket的工作模式,为什么选择socket

    socket是对tcp/ip协议的封装和应用,给我们提供了操作网络的接口

    工作模式:

    阻塞模式:是socket的缺省方式,也是最常用的方式,即函数阻塞直到调用完毕

    可能造成阻塞的函数有:connect()、accept()、读写函数、select()、poll()等

    非阻塞模式:非阻塞IO通过进程反复调用IO函数(多次系统调用,并且马上返回);在数据拷贝的过程中,进程是阻塞的

    I/O多路复用(同步I/O模式):使用select()、poll()等函数实现对多个socket的同步I/O操作。它能同时等待多个socket描述符,而这些socket描述符其中的任意一个进入读就绪/写就绪/出错状态

    为什么选择socket

    应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连续或多个应用程序进程可能需要通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了称为套接字(socket)的接口,区分不同应用程序进程间的网络通信和连接

    四十四:长连接和短连接

    短连接

    连接->传输数据->关闭数据

    HTTP是无状态的,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束后就中断连接。短连接是指socket连接后发送后接收完数据后马上断开连接

    长连接

    连接->传输数据->保持连接->传输数据->...->关闭连接

    长连接指建立socket连接后不管是否使用都保持连接,但安全性较差

    四十五:socket中阻塞和非阻塞的区别

    非阻塞模式可以理解为,执行此套接字的网络调用时,不管是否执行成功,都会立即返回

    如调用recv()函数读取网络缓冲区中的数据时,不管是否读到数据都立即返回,而不会一直挂在此函数的调用上

    而阻塞模式为只有接收到数据后才会返回,套接字默认的会创建阻塞模式

    四十六:QT的信号槽机制的理解

    Qt提供信号和槽机制用于完成界面操作的响应,是完成任意两个Qt对象之间的通信机制。其中,信号会在某个特定情况或动作下被触发,槽是等同于接收并处理信号的函数

    信号槽机制与回调的区别

    1.回调函数的本质是“你想让别人的代码执行你的代码,而别人的代码你又不能动“这种需求下产生的。回调函数是函数指针的一种用法,如果多个类都关注某个类的状态变化,此时需要维护一个列表,以存放多个回调函数的地址。对于每一个被关注的类,都需要做类似的工作,因此这种做法效率低,不灵活

    2.Qt使用信号和槽机制来解决这个问题,程序员只需要指定一个类含有哪些信号函数、哪些槽函数,Qt会处理信号函数和槽函数之间的绑定。当信号函数被调用时,Qt会找到并执行与其绑定的槽函数。允许一个信号函数和多个槽函数绑定,Qt会依次找到并执行与一个信号函数绑定的所有槽函数,这种处理方式更加灵活

    3.Qt信号与槽机制降低了Qt对象的耦合度。激发信号的Qt对象无需知道是哪个对象的哪个槽需要接收它发出的信号,它只需要做的是在适当的时间发送适当的信号就可以了,而不需要知道也不关心它的信号有没有被接收到,更不需要知道哪个对象的哪个槽接收到了信号。同样地,对象的槽也不知道是哪些信号关联了自己,而一旦关联信号和槽,Qt就保证了适合的槽得到了调用。即使关联的对象在运行时被删除。应用程序也不会崩溃

    四十七:Qt的线程的使用

    QThread类提供了一个与平台无关的管理线程的方法。一个QThread对象管理一个线程。QThread的执行从run()函数的执行开始,在Qt自带的QThread类中,run()函数通过调用exec()函数来启动事件循环机制,并且在线程内部处理Qt的事件。在Qt中建立线程的主要目的就是为了用线程来处理那些耗时的后台操作,从而让主界面能及时响应用户的请求操作

    1.自定义一个继承QThread的类MyThread,重载MyThread中的run()函数,在run()函数中写入需要执行的工作

    2.调用start()函数来启动线程

    四十八:QT样式表和选择器

    Qt样式表是另外一种自定义部件外观的机制,使用样式表可以更方便的设置界面的外观,而不用去子类化QStyle类

    四十九:Qt的STL和C++的STL有什么区别

    C++中容器类是属于标准模板库中的内容Qt提供了它自己的一套容器类,这就是说,在Qt的应用程序中,我们可以使用标准C++的STL,也可以使用Qt的容器类。Qt容器类的好处在于,它提供了平台无关的行为,以及隐式数据共享技术

    区别

    1.STL的vector最开始分配的空间是1个,而QVector开始分配的空间为4个(可见Qt在空间分配上的优化)

    2.STL的vector发生超过容量本身的访问,并不一定失败(因为内存可能足够大并存在),而QVector则发生了断言错误。而显然,第二种处理方式会更好。而第一种可能会造成莫名其妙的错误,尤其是当工程师忘记vector的范围,而vector本身并不出错的时候

    显然,Qt的容器基于STL进行了升级。如果有类似于Qt的容器之类的容器,优先选择更好的容器;而如果使用STL,要知道STL这种特点,并避免这类事情的发生

    五十:什么是野指针

    野指针

    指向内存被释放的内存或者没有访问权限的内存的指针

    ”野指针“的成因主要有3种

    1.指针变量没有被初始化

    2.指针p被free或者delete之后,没有置为NULL

    3.指针操作超越了变量的作用范围

    五十一:长连接和短连接

    短连接

    连接->传输数据->关闭连接

    HTTP是无状态的。浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束后就中断连接。

    短连接是指socket连接后发送后接收完数据后马上断开连接

    长连接

    连接->传输数据->保持连接->传输数据->...->关闭连接

    长连接指建立socket连接后不管是否使用都保持连接,但安全性较差

    五十二:客户端掉线和服务端掉线检测

    socket中用心跳包检测客户端与服务端掉线状况

    心跳包:

    它像心跳一样每隔固定时间发一次,以此来告诉服务器,这个客户端还活着。事实上这是为了保持长连接,至于这个包的内容,是没有什么特别规定的,不过一般都是很小的包,或者只包含包头的一个空包。

    心跳检测步骤

    1.客户端每隔一个时间间隔发送一个探测包给服务器

    2.客户端发包时启动一个超时定时器

    3.服务器端接收到检测包,应该回应一个包

    4.如果客户机收到服务器的应答包,则说明服务器正常,删除超时定时器

    5.如果客户端的超时定时器超时,依然没有收到应答包,则说明服务器挂了

    五十三:哈希表有什么特点

    哈希表(Hash Table,也叫散列表),是根据关键码值(Key-Value)而直接进行访问的数据结构,也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。哈希表的实现主要需要解决两个问题,哈希函数和冲突解决

    特点

    1.哈希表的查找效率主要取决于构造哈希表时选取的哈希函数和处理冲突的方法

    2.在各种查找方法中,平均查找长度与节点个数n无关的查找方法是哈希表查找法

    3.哈希函数取值是否均匀是评价哈希函数好坏的标准

    4.哈希存储方法只能存储数据元素的值,不能存储数据元素之间的关系

    5.哈希表的装填因子a<1,并不可以避免冲突的产生

    五十四:子进程继承父进程的哪些资源

    在Linux系统内,创建子进程的方法是使用系统调用fork()函数,fork()函数是Linux系统内一个非常重要的函数,它与我们之前学过的函数有一个显著的区别:fork()函数调用一次却会得到两个返回值

    fork()函数用于从一个已经存在的进程内创建一个新的进程,新的进程称为”子进程“,相应的称创建子进程的进程为”父进程“

    使用fork()函数得到的子进程是父进程的复制品,子进程完全复制了父进程的资源,包括进程上下文,代码区,数据区,堆区,栈区,内存信息,打开文件的文件描述符,信号处理函数,进程优先级,进程组号,当前工作目录,根目录,资源限制和控制终端等信息,而子进程与父进程的区别有进程号,资源使用情况和计时器等

    五十五:为什么选择共享内存

    共享内存

    顾名思义就是允许两个不相关的进程访问同一个逻辑内存,共享内存是两个正在运行的进程之间共享和传递数据的一种非常有效的方式

    优点

    我们可以看到使用共享内存进行进程之间的通信是非常方便的,而且函数的接口也比较简单,数据的共享还使进程间的数据不用传送,而是直接访问内存,加快了程序的效率

    五十六:Linux下如何调试

    逻辑错误用log

    内存错误用gdb

    单元测试用gtest

    编译器用clang

    log框架用log4cplus

    性能热点用gprof

    五十七:大量客户端连上来,服务器如何处理

    这时候服务器端应该使用多线程,每连接上一个客户端就给该客户端开启一个线程。监听端口的时候也要单独开一个线程、不然会阻塞主线程。这样做有一个明显的缺点,就是有N个客户端请求连接时,就会有N个线程,对程序的性能和计算机的性能影响很大,可以使用线程池进行管理

    使用线程池的好处

    主要用于减少因频繁创建和销毁线程带来的开销,因此那些经常使用且执行时间短的 线程需要用线程池来管理

    五十八:HTTP协议

    对客户端和服务器之间数据传输的格式规范,格式简称为”超文本传输协议”

    五十九:大端模式和小端模式

    主机字节序就是我们平常说的大端和小端模式:不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序

    引用标准的Big-Endian和Little-Endian的定义如下

    1.Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端

    2.Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端

    六十:C++静态库和动态库的区别

    二者的不同点在于 代码被载入的时刻不同

    静态库

    在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库,因此体积较大

    动态库

    在程序编译时并不会被连接到目标代码中,而是在程序运行时才被载入,因此在程序运行时还需要动态库存在,因此代码体积较小

    动态库的好处

    不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例

    六十一:双向链表如何实现

    双向循环链表

    即在单链表(上文描述的链表为单链表)的基础上进行改进,每个节点不光有存储下一个节点地址的指针域next,还增加了存储上一个节点地址的指针域prev,其中头(第一个节点)的prev指向尾节点(最后一个节点),尾节点的next指向头节点

    实现:

    在操作时创建temp指针指向链表中各元素,更改prev及next指向的节点就可以实现对链表的基本增删查改等操作

    六十二:FFmpeg如何实现编码

    编码思路分析

    1.注册所有的组件

    2.根据需要的码流数据的格式,来猜测的需要的编码器

    3.打开目标文件流

    4.新建视频流

    5.设置编码器上下结构的一系列参数,为编码做好准备

    6.查找对应的编码器

    7.打开编码器

    8.读取普通视频数据or摄像头数据,进行解码,保证得到的是YUV的像素数据

    9.先写入编码的头部信息

    10.正式开始,进行编码,将一帧像素数据压缩成码流数据

    11.保存写入文件中

    12.得到最终的编码后的码流数据

    六十三:FFmpeg如何实现解码

    解码思路分析

    1.注册所有组件 av_register_all()

    2.打开视频文件 avformat_open_input()有可能打开失败

    3.获取视频信息 视频码流 音频码流 文字码流

    4.查找流信息 avformat_find_stream_info()

    5.找到解码器 avcodec_find_decoder()有可能没找到

    6.打开解码器 avcoedec_open2()

    7.读取码流中的一帧码流数据 av_read_frame()

    8.解码读取到一帧码流数据 得到一帧的像素数据YUV RGB

    9.重复7-8的动作,直到视频所有的帧都处理完

    10.关闭解码器

    11.关闭视频文件

    六十四:FFmpeg如何实现转码

    转码思路分析

    1.注册组件

    2.打开视频流 打开视频文件

    3.查找有没有流数据

    4.查找视频码流数据

    5.根据需要的封装格式,来猜测格式对应编辑器

    6.打开对应文件

    7.新建流

    8.写入头部信息

    9.读取一帧一帧的码流数据

    10.转码 --->时间基的转化

    11.写入对应的一帧数据到文件中

    六十五:MVC框架

    MVC框架能够将业务进行分离,数据和界面进行分离,在团队项目中容易分工各个模块给不同的人员

    使得MVC架构模拟用户登录的流程

    举例 用户登录界面:

    1.发送请求到控制器

    2.控制器委托用户模型去做用户数据的查询

    3.用户模型查询用户表得到结果

    4.用户模型把查询到的结果返回给控制器

    5.控制器将用户模型返回的结果反馈给用户登录界面

    MVC的优点

    代码模块化,分工明确,提高工作效率。耦合性低,增加了组件的重用性,降低了维护成本

    MVC的缺点

    系统结构复杂,实现起来比较困难,所以小型项目不适用,只适合大型项目,view和controller联系过于紧密,MVC将其分离,导致应用的范围会受到限制,虽然MVC对代码进行了模块化处理,但是由于关联性,很难实现独立重用

  • 相关阅读:
    关于城市旅游的HTML网页设计 HTML+CSS上海博物馆网站 dreamweaver作业静态HTML网页设计 html网页制作期末大作业
    Java调用ChatGPT的API接口实现对话与图片生成
    springboot农机电招平台springboot37
    基于LVM通过添加硬盘实现分区扩容的方法介绍
    Cleanmymac X绿色中文苹果系统清理软件
    告别繁琐粘贴,CleanClip Mac 版,让复制粘贴变得简单快捷!粘贴队列功能太强大了!
    CC57 链表内指定区间反转
    Direct3D模板缓存
    c++视觉处理---直方图均衡化
    Java线程池的任务消息队列
  • 原文地址:https://blog.csdn.net/m0_56051805/article/details/126422524