├── .gitignore ├── .nojekyll ├── C++面经汇总 ├── README.md ├── basic_cpp.md └── 微软 - C++ 工程师面经汇总.md ├── EffectiveCPP ├── C++ explicit关键字详解.md ├── using的用法.md ├── 基本用法.md ├── 深入理解CC++中的指针.md └── 详解C++11中的智能指针.md ├── Git └── README.md ├── LICENSE ├── Linux命令及系统编程 ├── Linux.md └── 环境变量.md ├── README.md ├── STL ├── README.md └── STL.md ├── Shell脚本 └── shell编程入门.md ├── _coverpage.md ├── _navbar.md ├── _sidebar.md ├── index.html ├── src ├── header.svg ├── i_magic_box.png ├── imgs │ ├── C++开发工程师-Momenta.jpg │ ├── C++资深软件工程师-Momenta.jpg │ ├── Microsoft_Top100.png │ ├── Top100.png │ ├── 嵌入式开发工程师-Momenta.jpg │ ├── 嵌入式软件工程师-百度.jpg │ ├── 嵌入式软件开发工程师-蔚来.jpg │ ├── 智能驾驶软件开发工程师-蔚来.jpg │ ├── 解题模板.png │ ├── 高精度定位融合-腾讯.jpg │ └── 高级嵌入式开发工程师-小马智行.jpg ├── maiwei-planet.jpg └── one-logo.jpg ├── 操作系统 ├── Linux.md ├── README.md ├── 内存管理问题合集.md ├── 死锁问题合集.md ├── 进程与线程问题合集.md └── 链接问题合集.md ├── 数据库 ├── MySQL问题合集.md ├── README.md ├── Redis问题合集.md ├── SQL问题合集.md └── 数据库系统原理.md ├── 算法库 ├── Algorithm │ ├── BSTSearch.h │ ├── BinarySearch.h │ ├── BubbleSort.h │ ├── BucketSort.cpp │ ├── CountSort.cpp │ ├── FibonacciSearch.cpp │ ├── HeapSort.cpp │ ├── InsertSort.h │ ├── InsertionSearch.h │ ├── MergeSort.h │ ├── QuickSort.h │ ├── README.md │ ├── RadixSort.h │ ├── SelectionSort.h │ ├── SequentialSearch.h │ └── ShellSort.h ├── DataStructure │ ├── BinaryTree.cpp │ ├── HashTable.cpp │ ├── LinkList.cpp │ ├── LinkList_with_head.cpp │ ├── README.md │ ├── RedBlackTree.cpp │ ├── SqList.cpp │ └── SqStack.cpp ├── DesignPattern │ ├── AbstractFactoryPattern │ │ ├── Factory.cpp │ │ ├── Factory.h │ │ ├── FactoryMain.cpp │ │ ├── FactoryMain.h │ │ ├── concrete_factory.h │ │ ├── concrete_product.h │ │ └── product.h │ ├── AdapterPattern │ │ ├── AdapterMain.h │ │ ├── adaptee.h │ │ ├── adapter.h │ │ └── target.h │ ├── BridgePattern │ │ ├── BridgeMain.cpp │ │ ├── BridgeMain.h │ │ ├── abstraction.h │ │ ├── concrete_implementor.h │ │ ├── implementor.h │ │ └── refined_abstraction.h │ ├── CMakeLists.txt │ ├── ObserverPattern │ │ ├── ObserverMain.cpp │ │ ├── ObserverMain.h │ │ ├── concrete_observer.h │ │ ├── concrete_subject.h │ │ ├── observer.h │ │ └── subject.h │ ├── README.md │ ├── SingletonPattern │ │ ├── README.md │ │ ├── Singleton.cpp │ │ ├── Singleton.h │ │ └── SingletonMain.h │ └── main.cpp ├── LeetCode │ └── README.md ├── Problems │ ├── ChessboardCoverageProblem │ │ ├── ChessboardCoverage.cpp │ │ ├── ChessboardCoverage.exe │ │ └── README.md │ ├── KnapsackProblem │ │ ├── README.md │ │ ├── pack.cpp │ │ └── pack.exe │ ├── NeumannNeighborProblem │ │ ├── Formula │ │ │ ├── Neumann2_3_12.cpp │ │ │ ├── Neumann2_3_12.exe │ │ │ └── README.md │ │ ├── README.md │ │ └── Recursive │ │ │ ├── Neumann2_4_12.cpp │ │ │ ├── Neumann2_4_12.exe │ │ │ └── README.md │ ├── RoundRobinProblem │ │ ├── MatchTable.cpp │ │ ├── MatchTable.exe │ │ └── README.md │ └── TubingProblem │ │ ├── README.md │ │ ├── Tubing.cpp │ │ └── Tubing.exe └── README.md ├── 编译链接 ├── README.md └── 采用dlopen、dlsym、dlclose加载动态链接库.md └── 计算机网络 ├── C++并发编程.md ├── HTTP问题合集.md ├── README.md ├── Socket问题合集.md └── 网络基础问题合集.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /C++面经汇总/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## [:link: C++必会八股文](basic_cpp.md) 4 | 5 |
6 | 7 | ## 📝 面试题目经验 8 | 9 | * [牛客网 . 2020秋招面经大汇总!(岗位划分)](https://www.nowcoder.com/discuss/205497) 10 | * [牛客网 . 【备战秋招】2020届秋招备战攻略](https://www.nowcoder.com/discuss/197116) 11 | * [牛客网 . 2019校招面经大汇总!【每日更新中】](https://www.nowcoder.com/discuss/90907) 12 | * [牛客网 . 2019校招技术类岗位面经汇总【技术类】](https://www.nowcoder.com/discuss/146655) 13 | * [牛客网 . 2018校招笔试真题汇总](https://www.nowcoder.com/discuss/68802) 14 | * [牛客网 . 2017秋季校园招聘笔经面经专题汇总](https://www.nowcoder.com/discuss/12805) 15 | * [牛客网 . 史上最全2017春招面经大合集!!](https://www.nowcoder.com/discuss/25268) 16 | * [牛客网 . 面试题干货在此](https://www.nowcoder.com/discuss/57978) 17 | * [知乎 . 互联网求职路上,你见过哪些写得很好、很用心的面经?最好能分享自己的面经、心路历程。](https://www.zhihu.com/question/29693016) 18 | * [知乎 . 互联网公司最常见的面试算法题有哪些?](https://www.zhihu.com/question/24964987) 19 | * [CSDN . 全面整理的C++面试题](http://blog.csdn.net/ljzcome/article/details/574158) 20 | * [CSDN . 百度研发类面试题(C++方向)](http://blog.csdn.net/Xiongchao99/article/details/74524807?locationNum=6&fps=1) 21 | * [CSDN . c++常见面试题30道](http://blog.csdn.net/fakine/article/details/51321544) 22 | * [CSDN . 腾讯2016实习生面试经验(已经拿到offer)](http://blog.csdn.net/onever_say_love/article/details/51223886) 23 | * [cnblogs . C++面试集锦( 面试被问到的问题 )](https://www.cnblogs.com/Y1Focus/p/6707121.html) 24 | * [cnblogs . C/C++ 笔试、面试题目大汇总](https://www.cnblogs.com/fangyukuan/archive/2010/09/18/1829871.html) 25 | * [cnblogs . 常见C++面试题及基本知识点总结(一)](https://www.cnblogs.com/LUO77/p/5771237.html) 26 | * [segmentfault . C++常见面试问题总结](https://segmentfault.com/a/1190000003745529) 27 | 28 | 29 | ## :bookmark: 我的专栏 30 | 31 | - [大厂后端/算法面经分类整理](https://blog.csdn.net/charmve/category_9622929.html) 32 | 33 |
34 | 35 | 打赏, 36 | 37 | 38 | Buy Me A Coffee 39 | -------------------------------------------------------------------------------- /C++面经汇总/微软 - C++ 工程师面经汇总.md: -------------------------------------------------------------------------------- 1 | # 微软 - C++ 工程师面经汇总 2 | 3 | :date: 2020-10-20 4 | 5 | ## 语言 6 | - 虚函数的机制 7 | - buffer 拷贝的过程 8 | - 一般程序中栈大小多少 9 | - 栈和堆的区别,如何分配内存 10 | - extern 关键字的底层机制是怎么实现的 11 | - 静态绑定和动态绑定是怎么实现的 12 | - 虚函数表跟对象还是跟类绑定 13 | - 返回函数中的静态变量的地址会发生什么 14 | - 全局 static 变量和非 static 的有什么区别 15 | - 全局变量定义在头文件中有什么问题 16 | - memcpy 函数常用吗?怎么实现?怎么提高效率 17 | - copy-on-write 是什么?你用过吗 18 | - C++ 并发编程了解吗?一般怎么写 19 | - 讲 C++ 对象模型,多态怎么实现,虚函数对象存储结构 20 | - delete 怎么知道要删除多长的 21 | - unordered_map 怎么实现的?画一下底层的数据结构 22 | - sorted_set 怎么实现 23 | - volatile 关键字 24 | - .h 里面定义函数,会在什么阶段错误?为什么 .h 里面一般只放函数声明?为什么这么设计 25 | - C++ 模板做什么的? 26 | - 静态链接、动态链接 27 | - C++ 和 Java/Python 区别 28 | 29 | 30 | ## 计算机网络 31 | - TCP 三次握手 32 | - TCP 握手为什么需要三次 33 | - TCP 四次挥手最后 client 端的状态是什么 34 | - TIMEWAIT 是什么作用?为什么要等这个时间?等多长时间? MSL 是多长时间 35 | - http 和 https 有什么区别 36 | - TCP 和 UDP 区别 37 | - 抓取数据包得过程 38 | - NAT 转换怎么实现的 39 | 40 | 41 | ## 数据库 42 | - 各种 join 的区别 43 | - 数据库引擎熟悉吗?原理 44 | - 用过什么数据库 45 | - 数据库的索引能讲下吗?怎么设计的 46 | - ACID 解释下 47 | - 数据库范式都有哪些 48 | 49 | 50 | ## 操作系统 51 | - 进程和线程的区别 52 | - 线程有哪几种状态,相互关系得转化图 53 | - 多线程的好处 54 | - 假如我有一个 exe,这个 exe 是根据一个 cpp文件得到的,cpp 文件里面有一个 main 函数,main 里面有一句 print 函数,main 函数之前会有一些变量,说一下从我的鼠标点击运行开始到这个 main 的 return 之后的全部过程 55 | - 什么是静态链接库和动态链接库 56 | - 什么是内核 57 | - 死锁发生的条件 58 | - 生产者消费者 PV 操作伪代码 59 | - Linux 启动的过程 60 | - BootLoader 做了什么 61 | - Linux 进程默认占用多少内存 62 | - 怎么用 C++ 读取进程的内存占用情况 63 | - Linux 进程通信都有哪些方式 64 | - IO 多路复用,select,poll,epoll 的区别 65 | - 多线程编程常用吗?一个经典的生产者消费者模型怎么实现?怎么降低锁的竞争? 66 | - 虚拟内存映射怎么做的 67 | - 内存碎片怎么整理?用什么数据结构? 68 | - 为什么要有虚拟内存?4gb数据在2gb内存排序?有虚存能直接排吗 69 | - 中断机制 70 | 71 | 72 | ## 算法 73 | - 打乱数组 74 | - 链表深拷贝,可能有环,可能无环 75 | - 缺失数字 76 | - 把数字翻译成字符串 77 | - 验证IP地址 78 | - 最小k个数 79 | - 反转链表 80 | - 数据流中的中位数 81 | - LRU缓存 82 | - 合并二叉树 83 | - 二分查找 84 | - 快速排序得实现原理,非递归方式怎么实现 85 | - 合并k个升序链表 86 | - 二叉树的最大路径和 87 | - 括号生成 88 | - 删除二叉搜索树中的节点 89 | - 数组中重复的数 90 | - 大数加法 91 | - 删除链表倒数第k个节点 92 | - 旋转数组 93 | - 数组中的逆序对 94 | - 最大子序和 95 | - 按Z字型顺序打印二叉树 96 | - 格雷编码 97 | - 重建二叉树 98 | - 最长上升序列 99 | - 二叉树的最大路径和 100 | 101 | ## 其他 102 | - 英文自我介绍 103 | - 假设我现在要设计一个扫雷程序,你觉得你要怎么设计?就是假如你要设计类要设计几个,分别有什么功能 104 | - 你比较看重公司的哪些性质 105 | - 你的强项和弱项 106 | - 你希望在公司1-3年有什么收获 107 | - 随便说一分钟英语 108 | - 介绍一下你曾经遇到的难题和解决过程 109 | 110 | ## 总结 111 | - 外企看重算法,也看重综合素质和潜力。 112 | - 很少直接考 CS 基础,但细节暴露一切。 113 | 114 | -------------------------------------------------------------------------------- /EffectiveCPP/C++ explicit关键字详解.md: -------------------------------------------------------------------------------- 1 | # C++ explicit关键字详解 2 | 3 | 首先, C++中的explicit关键字只能用于修饰只有一个参数的类构造函数, 它的作用是表明该构造函数是显示的, 而非隐式的, 跟它相对应的另一个关键字是implicit, 意思是隐藏的, 类构造函数默认情况下即声明为implicit(隐式). 4 | 5 | 那么显示声明的构造函数和隐式声明的有什么区别呢? 我们来看下面的例子: 6 | 7 | ```cpp 8 | class CxString // 没有使用explicit关键字的类声明, 即默认为隐式声明 9 | { 10 | public: 11 | char *_pstr; 12 | int _size; 13 | CxString(int size) 14 | { 15 | _size = size; // string的预设大小 16 | _pstr = (char*) malloc(size + 1); // 分配string的内存 17 | memset(_pstr, 0, size + 1); 18 | } 19 | CxString(const char *p) 20 | { 21 | int size = strlen(p); 22 | _pstr = malloc(size + 1); // 分配string的内存 23 | strcpy(_pstr, p); // 复制字符串 24 | _size = strlen(_pstr); 25 | } 26 | // 析构函数这里不讨论, 省略... 27 | }; 28 | 29 | // 下面是调用: 30 | 31 | CxString string1(24); // 这样是OK的, 为CxString预分配24字节的大小的内存 32 | CxString string2 = 10; // 这样是OK的, 为CxString预分配10字节的大小的内存 33 | CxString string3; // 这样是不行的, 因为没有默认构造函数, 错误为: “CxString”: 没有合适的默认构造函数可用 34 | CxString string4("aaaa"); // 这样是OK的 35 | CxString string5 = "bbb"; // 这样也是OK的, 调用的是CxString(const char *p) 36 | CxString string6 = 'c'; // 这样也是OK的, 其实调用的是CxString(int size), 且size等于'c'的ascii码 37 | string1 = 2; // 这样也是OK的, 为CxString预分配2字节的大小的内存 38 | string2 = 3; // 这样也是OK的, 为CxString预分配3字节的大小的内存 39 | string3 = string1; // 这样也是OK的, 至少编译是没问题的, 但是如果析构函数里用free释放_pstr内存指针的时候可能会报错, 完整的代码必须重载运算符"=", 并在其中处理内存释放 40 | 41 | ``` 42 | 43 | 上面的代码中, ``"CxString string2 = 10;"`` 这句为什么是可以的呢? 在C++中, 如果的构造函数只有一个参数时, 那么在编译的时候就会有一个缺省的转换操作:将该构造函数对应数据类型的数据转换为该类对象. 也就是说 ``"CxString string2 = 10;"`` 这段代码, 编译器自动将整型转换为CxString类对象, 实际上等同于下面的操作: 44 | 45 | ```cpp 46 | CxString string2(10); 47 | 或 48 | CxString temp(10); 49 | CxString string2 = temp; 50 | ``` 51 | 52 | 但是, 上面的代码中的_size代表的是字符串内存分配的大小, 那么调用的第二句 ``"CxString string2 = 10;"`` 和第六句 ``"CxString string6 = 'c';"`` 就显得不伦不类, 而且容易让人疑惑. 有什么办法阻止这种用法呢? 答案就是使用explicit关键字. 我们把上面的代码修改一下, 如下: 53 | 54 | ``` 55 | class CxString // 使用关键字explicit的类声明, 显示转换 56 | { 57 | public: 58 | char *_pstr; 59 | int _size; 60 | explicit CxString(int size) 61 | { 62 | _size = size; 63 | // 代码同上, 省略... 64 | } 65 | CxString(const char *p) 66 | { 67 | // 代码同上, 省略... 68 | } 69 | }; 70 | 71 | // 下面是调用: 72 | 73 | CxString string1(24); // 这样是OK的 74 | CxString string2 = 10; // 这样是不行的, 因为explicit关键字取消了隐式转换 75 | CxString string3; // 这样是不行的, 因为没有默认构造函数 76 | CxString string4("aaaa"); // 这样是OK的 77 | CxString string5 = "bbb"; // 这样也是OK的, 调用的是CxString(const char *p) 78 | CxString string6 = 'c'; // 这样是不行的, 其实调用的是CxString(int size), 且size等于'c'的ascii码, 但explicit关键字取消了隐式转换 79 | string1 = 2; // 这样也是不行的, 因为取消了隐式转换 80 | string2 = 3; // 这样也是不行的, 因为取消了隐式转换 81 | string3 = string1; // 这样也是不行的, 因为取消了隐式转换, 除非类实现操作符"="的重载 82 | ``` 83 | 84 | **explicit关键字的作用就是防止类构造函数的隐式自动转换.** 85 | 86 | 上面也已经说过了, **explicit关键字只对有一个参数的类构造函数有效**, 如果类构造函数参数大于或等于两个时, 是不会产生隐式转换的, 所以explicit关键字也就无效了. 例如: 87 | 88 | ```cpp 89 | class CxString // explicit关键字在类构造函数参数大于或等于两个时无效 90 | { 91 | public: 92 | char *_pstr; 93 | int _age; 94 | int _size; 95 | explicit CxString(int age, int size) 96 | { 97 | _age = age; 98 | _size = size; 99 | // 代码同上, 省略... 100 | } 101 | CxString(const char *p) 102 | { 103 | // 代码同上, 省略... 104 | } 105 | }; 106 | 107 | // 这个时候有没有explicit关键字都是一样的 108 | ``` 109 | 110 | 111 | 但是, 也有一个例外, 就是**当除了第一个参数以外的其他参数都有默认值的时候, explicit关键字依然有效**, 此时, 当调用构造函数时只传入一个参数, 等效于只有一个参数的类构造函数, 例子如下: 112 | 113 | ```cpp 114 | class CxString // 使用关键字explicit声明 115 | { 116 | public: 117 | int _age; 118 | int _size; 119 | explicit CxString(int age, int size = 0) 120 | { 121 | _age = age; 122 | _size = size; 123 | // 代码同上, 省略... 124 | } 125 | CxString(const char *p) 126 | { 127 | // 代码同上, 省略... 128 | } 129 | }; 130 | 131 | // 下面是调用: 132 | 133 | CxString string1(24); // 这样是OK的 134 | CxString string2 = 10; // 这样是不行的, 因为explicit关键字取消了隐式转换 135 | CxString string3; // 这样是不行的, 因为没有默认构造函数 136 | string1 = 2; // 这样也是不行的, 因为取消了隐式转换 137 | string2 = 3; // 这样也是不行的, 因为取消了隐式转换 138 | string3 = string1; // 这样也是不行的, 因为取消了隐式转换, 除非类实现操作符"="的重载 139 | ``` 140 | 141 | ## 小结 142 | 143 | - explicit关键字的作用就是防止类构造函数的隐式自动转换 144 | - explicit关键字只对有一个参数的类构造函数有效 145 | - 当除了第一个参数以外的其他参数都有默认值的时候, explicit关键字依然有效 146 | -------------------------------------------------------------------------------- /EffectiveCPP/using的用法.md: -------------------------------------------------------------------------------- 1 | # using的用法 2 | 3 | ## 1、概述 4 | 我们用到的库函数基本上都属于命名空间std的,在程序使用的过程中要显示的将这一点标示出来,如std::cout。这个方法比较烦琐,而我们都知道使用using声明则更方便更安全。 5 | 6 | 这个我们程序员肯定都知道了,今天突发奇想就想对using整理一下。 7 | 8 | ## 2、命令空间的using声明 9 | 我们在书写模块功能时,为了防止命名冲突会对模块取命名空间,这样子在使用时就需要指定是哪个命名空间,使用using声明,则后面使用就无须前缀了。例如: 10 | 11 | ``` 12 | using std::cin; //using声明,当我们使用cin时,从命名空间std中获取它 13 | int main() 14 | { 15 | int i; 16 | cin >> i; //正确:cin和std::cin含义相同 17 | cout << i; //错误:没有对应的using声明,必须使用完整的名字 18 | return 0; 19 | } 20 | ``` 21 | 22 | ```cpp 23 | //main.cpp 24 | 25 | #include 26 | using namespace std; 27 | #define DString std::string //! 不建议使用! 28 | typedef std::string TString; //! 使用typedef的方式 29 | using Ustring = std::string; //!使用 using typeName_self = stdtypename; 30 | typedef void (tFunc*)(void);using uFunc = void(*)(void); //更直观 31 | int main(int argc, char *argv[]) 32 | { 33 | 34 | TString ts("String!"); 35 | Ustring us("Ustring!"); 36 | string s("sdfdfsd");   cout< intvec; 89 | using intvec = std::vector; //这两个写法是等价的 90 | ``` 91 | 92 | 这个还不是很明显的优势,在来看一个列子: 93 | 94 | ``` 95 | typedef void (*FP) (int, const std::string&); 96 | ``` 97 | 98 | 若不是特别熟悉函数指针与typedef,第一眼还是很难指出FP其实是一个别名,代表着的是一个函数指针,而指向的这个函数返回类型是void,接受参数是int, const std::string&。 99 | ``` 100 | using FP = void (*) (int, const std::string&); 101 | ``` 102 | 103 | 这样就很明显了,一看FP就是一个别名。using的写法把别名的名字强制分离到了左边,而把别名指向的放在了右边,比较清晰,可读性比较好。比如: 104 | 105 | ``` 106 | typedef std::string (* fooMemFnPtr) (const std::string&); 107 | 108 | using fooMemFnPtr = std::string (*) (const std::string&); 109 | ``` 110 | 111 | 来看一下模板别名。 112 | 113 | ``` 114 | template 115 | using Vec = MyVector>; 116 | 117 | // usage 118 | Vec vec; 119 | ``` 120 | 121 | 若使用typedef 122 | 123 | ``` 124 | template 125 | typedef MyVector> Vec; 126 | 127 | // usage 128 | Vec vec; 129 | ``` 130 | 131 | 当进行编译的时候,编译器会给出error: a typedef cannot be a template的错误信息。 132 | 133 | 那么,如果我们想要用typedef做到这一点,需要进行包装一层,如: 134 | 135 | ``` 136 | template 137 | struct Vec 138 | { 139 | typedef MyVector> type; 140 | }; 141 | 142 | // usage 143 | Vec::type vec; 144 | ``` 145 | 146 | 正如你所看到的,这样是非常不漂亮的。而更糟糕的是,如果你想要把这样的类型用在模板类或者进行参数传递的时候,你需要使用typename强制指定这样的成员为类型,而不是说这样的::type是一个静态成员亦或者其它情况可以满足这样的语法,如: 147 | ``` 148 | template 149 | class Widget 150 | { 151 | typename Vec::type vec; 152 | }; 153 | ``` 154 | 155 | 然而,如果是使用using语法的模板别名,你则完全避免了因为::type引起的问题,也就完全不需要typename来指定了。 156 | 157 | ``` 158 | template 159 | class Widget 160 | { 161 | Vec vec; 162 | }; 163 | ``` 164 | 165 | 一切都会非常的自然,所以于此,模板起别名时推荐using,而非typedef。 166 | 167 | 感谢大家,我是假装很努力的 [@Charmve(益达)](https://github.com/Charmve)。 168 | 169 | ---- 170 | 171 | 参考资料: 172 | https://zhuanlan.zhihu.com/p/21264013 173 | https://blog.csdn.net/shift_wwx/article/details/78742459 174 | -------------------------------------------------------------------------------- /EffectiveCPP/基本用法.md: -------------------------------------------------------------------------------- 1 | ## 容易混淆的用法 2 | 3 | - [for循环及break和continue、return的区别](https://www.cnblogs.com/sghy/p/7827255.html) 4 | 5 | 6 | - [Linux命令 - cksum、md5sum、sha1sum命令](https://www.sohu.com/a/438470706_261288) 7 | -------------------------------------------------------------------------------- /EffectiveCPP/深入理解CC++中的指针.md: -------------------------------------------------------------------------------- 1 | ## 深入理解C/C++中的指针 2 | 3 | C和C++中最强大的功能莫过于指针了(pointer),但是对于大多数人尤其是新手来说,指针是一个最容易出错、也最难掌握的概念了。本文将从指针的方方面面来讲述指针的概念和用法,希望对大家有所帮助。 4 | 5 | 6 | 7 | ### 内存模型 8 | 9 | 为了更好地理解指针,让我们来看一下计算机的内存模型。 10 | 11 | 内存分为**物理内存**和**虚拟内存**,物理内存对应计算机中的内存条,虚拟内存是操作系统内存管理系统假象出来的。由于这些不是我们本文的重点,下面不做区分。有不清楚这些概念的同学,可以给我留言或者在线询问。 12 | 13 | 在不考虑cpu缓存的情况下,计算机运行程序本质上就是对内存中的**数据的操作**,通俗地来说,就是将内存条某些部分的数据搬进搬出或者搬来搬去,其中“搬进搬出”是指将内存中的二进制数据搬入cpu寄存器及运算器中进行相应的加减运算或者将寄存器中的数据搬回内存单元中,而“搬来搬去”是指将内存中的数据由这个位置搬到另外一个位置(当然,一般不是直接搬,而是借助寄存器作为中间存储区)。如下图所示: 14 | 15 | ![](../imgs/pointer1.webp) 16 | 17 | 计算机为了方便管理内存,将内存的每个单元用一个数字编号,如下图所以: 18 | 19 | ![](../imgs/pointer2.webp) 20 | 21 | 图中所示,是一个大小为128个字节的内存空间,其中每一个空格代表一个字节,所以内存编号是0~127。 22 | 23 | 对于一个32位的操作系统来说,内存空间中每一个字节的编号是一个32位二进制数,所以内存编号从0000 0000 0000 0000 0000 0000 0000 0000至1111 1111 1111 1111 1111 1111 1111 1111,转换成16进制也就是0x00000000至0xFFFFFFFF,由于是从0开始的,所以化成10机制就是从0至2的32次方减1;对于64位操作系统,内存编号也就是从64个0至64个1。 24 | 25 | 大家需要注意的是,从上面两个图我们可以发现,我们一般将编号小的内存单元画在上面,编号大的画在下面,也就是说从上至下,内存编号越来越大。 26 | 27 | 28 | 29 | ### 指针与指针变量 30 | 31 | 指针的本意就是**内存地址**,我们可以通俗地理解成内存编号,既然计算机通过编号来操作内存单元,这就造就了指针的**高效率**。 32 | 33 | 那么什么是指针变量呢?指针变量可通俗地理解成存储指针的变量,也就是**存储内存地址(内存编号)的变量**。首先指针变量和整型变量、字符型变量以及其他数据类型的变量一样都是变量类型;但是,反过来,我们不应该按这样的方式来分类,即:整型指针变量、字符型指针变量、浮点型指针变量等等。为什么不推荐这样的分类方法呢?首先,指针变量就是一个数据类型,指针数据类型,这种数据类型首先是一个变量数据类型,那么它的大小是多少呢?很多同学理所当然地认为整型指针变量和一个字符指针变量的大小是不一样的,这种认识是错的。指针变量也是一个变量,它是一个用来存储其他变量的内存地址的,更准确地说,指针变量时用来存储其他变量的**内存首地址**的,因为不同的数据类型所占的内存大小不一样。举个例子,在32位机器上,假如a是int型变量,pa是指向a的指针变量,b是一个double型变量,pb是指向b的指针变量,那么a在内存中占四个字节,b在内存中占8个字节,假如a在内存中分布是从0x11111110~0x11111113,而b在内存中分布是0x11112221至0x11112228,那么指针变量pa中存储的内容是0x11111110,而pb中存储就是0x11112221,看到了吧,也就是说,pa和pb中存储的都是地址,而且都是32位的二进制地址;再者,因为存储这样的地址需要4个字节,所以无论是int型指针变量pa或者是double型指针变量pb,它们所占的内存大小都是四个字节,从这点来说,不管什么类型的指针都是一样的,所以不论按整型指针变量、字符型指针变量、浮点型指针变量等等来区分指针变量。总结起来,指针变量和int、float、char等类型一样同属变量类型,指针变量类型占四个字节(32位机器下),存储的是32位的内存地址。下面的代码证明这一点: 34 | 35 | ![](../imgs/pointer3.webp) 36 | 37 | ![](../imgs/pointer4.webp) 38 | 39 | 上面介绍的是指针变量的一个方面,指针变量还有另外一层含义:在C/C++中星号(\*)被定义成**取内容符号**,虽然所有指针变量占的内存大小和存储的内存地址大小都是一样的,但是由于存储的只是数据的内存首地址,所以指针变量存储的内存地址所指向的数据类型决定着如何解析这个首地址,也就是说对于int型指针变量,我们需要从该指针变量存储的(首)地址开始向后一直搜索4个字节的内存空间,以图中的变量a为例就是从0x12ff60~0x12ff63,对于变量b就是0x12ff44~0x12ff4b。所以从这个意义来上讲,当我们使用\*pa,必须先知道pa是一个整型的指针,这里强调“整型”,而a的值1也就存储在从0x12ff60~0x12ff63这四个字节里面,当我们使用\*pb,必须先知道pb是一个double型指针,这里强调"double",也就是说值2.0000存储在0x12ff44~0x12ff4b这八个字节里面。因此,我们对指针变量进行算术运算,比如pa + 2,pb + +之类的操作,是以数据类型大小为单位的,也就是说pa + 2,相当于0x12ff60 + sizeof(int) \* 2 = 0x12ff60 + 4 \* 2 = 0x12ff68,不是0x12ff60 + 2哦;而pb - -相当于0x12ff44 + sizeof(double) \* 1 = 0x12ff44 + 8 \* 1 = 0x12ff4c。理解这一点很重要。 同理&a + 2和&b - 1也是一样(注意由于&b是一个指针常量,所以写成&b - -是错误的)。 40 | 41 | 42 | 43 | ### 指针变量和指针常量 44 | 45 | 指针变量首先是一个变量,由于指针变量存储了某个变量的内存首地址,我们通常认为**”指针变量指向了该变量“**,但是在这个时刻指针变量pa指向变量a,下个时候可能不存储变量a的首地址,而是存储变量c的首地址,那么我们可以认为这个时候,pa不再指向a,而是指向c。请别嫌我啰嗦,为了帮助你理解,我是故意说得这么细的,后面我们讨论高级主题的时候,当你觉得迷糊,请回来反复咀嚼一下这段话。也就是说指针变量是一个变量,它的值可以**变动**的。 46 | 47 | 相反,指针常量可通俗地理解为存储固定的内存单元地址编号的”量“,它一旦存储了某个内存地址以后,不可再改存储其他的内存地址了。所以指针常量是坚韧,因为它”咬定青山不放松“;说是”痴情“,因为它”曾经沧海难为水“。我这里讲的指针常量对应的是const关键字定义的量,而不是指针字面量。像&a, &b, &a + 2等是指针字面量,而const int \*p = &a;中的p才算是真正的指针常量,指针常量一般用在**函数的参数**中,表示该函数不可改变实参的内容。来看一个例子吧: 48 | 49 | ![](../imgs/pointer5.webp) 50 | 51 | 上面的函数由于修改了一个常指针(多数情况下等同指针常量),因而会编译出错:error C3892: “x”: 不能给常量赋值。 52 | 53 | 54 | 55 | ### 指针变量与数组 56 | 57 | 记得多年以前,我在学生会给电子技术部和地理信息系统专业的同学进行C语言培训时,这是一个最让他们头疼和感到一头雾水的话题,尤其是指针变量与二维数组的结合,我永远忘不了胡永月那一脸迷惑与无助的表情。今天我这里给大家深入地分析一下。先看一个例子: 58 | 59 | ![](../imgs/pointer6.webp) 60 | 61 | 如果你能得出下面这样的结果,说明你已经基本上对数组与指针的概念理解清楚了: 62 | 63 | ![](../imgs/pointer7.webp) 64 | 65 | 通过上图,我们可以知道\*(a + 1) = 2, \*(ptr - 1) = 5。 66 | 67 | 且不说很多同学根本得不到这样的结果,他们看到int *ptr = (int*)(&a+1);这样的语句就已经懵了,首先,我们知道C语言中规定**数组名表示这个数组的首地址**,而这里竟然出现了&a这样的符号,本来a就是一个指针常量了,这里对&a再次取地址难道不是非法操作吗?哈哈,当你有这样的疑问的时候,说明你对二维数组相关知识理解不深入。我这里先给你补充下知识点吧: 68 | 69 | 看这样一个二维数组:int arr\[3][4],这个数组布局如下: 70 | 71 | ![](../imgs/pointer8.webp) 72 | 73 | 这是一个3行4列的数组,它在内存中的分布如下: 74 | 75 | ![](../imgs/pointer9.webp) 76 | 77 | 这里每一个数组元素占4字节空间,我们知道C语言规定,数组名arr是整个数组元素的首地址,比如是0x0012ff08,而像arr[0]、arr[1]、arr[2]分别是数组第一行、第二行、第三行的首地址,也就是0x0012ff08、0x0012ff18、0x0012ff28。 78 | 79 | 我们把arr、arr[0]和&arr\[0][0]单独拿出来分析,因为数组的首地址也是第一列的首地址,同时也是第一个元素的首地址,所以arr和arr[0]和&arr\[0][0]表示的都是同一个地址,但是这三个首地址在进行算术运算时是有区别的。如果&arr\[0][0] + 1,这里的相当于**跳一个元素的内存字节数**,也就是4个;但是arr[0] + 1,移动的内存字节数是**一列元素所占的字节数**,也就是4 * 4 = 16个;最后,也是最让人迷惑的的就是arr + 1,这个时候移动的内存数目是整个**数组占的内存字节数**,也就是48个字节数,所以a + 1所表示的内存地址已经不属于这个数组了,这个地址位于数组最后一个元素所占内存空间的**下一个字节空间**。 80 | 81 | 光有这些知识还是不能解决上面的问题,我们再补充一个知识点。 82 | 83 | C++是一种**强类型的语言**,其中有一种类型叫**void类型**,从本质上说void不是一种类型,因为变量都是”有类型“的,就好像人的性别,不是男人就是女人,不存在无性别的人,所以void更多是一种抽象。在程序中,void类型更多是用来**”修饰“**和**”限制“**一个函数的:例如一个函数如果不返回任何类型的值,可以用void作返回类型;如果一个函数无参数列表,可以用void作为参数列表。 84 | 85 | 跟void类型”修饰“作用不同,void型指针作为**指向抽象数据**的指针,它本质上表示一段**内存块**。如果两个指针类型不同,在进行指针类型赋值时必须进行**强制类型转换**,看下面的例子: 86 | 87 | ![](../imgs/pointer10.webp) 88 | 89 | 但是可以将任何指针类型赋值给void类型而无须进行强制类型转换: 90 | 91 | ![](../imgs/pointer11.webp) 92 | 93 | 当然,如果把void型指针转换成并不是它实际指向的数据类型,其结果是不可预测的。试想,如果把一个int型指针赋给void型,然后再把这个void型指针强制转换成double型指针,这样的结果是不可预测的。因为不同数据类型所占内存大小不一样,这样做可能或截断内存数据或者会增加一些未知的额外数据。所以,最好是将void类型指针转换成它实际数据类型指针。 94 | 95 | 有了上面的说明,你应该能看懂C函数库中下面这个函数的签名含义了吧? 96 | 97 | ``` 98 | void *memcpy(void *dest,const void *src,size_t len); 99 | ``` 100 | 101 | 在这里,任何数据类型的指针都可以传给这个函数,所以这个函数成为了一个通用的内存复制函数。 102 | 103 | 好了,说了这么多,回答最初的那个问题上: 104 | 105 | ![](../imgs/pointer12.webp) 106 | 107 | 我们来分析一下。首先,我们可以将这个数组看成是一个特殊的二维数组,也就是1行5列的二维数组,现在a表示的是第一个元素的首地址,那么a + 1指向的就是下一个元素的内存首地址,所以\*(a + 1) = 2;而&a则是表示整个数组的首地址,那么&a + 1移动的内存数目就是整个数组所占字节数,假如这里我们量化来说明,假如原先数组中第一个元素的首地址是1,那么&a + 1表示的就是21,而这个地址已经不属于数组了,接着通过(int\*)(&a + 1)将数组指针转换成整型指针,这样原先&a + 1表示的数据范围是21~40一下缩小到21~24,正好是一个int型的大小,所以ptr - 1的存储的地址就是17了,表示的数据内存范围是17~20,这样\*(ptr - 1)正好就是最后一个元素5了。 108 | 109 | 但是话说回来,首先这样的转换安全与否尚有争议,再次,这样的程序晦涩难懂,难于理解,所以建议不要写出这样的程序。 110 | 111 | 上面的例子,只是通过一些简单的数据类型来说明内存分布,但是实际对于一些复杂的数据类型,尤其是一些自定义的类或者结构体类型,内存分布必须还要充分考虑到**字节对齐**。比如下面的代码: 112 | 113 | ![](../imgs/pointer13.webp) 114 | 115 | 这是输出结果: 116 | 117 | ![](../imgs/pointer14.webp) 118 | 119 | 由于结构体s1中存在字节对齐现象(以sizeof(double) = 8个字节对齐),所以s1占据24字节内存,而s2只占16个字节。知道这点,我们平常在设计结构体字段的时候,就可以合理安排字段顺序来使用更少的内存空间了。 120 | 121 | 122 | 123 | ### 函数指针 124 | 125 | 函数指针是指向函数的**指针变量**。 因而“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。C/C++程序在编译时,每一个函数都有一个**入口地址**,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上是一致的。函数指针有两个用途:**调用函数**和**做函数的参数**。 126 | 127 | 我们先来先使用函数指针调用函数。如下图所示: 128 | 129 | ![](../imgs/pointer15.webp) 130 | 131 | 上面的代码首先是定义了一个函数f,然后是定义一个函数指针pf,接着在主函数里面将函数f的地址赋值给函数指针,这样pf就指向了函数f,这样使用*pf就可以直接调用函数了。但是上面的例子定义函数指针的方法在某些编译器中是无法通过的,最好通过**typedef关键字**定义函数指针,推荐的写法如下: 132 | 133 | ![](../imgs/pointer16.webp) 134 | 135 | 通过上面的例子,我们来总结下函数指针的定义和使用方法: 136 | 137 | 首先,通过**typedef关键字**定义一个**函数指针类型**,然后定义一个该函数**指针类型变量**,接着将函数的入口地址赋值给该函数指针类型变量,这样就可以通过这个函数指针变量调用函数了。 138 | 139 | 需要注意的是,定义函数指针类型时的**函数签名**(包括函数返回值和函数参数列表的类型、个数、顺序)要将赋值给该类型变量的函数签名保持一致,不然可能会发生很多无法预料的情况。还有一点,就是C/C++规定函数名就表示函数入口地址,所以,函数名赋值时函数名前面加不加取地址符&都一样,也就是说PF pf = f等价于PF pf = &f。这个**&是可以省略**的。但是这是单个函数的情况,在C++中取类的方法函数的地址时,这个&符号式不能省略的,见下面的例子: 140 | 141 | ![](../imgs/pointer17.webp) 142 | 143 | 函数指针的另外一个用处,而且是用的最多的,就是作为一个**函数的参数**。也就是说某个函数的某个参数类型是一个函数,这在windows编程中作为**回调函数**(callback)尤其常见。我们来看一个例子: 144 | 145 | ![](../imgs/pointer18.webp) 146 | 147 | 上图中,函数f2第一个参数类型是一个函数,我们传入函数f1作为参数。这种函数参数是函数类型的用法很重要,建议大家掌握。 148 | 149 | 150 | 151 | 152 | 153 | ### 指针变量的定义方法 154 | 155 | 先插播一段广告,说下main函数的返回值问题,如下图: 156 | 157 | ![](../imgs/pointer19.webp) 158 | 159 | 这种main函数无返回值的写法,在国内各大C/C++教材上屡见不鲜,这种写法是错误的! 160 | 161 | 有一点你必须明确:C/C++标准中从来没有定义过void main()这样的代码形式。C++之父Bjarne Stroustrup在他的主页FAQ中明确地写了这样一句话: 162 | 163 | > 在C++中绝对没有出现过void main(){ /* ... */ } 这样的函数定义,在C语言中也是。 164 | 165 | main函数的返回值应该**定义为int型**,在C/C++标准中都是这样规定的。在C99标准规定,只有以下两种定义方式是正确的的: 166 | 167 | ``` 168 | 1 int main(void); 169 | 2 int main(int argc,char *argv[]); 170 | ``` 171 | 172 | 虽然在C和C++标准中并不支持void main(),但是在部分编译器中void main()依旧是可以通过编译并执行的,比如微软的VC++。由于微软产品的市场占有率和影响力很大,因为在某种程度上加剧了这种不良习惯的蔓延。不过,并非所有犯人编译器都支持void main(),gcc就站在VC++的对立面,它是这一不良习气的坚定抵制者,它会在编译时明确地给出一个错误。 173 | 174 | 广告播完,我们回到正题上来。我们来看下如何定义一个指针,首先看一个例子: 175 | 176 | ![](../imgs/pointer20.webp) 177 | 178 | 我来替你回答吧,你肯定认为a是一个指针变量,b是一个整型变量,c和d都是一个指针变量。好吧,恭喜你,答错了! 179 | 180 | 其实定义指针变量的时候,星号(\*)无论是与数据类型结合还是与变量名结合在一起都是一样的!但是,为了便于理解,还是推荐大家写成第一种形式,第二种形式容易误导人,不是吗?而且第一种形式还有一个好处,我们可以这样看: 181 | 182 | ``` 183 | int *a; //将*a看成一个整体,它是一个int型数据,那么a自然就是指向*a的指针了。 184 | ``` 185 | 186 | 说完定义指针的方法,下面我们来看下如何初始化一个指针变量,看下面的代码: 187 | 188 | ![](../imgs/pointer21.webp) 189 | 190 | 上面的代码有错误吗? 191 | 192 | 错误在于我们不能这样写:int \*p = 1; 由于p是一个匿名指针,也就是说p没有正确的初始化,它可能指向一个不确定的内存地址,而这个内存地址可能是系统程序内存所在,我们将数值1装入那个不确定的内存单元中是很危险的,因为可能会破坏系统那个内存原来的数据,引发异常。换另一个方面来看,将整型数值1直接赋值给指针型变量p是非法的。 193 | 194 | 这样的指针我们称为**匿名指针**或者**野指针**。和其他变量类型一样,为了防止发生意料之外的错误,我们应该给新定义的指针变量一个初始值。但是有时候,我们又没有合适的初始值给这个指针,怎么办?我们可以使用**NULL关键字**或者C++中的**nullptr**。代码如下: 195 | 196 | ![](../imgs/pointer22.webp) 197 | 198 | 通过上面的写法就告诉编译器,这两个指针现在不会指向不确定的内存单元了,但是目前暂时不需要使用它们。  199 | 200 | 201 | 202 | ### C++中的引用 203 | 204 | C++中不仅有指针的概念,而且还存在一个**引用**的概念,看下面的代码: 205 | 206 | ![](../imgs/pointer23.webp) 207 | 208 | 我开始在接触这个概念的时候,老是弄错。当时这么想的,既然b是a的引用,那么&b应该等于a吧?也就是说,在需要使用变量a的时候,可以使用&b来代替。 209 | 210 | 上面的这种认识是错误的!所谓引用,使用另外一个变量名来代表某一块内存,也就是说a和b完全是一样,所以任何地方,可以使用a的,换成b也可以,而不是使用&b,这就相当于同一个人有不同的名字,但是不管哪个名字,指的都是同一个人。 211 | 212 | ![](../imgs/pointer24.webp) 213 | 214 | 新手在刚接触引用的使用,还有一个地方容易出错,就是忘记给引用**及时初始化**,注意这里的“及时”两个字,C++规定,定义一个引用时,必须马上初始化。看下面的代码: 215 | 216 | ![](../imgs/pointer25.webp) 217 | 218 | 219 | 220 | ### 传值还是传引用(by value or by reference) 221 | 222 | 看下面的伪代码: 223 | 224 | ![](../imgs/pointer26.webp) 225 | 226 | 在涉及到利用一个已有初值的变量给另外一个变量赋值时,必须考虑这样的情况。图中变量a已经有了初值,然后利用a来给b赋初值,那么最后改变b的值,a的值会不会受影响呢?这就取决于b到底是a的副本还是和a同时指向同一内存区域,这就是我们常说的赋值时是传值还是传引用。各大语言都是这样规定的,也就是说不局限于C/C++,同时Java、C#、php、javascript等都一样: 227 | 228 | > 如果变量类型是基元数据类型(基础数据类型),比如int、float、bool、char等小数据类型被称为基元数据类型(primitive data type),那么赋值时传的是值。也就是说,这个时候b的值是a的拷贝,那么更改b不会影响到a,同理更改a也不会影响到b。 229 | 230 | > 但是,如果变量类型是复杂数据类型(complex data type),不如数组、类对象,那么赋值时传的就是引用,这个时候,a和b指向的都是同一块内存区域,那么无论更改a或者b都会相互影响。 231 | 232 | 让我们来深入地分析下,为什么各大语言都采取这种机制。对于那些基元数据类型,由于数据本身占用的内存空间就小,这样复制起来不仅速度快,即使这样的变量数目很多,总共也不会占多大空间。但是对于复杂数据类型,比如一些类对象,它们包含的属性字段就很多,占用的空间就大,如果赋值时,也是复制数据,那么一个两个对象还好,一旦多一点比如10个、100个,会占很大的内存单元的,这就导致效率的下降。 233 | 234 | 最后,提醒一点,在利用C++中拷贝构造函数复制对象时需要注意,基元数据类型可以直接复制,但是对于引用类型数据,我们需要自己实现引用型数据的真正复制。 235 | 236 | 237 | 238 | ### C/C++中的new关键字与Java、C#中的关键字对比 239 | 240 | ![](../imgs/pointer27.webp) 241 | 242 | 我大学毕业的时候痴迷于于网页游戏开发,使用的语言是flash平台的actionscript 3.0(简称as3,唉,如今已经没落),我刚开始由as3转行至C/C++,对于C/C++中new出来的对象必须通过指针对象来引用它非常不习惯。上图中,Object是一个**类**(class),在Java或者C#等语言中,通过new关键字定义一个对象,直接得到Object的实例,也就是说后续引用这个对象,我们可以直接使用**obj.property**或者**obj.method()**等形式,但是在C++中不行,比如用一个指针去接受这个new出来的对象,我们引用这个对象必须使用**指针引用运算符->**,也就是我们需要这样写:pObj->property或pObject->method()。代码如下: 243 | 244 | ![](../imgs/pointer28.webp) 245 | 246 | 当然C++中还有一种不需要使用指针就可以实例化出来类对象的方法,从Java、C#等转向C++的程序员容易误解为未初始化对象变量的定义,看下列代码: 247 | 248 | ![](../imgs/pointer29.webp) 249 | 250 | 这是C++中利用Object类实例化两个对象obj1和obj2,obj2因为调用构造函数传了两个参数param1,param2还好理解一点,对于obj1很多Java或者C#的程序员开始很难接受这种写法,因为如果放在Java或者C#中,obj1根本就没有被实例化嘛,在他们看来,obj1只是一个简单的类型申明。希望Java、C#等程序员要转换过思维来看待C++中的这种写法。 251 | 252 | 还有一点也容易出错,在C++中,this关键字是一个指针,而不是像在Java、C#中是一个类实例。也就是说,在C++中*this才等价于Java、C#中的this。所以写法也就不一样了: 253 | 254 | ![](../imgs/pointer30.webp) 255 | 256 | ![](../imgs/pointer31.webp) 257 | 258 | 259 | 260 | ### Windows编程中的指针 261 | 262 | Windows是操作系统是用C语言写出来的,所以尽管你在Windows中看到很多不认识的数据类型,但是这些数据类型也是通过基本的C语言类型组装起来的。我们这里只介绍Windows中指针型数据。 263 | 264 | 定义指针数据类型必须使用星号(\*),但是Windows为了开发的方便,通过宏定义将指针“隐藏起来”,严格地说应该是将星号隐藏起来了,下面给出一些例子: 265 | 266 | ![](../imgs/pointer32.webp) 267 | 268 | 269 | 270 | 271 | 272 | ### C++中的智能指针 273 | 274 | 为了保持内容的完整性,暂且列一个标题放在这里,这个话题请参考本专题相关文章。 275 | 276 | 277 | 278 | 我能想到的关于C/C++中指针的内容就这么多了,希望本文对你有用。文中如果有不当或者纰漏的地方欢迎批评指正。 279 | -------------------------------------------------------------------------------- /Git/README.md: -------------------------------------------------------------------------------- 1 | # Git 的使用 2 | 3 | ## 连接到 GitLab 4 | 请在进行下面的操作前先将 ssh-key 添加到 GitLab 5 | 具体方法可以参考教程 6 | 7 | - 安装 Git LFS 8 | Git LFS 是 Git 上管理大文件的一个插件,从而避免 Git 对大文件管理效率不高的问题。为了使用 Git LFS,需要在这里安装。 9 | 10 | 安装方式:https://git-lfs.github.com/ 11 | ``` 12 | git lfs track *.tar // 将tar 文件加入到大文件管理中 13 | git lfs ls-files // 显示管理的文件 14 | ``` 15 | track 会生成一个track记录在.gitattributes,但有新的tar文件时,需要重新track 16 | track 以后需要重新add上去,git lfs ls-files 才能显示新track的文件 17 | 18 | - Git 的基础操作 19 | 这部分网上教程很多,请自行搜索。需要学会如下命令: 20 | ``` 21 | $ git pull 22 | $ git push 23 | $ git add 24 | $ git commit 25 | $ git rebase 26 | $ git rebase -i 27 | $ git merge 28 | $ git submodule update --init 29 | $ git clone 30 | $ git branch 31 | $ git checkout 32 | $ git fetch 33 | 34 | ``` 35 | 36 | 简略: 37 | ``` 38 | git reset --hard HEAD^ 39 | 40 | git clone --recurse-submodules https://github.com/chaconinc/MainProject 41 | 42 | git submodule update --init --recursive 43 | 44 | git config --global user.name "nameVal" 45 | 46 | git submodule add https://devops.momenta.works/Momenta/mf/_git/mjson thirdpart/mjson 47 | 48 | git checkout 6173ec6348cdc91aeb637cdf38252cc9bc3b8732 49 | 50 | 51 | git checkout -b [local_name] [origin/remote_name] 52 | 53 | git reset --hard 54 | git fsck –lost-found 找回删除掉的文件 55 | 56 | git cherry-pick 57 | git rebase 58 | git commit --amend 59 | 60 | 61 | 62 | 手动建立追踪关系 63 | $ git branch --set-upstream master origin/next 64 | 65 | $ git fetch origin master 66 | $ git log -p master..origin/master 67 | $ git merge origin/master 68 | ``` 69 | 70 | ## 组内 Git 使用要求 71 | - 默认大家没有 dev 分支权限,需要 merge request 合并入 dev; 72 | - 做一个新功能前,必须创建独立分支。分支命名规则为your_name/feature_name,例如zhangsan/add_python_interface; 73 | - git push 时的 commit 信息格式为[brain_name][jira_id] message,例如[zhangsan/add_python_interface][PS-1211] fix issue of detect() crash。可以用正则\[.+\] .*校验是否合标; 74 | - git push 前必须 rebase 到 dev 的最新节点。我们要保持 log 的线性结构,唯一可接受的 graph 形状为"仙人掌形"; 75 | - 对于一次修改,建议本地 squash 好再 push(参看 git rebase -i);或者在 merge request 时勾选 squash commits; 76 | “仙人掌形” Git 记录 77 | 如下图所示,如果将 git commit 直接连接关系想象成节点之间的边的话,则不允许形成环。各个分支仅可以从主干上长出,且不允许通过 merge 的方式合并回主干。相较于环形,仙人掌形结构比较清晰,在回退记录的时候也很容易操作。具体操作方式,可以参考本文最后的“课后练习“部分。 78 | 79 | ![image](https://user-images.githubusercontent.com/29084184/160098771-11468c19-ea5b-4e46-9977-d2faa664d325.png) 80 | 81 | 附:一些常见的撤销操作 82 | - 撤销文件[修改] : 83 | 命令: git checkout file_name 84 | - 撤销暂存区文件(撤销 git add file) 85 | 命令: git reset HEAD 全部撤销 86 | 命令: git reset HEAD file_name 对某个文件撤销 87 | - 修该上次提交 88 | 也可以说是新一次提交代替上一次提交,也可以说git使用amend选项提供了最后一次commit的反悔。 89 | 命令: git commit --amend 90 | - 对于历史提交的 commit -- rebase 91 | 往往在项目中,我们会创建工作分支来进行开发,开发完毕后需要将工作分支合并,比如当前有 92 | 工作分支: X 93 | 主流分支(合并到此分支): A 94 | 我们在merge request之前,需要把工作分支中老的提交丢弃,使分支看起来更简洁 95 | 命令: git rebase -i A 96 | 执行之后将会出现类似这样的: 97 | 98 | ``` 99 | pick **** add *** 100 | pick **** add *** 101 | pick **** add *** 102 | pick **** add *** 103 | ``` 104 | 105 | 我们可以使用squash,合并此条记录到前一个记录中,并把commit message也合并进去 。 106 | 之后还会跳出一个临时文件,修改commit message; 107 | 最后保存退出 (ESC wq) 108 | *补: 随时取消 rebase 事务 [ 未push ] 109 | (git rebase –abort) 110 | 111 | - git撤销commit(未git push) 112 | 使用命令(git log) 找到需要回退的commit id XXX 113 | 使用命令(git reset commit_id) 回退版本 114 | -------------------------------------------------------------------------------- /Linux命令及系统编程/Linux.md: -------------------------------------------------------------------------------- 1 | ## inux编程入门 2 | 3 | 4 | 参考: 5 | 6 | https://www.jianshu.com/p/a6416fbf17ae 7 | -------------------------------------------------------------------------------- /Linux命令及系统编程/环境变量.md: -------------------------------------------------------------------------------- 1 | # Linux 环境变量 2 | 3 | ## 1.Linux的变量种类 4 | 5 | 按变量的生存周期来划分,Linux变量可分为两类: 6 | 7 |  - 1.1 永久的:需要修改配置文件,变量永久生效。 8 | 9 |  - 1.2 临时的:使用export命令声明即可,变量在关闭shell时失效。 10 | 11 | ## 2.设置变量的三种方法 12 | 13 | ### 2.1 在/etc/profile文件中添加变量【对所有用户生效(永久的)】 14 | 15 |   用VI在文件/etc/profile文件中增加变量,该变量将会对Linux下所有用户有效,并且是“永久的”。 16 | 17 |   例如:编辑/etc/profile文件,添加CLASSPATH变量 18 | 19 |   ``# vi /etc/profile`` 20 | 21 |   ``export CLASSPATH=./JAVA_HOME/lib;$JAVA_HOME/jre/lib`` 22 | 23 | > 注:修改文件后要想马上生效还要运行# source /etc/profile不然只能在下次重进此用户时生效。 24 | 25 | ### 2.2 在用户目录下的.bash_profile文件中增加变量【对单一用户生效(永久的)】 26 | 27 |   用VI在用户目录下的.bash_profile文件中增加变量,改变量仅会对当前用户有效,并且是“永久的”。 28 | 29 |   例如:编辑guok用户目录(/home/guok)下的.bash_profile 30 | 31 |   ```$ vi /home/guok/.bash.profile``` 32 | 33 |   添加如下内容: 34 | 35 |   ```export CLASSPATH=./JAVA_HOME/lib;$JAVA_HOME/jre/lib``` 36 | 37 |   注:修改文件后要想马上生效还要运行$ source /home/guok/.bash_profile不然只能在下次重进此用户时生效。 38 | 39 | ### 2.3 直接运行export命令定义变量【只对当前shell(BASH)有效(临时的)】 40 | 41 |   在shell的命令行下直接使用[export 变量名=变量值] 定义变量,该变量只在当前的shell(BASH)或其子shell(BASH)下是有效的,shell关闭了,变量也就失效了,再打开新shell时就没有这个变量,需要使用的话还需要重新定义。 42 | 43 | ## 3.环境变量的查看 44 | 45 | ### 3.1 使用echo命令查看单个环境变量。例如: 46 | 47 |   ``echo $PATH`` 48 | 49 | ### 3.2 使用env查看所有环境变量。例如: 50 | 51 |   ``env`` 52 | 53 | ### 3.3 使用set查看所有本地定义的环境变量。 54 | 55 |   ``unset`` 可以删除指定的环境变量。 56 | 57 | ## 4.常用的环境变量 58 | ```bash 59 |   PATH 决定了shell将到哪些目录中寻找命令或程序 60 | 61 |   HOME 当前用户主目录 62 | 63 |   HISTSIZE 历史记录数 64 | 65 |   LOGNAME 当前用户的登录名 66 | 67 |   HOSTNAME 指主机的名称 68 | 69 |   SHELL   当前用户Shell类型 70 | 71 |   LANGUGE  语言相关的环境变量,多语言可以修改此环境变量 72 | 73 |   MAIL   当前用户的邮件存放目录 74 | 75 |   PS1   基本提示符,对于root用户是#,对于普通用户是$ 76 | ``` 77 | 78 | 别名的设置与变量设置方法相同,在不同文件中设置可以使其永久生效或者临时生效, 79 | 80 | 如:可以在/etc/profile文件中添加 alias ls='ls --color=auto' alise ll='ls -alF' 设置命令ll 和ls 81 | 82 | -------------------------------------------------------------------------------- /Shell脚本/shell编程入门.md: -------------------------------------------------------------------------------- 1 | # Shell 编程入门 2 | 3 | - 菜鸟教程- https://www.runoob.com/linux/linux-shell.html 4 | - Google Shell 风格指南 https://zh-google-styleguide.readthedocs.io/en/latest/google-shell-styleguide/contents/ 5 | 6 | 案例: 7 | 8 | ```bash 9 | [root@localhost ~]#name=dangxu //定义一般变量 10 | [root@localhost ~]# echo ${name} 11 | dangxu 12 | [root@localhost ~]# cat test.sh //验证脚本,实例化标题中的./*.sh 13 | #!/bin/sh 14 | echo ${name} 15 | [root@localhost ~]# ls -l test.sh //验证脚本可执行 16 | -rwxr-xr-x 1 root root 23 Feb 6 11:09 test.sh 17 | [root@localhost ~]# ./test.sh //以下三个命令证明了结论一 18 | 19 | [root@localhost ~]# sh ./test.sh 20 | 21 | [root@localhost ~]# bash ./test.sh 22 | 23 | [root@localhost ~]# . ./test.sh //以下两个命令证明了结论二 24 | dangxu 25 | [root@localhost ~]# source ./test.sh 26 | dangxu 27 | [root@localhost ~]# 28 | ``` 29 | 30 | 总结: 31 | 32 | 1. shell 脚本各种执行方式(source ./*.sh, . ./*.sh, ./*.sh)的区别 33 | 34 | 35 | 36 | 38 | 43 | 47 | 48 | 49 | 52 | 55 | 58 | 59 | 60 | 63 | 66 | 69 | 70 | 71 |
37 | 39 | ```./*.sh``` 40 |
``sh ./*.sh`` 41 |
``bash ./*.sh`` 42 |
44 | ``source ./*.sh`` 45 |
``. ./*.sh`` 46 |
50 | 是否需要执行权限 51 | 53 | 只有`./*.sh`需要权限 54 | 56 | 否 57 |
61 | 是否创建子shell 62 | 64 | 是,变量不与父shell环境交互 65 | 67 | 否, 变量与父shell环境交互 68 |
72 | 73 | 2. 脚本的第一行固定为#!/bin/bash,表示用/bin/bash执行这个脚本 74 | 3. 脚本用chmod +x获得可执行权限后,可以用./脚本名.sh的方式执行 75 | 76 | ```bash 77 | $ chmod +x test.sh 78 | ``` 79 | 80 | 81 |
82 | 83 | ## 大纲 84 | 85 | - Shell 变量 86 | - Shell 传递参数 87 | - Shell 数组 88 | - Shell 运算符 89 | - Shell echo命令 90 | - Shell printf命令 91 | - Shell test 命令 92 | - Shell 流程控制 93 | - Shell 函数 94 | - Shell 输入/输出重定向 95 | - Shell 文件包含 96 | -------------------------------------------------------------------------------- /_coverpage.md: -------------------------------------------------------------------------------- 1 |
2 | L0CV 3 |
4 | 5 | # C++ 开发进阶之路 🌱 6 | 7 | 8 | > 底层原理、高阶实战 9 | 10 |
11 | 12 | 15 | 18 | 19 |
20 | 21 | [GitHub](https://github.com/Charmve/CppMaster) 22 | [Get Started](/README.md) 23 | -------------------------------------------------------------------------------- /_navbar.md: -------------------------------------------------------------------------------- 1 | - [ 🏡 首页](/README.md) 2 | - [🚀 项目主页](https://charmve.github.io/L0CV-web) 3 | - [:octocat: GitHub](https://github.com/Charmve/computer-vision-in-action) 4 | - [📕 关于本书](book_preface.md) 5 | - [⛷ 读者微信交流群](https://mp.weixin.qq.com/s/jr6h1lXxWsQLF8GZnek7Fg) 6 | - [🌌 L0CV Universe](https://github.com/Charmve/computer-vision-in-action/tree/main/L0CV-Universe) 7 | - [🌼 Demo Day](https://github.com/Charmve/computer-vision-in-action/tree/main/L0CV-Universe/DemoDays.md) 8 | - [🍁 Meet Up](https://github.com/Charmve/computer-vision-in-action/tree/main/L0CV-Universe/MeetUp.md) 9 | - [🍀 Challenges](https://github.com/Charmve/computer-vision-in-action/tree/main/L0CV-Universe/Challenges.md) 10 | -------------------------------------------------------------------------------- /src/header.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 79 |
80 |

👾 Cpp-Master
C++ Advanced Programming

81 |

Easy-to-go 🚀

82 |
83 |
84 |
85 |
86 | -------------------------------------------------------------------------------- /src/i_magic_box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgramTraveler/CppMaster/b35252f8804b1a0821dbb10a5d502031973fe53f/src/i_magic_box.png -------------------------------------------------------------------------------- /src/imgs/C++开发工程师-Momenta.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgramTraveler/CppMaster/b35252f8804b1a0821dbb10a5d502031973fe53f/src/imgs/C++开发工程师-Momenta.jpg -------------------------------------------------------------------------------- /src/imgs/C++资深软件工程师-Momenta.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgramTraveler/CppMaster/b35252f8804b1a0821dbb10a5d502031973fe53f/src/imgs/C++资深软件工程师-Momenta.jpg -------------------------------------------------------------------------------- /src/imgs/Microsoft_Top100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgramTraveler/CppMaster/b35252f8804b1a0821dbb10a5d502031973fe53f/src/imgs/Microsoft_Top100.png -------------------------------------------------------------------------------- /src/imgs/Top100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgramTraveler/CppMaster/b35252f8804b1a0821dbb10a5d502031973fe53f/src/imgs/Top100.png -------------------------------------------------------------------------------- /src/imgs/嵌入式开发工程师-Momenta.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgramTraveler/CppMaster/b35252f8804b1a0821dbb10a5d502031973fe53f/src/imgs/嵌入式开发工程师-Momenta.jpg -------------------------------------------------------------------------------- /src/imgs/嵌入式软件工程师-百度.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgramTraveler/CppMaster/b35252f8804b1a0821dbb10a5d502031973fe53f/src/imgs/嵌入式软件工程师-百度.jpg -------------------------------------------------------------------------------- /src/imgs/嵌入式软件开发工程师-蔚来.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgramTraveler/CppMaster/b35252f8804b1a0821dbb10a5d502031973fe53f/src/imgs/嵌入式软件开发工程师-蔚来.jpg -------------------------------------------------------------------------------- /src/imgs/智能驾驶软件开发工程师-蔚来.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgramTraveler/CppMaster/b35252f8804b1a0821dbb10a5d502031973fe53f/src/imgs/智能驾驶软件开发工程师-蔚来.jpg -------------------------------------------------------------------------------- /src/imgs/解题模板.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgramTraveler/CppMaster/b35252f8804b1a0821dbb10a5d502031973fe53f/src/imgs/解题模板.png -------------------------------------------------------------------------------- /src/imgs/高精度定位融合-腾讯.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgramTraveler/CppMaster/b35252f8804b1a0821dbb10a5d502031973fe53f/src/imgs/高精度定位融合-腾讯.jpg -------------------------------------------------------------------------------- /src/imgs/高级嵌入式开发工程师-小马智行.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgramTraveler/CppMaster/b35252f8804b1a0821dbb10a5d502031973fe53f/src/imgs/高级嵌入式开发工程师-小马智行.jpg -------------------------------------------------------------------------------- /src/maiwei-planet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgramTraveler/CppMaster/b35252f8804b1a0821dbb10a5d502031973fe53f/src/maiwei-planet.jpg -------------------------------------------------------------------------------- /src/one-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgramTraveler/CppMaster/b35252f8804b1a0821dbb10a5d502031973fe53f/src/one-logo.jpg -------------------------------------------------------------------------------- /操作系统/README.md: -------------------------------------------------------------------------------- 1 | ## 💻 操作系统 2 | 3 | ### 进程与线程 4 | 5 | 对于有线程系统: 6 | * 进程是资源分配的独立单位 7 | * 线程是资源调度的独立单位 8 | 9 | 对于无线程系统: 10 | * 进程是资源调度、分配的独立单位 11 | 12 | #### 进程之间的通信方式以及优缺点 13 | 14 | * 管道(PIPE) 15 | * 有名管道:一种半双工的通信方式,它允许无亲缘关系进程间的通信 16 | * 优点:可以实现任意关系的进程间的通信 17 | * 缺点: 18 | 1. 长期存于系统中,使用不当容易出错 19 | 2. 缓冲区有限 20 | * 无名管道:一种半双工的通信方式,只能在具有亲缘关系的进程间使用(父子进程) 21 | * 优点:简单方便 22 | * 缺点: 23 | 1. 局限于单向通信 24 | 2. 只能创建在它的进程以及其有亲缘关系的进程之间 25 | 3. 缓冲区有限 26 | * 信号量(Semaphore):一个计数器,可以用来控制多个线程对共享资源的访问 27 | * 优点:可以同步进程 28 | * 缺点:信号量有限 29 | * 信号(Signal):一种比较复杂的通信方式,用于通知接收进程某个事件已经发生 30 | * 消息队列(Message Queue):是消息的链表,存放在内核中并由消息队列标识符标识 31 | * 优点:可以实现任意进程间的通信,并通过系统调用函数来实现消息发送和接收之间的同步,无需考虑同步问题,方便 32 | * 缺点:信息的复制需要额外消耗 CPU 的时间,不适宜于信息量大或操作频繁的场合 33 | * 共享内存(Shared Memory):映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问 34 | * 优点:无须复制,快捷,信息量大 35 | * 缺点: 36 | 1. 通信是通过将共享空间缓冲区直接附加到进程的虚拟地址空间中来实现的,因此进程间的读写操作的同步问题 37 | 2. 利用内存缓冲区直接交换信息,内存的实体存在于计算机中,只能同一个计算机系统中的诸多进程共享,不方便网络通信 38 | * 套接字(Socket):可用于不同计算机间的进程通信 39 | * 优点: 40 | 1. 传输数据为字节级,传输数据可自定义,数据量小效率高 41 | 2. 传输数据时间短,性能高 42 | 3. 适合于客户端和服务器端之间信息实时交互 43 | 4. 可以加密,数据安全性强 44 | * 缺点:需对传输的数据进行解析,转化成应用级的数据。 45 | 46 | #### 线程之间的通信方式 47 | 48 | * 锁机制:包括互斥锁/量(mutex)、读写锁(reader-writer lock)、自旋锁(spin lock)、条件变量(condition) 49 | * 互斥锁/量(mutex):提供了以排他方式防止数据结构被并发修改的方法。 50 | * 读写锁(reader-writer lock):允许多个线程同时读共享数据,而对写操作是互斥的。 51 | * 自旋锁(spin lock)与互斥锁类似,都是为了保护共享资源。互斥锁是当资源被占用,申请者进入睡眠状态;而自旋锁则循环检测保持者是否已经释放锁。 52 | * 条件变量(condition):可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。 53 | * 信号量机制(Semaphore) 54 | * 无名线程信号量 55 | * 命名线程信号量 56 | * 信号机制(Signal):类似进程间的信号处理 57 | * 屏障(barrier):屏障允许每个线程等待,直到所有的合作线程都达到某一点,然后从该点继续执行。 58 | 59 | 线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制 60 | 61 | > 进程之间的通信方式以及优缺点来源于:[进程线程面试题总结](http://blog.csdn.net/wujiafei_njgcxy/article/details/77098977) 62 | 63 | #### 进程之间私有和共享的资源 64 | 65 | * 私有:地址空间、堆、全局变量、栈、寄存器 66 | * 共享:代码段,公共数据,进程目录,进程 ID 67 | 68 | #### 线程之间私有和共享的资源 69 | 70 | * 私有:线程栈,寄存器,程序计数器 71 | * 共享:堆,地址空间,全局变量,静态变量 72 | 73 | #### 多进程与多线程间的对比、优劣与选择 74 | 75 | ##### 对比 76 | 77 | 对比维度 | 多进程 | 多线程 | 总结 78 | ---|---|---|--- 79 | 数据共享、同步|数据共享复杂,需要用 IPC;数据是分开的,同步简单|因为共享进程数据,数据共享简单,但也是因为这个原因导致同步复杂|各有优势 80 | 内存、CPU|占用内存多,切换复杂,CPU 利用率低|占用内存少,切换简单,CPU 利用率高|线程占优 81 | 创建销毁、切换|创建销毁、切换复杂,速度慢|创建销毁、切换简单,速度很快|线程占优 82 | 编程、调试|编程简单,调试简单|编程复杂,调试复杂|进程占优 83 | 可靠性|进程间不会互相影响|一个线程挂掉将导致整个进程挂掉|进程占优 84 | 分布式|适应于多核、多机分布式;如果一台机器不够,扩展到多台机器比较简单|适应于多核分布式|进程占优 85 | 86 | ##### 优劣 87 | 88 | 优劣|多进程|多线程 89 | ---|---|--- 90 | 优点|编程、调试简单,可靠性较高|创建、销毁、切换速度快,内存、资源占用小 91 | 缺点|创建、销毁、切换速度慢,内存、资源占用大|编程、调试复杂,可靠性较差 92 | 93 | ##### 选择 94 | 95 | * 需要频繁创建销毁的优先用线程 96 | * 需要进行大量计算的优先使用线程 97 | * 强相关的处理用线程,弱相关的处理用进程 98 | * 可能要扩展到多机分布的用进程,多核分布的用线程 99 | * 都满足需求的情况下,用你最熟悉、最拿手的方式 100 | 101 | > 多进程与多线程间的对比、优劣与选择来自:[多线程还是多进程的选择及区别](https://blog.csdn.net/lishenglong666/article/details/8557215) 102 | 103 | ### Linux 内核的同步方式 104 | 105 | #### 原因 106 | 107 | 在现代操作系统里,同一时间可能有多个内核执行流在执行,因此内核其实像多进程多线程编程一样也需要一些同步机制来同步各执行单元对共享数据的访问。尤其是在多处理器系统上,更需要一些同步机制来同步不同处理器上的执行单元对共享的数据的访问。 108 | 109 | #### 同步方式 110 | 111 | * 原子操作 112 | * 信号量(semaphore) 113 | * 读写信号量(rw_semaphore) 114 | * 自旋锁(spinlock) 115 | * 大内核锁(BKL,Big Kernel Lock) 116 | * 读写锁(rwlock) 117 | * 大读者锁(brlock-Big Reader Lock) 118 | * 读-拷贝修改(RCU,Read-Copy Update) 119 | * 顺序锁(seqlock) 120 | 121 | > 来自:[Linux 内核的同步机制,第 1 部分](https://www.ibm.com/developerworks/cn/linux/l-synch/part1/)、[Linux 内核的同步机制,第 2 部分](https://www.ibm.com/developerworks/cn/linux/l-synch/part2/) 122 | 123 | ### 死锁 124 | 125 | #### 原因 126 | 127 | * 系统资源不足 128 | * 资源分配不当 129 | * 进程运行推进顺序不合适 130 | 131 | #### 产生条件 132 | 133 | * 互斥 134 | * 请求和保持 135 | * 不剥夺 136 | * 环路 137 | 138 | #### 预防 139 | 140 | * 打破互斥条件:改造独占性资源为虚拟资源,大部分资源已无法改造。 141 | * 打破不可抢占条件:当一进程占有一独占性资源后又申请一独占性资源而无法满足,则退出原占有的资源。 142 | * 打破占有且申请条件:采用资源预先分配策略,即进程运行前申请全部资源,满足则运行,不然就等待,这样就不会占有且申请。 143 | * 打破循环等待条件:实现资源有序分配策略,对所有设备实现分类编号,所有进程只能采用按序号递增的形式申请资源。 144 | * 有序资源分配法 145 | * 银行家算法 146 | 147 | ### 文件系统 148 | 149 | * Windows:FCB 表 + FAT + 位图 150 | * Unix:inode + 混合索引 + 成组链接 151 | 152 | ### 主机字节序与网络字节序 153 | 154 | #### 主机字节序(CPU 字节序) 155 | 156 | ##### 概念 157 | 158 | 主机字节序又叫 CPU 字节序,其不是由操作系统决定的,而是由 CPU 指令集架构决定的。主机字节序分为两种: 159 | 160 | * 大端字节序(Big Endian):高序字节存储在低位地址,低序字节存储在高位地址 161 | * 小端字节序(Little Endian):高序字节存储在高位地址,低序字节存储在低位地址 162 | 163 | ##### 存储方式 164 | 165 | 32 位整数 `0x12345678` 是从起始位置为 `0x00` 的地址开始存放,则: 166 | 167 | 内存地址 | 0x00 | 0x01 | 0x02 | 0x03 168 | ---|---|---|---|--- 169 | 大端|12|34|56|78 170 | 小端|78|56|34|12 171 | 172 | 大端小端图片 173 | 174 | ![大端序](https://gitee.com/huihut/interview/raw/master/images/CPU-Big-Endian.svg.png) 175 | ![小端序](https://gitee.com/huihut/interview/raw/master/images/CPU-Little-Endian.svg.png) 176 | 177 | ##### 判断大端小端 178 | 179 | 判断大端小端 180 | 181 | 可以这样判断自己 CPU 字节序是大端还是小端: 182 | 183 | ```cpp 184 | #include 185 | using namespace std; 186 | 187 | int main() 188 | { 189 | int i = 0x12345678; 190 | 191 | if (*((char*)&i) == 0x12) 192 | cout << "大端" << endl; 193 | else 194 | cout << "小端" << endl; 195 | 196 | return 0; 197 | } 198 | ``` 199 | 200 | ##### 各架构处理器的字节序 201 | 202 | * x86(Intel、AMD)、MOS Technology 6502、Z80、VAX、PDP-11 等处理器为小端序; 203 | * Motorola 6800、Motorola 68000、PowerPC 970、System/370、SPARC(除 V9 外)等处理器为大端序; 204 | * ARM(默认小端序)、PowerPC(除 PowerPC 970 外)、DEC Alpha、SPARC V9、MIPS、PA-RISC 及 IA64 的字节序是可配置的。 205 | 206 | #### 网络字节序 207 | 208 | 网络字节顺序是 TCP/IP 中规定好的一种数据表示格式,它与具体的 CPU 类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。 209 | 210 | 网络字节顺序采用:大端(Big Endian)排列方式。 211 | 212 | ### 页面置换算法 213 | 214 | 在地址映射过程中,若在页面中发现所要访问的页面不在内存中,则产生缺页中断。当发生缺页中断时,如果操作系统内存中没有空闲页面,则操作系统必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。而用来选择淘汰哪一页的规则叫做页面置换算法。 215 | 216 | #### 分类 217 | 218 | * 全局置换:在整个内存空间置换 219 | * 局部置换:在本进程中进行置换 220 | 221 | #### 算法 222 | 223 | 全局: 224 | * 工作集算法 225 | * 缺页率置换算法 226 | 227 | 局部: 228 | * 最佳置换算法(OPT) 229 | * 先进先出置换算法(FIFO) 230 | * 最近最久未使用(LRU)算法 231 | * 时钟(Clock)置换算法 232 | -------------------------------------------------------------------------------- /操作系统/死锁问题合集.md: -------------------------------------------------------------------------------- 1 | # Q 1.1:死锁必要条件 2 | 3 | **什么是死锁** 4 | 5 | 死锁是进程死锁的简称,进程A占有资源R1,等待进程B占有的资源R2;进程B占有资源R2,等待进程A占有的资源R1。而且资源R1和R2只允许一个进程占用,即:不允许两个 6 | 7 | 进程同时占用。结果,两个进程都不能继续执行,若不采取其它措施,这种循环等待状况会无限期持续下去,就发生了进程死锁。 8 | 9 |

10 | 11 | 如果在计算机系统中同时具备下面四个必要条件时,那麽会发生死锁。换句话说,只要下面四个条件有一个不具备,系统就不会出现死锁。 12 | 13 | - **互斥**:每个资源要么已经分配给了一个进程,要么就是可用的。这种独占资源如CD-ROM驱动器,打印机等等,必须在占有该资源的进程主动释放它之后,其它进程才能占有该资源。这是由资源本身的属性所决定的。 14 | 15 | - **占有和等待**:已经得到了某个资源的进程可以再请求新的资源。以过独木桥为例,甲乙两人在桥上相遇。甲走过一段桥面(即占有了一些资源),还需要走其余的桥面(申请新的资源),但那部分桥面被乙占有(乙走过一段桥面)。甲过不去,前进不能,又不后退;乙也处于同样的状况 16 | 。 17 | - **不可抢占**:已经分配给一个进程的资源不能强制性地被抢占,它只能被占有它的进程显式地释放。 18 | 19 | - **环路等待**:有两个或者两个以上的进程组成一条环路,该环路中的每个进程都在等待下一个进程所占有的资源。 20 | 21 | 死锁产生的本质原因是: 22 | 23 | - 系统资源有限 24 | - 进程推进顺序不合理 25 | 26 | # Q 1.2:解决死锁的策略 27 | 28 | 主要有以下四种方法: 29 | 30 | - 鸵鸟策略 31 | - 死锁检测与死锁恢复 32 | - 死锁预防 33 | - 死锁避免 34 | 35 | # 鸵鸟策略 36 | 37 | 把头埋在沙子里,假装根本没发生问题。 38 | 39 | 因为解决死锁问题的代价很高,因此鸵鸟策略这种不采取任务措施的方案会获得更高的性能。 40 | 41 | 当发生死锁时不会对用户造成多大影响,或发生死锁的概率很低,可以采用鸵鸟策略。 42 | 43 | 大多数操作系统,包括 Unix,Linux 和 Windows,处理死锁问题的办法仅仅是忽略它。 44 | 45 | # 死锁检测与死锁恢复 46 | 47 | 不试图阻止死锁,而是当检测到死锁发生时,采取措施进行恢复。 48 | 49 | ## 1. 每种类型一个资源的死锁检测 50 | 51 |

52 | 53 | 上图为资源分配图,其中方框表示资源,圆圈表示进程。资源指向进程表示该资源已经分配给该进程,进程指向资源表示进程请求获取该资源。 54 | 55 | 图 a 可以抽取出环,如图 b,它满足了环路等待条件,因此会发生死锁。 56 | 57 | 每种类型一个资源的死锁检测算法是通过检测有向图是否存在环来实现,从一个节点出发进行深度优先搜索,对访问过的节点进行标记,如果访问了已经标记的节点,就表示有向图存在环,也就是检测到死锁的发生。 58 | 59 | ## 2. 每种类型多个资源的死锁检测 60 | 61 |

62 | 63 | 上图中,有三个进程四个资源,每个数据代表的含义如下: 64 | 65 | - E 向量:资源总量 66 | - A 向量:资源剩余量 67 | - C 矩阵:每个进程所拥有的资源数量,每一行都代表一个进程拥有资源的数量 68 | - R 矩阵:每个进程请求的资源数量 69 | 70 | 进程 P1 和 P2 所请求的资源都得不到满足,只有进程 P3 可以,让 P3 执行,之后释放 P3 拥有的资源,此时 A = (2 2 2 0)。P2 可以执行,执行后释放 P2 拥有的资源,A = (4 2 2 1) 。P1 也可以执行。所有进程都可以顺利执行,没有死锁。 71 | 72 | 算法总结如下: 73 | 74 | 每个进程最开始时都不被标记,执行过程有可能被标记。当算法结束时,任何没有被标记的进程都是死锁进程。 75 | 76 | 1. 寻找一个没有标记的进程 Pi,它所请求的资源小于等于 A。 77 | 2. 如果找到了这样一个进程,那么将 C 矩阵的第 i 行向量加到 A 中,标记该进程,并转回 1。 78 | 3. 如果没有这样一个进程,算法终止。 79 | 80 | ## 3. 死锁恢复 81 | 82 | - 利用抢占恢复 83 | - 利用回滚恢复 84 | - 通过杀死进程恢复 85 | 86 | # Q 1.3:写出和分析死锁代码 87 | ``` 88 | package deadlock; 89 | 90 | public class deadlock1 { 91 | public static String str1 = "str1"; 92 | public static String str2 = "str2"; 93 | 94 | public static void main(String[] args){ 95 | Thread a = new Thread(() -> { 96 | try{ 97 | while(true){ 98 | synchronized(deadlock1.str1){ 99 | System.out.println(Thread.currentThread().getName()+"锁住 str1"); 100 | Thread.sleep(1000); 101 | synchronized(deadlock1.str2){ 102 | System.out.println(Thread.currentThread().getName()+"锁住 str2"); 103 | } 104 | } 105 | } 106 | }catch(Exception e){ 107 | e.printStackTrace(); 108 | } 109 | }); 110 | 111 | Thread b = new Thread(() -> { 112 | try{ 113 | while(true){ 114 | synchronized(deadlock1.str2){ 115 | System.out.println(Thread.currentThread().getName()+"锁住 str2"); 116 | Thread.sleep(1000); 117 | synchronized(deadlock1.str1){ 118 | System.out.println(Thread.currentThread().getName()+"锁住 str1"); 119 | } 120 | } 121 | } 122 | }catch(Exception e){ 123 | e.printStackTrace(); 124 | } 125 | }); 126 | 127 | a.start(); 128 | b.start(); 129 | } 130 | } 131 | ``` 132 | 133 | 程序输出: 134 | 135 | *Thread-1锁住 str2 136 | Thread-0锁住 str1* 137 | 138 | 上面的代码就是一个完整的死锁程序,程序中有两个线程,线程1锁住了str1,获得锁之后休眠1秒钟,这个时候线程2锁住了str2,也进行休眠操作。 139 | 140 | 线程1休眠完了之后去锁str2,但是str2已经被线程2给锁住了,这边只能等待,同样的道理,线程2休眠完之后也要去锁str1,同样也会等待,这样死锁就产生了。 141 | 142 | # Q 1.4:说明在数据库管理系统或者JAVA中如何解决死锁 143 | 144 | ## A.死锁预防 145 | 146 | 在程序运行之前预防死锁。 147 | 148 | **1.破坏互斥条件** 149 | 产生死锁需要四个条件,那么,只要这四个条件中至少有一个条件得不到满足,就不可能发生死锁了。由于互斥条件是非共享资源所必须的,不仅不能改变,还应加以保证,所以,主要是破坏产生死锁的其他三个条件。 150 | 151 | **2.破坏占有和等待条件** 152 | - 方法一:所有的进程在开始运行之前,必须一次性地申请其在整个运行过程中的全部资源。 153 | 优点:简单易实施且安全; 154 | 缺点:因为某项资源不满足而导致进程无法启动,而其他已经满足了的资源也不会得到利用,严重降低了资源的利用率,造成资源浪费,使进程经常出现饥饿现象。 155 | 156 | - 方法二:是对方法一的改进,允许进程只获得运行初期需要的资源,便开始运行,在运行过程中逐步释放掉分配到的已经使用完毕的资源,然后再去请求新的资源。这样的话,资源的利用率会得到提高,也会减少进程的饥饿情况。 157 | 158 | **3.破坏不可抢占条件** 159 | 当一个已经持有了一些资源的进程在提出新的资源请求没有得到满足时,它必须释放已经保持的所有资源,待以后需要使用的时候再重新申请。这就意味着进程已占有的资源会被短暂地释放或者说是被抢占了。 160 | 该种方法实现起来比较复杂,且代价也比较大。释放已经保持的资源很有可能会导致进程之前的工作实效等,反复的申请和释放资源会导致进程的执行被无限的推迟,这不仅会延长进程的周转周期,还会影响系统的吞吐量。 161 | 162 | **4.破坏环路等待** 163 | 给资源统一编号,进程只能按编号顺序来请求资源。当一个进程占有编号为i的资源时,那么它下一次申请资源只能申请编号大于i的资源。如下图所示 164 |

165 | 166 | 这样虽然避免了循环等待,但是这种方法是比较低效的,资源的执行速度回变慢,并且可能在没有必要的情况下拒绝资源的访问,比如说,进程c想要申请资源1,如果资源1并没有被其他进程占有,此时将它分配个进程c是没有问题的,但是为了避免产生循环等待,该申请会被拒绝,这样就降低了资源的利用率 167 | 168 | ## B.死锁避免 169 | 在使用前进行判断,只允许不会产生死锁的进程申请资源 170 | 死锁避免是利用额外的检验信息,在分配资源时判断是否会出现死锁,只在不会出现死锁的情况下才分配资源。 171 | 两种避免办法: 172 | - 如果一个进程的请求会导致死锁,则不会启动该进程; 173 | - 如果一个进程的增加资源会导致死锁,那么拒绝该申请。 174 | 175 | **1.安全状态** 176 |

177 | 178 | 图 a 的第二列 Has 表示已拥有的资源数,第三列 Max 表示总共需要的资源数,Free 表示还有可以使用的资源数。从图 a 开始出发,先让 B 拥有所需的所有资源(图 b),运行结束后释放 B,此时 Free 变为 5(图 c);接着以同样的方式运行 C 和 A,使得所有进程都能成功运行,因此可以称图 a 所示的状态是安全的。 179 | 180 | 定义:如果没有死锁发生,并且即使所有进程突然请求对资源的最大需求,也仍然存在某种调度次序能够使得每一个进程运行完毕,则称该状态是安全的。 181 | 182 | 安全状态的检测与死锁的检测类似,因为安全状态必须要求不能发生死锁。下面的银行家算法与死锁检测算法非常类似,可以结合着做参考对比。 183 | 184 | 185 | **2.单个资源的银行家算法** 186 | 一个小城镇的银行家,他向一群客户分别承诺了一定的贷款额度,算法要做的是判断对请求的满足是否会进入不安全状态,如果是,就拒绝请求;否则予以分配。 187 | 188 |

189 | 190 | 上图 c 为不安全状态,因此算法会拒绝之前的请求,从而避免进入图 c 中的状态。 191 | 192 | **多个资源的银行家算法** 193 |

194 | 195 | 上图中有五个进程,四个资源。左边的图表示已经分配的资源,右边的图表示还需要分配的资源。最右边的 E、P 以及 A 分别表示:总资源、已分配资源以及可用资源,注意这三个为向量,而不是具体数值,例如 A=(1020),表示 4 个资源分别还剩下 1/0/2/0。 196 | 197 | 检查一个状态是否安全的算法如下: 198 | 199 | - 查找右边的矩阵是否存在一行小于等于向量 A。如果不存在这样的行,那么系统将会发生死锁,状态是不安全的。 200 | - 假若找到这样一行,将该进程标记为终止,并将其已分配资源加到 A 中。 201 | - 重复以上两步,直到所有进程都标记为终止,则状态时安全的。 202 | 203 | 如果一个状态不是安全的,需要拒绝进入这个状态。 204 | 205 | 206 | 207 | 208 | # 附录:死锁检测与恢复 209 | 不试图阻止死锁,而是当检测到死锁发生时,采取措施进行恢复。 210 | 211 | ## 1. 每种类型一个资源的死锁检测 212 | 213 |

214 | 215 | 上图为资源分配图,其中方框表示资源,圆圈表示进程。资源指向进程表示该资源已经分配给该进程,进程指向资源表示进程请求获取该资源。 216 | 217 | 图 a 可以抽取出环,如图 b,它满足了环路等待条件,因此会发生死锁。 218 | 219 | 每种类型一个资源的死锁检测算法是通过检测有向图是否存在环来实现,从一个节点出发进行深度优先搜索,对访问过的节点进行标记,如果访问了已经标记的节点,就表示有向图存在环,也就是检测到死锁的发生。 220 | 221 | ## 2. 每种类型多个资源的死锁检测 222 | 223 |

224 | 225 | 上图中,有三个进程四个资源,每个数据代表的含义如下: 226 | 227 | - E 向量:资源总量 228 | - A 向量:资源剩余量 229 | - C 矩阵:每个进程所拥有的资源数量,每一行都代表一个进程拥有资源的数量 230 | - R 矩阵:每个进程请求的资源数量 231 | 232 | 进程 P1 和 P2 所请求的资源都得不到满足,只有进程 P3 可以,让 P3 执行,之后释放 P3 拥有的资源,此时 A = (2 2 2 0)。P2 可以执行,执行后释放 P2 拥有的资源,A = (4 2 2 1) 。P1 也可以执行。所有进程都可以顺利执行,没有死锁。 233 | 234 | 算法总结如下: 235 | 236 | 每个进程最开始时都不被标记,执行过程有可能被标记。当算法结束时,任何没有被标记的进程都是死锁进程。 237 | 238 | 1. 寻找一个没有标记的进程 Pi,它所请求的资源小于等于 A。 239 | 2. 如果找到了这样一个进程,那么将 C 矩阵的第 i 行向量加到 A 中,标记该进程,并转回 1。 240 | 3. 如果没有这样一个进程,算法终止。 241 | 242 | ## 3. 死锁恢复 243 | 244 | - 利用抢占恢复 245 | - 利用回滚恢复 246 | - 通过杀死进程恢复 247 | 248 | -------------------------------------------------------------------------------- /操作系统/链接问题合集.md: -------------------------------------------------------------------------------- 1 | # Q 1.1 分析静态链接的不足 2 | 3 | ## 1.编译过程 4 | 5 |

6 | 7 | - 预处理阶段:处理以 # 开头的预处理命令; 8 | - 编译阶段:翻译成汇编文件; 9 | - 汇编阶段:将汇编文件翻译成可重定向目标文件; 10 | - 链接阶段:将可重定向目标文件和 printf.o 等单独预编译好的目标文件进行合并,得到最终的可执行目标文件。 11 | 12 | ## 2.什么是静态链接 13 | 14 | 静态链接器以一组可重定向目标文件为输入,生成一个完全链接的可执行目标文件作为输出。链接器主要完成以下两个任务: 15 | 16 | - 符号解析:每个符号对应于一个函数、一个全局变量或一个静态变量,符号解析的目的是将每个符号引用与一个符号定义关联起来。 17 | - 重定位:链接器通过把每个符号定义与一个内存位置关联起来,然后修改所有对这些符号的引用,使得它们指向这个内存位置。 18 | 19 |

20 | 21 | **名词解释** 22 | 23 | - 可执行目标文件:可以直接在内存中执行; 24 | - 可重定向目标文件:可与其它可重定向目标文件在链接阶段合并,创建一个可执行目标文件; 25 | - 共享目标文件:这是一种特殊的可重定向目标文件,可以在运行时被动态加载进内存并链接; 26 | 27 | ## 3.静态链接的缺点 28 | 29 | - 当静态库更新时那么整个程序都要重新进行链接; 30 | - 对于 printf 这种标准函数库,如果每个程序都要有代码,这会极大浪费资源。 31 | 32 | 33 | # Q 1.2 动态链接的特点 34 | 35 | 共享库是为了解决静态库的这两个问题而设计的,在 Linux 系统中通常用 .so 后缀来表示,Windows 系统上它们被称为 DLL。它具有以下特点: 36 | 37 | - 在给定的文件系统中一个库只有一个文件,所有引用该库的可执行目标文件都共享这个文件,它不会被复制到引用它的可执行文件中; 38 | - 在内存中,一个共享库的 .text 节(已编译程序的机器代码)的一个副本可以被不同的正在运行的进程共享。 39 | 40 |

41 | -------------------------------------------------------------------------------- /数据库/MySQL问题合集.md: -------------------------------------------------------------------------------- 1 | # 1.★★★ B+ Tree 原理,与其它查找树的比较 2 | 3 | ### 1. 数据结构 4 | 5 | B Tree 指的是 Balance Tree,也就是平衡树。平衡树是一颗查找树,并且所有叶子节点位于同一层。 6 | 7 | B+ Tree 是基于 B Tree 和叶子节点顺序访问指针进行实现,它具有 B Tree 的平衡性,并且通过顺序访问指针来提高区间查询的性能。 8 | 9 | 在 B+ Tree 中,一个节点中的 key 从左到右非递减排列,如果某个指针的左右相邻 key 分别是 keyi 和 keyi+1,且不为 null,则该指针指向节点的所有 key 大于等于 keyi 且小于等于 keyi+1。 10 | 11 |

12 | 13 | ### 2. 操作 14 | 15 | 进行查找操作时,首先在根节点进行二分查找,找到一个 key 所在的指针,然后递归地在指针所指向的节点进行查找。直到查找到叶子节点,然后在叶子节点上进行二分查找,找出 key 所对应的 data。 16 | 17 | 插入删除操作会破坏平衡树的平衡性,因此在插入删除操作之后,需要对树进行一个分裂、合并、旋转等操作来维护平衡性。 18 | 19 | ### 3. 与红黑树的比较 20 | 21 | 红黑树等平衡树也可以用来实现索引,但是文件系统及数据库系统普遍采用 B+ Tree 作为索引结构,主要有以下两个原因: 22 | 23 | (一)更少的查找次数 24 | 25 | 平衡树查找操作的时间复杂度和树高 h 相关,O(h)=O(logdN),其中 d 为每个节点的出度。 26 | 27 | 红黑树的出度为 2,而 B+ Tree 的出度一般都非常大,所以红黑树的树高 h 很明显比 B+ Tree 大非常多,查找的次数也就更多。 28 | 29 | (二)利用磁盘预读特性 30 | 31 | 为了减少磁盘 I/O 操作,磁盘往往不是严格按需读取,而是每次都会预读。预读过程中,磁盘进行顺序读取,顺序读取不需要进行磁盘寻道,并且只需要很短的旋转时间,速度会非常快。 32 | 33 | 操作系统一般将内存和磁盘分割成固定大小的块,每一块称为一页,内存与磁盘以页为单位交换数据。数据库系统将索引的一个节点的大小设置为页的大小,使得一次 I/O 就能完全载入一个节点。并且可以利用预读特性,相邻的节点也能够被预先载入。 34 | 35 | # 2.★★★MySQL 索引以及优化 36 | 37 | 索引是在存储引擎层实现的,而不是在服务器层实现的,所以不同存储引擎具有不同的索引类型和实现。 38 | 39 | ### 1. B+Tree 索引 40 | 41 | 是大多数 MySQL 存储引擎的默认索引类型。 42 | 43 | 因为不再需要进行全表扫描,只需要对树进行搜索即可,所以查找速度快很多。 44 | 45 | 除了用于查找,还可以用于排序和分组。 46 | 47 | 可以指定多个列作为索引列,多个索引列共同组成键。 48 | 49 | 适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。如果不是按照索引列的顺序进行查找,则无法使用索引。 50 | 51 | InnoDB 的 B+Tree 索引分为主索引和辅助索引。主索引的叶子节点 data 域记录着完整的数据记录,这种索引方式被称为聚簇索引。因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。 52 | 53 |

54 | 55 | 辅助索引的叶子节点的 data 域记录着主键的值,因此在使用辅助索引进行查找时,需要先查找到主键值,然后再到主索引中进行查找。 56 | 57 |

58 | 59 | ### 2. 哈希索引 60 | 61 | 哈希索引能以 O(1) 时间进行查找,但是失去了有序性: 62 | 63 | - 无法用于排序与分组; 64 | - 只支持精确查找,无法用于部分查找和范围查找。 65 | 66 | InnoDB 存储引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B+Tree 索引之上再创建一个哈希索引,这样就让 B+Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。 67 | 68 | ### 3. 全文索引 69 | 70 | MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。 71 | 72 | 查找条件使用 MATCH AGAINST,而不是普通的 WHERE。 73 | 74 | 全文索引使用倒排索引实现,它记录着关键词到其所在文档的映射。 75 | 76 | InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。 77 | 78 | ### 4. 空间数据索引 79 | 80 | MyISAM 存储引擎支持空间数据索引(R-Tree),可以用于地理数据存储。空间数据索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询。 81 | 82 | 必须使用 GIS 相关的函数来维护数据。 83 | 84 | ## 索引优化 85 | 86 | ### 1. 独立的列 87 | 88 | 在进行查询时,索引列不能是表达式的一部分,也不能是函数的参数,否则无法使用索引。 89 | 90 | 例如下面的查询不能使用 actor_id 列的索引: 91 | 92 | ```sql 93 | SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5; 94 | ``` 95 | 96 | ### 2. 多列索引 97 | 98 | 在需要使用多个列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。例如下面的语句中,最好把 actor_id 和 film_id 设置为多列索引。 99 | 100 | ```sql 101 | SELECT film_id, actor_ id FROM sakila.film_actor 102 | WHERE actor_id = 1 AND film_id = 1; 103 | ``` 104 | 105 | ### 3. 索引列的顺序 106 | 107 | 让选择性最强的索引列放在前面。 108 | 109 | 索引的选择性是指:不重复的索引值和记录总数的比值。最大值为 1,此时每个记录都有唯一的索引与其对应。选择性越高,查询效率也越高。 110 | 111 | 例如下面显示的结果中 customer_id 的选择性比 staff_id 更高,因此最好把 customer_id 列放在多列索引的前面。 112 | 113 | ```sql 114 | SELECT COUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity, 115 | COUNT(DISTINCT customer_id)/COUNT(*) AS customer_id_selectivity, 116 | COUNT(*) 117 | FROM payment; 118 | ``` 119 | 120 | ```html 121 | staff_id_selectivity: 0.0001 122 | customer_id_selectivity: 0.0373 123 | COUNT(*): 16049 124 | ``` 125 | 126 | ### 4. 前缀索引 127 | 128 | 对于 BLOB、TEXT 和 VARCHAR 类型的列,必须使用前缀索引,只索引开始的部分字符。 129 | 130 | 对于前缀长度的选取需要根据索引选择性来确定。 131 | 132 | ### 5. 覆盖索引 133 | 134 | 索引包含所有需要查询的字段的值。 135 | 136 | 具有以下优点: 137 | 138 | - 索引通常远小于数据行的大小,只读取索引能大大减少数据访问量。 139 | - 一些存储引擎(例如 MyISAM)在内存中只缓存索引,而数据依赖于操作系统来缓存。因此,只访问索引可以不使用系统调用(通常比较费时)。 140 | - 对于 InnoDB 引擎,若辅助索引能够覆盖查询,则无需访问主索引。 141 | 142 | # 3.★★★查询优化 143 | 144 | ## 使用 Explain 进行分析 145 | 146 | Explain 用来分析 SELECT 查询语句,开发人员可以通过分析 Explain 结果来优化查询语句。 147 | 148 | 比较重要的字段有: 149 | 150 | - select_type : 查询类型,有简单查询、联合查询、子查询等 151 | - key : 使用的索引 152 | - rows : 扫描的行数 153 | 154 | ## 优化数据访问 155 | 156 | ### 1. 减少请求的数据量 157 | 158 | - 只返回必要的列:最好不要使用 SELECT * 语句。 159 | - 只返回必要的行:使用 LIMIT 语句来限制返回的数据。 160 | - 缓存重复查询的数据:使用缓存可以避免在数据库中进行查询,特别在要查询的数据经常被重复查询时,缓存带来的查询性能提升将会是非常明显的。 161 | 162 | ### 2. 减少服务器端扫描的行数 163 | 164 | 最有效的方式是使用索引来覆盖查询。 165 | 166 | ## 重构查询方式 167 | 168 | ### 1. 切分大查询 169 | 170 | 一个大查询如果一次性执行的话,可能一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但重要的查询。 171 | 172 | ```sql 173 | DELETE FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH); 174 | ``` 175 | 176 | ```sql 177 | rows_affected = 0 178 | do { 179 | rows_affected = do_query( 180 | "DELETE FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH) LIMIT 10000") 181 | } while rows_affected > 0 182 | ``` 183 | 184 | ### 2. 分解大连接查询 185 | 186 | 将一个大连接查询分解成对每一个表进行一次单表查询,然后在应用程序中进行关联,这样做的好处有: 187 | 188 | - 让缓存更高效。对于连接查询,如果其中一个表发生变化,那么整个查询缓存就无法使用。而分解后的多个查询,即使其中一个表发生变化,对其它表的查询缓存依然可以使用。 189 | - 分解成多个单表查询,这些单表查询的缓存结果更可能被其它查询使用到,从而减少冗余记录的查询。 190 | - 减少锁竞争; 191 | - 在应用层进行连接,可以更容易对数据库进行拆分,从而更容易做到高性能和可伸缩。 192 | - 查询本身效率也可能会有所提升。例如下面的例子中,使用 IN() 代替连接查询,可以让 MySQL 按照 ID 顺序进行查询,这可能比随机的连接要更高效。 193 | 194 | ```sql 195 | SELECT * FROM tab 196 | JOIN tag_post ON tag_post.tag_id=tag.id 197 | JOIN post ON tag_post.post_id=post.id 198 | WHERE tag.tag='mysql'; 199 | ``` 200 | 201 | ```sql 202 | SELECT * FROM tag WHERE tag='mysql'; 203 | SELECT * FROM tag_post WHERE tag_id=1234; 204 | SELECT * FROM post WHERE post.id IN (123,456,567,9098,8904); 205 | ``` 206 | 207 | # 4.★★★InnoDB 与 MyISAM 比较 208 | 209 | - 事务:InnoDB 是事务型的,可以使用 Commit 和 Rollback 语句。 210 | 211 | - 并发:MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。 212 | 213 | - 外键:InnoDB 支持外键。 214 | 215 | - 备份:InnoDB 支持在线热备份。 216 | 217 | - 崩溃恢复:MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。 218 | 219 | - 其它特性:MyISAM 支持压缩表和空间数据索引。 220 | 221 | # 5.★★☆水平切分与垂直切分 222 | 223 | ## 水平切分 224 | 225 | 水平切分又称为 Sharding,它是将同一个表中的记录拆分到多个结构相同的表中。 226 | 227 | 当一个表的数据不断增多时,Sharding 是必然的选择,它可以将数据分布到集群的不同节点上,从而缓存单个数据库的压力。 228 | 229 |

230 | 231 | ## 垂直切分 232 | 233 | 垂直切分是将一张表按列切分成多个表,通常是按照列的关系密集程度进行切分,也可以利用垂直切分将经常被使用的列和不经常被使用的列切分到不同的表中。 234 | 235 | 在数据库的层面使用垂直切分将按数据库中表的密集程度部署到不同的库中,例如将原来的电商数据库垂直切分成商品数据库、用户数据库等。 236 | 237 |

238 | 239 | # 6.★★☆ 主从复制原理、作用、实现 240 | 241 | 主要涉及三个线程:binlog 线程、I/O 线程和 SQL 线程。 242 | 243 | - **binlog 线程** :负责将主服务器上的数据更改写入二进制日志(Binary log)中。 244 | - **I/O 线程** :负责从主服务器上读取二进制日志,并写入从服务器的中继日志(Relay log)。 245 | - **SQL 线程** :负责读取中继日志,解析出主服务器已经执行的数据更改并在从服务器中执行。 246 | 247 |

248 | 249 | # 7.★☆☆ redo、undo、binlog 日志的作用 250 | 251 | **重做日志(redo log)** 252 | 作用: 253 | 确保事务的持久性。 254 | 防止在发生故障的时间点,尚有脏页未写入磁盘,在重启mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性。 255 | 256 | **回滚日志(undo log)** 257 | 258 | 作用: 259 | 保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读。 260 | 261 | **二进制日志(binlog):** 262 | 263 | 作用: 264 | - 用于复制,在主从复制中,从库利用主库上的binlog进行重播,实现主从同步。 265 | - 用于数据库的基于时间点的还原。 266 | -------------------------------------------------------------------------------- /数据库/README.md: -------------------------------------------------------------------------------- 1 | ## 💾 数据库 2 | 3 | > 本节部分知识点来自《数据库系统概论(第 5 版)》 4 | 5 | ### 基本概念 6 | 7 | * 数据(data):描述事物的符号记录称为数据。 8 | * 数据库(DataBase,DB):是长期存储在计算机内、有组织的、可共享的大量数据的集合,具有永久存储、有组织、可共享三个基本特点。 9 | * 数据库管理系统(DataBase Management System,DBMS):是位于用户与操作系统之间的一层数据管理软件。 10 | * 数据库系统(DataBase System,DBS):是有数据库、数据库管理系统(及其应用开发工具)、应用程序和数据库管理员(DataBase Administrator DBA)组成的存储、管理、处理和维护数据的系统。 11 | * 实体(entity):客观存在并可相互区别的事物称为实体。 12 | * 属性(attribute):实体所具有的某一特性称为属性。 13 | * 码(key):唯一标识实体的属性集称为码。 14 | * 实体型(entity type):用实体名及其属性名集合来抽象和刻画同类实体,称为实体型。 15 | * 实体集(entity set):同一实体型的集合称为实体集。 16 | * 联系(relationship):实体之间的联系通常是指不同实体集之间的联系。 17 | * 模式(schema):模式也称逻辑模式,是数据库全体数据的逻辑结构和特征的描述,是所有用户的公共数据视图。 18 | * 外模式(external schema):外模式也称子模式(subschema)或用户模式,它是数据库用户(包括应用程序员和最终用户)能够看见和使用的局部数据的逻辑结构和特征的描述,是数据库用户的数据视图,是与某一应用有关的数据的逻辑表示。 19 | * 内模式(internal schema):内模式也称为存储模式(storage schema),一个数据库只有一个内模式。他是数据物理结构和存储方式的描述,是数据库在数据库内部的组织方式。 20 | 21 | ### 常用数据模型 22 | 23 | * 层次模型(hierarchical model) 24 | * 网状模型(network model) 25 | * 关系模型(relational model) 26 | * 关系(relation):一个关系对应通常说的一张表 27 | * 元组(tuple):表中的一行即为一个元组 28 | * 属性(attribute):表中的一列即为一个属性 29 | * 码(key):表中可以唯一确定一个元组的某个属性组 30 | * 域(domain):一组具有相同数据类型的值的集合 31 | * 分量:元组中的一个属性值 32 | * 关系模式:对关系的描述,一般表示为 `关系名(属性1, 属性2, ..., 属性n)` 33 | * 面向对象数据模型(object oriented data model) 34 | * 对象关系数据模型(object relational data model) 35 | * 半结构化数据模型(semistructure data model) 36 | 37 | ### 常用 SQL 操作 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
对象类型对象操作类型
数据库模式模式CREATE SCHEMA
基本表CREATE SCHEMAALTER TABLE
视图CREATE VIEW
索引CREATE INDEX
数据基本表和视图SELECTINSERTUPDATEDELETEREFERENCESALL PRIVILEGES
属性列SELECTINSERTUPDATEREFERENCESALL PRIVILEGES
72 | 73 | > SQL 语法教程:[runoob . SQL 教程](http://www.runoob.com/sql/sql-tutorial.html) 74 | 75 | ### 关系型数据库 76 | 77 | * 基本关系操作:查询(选择、投影、连接(等值连接、自然连接、外连接(左外连接、右外连接))、除、并、差、交、笛卡尔积等)、插入、删除、修改 78 | * 关系模型中的三类完整性约束:实体完整性、参照完整性、用户定义的完整性 79 | 80 | #### 索引 81 | 82 | * 数据库索引:顺序索引、B+ 树索引、hash 索引 83 | * [MySQL 索引背后的数据结构及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.html) 84 | 85 | ### 数据库完整性 86 | 87 | * 数据库的完整性是指数据的正确性和相容性。 88 | * 完整性:为了防止数据库中存在不符合语义(不正确)的数据。 89 | * 安全性:为了保护数据库防止恶意破坏和非法存取。 90 | * 触发器:是用户定义在关系表中的一类由事件驱动的特殊过程。 91 | 92 | ### 关系数据理论 93 | 94 | * 数据依赖是一个关系内部属性与属性之间的一种约束关系,是通过属性间值的相等与否体现出来的数据间相关联系。 95 | * 最重要的数据依赖:函数依赖、多值依赖。 96 | 97 | #### 范式 98 | 99 | * 第一范式(1NF):属性(字段)是最小单位不可再分。 100 | * 第二范式(2NF):满足 1NF,每个非主属性完全依赖于主键(消除 1NF 非主属性对码的部分函数依赖)。 101 | * 第三范式(3NF):满足 2NF,任何非主属性不依赖于其他非主属性(消除 2NF 非主属性对码的传递函数依赖)。 102 | * 鲍依斯-科得范式(BCNF):满足 3NF,任何非主属性不能对主键子集依赖(消除 3NF 主属性对码的部分和传递函数依赖)。 103 | * 第四范式(4NF):满足 3NF,属性之间不能有非平凡且非函数依赖的多值依赖(消除 3NF 非平凡且非函数依赖的多值依赖)。 104 | 105 | ### 数据库恢复 106 | 107 | * 事务:是用户定义的一个数据库操作序列,这些操作要么全做,要么全不做,是一个不可分割的工作单位。 108 | * 事物的 ACID 特性:原子性、一致性、隔离性、持续性。 109 | * 恢复的实现技术:建立冗余数据 -> 利用冗余数据实施数据库恢复。 110 | * 建立冗余数据常用技术:数据转储(动态海量转储、动态增量转储、静态海量转储、静态增量转储)、登记日志文件。 111 | 112 | ### 并发控制 113 | 114 | * 事务是并发控制的基本单位。 115 | * 并发操作带来的数据不一致性包括:丢失修改、不可重复读、读 “脏” 数据。 116 | * 并发控制主要技术:封锁、时间戳、乐观控制法、多版本并发控制等。 117 | * 基本封锁类型:排他锁(X 锁 / 写锁)、共享锁(S 锁 / 读锁)。 118 | * 活锁死锁: 119 | * 活锁:事务永远处于等待状态,可通过先来先服务的策略避免。 120 | * 死锁:事务永远不能结束 121 | * 预防:一次封锁法、顺序封锁法; 122 | * 诊断:超时法、等待图法; 123 | * 解除:撤销处理死锁代价最小的事务,并释放此事务的所有的锁,使其他事务得以继续运行下去。 124 | * 可串行化调度:多个事务的并发执行是正确的,当且仅当其结果与按某一次序串行地执行这些事务时的结果相同。可串行性时并发事务正确调度的准则。 125 | 126 | 127 | -------------------------------------------------------------------------------- /数据库/Redis问题合集.md: -------------------------------------------------------------------------------- 1 | # 1.★★☆ 字典和跳跃表原理分析 2 | 3 | ## 字典 4 | 5 | dictht 是一个散列表结构,使用拉链法保存哈希冲突。 6 | 7 | ```c 8 | /* This is our hash table structure. Every dictionary has two of this as we 9 | * implement incremental rehashing, for the old to the new table. */ 10 | typedef struct dictht { 11 | dictEntry **table; 12 | unsigned long size; 13 | unsigned long sizemask; 14 | unsigned long used; 15 | } dictht; 16 | ``` 17 | 18 | ```c 19 | typedef struct dictEntry { 20 | void *key; 21 | union { 22 | void *val; 23 | uint64_t u64; 24 | int64_t s64; 25 | double d; 26 | } v; 27 | struct dictEntry *next; 28 | } dictEntry; 29 | ``` 30 | 31 | Redis 的字典 dict 中包含两个哈希表 dictht,这是为了方便进行 rehash 操作。在扩容时,将其中一个 dictht 上的键值对 rehash 到另一个 dictht 上面,完成之后释放空间并交换两个 dictht 的角色。 32 | 33 | ```c 34 | typedef struct dict { 35 | dictType *type; 36 | void *privdata; 37 | dictht ht[2]; 38 | long rehashidx; /* rehashing not in progress if rehashidx == -1 */ 39 | unsigned long iterators; /* number of iterators currently running */ 40 | } dict; 41 | ``` 42 | 43 | rehash 操作不是一次性完成,而是采用渐进方式,这是为了避免一次性执行过多的 rehash 操作给服务器带来过大的负担。 44 | 45 | 渐进式 rehash 通过记录 dict 的 rehashidx 完成,它从 0 开始,然后每执行一次 rehash 都会递增。例如在一次 rehash 中,要把 dict[0] rehash 到 dict[1],这一次会把 dict[0] 上 table[rehashidx] 的键值对 rehash 到 dict[1] 上,dict[0] 的 table[rehashidx] 指向 null,并令 rehashidx++。 46 | 47 | 在 rehash 期间,每次对字典执行添加、删除、查找或者更新操作时,都会执行一次渐进式 rehash。 48 | 49 | 采用渐进式 rehash 会导致字典中的数据分散在两个 dictht 上,因此对字典的查找操作也需要到对应的 dictht 去执行。 50 | 51 | ```c 52 | /* Performs N steps of incremental rehashing. Returns 1 if there are still 53 | * keys to move from the old to the new hash table, otherwise 0 is returned. 54 | * 55 | * Note that a rehashing step consists in moving a bucket (that may have more 56 | * than one key as we use chaining) from the old to the new hash table, however 57 | * since part of the hash table may be composed of empty spaces, it is not 58 | * guaranteed that this function will rehash even a single bucket, since it 59 | * will visit at max N*10 empty buckets in total, otherwise the amount of 60 | * work it does would be unbound and the function may block for a long time. */ 61 | int dictRehash(dict *d, int n) { 62 | int empty_visits = n * 10; /* Max number of empty buckets to visit. */ 63 | if (!dictIsRehashing(d)) return 0; 64 | 65 | while (n-- && d->ht[0].used != 0) { 66 | dictEntry *de, *nextde; 67 | 68 | /* Note that rehashidx can't overflow as we are sure there are more 69 | * elements because ht[0].used != 0 */ 70 | assert(d->ht[0].size > (unsigned long) d->rehashidx); 71 | while (d->ht[0].table[d->rehashidx] == NULL) { 72 | d->rehashidx++; 73 | if (--empty_visits == 0) return 1; 74 | } 75 | de = d->ht[0].table[d->rehashidx]; 76 | /* Move all the keys in this bucket from the old to the new hash HT */ 77 | while (de) { 78 | uint64_t h; 79 | 80 | nextde = de->next; 81 | /* Get the index in the new hash table */ 82 | h = dictHashKey(d, de->key) & d->ht[1].sizemask; 83 | de->next = d->ht[1].table[h]; 84 | d->ht[1].table[h] = de; 85 | d->ht[0].used--; 86 | d->ht[1].used++; 87 | de = nextde; 88 | } 89 | d->ht[0].table[d->rehashidx] = NULL; 90 | d->rehashidx++; 91 | } 92 | 93 | /* Check if we already rehashed the whole table... */ 94 | if (d->ht[0].used == 0) { 95 | zfree(d->ht[0].table); 96 | d->ht[0] = d->ht[1]; 97 | _dictReset(&d->ht[1]); 98 | d->rehashidx = -1; 99 | return 0; 100 | } 101 | 102 | /* More to rehash... */ 103 | return 1; 104 | } 105 | ``` 106 | 107 | ## 跳跃表 108 | 109 | 是有序集合的底层实现之一。 110 | 111 | 跳跃表是基于多指针有序链表实现的,可以看成多个有序链表。 112 | 113 |

114 | 115 | 在查找时,从上层指针开始查找,找到对应的区间之后再到下一层去查找。下图演示了查找 22 的过程。 116 | 117 |

118 | 119 | 与红黑树等平衡树相比,跳跃表具有以下优点: 120 | 121 | - 插入速度非常快速,因为不需要进行旋转等操作来维护平衡性; 122 | - 更容易实现; 123 | - 支持无锁操作。 124 | 125 | # 2.★★★ 使用场景 126 | 127 | ## 计数器 128 | 129 | 可以对 String 进行自增自减运算,从而实现计数器功能。 130 | 131 | Redis 这种内存型数据库的读写性能非常高,很适合存储频繁读写的计数量。 132 | 133 | ## 缓存 134 | 135 | 将热点数据放到内存中,设置内存的最大使用量以及淘汰策略来保证缓存的命中率。 136 | 137 | ## 查找表 138 | 139 | 例如 DNS 记录就很适合使用 Redis 进行存储。 140 | 141 | 查找表和缓存类似,也是利用了 Redis 快速的查找特性。但是查找表的内容不能失效,而缓存的内容可以失效,因为缓存不作为可靠的数据来源。 142 | 143 | ## 消息队列 144 | 145 | List 是一个双向链表,可以通过 lpop 和 lpush 写入和读取消息。 146 | 147 | 不过最好使用 Kafka、RabbitMQ 等消息中间件。 148 | 149 | ## 会话缓存 150 | 151 | 可以使用 Redis 来统一存储多台应用服务器的会话信息。 152 | 153 | 当应用服务器不再存储用户的会话信息,也就不再具有状态,一个用户可以请求任意一个应用服务器,从而更容易实现高可用性以及可伸缩性。 154 | 155 | ## 分布式锁实现 156 | 157 | 在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。 158 | 159 | 可以使用 Redis 自带的 SETNX 命令实现分布式锁,除此之外,还可以使用官方提供的 RedLock 分布式锁实现。 160 | 161 | ## 其它 162 | 163 | Set 可以实现交集、并集等操作,从而实现共同好友等功能。 164 | 165 | ZSet 可以实现有序性操作,从而实现排行榜等功能。 166 | 167 | # 3.★★★ 与 Memchached 的比较 168 | 169 | 两者都是非关系型内存键值数据库,主要有以下不同: 170 | 171 | ## 数据类型 172 | 173 | Memcached 仅支持字符串类型,而 Redis 支持五种不同的数据类型,可以更灵活地解决问题。 174 | 175 | ## 数据持久化 176 | 177 | Redis 支持两种持久化策略:RDB 快照和 AOF 日志,而 Memcached 不支持持久化。 178 | 179 | ## 分布式 180 | 181 | Memcached 不支持分布式,只能通过在客户端使用一致性哈希来实现分布式存储,这种方式在存储和查询时都需要先在客户端计算一次数据所在的节点。 182 | 183 | Redis Cluster 实现了分布式的支持。 184 | 185 | ## 内存管理机制 186 | 187 | - 在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘,而 Memcached 的数据则会一直在内存中。 188 | 189 | - Memcached 将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题。但是这种方式会使得内存的利用率不高,例如块的大小为 128 bytes,只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了。 190 | 191 | # 4.★☆☆ 数据淘汰机制 192 | 193 | 可以设置内存最大使用量,当内存使用量超出时,会施行数据淘汰策略。 194 | 195 | Redis 具体有 6 种淘汰策略: 196 | 197 | | 策略 | 描述 | 198 | | :--: | :--: | 199 | | volatile-lru | 从已设置过期时间的数据集中挑选最近最少使用的数据淘汰 | 200 | | volatile-ttl | 从已设置过期时间的数据集中挑选将要过期的数据淘汰 | 201 | |volatile-random | 从已设置过期时间的数据集中任意选择数据淘汰 | 202 | | allkeys-lru | 从所有数据集中挑选最近最少使用的数据淘汰 | 203 | | allkeys-random | 从所有数据集中任意选择数据进行淘汰 | 204 | | noeviction | 禁止驱逐数据 | 205 | 206 | 作为内存数据库,出于对性能和内存消耗的考虑,Redis 的淘汰算法实际实现上并非针对所有 key,而是抽样一小部分并且从中选出被淘汰的 key。 207 | 208 | 使用 Redis 缓存数据时,为了提高缓存命中率,需要保证缓存数据都是热点数据。可以将内存最大使用量设置为热点数据占用的内存量,然后启用 allkeys-lru 淘汰策略,将最近最少使用的数据淘汰。 209 | 210 | Redis 4.0 引入了 volatile-lfu 和 allkeys-lfu 淘汰策略,LFU 策略通过统计访问频率,将访问频率最少的键值对淘汰。 211 | 212 | # 5.★★☆ RDB 和 AOF 持久化机制 213 | 214 | Redis 是内存型数据库,为了保证数据在断电后不会丢失,需要将内存中的数据持久化到硬盘上。 215 | 216 | ## RDB 持久化 217 | 218 | 将某个时间点的所有数据都存放到硬盘上。 219 | 220 | 可以将快照复制到其它服务器从而创建具有相同数据的服务器副本。 221 | 222 | 如果系统发生故障,将会丢失最后一次创建快照之后的数据。 223 | 224 | 如果数据量很大,保存快照的时间会很长。 225 | 226 | ## AOF 持久化 227 | 228 | 将写命令添加到 AOF 文件(Append Only File)的末尾。 229 | 230 | 使用 AOF 持久化需要设置同步选项,从而确保写命令什么时候会同步到磁盘文件上。这是因为对文件进行写入并不会马上将内容同步到磁盘上,而是先存储到缓冲区,然后由操作系统决定什么时候同步到磁盘。有以下同步选项: 231 | 232 | | 选项 | 同步频率 | 233 | | :--: | :--: | 234 | | always | 每个写命令都同步 | 235 | | everysec | 每秒同步一次 | 236 | | no | 让操作系统来决定何时同步 | 237 | 238 | - always 选项会严重减低服务器的性能; 239 | - everysec 选项比较合适,可以保证系统崩溃时只会丢失一秒左右的数据,并且 Redis 每秒执行一次同步对服务器性能几乎没有任何影响; 240 | - no 选项并不能给服务器性能带来多大的提升,而且也会增加系统崩溃时数据丢失的数量。 241 | 242 | 随着服务器写请求的增多,AOF 文件会越来越大。Redis 提供了一种将 AOF 重写的特性,能够去除 AOF 文件中的冗余写命令。 243 | 244 | # 6.★★☆ 事件驱动模型 245 | 246 | Redis是一个事件驱动的内存数据库,服务器需要处理两类时间 247 | 248 | ## 文件事件 249 | 250 | 服务器通过Socket套接字与客户端或者其它Redis服务器进行通信,文件事件就是对Socket套接字操作的抽象。 251 | 252 | Redis 基于 Reactor 模式开发了自己的网络事件处理器,使用 I/O 多路复用程序来同时监听多个套接字,并将到达的事件传送给文件事件分派器,分派器会根据套接字产生的事件类型调用相应的事件处理器。 253 | 254 |

255 | 256 | ## 时间事件 257 | 258 | 服务器有一些操作需要在给定的时间点执行,时间事件是对这类定时操作的抽象。 259 | 260 | 时间事件又分为: 261 | 262 | - 定时事件:是让一段程序在指定的时间之内执行一次; 263 | - 周期性事件:是让一段程序每隔指定时间就执行一次。 264 | 265 | Redis 将所有时间事件都放在一个无序链表中,通过遍历整个链表查找出已到达的时间事件,并调用相应的事件处理器。 266 | 267 | # 7.★☆☆ 主从复制原理 268 | 269 | 太难了,没看懂 270 | 271 | # 8.★★★ 集群与分布式 272 | 273 | 见OneNote 274 | 275 | # 9.★★☆ 事务原理 276 | 277 | 一个事务包含了多个命令,服务器在执行事务期间,不会改去执行其它客户端的命令请求。 278 | 279 | 事务中的多个命令被一次性发送给服务器,而不是一条一条发送,这种方式被称为流水线,它可以减少客户端与服务器之间的网络通信次数从而提升性能。 280 | 281 | Redis 最简单的事务实现方式是使用 MULTI 和 EXEC 命令将事务操作包围起来。 282 | 283 | Redis是通过WATCH命令,来保证当前事务的数据是否被修改过,如果被修改了,则整个事务会中止,不再执行。那么,Redis在实现的时候,会保存对应的watch key,然后中途如果该Key被修改了,则会将对应的所有客户端的标志位都置为CLIENT_DIRTY_CAS,表示数据被修改,后续执行EXEC的时候则会被中断,从而实现事务。而UNWATCH命令则是从保存的watch_keys里面移除。MULTI命令仅仅将客户端的标志位flags置为CLIENT_MULTI,表示处于MULTI状态,该状态下,后续的命令(除了MULTI/WATCH/DISCARD/EXEC)外,其它命令都会被保存到一个列表里面,直到EXEC或者DISCARD命令执行。如果中途出现了语法错误之类的命令,则会将flags置为CLIENT_DIRTY_EXEC。后续执行EXEC时,如果flags存在CLIENT_DIRTY_CAS或者CLIENT_DIRTY_EXEC,则整个事务会被中止,不执行任何命令。 284 | 285 | # 10.★★★ 线程安全问题 286 | 287 | redis实际上是采用了线程封闭的观念,把任务封闭在一个线程,自然避免了线程安全问题,不过对于需要依赖多个redis操作的复合操作来说,依然需要锁,而且有可能是分布式锁。 288 | -------------------------------------------------------------------------------- /数据库/SQL问题合集.md: -------------------------------------------------------------------------------- 1 | # 1.★★☆ 手写 SQL 语句,特别是连接查询与分组查询 2 | 3 | 复习sql 4 | 5 | # 2.★★☆ 连接查询与子查询的比较 6 | 7 | **连接查询**是将两个或多个的表按某个条件连接起来,从中选取需要的数据,连接查询是同时查询两个或两个以上的表的使用的。当不同的表中存在相同意义的字段时,可以通过该字段来连接这几个表。 8 | 9 | SQL 的四种连接查询 10 | 11 | 内连接 12 | inner join 或者 join 13 | 14 | 外连接 15 | 1. 左连接 left join 或者 left outer join 16 | 17 | 2. 右连接 right join 或者 right outer join 18 | 19 | 3. 完全外连接 full join 或者 full outer join 20 | 21 | **子查询**是将一个查询语句嵌套在另外一个查询语句中,内层查询语句的查询结果,可以为外层查询语句提供查询条件。 22 | 23 | 24 | # 3.★★☆ drop、delete、truncate 比较 25 | 26 | ## I.作用 27 | DELETE 删除表中 WHERE 语句指定的数据。 28 | 29 |

30 | 31 | TRUNCATE 清空表,相当于删除表中的所有数据。 32 | 33 |

34 | 35 | DROP 删除表结构。 36 | 37 |

38 | 39 | ## II.事务 40 | 41 | - DELETE 会被放到日志中以便进行回滚; 42 | - TRUNCATE 和 DROP 立即生效,不会放到日志中,也就不支持回滚。 43 | 44 | ## III.删除空间 45 | 46 | - DELETE 不会减少表和索引占用的空间; 47 | - TRUNCATE 会将表和索引占用的空间恢复到初始值; 48 | - DROP 会将表和索引占用的空间释放。 49 | 50 | ## IV.耗时 51 | 52 | 通常来说,DELETE < TRUNCATE < DROP。 53 | 54 | # 4.★★☆ 视图的作用,以及何时能更新视图 55 | 56 | 视图是虚拟的表,本身不包含数据,也就不能对其进行索引操作。 57 | 58 | 对视图的操作和对普通表的操作一样。 59 | 60 | 视图具有如下好处: 61 | 62 | - 简化复杂的 SQL 操作,比如复杂的连接; 63 | - 只使用实际表的一部分数据; 64 | - 通过只给用户访问视图的权限,保证数据的安全性; 65 | - 更改数据格式和表示。 66 | 67 | ```sql 68 | CREATE VIEW myview AS 69 | SELECT Concat(col1, col2) AS concat_col, col3*col4 AS compute_col 70 | FROM mytable 71 | WHERE col5 = val; 72 | ``` 73 | 74 | # 5.★☆☆ 理解存储过程、触发器等作用 75 | 76 | **存储过程**可以看成是对一系列 SQL 操作的批处理。 77 | 78 | 使用存储过程的好处: 79 | 80 | - 代码封装,保证了一定的安全性; 81 | - 代码复用; 82 | - 由于是预先编译,因此具有很高的性能。 83 | 84 | 命令行中创建存储过程需要自定义分隔符,因为命令行是以 ; 为结束符,而存储过程中也包含了分号,因此会错误把这部分分号当成是结束符,造成语法错误。 85 | 86 | 包含 in、out 和 inout 三种参数。 87 | 88 | 给变量赋值都需要用 select into 语句。 89 | 90 | 每次只能给一个变量赋值,不支持集合的操作。 91 | 92 | ```sql 93 | delimiter // 94 | 95 | create procedure myprocedure( out ret int ) 96 | begin 97 | declare y int; 98 | select sum(col1) 99 | from mytable 100 | into y; 101 | select y*y into ret; 102 | end // 103 | 104 | delimiter ; 105 | ``` 106 | 107 | ```sql 108 | call myprocedure(@ret); 109 | select @ret; 110 | ``` 111 | 112 | **触发器**会在某个表执行以下语句时而自动执行:DELETE、INSERT、UPDATE。 113 | 114 | 触发器必须指定在语句执行之前还是之后自动执行,之前执行使用 BEFORE 关键字,之后执行使用 AFTER 关键字。BEFORE 用于数据验证和净化,AFTER 用于审计跟踪,将修改记录到另外一张表中。 115 | 116 | INSERT 触发器包含一个名为 NEW 的虚拟表。 117 | 118 | ```sql 119 | CREATE TRIGGER mytrigger AFTER INSERT ON mytable 120 | FOR EACH ROW SELECT NEW.col into @result; 121 | 122 | SELECT @result; -- 获取结果 123 | ``` 124 | 125 | DELETE 触发器包含一个名为 OLD 的虚拟表,并且是只读的。 126 | 127 | UPDATE 触发器包含一个名为 NEW 和一个名为 OLD 的虚拟表,其中 NEW 是可以被修改的,而 OLD 是只读的。 128 | 129 | MySQL 不允许在触发器中使用 CALL 语句,也就是不能调用存储过程。 130 | 131 | -------------------------------------------------------------------------------- /数据库/数据库系统原理.md: -------------------------------------------------------------------------------- 1 | # 1.★★★ ACID 的作用以及实现原理 2 | 3 | ## ACID 4 | 5 | ### 1. 原子性(Atomicity) 6 | 7 | 事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。 8 | 9 | 回滚可以用回滚日志来实现,回滚日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。 10 | 11 | ### 实现原理:undo log 12 | 13 | 实现原子性的关键,是当事务回滚时能够撤销所有已经成功执行的sql语句。InnoDB实现回滚,靠的是undo log:当事务对数据库进行修改时,InnoDB会生成对应的undo log;如果事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。 14 | 15 | ### 2. 一致性(Consistency) 16 | 17 | 数据库在事务执行前后都保持一致性状态。在一致性状态下,所有事务对一个数据的读取结果都是相同的。 18 | 19 | ### 实现原理: 20 | 21 | 原子性、持久性和隔离性,都是为了保证数据库状态的一致性,实现一致性的措施包括: 22 | 23 | - 保证原子性、持久性和隔离性,如果这些特性无法保证,事务的一致性也无法保证 24 | - 数据库本身提供保障,例如不允许向整形列插入字符串值、字符串长度不能超过列的限制等 25 | - 应用层面进行保障,例如如果转账操作只扣除转账者的余额,而没有增加接收者的余额,无论数据库实现的多么完美,也无法保证状态的一致 26 | 27 | ### 3. 隔离性(Isolation) 28 | 29 | 一个事务所做的修改在最终提交以前,对其它事务是不可见的。 30 | 31 | ### 实现原理:锁机制 32 | 33 | ### 4. 持久性(Durability) 34 | 35 | 一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。 36 | 37 | 使用重做日志来保证持久性。 38 | 39 | ### 实现原理:redo log 40 | 41 | edo log和undo log都属于InnoDB的事务日志。下面先聊一下redo log存在的背景。 42 | 43 | InnoDB作为MySQL的存储引擎,数据是存放在磁盘中的,但如果每次读写数据都需要磁盘IO,效率会很低。为此,InnoDB提供了缓存(Buffer Pool),Buffer Pool中包含了磁盘中部分数据页的映射,作为访问数据库的缓冲:当从数据库读取数据时,会首先从Buffer Pool中读取,如果Buffer Pool中没有,则从磁盘读取后放入Buffer Pool;当向数据库写入数据时,会首先写入Buffer Pool,Buffer Pool中修改的数据会定期刷新到磁盘中(这一过程称为刷脏)。 44 | 45 | Buffer Pool的使用大大提高了读写数据的效率,但是也带了新的问题:如果MySQL宕机,而此时Buffer Pool中修改的数据还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证。 46 | 47 | 于是,redo log被引入来解决这个问题:当数据修改时,除了修改Buffer Pool中的数据,还会在redo log记录这次操作;当事务提交时,会调用fsync接口对redo log进行刷盘。如果MySQL宕机,重启时可以读取redo log中的数据,对数据库进行恢复。redo log采用的是WAL(Write-ahead logging,预写式日志),所有修改先写入日志,再更新到Buffer Pool,保证了数据不会因MySQL宕机而丢失,从而满足了持久性要求。 48 | 49 | 既然redo log也需要在事务提交时将日志写入磁盘,为什么它比直接将Buffer Pool中修改的数据写入磁盘(即刷脏)要快呢?主要有以下两方面的原因: 50 | 51 | (1)刷脏是随机IO,因为每次修改的数据位置随机,但写redo log是追加操作,属于顺序IO。 52 | 53 | (2)刷脏是以数据页(Page)为单位的,MySQL默认页大小是16KB,一个Page上一个小修改都要整页写入;而redo log中只包含真正需要写入的部分,无效IO大大减少。 54 | 55 | ---- 56 | 57 | 事务的 ACID 特性概念简单,但不是很好理解,主要是因为这几个特性不是一种平级关系: 58 | 59 | - 只有满足一致性,事务的执行结果才是正确的。 60 | - 在无并发的情况下,事务串行执行,隔离性一定能够满足。此时只要能满足原子性,就一定能满足一致性。 61 | - 在并发的情况下,多个事务并行执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性。 62 | - 事务满足持久化是为了能应对数据库崩溃的情况。 63 | 64 |

65 | 66 | # 2.★★★ 四大隔离级别,以及不可重复读和幻影读的出现原因 67 | 68 | ## 未提交读(READ UNCOMMITTED) 69 | 70 | 事务中的修改,即使没有提交,对其它事务也是可见的。 71 | 72 | ## 提交读(READ COMMITTED) 73 | 74 | 一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。 75 | 76 | ## 可重复读(REPEATABLE READ) 77 | 78 | 保证在同一个事务中多次读取同样数据的结果是一样的。 79 | 80 | ## 可串行化(SERIALIZABLE) 81 | 82 | 强制事务串行执行。 83 | 84 | ---- 85 | 86 | | 隔离级别 | 脏读 | 不可重复读 | 幻影读 | 加锁读 | 87 | | :---: | :---: | :---:| :---: | :---: | 88 | | 未提交读 | √ | √ | √ | × | 89 | | 提交读 | × | √ | √ | × | 90 | | 可重复读 | × | × | √ | × | 91 | | 可串行化 | × | × | × | √ | 92 | 93 | 94 | # 3.★★☆ 封锁的类型以及粒度,两段锁协议,隐式和显示锁定 95 | 96 | ### 1. 读写锁 97 | 98 | - 排它锁(Exclusive),简写为 X 锁,又称写锁。 99 | - 共享锁(Shared),简写为 S 锁,又称读锁。 100 | 101 | 有以下两个规定: 102 | 103 | - 一个事务对数据对象 A 加了 X 锁,就可以对 A 进行读取和更新。加锁期间其它事务不能对 A 加任何锁。 104 | - 一个事务对数据对象 A 加了 S 锁,可以对 A 进行读取操作,但是不能进行更新操作。加锁期间其它事务能对 A 加 S 锁,但是不能加 X 锁。 105 | 106 | 锁的兼容关系如下: 107 | 108 | | - | X | S | 109 | | :--: | :--: | :--: | 110 | |X|×|×| 111 | |S|×|√| 112 | 113 | ### 2. 意向锁 114 | 115 | 使用意向锁(Intention Locks)可以更容易地支持多粒度封锁。 116 | 117 | 在存在行级锁和表级锁的情况下,事务 T 想要对表 A 加 X 锁,就需要先检测是否有其它事务对表 A 或者表 A 中的任意一行加了锁,那么就需要对表 A 的每一行都检测一次,这是非常耗时的。 118 | 119 | 意向锁在原来的 X/S 锁之上引入了 IX/IS,IX/IS 都是表锁,用来表示一个事务想要在表中的某个数据行上加 X 锁或 S 锁。有以下两个规定: 120 | 121 | - 一个事务在获得某个数据行对象的 S 锁之前,必须先获得表的 IS 锁或者更强的锁; 122 | - 一个事务在获得某个数据行对象的 X 锁之前,必须先获得表的 IX 锁。 123 | 124 | 通过引入意向锁,事务 T 想要对表 A 加 X 锁,只需要先检测是否有其它事务对表 A 加了 X/IX/S/IS 锁,如果加了就表示有其它事务正在使用这个表或者表中某一行的锁,因此事务 T 加 X 锁失败。 125 | 126 | 各种锁的兼容关系如下: 127 | 128 | | - | X | IX | S | IS | 129 | | :--: | :--: | :--: | :--: | :--: | 130 | |X |× |× |× | ×| 131 | |IX |× |√ |× | √| 132 | |S |× |× |√ | √| 133 | |IS |× |√ |√ | √| 134 | 135 | 解释如下: 136 | 137 | - 任意 IS/IX 锁之间都是兼容的,因为它们只是表示想要对表加锁,而不是真正加锁; 138 | - S 锁只与 S 锁和 IS 锁兼容,也就是说事务 T 想要对数据行加 S 锁,其它事务可以已经获得对表或者表中的行的 S 锁。 139 | 140 | ### 两段锁协议 141 | 142 | 是指所有的事务必须分两个阶段对数据项加锁和解锁。即事务分两个阶段,第一个阶段是获得封锁。事务可以获得任何数据项上的任何类型的锁,但是不能释放;第二阶段是释放封锁,事务可以释放任何数据项上的任何类型的锁,但不能申请。 143 | 144 | 第一阶段是获得封锁的阶段,称为扩展阶段:其实也就是该阶段可以进入加锁操作,在对任何数据进行读操作之前要申请获得S锁,在进行写操作之前要申请并获得X锁,加锁不成功,则事务进入等待状态,直到加锁成功才继续执行。就是加锁后就不能解锁了。 145 | 146 | 第二阶段是释放封锁的阶段,称为收缩阶段:当事务释放一个封锁后,事务进入封锁阶段,在该阶段只能进行解锁而不能再进行加锁操作。 147 | 148 | ### 隐式和显示锁定 149 | 150 | MySQL的InnoDB存储引擎采用两段锁协议,会根据隔离级别在需要的时候自动加锁,并且所有的锁都是在同一时刻被释放,这被称为隐式锁定。 151 | 152 | InnoDB 也可以使用特定的语句进行显示锁定: 153 | 154 | ``` 155 | SELECT ... LOCK In SHARE MODE; 156 | SELECT ... FOR UPDATE; \\显示 157 | ``` 158 | 159 | # 4.★★★乐观锁与悲观锁 160 | 161 | ## 乐观锁 162 | 163 | 乐观锁不是数据库自带的,需要我们自己去实现。乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。 164 | 165 | 通常实现是这样的:在表中的数据进行操作时(更新),先给数据表加一个版本(version)字段,每操作一次,将那条记录的版本号加1。也就是先查询出那条记录,获取出version字段,如果要对那条记录进行操作(更新),则先判断此刻version的值是否与刚刚查询出来时的version的值相等,如果相等,则说明这段期间,没有其他程序对其进行操作,则可以执行更新,将version字段的值加1;如果更新时发现此刻的version值与刚刚获取出来的version的值不相等,则说明这段期间已经有其他程序对其进行操作了,则不进行更新操作。 166 | 167 | ## 悲观锁 168 | 169 | 与乐观锁相对应的就是悲观锁了。悲观锁就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作,这点跟java中的synchronized很相似,所以悲观锁需要耗费较多的时间。另外与乐观锁相对应的,悲观锁是由数据库自己实现了的,要用的时候,我们直接调用数据库的相关语句就可以了。 170 | 171 | 说到这里,由悲观锁涉及到的另外两个锁概念就出来了, 172 | 173 | 它们就是共享锁与排它锁。共享锁和排它锁是悲观锁的不同的实现,它俩都属于悲观锁的范畴 174 | 175 | # 5.★★★MVCC 原理,当前读以及快照读,Next-Key Locks 解决幻影读 176 | 177 | ### 1. MVCC原理 178 | 179 | 多版本并发控制(Multi-Version Concurrency Control, MVCC)是 MySQL 的 InnoDB 存储引擎实现隔离级别的一种具体方式,用于实现提交读和可重复读这两种隔离级别。而未提交读隔离级别总是读取最新的数据行,无需使用 MVCC。可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。 180 | 181 | ### 2. 快照读 182 | 183 | 使用 MVCC 读取的是快照中的数据,这样可以减少加锁所带来的开销。 184 | 185 | ```sql 186 | select * from table ...; 187 | ``` 188 | 189 | ### 3. 当前读 190 | 191 | 读取的是最新的数据,需要加锁。以下第一个语句需要加 S 锁,其它都需要加 X 锁。 192 | 193 | ```sql 194 | select * from table where ? lock in share mode; 195 | select * from table where ? for update; 196 | insert; 197 | update; 198 | delete; 199 | ``` 200 | 201 | Next-Key Locks 是 MySQL 的 InnoDB 存储引擎的一种锁实现。 202 | 203 | MVCC 不能解决幻读的问题,Next-Key Locks 就是为了解决这个问题而存在的。在可重复读(REPEATABLE READ)隔离级别下,使用 MVCC + Next-Key Locks 可以解决幻读问题。 204 | 205 | ## Record Locks 206 | 207 | 锁定一个记录上的索引,而不是记录本身。 208 | 209 | 如果表没有设置索引,InnoDB 会自动在主键上创建隐藏的聚簇索引,因此 Record Locks 依然可以使用。 210 | 211 | ## Gap Locks 212 | 213 | 锁定索引之间的间隙,但是不包含索引本身。例如当一个事务执行以下语句,其它事务就不能在 t.c 中插入 15。 214 | 215 | ```sql 216 | SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE; 217 | ``` 218 | 219 | ## Next-Key Locks 220 | 221 | 它是 Record Locks 和 Gap Locks 的结合,不仅锁定一个记录上的索引,也锁定索引之间的间隙。例如一个索引包含以下值:10, 11, 13, and 20,那么就需要锁定以下区间: 222 | 223 | ```sql 224 | (negative infinity, 10] 225 | (10, 11] 226 | (11, 13] 227 | (13, 20] 228 | (20, positive infinity) 229 | ``` 230 | 231 | # 6.★★☆范式理论 232 | 233 | ## 异常 234 | 235 | 以下的学生课程关系的函数依赖为 Sno, Cname -> Sname, Sdept, Mname, Grade,键码为 {Sno, Cname}。也就是说,确定学生和课程之后,就能确定其它信息。 236 | 237 | | Sno | Sname | Sdept | Mname | Cname | Grade | 238 | | :---: | :---: | :---: | :---: | :---: |:---:| 239 | | 1 | 学生-1 | 学院-1 | 院长-1 | 课程-1 | 90 | 240 | | 2 | 学生-2 | 学院-2 | 院长-2 | 课程-2 | 80 | 241 | | 2 | 学生-2 | 学院-2 | 院长-2 | 课程-1 | 100 | 242 | | 3 | 学生-3 | 学院-2 | 院长-2 | 课程-2 | 95 | 243 | 244 | 不符合范式的关系,会产生很多异常,主要有以下四种异常: 245 | 246 | - 冗余数据:例如 `学生-2` 出现了两次。 247 | - 修改异常:修改了一个记录中的信息,但是另一个记录中相同的信息却没有被修改。 248 | - 删除异常:删除一个信息,那么也会丢失其它信息。例如删除了 `课程-1` 需要删除第一行和第三行,那么 `学生-1` 的信息就会丢失。 249 | - 插入异常:例如想要插入一个学生的信息,如果这个学生还没选课,那么就无法插入。 250 | 251 | 范式理论是为了解决以上提到四种异常。 252 | 253 | 高级别范式的依赖于低级别的范式,1NF 是最低级别的范式。 254 | 255 |

256 | 257 | ### 1. 第一范式 (1NF) 258 | 259 | 属性不可分。 260 | 261 | ### 2. 第二范式 (2NF) 262 | 263 | 每个非主属性完全函数依赖于键码。 264 | 265 | 可以通过分解来满足。 266 | 267 | **分解前**
268 | 269 | | Sno | Sname | Sdept | Mname | Cname | Grade | 270 | | :---: | :---: | :---: | :---: | :---: |:---:| 271 | | 1 | 学生-1 | 学院-1 | 院长-1 | 课程-1 | 90 | 272 | | 2 | 学生-2 | 学院-2 | 院长-2 | 课程-2 | 80 | 273 | | 2 | 学生-2 | 学院-2 | 院长-2 | 课程-1 | 100 | 274 | | 3 | 学生-3 | 学院-2 | 院长-2 | 课程-2 | 95 | 275 | 276 | 以上学生课程关系中,{Sno, Cname} 为键码,有如下函数依赖: 277 | 278 | - Sno -> Sname, Sdept 279 | - Sdept -> Mname 280 | - Sno, Cname-> Grade 281 | 282 | Grade 完全函数依赖于键码,它没有任何冗余数据,每个学生的每门课都有特定的成绩。 283 | 284 | Sname, Sdept 和 Mname 都部分依赖于键码,当一个学生选修了多门课时,这些数据就会出现多次,造成大量冗余数据。 285 | 286 | **分解后**
287 | 288 | 关系-1 289 | 290 | | Sno | Sname | Sdept | Mname | 291 | | :---: | :---: | :---: | :---: | 292 | | 1 | 学生-1 | 学院-1 | 院长-1 | 293 | | 2 | 学生-2 | 学院-2 | 院长-2 | 294 | | 3 | 学生-3 | 学院-2 | 院长-2 | 295 | 296 | 有以下函数依赖: 297 | 298 | - Sno -> Sname, Sdept 299 | - Sdept -> Mname 300 | 301 | 关系-2 302 | 303 | | Sno | Cname | Grade | 304 | | :---: | :---: |:---:| 305 | | 1 | 课程-1 | 90 | 306 | | 2 | 课程-2 | 80 | 307 | | 2 | 课程-1 | 100 | 308 | | 3 | 课程-2 | 95 | 309 | 310 | 有以下函数依赖: 311 | 312 | - Sno, Cname -> Grade 313 | 314 | ### 3. 第三范式 (3NF) 315 | 316 | 非主属性不传递函数依赖于键码。 317 | 318 | 上面的 关系-1 中存在以下传递函数依赖: 319 | 320 | - Sno -> Sdept -> Mname 321 | 322 | 可以进行以下分解: 323 | 324 | 关系-11 325 | 326 | | Sno | Sname | Sdept | 327 | | :---: | :---: | :---: | 328 | | 1 | 学生-1 | 学院-1 | 329 | | 2 | 学生-2 | 学院-2 | 330 | | 3 | 学生-3 | 学院-2 | 331 | 332 | 关系-12 333 | 334 | | Sdept | Mname | 335 | | :---: | :---: | 336 | | 学院-1 | 院长-1 | 337 | | 学院-2 | 院长-2 | 338 | 339 | # 7.★★★SQL 与 NoSQL 的比较 340 | 341 | SQL和NoSQL数据库只是用不同的方式来完成相同的事情。我们可能会先选择其中之一然后更换到另一个上,但是在选择之前制定一个计划将会节约许多的时间和金钱。 342 | 343 | ## 适合使用SQL开发的项目: 344 | 345 | - 可以预先定义逻辑相关的离散数据的需求 346 | - 数据一致性是必要的 347 | - 具有良好的开发者经验和技术支持的标准的成熟技术 348 | 349 | ## 适合使用NoSQL开发的项目: 350 | 351 | - 不相关,不确定和逐步发展的数据需求 352 | - 更简单或者更宽松的能够快速开始编程的项目 353 | - 速度和可扩展性至关重要的 354 | 355 | -------------------------------------------------------------------------------- /算法库/Algorithm/BSTSearch.h: -------------------------------------------------------------------------------- 1 | /* 2 | 二叉搜索树的查找算法: 3 | 4 | 在二叉搜索树b中查找x的过程为: 5 | 6 | 1. 若b是空树,则搜索失败,否则: 7 | 2. 若x等于b的根节点的数据域之值,则查找成功;否则: 8 | 3. 若x小于b的根节点的数据域之值,则搜索左子树;否则: 9 | 4. 查找右子树。 10 | 11 | */ 12 | 13 | // 在根指针T所指二叉查找树中递归地查找其关键字等于key的数据元素,若查找成功, 14 | // 则指针p指向該数据元素节点,并返回TRUE,否则指针指向查找路径上访问的最终 15 | // 一个节点并返回FALSE,指针f指向T的双亲,其初始调用值为NULL 16 | Status SearchBST(BiTree T, KeyType key, BiTree f, BiTree &p){ 17 | 18 | if(!T) { //查找不成功 19 | p=f; 20 | return false; 21 | } 22 | else if (key == T->data.key) { //查找成功 23 | p=T; 24 | return true; 25 | } 26 | else if (key < T->data.key) //在左子树中继续查找 27 | return SearchBST(T->lchild, key, T, p); 28 | else //在右子树中继续查找 29 | return SearchBST(T->rchild, key, T, p); 30 | } -------------------------------------------------------------------------------- /算法库/Algorithm/BinarySearch.h: -------------------------------------------------------------------------------- 1 | // 二分查找(折半查找):对于已排序,若无序,需要先排序 2 | 3 | // 非递归 4 | 5 | int BinarySearch(vector v, int value , int low, int high) { 6 | if (v.size() <= 0) { 7 | return -1; 8 | } 9 | while (low <= high) { 10 | int mid = low + (high - low) / 2; 11 | if (v[mid] == value) { 12 | return mid; 13 | } 14 | else if (v[mid] > value) { 15 | high = mid - 1; 16 | } 17 | else { 18 | low = mid + 1; 19 | } 20 | } 21 | 22 | return -1; 23 | } 24 | 25 | // 递归 26 | int BinarySearch2(vector v, int value, int low, int high) 27 | { 28 | if (low > high) 29 | return -1; 30 | int mid = low + (high - low) / 2; 31 | if (v[mid] == value) 32 | return mid; 33 | else if (v[mid] > value) 34 | return BinarySearch2(v, value, low, mid - 1); 35 | else 36 | return BinarySearch2(v, value, mid + 1, high); 37 | } 38 | -------------------------------------------------------------------------------- /算法库/Algorithm/BubbleSort.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | (无序区,有序区)。从无序区通过交换找出最大元素放到有序区前端。 4 | 5 | 选择排序思路: 6 | 1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。 7 | 2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。 8 | 3. 针对所有的元素重复以上的步骤,除了最后一个。 9 | 4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。 10 | 11 | */ 12 | 13 | // 冒泡排序 14 | void BubbleSort(vector& v) { 15 | int len = v.size(); 16 | for (int i = 0; i < len - 1; ++i) 17 | for (int j = 0; j < len - 1 - i; ++j) 18 | if (v[j] > v[j + 1]) 19 | swap(v[j], v[j + 1]); 20 | } 21 | 22 | // 模板实现冒泡排序 23 | template //整數或浮點數皆可使用,若要使用物件(class)時必須設定大於(>)的運算子功能 24 | void bubble_sort(T arr[], int len) { 25 | for (int i = 0; i < len - 1; i++) 26 | for (int j = 0; j < len - 1 - i; j++) 27 | if (arr[j] > arr[j + 1]) 28 | swap(arr[j], arr[j + 1]); 29 | } 30 | 31 | // 冒泡排序(改进版) 32 | void BubbleSort_orderly(vector& v) { 33 | int len = v.size(); 34 | bool orderly = false; 35 | for (int i = 0; i < len - 1 && !orderly; ++i) { 36 | orderly = true; 37 | for (int j = 0; j < len - 1 - i; ++j) { 38 | if (v[j] > v[j + 1]) { // 从小到大 39 | orderly = false; // 发生交换则仍非有序 40 | swap(v[j], v[j + 1]); 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /算法库/Algorithm/BucketSort.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | using std::vector; 5 | 6 | /***************** 7 | 8 | 桶排序:将值为i的元素放入i号桶,最后依次把桶里的元素倒出来。 9 | 10 | 桶排序序思路: 11 | 1. 设置一个定量的数组当作空桶子。 12 | 2. 寻访序列,并且把项目一个一个放到对应的桶子去。 13 | 3. 对每个不是空的桶子进行排序。 14 | 4. 从不是空的桶子里把项目再放回原来的序列中。 15 | 16 | 假设数据分布在[0,100)之间,每个桶内部用链表表示,在数据入桶的同时插入排序,然后把各个桶中的数据合并。 17 | 18 | *****************/ 19 | 20 | 21 | const int BUCKET_NUM = 10; 22 | 23 | struct ListNode{ 24 | explicit ListNode(int i=0):mData(i),mNext(NULL){} 25 | ListNode* mNext; 26 | int mData; 27 | }; 28 | 29 | ListNode* insert(ListNode* head,int val){ 30 | ListNode dummyNode; 31 | ListNode *newNode = new ListNode(val); 32 | ListNode *pre,*curr; 33 | dummyNode.mNext = head; 34 | pre = &dummyNode; 35 | curr = head; 36 | while(NULL!=curr && curr->mData<=val){ 37 | pre = curr; 38 | curr = curr->mNext; 39 | } 40 | newNode->mNext = curr; 41 | pre->mNext = newNode; 42 | return dummyNode.mNext; 43 | } 44 | 45 | 46 | ListNode* Merge(ListNode *head1,ListNode *head2){ 47 | ListNode dummyNode; 48 | ListNode *dummy = &dummyNode; 49 | while(NULL!=head1 && NULL!=head2){ 50 | if(head1->mData <= head2->mData){ 51 | dummy->mNext = head1; 52 | head1 = head1->mNext; 53 | }else{ 54 | dummy->mNext = head2; 55 | head2 = head2->mNext; 56 | } 57 | dummy = dummy->mNext; 58 | } 59 | if(NULL!=head1) dummy->mNext = head1; 60 | if(NULL!=head2) dummy->mNext = head2; 61 | 62 | return dummyNode.mNext; 63 | } 64 | 65 | void BucketSort(int n,int arr[]){ 66 | vector buckets(BUCKET_NUM,(ListNode*)(0)); 67 | for(int i=0;imData; 78 | head = head->mNext; 79 | } 80 | } -------------------------------------------------------------------------------- /算法库/Algorithm/CountSort.cpp: -------------------------------------------------------------------------------- 1 | /***************** 2 | 3 | 计数排序:统计小于等于该元素值的元素的个数i,于是该元素就放在目标数组的索引i位(i≥0)。 4 | 5 | 计数排序基于一个假设,待排序数列的所有数均为整数,且出现在(0,k)的区间之内。 6 | 如果 k(待排数组的最大值) 过大则会引起较大的空间复杂度,一般是用来排序 0 到 100 之间的数字的最好的算法,但是它不适合按字母顺序排序人名。 7 | 计数排序不是比较排序,排序的速度快于任何比较排序算法。 8 | 时间复杂度为 O(n+k),空间复杂度为 O(n+k) 9 | 10 | 算法的步骤如下: 11 | 12 | 1. 找出待排序的数组中最大和最小的元素 13 | 2. 统计数组中每个值为 i 的元素出现的次数,存入数组 C 的第 i 项 14 | 3. 对所有的计数累加(从 C 中的第一个元素开始,每一项和前一项相加) 15 | 4. 反向填充目标数组:将每个元素 i 放在新数组的第 C[i] 项,每放一个元素就将 C[i] 减去 1 16 | 17 | *****************/ 18 | 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | using namespace std; 25 | 26 | // 计数排序 27 | void CountSort(vector& vecRaw, vector& vecObj) 28 | { 29 | // 确保待排序容器非空 30 | if (vecRaw.size() == 0) 31 | return; 32 | 33 | // 使用 vecRaw 的最大值 + 1 作为计数容器 countVec 的大小 34 | int vecCountLength = (*max_element(begin(vecRaw), end(vecRaw))) + 1; 35 | vector vecCount(vecCountLength, 0); 36 | 37 | // 统计每个键值出现的次数 38 | for (int i = 0; i < vecRaw.size(); i++) 39 | vecCount[vecRaw[i]]++; 40 | 41 | // 后面的键值出现的位置为前面所有键值出现的次数之和 42 | for (int i = 1; i < vecCountLength; i++) 43 | vecCount[i] += vecCount[i - 1]; 44 | 45 | // 将键值放到目标位置 46 | for (int i = vecRaw.size(); i > 0; i--) // 此处逆序是为了保持相同键值的稳定性 47 | vecObj[--vecCount[vecRaw[i - 1]]] = vecRaw[i - 1]; 48 | } 49 | 50 | int main() 51 | { 52 | vector vecRaw = { 0,5,7,9,6,3,4,5,2,8,6,9,2,1 }; 53 | vector vecObj(vecRaw.size(), 0); 54 | 55 | CountSort(vecRaw, vecObj); 56 | 57 | for (int i = 0; i < vecObj.size(); ++i) 58 | cout << vecObj[i] << " "; 59 | cout << endl; 60 | 61 | return 0; 62 | } -------------------------------------------------------------------------------- /算法库/Algorithm/FibonacciSearch.cpp: -------------------------------------------------------------------------------- 1 | // 斐波那契查找 2 | 3 | #include "stdafx.h" 4 | #include 5 | #include 6 | using namespace std; 7 | 8 | const int max_size=20;//斐波那契数组的长度 9 | 10 | /*构造一个斐波那契数组*/ 11 | void Fibonacci(int * F) 12 | { 13 | F[0]=0; 14 | F[1]=1; 15 | for(int i=2;iF[k]-1)//计算n位于斐波那契数列的位置 30 | ++k; 31 | 32 | int * temp;//将数组a扩展到F[k]-1的长度 33 | temp=new int [F[k]-1]; 34 | memcpy(temp,a,n*sizeof(int)); 35 | 36 | for(int i=n;itemp[mid]) 48 | { 49 | low=mid+1; 50 | k-=2; 51 | } 52 | else 53 | { 54 | if(mid=n则说明是扩展的数值,返回n-1 58 | } 59 | } 60 | delete [] temp; 61 | return -1; 62 | } 63 | 64 | int main() 65 | { 66 | int a[] = {0,16,24,35,47,59,62,73,88,99}; 67 | int key=100; 68 | int index=FibonacciSearch(a,sizeof(a)/sizeof(int),key); 69 | cout< 2 | #include 3 | using namespace std; 4 | 5 | // 堆排序:(最大堆,有序区)。从堆顶把根卸出来放在有序区之前,再恢复堆。 6 | 7 | void max_heapify(int arr[], int start, int end) { 8 | //建立父節點指標和子節點指標 9 | int dad = start; 10 | int son = dad * 2 + 1; 11 | while (son <= end) { //若子節點指標在範圍內才做比較 12 | if (son + 1 <= end && arr[son] < arr[son + 1]) //先比較兩個子節點大小,選擇最大的 13 | son++; 14 | if (arr[dad] > arr[son]) //如果父節點大於子節點代表調整完畢,直接跳出函數 15 | return; 16 | else { //否則交換父子內容再繼續子節點和孫節點比較 17 | swap(arr[dad], arr[son]); 18 | dad = son; 19 | son = dad * 2 + 1; 20 | } 21 | } 22 | } 23 | 24 | void heap_sort(int arr[], int len) { 25 | //初始化,i從最後一個父節點開始調整 26 | for (int i = len / 2 - 1; i >= 0; i--) 27 | max_heapify(arr, i, len - 1); 28 | //先將第一個元素和已经排好的元素前一位做交換,再從新調整(刚调整的元素之前的元素),直到排序完畢 29 | for (int i = len - 1; i > 0; i--) { 30 | swap(arr[0], arr[i]); 31 | max_heapify(arr, 0, i - 1); 32 | } 33 | } 34 | 35 | int main() { 36 | int arr[] = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 }; 37 | int len = (int) sizeof(arr) / sizeof(*arr); 38 | heap_sort(arr, len); 39 | for (int i = 0; i < len; i++) 40 | cout << arr[i] << ' '; 41 | cout << endl; 42 | return 0; 43 | } -------------------------------------------------------------------------------- /算法库/Algorithm/InsertSort.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | (有序区,无序区)。把无序区的第一个元素插入到有序区的合适的位置。对数组:比较得少,换得多。 4 | 5 | 插入排序思路: 6 | 1. 从第一个元素开始,该元素可以认为已经被排序 7 | 2. 取出下一个元素,在已经排序的元素序列中从后向前扫描 8 | 3. 如果该元素(已排序)大于新元素,将该元素移到下一位置 9 | 4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置 10 | 5. 将新元素插入到该位置后 11 | 6. 重复步骤2~5 12 | 13 | */ 14 | 15 | // 插入排序 16 | void InsertSort(vector& v) 17 | { 18 | int len = v.size(); 19 | for (int i = 1; i < len; ++i) { 20 | int temp = v[i]; 21 | for(int j = i - 1; j >= 0; --j) 22 | { 23 | if(v[j] > temp) 24 | { 25 | v[j + 1] = v[j]; 26 | v[j] = temp; 27 | } 28 | else 29 | break; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /算法库/Algorithm/InsertionSearch.h: -------------------------------------------------------------------------------- 1 | //插值查找 2 | int InsertionSearch(int a[], int value, int low, int high) 3 | { 4 | int mid = low+(value-a[low])/(a[high]-a[low])*(high-low); 5 | if(a[mid]==value) 6 | return mid; 7 | if(a[mid]>value) 8 | return InsertionSearch(a, value, low, mid-1); 9 | if(a[mid] 9 | void merge_sort(T arr[], int len) { 10 | T* a = arr; 11 | T* b = new T[len]; 12 | for (int seg = 1; seg < len; seg += seg) { 13 | for (int start = 0; start < len; start += seg + seg) { 14 | int low = start, mid = min(start + seg, len), high = min(start + seg + seg, len); 15 | int k = low; 16 | int start1 = low, end1 = mid; 17 | int start2 = mid, end2 = high; 18 | while (start1 < end1 && start2 < end2) 19 | b[k++] = a[start1] < a[start2] ? a[start1++] : a[start2++]; 20 | while (start1 < end1) 21 | b[k++] = a[start1++]; 22 | while (start2 < end2) 23 | b[k++] = a[start2++]; 24 | } 25 | T* temp = a; 26 | a = b; 27 | b = temp; 28 | } 29 | if (a != arr) { 30 | for (int i = 0; i < len; i++) 31 | b[i] = a[i]; 32 | b = a; 33 | } 34 | delete[] b; 35 | } 36 | 37 | /***************** 38 | 递归版 39 | *****************/ 40 | template 41 | void merge_sort_recursive(T arr[], T reg[], int start, int end) { 42 | if (start >= end) 43 | return; 44 | int len = end - start, mid = (len >> 1) + start; 45 | int start1 = start, end1 = mid; 46 | int start2 = mid + 1, end2 = end; 47 | merge_sort_recursive(arr, reg, start1, end1); 48 | merge_sort_recursive(arr, reg, start2, end2); 49 | int k = start; 50 | while (start1 <= end1 && start2 <= end2) 51 | reg[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++]; 52 | while (start1 <= end1) 53 | reg[k++] = arr[start1++]; 54 | while (start2 <= end2) 55 | reg[k++] = arr[start2++]; 56 | for (k = start; k <= end; k++) 57 | arr[k] = reg[k]; 58 | } 59 | //整數或浮點數皆可使用,若要使用物件(class)時必須設定"小於"(<)的運算子功能 60 | template 61 | void merge_sort(T arr[], const int len) { 62 | T *reg = new T[len]; 63 | merge_sort_recursive(arr, reg, 0, len - 1); 64 | delete[] reg; 65 | } -------------------------------------------------------------------------------- /算法库/Algorithm/QuickSort.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | (小数,基准元素,大数)。在区间中随机挑选一个元素作基准,将小于基准的元素放在基准之前,大于基准的元素放在基准之后,再分别对小数区与大数区进行排序。 4 | 5 | 快速排序思路: 6 | 1. 选取第一个数为基准 7 | 2. 将比基准小的数交换到前面,比基准大的数交换到后面 8 | 3. 对左右区间重复第二步,直到各区间只有一个数 9 | 10 | */ 11 | 12 | // ---------------------------------------------------- 13 | 14 | // 快速排序(递归) 15 | void QuickSort(vector& v, int low, int high) { 16 | if (low >= high) // 结束标志 17 | return; 18 | int first = low; // 低位下标 19 | int last = high; // 高位下标 20 | int key = v[first]; // 设第一个为基准 21 | 22 | while (first < last) 23 | { 24 | // 将比第一个小的移到前面 25 | while (first < last && v[last] >= key) 26 | last--; 27 | if (first < last) 28 | v[first++] = v[last]; 29 | 30 | // 将比第一个大的移到后面 31 | while (first < last && v[first] <= key) 32 | first++; 33 | if (first < last) 34 | v[last--] = v[first]; 35 | } 36 | // 基准置位 37 | v[first] = key; 38 | // 前半递归 39 | QuickSort(v, low, first - 1); 40 | // 后半递归 41 | QuickSort(v, first + 1, high); 42 | } 43 | 44 | // ---------------------------------------------------- 45 | 46 | // 模板实现快速排序(递归) 47 | template 48 | void quick_sort_recursive(T arr[], int start, int end) { 49 | if (start >= end) 50 | return; 51 | T mid = arr[end]; 52 | int left = start, right = end - 1; 53 | while (left < right) { 54 | while (arr[left] < mid && left < right) 55 | left++; 56 | while (arr[right] >= mid && left < right) 57 | right--; 58 | std::swap(arr[left], arr[right]); 59 | } 60 | if (arr[left] >= arr[end]) 61 | std::swap(arr[left], arr[end]); 62 | else 63 | left++; 64 | quick_sort_recursive(arr, start, left - 1); 65 | quick_sort_recursive(arr, left + 1, end); 66 | } 67 | template //整數或浮點數皆可使用,若要使用物件(class)時必須設定"小於"(<)、"大於"(>)、"不小於"(>=)的運算子功能 68 | void quick_sort(T arr[], int len) { 69 | quick_sort_recursive(arr, 0, len - 1); 70 | } 71 | 72 | // ---------------------------------------------------- 73 | 74 | // 模板实现快速排序(迭代) 75 | struct Range { 76 | int start, end; 77 | Range(int s = 0, int e = 0) { 78 | start = s, end = e; 79 | } 80 | }; 81 | template // 整數或浮點數皆可使用,若要使用物件(class)時必須設定"小於"(<)、"大於"(>)、"不小於"(>=)的運算子功能 82 | void quick_sort(T arr[], const int len) { 83 | if (len <= 0) 84 | return; // 避免len等於負值時宣告堆疊陣列當機 85 | // r[]模擬堆疊,p為數量,r[p++]為push,r[--p]為pop且取得元素 86 | Range r[len]; 87 | int p = 0; 88 | r[p++] = Range(0, len - 1); 89 | while (p) { 90 | Range range = r[--p]; 91 | if (range.start >= range.end) 92 | continue; 93 | T mid = arr[range.end]; 94 | int left = range.start, right = range.end - 1; 95 | while (left < right) { 96 | while (arr[left] < mid && left < right) left++; 97 | while (arr[right] >= mid && left < right) right--; 98 | std::swap(arr[left], arr[right]); 99 | } 100 | if (arr[left] >= arr[range.end]) 101 | std::swap(arr[left], arr[range.end]); 102 | else 103 | left++; 104 | r[p++] = Range(range.start, left - 1); 105 | r[p++] = Range(left + 1, range.end); 106 | } 107 | } -------------------------------------------------------------------------------- /算法库/Algorithm/README.md: -------------------------------------------------------------------------------- 1 | ## ⚡️ 算法 2 | 3 | ### 排序 4 | 5 | 排序算法 | 平均时间复杂度 | 最差时间复杂度 | 空间复杂度 | 数据对象稳定性 6 | ---|---|---|---|--- 7 | [冒泡排序](BubbleSort.h) | O(n2)|O(n2)|O(1)|稳定 8 | [选择排序](SelectionSort.h) | O(n2)|O(n2)|O(1)|数组不稳定、链表稳定 9 | [插入排序](InsertSort.h) | O(n2)|O(n2)|O(1)|稳定 10 | [快速排序](QuickSort.h) | O(n*log2n) | O(n2) | O(log2n) | 不稳定 11 | [堆排序](HeapSort.cpp) | O(n*log2n)|O(n*log2n)|O(1)|不稳定 12 | [归并排序](MergeSort.h) | O(n*log2n) | O(n*log2n)|O(n)|稳定 13 | [希尔排序](ShellSort.h) | O(n*log2n)|O(n2)|O(1)|不稳定 14 | [计数排序](CountSort.cpp) | O(n+m)|O(n+m)|O(n+m)|稳定 15 | [桶排序](BucketSort.cpp) | O(n)|O(n)|O(m)|稳定 16 | [基数排序](RadixSort.h) | O(k*n)|O(n2)| |稳定 17 | 18 | > * 均按从小到大排列 19 | > * k:代表数值中的 “数位” 个数 20 | > * n:代表数据规模 21 | > * m:代表数据的最大值减最小值 22 | > * 来自:[wikipedia . 排序算法](https://zh.wikipedia.org/wiki/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95) 23 | 24 | ### 查找 25 | 26 | 查找算法 | 平均时间复杂度 | 空间复杂度 | 查找条件 27 | ---|---|---|--- 28 | [顺序查找](SequentialSearch.h) | O(n) | O(1) | 无序或有序 29 | [二分查找(折半查找)](BinarySearch.h) | O(log2n)| O(1) | 有序 30 | [插值查找](InsertionSearch.h) | O(log2(log2n)) | O(1) | 有序 31 | [斐波那契查找](FibonacciSearch.cpp) | O(log2n) | O(1) | 有序 32 | [哈希查找](DataStructure/HashTable.cpp) | O(1) | O(n) | 无序或有序 33 | [二叉查找树(二叉搜索树查找)](BSTSearch.h) |O(log2n) | | 34 | [红黑树](DataStructure/RedBlackTree.cpp) |O(log2n) | | 35 | 2-3树 | O(log2n - log3n) | | 36 | B树/B+树 |O(log2n) | | 37 | 38 | ### 图搜索算法 39 | 40 | 图搜索算法 |数据结构| 遍历时间复杂度 | 空间复杂度 41 | ---|---|---|--- 42 | [BFS广度优先搜索](https://zh.wikipedia.org/wiki/%E5%B9%BF%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2)|邻接矩阵
邻接链表|O(\|v\|2)
O(\|v\|+\|E\|)|O(\|v\|2)
O(\|v\|+\|E\|) 43 | [DFS深度优先搜索](https://zh.wikipedia.org/wiki/%E6%B7%B1%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2)|邻接矩阵
邻接链表|O(\|v\|2)
O(\|v\|+\|E\|)|O(\|v\|2)
O(\|v\|+\|E\|) 44 | 45 | ### 其他算法 46 | 47 | 算法 |思想| 应用 48 | ---|---|--- 49 | [分治法](https://zh.wikipedia.org/wiki/%E5%88%86%E6%B2%BB%E6%B3%95)|把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并|[循环赛日程安排问题](https://github.com/huihut/interview/tree/master/Problems/RoundRobinProblem)、排序算法(快速排序、归并排序) 50 | [动态规划](https://zh.wikipedia.org/wiki/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92)|通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法,适用于有重叠子问题和最优子结构性质的问题|[背包问题](https://github.com/huihut/interview/tree/master/Problems/KnapsackProblem)、斐波那契数列 51 | [贪心法](https://zh.wikipedia.org/wiki/%E8%B4%AA%E5%BF%83%E6%B3%95)|一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是最好或最优的算法|旅行推销员问题(最短路径问题)、最小生成树、哈夫曼编码 52 | -------------------------------------------------------------------------------- /算法库/Algorithm/RadixSort.h: -------------------------------------------------------------------------------- 1 | // 基数排序:一种多关键字的排序算法,可用桶排序实现。 2 | 3 | int maxbit(int data[], int n) //辅助函数,求数据的最大位数 4 | { 5 | int maxData = data[0]; ///< 最大数 6 | /// 先求出最大数,再求其位数,这样有原先依次每个数判断其位数,稍微优化点。 7 | for (int i = 1; i < n; ++i) 8 | { 9 | if (maxData < data[i]) 10 | maxData = data[i]; 11 | } 12 | int d = 1; 13 | int p = 10; 14 | while (maxData >= p) 15 | { 16 | //p *= 10; // Maybe overflow 17 | maxData /= 10; 18 | ++d; 19 | } 20 | return d; 21 | /* int d = 1; //保存最大的位数 22 | int p = 10; 23 | for(int i = 0; i < n; ++i) 24 | { 25 | while(data[i] >= p) 26 | { 27 | p *= 10; 28 | ++d; 29 | } 30 | } 31 | return d;*/ 32 | } 33 | void radixsort(int data[], int n) //基数排序 34 | { 35 | int d = maxbit(data, n); 36 | int *tmp = new int[n]; 37 | int *count = new int[10]; //计数器 38 | int i, j, k; 39 | int radix = 1; 40 | for(i = 1; i <= d; i++) //进行d次排序 41 | { 42 | for(j = 0; j < 10; j++) 43 | count[j] = 0; //每次分配前清空计数器 44 | for(j = 0; j < n; j++) 45 | { 46 | k = (data[j] / radix) % 10; //统计每个桶中的记录数 47 | count[k]++; 48 | } 49 | for(j = 1; j < 10; j++) 50 | count[j] = count[j - 1] + count[j]; //将tmp中的位置依次分配给每个桶 51 | for(j = n - 1; j >= 0; j--) //将所有桶中记录依次收集到tmp中 52 | { 53 | k = (data[j] / radix) % 10; 54 | tmp[count[k] - 1] = data[j]; 55 | count[k]--; 56 | } 57 | for(j = 0; j < n; j++) //将临时数组的内容复制到data中 58 | data[j] = tmp[j]; 59 | radix = radix * 10; 60 | } 61 | delete []tmp; 62 | delete []count; 63 | } -------------------------------------------------------------------------------- /算法库/Algorithm/SelectionSort.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | (有序区,无序区)。在无序区里找一个最小的元素跟在有序区的后面。对数组:比较得多,换得少。 4 | 5 | 选择排序思路: 6 | 1. 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置 7 | 2. 从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾 8 | 3. 以此类推,直到所有元素均排序完毕 9 | 10 | */ 11 | 12 | // 选择排序 13 | void SelectionSort(vector& v) { 14 | int min, len = v.size(); 15 | for (int i = 0; i < len - 1; ++i) { 16 | min = i; 17 | for (int j = i + 1; j < len; ++j) { 18 | if (v[j] < v[min]) { // 标记最小的 19 | min = j; 20 | } 21 | } 22 | if (i != min) // 交换到前面 23 | swap(v[i], v[min]); 24 | } 25 | } 26 | 27 | // 模板实现 28 | template 29 | void Selection_Sort(std::vector& arr) { 30 | int len = arr.size(); 31 | for (int i = 0; i < len - 1; i++) { 32 | int min = i; 33 | for (int j = i + 1; j < len; j++) 34 | if (arr[j] < arr[min]) 35 | min = j; 36 | if(i != min) 37 | std::swap(arr[i], arr[min]); 38 | } 39 | } -------------------------------------------------------------------------------- /算法库/Algorithm/SequentialSearch.h: -------------------------------------------------------------------------------- 1 | // 顺序查找 2 | int SequentialSearch(vector& v, int k) { 3 | for (int i = 0; i < v.size(); ++i) 4 | if (v[i] == k) 5 | return i; 6 | return -1; 7 | } 8 | 9 | 10 | /* The following is a Sentinel Search Algorithm which only performs 11 | just one test in each loop iteration thereby reducing time complexity */ 12 | 13 | int BetterSequentialSearch(vector& v, int k) { 14 | int last = v[v.size()-1]; 15 | v[v.size()-1] = k; 16 | int i = 0; 17 | while (v[i]!= k) 18 | i++; 19 | v[v.size()-1] = last; 20 | if(i < v.size()-1 || v[v.size()-1] == k) 21 | return i; 22 | return -1; 23 | } -------------------------------------------------------------------------------- /算法库/Algorithm/ShellSort.h: -------------------------------------------------------------------------------- 1 | // 希尔排序:每一轮按照事先决定的间隔进行插入排序,间隔会依次缩小,最后一次一定要是1。 2 | template 3 | void shell_sort(T array[], int length) { 4 | int h = 1; 5 | while (h < length / 3) { 6 | h = 3 * h + 1; 7 | } 8 | while (h >= 1) { 9 | for (int i = h; i < length; i++) { 10 | for (int j = i; j >= h && array[j] < array[j - h]; j -= h) { 11 | std::swap(array[j], array[j - h]); 12 | } 13 | } 14 | h = h / 3; 15 | } 16 | } -------------------------------------------------------------------------------- /算法库/DataStructure/BinaryTree.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define TRUE 1 5 | #define FALSE 0 6 | #define OK 1 7 | #define ERROR 0 8 | #define OVERFLOW -1 9 | #define SUCCESS 1 10 | #define UNSUCCESS 0 11 | #define dataNum 5 12 | int i = 0; 13 | int dep = 0; 14 | char data[dataNum] = { 'A', 'B', 'C', 'D', 'E' }; 15 | 16 | typedef int Status; 17 | typedef char TElemType; 18 | 19 | // 二叉树结构 20 | typedef struct BiTNode 21 | { 22 | TElemType data; 23 | struct BiTNode *lchild, *rchild; 24 | }BiTNode, *BiTree; 25 | 26 | // 初始化一个空树 27 | void InitBiTree(BiTree &T) 28 | { 29 | T = NULL; 30 | } 31 | 32 | // 构建二叉树 33 | BiTree MakeBiTree(TElemType e, BiTree L, BiTree R) 34 | { 35 | BiTree t; 36 | t = (BiTree)malloc(sizeof(BiTNode)); 37 | if (NULL == t) return NULL; 38 | t->data = e; 39 | t->lchild = L; 40 | t->rchild = R; 41 | return t; 42 | } 43 | 44 | // 访问结点 45 | Status visit(TElemType e) 46 | { 47 | printf("%c", e); 48 | return OK; 49 | } 50 | 51 | // 对二叉树T求叶子结点数目 52 | int Leaves(BiTree T) 53 | { 54 | int l = 0, r = 0; 55 | if (NULL == T) return 0; 56 | if (NULL == T->lchild && NULL == T->rchild) return 1; 57 | 58 | // 求左子树叶子数目 59 | l = Leaves(T->lchild); 60 | // 求右子树叶子数目 61 | r = Leaves(T->rchild); 62 | // 组合 63 | return r + l; 64 | } 65 | 66 | // 层次遍历:dep是个全局变量,高度 67 | int depTraverse(BiTree T) 68 | { 69 | if (NULL == T) return ERROR; 70 | 71 | dep = (depTraverse(T->lchild) > depTraverse(T->rchild)) ? depTraverse(T->lchild) : depTraverse(T->rchild); 72 | 73 | return dep + 1; 74 | } 75 | 76 | // 高度遍历:lev是局部变量,层次 77 | void levTraverse(BiTree T, Status(*visit)(TElemType e), int lev) 78 | { 79 | if (NULL == T) return; 80 | visit(T->data); 81 | printf("的层次是%d\n", lev); 82 | 83 | levTraverse(T->lchild, visit, ++lev); 84 | levTraverse(T->rchild, visit, lev); 85 | } 86 | 87 | // num是个全局变量 88 | void InOrderTraverse(BiTree T, Status(*visit)(TElemType e), int &num) 89 | { 90 | if (NULL == T) return; 91 | visit(T->data); 92 | if (NULL == T->lchild && NULL == T->rchild) { printf("是叶子结点"); num++; } 93 | else printf("不是叶子结点"); 94 | printf("\n"); 95 | InOrderTraverse(T->lchild, visit, num); 96 | InOrderTraverse(T->rchild, visit, num); 97 | } 98 | 99 | // 二叉树判空 100 | Status BiTreeEmpty(BiTree T) 101 | { 102 | if (NULL == T) return TRUE; 103 | return FALSE; 104 | } 105 | 106 | // 打断二叉树:置空二叉树的左右子树 107 | Status BreakBiTree(BiTree &T, BiTree &L, BiTree &R) 108 | { 109 | if (NULL == T) return ERROR; 110 | L = T->lchild; 111 | R = T->rchild; 112 | T->lchild = NULL; 113 | T->rchild = NULL; 114 | return OK; 115 | } 116 | 117 | // 替换左子树 118 | Status ReplaceLeft(BiTree &T, BiTree <) 119 | { 120 | BiTree temp; 121 | if (NULL == T) return ERROR; 122 | temp = T->lchild; 123 | T->lchild = LT; 124 | LT = temp; 125 | return OK; 126 | } 127 | 128 | // 替换右子树 129 | Status ReplaceRight(BiTree &T, BiTree &RT) 130 | { 131 | BiTree temp; 132 | if (NULL == T) return ERROR; 133 | temp = T->rchild; 134 | T->rchild = RT; 135 | RT = temp; 136 | return OK; 137 | } 138 | 139 | // 合并二叉树 140 | void UnionBiTree(BiTree &Ttemp) 141 | { 142 | BiTree L = NULL, R = NULL; 143 | L = MakeBiTree(data[i++], NULL, NULL); 144 | R = MakeBiTree(data[i++], NULL, NULL); 145 | ReplaceLeft(Ttemp, L); 146 | ReplaceRight(Ttemp, R); 147 | } 148 | 149 | int main() 150 | { 151 | BiTree T = NULL, Ttemp = NULL; 152 | 153 | InitBiTree(T); 154 | if (TRUE == BiTreeEmpty(T)) printf("初始化T为空\n"); 155 | else printf("初始化T不为空\n"); 156 | 157 | T = MakeBiTree(data[i++], NULL, NULL); 158 | 159 | Ttemp = T; 160 | UnionBiTree(Ttemp); 161 | 162 | Ttemp = T->lchild; 163 | UnionBiTree(Ttemp); 164 | 165 | Status(*visit1)(TElemType); 166 | visit1 = visit; 167 | int num = 0; 168 | InOrderTraverse(T, visit1, num); 169 | printf("叶子结点是 %d\n", num); 170 | 171 | printf("叶子结点是 %d\n", Leaves(T)); 172 | 173 | int lev = 1; 174 | levTraverse(T, visit1, lev); 175 | 176 | printf("高度是 %d\n", depTraverse(T)); 177 | 178 | getchar(); 179 | return 0; 180 | } -------------------------------------------------------------------------------- /算法库/DataStructure/HashTable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define SUCCESS 1 5 | #define UNSUCCESS 0 6 | #define OVERFLOW -1 7 | #define OK 1 8 | #define ERROR -1 9 | #define MAXNUM 9999 // 用于初始化哈希表的记录 key 10 | 11 | typedef int Status; 12 | typedef int KeyType; 13 | 14 | // 哈希表中的记录类型 15 | typedef struct { 16 | KeyType key; 17 | }RcdType; 18 | 19 | // 哈希表类型 20 | typedef struct { 21 | RcdType *rcd; 22 | int size; 23 | int count; 24 | int *tag; 25 | }HashTable; 26 | 27 | // 哈希表每次重建增长后的大小 28 | int hashsize[] = { 11, 31, 61, 127, 251, 503 }; 29 | int index = 0; 30 | 31 | // 初始哈希表 32 | Status InitHashTable(HashTable &H, int size) { 33 | int i; 34 | H.rcd = (RcdType *)malloc(sizeof(RcdType)*size); 35 | H.tag = (int *)malloc(sizeof(int)*size); 36 | if (NULL == H.rcd || NULL == H.tag) return OVERFLOW; 37 | KeyType maxNum = MAXNUM; 38 | for (i = 0; i < size; i++) { 39 | H.tag[i] = 0; 40 | H.rcd[i].key = maxNum; 41 | } 42 | H.size = size; 43 | H.count = 0; 44 | return OK; 45 | } 46 | 47 | // 哈希函数:除留余数法 48 | int Hash(KeyType key, int m) { 49 | return (3 * key) % m; 50 | } 51 | 52 | // 处理哈希冲突:线性探测 53 | void collision(int &p, int m) { 54 | p = (p + 1) % m; 55 | } 56 | 57 | // 在哈希表中查询 58 | Status SearchHash(HashTable H, KeyType key, int &p, int &c) { 59 | p = Hash(key, H.size); 60 | int h = p; 61 | c = 0; 62 | while ((1 == H.tag[p] && H.rcd[p].key != key) || -1 == H.tag[p]) { 63 | collision(p, H.size); c++; 64 | } 65 | 66 | if (1 == H.tag[p] && key == H.rcd[p].key) return SUCCESS; 67 | else return UNSUCCESS; 68 | } 69 | 70 | //打印哈希表 71 | void printHash(HashTable H) 72 | { 73 | int i; 74 | printf("key : "); 75 | for (i = 0; i < H.size; i++) 76 | printf("%3d ", H.rcd[i].key); 77 | printf("\n"); 78 | printf("tag : "); 79 | for (i = 0; i < H.size; i++) 80 | printf("%3d ", H.tag[i]); 81 | printf("\n\n"); 82 | } 83 | 84 | // 函数声明:插入哈希表 85 | Status InsertHash(HashTable &H, KeyType key); 86 | 87 | // 重建哈希表 88 | Status recreateHash(HashTable &H) { 89 | RcdType *orcd; 90 | int *otag, osize, i; 91 | orcd = H.rcd; 92 | otag = H.tag; 93 | osize = H.size; 94 | 95 | InitHashTable(H, hashsize[index++]); 96 | //把所有元素,按照新哈希函数放到新表中 97 | for (i = 0; i < osize; i++) { 98 | if (1 == otag[i]) { 99 | InsertHash(H, orcd[i].key); 100 | } 101 | } 102 | return OK; 103 | } 104 | 105 | // 插入哈希表 106 | Status InsertHash(HashTable &H, KeyType key) { 107 | int p, c; 108 | if (UNSUCCESS == SearchHash(H, key, p, c)) { //没有相同key 109 | if (c*1.0 / H.size < 0.5) { //冲突次数未达到上线 110 | //插入代码 111 | H.rcd[p].key = key; 112 | H.tag[p] = 1; 113 | H.count++; 114 | return SUCCESS; 115 | } 116 | else recreateHash(H); //重构哈希表 117 | } 118 | return UNSUCCESS; 119 | } 120 | 121 | // 删除哈希表 122 | Status DeleteHash(HashTable &H, KeyType key) { 123 | int p, c; 124 | if (SUCCESS == SearchHash(H, key, p, c)) { 125 | //删除代码 126 | H.tag[p] = -1; 127 | H.count--; 128 | return SUCCESS; 129 | } 130 | else return UNSUCCESS; 131 | } 132 | 133 | int main() 134 | { 135 | printf("-----哈希表-----\n"); 136 | HashTable H; 137 | int i; 138 | int size = 11; 139 | KeyType array[8] = { 22, 41, 53, 46, 30, 13, 12, 67 }; 140 | KeyType key; 141 | 142 | //初始化哈希表 143 | printf("初始化哈希表\n"); 144 | if (SUCCESS == InitHashTable(H, hashsize[index++])) printf("初始化成功\n"); 145 | 146 | //插入哈希表 147 | printf("插入哈希表\n"); 148 | for (i = 0; i <= 7; i++) { 149 | key = array[i]; 150 | InsertHash(H, key); 151 | printHash(H); 152 | } 153 | 154 | //删除哈希表 155 | printf("删除哈希表中key为12的元素\n"); 156 | int p, c; 157 | if (SUCCESS == DeleteHash(H, 12)) { 158 | printf("删除成功,此时哈希表为:\n"); 159 | printHash(H); 160 | } 161 | 162 | //查询哈希表 163 | printf("查询哈希表中key为67的元素\n"); 164 | if (SUCCESS == SearchHash(H, 67, p, c)) printf("查询成功\n"); 165 | 166 | //再次插入,测试哈希表的重建 167 | printf("再次插入,测试哈希表的重建:\n"); 168 | KeyType array1[8] = { 27, 47, 57, 47, 37, 17, 93, 67 }; 169 | for (i = 0; i <= 7; i++) { 170 | key = array1[i]; 171 | InsertHash(H, key); 172 | printHash(H); 173 | } 174 | 175 | getchar(); 176 | return 0; 177 | } -------------------------------------------------------------------------------- /算法库/DataStructure/LinkList.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @author huihut 3 | * @E-mail:huihut@outlook.com 4 | * @version 创建时间:2016年9月18日 5 | * 说明:本程序实现了一个单链表。 6 | */ 7 | 8 | #include "stdio.h" 9 | #include "stdlib.h" 10 | #include "malloc.h" 11 | 12 | //5个常量定义 13 | #define TRUE 1 14 | #define FALSE 0 15 | #define OK 1 16 | #define ERROR 0 17 | #define OVERFLOW -1 18 | 19 | //类型定义 20 | typedef int Status; 21 | typedef int ElemType; 22 | 23 | //测试程序长度定义 24 | #define LONGTH 5 25 | 26 | //链表的类型 27 | typedef struct LNode { 28 | ElemType data; 29 | struct LNode *next; 30 | } LNode, *LinkList; 31 | 32 | //创建包含n个元素的链表L,元素值存储在data数组中 33 | Status create(LinkList &L, ElemType *data, int n) { 34 | LNode *p, *q; 35 | int i; 36 | if (n < 0) return ERROR; 37 | L = NULL; 38 | p = L; 39 | for (i = 0; i < n; i++) 40 | { 41 | q = (LNode *)malloc(sizeof(LNode)); 42 | if (NULL == q) return OVERFLOW; 43 | q->data = data[i]; 44 | q->next = NULL; 45 | if (NULL == p) L = q; 46 | else p->next = q; 47 | p = q; 48 | } 49 | return OK; 50 | } 51 | 52 | //e从链表末尾入链表 53 | Status EnQueue_LQ(LinkList &L, ElemType &e) { 54 | LinkList p, q; 55 | 56 | if (NULL == (q = (LNode *)malloc(sizeof(LNode)))) return OVERFLOW; 57 | q->data = e; 58 | q->next = NULL; 59 | if (NULL == L) L = q; 60 | else 61 | { 62 | p = L; 63 | while (p->next != NULL) 64 | { 65 | p = p->next; 66 | } 67 | p->next = q; 68 | } 69 | return OK; 70 | } 71 | 72 | 73 | //从链表头节点出链表到e 74 | Status DeQueue_LQ(LinkList &L, ElemType &e) { 75 | if (NULL == L) return ERROR; 76 | LinkList p; 77 | p = L; 78 | e = p->data; 79 | L = L->next; 80 | free(p); 81 | return OK; 82 | } 83 | 84 | //遍历调用 85 | Status visit(ElemType e) { 86 | printf("%d\t", e); 87 | return OK; 88 | } 89 | 90 | //遍历单链表 91 | void ListTraverse_L(LinkList L, Status(*visit)(ElemType e)) 92 | { 93 | if (NULL == L) return; 94 | for (LinkList p = L; NULL != p; p = p->next) { 95 | visit(p->data); 96 | } 97 | } 98 | 99 | int main() { 100 | int i; 101 | ElemType e, data[LONGTH] = { 1, 2, 3, 4, 5 }; 102 | LinkList L; 103 | 104 | //显示测试值 105 | printf("---【单链表】---\n"); 106 | printf("待测试元素为:\n"); 107 | for (i = 0; i < LONGTH; i++) printf("%d\t", data[i]); 108 | printf("\n"); 109 | 110 | //创建链表L 111 | printf("创建链表L\n"); 112 | if (ERROR == create(L, data, LONGTH)) 113 | { 114 | printf("创建链表L失败\n"); 115 | return -1; 116 | } 117 | printf("成功创建包含%d个元素的链表L\n元素值存储在data数组中\n", LONGTH); 118 | 119 | //遍历单链表 120 | printf("此时链表中元素为:\n"); 121 | ListTraverse_L(L, visit); 122 | 123 | //从链表头节点出链表到e 124 | printf("\n出链表到e\n"); 125 | DeQueue_LQ(L, e); 126 | printf("出链表的元素为:%d\n", e); 127 | printf("此时链表中元素为:\n"); 128 | 129 | //遍历单链表 130 | ListTraverse_L(L, visit); 131 | 132 | //e从链表末尾入链表 133 | printf("\ne入链表\n"); 134 | EnQueue_LQ(L, e); 135 | printf("入链表的元素为:%d\n", e); 136 | printf("此时链表中元素为:\n"); 137 | 138 | //遍历单链表 139 | ListTraverse_L(L, visit); 140 | printf("\n"); 141 | 142 | getchar(); 143 | return 0; 144 | } -------------------------------------------------------------------------------- /算法库/DataStructure/LinkList_with_head.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @author huihut 3 | * @E-mail:huihut@outlook.com 4 | * @version 创建时间:2016年9月23日 5 | * 说明:本程序实现了一个具有头结点的单链表。 6 | */ 7 | 8 | #include "stdio.h" 9 | #include "stdlib.h" 10 | #include "malloc.h" 11 | 12 | //5个常量定义 13 | #define TRUE 1 14 | #define FALSE 0 15 | #define OK 1 16 | #define ERROR 0 17 | #define OVERFLOW -1 18 | 19 | //类型定义 20 | typedef int Status; 21 | typedef int ElemType; 22 | 23 | //测试程序长度定义 24 | #define LONGTH 5 25 | 26 | //链表的类型 27 | typedef struct LNode { 28 | ElemType data; 29 | struct LNode *next; 30 | } LNode, *LinkList; 31 | 32 | //创建包含n个元素的链表L,元素值存储在data数组中 33 | Status create(LinkList &L, ElemType *data, int n) { 34 | LNode *p, *q; 35 | int i; 36 | if (n < 0) return ERROR; 37 | p = L = NULL; 38 | 39 | q = (LNode *)malloc(sizeof(LNode)); 40 | if (NULL == q) return OVERFLOW; 41 | q->next = NULL; 42 | p = L = q; 43 | 44 | for (i = 0; i < n; i++) 45 | { 46 | q = (LNode *)malloc(sizeof(LNode)); 47 | if (NULL == q) return OVERFLOW; 48 | q->data = data[i]; 49 | q->next = NULL; 50 | p->next = q; 51 | p = q; 52 | } 53 | return OK; 54 | } 55 | 56 | //e从链表末尾入链表 57 | Status EnQueue_LQ(LinkList &L, ElemType &e) { 58 | LinkList p, q; 59 | 60 | if (NULL == (q = (LNode *)malloc(sizeof(LNode)))) return OVERFLOW; 61 | q->data = e; 62 | q->next = NULL; 63 | if (NULL == L) 64 | { 65 | L = (LNode *)malloc(sizeof(LNode)); 66 | if (NULL == L) return OVERFLOW; 67 | L->next = q; 68 | } 69 | else if (NULL == L->next) 70 | { 71 | L->next = q; 72 | } 73 | else 74 | { 75 | p = L; 76 | while (p->next != NULL) 77 | { 78 | p = p->next; 79 | } 80 | p->next = q; 81 | } 82 | return OK; 83 | } 84 | 85 | 86 | //从链表头节点出链表到e 87 | Status DeQueue_LQ(LinkList &L, ElemType &e) { 88 | if (NULL == L || NULL == L->next) return ERROR; 89 | LinkList p; 90 | p = L->next; 91 | e = p->data; 92 | L->next = p->next; 93 | free(p); 94 | return OK; 95 | } 96 | 97 | //遍历调用 98 | Status visit(ElemType e) { 99 | printf("%d\t", e); 100 | return OK; 101 | } 102 | 103 | //遍历单链表 104 | void ListTraverse_L(LinkList L, Status(*visit)(ElemType e)) 105 | { 106 | if (NULL == L || NULL == L->next) return; 107 | for (LinkList p = L->next; NULL != p; p = p->next) { 108 | visit(p->data); 109 | } 110 | } 111 | 112 | int main() { 113 | int i; 114 | ElemType e, data[LONGTH] = { 1, 2, 3, 4, 5 }; 115 | LinkList L; 116 | 117 | //显示测试值 118 | printf("---【有头结点的单链表】---\n"); 119 | printf("待测试元素为:\n"); 120 | for (i = 0; i < LONGTH; i++) printf("%d\t", data[i]); 121 | printf("\n"); 122 | 123 | //创建链表L 124 | printf("创建链表L\n"); 125 | if (ERROR == create(L, data, LONGTH)) 126 | { 127 | printf("创建链表L失败\n"); 128 | return -1; 129 | } 130 | printf("成功创建包含1个头结点、%d个元素的链表L\n元素值存储在data数组中\n", LONGTH); 131 | 132 | //遍历单链表 133 | printf("此时链表中元素为:\n"); 134 | ListTraverse_L(L, visit); 135 | 136 | //从链表头节点出链表到e 137 | printf("\n出链表到e\n"); 138 | DeQueue_LQ(L, e); 139 | printf("出链表的元素为:%d\n", e); 140 | printf("此时链表中元素为:\n"); 141 | 142 | //遍历单链表 143 | ListTraverse_L(L, visit); 144 | 145 | //e从链表末尾入链表 146 | printf("\ne入链表\n"); 147 | EnQueue_LQ(L, e); 148 | printf("入链表的元素为:%d\n", e); 149 | printf("此时链表中元素为:\n"); 150 | 151 | //遍历单链表 152 | ListTraverse_L(L, visit); 153 | printf("\n"); 154 | 155 | getchar(); 156 | return 0; 157 | } -------------------------------------------------------------------------------- /算法库/DataStructure/README.md: -------------------------------------------------------------------------------- 1 | ## 〽️ 数据结构 2 | 3 | ### 顺序结构 4 | 5 | #### 顺序栈(Sequence Stack) 6 | 7 | [SqStack.cpp](DataStructure/SqStack.cpp) 8 | 9 | 顺序栈数据结构和图片 10 | 11 | ```cpp 12 | typedef struct { 13 | ElemType *elem; 14 | int top; 15 | int size; 16 | int increment; 17 | } SqStack; 18 | ``` 19 | 20 | ![](https://gitee.com/huihut/interview/raw/master/images/SqStack.png) 21 | 22 | #### 队列(Sequence Queue) 23 | 24 | 队列数据结构 25 | 26 | ```cpp 27 | typedef struct { 28 | ElemType * elem; 29 | int front; 30 | int rear; 31 | int maxSize; 32 | }SqQueue; 33 | ``` 34 | 35 | ##### 非循环队列 36 | 37 | 非循环队列图片 38 | 39 | ![](https://gitee.com/huihut/interview/raw/master/images/SqQueue.png) 40 | 41 | `SqQueue.rear++` 42 | 43 | ##### 循环队列 44 | 45 | 循环队列图片 46 | 47 | ![](https://gitee.com/huihut/interview/raw/master/images/SqLoopStack.png) 48 | 49 | `SqQueue.rear = (SqQueue.rear + 1) % SqQueue.maxSize` 50 | 51 | #### 顺序表(Sequence List) 52 | 53 | [SqList.cpp](DataStructure/SqList.cpp) 54 | 55 | 顺序表数据结构和图片 56 | 57 | ```cpp 58 | typedef struct { 59 | ElemType *elem; 60 | int length; 61 | int size; 62 | int increment; 63 | } SqList; 64 | ``` 65 | 66 | ![](https://gitee.com/huihut/interview/raw/master/images/SqList.png) 67 | 68 | 69 | ### 链式结构 70 | 71 | [LinkList.cpp](DataStructure/LinkList.cpp) 72 | 73 | [LinkList_with_head.cpp](DataStructure/LinkList_with_head.cpp) 74 | 75 | 链式数据结构 76 | 77 | ```cpp 78 | typedef struct LNode { 79 | ElemType data; 80 | struct LNode *next; 81 | } LNode, *LinkList; 82 | ``` 83 | 84 | #### 链队列(Link Queue) 85 | 86 | 链队列图片 87 | 88 | ![](https://gitee.com/huihut/interview/raw/master/images/LinkQueue.png) 89 | 90 | #### 线性表的链式表示 91 | 92 | ##### 单链表(Link List) 93 | 94 | 单链表图片 95 | 96 | ![](https://gitee.com/huihut/interview/raw/master/images/LinkList.png) 97 | 98 | ##### 双向链表(Du-Link-List) 99 | 100 | 双向链表图片 101 | 102 | ![](https://gitee.com/huihut/interview/raw/master/images/DuLinkList.png) 103 | 104 | ##### 循环链表(Cir-Link-List) 105 | 106 | 循环链表图片 107 | 108 | ![](https://gitee.com/huihut/interview/raw/master/images/CirLinkList.png) 109 | 110 | ### 哈希表 111 | 112 | [HashTable.cpp](DataStructure/HashTable.cpp) 113 | 114 | #### 概念 115 | 116 | 哈希函数:`H(key): K -> D , key ∈ K` 117 | 118 | #### 构造方法 119 | 120 | * 直接定址法 121 | * 除留余数法 122 | * 数字分析法 123 | * 折叠法 124 | * 平方取中法 125 | 126 | #### 冲突处理方法 127 | 128 | * 链地址法:key 相同的用单链表链接 129 | * 开放定址法 130 | * 线性探测法:key 相同 -> 放到 key 的下一个位置,`Hi = (H(key) + i) % m` 131 | * 二次探测法:key 相同 -> 放到 `Di = 1^2, -1^2, ..., ±(k)^2,(k<=m/2)` 132 | * 随机探测法:`H = (H(key) + 伪随机数) % m` 133 | 134 | #### 线性探测的哈希表数据结构 135 | 136 | 线性探测的哈希表数据结构和图片 137 | 138 | ```cpp 139 | typedef char KeyType; 140 | 141 | typedef struct { 142 | KeyType key; 143 | }RcdType; 144 | 145 | typedef struct { 146 | RcdType *rcd; 147 | int size; 148 | int count; 149 | bool *tag; 150 | }HashTable; 151 | ``` 152 | 153 | ![](https://gitee.com/huihut/interview/raw/master/images/HashTable.png) 154 | 155 | ### 递归 156 | 157 | #### 概念 158 | 159 | 函数直接或间接地调用自身 160 | 161 | #### 递归与分治 162 | 163 | * 分治法 164 | * 问题的分解 165 | * 问题规模的分解 166 | * 折半查找(递归) 167 | * 归并排序(递归) 168 | * 快速排序(递归) 169 | 170 | #### 递归与迭代 171 | 172 | * 迭代:反复利用变量旧值推出新值 173 | * 折半查找(迭代) 174 | * 归并排序(迭代) 175 | 176 | #### 广义表 177 | 178 | ##### 头尾链表存储表示 179 | 180 | 广义表的头尾链表存储表示和图片 181 | 182 | ```cpp 183 | // 广义表的头尾链表存储表示 184 | typedef enum {ATOM, LIST} ElemTag; 185 | // ATOM==0:原子,LIST==1:子表 186 | typedef struct GLNode { 187 | ElemTag tag; 188 | // 公共部分,用于区分原子结点和表结点 189 | union { 190 | // 原子结点和表结点的联合部分 191 | AtomType atom; 192 | // atom 是原子结点的值域,AtomType 由用户定义 193 | struct { 194 | struct GLNode *hp, *tp; 195 | } ptr; 196 | // ptr 是表结点的指针域,prt.hp 和 ptr.tp 分别指向表头和表尾 197 | } a; 198 | } *GList, GLNode; 199 | ``` 200 | 201 | ![](https://gitee.com/huihut/interview/raw/master/images/GeneralizedList1.png) 202 | 203 | ##### 扩展线性链表存储表示 204 | 205 | 扩展线性链表存储表示和图片 206 | 207 | ```cpp 208 | // 广义表的扩展线性链表存储表示 209 | typedef enum {ATOM, LIST} ElemTag; 210 | // ATOM==0:原子,LIST==1:子表 211 | typedef struct GLNode1 { 212 | ElemTag tag; 213 | // 公共部分,用于区分原子结点和表结点 214 | union { 215 | // 原子结点和表结点的联合部分 216 | AtomType atom; // 原子结点的值域 217 | struct GLNode1 *hp; // 表结点的表头指针 218 | } a; 219 | struct GLNode1 *tp; 220 | // 相当于线性链表的 next,指向下一个元素结点 221 | } *GList1, GLNode1; 222 | ``` 223 | 224 | ![](https://gitee.com/huihut/interview/raw/master/images/GeneralizedList2.png) 225 | 226 | ### 二叉树 227 | 228 | [BinaryTree.cpp](DataStructure/BinaryTree.cpp) 229 | 230 | #### 性质 231 | 232 | 1. 非空二叉树第 i 层最多 2(i-1) 个结点 (i >= 1) 233 | 2. 深度为 k 的二叉树最多 2k - 1 个结点 (k >= 1) 234 | 3. 度为 0 的结点数为 n0,度为 2 的结点数为 n2,则 n0 = n2 + 1 235 | 4. 有 n 个结点的完全二叉树深度 k = ⌊ log2(n) ⌋ + 1 236 | 5. 对于含 n 个结点的完全二叉树中编号为 i (1 <= i <= n) 的结点 237 | 1. 若 i = 1,为根,否则双亲为 ⌊ i / 2 ⌋ 238 | 2. 若 2i > n,则 i 结点没有左孩子,否则孩子编号为 2i 239 | 3. 若 2i + 1 > n,则 i 结点没有右孩子,否则孩子编号为 2i + 1 240 | 241 | #### 存储结构 242 | 243 | 二叉树数据结构 244 | 245 | ```cpp 246 | typedef struct BiTNode 247 | { 248 | TElemType data; 249 | struct BiTNode *lchild, *rchild; 250 | }BiTNode, *BiTree; 251 | ``` 252 | 253 | ##### 顺序存储 254 | 255 | 二叉树顺序存储图片 256 | 257 | ![](https://gitee.com/huihut/interview/raw/master/images/SqBinaryTree.png) 258 | 259 | ##### 链式存储 260 | 261 | 二叉树链式存储图片 262 | 263 | ![](https://gitee.com/huihut/interview/raw/master/images/LinkBinaryTree.png) 264 | 265 | #### 遍历方式 266 | 267 | * 先序遍历 268 | * 中序遍历 269 | * 后续遍历 270 | * 层次遍历 271 | 272 | #### 分类 273 | 274 | * 满二叉树 275 | * 完全二叉树(堆) 276 | * 大顶堆:根 >= 左 && 根 >= 右 277 | * 小顶堆:根 <= 左 && 根 <= 右 278 | * 二叉查找树(二叉排序树):左 < 根 < 右 279 | * 平衡二叉树(AVL树):| 左子树树高 - 右子树树高 | <= 1 280 | * 最小失衡树:平衡二叉树插入新结点导致失衡的子树:调整: 281 | * LL型:根的左孩子右旋 282 | * RR型:根的右孩子左旋 283 | * LR型:根的左孩子左旋,再右旋 284 | * RL型:右孩子的左子树,先右旋,再左旋 285 | 286 | ### 其他树及森林 287 | 288 | #### 树的存储结构 289 | 290 | * 双亲表示法 291 | * 双亲孩子表示法 292 | * 孩子兄弟表示法 293 | 294 | #### 并查集 295 | 296 | 一种不相交的子集所构成的集合 S = {S1, S2, ..., Sn} 297 | 298 | #### 平衡二叉树(AVL树) 299 | 300 | ##### 性质 301 | 302 | * | 左子树树高 - 右子树树高 | <= 1 303 | * 平衡二叉树必定是二叉搜索树,反之则不一定 304 | * 最小二叉平衡树的节点的公式:`F(n)=F(n-1)+F(n-2)+1` (1 是根节点,F(n-1) 是左子树的节点数量,F(n-2) 是右子树的节点数量) 305 | 306 | 平衡二叉树图片 307 | 308 | ![](https://gitee.com/huihut/interview/raw/master/images/Self-balancingBinarySearchTree.png) 309 | 310 | ##### 最小失衡树 311 | 312 | 平衡二叉树插入新结点导致失衡的子树 313 | 314 | 调整: 315 | 316 | * LL 型:根的左孩子右旋 317 | * RR 型:根的右孩子左旋 318 | * LR 型:根的左孩子左旋,再右旋 319 | * RL 型:右孩子的左子树,先右旋,再左旋 320 | 321 | #### 红黑树 322 | 323 | [RedBlackTree.cpp](DataStructure/RedBlackTree.cpp) 324 | 325 | ##### 红黑树的特征是什么? 326 | 327 | 1. 节点是红色或黑色。 328 | 2. 根是黑色。 329 | 3. 所有叶子都是黑色(叶子是 NIL 节点)。 330 | 4. 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)(新增节点的父节点必须相同) 331 | 5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。(新增节点必须为红) 332 | 333 | ##### 调整 334 | 335 | 1. 变色 336 | 2. 左旋 337 | 3. 右旋 338 | 339 | ##### 应用 340 | 341 | * 关联数组:如 STL 中的 map、set 342 | 343 | ##### 红黑树、B 树、B+ 树的区别? 344 | 345 | * 红黑树的深度比较大,而 B 树和 B+ 树的深度则相对要小一些 346 | * B+ 树则将数据都保存在叶子节点,同时通过链表的形式将他们连接在一起。 347 | 348 | #### B 树(B-tree)、B+ 树(B+-tree) 349 | 350 | B 树、B+ 树图片 351 | 352 | ![B 树(B-tree)、B+ 树(B+-tree)](https://i.stack.imgur.com/l6UyF.png) 353 | 354 | ##### 特点 355 | 356 | * 一般化的二叉查找树(binary search tree) 357 | * “矮胖”,内部(非叶子)节点可以拥有可变数量的子节点(数量范围预先定义好) 358 | 359 | ##### 应用 360 | 361 | * 大部分文件系统、数据库系统都采用B树、B+树作为索引结构 362 | 363 | ##### 区别 364 | 365 | * B+树中只有叶子节点会带有指向记录的指针(ROWID),而B树则所有节点都带有,在内部节点出现的索引项不会再出现在叶子节点中。 366 | * B+树中所有叶子节点都是通过指针连接在一起,而B树不会。 367 | 368 | ##### B树的优点 369 | 370 | 对于在内部节点的数据,可直接得到,不必根据叶子节点来定位。 371 | 372 | ##### B+树的优点 373 | 374 | * 非叶子节点不会带上 ROWID,这样,一个块中可以容纳更多的索引项,一是可以降低树的高度。二是一个内部节点可以定位更多的叶子节点。 375 | * 叶子节点之间通过指针来连接,范围扫描将十分简单,而对于B树来说,则需要在叶子节点和内部节点不停的往返移动。 376 | 377 | > B 树、B+ 树区别来自:[differences-between-b-trees-and-b-trees](https://stackoverflow.com/questions/870218/differences-between-b-trees-and-b-trees)、[B树和B+树的区别](https://www.cnblogs.com/ivictor/p/5849061.html) 378 | 379 | #### 八叉树 380 | 381 | 八叉树图片 382 | 383 | ![](https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Octree2.png/400px-Octree2.png) 384 | 385 | 八叉树(octree),或称八元树,是一种用于描述三维空间(划分空间)的树状数据结构。八叉树的每个节点表示一个正方体的体积元素,每个节点有八个子节点,这八个子节点所表示的体积元素加在一起就等于父节点的体积。一般中心点作为节点的分叉中心。 386 | 387 | ##### 用途 388 | 389 | * 三维计算机图形 390 | * 最邻近搜索 391 | -------------------------------------------------------------------------------- /算法库/DataStructure/RedBlackTree.cpp: -------------------------------------------------------------------------------- 1 | #define BLACK 1 2 | #define RED 0 3 | #include 4 | 5 | using namespace std; 6 | 7 | class bst { 8 | private: 9 | 10 | struct Node { 11 | int value; 12 | bool color; 13 | Node *leftTree, *rightTree, *parent; 14 | 15 | Node() : value(0), color(RED), leftTree(NULL), rightTree(NULL), parent(NULL) { } 16 | 17 | Node* grandparent() { 18 | if (parent == NULL) { 19 | return NULL; 20 | } 21 | return parent->parent; 22 | } 23 | 24 | Node* uncle() { 25 | if (grandparent() == NULL) { 26 | return NULL; 27 | } 28 | if (parent == grandparent()->rightTree) 29 | return grandparent()->leftTree; 30 | else 31 | return grandparent()->rightTree; 32 | } 33 | 34 | Node* sibling() { 35 | if (parent->leftTree == this) 36 | return parent->rightTree; 37 | else 38 | return parent->leftTree; 39 | } 40 | }; 41 | 42 | void rotate_right(Node *p) { 43 | Node *gp = p->grandparent(); 44 | Node *fa = p->parent; 45 | Node *y = p->rightTree; 46 | 47 | fa->leftTree = y; 48 | 49 | if (y != NIL) 50 | y->parent = fa; 51 | p->rightTree = fa; 52 | fa->parent = p; 53 | 54 | if (root == fa) 55 | root = p; 56 | p->parent = gp; 57 | 58 | if (gp != NULL) { 59 | if (gp->leftTree == fa) 60 | gp->leftTree = p; 61 | else 62 | gp->rightTree = p; 63 | } 64 | 65 | } 66 | 67 | void rotate_left(Node *p) { 68 | if (p->parent == NULL) { 69 | root = p; 70 | return; 71 | } 72 | Node *gp = p->grandparent(); 73 | Node *fa = p->parent; 74 | Node *y = p->leftTree; 75 | 76 | fa->rightTree = y; 77 | 78 | if (y != NIL) 79 | y->parent = fa; 80 | p->leftTree = fa; 81 | fa->parent = p; 82 | 83 | if (root == fa) 84 | root = p; 85 | p->parent = gp; 86 | 87 | if (gp != NULL) { 88 | if (gp->leftTree == fa) 89 | gp->leftTree = p; 90 | else 91 | gp->rightTree = p; 92 | } 93 | } 94 | 95 | void inorder(Node *p) { 96 | if (p == NIL) 97 | return; 98 | 99 | if (p->leftTree) 100 | inorder(p->leftTree); 101 | 102 | cout << p->value << " "; 103 | 104 | if (p->rightTree) 105 | inorder(p->rightTree); 106 | } 107 | 108 | string outputColor(bool color) { 109 | return color ? "BLACK" : "RED"; 110 | } 111 | 112 | Node* getSmallestChild(Node *p) { 113 | if (p->leftTree == NIL) 114 | return p; 115 | return getSmallestChild(p->leftTree); 116 | } 117 | 118 | bool delete_child(Node *p, int data) { 119 | if (p->value > data) { 120 | if (p->leftTree == NIL) { 121 | return false; 122 | } 123 | return delete_child(p->leftTree, data); 124 | } 125 | else if (p->value < data) { 126 | if (p->rightTree == NIL) { 127 | return false; 128 | } 129 | return delete_child(p->rightTree, data); 130 | } 131 | else if (p->value == data) { 132 | if (p->rightTree == NIL) { 133 | delete_one_child(p); 134 | return true; 135 | } 136 | Node *smallest = getSmallestChild(p->rightTree); 137 | swap(p->value, smallest->value); 138 | delete_one_child(smallest); 139 | 140 | return true; 141 | } 142 | else { 143 | return false; 144 | } 145 | } 146 | 147 | void delete_one_child(Node *p) { 148 | Node *child = p->leftTree == NIL ? p->rightTree : p->leftTree; 149 | if (p->parent == NULL && p->leftTree == NIL && p->rightTree == NIL) { 150 | p = NULL; 151 | root = p; 152 | return; 153 | } 154 | 155 | if (p->parent == NULL) { 156 | delete p; 157 | child->parent = NULL; 158 | root = child; 159 | root->color = BLACK; 160 | return; 161 | } 162 | 163 | if (p->parent->leftTree == p) { 164 | p->parent->leftTree = child; 165 | } 166 | else { 167 | p->parent->rightTree = child; 168 | } 169 | child->parent = p->parent; 170 | 171 | if (p->color == BLACK) { 172 | if (child->color == RED) { 173 | child->color = BLACK; 174 | } 175 | else 176 | delete_case(child); 177 | } 178 | 179 | delete p; 180 | } 181 | 182 | void delete_case(Node *p) { 183 | if (p->parent == NULL) { 184 | p->color = BLACK; 185 | return; 186 | } 187 | if (p->sibling()->color == RED) { 188 | p->parent->color = RED; 189 | p->sibling()->color = BLACK; 190 | if (p == p->parent->leftTree) 191 | rotate_left(p->sibling()); 192 | else 193 | rotate_right(p->sibling()); 194 | } 195 | if (p->parent->color == BLACK && p->sibling()->color == BLACK 196 | && p->sibling()->leftTree->color == BLACK && p->sibling()->rightTree->color == BLACK) { 197 | p->sibling()->color = RED; 198 | delete_case(p->parent); 199 | } 200 | else if (p->parent->color == RED && p->sibling()->color == BLACK 201 | && p->sibling()->leftTree->color == BLACK && p->sibling()->rightTree->color == BLACK) { 202 | p->sibling()->color = RED; 203 | p->parent->color = BLACK; 204 | } 205 | else { 206 | if (p->sibling()->color == BLACK) { 207 | if (p == p->parent->leftTree && p->sibling()->leftTree->color == RED 208 | && p->sibling()->rightTree->color == BLACK) { 209 | p->sibling()->color = RED; 210 | p->sibling()->leftTree->color = BLACK; 211 | rotate_right(p->sibling()->leftTree); 212 | } 213 | else if (p == p->parent->rightTree && p->sibling()->leftTree->color == BLACK 214 | && p->sibling()->rightTree->color == RED) { 215 | p->sibling()->color = RED; 216 | p->sibling()->rightTree->color = BLACK; 217 | rotate_left(p->sibling()->rightTree); 218 | } 219 | } 220 | p->sibling()->color = p->parent->color; 221 | p->parent->color = BLACK; 222 | if (p == p->parent->leftTree) { 223 | p->sibling()->rightTree->color = BLACK; 224 | rotate_left(p->sibling()); 225 | } 226 | else { 227 | p->sibling()->leftTree->color = BLACK; 228 | rotate_right(p->sibling()); 229 | } 230 | } 231 | } 232 | 233 | void insert(Node *p, int data) { 234 | if (p->value >= data) { 235 | if (p->leftTree != NIL) 236 | insert(p->leftTree, data); 237 | else { 238 | Node *tmp = new Node(); 239 | tmp->value = data; 240 | tmp->leftTree = tmp->rightTree = NIL; 241 | tmp->parent = p; 242 | p->leftTree = tmp; 243 | insert_case(tmp); 244 | } 245 | } 246 | else { 247 | if (p->rightTree != NIL) 248 | insert(p->rightTree, data); 249 | else { 250 | Node *tmp = new Node(); 251 | tmp->value = data; 252 | tmp->leftTree = tmp->rightTree = NIL; 253 | tmp->parent = p; 254 | p->rightTree = tmp; 255 | insert_case(tmp); 256 | } 257 | } 258 | } 259 | 260 | void insert_case(Node *p) { 261 | if (p->parent == NULL) { 262 | root = p; 263 | p->color = BLACK; 264 | return; 265 | } 266 | if (p->parent->color == RED) { 267 | if (p->uncle()->color == RED) { 268 | p->parent->color = p->uncle()->color = BLACK; 269 | p->grandparent()->color = RED; 270 | insert_case(p->grandparent()); 271 | } 272 | else { 273 | if (p->parent->rightTree == p && p->grandparent()->leftTree == p->parent) { 274 | rotate_left(p); 275 | rotate_right(p); 276 | p->color = BLACK; 277 | p->leftTree->color = p->rightTree->color = RED; 278 | } 279 | else if (p->parent->leftTree == p && p->grandparent()->rightTree == p->parent) { 280 | rotate_right(p); 281 | rotate_left(p); 282 | p->color = BLACK; 283 | p->leftTree->color = p->rightTree->color = RED; 284 | } 285 | else if (p->parent->leftTree == p && p->grandparent()->leftTree == p->parent) { 286 | p->parent->color = BLACK; 287 | p->grandparent()->color = RED; 288 | rotate_right(p->parent); 289 | } 290 | else if (p->parent->rightTree == p && p->grandparent()->rightTree == p->parent) { 291 | p->parent->color = BLACK; 292 | p->grandparent()->color = RED; 293 | rotate_left(p->parent); 294 | } 295 | } 296 | } 297 | } 298 | 299 | void DeleteTree(Node *p) { 300 | if (!p || p == NIL) { 301 | return; 302 | } 303 | DeleteTree(p->leftTree); 304 | DeleteTree(p->rightTree); 305 | delete p; 306 | } 307 | public: 308 | 309 | bst() { 310 | NIL = new Node(); 311 | NIL->color = BLACK; 312 | root = NULL; 313 | } 314 | 315 | ~bst() { 316 | if (root) 317 | DeleteTree(root); 318 | delete NIL; 319 | } 320 | 321 | void inorder() { 322 | if (root == NULL) 323 | return; 324 | inorder(root); 325 | cout << endl; 326 | } 327 | 328 | void insert(int x) { 329 | if (root == NULL) { 330 | root = new Node(); 331 | root->color = BLACK; 332 | root->leftTree = root->rightTree = NIL; 333 | root->value = x; 334 | } 335 | else { 336 | insert(root, x); 337 | } 338 | } 339 | 340 | bool delete_value(int data) { 341 | return delete_child(root, data); 342 | } 343 | private: 344 | Node *root, *NIL; 345 | }; 346 | 347 | int main() 348 | { 349 | cout << "---【红黑树】---" << endl; 350 | // 创建红黑树 351 | bst tree; 352 | 353 | // 插入元素 354 | tree.insert(2); 355 | tree.insert(9); 356 | tree.insert(-10); 357 | tree.insert(0); 358 | tree.insert(33); 359 | tree.insert(-19); 360 | 361 | // 顺序打印红黑树 362 | cout << "插入元素后的红黑树:" << endl; 363 | tree.inorder(); 364 | 365 | // 删除元素 366 | tree.delete_value(2); 367 | 368 | // 顺序打印红黑树 369 | cout << "删除元素 2 后的红黑树:" << endl; 370 | tree.inorder(); 371 | 372 | // 析构 373 | tree.~bst(); 374 | 375 | getchar(); 376 | return 0; 377 | } -------------------------------------------------------------------------------- /算法库/DataStructure/SqList.cpp: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @author huihut 4 | * @E-mail:huihut@outlook.com 5 | * @version 创建时间:2016年9月9日 6 | * 说明:本程序实现了一个顺序表。 7 | */ 8 | 9 | #include "stdio.h" 10 | #include "stdlib.h" 11 | #include "malloc.h" 12 | 13 | //5个常量定义 14 | #define TRUE 1 15 | #define FALSE 0 16 | #define OK 1 17 | #define ERROR 0 18 | #define OVERFLOW -1 19 | 20 | //测试程序长度定义 21 | #define LONGTH 5 22 | 23 | //类型定义 24 | typedef int Status; 25 | typedef int ElemType; 26 | 27 | //顺序栈的类型 28 | typedef struct { 29 | ElemType *elem; 30 | int length; 31 | int size; 32 | int increment; 33 | } SqList; 34 | 35 | //初始化顺序表L 36 | Status InitList_Sq(SqList &L, int size, int inc) { 37 | L.elem = (ElemType *)malloc(size * sizeof(ElemType)); 38 | if (NULL == L.elem) return OVERFLOW; 39 | L.length = 0; 40 | L.size = size; 41 | L.increment = inc; 42 | return OK; 43 | } 44 | 45 | //销毁顺序表L 46 | Status DestroyList_Sq(SqList &L) { 47 | free(L.elem); 48 | L.elem = NULL; 49 | return OK; 50 | } 51 | 52 | //将顺序表L清空 53 | Status ClearList_Sq(SqList &L) { 54 | if (0 != L.length) L.length = 0; 55 | return OK; 56 | } 57 | 58 | //若顺序表L为空表,则返回TRUE,否则FALSE 59 | Status ListEmpty_Sq(SqList L) { 60 | if (0 == L.length) return TRUE; 61 | return FALSE; 62 | } 63 | 64 | //返回顺序表L中元素个数 65 | int ListLength_Sq(SqList L) { 66 | return L.length; 67 | } 68 | 69 | // 用e返回顺序表L中第i个元素的值 70 | Status GetElem_Sq(SqList L, int i, ElemType &e) { 71 | e = L.elem[--i]; 72 | return OK; 73 | } 74 | 75 | 76 | // 在顺序表L顺序查找元素e,成功时返回该元素在表中第一次出现的位置,否则返回 - 1 77 | int Search_Sq(SqList L, ElemType e) { 78 | int i = 0; 79 | while (i < L.length && L.elem[i] != e) i++; 80 | if (i < L.length) return i; 81 | else return -1; 82 | } 83 | 84 | //遍历调用 85 | Status visit(ElemType e) { 86 | printf("%d\t", e); 87 | return OK; 88 | } 89 | 90 | //遍历顺序表L,依次对每个元素调用函数visit() 91 | Status ListTraverse_Sq(SqList L, Status(*visit)(ElemType e)) { 92 | if (0 == L.length) return ERROR; 93 | for (int i = 0; i < L.length; i++) { 94 | visit(L.elem[i]); 95 | } 96 | return OK; 97 | } 98 | 99 | //将顺序表L中第i个元素赋值为e 100 | Status PutElem_Sq(SqList &L, int i, ElemType e) { 101 | if (i > L.length) return ERROR; 102 | e = L.elem[--i]; 103 | return OK; 104 | 105 | } 106 | 107 | //在顺序表L表尾添加元素e 108 | Status Append_Sq(SqList &L, ElemType e) { 109 | if (L.length >= L.size) return ERROR; 110 | L.elem[L.length] = e; 111 | L.length++; 112 | return OK; 113 | } 114 | 115 | //删除顺序表L的表尾元素,并用参数e返回其值 116 | Status DeleteLast_Sq(SqList &L, ElemType &e) { 117 | if (0 == L.length) return ERROR; 118 | e = L.elem[L.length - 1]; 119 | L.length--; 120 | return OK; 121 | } 122 | 123 | int main() { 124 | //定义表L 125 | SqList L; 126 | 127 | //定义测量值 128 | int size, increment, i; 129 | 130 | //初始化测试值 131 | size = LONGTH; 132 | increment = LONGTH; 133 | ElemType e, eArray[LONGTH] = { 1, 2, 3, 4, 5 }; 134 | 135 | //显示测试值 136 | printf("---【顺序栈】---\n"); 137 | printf("表L的size为:%d\n表L的increment为:%d\n", size, increment); 138 | printf("待测试元素为:\n"); 139 | for (i = 0; i < LONGTH; i++) { 140 | printf("%d\t", eArray[i]); 141 | } 142 | printf("\n"); 143 | 144 | //初始化顺序表 145 | if (!InitList_Sq(L, size, increment)) { 146 | printf("初始化顺序表失败\n"); 147 | exit(0); 148 | } 149 | printf("已初始化顺序表\n"); 150 | 151 | //判空 152 | if (TRUE == ListEmpty_Sq(L)) printf("此表为空表\n"); 153 | else printf("此表不是空表\n"); 154 | 155 | //入表 156 | printf("将待测元素入表:\n"); 157 | for (i = 0; i < LONGTH; i++) { 158 | if (ERROR == Append_Sq(L, eArray[i])) printf("入表失败\n");; 159 | } 160 | printf("入表成功\n"); 161 | 162 | //遍历顺序表L 163 | printf("此时表内元素为:\n"); 164 | ListTraverse_Sq(L, visit); 165 | 166 | //出表 167 | printf("\n将表尾元素入表到e:\n"); 168 | if (ERROR == DeleteLast_Sq(L, e)) printf("出表失败\n"); 169 | printf("出表成功\n出表元素为%d\n", e); 170 | 171 | //遍历顺序表L 172 | printf("此时表内元素为:\n"); 173 | ListTraverse_Sq(L, visit); 174 | 175 | //销毁顺序表 176 | printf("\n销毁顺序表\n"); 177 | if (OK == DestroyList_Sq(L)) printf("销毁成功\n"); 178 | else printf("销毁失败\n"); 179 | 180 | getchar(); 181 | return 0; 182 | } -------------------------------------------------------------------------------- /算法库/DataStructure/SqStack.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @author huihut 3 | * @E-mail:huihut@outlook.com 4 | * @version 创建时间:2016年9月9日 5 | * 说明:本程序实现了一个顺序栈。 6 | * 功能:有初始化、销毁、判断空、清空、入栈、出栈、取元素的操作。 7 | */ 8 | 9 | #include "stdio.h" 10 | #include "stdlib.h" 11 | #include "malloc.h" 12 | 13 | //5个常量定义 14 | #define TRUE 1 15 | #define FALSE 0 16 | #define OK 1 17 | #define ERROR 0 18 | #define OVERFLOW -1 19 | 20 | //测试程序长度定义 21 | #define LONGTH 5 22 | 23 | //类型定义 24 | typedef int Status; 25 | typedef int ElemType; 26 | 27 | //顺序栈的类型 28 | typedef struct { 29 | ElemType *elem; 30 | int top; 31 | int size; 32 | int increment; 33 | } SqSrack; 34 | 35 | //初始化顺序栈 36 | Status InitStack_Sq(SqSrack &S, int size, int inc) { 37 | S.elem = (ElemType *)malloc(size * sizeof(ElemType)); 38 | if (NULL == S.elem) return OVERFLOW; 39 | S.top = 0; 40 | S.size = size; 41 | S.increment = inc; 42 | return OK; 43 | } 44 | 45 | //销毁顺序栈 46 | Status DestroyStack_Sq(SqSrack &S) { 47 | free(S.elem); 48 | S.elem = NULL; 49 | return OK; 50 | } 51 | 52 | //判断S是否空,若空则返回TRUE,否则返回FALSE 53 | Status StackEmpty_Sq(SqSrack S) { 54 | if (0 == S.top) return TRUE; 55 | return FALSE; 56 | } 57 | 58 | //清空栈S 59 | void ClearStack_Sq(SqSrack &S) { 60 | if (0 == S.top) return; 61 | S.size = 0; 62 | S.top = 0; 63 | } 64 | 65 | //元素e压入栈S 66 | Status Push_Sq(SqSrack &S, ElemType e) { 67 | ElemType *newbase; 68 | if (S.top >= S.size) { 69 | newbase = (ElemType *)realloc(S.elem, (S.size + S.increment) * sizeof(ElemType)); 70 | if (NULL == newbase) return OVERFLOW; 71 | S.elem = newbase; 72 | S.size += S.increment; 73 | } 74 | S.elem[S.top++] = e; 75 | return OK; 76 | } 77 | 78 | //取栈S的栈顶元素,并用e返回 79 | Status GetTop_Sq(SqSrack S, ElemType &e) { 80 | if (0 == S.top) return ERROR; 81 | e = S.elem[S.top - 1]; 82 | return e; 83 | } 84 | 85 | //栈S的栈顶元素出栈,并用e返回 86 | Status Pop_Sq(SqSrack &S, ElemType &e) { 87 | if (0 == S.top) return ERROR; 88 | e = S.elem[S.top - 1]; 89 | S.top--; 90 | return e; 91 | } 92 | 93 | int main() { 94 | //定义栈S 95 | SqSrack S; 96 | 97 | //定义测量值 98 | int size, increment, i; 99 | 100 | //初始化测试值 101 | size = LONGTH; 102 | increment = LONGTH; 103 | ElemType e, eArray[LONGTH] = { 1, 2, 3, 4, 5 }; 104 | 105 | //显示测试值 106 | printf("---【顺序栈】---\n"); 107 | printf("栈S的size为:%d\n栈S的increment为:%d\n", size, increment); 108 | printf("待测试元素为:\n"); 109 | for (i = 0; i < LONGTH; i++) { 110 | printf("%d\t", eArray[i]); 111 | } 112 | printf("\n"); 113 | 114 | //初始化顺序栈 115 | if (!InitStack_Sq(S, size, increment)) { 116 | printf("初始化顺序栈失败\n"); 117 | exit(0); 118 | } 119 | printf("已初始化顺序栈\n"); 120 | 121 | //入栈 122 | for (i = 0; i < S.size; i++) { 123 | if (!Push_Sq(S, eArray[i])) { 124 | printf("%d入栈失败\n", eArray[i]); 125 | exit(0); 126 | } 127 | } 128 | printf("已入栈\n"); 129 | 130 | //判断非空 131 | if (StackEmpty_Sq(S)) printf("S栈为空\n"); 132 | else printf("S栈非空\n"); 133 | 134 | //取栈S的栈顶元素 135 | printf("栈S的栈顶元素为:\n"); 136 | printf("%d\n", GetTop_Sq(S, e)); 137 | 138 | //栈S元素出栈 139 | printf("栈S元素出栈为:\n"); 140 | for (i = 0, e = 0; i < S.size; i++) { 141 | printf("%d\t", Pop_Sq(S, e)); 142 | } 143 | printf("\n"); 144 | 145 | //清空栈S 146 | ClearStack_Sq(S); 147 | printf("已清空栈S\n"); 148 | 149 | getchar(); 150 | return 0; 151 | } -------------------------------------------------------------------------------- /算法库/DesignPattern/AbstractFactoryPattern/Factory.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/20. 3 | // 4 | 5 | #include "Factory.h" 6 | #include "concrete_factory.h" 7 | 8 | Factory* Factory::CreateFactory(FACTORY_TYPE factory) 9 | { 10 | Factory *pFactory = nullptr; 11 | switch (factory) { 12 | case FACTORY_TYPE::BENZ_FACTORY: // Benz factory 13 | pFactory = new BenzFactory(); 14 | break; 15 | case FACTORY_TYPE::BMW_FACTORY: // BMW factory 16 | pFactory = new BmwFactory(); 17 | break; 18 | case FACTORY_TYPE::AUDI_FACTORY: // Audi factory 19 | pFactory = new AudiFactory(); 20 | break; 21 | default: 22 | break; 23 | } 24 | return pFactory; 25 | } -------------------------------------------------------------------------------- /算法库/DesignPattern/AbstractFactoryPattern/Factory.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/20. 3 | // 4 | 5 | #ifndef DESIGNPATTERN_FACTORY_H 6 | #define DESIGNPATTERN_FACTORY_H 7 | 8 | #include "product.h" 9 | 10 | // Abstract factory pattern 11 | class Factory { 12 | public: 13 | enum FACTORY_TYPE { 14 | BENZ_FACTORY, // Benz factory 15 | BMW_FACTORY, // BMW factory 16 | AUDI_FACTORY // Audi factory 17 | }; 18 | 19 | virtual ICar* CreateCar() = 0; // Production car 20 | virtual IBike* CreateBike() = 0; // Production bicycle 21 | static Factory * CreateFactory(FACTORY_TYPE factory); // Create factory 22 | }; 23 | 24 | #endif //DESIGNPATTERN_FACTORY_H 25 | -------------------------------------------------------------------------------- /算法库/DesignPattern/AbstractFactoryPattern/FactoryMain.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/20. 3 | // 4 | 5 | #include "Factory.h" 6 | #include "product.h" 7 | #include "FactoryMain.h" 8 | #include 9 | using namespace std; 10 | 11 | void FactoryMain() 12 | { 13 | // Benz 14 | Factory * pFactory = Factory::CreateFactory(Factory::FACTORY_TYPE::BENZ_FACTORY); 15 | ICar * pCar = pFactory->CreateCar(); 16 | IBike * pBike = pFactory->CreateBike(); 17 | 18 | cout << "Benz factory - Car: " << pCar->Name() << endl; 19 | cout << "Benz factory - Bike: " << pBike->Name() << endl; 20 | 21 | SAFE_DELETE(pCar); 22 | SAFE_DELETE(pBike); 23 | SAFE_DELETE(pFactory); 24 | 25 | // BMW 26 | pFactory = Factory::CreateFactory(Factory::FACTORY_TYPE::BMW_FACTORY); 27 | pCar = pFactory->CreateCar(); 28 | pBike = pFactory->CreateBike(); 29 | cout << "Bmw factory - Car: " << pCar->Name() << endl; 30 | cout << "Bmw factory - Bike: " << pBike->Name() << endl; 31 | 32 | SAFE_DELETE(pCar); 33 | SAFE_DELETE(pBike); 34 | SAFE_DELETE(pFactory); 35 | 36 | // Audi 37 | pFactory = Factory::CreateFactory(Factory::FACTORY_TYPE::AUDI_FACTORY); 38 | pCar = pFactory->CreateCar(); 39 | pBike = pFactory->CreateBike(); 40 | cout << "Audi factory - Car: " << pCar->Name() << endl; 41 | cout << "Audi factory - Bike: " << pBike->Name() << endl; 42 | 43 | SAFE_DELETE(pCar); 44 | SAFE_DELETE(pBike); 45 | SAFE_DELETE(pFactory); 46 | } -------------------------------------------------------------------------------- /算法库/DesignPattern/AbstractFactoryPattern/FactoryMain.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/20. 3 | // 4 | 5 | #ifndef DESIGNPATTERN_FACTORYMAIN_H 6 | #define DESIGNPATTERN_FACTORYMAIN_H 7 | 8 | #ifndef SAFE_DELETE 9 | #define SAFE_DELETE(p) { if(p) {delete(p); (p)=nullptr;}} 10 | #endif 11 | 12 | void FactoryMain(); 13 | 14 | #endif //DESIGNPATTERN_FACTORYMAIN_H 15 | -------------------------------------------------------------------------------- /算法库/DesignPattern/AbstractFactoryPattern/concrete_factory.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/20. 3 | // 4 | 5 | #ifndef DESIGNPATTERN_CONCRETE_FACTORY_H 6 | #define DESIGNPATTERN_CONCRETE_FACTORY_H 7 | 8 | #include "Factory.h" 9 | #include "concrete_product.h" 10 | 11 | // Benz factory 12 | class BenzFactory : public Factory 13 | { 14 | public: 15 | ICar* CreateCar() 16 | { 17 | return new BenzCar(); 18 | } 19 | IBike* CreateBike() 20 | { 21 | return new BenzBike(); 22 | } 23 | }; 24 | 25 | // BMW factory 26 | class BmwFactory : public Factory 27 | { 28 | public: 29 | ICar* CreateCar() { 30 | return new BmwCar(); 31 | } 32 | 33 | IBike* CreateBike() { 34 | return new BmwBike(); 35 | } 36 | }; 37 | 38 | // Audi factory 39 | class AudiFactory : public Factory 40 | { 41 | public: 42 | ICar* CreateCar() { 43 | return new AudiCar(); 44 | } 45 | 46 | IBike* CreateBike() { 47 | return new AudiBike(); 48 | } 49 | }; 50 | 51 | #endif //DESIGNPATTERN_CONCRETE_FACTORY_H 52 | -------------------------------------------------------------------------------- /算法库/DesignPattern/AbstractFactoryPattern/concrete_product.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/20. 3 | // 4 | 5 | #ifndef DESIGNPATTERN_CONCRETE_PRODUCT_H 6 | #define DESIGNPATTERN_CONCRETE_PRODUCT_H 7 | 8 | #include "product.h" 9 | 10 | /********** Car **********/ 11 | // Benz 12 | class BenzCar : public ICar 13 | { 14 | public: 15 | string Name() 16 | { 17 | return "Benz Car"; 18 | } 19 | }; 20 | 21 | // BMW 22 | class BmwCar : public ICar 23 | { 24 | public: 25 | string Name() 26 | { 27 | return "Bmw Car"; 28 | } 29 | }; 30 | 31 | // Audi 32 | class AudiCar : public ICar 33 | { 34 | public: 35 | string Name() 36 | { 37 | return "Audi Car"; 38 | } 39 | }; 40 | 41 | /********** Bicycle **********/ 42 | // Benz 43 | class BenzBike : public IBike 44 | { 45 | public: 46 | string Name() 47 | { 48 | return "Benz Bike"; 49 | } 50 | }; 51 | 52 | // BMW 53 | class BmwBike : public IBike 54 | { 55 | public: 56 | string Name() 57 | { 58 | return "Bmw Bike"; 59 | } 60 | }; 61 | 62 | // Audi 63 | class AudiBike : public IBike 64 | { 65 | public: 66 | string Name() 67 | { 68 | return "Audi Bike"; 69 | } 70 | }; 71 | 72 | #endif //DESIGNPATTERN_CONCRETE_PRODUCT_H 73 | -------------------------------------------------------------------------------- /算法库/DesignPattern/AbstractFactoryPattern/product.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/20. 3 | // 4 | 5 | #ifndef DESIGNPATTERN_PRODUCT_H 6 | #define DESIGNPATTERN_PRODUCT_H 7 | 8 | #include 9 | using std::string; 10 | 11 | // Car Interface 12 | class ICar 13 | { 14 | public: 15 | virtual string Name() = 0; 16 | }; 17 | 18 | // Bike Interface 19 | class IBike 20 | { 21 | public: 22 | virtual string Name() = 0; 23 | }; 24 | 25 | #endif //DESIGNPATTERN_PRODUCT_H 26 | -------------------------------------------------------------------------------- /算法库/DesignPattern/AdapterPattern/AdapterMain.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/20. 3 | // 4 | 5 | #ifndef DESIGNPATTERN_ADAPTERMAIN_H 6 | #define DESIGNPATTERN_ADAPTERMAIN_H 7 | 8 | #include "adapter.h" 9 | 10 | void AdapterMain() 11 | { 12 | // Create a power adapter 13 | IRussiaSocket * pAdapter = new PowerAdapter(); 14 | 15 | // Recharge 16 | pAdapter->Charge(); 17 | 18 | SAFE_DELETE(pAdapter); 19 | } 20 | 21 | #endif //DESIGNPATTERN_ADAPTERMAIN_H 22 | -------------------------------------------------------------------------------- /算法库/DesignPattern/AdapterPattern/adaptee.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/20. 3 | // 4 | 5 | #ifndef DESIGNPATTERN_ADAPTEE_H 6 | #define DESIGNPATTERN_ADAPTEE_H 7 | 8 | #include 9 | 10 | // Built-in charger (two-leg flat type) 11 | class OwnCharger 12 | { 13 | public: 14 | void ChargeWithFeetFlat() 15 | { 16 | std::cout << "OwnCharger::ChargeWithFeetFlat\n"; 17 | } 18 | }; 19 | 20 | #endif //DESIGNPATTERN_ADAPTEE_H 21 | -------------------------------------------------------------------------------- /算法库/DesignPattern/AdapterPattern/adapter.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/20. 3 | // 4 | 5 | #ifndef DESIGNPATTERN_ADAPTER_H 6 | #define DESIGNPATTERN_ADAPTER_H 7 | 8 | #include "target.h" 9 | #include "adaptee.h" 10 | 11 | #ifndef SAFE_DELETE 12 | #define SAFE_DELETE(p) { if(p){delete(p); (p)=NULL;} } 13 | #endif 14 | 15 | // Power Adapter 16 | class PowerAdapter : public IRussiaSocket 17 | { 18 | public: 19 | PowerAdapter() : m_pCharger(new OwnCharger()){} 20 | ~PowerAdapter() 21 | { 22 | SAFE_DELETE(m_pCharger); 23 | } 24 | void Charge() 25 | { 26 | // Use the built-in charger (two-pin flat) to charge 27 | m_pCharger->ChargeWithFeetFlat(); 28 | } 29 | private: 30 | // Hold the interface object that needs to be adapted (the built-in charger) 31 | OwnCharger* m_pCharger; 32 | }; 33 | 34 | #endif //DESIGNPATTERN_ADAPTER_H 35 | -------------------------------------------------------------------------------- /算法库/DesignPattern/AdapterPattern/target.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/20. 3 | // 4 | 5 | #ifndef DESIGNPATTERN_TARGET_H 6 | #define DESIGNPATTERN_TARGET_H 7 | 8 | // Sockets provided by Russia 9 | class IRussiaSocket 10 | { 11 | public: 12 | // Use both feet to charge in a round shape (not implemented yet) 13 | virtual void Charge() = 0; 14 | }; 15 | 16 | #endif //DESIGNPATTERN_TARGET_H 17 | -------------------------------------------------------------------------------- /算法库/DesignPattern/BridgePattern/BridgeMain.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/21. 3 | // 4 | 5 | #include "BridgeMain.h" 6 | 7 | void BridgeMain() 8 | { 9 | // Create electrical appliances (electric lights, electric fans) 10 | IElectricalEquipment * light = new Light(); 11 | IElectricalEquipment * fan = new Fan(); 12 | 13 | // Create switch (pull chain switch, two-position switch) 14 | // Associating a pull chain switch with a light and a two-position switch with a fan 15 | ISwitch * pullChain = new PullChainSwitch(light); 16 | ISwitch * twoPosition = new TwoPositionSwitch(fan); 17 | 18 | // Lights on, lights off 19 | pullChain->On(); 20 | pullChain->Off(); 21 | 22 | // Turn on the fan, turn off the fan 23 | twoPosition->On(); 24 | twoPosition->Off(); 25 | 26 | SAFE_DELETE(twoPosition); 27 | SAFE_DELETE(pullChain); 28 | SAFE_DELETE(fan); 29 | SAFE_DELETE(light); 30 | } -------------------------------------------------------------------------------- /算法库/DesignPattern/BridgePattern/BridgeMain.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/21. 3 | // 4 | 5 | #ifndef DESIGNPATTERN_BRIDGEMAIN_H 6 | #define DESIGNPATTERN_BRIDGEMAIN_H 7 | 8 | #include "refined_abstraction.h" 9 | #include "concrete_implementor.h" 10 | 11 | #ifndef SAFE_DELETE 12 | #define SAFE_DELETE(p) { if(p){delete(p); (p)=nullptr;} } 13 | #endif 14 | 15 | void BridgeMain(); 16 | 17 | #endif //DESIGNPATTERN_BRIDGEMAIN_H 18 | -------------------------------------------------------------------------------- /算法库/DesignPattern/BridgePattern/abstraction.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/21. 3 | // 4 | 5 | #ifndef DESIGNPATTERN_ABSTRACTION_H 6 | #define DESIGNPATTERN_ABSTRACTION_H 7 | 8 | #include "implementor.h" 9 | 10 | // Switch 11 | class ISwitch 12 | { 13 | public: 14 | ISwitch(IElectricalEquipment *ee){ m_pEe = ee; } 15 | virtual ~ISwitch(){} 16 | virtual void On() = 0; // Turn on the appliance 17 | virtual void Off() = 0; // Turn off the appliance 18 | 19 | protected: 20 | IElectricalEquipment * m_pEe; 21 | }; 22 | 23 | #endif //DESIGNPATTERN_ABSTRACTION_H 24 | -------------------------------------------------------------------------------- /算法库/DesignPattern/BridgePattern/concrete_implementor.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/21. 3 | // 4 | 5 | #ifndef DESIGNPATTERN_CONCRETE_IMPLEMENTOR_H 6 | #define DESIGNPATTERN_CONCRETE_IMPLEMENTOR_H 7 | 8 | #include "implementor.h" 9 | #include 10 | 11 | // Electric lights 12 | class Light : public IElectricalEquipment 13 | { 14 | public: 15 | // Turn on the lights 16 | virtual void PowerOn() override 17 | { 18 | std::cout << "Light is on." << std::endl; 19 | } 20 | // Turn off the lights 21 | virtual void PowerOff() override 22 | { 23 | std::cout << "Light is off." << std::endl; 24 | } 25 | }; 26 | 27 | // Electric Fan 28 | class Fan : public IElectricalEquipment 29 | { 30 | public: 31 | // Turn on the fan 32 | virtual void PowerOn() override 33 | { 34 | std::cout << "Fan is on." << std::endl; 35 | } 36 | // Turn off the fan 37 | virtual void PowerOff() override 38 | { 39 | std::cout << "Fan is off." << std::endl; 40 | } 41 | }; 42 | 43 | 44 | #endif //DESIGNPATTERN_CONCRETE_IMPLEMENTOR_H 45 | -------------------------------------------------------------------------------- /算法库/DesignPattern/BridgePattern/implementor.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/21. 3 | // 4 | 5 | #ifndef DESIGNPATTERN_IMPLEMENTOR_H 6 | #define DESIGNPATTERN_IMPLEMENTOR_H 7 | 8 | // Electric equipment 9 | class IElectricalEquipment 10 | { 11 | public: 12 | virtual ~IElectricalEquipment(){} 13 | virtual void PowerOn() = 0; 14 | virtual void PowerOff() = 0; 15 | }; 16 | 17 | #endif //DESIGNPATTERN_IMPLEMENTOR_H 18 | -------------------------------------------------------------------------------- /算法库/DesignPattern/BridgePattern/refined_abstraction.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/21. 3 | // 4 | 5 | #ifndef DESIGNPATTERN_REFINED_ABSTRACTION_H 6 | #define DESIGNPATTERN_REFINED_ABSTRACTION_H 7 | 8 | #include "abstraction.h" 9 | #include 10 | 11 | // Zipper switch 12 | class PullChainSwitch : public ISwitch 13 | { 14 | public: 15 | PullChainSwitch(IElectricalEquipment *ee) : ISwitch(ee) {} 16 | 17 | // Turn on the equipment with a zipper switch 18 | virtual void On() override 19 | { 20 | std::cout << "Turn on the equipment with a zipper switch." << std::endl; 21 | m_pEe->PowerOn(); 22 | } 23 | 24 | // Turn off the equipment with a zipper switch 25 | virtual void Off() override 26 | { 27 | std::cout << "Turn off the equipment with a zipper switch." << std::endl; 28 | m_pEe->PowerOff(); 29 | } 30 | }; 31 | 32 | // Two-position switch 33 | class TwoPositionSwitch : public ISwitch 34 | { 35 | public: 36 | TwoPositionSwitch(IElectricalEquipment *ee) : ISwitch(ee) {} 37 | 38 | // Turn on the equipment with a two-position switch 39 | virtual void On() override 40 | { 41 | std::cout << "Turn on the equipment with a two-position switch." << std::endl; 42 | m_pEe->PowerOn(); 43 | } 44 | 45 | // Turn off the equipment with a two-position switch 46 | virtual void Off() override { 47 | std::cout << "Turn off the equipment with a two-position switch." << std::endl; 48 | m_pEe->PowerOff(); 49 | } 50 | }; 51 | 52 | 53 | #endif //DESIGNPATTERN_REFINED_ABSTRACTION_H 54 | -------------------------------------------------------------------------------- /算法库/DesignPattern/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(DesignPattern) 3 | 4 | set(CMAKE_CXX_STANDARD 11) 5 | 6 | add_executable(DesignPattern main.cpp AbstractFactoryPattern/product.h AbstractFactoryPattern/concrete_product.h AbstractFactoryPattern/Factory.cpp AbstractFactoryPattern/Factory.h AbstractFactoryPattern/concrete_factory.h AbstractFactoryPattern/FactoryMain.cpp AbstractFactoryPattern/FactoryMain.h SingletonPattern/Singleton.cpp SingletonPattern/Singleton.h SingletonPattern/SingletonMain.h AdapterPattern/target.h AdapterPattern/adaptee.h AdapterPattern/adapter.h AdapterPattern/AdapterMain.h BridgePattern/implementor.h BridgePattern/concrete_implementor.h BridgePattern/abstraction.h BridgePattern/refined_abstraction.h BridgePattern/BridgeMain.h BridgePattern/BridgeMain.cpp ObserverPattern/subject.h ObserverPattern/observer.h ObserverPattern/concrete_subject.h ObserverPattern/concrete_observer.h ObserverPattern/ObserverMain.cpp ObserverPattern/ObserverMain.h) -------------------------------------------------------------------------------- /算法库/DesignPattern/ObserverPattern/ObserverMain.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/21. 3 | // 4 | 5 | #include "ObserverMain.h" 6 | 7 | void ObserverMain() 8 | { 9 | // Create Subject 10 | ConcreteSubject * pSubject = new ConcreteSubject(); 11 | 12 | // Create Observer 13 | IObserver * pObserver1 = new ConcreteObserver("Jack Ma"); 14 | IObserver * pObserver2 = new ConcreteObserver("Pony"); 15 | 16 | // Attach Observers 17 | pSubject->Attach(pObserver1); 18 | pSubject->Attach(pObserver2); 19 | 20 | // Change the price and notify the observer 21 | pSubject->SetPrice(12.5); 22 | pSubject->Notify(); 23 | 24 | // Detach Observers 25 | pSubject->Detach(pObserver2); 26 | 27 | // Change the state again and notify the observer 28 | pSubject->SetPrice(15.0); 29 | pSubject->Notify(); 30 | 31 | SAFE_DELETE(pObserver1); 32 | SAFE_DELETE(pObserver2); 33 | SAFE_DELETE(pSubject); 34 | } -------------------------------------------------------------------------------- /算法库/DesignPattern/ObserverPattern/ObserverMain.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/21. 3 | // 4 | 5 | #ifndef DESIGNPATTERN_OBSERVERMAIN_H 6 | #define DESIGNPATTERN_OBSERVERMAIN_H 7 | 8 | #include "concrete_subject.h" 9 | #include "concrete_observer.h" 10 | 11 | #ifndef SAFE_DELETE 12 | #define SAFE_DELETE(p) { if(p){delete(p); (p)=nullptr;} } 13 | #endif 14 | 15 | void ObserverMain(); 16 | 17 | #endif //DESIGNPATTERN_OBSERVERMAIN_H 18 | -------------------------------------------------------------------------------- /算法库/DesignPattern/ObserverPattern/concrete_observer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/21. 3 | // 4 | 5 | #ifndef DESIGNPATTERN_CONCRETE_OBSERVER_H 6 | #define DESIGNPATTERN_CONCRETE_OBSERVER_H 7 | 8 | #include "observer.h" 9 | #include 10 | #include 11 | 12 | class ConcreteObserver : public IObserver 13 | { 14 | public: 15 | ConcreteObserver(std::string name) { m_strName = name; } 16 | void Update(float price) 17 | { 18 | std::cout << m_strName << " - price" << price << "\n"; 19 | } 20 | 21 | private: 22 | std::string m_strName; // name 23 | }; 24 | 25 | #endif //DESIGNPATTERN_CONCRETE_OBSERVER_H 26 | -------------------------------------------------------------------------------- /算法库/DesignPattern/ObserverPattern/concrete_subject.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/21. 3 | // 4 | 5 | #ifndef DESIGNPATTERN_CONCRETE_SUBJECT_H 6 | #define DESIGNPATTERN_CONCRETE_SUBJECT_H 7 | 8 | #include "subject.h" 9 | #include "observer.h" 10 | #include 11 | #include 12 | 13 | // Specific Subject 14 | class ConcreteSubject : public ISubject 15 | { 16 | public: 17 | ConcreteSubject(){ m_fPrice = 10.0; } 18 | void SetPrice(float price) 19 | { 20 | m_fPrice = price; 21 | } 22 | void Attach(IObserver * observer) 23 | { 24 | m_observers.push_back(observer); 25 | } 26 | void Detach(IObserver * observer) 27 | { 28 | m_observers.remove(observer); 29 | } 30 | // Notify all observers 31 | void Notify() 32 | { 33 | std::list::iterator it = m_observers.begin(); 34 | while (it != m_observers.end()) 35 | { 36 | (*it)->Update(m_fPrice); 37 | ++it; 38 | } 39 | } 40 | private: 41 | std::list m_observers; // Observer list 42 | float m_fPrice; // Price 43 | }; 44 | 45 | #endif //DESIGNPATTERN_CONCRETE_SUBJECT_H 46 | -------------------------------------------------------------------------------- /算法库/DesignPattern/ObserverPattern/observer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/21. 3 | // 4 | 5 | #ifndef DESIGNPATTERN_OBSERVER_H 6 | #define DESIGNPATTERN_OBSERVER_H 7 | 8 | // Abstract observer 9 | class IObserver 10 | { 11 | public: 12 | virtual void Update(float price) = 0; // Update price 13 | }; 14 | 15 | #endif //DESIGNPATTERN_OBSERVER_H 16 | -------------------------------------------------------------------------------- /算法库/DesignPattern/ObserverPattern/subject.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/21. 3 | // 4 | 5 | #ifndef DESIGNPATTERN_SUBJECT_H 6 | #define DESIGNPATTERN_SUBJECT_H 7 | 8 | class IObserver; 9 | 10 | class ISubject 11 | { 12 | public: 13 | virtual void Attach(IObserver *) = 0; // Attach observer 14 | virtual void Detach(IObserver *) = 0; // Detach observer 15 | virtual void Notify() = 0; // Notify observer 16 | }; 17 | 18 | #endif //DESIGNPATTERN_SUBJECT_H 19 | -------------------------------------------------------------------------------- /算法库/DesignPattern/README.md: -------------------------------------------------------------------------------- 1 | # 设计模式 2 | 3 | > 各大设计模式例子参考:[CSDN专栏 . C++ 设计模式](https://blog.csdn.net/liang19890820/article/details/66974516) 系列博文 4 | 5 | 此文件夹为一个 CLion 工程,由 CMake 构建,各个文件夹为各个设计模式的具体实现。文件中可能会有中文乱码问题,请以 `GB2312`(中文) 编码打开。 6 | 7 | * [单例模式例子](SingletonPattern) 8 | * [抽象工厂模式例子](AbstractFactoryPattern) 9 | * [适配器模式例子](AdapterPattern) 10 | * [桥接模式例子](BridgePattern) 11 | * [观察者模式例子](ObserverPattern) -------------------------------------------------------------------------------- /算法库/DesignPattern/SingletonPattern/README.md: -------------------------------------------------------------------------------- 1 | # 单例模式 2 | 3 | ```cpp 4 | // 懒汉式单例模式 5 | class Singleton 6 | { 7 | private: 8 | Singleton() { } 9 | static Singleton * pInstance; 10 | public: 11 | static Singleton * GetInstance() 12 | { 13 | if (pInstance == nullptr) 14 | pInstance = new Singleton(); 15 | return pInstance; 16 | } 17 | }; 18 | 19 | // 线程安全的单例模式 20 | class Singleton 21 | { 22 | private: 23 | Singleton() { } 24 | ~Singleton() { } 25 | Singleton(const Singleton &); 26 | Singleton & operator = (const Singleton &); 27 | public: 28 | static Singleton & GetInstance() 29 | { 30 | static Singleton instance; 31 | return instance; 32 | } 33 | }; 34 | ``` -------------------------------------------------------------------------------- /算法库/DesignPattern/SingletonPattern/Singleton.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/20. 3 | // 4 | 5 | #include 6 | #include "Singleton.h" 7 | 8 | void Singleton::DoSomething() 9 | { 10 | std::cout << "Singleton do something\n"; 11 | } -------------------------------------------------------------------------------- /算法库/DesignPattern/SingletonPattern/Singleton.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/20. 3 | // 4 | 5 | #ifndef DESIGNPATTERN_SINGLETON_H 6 | #define DESIGNPATTERN_SINGLETON_H 7 | 8 | // Singleton mode 9 | class Singleton { 10 | private: 11 | Singleton(){} 12 | ~Singleton(){} 13 | Singleton(const Singleton &); 14 | Singleton & operator= (const Singleton &); 15 | 16 | public: 17 | static Singleton & GetInstance() 18 | { 19 | static Singleton instance; 20 | return instance; 21 | } 22 | void DoSomething(); 23 | }; 24 | 25 | #endif //DESIGNPATTERN_SINGLETON_H 26 | -------------------------------------------------------------------------------- /算法库/DesignPattern/SingletonPattern/SingletonMain.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/20. 3 | // 4 | 5 | #ifndef DESIGNPATTERN_SINGLETONMAIN_H 6 | #define DESIGNPATTERN_SINGLETONMAIN_H 7 | 8 | #include "Singleton.h" 9 | 10 | void SingletonMain() 11 | { 12 | Singleton::GetInstance().DoSomething(); 13 | } 14 | 15 | #endif //DESIGNPATTERN_SINGLETONMAIN_H 16 | -------------------------------------------------------------------------------- /算法库/DesignPattern/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by xiemenghui on 2018/7/20. 3 | // 4 | 5 | #include 6 | #include "SingletonPattern/SingletonMain.h" 7 | #include "AbstractFactoryPattern/FactoryMain.h" 8 | #include "AdapterPattern/AdapterMain.h" 9 | #include "BridgePattern/BridgeMain.h" 10 | #include "ObserverPattern/ObserverMain.h" 11 | 12 | int main() { 13 | std::cout << "*******************" << std::endl; 14 | std::cout << "** Design pattern example **" << std::endl; 15 | std::cout << "*******************" << std::endl; 16 | 17 | std::cout << "*******************" << std::endl; 18 | std::cout << "** Singleton mode **" << std::endl; 19 | std::cout << "*******************" << std::endl; 20 | SingletonMain(); 21 | 22 | std::cout << "*******************" << std::endl; 23 | std::cout << "** Abstract factory pattern **" << std::endl; 24 | std::cout << "*******************" << std::endl; 25 | FactoryMain(); 26 | 27 | std::cout << "*******************" << std::endl; 28 | std::cout << "** Adapter mode **" << std::endl; 29 | std::cout << "*******************" << std::endl; 30 | AdapterMain(); 31 | 32 | std::cout << "*******************" << std::endl; 33 | std::cout << "** Bridge mode **" << std::endl; 34 | std::cout << "*******************" << std::endl; 35 | 36 | std::cout << "*******************" << std::endl; 37 | std::cout << "** Observer mode **" << std::endl; 38 | std::cout << "*******************" << std::endl; 39 | ObserverMain(); 40 | 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /算法库/LeetCode/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | URL - https://github.com/Charmve/LeetCode4FLAG 4 | 5 |
6 | 7 |

LeetCode4FLAG 8 | 9 |   10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |

24 | 25 | 🌍 English | [简体中文](README-zh_CN.md)| [日本語](README-jp_JP.md) | [Українською](README-uk_UA.md) 26 |
27 | 28 |
29 |
30 | 31 |

32 |

33 | CircleCI 34 | Version No. 35 | Code License 36 | Chinese Version 37 |

38 |
39 |

40 | High frequent interview LeetCode test for FaceBook, Linkedin, Amazon, Google. More importantly, the problems' solutions are provided in C/C++, Python and Java. Offer, Offer, Offer ! 41 |

42 |

43 | 高频合集 • 44 | 答题模板 • 45 | 打卡群 • 46 | 📕 Docs 47 |

48 |
49 | 50 | 51 | ---- 52 | Note: Please raise an issue for any suggestions, corrections, and feedback. 53 | 54 | - [2020年最新FLAMG面试频率最高的127道题.xlsx](https://github.com/Charmve/LeetCode4FLAG/raw/main/2020最新-FLAMG面试频率最高的127道题.xlsx) 55 | - [Google最常考的40道动态规划题.xlsx](https://github.com/Charmve/LeetCode4FLAG/raw/main/Google最常考的40道动态规划题.xlsx) 56 | - [FaceBook高频题100道.xlsx](https://github.com/Charmve/LeetCode4FLAG/raw/main/Facebook高频题100道.xls) 57 | - CodeTop持续更新 https://codetop.cc/home 58 | 59 |
60 | -------------------------------------------------------------------------------- /算法库/Problems/ChessboardCoverageProblem/ChessboardCoverage.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | 7 | int num_Now = 0; // 记录L型骨牌编号 8 | int **board = NULL; // 棋盘指针 9 | 10 | // 函数声明 11 | void ChessBoard(int num_BoardTopLeftRow, int num_BoardTopLeftColumn, int num_SpecialRow, int num_SpecialColumn, int boardSize); 12 | 13 | int main() { 14 | 15 | int num_BoardTopLeftRow = 0, // 棋盘左上角的行号 16 | num_BoardTopLeftColumn = 0, // 棋盘左上角的列号 17 | num_SpecialRow = 0, // 特殊方格所在的行号 18 | num_SpecialColumn = 0, // 特殊方格所在的列号 19 | boardSize = 0, // 棋盘大小 20 | k = 0; // 构成的(2^k)*(2^k)个方格的棋盘 21 | 22 | // 用户界面 23 | cout << "---------------- 棋盘覆盖问题 ----------------" << endl; 24 | cout << "请输入k(k>=0),构成(2^k)*(2^k)个方格的棋盘" << endl; 25 | 26 | // 输入k值 27 | cin >> k; 28 | 29 | // 判断输入数据合法性,包括检查输入是否为数字,k值是否大于0 30 | if (cin.fail() || k < 0) 31 | { 32 | cout << "输入k错误!" << endl; 33 | system("pause"); 34 | return 0; 35 | } 36 | 37 | // 计算棋盘大小 38 | boardSize = pow(2, k); 39 | 40 | cout << "请输入特殊方格所在的行号和列号(从0开始,用空格隔开)" << endl; 41 | 42 | // 输入特殊方格所在的行号和列号 43 | cin >> num_SpecialRow >> num_SpecialColumn; 44 | 45 | // 判断输入数据合法性,包括检查输入是否为数字,特殊方格行号列号是否大于0,特殊方格行号列号是否不大于棋盘大小 46 | if (cin.fail() || num_SpecialRow < 0 || num_SpecialColumn < 0 || num_SpecialRow >= boardSize || num_SpecialColumn >= boardSize) 47 | { 48 | cout << "输入行号或列号错误!" << endl; 49 | system("pause"); 50 | return 0; 51 | } 52 | 53 | // 分配棋盘空间 54 | board = new int *[boardSize]; 55 | for (auto i = 0; i < boardSize; i++) 56 | { 57 | board[i] = new int[boardSize]; 58 | } 59 | 60 | // 为特殊方格赋初值0 61 | board[num_SpecialRow][num_SpecialColumn] = 0; 62 | 63 | //执行棋盘覆盖函数 64 | ChessBoard(num_BoardTopLeftRow, num_BoardTopLeftColumn, num_SpecialRow, num_SpecialColumn, boardSize); 65 | 66 | // 显示输出 67 | cout << "------------------------------------------------" << endl; 68 | for (auto i = 0; i < boardSize; i++) 69 | { 70 | for (auto j = 0; j < boardSize; j++) 71 | { 72 | cout << board[i][j] << "\t"; 73 | } 74 | cout << endl; 75 | } 76 | cout << "------------------------------------------------" << endl; 77 | 78 | // 暂停查看结果 79 | system("pause"); 80 | 81 | // 释放内存 82 | for (int i = 0; i <= boardSize; i++) 83 | delete[] board[i]; 84 | delete[] board; 85 | 86 | // 指针置空 87 | board = NULL; 88 | 89 | return 0; 90 | } 91 | 92 | // 棋盘覆盖函数 93 | void ChessBoard(int num_BoardTopLeftRow, int num_BoardTopLeftColumn, int num_SpecialRow, int num_SpecialColumn, int boardSize) 94 | { 95 | // 棋盘大小为1则直接返回 96 | if (boardSize == 1) return; 97 | 98 | int num = ++num_Now, // L型骨牌编号 99 | size = boardSize / 2; // 分割棋盘,行列各一分为二 100 | 101 | // 覆盖左上角子棋盘 102 | if (num_SpecialRow < num_BoardTopLeftRow + size && num_SpecialColumn < num_BoardTopLeftColumn + size) 103 | { 104 | // 递归覆盖含有特殊方格的子棋盘 105 | ChessBoard(num_BoardTopLeftRow, num_BoardTopLeftColumn, num_SpecialRow, num_SpecialColumn, size); 106 | } 107 | else 108 | { 109 | // 用编号为num的L型骨牌覆盖右下角 110 | board[num_BoardTopLeftRow + size - 1][num_BoardTopLeftColumn + size - 1] = num; 111 | 112 | // 递归覆盖其余棋盘 113 | ChessBoard(num_BoardTopLeftRow, num_BoardTopLeftColumn, num_BoardTopLeftRow + size - 1, num_BoardTopLeftColumn + size - 1, size); 114 | } 115 | 116 | // 覆盖右上角子棋盘 117 | if (num_SpecialRow < num_BoardTopLeftRow + size && num_SpecialColumn >= num_BoardTopLeftColumn + size) 118 | { 119 | // 递归覆盖含有特殊方格的子棋盘 120 | ChessBoard(num_BoardTopLeftRow, num_BoardTopLeftColumn + size, num_SpecialRow, num_SpecialColumn, size); 121 | } 122 | else 123 | { 124 | // 用编号为num的L型骨牌覆盖左下角 125 | board[num_BoardTopLeftRow + size - 1][num_BoardTopLeftColumn + size] = num; 126 | 127 | // 递归覆盖其余棋盘 128 | ChessBoard(num_BoardTopLeftRow, num_BoardTopLeftColumn + size, num_BoardTopLeftRow + size - 1, num_BoardTopLeftColumn + size, size); 129 | } 130 | 131 | // 覆盖左下角子棋盘 132 | if (num_SpecialRow >= num_BoardTopLeftRow + size && num_SpecialColumn < num_BoardTopLeftColumn + size) 133 | { 134 | // 递归覆盖含有特殊方格的子棋盘 135 | ChessBoard(num_BoardTopLeftRow + size, num_BoardTopLeftColumn, num_SpecialRow, num_SpecialColumn, size); 136 | } 137 | else 138 | { 139 | // 用编号为num的L型骨牌覆盖右上角 140 | board[num_BoardTopLeftRow + size][num_BoardTopLeftColumn + size - 1] = num; 141 | 142 | // 递归覆盖其余棋盘 143 | ChessBoard(num_BoardTopLeftRow + size, num_BoardTopLeftColumn, num_BoardTopLeftRow + size, num_BoardTopLeftColumn + size - 1, size); 144 | } 145 | 146 | // 覆盖右下角子棋盘 147 | if (num_SpecialRow >= num_BoardTopLeftRow + size && num_SpecialColumn >= num_BoardTopLeftColumn + size) 148 | { 149 | // 递归覆盖含有特殊方格的子棋盘 150 | ChessBoard(num_BoardTopLeftRow + size, num_BoardTopLeftColumn + size, num_SpecialRow, num_SpecialColumn, size); 151 | } 152 | else 153 | { 154 | // 用编号为num的L型骨牌覆盖左上角 155 | board[num_BoardTopLeftRow + size][num_BoardTopLeftColumn + size] = num; 156 | 157 | // 递归覆盖其余棋盘 158 | ChessBoard(num_BoardTopLeftRow + size, num_BoardTopLeftColumn + size, num_BoardTopLeftRow + size, num_BoardTopLeftColumn + size, size); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /算法库/Problems/ChessboardCoverageProblem/ChessboardCoverage.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgramTraveler/CppMaster/b35252f8804b1a0821dbb10a5d502031973fe53f/算法库/Problems/ChessboardCoverageProblem/ChessboardCoverage.exe -------------------------------------------------------------------------------- /算法库/Problems/ChessboardCoverageProblem/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 棋盘覆盖问题 3 | 4 | ### 代码 5 | 6 | [棋盘覆盖问题代码](ChessboardCoverage.cpp) 7 | 8 | ### 问题说明 9 | 10 | 在一个2^k * 2^k个方格组成的棋盘中,恰有一个方格与其它方格不同,称该方格为一特殊方格。 11 | 12 | 棋盘覆盖问题就是要用图示的4种不同形态的L型骨牌覆盖给定棋盘上除特殊方格之外的所有方格,且任何2个L型骨牌不得重叠覆盖。 13 | 14 | ![](http://blog.chinaunix.net/attachment/201303/1/26548237_1362125215RWwI.png) 15 | 16 | ### 功能说明 17 | 18 | 本程序用分治法的思想解决了棋盘覆盖问题,显示输出 19 | 20 | ### 代码简述 21 | 22 | 用户输入数据,程序输入检测,动态分配空间,调用棋盘覆盖函数,把计算结果存储到board(二维数组指针),显示输出。 23 | 24 | 其中棋盘覆盖函数用分治的思想把棋盘分成四份,递归求解。 -------------------------------------------------------------------------------- /算法库/Problems/KnapsackProblem/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 背包问题 3 | 4 | ### 代码 5 | 6 | [背包问题代码](pack.cpp) 7 | 8 | ### 问题说明 9 | 10 | 有N件物品和一个容量为V的背包。 11 | 12 | 第i件物品的重量是w[i],价值是v[i]。 13 | 14 | 求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量, 15 | 16 | 且价值总和最大。 17 | 18 | ### 功能说明 19 | 20 | 本程序用动态规划的思想解决了背包问题,并用了两种算法: 21 | 迭代法、递归法。在迭代法中实现了打印背包问题的表格。 22 | 23 | ### 代码简述 24 | 25 | 通过用户输入数据,程序输入检测,动态分配空间,选择算法, 26 | 用动态规划的思想求解背包问题。 27 | 28 | #### 迭代法: 29 | 通过遍历n行W列,迭代每行每列的值,并把最优解放到 30 | n行(在数组中为第n+1行)W列(在数组中为第W+1列)中。 31 | 32 | #### 递归法: 33 | 通过每次返回前i个物品和承重为j的最优解, 34 | 递归计算总背包问题的最优解。 35 | -------------------------------------------------------------------------------- /算法库/Problems/KnapsackProblem/pack.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | using namespace std; 4 | 5 | int **T = NULL; // 存储背包问题表格的数组指针 6 | 7 | // 返回两个值的最大值 8 | int max(int a, int b) { 9 | return (a > b) ? a : b; 10 | } 11 | 12 | // 迭代法,能显示背包问题的表格 13 | int packIterative(int n, int W, int *w, int *v) { 14 | 15 | // 循环遍历n行 16 | for (int i = 1; i <= n; ++i) 17 | { 18 | // 循环遍历W列 19 | for (int j = 1; j <= W; ++j) 20 | { 21 | //第i个物品能装下,则比较包括第i个物品和不包括第i个物品,取其最大值 22 | if (w[i] <= j) 23 | T[i][j] = max(v[i] + T[i - 1][j - w[i]], T[i - 1][j]); 24 | 25 | // 第i个物品不能装下,则递归装i-1个 26 | else 27 | T[i][j] = T[i - 1][j]; 28 | } 29 | } 30 | return T[n][W]; 31 | } 32 | 33 | // 递归法,不支持显示背包问题的表格 34 | int packRecursive(int n, int W, int *w, int *v) { 35 | // 结束条件(初始条件),i或者j为0时最大总价值为0 36 | if (n == 0 || W == 0) { 37 | return 0; 38 | } 39 | // 第i个物品不能装下,则递归装i-1个 40 | if (w[n] > W) { 41 | return packRecursive(n - 1, W, w, v); 42 | } 43 | //第i个物品能装下,则比较包括第i个物品和不包括第i个物品,取其最大值 44 | else { 45 | return max(v[n] + packRecursive(n - 1, W - w[n], w, v), packRecursive(n - 1, W, w, v)); 46 | } 47 | } 48 | 49 | // 打印背包问题的表格 50 | void printT(int n, int W) 51 | { 52 | // 打印n行 53 | for (auto i = 0; i <= n; i++) 54 | { 55 | // 打印行数 56 | cout << i << ":\t"; 57 | 58 | // 打印W列 59 | for (int w = 0; w <= W; w++) 60 | { 61 | cout << T[i][w] << "\t"; 62 | } 63 | 64 | // 换行 65 | cout << endl; 66 | } 67 | } 68 | 69 | int main() { 70 | int *w = NULL; // 存储每件物品重量的数组指针 71 | int *v = NULL; // 存储每件物品价值的数组指针 72 | int n; // 物品个数n 73 | int W; // 背包总承重W 74 | 75 | cout << "---------------- 背包问题 ----------------" << endl; 76 | cout << "请输入物品数 n (n>=0) " << endl; 77 | 78 | // 输入背包数 79 | cin >> n; 80 | 81 | if (cin.fail() || n < 0) 82 | { 83 | cout << "输入n错误!" << endl; 84 | system("pause"); 85 | return 0; 86 | } 87 | 88 | cout << "请输入背包承重量 W (W>=0) " << endl; 89 | 90 | // 输入背包承重量 91 | cin >> W; 92 | 93 | if (cin.fail() || W < 0) 94 | { 95 | cout << "输入W错误!" << endl; 96 | system("pause"); 97 | return 0; 98 | } 99 | 100 | // 分配空间 101 | // 对w和v分配n+1大小 102 | w = new int[n + 1]; 103 | v = new int[n + 1]; 104 | 105 | // 对T分配n+1行,并初始化为0 106 | T = new int *[n + 1](); 107 | // 对T分配W+1列,并初始化为0 108 | for (auto i = 0; i <= n; i++) 109 | { 110 | T[i] = new int[W + 1](); 111 | } 112 | 113 | // 输入背包的重量和价值 114 | for (auto i = 1; i <= n; i++) 115 | { 116 | cout << "请输入第 " << i << " 个物品的重量和价值(用空格隔开)" << endl; 117 | cin >> w[i] >> v[i]; 118 | if (cin.fail() || w[i] < 0 || v[i] < 0) 119 | { 120 | cout << "输入错误!" << endl; 121 | system("pause"); 122 | return 0; 123 | } 124 | } 125 | 126 | cout << "------------------------------------------------" << endl; 127 | cout << "请选择算法:" << endl; 128 | cout << "【1】迭代法" << endl; 129 | cout << "【2】递归法" << endl; 130 | cout << "------------------------------------------------" << endl; 131 | 132 | int choose; 133 | 134 | // 输入算法的选择 135 | cin >> choose; 136 | switch (choose) 137 | { 138 | case 1: 139 | { 140 | // 迭代法,能显示背包问题的表格 141 | cout << "能装下物品的最大价值为 " << packIterative(n, W, w, v) << endl; 142 | cout << "------------------------------------------------" << endl; 143 | printT(n, W); 144 | break; 145 | } 146 | case 2: 147 | { 148 | // 递归法,不支持显示背包问题的表格 149 | cout << "能装下物品的最大价值为 " << packRecursive(n, W, w, v) << endl; 150 | break; 151 | } 152 | default: 153 | { 154 | cout << "输入错误!" << endl; 155 | break; 156 | } 157 | } 158 | 159 | cout << "------------------------------------------------" << endl; 160 | 161 | delete w; 162 | delete v; 163 | for (int i = 0; i <= n; ++i) { 164 | delete[] T[i]; 165 | } 166 | delete[] T; 167 | 168 | system("pause"); 169 | return 0; 170 | } 171 | -------------------------------------------------------------------------------- /算法库/Problems/KnapsackProblem/pack.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgramTraveler/CppMaster/b35252f8804b1a0821dbb10a5d502031973fe53f/算法库/Problems/KnapsackProblem/pack.exe -------------------------------------------------------------------------------- /算法库/Problems/NeumannNeighborProblem/Formula/Neumann2_3_12.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | //通项法 4 | int Neumann2_3_12(int n) { 5 | 6 | //通项公式的求解请查看说明文档 7 | return 2 * n*n + 2 * n + 1; 8 | } 9 | 10 | int main() { 11 | int n = 0, a = 0; 12 | 13 | printf("------冯诺依曼邻居问题------\n"); 14 | printf("已知:\n"); 15 | printf(" 0 阶冯诺依曼邻居的元胞数为 1 \n"); 16 | printf(" 1 阶冯诺依曼邻居的元胞数为 5 \n"); 17 | printf(" 2 阶冯诺依曼邻居的元胞数为 13 \n"); 18 | printf("求:\n"); 19 | printf(" n 阶冯诺依曼邻居的元胞数\n"); 20 | printf("----------------------------\n"); 21 | printf("请输入n\n"); 22 | scanf("%d", &n); 23 | 24 | //用通项公式求解 25 | a = Neumann2_3_12(n); 26 | 27 | printf("------------通项法-------------\n"); 28 | printf(" %d 阶冯诺依曼邻居的元胞数为 %d\n", n, a); 29 | 30 | getchar(); 31 | getchar(); 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /算法库/Problems/NeumannNeighborProblem/Formula/Neumann2_3_12.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgramTraveler/CppMaster/b35252f8804b1a0821dbb10a5d502031973fe53f/算法库/Problems/NeumannNeighborProblem/Formula/Neumann2_3_12.exe -------------------------------------------------------------------------------- /算法库/Problems/NeumannNeighborProblem/Formula/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 冯诺依曼邻居问题(通项公式) 3 | 4 | ### 代码 5 | 6 | [冯诺依曼邻居问题(通项公式)代码](Neumann2_3_12.cpp) 7 | 8 | ### 问题说明 9 | 10 | 某算法从一个1×1的方格开始,每次都会在上次图形的周围再加上一圈方格,在第n次的时候要生成多少个方格?下图给出了n = 0,1,2是的结果。 11 | 12 | ![](https://huihut-img.oss-cn-shenzhen.aliyuncs.com/NeumannNeighborProblem.jpg) 13 | 14 | ### 功能说明 15 | 16 | 本程序使用通项公式求解。 17 | 18 | ### 代码简述 19 | 20 | 若设第n次生成的方格数是a(n),则: 21 | 22 | a(1) = a(0) + 4 * 1 23 | a(2) = a(1) + 4 * 2 24 | a(3) = a(2) + 4 * 3 25 | ... 26 | a(n) = a(n-1) + 4 * n 27 | 28 | 化简可得: 29 | 30 | a(n) - a(1) = 4 * (n + (n-1) + ... + 2 ) 31 | 32 | 即: 33 | 34 | a(n) = 2 * n*n + 2 * n + 1 35 | 36 | 则可得出a(n)的通项公式,即可用通项公式直接求解。 37 | 38 | 在程序中用Neumann2_3_12函数返回a(n)的值。 -------------------------------------------------------------------------------- /算法库/Problems/NeumannNeighborProblem/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 冯诺依曼邻居问题 3 | 4 | ### 问题说明 5 | 6 | 某算法从一个1×1的方格开始,每次都会在上次图形的周围再加上一圈方格,在第n次的时候要生成多少个方格?下图给出了n = 0,1,2是的结果。 7 | 8 | ![](https://huihut-img.oss-cn-shenzhen.aliyuncs.com/NeumannNeighborProblem.jpg) 9 | 10 | ### 解法 11 | 12 | * [通项公式解法](Formula) 13 | * [递推关系解法](Recursive) -------------------------------------------------------------------------------- /算法库/Problems/NeumannNeighborProblem/Recursive/Neumann2_4_12.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | //递归法 4 | int Neumann2_4_12(int n) { 5 | 6 | //由图可知第0次有1个方格 7 | if (n == 0) return 1; 8 | 9 | //递推关系的求解请查看说明文档 10 | return Neumann2_4_12(n - 1) + 4 * n; 11 | } 12 | 13 | int main() { 14 | int n = 0, a = 0; 15 | 16 | printf("------冯诺依曼邻居问题------\n"); 17 | printf("已知:\n"); 18 | printf(" 0 阶冯诺依曼邻居的元胞数为 1 \n"); 19 | printf(" 1 阶冯诺依曼邻居的元胞数为 5 \n"); 20 | printf(" 2 阶冯诺依曼邻居的元胞数为 13 \n"); 21 | printf("求:\n"); 22 | printf(" n 阶冯诺依曼邻居的元胞数\n"); 23 | printf("----------------------------\n"); 24 | printf("请输入n\n"); 25 | scanf("%d", &n); 26 | 27 | //建立递推关系,使用递归求解 28 | a = Neumann2_4_12(n); 29 | 30 | printf("------------通项法-------------\n"); 31 | printf(" %d 阶冯诺依曼邻居的元胞数为 %d\3n", n, a); 32 | 33 | getchar(); 34 | getchar(); 35 | return 0; 36 | } 37 | -------------------------------------------------------------------------------- /算法库/Problems/NeumannNeighborProblem/Recursive/Neumann2_4_12.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgramTraveler/CppMaster/b35252f8804b1a0821dbb10a5d502031973fe53f/算法库/Problems/NeumannNeighborProblem/Recursive/Neumann2_4_12.exe -------------------------------------------------------------------------------- /算法库/Problems/NeumannNeighborProblem/Recursive/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 冯诺依曼邻居问题(递推关系) 3 | 4 | ### 代码 5 | 6 | [冯诺依曼邻居问题(递推关系)代码](Neumann2_4_12.cpp) 7 | 8 | ### 问题说明 9 | 10 | 某算法从一个1×1的方格开始,每次都会在上次图形的周围再加上一圈方格,在第n次的时候要生成多少个方格?下图给出了n = 0,1,2是的结果。 11 | 12 | ![](https://huihut-img.oss-cn-shenzhen.aliyuncs.com/NeumannNeighborProblem.jpg) 13 | 14 | ### 功能说明 15 | 16 | 本程序使用递推关系求解。 17 | 18 | ### 代码简述 19 | 20 | 若设第n次生成的方格数是a(n),则: 21 | 22 | a(1) = a(0) + 4 * 1 23 | a(2) = a(1) + 4 * 2 24 | a(3) = a(2) + 4 * 3 25 | ... 26 | a(n) = a(n-1) + 4 * n 27 | 28 | 则可得: 29 | 30 | a(n) = a(n - 1) + 4 * n 31 | 32 | 然后在代码中使用递归法,递归结束条件为n = 0,即 33 | 34 | if (n == 0) return 1; 35 | 36 | 则可写出递归法的代码。 37 | 38 | 在程序中用Neumann2_4_12函数进行递归求解。 -------------------------------------------------------------------------------- /算法库/Problems/RoundRobinProblem/MatchTable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std; 5 | 6 | // 循环赛日程安排函数声明 7 | void MatchTable(int k, int n, int **table); 8 | 9 | int main() 10 | { 11 | int n = 0, k = 0; 12 | 13 | // 用户界面 14 | cout << "---------------- 循环赛日程安排问题 ----------------" << endl; 15 | cout << "请输入k(k>=0),构成 n=(2^k) 个选手的循环赛" << endl; 16 | 17 | // 输入k值 18 | cin >> k; 19 | 20 | // 判断输入数据合法性,包括检查输入是否为数字,k值是否大于0 21 | if (cin.fail() || k < 0) 22 | { 23 | cout << "输入k错误!" << endl; 24 | system("pause"); 25 | return 0; 26 | } 27 | 28 | // 计算比赛日程表大小 29 | n = pow(2, k); 30 | 31 | // 分配日程表空间 32 | int **table = new int *[n + 1]; 33 | for (int i = 0; i <= n; i++) 34 | { 35 | table[i] = new int[n + 1]; 36 | } 37 | 38 | // 进行循环赛日程安排,生成日程表 39 | MatchTable(k, n, table); 40 | 41 | // 显示输出 42 | cout << "------------------------------------------------" << endl; 43 | for (int i = 1; i <= n; i++) 44 | { 45 | for (int j = 1; j <= n; j++) 46 | { 47 | cout << table[i][j] << "\t"; 48 | } 49 | cout << endl; 50 | } 51 | cout << "------------------------------------------------" << endl; 52 | 53 | // 暂停查看结果 54 | system("pause"); 55 | 56 | // 释放内存 57 | for (int i = 0; i <= n; i++) 58 | delete[] table[i]; 59 | delete[] table; 60 | 61 | // 指针置空 62 | table = NULL; 63 | 64 | return 0; 65 | } 66 | 67 | // 进行循环赛日程安排,生成日程表 68 | void MatchTable(int k, int n, int **table) 69 | { 70 | // 设置日程表第一行的值 71 | for (int i = 1; i <= n; i++) 72 | table[1][i] = i; 73 | 74 | // 每次填充的起始填充位置 75 | int begin = 1; 76 | 77 | // 用分治法分separate份,循环求解 78 | for (int separate = 1; separate <= k; separate++) 79 | { 80 | // 日程表进行划分 81 | n /= 2; 82 | 83 | // flag为每一小份的列的标记 84 | for (int flag = 1; flag <= n; flag++) 85 | { 86 | // 操作行 87 | for (int i = begin + 1; i <= 2 * begin; i++) 88 | { 89 | // 操作列 90 | for (int j = begin + 1; j <= 2 * begin; j++) 91 | { 92 | // 把左上角的值赋给右下角 93 | table[i][j + (flag - 1) * begin * 2] = table[i - begin][j + (flag - 1) * begin * 2 - begin]; 94 | // 把右上角的值赋给左下角 95 | table[i][j + (flag - 1) * begin * 2 - begin] = table[i - begin][j + (flag - 1) * begin * 2]; 96 | } 97 | } 98 | } 99 | // 进入日程表的下一个划分进行填充 100 | begin *= 2; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /算法库/Problems/RoundRobinProblem/MatchTable.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgramTraveler/CppMaster/b35252f8804b1a0821dbb10a5d502031973fe53f/算法库/Problems/RoundRobinProblem/MatchTable.exe -------------------------------------------------------------------------------- /算法库/Problems/RoundRobinProblem/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 循环赛日程安排问题 3 | 4 | ### 代码 5 | 6 | [循环赛日程安排问题代码](MatchTable.cpp) 7 | 8 | ### 问题说明 9 | 10 | 设有n=2k个选手要进行网球循环赛, 11 | 要求设计一个满足以下要求的比赛日程表: 12 | 13 | (1)每个选手必须与其他n-1个选手各赛一次; 14 | (2)每个选手一天只能赛一次。 15 | 16 | 按此要求,可将比赛日程表设计成一个 n 行n-1列的二维表, 17 | 其中,第 i 行第 j 列表示和第 i 个选手在第 j 天比赛的选手。 18 | 19 | ### 功能说明 20 | 21 | 本程序运用分治的思想,实现了循环赛日程安排问题的求解, 22 | 生成日程表,输出。 23 | 24 | ### 代码简述 25 | 26 | 通过用户输入数据,程序输入检测,动态分配空间, 27 | 调用生成日程表函数,显示输出。 28 | 29 | 其中,生成日程表函数运用分治的思想,分成separate份, 30 | 先安排第一行(第一份),然后每一份填充,最终求解完毕, 31 | 生成日程表。 -------------------------------------------------------------------------------- /算法库/Problems/TubingProblem/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 输油管道问题 3 | 4 | ### 代码 5 | 6 | [输油管道问题代码](Tubing.cpp) 7 | 8 | ### 问题说明 9 | 10 | 某石油公司计划建造一条由东向西的主输油管道。 11 | 该管道要穿过一个有n 口油井的油田。 12 | 从每口油井都要有一条输油管道沿最短路经(或南或北)与主管道相连。 13 | 如果给定n口油井的位置,即它们的x 坐标(东西向)和y 坐标(南北向), 14 | 应如何确定主管道的最优位置, 15 | 即使各油井到主管道之间的输油管道长度总和最小的位置? 16 | 17 | ### 功能说明 18 | 19 | 本程序用排序求中值的方法求解输油管道问题。 20 | 21 | ### 代码简述 22 | 23 | 通过用户输入数据(只输入油井数n、每个油井的y坐标), 24 | 程序输入检测,动态分配空间,排序(使用快速排序), 25 | 求出中间值,输出。 26 | 27 | 输出有以下两种情况: 28 | 29 | 1. 当n为奇数,则最优位置为y数组的第n/2个油井的y坐标 30 | 31 | 2. 当n为偶数,则最优位置为y数组的中间两个油井的y坐标的区间 -------------------------------------------------------------------------------- /算法库/Problems/TubingProblem/Tubing.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | 4 | // 油井y坐标指针 5 | float * y = NULL; 6 | 7 | // 快速排序 8 | void quick_sort(int low, int high) 9 | { 10 | if (low >= high) // 结束标志 11 | return; 12 | int first = low; // 低位下标 13 | int last = high; // 高位下标 14 | float key = y[first]; // 设第一个为基准 15 | 16 | while (first < last) 17 | { 18 | // 将比第一个小的移到前面 19 | while (first < last && y[last] >= key) 20 | last--; 21 | if (first < last) 22 | y[first++] = y[last]; 23 | 24 | // 将比第一个大的移到后面 25 | while (first < last && y[first] <= key) 26 | first++; 27 | if (first < last) 28 | y[last--] = y[first]; 29 | } 30 | // 基准置位 31 | y[first] = key; 32 | // 前半递归 33 | quick_sort(low, first - 1); 34 | // 后半递归 35 | quick_sort(first + 1, high); 36 | } 37 | 38 | int main() 39 | { 40 | int n; // 油井数 41 | float mid; // y数组的中间位置的数 42 | float minDistance = 0; // 各油井到主管道之间的管道长度总和最小位置 43 | 44 | cout << "---------------- 输油管问题 ----------------" << endl; 45 | cout << "请输入油井数 n (n>=0) " << endl; 46 | 47 | // 输入油井数 48 | cin >> n; 49 | 50 | // 判断输入数据合法性,包括检查输入是否为数字,k值是否大于0 51 | if (cin.fail() || n < 0) 52 | { 53 | cout << "输入n错误!" << endl; 54 | system("pause"); 55 | return 0; 56 | } 57 | 58 | // 分配n个y坐标存储空间 59 | y = new float[n]; 60 | 61 | cout << "请输入 " << n << " 个油井的 y 坐标(用空格隔开)" << endl; 62 | 63 | // 输入油井的 y 坐标 64 | for (auto i = 0; i < n; i++) 65 | { 66 | cin >> y[i]; 67 | } 68 | 69 | // 判断输入数据合法性 70 | if (cin.fail()) 71 | { 72 | cout << "输入y坐标错误!" << endl; 73 | system("pause"); 74 | return 0; 75 | } 76 | 77 | // 运用快速排序对y坐标数组进行排序 78 | quick_sort(0, n - 1); 79 | 80 | // 计算y数组的中间位置的数 81 | mid = y[n / 2]; 82 | 83 | // 计算各个油井到主输油管的长度之和 84 | for (auto i = 0; i < n; i++) 85 | { 86 | minDistance += abs(y[i] - mid); 87 | } 88 | 89 | // 显示输出 90 | cout << "------------------------------------------------" << endl; 91 | // 判断油井奇偶,做不同的输出 92 | if (n & 1) 93 | { 94 | // n为奇数,则最优位置为y数组的第n/2个油井的y坐标 95 | cout << "主管道的最优位置为:y = " << mid << endl; 96 | } 97 | else 98 | { 99 | // n为偶数,则最优位置为y数组的中间两个油井的y坐标的区间 100 | cout << "主管道的最优位置为:y = [" << y[n / 2 - 1] << "," << mid << "]" << endl; 101 | } 102 | // 输出各油井到主管道之间的管道总长度 103 | cout << "各油井到主管道之间的管道总长度为:" << minDistance << endl; 104 | cout << "------------------------------------------------" << endl; 105 | 106 | // 暂停查看结果 107 | system("pause"); 108 | 109 | // 释放内存 110 | delete[] y; 111 | 112 | // 指针置空 113 | y = NULL; 114 | 115 | return 0; 116 | } 117 | -------------------------------------------------------------------------------- /算法库/Problems/TubingProblem/Tubing.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProgramTraveler/CppMaster/b35252f8804b1a0821dbb10a5d502031973fe53f/算法库/Problems/TubingProblem/Tubing.exe -------------------------------------------------------------------------------- /算法库/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## ❓ Problems 4 | 5 | ### Single Problem 6 | 7 | * [Chessboard Coverage Problem(棋盘覆盖问题)](Problems/ChessboardCoverageProblem) 8 | * [Knapsack Problem(背包问题)](Problems/KnapsackProblem) 9 | * [Neumann Neighbor Problem(冯诺依曼邻居问题)](Problems/NeumannNeighborProblem) 10 | * [Round Robin Problem(循环赛日程安排问题)](Problems/RoundRobinProblem) 11 | * [Tubing Problem(输油管道问题)](Problems/TubingProblem) 12 | 13 | ### Leetcode Problems 14 | 15 | * [Github . Charmve/LeetCode4FLAG](https://github.com/Charmve/LeetCode4FLAG) 16 | * [Github . haoel/leetcode](https://github.com/haoel/leetcode) 17 | * [Github . pezy/LeetCode](https://github.com/pezy/LeetCode) 18 | 19 | ### 剑指 Offer 20 | 21 | * [Github . zhedahht/CodingInterviewChinese2](https://github.com/zhedahht/CodingInterviewChinese2) 22 | * [Github . gatieme/CodingInterviews](https://github.com/gatieme/CodingInterviews) 23 | -------------------------------------------------------------------------------- /编译链接/采用dlopen、dlsym、dlclose加载动态链接库.md: -------------------------------------------------------------------------------- 1 | # 采用dlopen、dlsym、dlclose加载动态链接库,以及GDB调试 2 | 3 | 为了使程序方便扩展,具备通用性,可以采用插件形式。采用异步事件驱动模型,保证主程序逻辑不变,将各个业务已动态链接库的形式加载进来,这就是所谓的插件。 4 | linux提供了加载和处理动态链接库的系统调用,非常方便。本文先从使用上进行总结,涉及到基本的操作方法,关于动态链接库的本质及如何加载进来,需要进一步学习, 5 | 后续继续补充。如何将程序设计为插件形式,挖掘出主题和业务之间的关系,需要进一步去学习。 6 | 7 | ## 环境 8 | 9 | 系统: 16.04.1-Ubuntu 10 | 11 | 编译器: gnu 5.4.0 12 | 13 | python: 2.7.12 14 | 15 | ## 参考 16 | [采用dlopen、dlsym、dlclose加载动态链接库【总结】](https://www.cnblogs.com/Anker/p/3746802.html) 17 | 18 | ## dlopen、dlsym及dlclose 基本使用 19 | ``` 20 | // file : add.c 21 | int add(int a, int b) { return a+b; }; 22 | 23 | // cmd: gcc -fPIC -shared -o libadd.so add.c 24 | // 编译生成动态库文件 25 | 26 | // file : demo.c 27 | #include 28 | #include // EXIT_FAILURE 29 | #include // dlopen, dlerror, dlsym, dlclose 30 | 31 | typedef int(* FUNC_ADD)(int, int); // 定义函数指针类型的别名 32 | const char* dllPath = "./libadd.so"; 33 | 34 | int main() 35 | { 36 | void* handle = dlopen( dllPath, RTLD_LAZY ); 37 | 38 | if( !handle ) 39 | { 40 | fprintf( stderr, "[%s](%d) dlopen get error: %s\n", __FILE__, __LINE__, dlerror() ); 41 | exit( EXIT_FAILURE ); 42 | } 43 | 44 | do{ // for resource handle 45 | FUNC_ADD add_func = (FUNC_ADD)dlsym( handle, "add" ); 46 | printf( "1 add 2 is %d \n", add_func(1,2) ); 47 | }while(0); // for resource handle 48 | dlclose( handle ); 49 | } 50 | // cmd : gcc -o demo demo.c -ldl; ./demo 51 | // output: 1 add 2 is 3 52 | ``` 53 | 54 | ## C++ 的命名 55 | 对于上述文件,采用 g++ 编译,会导致段错误如下: 56 | ```bash 57 | > g++ -fPIC -shared -g -o libadd.so add.c // -g 添加调试信息 58 | > g++ -g -o demo demo.c -ldl 59 | > ./demo 60 | 61 | 段错误 (核心已转储) 62 | 63 | > ulimit -c unlimited // 设置 core 文件大小为无限制 64 | > ./demo 生成 core 文件 65 | > gdb ./demo core 调试段错误 66 | 67 | [New LWP 4396] 68 | Core was generated by `./demo'. 69 | Program terminated with signal SIGSEGV, Segmentation fault. 70 | #0 0x0000000000000000 in ?? () 71 | 72 | > (gdb) break 19 // 设置断点在 FUNC_ADD add_func = (FUNC_ADD)dlsym( handle, "add" ); 73 | > (gdb) print add_func 74 | 75 | $1 = (FUNC_ADD) 0x0 76 | 77 | > (gdb) n 78 | 20 printf( "1 add 2 is %d \n", add_func(1,2) ); 79 | > (gdb) n 80 | 81 | Program received signal SIGSEGV, Segmentation fault. 82 | 0x0000000000000000 in ?? () 83 | 84 | 错误原因在于 dlsym 返回值为 0,通过该地址执行 add_func 导致段错误 85 | ``` 86 | 87 | 进一步分析原因,C++中尽管函数名称为 add 但是生成的 so 文件中的符号不是 add, 因为 c++ 要实现同名函数的重载,需要对函数命进行修饰,具体规则如下: C++函数名称修饰规则,可以通过查看符号表确认: 88 | ``` 89 | > nm libadd.so 90 | 0000000000000600 T _Z3addii 91 | > readelf -s libadd.so 92 | 11: 0000000000000600 20 FUNC GLOBAL DEFAULT 9 _Z3addii 93 | ``` 94 | 95 | 那么是不是可以通过 dlsym 打开符号 _Z3addii 来正确调用,尝试如下 96 | ``` 97 | > vim demo.c 98 | 99 | 19 // FUNC_ADD add_func = (FUNC_ADD)dlsym( handle, "add" ); 100 | 20 FUNC_ADD add_func = (FUNC_ADD)dlsym( handle, "_Z3addii" ); 101 | 102 | > g++ -g -o demo demo.c; ./demo 103 | 104 | 1 add 2 is 3 105 | 106 | > gdb demo 107 | > (gdb) break 21 108 | > (gdb) run 109 | > (gdb) print add_func 110 | $1 = (FUNC_ADD) 0x7ffff7607600 111 | 112 | // ? 这里的地址与 so 的不一致,待深入分析 113 | ``` 114 | 115 | 最好的方法是通过 extern C 来处理 116 | ``` 117 | // file : add.c 118 | #ifdef __cplusplus 119 | extern "C"{ 120 | #endif 121 | 122 | int add(int a, int b) {return a+b; } 123 | 124 | #ifdef __cplusplus 125 | } 126 | #endif 127 | > g++ -fPIC -shared -g -o libadd.so add.c // -g 添加调试信息 128 | > nm libadd.so 129 | 130 | 0000000000000600 T add 131 | ``` 132 | 133 | ## 尝试更改 add 的可见性 134 | 135 | ``` 136 | // file : add.c 137 | #ifdef __cplusplus 138 | extern "C"{ 139 | #endif 140 | 141 | static int add(int a, int b) {return a+b; } 142 | 143 | #ifdef __cplusplus 144 | } 145 | #endif 146 | > g++ -fPIC -shared -g -o libadd.so add.c // -g 添加调试信息 147 | > nm libadd.so 148 | 149 | 00000000000005e0 t add 150 | 151 | > g++ -o demo demo.c -ldl; ./demo 152 | 153 | 段错误 (核心已转储) 154 | 155 | 156 | ## python调用动态库(ctypes方式) 157 | 158 | [Python调用C/C++动态链接库](https://blog.csdn.net/cjf_iceking/article/details/17113839) 159 | 160 | ```python 161 | #! /usr/bin/python 162 | #-*- coding=utf-8 -*- 163 | 164 | import ctypes 165 | 166 | libadd = ctypes.cdll.LoadLibrary( ".//libadd.so" ) 167 | print "1 add 2 is", libadd.add( 1, 2 ) 168 | 169 | # chmod +x ./demo.py 170 | # ./demo.py 171 | 172 | 1 add 2 is 3 173 | ``` 174 | 175 | ## ctypes 的源码实现 176 | 177 | > 猜想是 dlopen 立 flags 睡觉 178 | 179 |
180 | 181 | 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 182 | -------------------------------------------------------------------------------- /计算机网络/C++并发编程.md: -------------------------------------------------------------------------------- 1 | ## C++并发编程 2 | 3 | - [C++11并发之std::thread](https://blog.csdn.net/liuker888/article/details/46848905) 4 | - [C++11并发之std::mutex](https://blog.csdn.net/liuker888/article/details/46848957) 5 | -------------------------------------------------------------------------------- /计算机网络/Socket问题合集.md: -------------------------------------------------------------------------------- 1 | # Q1 ★★☆ 五种 IO 模型的特点以及比较 2 | 3 | Unix 有五种 I/O 模型: 4 | 5 | - 阻塞式 I/O 6 | - 非阻塞式 I/O 7 | - I/O 复用(select 和 poll) 8 | - 信号驱动式 I/O(SIGIO) 9 | - 异步 I/O(AIO) 10 | 11 | ## 阻塞式 I/O 12 | 13 | 应用进程被阻塞,直到数据从内核缓冲区复制到应用进程缓冲区中才返回。 14 | 15 | 应该注意到,在阻塞的过程中,其它应用进程还可以执行,因此阻塞不意味着整个操作系统都被阻塞。因为其它应用进程还可以执行,所以不消耗 CPU 时间,这种模型的 CPU 利用率效率会比较高。 16 | 17 | 下图中,recvfrom() 用于接收 Socket 传来的数据,并复制到应用进程的缓冲区 buf 中。这里把 recvfrom() 当成系统调用。 18 | 19 | ```c 20 | ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); 21 | ``` 22 | 23 |

24 | 25 | ## 非阻塞式 I/O 26 | 27 | 应用进程执行系统调用之后,内核返回一个错误码。应用进程可以继续执行,但是需要不断的执行系统调用来获知 I/O 是否完成,这种方式称为轮询(polling)。 28 | 29 | 由于 CPU 要处理更多的系统调用,因此这种模型的 CPU 利用率比较低。 30 | 31 |

32 | 33 | ## I/O 复用 34 | 35 | 使用 select 或者 poll 等待数据,并且可以等待多个套接字中的任何一个变为可读。这一过程会被阻塞,当某一个套接字可读时返回,之后再使用 recvfrom 把数据从内核复制到进程中。 36 | 37 | 它可以让单个进程具有处理多个 I/O 事件的能力。又被称为 Event Driven I/O,即事件驱动 I/O。 38 | 39 | 如果一个 Web 服务器没有 I/O 复用,那么每一个 Socket 连接都需要创建一个线程去处理。如果同时有几万个连接,那么就需要创建相同数量的线程。相比于多进程和多线程技术,I/O 复用不需要进程线程创建和切换的开销,系统开销更小。 40 | 41 |

42 | 43 | ## 信号驱动 I/O 44 | 45 | 应用进程使用 sigaction 系统调用,内核立即返回,应用进程可以继续执行,也就是说等待数据阶段应用进程是非阻塞的。内核在数据到达时向应用进程发送 SIGIO 信号,应用进程收到之后在信号处理程序中调用 recvfrom 将数据从内核复制到应用进程中。 46 | 47 | 相比于非阻塞式 I/O 的轮询方式,信号驱动 I/O 的 CPU 利用率更高。 48 | 49 |

50 | 51 | ## 异步 I/O 52 | 53 | 应用进程执行 aio_read 系统调用会立即返回,应用进程可以继续执行,不会被阻塞,内核会在所有操作完成之后向应用进程发送信号。 54 | 55 | 异步 I/O 与信号驱动 I/O 的区别在于,异步 I/O 的信号是通知应用进程 I/O 完成,而信号驱动 I/O 的信号是通知应用进程可以开始 I/O。 56 | 57 |

58 | 59 | ## 五大 I/O 模型比较 60 | 61 | - 同步 I/O:将数据从内核缓冲区复制到应用进程缓冲区的阶段,应用进程会阻塞。 62 | - 异步 I/O:不会阻塞。 63 | 64 | 阻塞式 I/O、非阻塞式 I/O、I/O 复用和信号驱动 I/O 都是同步 I/O,它们的主要区别在第一个阶段。 65 | 66 | 非阻塞式 I/O 、信号驱动 I/O 和异步 I/O 在第一阶段不会阻塞。 67 | 68 |

69 | 70 | 71 | 72 | 73 | # Q2 ★★★ select、poll、epoll 的原理、比较、以及使用场景;epoll 的水平触发与边缘触发 74 | 75 | select/poll/epoll 都是 I/O 多路复用的具体实现,select 出现的最早,之后是 poll,再是 epoll。 76 | 77 | ## select 78 | 79 | ```c 80 | int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 81 | ``` 82 | 83 | 有三种类型的描述符类型:readset、writeset、exceptset,分别对应读、写、异常条件的描述符集合。fd_set 使用数组实现,数组大小使用 FD_SETSIZE 定义。 84 | 85 | timeout 为超时参数,调用 select 会一直阻塞直到有描述符的事件到达或者等待的时间超过 timeout。 86 | 87 | 成功调用返回结果大于 0,出错返回结果为 -1,超时返回结果为 0。 88 | 89 | ```c 90 | fd_set fd_in, fd_out; 91 | struct timeval tv; 92 | 93 | // Reset the sets 94 | FD_ZERO( &fd_in ); 95 | FD_ZERO( &fd_out ); 96 | 97 | // Monitor sock1 for input events 98 | FD_SET( sock1, &fd_in ); 99 | 100 | // Monitor sock2 for output events 101 | FD_SET( sock2, &fd_out ); 102 | 103 | // Find out which socket has the largest numeric value as select requires it 104 | int largest_sock = sock1 > sock2 ? sock1 : sock2; 105 | 106 | // Wait up to 10 seconds 107 | tv.tv_sec = 10; 108 | tv.tv_usec = 0; 109 | 110 | // Call the select 111 | int ret = select( largest_sock + 1, &fd_in, &fd_out, NULL, &tv ); 112 | 113 | // Check if select actually succeed 114 | if ( ret == -1 ) 115 | // report error and abort 116 | else if ( ret == 0 ) 117 | // timeout; no event detected 118 | else 119 | { 120 | if ( FD_ISSET( sock1, &fd_in ) ) 121 | // input event on sock1 122 | 123 | if ( FD_ISSET( sock2, &fd_out ) ) 124 | // output event on sock2 125 | } 126 | ``` 127 | 128 | ## poll 129 | 130 | ```c 131 | int poll(struct pollfd *fds, unsigned int nfds, int timeout); 132 | ``` 133 | 134 | pollfd 使用链表实现。 135 | 136 | ```c 137 | // The structure for two events 138 | struct pollfd fds[2]; 139 | 140 | // Monitor sock1 for input 141 | fds[0].fd = sock1; 142 | fds[0].events = POLLIN; 143 | 144 | // Monitor sock2 for output 145 | fds[1].fd = sock2; 146 | fds[1].events = POLLOUT; 147 | 148 | // Wait 10 seconds 149 | int ret = poll( &fds, 2, 10000 ); 150 | // Check if poll actually succeed 151 | if ( ret == -1 ) 152 | // report error and abort 153 | else if ( ret == 0 ) 154 | // timeout; no event detected 155 | else 156 | { 157 | // If we detect the event, zero it out so we can reuse the structure 158 | if ( fds[0].revents & POLLIN ) 159 | fds[0].revents = 0; 160 | // input event on sock1 161 | 162 | if ( fds[1].revents & POLLOUT ) 163 | fds[1].revents = 0; 164 | // output event on sock2 165 | } 166 | ``` 167 | 168 | ## 比较 169 | 170 | ### 1. 功能 171 | 172 | select 和 poll 的功能基本相同,不过在一些实现细节上有所不同。 173 | 174 | - select 会修改描述符,而 poll 不会; 175 | - select 的描述符类型使用数组实现,FD_SETSIZE 大小默认为 1024,因此默认只能监听 1024 个描述符。如果要监听更多描述符的话,需要修改 FD_SETSIZE 之后重新编译;而 poll 的描述符类型使用链表实现,没有描述符数量的限制; 176 | - poll 提供了更多的事件类型,并且对描述符的重复利用上比 select 高。 177 | - 如果一个线程对某个描述符调用了 select 或者 poll,另一个线程关闭了该描述符,会导致调用结果不确定。 178 | 179 | ### 2. 速度 180 | 181 | select 和 poll 速度都比较慢。 182 | 183 | - select 和 poll 每次调用都需要将全部描述符从应用进程缓冲区复制到内核缓冲区。 184 | - select 和 poll 的返回结果中没有声明哪些描述符已经准备好,所以如果返回值大于 0 时,应用进程都需要使用轮询的方式来找到 I/O 完成的描述符。 185 | 186 | ### 3. 可移植性 187 | 188 | 几乎所有的系统都支持 select,但是只有比较新的系统支持 poll。 189 | 190 | ## epoll 191 | 192 | ```c 193 | int epoll_create(int size); 194 | int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 195 | int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); 196 | ``` 197 | 198 | epoll_ctl() 用于向内核注册新的描述符或者是改变某个文件描述符的状态。已注册的描述符在内核中会被维护在一棵红黑树上,通过回调函数内核会将 I/O 准备好的描述符加入到一个链表中管理,进程调用 epoll_wait() 便可以得到事件完成的描述符。 199 | 200 | 从上面的描述可以看出,epoll 只需要将描述符从进程缓冲区向内核缓冲区拷贝一次,并且进程不需要通过轮询来获得事件完成的描述符。 201 | 202 | epoll 仅适用于 Linux OS。 203 | 204 | epoll 比 select 和 poll 更加灵活而且没有描述符数量限制。 205 | 206 | epoll 对多线程编程更有友好,一个线程调用了 epoll_wait() 另一个线程关闭了同一个描述符也不会产生像 select 和 poll 的不确定情况。 207 | 208 | ```c 209 | // Create the epoll descriptor. Only one is needed per app, and is used to monitor all sockets. 210 | // The function argument is ignored (it was not before, but now it is), so put your favorite number here 211 | int pollingfd = epoll_create( 0xCAFE ); 212 | 213 | if ( pollingfd < 0 ) 214 | // report error 215 | 216 | // Initialize the epoll structure in case more members are added in future 217 | struct epoll_event ev = { 0 }; 218 | 219 | // Associate the connection class instance with the event. You can associate anything 220 | // you want, epoll does not use this information. We store a connection class pointer, pConnection1 221 | ev.data.ptr = pConnection1; 222 | 223 | // Monitor for input, and do not automatically rearm the descriptor after the event 224 | ev.events = EPOLLIN | EPOLLONESHOT; 225 | // Add the descriptor into the monitoring list. We can do it even if another thread is 226 | // waiting in epoll_wait - the descriptor will be properly added 227 | if ( epoll_ctl( epollfd, EPOLL_CTL_ADD, pConnection1->getSocket(), &ev ) != 0 ) 228 | // report error 229 | 230 | // Wait for up to 20 events (assuming we have added maybe 200 sockets before that it may happen) 231 | struct epoll_event pevents[ 20 ]; 232 | 233 | // Wait for 10 seconds, and retrieve less than 20 epoll_event and store them into epoll_event array 234 | int ready = epoll_wait( pollingfd, pevents, 20, 10000 ); 235 | // Check if epoll actually succeed 236 | if ( ret == -1 ) 237 | // report error and abort 238 | else if ( ret == 0 ) 239 | // timeout; no event detected 240 | else 241 | { 242 | // Check if any events detected 243 | for ( int i = 0; i < ret; i++ ) 244 | { 245 | if ( pevents[i].events & EPOLLIN ) 246 | { 247 | // Get back our connection pointer 248 | Connection * c = (Connection*) pevents[i].data.ptr; 249 | c->handleReadEvent(); 250 | } 251 | } 252 | } 253 | ``` 254 | 255 | 256 | ## 工作模式 257 | 258 | epoll 的描述符事件有两种触发模式:LT(level trigger)和 ET(edge trigger)。 259 | 260 | ### 1. LT 模式 261 | 262 | 当 epoll_wait() 检测到描述符事件到达时,将此事件通知进程,进程可以不立即处理该事件,下次调用 epoll_wait() 会再次通知进程。是默认的一种模式,并且同时支持 Blocking 和 No-Blocking。 263 | 264 | ### 2. ET 模式 265 | 266 | 和 LT 模式不同的是,通知之后进程必须立即处理事件,下次再调用 epoll_wait() 时不会再得到事件到达的通知。 267 | 268 | 很大程度上减少了 epoll 事件被重复触发的次数,因此效率要比 LT 模式高。只支持 No-Blocking,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。 269 | 270 | ## 应用场景 271 | 272 | 很容易产生一种错觉认为只要用 epoll 就可以了,select 和 poll 都已经过时了,其实它们都有各自的使用场景。 273 | 274 | ### 1. select 应用场景 275 | 276 | select 的 timeout 参数精度为 1ns,而 poll 和 epoll 为 1ms,因此 select 更加适用于实时性要求比较高的场景,比如核反应堆的控制。 277 | 278 | select 可移植性更好,几乎被所有主流平台所支持。 279 | 280 | ### 2. poll 应用场景 281 | 282 | poll 没有最大描述符数量的限制,如果平台支持并且对实时性要求不高,应该使用 poll 而不是 select。 283 | 284 | ### 3. epoll 应用场景 285 | 286 | 只需要运行在 Linux 平台上,有大量的描述符需要同时轮询,并且这些连接最好是长连接。 287 | 288 | 需要同时监控小于 1000 个描述符,就没有必要使用 epoll,因为这个应用场景下并不能体现 epoll 的优势。 289 | 290 | 需要监控的描述符状态变化多,而且都是非常短暂的,也没有必要使用 epoll。因为 epoll 中的所有描述符都存储在内核中,造成每次需要对描述符的状态改变都需要通过 epoll_ctl() 进行系统调用,频繁系统调用降低效率。并且 epoll 的描述符存储在内核,不容易调试。 291 | -------------------------------------------------------------------------------- /计算机网络/网络基础问题合集.md: -------------------------------------------------------------------------------- 1 | # Q 1. ★★★ 各层协议的作用,以及 TCP/IP 协议的特点 2 | 3 | - **应用层** :为特定应用程序提供数据传输服务,例如 HTTP、DNS 等协议。数据单位为报文。 4 | 5 | - **传输层** :为进程提供通用数据传输服务。由于应用层协议很多,定义通用的传输层协议就可以支持不断增多的应用层协议。运输层包括两种协议:传输控制协议 TCP,提供面向连接、可靠的数据传输服务,数据单位为报文段;用户数据报协议 UDP,提供无连接、尽最大努力的数据传输服务,数据单位为用户数据报。TCP 主要提供完整性服务,UDP 主要提供及时性服务。 6 | 7 | - **网络层** :为主机提供数据传输服务。而传输层协议是为主机中的进程提供数据传输服务。网络层把传输层传递下来的报文段或者用户数据报封装成分组。 8 | 9 | - **数据链路层** :网络层针对的还是主机之间的数据传输服务,而主机之间可以有很多链路,链路层协议就是为同一链路的主机提供数据传输服务。数据链路层把网络层传下来的分组封装成帧。 10 | 11 | - **物理层** :考虑的是怎样在传输媒体上传输数据比特流,而不是指具体的传输媒体。物理层的作用是尽可能屏蔽传输媒体和通信手段的差异,使数据链路层感觉不到这些差异。 12 | 13 | ## TCP/IP协议的特点 14 | 15 | 它只有四层,相当于五层协议中数据链路层和物理层合并为网络接口层。 16 | 17 | TCP/IP 体系结构不严格遵循 OSI 分层概念,应用层可能会直接使用 IP 层或者网络接口层。 18 | 19 |

20 | 21 | 22 | # Q 2. ★★☆ 以太网的特点,以及帧结构 23 | 24 | 以太网是一种星型拓扑结构局域网 25 | 26 |

27 | 28 | 早期使用集线器进行连接,集线器是一种**物理层设备**, 作用于比特而不是帧,当一个比特到达接口时,集线器重新生成这个比特,并将其能量强度放大,从而扩大网络的传输距离,之后再将这个比特发送到其它所有接口。如果集线器同时收到两个不同接口的帧,那么就发生了碰撞。 29 | 30 | 目前以太网使用交换机替代了集线器,交换机是一种**链路层设备**,它不会发生碰撞,能根据 MAC 地址进行存储转发。 31 | 32 | 以太网帧格式: 33 | 34 | - **类型** :标记上层使用的协议; 35 | - **数据** :长度在 46-1500 之间,如果太小则需要填充; 36 | - **FCS** :帧检验序列,使用的是 CRC 检验方法; 37 | 38 |

39 | 40 | # Q 3. ★★☆ 集线器、交换机、路由器的作用,以及所属的网络层 41 | 42 | 集线器是一种**物理层设备**, 作用于比特而不是帧,当一个比特到达接口时,集线器重新生成这个比特,并将其能量强度放大,从而扩大网络的传输距离,之后再将这个比特发送到其它所有接口。如果集线器同时收到两个不同接口的帧,那么就发生了碰撞。 43 | 44 | 交换机是一种**链路层设备**,具有自学习能力,学习的是交换表的内容,交换表中存储着 MAC 地址到接口的映射。它不会发生碰撞,能根据 MAC 地址进行存储转发。 45 | 46 | 路由器是一种**网络层设备**,拥有如下作用: 47 | 48 | - 网络互连:路由器支持各种局域网和广域网接口,主要用于互连局域网和广域网,实现不同网络互相通信; 49 | - 数据处理:提供包括分组过滤、分组转发、优先级、复用、加密、压缩和防火墙等功能; 50 | - 网络管理:路由器提供包括路由器配置管理、性能管理、容错管理和流量控制等功能。 51 | 52 | 53 | # Q 4. ★★☆ IP 数据数据包常见字段的作用 54 | 55 |

56 | 57 | - **版本** : 有 4(IPv4)和 6(IPv6)两个值; 58 | 59 | - **首部长度** : 占 4 位,因此最大值为 15。值为 1 表示的是 1 个 32 位字的长度,也就是 4 字节。因为首部固定长度为 20 字节,因此该值最小为 5。如果可选字段的长度不是 4 字节的整数倍,就用尾部的填充部分来填充。 60 | 61 | - **区分服务** : 用来获得更好的服务,一般情况下不使用。 62 | 63 | - **总长度** : 包括首部长度和数据部分长度。 64 | 65 | - **生存时间** :TTL,它的存在是为了防止无法交付的数据报在互联网中不断兜圈子。以路由器跳数为单位,当 TTL 为 0 时就丢弃数据报。 66 | 67 | - **协议** :指出携带的数据应该上交给哪个协议进行处理,例如 ICMP、TCP、UDP 等。 68 | 69 | - **首部检验和** :因为数据报每经过一个路由器,都要重新计算检验和,因此检验和不包含数据部分可以减少计算的工作量。 70 | 71 | - **标识** : 在数据报长度过长从而发生分片的情况下,相同数据报的不同分片具有相同的标识符。 72 | 73 | - **片偏移** : 和标识符一起,用于发生分片的情况。片偏移的单位为 8 字节。 74 | 75 |

76 | 77 | # Q 5. ★☆☆ ARP 协议的作用,以及维护 ARP 缓存的过程 78 | 79 | 网络层实现主机之间的通信,而链路层实现具体每段链路之间的通信。因此在通信过程中,IP 数据报的源地址和目的地址始终不变,而 MAC 地址随着链路的改变而改变。 80 | 81 |

82 | 83 | ARP 实现由 IP 地址得到 MAC 地址。 84 | 85 |

86 | 87 | 每个主机都有一个 ARP 高速缓存,里面有本局域网上的各主机和路由器的 IP 地址到 MAC 地址的映射表。 88 | 89 | 如果主机 A 知道主机 B 的 IP 地址,但是 ARP 高速缓存中没有该 IP 地址到 MAC 地址的映射,此时主机 A 通过广播的方式发送 ARP 请求分组,主机 B 收到该请求后会发送 ARP 响应分组给主机 A 告知其 MAC 地址,随后主机 A 向其高速缓存中写入主机 B 的 IP 地址到 MAC 地址的映射。 90 | 91 |

92 | 93 | 94 | # Q 6.1 ★★☆ ICMP 报文种类以及作用; 95 | 96 | ICMP报文分为差错报告报文和询问报文。 97 | 98 |

99 | 100 | # Q 6.2 ★★☆ 和 IP 数据包的关系; 101 | 102 | ICMP 是为了更有效地转发 IP 数据报和提高交付成功的机会。它封装在 IP 数据报中,但是不属于高层协议。 103 | 104 |

105 | 106 | # Q 6.3 ★★☆ Ping 和 Traceroute 的具体原理 107 | 108 | ## 1. Ping 109 | 110 | Ping 是 ICMP 的一个重要应用,主要用来测试两台主机之间的连通性。 111 | 112 | Ping 的原理是通过向目的主机发送 ICMP Echo 请求报文,目的主机收到之后会发送 Echo 回答报文。Ping 会根据时间和成功响应的次数估算出数据包往返时间以及丢包率。 113 | 114 | ## 2. Traceroute 115 | 116 | Traceroute 是 ICMP 的另一个应用,用来跟踪一个分组从源点到终点的路径。 117 | 118 | Traceroute 发送的 IP 数据报封装的是无法交付的 UDP 用户数据报,并由目的主机发送终点不可达差错报告报文。 119 | 120 | - 源主机向目的主机发送一连串的 IP 数据报。第一个数据报 P1 的生存时间 TTL 设置为 1,当 P1 到达路径上的第一个路由器 R1 时,R1 收下它并把 TTL 减 1,此时 TTL 等于 0,R1 就把 P1 丢弃,并向源主机发送一个 ICMP 时间超过差错报告报文; 121 | - 源主机接着发送第二个数据报 P2,并把 TTL 设置为 2。P2 先到达 R1,R1 收下后把 TTL 减 1 再转发给 R2,R2 收下后也把 TTL 减 1,由于此时 TTL 等于 0,R2 就丢弃 P2,并向源主机发送一个 ICMP 时间超过差错报文。 122 | - 不断执行这样的步骤,直到最后一个数据报刚刚到达目的主机,主机不转发数据报,也不把 TTL 值减 1。但是因为数据报封装的是无法交付的 UDP,因此目的主机要向源主机发送 ICMP 终点不可达差错报告报文。 123 | - 之后源主机知道了到达目的主机所经过的路由器 IP 地址以及到达每个路由器的往返时间。 124 | 125 | 126 | # Q 7. ★★★ UDP 与 TCP 比较,分析上层协议应该使用 UDP 还是 TCP 127 | 128 | - 用户数据报协议 UDP(User Datagram Protocol)是无连接的,尽最大可能交付,没有拥塞控制,面向报文(对于应用程序传下来的报文不合并也不拆分,只是添加 UDP 首部),支持一对一、一对多、多对一和多对多的交互通信。 129 | 130 | - 传输控制协议 TCP(Transmission Control Protocol)是面向连接的,提供可靠交付,有流量控制,拥塞控制,提供全双工通信,面向字节流(把应用层传下来的报文看成字节流,把字节流组织成大小不等的数据块),每一条 TCP 连接只能是点对点的(一对一)。 131 | 132 | # Q 8.1 ★★★ 理解三次握手以及四次挥手具体过程,三次握手的原因、四次挥手原因 133 | 134 | # TCP 的三次握手 135 | 136 |

137 | 138 | 为了可靠,第三次握手是为了防止失效的连接请求到达服务器,让服务器错误打开连接。 139 | 140 | 客户端发送的连接请求如果在网络中滞留,那么就会隔很长一段时间才能收到服务器端发回的连接确认。客户端等待一个超时重传时间之后,就会重新请求连接。但是这个滞留的连接请求最后还是会到达服务器,如果不进行三次握手,那么服务器就会打开两个连接。如果有第三次握手,客户端会忽略服务器之后发送的对滞留连接请求的连接确认,不进行第三次握手,因此就不会再次打开连接。 141 | 142 | 143 | # TCP 的四次挥手 144 | 145 |

146 | 147 | 客户端发送了 FIN 连接释放报文之后,客户端进入FIN_WAIT_1 的状态;服务器收到了这个报文,就进入了 CLOSE-WAIT 状态。这个状态是为了让服务器端发送还未传送完毕的数据,传送完毕之后,服务器会发送 FIN 连接释放报文。 148 | 149 | # Q 8.2 ★★★ TIME_WAIT 的作用 150 | 151 | 有两个原因: 152 | - TCP协议要求客户端最后会等待一段时间TIME_WAIT=2MSL(MSL是报文最大生存时间),这个时间足够长,长到如果服务器没有收到ACK的话,服务器的FIN会重新发的,客户端会重新发一个ACK并且足够时间到达服务器。 153 | - 等待一段时间是为了让本连接持续时间内所产生的所有报文都从网络中消失,使得下一个新的连接不会出现旧的连接请求报文。 154 | 155 | # Q 9. ★★★ 可靠传输原理,并设计可靠 UDP 协议 156 | 157 | TCP 使用**超时重传**来实现可靠传输,对每一个发送了但是还没有ACK的包,都有设定一个定时器,如果超过了一定时间,就需要重新尝试。 158 | 159 | 第二种是**Selective Acknowledgment(SACK)** 这种方式需要在TCP头里增加一个SACK的东西,可以将缓存的地图发送给发送方,例如可以发送SACK6、SACK8、SACK9,有了地图,发送方一下子就能看出是7丢了。 160 | 161 | 如果一个已经发送的报文段在超时时间内没有收到确认,那么就重传这个报文段。这种模式被称为**累计确认**和**累计应答** 162 | 163 | 为了记录所有发送的包和接受的包,TCP也需要发送端和接收端分别斗鱼缓存来保存这些记录。发送端的缓存是按照包的ID一个个排列,根绝情况分成四个部分。 164 | 165 | - 1.发送了并且已经确认了的; 166 | 167 | - 2.发送了并且尚未确认的; 168 | 169 | - 3.没有发送,但是已经等待发送的; 170 | 171 | - 4.没有发送,并且暂时还不会发送的。 172 | 173 | ## 设计可靠UDP协议 174 | 175 | UDP协议的缺点是: 176 | 1. 丢包:帧同步中逻辑帧在每个Client上一定是一致的,也就是说决定不能出现丢帧的情况 177 | 178 | 2. 数据完整性:UDP协议头部虽然有16位的校验和,但是IPv4并不强制执行,也就是说UDP无法抱枕数据的完整性 179 | 180 | 3. 乱序: UDP并不保证数据的顺序,故可能出现数据包乱序问题 181 | 182 | 既然原生UDP有这些缺点,那么能不能像应用层协议一样在UDP数据包头再加一段包头,从而定义RUDP呢,首先思考RUDP需要解决哪些问题,然后根据问题加上必要的包头字段。 183 | 1. 数据完整性 –> 加上一个16或者32位的CRC验证字段 184 | 185 | 2. 乱序 –> 加上一个数据包序列号SEQ 186 | 187 | 3. 丢包 –> 需要确认和重传机制,就是和Tcp类似的Ack机制 188 | 189 | 在思考一下既然是自定义协议是不是需要一个协议号字段来过滤一些非法包,那么又可以加入一个新字段: 190 | 191 | 4. 协议字段 –> protol 字段,标识当前使用协议 192 | 193 | 综合以上字段,我们的RUDP就可以简单实现成如下: 194 | 195 |

196 | 197 | 198 | # Q 10. ★★☆ TCP 拥塞控制的作用,理解具体原理 199 | 200 | 如果网络出现拥塞,分组将会丢失,此时发送方会继续重传,从而导致网络拥塞程度更高。因此当出现拥塞时,应当控制发送方的速率。这一点和流量控制很像,但是出发点不同。流量控制是为了让接收方能来得及接收,而拥塞控制是为了降低整个网络的拥塞程度。 201 | 202 |

203 | 204 | TCP 主要通过四个算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。 205 | 206 | 发送方需要维护一个叫做拥塞窗口(cwnd)的状态变量,注意拥塞窗口与发送方窗口的区别:拥塞窗口只是一个状态变量,实际决定发送方能发送多少数据的是发送方窗口。 207 | 208 | 为了便于讨论,做如下假设: 209 | 210 | - 接收方有足够大的接收缓存,因此不会发生流量控制; 211 | - 虽然 TCP 的窗口基于字节,但是这里设窗口的大小单位为报文段。 212 | 213 |

214 | 215 | ## 1. 慢开始与拥塞避免 216 | 217 | 发送的最初执行慢开始,令 cwnd = 1,发送方只能发送 1 个报文段;当收到确认后,将 cwnd 加倍,因此之后发送方能够发送的报文段数量为:2、4、8 ... 218 | 219 | 注意到慢开始每个轮次都将 cwnd 加倍,这样会让 cwnd 增长速度非常快,从而使得发送方发送的速度增长速度过快,网络拥塞的可能性也就更高。设置一个慢开始门限 ssthresh,当 cwnd >= ssthresh 时,进入拥塞避免,每个轮次只将 cwnd 加 1。 220 | 221 | 如果出现了超时,则令 ssthresh = cwnd / 2,然后重新执行慢开始。 222 | 223 | ## 2. 快重传与快恢复 224 | 225 | 在接收方,要求每次接收到报文段都应该对最后一个已收到的有序报文段进行确认。例如已经接收到 M1 和 M2,此时收到 M4,应当发送对 M2 的确认。 226 | 227 | 在发送方,如果收到三个重复确认,那么可以知道下一个报文段丢失,此时执行快重传,立即重传下一个报文段。例如收到三个 M2,则 M3 丢失,立即重传 M3。 228 | 229 | 在这种情况下,只是丢失个别报文段,而不是网络拥塞。因此执行快恢复,令 ssthresh = cwnd / 2 ,cwnd = ssthresh,注意到此时直接进入拥塞避免。 230 | 231 | 慢开始和快恢复的快慢指的是 cwnd 的设定值,而不是 cwnd 的增长速率。慢开始 cwnd 设定为 1,而快恢复 cwnd 设定为 ssthresh。 232 | 233 |

234 | 235 | 236 | # Q 11. ★★☆ DNS 的端口号;TCP 还是 UDP;作为缓存、负载均衡 237 | 238 | DNS 是一个分布式数据库,提供了主机名和 IP 地址之间相互转换的服务。这里的分布式数据库是指,每个站点只保留它自己的那部分数据。 239 | 240 | 域名具有层次结构,从上到下依次为:根域名、顶级域名、二级域名。 241 | 242 |

243 | 244 | - 根DNS服务器:返回顶级域DNS服务器的IP地址 245 | 246 | - 顶级域DNS服务器:返回权威DNS服务器的IP地址 247 | 248 | - 权威DNS服务器:返回响应主机的IP地址 249 | 250 | DNS 可以使用 UDP 或者 TCP 进行传输,使用的端口号都为 53。大多数情况下 DNS 使用 UDP 进行传输,这就要求域名解析器和域名服务器都必须自己处理超时和重传来保证可靠性。在两种情况下会使用 TCP 进行传输: 251 | 252 | - 如果返回的响应超过的 512 字节(UDP 最大只支持 512 字节的数据)。 253 | - 区域传送(区域传送是主域名服务器向辅助域名服务器传送变化的那部分数据)。 254 | 255 | 为了提高DNS解析的性能,很多网络都会就近部署DNS缓存服务器 256 | 257 |

258 | 259 | ## 负载均衡 260 | 261 | DNS首先可以做**内部负载均衡** 262 | 263 | 如果某个应用要访问另一个应用,配置另一个应用的域名,在域名解析的时候uzhiyao配置策略,这次返回第一个IP,下次返回第二个IP,就可以实现负载均衡了。 264 | 265 | 另外,DNS还可以做**全局负载均衡** 266 | 267 | 根据地址和运营商做全局的负载均衡。 268 | 269 | --------------------------------------------------------------------------------