diff --git "a/C++/C++\345\237\272\347\241\200.md" "b/C++/C++\345\237\272\347\241\200.md"
new file mode 100644
index 0000000..2ca1ac6
--- /dev/null
+++ "b/C++/C++\345\237\272\347\241\200.md"
@@ -0,0 +1,48 @@
+1. 变量声明和变量定义的区别
+ * 声明不分配地址和存储空间,定义分配。
+ * 同一个变量可以声明多次,只能定义一次。
+2. typedef和define的区别
+ * type定义数据类型的别名增加可读性,define定义常亮和宏
+ * typedef在编译时期进行有类型检查,define在预编译时期进行只是简单字符串替换没有类型检查。
+3. 重写和重载的区别
+ * 重写的函数名参数返回值完全相同,重写用于子类定义父类虚函数。
+ * 重载只有函数名相同,但是参数必须不同。
+4. static
+ * static全局变量和static全局函数只能在当前模块内被访问。
+ * 函数内static变量只被分配一次,且函数退出后也不会被销毁。
+ * 类内static成员变量只被创建一次,所有实例化对象共用同一份static成员变量。
+ * 类内static成员函数同样被所有实例化对象共用,不接受this指针,只能访问类的static成员变量。
+
+5. 深拷贝和浅拷贝
+ * 类中未定义拷贝构造函数时,系统调用默认拷贝构造函数即浅拷贝。当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象,所以,此时,必须采用深拷贝。
+ * 深拷贝与浅拷贝的区别在于深拷贝会申请堆内存空间。
+
+6. 引用和指针的区别
+ * 引用初始化后不能被改变引用对象,指针可以随时改变指向对象。
+ * 引用不可以为空,指针可以为空。
+
+7. sizeof和strlen的区别
+ * sizeof是运算符,strlen是函数。sizeof在编译时计算,strlen在运行到它时进行计算。
+
+8. 内存模型
+ * 内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。
+ * 栈(由高向低):函数内局部变量在栈上创建,函数执行结束自动释放。容量有限。
+ * 堆(由低向高):就是那些由new分配的内存块一般一个new就要对应一个 delete。,需要手动管理。
+ * 自由存储区:由malloc等分配的内存块,它是用free来结束自己的生命的。
+ * 全局静态存储区:分配全局变量和静态变量。
+ * 常亮存储区:存放常量。
+
+9. 内存分配:
+ * 常亮静态存储区在编译时分配。
+ * 函数执行时在栈区分配。
+ * new/malloc delete/free在堆区分配。
+
+10. new/delete组合和malloc/free组合的区别:
+ * malloc/free是标准库函数,new/delete是关键字&运算符。new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。
+ * new和delete会分别调用:构造函数和析构函数。
+ * new自动计算需要分配的空间,而malloc需要手工计算字节数
+ * new是类型安全的,而malloc不是。
+
+11. 智能指针
+ * 智能指针使用引用计数器机制实现。建立一个引用计数器类对象,它内容应该包括指向类A对象的指针,以及引用计数器。在以后要使用对象A时,就直接使用该引用计数器类对象。
+ * 智能指针种类:shared_ptr, unique_ptr, weak_ptr。shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,自动删除所指向的堆内存。shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。
diff --git "a/C++/C++\345\257\271\350\261\241\346\250\241\345\236\213.md" "b/C++/C++\345\257\271\350\261\241\346\250\241\345\236\213.md"
index 44ab65b..2600f61 100644
--- "a/C++/C++\345\257\271\350\261\241\346\250\241\345\236\213.md"
+++ "b/C++/C++\345\257\271\350\261\241\346\250\241\345\236\213.md"
@@ -27,7 +27,7 @@
* [附:使用gdb分析对象模型](#附使用gdb分析对象模型)
-
+
@@ -1872,4 +1872,4 @@ int main()
画图表示如下(typeinfo在虚函数表上方):
-
\ No newline at end of file
+
diff --git a/README.md b/README.md
index 3a6e3e7..f6154e8 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,7 @@
* [《操作系统——精髓与设计原理》](操作系统/操作系统.md)
* [《UNIX环境高级编程》](操作系统/UNIX环境高级编程.md)
+* [《深入理解Linux内核》](操作系统/深入理解Linux内核/book.md)
## 计算机网络
@@ -9,7 +10,6 @@
* [《UNIX网络编程:卷1》](计算机网络/UNIX网络编程卷1.md)
## 数据结构与算法
-
* [1.排序](数据结构与算法/排序.md)
* [2.二叉树](数据结构与算法/二叉树.md)
* [3.堆](数据结构与算法/堆.md)
diff --git a/interview/C++.md b/interview/C++.md
index c958f5b..269c7f3 100644
--- a/interview/C++.md
+++ b/interview/C++.md
@@ -1,29 +1,59 @@
* **一.变量**
- * 1)全局变量与static变量?(作用域、生存周期)
- * 2)[static函数与普通函数的区别?](temp/C++.md#4static函数与普通函数的区别)
- * 3)两个文件中声明两个同名变量?(使用了与未使用extern?)
- * 4)全局数组和局部数组的初始化?
- * 5)[指针和引用的区别](https://www.nowcoder.com/ta/nine-chapter/review?page=11)?(代表意义、内存占用、初始化、指向是否可改、能否为空)
- * 6)[C/C++中的强制转换](https://github.com/arkingc/note/blob/master/C++/EffectiveC++.md#%E6%9D%A1%E6%AC%BE27%E5%B0%BD%E9%87%8F%E5%B0%91%E5%81%9A%E8%BD%AC%E5%9E%8B%E5%8A%A8%E4%BD%9C)
+ * 1)全局变量与static变量?(作用域、生存周期)
+ >将局部变量声明为static改变了它的生存周期,是从程序的开始到结束;将全局变量声明为static改变了它的作用域,只能作用于声明的文件。static变量默认初始化为0。
+ * 2)[static函数与普通函数的区别?](temp/C++.md#4static函数与普通函数的区别)
+ >作用域不同,static函数只能被本文件中的函数调用。
+ * 3)两个文件中声明两个同名变量?(使用了与未使用extern?)
+ >未使用extern会造成重定义,使用extern告诉编译器该变量在其他文件中定义,在编译时不报错,交给链接器处理。
+ * 4)全局数组和局部数组的初始化?
+ >全局数组会执行默认初始化,全部元素由0填充;局部数组不会默认初始化,元素由随机数填充。
+ * 5)[指针和引用的区别](https://www.nowcoder.com/ta/nine-chapter/review?page=11)?(代表意义、内存占用、初始化、指向是否可改、能否为空)
+ >指针是地址,需要分配内存空间;引用是别名,不需要分配内存空间。
+ >指针定义时不一定要初始化,运行过程中指向可改变;引用定义时一定要初始化,指向不可改。
+ >指针可以为空;引用不能为空。
+ * 6)[C/C++中的强制转换](https://github.com/arkingc/note/blob/master/C++/EffectiveC++.md#%E6%9D%A1%E6%AC%BE27%E5%B0%BD%E9%87%8F%E5%B0%91%E5%81%9A%E8%BD%AC%E5%9E%8B%E5%8A%A8%E4%BD%9C)
+ >static_cast:用于基本类型之间的转换,如int to float,void* to double*。在继承体系中,向上转换安全,向下转换不安全,因为只在编译时类型检查,没有运行时类型检查。
+ const_cast:用于给变量去除或添加const属性,如将一个const类型的变量传给接受非const类型变量的函数。
+ reinterpret_cast:~~为运算对象的位模式提供较低层次上的解释。~~
+ dynamic_cast:用于安全的将基类指针或引用转换为指向派生类类型的指针或引用,通过虚函数表进行运行时类型检查。失败时返回0或bad_cast异常。
* 7)[如何修改const变量、const与volatile](https://blog.csdn.net/heyabo/article/details/8745942)
* 8)静态类型获取与动态类型获取([typeid](https://github.com/arkingc/llc/blob/master/cpp/RTTI/typeid.cpp#L4)、dynamic_cast:转换目标类型必须是引用类型)
* 9)[如何比较浮点数大小?](https://blog.csdn.net/jk110333/article/details/8902707)([直接使用==比较出现错误的例子](https://stackoverflow.com/questions/26261466/in-current-c-and-java-double-type-and-float-type-if-x-0-0-is-correct))
+ * 10) [extern关键字详解](temp/C++.md/#5extern关键字详解)
* **二.函数**
* 1)重载([参数必须不同(const修饰形参)](https://github.com/arkingc/llc/blob/master/cpp/overload/main.cpp#L9)、重载与作用域、继承中的重载\(using\)、重载与const成员函数)
* **三.类**
- * 1)面向对象的三大特性(封装、继承、多态)
- * 2)struct和class的区别?
+ * 1)面向对象的三大特性(封装、继承、多态)
+ >封装:public、private、protected访问控制
+ 继承:单继承、多继承
+ 多态:静态多态:通过重载、模板,编译时确定调用函数的类型;动态多态:通过虚函数在运行时确定调用哪个函数。
+ * 2)struct和class的区别?
+ >struct:默认访问说明符是public,作为子类继承时默认继承访问权限是public;
+ class:默认访问说明符是private,作为子类继承时默认继承访问权限是private。
* 3)[访问权限说明符](temp/C++.md/#3访问控制说明符)?(目的是加强类的封装性)
* 4)类的静态成员(所属?静态成员函数不能声明成const、类类型的成员、定义时不能重复使用static、具有类内初始值的静态成员定义时不可再设初值)
* 5)构造函数相关
- 有哪些构造函数(默认、委托、拷贝、移动)
- - 合成的默认拷贝构造函数(默认行为?什么情况下不会合成?怎么解决?如果成员包含类内初始值,合成默认构造函数会使用该成员的类内初始值初始化该成员)
- - 拷贝构造函数(调用时机、合成版的行为、explict?、为何第一个参数必须是引用类型)
- - 移动拷贝构造函数(非拷贝而是窃取资源、与noexcept?、何时合成)
+ - 合成的默认拷贝构造函数(默认行为?什么情况下不会合成?怎么解决?如果成员包含类内初始值,合成默认构造函数会使用该成员的类内初始值初始化该成员)
+ >如果有类内初始值,则用它来初始化成员;否则,执行默认初始化。
+ 已经定义了其他的构造函数。
+ A() = default;
+ - 拷贝构造函数(调用时机、合成版的行为、explict?、为何第一个参数必须是引用类型)
+ >拷贝初始化;将对象作为实参传递给非引用类型的形参;从返回类型为非引用类型的函数返回对象;用花括号列表初始化数组中的元素。
+ 将参数的成员逐个拷贝到正在创建的对象中。
+ 会被隐式使用,不应该是explicit的。
+ 函数调用过程中,非引用类型的参数要进行拷贝初始化,如果不是引用类型,会造成无限循环。
+ - 移动拷贝构造函数(非拷贝而是窃取资源、与noexcept?、何时合成)
+ >不分配新内存,一般不会抛出异常,声明为noexpect。
+ 没有定义任何拷贝控制成员,且所有数据成员都能移动构造时,才会自动合成。
- 可否通过对象或对象的引用(指针或引用)调用
- * 6)初始值列表(顺序、效率(内置类型不进行隐式初始化故无所谓,但..)、无默认构造函数的成员,const成员,引用成员必须通过初始值列表初始化)
+ * 6)初始值列表(顺序、效率(内置类型不进行隐式初始化故无所谓,但..)、无默认构造函数的成员,const成员,引用成员必须通过初始值列表初始化)
+ >初始化顺序与再类定义中出现的顺序一致,与初始值列表中的顺序无关。
* 7)赋值运算符相关
- - 拷贝赋值运算符(合成版的行为?、与delete?、自定义时要注意自赋值,参数与返回类型、大部分组合了拷贝构造函数与析构函数的工作)
+ - 拷贝赋值运算符(合成版的行为?、与delete?、自定义时要注意自赋值,参数与返回类型、大部分组合了拷贝构造函数与析构函数的工作)
+ >将右侧运算对象的每个非static成员赋予左侧运算对象的对应成员。
+ 加 = delete 用于阻止拷贝。
+ 销毁左侧运算对象资源之前拷贝右侧运算对象。
- 阻止拷贝(某些对象应该独一无二(比方说人)、C++11前:private并且不定义(试图拷贝会报链接错误),C++11:=delete [《Effective C++:条款6》](https://github.com/arkingc/note/blob/master/C++/EffectiveC++.md#%E6%9D%A1%E6%AC%BE06%E8%8B%A5%E4%B8%8D%E6%83%B3%E4%BD%BF%E7%94%A8%E7%BC%96%E8%AF%91%E5%99%A8%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90%E7%9A%84%E5%87%BD%E6%95%B0%E5%B0%B1%E8%AF%A5%E6%98%8E%E7%A1%AE%E6%8B%92%E7%BB%9D))
- 移动赋值运算符(与noexcept?何时合成)
- 可以定义为成员或非成员函数,定义成成员函数时第一个操作数隐式绑定到this指针
@@ -33,37 +63,61 @@
- 为什么析构函数中不能抛出异常?(不能是指“不应该”,C++本身并不禁止[《Effective C++:条款8》](https://github.com/arkingc/note/blob/master/C++/EffectiveC++.md#%E6%9D%A1%E6%AC%BE08%E5%88%AB%E8%AE%A9%E5%BC%82%E5%B8%B8%E9%80%83%E7%A6%BB%E6%9E%90%E6%9E%84%E5%87%BD%E6%95%B0))
- 如果析构函数中包含可能抛出异常的代码怎么办?(Effective C++:条款8》)
- 可否通过对象或对象的引用(指针或引用)调用
- - 为什么将继承体系中基类的析构函数声明为虚函数?([《Effective C++:条款7》](https://github.com/arkingc/note/blob/master/C++/EffectiveC++.md#%E6%9D%A1%E6%AC%BE07%E4%B8%BA%E5%A4%9A%E6%80%81%E5%9F%BA%E7%B1%BB%E5%A3%B0%E6%98%8Evirtual%E6%9E%90%E6%9E%84%E5%87%BD%E6%95%B0))
+ - 为什么将继承体系中基类的析构函数声明为虚函数?([《Effective C++:条款7》](https://github.com/arkingc/note/blob/master/C++/EffectiveC++.md#%E6%9D%A1%E6%AC%BE07%E4%B8%BA%E5%A4%9A%E6%80%81%E5%9F%BA%E7%B1%BB%E5%A3%B0%E6%98%8Evirtual%E6%9E%90%E6%9E%84%E5%87%BD%E6%95%B0))
+ >基类指针可以指向派生类对象,当删除该指针时,就会调用派生类的析构函数,派生类的析构函数又自动调用基类的析构函数;如果没有声明为虚函数,删除基类指针时,只会调用基类的析构函数,派生类对象就析构不完全。
- 不应该将非继承体系中的类的虚函数声明为虚函数([《Effective C++:条款7》](https://github.com/arkingc/note/blob/master/C++/EffectiveC++.md#%E6%9D%A1%E6%AC%BE07%E4%B8%BA%E5%A4%9A%E6%80%81%E5%9F%BA%E7%B1%BB%E5%A3%B0%E6%98%8Evirtual%E6%9E%90%E6%9E%84%E5%87%BD%E6%95%B0))
+ >引入虚函数指针增加开销。
- 不应该继承析构函数非虚的类([《Effective C++:条款7》](https://github.com/arkingc/note/blob/master/C++/EffectiveC++.md#%E6%9D%A1%E6%AC%BE07%E4%B8%BA%E5%A4%9A%E6%80%81%E5%9F%BA%E7%B1%BB%E5%A3%B0%E6%98%8Evirtual%E6%9E%90%E6%9E%84%E5%87%BD%E6%95%B0),final防止继承)
- [防止继承的方式](https://blog.twofei.com/672/)
* 9)[删除的合成函数](https://github.com/arkingc/llc/blob/master/cpp/class/delete/README.md)(一般函数而言不想调用的话不定义就好)
* 10)继承相关
- 继承体系中的构造、拷贝、析构顺序?(派生类只负责自己成员的拷贝控制,可以(换而言之非必须,如果不显示调用,会调用父类合成的默认版本)在初始值列表或函数体中调用基类相应函数)
+ >构造:先初始化基类部分,按声明顺序初始化派生类成员;
+ 析构:先执行派生类析构函数,再执行基类析构函数。
- 继承中的名字查找(作用域嵌套、从子类到父类查找;[成员名字的处理](https://github.com/arkingc/note/blob/master/C++/C++%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B.md#%E5%90%8D%E7%A7%B0%E7%9A%84%E7%89%B9%E6%AE%8A%E5%A4%84%E7%90%86))
- [成员函数体内、成员函数的参数列表的名字解析时机](https://github.com/arkingc/note/blob/master/C++/C++%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B.md#31-data-member%E7%9A%84%E7%BB%91%E5%AE%9A)(因此,务必将“内嵌的类型声明”放在class起始处)
- 同名名字隐藏(如何解决?(域作用符,从指示的类开始查找)、不同作用域无法重载、using的作用?除此之外呢?)
- 虚继承(解决什么问题?(多继承中的子对象冗余))
* 11)多态的实现?
* 12)[虚函数的实现原理?对类大小的影响?](https://www.cnblogs.com/malecrab/p/5572730.html)(vtbl是一个由函数指针组成的数组,无论pb指向哪种类型的对象,只要能够确定被调函数在虚函数中的偏移值,待运行时,能够确定具体类型,并能找到相应vptr,进一步能找出真正应该调用的函数)
- * 13)为什么不要在构造、析构函数中调用虚函数?(子对象的base class构造期间,对象的类型是base class [《Effective C++:条款9》](https://github.com/arkingc/note/blob/master/C++/EffectiveC++.md#%E6%9D%A1%E6%AC%BE09%E7%BB%9D%E4%B8%8D%E5%9C%A8%E6%9E%84%E9%80%A0%E5%92%8C%E6%9E%90%E6%9E%84%E8%BF%87%E7%A8%8B%E4%B8%AD%E8%B0%83%E7%94%A8virtual%E5%87%BD%E6%95%B0),[设置虚函数指针的时机](https://github.com/arkingc/note/blob/master/C++/C++%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B.md#vptr%E7%9A%84%E8%AE%BE%E7%BD%AE))
+ * 13)为什么不要在构造、析构函数中调用虚函数?(子对象的base class构造期间,对象的类型是base class [《Effective C++:条款9》](https://github.com/arkingc/note/blob/master/C++/EffectiveC++.md#%E6%9D%A1%E6%AC%BE09%E7%BB%9D%E4%B8%8D%E5%9C%A8%E6%9E%84%E9%80%A0%E5%92%8C%E6%9E%90%E6%9E%84%E8%BF%87%E7%A8%8B%E4%B8%AD%E8%B0%83%E7%94%A8virtual%E5%87%BD%E6%95%B0),[设置虚函数指针的时机](https://github.com/arkingc/note/blob/master/C++/C++%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B.md#vptr%E7%9A%84%E8%AE%BE%E7%BD%AE))
+ >派生类的构造函数中,基类部分先构造,如果在基类构造函数中调用虚函数,对象的类型会被视为是基类,否则会访问派生类未被初始化的成员。
+ 析构时,派生类成员先被析构,进入基类构造函数时,对象就被视为是基类对象。
* 14)[虚函数被覆盖?](https://github.com/arkingc/llc/blob/master/cpp/class/inheritance/virtual_function_hide.cpp#L1)
* 15)virtual函数动态绑定,缺省参数值静态绑定([《Effective C++:条款37》](https://github.com/arkingc/note/blob/master/C++/EffectiveC++.md#%E6%9D%A1%E6%AC%BE37%E7%BB%9D%E4%B8%8D%E9%87%8D%E6%96%B0%E5%AE%9A%E4%B9%89%E7%BB%A7%E6%89%BF%E8%80%8C%E6%9D%A5%E7%9A%84%E7%BC%BA%E7%9C%81%E5%8F%82%E6%95%B0%E5%80%BC))
* 16)纯虚函数与抽象基类([纯虚函数与虚函数、一般成员函数的选择](../C++/EffectiveC++.md#条款34区分接口继承和实现继承))
* 17)静态类型与动态类型(引用是否可实现动态绑定)
- * 18)浅拷贝与深拷贝(安全性、行为像值的类与行为像指针的类)
+ * 18)浅拷贝与深拷贝(安全性、行为像值的类与行为像指针的类)
+ >浅拷贝:副本和原对象使用相同的底层数据,改变其中一个也会改变另一个。
+ 深拷贝:副本开辟新的空间存放原对象复制的数据,完全独立。
* 19)如何定义类内常量?(enum而不是static const [《Effective C++:条款2》](https://github.com/arkingc/note/blob/master/C++/EffectiveC++.md#%E6%9D%A1%E6%AC%BE02%E5%B0%BD%E9%87%8F%E4%BB%A5constenuminline%E6%9B%BF%E6%8D%A2define))
* 20)继承与组合(复合)之间如何选择?([《Effective C++:条款38》](https://github.com/arkingc/note/blob/master/C++/EffectiveC++.md#%E6%9D%A1%E6%AC%BE38%E9%80%9A%E8%BF%87%E5%A4%8D%E5%90%88%E5%A1%91%E6%A8%A1%E5%87%BAhas-a%E6%88%96%E6%A0%B9%E6%8D%AE%E6%9F%90%E7%89%A9%E5%AE%9E%E7%8E%B0%E5%87%BA))
* 21)private继承?([《Effective C++:条款39》](https://github.com/arkingc/note/blob/master/C++/EffectiveC++.md#%E6%9D%A1%E6%AC%BE39%E6%98%8E%E6%99%BA%E8%80%8C%E5%AE%A1%E6%85%8E%E5%9C%B0%E4%BD%BF%E7%94%A8private%E7%BB%A7%E6%89%BF))
* 22)[如何定义一个只能在堆上(栈上)生成对象的类?](https://www.nowcoder.com/questionTerminal/0a584aa13f804f3ea72b442a065a7618)
- * 23)[内联函数、构造函数、静态成员函数可以是虚函数吗?](https://www.nowcoder.com/ta/nine-chapter/review?page=24)
+ * 23)[内联函数、构造函数、静态成员函数可以是虚函数吗?](https://www.nowcoder.com/ta/nine-chapter/review?page=24)
+ >内联函数是在编译时展开,虚函数是运行时动态绑定;
+ 构造函数在调用时也不需要动态绑定;
+ 静态成员函数是与类相关的,与对象无关,虚函数是与对象动态绑定的。
* **四.内存管理**
- * 1)[C++内存分区](../C++/内存分区.md)
- * 2)[new](https://github.com/arkingc/note/blob/master/C++/C++%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B.md#1new)和malloc的区别?(函数,运算符、类型安全、计算空间、步骤,[operator new的实现](../C++/C++对象模型.md#3operator-new和operator-delete的实现))
+ * 1)[C++内存分区](../C++/内存分区.md)
+ >沿虚拟地址递增方向
+ 文本段:存放程序的机器语言指令;
+ 初始化数据段:存放显式初始化的全局变量和静态变量;
+ 未初始化数据段:存放未显式初始化的全局变量和静态变量,会默认初始化为0;
+ 堆:用于存放运行时动态申请的内存空间,由用户通过malloc/new申请内存,free/delete释放内存,若不进行手动释放,会由系统在程序结束后回收内存,生命周期为程序的生命周期。
+ 栈:用于保存局部变量、参数、返回值,函数退出后系统自动释放栈区内存。地址由高到低增长。
+ * 2)[new](https://github.com/arkingc/note/blob/master/C++/C++%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B.md#1new)和malloc的区别?(函数,运算符、类型安全、计算空间、步骤,[operator new的实现](../C++/C++对象模型.md#3operator-new和operator-delete的实现))
+ >属性:new是C++关键字,malloc是库函数;
+ 参数:new分配空间的大小由编译器根据对象类型的大小自动计算,malloc需要显式指定分配空间的大小;
+ 类型安全:new成功分配时返回的是对象类型的指针,malloc返回的是void* ,需要强制类型转换为需要的类型;
+ 步骤:new先调用operator new函数申请内存,再调用类型的构造函数,初始化成员变量,最后返回该对象的指针;malloc只能分配内存。
* 3)[new[]与delete[]?](../C++/C++对象模型.md#4针对数组的new语意)(步骤:如何分配内存,构建对象、如何析构与释放内存?[构造与析构](../C++/C++对象模型.md#3对象数组))
* 4)new带括号和不带的区别?(无自定义构造函数时,不带括号的new只分配内存,带括号的new会初始化为0)
* 5)new时内存不足?([《Effective C++:条款49》](https://github.com/arkingc/note/blob/master/C++/EffectiveC++.md#%E6%9D%A1%E6%AC%BE49%E4%BA%86%E8%A7%A3new-handler%E7%9A%84%E8%A1%8C%E4%B8%BA))(new-handler)
- * 6)[malloc](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/UNIX%E7%8E%AF%E5%A2%83%E9%AB%98%E7%BA%A7%E7%BC%96%E7%A8%8B.md#5%E5%AD%98%E5%82%A8%E7%A9%BA%E9%97%B4%E5%88%86%E9%85%8D)、[calloc](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/UNIX%E7%8E%AF%E5%A2%83%E9%AB%98%E7%BA%A7%E7%BC%96%E7%A8%8B.md#5%E5%AD%98%E5%82%A8%E7%A9%BA%E9%97%B4%E5%88%86%E9%85%8D)、[realloc](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/UNIX%E7%8E%AF%E5%A2%83%E9%AB%98%E7%BA%A7%E7%BC%96%E7%A8%8B.md#5%E5%AD%98%E5%82%A8%E7%A9%BA%E9%97%B4%E5%88%86%E9%85%8D)、[alloca](https://blog.csdn.net/lan120576664/article/details/38078855),malloc的实现?
+ * 6)[malloc](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/UNIX%E7%8E%AF%E5%A2%83%E9%AB%98%E7%BA%A7%E7%BC%96%E7%A8%8B.md#5%E5%AD%98%E5%82%A8%E7%A9%BA%E9%97%B4%E5%88%86%E9%85%8D)、[calloc](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/UNIX%E7%8E%AF%E5%A2%83%E9%AB%98%E7%BA%A7%E7%BC%96%E7%A8%8B.md#5%E5%AD%98%E5%82%A8%E7%A9%BA%E9%97%B4%E5%88%86%E9%85%8D)、[realloc](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/UNIX%E7%8E%AF%E5%A2%83%E9%AB%98%E7%BA%A7%E7%BC%96%E7%A8%8B.md#5%E5%AD%98%E5%82%A8%E7%A9%BA%E9%97%B4%E5%88%86%E9%85%8D)、[alloca](https://blog.csdn.net/lan120576664/article/details/38078855),malloc的实现?
+ >calloc会将分配的内存初始化为0;
+ realloc对malloc分配的内存大小进行调整;
+ alloca在栈上分配内存。
* 7)调用malloc函数之后,OS会马上分配内存空间吗?(不会,只会返回一个虚拟地址,待用户要使用内存时,OS会发出一个缺页中断,此时,内存管理模块才会为程序分配真正内存)
* 8)[delete](https://github.com/arkingc/note/blob/master/C++/C++%E5%AF%B9%E8%B1%A1%E6%A8%A1%E5%9E%8B.md#1new)(步骤、delete与析构、可以delete空指针、可以delete动态const对象)
* 9)为什么要内存对齐?([性能原因、平台原因](temp/C++.md/#1为什么要内存对齐))
diff --git a/interview/temp/C++.md b/interview/temp/C++.md
index 701d87d..0e55e3c 100644
--- a/interview/temp/C++.md
+++ b/interview/temp/C++.md
@@ -291,4 +291,10 @@ public:
## 4.static函数与普通函数的区别
* static函数与普通函数作用域不同,仅在本文件。只在当前源文件中使用的函数应该说明为内部函数(static修饰的函数),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件
-* static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝(这里暂时不理解)
\ No newline at end of file
+* static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝(这里暂时不理解)
+
+## 5.extern关键字详解
+
+作用:
+* 与“C”一起用的时候,extern "C" void fun(int a, int b);则告诉编译器在编译fun这个函数名时按着C的规则去翻译相应的函数名而不是C++的。
+* 当extern不与"C"在一起修饰变量或函数时,如在头文件中: extern int g_Int; 它的作用就是声明函数或全局变量的作用范围的关键字,其声明的函数和变量可以在本模块或其他模块中使用,记住它是一个声明不是定义!也就是说B模块(编译单元)要是引用模块(编译单元)A中定义的全局变量或函数时,它只要包含A模块的头文件即可,在编译阶段,模块B虽然找不到该函数或变量,但它不会报错,它会在连接时从模块A生成的目标代码中找到此函数。
diff --git a/interview/temp/pic/10-1.png b/interview/temp/pic/10-1.png
index bbe9b91..565c3a3 100644
Binary files a/interview/temp/pic/10-1.png and b/interview/temp/pic/10-1.png differ
diff --git a/interview/temp/pic/10.png b/interview/temp/pic/10.png
index 7bdc1b3..eb424af 100644
Binary files a/interview/temp/pic/10.png and b/interview/temp/pic/10.png differ
diff --git a/interview/temp/pic/11-1.png b/interview/temp/pic/11-1.png
index b8bbbc7..2d8d171 100644
Binary files a/interview/temp/pic/11-1.png and b/interview/temp/pic/11-1.png differ
diff --git a/interview/temp/pic/11.png b/interview/temp/pic/11.png
index 2898719..fdb0ef8 100644
Binary files a/interview/temp/pic/11.png and b/interview/temp/pic/11.png differ
diff --git a/interview/temp/pic/12-1.png b/interview/temp/pic/12-1.png
index 57a527e..939c12b 100644
Binary files a/interview/temp/pic/12-1.png and b/interview/temp/pic/12-1.png differ
diff --git a/interview/temp/pic/12.png b/interview/temp/pic/12.png
index 64fecd6..7212728 100644
Binary files a/interview/temp/pic/12.png and b/interview/temp/pic/12.png differ
diff --git a/interview/temp/pic/6.png b/interview/temp/pic/6.png
index ae5b3aa..a2c660a 100644
Binary files a/interview/temp/pic/6.png and b/interview/temp/pic/6.png differ
diff --git a/interview/temp/pic/7.png b/interview/temp/pic/7.png
index 863ddb5..685b5d4 100644
Binary files a/interview/temp/pic/7.png and b/interview/temp/pic/7.png differ
diff --git a/interview/temp/pic/8-1.png b/interview/temp/pic/8-1.png
index d0769bb..3aaaf39 100644
Binary files a/interview/temp/pic/8-1.png and b/interview/temp/pic/8-1.png differ
diff --git a/interview/temp/pic/9-1.png b/interview/temp/pic/9-1.png
index 616a8a2..0ec8656 100644
Binary files a/interview/temp/pic/9-1.png and b/interview/temp/pic/9-1.png differ
diff --git a/interview/temp/pic/9.png b/interview/temp/pic/9.png
index 1908031..21b048c 100644
Binary files a/interview/temp/pic/9.png and b/interview/temp/pic/9.png differ
diff --git "a/interview/\346\223\215\344\275\234\347\263\273\347\273\237.md" "b/interview/\346\223\215\344\275\234\347\263\273\347\273\237.md"
index 02f6208..742c78f 100644
--- "a/interview/\346\223\215\344\275\234\347\263\273\347\273\237.md"
+++ "b/interview/\346\223\215\344\275\234\347\263\273\347\273\237.md"
@@ -1,14 +1,32 @@
* **一.理论**
- **1.进程与线程**
- - 1)进行间通信的方式?([管道](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#31-%E7%AE%A1%E9%81%93)、[消息](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#32-%E6%B6%88%E6%81%AF)、[共享内存](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#33-%E5%85%B1%E4%BA%AB%E5%86%85%E5%AD%98)、[信号量](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#12-%E4%BA%92%E6%96%A5%E7%9A%84%E8%BD%AF%E4%BB%B6%E6%94%AF%E6%8C%81)、[信号](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#35-%E4%BF%A1%E5%8F%B7)、套接字)
- - 2)[进程和线程的区别联系](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#1%E8%BF%9B%E7%A8%8B%E4%B8%8E%E7%BA%BF%E7%A8%8B)?(组成、效率、通信、安全性)
+ - 1)进行间通信的方式?([管道](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#31-%E7%AE%A1%E9%81%93)、[消息](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#32-%E6%B6%88%E6%81%AF)、[共享内存](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#33-%E5%85%B1%E4%BA%AB%E5%86%85%E5%AD%98)、[信号量](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#12-%E4%BA%92%E6%96%A5%E7%9A%84%E8%BD%AF%E4%BB%B6%E6%94%AF%E6%8C%81)、[信号](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#35-%E4%BF%A1%E5%8F%B7)、套接字)
+ >管道:是一个环形缓冲区,两个进程可以对管道进行读和写操作。匿名管道只能在父子进程间通信, 命名管道可以在不相关的进程间使用。
+ >消息队列:独立于读写进程,避免了同步阻塞的问题,读进程可以根据消息的类型有选择的接收。
+ >共享内存:进程共享一个存储区,不需要进程间复制,速度最快。需要使用信号量同步对共享内存的访问。
+ >信号量:是一个非负整数的全局变量,进程可以对信号量进行PV操作来实现对共享内存的保护。
+ >套接字:主要用于不同机器间的进程通信。
+ - 2)[进程和线程的区别联系](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#1%E8%BF%9B%E7%A8%8B%E4%B8%8E%E7%BA%BF%E7%A8%8B)?(组成、效率、通信、安全性)
+ >资源:进程是资源分配的基本单位,线程不拥有资源,可以访问隶属进程的资源。
+ >调度:线程是调度的基本单位,同一进程的线程间切换不会引起进程切换。
+ >系统开销:创建或销毁进程时,需要分配资源,开销大于创建或销毁线程。线程切换只需要保存少量寄存器的状态,开销也小于进程切换。
+ >通信:同一进程的线程间通信可以直接访问进程的资源,不同进程通信需要IPC。
- 3)[进程的地址空间布局](https://blog.csdn.net/yusiguyuan/article/details/45155035)
- 4)程序状态字(PSW)?(一个或一组处理器寄存器,包含有进程的状态信息)
- - 5)进程[创建的步骤](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#21-%E8%BF%9B%E7%A8%8B%E7%9A%84%E5%88%9B%E5%BB%BA%E4%B8%8E%E7%BB%88%E6%AD%A2)?
+ - 5)进程[创建的步骤](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#21-%E8%BF%9B%E7%A8%8B%E7%9A%84%E5%88%9B%E5%BB%BA%E4%B8%8E%E7%BB%88%E6%AD%A2)?
+ >首先分配进程标识符,然后分配空间,再初始化进程控制块。
+ >Linux为子进程分配空间时运用了写时复制技术,调用fork()后子进程和父进程共享一个地址空间,当父或子进程对共享的地址空间写入时,再复制地址空间,使父子进程拥有各自的地址空间。
+ >进程控制块存储程序计数器、堆栈指针、内存分配状况等信息。
- 6)进程[切换的步骤](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#42-%E8%BF%9B%E7%A8%8B%E5%88%87%E6%8D%A2)?
- - 7)一个程序从开始运行到结束的完整过程
+ - 7)一个程序从开始运行到结束的完整过程
+ >预处理:宏定义替换、头文件包含、删除注释。
+ >编译:词法分析、语法分析,无误后转换成汇编代码。
+ >汇编:将汇编代码转换成二进制机器代码。
+ >链接:将多个目标文件和库文件链接成可执行文件。
- 8)[线程分配什么?TCB(线程控制块)?](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#1%E8%BF%9B%E7%A8%8B%E4%B8%8E%E7%BA%BF%E7%A8%8B)
- - 9)[线程共享进程的什么?不共享什么?CPU共享吗?](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#1%E8%BF%9B%E7%A8%8B%E4%B8%8E%E7%BA%BF%E7%A8%8B)
+ - 9)[线程共享进程的什么?不共享什么?CPU共享吗?](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#1%E8%BF%9B%E7%A8%8B%E4%B8%8E%E7%BA%BF%E7%A8%8B)
+ >共享:地址空间、全局变量、打开的文件描述符。
+ >不共享:程序计数器、寄存器、堆栈、状态。
- 10)怎样保证一个CPU只有一个线程运行?([CPU核数与多线程](https://blog.csdn.net/qq_33530388/article/details/62448212))
- 11)[线程有什么状态?](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#2%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81)
- 12)线程池的了解、优点、调度处理方式和保护任务队列的方式?
@@ -16,11 +34,31 @@
- 14)进程->线程->协程[——知乎阿猫](https://www.zhihu.com/question/20511233)(本质好像是[用户态线程](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#3%E7%BA%BF%E7%A8%8B%E5%88%86%E7%B1%BB),线程与协程最大的区别在是否依赖CPU时钟发出的中断来调度,协程的调度完全由用户控制)
- 15)[线程与协程的区别](http://www.jianshu.com/p/d058a0fd4ac8)
- 16)[守护进程、僵尸进程、孤儿进程?](http://liubigbin.github.io/2016/03/11/Linux-%E4%B9%8B%E5%AE%88%E6%8A%A4%E8%BF%9B%E7%A8%8B%E3%80%81%E5%83%B5%E6%AD%BB%E8%BF%9B%E7%A8%8B%E4%B8%8E%E5%AD%A4%E5%84%BF%E8%BF%9B%E7%A8%8B/)(守护进程运行在后台,独立于控制终端,周期性执行某种任务,父进程为init,一般系统启动时运行;僵尸进程会占据PID等系统资源,可以通过kill其父进程,转交给init周期性调用wait操作清理)
- - 17)[进程调度方法详细介绍](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#2%E8%B0%83%E5%BA%A6%E7%AE%97%E6%B3%95)(FCFS、轮转、SPN、SRT、HRRN、反馈法)
+ - 17)[进程调度方法详细介绍](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#2%E8%B0%83%E5%BA%A6%E7%AE%97%E6%B3%95)(FCFS、轮转、SPN、SRT、HRRN、反馈法)
+ >批处理系统:没有太多的用户操作,调度目标是保证高吞吐量和少的周转时间。
+ >>先来先服务:按照请求的顺序进行调度,有利于长作业,不利于短作业。
+ >>短作业优先:按估计运行时间最短的顺序进行调度,长作业可能会饿死。
+ >>最短剩余时间优先:需要提前知道程序执行的时间。
+
+ >交互式系统:有大量用户操作,调度目标是快的响应时间。
+ >>时间片轮转:按先来先服务的顺序将进程排成一个队列,队列头的进程先执行一个时间片,执行完后计时器发出时钟中断,该进程便被停止,加入到队列末尾,同时下一个进程再开始执行一个时间片。时间片选择的短会造成进程频繁切换,时间片长会导致响应时间变慢。
+ >>优先级调度:将所有进程按优先级从高到低的顺序执行。为防止低优先级的进程一直得不到执行,会随时间增加或降低等待进程的优先级。
+ >>多级队列:设置多个队列,每个队列上的进程可执行的时间片递增,如1,2,4,8...进程在第一个队列没执行完,就移到下一个队列。可以减少进程交换的次数。
- 18)[中断、陷阱和系统调用](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#42-%E8%BF%9B%E7%A8%8B%E5%88%87%E6%8D%A2)(异常和[中断](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#3%E4%B8%AD%E6%96%AD)的区别?)
- **2.并发**
- - 1)[什么是条件变量?信号量?](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#12-%E4%BA%92%E6%96%A5%E7%9A%84%E8%BD%AF%E4%BB%B6%E6%94%AF%E6%8C%81)
- - 2)[死锁条件](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#21-%E6%AD%BB%E9%94%81%E7%9A%84%E6%9D%A1%E4%BB%B6),解决死锁的方法?([死锁预防](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#22-%E6%AD%BB%E9%94%81%E9%A2%84%E9%98%B2)、[死锁避免](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#23-%E6%AD%BB%E9%94%81%E9%81%BF%E5%85%8D)、[死锁检测](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#24-%E6%AD%BB%E9%94%81%E6%A3%80%E6%B5%8B))
+ - 1)[什么是条件变量?信号量?](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#12-%E4%BA%92%E6%96%A5%E7%9A%84%E8%BD%AF%E4%BB%B6%E6%94%AF%E6%8C%81)
+ >条件变量:使线程阻塞于共享变量的某个状态,并在状态改变时被唤醒。
+ >信号量:是一个非负整数,可在其上进行PV操作:
+ >>P:若信号量值大于0,则减一并继续,若为0,则挂起进程。
+ >>V:将信号量加一,若有进程阻塞在该信号量上,则唤醒该进程,使其完成P操作,并返回。
+ - 2)[死锁条件](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#21-%E6%AD%BB%E9%94%81%E7%9A%84%E6%9D%A1%E4%BB%B6),解决死锁的方法?([死锁预防](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#22-%E6%AD%BB%E9%94%81%E9%A2%84%E9%98%B2)、[死锁避免](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#23-%E6%AD%BB%E9%94%81%E9%81%BF%E5%85%8D)、[死锁检测](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#24-%E6%AD%BB%E9%94%81%E6%A3%80%E6%B5%8B))
+ >死锁条件:
+ >>互斥:一个资源要么被某个进程占有,要么就是可用的。
+ >>占有和等待:进程可以在占有某个资源的情况下请求新的资源。
+ >>不可抢占:已经被某个进程占有的资源不可被其他进程强制抢占。
+ >>环路等待:进程组成一个环路,每个进程都在请求下一个进程占有的资源。
+ >死锁预防:
+ >>破坏互斥:进程只能按顺序请求资源。
- 3)互斥和同步?(互斥是对资源独占访问,同步是在互斥基础上通过其它机制实现对资源有序访问)
- 4)互斥量和信号量的区别?(一个互斥一个同步、值的区别、加锁解锁的线程)
- 5)自旋锁和互斥量的区别?(失败后的表现,一个忙等一个睡眠)
@@ -34,7 +72,8 @@
- 5)[缓冲区溢出是什么?会造成什么危害呢?出现原因是什么?](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#51-%E7%BC%93%E5%86%B2%E5%8C%BA%E6%BA%A2%E5%87%BA)
- 6)分区、分页和分段都要求程序整个载入内存(分区要求连续,分页和分段不要求连续)
- 7)[虚拟内存](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#%E7%AC%AC%E4%B8%83%E7%AB%A0%E8%99%9A%E6%8B%9F%E5%86%85%E5%AD%98)(内存管理单元(MMU):CPU中的一个模块,可以将虚拟地址转换成实际物理地址)
- + 虚拟内存的作用?(程序可以比实际物理内存更大、程序不必完全载入内存即可运行,因此活动进程数更多、系统抖动?)
+ + 虚拟内存的作用?(程序可以比实际物理内存更大、程序不必完全载入内存即可运行,因此活动进程数更多、系统抖动?)
+ >程序拥有自己的地址空间,地址空间被分割成页,一部分页被载入到物理内存中,当程序执行时访问到不在物理内存中的页时,引发缺页中断,操作系统把引发中断的地址空间的页载入内存,重新执行指令。
+ 基于[分页](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#1-%E5%88%86%E9%A1%B5)的实现(和不使用虚拟内存相比,页表中多了2个位,一位表示页是否修改,一位表示页是否在内存中、[两级分页系统](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#13-%E4%B8%A4%E7%BA%A7%E5%88%86%E9%A1%B5%E7%B3%BB%E7%BB%9F%E4%B8%AD%E7%9A%84%E5%9C%B0%E5%9D%80%E8%BD%AC%E6%8D%A2))
+ [TLB?](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#15-%E8%BD%AC%E6%8D%A2%E6%A3%80%E6%B5%8B%E7%BC%93%E5%86%B2%E5%8C%BAtlb)(加速页表的访问)
+ 基于[分段](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#2-%E5%88%86%E6%AE%B5)的实现(和不使用虚拟内存相比,段表中多了2个位,一位表示段是否修改,一位表示段是否在内存中)
@@ -42,7 +81,13 @@
+ 基于[段页式](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#3-%E6%AE%B5%E9%A1%B5%E5%BC%8F)的实现
* 分段对程序员可见、分页对程序员透明
* 分段有助于扩展性与内存保护、分页有助于消除外部碎片
- + [页面置换方法详细介绍](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#43-%E7%BD%AE%E6%8D%A2%E7%AD%96%E7%95%A5)(OPT、LRU、FIFO、时钟)
+ + [页面置换方法详细介绍](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#43-%E7%BD%AE%E6%8D%A2%E7%AD%96%E7%95%A5)(OPT、LRU、FIFO、时钟)
+ >OPT:置换下次使用距当前时间最短的页。
+ >LRU:置换上次使用距当前时间最短的页。
+ >NRU:用两个状态位标识R和M,访问到则将R置为1,修改则将M置为1同时R置为0,置换出被修改的脏页面,而不是频繁使用的干净页面。
+ >FIFO:置换最先进入的页面,可能会导致经常使用的页也被置换,增高缺页率。
+ >第二次机会:如果最先进入的页面R为1,则将其放入链表尾端,重新选择链表头部的页面置换。
+ >时钟:用环形链表将页面连接起来,用一个指针指向最老的页面。
+ [驻留集](https://github.com/arkingc/note/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md#44-%E9%A9%BB%E7%95%99%E9%9B%86%E7%AE%A1%E7%90%86)(分配给每个进程的内存大小)管理
* 驻留集越小,获得进程越多,缺页越高;驻留集越大,活动进程越小,太多时缺页率无明显变化
* 分配策略与置换范围(固定分配、可变分配、局部置换、全局置换,不存在固定分配全局置换)
diff --git a/pic/apue-filestat-13.png b/pic/apue-filestat-13.png
index 1ea8dfa..95cd111 100644
Binary files a/pic/apue-filestat-13.png and b/pic/apue-filestat-13.png differ
diff --git a/pic/apue-threadctr-5.png b/pic/apue-threadctr-5.png
index 6fc8c97..822aeb2 100644
Binary files a/pic/apue-threadctr-5.png and b/pic/apue-threadctr-5.png differ
diff --git a/pic/c++-class-1.png b/pic/c++-class-1.png
index 8f33d61..1c9b20c 100644
Binary files a/pic/c++-class-1.png and b/pic/c++-class-1.png differ
diff --git a/pic/docker-2-1.png b/pic/docker-2-1.png
index 441b206..d2c3ccc 100644
Binary files a/pic/docker-2-1.png and b/pic/docker-2-1.png differ
diff --git a/pic/git-1.png b/pic/git-1.png
index 0a17af2..1265462 100644
Binary files a/pic/git-1.png and b/pic/git-1.png differ
diff --git a/pic/git-2.png b/pic/git-2.png
index 5599c60..1eda6d6 100644
Binary files a/pic/git-2.png and b/pic/git-2.png differ
diff --git a/pic/git-3.png b/pic/git-3.png
index a4f6217..bdf64f6 100644
Binary files a/pic/git-3.png and b/pic/git-3.png differ
diff --git a/pic/leetcode-146-1.png b/pic/leetcode-146-1.png
index c604c5a..bae98cd 100644
Binary files a/pic/leetcode-146-1.png and b/pic/leetcode-146-1.png differ
diff --git a/pic/net-2-12.png b/pic/net-2-12.png
index b4f6f92..4d60f7e 100644
Binary files a/pic/net-2-12.png and b/pic/net-2-12.png differ
diff --git a/pic/net-5-5.png b/pic/net-5-5.png
index b699ad4..e70b64a 100644
Binary files a/pic/net-5-5.png and b/pic/net-5-5.png differ
diff --git a/pic/net-5-7.png b/pic/net-5-7.png
index 72e06e1..cf48d8f 100644
Binary files a/pic/net-5-7.png and b/pic/net-5-7.png differ
diff --git a/pic/os-5-extra1.jpeg b/pic/os-5-extra1.jpeg
index e0b1e12..209170c 100644
Binary files a/pic/os-5-extra1.jpeg and b/pic/os-5-extra1.jpeg differ
diff --git a/pic/os-5-extra6.png b/pic/os-5-extra6.png
index d4c783c..a873401 100644
Binary files a/pic/os-5-extra6.png and b/pic/os-5-extra6.png differ
diff --git a/pic/os-5-extra7.png b/pic/os-5-extra7.png
index 89f364c..9578529 100644
Binary files a/pic/os-5-extra7.png and b/pic/os-5-extra7.png differ
diff --git a/pic/os-7-10.jpeg b/pic/os-7-10.jpeg
index 6413496..1a4713f 100644
Binary files a/pic/os-7-10.jpeg and b/pic/os-7-10.jpeg differ
diff --git a/pic/stl-1-1.png b/pic/stl-1-1.png
index 1df34f8..e0916de 100644
Binary files a/pic/stl-1-1.png and b/pic/stl-1-1.png differ
diff --git a/pic/stl-4-1.jpeg b/pic/stl-4-1.jpeg
index 0677844..ef6e15b 100644
Binary files a/pic/stl-4-1.jpeg and b/pic/stl-4-1.jpeg differ
diff --git a/pic/stl-4-12.png b/pic/stl-4-12.png
index 747cbc8..9b1b7bd 100644
Binary files a/pic/stl-4-12.png and b/pic/stl-4-12.png differ
diff --git a/pic/unp-8-2.png b/pic/unp-8-2.png
index 1d7dc03..e4ffc8f 100644
Binary files a/pic/unp-8-2.png and b/pic/unp-8-2.png differ
diff --git a/pic/unp-design-2.jpeg b/pic/unp-design-2.jpeg
index 435de24..7290b62 100644
Binary files a/pic/unp-design-2.jpeg and b/pic/unp-design-2.jpeg differ
diff --git a/pic/unp-io-5.jpeg b/pic/unp-io-5.jpeg
index 8182368..57a5520 100644
Binary files a/pic/unp-io-5.jpeg and b/pic/unp-io-5.jpeg differ
diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/\346\267\261\345\205\245\347\220\206\350\247\243Linux\345\206\205\346\240\270/book.md" "b/\346\223\215\344\275\234\347\263\273\347\273\237/\346\267\261\345\205\245\347\220\206\350\247\243Linux\345\206\205\346\240\270/book.md"
new file mode 100644
index 0000000..388b37e
--- /dev/null
+++ "b/\346\223\215\344\275\234\347\263\273\347\273\237/\346\267\261\345\205\245\347\220\206\350\247\243Linux\345\206\205\346\240\270/book.md"
@@ -0,0 +1,226 @@
+
+* [五.内核同步](#五内核同步)
+ * [1.内核如何为不同的请求提供服务](#1内核如何为不同的请求提供服务)
+ * [1.1 内核抢占](#11-内核抢占)
+ * [2.同步原语](#2同步原语)
+ * [2.1 每CPU变量](#21-每cpu变量)
+ * [2.2 原子操作](#22-原子操作)
+ * [2.3 优化和内存屏障](#23-优化和内存屏障)
+ * [2.4 自旋锁](#24-自旋锁)
+ * [2.5 读/写自旋锁](#25-读写自旋锁)
+ * [2.6 顺序锁](#26-顺序锁)
+ * [2.7 读-拷贝-更新(RCU)](#27-读-拷贝-更新rcu)
+ * [2.8 信号量](#28-信号量)
+ * [2.9 读/写信号量](#29-读写信号量)
+ * [3.对内核数据结构的同步访问](#3对内核数据结构的同步访问)
+
+
+
+
+
+# 五.内核同步
+
+> 可以把内核看作是不断对请求进行响应的服务器,这些请求可能来自在CPU上执行的进程,也可能来自发出中断请求的外部设备
+
+
+
+## 1.内核如何为不同的请求提供服务
+
+把内核看作必须满足2种请求的侍者:
+
+1. 来自顾客的请求(相当于用户态进程发出的**系统调用**或**异常**,这章剩余部分将笼统地表示为“异常”)
+2. 来自数量有限的几个不同老板的请求(相当于**中断**)
+
+对不同请求,采用如下策略:
+
+* 老板提出请求时,如果侍者正空闲,则为老板服务;
+* 如果老板提出请求时侍者正在为顾客服务,那么停止为顾客服务,开始服务老板;
+* 如果老板提出请求时侍者正在为另一个老板服务,那么停止为第一个老板服务,为第二个老板服务后再继续服务第一个老板;
+* 一个老板可能命令侍者停止服务顾客。在完成对老板最近请求的服务后,可能暂时不理会原来的顾客而去为新选中的顾客服务
+
+侍者提供的服务对应于CPU处于内核态时所执行的代码、如果CPU在用户态执行,则侍者被认为处于空闲状态
+
+### 1.1 内核抢占
+
+无论在抢占还是非抢占内核中,运行在内核态的进程都可以**自动放弃CPU**,比如,进程由于等待资源而不得不转入睡眠状态。我们将把这种进程切换称为**计划性进程切换**。但是,抢占式内核在响应引起进程切换的异步事件(如唤醒高优先权进程的中断处理程序)的方式上与非抢占内核有差别,我们将把这种进程切换称作**强制性进程切换**
+
+**内核抢占的主要特点是:一个在内核态运行的进程,可能在执行内核函数期间被另外一个进程取代**
+
+下面例子说明抢占内核与非抢占内核的区别:
+
+* 当进程A执行异常处理程序时(肯定在内核态),一个具有较高优先级的进程B变为可执行状态。例如,发生了中断请求而且相应的处理程序唤醒了进程B
+ * 如果内核是抢占的,就会发生强制性进程切换,让进程B取代进程A。异常处理程序的执行暂停,直到调度程序再次选择进程A才恢复执行
+ * 如果内核是非抢占的,在进程A完成异常处理程序的执行之前是不会发生进程切换的,除非进程A自动放弃CPU
+* 考虑一个执行异常处理程序的进程已经用完了它的时间配额
+ * 如果内核是抢占的,进程可能会立即被取代
+ * 如果内核是非抢占的,进程继续运行直到它执行完异常处理程序或自动放弃CPU
+
+**使内核可抢占的目的是减少用户态进程的分派延迟,即从进程变为可执行状态到它实际开始运行之间的时间间隔**
+
+使Linux2.6内核具有可抢占的特性无需对支持非抢占的旧内核在设计上做太大的改变,当被`current_thread_info()`宏所引用的`thread_info`描述符的`preempt_count`字段大于`0`时,就禁止内核抢占。下列宏处理`preempt_count`字段的抢占计数器:
+
+
+
+**内核抢占会引起不容忽视的开销。Linux2.6独具特色地允许用户在编译内核时通过设置选项来禁用或启用内核抢占**
+
+
+
+## 2.同步原语
+
+下表是Linux内核使用的同步技术。“适用范围”一栏表示同步技术是适用于系统中所有CPU还是单个CPU:
+
+
+
+### 2.1 每CPU变量
+
+**每CPU变量主要是数据结构的数组,系统的每个CPU对应数组的一个元素**
+
+* 一个CPU不应该访问与其他CPU对应的数组元素,另外,它可以随意读或修改自己的元素而不用担心出现竞争条件,因为它是唯一有资格这么做的CPU
+* 但是,这也意味着每CPU变量基本上只能在特殊情况下使用,也就是当它确定在系统的CPU上的数据在逻辑上是独立的时候
+
+> 每CPU的数组元素在内存中被排列以使每个数据结构存放在硬件高速缓存的不同行,因此,对每CPU数组的并发访问不会导致cache-line的窃用和失效
+
+**在单处理器和多处理器系统中,内核抢占都可能使每CPU变量产生竞争条件。总的原则是内核控制路径应该在禁用抢占的情况下访问每CPU变量**。考虑这种情况会产生什么后果——一个内核控制路径获得了它的每CPU变量本地副本的地址,然后它因被抢占而转移到另外一个CPU上,但仍然引用原来CPU元素的地址
+
+### 2.2 原子操作
+
+若干汇编语言指令具有“读—修改—写”类型。也就是说,它们访问存储器单元两次,第一次读原值,第二次写新值
+
+为了避免由于“读—修改—写”指令引起的竞争条件,最容易的就是确保这样的操作在芯片级是原子的。任何一个这样的操作都必须以单个指令执行,
+
+1. 中间不能中断
+2. 且避免其他的CPU访问同一存储器单元
+
+80x86指令:
+
+* 进行零次或一次对齐内存访问的汇编指令是原子的
+* 如果在读操作之后,写操作之前没有其他处理器占用内存总线,那么从内存中读取数据,更新数据并写回更新数据的这些“读—修改—写”汇编语言指令(如`inc`或`dec`)是原子的。当然,在单处理器系统中,永远都不会发生内存总线窃用的情况
+* 操作码前缀是`lock`字节的“读—修改—写”汇编语言指令即使在多处理器系统中也是原子的。当控制单元检测到这个前缀时,就“锁定”内存总线,知道这条指令执行完成为止。所以加锁的指令执行时,其它处理器不能访问这个内存单元
+
+C程序中,并不能保证编译器会为`a=a+1`或甚至像`a++`这样的操作使用一个原子指令。因此,Linux内核提供了一个专门的`atomic_t`类型(一个原子访问计数器)和一些专门的函数和宏,这些函数和宏作用于`atomic_t`类型的变量,并当作单独的、原子的汇编语言指令来使用。在多处理器系统中,每条这样的指令都有一个`lock`字节的前缀
+
+### 2.3 优化和内存屏障
+
+> 当使用优化的编译器时,不要认为指令会严格按照源代码中出现的顺序执行(例如,编译器可能重新安排汇编语言指令以使寄存器以最优的方式使用。此外,现代CPU通常并行地执行若干条指令,且可能重新安排内存访问。这种重新排序可以极大加速程序的执行)
+
+所有的同步原语起优化和内存屏障的作用
+
+**优化屏障**原语保证,编译程序不会混淆放在原语操作之前的汇编语言指令和放在原语操作之后的汇编语言指令
+
+**内存屏障**原语保证,在原语之后的操作开始执行之前,原语之前的操作已经完成
+
+### 2.4 自旋锁
+
+自旋锁用在**多处理器环境中**
+
+* 如果内核控制路径发现自旋锁“开着”,就获取锁并继续自己的执行
+* 如果内核控制路径发现锁由运行在另一个CPU上的内核控制路径“锁着”,则反复执行一条紧凑的循环指令进行忙等,直到锁被释放
+
+自旋锁通常非常方便,因为很多内核资源只锁1毫秒的时间片段;所以说,释放CPU和随后又获得CPU都不会消耗多少时间
+
+一般来说,由自旋锁所保护的每个临界区都是禁止内核抢占的。在单处理系统上,这种锁本身并不起锁的作用,自旋锁的原语仅仅是禁止或启用内核抢占(注意,自旋锁忙等期间,内核抢占还是有效的,因此,等待自旋锁释放的进程有可能被更高优先级的进程替代)
+
+### 2.5 读/写自旋锁
+
+**只要没有内核控制路径对数据结构进行修改,读/写自旋锁就允许多个内核控制路径同时读同一数据结构。如果一个内核控制路径想对这个结构进行写操作,那么它必须首先获取读/写锁的写锁,写锁授权独占访问这个资源**
+
+每个读/写自旋锁都是一个`rwlock_t`结构,其`lock`字段是一个32位的字段,分为两个不同的部分:
+
+* **24位计数器**,表示对受保护的数据结构并发地进行读操作的内核控制路径的数目,这个计数器的二进制补码存放在这个字段的`0~23`位
+* **“未锁”标志字段**,当没有内核控制路径在读或写时设置该位,否则清`0`.这个“未锁”标志存放在`lock`字段的第`24`位
+
+注意:
+
+* 如果自旋锁为空(设置了“未锁”标志且无读者),那么`lock`字段的值为`0x01000000`
+* 如果写者已经获得自旋锁(“未锁”标志清`0`且无读者),那么`lock`字段的值为`0x00000000`
+* 如果一个、两个或多个进程因为读获取了自旋锁,那么`lock`字段的值为`0x00ffffff`,`0x00fffffe`等
+
+### 2.6 顺序锁
+
+**当使用读/写锁时,内核控制路径发出的执行`read_lock`或`write_lock`操作的请求具有相同的优先权**。读者必须等待,直到写操作完成。同样地,写者也必须等待,直到读操作完成
+
+Linux2.6引入了**顺序锁,它与读/写自旋锁非常相似,只是它为写者赋予了较高的优先级(事实上,即使在读者正在读的时候也允许写者继续运行。这种策略的好处是写者永远不会等待,除非另一个写者正在写,缺点是有些时候读者不得不反复多次读相同的数据直到它获得有效的副本)**
+
+每个顺序锁都是包含2个字段的`seqlock_t`结构:
+
+```c
+struct seqlock_t {
+ spinlock_t lock
+ int sequence //顺序计数器
+}
+```
+
+每个读者都必须在读数据前后两次读顺序计数器,并检查两次读到的值是否相同,如果不相同,说明新的写者已经开始写并增加了顺序计数器,因此暗示读者刚读到的数据是无效的
+
+写者通过调用`write_seqlock()`和`write_sequnlock()`获取和释放顺序锁
+
+* 第一个函数获取`seqlock_t`数据结构中的自旋锁,然后使顺序计数器加1
+* 第二个函数再次增加顺序计数器,然后释放自旋锁
+
+这样可以保证写者写的过程中计数器的值是奇数,当没有写者在改变数据的时候,计数器的值是偶数
+
+注意,当读者进入临界区时,不必禁用内核抢占;另一方面,由于写者获取自旋锁,所以它进入临界区时自动禁用内核抢占
+
+### 2.7 读-拷贝-更新(RCU)
+
+> RCU是Linux 2.6新加的功能,用在网络层和虚拟文件系统(VFS)中
+
+读-拷贝-更新(RCU)是为了保护在多数情况下被多个CPU读的数据结构而设计的另一种同步技术。**RCU允许多个读者和写者并发执行(相对于只允许一个写者执行的顺序锁有了改进)而且,RCU你使用锁,即不使用被所有CPU共享的锁或计数器,这一点与读/写自旋锁和顺序锁(由于cache-line窃用和失效而有很高的开销)相比,RCU具有更大的优势**
+
+RCU的关键思想包括限制RCP的范围:
+
+1. RCU只保护被动态分配并通过指针引用的数据结构
+2. 在被RCU保护的临界区中,任何内核控制路径都不能睡眠
+
+当内核控制路径要读取被RCU保护的数据结构时,执行宏`rcu_read_lock()`,它等同于`preempt_disable()`。接下来,读者间接引用该数据结构指针所对应的内存单元并开始读这个数据结构。读者在完成对数据结构的读操作之前,是不能睡眠的。用等同于`preempt_enable()`的宏`rcu_read_unlock()`标记临界区的结束
+
+读者几乎不做任何事来防止竞争条件出现,所以写者不得不做更多。当写者要更新数据结构时,它间接引用指针并生成整个数据结构的副本。接下来,写者修改这个副本。一旦修改完毕,写者改变指向数据结构的指针,以使它指向被修改后的副本。由于修改指针值的操作是一个原子操作,所以旧副本和新副本对每个读者或写者都是可见的,在数据结构中不会出现数据崩溃。尽管如此,还需要内存屏障来保证:只有在数据结构被修改之后,已更新的指针对其他CPU才是可见的。如果把自旋锁和RCU结合起来以禁止写者的并发执行,就隐含地引入这样的内存屏障
+
+然而,使用RCU技术的真正困难在于:写者修改指针时不能立即释放数据结构的旧副本。实际上,写者开始修改时,正在访问数据结构的读者可能还在读旧副本。只有在CPU上的所有(潜在的)读者都执行完宏`rcu_read_unlock()`之后,才可以释放旧副本。内核要求每个潜在的读者在下面的操作之前执行`rcu_read_unlock()`:
+
+* CPU执行进程切换
+* CPU开始在用户态执行
+* CPU执行空循环
+
+对上述每种情况,我们说CPU已经经过静止状态
+
+写者调用函数`call_rcu()`来释放数据结构的旧副本。当所有的CPU都通过静止状态之后,`call_rcu()`接受`rcu_head`描述符(通常嵌在要被释放的数据结构中)的地址和将要调用的回调函数的地址作为参数。一旦回调函数被执行,它通常释放数据结构的旧副本
+
+函数`call_rcu()`把回调函数和其参数的地址存放在`rcu_head`描述符中,然后把描述符插入回调函数的每CPU(per-CPU)链表中。内核每经过一个时钟滴答就周期性地检查本地CPU是否经过了一个静止状态。如果所有CPU都经过了静止状态,本地tasklet(它的描述符存放在每CPU变量`rcu_tasklet`中)就执行链表中的所有回调函数
+
+### 2.8 信号量
+
+> 这里主要指内核信号量,Linux提供的System V IPC信号量,由用户态进程使用
+
+内核信号量类似于自旋锁,因为当锁关闭时,它不允许内核控制路径继续进行。然而,当内核控制路径试图获取内核信号量所保护的忙资源时,相应的进程被挂起。只有在资源被释放时,进程才再次变为可运行的。因此,只有可以睡眠的函数才能获取内核信号量:中断处理程序和可延迟函数都不能使用内核信号量
+
+### 2.9 读/写信号量
+
+读/写信号量类似于”读/写自旋锁“,不同之处是:信号量再次变为打开之前,等待的进程挂起而不是自旋
+
+很多内核控制路径为读可以并发地获取读/写信号量。但是,任何写者内核控制路径必须有对被保护资源的互斥访问。因此,只有在没有内核控制路径为读访问或写访问持有信号量时,才可以为写获取信号量。读/写信号量可以提高内核中的并发度
+
+内核以严格的FIFO顺序处理等待读/写信号量的所有进程。如果读者或写者进程发现信号量关闭,这些进程就被插入到信号量等待队列链表的末尾。当信号量被释放时,就检查处于等待队列链表第一个位置的进程。第一个进程被唤醒。如果是一个写者进程,等待队列上其他的进程就继续睡眠。如果是一个读者进程,那么紧跟第一个进程的其它所有读者进程也被唤醒并获得锁。不过,在写者进程之后排队的读者进程继续睡眠
+
+
+
+## 3.对内核数据结构的同步访问
+
+系统中的并发度取决于两个主要因素:
+
+* 同时运转的I/O设备数
+* 进行有效工作的CPU数
+
+为了使I/O吞吐量最大化,应该使中断禁止保持在很短的时间。正如第四章的”IRQ和中断“一节描述的那样,当中断被禁止时,由I/O设备产生的IRQ被PIC暂时忽略,因此,就没有新的活动在这种设备上开始
+
+为了有效地利用CPU,应该尽可能避免使用基于自旋锁的同步原语。当一个CPU执行紧指令循环等待自旋锁打开时,是在浪费宝贵的机器周期。就像我们前面所描述的,更糟糕的是:由于自旋锁对硬件高速缓存的影响而使其对系统的整体性能产生不利影响
+
+在以下两种情况下,既可以维持较高的并发度,也可以达到同步:
+
+* 共享的数据结构是一个单独的整数值,可以把它声明为`atomic_t`类型并使用原子操作对其更新。原子操作比自旋锁和中断禁止都快,只有在几个内核控制路径同时访问这个数据结构时速度才会慢下来
+* 把一个元素插入到共享链表的操作决不是原子的,因为这至少涉及两个指针赋值。不过,内核有时并不用锁或禁止中断就可以执行这种插入操作。考虑如下代码,在汇编语言中,插入简化为两个连续的原子指令。第一条指令建立`new`元素的`next`指针,但不修改链表。因此,如果中断处理程序在第一条指令和第二条指令执行的中间查看这个链表,看到的就是没有新元素的链表。如果该处理程序在第二条指令执行后查看链表,就会看到有新的元素的链表。关键是,在任一情况下,链表都是一致的且处于未损坏状态。然而,只有在中断处理程序不修改链表的情况下才能保证这种完整性。如果修改了链表,那么在`new`元素内刚刚设置的`next`指针就可能变为无效的。同时,开发者必须确保两个赋值操作的顺序不被编译器或CPU控制单元搅乱。否则,如果中断处理程序在两个赋值之间中断了系统调用服务例程,处理程序就会看到一个损坏的链表。因此,就需要一个写内存屏障原语
+
+ ```c
+ new->next = list_element->next;
+ list_element->next = new;
+ ```
diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/\346\267\261\345\205\245\347\220\206\350\247\243Linux\345\206\205\346\240\270/pic/table-5-1.png" "b/\346\223\215\344\275\234\347\263\273\347\273\237/\346\267\261\345\205\245\347\220\206\350\247\243Linux\345\206\205\346\240\270/pic/table-5-1.png"
new file mode 100644
index 0000000..2719712
Binary files /dev/null and "b/\346\223\215\344\275\234\347\263\273\347\273\237/\346\267\261\345\205\245\347\220\206\350\247\243Linux\345\206\205\346\240\270/pic/table-5-1.png" differ
diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/\346\267\261\345\205\245\347\220\206\350\247\243Linux\345\206\205\346\240\270/pic/table-5-2.png" "b/\346\223\215\344\275\234\347\263\273\347\273\237/\346\267\261\345\205\245\347\220\206\350\247\243Linux\345\206\205\346\240\270/pic/table-5-2.png"
new file mode 100644
index 0000000..9ad7519
Binary files /dev/null and "b/\346\223\215\344\275\234\347\263\273\347\273\237/\346\267\261\345\205\245\347\220\206\350\247\243Linux\345\206\205\346\240\270/pic/table-5-2.png" differ
diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\345\233\276.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\345\233\276.md"
index e5e0221..831f220 100644
--- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\345\233\276.md"
+++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\345\233\276.md"
@@ -128,4 +128,10 @@ BFS执行过程将产生一棵**BFS(广度优先搜索)树**:
> 相关题目:
-> * [hihoCoder:1097.最小生成树一·Prim算法](http://hihocoder.com/problemset/problem/1097)
\ No newline at end of file
+> * [hihoCoder:1097.最小生成树一·Prim算法](http://hihocoder.com/problemset/problem/1097)
+
+### Dijkstra算法
+
+**原理**:是从一个顶点到其余各顶点的最短路径算法,解决的是有向图中最短路径的问题。特点:以起始点为中心向外层层扩展,直到扩展为终点是一种广度优先的搜索算法。
+最优子路径存在,假设S->E存在一条最短路径SE,且路径经过A,那么可以确定S->A一定是最短路径。
+**缺点**:算法能够求出从起点到每个结点的最短路径,因此要遍历所有的路径和节点,计算复杂度大。
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\216\222\345\272\217.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\216\222\345\272\217.md"
index 494550d..5e61f21 100644
--- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\216\222\345\272\217.md"
+++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\216\222\345\272\217.md"
@@ -1,43 +1,88 @@
-* **内排序**
- * [1.插入排序](#1插入排序)(稳定)
- * [2.冒泡排序](#2冒泡排序)(稳定)
- * [3.选择排序](#3选择排序)(不稳定)
- * [4.shell排序](#4shell排序)(不稳定)
- * [5.快速排序](#5快速排序)(不稳定)
- * [6.归并排序](#6归并排序)(稳定)
- * [7.堆排序](#7堆排序)(不稳定)
-* **外排序**
- * [1.多路归并](#1多路归并)
-
-> 稳定性:相同的元素在排序前和排序后的前后位置是否发生改变,没有改变则排序是稳定的,改变则排序是不稳定的 [——八大排序算法的稳定性](https://www.cnblogs.com/codingmylife/archive/2012/10/21/2732980.html)
-
-
-
-
-## 1.插入排序
-
-逐个处理待排序的记录,每个记录与前面已排序已排序的子序列进行比较,将它插入子序列中正确位置
-
+
+
+* [排序算法](#%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95)
+ * [1.插入排序](#1%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F)
+ * [代码](#%E4%BB%A3%E7%A0%81)
+ * [性能](#%E6%80%A7%E8%83%BD)
+ * [优化](#%E4%BC%98%E5%8C%96)
+ * [2.冒泡排序](#2%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F)
+ * [代码](#%E4%BB%A3%E7%A0%81-1)
+ * [性能](#%E6%80%A7%E8%83%BD-1)
+ * [优化](#%E4%BC%98%E5%8C%96-1)
+ * [3.选择排序](#3%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F)
+ * [代码](#%E4%BB%A3%E7%A0%81-2)
+ * [性能](#%E6%80%A7%E8%83%BD-2)
+ * [优化](#%E4%BC%98%E5%8C%96-2)
+ * [4.shell排序](#4shell%E6%8E%92%E5%BA%8F)
+ * [代码](#%E4%BB%A3%E7%A0%81-3)
+ * [性能](#%E6%80%A7%E8%83%BD-3)
+ * [5.快速排序](#5%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F)
+ * [代码](#%E4%BB%A3%E7%A0%81-4)
+ * [性能](#%E6%80%A7%E8%83%BD-4)
+ * [优化](#%E4%BC%98%E5%8C%96-3)
+ * [6.归并排序](#6%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F)
+ * [代码](#%E4%BB%A3%E7%A0%81-5)
+ * [性能](#%E6%80%A7%E8%83%BD-5)
+ * [优化](#%E4%BC%98%E5%8C%96-4)
+ * [堆排序](#%E5%A0%86%E6%8E%92%E5%BA%8F)
+ * [代码](#%E4%BB%A3%E7%A0%81-6)
+ * [性能](#%E6%80%A7%E8%83%BD-6)
+ * [常见面试问题](#%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%97%AE%E9%A2%98)
+ * [1.多路归并](#1%E5%A4%9A%E8%B7%AF%E5%BD%92%E5%B9%B6)
+ * [k的选择](#k%E7%9A%84%E9%80%89%E6%8B%A9)
+ * [优化](#%E4%BC%98%E5%8C%96-5)
+
+
+```diff
++here
+```
+
+#排序算法
+
+
+##1.插入排序
+- `低效 n平方` ` 稳定`
+- 逐个处理待排序的记录,每个记录与前面已排序已排序的子序列进行比较,将它插入子序列中正确位置
+- 步骤
+ - 从第一个元素开始,该元素可以认为已经被排序
+ - 取下一个元素,在已经排序的元素序列中从后向前扫描
+ - 如果该元素大于新元素,将该元素移到下一位
+ - 重复步骤三,直到找到已排序的元素小于或者等于新元素的位置
+ - 将新元素插入到该位置后面
+ - 重复
+
+
### 代码
```c++
-template
-void inssort(Elem A[],int n)
-{
- for(int i = 1;i < n;i++)
- for(int j = i;j >= 1 && A[j] < A[j-1];j--)
- swap(A,j,j-1);
+void insertSort(std::vector&nums){
+ if(nums.size() < 2) return;
+ int firstIndex = 0;
+ int len = nums.size();
+ for(int i = firstIndex + 1;i < len;i++){
+ int tmp = nums[i];
+ int j = i-1;
+ while(j >= firstIndex && nums[j] > tmp){
+ nums[j+1] = nums[j];
+ j--;
+ }
+
+ nums[j+1] = tmp;
+ }
}
```
+
### 性能
* 最佳:升序。时间复杂度为O(n)
* 最差:降序。时间复杂度为O(n^2)
* 平均:对于每个元素,前面有一半元素比它大。时间复杂度为O(n^2)
+* 实现上基本是in-place,在数组上实现。
>如果待排序数据已经“基本有序”,使用插入排序可以获得接近O(n)的性能
+
### 优化
```c++
@@ -56,63 +101,98 @@ void inssort(Elem A[],int n)
+
## 2.冒泡排序
+- $低效 n平方$ ` 稳定`
+- 扫描列表邻接元素,如果不是按照相对顺序排列则将他们交换,每经过一轮,冒泡排序算法都会将最大值移到最终的位置。
+- 虽然冒泡排序被认为时间复杂度$O(n^2)$最差,但是如果在循环中加入flag判断基本有序后跳出循环,对于基本有序的数组来说可以大大降低排序时间。
+- 将一个递减的数组bubble成递增的,那么就是冒泡排序的最坏情况,需要$n(n-1)/2$次比较和交换
-从数组的底部比较到顶部,比较相邻元素。如果下面的元素更小则交换,否则,上面的元素继续往上比较。这个过程每次使最小元素像个“气泡”似地被推到数组的顶部
-
+
### 代码
```c++
-template
-void bubsort(Elem A[],int n)
-{
- for(int i = 0;i < n - 1;i++)
- for(int j = n - 1;j > i;j--)
- if(A[j] < A[j-1])
- swap(A,j,j-1);
+void bubbleSort(vector&nums) {
+ int len = nums.size();
+ for(int i = 0; i < len; i++) {
+ bool flag = true; // 如果某一趟冒泡排序没有交换数据,数组基本有序,就可以退出循环
+ for(int j = 0; j < len - i - 1; j++) {
+ if(nums[j] > nums[j + 1]) {
+ int tmp = nums[j];
+ nums[j] = nums[j + 1];
+ nums[j + 1] = tmp;
+ flag = false;
+ }
+ }
+ if(flag) {
+ break;
+ }
+ }
+
+ return;
}
```
+
### 性能
-冒泡排序是一种相对较慢的排序,没有较好的最佳情况执行时间。通常情况下时间复杂度都是O(n^2)
+- 冒泡排序是一种相对较慢的排序,没有较好的最佳情况执行时间。通常情况下时间复杂度都是O(n^2),但是如果在循环中加入flag判断基本有序后跳出循环,对于基本有序的数组来说可以大大降低排序时间。
+- 将一个递减的数组bubble成递增的,那么就是冒泡排序的最坏情况,需要$n(n-1)/2$次比较和交换
+
### 优化
增加一个变量flag,用于记录一次循环是否发生了交换,如果没发生交换说明已经有序,可以提前结束
+
## 3.选择排序
-
+- `低效` $O(n^2)$ ` 不稳定`
+- 步骤
+ - 扫描整个列表找出最小值,将它与列表第一个位置的值交换
+ - 扫描剩余部分的列表,找出最小值,与该列表第二个位置处的值交换
+ - 对列表中的每一个位置继续使用该过程。
第i次“选择”数组中第i小的记录,并将该记录放到数组的第i个位置。换句话说,每次从未排序的序列中找到最小元素,放到未排序数组的最前面
+
### 代码
```c++
-template
-void selsort(Elem A[],int n)
-{
- for(int i = 0;i < n - 1;i++){
- int lowindex = i;
- for(int j = i + 1;j < n;j++)
- if(A[j] < A[lowindex])
- lowindex = j;
- swap(A,i,lowindex);//n次交换
+void selectSort(std::vector &nums) {
+ if(nums.size() < 2) return;
+
+ for(int i = 0; i < nums.size() - 1; i++) {
+ int minNum = i;
+ for(int j = i + 1; j < nums.size(); j++) {
+ if(nums[j] < nums[minNum]) {
+ minNum = j;
+ }
+ }
+
+ if(minNum != i) {
+ int tmp = nums[minNum];
+ nums[minNum] = nums[i];
+ nums[i] = tmp;
+ }
}
}
```
+
### 性能
-不管数组是否有序,在从未排序的序列中查找最小元素时,都需要遍历完最小序列,所以时间复杂度为O(n^2)
+- 不管数组是否有序,在从未排序的序列中查找最小元素时,都需要遍历完最小序列,所以时间复杂度为O(n^2)
+- 交换次数介于0和n-1之间,比较次数介于n(n-1)/2。交换次数少于冒泡排序,由于交换需要的CPU时间较多,所以n比较小的时候,选择排序比冒泡排序快。
+
### 优化
每次内层除了找出一个最小值,同时找出一个最大值(初始为数组结尾)。将最小值与每次处理的初始位置的元素交换,将最大值与每次处理的末尾位置的元素交换。这样一次循环可以将数组规模减小2,相比于原有的方案(减小1)会更快
+
## 4.shell排序
shell排序在不相邻的元素之间比较和交换。利用了插入排序的最佳时间代价特性,它试图将待排序序列变成基本有序的,然后再用插入排序来完成排序工作
@@ -123,6 +203,7 @@ shell排序在不相邻的元素之间比较和交换。利用了插入排序的

+
### 代码
```c++
@@ -144,6 +225,7 @@ void shellsort(Elem A[],int n)
}
```
+
### 性能
选择适当的增量序列可使Shell排序比其他排序法更有效,一般来说,增量每次除以2时并没有多大效果,而“增量每次除以3”时效果更好
@@ -152,39 +234,67 @@ void shellsort(Elem A[],int n)
+
## 5.快速排序
-首先选择一个轴值,小于轴值的元素被放在数组中轴值左侧,大于轴值的元素被放在数组中轴值右侧,这称为数组的一个分割(partition)。快速排序再对轴值左右子数组分别进行类似的操作
+- `高效 nlogn` ` 不稳定`
+- 基本思想:通过一趟排序将待排记录分隔成独立的两部分,一部分的关键字比
+- 是所有排序算法中`最高效`的一种,采用了分治的思想:先保证列表的前半部分都小于后半部分,然后再对前半部分和后半部分排序,这样整个列表就有序了。
+- 最坏情况:$O(n^2)$比较,但是这种q
选择轴值有多种方法。最简单的方法是使用首或尾元素。但是,如果输入的数组是正序或者逆序时,会将所有元素分到轴值的一边。较好的方法是随机选取轴值
+
### 代码
```c++
-template
-int partition(Elem A[],int i,int j)
-{
- //这里选择尾元素作为轴值,轴值的选择可以设计为一个函数
- //如果选择的轴值不是尾元素,还需将轴值与尾元素交换
- int pivot = A[j];
- int l = i - 1;
- for(int r = i;r < j;r++)
- if(A[r] <= pivot)
- swap(A,++l,r);
- swap(A,++l,j);//将轴值从末尾与++l位置的元素交换
- return l;
+void quickSort(int data[],int low,int high){
+ if(low < high){
+ //create partition
+ int indexofpartition = partition(data,min,max);
+ //sort the left partition
+ quickSort(data,min,indexofpartition - 1);
+ //sort the right partition
+ quickSort(data,indexofpartion + 1,max);
+ }
}
-template
-void qsort(Elem A[],int i,int j)
-{
- if(j <= i) return;
- int p = partition(A,i,j);
- qsort(A,i,p - 1);
- qsort(A,p + 1,j);
+int partition(int data[],int low,int high){
+ int partitiononelement;
+ int left,right;
+ int middle = (min + max)/2;
+ //using the middle data as the partition element
+ partitiononelement = data[middle];
+ swap(data,middle,low);
+ left = low;
+ right = high;
+
+ while(left < right){
+ //search an element that is big than the partition element
+ while(leftpartitiononelement){
+ right--;
+ }
+ if(left < right){
+ swap(data,left,right);
+ }
+ }
+ swap(data,low,right);
+ return right;
+}
+
+void swap(int data[],int a,int b){
+ int tmp;
+ tmp = data[a];
+ data[a] = data[b];
+ data[b] = tmp;
}
```
+
### 性能
* 最佳情况:O(nlogn)
@@ -193,64 +303,72 @@ void qsort(Elem A[],int i,int j)
> 快速排序平均情况下运行时间与其最佳情况下的运行时间很接近,而不是接近其最坏情况下的运行时间。**快速排序是所有内部排序算法中平均性能最优的排序算法**
+
### 优化
1. 最明显的改进之处是轴值的选取,如果轴值选取合适,每次处理可以将元素较均匀的划分到轴值两侧:
- **三者取中法**:三个随机值的中间一个。为了减少随机数生成器产生的延迟,可以选取首中尾三个元素作为随机值
+ **三者取中法**:三个随机值的中间一个。为了减少随机数生成器产生的延迟,可以选取首中尾三个元素作为随机值
2. 当n很小时,快速排序会很慢。因此当子数组小于某个长度(经验值:9)时,什么也不要做。此时数组已经基本有序,最后调用一次插入排序完成最后处理
+
## 6.归并排序
-将一个序列分成两个长度相等的子序列,为每一个子序列排序,然后再将它们合并成一个序列。合并两个子序列的过程称为归并
+- `nlog2n最坏 ` ` 稳定`
+- 步骤
+ - Divide:将长度为n的输入序列分成两个长度为n/2 的子序列
+ - Conquer:对两个子序列分别采用归并排序
+ - Combine:将两个排序好的子序列合并成一个最终的排序序列
+
### 代码
```c++
-template
-void mergesortcore(Elem A[],Elem temp[],int i,int j)
-{
- if(i == j) return;
- int mid = (i + j)/2;
-
- mergesortcore(A,temp,i,mid);
- mergesortcore(A,temp,mid + 1,j);
-
- /*归并*/
- int i1 = i,i2 = mid + 1,curr = i;
- while(i1 <= mid && i2 <= j){
- if(A[i1] < A[i2])
- temp[curr++] = A[i1++];
- else
- temp[curr++] = A[i2++];
+void MergeArray(std::vector &nums, int first, int mid, int last) {
+ int i = first, j = mid + 1;
+ int first_end = mid, second_end = last;
+ std::vector result;
+ while(i <= first_end && j <= second_end) {
+ if(nums[i] <= nums[j]) {
+ result.push_back(nums[i++]);
+ } else {
+ result.push_back(nums[j++]);
+ }
+ }
+
+ while(i <= first_end) {
+ result.push_back(nums[i++]);
+ }
+ while(j <= second_end) {
+ result.push_back(nums[j++]);
+ }
+
+ for(int i = 0; i < result.size(); i++) {
+ nums[first + i] = result[i];
}
- while(i1 <= mid)
- temp[curr++] = A[i1++];
- while(i2 <= j)
- temp[curr++] = A[i2++];
- for(curr = i;curr <= j;curr++)
- A[curr] = temp[curr];
}
-template
-void mergesort(Elem A[],int sz)
-{
- Elem *temp = new Elem[sz]();
- int i = 0,j = sz - 1;
- mergesortcore(A,temp,i,j);
- delete [] temp;
+void MergeSort(vector &nums, int first, int last) {
+ if(first < last) {
+ int mid = (first + last) / 2;
+ MergeSort(nums, first, mid);
+ MergeSort(nums, mid + 1, last);
+ MergeArray(nums, first, mid, last);
+ }
}
```
+
### 性能
logn层递归中,每一层都需要O(n)的时间代价,因此总的时间复杂度是O(nlogn),该时间复杂度不依赖于待排序数组中数值的相对顺序。因此,是最佳,平均和最差情况下的运行时间
由于需要一个和带排序数组大小相同的辅助数组,所以空间代价为O(n)
+
### 优化
原地归并排序不需要辅助数组即可归并
@@ -291,12 +409,14 @@ void merge(int *arr,int begin,int mid,int end)
-## 7.堆排序
+
+## [堆排序](https://blog.csdn.net/han_xiaoyang/article/details/12163251)
堆排序首先根据数组构建最大堆,然后每次“删除”堆顶元素(将堆顶元素移至末尾)。最后得到的序列就是从小到大排序的序列

+
### 代码
这里直接使用C++ STL中堆的构建与删除函数
@@ -318,110 +438,81 @@ void heapsort(Elem A[],int n)
如果不能使用现成的库函数:
```c++
-/********************************************
- * 向堆中插入元素
- * hole:新元素所在的位置
- ********************************************/
-template
-void _push_heap(vector &arr,int hole){
- value v = arr[hole];//取出新元素,从而产生一个空洞
- int parent = (hole - 1) / 2;
- //建最大堆,如果建最小堆换成 arr[parent] > value
- while(hole > 0 && arr[parent] < v){
- arr[hole] = arr[parent];
- hole = parent;
- parent = (hole - 1) / 2;
+void minHeapfixdown(std::vector &nums, int i, int len) {
+ // int len = nums.size();
+ int temp = nums[i];
+ int j = 2 * i + 1;
+ while(j < len) {
+ if(j + 1 < len && nums[j + 1] < nums[j])
+ j++;
+ if(nums[j] >= temp)
+ break;
+ nums[i] = nums[j];
+ i = j;
+ j = 2 * i + 1;
}
- arr[hole] = v;
+ nums[i] = temp;
}
-/********************************************
- * 删除堆顶元素
- ********************************************/
-template
-void _pop_heap(vector &arr,int sz)
-{
- value v = arr[sz - 1];
- arr[sz - 1] = arr[0];
- --sz;
- int hole = 0;
- int child = 2 * (hole + 1); //右孩子
- while(child < sz){
- if(arr[child] < arr[child - 1])
- --child;
- arr[hole] = arr[child];
- hole = child;
- child = 2 * (hole + 1);
- }
- if(child == sz){
- arr[hole] = arr[child - 1];
- hole = child - 1;
+void buildHeap(std::vector &nums) {
+ int len = nums.size();
+ for(int i = len / 2 - 1; i >= 0; i--) {
+ minHeapfixdown(nums, i, len);
}
- arr[hole] = v;
- _push_heap(arr,hole);
}
-/********************************************
- * 建堆
- * sz:删除堆顶元素后的大小
- * v: 被堆顶元素占据的位置原来的元素的值
- ********************************************/
-template
-void _make_heap(vector &arr)
-{
- int sz = arr.size();
- int parent = (sz - 2) / 2;
- while(parent >= 0){
- int hole = parent;
- int child = 2 * (hole + 1); //右孩子
- value v = arr[hole];
- while(child < sz){
- if(arr[child] < arr[child - 1])
- --child;
- arr[hole] = arr[child];
- hole = child;
- child = 2 * (hole + 1);
- }
- if(child == sz){
- arr[hole] = arr[child - 1];
- hole = child - 1;
- }
- arr[hole] = v;
- _push_heap(arr,hole);
- --parent;
+void Swap(std::vector &v, int i, int j) {
+ int len = v.size();
+ if(i < 0 || j < 0 || i >= len || j >= len) {
+ return;
}
+ int temp = v[i];
+ v[i] = v[j];
+ v[j] = temp;
+ return;
}
-template
-void heap_sort(vector &arr)
-{
- _make_heap(arr);
- for(int sz = arr.size();sz > 1;sz--)
- _pop_heap(arr,sz);
+void Minheapsort(std::vector &nums) {
+ int len = nums.size();
+ if(len < 2) return;
+ buildHeap(nums);
+ for(int i = len - 1; i > 0; i--) {
+ Swap(nums, i, 0);
+ minHeapfixdown(nums, 0, i);
+ }
}
```
+
### 性能
根据已有数组构建堆需要O(n)的时间复杂度,每次删除堆顶元素需要O(logn)的时间复杂度,所以总的时间开销为,O(n+nlogn),平均时间复杂度为O(nlogn)
>注意根据已有元素建堆是很快的,如果希望找到数组中第k大的元素,可以用O(n+klogn)的时间,如果k很小,时间开销接近O(n)
+
+### 常见面试问题
+
+TopK问题,如从十亿个数中选出其中最大的10000个。
+
+
## 1.多路归并
多路归并是**外部排序最常用**的算法:**将原文件分解成多个能够一次性装入内存的部分,分别把每一部分调入内存完成排序。然后,对已经排序的子文件进行归并排序**

+
### k的选择
-假设总共m个子文件,每次归并k个子文件,那么一共需要归并  次(扫描磁盘),在k个元素中找出最小值(或最大值)需要比较k-1次。如果总记录数为N,所以时间复杂度就是 Nlog_km=\frac{(k-1)}{logk}Nlogm$), 由于 }{logk}$) 随k的增大而增大,所以比较次数的增加会逐步抵消“低扫描次数”带来的性能增益,所以对于k值的选择,主要涉及两个问题:
+假设总共m个子文件,每次归并k个子文件,那么一共需要归并  次(扫描磁盘),在k个元素中找出最小值(或最大值)需要比较k-1次。如果总记录数为N,所以时间复杂度就是 Nlog_km=\frac{(k-1)}{logk}Nlogm$), $(k-1)Nlog_km=\frac{(k-1)}{logk}Nlogm$ 由于 }{logk}$) 随k的增大而增大,所以比较次数的增加会逐步抵消“低扫描次数”带来的性能增益,所以对于k值的选择,主要涉及两个问题:
1. **每一轮归并会将结果写回到磁盘,那么k越小,磁盘与内存之间数据的传输就会越多,增大k可以较少扫描次数**
2. **k个元素中选取最小的元素需要比较k-1次,如果k越大,比较的次数就会越大**
+
### 优化
可以利用下列方法**减少比较次数**:
diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/pic/5-1.png" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/pic/5-1.png"
index 4a9fea5..0b50a1c 100644
Binary files "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/pic/5-1.png" and "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/pic/5-1.png" differ
diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/pic/5-4.png" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/pic/5-4.png"
index 7a6d4eb..ef49cab 100644
Binary files "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/pic/5-4.png" and "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/pic/5-4.png" differ
diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/pic/5-6.png" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/pic/5-6.png"
index 48452b0..f211142 100644
Binary files "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/pic/5-6.png" and "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/pic/5-6.png" differ
diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/pic/5-7.png" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/pic/5-7.png"
index 596b96a..ddcca3e 100644
Binary files "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/pic/5-7.png" and "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/pic/5-7.png" differ
diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/pic/8-1.png" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/pic/8-1.png"
index 6b49b4c..e8f71d9 100644
Binary files "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/pic/8-1.png" and "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/pic/8-1.png" differ
diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md"
index 1612821..3647399 100644
--- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md"
+++ "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md"
@@ -577,7 +577,7 @@ TCP RFC并没有规定失序到达的分组应该如何处理,而是交给程
如果应用程序读取数据相当慢,而发送方发送数据太多、太快,会很容易使接收方的接收缓存溢出,流量控制就是用来进行发送速度和接收速度的匹配。发送方维护一个“接收窗口”变量,这个变量表示接收方当前可用的缓存空间
* LastByteRead:接收方应用程序从接收缓存中读取的最后一个字节
-* LastByteRcvd:接收方接收到的最后一个字节
+* LastByteRecv:接收方接收到的最后一个字节
要防止缓存溢出,则应该满足如下条件: