C++

一、语言基础

1.C++语言特点:

面向对象+兼容C(面向过程)

C++是对C 的一个扩展,在 C 语言的基础上添加了面向对象编程和泛型编程的支持,根据这件事件抽象出一个类,所有的操作都被封装到这个类里面,所有的操作都有这个类去完成,面向对象是一种编程的思想,它的特点是复核人类的思维,把复杂的问题简单化,将我们的位置从执行者变为指挥者,C++完全兼容C 语言的特性。而 C 语言是面向过程的语言:在做一件事儿的时候,我们需要考虑好我们需要什么,怎样去做,第一步干什么,第二步干什么,第三步干什么,...最后一个干什么,这个件事儿才会被完成。这就是一个典型的面向过程的编程。

三大特性:封装+继承+多态

*封装:将变量和函数进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。封装本质上是一种管理:我们如何管理兵马俑呢?比如如果什么都不管,兵马俑就被随意破坏了。那么我们首先建了一座房子把兵马俑给封装起来。但是我们目的不是全封装起来,不让别人看。所以我们开放了售票通道,可以买票突破封装在合理的监管机制下进去参观。类也是一样,不想给别人看到的,我们使用protected/private把成员封装起来。开放一些共有的成员函数对成员合理的访问。所以封装本质是一种管理。

*继承:可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。

*多态:用父类的指针指向其子类的对象,然后通过父类的指针调用实际子类的成员函数实际上还是子类对象调用子类方法。实现多态的方式:重写,重载

重写:是指派生类中存在重新定义的函数。其函数名,参数列表,返回值类型,所有都必须同基类中被重写的函数一致只有函数体不同(花括号内),派生类对象调用时会调用派生类的重写函数,不会调用被重写函数。重写的基类中被重写的函数必须有virtual修饰。

存在虚函数的类都有一个一维的虚函数表叫做虚表,类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。

重载:在平时写代码中会用到几个函数但是他们的实现功能相同,但是有些细节却不同。例如:交换两个数的值其中包括(int, float,char,double)这些个类型。在C语言中我们是利用不同的函数名来加以区分。于是在C++中人们提出了用一个函数名定义多个函数,也就是所谓的函数重载。函数重载是指同一可访问区内被声明的几个具有不同参数列(参数的类型,个数,顺序不同)的同名函数,根据参数列表确定调用哪个函数,重载不关心函数返回类型

代码可读性好,质量高,运行效率高,安全性好,可复用性高,不断更新

具有相同性质的对象,我们可以抽象称为类

2.struct和class

①struct 一般用于描述一个数据结构集合,而 class 是对一个对象数据的封装

struct 中默认的访问控制权限是 public 的,而 class 中默认的访问控制权限是 private

③在继承关系中,struct 默认是公有继承,而 class 是私有继承

④class 关键字可以用于定义模板参数,就像 typename,而 struct 不能用于定义模板参数

C++允许有成员函数,且允许该函数是虚函数。

C 中使用结构体需要加上 struct 关键字,或者对结构体使用 typedef 取别名,而 C++ 中可以省略 struct 关键字直接使用。

sizeof(struct)

在计算结构体大小的时候存在对齐的问题

3.双引号" "和尖括号<>的区别

①区别:

(1)尖括号<>的头文件是系统文件,双引号" "的头文件是自定义文件

(2)编译器预处理阶段查找头文件的路径不一样。

②查找路径:

(1)使用尖括号<>的头文件的查找路径:编译器设置的头文件路径-->系统变量。

(2)使用双引号" "的头文件的查找路径:当前头文件目录-->编译器设置的头文件路径-->系统变量。

4.导入C函数的关键字是什么,C++编译时和C有什么不同?

①关键字:在C++中,导入C函数的关键字是extern,表达形式为extern “C”,主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。

②编译区别:由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名

5.从源码到可执行文件?

①预处理:展开宏定义+处理预编译指令+忽略注释

(1) 将所有的#define删除,并且展开所有的宏定义

(2) 处理所有的条件预编译指令,如#if、#ifdef

(3) 处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。

(4) 过滤所有的注释

(5) 添加行号和文件名标识。

②编译:词法分析+语法分析+语义分析+生成汇编代码

(1) 词法分析:将源代码的字符序列分割成一系列的记号。

(2) 语法分析:对记号进行语法分析,产生语法树。

(3) 语义分析:判断表达式是否有意义。

(4) 代码优化:

(5) 目标代码生成:生成汇编代码。

(6) 目标代码优化:

③汇编:将汇编代码转变成机器可以执行的指令

④链接:将不同的源文件产生的目标文件进行链接,从而形成一个可以执行的程序.exe

链接分为静态链接和动态链接。

静态链接:是在链接的时候就已经把要调用的函数或者过程链接到了生成的可执行文件中,就算把静态库删除也不会影响可执行程序的执行;生成的静态链接库,Windows下以.lib为后缀,Linux下以.a为后缀。

动态链接:是在链接的时候没有把调用的函数代码链接进去,而是在执行的过程中,再去找要链接的函数,生成的可执行文件中没有函数代码,只包含函数的重定位信息,所以当删除动态库时,可执行程序就不能运行。生成的动态链接库,Windows下以.dll为后缀,Linux下以.so为后缀。

6.static关键字:所有对象共享同一份数据/函数

定义全局/局部静态变量:在变量前面加上static关键字。初始化的静态变量会在数据段分配内存,未初始化的静态变量会在BSS段分配内存。直到程序结束,静态变量始终会维持前值。因为静态变量都在静态存储区,所以下次调用函数的时候还是能取到原来的值;static定义的静态常量在函数执行后不会释放其存储空间。

const定义的常量在超出其作用域之后其空间会被释放,故定义在栈上。

**用const_cast来去除const限定:

定义静态函数:在函数返回类型前加上static关键字,函数即被定义为静态函数。

静态变量和静态函数只能在本源文件中使用;(C++规定静态成员函数没有this指针)

7.指针、数组、引用!!!!!!!!!!!!!!!!!!

指针:相当于一个变量,存放的是其它变量在内存中的地址。指针名指向了内存的首地址;所以指针(名)=地址

数组:数组是用于储存多个相同类型数据的集合。数组名是首元素的地址。由连续的内存位置组成的。

同类型指针变量可以相互赋值;数组不行,只能一个一个元素的赋值或拷贝

③数组所占存储空间的内存大小:sizeof(数组名)/sizeof(数据类型)

32位平台下,无论指针/引用的类型是什么,sizeof(指针/引用)都是4

64位平台下,sizeof(指针/引用)都是8

④初始化

*数组元素、个数、地址

一维数组
二维数组

*使用指针时应注意:

①定义指针时,指针可以不初始化但一般先初始化为NULL(空指针指向的内存不可以进行访问)。

②用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。

③不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。

④避免数字或指针的下标越界,特别要当心发生“多1”或者“少1”操作

⑤动态内存的申请与释放必须配对,防止内存泄漏

⑥用free或delete释放了内存之后,立即将指针设置为NULL,防止“野指针”(一个已销毁或者访问受限内存区域的指针,该指针指向的位置是不可知的,野指针不能判断是否为NULL来避免)

*const修饰指针

看const右侧紧跟着的是指针还是常量, 是指针就是常量指针,是常量就是指针常量

普通变量存放的是数据,指针变量存放的是地址,通过&取地址,*取内容

指针变量可以通过" * "操作符,操作指针变量指向的内存空间,这个过程称为解引用

this指针:指向被调用的成员函数所属的对象

this指针是隐含每一个非静态成员函数内的一种指针

this指针不需要定义,直接使用即可

this指针的用途:

