mst3

gcc 和 g++的区别
简单来说,gcc与g++都是GNU(组织)的一个编译器。需要注意以下几点:

gcc与g++都可以编译c代码与c++代码。但是:后缀为.c的,gcc把它当做C程序,而g++当做是C++程序;后缀为.cpp的,两者都会认为是C++程序。

编译阶段,g++会调用gcc,对于c++代码,两者是等价的,但是因为gcc命令不能自动和C++程序使用的库联接,所以通常用g++来完成链接。

编译可以用gcc/g++,而链接可以用g++或者gcc -lstdc++。因为gcc命令不能自动和C++程序使用的库联接(当然可以选择手动链接,使用命令如下),所以通常使用g++来完成联接。但在编译阶段,g++会自动调用gcc,二者等价。

gcc main.cpp -lstdc++
gcc编译的四个步骤, 以最简单的hello.c为例子

预处理-E/.i、编译-S/.s,汇编-c/.o,连接/可执行文件 gcc -E hello.c -o hello.i gcc -S hello.i -o hello.s gcc -c hello.s -o hello.o gcc hello.o -o hello

一步到位:gcc hello.c 这条命令隐含执行了 (1)预处理 (2)编译 (3)汇编 (4)链接 这里未指定输出文件,默认输出为a.out gcc编译C源码有四个步骤: 预处理 ----> 编译 ----> 汇编 ----> 链接 现在我们就用gcc的命令选项来逐个剖析gcc过程。 1)预处理(Pre-processing) 在该阶段,编译器将C源代码中的包含的头文件如stdio.h添加进来 参数:”-E” 用法:gcc -E hello.c -o hello.i 作用:将hello.c预处理输出hello.i文件。 2)编译(Compiling) 第二步进行的是编译阶段,在这个阶段中,gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc把代码翻译成汇编语言。 参数:”-S” 用法:gcc –S hello.i –o hello.s 作用:将预处理输出文件hello.i汇编成hello.s文件。 3)汇编(Assembling) 汇编阶段是把编译阶段生成的”.s”文件转成二进制目标代码“.o”文件 参数:“-c” 用法:gcc –c hello.s –o hello.o 作用:将汇编输出文件hello.s编译输出hello.o文件。 4)链接(Link) 在成功编译之后,就进入了链接阶段。 用法:gcc hello.o –o hello 作用:将编译输出文件hello.o链接成最终可执行文件hello。 运行该可执行文件,出现正确的结果如下。 >>> ./hello Hello World!

C++11包含大量的新特性:包含lambda表达式,类型推导keyword : auto、decltype,和模板的大量改进。

decltype实际上有点像auto的反函数,auto能够让你声明一个变量。而decltype则能够从一个变量或表达式中得到类型

nullptr是为了解决原来C++中NULL的二义性问题而引进的一种新的类型,由于NULL实际上代表的是0,

简化的for循环,能够用于遍历数组、容器、string以及由begin和end函数定义的序列(即有Iterator),for (auto p : m)

lambda表达式,能够用于创建并定义匿名的函数对象,以简化编程工作。Lambda的语法例如以下: [函数对象參数](操作符重载函数參数)->返回值类型{函数体}

vector iv{5, 4, 3, 2, 1};
int a = 2, b = 1;

for_each(iv.begin(), iv.end(), [b](int &x){cout<<(x + b)<

for_each(iv.begin(), iv.end(), [=](int &x){x *= (a + b);}); // (2)

for_each(iv.begin(), iv.end(), [=](int &x)->int{return x * (a + b);});// (3)

[]内的參数指的是Lambda表达式能够取得的全局变量。(1)函数中的b就是指函数能够得到在Lambda表达式外的全局变量,假设在[]中传入=的话,即是能够取得全部的外部变量,如(2)和(3)Lambda表达式

()内的參数是每次调用函数时传入的參数。

->后加上的是Lambda表达式返回值的类型。如(3)中返回了一个int类型的变量

变长參数的模板,C++11中引入了变长參数模板,所以发明了新的数据类型:tuple,tuple是一个N元组。能够传入1个, 2个甚至多个不同类型的数据

auto t1 = make_tuple(1, 2.0, “C++ 11”);
auto t2 = make_tuple(1, 2.0, “C++ 11”, {1, 0, 2});
避免了从前的pair中嵌套pair的丑陋做法。使得代码更加整洁

更加优雅的初始化方法,在引入C++11之前。仅仅有数组能使用初始化列表,其它容器想要使用初始化列表,仅仅能用下面方法:

int arr[3] = {1, 2, 3}
vector v(arr, arr + 3);
在C++11中,我们能够使用下面语法来进行替换:

int arr[3]{1, 2, 3};
vector iv{1, 2, 3};
map{ {1, “a”}, {2, “b”}};
string str{“Hello World”};
什么是智能指针?智能指针的原理

将基本类型指针封装为类对象指针(这个类肯定是个模板,以适应不同基本类型的需求),并在析构函数里编写delete语句删除指针指向的内存空间。

智能指针是一个类,这个类的构造函数中传入一个普通指针,析构函数中释放传入的指针。智能指针的类都是栈上的对象,所以当函数(或程序)结束时会自动被释放,

智能指针就是一种栈上创建的对象,函数退出时会调用其析构函数,这个析构函数里面往往就是一堆计数之类的条件判断,如果达到某个条件,就把真正指针指向的空间给释放了。

注意事项:

不能将指针直接赋值给一个智能指针,一个是类,一个是指针。

常用的智能指针

智能指针在C++11版本之后提供,包含在头文件中,shared_ptr、unique_ptr、weak_ptr

1)std::auto_ptr,有很多问题。 不支持复制(拷贝构造函数)和赋值(operator =),但复制或赋值的时候不会提示出错。所以可能会造成程序崩溃,比如

auto_ptr p1(new string (“auto”) ; //#1
auto_ptr p2; //#2
p2 = p1; //#3
在语句#3中,p2接管string对象的所有权后,p1的所有权将被剥夺。前面说过,这是好事,可防止p1和p2的析构函数试图刪同—个对象; 但如果程序随后试图使用p1,这将是件坏事,因为p1不再指向有效的数据。如果再访问p1指向的内容则会导致程序崩溃。

auto_ptr是C++98提供的解决方案,C+11已将将其摒弃,摒弃auto_ptr的原因,一句话总结就是:避免潜在的内存崩溃问题。

  1. C++11引入的unique_ptr, 也不支持复制和赋值,但比auto_ptr好,直接赋值会编译出错。实在想赋值的话,需要使用:std::move。例如:

std::unique_ptr p1(new int(5)) // #4
std::unique_ptr p2 = p1; // 编译会出错 //#5
std::unique_ptr p3 = std::move(p1); // 转移所有权, 现在那块内存归p3所有, p1成为无效的指针. //#6
编译器认为语句#5非法,因此,unique_ptr比auto_ptr更安全。

但unique_ptr还有更聪明的地方。 有时候,会将一个智能指针赋给另一个并不会留下危险的悬挂指针。当程序试图将一个 unique_ptr 赋值给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这么做;如果源 unique_ptr 将存在一段时间,编译器将禁止这么做

unique_ptr pu1(new string (“hello world”));
unique_ptr pu2;
pu2 = pu1; // #1 not allowed
unique_ptr pu3;
pu3 = unique_ptr(new string (“You”)); // #2 allowed
其中#1留下悬挂的unique_ptr(pu1),这可能导致危害。而#2不会留下悬挂的unique_ptr,因为它调用 unique_ptr 的构造函数,该构造函数创建的临时对象在其所有权让给 pu3 后就会被销毁。这种随情况而已的行为表明,unique_ptr 优于允许两种赋值的auto_ptr 。

  1. C++11或boost的shared_ptr,基于引用计数的智能指针。可随意赋值,直到内存的引用计数为0的时候这个内存会被释放。

4)C++11或boost的weak_ptr,弱引用。 引用计数有一个问题就是互相引用形成环,这样两个指针指向的内存都无法释放。需要手动打破循环引用或使用weak_ptr。顾名思义,weak_ptr是一个弱引用,只引用,不计数。如果一块内存被shared_ptr和weak_ptr同时引用,当所有shared_ptr析构了之后,不管还有没有weak_ptr引用该内存,内存也会被释放。所以weak_ptr不保证它指向的内存一定是有效的,在使用之前需要检查weak_ptr是否为空指针。

智能指针的作用

C++程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理。程序员自己管理堆内存可以提高了程序的效率,但是整体来说堆内存的管理是麻烦的,C++11中引入了智能指针的概念,方便管理堆内存。使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,野指针,程序发生异常时内存泄露等问题等,使用智能指针能更好的管理堆内存。

1、C和C++的区别

1)C是面向过程的语言,是一个结构化的语言,考虑如何通过一个过程对输入进行处理得到输出;C++是面向对象的语言,主要特征是“封装、继承和多态”。封装隐藏了实现细节,使得代码模块化;派生类可以继承父类的数据和方法,扩展了已经存在的模块,实现了代码重用;多态则是“一个接口,多种实现”,通过派生类重写父类的虚函数,实现了接口的重用。

2)C和C++动态管理内存的方法不一样,C是使用malloc/free,而C++除此之外还有new/delete关键字。

3)C++支持函数重载,C不支持函数重载

4)C++中有引用,C中不存在引用的概念

2、C++中指针和引用的区别

1)指针是一个新的变量,存储了另一个变量的地址,我们可以通过访问这个地址来修改另一个变量;

引用只是一个别名,还是变量本身,对引用的任何操作就是对变量本身进行操作,以达到修改变量的目的

2)引用只有一级,而指针可以有多级

3)指针传参的时候,还是值传递,指针本身的值不可以修改,需要通过解引用才能对指向的对象进行操作

引用传参的时候,传进来的就是变量本身,因此变量可以被修改

3、结构体struct和共同体union(联合)的区别

结构体:将不同类型的数据组合成一个整体,是自定义类型

共同体:不同类型的几个变量共同占用一段内存

1)结构体中的每个成员都有自己独立的地址,它们是同时存在的;

共同体中的所有成员占用同一段内存,它们不能同时存在;

2)sizeof(struct)是内存对齐后所有成员长度的总和,sizeof(union)是内存对齐后最长数据成员的长度、

结构体为什么要内存对齐呢?

1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常

2.硬件原因:经过内存对齐之后,CPU的内存访问速度大大提升。

4、#define和const的区别

1)#define定义的常量没有类型,所给出的是一个立即数;const定义的常量有类型名字,存放在静态区域

2)处理阶段不同,#define定义的宏变量在预处理时进行替换,可能有多个拷贝,const所定义的变量在编译时确定其值,只有一个拷贝。

3)#define定义的常量是不可以用指针去指向,const定义的常量可以用指针去指向该常量的地址

4)#define可以定义简单的函数,const不可以定义函数

5、重载overload,覆盖(重写)override,隐藏(重定义)overwrite,这三者之间的区别

1)overload,将语义相近的几个函数用同一个名字表示,但是参数列表(参数的类型,个数,顺序不同)不同,这就是函数重载,返回值类型可以不同

特征:相同范围(同一个类中)、函数名字相同、参数不同、virtual关键字可有可无

2)override,派生类覆盖基类的虚函数,实现接口的重用,返回值类型必须相同

特征:不同范围(基类和派生类)、函数名字相同、参数相同、基类中必须有virtual关键字(必须是虚函数)

3)overwrite,派生类屏蔽了其同名的基类函数,返回值类型可以不同

特征:不同范围(基类和派生类)、函数名字相同、参数不同或者参数相同且无virtual关键字

6、new、delete、malloc、free之间的关系

new/delete,malloc/free都是动态分配内存的方式

1)malloc对开辟的空间大小严格指定,而new只需要对象名

2)new为对象分配空间时,调用对象的构造函数,delete调用对象的析构函数

既然有了malloc/free,C++中为什么还需要new/delete呢?

运算符是语言自身的特性,有固定的语义,编译器知道意味着什么,由编译器解释语义,生成相应的代码。

库函数是依赖于库的,一定程度上独立于语言的。编译器不关心库函数的作用,只保证编译,调用函数参数和返回值符合语法,生成call函数的代码。

malloc/free是库函数,new/delete是C++运算符。对于非内部数据类型而言,光用malloc/free无法满足动态对象都要求。new/delete是运算符,编译器保证调用构造和析构函数对对象进行初始化/析构。但是库函数malloc/free是库函数,不会执行构造/析构。

7、delete和delete[]的区别

delete只会调用一次析构函数,而delete[]会调用每个成员的析构函数

用new分配的内存用delete释放,用new[]分配的内存用delete[]释放

多态, 虚函数, 纯虚函数

多态:不同对象接收相同的消息产生不同的动作。多态包括 编译时多态和 运行时多态

运行时多态是:通过继承和虚函数来体现的。 编译时多态:运算符重载上。 封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。多态也有代码重用的功能,还有解决项目中紧耦合的问题,提高程序的可扩展性。C++实现多态的机制很简单,在继承体系下,将父类的某个函数给成虚函数(即加上virtual关键字),在派生类中对这个虚函数进行重写,利用父类的指针或引用调用虚函数。通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。对于虚函数调用来说,每一个对象内部都有一个虚表指针,在构造子类对象时,执行构造函数中进行虚表的创建和虚表指针的初始化,该虚表指针被初始化为本类的虚表。所以在程序中,不管你的对象类型如何转换,但该对象内部的虚表指针是固定的,所以呢,才能实现动态的对象函数调用,这就是C++多态性实现的原理。 需要注意的几点总结(基类有虚函数): 1、每一个类都有虚表,单继承的子类拥有一张虚表,子类对象拥有一个虚表指针;若子类是多重继承(同时继承多个基类),则子类维护多张虚函数表(针对不同基类构建不同虚表),该子类的对象也将包含多个虚表指针。

2、虚表可以继承,如果子类没有重写虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的是基类的虚函数实现。如果基类3个虚函数,那么基类的虚表中就有三项(虚函数地址),派生类也会有虚表,至少有三项,如果重写了相应的虚函数,那么虚表中的地址就会改变,指向自身的虚函数实现。如果派生类有自己的虚函数,那么虚表中就会添加该项。 3、派生类的虚表中虚函数地址的排列顺序和基类的虚表中虚函数地址排列顺序相同。

第一:编译器在发现Father 类中有虚函数时,会自动为每个含有虚函数的类生成一份虚函数表,也叫做虚表,该表是一个一维数组,虚表里保存了虚函数的入口地址。

第二:编译器会在每个对象的前四个字节中保存一个虚表指针,即(vptr),指向对象所属类的虚表。在程序运行时的合适时机,根据对象的类型去初始化vptr,从而让vptr指向正确的虚表,从而在调用虚函数时,能找到正确的函数。

第三:所谓的合适时机,在派生类定义对象时,程序运行会自动调用构造函数,在构造函数中创建虚表并对虚表初始化。在构造子类对象时,会先调用父类的构造函数,此时,编译器只“看到了”父类,并为父类对象初始化虚表指针,令它指向父类的虚表;当调用子类的构造函数时,为子类对象初始化虚表指针,令它指向子类的虚表。

虚函数: 在基类中用virtual的成员函数。允许在派生类中对基类的虚函数重新定义。 基类的虚函数可以有函数体,基类也可以实例化。 虚函数要有函数体,否则编译过不去。 虚函数在子类中可以不覆盖。 构造函数不能是虚函数。

纯虚函数:基类中为其派生类保留一个名字,以便派生类根据需要进行定义。 包含一个纯虚函数的类是抽象类。 纯虚函数后面有 = 0; 抽象类不可以实例化。但可以定义指针。 如果派生类如果不是先基类的纯虚函数,则仍然是抽象类。 抽象类可以包含虚函数。

8、STL库用过吗?常见的STL容器有哪些?算法用过几个?

STL包括两部分内容:容器和算法

容器即存放数据的地方,比如array, vector,分为两类,序列式容器和关联式容器

序列式容器,其中的元素不一定有序,但是都可以被排序,比如vector,list,queue,stack,heap, priority-queue, slist

关联式容器,内部结构是一个平衡二叉树,每个元素都有一个键值和一个实值,比如map, set, hashtable, hash_set

算法有排序,复制等,以及各个容器特定的算法

迭代器是STL的精髓,迭代器提供了一种方法,使得它能够按照顺序访问某个容器所含的各个元素,但无需暴露该容器的内部结构,它将容器和算法分开,让二者独立设计。

9、const知道吗?解释一下其作用

const修饰类的成员变量,表示常量不可能被修改

const修饰类的成员函数,表示该函数不会修改类中的数据成员,不会调用其他非const的成员函数

const函数只能调用const函数,非const函数可以调用const函数

10、虚函数是怎么实现的

每一个含有虚函数的类都至少有有一个与之对应的虚函数表,其中存放着该类所有虚函数对应的函数指针(地址),

类的实例对象不包含虚函数表,只有虚指针;

派生类会生成一个兼容基类的虚函数表。

11、堆和栈的区别

1)栈 stack 存放函数的参数值、局部变量,由编译器自动分配释放

堆heap,是由new分配的内存块,由应用程序控制,需要程序员手动利用delete释放,如果没有,程序结束后,操作系统自动回收

2)因为堆的分配需要使用频繁的new/delete,造成内存空间的不连续,会有大量的碎片

3)堆的生长空间向上,地址越大,栈的生长空间向下,地址越小

12、关键字static的作用

1)函数体内: static 修饰的局部变量作用范围为该函数体,不同于auto变量,其内存只被分配一次,因此其值在下次调用的时候维持了上次的值

2)模块内:static修饰全局变量或全局函数,可以被模块内的所有函数访问,但是不能被模块外的其他函数访问,使用范围限制在声明它的模块内

3)类中:修饰成员变量,表示该变量属于整个类所有,对类的所有对象只有一份拷贝

4)类中:修饰成员函数,表示该函数属于整个类所有,不接受this指针,只能访问类中的static成员变量

注意和const的区别!!!const强调值不能被修改,而static强调唯一的拷贝,对所有类的对象

13、STL中map和set的原理(关联式容器)

map和set的底层实现主要通过红黑树来实现

红黑树是一种特殊的二叉查找树

1)每个节点或者是黑色,或者是红色

2)根节点是黑色

3) 每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]

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

5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

特性4)5)决定了没有一条路径会比其他路径长出2倍,因此红黑树是接近平衡的二叉树。

14、#include #include “file.h” 的区别

前者是从标准库路径寻找

后者是从当前工作路径

15、什么是内存泄漏?面对内存泄漏和指针越界,你有哪些方法?

动态分配内存所开辟的空间,在使用完毕后未手动释放,导致一直占据该内存,即为内存泄漏。

方法:malloc/free要配套,对指针赋值的时候应该注意被赋值的指针是否需要释放;使用的时候记得指针的长度,防止越界

16、定义和声明的区别

声明是告诉编译器变量的类型和名字,不会为变量分配空间

定义需要分配空间,同一个变量可以被声明多次,但是只能被定义一次

17、C++文件编译与执行的四个阶段

1)预处理:根据文件中的预处理指令来修改源文件的内容

2)编译:编译成汇编代码

3)汇编:把汇编代码翻译成目标机器指令

4)链接:链接目标代码生成可执行程序

18、STL中的vector的实现,是怎么扩容的?

vector使用的注意点及其原因,频繁对vector调用push_back()对性能的影响和原因。

vector就是一个动态增长的数组,里面有一个指针指向一片连续的空间,当空间装不下的时候,会申请一片更大的空间,将原来的数据拷贝过去,并释放原来的旧空间。当删除的时候空间并不会被释放,只是清空了里面的数据。对比array是静态空间一旦配置了就不能改变大小。

vector的动态增加大小的时候,并不是在原有的空间上持续新的空间(无法保证原空间的后面还有可供配置的空间),而是以原大小的两倍另外配置一块较大的空间,然后将原内容拷贝过来,并释放原空间。在VS下是1.5倍扩容,在GCC下是2倍扩容。

在原来空间不够存储新值时,每次调用push_back方法都会重新分配新的空间以满足新数据的添加操作。如果在程序中频繁进行这种操作,还是比较消耗性能的。

19、STL中unordered_map和map的区别

map是STL中的一个关联容器,提供键值对的数据管理。底层通过红黑树来实现,实际上是二叉排序树和非严格意义上的二叉平衡树。所以在map内部所有的数据都是有序的,且map的查询、插入、删除操作的时间复杂度都是O(logN)。

unordered_map和map类似,都是存储key-value对,可以通过key快速索引到value,不同的是unordered_map不会根据key进行排序。unordered_map底层是一个防冗余的哈希表,存储时根据key的hash值判断元素是否相同,即unoredered_map内部是无序的。

20、C++的内存管理

在C++中,内存被分成五个区:栈、堆、自由存储区、静态存储区、常量区

栈:存放函数的参数和局部变量,编译器自动分配和释放

堆:new关键字动态分配的内存,由程序员手动进行释放,否则程序结束后,由操作系统自动进行回收

自由存储区:由malloc分配的内存,和堆十分相似,由对应的free进行释放

全局/静态存储区:存放全局变量和静态变量

常量区:存放常量,不允许被修改

21、 构造函数为什么一般不定义为虚函数?而析构函数一般写成虚函数的原因 ?

1、构造函数不能声明为虚函数

1)因为创建一个对象时需要确定对象的类型,而虚函数是在运行时确定其类型的。而在构造一个对象时,由于对象还未创建成功,编译器无法知道对象的实际类型,是类本身还是类的派生类等等

2)虚函数的调用需要虚函数表指针,而该指针存放在对象的内存空间中;若构造函数声明为虚函数,那么由于对象还未创建,还没有内存空间,更没有虚函数表地址用来调用虚函数即构造函数了

2、析构函数最好声明为虚函数

首先析构函数可以为虚函数,当析构一个指向派生类的基类指针时,最好将基类的析构函数声明为虚函数,否则可以存在内存泄露的问题。

如果析构函数不被声明成虚函数,则编译器实施静态绑定,在删除指向派生类的基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会造成派生类对象析构不完全。

子类析构时,要调用父类的析构函数吗?

析构函数调用的次序时先派生类后基类的。和构造函数的执行顺序相反。并且析构函数要是virtual的,否则如果用父类的指针指向子类对象的时候,析构函数静态绑定,不会调用子类的析构。

不用显式调用,会自动调用

22、静态绑定和动态绑定的介绍

静态绑定和动态绑定是C++多态性的一种特性

1)对象的静态类型和动态类型

静态类型:对象在声明时采用的类型,在编译时确定

动态类型:当前对象所指的类型,在运行期决定,对象的动态类型可变,静态类型无法更改

2)静态绑定和动态绑定

静态绑定:绑定的是对象的静态类型,函数依赖于对象的静态类型,在编译期确定

动态绑定:绑定的是对象的动态类型,函数依赖于对象的动态类型,在运行期确定

只有虚函数才使用的是动态绑定,其他的全部是静态绑定

23、 引用是否能实现动态绑定,为什么引用可以实现

可以。因为引用(或指针)既可以指向基类对象也可以指向派生类对象,这一事实是动态绑定的关键。用引用(或指针)调用的虚函数在运行时确定,被调用的函数是引用(或指针)所指的对象的实际类型所定义的。

24、深拷贝和浅拷贝的区别

深拷贝和浅拷贝可以简单的理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,如果资源重新分配了就是深拷贝;反之没有重新分配资源,就是浅拷贝。

25、 什么情况下会调用拷贝构造函数(三种情况)

系统自动生成的构造函数:普通构造函数和拷贝构造函数 (在没有定义对应的构造函数的时候)

生成一个实例化的对象会调用一次普通构造函数,而用一个对象去实例化一个新的对象所调用的就是拷贝构造函数

调用拷贝构造函数的情形:

1)用类的一个对象去初始化另一个对象的时候

2)当函数的参数是类的对象时,就是值传递的时候,如果是引用传递则不会调用

3)当函数的返回值是类的对象或者引用的时候

举例:

#include
#include
using namespace std;
class A{
private:
int data;
public:
A(int i){ data = i;} //自定义的构造函数
A(A && a); //拷贝构造函数
int getdata(){return data;}
};
//拷贝构造函数
A::A(A && a){
data = a.data;
cout <<“拷贝构造函数执行完毕”< }
//参数是对象,值传递,调用拷贝构造函数
int getdata1(A a){
return a.getdata();
}
//参数是引用,引用传递,不调用拷贝构造函数
int getdata2(A &a){
return a.getdata();
}
//返回值是对象类型,会调用拷贝构造函数
A getA1(){
A a(0);
return a;
}
//返回值是引用类型,会调用拷贝构造函数,因为函数体内生成的对象是临时的,离开函数就消失
A& getA2(){
A a(0);
return a;
}
int main(){
A a1(1);
A b1(a1); //用a1初始化b1,调用拷贝构造函数
A c1=a1; //用a1初始化c1,调用拷贝构造函数
int i=getdata1(a1); //函数形参是类的对象,调用拷贝构造函数
int j=getdata2(a1); //函数形参类型是引用,不调用拷贝构造函数
A d1=getA1(); //调用拷贝构造函数
A e1=getA2(); //调用拷贝构造函数
return 0;
}
26、 C++的四种强制转换

类型转化机制可以分为隐式类型转换和显示类型转化(强制类型转换)

(new-type) expression

new-type (expression)

隐式类型转换比较常见,在混合类型表达式中经常发生;四种强制类型转换操作符:

static_cast、dynamic_cast、const_cast、reinterpret_cast

1)static_cast :编译时期的静态类型检查

static_cast < type-id > ( expression )

该运算符把expression转换成type-id类型,在编译时使用类型信息执行转换,在转换时执行必要的检测(指针越界、类型检查),其操作数相对是安全的

2)dynamic_cast:运行时的检查

用于在集成体系中进行安全的向下转换downcast,即基类指针/引用->派生类指针/引用

dynamic_cast是4个转换中唯一的RTTI操作符,提供运行时类型检查。

dynamic_cast如果不能转换返回NULL

dynamic_cast转为引用类型的时候转型失败会抛bad_cast

源类中必须要有虚函数,保证多态,才能使用dynamic_cast(expression)

3)const_cast

去除const常量属性,使其可以修改 ; volatile属性的转换

4)reinterpret_cast

通常为了将一种数据类型转换成另一种数据类型

27、调试程序的方法

windows下直接使用vs的debug功能

linux下直接使用gdb,我们可以在其过程中给程序添加断点,监视等辅助手段,监控其行为是否与我们设计相符

28、extern“C”作用

extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。

29、typdef和define区别

#define是预处理命令,在预处理是执行简单的替换,不做正确性的检查

typedef是在编译时处理的,它是在自己的作用域内给已经存在的类型一个别名

typedef (int*) pINT;

#define pINT2 int*

效果相同?实则不同!实践中见差别:pINT a,b;的效果同int *a; int *b;表示定义了两个整型指针变量。而pINT2 a,b;的效果同int *a, b;表示定义了一个整型指针变量a和整型变量b。

30、volatile关键字在程序设计中有什么作用

volatile是“易变的”、“不稳定”的意思。volatile是C的一个较为少用的关键字,它用来解决变量在“共享”环境下容易出现读取错误的问题。

变量如果加了voletile修饰,则会从内存中重新装载内容,而不是直接从寄存器中拷贝内容。

在本次线程内,当读取一个变量时,为了提高读取速度,编译器进行优化时有时会先把变量读取到一个寄存器中;以后,再读取变量值时,就直接从寄存器中读取;当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以保持一致。当变量因别的线程值发生改变,上面寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致。

volatile可以避免优化、强制内存读取的顺序,但是volatile并没有线程同步的语义,C++标准并不能保证它在多线程情况的正确性。C++11开始有一个很好用的库,那就是atomic类模板,在头文件中,多个线程对atomic对象进行访问是安全的,并且提供不同种类的线程同步。它默认使用的是最强的同步,所以我们就使用默认的就好。

31、引用作为函数参数以及返回值的好处

对比值传递,引用传参的好处:

1)在函数内部可以对此参数进行修改

2)提高函数调用和运行的效率(所以没有了传值和生成副本的时间和空间消耗)

如果函数的参数实质就是形参,不过这个形参的作用域只是在函数体内部,也就是说实参和形参是两个不同的东西,要想形参代替实参,肯定有一个值的传递。函数调用时,值的传递机制是通过“形参=实参”来对形参赋值达到传值目的,产生了一个实参的副本。即使函数内部有对参数的修改,也只是针对形参,也就是那个副本,实参不会有任何更改。函数一旦结束,形参生命也宣告终结,做出的修改一样没对任何变量产生影响。

用引用作为返回值最大的好处就是在内存中不产生被返回值的副本。

但是有以下的限制:

1)不能返回局部变量的引用。因为函数返回以后局部变量就会被销毁

2)不能返回函数内部new分配的内存的引用。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一 个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak

3)可以返回类成员的引用,但是最好是const。因为如果其他对象可以获得该属性的非常量的引用,那么对该属性的单纯赋值就会破坏业务规则的完整性。

32、纯虚函数

纯虚函数是只有声明没有实现的虚函数,是对子类的约束,是接口继承

包含纯虚函数的类是抽象类,它不能被实例化,只有实现了这个纯虚函数的子类才能生成对象

普通函数是静态编译的,没有运行时多态

33、什么是野指针

野指针不是NULL指针,是未初始化或者未清零的指针,它指向的内存地址不是程序员所期望的,可能指向了受限的内存

成因:

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

2)指针指向的内存被释放了,但是指针没有置NULL

3)指针超过了变量了的作用范围,比如b[10],指针b+11

33、线程安全和线程不安全

线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可以使用,不会出现数据不一致或者数据污染。

线程不安全就是不提供数据访问保护,有可能多个线程先后更改数据所得到的数据就是脏数据。

34、C++中内存泄漏的几种情况

内存泄漏是指己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

1)类的构造函数和析构函数中new和delete没有配套

2)在释放对象数组时没有使用delete[],使用了delete

3)没有将基类的析构函数定义为虚函数,当基类指针指向子类对象时,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确释放,因此造成内存泄露

4)没有正确的清楚嵌套的对象指针

35、栈溢出的原因以及解决方法

栈溢出是指函数中的局部变量造成的溢出(注:函数中形参和函数中的局部变量存放在栈上)

栈的大小通常是1M-2M,所以栈溢出包含两种情况,一是分配的的大小超过栈的最大值,二是分配的大小没有超过最大值,但是接收的buf比原buf小。

1)函数调用层次过深,每调用一次,函数的参数、局部变量等信息就压一次栈

2)局部变量体积太大。

解决办法大致说来也有两种:

1> 增加栈内存的数目;如果是不超过栈大小但是分配值小的,就增大分配的大小

2> 使用堆内存;具体实现由很多种方法可以直接把数组定义改成指针,然后动态申请内存;也可以把局部变量变成全局变量,一个偷懒的办法是直接在定义前边加个static,呵呵,直接变成静态变量(实质就是全局变量)

36、C++标准库vector以及迭代器

每种容器类型都定义了自己的迭代器类型,每种容器都定义了一队命名为begin和end的函数,用于返回迭代器。

迭代器是容器的精髓,它提供了一种方法使得它能够按照顺序访问某个容器所含的各个元素,但无需暴露该容器的内部结构,它将容器和算法分开,让二者独立设计。

38、C++中vector和list的区别

vector和数组类似,拥有一段连续的内存空间。vector申请的是一段连续的内存,当插入新的元素内存不够时,通常以2倍重新申请更大的一块内存,将原来的元素拷贝过去,释放旧空间。因为内存空间是连续的,所以在进行插入和删除操作时,会造成内存块的拷贝,时间复杂度为o(n)。

list是由双向链表实现的,因此内存空间是不连续的。只能通过指针访问数据,所以list的随机存取非常没有效率,时间复杂度为o(n); 但由于链表的特点,能高效地进行插入和删除。

vector拥有一段连续的内存空间,能很好的支持随机存取,因此vector::iterator支持“+”,“+=”,“<”等操作符。

list的内存空间可以是不连续,它不支持随机访问,因此list::iterator则不支持“+”、“+=”、“<”等

vector::iterator和list::iterator都重载了“++”运算符。

总之,如果需要高效的随机存取,而不在乎插入和删除的效率,使用vector;

如果需要大量的插入和删除,而不关心随机存取,则应使用list。

39、C语言的函数调用过程

函数的调用过程:

1)从栈空间分配存储空间

2)从实参的存储空间复制值到形参栈空间

3)进行运算

形参在函数未调用之前都是没有分配存储空间的,在函数调用结束之后,形参弹出栈空间,清除形参空间。

数组作为参数的函数调用方式是地址传递,形参和实参都指向相同的内存空间,调用完成后,形参指针被销毁,但是所指向的内存空间依然存在,不能也不会被销毁。

当函数有多个返回值的时候,不能用普通的 return 的方式实现,需要通过传回地址的形式进行,即地址/指针传递。

传值:传值,实际是把实参的值赋值给行参,相当于copy。那么对行参的修改,不会影响实参的值 。

传址: 实际是传值的一种特殊方式,只是他传递的是地址,不是普通的赋值,那么传地址以后,实参和行参都指向同一个对象,因此对形参的修改会影响到实参。

40、C++中的基本数据类型及派生类型

1)整型 int

2)浮点型 单精度float,双精度double

3)字符型 char

4)逻辑型 bool

5)控制型 void

基本类型的字长及其取值范围可以放大和缩小,改变后的类型就叫做基本类型的派生类型。派生类型声明符由基本类型关键字char、int、float、double前面加上类型修饰符组成。

类型修饰符包括:

short 短类型,缩短字长

long 长类型,加长字长

signed 有符号类型,取值范围包括正负值

unsigned 无符号类型,取值范围只包括正值

41、友元函数和友元类

友元提供了不同类的成员函数之间、类的成员函数和一般函数之间进行数据共享的机制。

通过友元,一个不同函数或者另一个类中的成员函数可以访问类中的私有成员和保护成员。

友元的正确使用能提高程序的运行效率,但同时也破坏了类的封装性和数据的隐藏性,导致程序可维护性变差。

1)友元函数

有元函数是可以访问类的私有成员的非成员函数。它是定义在类外的普通函数,不属于任何类,但是需要在类的定义中加以声明。

friend 类型 函数名(形式参数);

一个函数可以是多个类的友元函数,只需要在各个类中分别声明。

2)友元类

友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。

friend class 类名;

使用友元类时注意:

(1) 友元关系不能被继承。

(2) 友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。

(3) 友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的申明

c++函数库中一些实用的函数

  1. __gcd(x, y)

求两个数的最大公约数,如__gcd(6, 8) 就返回2。

  1. reverse(a + 1, a + n + 1)

将数组中的元素反转。a 是数组名,n是长度,跟 sort 的用法一样。值得一提的是,对于字符型数组也同样适用。

  1. unique(a + 1, a + n + 1)

去重函数。跟sort的用法一样。不过他返回的值是最后一个数的地址,所以要得到新的数组长度应该这么写: _n = unique(a + 1, a + n + 1) - a - 1.

4.lower_bound(a + 1, a + n + 1, x); upper_bound(a + 1, a + n + 1, x)

lower_bound是查找数组中第一个大于等于x的数,返回该地址,同理也是 pos = lower_bound(a + 1, a + n + 1, x) - a

upper_bound是查找第一个大于x的数,用法和lower_bound一样

复杂度是二分的复杂度,O(logn)。(其实就是代替了手写二分)

5.fill(a + 1, a + n + 1, x)

例如

int数组:fill(arr, arr + n, 要填入的内容);

vector也可以:fill(v.begin(), v.end(), 要填入的内容);

fill(vector.begin(), cnt, val); // 从当前起始点开始,将之后的cnt个元素赋值为val。

memset(arr, val, cnt); // 在头文件里。

将数组a中的每一个元素都赋成x,跟memset的区别是,memset函数按照字节填充,所以一般memset只能用来填充char型数组,(因为只有char型占一个字节)如果填充int型数组,除了0和-1,其他的不能。

●多进程和多线程的区别

进程它是具有独立地址空间的,优点就是隔离度好,稳定,因为它是操作系统管理的,进程和进程之间是逻辑隔离的,只要操作系统不出问题的话,一个进程的错误一般不会影响到其它进程,缺点就是信息资源共享麻烦。而线程只是进程启动的执行单元,它是共享进程资源的,创建销毁、切换简单,速度很快,占用内存少,CPU利用率高。但是需要程序员管控的东西也比较多,相互影响出问题的机率较大,一个线程挂掉将导致整个进程挂掉,所以从程序员的角度来讲,我们只能看到某种代码是线程安全的,而没有说进程安全的。

●在进程和线程上,应该怎么选择

我们平时在写代码的时候一般使用线程会比较多,像需要频繁创建销毁的,要处理大量运算、数据,又要能很好的显示界面和及时响应消息的优先选择多线程,因为像这些运算会消耗大量的CPU,常见的有算法处理和图像处理。还有一些操作允许并发而且有可能阻塞的, 也推荐使用多线程. 例如SOCKET, 磁盘操作等等。进程一般来说更稳定,而且它是内存隔离的,单个进程的异常不会导致整个应用的崩溃,方便调试,像很多服务器默认是使用进程模式的。

●线程之间是如何通信的

一个是使用全局变量进行通信,还有就是可以使用自定义的消息机制传递信息。其实因为各个线程之间它是共享进程的资源的,所以它没有像进程通信中的用于数据交换的通信方式,它通信的主要目的是用于线程同步,所以像一些互斥锁啊临界区啊CEvent事件对象和信号量对象都可以实现线程的通信和同步。

●进程之间是如何通信的

进程间的通信方式有PIPE管道,信号量,消息队列,共享内存,还可以通过 socket套接字进行通信。根据信息量大小的不同可以分为低级通信和高级通信,在选择上,如果用户传递的信息较少.或是需要通过信号来触发某些行为的,一般用信号机制就能解决,如果进程间要求传递的信息量比较大或者有交换数据的要求,那么就要使用共享内存和套接字这些通信方式。

名词解释:

管道其实是存在于内存中的一种特殊文件,它不属于文件系统,有自己的数据结构,根据使用范围还可分为无名管道和命名管道。

共享内存是通过将共享的内存缓冲区直接附加到进程的虚拟地址空间中来实现的,它是利用内存缓冲区直接交换信息,不需要复制,很快捷、信息量大。

消息队列缓冲是由系统调用函数来实现消息发送和接收之间的同步,它允许任意进程通过共享消息队列来实现进程间通信.但是信息的复制需要耗费大量CPU,所以不适用于信息量大或操作频繁的场合。

●线程同步和线程异步

同步是指一个线程要等待另一个线程执行完之后才开始执行当前的线程。

异步是指一个线程去执行,它的下一个线程不必等待它执行完就开始执行。

一般一个进程启动的多个不相干线程,它们之间的相互关系就为异步,比如游戏有图像和背景音乐,图像是由玩家操作的 而背景音乐是系统循环播放,它们两个线程之间没什么关系各干各的,这就是线程异步。至于同步的话指的是多线程同时操作一个数据,这个时候需要对数据添加保护,这个保护就是线程的同步

同步使用场景:对于多个线程同时访问一块数据的时候,必须使用同步,否则可能会出现不安全的情况,有一种情况不需要同步技术,那就是原子操作,也就是说操作系统在底层保证了操作要么全部做完,要么不做。

异步的使用场景:当只有一个线程访问当前数据的时候。比如观察者模式,它没有共享区,主题发生变化后通知观察者更新,主题继续做自己的事情,不需要等待观察者更新完成后再工作。

●多线程同步和互斥有几种实现方法,分别适用什么情况

线程同步的话有临界区,互斥量,信号量,事件。

临界区适合一个进程内的多线程访问公共区域或代码段时使用。

互斥量是可以命名的,也就是说它可以适用不同进程内多线程访问公共资源时使用。所以在选择上如果是在进程内部使用的话,用临界区会带来速度上的优势并且能够减少资源占用量。

信号量与临界区和互斥量不同,它是允许多个线程同时访问公共资源的,它相当于操作系统的PV操作,它会事先设定一个最大线程数,如果线程占用数达到最大,那么其它线程就不能再进来,如果有部分线程释放资源了,那么其它线程才能进来访问资源。

事件是通过通知操作的方式来保持线程同步。

注意:互斥量,事件,信号量都是内核对象,可以跨进程使用。

●C++多线程有几种实现方法

直接使用WIN32 API CreateThread,或者用C运行库_beginthread创建线程,MFC的话用AfxBeginThread. 还有就是运用第三方线程库,比如boost的thread等等。

_beginthread和CreateThread的区别:_beginthread内部调用了CreateThread.

如果你的程序只调用 Win32 API/SDK ,就放心用 CreateThread,如果要用到C++运行时库,那么就要使用_beginthreadex,因为C++运行库有一些函数里面使用了全局变量,beginthreadex 为这些全局变量做了处理,使得每个线程都有一份独立的“全局”量,在这种情况下使用CreateThread的话就会出现不安全的问题

●进程有哪几种状态,状态转换图,及导致转换的事件

●死锁

概念:进程间进行通信或相互竞争系统资源而产生的永久阻塞,若无外力作用将永远处在死锁状态。

产生原因:(1)系统资源不足;(2)进程运行推进顺序与速度不同也可能导致死锁;(3)资源分配不当;

产生死锁四个必要条件:

(1) 互斥条件:就是一个资源每次只能被一个进程使用。

(2) 请求与保持条件:一个进程在请求其它资源而阻塞时,但是它对自己已获得的资源又保持不放。

(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

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

预防死锁和避免死锁的方法:在系统设计、进程调度方面注意不让产生死锁的四个必要条件成立,确定资源的合理分配算法,避免进程永远占用系统资源,对资源分配要进行合理的规划。

●多线程中栈与堆是公有的还是私有的

因为线程是共享进程的资源的,所以栈是私有的,堆是公有的。

●线程池的概念

线程池就是一堆已经创建好的线程,最大数目一定,然后初始后都处于空闲状态,当有新任务进来时就从线程池中取出空闲线程处理任务,任务完成之后又重新放回去,当线程池中的所有线程都在任务时,只能等待有线程结束任务才能继续执行。

1、Socket通信流程

Socket通信流程

服务端:创建socket()、绑定socket和端口号bind(),监听端口llisten(),接收来自客户端的请求accpet(),从socket中读取字符recv(),关闭close()

客户端: 创建socket()、 连接指定端口connect(), 发送数据send() 关闭close()

C/S通信方式

2、字节序分为大端字节序和小端字节序。
大端字节序:最高有效位存储于最低内存地址处,最低有效位存储于最高内存地址。
小端字节序:最高有效位存储于最高内存地址处,最低有效位存储于最低内存地址。

主机字节序:不同的主机有不同的字节序。
(1)x86硬件架构下的主机为小端字节序
(2)Motorola 68000为大端字节序
(3)ARM架构的主机字节序是可配置的

网络字节序:网络字节序规定为大端字节序。

3、进程间通讯的方式:剪切板、命名管道、匿名通道、油槽。

4、线程间通讯的方式:全局变量、自定义消息、事件。

5、线程间同步的方式:临界区、互斥量、信号量、事件。

C++ REST SDK

C++ REST SDK是微软开源的一套客户端-服务器通信库,提供了URI构造/解析,JSON编解码,HTTP客户端、HTTP服务端,WebSocket客户端,流式传输,oAuth验证等C++类,方便C++语言编写的客户端程序访问互联网服务。其中HTTP服务端相关的类是最近新增的(尚处于beta测试阶段),这些类拓展了C++ REST SDK的功能,现在不仅能开发客户端程序,也能做服务端开发了。
访问barcode服务器就是使用c++ rest sdk

URI
通一资源标志符(Uniform Resource Identifier, URI),表示的是web上每一种可用的资源,如 HTML文档、图像、视频片段、程序等都由一个URI进行定位的。

URL
是URI的一个子集。它是Uniform Resource Locator的缩写,译为“统一资源定位 符”。
通俗地说,URL是Internet上描述信息资源的字符串,主要用在各种WWW客户程序和服务器程序上。
采用URL可以用一种统一的格式来描述各种信息资源,包括文件、服务器的地址和目录等。URL是URI概念的一种实现方式。

ICE

ICE的产生就是源于.NET、CORBA及WEB SERVICE这些中间件的不足,它可以支持不同的系统,如WINDOWS、LINUX等,也可以支持在多种开发语言上使用,

如C++、C、JAVA、RUBY、PYTHON、VB等,服务端可以是上面提到的任何一种语言实现的,客户端也可以根据自己的实际情况选择不同的语言实现,如服务端采用C语言实现,

而客户端采用JAVA语言实现,底层的通讯逻辑通过ICE的封装实现,我们只需要关注业务逻辑。

3、ICE是如何工作的?

Ice 是一种面向对象的中间件平台,这意味着 Ice为构建面向对象的客户-服务器应用提供了工具、API 和库支持。要与Ice持有的对象进行通信,

客户端必须持有这个对象的代理(与CORBA的引用是相同的意思),这里的代理指的是这个对象的实例,ICE在运行时会定位到这个对象,然后寻找或激活它,再把In参数传给远程对象,再通过Out参数获取返回结果。

1 安装ICE。svn:http://terminal-svn4.hytera.com/Terminal/PC_Application/02_Sourcecode/Tools/commom/OpenSource/ice

可以安装在加密盘,重启后也能用

2.将工程中创建的slice(.ice)文件拷贝到ICE的目录下(E:\Tools_Release\ICE_3.6.1.2\Ice-3.6.1)

3.使用dos在ICE的安装目录下启动slice2cpp (E:\Tools_Release\ICE_3.6.1.2\Ice-3.6.1)

4.对照编译说明敲指令,例如:

D:\ZeroC\Ice-3.6.1>bin\slice2cpp.exe -Islice .\g2pc_Licence.ice

5.在ICE的安装目录下获取.cpp、.h文件

11、TCP/IP、OSI协议

TCP/IP:数据链路层、 网络层、传输层、 应用层

OSI:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层

6、TCP/IP三次握手:

syn (Synchronize) 标记的包,SYN=1则告诉B请求建立连接.

seq是序列号,这是为了连接以后传送数据用的,

ack是对收到的数据包的确认,值是等待接收的数据包的序列号=seq+1。

过程分析

第一次握手:主机A发送位码为syn=1,随机产生seq number=1234567的数据包到服务器,主机B由SYN=1知道,A要求建立联机;

第二次握手:主机B收到请求后要确认联机信息,向A发送ack=Seq+1,ack number=(主机A的seq+1),syn=1,随机产生seq=7654321的包

第三次握手:主机A收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1,若正确,主机A会再发送ack number=(主机B的seq+1),,主机B收到后确认seq值与ack=1则连接建立成功。

过程状态

第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。 完成三次握手,客户端与服务器开始传送数据.

四、HTTP通信传输

客户端输入URL回车,DNS解析域名得到服务器的IP地址,服务器在80端口监听客户端请求,端口通过TCP/IP协议(可以通过Socket实现)建立连接。HTTP属于TCP/IP模型中的运用层协议,所以通信的过程其实是对应数据的入栈和出栈。

报文从运用层传送到运输层,运输层通过TCP三次握手和服务器建立连接,四次挥手释放连接。

为什么需要三次握手呢?为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。

比如:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段,但是server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求,于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了,由于client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据,但server却以为新的运输连接已经建立,并一直等待client发来数据。所以没有采用“三次握手”,这种情况下server的很多资源就白白浪费掉了。

为什么需要四次挥手呢?TCP是全双工模式,当client发出FIN报文段时,只是表示client已经没有数据要发送了,client告诉server,它的数据已经全部发送完毕了;但是,这个时候client还是可以接受来server的数据;当server返回ACK报文段时,表示它已经知道client没有数据发送了,但是server还是可以发送数据到client的;当server也发送了FIN报文段时,这个时候就表示server也没有数据要发送了,就会告诉client,我也没有数据要发送了,如果收到client确认报文段,之后彼此就会愉快的中断这次TCP连接。

五、HTTPS实现原理

https://blog.csdn.net/xiaoming100001/article/details/81109617

SSL建立连接过程

client向server发送请求https://baidu.com,然后连接到server的443端口,发送的信息主要是随机值1和客户端支持的加密算法。

server接收到信息之后给予client响应握手信息,包括随机值2和匹配好的协商加密算法,这个加密算法一定是client发送给server加密算法的子集。

随即server给client发送第二个响应报文是数字证书。服务端必须要有一套数字证书,可以自己制作,也可以向组织申请。区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用受信任的公司申请的证书则不会弹出提示页面,这套证书其实就是一对公钥和私钥。传送证书,这个证书其实就是公钥,只是包含了很多信息,如证书的颁发机构,过期时间、服务端的公钥,第三方证书认证机构(CA)的签名,服务端的域名信息等内容。

客户端解析证书,这部分工作是由客户端的TLS来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等等,如果发现异常,则会弹出一个警告框,提示证书存在问题。如果证书没有问题,那么就生成一个随即值(预主秘钥)。

客户端认证证书通过之后,接下来是通过随机值1、随机值2和预主秘钥组装会话秘钥。然后通过证书的公钥加密会话秘钥。

传送加密信息,这部分传送的是用证书加密后的会话秘钥,目的就是让服务端使用秘钥解密得到随机值1、随机值2和预主秘钥。

服务端解密得到随机值1、随机值2和预主秘钥,然后组装会话秘钥,跟客户端会话秘钥相同。

客户端通过会话秘钥加密一条消息发送给服务端,主要验证服务端是否正常接受客户端加密的消息。

同样服务端也会通过会话秘钥加密一条消息回传给客户端,如果客户端能够正常接受的话表明SSL层连接建立完成了。

问题:

1.怎么保证保证服务器给客户端下发的公钥是真正的公钥,而不是中间人伪造的公钥呢?

2.证书如何安全传输,被掉包了怎么办?

数字证书内容

包括了加密后服务器的公钥、权威机构的信息、服务器域名,还有经过CA私钥签名之后的证书内容(经过先通过Hash函数计算得到证书数字摘要,然后用权威机构私钥加密数字摘要得到数字签名),签名计算方法以及证书对应的域名。

验证证书安全性过程

当客户端收到这个证书之后,使用本地配置的权威机构的公钥对证书进行解密得到服务端的公钥和证书的数字签名,数字签名经过CA公钥解密得到证书信息摘要。

然后证书签名的方法计算一下当前证书的信息摘要,与收到的信息摘要作对比,如果一样,表示证书一定是服务器下发的,没有被中间人篡改过。因为中间人虽然有权威机构的公钥,能够解析证书内容并篡改,但是篡改完成之后中间人需要将证书重新加密,但是中间人没有权威机构的私钥,无法加密,强行加密只会导致客户端无法解密,如果中间人强行乱修改证书,就会导致证书内容和证书签名不匹配。

那第三方攻击者能否让自己的证书显示出来的信息也是服务端呢?(伪装服务端一样的配置)显然这个是不行的,因为当第三方攻击者去CA那边寻求认证的时候CA会要求其提供例如域名的whois信息、域名管理邮箱等证明你是服务端域名的拥有者,而第三方攻击者是无法提供这些信息所以他就是无法骗CA他拥有属于服务端的域名。

六、运用与总结

安全性考虑:

HTTPS协议的加密范围也比较有限,在黑客攻击、拒绝服务攻击、服务器劫持等方面几乎起不到什么作用

SSL证书的信用链体系并不安全,特别是在某些国家可以控制CA根证书的情况下,中间人攻击一样可行

中间人攻击(MITM攻击)是指,黑客拦截并篡改网络中的通信数据。又分为被动MITM和主动MITM,被动MITM只窃取通信数据而不修改,而主动MITM不但能窃取数据,还会篡改通信数据。最常见的中间人攻击常常发生在公共wifi或者公共路由上。

成本考虑:

SSL证书需要购买申请,功能越强大的证书费用越高

SSL证书通常需要绑定IP,不能在同一IP上绑定多个域名,IPv4资源不可能支撑这个消耗(SSL有扩展可以部分解决这个问题,但是比较麻烦,而且要求浏览器、操作系统支持,Windows XP就不支持这个扩展,考虑到XP的装机量,这个特性几乎没用)。

根据ACM CoNEXT数据显示,使用HTTPS协议会使页面的加载时间延长近50%,增加10%到20%的耗电。

HTTPS连接缓存不如HTTP高效,流量成本高。

HTTPS连接服务器端资源占用高很多,支持访客多的网站需要投入更大的成本。

HTTPS协议握手阶段比较费时,对网站的响应速度有影响,影响用户体验。比较好的方式是采用分而治之,类似12306网站的主页使用HTTP协议,有关于用户信息等方面使用HTTPS。

7、指针和引用的区别:

a、引用被创建时必须进行初始化,指针可以在任何时候初始化。

b、不能有空引用,但可以有空指针。

c、一旦引用被初始化就不能改变引用的关系,指针可以改变指向的对象。

8、程序和进程的区别:

a、程序是指令的有序集合,其本身没有任何运行的含义,是一个静态的概念。可以作为一种软件资料长期存在。

b、进程是程序在处理机上的一次执行过程,是动态的概念。是有一定生命期的。进程是程序的应用实例。

9、MFC CStirng相关函数源码

10、Boost中的智能指针
智能指针有两种分类:

1.不带引用计数的智能指针

auto_ptr、unique_ptr、scoped_ptr

2.带引用计数的智能指针

shared_ptr(强智能指针)、weak_ptr(弱智能指针)

其中我们最主要用的就是带引用计数的智能指针,因为它的引用计数可以方便我们对函数进行适当时间的析构,避免发生没存泄露问题(说起这个内存泄漏问题,如何在windows上进行内存泄露的快速定位。几种方法:1.可以在VS调到DEBUG版本,然后利用它自身的宏 _CrtDumpMemoryLeaks()就可以再运行结果出爆出内存泄漏的大概地方 2.有一个插件吧,它是专门用来检测内存泄漏的,你们可以去下载一下 VISUAL LEAK PETECTOR(VLD),就是这个软件,然后你在后面每次写代码之前包含一下它的头文件,这样的话运行过程中要是有内存泄露问题,它在结果显示部分会给你清楚的显示出泄露了多少字节,泄露的准确行数再哪里,总之用起来很方便。对了,关于如何去判断内存泄漏我也说出一种方法吧,其实真正的内存泄漏并不是说你malloc,new了以后没有free,delete,要是你觉得这个就算是内存泄露的真正含义的话,那你就是个外行,真正的内存泄漏是指在系统运行的期间,某种原因会一直损耗其内存,直到其消耗至空为止,这才是真正的内存泄漏,所以说我们可以打开其任务管理器区查看其没存使用情况和虚拟内存空间的占用率,要是不停的再增长的话,那肯定就内存泄露了。)

对于智能指针早成的困扰,我想你应该也知道,就是智能指针的交叉引用,那么如何解决呢,其实很简单,就是奖两个中的一个改成weak_ptr就行啦,毕竟weak_ptr它是依赖于shared_ptr存活的。

1 什么是 RPC ?
我们使用开源的buttonRPC库

RPC (Remote Procedure Call)即远程过程调用,是分布式系统常见的一种通信方法。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。
除 RPC 之外,常见的多系统数据交互方案还有分布式消息队列、HTTP 请求调用、数据库和分布式缓存等。
其中 RPC 和 HTTP 调用是没有经过中间件的,它们是端到端系统的直接数据交互。
简单的说

RPC就是从一台机器(客户端)上通过参数传递的方式调用另一台机器(服务器)上的一个函数或方法(可以统称为服务)并得到返回的结果。
RPC会隐藏底层的通讯细节(不需要直接处理Socket通讯或Http通讯)。
客户端发起请求,服务器返回响应(类似于Http的工作方式)RPC在使用形式上像调用本地函数(或方法)一样去调用远程的函数(或方法)。
2 为什么我们要用RPC?
RPC 的主要目标是让构建分布式应用更容易,在提供强大的远程调用能力时不损失本地调用的语义简洁性。为实现该目标,RPC 框架需提供一种透明调用机制让使用者不必显式的区分本地调用和远程调用。

3 RPC需要解决的三个问题
RPC要达到的目标:远程调用时,要能够像本地调用一样方便,让调用者感知不到远程调用的逻辑。

Call ID映射。我们怎么告诉远程机器我们要调用哪个函数呢?在本地调用中,函数体是直接通过函数指针来指定的,我们调用具体函数,编译器就自动帮我们调用它相应的函数指针。但是在远程调用中,是无法调用函数指针的,因为两个进程的地址空间是完全不一样。所以,在RPC中,所有的函数都必须有自己的一个ID。这个ID在所有进程中都是唯一确定的。客户端在做远程过程调用时,必须附上这个ID。然后我们还需要在客户端和服务端分别维护一个 {函数 <–> Call ID} 的对应表。两者的表不一定需要完全相同,但相同的函数对应的Call ID必须相同。当客户端需要进行远程调用时,它就查一下这个表,找出相应的Call ID,然后把它传给服务端,服务端也通过查表,来确定客户端需要调用的函数,然后执行相应函数的代码。
序列化和反序列化。客户端怎么把参数值传给远程的函数呢?在本地调用中,我们只需要把参数压到栈里,然后让函数自己去栈里读就行。但是在远程过程调用时,客户端跟服务端是不同的进程,不能通过内存来传递参数。甚至有时候客户端和服务端使用的都不是同一种语言(比如服务端用C++,客户端用Java或者Python)。这时候就需要客户端把参数先转成一个字节流,传给服务端后,再把字节流转成自己能读取的格式。这个过程叫序列化和反序列化。同理,从服务端返回的值也需要序列化反序列化的过程。
网络传输。远程调用往往是基于网络的,客户端和服务端是通过网络连接的。所有的数据都需要通过网络传输,因此就需要有一个网络传输层。网络传输层需要把Call ID和序列化后的参数字节流传给服务端,然后再把序列化后的调用结果传回客户端。只要能完成这两者的,都可以作为传输层使用。因此,它所使用的协议其实是不限的,能完成传输就行。尽管大部分RPC框架都使用TCP协议,但其实UDP也可以,而gRPC干脆就用了HTTP2。Java的Netty也属于这层的东西。
4 实现高可用RPC框架需要考虑到的问题
既然系统采用分布式架构,那一个服务势必会有多个实例,要解决如何获取实例的问题。所以需要一个服务注册中心,比如在Dubbo中,就可以使用Zookeeper作为注册中心,在调用时,从Zookeeper获取服务的实例列表,再从中选择一个进行调用;
如何选择实例呢?就要考虑负载均衡,例如dubbo提供了4种负载均衡策略;
如果每次都去注册中心查询列表,效率很低,那么就要加缓存;
客户端总不能每次调用完都等着服务端返回数据,所以就要支持异步调用;
服务端的接口修改了,老的接口还有人在用,这就需要版本控制;
服务端总不能每次接到请求都马上启动一个线程去处理,于是就需要线程池;
5 理论结构模型
RPC 服务端通过RpcServer去导出(export)远程接口方法,而客户端通过RpcClient去导入(import)远程接口方法。客户端像调用本地方法一样去调用远程接口方法,RPC 框架提供接口的代理实现,实际的调用将委托给代理RpcProxy。代理封装调用信息并将调用转交给RpcInvoker去实际执行。在客户端的RpcInvoker通过连接器RpcConnector去维持与服务端的通道RpcChannel,并使用RpcProtocol执行协议编码(encode)并将编码后的请求消息通过通道发送给服务端。

RPC 服务端接收器RpcAcceptor接收客户端的调用请求,同样使用RpcProtocol执行协议解码(decode)。

解码后的调用信息传递给RpcProcessor去控制处理调用过程,最后再委托调用给RpcInvoker去实际执行并返回调用结果。

主流的RPC框架
服务治理型
dubbo:是阿里巴巴公司开源的一个Java高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成。dubbo 已经与12年年底停止维护升级。
dubbox:是当当团队基于dubbo升级的一个版本。是一个分布式的服务架构,可直接用于生产环境作为SOA服务框架。dubbox资源链接
motan:是新浪微博开源的一个Java框架。它诞生的比较晚,起于2013年,2016年5月开源。Motan 在微博平台中已经广泛应用,每天为数百个服务完成近千亿次的调用。motan资源链接
腾讯Tars

阿里dubbo

开源buttonrpc

远程调用在平时开发中都经常会用到,一般常用的是http,webservice,rmi等 RPC 方式

http和rpc最大的区别:http是超文本传输协议,rpc更底层 是tcp的传输协议

远程调用的区别无非在于,通讯协议:http,tcp等等,序列号方式:json,xml,hessian,protobuf;json-rpc,基于http通讯的json序列号话的调用方式。

联系

http 也是rpc的一种方式,http和rpc的关系更像is-a 的关系。

区别

性能:

http 传输包含除了自身参数,还有一系列的头信息,request和response 信息。rpc的这些信息占用的字节较少

效率

http每次都需要三次握手,rpc则不需要 并且是长链接

每次访问要经过dns,lvs,ng,rpc只需要经过注册中心找到服务提供者即可。可以走内网。

负载均衡

http 需要经过ng或者lvs

rpc走自带的负载均衡服务。

总结如何选择:

http:对外不系统,简单高效的,系统设计初期。第三方api等等。

rpc:更多的是内部系统之间的交互,且对性能要求比较高的系统。

常用的rpc方式:

Grpc,gg的基于http2.0的传输协议,protobuf 序列化方式。

dubbo:阿里的远程调用协议,无非就是生产者,消费者,注册中心等等。具体写过小的demo而已,内部实现没有看过源码,有时间要研究研究

RSF:我司的一个内部使用的系统调用方式。生产者,消费者注册到统一的平台,可以指定协议列表,序列号方式是二进制,可以进行流控,监控,负载均衡,熔断,权限控制等等操作,还是非常方便的。具体细节有时间还是看看的。

先写这么多,还是希望有时间多看看原理性的东西,而不是搬运工,多一点自己的思考,形成自己的知识体系。

12、数据库

sqlite原理:https://www.kancloud.cn/kangdandan/sqlite/64326

SQL:structured query language 结构化查询语言。专门对数据库进行查找、增加、修改、删除、统计的操作语言。

CURD 增删查改 create update retrieve delete。

书写风格,关键字大小写都行,建议大写。表名大小写都行,但是在一些数据库中不区分大小写,建议小写。

(重要)基本语法。

查找

SELECT 字段1,字段2,字段3,… FROM 表名; python中返回值形如[(1, 502班, 小明, 男), (), ()]。

字段比较多时简写为 SELECT * FROM 表名; 由于数据库执行时会把*转换为字段再执行,性能极微小下降。

SELECT * FROM 表名 WHERE 字段1 = 过滤值,字典2=过滤值 ; where限定条件查找。

添加

INSERT 字段1,字段2,… INTO 表名 VALUES (1, “小明”, “男”);

简写 INSERT INTO 表名 VALUES (1, “小明”, “男”);

修改

UPDATE 表名 SET 字段1=新值,字段2=新值 WHERE 字段1 = 过滤值;

注意没有where条件限定行的话将会更新整张表。

删除

DELETE FROM 表名; 注意会删除整张表。

DELETE FROM 表名 WHERE 字段1 = 过滤值; 限定条件删除某些行。

创建表

CREATE TABLE 表名 {

字段类型 字段名 其它关键字(主键 备注),

INT id PRIMARY KEY,

VARCHAR(20) username

13、Hadoop大数据

1、Hadoop是什么

1.1、小故事版本的解释

小明接到一个任务:计算一个100M的文本文件中的单词的个数,这个文本文件有若干行,每行有若干个单词,每行的单词与单词之间都是以空格键分开的。对于处理这种100M量级数据的计算任务,小明感觉很轻松。他首先把这个100M的文件拷贝到自己的电脑上,然后写了个计算程序在他的计算机上执行后顺利输出了结果。

后来,小明接到了另外一个任务,计算一个1T(1024G)的文本文件中的单词的个数。再后来,小明又接到一个任务,计算一个1P(1024T)的文本文件中的单词的个数……

面对这样大规模的数据,小明的那一台计算机已经存储不下了,也计算不了这样大的数据文件中到底有多少个单词了。机智的小明上网百度了一下,他在百度的输入框中写下了:大数据存储和计算怎么办?按下回车键之后,出现了有关Hadoop的网页。

看了很多网页之后,小明总结一句话:Hadoop就是存储海量数据和分析海量数据的工具。

1.2、稍专业点的解释

Hadoop是由java语言编写的,在分布式服务器集群上存储海量数据并运行分布式分析应用的开源框架,其核心部件是HDFS与MapReduce。

   HDFS是一个分布式文件系统:引入存放文件元数据信息的服务器Namenode和实际存放数据的服务器Datanode,对数据进行分布式储存和读取。

MapReduce是一个计算框架:MapReduce的核心思想是把计算任务分配给集群内的服务器里执行。通过对计算任务的拆分(Map计算/Reduce计算)再根据任务调度器(JobTracker)对任务进行分布式计算。

1.3、记住下面的话:

   Hadoop的框架最核心的设计就是:HDFS和MapReduce。HDFS为海量的数据提供了存储,则MapReduce为海量的数据提供了计算。

 把HDFS理解为一个分布式的,有冗余备份的,可以动态扩展的用来存储大规模数据的大硬盘。

 把MapReduce理解成为一个计算引擎,按照MapReduce的规则编写Map计算/Reduce计算的程序,可以完成计算任务。

2、Hadoop能干什么

大数据存储:分布式存储

日志处理:擅长日志分析

ETL:数据抽取到oracle、mysql、DB2、mongdb及主流数据库

机器学习: 比如Apache Mahout项目

搜索引擎:Hadoop + lucene实现

数据挖掘:目前比较流行的广告推荐,个性化广告推荐

Hadoop是专为离线和大规模数据分析而设计的,并不适合那种对几个记录随机读写的在线事务处理模式。

实际应用:

(1)Flume+Logstash+Kafka+Spark Streaming进行实时日志处理分析

(2)酷狗音乐的大数据平台

3、怎么使用Hadoop

3.1、Hadoop集群的搭建

无论是在windows上装几台虚拟机玩Hadoop,还是真实的服务器来玩,说简单点就是把Hadoop的安装包放在每一台服务器上,改改配置,启动就完成了Hadoop集群的搭建。

3.2、上传文件到Hadoop集群

Hadoop集群搭建好以后,可以通过web页面查看集群的情况,还可以通过Hadoop命令来上传文件到hdfs集群,通过Hadoop命令在hdfs集群上建立目录,通过Hadoop命令删除集群上的文件等等。

3.3、编写map/reduce程序

通过集成开发工具(例如eclipse)导入Hadoop相关的jar包,编写map/reduce程序,将程序打成jar包扔在集群上执行,运行后出计算结果。

敏捷开发的四条原则

1、递增,而不是连续的:如果开发实践是真正的敏捷精神,那么交付的工作软件是一小部分一小部分递增的。不必等到一个阶段完全完成后才开始另一个,工作也不是向大的发布日期而努力。完成的工作,但并不是业务最终期限,驱动着敏捷交付。但敏捷精神也承认业务操纵着最后截止日期。
2、避免不必要的开销:如果实践仍然是真正的敏捷精神,那么团队就致力于尽可能多地减少项目计划和文档。与其讨论要做什么,然后再写下来,不如赶紧动手去做,否则,就是在浪费时间在工作的工作上。在工作对工作中,敏捷精神有利于实际的工——作交付工作软件。而且它也值面对面的交流通过邮件和其他书面文件。
3、协作:根据需求,团队成员一直与其它人进行交互,以及一些外部利益相关者。在敏捷教练世界中,整个团队的负责人Lisa Crispin能够解决所有问题,在问题出现之前 。真正的敏捷精神团队是自助的。他们分配需要做的工作。虽然每个成员承担的任务都在他们的专业技能范围内,他们还是需要与团队协作的。没有人的工作是孤立的,也没有团队本身是独立工作的。没有业务利益相关者,以及诸如用户体验方面的外部专家的重大投入,团队就不可能使项目向前发展,
4、说真话:为了保证真正的敏捷,团队探讨的与项目相关的一切都要是真实的。在一些至关重要的专业领域,如冲刺测试的编码技能,他们承认存在差距。关于实际生产力,他们的要讲事实;这也就是说,在y时间内,团队是否有能力做到x。他们承认错误。说真话是一项挑战,因为他们害怕承认缺点会让他们显得很弱。但敏捷精神知道说出事实需要勇气。承认问题需要信心,然后快速地去解决问题。

微服务架构:

单体架构

单体架构是最简单的软件架构,常用于传统的应用软件开发以及传统 Web 应用。传统 Web 应用,一般是将所有功能模块都打包(jar、war)在一个 Web 容器(JBoss、Tomcat)中部署、运行。随着业务复杂度增加、技术团队规模扩大,在一个单体应用中维护代码,会降低开发效率,即使是处理一个小需求,也需要将所有机器上的应用全部部署一遍,增加了运维的复杂度。

SOA 架构

当某一天使用单体架构发现很难推进需求的开发、以及日积月累的技术债时,很多企业会开始做单体服务的拆分,拆分的方式一般有水平拆分和垂直拆分。垂直拆分是把一个应用拆成松耦合的多个独立的应用,让应用可以独立部署,有独立的团队进行维护;水平拆分是把一些通用的,会被很多上层服务调用的模块独立拆分出去,形成一个共享的基础服务,这样拆分可以对一些性能瓶颈的应用进行单独的优化和运维管理,也在一定程度上防止了垂直拆分的重复造轮子。

SOA 也叫面向服务的架构,从单体服务到 SOA 的演进,需要结合水平拆分及垂直拆分。SOA 强调用统一的协议进行服务间的通信,服务间运行在彼此独立的硬件平台但是需通过统一的协议接口相互协作,也即将应用系统服务化。举个易懂的例子,单体服务如果相当于一个快餐店,所有的服务员职责都是一样的,又要负责收银结算,又要负责做汉堡,又要负责端盘子,又要负责打扫,服务员之间不需要有交流,用户来了后,服务员从前到后负责到底。SOA 相当于让服务员有职责分工,收银员负责收银,厨师负责做汉堡,保洁阿姨负责打扫等,所有服务员需要用同一种语言交流,方便工作协调。

微服务也是一种服务化,不过其和 SOA 架构的服务化概念也是有区别的,可以从以下几个关键字来理解:

松耦合:每个微服务内部都可以使用 DDD(领域驱动设计)的思想进行设计领域模型,服务间尽量减少同步的调用,多使用消息的方式让服务间的领域事件来进行解耦。

轻量级协议:Dubbo 是 SOA 的开源的标准实现之一,类似的还有像 gRPC、Thrift 等。微服务更倾向于使用 Restful 风格的 API,轻量级的协议可以很好地支持跨语言开发的服务,可能有的微服务用 Java 语言实现,有的用 Go 语言,有的用 C++,但所有的语言都可以支持 Http 协议通信,所有的开发人员都能理解 Restful 风格 API 的含义。

高度自治和持续集成:从底层的角度来说,SOA 更加倾向于基于虚拟机或者服务器的部署,每个应用都部署在不同的机器上,一般持续集成工具更多是由运维团队写一些 Shell 脚本以及提供基于共同协议(比如 Dubbo 管理页面)的开发部署页面。微服务可以很好得和容器技术结合,容器技术比微服务出现得晚,但是容器技术的出现让微服务的实施更加简便,目前 Docker 已经成为很多微服务实践的基础容器。因为容器的特色,所以一台机器上可以部署几十个、几百个不同的微服务。如果某个微服务流量压力比其他微服务大,可以在不增加机器的情况下,在一台机器上多分配一些该微服务的容器实例。同时,因为 Docker 的容器编排社区日渐成熟,类似 Mesos、Kubernetes 及 Docker 官方提供的 Swarm 都可以作为持续集成部署的技术选择。

其实从架构的演进的角度来看,整体的演进都是朝着越来越轻量级、越来越灵活的应用方向发展,甚至到近两年日渐成熟起来的 Serverless(无服务)架构。从单体服务到分层的服务,再到面向服务、再到微服务甚至无服务,对于架构的挑战是越来越大。

https://blog.csdn.net/valada/article/details/80993643

DDD(Domain-Drive Design)领域驱动设计

使用领域驱动设计的理念,工程师们的关注点需要从 CRUD 思维中跳出来,更多关注通用语言的设计、实体以及值对象的设计。至于数据仓库,会有更多样化的选择。分布式系统中数据存储服务是基础,微服务的领域拆分、领域建模可以让数据存储方案的选择更具灵活性。

Makefile 简介
Makefile 是一种常用于编译的脚本语言。它可以更好更方便的管理你的项目的代码编译,节约编译时间(没改动的文件不编译)。
注意 Makefile 文件命令必须是 Makefile 或者 makefile,并使用 make 命令编译。

范例1:
ALL : hello.out #ALL 表示最终生成的目标文件

hello.out : hello.c #hello.out : 依赖于 hello.c
gcc hello.c -o hello.out #命令

范例2:
SRC = $(wildcard ./*.c) #定义变量
OBJ = $(patsubst %.c, %.o, $(SRC))

ALL : hello.out

hello.out : $(OBJ)
gcc $(OBJ) -o hello.out

$(OBJ) : $(SRC)
gcc -c $(SRC) -o $(OBJ)

范例3:
SRC = $(wildcard ./*.c)
OBJ = $(patsubst %.c, %.o, $(SRC))

ALL : hello.out

hello.out : $(OBJ)
gcc $< -o $@

$(OBJ) : $(SRC) #源 OBJ : 依赖于 SRC
gcc -c $< -o $@ # $<表示当前语句中的依赖项 $@表示当前语句中的源

.PHONY: clean
clean:
-rm -f $(OBJS)

二、1个规则

  1. 语法规则
    目标…: 依赖…
    命令1
    命令2

  2. 目标
    目标即要生成的文件。如果目标文件的更新时间晚于依赖文件的更新时间,则说明依赖文件没有改动,目标文件不需要重新编译。否则重新编译并更新目标。

  3. 依赖
    即目标文件由哪些文件生成。如果依赖条件中存在不存在的依赖条件,则会寻找其它规则是否可以产生依赖条件。

例如:规则一是生成目标 hello.out 需要使用到依赖条件 hello.o,但是 hello.o 不存在。则 Makefile 会寻找到一个生成 hello.o 的规则二并执行。

  1. 命令
    即通过执行该命令,由依赖文件生成目标文件。

注意每条命令前必须有且仅有一个 tab 保持缩进,这是语法要求。

  1. ALL
    Makefile 文件默认只生成第一个目标文件即完成编译,但是我们可以通过 “ALL” 指定需要生成的目标文件。

  2. 示例
    针对以上所说的,先写一个示例让大家了解一下,首先准备一个 hello.c:

#include

int main()
{
printf(“Hello World !\n”);
return 0;
}
然后写一个 Makefile

ALL: hello.out

hello.out: hello.c
gcc hello.c -o hello.out
编译并执行:

$ make
gcc hello.c -o hello.out
$ ./hello.out
Hello World !

三、2个函数

  1. wildcard
    例如

SRC = $(wildcard ./*.c)
匹配目录下所有的 .c 文件,并将其赋值给 SRC 变量。

  1. patsubst
    pat 是 pattern 的缩写,subst 是 substring 的缩写。例如

OBJ = $(patsubst %.c, %.o, $(SRC))
这个函数有三个参数,意思是取出 SRC 中所有的值,然后将 “.c” 替换为 “.o”,最后赋值给 OBJ 变量。

  1. 示例
    通过上面两个函数,加入我们目录下有很多个 “.c” 后缀的源文件,就不需要写很多条规则语句了,而是可以像下面这样写

SRC = $(wildcard *.c)
OBJ = $(patsubst %.c, %.o, $(SRC))

ALL: hello.out

hello.out: $(OBJ)
gcc $(OBJ) -o hello.out

$(OBJ): $(SRC)
gcc -c $(SRC) -o $(OBJ)
这里我们先将所有的 “.c” 文件编译为 “.o” 文件,这样后面更改某个 “.c” 文件时,其它的 “.c” 文件将不再编译,而只是编译有更改的 “.c” 文件,可以大大节约大项目中的编译速度。

四、3个变量
Makefile 中也有一些已经定义好的常用变量,这里介绍其中常用的3个。

  1. $@
    表示规则中目标,例如 hello.out。

  2. $<
    表示规则中的第一个依赖条件,例如 hello.c

  3. $^
    表示规则中的所有依赖条件,由于我们示例中都只有一个依赖条件,这种情况下 $^ 和 $< 区别不大。

  4. 示例
    使用这些变量替换上面写的 Makefile,即是:

SRC = $(wildcard *.c)
OBJ = $(patsubst %.c, %.o, $(SRC))

ALL: hello.out

hello.out: $(OBJ)
gcc $< -o $@

$(OBJ): $(SRC)
gcc -c $< -o $@

五、其它常用功能

  1. 代码清理 clean
    我们可以编译一条属于自己的 clean 语句,来清理 make 命令所产生的所有文件。例如

SRC = $(wildcard *.c)
OBJ = $(patsubst %.c, %.o, $(SRC))

ALL: hello.out

hello.out: $(OBJ)
gcc $< -o $@

$(OBJ): $(SRC)
gcc -c $< -o $@

clean:
-rm -rf $(OBJ) hello.out
这样我们就可以使用 clean 命令来清理生成的文件了:

$ ls
hello.c hello.o hello.out Makefile
$ make clean
rm -rf hello.o hello.out
$ ls
hello.c Makefile

  1. 伪目标 .PHONY
    上面我们写了一个 clean 语句,使得我们执行 “make clean” 命令的时候,可以清理我们生成的文件。

但是假如还存在一个文件名就是 clean 文件,那么我们再执行 “make clean” 命令的时候就只是显示

$ make clean
make: `clean’ is up to date.
解决方法就是我们使用伪目标,这样就可以避免出现上面的问题了,例如:

SRC = $(wildcard *.c)
OBJ = $(patsubst %.c, %.o, $(SRC))

ALL: hello.out

hello.out: $(OBJ)
gcc $< -o $@

$(OBJ): $(SRC)
gcc -c $< -o $@

clean:
-rm -rf $(OBJ) hello.out

.PHONY: clean ALL

lucky pass

Reactor模式是什么

反应器设计模式(Reactor pattern)是一种为处理并发服务请求,并将请求提交到一个或者多个服务处理程序的事件设计模式。当客户端请求抵达后,服务处理程序使用多路分配策略,由一个非阻塞的线程来接收所有的请求,然后派发这些请求至相关的工作线程进行处理。

g++和gcc

g++调用gcc,针对.c文件时,g++当做cpp文件,gcc当做c文件,针对.cpp文件时,都认为是cpp文件。

gcc不能自动和c++程序使用库连接,通常用g++

编译都可以用,连接使用g++或者gcc -lstdc++

预处理.i -E

gcc -E hello.c -o hello.i

编译.s -S

gcc -S hello.i -o hello.s

汇编.o -c

gcc -c hello.s -o hello.o

连接可执行.out等

gcc hello.o -o hello

执行./hello

C++11新特性

lambda表达式,智能指针,类型推导,模板,for循环遍历

智能指针

将基本指针类型封装为类对象指针,并在析构函数里编写delete语句删除指针指向的空间

智能指针是一个类,这个类的构造函数中传入基本指针,析构函数中释放指针,智能指针的类都是栈上的对象,所以当函数或程序结束时自动被释放。析构函数里是一堆计数之类的判断,达到某个条件,会把指针指向的空间释放。

auto_ptr不支持拷贝构造函数和赋值,不提示错误,会造成崩溃

unique_ptr不支持拷贝构造函数和赋值,但是会提示编译出错。

shared_ptr基于引用计数的指针,可随意赋值,直到内存中的引用计数为0时,内存会被释放

weak_ptr弱引用,引用计数会相互相成环,这样两个指针指向的内存都无法释放,需要手动打破循环引用weak_ptr。

使用普通指针容易引起堆内存泄漏,野指针,二次释放。

野指针

不是null的指针,未初始化或者未清零的指针,它所指向的内存地址不是程序员所期望的,可能指向了受限的内存。

C、C++

c是面向过程的语言,输入参数执行处理,得到输出。分配内存使用malloc/free

C++面向对象,封装继承多态,封装隐藏了实现细节,使代码模块化,继承,派生类可以继承基类的属性和方法,也可以扩展,实现了代码的重用,多态是一个接口实现了多种功能,通过派生类重写基类的虚函数,实现了接口的重用。分配内存使用new/delete。支持函数重载,有引用的概念。

重载:

将一系列功能相同的函数定义为一个函数名,而函数的参数列表不同,即参数个数、类型、顺序不同,返回值不同。

重写:

派生类中重新实现基类中的virtual函数,函数名,类型,参数均相同。目的是实现多态,在使用指向派生来的基类指针时,可以调用到不同派生类的方法

死锁的必要条件

1、互斥量,一个资源一次只允许一个对象访问

2、一个线程在申请其他资源时,自身的资源不会释放

3、不可剥夺,不能剥夺已获取资源的线程的权限

3、多个线程形成循环等待。环路,每个进程都会相邻的不停的申请已占用的资源的权限

破解死锁

1、检测死锁,进行恢复

2、对资源进行动态分配,避免死锁,合理的规划

3、申请时检测是否会出现死锁

4、加锁顺序,在能确保所有线程都是按照相同的顺序获得锁,就不会死锁

加锁时增加超时时间

死锁检测,当一个线程请求加锁失败时,遍历锁,看是否有死锁发送

再系统设计、进程调度时不让产生死锁的4个必要条件成立

静态连接

是编译时将函数体复制到本模块中,编译完成就不需要静态库了

动态编译

在执行时,去访问动态库中的实现。只是一个接口。

大端

高字节存放在低地址的位置

小端

高字节存放在高地址

一般操作系统都是小端,而通讯协议是大端的。

TCP
20201109
是面向连接的,可靠的,字节流模式,用途上

1.面向连接,客户端服务器必须建立3次握手,连接,只有一个通信,而udp不需要建立连接,可以多播和广播实现一对多

2.提供端到端的可靠数据流控制,对接收的数据进行确认,采用超时重发,对失序的数据进行重排,保证数据可靠。udp不可靠,接收方可能收不到发送方的数据

3.tpc是流模式,udp是报文模式协议,tcp保证数据的顺序,有拥堵控制,流量控制,udp不保证。

4.效率上,tcp需要对数据进行排序,超时重发,对数据进行确认,耗时,upd无这些机制,速度快

5.tcp头20字节,upd8字节,tcp可靠,http、ftp使用,upd速度快,视频游戏使用

6.tcp提供的是面向连接,可靠的字节流服务,当客户端和服务器彼此交换数据前,必须在双方中建立一个tcp连接,提供超时重发,丢弃重复数据,检测数据,流量控制,保证数据一端到另一端。

TCP应用场景:

效率要求相对低,但对准确性要求相对高的场景。因为传输中需要对数据确认、重发、排序等操作,相比之下效率没有UDP高。举几个例子:文件传输(准确高要求高、但是速度可以相对慢)、接受邮件、远程登录。

7.upd是简单的面向数据报文,是不可靠的,只是把程序的数据报发出去,不保证能到达目的地,不需要在客户端服务器之间建立一个连接,没有超时重发,速度快

效率要求相对高,对准确性要求相对低的场景。举几个例子:QQ聊天、在线视频、网络语音电话(即时通讯,速度要求高,但是出现偶尔断续不是太大问题,并且此处完全不可以使用重发机制)、广播通信(广播、多播)。

TCP保证可靠性主要依靠下面7种机制:

1、检验和 2、序列号 3、确认应答机制(ACK) 4、超时重传机制 5、连接管理机制 6、流量控制 7、拥塞控制

TCP/IP:数据链路层,网络层,传输层,应用层

OSI:物理层,数据链路层,网络层,传输层,会话层,表示层,应用层

网络层:IP协议

传输层:TCP协议、UDP协议

应用层:FTP、HTTP、SMTP

编译步骤

1、预处理.i

2.编译.s

3、汇编.o

4、连接 可执行

线程池

一堆创建好的线程,最大数目已定,初始化好后一直处于空闲状态,有新任务来时从线程池中取出空闲线程处理任务。任务完成后又重新放回去,当线程池中的所有线程都在任务时,只能等待有线程结束任务才能继续执行。

程序、进程

程序是指令的有序集合,本身没有任何运行的含义,是一个静态的概念,可以作为一个资料长期存在。

进程是程序在处理机上一次执行过程,是动态概念,是有生命周期的,是程序的应用实例。

进程、线程

进程是一定独立功能的一个程序关于某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单位。一个程序至少一个进程。拥有独立的内存资源,弱相关处理使用进程。

线程是进程的一个实体,是CPU调度的基本单位,自己不拥有系统资源,与其他线程公用进程的资源。一个进程至少一个线程,线程开销小,需要频繁调用销毁使用线程,需要大量计算,与主UI同时异步处理,使用线程

线程/进程

线程可以共享进程的内存,有共同的地址空间,切换时间短,通信方便,占用内存小,cpu利用率高,同步复杂,切换简单,速度快,调试复杂,是进程启动的执行单元,需要程序员管控的东西多,相互影响的几率大。需要频繁创建销毁的,大量运算,数据,界面及时响应消息,并发可能阻塞的用多线程

进程必须独立分配内存,有独立的地址空间,不会相互影响隔离度好,开销大,进程间通信、资源共享麻烦,需要使用IPC。CPU利用率低,同步简单。切换复杂,速度慢,编程调试简单,

进程间通信

管道、共享内存、socket、信号量、消息队列、条件变量

管道pipe,无名管道,用于具有亲缘关系的父子进程之间的通信,命名管道named pipe,允许无亲缘关系的进程间通信

信号量,signal,是在软件层次上对中断机制的一种模拟,用于通知进程有哪些事件发生,同步和互斥的手段

消息队列,message queue,消息链表,具有权限的进程可以按照一定的规则向消息队列中添加新消息,有读权限的进程可以从消息队列中读取消息

共享内存semaphore:最有用的进程间通信方式,使得多个进程可以访问同一块内存空间,不同进程可以看到对方进程中数据的更新。

套接字socket,一种进程间通信,可以在不同机器间通信。

多线程互斥:多线程访问一个全局变量,一个资源时,需要线程间互斥,保证访问的安全性,比如同时抢票

互斥量、信号量、事件、临界区

临界区只能用于线程互斥,性能好

互斥量属于内核对象,用于线程互斥,性能差

互斥量可以复位,临界区不行

线程同步:多线程同时执行时,比如线程A负责读取数据到内存,线程B负责分析数据,则需要A先执行,再执行B

事件和信号量

都是内核对象,使用完成后关闭句柄

信号量可以用于复杂的同步线程控制

分为用户模式和内核模式

内核模式需要切换到内核和用户态:事件,信号量,互斥量。

用户模式:临界区,原子操作,单一的全局变量,

临界区,对多线程的串行化来访问公共资源或代码,速度快,适合控制数据访问,适合对速度有要求的场合。

互斥量,为了协调同一个公共资源的单独访问而设计的

信号量,控制一个具有有限数量的用户资源设计,线程占用数达到最大,其他线程就不能再进来,有部分线程释放资源后,其他线程才能访问资源。

事件,为了通知线程有一些事件已经发生,从而启动后续任务的开始。

互斥锁

pthread_mutex_init

ptherad_mutex_destory

pthread_mutex_lock

ptherad_mutex_trylock

pthread_mutex_unlock

信号量

include

sem_init

sem_destory

sem_wait

sem_trywait

sem_post

创建线程

win32api CreateThread

C运行时库 _beginthread,内部调用了CreateThread,对C RunTime做了一些初始化工作,保证C RunTime库正常工作后,调用CreateThread。

MFC,afxBeginThread,先创建相应的CWinThread完成对象的初始化工作,然后调用_beginthread,相对更安全,在MFC中只能使用afxBeginThread.

boost,thread

栈:操作系统分配,存储临时变量,函数参数,局部变量,自动分配释放,速度快,效率高,内存空间连续,空间有限2M左右,高地址往低地址增长

堆:程序员自行分配,释放,动态分配,内存空间不连续,会产生碎片new、delete、malloc、free,申请空间比较灵活,低地址往高地址增长

全局静态存储区;全局变量,静态变量存储,程序结束后自动释放,

文字常量区,存放常量,不允许修改,程序结束后自行释放

代码区,代码二进制

C++ REST DISK

访问barcode服务器就是使用Rest Disk

是微软开源的一套客户端服务器通讯库,提供各类URI构造解析,json编解码,http客户端,服务器,websocket客户端,流式传输,方便c++语言编写的客户端访问互联网服务

URI 统一资源标识符,表示web上每一种可用的资源,html文档,图像,视频,程序等

URL 统一资源定位符,时URI的子集,是internet上描述信息资源的字符串,用在www客户程序,服务器程序上,采用URL可用用一种统一格式来描述各种信息资源,包括文件,服务器地址,目录等。

ICE

支持不同操作系统,不同开发语言,服务器可用是任何一种语言实现,客户端根据自己的情况选择语言,不需关注通信,只需关注业务逻辑。是一种面向对象的中间件,为构建客户端服务器应用提供了工具,API和库支持。与ice持有的对象进行通信,客户端必须持有这个对象的代理,即对象的实例,ice在运行时会定位到这个对象,任何寻找激活它,再把in参数传递给远程对象,再通过out获取返回结果。

设计模式

简单工厂模式

观察者模式

策略模式

装饰模式

代理模式

原型模式

外观模式

建造者模式

抽象工厂模式

状态模式

适配器模式

备忘录模式

命令模式

桥接模式

中介者模式

享元模式

解释器模式

访问者模式

python内存管理机制

引用计数:

当有一个引用时,引用计数加一,当引用计数为0时,对象就删除了

垃圾回收:

引用计数回收,每个对象为pyobject,内部有个引用计数,当为0 时,该对象就需要被收回

标记回收,标记在循环时使用容器对象时产生,字典,元祖,列表。需要每个容器对象维护2个额外指针,

分代回收,老中青,210,第0带在一次gc中存活,则0会升为1,

内存池:

内存机制为金字塔形状,-1、-2为操作系统操作,

0:malloc/free操作

1、2位内存池,有python接口函数实现,分配小于256k的内存

3:最上层,操作python对象

调优手段:手动垃圾回收,避免循环引用,调高垃圾回收阈值

extern"c"作用

告诉编译器,按照c语言的格式来进行编译,因为c++的多态性,实现函数重载,实现c、C++混合编译,使c++中可以调用c函数,编译时函数名会进行修改。

static关键字

强调唯一的拷贝

面向过程语言,c中,修饰变量时,表示该变量是静态变量,存储在静态存储区,只能在该文件中使用。修饰函数时,表面该函数为静态函数,不能再其他文件中使用,再函数体内维持其值不变。

面向对象语言中,c++中,修饰变量,表示该变量时静态成员变量,存储在全局静态存储区,只能在该类中使用,该类的所有对象都可以使用。修饰函数时,表示是静态成员函数不和任何对象有联系,没有this

const关键字

强调不能修改

定义时必须初始化

修饰变量时,表面该变量不可修改

修饰指针时,可以表示指向常量的指针,和指针常量,指针地址上存放在值不可修改,指针本身存放的地址不可修改

修饰函数形参,表示传入参数在函数体内不可被修改

修饰成员变量,该变量不可被更改,且必须才构造函数初始化列表中进行初始化

修饰成员函数,表面函数是常成员函数,不能修改类的成员变量

#define和const

define只是在预处理时进行简单的替换,是个立即数,没有类型检查,不分配内存,可以有多个拷贝,可以定义简单的函数

const有类型检查,在编译时执行,分配内存,只能有一份拷贝,存放在静态区域,开元用指针指向

#define和typedef

define是预处理时简单的替换,不做类型检查

typedef是编译时处理,在自己的作用域内给已经存在的类型定一个别名

#define pInt int*

typedef (int*) pInt;

volatile

表示变量时可变的,正常操作系统会将数据从内存中获取到寄存器中,再从寄存器中拿值。而声明为volatile后,之间从内存拿值,降低了效率,编译器不再进行优化

new/delete/malloc/free

malloc需要按内存大小进行分配,返回的对象是void*,malloc是库函数,分配失败会返回null,分配内存不足会调用realloc重新分配,

new只需要传入对象,会调用对象的构造函数,且返回的是对象的指针,new是操作符,且可以重载,分配失败会抛出异常,分配内存不足会报错,delete会调用对象的析构函数

指针、引用

指针是一个新的变量,存储了另一个变量的地址,可以通过访问这个地址来修改另一个变量

引用只是一个别名,还是变量本身,对引用操作就是对变量本身进行操作。

指针在初始化时可以为空,可以有多级指针,指针赋值后可修改,指向存放数据的地址。指针需要解引用访问,间接访问,有常量指针

引用初始化时必须赋值,不能为空,引用时对象的别名,赋值后不能修改,引用时直接访问,没有常量引用

引用作为函数参数,使得该参数可在函数内进行改变,提高了函数的运行效率,没有了值传递时生成副本的时间和空间消耗

不能返回局部变量的引用,函数返回后局部变量被销毁

不能返回函数内部new分配的内存引用

可以返回类成员的引用,最好是const

结构体,联合体

结构体存放多个数据,每个数据都有自己独立地址,同时存在,大小为所有数据中最大数据的整数倍,

联合体公用一个内存,不能同时存在,大小为内存对齐后联合体中占用内存最大的变量

内存对齐#pragma pack(1)

4、8字节对齐,cpu的访问速度大大提升,有的硬件平台不能访问任意地址上的数据。

vector、list

vector是数组,拥有连续的内存空间,当新加入的元素内存不够时,会重新申请原来2倍的空间,拷贝原数据,释放旧空间,会造成内存块的拷贝。随机存取。

list是双向链表,内存不连续,可以高效的插入删除,随机存取没有销量。

map/multimap是基于二叉树实现

unordered_map和unordered_multimap散列表

C语言函数调用过程

从栈分配存储空间,将实参的存储空间复制到形参的栈空间,进行运算

友元函数和友元类

一个不同的函数,或者一个类中的成员函数可以访问类中的私有成员和保护成员

可以提高程序运行效率,但也破坏了类的封装性和数据隐蔽性,程序可维护性变差

友元函数是可以访问类的私有成员和非成员函数,定义在类外的普通函数,不属于类,需要在类的定义中声明

友元类的所有成员函数都是另一个类的友元函数,可以访问一个类中的私有,保护成员

友元关系不能被继承,是单向的,不具有传递性

内联函数

在编译时进行代码段的替换,适合简单的函数,有类型检查

define可以定义简单的函数,无类型检查,只是简单的文本替换

虚函数

虚函数用于实现多态,还具有抽象和封装的作用。

纯虚函数

在虚函数后=0,只提供声明,供接口使用,无实现。定义了纯虚函数的类是抽象类,不能实例化对象,但可以有构造函数,继承于抽象类的子类必须实现纯虚函数,才能实例化对象。

多态

静态多态,通过模板和重载实现,在编译时确定

动态多态,通过虚函数和实现关系实现,执行动态绑定,执行时确定

每个对象内部都有一个虚表指针,虚表指针被初始化指向本类的虚函数表,在程序中,不管对象的类型如何转换,对象内部的虚函数表指针是固定的,才能实现动态的对象的函数调用

多态的基础是继承,需要虚函数的支持。子类可以继承父类大部分资源,不能继承构造函数,析构函数,拷贝构造函数,友元函数,操作符重载

实现代码模块化,隐藏实现细节,接口可以重用,使类在继承时可以正确的调用

静态类型:对象在声明时采用的类型,在编译时确定。

动态类型:当前对象所指的类型,在运行期确定,动态类型可以变,静态类型不可变

静态绑定:绑定对象的静态类型,编译期

动态绑定:绑定对象的动态类型,运行期

虚函数表

每个类都有一个虚函数表,构造函数中会创建一个虚表指针。继承时,虚函数表也会从基类继承过来,如果覆盖了某个虚函数,那么虚函数表的虚表指针就会被替换,可以通过指针找到确切的函数。

虚析构函数

虚析构函数用于释放指向子类的基类指针时,先调用子类的析构函数,再调用基类的析构函数,如果不是虚函数,则只能释放基类的析构函数,会造成内存泄漏。

构造函数,内联函数,静态函数,不能是虚函数

构造函数需要一个确切的值,且必须有构造函数,才能创建虚表指针,如果定义为虚函数,则没有虚函数表,虚表指针,无法执行。

创建一个对象需要确定对象类型,而虚函数是在运行时才确定类型,在构造一个对象时,对象还未创建成功,编译器无法知道对象的实际类型是类本身还是子类等,虚函数需要虚表指针,虚表指针存放在对象的内存空间中,构造函数声明未虚函数,那么对象还未创建,还没有内存空间,更没有虚函数表,续表指针来调用构造函数了。

C++四种类型转换

const_cast转换为非const,volatile属性转换

static_cast转换为const,编译期静态类型检查static_cast(var1)

dynamic_cast动态类型转换,运行时检查,

reinerpret_cast什么都能转,数据类型切换

栈溢出

函数中的局部变量造成的溢出,栈通常大小是1-2M,分配的大小超过了栈最大值,接收的buff小于原buff,函数调用层次过深,局部变量体积过大

增加栈内存大小,使用堆内存替代

内存泄漏

动态分配所开辟的空间,在使用完毕后未手动释放,导致一直占据内存

malloc/free要配套,及时释放,防止越界

释放时未使用delete[]

没有将基类析构函数定义未虚函数

没有正确的清楚嵌套的对象指针

windows通过_crtDumpMemoryleaks()

linux使用valgrind

调试方法

windows debug

linux gdb

定义和声明

声明告诉编译器变量的名字和类型,不会为变量分配空间

定义需要分配空间,同一个变量可以声明多次,但只能定义一次

linux

进程:exec,fork,wait

线程:pthread_create,pthread_exit,pthread_join,pthread_kill,pthera_cond_init,pthread_cond_signal,ptherad_cond_wait,ptherad_mutex_lock,ptherad_mutex_unlock

线程同步:互斥锁,信号量,条件变量,读写锁

互斥锁得不到资源时阻塞,不占用系统资源,自旋锁,不停的查询,消耗系统资源。

exit()清理后进入内核,_exit()直接陷入内核。

绝对路径 /etc/init

当前目录./

上级目录…/

主目录 ~/

切换目录 cd

查看当前进程 ps

执行退出exit

查看当前路径pwd

清屏幕clear

退出当前命令ctrl+c

执行睡眠ctrl+Z

查看用户id

查看帮助–help

列出目录中的文件ls,-a所有文件,-l相信信息

软连接 ln -s slink source

硬链接 ln link source

创建目录 mkdir

创建文件 touch、vi

复制文件 cp

修改文件权限chmod

查看文件vi

显示文件内容cat,分页显示more,前一页less,查看尾部tail,查看头head

显示hello,echo hello

终端 /dev/tty

黑洞文件/dev/null

移动文件mv

复制文件cp

删除文件 rm

删除文件夹及文件 rm -r

删除空文件夹rmdir

单个通配符?

多个通配符*

指定通配符[]

统计文件内容 wc -c字节数,-l行数,-w字数

文本搜索工具grep filename

硬链接(hard link):

A是B的硬链接(A和B都是文件名),则A的目录项中的inode节点号与B的目录项中的inode节点号相同,即一个inode节点对应两个不同的文件名,两个文件名指向同一个文件,A和B对文件系统来说是完全平等的。如果删除了其中一个,对另外一个没有影响。每增加一个文件名,inode节点上的链接数增加一,每删除一个对应的文件名,inode节点上的链接数减一,直到为0,inode节点和对应的数据块被回收。注:文件和文件名是不同的东西,rm A删除的只是A这个文件名,而A对应的数据块(文件)只有在inode节点链接数减少为0的时候才会被系统回收。

软链接(soft link):

A是B的软链接(A和B都是文件名),A的目录项中的inode节点号与B的目录项中的inode节点号不相同,A和B指向的是两个不同的inode,继而指向两块不同的数据块。但是A的数据块中存放的只是B的路径名(可以根据这个找到B的目录项)。A和B之间是“主从”关系,如果B被删除了,A仍然存在(因为两个是不同的文件),但指向的是一个无效的链接。

进程有哪些状态

不可中断状态,处于睡眠时,不可中断,不响应异步信号

暂停、跟踪状态,

就绪状态

允许状态

可中断睡眠状态

僵尸状态

退出状态

命令在后台允许 ……&在命令结尾,让其在结尾允许

显示所有进程,ps -ef

查看后台任务,job -l

后台任务调用到前台fg

停下的后台任务执行起来bg

终止进程kill

搜索文件 find 目录 条件 动作

whereis 参数文件名

locate文件名

find磁盘找

查看自己所在中断信息who am i

看谁在用主机 who

用过的命令列表history

磁盘空间 df -hl

网络是否联通netstart可以用来察看网络连接状态、接口配置、路由表等,并取得一些统计信息。该命令的主要参数和用途如下:

-a :显示所有配置的接口
  -i :显示接口的统计信息
  -n :以数字的形式显示ip地址
  -r :显示内核的路由表
  -s :显示计数器的值

IP信息ipconfig

环境变量env

bind可以方便的在shell中实现宏或者按键的绑定

centos linux系统默认的shells bash

shell脚本

#!/bin/bash

清除日志脚本, 版本 2

LOG_DIR=/var/log

ROOT_UID=0 # $UID为0的时候,用户才具有root用户的权限

要使用root用户来运行,因此,对当前用户进行判断,不合要求给出友好提示,并终止程序运行。

if [ “ U I D " − n e " UID" -ne " UID"ne"ROOT_UID” ]

then

echo “Must be root to run this script.”

exit 1

fi

如果切换到指定目录不成功,给出提示,并终止程序运行。

cd $LOG_DIR || {

echo “Cannot change to necessary directory.” >&2

exit 1

}

经过上述两个判断后,此处的用户权限和路径就应该是对的了,只有清空成功,才打印成功提示。

cat /dev/null > messages &&{

echo “Logs cleaned up.”

exit 0 # 退出之前返回0表示成功. 返回1表示失败。

}

echo “Logs cleaned up fail.”

exit 1

http

支持CS架构,简单快速,客户端发送请求时,至少简单的填写路径,请求方法,即可通过浏览器发送请求

灵活,允许客户端和服务器之间传输任意类型数据

无连接,每次连接只处理一个请求,处理完就断开连接,

无状态,处理事务没有记忆能力,

超文本传输协议,

端口80

https

需要用到ca,多一个ssl加密协议,需要费用。安全较高,ssl证书需要绑定ip,流量成本高,占用资源多,费时响应速度慢

端口443

1.客户端使用https的url访问服务器,要求web服务器建立ssl安全连接。

2.web服务器收到客户端请求后,生成一对公钥私钥,把公钥放在证书中发给客户端浏览器。

3.客户端根据双方同意的ssl连接的安全等级,建立会话秘钥,使用公钥将会话加密,并传给服务器

4.web服务器使用字节的私钥解密出会话

5.web服务器利用会话秘钥加密与客户端之前的通信。

client向server发送https://baidu.com,连接道server的443端口,发送消息重要是随机值1和客户端支持的加密算法

server接收到信息后给与client响应握手信息,包括随机值2和匹配好的协商加密算法,是client加密算法的子集

server给client发送第二个响应报文,是证书,服务器通过向ca申请的,包含公钥,颁发机构,过期时间,ca的签名,服务器域名等

client解析证书,通过随机值1和2,预主秘钥组合成回话秘钥,通过证书公钥加密回话秘钥

传递加密信息,服务器得到回话秘钥,发送一条信息给client,client也发一条给server,都能正常收到则ssl建立完成。

验证证书的安全性

客户端收到证书后,先使用本地配置的权威机构的公钥,对证书进行解密,得到服务器的公钥和证书签名,证书签名通过CA公钥解密得到证书信息摘要。然后使用证书签名算法计算当前证书的信息摘要,与收到的信息摘要一致,则是服务器下发的。

常用命令

get/post/put/head/delete/options

get/post

get发送一条数据,是向服务器请求数据,数据量小,效率高,不安全,通过url请求,只能支持ASCII,传输中文乱码

post发送两条数据,向服务器发送数据,得到结果。传输数量大,传输文件。可以传递标准字符集

请求报文包含三部分:

a、请求行:包含请求方法、URI、HTTP版本信息

b、请求首部字段

c、请求内容实体

响应报文包含三部分:

a、状态行:包含HTTP版本、状态码、状态码的原因短语

b、响应首部字段

c、响应内容实体

静态分配、动态分配

静态分配为编译时完成,不占操作系统资源,分配在栈上,只需要基本类型.按照计划分配.存控制权交给编译器.效率高,

动态分配为运行时完成,分配和释放占用操作系统资源。分配在堆上,需要使用指针或者引用数据类型.按需分配.交给程序员,需要分配与释放,额外开销,依赖于程序员水平

#ifndef

#define

#endif

预处理,防止文件重复引用,防止宏被重复定义,条件编译

#program once基本相同效果

#pragma pack(1)

按1字节对其

#define min(a,b) ((a) > (b) ? (a) : (b))

#define DF_CONST (60 * 60 * 24 * 365)UL

while(1)

;

for(;

;

void* memcpy(void* voidDst, const void* voidSrc, size_t iCount)

{

if(voidDst == NULL || voidSrc == NULL)

return null;

void* szOut = voidDst;

unsigned char* szSrc = (unsigned char*)voidSrc;

unsigned char* szDst = (unsigned char*)voidDst;

while(iCount–)

{

*szDst++ = *szSrc++;

}

return szOut;

}

char* strcpy(char * strDst, const char* strSrc)

{

assert(strDst != NULL && strSrc != NULL);

char *strOut = strDes;

while((*strDst++ = *strSrc++ ) != ‘\0’)

;

return strOut;

}

char* strcat(char* strDst, const char* strSrc)

{

assert(strDst != NULL && strSrc != NULL);

char* strOut = strDst;

while(*strSrc != ‘\0’)

strSrc++;

while((*strDst++ = *strSrc++) != ‘\0’)

;

return strOut;

}

int strcmp(const char* szStr1, const char* szStr2)

{

while(*szStr1 == *szStr2)

{

if(szStr1 == ‘\0’)

return 0;

++szStr1;

++szStr2;

}

return (*szStr1-*szStr2);

}

int strlen(const char* szSrc)

{

assert(szSrc != NULL);

int iLen = 0;

while(*szSrc++ != ‘\0’)

{

iLen++;

}

return iLen;

}

0 1 2 3 4

0-----1

1------0

void MaoPaoSort(int a[], int count)

{

for(int i = 0; i < count - 1; ++i)

{

for(int j = count - 1; j > i; j–)

{

if(a[j] > a[i])

{

int t = a[j];

a[j] = a[i];

a[i] = t;

}

}

}

}

0 1 2 3 4

0-----1

0-----1

void XuanZeSort(int a[], int count)

{

for(int i = 0; i < count - 1; ++i)

{

int t = i;

for(int j = i + 1; j <= count -1; j++)

{

if(a[j] > a[t])

{

t = j;

}

}

if(t != i)

{

int k = a[t];

a[t] = a[i];

a[i] = k;

}

}

}

0 1 2 3 4 5

0-------1

1-------0

1---1

void InsertSort(int a[], int count)

{

for(int i = 1; i < count; ++i)

{

for(int j = i - 1; j >= 0; j–)

{

if(a[j] > a[i])

{

break;

}

}

if(j != i - 1)

{

int t = a[i];

for(int k = i - 1; k > j; k–)

{

a[k + 1] = a[k];

}

a[k + 1] = t;

}

}

}

struct和class

struct默认访问权限是共有,继承时默认继承public,C中可以用{}进行初始化,C中不能包含函数。C++中可以被继承,包含成员函数,可以实现多态

class默认访问权限是私有,继承默认private

派生类、虚函数

如果派生类没有定义某个虚函数,则使用基类中的版本。派生类想要使用自己的方法,则需要重写覆盖基类的方法

派生类中函数的声明必须与基类完全相同的定义

基类中时虚函数,则派生类也为虚函数

纯虚函数

多用于接口,只是声明一个方法,纯虚函数必须在子类中实现,否则子类仍为抽象类,抽象类不能实例化对象,

浅拷贝:没有新分配内存,两个指针指向同一个内存,不进行指针拷贝。如果对象析构,会出现析构2次,两个指针指向同一个地址,一方变动会影响另外一方。

深拷贝:有新分配内存,拷贝了指针指向的内容。是两个不同地址的指针。

调用拷贝构造函数的情形:

用一个类的对象去初始化另一个对象

函数的形参是类的对象时,值传递

当函数返回值是类的对象或引用时

_gcd(6,8)最大公约数2

reverse(a, a+n)反转a[10]

unique(a, a+n)去重

lower_bound(a, a+n, x)找第一个小于x的数upper_bound(a, a+n, x)

fill(a, a+n, x)

模板

template class T(T& tTemp);

使用迭代器删除元素,会使迭代器失效

原子操作

不会被线程调度机制打断的操作,一旦开始,就会一直运行到结束,中间不会有任何的context switch

socket

create/bind/listen/addcept/receive/send/close

create/connect/send/recv/close

共享内存被映射到进程空间后,最大限制是32M,通过指针访问该共享内存,通过mmap将文件映射到进程地址的共享区

linux同步机制

原子操作、信号量、读写信号量读写锁、自旋锁、内核锁、顺序锁

linux任务调度机制

实时进程:fifo先来先服务、RR时间片轮转调度

普通进程

每个进程都有2个优先级,动态优先级和实时优先级,实时是衡量进程是否值得运行,非实时有2种优先级,一种是静态,另一种是动态,优先级越高,得到cpu的时间机会越大

linux5种io模式、异步模式

同步阻塞、同步非阻塞、同步io复用,同步信号驱动、异步io模型

三次握手,注意大小写,大写表示表示为只能是0/1,小写是序号

SYN同步序列标识位synchronize sequence numbers,SYN=1,ACK=0表示请求报文,SYN=1,ACK=1表示同意连接。SYN只有在建立TCP连接时才会置位1,握手完成置位0

seq序列号

ack确认包号

ACK确认标识位

FIN终止位

client SYN=1 seq = x SYN_SEND状态等待服务器确认

server SYN=1 ACK=1 seq = y ack = x + 1 服务器进入SYN_RECV状态

client ACK=1 seq=x+1 ack = y + 1 客户端、服务器进入established

四次挥手

client FIN=1 seq=u

server ACK=1 seq=v ack=u+1

server FIN=1 ACK=1 seq=w ack=u+1

client ACK=1 seq=u+1 ack=w+1

为什么是3次握手,4次挥手

连接时,当server收到client的SYN连接请求是,可以直接发送ACK和SYN报文,ACK用于应答,SYN用于同步。

断开时,当server收到FIN报文,可能并不会立即关闭socket,所以先回复一个ACK,告诉client,你发的FIN我收到了,只有我所有报文都发完才会发FIN报文。

为什么不能2次握手

3次握手完成两个重要功能,即双发做好发送数据的准备工作,且双方都知道彼此准备好,通过序列号进行协商,这个序列号在握手过程中被确认。如果改成2次,则可能发生死锁,不知道对方有无准备好。

11种TCP状态

l CLOSED:初始状态,表示TCP连接是“关闭着的”或“未打开的”。

l LISTEN :表示服务器端的某个SOCKET处于监听状态,可以接受客户端的连接。

l SYN_RCVD :表示服务器接收到了来自客户端请求连接的SYN报文。在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂,基本上用netstat很难看到这种状态,除非故意写一个监测程序,将三次TCP握手过程中最后一个ACK报文不予发送。当TCP连接处于此状态时,再收到客户端的ACK报文,它就会进入到ESTABLISHED 状态。

l SYN_SENT :这个状态与SYN_RCVD 状态相呼应,当客户端SOCKET执行connect()进行连接时,它首先发送SYN报文,然后随即进入到SYN_SENT 状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT 状态表示客户端已发送SYN报文。

l ESTABLISHED :表示TCP连接已经成功建立。

l FIN_WAIT_1 :这个状态得好好解释一下,其实FIN_WAIT_1 和FIN_WAIT_2 两种状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET进入到FIN_WAIT_1 状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2 状态。当然在实际的正常情况下,无论对方处于任何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1 状态一般是比较难见到的,而FIN_WAIT_2 状态有时仍可以用netstat看到。

l FIN_WAIT_2 :上面已经解释了这种状态的由来,实际上FIN_WAIT_2状态下的SOCKET表示半连接,即有一方调用close()主动要求关闭连接。注意:FIN_WAIT_2 是没有超时的(不像TIME_WAIT 状态),这种状态下如果对方不关闭(不配合完成4次挥手过程),那这个 FIN_WAIT_2 状态将一直保持到系统重启,越来越多的FIN_WAIT_2 状态会导致内核crash。

l TIME_WAIT :表示收到了对方的FIN报文,并发送出了ACK报文。 TIME_WAIT状态下的TCP连接会等待2*MSL(Max Segment Lifetime,最大分段生存期,指一个TCP报文在Internet上的最长生存时间。每个具体的TCP协议实现都必须选择一个确定的MSL值,RFC 1122建议是2分钟,但BSD传统实现采用了30秒,Linux可以cat /proc/sys/net/ipv4/tcp_fin_timeout看到本机的这个值),然后即可回到CLOSED 可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。(这种情况应该就是四次挥手变成三次挥手的那种情况)

l CLOSING :这种状态在实际情况中应该很少见,属于一种比较罕见的例外状态。正常情况下,当一方发送FIN报文后,按理来说是应该先收到(或同时收到)对方的ACK报文,再收到对方的FIN报文。但是CLOSING 状态表示一方发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?那就是当双方几乎在同时close()一个SOCKET的话,就出现了双方同时发送FIN报文的情况,这是就会出现CLOSING 状态,表示双方都正在关闭SOCKET连接。

l CLOSE_WAIT :表示正在等待关闭。怎么理解呢?当对方close()一个SOCKET后发送FIN报文给自己,你的系统毫无疑问地将会回应一个ACK报文给对方,此时TCP连接则进入到CLOSE_WAIT状态。接下来呢,你需要检查自己是否还有数据要发送给对方,如果没有的话,那你也就可以close()这个SOCKET并发送FIN报文给对方,即关闭自己到对方这个方向的连接。有数据的话则看程序的策略,继续发送或丢弃。简单地说,当你处于CLOSE_WAIT 状态下,需要完成的事情是等待你去关闭连接。

l LAST_ACK :当被动关闭的一方在发送FIN报文后,等待对方的ACK报文的时候,就处于LAST_ACK 状态。当收到对方的ACK报文后,也就可以进入到CLOSED 可用状态了。

1.HTTP

从根本上讲,HTTP还是半双工的协议,也就是说,在同一时刻流量只能单向流动:客户端向服务器发送请求(单向),然后服务器响应请求(单向)。

2.WebSocket

WebSocket是一种自然的全双工、双向、单套接字连接。使用WebSocket,一旦建立连接,服务器与客户端可以随时发送消息。与HTTP轮询不同,WebSocket只发有一个请求,服务器不需要等待来自客户端的请求。相似的,客户端可以在任何时候想服务器发送消息。相比轮询,不管是否用可用消息,每隔一段时间都发送一个请求,单一请求大大减少了延迟。

WebSocket只接受文档与二进制数据。

根据用户的id,获取用户的类型,传递不同的内容就行了,建立的链接本身只是用来传递信息用的

红黑树

1、节点可以是红色或者黑色

2、不会有两个连续的红色

3、从任何一个节点访问子节点时,黑色节点的个数都是相同的

4、根节点和NULL的叶子节点为黑色

5、每个叶子节点都有2个黑色的空节点

浏览器输入url后所处理的内容

1、浏览器输入url,先解析url释放合法

2、浏览器检测是否有缓存,浏览器缓存,系统缓存,路由器缓存,如果有,则直接显示

3、先域名解析DNS,解析对应的ip地址,服务器在80端口监听请求

4、浏览器向服务器发起tcp连接,建立三次握手

5、握手成功后,浏览器向服务器发送http请求,请求数据包

6、服务器收到请求,将数据返回给浏览器

7、浏览器收到http回应

8、浏览器解析响应数据,存入缓存

9、浏览器获取数据嵌入到html资源中

10、浏览器发送异步请求,页面显示渲染效果。

C++/C#区别

C#不支持多继承,C#安全代码中不支持指针,对象只能用new创建。万物皆对象。数组变成了类,数组可以执行查找排序倒置,数组元素放在托管堆内,比C++安全。switch后可以是string。

抛出异常基层System.Exception的对象。没有define,没有模板,有委托,么有全局变量,建议用命名空间包着类,定义类时可以直接赋值。静态构造函数,构造函数只执行一次。垃圾回收机制,gc

更强大的类型转换保护机制,委托、事件、装箱、拆箱、webserver等。

C++创建对象可以用类名,对象名。switch后只能是int,C++允许抛出任何异常,

C#

装箱:值类型转换为引用类型(隐式转换)把数据从堆栈“装到”托管堆中
拆箱:引用类型转换为值类型(显式转换)
mysql命令行

插入alter table table1 add ziduan1 int after ziduan2

修改alter table table1 modify ziduan1 double

alter table table1 change ziduan1 ziduan2 float

查询desc table1

删除alter table drop table

MySQL

SELECT VERSION() DATABASE() ;显示当前系统版本,数据库名称

SELECT * FROM TABLE1 WHERE user != SAM;

SELECT *FROM table1 WHRER user = SAM AND root=phil

SELECT *FROM table WHERE user!=SAM OR root!=phil

SELECT name, IFNULL(id, ‘Unknown’) AS ‘id’ FROM table1;如果为空则显示Unknown

SELECT * FROM table1 LIMIT 1;只查找一条

SELECT * FROM table1 ORDER BY index LIMIT 1;

SELECT CURRENT_DATE();当前日期

mysql -u username -xml -e ‘SELECT * FROM TABLE table1’ > table1.xml

show index form table1;显示出表中的所有索引

MySQL_pconnect:打开一个持久的数据库连接,数据库不是在每次页面加载时被打开一个新连接,

MySQL_connect:在每次页面加载时打开连接,可以用MySQL_close()关闭。

Oracle和MySQL如何选择

Oracle的大型数据库,占用内存空间大,支持大并发访问量,没有知道增长类型,有成熟的管理工具,对事物需要手动提交,需要用到伪列和rownum嵌套查询,数据持久性,收费,有任何服务

MySQL是中小型,占用空间小,并发小,大访问量可以做分表库优化,异步使用知道增长类型,管理工具少,对事物自动提交,limit就可以实现分页,数据库更新或者重启后,丢失数据,权限与主机有关,免费,开源,没有数据恢复服务

并发性

mysql以锁表为主,对资源锁定粒度大,如果对一个会话session加锁时间过长,让其他会话无法更新数据。如果有索引,innoDB开源用行级别锁

oracle用行级别锁,粒度小,不依赖索引

MYSQL的四个数据库引擎:

ISAM:不支持事务处理,也不能够容错

MyISAM:MyISAM是MySQL的ISAM扩展格式和缺省的数据库引擎。MYISAM强调了快速读取操作。MyISAM格式的一个重要缺陷就是不能在表损坏后恢复数据。

InnoDB:InnoDB包括了对事务处理和外来键的支持,

MEMORY:所有的数据都在内存中,数据的处理速度快,但是安全性不高如果重启或者关机,所有数据都会消失。因此,基于MEMORY的表的生命周期很短,一般是一次性的

CSV表,用excel打开

逗号分隔值,或字符分隔值,以纯文本和表格形式来存储数据。

每一条记录都使用特定的分隔符隔开,广泛的应用于存储导入导出联系人,存储任何类型的纯文本数据

优化数据库的查询

对查询进行优化,避免全表扫描,首先应该在where和order by涉及的列上建立索引。

避免在WHERE语句上使用!=, <, >, null值判断,or连接,in和not in也要避免,以及表达式操作,函数操作

用exists代替in

优化查询速度

建立索引,减少表之间的关联,优化sql,让sql利用索引定位,不要做全表查询

简化查询字段,没用的字段不要,对返回结果控制,尽量返回少量数据。利用分页查询,减少返回的数据量

数据库中事务是什么,有哪些特性

事务是访问并可能更新数据库中各个数据项的一个程序单元,事务通常由sql语言或者编程语言发起控制。

事务是恢复和并发控制的基本单位,属性有,原子性,一致性,隔离性,持久性

sql语句执行顺序

FROM、WHRE、GROUP BY、聚合函数、HAVING筛选分组、计算表达式、ORDER BY排序

五中约束类型

主键约束PRIMARY KEY

外键约束FOREIGN KEY

唯一约束UNIQUE

检查约束CHECK

非空约束NOT NULL

数据库执行计划

SQL语句发送到数据库后,需要翻译为执行计划,SQL语句到功能是利用执行计划实现的

数据库执行完全一样的SQL语句时会重用相同的执行计划

建立索引的原则

在大量数据的表上建立索引才有意义

在WHERE子句或者连接条件上经常引用的列上建立索引

在很少或从不引用的字段和逻辑型的字段,不要建立索引

表与表之间的关联关系

一对一

一对多

多对多,需要中间关系表

架构设计

tars、springcloud、dubbo

dubbo相对而言,成熟稳定,文档齐全。缺陷,服务治理,服务限流,突发流量、服务熔断监控,通信方式,监控中心和管理后台没有整合。

springcloud门槛高,功能强大,不成熟

分布式定时任务和一般任务的区别

分布式定时任务一般是多台服务器可以同时跑完任务,效率比一般任务高,可用性比一般任务高,性能强,并发性能高。

高并发,高性能区别与联系

高并发:是访问量,qps、tps、

高性能:响应时间,性能量化指标一般是处理时间。

并发量越大,性能越差,架构合理并发量对性能基本没影响,加机器即可

redis

redis是基于ANSI C语言编写的支持网络,基于内存,可持久化,日志型的key-value数据库,提供多语言的api支持

MongoDB和Redis区别

MongoDB基于分布式文件存储

Redis基于缓存,缓存失效时间最多为7天,超过7天重新生成热点数据。

支持的数据类型

string、hash、列表、集合、有序集合

redis持久化

持久化是把redis数据写入到磁盘中,防止服务器宕机,内存数据丢失。提供了两种持久化方式,rdb、aof

RDB

redis database,rdbsave和rdbload

AOF

append only file, flush append only file

执行服务器会定时任务或者函数,append only file都会被调用,执行write把aof buff中的缓存写入到aof文件中。save根据条件,将aof文件保存到磁盘中。

AOF比RDB更新频率更高,优先使用AOF,AOF比RDB安全更大,RDB性能好。

redis架构模式有哪些

1、单机版,简单,内容有限,处理能力有限,无法高可用

2、主从复制,redis的复制允许客户根据一个redis服务器来创建任意多个slaver,主从服务器会有相同的数据,主服务器会把自己身上的数据同步更新到从服务器上,从而保证从服务器数据一致。

3、哨兵,redis setinel 是一个分布式监控redis主从服务器,在服务器下线时,进行故障转移

监控,不断检测主服务器是否运转正常

提醒,被监控的redis服务器出现问题时,通过api向管理员或其他应用发送通知

自动故障转移

4、代理集群

twemproxy,开元的redis和memcache快速轻量级代理,支持多种hash算法,md5、crc16、jenkins

5、直连集群

无中心架构,无proxy代理,高扩展性,高可用性,部分节点不可用,集群仍可用,自动实现故障修复,,通过投票机制实现slave和master之间的转换。隔离性较差,容易出现相互影响,数据异步复制,不保证数据强一致性

redis分布式锁是怎么实现的

先使用setnx来争抢锁,抢到后,再用expire设置一个过期时间,防止锁忘记释放。

如果setnx和expire之间宕机了,要维护了,如何处理

可以将expire和setnx合成一条指令来执行

redis做异步队列

一般使用lis做为队列,rpush产生消息,lpop消费消息,当lpop没有消息时,要适当sleep

产生一次,消费多次

使用pub、sub主题订阅,发布,订阅模式,实现1:N消息队列

缓存穿透

一般的缓存系统,都是按key去缓存查询,如果不存在对应的value,就应该后端查找db,一些恶意的请求会故意查询不存在的可以,请求量很大,对后端造成很大压力,这就叫做缓存穿透。

避免缓存穿透

对查询结果为空的情况也进行缓存,缓存时间设置短一些,或者该key对应的数据insert后清理缓存

对一定不存在的key进行过滤,可以把所有存在的key放到一个大bitmap中,查询是通过bitmap过滤。

缓存雪崩

当缓存服务器重启或者大量缓存集中在某个时间段失效,这样在失效时,再次访问给后端带来很大的压力,导致雪崩

避免缓存雪崩

在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量,比如某个key只允许一个线程进行查询数据,写缓存,其他线程等待,

做二次缓存,a1为原始缓存,a2为拷贝缓存,a1失效时,可以访问a2,a1失效时间可以设置短,a2设置长

不同的key,设置不同的过期时间,让缓存失效时间尽量均衡。

RPC

随着互联网的发展,网站应用规模变大,常规的垂直架构已经无法应付,分布式服务架构,流动计算架构势在必行。

单一应用架构

一个应用,所有功能部署在一起,减少部署环节和成本,当访问加大,单一应用增加机器带来的效果太小。将应用拆成几个不互相关的应用,提升效率,加速前端页面的web,mvc框架是关键。

当垂直应用变多,应用直接交互不可避免,将核心业务抽出来,作为独立的服务,逐渐形成服务中心,使前端应用能够更快响应需求,此时提高业务复用及整合的RPC分布式服务框架是关键。各团队不需要各自实现一套序列化,反序列化,网络框架,连接池,收发线程,超时处理,状态机等技术,不需要重复劳动。

当服务越来越多,容量的评估,小服务资源的浪费逐渐明晰,需要增加一个调度中心,基于访问压力实时管理集群容量,提高集群利用率,资源调度,管理中心SOA是关键

几个进场内,分布于不同的机器,无法共用内存空间,不同系统之间通讯,机器的横向扩展,需要多台机器组成的集群上部署。

所以需要RPC框架来统一解决上述问题。

解决的三个问题

callID映射,序列号反序列化,网络传输

RPC remote procedure call protocol 远程过程调用协议,是一种通过网络从远程计算机程序上请求服务,而不需要了解网络技术的协议。RPC让程序可以像访问本地系统资源一样,去访问远程系统资源。包括通讯协议,序列号,资源描述,服务框架,性能,语言支持等。

简单来说,就是从一台机器通过参数传递的方式,调用到另外一台电脑上的函数或者方法,并返回得到结果。

框架包含

客户端,服务的调用方

客户端存根,存放服务器的地址信息,将客户端的请求参数信息打包成网络信息,通过网络传输发送给服务器。

服务器存根,接受客户端发来的请求消息并解包,然后调用本地服务进行处理

服务端,服务真正的提供者

过程:

客户端调用本地方法

客户端存根接收到调用后将方法、入参序列号后进行网络传输

客户端存根找到远程服务器地址,并将消息通过网络发送给服务器

服务器存根收到消息后,进行解码,反序列化

服务器存根根据解码结果调用本地服务器进行处理

本地服务器执行具体的业务逻辑,并将结果返回给服务器存根

服务器存根将返回的结果进行序列号并打包通过网络发给客户端

客户端存根和搜到消息,进行反序列化

客户端得到最终结果

建立通信

客户端与服务器建立tcp连接,可以是按需连接,调用结束后断掉;也可以是长连接,多个远程调用共享一个连接,长期持有,不管有无数据包发送。可以配合心跳检测机制,定期检测连接是否存活。

服务寻址

通常提供服务器的地址和端口,指定调用方法或者函数名称,入参,出参,完成一个调用。

服务器角度

服务器启动需要将自己的服务注册到注册中心,以便客户端能够从注册中心查找,服务器停止服务时,需要向注册中心注销停止服务。服务器需定期给注册中心发心跳检测,否则注册中心认为服务停止,就从服务中心去掉该服务。

客户端角度

客户端启动时根据自己订阅的服务向注册中心查找服务器提供的地址信息,当服务器下线时,告诉客户端取消订阅

服务调用

客户端本地通过代理proxy、反射调用调用时,得到返回值,此时需要把返回值发送给服务器,同样需要序列化,将二进制发送给服务器后,服务器收到这些返回值,在进行反序列,恢复内存中的数据,指向相关处理。

动态代理

生成客户端存根和服务端存根需要java动态代理技术,可以使用jdk提供原生的动态代理机制

序列化、反序列化

高效的框架为kryo、fastjson、protobuf

NIO

出于并发考虑,传统的阻塞式io不合适,需要异步的io,java提供了nio的解决方案

注册中心

redis、zookeeper、consul、etcd,

redis是一个key-value存储系统,交换数据非常快,以内存未存储介质,读写效率高,远远超过数据库,支持丰富的类型,因此dubbo利用了redis的value支持map数据类型,将redis的key作为服务器名称和类型,map中的key作为url地址,map中的value作为过期时间,利用判断脏数据,由健康中心删除。

redis机制

redis服务器是事件驱动,需要处理文件事件,时间事件

一个时间事件由时间标识号,时间到达时间,时间处理器

时间事件与实际处理事件并不准时,通常会比设定的时间晚

redis是单线程方式运行,通过i/o多路复用来监听多个套接字,文件事件处理器既实现了高性能的网络通信,又很好的与redis服务器中其他单线程运行的模块进行对接,保证了简单性

redis底层有多种数据类型,字符串,列表,哈希,集合,有序集合,整形,

redis支持落地化持久存储,支持主从复制,数据顺序提交,

单线程,可以开启多个redis进程

主从复制

我们的数据存在数据库中,为了保证数据可靠,一般使用多台数据库服务器做集群,选一台做主服务器master,其余作为从服务器slave

master上数据更新后,根据配置文件配置策略,自动同步到slave上,实现主从复制

主从复制保证了数据可用,数据在多台服务器上都存了,稳定

实现了读写分离

写数据在master上,读数据是在slave上,缓解了master处理读写的压力,提高了数据库服务器的性能

容灾恢复

主机宕机后,就没办法写数据了,其余的slave根据优选策略,选一台晋升为master,其余的成为新slave,如果之前的master恢复后,自动成为slave,实现了容灾恢复

主从复制

需要开启多个redis服务,使用不同的pid,端口号,产生不同的rdb文件,产生不同的日志

master复制给slave时,slave开启成功连接到master,会发一个sync命令

master收到后会开启后台存盘进程,将内存数据持久化到rdb中,持久化完成后master会把文件传递给slave

slave接收到后,可以全景复制,即整个数据库文件加载到内存

增量复制:master会将修改数据的指令传给slave,slave会执行这些命令,完成同步,如果断开连接,再次连接时,也会全景复制

主从复制的缺点

当全景复制数据量很大是,会有时延,slave过多时,繁忙加重

实现高可用的PRC框架

既然采用分布式架构,那一个服务势必会有多个实例,如果获取实例的问题,就需要有一个注册中心,比如在dubbo中,就可用使用zookeeper作为注册中心,调用时,从zookeeper获取服务的实例列表,再从中获取一个进行调用。

如何选择实例呢,就需要考虑负载均衡

每次去注册中心查询效率很低,就需要增加缓存

客户端每次总不能一直等待服务器返回结果,需要异步调用。

服务器修改了,老的接口一直再用,需要版本控制

服务器不能一收到请求就开一个线程去处理,需要有线程池。

python

1)re.VERBOSE

忽略正则表达式字符 串中的空白符和注释,向 re.compile() 传入变量 re.VERBOSE

2)re.IGNORECASE

要让正则表达式 不区分大小写,可以向 re.compile()传入 re.IGNORECASE 或 re.I,作为第二个参数

?匹配零次或一次前面的分组。

*匹配零次或多次前面的分组。

+匹配一次或多次前面的分组。

{n}匹配 n 次前面的分组。

{n,}匹配 n 次或更多前面的分组。

{,m}匹配零次到 m 次前面的分组。

{n,m}匹配至少 n 次、至多 m 次前面的分组。

{n,m}?或*?或+?对前面的分组进行非贪心匹配。

‘^spam’ 意味着字符串必须以 spam 开始。

'spam$'意味着字符串必须以 spam 结束。

.匹配所有字符,换行符除外。

\d、\w 和\s 分别匹配数字、单词和空格。

\D、\W 和\S 分别匹配出数字、单词和空格外的所有字符。

[abc]匹配方括号内的任意字符(诸如 a、b 或 c)。

[^abc]匹配不在方括号内的任意字符。

#This is a program

print(‘hello world’) #打印字符串

print(70) #打印整形

print('What is your name? ’ ‘boy’) #打印两个字符串连接

print(‘my name is :’,‘good boy’) #打印两个字符串对象

strGirl = 'good girl is ’

iAge = 18

print(strGirl, iAge) #打印两个任意对象

print(‘www’, ‘baidu’, ‘com’, sep=’.’) #用分隔符连接字符串

‘’’

print('please input your name: ')

myName = input() #命令行输入参数

print('Nice to meet you, ’ + myName)

print(len(myName)) #获取字符串长度

print('please input you age: ')

myAge = input()

print(‘your name is :’, myAge)

#while循环 if/else判断

strName = ‘’

while strName != ‘like’:

print('input your name:')

strName = input()

else:

print('name is good !')

print(‘please input your age’)

strAge = input()

if int(strAge) <= 30: #input()获取的输入为字符串

print('you so young')

elif int(strAge) > 30 and int(strAge) < 40:

print('you young')

else:

print('old')

‘’’

#for循环

print(’\r\n’)

for i in range(10):

print(i)

print(’\r\n’)

for i in range(0, 10, 2):

print(str(i))

print(’\r\n’)

for i in range(9, -10, -2):

print(i)

print(’\r\n’)

#随机数

import random

import sys

for i in range(2):

number = random.randint(1, 10) #获取1-10内的随机数包含1和10

if number == 2:

    sys.exit() #结束程序

else:

    print(number)

‘’’

print(’\r\n’)

while True:

spam = input() 

if spam == 'e': #input()获取的输入为字符串

    break

elif int(spam) == 1: #需要强转

    print('hello')

elif int(spam) == 2:

    print('Howdy')

else:

    print('Greetings')

‘’’

print(’\r\n’)

#函数

def helloPeople(name):

print('hello ', name)

helloPeople(123)

helloPeople(‘234’)

#try except

‘’’

def collatz(number):

if(number % 2 == 0):

    print(number // 2)

    return (number // 2)

else:

    print(3 * number + 1)

    return (3 * number + 1)

print(‘please input a number:’)

while True:

try :

    intInput = int(input())

    break

except ValueError: #值错误

    print('please input a new number:')

    continue

while True:

intInput = collatz(intInput)

if(intInput == 1):

    break

‘’’

print(’\r\n’)

#列表

inputBuff = []

i = 0

while i < 3 :

print('please input your name')

name = input()

if(name != ''):

    inputBuff += [name]

i += 1

print(inputBuff)

for strBuff in inputBuff:

print(strBuff)

print(’\r\n’)

strList = [‘123’, ‘234’, ‘345’]

for istr in range(len(strList)): #便利列表

print(strList[istr]) #以数组模式获取每个列表项

‘’’

if inputBuff in strList: #判断字符串是否在字符串列表中

print('have ' + inputBuff +' in old list')

‘’’

print(’\r\n’)

strList = [‘1’, ‘2’, ‘3’]

a, b, c = strList #将列表项分别赋值给对象

print(‘a=’ + a + ’ b=’ + b + ’ c=’ + c)

print(’\r\n’)

strList.append(‘4’) #列表添加

strList.insert(1, ‘5’) #列表插入

strList.remove(‘1’) #列表删除

print(strList)

listInt = [1, 9, 5, -4, 0, -9]

listInt.sort()

print(listInt)

print(’\r\n’)

strChar = [‘a’, ‘r’, ‘c’, ‘A’, ‘Z’, ‘D’]

strChar.sort(reverse=True) #逆序

print(strChar)

#使用,连接列表项

print(’\r\n’)

spam = [‘apple’, ‘banana’, ‘tofu’, ‘cats’]

def Function(listSrc):

strOut = ''

iLen = len(listSrc)

for i in range(iLen):

    if i != iLen - 1:

        strOut += listSrc[i] + ', '

    else:

        strOut += 'and ' + listSrc[i]

print(strOut)

Function(spam)

#按要求打印图案

grid = [[’.’, ‘.’, ‘.’, ‘.’, ‘.’, ‘.’],

    ['.', '0', '0', '.', '.', '.'],

    ['0', '0', '0', '0', '.', '.'],

    ['0', '0', '0', '0', '0', '.'],

    ['.', '0', '0', '0', '0', '0'],

    ['0', '0', '0', '0', '0', '.'],

    ['0', '0', '0', '0', '.', '.'],

    ['.', '0', '0', '.', '.', '.'],

    ['.', '.', '.', '.', '.', '.'],]

for i in range(len(grid[0])):

str = ''

for j in range(len(grid)):

    str += grid[j][i]

print(str)

import logging #使用log打日志

import traceback

logging.basicConfig(level=logging.DEBUG, format=’ %(asctime)s - %(levelname)s - %(message)s’)

#将日志保存到log.txt中

#logging.basicConfig(filename=‘log.txt’, level=logging.DEBUG, format=’ %(asctime)s - %(levelname)s - %(message)s’)

#logging.disable(logging.CRITICAL) #关闭所有该语句之后的日志

logging.debug(‘start file progress’)

logging.info(‘test error info code %s’ % 123)

strTest = ‘abc’

logging.error(‘test error code %s’ % strTest)

def tryFunction(iAllCount, iBreakCount):

iOut = 0

for i in range(iAllCount):

    if i % 2 != 0:

        iOut *= 2

    if i == iBreakCount:

        # assert i != iBreakCount, 'i == %d' % iBreakCount

        print('i == %d' % iBreakCount)

        

    if i == 100:

        raise Exception('i == 100')

    print(str(i))

try:

tryFunction(120, 50)

except Exception as err:

print('error: ' + str(err))

import os

import shelve

import shutil

import zipfile

strPath = r’c:\windows\123.exe’

print(os.path.basename(strPath)) #文件名

print(os.path.dirname(strPath)) #路径名

print(’\r\n’)

strList = os.path.split(strPath) #将路径名和文件名分隔到列表中

for i in strList:

print(i)

print(’\r\n’)

strList1 = strPath.split(os.path.sep) #将路径用\划分并获取到列表中

for i in strList1:

print(i)

print(’\r\n’)

fileList = [‘123.txt’, ‘abc.exe’]

for ifile in fileList: #组合路径和文件名

filepath = os.path.join(r'D:\06_MakeDataPackage\01_MakeDataPackageDll', ifile)

print(filepath)

print(’\r\n’)

for ifile in fileList: #组合路径和文件名

fileCurPath = os.path.join(os.getcwd() + ifile)

print(fileCurPath)

print(’\r\n’)

bAbsPath = os.path.isabs(os.getcwd()) #是否是绝对路径

print(str(bAbsPath))

print(’\r\n’)

strRelPath = os.path.relpath(os.getcwd(), ‘D:\’) #相对于D盘的 相对路径

print(strRelPath)

print(’\r\n’)

iFileSize = os.path.getsize(os.getcwd() + r’\文件.py’) #获取文件大小

print(str(iFileSize))

print(’\r\n’)

iDirSize = 0

for i in os.listdir(os.getcwd()): #获取文件夹大小

iDirSize += os.path.getsize(os.path.join(os.getcwd(), i))

print(str(iDirSize))

print(’\r\n’)

bFileExists = os.path.exists(os.path.join(os.getcwd(), ‘1.txt’)) #文件是否存在

print(str(bFileExists))

print(’\r\n’)

bIsFile = os.path.isfile(os.getcwd()) #是否是文件

print(str(bIsFile))

print(’\r\n’)

bIsFile = os.path.isdir(os.getcwd()) #是否是文件夹

print(str(bIsFile))

print(’\r\n’)

hFile = open(os.path.join(os.getcwd(), ‘1.py’))

strFileData = hFile.read() #读取整个文件

hFile.close()

print(strFileData)

print(’\r\n’)

hFile = open(os.path.join(os.getcwd(), ‘1.py’))

strFileData = hFile.readlines() #一行一行读取,并存入列表中

hFile.close()

print(strFileData)

print(’\r\n’)

hFile = open(“test.txt”, ‘w’)

hFile.write(‘hello world’) #写入内容

hFile.close()

print(’\r\n’)

hFile = open(‘test.txt’, ‘a’) #追加内容

hFile.write(‘append something’)

hFile.close()

hFile = open(‘test.txt’, ‘r’)

strRead = hFile.read() #读取内容

print(strRead)

hFile.close()

print(’\r\n’)

shelveFile = shelve.open(‘testData’) #json中的shelve文件类似C++的ini文件

strList = [‘cat’, ‘dog’, ‘pook’]

strList1 = [‘cat1’, ‘dog1’, ‘pook1’]

shelveFile[‘key1’] = strList

shelveFile[‘key2’] = strList1

shelveFile.close()

shelveFile = shelve.open(‘testData’)

strKey = list(shelveFile.keys())

print(strKey)

listValues = list(shelveFile.values())

print(listValues)

shelveFile.close()

‘’’

os.makedirs(os.getcwd() + r’\123’) #创建文件夹

print(’\r\n’)

#拷贝文件为另一个文件

strDest = shutil.copy(os.path.join(os.getcwd(), ‘1.py’), os.path.join(os.getcwd(), r’\1附件.py’))

print(strDest)

print(’\r\n’)

#拷贝文件到一个目录(目录必须存在)

strDest = shutil.copy(os.path.join(os.getcwd(), ‘1.py’), ‘c:\’)

print(strDest)

print(’\r\n’)

#拷贝文件夹

strDest = shutil.copytree(r’c:\Intel’, r’c:\123’)

print(strDest)

print(’\r\n’)

#拷贝文件夹

strDest = shutil.move(r’c:\Intel\1.txt’, ‘c:\’)

print(strDest)

#删除单个文件 os.unlink(path)

#删除空文件夹 os.rmdir(path)

#删除非空文件夹 shutil.rmtree(path)

#将文件删除到回收站

#import send2trash

#send2trash.send2trash(path)

print(’\r\n’)

for i in os.listdir(r’c:\123’):

if i.endswith('.txt'):

    os.unlink(i) 

‘’’

#便利文件夹下所有文件、文件夹 os.walk(path)

for folderName, subfolders, filenames in os.walk(‘c:\SDCSafeFiles’):

print(folderName)

print(subfolders)

print(filenames)

print('\r\n')

print(’\r\n’)

#获取zip压缩文件信息

handleZipFile = zipfile.ZipFile(‘c:\SDCSafeFiles\1.zip’)

fileList = handleZipFile.namelist()#列出压缩文件中的文件

print(fileList)

fileInfo = handleZipFile.getinfo(‘package1.csv’)#获取压缩文件中文件信息

iFileSize = fileInfo.file_size #获取压缩文件中文件大小

print(str(iFileSize))

handleZipFile.extractall(‘c:\SDCSafeFiles\123’) #将压缩文件解压到制定文件夹

handleZipFile.close()

print(’\r\n’)

#将文件压缩为zip文件

handleZipFile1 = zipfile.ZipFile(‘c:\SDCSafeFiles\newZip.zip’, ‘w’) #新建压缩文件,'a’追加到压缩文件中

handleZipFile1.write(‘c:\SDCSafeFiles\xiePin.rcd’, compress_type=zipfile.ZIP_DEFLATED)

handleZipFile1.close()

import sys

#元组

print(type((‘123’,))) #tuple

print(type((‘123’))) #str

listBuff1 = [‘cat’, 12, 23.02]

print(listBuff1)

print(tuple(listBuff1)) #列表转换为元组

#字典

#统计所有字典元素的个数

dictInput1 = {‘blackball’: 5, ‘whiteball’: 4, ‘redball’: 2} #字典的键和值,可以为任意类型,不只是整数

dictInput2 = {‘blackball’: 8, ‘greenball’: 1, ‘blueball’: 9}

def Function(dictInput):

print('inputBuff:')

icount = 0

for i, k in dictInput.items():

    print(i + 'have ' + str(k) + ' count')

    icount += k

print('total count ' + str(icount))

Function(dictInput1)

Function(dictInput2)

print(’\r\n’)

#列表中个数添加到字典中

import pprint

dictOld = {‘gold coin’: 42, ‘rope’: 1}

bInOrNot = ‘rope’ in dictOld.keys() #检查字典的键中是否包含

print(bInOrNot)

print(2 in dictOld.values()) #检查字典的值中是否包含

print(dictOld.get(‘gold coin’, 333)) #get(,)获取某个键,如果存在则返回键的值,如果不存在则返回默认值(第二个参数)

print(dictOld.get(‘abc’, 333))

for i in dictOld.items(): #item()返回字典中键和值组成的元组 (‘gold coin’, 42) (‘rope’, 1)

print(i)

listdragonloot = [‘gold coin’, ‘dagger’, ‘gold coin’, ‘gold coin’, ‘ruby’]

def Function(dictInput, listInput):

for i in listdragonloot:

    dictInput.setdefault(i, 0) #设置字典中某一个键的默认值

    dictInput[i] += 1

pprint.pprint(dictInput)

Function(dictOld, listdragonloot)

print(’\r\n’)

#字典例子

import pprint

dict = {‘1’: ‘123’, ‘2’: ‘234’}

for i in dict.keys():

print(i)

for j in dict.values():

print(j)

for k in dict.items():

print(k)

print(’\r\n’)

#统计字符出现的个数

strBuff = ‘It was a bright cold day in April, and the clocks were striking thirteen.’

dictCount = {}

for charOne in strBuff:

dictCount.setdefault(charOne, 0)

dictCount[charOne] += 1

pprint.pprint(dictCount) #pprint漂亮的打印

dictPeopleHaveFood = {‘zhangsan’:{‘apple’:2, ‘banana’:1, ‘orange’:3},

                  'lisi': {'youzi':1, 'lizi':10}}

print(’\r\n’)

#统计水果个数

dictFoodCount = {}

for l in dictPeopleHaveFood.values():

for k in l:

    dictFoodCount.setdefault(k, 0)

    dictFoodCount[k] += l[k]

pprint.pprint(dictFoodCount)

sys.exit()

import re

import sys

phonenumber = re.compile(r’\d{3}-\d{3}-\d{4}’) #定义正则表达式为3个数字-3个数字-4个数字

number = phonenumber.search(‘my phone is 123-234-8888’)

print(number.group()) #将符合正则表达式的字符串输出

print(’\r\n’)

phonenumber = re.compile(r’(\d{3})-(\d{3})-(\d{4})’)

number = phonenumber.search(‘my phone is 123-234-8888’)

print(number.group(0)) #group(0) 则获取整个字符串,等同于不传参数

print(number.group(1)) #第一个元组

print(number.group(2)) #第二个元组

print(number.group(3)) #第三个元组

print(’\r\n’)

part1, part2, part3 = number.groups() #将列表赋值给变量

print(part1)

print(part2)

print(part3)

print(’\r\n’)

select1 = re.compile(r’[0-9a-z]’) #0-9之间的数字 a-z之间的字母

select2 = re.compile(r’[^0-9a-z]’) #除了 0-9之间的数字 a-z之间的字母 之外的字符

buff1 = select1.search(‘32424werwersdsfsASFDSFFD’) #找到第一个3符合正则表达式就返回

buff2 = select1.findall(‘32424werwersdsfsASFDSFFD’) #找到所有匹配的字符

buff3 = select2.findall(‘32424werwersdsfsASFDSFFD’) #找到所有不匹配的字符

print(buff1.group())

print(buff2)

print(buff3)

print(’\r\n’)

select3 = re.compile(r’<123.*>’) # .*贪心模式,匹配尽可能多的字符,遇到最后一个>结尾才结束

buff4 = select3.search(’<123 43345> 234453>’) #以<123开头,后面跟任意字符,以>结尾

print(buff4.group())

print(’\r\n’)

select4 = re.compile(r’<123.*?>’) #非贪心模式,匹配尽可能少的字符,遇到>结尾就结束

buff5 = select4.search(’<123 43345> 234453>’)

print(buff5.group())

print(’\r\n’)

select5 = re.compile(’.*’) #不包含换行 遇到换行就结束

buff6 = select5.search(‘happy birthday to the old’

                 '\nbig mother').group()

print(buff6)

print(’\r\n’)

select6 = re.compile(’.*’, re.DOTALL) #包含换行 匹配所有字符

buff7 = select6.search(‘happy birthday to the old’

                 '\nbig mother').group()

print(buff7)

sys.exit()

import sys

print(‘I’m is luckcy \nboy!’)

print(r’I’m is luckcy \nboy!’) #原始字符串,字符串中的 \,\n也将原样打印出来

print(’’'Dear wifi:

\tI’m so love of you!

Best wishes to you!

\t\t\tlike’’’) #’’'多行字符串 \t制表符

strBuff = r’I’m is luckcy \nboy!’

print(strBuff[0]) #第一个字符I

print(strBuff[-1]) #最后一个字符!

print(strBuff[0:5]) #I’m

print(strBuff[:5]) #I’m

print(strBuff[5:]) #is luckcy \nboy!

print(’\r\n’)

print(‘boy’ in strBuff)

print(‘23’ in strBuff) #是否在字符串中

print(strBuff.lower()) #转小写 i’m is luckcy \nboy!

print(strBuff.upper()) #转大写 I’M IS LUCKCY \NBOY!

print(strBuff.islower()) #判断是否是小写

print(strBuff.isupper()) #判断是否是大写

print(’\r\n’)

print(‘hello’.isalpha()) #字符串只包含字母,且非空

print(‘123’.isdecimal()) #字符串只包含数字

print(‘hello123’.isalnum()) #字符串只包含字母和数字

print(’ '.isspace()) #字符串只包含空格、字表副、换行

print(‘This Is Good!’.istitle()) #是否所有的单词首字母都是大写

print(‘This is good!’.startswith(‘This’)) #是否以某个字符串开头

print(‘This is good!’.endswith(‘good!’)) #是否以某个字符串结尾

print(’—’.join([‘abc’, ‘def’, ‘ghijk’])) #将字符串列表中的各项字符以—连接

print(‘123 234 345’.split()) #将字符串默认以空格进行分隔,开并传入列表中

print(‘123;234;345’.split(’;’)) #将字符串以;号隔开,并传入列表汇总

print(‘hello’.rjust(10)) #右对齐,插入空格

print(‘hello’.ljust(10, '’)) #左对齐,插入

print(‘hello’.center(10, ‘=’)) #居中对齐,插入=

print(’ hello '.strip()) #删除左右空白字符

print(’ hello '.lstrip())#删除左空白字符

print(’ hello '.rstrip())#删除右空白字符

sys.exit()

intList={}

def printTable(tableData):

iColumn = len(tableData[0])

iRow = len(tableData)

for i in range(iColumn):

    iMaxLen = 0;

    for j in range(iRow):

        if iMaxLen < len(tableData[j][i]):

            iMaxLen = len(tableData[j][i])

            intList[i] = iMaxLen

            

for i in range(len(intList)):

    print(str(intList[i]))



for i in range(len(tableData)):

    strLine = ''

    for j in range(len(tableData[i])):

        strOut = tableData[i][j].rjust(intList[j])

        strLine += strOut

        strLine += ' '

    print(strLine)

tableDataSrc = [[‘apples’, ‘oranges’, ‘cherries’, ‘banana’],

            ['alice', 'bob', 'cherriescarol', 'david'],

            ['dogs', 'cats', 'moose', 'gooosesss']]

printTable(tableDataSrc)
上文提到了工具链的打通,那么工具自然就需要做好准备。现将工具类型及对应的不完全列举整理如下:

代码管理(SCM):GitHub、GitLab、BitBucket、SubVersion

构建工具:Ant、Gradle、maven

自动部署:Capistrano、CodeDeploy

持续集成(CI):Bamboo、Hudson、Jenkins

配置管理:Ansible、Chef、Puppet、SaltStack、ScriptRock GuardRail

容器:Docker、LXC、第三方厂商如AWS

编排:Kubernetes、Core、Apache Mesos、DC/OS

服务注册与发现:Zookeeper、etcd、Consul

脚本语言:python、ruby、shell

日志管理:ELK、Logentries

系统监控:Datadog、Graphite、Icinga、Nagios

性能监控:AppDynamics、New Relic、Splunk

压力测试:JMeter、Blaze Meter、loader.io

预警:PagerDuty、pingdom、厂商自带如AWS SNS

HTTP加速器:Varnish

消息总线:ActiveMQ、SQS

应用服务器:Tomcat、JBoss

Web服务器:Apache、Nginx、IIS

数据库:MySQL、Oracle、PostgreSQL等关系型数据库;cassandra、mongoDB、redis等NoSQL数据库

项目管理(PM):Jira、Asana、Taiga、Trello、Basecamp、Pivotal Tracker

敏捷开发四条原则

1、递增,不是连续的,交付的软件是一小部分一小部分的。

2、避免不必要的开销,减少项目计划,文档,面对面交流

3、协作,负责人解决所有能够解决的问题

4、说真话,承认问题要有信心,然后去解决问题。

makefile

SRC=$(wildcard ./*.c)

OBJ=$(patsubst %.c, %.o, $(SRC))

ALL:hello.out

hello.out : $(OBJ)

gcc $< -o $@

$(OBJ) : $(SRC)

gcc -c $< -o $@

.PHONY:clean

clean:

-rm -f $(OBJ)

TCP保证可靠性主要依靠下面7种机制:

1、检验和

TCP检验和的计算与UDP一样,在计算时要加上12byte的伪首部,检验范围包括TCP首部及数据部分,但是UDP的检验和字段为可选的,而TCP中是必须有的。计算方法为:在发送方将整个报文段分为多个16位的段,然后将所有段进行反码相加,将结果存放在检验和字段中,接收方用相同的方法进行计算,如最终结果为检验字段所有位是全1则正确(UDP中为0是正确),否则存在错误。

2、序列号

TCP将每个字节的数据都进行了编号,这就是序列号。

序列号的作用:

a、保证可靠性(当接收到的数据总少了某个序号的数据时,能马上知道)

b、保证数据的按序到达

c、提高效率,可实现多次发送,一次确认

d、去除重复数据

数据传输过程中的确认应答处理、重发控制以及重复控制等功能都可以通过序列号来实现

3、确认应答机制(ACK)

TCP通过确认应答机制实现可靠的数据传输。在TCP的首部中有一个标志位——ACK,此标志位表示确认号是否有效。接收方对于按序到达的数据会进行确认,当标志位ACK=1时确认首部的确认字段有效。进行确认时,确认字段值表示这个值之前的数据都已经按序到达了。而发送方如果收到了已发送的数据的确认报文,则继续传输下一部分数据;而如果等待了一定时间还没有收到确认报文就会启动重传机制。

正常情况下的应答机制:

4、超时重传机制

当报文发出后在一定的时间内未收到接收方的确认,发送方就会进行重传(通常是在发出报文段后设定一个闹钟,到点了还没有收到应答则进行重传),其基本过程如下:

当然,未收到确认不一定就是发送的数据包丢了,还可能是确认的ACK丢了:

当接收方接收到重复的数据时就将其丢掉,重新发送ACK。而要识别出重复的数据,就要用到前面提到的序列号了,利用序列号很容易就可以做到去重的效果。

重传时间的确定:报文段发出到收到应答中间有一个报文段的往返时间RTT,显然超时重传时间RTO会略大于这个RTT,TCP会根据网络情况动态的计算RTT,即RTO是不断变化的。在Linux中,超时以500ms为单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍。其规律为:如果重发一次仍得不到应答,就等待2500ms后再进行重传,如果仍然得不到应答就等待4500ms后重传,依次类推,以指数形式递增,重传次数累计到一定次数后,TCP认为网络或对端主机出现异常,就会强行关闭连接。

5、连接管理机制

连接管理机制即TCP建立连接时的三次握手和断开连接时的四次挥手。

首先三次握手:

6、流量控制

接收端处理数据的速度是有限的,如果发送方发送数据的速度过快,导致接收端的缓冲区满,而发送方继续发送,就会造成丢包,继而引起丢包重传等一系列连锁反应。

因此TCP支持根据接收端的处理能力,来决定发送端的发送速度,这个机制叫做流量控制。

在TCP报文段首部中有一个16位窗口长度,当接收端接收到发送方的数据后,在应答报文ACK中就将自身缓冲区的剩余大小,放入16窗口大小中。这个大小随数据传输情况而变,窗口越大,网络吞吐量越高,而一旦接收方发现自身的缓冲区快满了,就将窗口设置为更小的值通知发送方。如果缓冲区满,就将窗口置为0,发送方收到后就不再发送数据,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端。

其过程如下:

注意:窗口大小不受16位窗口大小限制,在TCP首部40字节选项中还包含一个窗口扩大因子M,实际窗口大小是窗口字段的值左移M位。

7、拥塞控制

流量控制解决了 两台主机之间因传送速率而可能引起的丢包问题,在一方面保证了TCP数据传送的可靠性。然而如果网络非常拥堵,此时再发送数据就会加重网络负担,那么发送的数据段很可能超过了最大生存时间也没有到达接收方,就会产生丢包问题。

为此TCP引入慢启动机制,先发出少量数据,就像探路一样,先摸清当前的网络拥堵状态后,再决定按照多大的速度传送数据。

此处引入一个拥塞窗口:

发送开始时定义拥塞窗口大小为1;每次收到一个ACK应答,拥塞窗口加1;而在每次发送数据时,发送窗口取拥塞窗口与接送段接收窗口最小者。

慢启动:在启动初期以指数增长方式增长;设置一个慢启动的阈值,当以指数增长达到阈值时就停止指数增长,按照线性增长方式增加;线性增长达到网络拥塞时立即“乘法减小”,拥塞窗口置回1,进行新一轮的“慢启动”,同时新一轮的阈值变为原来的一半。

“慢启动”机制可用图表示:

linux

查看某个端口的占用netstat + grep

查看共享内存ipcs -m

查看进程句柄 ps -ef | grep 进程名称

查看句柄数量ls -l /proc/进程ID/fd | wc -l

网络连接状态netstat -anp |grep :8096

1:抓取所有经过eth0的网络数据包,当前eth0为10.0.10.125:

#tcpdump –i eth0 host 10.0.10.125

2:抓取所有源地址为eth0的网络数据包

#tcpdump –i eth0 src host 10.0.10.125

3:抓取所有目标地址为eth0的网络数据包

#tcpdump –i eth0 dst host 10.0.10.125

epoll,poll,select这些。说说他们的原理,以及epoll和select有哪些不同点。

epoll,poll和select都是linux下I/O多路复用的实现,可以实现单线程管理多个连接,select是基于轮询的,轮询连接的状态,返回I/O状态,poll和select的原理基本相同,只是poll没有最大连接数的限制,因为它是基于链表的,而select是基于数组的,有最大连接数的限制。epoll和那两者的区别是,epoll不是基于轮询的检查,而是为每个fd注册回调,I/O准备好时,会执行回调,效率比select(32位1024。64位2048)和poll高很多。

select

(1)单进程可以打开fd有限制;

    (2)对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低;

    (2)用户空间和内核空间的复制非常消耗资源;

poll

同步多路IO复用

  调用过程和select类似

  时间复杂度:O(n)

  其和select不同的地方:采用链表的方式替换原有fd_set数据结构,而使其没有连接数的限制。

epoll的工作方式

epoll的两种工作方式:1.水平触发(LT)2.边缘触发(ET)

LT模式:若就绪的事件一次没有处理完要做的事件,就会一直去处理。即就会将没有处理完的事件继续放回到就绪队列之中(即那个内核中的链表),一直进行处理。

ET模式:就绪的事件只能处理一次,若没有处理完会在下次的其它事件就绪时再进行处理。而若以后再也没有就绪的事件,那么剩余的那部分数据也会随之而丢失。

由此可见:ET模式的效率比LT模式的效率要高很多。只是如果使用ET模式,就要保证每次进行数据处理时,要将其处理完,不能造成数据丢失,这样对编写代码的人要求就比较高。

注意:ET模式只支持非阻塞的读写:为了保证数据的完整性。

void * memcpy(void *dst, const void * src, size_t size)

{

if(dest == NULL || src == NULL)

return NULL;



unsigned char* pdest = (unsigned char*)dst;

unsigned char* psrc = (unsigned char*)src;

while(size-- > 0)

{

    *pdest = *psrc;    

      pdst++;

      psrc++;

}

return dst;

}

class node

{

public:

                    ListNode* merge(ListNode* phead1, ListNode* phead2)

        {

        if(phead1 == NULL)

            return phead2;

        if(phead2 == NULL)

            return phead1;

        if(phead1->val <= phead2->val)

            phead1->next = merge(phead1->next, pHead2);

             return pHead1;

        else

        {

            phead2->next = merge(phead2->next, phead1);

            return pHead2;

        }

        

    }

}

void remove(struct linknode*delete)

{

linknode* temp = head;

if(temp == null)

    return;

if(delete == head)

    temp.next.prev = null

    delete head;

    return;

while(temp.next != delete)

    temp = temp.next;

temp.next = delte.next;

delete.next.prev=temp;

delete delete;

return;

}

下图是c/c++的进程的内存分布布局图,搞清楚内存布局对于理解一个程序是非常重要的。

?? 一个程序运行起来,操作系统会给每个进程分配一个 4G 的程序地址空间,当然这都是虚拟地址空间,因为如果一个进程分 4G 的内存,那么就算有再多的内存也不够分。

?? 这些虚拟地址空间中的内存分段都是什么意思呢?下面进行讲解:

这里写图片描述

C/C++语言基础

extern 关键字作用 参考链接

extern声明变量或者函数时,它告诉编译器去其他文件中寻找定义或者实现。

extern “C”的作用:为了实现C++、C的混合编程,使C++中能够调用C写的函数。它告诉C++编译器按照C的编译、链接规范来编译。因为C++编译器为了实现函数重载的功能,对函数名的编译和C编译器不一样,所以要加上extern “C”.

static关键字作用 参考链接

一种是面向过程的程序设计中的static,不涉及类。修饰变量时表示的是一个静态变量,在全局数据区分配内存,只在文件内可见,而文件之外是不可见的。修饰函数中表示静态函数,不能在其他文件中使用。

一种是面向对象程序设计中的static, 涉及到类。在类的数据成员前面加上static关键字,就是类的静态成员变量,是属于类的,在全局数据区分配内存,被所有的对象共享。在类的函数前面加上static,表示静态成员函数。不和任何对象有联系,没有this指针。

volatile关键字的作用 参考链接

volatile是一种类型修饰符,遇到这个关键字声明的变量, 编译器对访问该变量的代码不再进行优化。访问寄存器要比访问内存块,因此CPU总是优先访问寄存器。但是有时候可能内存中的数据发生了变化,而寄存器还保存原来的值。为了防止这种情况,使用volatile来声明变量时,系统总是从内存中读取数据,而不会从寄存器中读取。

const关键字的作用

const关键字所修饰的表示的是一个常量。取代C中的宏定义,声明的时候必须初始化。const修饰的变量不可以被修改。

const修饰指针时。const int * 和int * const

const 修饰引用或指针做函数的形参

const 修改引用或指针做函数的返回值

const修饰成员变量时,必须在构造函数列表中初始化

const修饰成员函数时,说明该函数不应该修改非静态成员

new与malloc的区别

new 分配内存按照数据类型进行分配,malloc分配内存按照大小分配。

给对象分配内存时,new 会调用构造函数,而malloc不会

new返回的是指向对象的指针,而malloc返回的是(void * )

new是一个操作符可以重载,而malloc是一个库函数

new分配的内存用delete销毁,malloc用free销毁。delete销毁时调用析构函数,而free不会。

new如果分配失败会抛出bad_malloc异常,而malloc失败会返回NULL。

malloc分配内存不够时,可以用realloc扩容,new不可以。

#define和const定义常量的区别

define宏是在预处理阶段展开,const常量是在编译运行时候使用。

define宏不做类型检查,仅仅是展开替换。const常量有具体的类型,编译的时候执行类型检查。

const定义的变量需要分配内存,在常量区分配。define定义的不占有内存。

指针和引用的区别

指针保存的是对象的地址,引用是对象的别名

指针需要通过解引用来间接访问,而引用是直接访问。

引用在定义的时候必须初始化,而指针则不需要。

指针在赋值后还可以改变,而引用不能改变。

有常量指针,而没有常量引用.

结构体中内存对齐?

从0位置开始存储

变量存储的起始位置是该变量大小的整数倍

结构体总的大小是其最大元素的整数倍,不足的后面要补齐

结构体中包含结构体,从结构体中最大元素的整数倍开始存

如果加入pragma pack(n) ,取n和变量自身大小较小的一个

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

宏定义在预编译的时候就会进行宏替换

内联函数在编译阶段,在调用内联函数的地方进行替换,减少了函数的调用过程,但是使得编译文件变大。因此,内联函数适合简单函数,对于复杂函数,即使定义了内联编译器可能也不会按照内联的方式进行编译。

内联函数相比宏定义更安全,内联函数可以检查参数,而宏定义只是简单的文本替换。因此推荐使用内联函数,而不是宏定义。

使用宏定义函数要特别注意给所有单元都加上括号,#define MUL(a, b) a * b,这很危险,正确写法:#define MUL(a, b) ((a) * (b))

模板特例化

模板特化分为全特化和偏特化,模板特化的目的就是对于某一种变量类型具有不同的实现,因此需要特化版本。例如,在STL里迭代器为了适应原生指针就将原生指针进行特化

纯虚函数的介绍?

纯虚函数是用在虚函数后加上 = 0来声明的。

纯虚函数只提供声明,没有实现。纯虚函数是起到声明接口的作用。

声明虚函数的类是抽象类,不能实例化为对象。继承抽象类的子类必须重写类的纯虚函数。否则该子类还是抽象类。

虽然抽象类不能实例化,但是抽象类可以有构造函数。

C++多态性与虚函数表

C++多态的实现?

多态分为静态多态和动态多态。静态多态是通过重载和模板技术实现,在编译的时候确定。动态多态通过虚函数和继承关系来实现,执行动态绑定,在运行的时候确定。

虚函数的作用

1.虚函数用于实现多态。

2.虚函数在设计上还具有封装和抽象的作用。比如抽象工厂模式。

谈谈虚函数表?

基类指针在调用所指对象的虚函数时,就会去查找该对象的虚函数表。虚函数表的地址在每个对象的首地址。查找该虚函数表中该函数的指针进行调用。

每个对象中保存的只是一个虚函数表的指针,C++内部为每一个类维持一个虚函数表,该类的对象的vptr指针都指向这同一个虚函数表。

虚函数表中为什么就能准确查找相应的函数指针呢?因为继承的时候,派生类的虚函数表直接从基类也继承过来,如果覆盖了其中的某个虚函数,那么虚函数表的指针就会被替换,因此可以根据指针准确找到该调用哪个函数。

为什么要有虚析构函数?

当基类指针指向派生类对象时,delete指针销毁对象时,如果析构函数没有定义为虚析构函数,则会调用基类的析构函数,显然只能销毁部分数据。如果要调用所指对象的析构函数,就需要将该对象的析构函数定义为虚函数,销毁时通过虚函数表找到对应的析构函数。

构造函数可以是虚函数吗?

不可以。因为虚函数的执行依赖于虚函数表,调用虚函数需要通过对象中指向虚函数表的vptr指针。而对象的vptr指针是在构造函数中初始化的。所以,如果构造函数是虚函数,构造对象时,vptr指针还没有初始化,将没办法进行。

析构函数能够抛出异常吗?

如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。

通常异常发生时,c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题。

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

const常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面

引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面

没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化

C++11新特性

智能指针怎么实现的?

构造函数中计数初始化为1

拷贝构造函数中计数值加1

赋值运算符中,左边的对象引用计数减一,右边的对象引用计数加一

析构函数中引用计数减一

在赋值运算符和析构函数中,如果减一后为0,则调用delete释放对象

C++四种类型转换:static_cast, dynamic_cast, const_cast, reinterpret_cast

const_cast用于将const变量转为非const

static_cast用的最多,对于各种隐式转换,非const转const,void * 转指针等, static_cast能用于多态想上转化,如果向下转能成功但是不安全,结果未知

dynamic_cast用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。向下转化时,如果是非法的对于指针返回NULL,对于引用抛异常。要深入了解内部转换的原理。

reinterpret_cast几乎什么都可以转,比如将int转指针,可能会出问题,尽量少用

为什么不使用C的强制转换?C的强制转换表面上看起来功能强大什么都能转,但是转化不够明确,不能进行错误检查,容易出错

C++内存管理

C++内存分为那几块?(栈区,堆区,常量区,静态/全局数据区区,代码区)

堆区和栈区的区别?

申请方式上:栈区由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。堆区一般由程序员手动分配释放。

申请大小的限制:堆的空间比较灵活,也比较大

申请效率上:栈由系统自动分配,速度较快。堆是由new/malloc分配的内存,一般速度比较慢,而且容易产生内存碎片

内存空间的增长方式:栈是从高地址往低地址增长,而堆是从低地址往高地址增长。

如何定位内存泄漏?

在windows平台下通过CRT中的库函数进行检测

在可能泄漏的调用前后生成块的快照,比较前后的状态,定位泄漏的位置

Linux下通过工具valgrind检测

内存泄露定位、检测原理?

自己重载new操作符,用list或者map对分配的内存进行收集,如果释放了,就删除节点,最后检测容器里面还有没有节点,有就是泄露了。可以在重载的new中记录是哪一行的代码分配的内存被泄露了,这样就可以定位到内存泄露的位置。

如何判断大小端?

union un

{
     

  int i;

  char ch;

};

void fun()

{
     

  union un test;

  test.i = 1;

  if(test.ch == 1)

    cout << "小端" << endl;

  else

    cout << "大端" << endl;

}

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

STL中内存池的实现
STL内存分配分为一级分配器和二级分配器,一级分配器就是采用malloc分配内存,二级分配器采用内存池。
二级分配器设计的非常巧妙,分别给8k,16k,…, 128k等比较小的内存片都维持一个空闲链表,每个链表的头节点由一个数组来维护。需要分配内存时从合适大小的链表中取一块下来。假设需要分配一块10K的内存,那么就找到最小的大于等于10k的块,也就是16K,从16K的空闲链表里取出一个用于分配。释放该块内存时,将内存节点归还给链表。

如果要分配的内存大于128K则直接调用一级分配器。

为了节省维持链表的开销,采用了一个union结构体,分配器使用union里的next指针来指向下一个节点,而用户则使用union的空指针来表示该节点的地址。

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

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

红黑树的定义:

(1) 节点是红色或者黑色;

(2) 父节点是红色的话,子节点就不能为红色;

(3) 从根节点到每个页子节点路径上黑色节点的数量相同;

(4) 根是黑色的,NULL节点被认为是黑色的。

红黑树是一种平衡二叉查找树,与AVL树的区别是什么?(AVL树是完全平衡的,红黑树基本上是平衡的。)

为什么选用红黑数呢?因为红黑数是平衡二叉树,其插入和删除的效率都是N(logN),与AVL相比红黑数插入和删除最多只需要3次旋转,而AVL树为了维持其完全平衡性,在坏的情况下要旋转的次数太多。

Linux操作系统

进程与线程

进程和线程的联系区别?

进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.

一个程序至少有一个进程,一个进程至少有一个线程,线程的划分尺度小于进程

进程在执行过程中拥有独立的内存单元,而多个线程共享所在进程的地址空间和其它资源

线程执行开销小,但不利于资源的管理和保护,而进程正相反

什么时候用多进程?什么时候用多线程?

需要频繁创建销毁的优先用线程

需要进行大量计算的优先使用线程

强相关的处理用线程,弱相关的处理用进程

什么叫强相关、弱相关?理论上很难定义,给个简单的例子就明白了。

一般的Server需要完成如下任务:消息收发、消息处理。“消息收发”和“消息处理”就是弱相关的任务,而“消息处理”里面可能又分为“消息解码”、“业务处理”,这两个任务相对来说相关性就要强多了。因此“消息收发”和“消息处理”可以分进程设计,“消息解码”、“业务处理”可以分线程设计。

当然这种划分方式不是一成不变的,也可以根据实际情况进行调整。

可能要扩展到多机分布的用进程,多核分布的用线程

LINUX中进程和线程使用的几个函数?

进程:fork, exec, wait

线程:pthread_create、pthread_exit、pthread_jion、pthread_kill; pthread_mutex_lock、pthread_mutex_unlock、pthread_cond_init、pthread_cond_signal、pthread_cond_wait

Linux线程同步

互斥锁、条件变量、读写锁、信号量

互斥锁和自旋锁的区别?

互斥锁得不到资源的时候阻塞,不占用cpu资源。自旋锁得不到资源的时候,不停的查询,而然占用cpu资源。

Linux进程间通讯

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

命名管道 (FIFO) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

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

信号量:信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据,有XSI信号量和POSIX信号量,POSIX信号量更加完善。

共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。(原理一定要清楚,常考)

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

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

匿名管道与命名管道的区别?

匿名管道只能在具有公共祖先的两个进程间使用

共享内存映射mmap?
mmap是内存文件映射,将一个文件映射到进程的地址空间,用户进程的地址空间的管理是通过vm_area_struct结构体进行管理的。mmap通过映射一个相同的文件到两个不同的进程,就能实现这两个进程的通信,采用该方法可以实现任意进程之间的通信。mmap也可以采用匿名映射,不指定映射的文件,但是只能在父子进程间通信。

常见的信号有哪些?
SIGINT,SIGKILL(不能被捕获),SIGTERM(可以被捕获),SIGSEGV,SIGCHLD,SIGALRM

进程调度算法?
先来先服务FIFO、最高优先级算法HPF、时间片轮转算法、多级队列反馈法

exit()与_exit()区别
exit()清理后进入内核,_exit()直接陷入内核。

什么是孤儿进程?
一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作

什么是僵尸进程?
一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。

僵尸进程的危害?
僵死进程的PID还占据着。系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程。

如何避免僵尸进程?
父进程调用wait/waitpid函数等待子进程结束然后回收。
通过信号机制。子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用wait进行处理僵尸进程。
fork两次。子进程退出,从而孙进程成为孤儿进程,他的父进程变为init进程,通过init进程可以处理僵尸进程。

2、settimer,信号实现

3、最小堆、红黑树

4、时间轮

页面替换算法?
最佳置换算法、先进先出页面置换算法、改进型FIFO算法、最近不常使用算法、最久未使用算法(LRU)、时钟替换算法

Linux是如何避免内存碎片的
伙伴算法,用于管理物理内存,避免内存碎片

高速缓存Slab层用于管理内核分配内存,避免碎片

Linux IO模型

五种IO模型 参考链接

阻塞IO,非阻塞IO,IO复用,信号驱动式IO,异步IO

什么是IO多路复用?为什么要使用IO多路复用?

select,poll,epoll的区别?

epoll两种触发模式?有什么区别?使用哪一种好?

select/poll/epoll区别

几种网络服务器模型的介绍与比较

epoll为什么这么快

死锁

死锁产生的必要条件?

互斥条件:一个资源每次只能被一个进程使用

请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放

不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺

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

死锁的避免?

加锁顺序:如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生

加锁时限:个可以避免死锁的方法是在尝试获取锁的时候加一个超时时间

死锁检测:当一个线程请求锁失败时,这个线程可以遍历锁的关系图看看是否有死锁发生

Linux常用的命令与shell编程

与CPU,内存,磁盘相关的命令(top,free, df, fdisk)

网络相关的命令netstat,tcpdump等

sed, awk, grep三个超强大的命名,分别用与格式化修改,统计,和正则查找

ipcs和ipcrm命令

查找当前目录以及字母下以.c结尾的文件,且文件中包含”hello world”的文件的路径

创建定时任务

定时器的实现?

Linux命令 在一个文件中,倒序打印第二行前100个大写字母

cat filename | head -n 2 | tail -n 1 | grep ‘[[:upper:]]’ -o | tr -d ‘\n’| cut -c 1-100 | rev

计算机网络TCP/IP

TCP/UDP

TCP、UDP的首部?

TCP、UDP的区别

TCP、UDP的应用场景

如何实现可靠的UDP

TCP三次握手与四次挥手

详细说明TCP状态迁移过程

2MSL是什么状态?

主动关闭的Socket端会进入TIME_WAIT状态,并且持续2MSL时间长度,MSL就是maximum segment lifetime(最大分节生命期),这是一个IP数据包能在互联网上生存的最长时间,超过这个时间将在网络中消失。MSL在RFC 1122上建议是2分钟,而源自berkeley的TCP实现传统上使用30秒,因而,TIME_WAIT状态一般维持在1-4分钟。

TIME_WAIT作用是什么?
可靠地实现TCP全双工连接的终止

在进行关闭连接四路握手协议时,最后的ACK是由主动关闭端发出的,如果这个最终的ACK丢失,服务器将重发最终的FIN,因此客户端必须维护状态信息允 许它重发最终的ACK。如果不维持这个状态信息,那么客户端将响应RST分节。因而,要实现TCP全双工连接的正常终止,必须处理终止序列四个分节中任何一个分节的丢失情况,主动关闭 的客户端必须维持状态信息进入TIME_WAIT状态。

防止旧的连接中的重复分节对新连接造成干扰

TCP分节可能由于路由器异常而“迷途”,在迷途期间,TCP发送端可能因确认超时而重发这个分节,迷途的分节在路由器修复后也会被送到最终目的地,这个 原来的迷途分节就称为lost duplicate。在关闭一个TCP连接后,马上又重新建立起一个相同的IP地址和端口之间的TCP连接,后一个连接被称为前一个连接的化身 (incarnation),那么有可能出现这种情况,前一个连接的迷途重复分组在前一个连接终止后出现,从而被误解成从属于新的化身。为了避免这个情 况,TCP不允许处于TIME_WAIT状态的连接启动一个新的化身,因为TIME_WAIT状态持续2MSL,就可以保证当成功建立一个TCP连接的时 候,来自连接先前化身的重复分组已经在网络中消逝。

TCP重发机制,Nagle算法
TCP的拥塞控制使用的算法和具体过程
TCP的窗口滑动流量控制
TCP客户与服务器模型,用到哪些函数?
UDP客户与服务器模型,用到哪些函数?
什么是坚持定时器?

http的主要特点
简单快速:当客户端向服务器端发送请求时,只是简单的填写请求路径和请求方法即可,然后就可以通过浏览器或其他方式将该请求发送就行了
灵活: HTTP 协议允许客户端和服务器端传输任意类型任意格式的数据对象
3.无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接,采用这种方式可以节省传输时间。(当今多数服务器支持Keep-Alive功能,使用服务器支持长连接,解决无连接的问题)

无状态:无状态是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。即客户端发送HTTP请求后,服务器根据请求,会给我们发送数据,发送完后,不会记录信息。(使用 cookie 机制可以保持 session,解决无状态的问题)

http1.1的特点
默认持久连接节省通信量,只要客户端服务端任意一端没有明确提出断开TCP连接,就一直保持连接,可以发送多次HTTP请求
管线化,客户端可以同时发出多个HTTP请求,而不用一个个等待响应
断点续传ftghh
http2.0的特点
HTTP/2采用二进制格式而非文本格式
HTTP/2是完全多路复用的,而非有序并阻塞的——只需一个HTTP连接就可以实现多个请求响应
使用报头压缩,HTTP/2降低了开销
HTTP/2让服务器可以将响应主动“推送”到客户端缓存中

get/post 区别
get重点在从服务器上获取资源,post重点在向服务器发送数据
get传输数据是通过URL请求,以field(字段)= value的形式,置于URL后,并用”?”连接,多个请求数据间用”&”连接,这个过程用户是可见的。post传输数据通过Http的post机制,将字段与对应值封存在请求实体中发送给服务器,这个过程对用户是不可见的。
Get传输的数据量小,因为受URL长度限制,但效率较高。Post可以传输大量数据,所以上传文件时只能用Post方式
get是不安全的,因为URL是可见的,可能会泄露私密信息,如密码等。post较get安全性较高

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

http 协议头相关

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协议。

你可能感兴趣的:(C++)