*当形参和成员变量同名时,可用this指针来区分

*在类的非静态成员函数中返回对象本身,可使用return *this

引用就是取别名

a和b其实是一个东西

*引用必须初始化,因为引用其实就是一个别名,需要告诉编译器定义的是谁的引用不可以初始化为为NULL

*引用在初始化后不可以改变

*引用只能是一级

*指针和引用作为函数参数时,指针需要检查是否为空,引用不需要

C++左值left value和右值right value

C++对于左值和右值没有标准定义,但是有一个被广泛认同的说法:

可以取地址的,有名字的,非临时的就是左值;

不能取地址的,没有名字的,临时的就是右值;

从本质上理解,创建和销毁由编译器幕后控制,程序员只能确保在本行代码有效的,就是右值(包括立即数);而用户创建的,通过作用域规则可知其生存期的,就是左值(包括函数返回的局部变量的引用以及const对象)。

左值引用:

左值引用要求右边的值必须能够取地址,如果无法取地址,可以用常引用;但使用常引用后,我们只能通过引用来读取数据,无法去修改数据,因为其被const修饰成常量引用了。

右值引用(C++11新特性)

和声明左值引用一样,右值引用也必须立即进行初始化操作,且只能使用右值进行初始化。


*二级指针

8.静态局部变量、静态全局变量      局部变量、全局变量

①首先从作用域考虑

全局变量:全局作用域,可以通过extern作用于其他非定义的源文件。

静态全局变量 :全局作用域+文件作用域,所以无法在其他文件中使用。

局部变量:局部作用域,比如函数的参数,函数内的局部变量等等。

静态局部变量 :所在的函数在多调用多次时,只有第一次才经历变量定义和初始化,以后多次在调用时不再定义和初始化,而是维持之前上一次调用时执行后这个变量的值。本次接着来使用。

②从所在空间考虑

除了局部变量上外,其他都在静态存储区。因为静态变量都在静态存储区,所以下次调用函数的时候还是能取到原来的值。

③生命周期

 局部变量在栈上,出了作用域就回收内存;而全局变量、静态全局变量、静态局部变量都在静态存储区,直到程序结束才会回收内存

9.内联函数inline和宏定义define

宏定义不是函数,但是使用起来像函数。预处理器用复制宏代码的方式代替函数的调用,省去了函数压栈退栈过程,提高了效率;而内联函数本质上是一个函数,内联函数一般用于函数体的代码比较简单的函数,不能包含复杂的控制语句,while、switch,并且内联函数本身不能直接调用自身。

宏定义是在预编译的时候把所有的宏名用宏体来替换,简单的说就是字符串替换 ;而内联函数则是在编译的时候进行代码插入,编译器会在每处调用内联函数的地方直接把内联函数的内容展开,不需要寻址,这样可以省去函数的调用的开销,提高效率

宏定义是没有类型检查的,无论对还是错都是直接替换;而内联函数在编译的时候会进行类型的检查,内联函数满足函数的性质,比如有返回值、参数列表等

1、使用时的一些注意事项:

使用宏定义一定要注意错误情况的出现,比如宏定义函数没有类型检查,可能传进来任意类型,从而带来错误,如举例。还有就是括号的使用,宏在定义时要小心处理宏参数,一般用括号括起来,否则容易出现二义性

inline函数一般用于比较小的,频繁调用的函数,这样可以减少函数调用带来的开销。只需要在函数返回类型前加上关键字inline,即可将函数指定为inline函数。

同其它函数不同的是,最好将inline函数定义在头文件,而不仅仅是声明,因为编译器在处理inline函数时,需要在调用点内联展开该函数,所以仅需要函数声明是不够的。

2、内联函数使用的条件:

内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率 的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。以下情况不宜使用内联:

(1)如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。

(2)如果函数体内出现循环和开关语句,那么执行函数体内代码的时间要比函数调用的开销大。

内联不是什么时候都能展开的,一个好的编译器将会根据函数的定义体,自动地取消不符合要求的内联;内联函数不需要寻址;避免了函数调用的开销

10.new和malloc

new是操作符,而malloc是函数

new在调用的时候先分配内存,再调用构造函数,释放的时候调用析构函数;而malloc没有构造函数和析构函数。

malloc需要给定申请内存的大小,返回的指针需要强转;new会调用构造函数,不用指定内存的大小,返回指针不用强转。

new可以被重载;malloc不行

new分配内存更直接和安全

new发生错误抛出异常,malloc返回null

*malloc底层实现:当开辟的空间小于 128K 时,调用 brk()函数;当开辟的空间大于 128K 时,调用mmap()。malloc采用的是内存池的管理方式,以减少内存碎片。先申请大块内存作为堆区,然后将堆区分为多个内存块。当用户申请内存时,直接从堆区分配一块合适的空闲快。采用隐式链表将所有空闲块,每一个空闲块记录了一个未分配的、连续的内存地址。

new底层实现:关键字new在调用构造函数的时候实际上进行了如下的几个步骤:

1.创建一个新的对象

2.将构造函数的作用域赋值给这个新的对象(因此this指向了这个新的对象)

3.执行构造函数中的代码(为这个新对象添加属性)

4.返回新对象

11.const和define

const用于定义常量

define用于定义宏,而宏也可以用于定义常量。都用于常量定义时,它们的区别有:

①const生效于编译的阶段;define生效于预处理阶段。

②const定义的常量,在C语言中是存储在内存中、需要额外的内存空间的;define定义的常量,运行时是直接的操作数,并不会存放在内存中。

③const定义的常量是带类型的;define定义的常量不带类型。因此define定义的常量不利于类型检查。

12.函数指针和指针函数

①定义不同

指针函数本质是一个函数,其返回值为指针。

函数指针本质是一个指针,其指向这个函数。

②写法不同

指针函数:int  *fun(int x,int y);       int  *  fun(int x,int y);      int*  fun(int x,int y);

函数指针:int (*fun)(int x,int y);函数指针是需要把一个函数的地址赋值给它

函数名带括号的就是函数指针,否则就是指针函数。

③用法不同

回调函数(callback)

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

回调函数不是在函数的定义上区别于普通函数,而是在调用的方式有区别

13.传值方式:值传递+引用传递+指针传递

①值传递:形参即使在函数体内值发生变化,不会影响实参的值;void swap(int a,int b)---swap(a,b)

②引用传递:形参在函数体内值发生变化,会影响实参的值;void swap(int &a,int &b)---swap(a,b)

③指针传递:在指针指向没有发生改变的前提下,形参在函数体内值发生变化,会影响实参的值;void swap(int *a,int *b)---swap(&a,&b)

值传递用于对象时,整个对象会拷贝一个副本,这样效率低;而引用传递用于对象时,不发生拷贝行为,只是绑定对象,更高效;指针传递同理,但不如引用传递安全。

14.三目运算符

二、C++内存

1.堆和栈

①堆栈空间分配不同

栈由操作系统/编译器自动申请分配释放,速度快 ,存放函数的参数、局部变量等;栈是由高地址向低地址扩展;栈由于其先进后出的特性,不会产生内存碎片

堆一般由程序员分配释放,只要程序员感觉程序此处需要用到多大的内存空间,那么就使用malloc或者new来申请固定大小的内存使用,比较方便;频繁调用malloc和free,会产生内存碎片,降低程序效率堆是由低地址向高地址扩展

②堆栈缓存方式不同。栈使用的是一级缓存, 它们通常都是被调用时处于存储空间中,调用完毕立即释放;堆则是存放在二级缓存中,速度要慢些。

③堆栈数据结构不同。堆类似数组结构;栈类似栈结构,先进后出。

④存放内容不同。

存放的内容,一般来说是函数地址和相关参数。当主函数要调用一个函数的时候,要对当前断点进行保存,需要使用栈来实现,首先入栈的是主函数下一条语句的地址,然后是调用函数的参数,一般情况下是按照从右向左的顺序入栈,之后是调用函数的局部变量注意静态变量是存放在全局内存区,是不入栈的;

堆,一般情况堆顶使用一个字节的空间来存放堆的大小,而堆中具体存放内容是由程序员来完成的。

怎么判断数据分配在栈上还是堆上??:首先局部变量分配在栈上;而通过malloc和new申请的空间是在

eg..堆区创建数组:在堆区中创建了个int类型的一维数组,长度是10,并且用int类型的指针指向他来便于后期维护。

2.内存管理

①内存分配方式

在C++中,内存分成5个区:堆、栈、自由存储区、全局/静态存储区和常量存储区。

,在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放

堆(完全二叉树,大根堆/小根堆),由malloc和free申请和释放的内存或由new和delete申请和释放的内存。C++在堆上创建对象。

自由存储区,由new和delete申请和释放的内存,默认在上,通过重载new可以在其他几个区实现数据的存储,这些new申请和delete释放的区域都被称为自由存储区

全局/静态存储区,全局变量和静态变量被分配到同一块内存中

常量存储区,这是一块比较特殊的存储区,里面存放的是常量,不允许修改。

②常见的内存错误及其对策

(1)内存分配未成功,却使用了它。

(2)内存分配虽然成功,但是尚未初始化就引用它。

(3)内存分配成功并且已经初始化,但操作越过了内存的边界。

(4)忘记了释放内存,造成内存泄露

(5)释放了内存却继续使用它。

对策:

(1)定义指针时,先初始化为NULL。

(2)用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。

(3)不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。

(4)避免数字或指针的下标越界,特别要当心发生“多1”或者“少1”操作

(5)动态内存的申请与释放必须配对,防止内存泄漏

(6)用free或delete释放了内存之后,立即将指针设置为NULL,防止“野指针”

(7)使用智能指针。

③内存泄露及解决办法

什么是内存泄露?

简单地说就是申请了一块内存空间,使用完毕后没有释放掉:

(1)new和malloc申请资源使用后,没有用delete和free释放;

(2)子类继承父类时,父类析构函数不是虚函数。

(3)操作系统的句柄资源(管理和指向资源的指针)使用后没有释放。

怎么检测?

第一:良好的编码习惯,使用了内存分配的函数,一旦使用完毕,要记得使用其相应的函数释放掉。

第二:将分配的内存的指针以链表的形式自行管理,使用完毕之后从链表中删除,程序结束时可检查改链表。

第三:使用智能指针。

第四:一些常见的工具插件,如ccmalloc、Dmalloc、Leaky、Valgrind等等

3. 程序有哪些section,分别的作用?程序启动的过程?怎么判断数据分配在栈上还是堆上?

①一个程序有哪些section:

如上图,从低地址到高地址,一个程序由代码段、数据段、 BSS 段组成。

*数据段:存放程序中已初始化的全局变量和静态变量的一块内存区域。

*代码段:存放程序执行代码的一块内存区域。只读,代码段的头部还会包含一些只读的常数变量。

*BSS 段:存放程序中未初始化或初始化为0的全局变量和静态变量的一块内存区域;可R/W;程序执行之前BSS段会自动清0

**可执行程序在运行时又会多出两个区域:堆区和栈区。

*堆区:动态申请内存用。堆从低地址向高地址增长。

*栈区:存储局部变量、函数参数值。栈从高地址向低地址增长。是一块连续的空间。

*最后还有一个文件映射区,位于堆和栈之间。

三、面向对象

1.什么是面向对象

①面向对象是一种编程思想,把一切东西看成是一个个对象,比如人、耳机、鼠标、水杯等,他们各自都有属性,比如:耳机是白色的,鼠标是黑色的,水杯是圆柱形的等等,把这些对象拥有的属性变量和操作这些属性变量的函数打包成一个来表示

②面向过程和面向对象的区别:

面向过程:根据业务逻辑从上到下写代码

面向对象:将数据与函数绑定到一起,进行封装,这样能够更快速的开发程序,减少了重复代码的重写过程

2.构造函数:默认构造函数+初始化构造函数+拷贝构造函数+移动构造函数

类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值

默认构造函数和初始化构造函数:在定义类的对象的时候,完成对象的初始化工作。有了有参的构造了,编译器就不提供默认的构造函数。

有了有参的构造了,编译器就不提供默认的构造函数

②拷贝构造函数:用于复制本类的对象

赋值构造函数默认实现的是值拷贝(浅拷贝)

③移动构造函数。用于将其他类型的变量,隐式转换为本类对象。下面的转换构造函数,将int类型的r转换为Student类型的对象,对象的age为r,num为1004.

析构函数 :~类名( ){ }

类的析构函数是类的一种特殊的成员函数,它会在每次删除/销毁所创建的对象时执行。只定义了析构函数,编译器将自动生成拷贝构造函数默认构造函数

析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数,因此不可以发生重载。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源,而且只会自动调用一次。

3.深拷贝和浅拷贝

浅拷贝:又称值拷贝,将源对象的值拷贝到目标对象中去,本质上来说源对象和目标对象共用一份实体,只是所引用的变量名不同,地址其实还是相同的(别名)。举个简单的例子,你的小名叫西西,大名叫冬冬,当别人叫你西西或者冬冬的时候你都会答应,这两个名字虽然不相同,但是都指的是你。浅拷贝会带来析构函数重复释放堆区问题

深拷贝:拷贝的时候先在堆区开辟出和源对象大小一样的空间,然后将源对象里的内容拷贝到目标对象中去,这样两个指针就指向了不同的内存位置。并且里面的内容是一样的,这样不但达到了我们想要的目的,还不会出现问题,两个指针先后去调用析构函数,分别释放自己所指向的位置。即为每次增加一个指针,便申请一块新的内存,并让这个指针指向新的内存,深拷贝情况下,不会出现重复释放同一块内存的错误。

4.多态:行为的封装

同一个事物在不同场景下的多种形态。 多态分为静态多态和动态多态:

静态多态/早绑定:编译器在编译期间完成的,编译器会根据实参类型来推断该调用哪个函数,如果有对应的函数,就调用,没有则在编译时报错;函数重载 运算符重载属于静态多态,复用函数名

动态多态/晚绑定:编译器在运行期间完成的, 派生类和虚函数实现运行时多态

动态绑定条件:

虚函数:基类中必须有虚函数,在派生类中必须重写基类虚函数   virtual

通过基类的指针或引用来调用虚函数:(当父类指针(引用)指向 父类对象时,就调用父类中定义的虚函数;即当父类指针(引用)指向 子类对象时,就调用子类中定义的虚函数)

5.类继承

类内可以直接访问自己类的public、protected、private成员,但类对象/类外只能访问自己类的public成员,不能访问protected、private成员。

一个派生类继承了所有的基类方法,但下列情况除外:

*基类的构造函数、析构函数和拷贝构造函数。

*基类的重载运算符。

*基类的友元函数

①public继承:派生类可以访问基类的public、protected成员,不可以访问基类的private成员;但是可以通过调用基类的公有保护成员来访问。

派生类对象可以访问基类的public成员,不可以访问基类的protected、private成员。

②protected继承:派生类可以访问基类的public、protected成员,不可以访问基类的private成员;

派生类对象不可以访问基类的public、protected、private成员。

③private继承:派生类可以访问基类的public、protected成员,不可以访问基类的private成员;

派生类对象不可以访问基类的public、protected、private成员。

*C++类对象的初始化顺序

①创建派生类的对象,基类的构造函数优先被调用(也优先于派生类里的成员类);

②如果类里面有成员类,成员类的构造函数优先被调用;(也优先于该类本身的构造函数)

③基类构造函数如果有多个基类,则构造函数的调用顺序是某类在类派生表中出现的顺序而不是它们在成员初始化表中的顺序;

④成员类对象构造函数如果有多个成员类对象,则构造函数的调用顺序是对象在类中被声明的顺序而不是它们出现在成员初始化表中的顺序;

⑤派生类构造函数,作为一般规则派生类构造函数应该不能直接向一个基类数据成员赋值而是把值传递给适当的基类构造函数,否则两个类的实现变成紧耦合的(tightly coupled)将更加难于正确地修改或扩展基类的实现。(基类设计者的责任是提供一组适当的基类构造函数)

父类构造函数–>成员类对象构造函数–>自身构造函数。其中成员变量的初始化与声明顺序有关,构造函数的调用顺序是类派生列表中的顺序。

析构顺序和构造顺序相反

6.c++类内定义引用成员变量:

①不能用默认构造函数初始化,必须提供构造函数来初始化引用成员变量。否则会造成引用未初始化错误。

②构造函数的形参也必须是引用类型。

③不能在构造函数里初始化,必须在初始化列表中进行初始化。

7.构造函数为什么不能被声明为虚函数

①从存储空间角度:虚函数对应一个vtale,这个表的地址是存储在对象的内存空间的。如果将构造函数设置为虚函数,就需要到vtable 中调用,可是对象还没有实例化,没有内存空间分配,如何调用。(悖论)

②从使用角度:虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用。构造函数本身就是要初始化实例,那使用虚函数也没有实际意义呀。所以构造函数没有必要是虚函数。虚函数的作用在于通过父类的指针或者引用来调用它的时候能够变成调用子类的那个成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。

③从实现上看,vbtl 在构造函数调用后才建立,因而构造函数不可能成为虚函数。从实际含义上看,在调用构造函数时还不能确定对象的真实类型(因为子类会调父类的构造函数);而且构造函数的作用是提供初始化,在对象生命期只执行一次,不是对象的动态行为,也没有太大的必要成为虚函数。

7.常函数:函数名()+const

不会对这个类对象的数据成员(准确地说是非静态数据成员)作任何改变

有 const 修饰的成员函数(指 const 放在函数参数表的后面,而不是在函数前面或者参数表内),只能读取数据成员,不能改变数据成员和其值;没有 const 修饰的成员函数,对数据成员则是可读可写的。

除此之外,在类的成员函数后面加 const 还有什么好处呢?那就是常量(即 const)对象可以调用 const 成员函数,而不能调用非const修饰的函数。正如非const类型的数据可以给const类型的变量赋值一样,反之则不成立。

8.虚函数和纯虚函数

虚函数的作用主要是实现了多态的机制:用父类型的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数;在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。

纯虚函数在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,,但是在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数,(Python中的pass),以便在派生类中对其进行实现。

9.抽象类

抽象类的定义如下:

纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”,有虚函数的类就叫做抽象类。

抽象类有如下几个特点:

1)抽象类只能用作其他类的基类,不能建立抽象类对象。

2)抽象类不能用作参数类型、函数返回类型或显式转换的类型。

3)可以定义指向抽象类的指针和引用,此指针可以指向它的派生类,进而实现多态性。

10.虚析构函数:防止内存泄露

virtual ~函数名() {  }

11.

四、标准模板库(Standard Template Library ,STL)

1.STL的组成

①容器(Container):

vetor:能够像容器一样存放各种类型的对象,简单地说,vector是一个能够存放任意类型的动态数组,能够增加和压缩数据。

②算法(Algorithm)

是用来操作容器中的数据的模板函数。例如,STL用sort()来对一 个vector中的数据进行排序,用find()来搜索一个list中的对象, 函数本身与他们操作的数据的结构和类型无关,因此他们可以用于从简单数组到高度复杂容器的任何数据结构上。

③迭代器(Iterator)

提供了访问容器中对象的方法。例如,可以使用一对迭代器指定list或vector中的一定范围的对象。 迭代器就如同一个指针。事实上,C++ 的指针也是一种迭代器。 但是,迭代器也可以是那些定义了operator*()以及其他类似于指针的操作符方法的类对象;

④仿函数(Function object)

仿函数又称之为函数对象, 其实就是重载了操作符的struct,没有什么特别的地方。

⑤适配器(Adaptor)

简单的说就是一种接口类,专门用来修改现有类的接口,提供一中新的接口;或调用现有的函数来实现所需要的功能。主要包括3中适配器Container Adaptor、Iterator Adaptor、Function Adaptor。

⑥空间配制器(Allocator)

为STL提供空间配置的系统。其中主要工作包括两部分:

(1)对象的创建与销毁;

(2)内存的获取与释放。

2.STL常见容器

容器可以用于存放各种类型的数据(基本类型的变量,对象等)的数据结构,都是模板类,分为顺序容器、关联式容器、容器适配器三种类型,三种类型容器特性分别如下:

①顺序容器

容器并非排序的,元素的插入位置同元素的值无关。包含vector、deque、list,具体实现原理如下:

(1)vector 头文件

动态数组。元素在内存连续存放。随机存取任何元素都能在常数时间O(1)完成。在尾端增删元素具有较佳的性能。

(2)deque 头文件

双向队列。元素在内存连续存放。随机存取任何元素都能在常数时间完成(仅次于vector)。在两端增删元素具有较佳的性能(大部分情况下是常数时间)。

(3)list 头文件

双向链表。元素在内存不连续存放。在任何位置增删元素都能在常数时间完成。不支持随机存取。

②关联式容器

元素是排序的;插入任何元素,都按相应的排序规则来确定其位置;在查找时具有非常好的性能;通常以平衡二叉树的方式实现。包含set、multiset、map、multimap,具体实现原理如下:

(1)set/multiset 头文件

set 即集合。set中不允许相同元素,multiset中允许存在相同元素。

(2)map/multimap 头文件

map与set的不同在于map中存放的元素有且仅有两个成员变,一个名为first,另一个名为second, map根据first值对元素从小到大排序,并可快速地根据first来检索元素。

 注意:map同multimap的不同在于是否允许相同first值的元素。

③容器适配器

封装了一些基本的容器,使之具备了新的函数功能,比如把deque封装一下变为一个具有stack功能的数据结构。这新得到的数据结构就叫适配器。包含stack,queue,priority_queue,具体实现原理如下:

(1)stack 头文件

栈是项的有限序列,并满足序列中被删除、检索和修改的项只能是最进插入序列的项(栈顶的项)。后进先出。

(2)queue 头文件

队列。插入只可以在尾部进行,删除、检索和修改只允许从头部进行。先进先出。

(3)priority_queue 头文件

优先级队列。内部维持某种有序,然后确保优先级最高的元素总是位于头部。最高优先级元素总是第一个出列。

五、C++11新特性

1.C++11新特性

①语法的改进

(1)统一的初始化方法:直接在变量名后面跟上初始化列表,来进行对象的初始化

(2)成员变量默认初始化:构建一个类的对象不需要用构造函数初始化成员变量。

(3)auto关键字用于定义变量,编译器可以自动判断的类型(前提:定义一个变量时对其进行初始化)

(4)decltype 求表达式的类型

(5)智能指针 shared_ptr

(6)空指针 nullptr(原来NULL)

(7)基于范围的for循环

(8)右值引用和move语义 让程序员有意识减少进行深拷贝操作

②标准库扩充(往STL里新加进一些模板类,比较好用)

(9)无序容器(哈希表) 用法和功能同map一模一样,区别在于哈希表的效率更高

(10)正则表达式 可以认为正则表达式实质上是一个字符串,该字符串描述了一种特定模式的字符串

(11)Lambda表达式

六、基础题

1.C++程序文件的额后缀与所运行的具体编译器有关,常见的有:.cc   .cxx    .cpp   .cp    .C  (.c不是) .c++

2.C++标识符:以字母 A-Z 或 a-z 或下划线 _ 开始,后跟零个或多个字母、下划线和数字(0-9);标识符不能是关键字;C++区分大小写

3.相同作用域中不能有两个同名变量

4.C++定义常量的两种方法:

5.switch语句

6.continue:跳过本次循环中余下尚未执行的语句,继续执行下一次循环

7.goto无条件跳转

8.函数的声明可以多次,但是函数的定义只能有一次

七、C++多线程

多线程是多任务处理的一种特殊形式,多任务处理允许让电脑同时运行两个或两个以上的程序。一般情况下,有两种类型的多任务处理:基于进程和基于线程

*基于进程的多任务处理是程序的并发执行。

*基于线程的多任务处理是同一程序的片段的并发执行。

多线程程序包含可以同时运行的两个或多个部分。这样的程序中的每个部分称为一个线程,每个线程定义了一个单独的执行路径。

1.创建线程

创建线程成功时,函数返回 0,若返回值不为 0 则说明创建线程失败。

2.终结线程

在这里,pthread_exit 用于显式地退出一个线程。通常情况下,pthread_exit() 函数是在线程完成工作后无需继续存在时被调用。

如果 main() 是在它所创建的线程之前结束,并通过 pthread_exit() 退出,那么其他线程将继续执行。否则,它们将在 main() 结束时自动被终止。

3.连接或分离线程:

pthread_join() 子程序阻碍调用程序,直到指定的 threadid 线程终止为止。当创建一个线程时,它的某个属性会定义它是否是可连接的(joinable)或可分离的(detached)。只有创建时定义为可连接的线程才可以被连接。如果线程创建时被定义为可分离的,则它永远也不能被连接。

八、Hook技术

Hook 技术又叫做钩子函数,在系统没有调用该函数之前,钩子程序就先捕获该消息,钩子函数先得到控制权,这时钩子函数既可以加工处理(改变)该函数的执行行为,还可以强制结束消息的传递。简单来说,就是把系统的程序拉出来变成我们自己执行代码片段。

要实现钩子函数,有两个步骤:

1. 利用系统内部提供的接口,通过实现该接口,然后注入进系统(特定场景下使用)

2.动态代理(使用所有场景)

九、Windows API(Application Programming Interface,应用程序接口)

API函数是一些预先定义的函数。操作系统除了协调应用程序的执行、内存分配、系统资源管理外,同时也是一个很大的服务中心,调用这个服务中心的各种服务(每一种服务是一个函数),可以帮助应用程序达到开启视窗、描绘图形、使用周边设备的目的。




操作系统


一、基础知识

1.进程空间

命令行参数和环境变量

栈区:存储局部变量、函数参数值。栈从高地址向低地址增长。是一块连续的空间。

文件映射区,位于堆和栈之间。

堆区:动态申请内存用。堆从低地址向高地址增长。

BSS 段:存放程序中未初始化的全局变量和静态变量的一块内存区域。

数据段:存放程序中已初始化的全局变量和静态变量的一块内存区域。

代码段:存放程序执行代码的一块内存区域。只读,代码段的头部还会包含一些只读的常数变量。

2.并发和并行

①并发:对于单个CPU,在一个时刻只有一个进程在运行,但是线程的切换时间减少到纳秒数量级,多个任务不停来回快速切换

②并行:对于多个CPU,多个进程同时运行

区别。通俗来讲,它们虽然都说是"多个进程同时运行",但是它们的"同时"不是一个概念。并行的"同时"是同一时刻可以多个任务在运行(处于running),并发的"同时"是经过不同线程快速切换,使得看上去多个任务同时都在运行的现象。

3.进程、线程、协程

①进程:程序是指令、数据及其组织形式的描述,而进程则是程序的运行实例,包括程序计数器、寄存器和变量的当前值。

②线程微进程,一个进程里更小粒度的执行单元。一个进程里包含多个线程并发执行任务。

③协程:协程是微线程,在子程序内部执行,可在子程序内部中断,转而执行别的子程序,在适当的时候再返回来接着执行。

区别

线程与进程的区别

(1)一个线程从属于一个进程;一个进程可以包含多个线程。

(2)一个线程挂掉,对应的进程挂掉;一个进程挂掉,不会影响其他进程。

(3)进程是系统资源调度的最小单位线程CPU调度的最小单位

(4)进程系统开销显著大于线程开销;线程需要的系统资源更少。

(5)进程在执行时拥有独立的内存单元,多个线程共享进程的内存,如代码段、数据段、扩展段;但每个线程拥有自己的栈段和寄存器组。

(6)进程切换时需要刷新TLB并获取新的地址空间,然后切换硬件上下文和内核栈,线程切换时只需要切换硬件上下文和内核栈。

(7)通信方式不一样。

(8)进程适应于多核、多机分布;线程适用于多核

共同点: 它们都能提高程序的并发度,提高程序运行效率和响应时间。

线程与协程的区别:

(1)协程执行效率极高。协程直接操作栈基本没有内核切换的开销,所以上下文的切换非常快,切换开销比线程更小。

(2)协程不需要多线程的锁机制,因为多个协程从属于一个线程,不存在同时写变量冲突,效率比线程高。

(3)一个线程可以有多个协程。

4.进程通信!!!!!!!!!!!!!!!!

①管道:包括无名管道和命名管道,无名管道半双工,只能用于具有亲缘关系的进程直接的通信(父子进程或者兄弟进程),可以看作一种特殊的文件;命名管道可以允许无亲缘关系进程间的通信。

②系统IPC

消息队列:消息的链接表,放在内核中。消息队列独立于发送与接收进程,进程终止时,消息队列及其内容并不会被删除;消息队列可以实现消息的随机查询,可以按照消息的类型读取。

信号量semaphore:是一个计数器,可以用来控制多个进程对共享资源的访问。信号量用于实现进程间的互斥与同步。

信号:用于通知接收进程某个事件的发生。

共享内存:使多个进程访问同一块内存空间。

③套接字socket:用于不同主机直接的通信。Socket是由主机IP端口(pid)组成的

5.进程同步的方式

①信号量semaphore:是一个计数器,可以用来控制多个进程对共享资源的访问。信号量用于实现进程间的互斥与同步。P操作(递减操作)可以用于阻塞一个进程,V操作(增加操作)可以用于解除阻塞一个进程。

②管道:一个进程通过调用管程的一个过程进入管程。在任何时候,只能有一个进程在管程中执行,调用管程的任何其他进程都被阻塞,以等待管程可用。

③消息队列:消息的链接表,放在内核中。消息队列独立于发送与接收进程,进程终止时,消息队列及其内容并不会被删除;消息队列可以实现消息的随机查询,可以按照消息的类型读取。

6.进程调度算法

①先来先服务调度算法:每次调度都是从后备作业(进程)队列中选择一个或多个最先进入该队列的作业(进程),将它们调入内存,为它们分配资源、创建进程,然后放入就绪队列。

②短作业(进程)优先调度算法:短作业优先(SJF)的调度算法是从后备队列中选择一个或若干个估计运行时间最短的作业(进程),将它们调入内存运行。

③高优先级优先调度算法:当把该算法用于作业调度时,系统将从后备队列中选择若干个优先权最高的作业装入内存。当用于进程调度时,该算法是把处理机分配给就绪队列中优先权最高的进程

④时间片轮转法:每次调度时,把CPU 分配给队首进程,并令其执行一个时间片。时间片的大小从几ms 到几百ms。当执行的时间片用完时,由一个计时器发出时钟中断请求,调度程序便据此信号来停止该进程的执行,并将它送往就绪队列的末尾;然后,再把处理机分配给就绪队列中新的队首进程,同时也让它执行一个时间片。

⑤多级反馈队列调度算法:综合前面多种调度算法。

7.进程状态转化

创建状态 一个应用程序从系统上启动,首先就是进入创建状态,需要获取系统资源创建进程管理块(PCB:Process Control Block)完成资源分配。

就绪状态 在创建状态完成之后,进程已经准备好,处于就绪状态,但是还未获得处理器资源,无法运行。

运行状态 获取处理器资源,被系统调度,当具有时间片开始进入运行状态。如果进程的时间片用完了就进入就绪状态

阻塞状态 在运行状态期间,如果进行了阻塞的操作,如耗时的I/O操作,此时进程暂时无法操作就进入到了阻塞状态,在这些操作完成后就进入就绪状态。等待再次获取处理器资源,被系统调度,当具有时间片就进入运行状态

终止状态 进程结束或者被系统终止,进入终止状态

8.线程通信!!!!!!

线程通信就是当多个线程共同操作共享的资源时,互相告知自己的状态以避免资源争夺。 线程通信主要可以分为三种方式,分别为共享内存、消息传递和管道流。

volatile共享内存:线程之间共享程序的公共状态,线程之间通过读-写内存中的公共状态来隐式通信。

消息传递:线程之间没有公共的状态,线程之间必须通过明确的发送信息来显示的进行通信。

管道流:管道输入/输出流的形式

9.死锁

死锁: 是指多个进程在执行过程中,因争夺资源而造成了互相等待。此时系统产生了死锁。比如两只羊过独木桥,若两只羊互不相让,争着过桥,就产生死锁。

产生的条件:死锁发生有四个必要条件

(1)互斥条件:进程对所分配到的资源不允许其他进程访问,若其他进程访问,只能等待,直到进程使用完成后释放该资源;

(2)请求保持条件:进程获得一定资源后,又对其他资源发出请求,但该资源被其他进程占有,此时请求阻塞,而且该进程不会释放自己已经占有的资源;

(3)不可剥夺条件:进程已获得的资源,只能自己释放,不可剥夺;

(4)环路等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

如何解决

(1)资源一次性分配,从而解决请求保持的问题

(2)可剥夺资源:当进程新的资源未得到满足时,释放已有的资源;

(3)资源有序分配:资源按序号递增,进程请求按递增请求,释放则相反。

10. 有了进程,为什么还要有线程

原因:进程在早期的多任务操作系统中是基本的执行单元。每次进程切换,都要先保存进程资源然后再恢复,这称为上下文切换但是进程频繁切换将引起额外开销,从而严重影响系统的性能。为了减少进程切换的开销,人们把两个任务放到一个进程中,每个任务用一个更小粒度的执行单元来实现并发执行,这就是线程

线程与进程对比

(1)进程间的信息难以共享。由于除去只读代码段外,父子进程并未共享内存,因此必须采用一些进程间通信方式,在进程间进行信息交换。

多个线程共享进程的内存,如代码段、数据段、扩展段,线程间进行信息交换十分方便。

(2)调用 fork() 来创建进程的代价相对较高,即便利用写时复制技术,仍然需要复制诸如内存页表和文件描述符表之类的多种进程属性,这意味着 fork() 调用在时间上的开销依然不菲。

但创建线程比创建进程通常要快 10 倍甚至更多。线程间是共享虚拟地址空间的,无需采用写时复制来复制内存,也无需复制页表。

二、Linux

1.Linux中查看进程运行状态的指令、查看内存使用情况的指令、tar解压文件的参数

①查看进程运行状态的指令:ps命令。“ps -aux | grep PID”,用来查看某PID进程状态

②查看内存使用情况的指令:free命令。“free -m”,命令查看内存使用情况。

③tar解压文件的参数

2.文件权限修改指令:chmod

3.常用的Linux命令

cd命令:用于切换当前目录

ls命令:查看当前文件与目录

grep命令:该命令常用于分析一行的信息,若当中有我们所需要的信息,就将该行显示出来,该命令通常与管道命令一起使用,用于对一些命令的输出进行筛选加工。

cp命令:复制命令

mv命令:移动文件或文件夹命令

rm命令:删除文件或文件夹命令

ps命令:查看进程情况

kill命令:向进程发送终止信号

tar命令:对文件进行打包,调用gzip或bzip对文件进行压缩或解压

cat命令:查看文件内容,与less、more功能相似

top命令:可以查看操作系统的信息,如进程、CPU占用率、内存信息等

pwd命令:命令用于显示工作目录


计网


五层协议:

越往下越靠近计算机硬件,而越往上则越靠近用户

1. MAC地址和IP地址

IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。

MAC地址指的是物理地址,用来定义网络设备的位置。

IP地址的分配是根据网络的拓扑结构,而不是根据谁制造了网络设置。若将高效的路由选择方案建立在设备制造商的基础上而不是网络所处的拓朴位置基础上,这种方案是不可行的。

无论是局域网,还是广域网中的计算机之间的通信,最终都表现为将数据包从某种形式的链路上的初始节点出发,从一个节点传递到另一个节点,最终传送到目的节点。数据包在这些节点之间的移动都是由ARP(Address Resolution Protocol:地址解析协议)负责将IP地址映射到MAC地址上来完成的,即即将数据包向本网络内所有的计算机发送,让每台计算机自己判断是否是接收方,这种方式叫广播

2.TCP 和 UDP

TCP(Transmission Control Protocol,传输控制协议):面向连接的、可靠的、基于字节流的传输层协议。包括 Socket、序列号及窗口大小。

其中Socket是由主机IP端口(pid)组成的,序列号是用来解决乱序问题的,而窗口大小则是用来做流量控制的。

UDP(User Data Protocol ,用户数据报协议):面向无连接、不可靠的尽最大努力交付、基于数据报的传输层协议。

TCP和UDP协议都是传输层协议,都为应用层服务

TCP协议是有连接的,有连接的意思是开始传输实际数据之前TCP的客户端和服务器端必须通过三次握手建立连接,会话结束之后也要结束连接。而UDP是无连接的

TCP协议保证数据按序发送,按序到达,提供超时重传来保证可靠性,但是UDP不保证按序到达,甚至不保证到达,只是努力交付,即便是按序发送的序列,也不保证按序送到。

TCP协议所需资源多,TCP首部需20个字节(不算可选项),UDP首部字段只需8个字节。

TCP有流量控制和拥塞控制,传输速度较慢;UDP没有,网络拥堵不会影响发送端的发送速率,传输速度快,实时性高。

TCP是一对一的全双工连接,而UDP则可以支持一对一,多对多,一对多的通信。

TCP面向的是字节流的服务,UDP面向的是报文的服务。

3.TCP三次握手和四次挥手

1)第一次握手:建立连接时,客户端服务器端发送连接请求报文段,包含自身数据通讯初始序号,进入SYN-SENT状态。

2)第二次握手:服务端收到客户端的SYN包,回一个ACK包(ACK=x+1)确认收到,同时发送一个SYN包(seq=y)给客户端

3)第三次握手:客户端收到应答包,再回一个ACK包(ACK=y+1)告诉服务端已经收到确认信息,进入ESTABLISHED状态

三次握手完成,成功建立连接,开始传输数据。

***为什么要三次握手?

避免重复连接,即已经失效的连接请求报文突然又被传送给了服务器端而引起连接混乱问题。如果不采用“三次握手”,那么只要B端发出确认报文就会认为新的连接已经建立了,但是A端并没有发出建立连接的请求,因此不会去向B端发送数据,B端没有收到数据就会一直等待,这样B端就会白白浪费掉很多资源。如果采用“三次握手”的话就不会出现这种情况,B端收到一个过时失效的报文段之后,向A端发出确认,此时A并没有要求建立连接,所以就不会向B端发送确认,这个时候B端也能够知道连接没有建立。

1)客户端发送FIN包(FIN=1)给服务端,告诉它自己的数据已经发送完毕请求终止连接,此时客户端不发送数据,但还能接收数据

2)服务端收到FIN包,回一个ACK包给客户端告诉它已经收到包了,此时还没有断开socket连接,而是等待剩下的数据传输完毕

3)服务端等待数据传输完毕后,向客户端发送FIN包表明可以断开连接

4)客户端收到后,回一个ACK包表明确认收到,等待一段时间,确保服务端不再有数据发过来,然后彻底断开连接

4.浏览器从输入 URL 到展现页面的全过程

1、输入地址

2、浏览器查找域名的 IP 地址

3、浏览器向 web 服务器发送一个 HTTP 请求

4、服务器的永久重定向响应

6、服务器处理请求

7、服务器返回一个 HTTP 响应

8、浏览器显示 HTML

9、浏览器发送请求获取嵌入在 HTML 中的资源(如图片、音频、视频、CSS、JS等等)

5.简述 HTTP 和 HTTPS 的区别

HTTP:是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。

HTTPS:是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。

HTTPS协议的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另一种就是确认网站的真实性。

HTTP与HTTPS的区别:

https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。

http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。

http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。

http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

6.Cookie 和 Session 的关系和区别

1.Cookie与Session都是会话的一种方式。它们的典型使用场景比如“购物车”,当你点击下单按钮时,服务端并不清楚具体用户的具体操作,为了标识并跟踪该用户,了解购物车中有几样物品,服务端通过为该用户创建Cookie/Session来获取这些信息。

2.cookie数据存放在客户的浏览器上,session数据放在服务器上。

3.cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗   考虑到安全应当使用session。

4.session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能   考虑到减轻服务器性能方面,应当使用COOKIE。

5.单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。


数据库


1.SQL中的聚合函数

①COUNT()函数:

COUNT()函数统计数据表中包含的记录行的总数,或者根据查询结果返回列中包含的数据行数,它有两种用法:

COUNT(*)计算表中总行数,不管某列是否有数值或者为空值。

COUNT(字段名)计算指定列下总的行数,计算时将忽略空值的行。

COUNT()函数可以与GROUP BY一起使用来计算每个分组的总和。

②AVG()函数():

AVG()函数通过计算返回的行数和每一行数据的和,求得指定列数据的平均值。

AVG()函数可以与GROUP BY一起使用,来计算每个分组的平均值。

③SUM()函数:

SUM()是一个求总和的函数,返回指定列值的总和。

SUM()可以与GROUP BY一起使用,来计算每个分组的总和。

④MAX()函数:

MAX()返回指定列中的最大值。

MAX()也可以和GROUP BY关键字一起使用,求每个分组中的最大值。

MAX()函数不仅适用于查找数值类型,也可应用于字符类型。

⑤MIN()函数:

MIN()返回查询列中的最小值。

MIN()也可以和GROUP BY关键字一起使用,求出每个分组中的最小值。

MIN()函数与MAX()函数类似,不仅适用于查找数值类型,也可应用于字符类型。

2. 表跟表是怎么关联的

内连接:

内连接通过inner JOIN来实现,它将返回两张表中满足连接条件的数据,不满足条件的数据不会查询出来。

外连接:

外连接通过outer JOIN来实现,它会返回两张表中满足连接条件的数据同时返回不满足连接条件的数据。外连接有两种形式:左外连接(LEFT OUTER JOIN)、右外连接(RIGHT OUTER JOIN)。

左外连接:可以简称为左连接(LEFT JOIN),它会返回左表中的所有记录和右表中满足连接条件的记录。

右外连接:可以简称为右连接(RIGHT JOIN),它会返回右表中的所有记录和左表中满足连接条件的记录。

等值连接:这种连接是通过where子句中的条件,将两张表连接在一起,它的实际效果等同于内连接。出于语义清晰的考虑,一般更建议使用内连接,而不是等值连接。

表的关系上来说,比较常见的关联关系有:一对多关联、多对多关联、自关联。

一对多关联:这种关联形式最为常见,一般是两张表具有主从关系,并且以主表的主键关联从表的外键来实现这种关联关系。另外,以从表的角度来看,它们是具有多对一关系的,所以不再赘述多对一关联了。

多对多关联:这种关联关系比较复杂,如果两张表具有多对多的关系,那么它们之间需要有一张中间表来作为衔接,以实现这种关联关系。这个中间表要设计两列,分别存储那两张表的主键。因此,这两张表中的任何一方,都与中间表形成了一对多关系,从而在这个中间表上建立起了多对多关系。

自关联:自关联就是一张表自己与自己相关联,为了避免表名的冲突,需要在关联时通过别名将它们当做两张表来看待。一般在表中数据具有层级(树状)时,可以采用自关联一次性查询出多层级的数据

3.WHERE和HAVING

WHERE是一个约束声明,使用WHERE约束来自数据库的数据,WHERE是在结果返回之前起作用的,WHERE中不能使用聚合函数

HAVING是一个过滤声明,是在查询返回结果集以后对查询结果进行的过滤操作,在HAVING中可以使用聚合函数。另一方面,HAVING子句中不能使用除了分组字段和聚合函数之外的其他字段。

从性能的角度来说,HAVING子句中如果使用了分组字段作为过滤条件,应该替换成WHERE子句。因为WHERE可以在执行分组操作和计算聚合函数之前过滤掉不需要的数据,性能会更好。

4.数据库事务

事务可由一条非常简单的SQL语句组成,也可以由一组复杂的SQL语句组成。在事务中的操作,要么都执行修改,要么都不执行,这就是事务的目的,也是事务模型区别于文件系统的重要特征之一。

事务需遵循ACID四个特性:

A(atomicity),原子性。原子性指整个数据库事务是不可分割的工作单位。只有使事务中所有的数据库操作都执行成功,整个事务的执行才算成功。事务中任何一个SQL语句执行失败,那么已经执行成功的SQL语句也必须撤销,数据库状态应该退回到执行事务前的状态。

C(consistency),一致性。一致性指事务将数据库从一种状态转变为另一种一致的状态。在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。

I(isolation),隔离性。事务的隔离性要求每个读写事务的对象与其他事务的操作对象能相互分离,即该事务提交前对其他事务都不可见,这通常使用锁来实现。

D(durability) ,持久性。事务一旦提交,其结果就是永久性的,即使发生宕机等故障,数据库也能将数据恢复。持久性保证的是事务系统的高可靠性,而不是高可用性。



数据结构


一、树

1.B+树

B+是一种多路搜索树,主要为磁盘或其他直接存取辅助设备而设计的一种平衡查找树,在B+树中,每个节点的可以有多个孩子,并且按照关键字大小有序排列。所有记录节点都是按照键值的大小顺序存放在同一层的叶节点中。相比B树,其具有以下几个特点:

每个节点上的指针上限为2d而不是2d+1(d为节点的出度)

内节点不存储data,只存储key;叶子节点不存储指针

1、B+树的层级更少:相较于B树B+每个非叶子节点存储的关键字数更多,树的层级更少所以查询数据更快;

2、B+树查询速度更稳定:B+所有关键字数据地址都存在叶子节点上,所以每次查找的次数都相同所以查询速度要比B树更稳定;

3、B+树天然具备排序功能:B+树所有的叶子节点数据构成了一个有序链表,在查询大小区间的数据时候更方便,数据紧密性很高,缓存的命中率也会比B树高。

4、B+树全节点遍历更快:B+树遍历整棵树只需要遍历所有的叶子节点即可,,而不需要像B树一样需要对每一层进行遍历,这有利于数据库做全表扫描。

B树相对于B+树的优点是,如果经常访问的数据离根节点很近,而B树的非叶子节点本身存有关键字其数据的地址,所以这种数据检索的时候会要比B+树快。

2.平衡二叉树(AVL树)

平衡二叉树又称为AVL树,是一种特殊的二叉排序树。其左右子树都是平衡二叉树,且左右子树高度之差的绝对值不超过1。一句话表述为:以树中所有结点为根的树的左右子树高度之差的绝对值不超过1。将二叉树上结点的左子树深度减去右子树深度的值称为平衡因子BF,那么平衡二叉树上的所有结点的平衡因子只可能是-1、0和1。只要二叉树上有一个结点的平衡因子的绝对值大于1,则该二叉树就是不平衡的。

3.红黑树

红黑树是一种二叉查找树,但在每个节点增加一个存储位表示节点的颜色,可以是红或黑(非红即黑)。通过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保没有一条路径会比其它路径长出两倍,因此,红黑树是一种弱平衡二叉树,相对于要求严格的AVL树来说,它的旋转次数少,所以对于搜索,插入,删除操作较多的情况下,通常使用红黑树。

性质:

1. 每个节点非红即黑

2. 根节点是黑的;

3. 每个叶节点(叶节点即树尾端NULL指针或NULL节点)都是黑的;

4. 如果一个节点是红色的,则它的子节点必须是黑色的。

5. 对于任意节点而言,其到叶子点树NULL指针的每条路径都包含相同数目的黑节点;

区别:

AVL 树是高度平衡的,频繁的插入和删除,会引起频繁的重新调平衡,导致效率下降;红黑树不是高度平衡的,算是一种折中,插入最多两次旋转,删除最多三次旋转。

所以红黑树在查找、插入、删除的性能都是O(logn),且性能稳定,所以STL里面很多结构包括map底层实现都是使用的红黑树。

二、排序算法


MFC


一、基本概念

1.API

Windows操作系统提供了各种各样的函数,以方便我们开发Windows应用程序。这些程序是Windows操作系统提供给应用程序编程的接口(Application Programming Interface),简称为API函数。我们在编写Windows程序时所说的API函数,就是指系统提供的函数,所有主要的Windows函数都在Windows头文件中进行了声明。

***Windows中播放声音的API函数,函数原型如下:

BOOL  PlaySound(LPCSTR  pszSound,HMODULE  hmod,DWORD  fdwSound);

pszSound就是你要播放的文件路径,fdwSound就是你要选择的播放模式。

***在使用API时,注意头文件和库文件对API的支持,否则程序编译或链接会提示错误。API PlaySound需要头文件mmsystem.h和库文件WINMM.LIB的支持。

#include<mmsystem.h>

#pragma  comment(lib,"WINMM.LIB")

2.SDK(Software Development Kit, 即软件开发工具包 )

一般是一些被软件工程师用于为特定的软件包、软件框架、硬件平台、操作系统等建立应用软件的开发工具的集合。举个例子,假如我们要开发无线信号发射器,在购买芯片或主板的同时,厂商同时会提供主板的SDK开发包,以方便我们对主板的编程操作。这个开发包通常会包含主板的API函数、帮助文档、使用手册、辅助工具等资源,也就是定义里面说的,SDK实际上一个开发所需资源的一个集合。

3.控制台程序

所谓的控制台应用程序,就是能够运行在MS-DOS环境中的程序。控制台应用程序通常没有可视化的界面,只是通过字符串来显示或者监控程序。控制台程序常常被应用在测试、监控等用途,用户往往只关心数据,不在乎界面。一个典型的应用就是ping网络。DOS 的这种界面叫CUI (Command line User Interface ) 命令行模式的人机接口

4.GUI程序( Graphical User Interface ,即图形用户界面)

5.MFC(Microsoft Foundation Classes,微软基础类库)

是微软公司实现的一个c++类库,主要封装了大部分的windows API函数,所以在MFC中,你可以直接调用 windows API,同时需要引用对应的头文件或库文件;另外,MFC除了是一个类库以外,还是一个框架,在vc++里新建一个MFC的工程,开发环境会自动帮你产生许多文件,同时它使用了mfcxx.dll。xx是版本,它封装了mfc内核,所以你在你的代码看不到原本的SDK编程中的消息循环等等东西,因为MFC框架帮你封装好了,这样你就可以专心的考虑你程序的逻辑,而不是这些每次编程都要重复的东西。但是由于是通用框架,没有最好的针对性,当然也就丧失了一些灵活性和效率,但是MFC的封装很浅,所以在灵活性以及效率上损失不大,可以忽略不计。

6.字符编码:Unicode和多字节

字符编码是一个复杂的过程。简单地讲,就是将机器语言(0和1组成的字符串)转换成我们能识别的信息的一个过程。我们不必在这里深究这个过程是怎么完成的,暂时没有必要。这里我们知道的是,MFC有两种编码方式,Unicode和多字节并且可以设置切换。切换方法是打开项目属性页,常规项对应的字符集中可切换编码方式。Visual C++6.0用的是多字节编码;Virtual Studio 2010 默认使用的是Unicode编码,所以在代码移植的时候经常会提示很多编码方式相关的错误,解决方法是将Unicode编码改为多字节编码即可。这里提示一点,使用 _T 宏有条件地编写字符串的代码,使之可移植到 Unicode。这一招可以解决大多数编码方式不匹配引发的问题。

二、建立MFC应用程序

从程序设计转化为软件设计,这里面对的不再是黑色的、单调的控制台界面,而是一个崭新的环境。各种功能强大,界面漂亮的软件,将从你的手上诞生。


C++算法题


1.如何判断一个单链表有环

①穷举遍历

首先从头节点开始,依次遍历单链表的每一个节点。每遍历到一个新节点,就从头节点重新遍历新节点之前的所有节点,用新节点ID和此节点之前所有节点ID依次作比较。如果发现新节点之前的所有节点当中存在相同节点ID,则说明该节点被遍历过两次,链表有环;如果之前的所有节点当中不存在相同的节点,就继续遍历下一个新节点,继续重复刚才的操作。

②哈希表缓存(key-value)/数组存值后遍历

③快慢指针

首先创建两个指针1和2(在java里就是两个对象引用),同时指向这个链表的头节点。然后开始一个大循环,在循环体中,让指针1每次向下移动一个节点,让指针2每次向下移动两个节点,然后比较两个指针指向的节点是否相同。如果相同,则判断出链表有环,如果不同,则继续下一次循环。假设从链表头节点到入环点的距离是D,链表的环长是S。那么循环会进行S次,可以简单理解为O(N)。除了两个指针以外,没有使用任何额外存储空间,所以空间复杂度是O(1)。

此方法也可以用一个更生动的例子来形容:在一个环形跑道上,两个运动员在同一地点起跑,一个运动员速度快,一个运动员速度慢。当两人跑了一段时间,速度快的运动员必然会从速度慢的运动员身后再次追上并超过,原因很简单,因为跑道是环形的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容