├── .gitignore ├── README.md ├── blog ├── 2020届秋招笔试编程题集 │ ├── README.md │ ├── coding │ │ ├── dp01.cpp │ │ ├── dp02.cpp │ │ ├── dp03.cpp │ │ ├── dp04.cpp │ │ ├── dp05.cpp │ │ ├── dp06.cpp │ │ ├── dp07.cpp │ │ ├── dp08.cpp │ │ ├── dp09.cpp │ │ └── hs01.cpp │ ├── lam │ │ ├── numbersOfOne.cpp │ │ └── test.cpp │ ├── palindrome │ │ ├── pd01.cpp │ │ └── pd02.cpp │ ├── prime.cpp │ └── 滴滴 │ │ ├── README.md │ │ ├── dd01.cpp │ │ ├── dd02.cpp │ │ ├── dd03.cpp │ │ ├── dd04.cpp │ │ ├── dd05.cpp │ │ └── dd06.cpp ├── 2020秋招笔经 │ ├── README.md │ └── coding │ │ ├── threeSum.cpp │ │ └── twoSum.cpp ├── 2020秋招面经 │ ├── README.md │ ├── hr.md │ ├── main.md │ ├── 哔哩哔哩面经.md │ ├── 大华面经.md │ ├── 有赞面经.md │ ├── 滴滴面经.md │ ├── 百度面经.md │ ├── 网易有道面经.md │ ├── 腾讯面经.md │ └── 艾为面经.md ├── README.md ├── cpp │ ├── LRU.h │ ├── LevelOrder.h │ ├── SharePtr.h │ ├── Singleton.h │ ├── Sort.h │ ├── String.h │ ├── cpp │ │ ├── GetNext.h │ │ ├── IsPopOrder.h │ │ ├── MatchBrackets.h │ │ ├── String.h │ │ ├── TwoQueueImplementAStack.h │ │ ├── TwoStackImplementAQueue.h │ │ └── Vector.h │ ├── epoll.md │ ├── md │ │ ├── C++对象内存分布.md │ │ ├── Makefile │ │ ├── SharedPtr.h │ │ ├── String.h │ │ ├── TreeOrder.h │ │ ├── TreeOrderLoop.h │ │ ├── bfs.cpp │ │ ├── const关键字.md │ │ ├── core.14799 │ │ ├── core.15022 │ │ ├── list详解.md │ │ ├── map和set详解.md │ │ ├── static关键字.md │ │ ├── struct和class.md │ │ ├── test.cpp │ │ ├── vector详解.md │ │ ├── 智能指针.md │ │ ├── 深浅拷贝.md │ │ ├── 菱形继承.md │ │ ├── 虚函数实现原理.md │ │ └── 面向对象三大特性七大原则.md │ ├── nowcoder │ │ ├── constructBinaryTree.h │ │ └── printFromTailToFrontList.h │ └── webd.log ├── doc │ ├── C++ │ │ ├── C++11新特性.md │ │ ├── README.md │ │ ├── STL.md │ │ ├── 内存管理篇.md │ │ ├── 多态篇.md │ │ └── 语言基础.md │ ├── MySQL │ │ └── README.md │ ├── coding │ │ └── HorseBoard.cpp │ ├── shell │ │ ├── Linux下的内置命令.md │ │ ├── README.md │ │ ├── grep工具.md │ │ ├── sed工具.md │ │ ├── shell的具体执行过程.md │ │ └── shell脚本程序.md │ ├── 其他 │ │ ├── README.md │ │ └── git命令.md │ ├── 操作系统 │ │ ├── README.md │ │ └── 操作系统基础概念.md │ ├── 算法数据结构 │ │ ├── README.md │ │ ├── 数据结构.md │ │ ├── 算法.md │ │ └── 高级算法.md │ └── 计算机网络 │ │ └── README.md ├── file ├── fork.md ├── g.md ├── issue │ └── 贡献名单.md ├── km │ ├── LevelOrder.h │ ├── SharePtr.h │ ├── Singleton.h │ ├── Sort.h │ └── String.h ├── zxing-android-embedded-debug.aar ├── 剑指offer │ ├── README.md │ ├── coding │ │ └── 删除链表中的重复节点.cpp │ ├── 二维数组的查找.md │ └── 替换空格.md └── 项目 │ ├── README.md │ ├── debug.md │ └── web.md ├── pics ├── cas.png ├── debug.png ├── epoll.png ├── innodb.png ├── myisam.png ├── queue.png ├── 右旋.png └── 左旋.png └── zxing-android-embedded-debug.aar /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | *.sh 7 | 8 | *.swp 9 | 10 | # Test binary, build with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 17 | .glide/ 18 | .idea/ 19 | 20 | *.toml 21 | gen_leetcode 22 | tasks.txt 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 应战秋招指南! 3 | --- 4 | 5 | # 2020 届秋招笔经面经及个人总结 6 | 7 | [![](https://img.shields.io/badge/notes-protect-blue)](https://github.com/Apriluestc/2020/blob/master/README.md) 8 | [![](https://img.shields.io/badge/build-passing-brightgreen)](https://github.com/Apriluestc/2020/blob/master/README.md) 9 | [![](https://img.shields.io/badge/build-issue-brightgreen)](https://github.com/Apriluestc/2020/issues) 10 | 11 | 【2020 😀😁😀😁😀 秋招笔试面试合集,以及2019届公司真题模拟题集 & 附上自己的后端指南,💻🎓包括(C/C++基础、数据结构、算> 法、操作系统💻、计算机网络、MySQL、shell(ps:sed、awk、grep))】 12 | - [博客](https://github.com/Apriluestc/2020/blob/master/blog/README.md) 13 | 14 | - [项目](https://github.com/Apriluestc/2020/blob/master/blog/%E9%A1%B9%E7%9B%AE/README.md) 15 | 16 | ## 致谢 17 | 18 | 欢迎大家 star . Thanks 19 | 20 | ## 贡献 21 | 22 | - [如何贡献请参考](https://github.com/Apriluestc/2020/blob/master/blog/fork.md) 23 | 24 | ## 友情链接 25 | 26 | - [技术面试必备基础知识 @Cyc2018](https://github.com/CyC2018/CS-Notes) 27 | 28 | - [C/C++ 技术面试基础知识总结 @huitui](https://github.com/huihut/interview) 29 | 30 | - [SGI-STL 源码剖析 @steveLauwh](https://github.com/steveLauwh/SGI-STL) 31 | 32 | - [leetcode 题解 $java 语言版 @MisterBooo](https://github.com/MisterBooo/LeetCodeAnimation) 33 | 34 | - [leetcode 题解 $go 语言版 @aQuaYi](https://github.com/aQuaYi/LeetCode-in-Go) 35 | 36 | - [极客博客首选 PS:对于搭建个人博客、网页素材首选 @kitian616](https://github.com/kitian616/jekyll-TeXt-theme) 37 | -------------------------------------------------------------------------------- /blog/2020届秋招笔试编程题集/README.md: -------------------------------------------------------------------------------- 1 | # 2020 届秋招笔试编程题集 2 | 3 | ### 【背包问题】 4 | 5 | **【对于背包问题而言】** 定义物品种类为 N,背包容量 C,W[i] 对应第 i 件物品的体积或者花费或者重量,V[i] 对应第 i 件物品的价值 6 | 7 | - [01 背包【有价值问题】](https://github.com/Apriluestc/2020/blob/master/blog/2020%E5%B1%8A%E7%A7%8B%E6%8B%9B%E7%AC%94%E8%AF%95%E7%BC%96%E7%A8%8B%E9%A2%98%E9%9B%86/coding/dp01.cpp) 8 | - [01 背包【无价值问题】](https://github.com/Apriluestc/2020/blob/master/blog/2020%E5%B1%8A%E7%A7%8B%E6%8B%9B%E7%AC%94%E8%AF%95%E7%BC%96%E7%A8%8B%E9%A2%98%E9%9B%86/coding/dp02.cpp) 9 | - [完全背包](https://github.com/Apriluestc/2020/blob/master/blog/2020%E5%B1%8A%E7%A7%8B%E6%8B%9B%E7%AC%94%E8%AF%95%E7%BC%96%E7%A8%8B%E9%A2%98%E9%9B%86/coding/dp03.cpp) 10 | - [完全背包【方案数问题】](https://github.com/Apriluestc/2020/blob/master/blog/2020%E5%B1%8A%E7%A7%8B%E6%8B%9B%E7%AC%94%E8%AF%95%E7%BC%96%E7%A8%8B%E9%A2%98%E9%9B%86/coding/dp05.cpp) 11 | - [多重背包](https://github.com/Apriluestc/2020/blob/master/blog/2020%E5%B1%8A%E7%A7%8B%E6%8B%9B%E7%AC%94%E8%AF%95%E7%BC%96%E7%A8%8B%E9%A2%98%E9%9B%86/coding/dp04.cpp) 12 | 13 | ### 【LCS 问题】 14 | 15 | **【LCS 问题】** 包括最长公共子序列问题和最长公共子串问题等衍生问题,对于最长公共子序列问题而言, 16 | 给定两个字符串,找出最长公共子序列,返回 dp 长度,其中 dp[i][j] 表示分别以 A[i-1]、B[j-1] 字符结尾产生的最长公共子序列或字串,字串问题要求连续,子序列 17 | 问题要求可以不连续 18 | 19 | - [最长公共子序列](https://github.com/Apriluestc/2020/blob/master/blog/2020%E5%B1%8A%E7%A7%8B%E6%8B%9B%E7%AC%94%E8%AF%95%E7%BC%96%E7%A8%8B%E9%A2%98%E9%9B%86/coding/dp06.cpp) 20 | - [最长公共子串](https://github.com/Apriluestc/2020/blob/master/blog/2020%E5%B1%8A%E7%A7%8B%E6%8B%9B%E7%AC%94%E8%AF%95%E7%BC%96%E7%A8%8B%E9%A2%98%E9%9B%86/coding/dp07.cpp) 21 | - [编辑距离](https://github.com/Apriluestc/2020/blob/master/blog/2020%E5%B1%8A%E7%A7%8B%E6%8B%9B%E7%AC%94%E8%AF%95%E7%BC%96%E7%A8%8B%E9%A2%98%E9%9B%86/coding/dp08.cpp) 22 | - [最长上升子序列](https://github.com/Apriluestc/2020/blob/master/blog/2020%E5%B1%8A%E7%A7%8B%E6%8B%9B%E7%AC%94%E8%AF%95%E7%BC%96%E7%A8%8B%E9%A2%98%E9%9B%86/coding/dp09.cpp) 23 | 24 | ### 【DFS or BFS 问题】 25 | 26 | - [递增子序列](https://github.com/Apriluestc/2020/blob/master/blog/2020%E5%B1%8A%E7%A7%8B%E6%8B%9B%E7%AC%94%E8%AF%95%E7%BC%96%E7%A8%8B%E9%A2%98%E9%9B%86/coding/hs01.cpp) 27 | 28 | ### 【真题训练】 29 | 30 | - [滴滴](https://github.com/Apriluestc/2020/blob/master/blog/2020%E5%B1%8A%E7%A7%8B%E6%8B%9B%E7%AC%94%E8%AF%95%E7%BC%96%E7%A8%8B%E9%A2%98%E9%9B%86/%E6%BB%B4%E6%BB%B4/README.md) 31 | -------------------------------------------------------------------------------- /blog/2020届秋招笔试编程题集/coding/dp01.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | /* 6 | * 给定 N 种物品 7 | * 一个容量为 C 的背包 8 | * 物品 i 的重量或者体积是 W[i] 9 | * 价值是 V[i] 10 | * 11 | * 【有价值问题】问:从给定的 N 件物品中,如何选择装入背包的物品 12 | * 使得装入背包物品的总价值最大 13 | * 示例: 14 | * N = 6 15 | * C = 12 16 | * W[] = {4, 6, 2, 2, 5, 1}; 17 | * V[] = {8, 10, 6, 3, 7, 2}; 18 | * 19 | * 24 20 | * 21 | * dp[i][j] 表示放入第 i 件物品在背包容量是 j 的情况下产生的最佳价值 22 | * */ 23 | 24 | /* 25 | * | name | weight | value | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 26 | * | :--: | :----: | :---: | ---- | ---- | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | 27 | * | 6 | 1 | 2 | 0 | 2 | 6 | 6 | 9 | 10 | 14 | 14 | 17 | 17 | 19 | 19 | 24 | 28 | * | 5 | 5 | 7 | 0 | 0 | 6 | 6 | 9 | 9 | 14 | 14 | 17 | 17 | 19 | 19 | 24 | 29 | * | 4 | 2 | 3 | 0 | 0 | 6 | 6 | 9 | 9 | 14 | 14 | 17 | 17 | 19 | 19 | 24 | 30 | * | 3 | 2 | 6 | 0 | 0 | 6 | 6 | 8 | 8 | 14 | 14 | 16 | 16 | 18 | 18 | 24 | 31 | * | 2 | 6 | 10 | 0 | 0 | 0 | 0 | 8 | 8 | 10 | 10 | 10 | 10 | 18 | 18 | 24 | 32 | * | 1 | 4 | 8 | 0 | 0 | 0 | 0 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 33 | * */ 34 | 35 | int main() 36 | { 37 | 38 | // N 物品种类 39 | // C 背包容量 40 | int N, C; 41 | while (cin >> N >> C) { 42 | 43 | // W 对应第 i 种物品的重量或体积 44 | // V 对应第 i 种物品的价值 45 | vector W(N + 1, 0); 46 | vector V(N + 1, 0); 47 | for (int i = 1; i <= N; i++) { 48 | cin >> W[i]; 49 | } 50 | for (int i = 1; i <= N; i++) { 51 | cin >> V[i]; 52 | } 53 | 54 | // dp[i][j] 表示放入前 i 种物品之后产生的最大价值 55 | vector dp(C + 1, 0); 56 | for (int i = 1; i <= N; i++) { 57 | // 逆序 58 | for (int j = C; j >= W[i]; j--) { 59 | dp[j] = max(dp[j], dp[j-W[i]] + V[i]); 60 | } 61 | } 62 | cout << dp[C] << endl; 63 | } 64 | return 0; 65 | } 66 | -------------------------------------------------------------------------------- /blog/2020届秋招笔试编程题集/coding/dp02.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | /* 6 | * 给定 N 种物品 7 | * 一个容量为 C 的背包 8 | * 物品 i 的重量或者体积是 W[i] 9 | * 价值是 V[i] 10 | * 11 | * 【无价值问题】问:从给定的 N 件物品中,如何选择若干件物品恰好能装入背包 12 | * 找出满足条件的所有解 13 | * 示例: 14 | * N = 6 15 | * C = 10 16 | * W[] = {1, 8, 4, 3, 5, 2}; 17 | * 18 | * [1, 4, 3, 2] 19 | * [1, 4, 5] 20 | * [8, 2] 21 | * [3, 5, 2] 22 | * 23 | * vector >; 24 | * 25 | * */ 26 | 27 | 28 | int main() 29 | { 30 | // N 物品种类 31 | // C 背包容量 32 | int N, C; 33 | while (cin >> N >> C) { 34 | // W 物品体积或者花费或者重量 35 | vector W(N + 1, 0); 36 | for (int i = 1; i <= N; i++) { 37 | cin >> W[i]; 38 | } 39 | vector dp(C + 1, 0); 40 | for (int i = 1; i <= N; i++) { 41 | // 注意逆序保证每件物品只取一次 42 | for (int j = C; j >= W[i]; j--) { 43 | dp[j] = max(dp[j], dp[j-W[i]] + W[i]); 44 | } 45 | } 46 | cout << dp[C] << endl; 47 | } 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /blog/2020届秋招笔试编程题集/coding/dp03.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | /* 6 | * 物品种类 N 7 | * 背包容量 C 8 | * 对应第 i 种物品的体积或者重量或者花费是 W[i] 9 | * 对应第 i 种物品的价值为 V[i] 10 | * 【完全背包】每种物品对应的数量是无限的,即每种物品可以无限件取 11 | * 求解将哪些物品放入背包可使得这些物品的总体积不超过背包容量且总价值最大 12 | * */ 13 | 14 | int main() 15 | { 16 | // 物品种类数目 N 17 | // 背包容量 C 18 | int N, C; 19 | while (cin >> N >> C) { 20 | 21 | // W 对应第 i 件物品的重量或者体积 22 | // V 对应第 i 件物品的价值 23 | vector W(N + 1, 0); 24 | vector V(N + 1, 0); 25 | for (int i = 1; i <= N; i++) { 26 | cin >> W[i]; 27 | } 28 | for (int i = 1; i <= N; i++) { 29 | cin >> V[i]; 30 | } 31 | vector dp(C + 1, 0); 32 | for (int i = 1; i <= N; i++) { 33 | // 与 01 背包不同,这里为顺序,01 背包为逆序 34 | // 空间优化为一维数组 35 | for (int j = W[i]; j <= C; j++) { 36 | dp[j] = max(dp[j], dp[j-W[i]] + V[i]); 37 | } 38 | } 39 | cout << dp[C] << endl; 40 | } 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /blog/2020届秋招笔试编程题集/coding/dp04.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | /* 6 | * 物品种类 N 7 | * 背包容量 C 8 | * nums 对应第 i 种物品的最大数量 9 | * W 对应第 i 种物品的体积或者重量 10 | * V 对应第 i 种物品的价值 11 | * 【多重背包问题】 12 | * */ 13 | 14 | int main() 15 | { 16 | int N, C; 17 | while (cin >> N >> C) { 18 | vector W(N + 1, 0); 19 | vector V(N + 1, 0); 20 | vector nums(N + 1, 0); 21 | for (int i = 1; i <= N; i++) { 22 | cin >> W[i]; 23 | } 24 | for (int i = 1; i <= N; i++) { 25 | cin >> V[i]; 26 | } 27 | for (int i = 1; i <= N; i++) { 28 | cin >> nums[i]; 29 | } 30 | vector dp(C + 1, 0); 31 | for (int i = 1; i <= N; i++) { 32 | // 把每种物品展开,调用 nums[i] 次 01 背包代码 33 | for (int k = 1; k <= nums[i]; k++) { 34 | // 正常的 01 背包代码,注意是逆序保证每种物品只取了一个 35 | for (int j = C; j > W[i]; j--) { 36 | dp[j] = max(dp[j], dp[j-W[i]] + V[i]); 37 | } 38 | } 39 | } 40 | cout << dp[C] << endl; 41 | } 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /blog/2020届秋招笔试编程题集/coding/dp05.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | int main() 6 | { 7 | // N 物品种类 8 | // C 背包容量 9 | // W[i] 对应第 i 种物品的体积或者重量 10 | int N, C; 11 | while (cin >> N >> C) { 12 | vector W(N + 1, 0); 13 | for (int i = 1; i <= N; i++) { 14 | cin >> W[i]; 15 | } 16 | vector dp(C + 1, 1); 17 | for (int i = 1; i <= N; i++) { 18 | // 完全背包的总方案问题 19 | for (int j = W[i]; j <= C; j++) { 20 | dp[j] = dp[j] + dp[j-W[i]]; 21 | } 22 | } 23 | cout << dp[C] << endl; 24 | } 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /blog/2020届秋招笔试编程题集/coding/dp06.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | /* 6 | * 【最长公共子序列】给出两个字符户,找出最长公共子序列,返回 dp 长度 7 | * 8 | * 最长公共子序列问题是在一组序列中找出最长公共子序列(可以不连续) 9 | * 10 | * ABCD 11 | * EDCA 12 | * 13 | * 给定 A[] B[] 14 | * 15 | * 1 16 | * 17 | * dp A 或者 D 或者 C 18 | * 19 | * dp[i][j] 表示以字符串 A[i] 结尾,以 B[j] 结尾产生的最长子序列 20 | * 21 | * */ 22 | 23 | int main() 24 | { 25 | string A, B; 26 | while (cin >> A >> B) { 27 | int M = A.size(); 28 | int N = B.size(); 29 | vector > dp(M + 1, vector(N + 1, 0)); 30 | for (int i = 1; i <= M; i++) { 31 | for (int j = 1; j <= N; j++) { 32 | if (A[i-1] == B[i-1]) { 33 | dp[i][j] = dp[i-1][j-1] + 1; 34 | } else { 35 | dp[i][j] = max(dp[i][j-1], dp[i-1][j]); 36 | } 37 | } 38 | } 39 | cout << dp[M][N] << endl; 40 | } 41 | return 0; 42 | 43 | /* 44 | * 空间优化方案 45 | * 操作方案: 46 | * 因为我们每次都会将新的结果覆盖到之前的一维数组之上,所以说,我们在进行计 47 | * 算当前的 dp[i] 的时候,必须提前将我们的 dp[i-1](dp[i] 的对角线的元素)记录下来(slave) 48 | * 我们最后要计算的就是这么几个情况: 49 | * 1.当前匹配:dp[i] = slave + 1; 50 | * 2.当前不匹配:dp[i-1](刚计算完的)和 dp[i]取最大值即可 51 | * 所以说,我们这里的最核心的要点就是保存 slave 变量 52 | * */ 53 | 54 | /* 55 | * // 空间优化 56 | * int M = A.size(); 57 | * int N = B.size(); 58 | * vector dp(N + 1, 0); 59 | * int slave; 60 | * for (int i = 1; i <= M; i++) { 61 | * slave = dp[0]; 62 | * for (int j = 1; j <= N; j++) { 63 | * // 提取当前值 64 | * int tmp = dp[j]; 65 | * if (A[i-1] == B[j-1]) { 66 | * dp[j] = slave + 1; 67 | * } else { 68 | * dp[j] = max(dp[j-1], dp[j]); 69 | * } 70 | * // slave 记录 71 | * slave = tmp; 72 | * } 73 | * } 74 | * cout << dp[N] << endl; 75 | * */ 76 | } 77 | -------------------------------------------------------------------------------- /blog/2020届秋招笔试编程题集/coding/dp07.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | /* 6 | * 7 | * 【最长公共子串】给出两个字符串,找到最长公共子串,并返回长度 8 | * dp[i][j] 表示以 A[i-1] B[j-1] 为结尾的最长公共子串的长度 9 | * 10 | * count 表示全局最长长度,至于为什么,我不清楚 11 | * */ 12 | 13 | int main() 14 | { 15 | string A, B; 16 | while (cin >> A >> B) { 17 | int M = A.size(); 18 | int N = B.size(); 19 | vector > dp(M + 1, vector(N + 1, 0)); 20 | int count = 0; 21 | for (int i = 1; i <= M; i++) { 22 | for (int j = 1; j <= N; j++) { 23 | if (A[i-1] == B[i-1]) { 24 | dp[i][j] = dp[i-1][j-1] + 1; 25 | count = max(dp[i][j], count); 26 | } else { 27 | // 与最长公共子序列不同的是,当 A[i-1] != B[j-1] 此时 dp[i][j] = 0; 28 | // 而子序列问题 dp[i][j] = max(dp[i-1][j], dp[i][j-1]) 29 | dp[i][j] = 0; 30 | } 31 | } 32 | } 33 | cout << count << endl; 34 | } 35 | return 0; 36 | 37 | // 空间优化 38 | } 39 | -------------------------------------------------------------------------------- /blog/2020届秋招笔试编程题集/coding/dp08.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | /* 6 | * 【编辑距离】给出两个字符串 A,B,要求将 A 转换为 B 的最少操作次数 7 | * 8 | * 操作方法有:插入、删除、替换 9 | * 10 | * horse 11 | * ros 12 | * 13 | * horse->rorse 替换 h 为 r 14 | * rorse->rose 删除 r 15 | * rose->ros 删除 e 16 | * 17 | * intention 18 | * execytion 19 | * 20 | * intention->inention 删除 t 21 | * inention->enention 替换 i 为 e 22 | * enention->exention 替换 n 为 x 23 | * exention->exection 替换 n 为 c 24 | * exection->execution 插入 u 25 | * 26 | * dp[i][j] 表示 A[i] B[j] 的最小编辑距离 27 | * 要求 dp[i+1][j+1] 就先看 A[i+1] == B[j+1] ? 28 | * 如果相等说明这个字符不需要操作,只需取前面的最优解 dp[i][j] 29 | * 不等则 dp[i][j]、dp[i+1][j]、dp[i][j+1] 中取最优值 30 | * */ 31 | 32 | int main() 33 | { 34 | string A, B; 35 | while (cin >> A >> B) { 36 | int M = A.size(); 37 | int N = B.size(); 38 | vector > dp(M + 1, vector(N + 1, 0)); 39 | } 40 | return 0; 41 | } 42 | -------------------------------------------------------------------------------- /blog/2020届秋招笔试编程题集/coding/dp09.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | /* 6 | * 【LCS 问题】最长上升子序列 7 | * 给定一个无序数组,找到其中最长的上升子序列 8 | * */ 9 | 10 | int main() 11 | { 12 | int N; 13 | while (cin >> N) { 14 | vector nums(N, 0); 15 | for (int i = 0; i < N; i++) { 16 | cin >> nums[i]; 17 | } 18 | vector dp(N, 1); 19 | int global; 20 | if (nums.size() == 0) { 21 | global = 0; 22 | } else { 23 | global = 1; 24 | } 25 | // dp[i] 表示以 nums[i] 结尾的最长上升子序列 26 | for (int i = 0; i < N; i++) { 27 | for (int j = 0; j < i; j++) { 28 | if (nums[i] > nums[j]) { 29 | dp[i] = max(dp[i], dp[j] + 1); 30 | global = max(global, dp[i]); 31 | } 32 | } 33 | } 34 | cout << global << endl; 35 | } 36 | 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /blog/2020届秋招笔试编程题集/coding/hs01.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | /* 6 | * 【DFS、BFS 问题】 7 | * 给定一个数组,找出所有该数组的递增子序列 8 | * 9 | * [4, 6, 7, 7] 10 | * 11 | * [4, 6] 12 | * [4, 7] 13 | * [4, 6, 7] 14 | * [4, 6, 7, 7] 15 | * [6, 7] 16 | * [6, 7, 7] 17 | * [7, 7] 18 | * [4, 7, 7] 19 | * 20 | * 21 | * 因为要查找所有有可能的序列,所以需要深度搜索或者广度搜索算法 22 | * 有一个注意的点就是如何处理重复问题 23 | * 我们可以使用 set 容器对元素进行唯一性处理 24 | * */ 25 | 26 | void DFS(set >& res, vector& nums, vector& tempRes, int lastNum, int beginIndex); 27 | 28 | vector > findSubsequences(vector& nums) { 29 | vector tempRes; 30 | set > res; 31 | DFS(res, nums, tempRes, -100, 0); 32 | return vector >(res.begin(), res.end()); 33 | } 34 | 35 | void DFS(set >& res, vector& nums, vector& tempRes, int lastNum, int beginIndex) { 36 | int numsSize = nums.size(); 37 | 38 | // 搜索到了尾端 39 | if (beginIndex == numsSize) { 40 | 41 | // 判断这个结果是否符合要求 42 | if (tempRes.size() > 1) { 43 | // 插入到队列中 44 | res.insert(tempRes); 45 | } 46 | } else { 47 | // 跳过这个元素 48 | DFS(res, nums, tempRes, lastNum, beginIndex + 1); 49 | 50 | // 或者当这个元素符合放入条件(递增) 51 | if (lastNum <= nums[beginIndex]) { 52 | tempRes.push_back(nums[beginIndex]); 53 | DFS(res, nums, tempRes, nums[beginIndex], beginIndex + 1); 54 | tempRes.pop_back(); 55 | } 56 | } 57 | return; 58 | } 59 | 60 | int main() 61 | { 62 | int N; 63 | while (cin >> N) { 64 | vector nums(N, 0); 65 | for (int i = 0; i < N; i++) { 66 | cin >> nums[i]; 67 | } 68 | vector > tmp = findSubsequences(nums); 69 | for (size_t i = 0; i < tmp[0].size(); i++) { 70 | for (size_t j = 0; j < tmp[0].size(); j++) { 71 | cout << tmp[i][j] << " "; 72 | } 73 | cout << endl; 74 | } 75 | cout << endl; 76 | } 77 | return 0; 78 | } 79 | -------------------------------------------------------------------------------- /blog/2020届秋招笔试编程题集/lam/numbersOfOne.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | int main() 6 | { 7 | int N; 8 | while (cin >> N) { 9 | vector nums(N, 0); 10 | vector res(N, 0); 11 | int count = 0; 12 | for (int i = 0; i < N; i++) { 13 | cin >> nums[i]; 14 | while (nums[i]) { 15 | if ((nums[i] & 1) == 1) { 16 | res[i]++; 17 | } 18 | nums[i] >>= 1; 19 | } 20 | } 21 | for (int j = 0; j < N; j++) { 22 | if (res[j] != res[j+1]) { 23 | count++; 24 | } 25 | } 26 | cout << count << endl; 27 | } 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /blog/2020届秋招笔试编程题集/lam/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | int main() 6 | { 7 | int N; 8 | while (cin >> N) { 9 | vector nums(N + 1, 0); 10 | // 1, 2, 3, 5, 7, 9 11 | for (int i = 1; i <= N; i++) { 12 | cin >> nums[i]; 13 | } 14 | vector dp(N + 1, 0); 15 | for (int i = 1; i <= N; i++) { 16 | dp[i] = max(dp[i], dp[i-1] + nums[i]); 17 | } 18 | cout << dp[N] << endl; 19 | } 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /blog/2020届秋招笔试编程题集/palindrome/pd01.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | /* 6 | * 方案一:将整数转换为字符串,判断其是否为一个回文串 7 | * 方案二: 8 | * 回文数,判断一个整数是不是回文 9 | * 1221 10 | * 1 == 1 11 | * 2 == 2 12 | * 1221 / 1000 -> 1 && 1221 % 10 -> 1 true 13 | * 方案三:取出整数后半段数字进行翻转 14 | * */ 15 | 16 | bool isPalindromeOne(int x) { 17 | if (x < 0) { 18 | return false; 19 | } 20 | // 相当于统计 x 有几位数 21 | int div = 1; 22 | while (x / div >= 10) { 23 | div *= 10; 24 | } 25 | while (x > 0) { 26 | // 拿到第一位 27 | int left = x / div; 28 | // 拿到最后一位 29 | int right = x % 10; 30 | if (left != right) { 31 | return false; 32 | } 33 | x = (x % div) / 10; 34 | // 由于依次少两位,所以除以 100 35 | div /= 100; 36 | } 37 | return true; 38 | } 39 | 40 | /* 41 | * 每次进行取余操作 ( %10),取出最低的数字:y = x % 10 42 | * 将最低的数字加到取出数的末尾:revertNum = revertNum * 10 + y 43 | * 每取一个最低位数字,x 都要自除以 10 44 | * 判断 x 是不是小于 revertNum ,当它小于的时候,说明数字已经对半或者过半了 45 | * 最后,判断奇偶数情况:如果是偶数的话,revertNum 和 x 相等;如果是奇数的话,最中间的数字就在revertNum 的最低位上,将它除以 10 以后应该和 x 相等。 46 | * */ 47 | 48 | bool isPalindromeTwo(int x) { 49 | // 整数末尾为 0 直接返回 50 | if (x < 0 || (x % 10 == 0 && x != 0)) { 51 | return false; 52 | } 53 | int ans = 0; 54 | while (x > ans) { 55 | ans = ans * 10 + x % 10; 56 | x /= 10; 57 | } 58 | return x == ans || x == ans / 10; 59 | } 60 | 61 | int main() 62 | { 63 | int x; 64 | while (cin >> x) { 65 | cout << isPalindromeOne(x) << endl; 66 | } 67 | return 0; 68 | } 69 | -------------------------------------------------------------------------------- /blog/2020届秋招笔试编程题集/palindrome/pd02.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | /* 6 | * 回文素数 7 | * 遍历所有数字,检查是不是回文串,如果是检查是不是素数,如果 8 | * 当前数字长度为 8,可以跳过检查,因为不存在长度为 8 的素数 9 | * 暴力破解,时间复杂度 O(n) 10 | * */ 11 | 12 | bool isPrime(int x) { 13 | if (x < 2) { 14 | return false; 15 | } 16 | for (int i = 2; i <= sqrt(x); i++) { 17 | if (x % i == 0) { 18 | return false; 19 | } 20 | } 21 | return true; 22 | } 23 | 24 | int reversePalindrome(int x) { 25 | if (x < 0 || (x % 10 == 0 && x!= 0)) { 26 | return 0; 27 | } 28 | int ans = 1; 29 | while (x > 0) { 30 | ans = 10 * ans + (x % 10); 31 | x /= 10; 32 | } 33 | return x == ans || x == ans / 10; 34 | } 35 | 36 | int primePalindrome(int x) { 37 | while (true) { 38 | if (reversePalindrome(x) && isPrime(x)) { 39 | return x; 40 | } 41 | x++; 42 | } 43 | } 44 | 45 | /* 46 | * 方案二 47 | * */ 48 | 49 | int main() 50 | { 51 | int N; 52 | while (cin >> N) { 53 | cout << primePalindrome(N) << endl; 54 | } 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /blog/2020届秋招笔试编程题集/prime.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | bool isPrime(int x) { 6 | if (x < 2) { 7 | return false; 8 | } 9 | for (int i = 2; i < sqrt(x); i++) { 10 | if (x % i == 0) { 11 | return false; 12 | } 13 | } 14 | return true; 15 | } 16 | 17 | int reversePalindrome(int x) { 18 | if (x < 0 || (x % 10 == 0 && x != 0)) { 19 | return 0; 20 | } 21 | int ans; 22 | while (x > 0) { 23 | ans = 10 * ans + (x % 10); 24 | x /= 10; 25 | } 26 | return x == ans || x == ans / 10; 27 | } 28 | 29 | int main() 30 | { 31 | int N = 20; 32 | vector v; 33 | for (int i = 1; i <= N; i++) { 34 | if (isPrime(i) && reversePalindrome(i)) { 35 | v.push_back(i); 36 | } 37 | } 38 | for (size_t i = 0; i < v.size(); i++) { 39 | cout << "[" << endl; 40 | cout << v[i] << ","; 41 | } 42 | cout << "]" << endl; 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /blog/2020届秋招笔试编程题集/滴滴/README.md: -------------------------------------------------------------------------------- 1 | ### 滴滴笔试题集 2 | 3 | - [连续最大和](https://github.com/Apriluestc/2020/blob/master/blog/2020%E5%B1%8A%E7%A7%8B%E6%8B%9B%E7%AC%94%E8%AF%95%E7%BC%96%E7%A8%8B%E9%A2%98%E9%9B%86/%E6%BB%B4%E6%BB%B4/dd01.cpp) 4 | - [餐馆](https://github.com/Apriluestc/2020/blob/master/blog/2020%E5%B1%8A%E7%A7%8B%E6%8B%9B%E7%AC%94%E8%AF%95%E7%BC%96%E7%A8%8B%E9%A2%98%E9%9B%86/%E6%BB%B4%E6%BB%B4/dd04.cpp) 5 | - [末尾 0 的个数](https://github.com/Apriluestc/2020/blob/master/blog/2020%E5%B1%8A%E7%A7%8B%E6%8B%9B%E7%AC%94%E8%AF%95%E7%BC%96%E7%A8%8B%E9%A2%98%E9%9B%86/%E6%BB%B4%E6%BB%B4/dd05.cpp) 6 | - [进制转换](https://github.com/Apriluestc/2020/blob/master/blog/2020%E5%B1%8A%E7%A7%8B%E6%8B%9B%E7%AC%94%E8%AF%95%E7%BC%96%E7%A8%8B%E9%A2%98%E9%9B%86/%E6%BB%B4%E6%BB%B4/dd03.cpp) 7 | - [数字和为 sum 的方法数](https://github.com/Apriluestc/2020/blob/master/blog/2020%E5%B1%8A%E7%A7%8B%E6%8B%9B%E7%AC%94%E8%AF%95%E7%BC%96%E7%A8%8B%E9%A2%98%E9%9B%86/%E6%BB%B4%E6%BB%B4/dd02.cpp) 8 | -------------------------------------------------------------------------------- /blog/2020届秋招笔试编程题集/滴滴/dd01.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | int main() 6 | { 7 | int N; 8 | while (cin >> N) { 9 | vector nums(N + 1, 0); 10 | for (int i = 1; i <= N; i++) { 11 | cin >> nums[i]; 12 | } 13 | 14 | // 全局最优值 15 | int global = INT_MIN; 16 | 17 | // dp 数组,dp[i] 表示以 nums[i] 结尾产生的连续最大和 18 | vector dp(N + 1, 0); 19 | for (int i = 1; i <= N; i++) { 20 | 21 | // 要么是上一个状态产生的最大值加上当前值,要么是当前值 22 | dp[i] = max(nums[i], dp[i-1] + nums[i]); 23 | 24 | // 保存全局最优值 25 | global = max(global, dp[i]); 26 | } 27 | cout << global << endl; 28 | } 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /blog/2020届秋招笔试编程题集/滴滴/dd02.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | long long dp[1024] = {1}; 6 | 7 | /* 8 | * 01 背包方案数问题 9 | * */ 10 | 11 | int main() 12 | { 13 | int N, M; 14 | while (cin >> N >> M) { 15 | vector A(N + 1, 0); 16 | for (int i = 1; i <= N; i++) { 17 | cin >> A[i]; 18 | } 19 | for (int i = 1; i <= N; i++) { 20 | for (int j = M; j >= A[i]; j--) { 21 | dp[j] = dp[j] + dp[j-A[i]]; 22 | } 23 | } 24 | cout << dp[M] << endl; 25 | } 26 | return 0; 27 | } 28 | -------------------------------------------------------------------------------- /blog/2020届秋招笔试编程题集/滴滴/dd03.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | /* 6 | * 10 进制数转 N 进制数 7 | * */ 8 | 9 | int main() 10 | { 11 | string s = ""; 12 | string table = "0123456789ABCDEF"; 13 | int M, N; 14 | while (cin >> M >> N) { 15 | while(M) { 16 | if (M < 0) { 17 | M = -M; 18 | cout << "-"; 19 | } 20 | s = table[M%N] + s; 21 | M /= N; 22 | } 23 | cout << s << endl; 24 | } 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /blog/2020届秋招笔试编程题集/滴滴/dd04.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | /* 6 | * 某餐馆有n张桌子,每张桌子有一个参数:a 可容纳的最大人数; 有 m 批 7 | * 客人,每批客人有两个参数:b人数,c预计消费金额。 在不允许拼桌的 8 | * 情况下,请实现一个算法选择其中一部分客人,使得总预计消费金额最大 9 | * */ 10 | 11 | int main() 12 | { 13 | // N 张桌子 14 | // M 批客人 15 | int N, M; 16 | while (cin >> N >> M) { 17 | // table 对应每张左子的容量 18 | vector table(N, 0); 19 | for (int i = 0; i < N; i++) { 20 | cin >> table[i]; 21 | } 22 | sort(table.begin(), table.end()); 23 | reverse(table.begin(), table.end()); 24 | vector> guest(M, make_pair(0, 0)); 25 | for (int i = 0; i < M; i++) { 26 | cin >> guest[i].second >> guest[i].first; 27 | } 28 | sort(guest.begin(), guest.end()); 29 | reverse(guest.begin(), guest.end()); 30 | long long res = 0; 31 | for (size_t i = 0; i < guest.size(); i++) { 32 | if (table.size() == 0) { 33 | break; 34 | } 35 | int money = guest[i].first; 36 | int people = guest[i].second; 37 | if (table[0] < people) { 38 | continue; 39 | } 40 | size_t j = 0; 41 | while (j < table.size() && table[j] >= people) { 42 | j++; 43 | } 44 | res += money; 45 | table.erase(table.begin() + j - 1); 46 | } 47 | cout << res << endl; 48 | } 49 | return 0; 50 | } 51 | -------------------------------------------------------------------------------- /blog/2020届秋招笔试编程题集/滴滴/dd05.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | /* 6 | * 输入一个正整数n,求n!(即阶乘)末尾有多少个0? 比如: n = 10; n! = 3628800,所 7 | * 以答案为2 8 | * */ 9 | 10 | int main() 11 | { 12 | int N; 13 | int count = 0; 14 | while (cin >> N) { 15 | while (N /= 5) 16 | { 17 | count += N; 18 | } 19 | cout << count << endl; 20 | } 21 | return 0; 22 | } 23 | 24 | -------------------------------------------------------------------------------- /blog/2020届秋招笔试编程题集/滴滴/dd06.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | /* 6 | * 迷宫可以看成是有向图,向上的边权值为 3,向下的边权值为 0,等等 7 | * 不相邻的边或者一端有障碍物的边,权值是无穷大,本质上就是求这个有向 8 | * 图从 (0,0) 到 (0,m-1) 的最短路径 9 | * 可利用 Dijkstra 算法 10 | * */ 11 | 12 | int main() 13 | { 14 | // N 行 15 | // M 列 16 | // P 体力值 17 | int N, M, P; 18 | while (cin >> N >> M >> P) { 19 | // board 迷宫矩阵 20 | vector> board(N, vector(M, 0)); 21 | for (int i = 0; i < N; i++) { 22 | for (int j = 0; j < M; j++) { 23 | cin >> board[i][j]; 24 | } 25 | } 26 | // 构建有向图 27 | vector> graph(N * M, vector(N * M, INT_MAX)); 28 | for (int i = 0; i < N; i++) { 29 | for (int j = 0; j < M; j++) { 30 | if (i > 0 && board[i-1][j]) { 31 | graph[i*M+j][(i-1)*M+j] = 3; 32 | } 33 | if (i < N - 1 && board[i+1][j]) { 34 | graph[i*M+j][(i+1)*M+j] = 0; 35 | } 36 | if (j > 0 && board[i][j-1]) { 37 | graph[i*M+j][i*M+j-1] = 1; 38 | } 39 | if (j < M - 1 && board[i][j+1]) { 40 | graph[i*M+j][i*M+j+1] = 1; 41 | } 42 | } 43 | } 44 | // Dijkstra 算法求解最短路 45 | vector pre(N * M, 0); 46 | vector mindist(N * M, 0); 47 | vector found(N * M, false); 48 | for (int i = 0; i < N * M; i++) { 49 | mindist[i] = graph[0][i]; 50 | } 51 | found[0] = true; 52 | for (int i = 1; i < N * M; i++) { 53 | int near; 54 | int min = INT_MAX; 55 | for (int j = 0; j< N * M; j++) { 56 | if (!found[j] && mindist[j] < min) { 57 | min = mindist[j]; 58 | near = j; 59 | } 60 | } 61 | if (min == INT_MAX) { 62 | break; 63 | } 64 | found[near] = true; 65 | for (int k = 0; k < N * M; k++) { 66 | if (!found[k] && min != INT_MAX && graph[near][k] != INT_MAX && (min + graph[near][k] < mindist[k])) { 67 | pre[k] = near; 68 | mindist[k] = min + graph[near][k]; 69 | } 70 | } 71 | } 72 | if (mindist[M-1] > P) { 73 | cout << "Can not escape!" << endl; 74 | } else { 75 | stack> path; 76 | for (int i = M - 1; i != 0; i = pre[i]) { 77 | path.push({i / M, i % M}); 78 | } 79 | cout << "[0,0]"; 80 | while (!path.empty()) { 81 | const auto& p = path.top(); 82 | cout << ",[" << p.first << ',' << p.second << ']'; 83 | path.pop(); 84 | } 85 | cout << endl; 86 | } 87 | } 88 | return 0; 89 | } 90 | -------------------------------------------------------------------------------- /blog/2020秋招笔经/README.md: -------------------------------------------------------------------------------- 1 | # 2020 届秋招笔经 2 | 3 | * [实现生产者消费者模型](#实现生产者消费者模型) 4 | * [实现 LRU](#实现-lru) 5 | * [线程安全的单例模式](#线程安全的单例模式) 6 | * [实现 string 类](#实现-string-类) 7 | 8 | ### 实现生产者消费者模型 9 | 10 | **描述**:一群生产者和一群消费者提供产品,共享缓冲区 11 | 12 | **规则**:不能向满缓冲区写数据,不能向空缓冲区取数据,每个时刻仅允许 1 个生产者或消费者拿或取走一个数据 13 | 14 | ```cpp 15 | void producer() { 16 | while (true) { 17 | // 生产一个数据 18 | P(empty); 19 | P(mutex); 20 | // 存一个数据到缓冲区 21 | V(mutex); 22 | V(empty); 23 | } 24 | } 25 | 26 | void consumer() { 27 | while (true) { 28 | P(full); 29 | P(mutex); 30 | // 从缓冲区取 1 个数据 31 | V(mutex); 32 | V(empty); 33 | } 34 | } 35 | ``` 36 | 37 | ### 实现 LRU 38 | 39 | **算法描述**:LRU 算法实际上是让你设计数据结构:首先要接收一个 capacity 参数作为缓存的最大容量,然 40 | 后实现两个 API,一个是 put(key, val) 方法存入键值对,另一个 41 | 是 get(key) 方法获取 key 对应的 val,如果 key 不存在则返回 -1。 42 | 43 | ```cpp 44 | // 缓存容量为 2 45 | LRUCache cache = new LRUCache(2); 46 | 47 | // 你可以把 cache 理解成一个队列 48 | // 假设左边是队头,右边是队尾 49 | // 最近使用的排在队头,久未使用的排在队尾 50 | // 圆括号表示键值对 (key, val) 51 | 52 | cache.put(1, 1); 53 | 54 | // cache = [(1, 1)] 55 | cache.put(2, 2); 56 | 57 | // cache = [(2, 2), (1, 1)] 58 | cache.get(1); // 返回 1 59 | 60 | // cache = [(1, 1), (2, 2)] 61 | // 解释:因为最近访问了键 1,所以提前至队头 62 | // 返回键 1 对应的值 1 63 | cache.put(3, 3); 64 | 65 | // cache = [(3, 3), (1, 1)] 66 | // 解释:缓存容量已满,需要删除内容空出位置 67 | // 优先删除久未使用的数据,也就是队尾的数据 68 | // 然后把新的数据插入队头 69 | cache.get(2); // 返回 -1 (未找到) 70 | 71 | // cache = [(3, 3), (1, 1)] 72 | // 解释:cache 中不存在键为 2 的数据 73 | cache.put(1, 4); 74 | 75 | // cache = [(1, 4), (3, 3)] 76 | // 解释:键 1 已存在,把原始值 1 覆盖为 4 77 | // 不要忘了也要将键值对提前到队头 78 | ``` 79 | 80 | **设计原则**:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。也就是说,当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰。 81 | 82 | **这个算法的出发点在于**:某个页面被访问了,则它可能马上还要访问,反之,如过很长时间没被访问,则它在最近一段时间也不会被访问 83 | 84 | **最近最久未使用算法示例**:假定系统为某进程分配了 3 个物理块,进程运行时的页面走向为 1,2,3,4,1,2,5,1,2,3,4,5,开始时 3 个物理块均为空,计算采用 最近最久未使用页 85 | 面淘汰算法时的缺页率 ?(10/12) 86 | 87 | | 页面走向 | 1 | 2 | 3 | 4 | 1 | 2 | 5 | 1 | 2 | 3 | 4 | 5 | 88 | | :------: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | 89 | | 物理块 1 | 1 | 1 | 1 | 4 | 4 | 4 | 5 | | | 3 | 3 | 3 | 90 | | 物理块 2 | | 2 | 2 | 2 | 1 | 1 | 1 | | | 1 | 4 | 4 | 91 | | 物理块 3 | | | 3 | 3 | 3 | 2 | 2 | | | 2 | 2 | 5 | 92 | | 缺页 | 缺 | 缺 | 缺 | 缺 | 缺 | 缺 | 缺 | | | 缺 | 缺 | 缺 | 93 | 94 | **较为常见的实现方法**:unordered_map + list 95 | 96 | ```cpp 97 | class LRU { 98 | public: 99 | LRU(int capacity) { 100 | this->cap_ = capacity; 101 | } 102 | int get(int key) { 103 | auto it = map_.find(key); 104 | 105 | // 访问的 key 不存在 106 | if (it == map_.end()) { 107 | return -1; 108 | } 109 | 110 | // key 存在,把(k, v)调到队头 111 | pair kv = *map_[key]; 112 | cache.erase(map_[key]); 113 | cache.push_front(kv); 114 | 115 | // 更新(key, value)在 cache 中的位置 116 | map_[key] = cache.begin(); 117 | return kv.second; 118 | } 119 | void put(int key, int value) { 120 | auto it = map_.find(key); 121 | if (it == map_.end()) { 122 | 123 | // key 不存在 124 | if (cache.size() == cap_) { 125 | 126 | // cache 已满,删除尾部的键值对腾出位置 127 | // cache 和 map_ 中的数据都要删除 128 | auto lastPair = cache.back(); 129 | int lastKey = lastPair.first; 130 | map_.erase(lastKey); 131 | cache.pop_back(); 132 | } 133 | 134 | // cache 没满可以直接添加 135 | cache.push_front(make_pair(key, value)); 136 | map_[key] = cache.begin(); 137 | } else { 138 | 139 | // key 存在更改 value 并切换到队头 140 | cache.erase(map_[key]); 141 | cache.push_front(make_pair(key, value)); 142 | map_[key] = cache.begin(); 143 | } 144 | } 145 | private: 146 | 147 | // 缓存的最大容量 148 | int cap_; 149 | 150 | // 双链表,存储(key, value)元组 151 | list> cache; 152 | 153 | // 哈希表,key 映射到(key, value)在 cache 中的位置 154 | unordered_map>::iterator> map_; 155 | }; 156 | ``` 157 | 158 | ### 线程安全的单例模式 159 | 160 | **单例模式**:私有的构造函数、一个静态的方法,返回这个唯一实例的引用、一个指针静态变量、选择一个解决线程安全问题的方法 161 | 162 | `int pthread_once(pthread_once_t* once_control, void (*init_routine)(void));` 163 | 164 | 功能是本函数使用初值为 PTHREAD_ONCE_INIT 的 once_control 变量保证 init_routine()函数在本进程执行序列中仅执行一次。 165 | 166 | ```cpp 167 | template 168 | class Singlrton : boost::noncopyable { 169 | public: 170 | static T& instance() { 171 | 172 | // 只调用一次 init() 173 | pthread_once(&once_, &Singleton::init); 174 | return *value; 175 | } 176 | private: 177 | Singleton(); 178 | ~Singleton(); 179 | static void init() { 180 | value_ = new T(); 181 | } 182 | static pthread_once_t once_; 183 | static T* value_; 184 | }; 185 | 186 | template 187 | pthread_once_t Singleton::once_ = PTHREAD_ONCE_INIT; 188 | 189 | template 190 | T* Singleton::value_ = nullptr; 191 | ``` 192 | -------------------------------------------------------------------------------- /blog/2020秋招笔经/coding/threeSum.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | /* 6 | * 三数之和 7 | * 8 | * 给定一个包含 N 个整数的 nums,判断 nums 中是否存在 9 | * 三个元素 a, b, c 使得 a + b + c = 0,找出所有序列 10 | * 11 | * 给定 nums = {-1, 0, 1, 2, -1, -4} 12 | * 13 | * [ 14 | * [-1, 0, 1] 15 | * [-1, -1, 2] 16 | * ] 17 | * */ 18 | 19 | /* 20 | * 题解 21 | * 22 | * 一个新的周末,你再次去现在参加了一个,额,这次不是相亲会,是参加一个街篮比赛 23 | * ,赛前当然要组队啦,现在要想一个方法找到队友。组队还有一个要求,就是队伍的平 24 | * 均实力要符合要求,比如菜鸟抱两个大腿,或者有王者带两个弱鸡 25 | * 26 | * 当然,类似于两数之和,我们让每个数都相加,看会不会与我们的 target 相等 27 | * 28 | * */ 29 | 30 | class Solution { 31 | public: 32 | vector > threeSumOne(vector& nums, int target) { 33 | vector > res; 34 | for (size_t i = 0; i < nums.size(); i++) { 35 | for (size_t j = i + 1; j < nums.size(); j++) { 36 | for (size_t k = j + 1; k < nums.size(); k++) { 37 | if (nums[i] + nums[j] + nums[k] == target) { 38 | res.push_back(vector{nums[i], nums[j], nums[k]}); 39 | } 40 | } 41 | } 42 | } 43 | return vector > (); 44 | } 45 | 46 | /* 47 | * 受到两数之和的启发,在凑齐两人之后,我们可以找主持人登记第三个人,而不必在茫茫人海中找,这样时间复杂 48 | * 度为 n^2 49 | * */ 50 | 51 | /* 52 | * 再优化 53 | * 54 | * 可以先对给定序列进行排序,时间复杂度 nlogn 55 | * 我后我们选择一个人做 C 位,既然是 C 位,那么我们就需要左右各有一个人 56 | * 57 | * 先选择队伍最左边和队伍最右边两个人加上 C 位,算一下总和,如果大于 target 说明 58 | * 实力强,那么就把右边的人 right-- 一下,重新计算实例值 59 | * 60 | * 如果某边选到紧挨着你时,结束 61 | * */ 62 | }; 63 | 64 | int main() 65 | { 66 | return 0; 67 | } 68 | -------------------------------------------------------------------------------- /blog/2020秋招笔经/coding/twoSum.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | /* 6 | * 给定一个整数数组 nums 和一个目标值 target,请你在该数组 7 | * 中找出和为目标值的那 两个 整数,并返回他们的数组下标 8 | * */ 9 | 10 | /* 11 | * 题解 12 | * 13 | * 最简单的想法就是把每两个数都拿出来相加,看看结果是不是我们想要的值 14 | * 但是这样的时间复杂度为 n^2 15 | * */ 16 | 17 | class Solution { 18 | public: 19 | vector twoSumOne(vector& nums, int target) { 20 | for (size_t i = 0; i < nums.size(); i++) { 21 | for (size_t j = i + 1; j < nums.size(); j++) { 22 | if (nums[i] + nums[j] == target) { 23 | return vector{nums[i], nums[j]}; 24 | } 25 | } 26 | } 27 | return vector{}; 28 | } 29 | 30 | /* 31 | * 比如这个周末你要参加线下相亲会,全场只有两个人才是真爱,于是我们每个人都要去 32 | * 找其他所有聊天,但是这样的时间复杂度为 n^2 33 | * 34 | * 这时候引入哈希表,其实就是一个登记册子,写上你的名字和你的要求 35 | * 如果每个人都去登记一下,然后大家依次报出自己的名字,主持人就能够识别它 36 | * 37 | * 38 | * 两边哈希表 39 | * */ 40 | 41 | vector twoSumTwo(vector& nums, int target) { 42 | // 注册表 43 | unordered_map hash; 44 | 45 | // 开始登记 46 | for (size_t i = 0; i < nums.size(); i++) { 47 | hash[target - nums[i]] = nums[i]; 48 | } 49 | 50 | // 每个人再次报数的时候,主持人看一下名单里面有没有他 51 | for (size_t j = 0; j < nums.size(); j++) { 52 | if (hash.count(nums[j])) { 53 | return vector{nums[j], hash[nums[j]]}; 54 | } 55 | } 56 | return vector{}; 57 | } 58 | 59 | /* 60 | * 再优化,一遍哈希表 61 | * 62 | * 在上述情况之下,就是每个人都来问一下主持人,自己要找的人是否来登记过 63 | * 如果有就返回,没有就登记 64 | * */ 65 | 66 | vector twoSumThree(vector& nums, int target) { 67 | // 注册表 68 | unordered_map hash; 69 | for (size_t i = 0; i < nums.size(); i++) { 70 | // 如果要找的人被登记,直接返回 71 | if (hash.count(nums[i])) { 72 | return vector{nums[i], hash[nums[i]]}; 73 | } else { 74 | // 否则登记 75 | hash[target - nums[i]] = nums[i]; 76 | } 77 | } 78 | return vector{}; 79 | } 80 | }; 81 | 82 | int main() 83 | { 84 | return 0; 85 | } 86 | -------------------------------------------------------------------------------- /blog/2020秋招面经/README.md: -------------------------------------------------------------------------------- 1 | # 2020 届秋招面经 2 | 3 | **【感觉自己好菜,觉悟太晚,拿不到 offer 泪奔泪奔】** 4 | 5 | - [浙江大华提前批:8.5 大数据岗](https://github.com/Apriluestc/2020/blob/master/blog/2020%E7%A7%8B%E6%8B%9B%E9%9D%A2%E7%BB%8F/%E5%A4%A7%E5%8D%8E%E9%9D%A2%E7%BB%8F.md) 6 | - [哔哩哔哩提前批:8.5 推荐系统研发](https://github.com/Apriluestc/2020/blob/master/blog/2020%E7%A7%8B%E6%8B%9B%E9%9D%A2%E7%BB%8F/%E5%93%94%E5%93%A9%E5%93%94%E5%93%A9%E9%9D%A2%E7%BB%8F.md) 7 | - [有赞秋招:8.11 免笔试 运维【已拿 offer】](https://github.com/Apriluestc/2020/blob/master/blog/2020%E7%A7%8B%E6%8B%9B%E9%9D%A2%E7%BB%8F/%E6%9C%89%E8%B5%9E%E9%9D%A2%E7%BB%8F.md) 8 | - [滴滴提前批:8.21 后台开发](https://github.com/Apriluestc/2020/blob/master/blog/2020%E7%A7%8B%E6%8B%9B%E9%9D%A2%E7%BB%8F/%E6%BB%B4%E6%BB%B4%E9%9D%A2%E7%BB%8F.md) 9 | - [网易有道提前批:8.25 算法](https://github.com/Apriluestc/2020/blob/master/blog/2020%E7%A7%8B%E6%8B%9B%E9%9D%A2%E7%BB%8F/%E7%BD%91%E6%98%93%E6%9C%89%E9%81%93%E9%9D%A2%E7%BB%8F.md) 10 | - [腾讯提前批:8.29 后台](https://github.com/Apriluestc/2020/blob/master/blog/2020%E7%A7%8B%E6%8B%9B%E9%9D%A2%E7%BB%8F/%E8%85%BE%E8%AE%AF%E9%9D%A2%E7%BB%8F.md) 11 | - [艾为秋招:后台](https://github.com/Apriluestc/2020/blob/master/blog/2020%E7%A7%8B%E6%8B%9B%E9%9D%A2%E7%BB%8F/%E8%89%BE%E4%B8%BA%E9%9D%A2%E7%BB%8F.md) 12 | -------------------------------------------------------------------------------- /blog/2020秋招面经/hr.md: -------------------------------------------------------------------------------- 1 | # 你还有什么想问我的吗? 2 | 3 | #### 针对于一面二面面试官 4 | 5 | - 如果不清楚它们做什么的,可以问问它们主要做什么,主要负责哪块? 6 | - 如果我入职了,可能会负责哪块,主要做什么? 7 | - 平时会有一些技术分享活动没?或者技术分享会议什么的? 8 | - 面试中遇到自己不会的知识点,但是不是那种网上能立马找到答案的或者说有固定答案的,也可以问问面试官? 9 | 10 | #### 针对业务负责人面或者部门 Leader 11 | 12 | - 产品的以后发展方向? 13 | - 产品或是业务线需要应对的问题或是难关? 14 | - 团队规模? 15 | 16 | #### HR 面 17 | 18 | - 除了薪水外其他的福利情况,我想了解一下? 19 | - 年终奖、绩效奖金一般发放几个月的? 20 | - 五险一金的缴纳比例? 21 | - 薪资一般多久发一次? 22 | 23 | #### Boss 面 24 | 25 | - 公司的盈利模式、商业模式是怎么样的? 26 | - 公司的未来发展方向? 27 | -------------------------------------------------------------------------------- /blog/2020秋招面经/main.md: -------------------------------------------------------------------------------- 1 | * [什么是 SQL 注入?如何预防 SQL 注入?](#什么是-sql-注入如何预防-sql-注入) 2 | * [fork 和 vfork](#fork-和-vfork) 3 | * [Web 请求页面的全过程](#web-请求页面的全过程) 4 | 5 | ### 什么是 SQL 注入?如何预防 SQL 注入? 6 | 7 | **什么是 SQL 注入** 8 | 9 | 所谓 SQL 注入式攻击,就是攻击者把 SQL 命令插入到 Web 表单的输入域或页面请求的查询字符串,欺骗服务器恶意执行 SQL 命令,**具 10 | 体来说,它就是利用现有程序,将恶意的 SQL 命令注入到后台数据库引擎上的能力,它可以通过在 Web 表单中输入恶意 SQL 语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行 SQL 语句** 11 | 12 | **如何防范 SQL 注入(针对 JSP)** 13 | 14 | 在利用表单输入的内容构造 SQL 命令之前,把所有输入内容过滤一番就可以了 15 | 16 | - 使用正则表达式过滤传入的参数 17 | - 字符串过滤 18 | - JSP 中调用该函数检查是否包含非法字符 19 | - JSP 页面判断代码 20 | 21 | 凡涉及到执行的 SQL 语句中有变量时,用 JDBC 提供的 PreparedStatement 就可以,切记不要用拼接字符串的方法就可以了 22 | 23 | ### fork 和 vfork 24 | 25 | fork 和 vfork 都是创建一个进程,但是有一下区别 26 | 27 | - fork 子进程拷贝父进程的数据段、代码段,vfork 子进程和父进程则共享数据段 28 | - fork 父子进程执行次序不确定,vfork 保证子进程先运行,在调用 exec 或 exit 之前与父进程数据是共享的,在它调用 exec 或 exit 之后父进程才可能被调度运行 29 | - vfork 保证子进程先运行,在它调用 exec 或 exit 之后父进程才可能被调度运行,如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁 30 | 31 | 进程创建 fork,此奇妙之处在于它被调用一次,却返回两次,它可能有三种不同的返回值 32 | 33 | - 在父进程中,fork 返回新创建的子进程的 PID 34 | - 在子进程中,fork 返回 0 35 | - 如果出现错误,fork 返回一个负值 36 | 37 | **操作系统创建进程的步骤** 38 | 39 | - 申请新的 PCB 40 | - 为新进程分配资源 41 | - 初始化 PCB 42 | - 将新进程插入到就绪队列中 43 | 44 | **fork 调用底层** 45 | 46 | - fork、vfork、_clone -> clone -> do_fork -> copy_process -> 47 | - dup_task_struct 为新进程创建与父进程值相同的内核栈,thread_info 和 task_struct,此时父子进程描述符相同 48 | - check 当前用户拥有进程数未超过给它分配的资源限制 49 | - 区别父子进程描述符,部分进程描述符成员清零或设置 50 | - set 子进程 state 为 TASK_UNINTERRUPTIBLE 51 | - copy_flags 更新 task_struct 中的 flags,进程是否拥有超级用户权限清零,进程还没调用 exec 函数表示设置 52 | - alloc_pid 为新进程分配有效的 PID 53 | - 根据 clone 参数,copy_process 拷贝或共享打开的文件,进程地址空间等 54 | - copy_process 扫尾并返回指向子进程的指针 55 | 56 | ### Web 请求页面的全过程 57 | 58 | 输入地址->域名解析->浏览器向服务器发起 HTTP 请求->服务器处理 HTTP 请求->浏览器处理并显示 html 59 | 60 | - 浏览器查找域名对应的 IP 地址(DNS 协议) 61 | 62 | - 请求一旦发起,浏览器首先要做的就是解析这个域名,一般来说,浏览器会首先查看本地的 hosts 文件,看看其中有没有和这个域名对应的规则,如果有的化就直接使用 hosts 文件里面的 IP 地址 63 | - 如果本地的 hosts 文件中未命中,浏览器会发出一个 DNS 请求到本地 DNS服务器 64 | - 查询你输入网址的 DNS 请求到达本地 DNS 服务器后,本地 DNS 服务器会首先查询它的缓存记录,如果缓存中有此记录,就可以直接返回结果,此过程是递归的方式进行查询,如果没有,本地 DNS 服务器还要向根域名服务器进行查询 65 | - 根域名服务器没有记录具体的域名和 IP 地址的对应关系,而是告诉本地 DNS 服务器,你可以去上级域名服务器上查询,并给出域名服务器的地址,这种是迭代的过程 66 | - 本地 DNS 服务器继续向域名服务器发出请求,在这个例子中,请求的对象是 .com 域名服务器,.com 服务器收到请求后,也不会返回域名和 IP 地址的映射关系,而是告诉本地 DNS 服务器,你的域名解析服务器的地址 67 | - 最后,本地 DNS 服务器向域名的域名解析服务器发出请求,这时就会收到一个域名和 IP 地址的映射关系,本地 DNS 服务器不仅要把 IP 地址返回给用户电脑,还要把这个映射关系保存在缓存中,以备下次访问时,直接返回结果,加快网络访问 68 | 69 | - 浏览器携带 IP 向服务器发器 HTTP 请求 70 | 71 | 拿到域名对应的 IP 地址之后,浏览器会一个随即端口向服务器 80 端口发起 TCP 连接请求,这个连接请求达到服务器端后(通过各种路由选择设备)进入到网卡,然后进入到内核的 TCP/IP 协议栈(用于识别该连接请求,解封包),还有可能要经 72 | 过防火墙(属于内核的模块)的过滤,最终达到 web 程序,最终建立了 TCP/IP 连接 73 | 74 | 建立了 TCP 连接之后,发起一个 HTTP 请求,一个典型的 HTTP Request Header 一般需要包括请求的方法,例如 GET 和 POST 75 | 76 | - 服务器的永久重定向 HTTP 响应 77 | 78 | 服务器给浏览器响应一个 301 永久重定向响应,这样浏览器就会访问 www.baidu.com 而非 baidu.com 79 | 80 | **为什么服务器一定要重定向而不是直接发送用户想看的内容呢?** 81 | 82 | 网站调整、网页被移到一个新地址、网页扩展名改变(如需要把 .php 改成 .html)、这种情况下,如果 83 | 不做重定向,则用户收藏夹或搜索引擎数据库的旧地址只能让访问的用户得到一条 404 消息,访问流量丧失,再者注册多域名网站,也需要重定向让用户能够访问到这些站点 84 | 85 | **那么用 301 还是 302 好呢?** 86 | 87 | 当一个网站 24-48 小时内临时移动到一个新的位置,这时候就要 302 跳转,而使用 301 跳转的场景就是之前的站点因为某种原因永久的移除掉,然后要到新的地址访问,是永久性的 88 | 89 | **【301 使用场景如下】**:域名到期不想续费,想换域名、空间服务器不稳定,换空间的时候、在搜索引擎的搜索结果中出现了不带 www 的域名,而带 www 的域名却没有收录,这个时候可以用 301 重定向来告诉搜索引擎我们的目标域名是哪一个 90 | 91 | - Web 应用程序处理 HTTP 请求(以 nginx 为例) 92 | 93 | - nginx 读取配置文件,并寻找静态文件,如 index.php 94 | - 把 php 文件交给 fastcgi 进程去处理 95 | 96 | 后端从在固定的端口接收到 TCP 报文开始,它会对 TCP 连接进行处理,对 HTTP 协议进行解析,并按照报文格式进一步封装成 HTTP Request 对象,供上层使用。 97 | 98 | - 浏览器处理并显示 html 99 | 100 | 在浏览器没有完整的接受全部 html 文档时,它就已经开始显示这个页面了,一般渲染过程 101 | 102 | 【对于 webkit 来说】解析 html 以构建 dom 树->构建 render 树->布局 render 树->绘制->render 树 103 | -------------------------------------------------------------------------------- /blog/2020秋招面经/哔哩哔哩面经.md: -------------------------------------------------------------------------------- 1 | **【哔哩哔哩提前批一面:推荐系统研发】** 2 | 3 | - 自我介绍 4 | - 将近 30 分钟的数学建模提问【早知道不写在简历上了】 5 | - gdb 基本调试命令【easy】 6 | - 三次握手、两次会怎么样 7 | - 一道相等二叉树【类似二叉树镜像】 8 | - 01 背包方案数问题【一面挂】 9 | -------------------------------------------------------------------------------- /blog/2020秋招面经/大华面经.md: -------------------------------------------------------------------------------- 1 | **【浙江大华提前批一面:大数据 ps:岗位投错了】** 2 | 3 | - 什么是堆、栈及各自的区别【分别从数据结构堆栈和操作系统堆栈说明】 4 | - 排序算法的时间复杂度,稳定性、口述快排流程及优化【基准随机化、三数取中、序列达到一定长度使用插排、尾递归、多线程快排】 5 | - vector、list 区别及使用场景,vector 底层,为什么以 2 倍扩容 6 | - C++ 多态怎么实现,虚函数、虚表、虚指针概念 7 | - 构造函数可以是为虚函数吗,析构函数呢 8 | - 智能指针相关,及底层实现,auto_ptr 的解决方案 9 | - 什么是守护进程,及创建原理流程 10 | - 进程线程区别,fork、vfork 区别 11 | - 中断处理过程 12 | - 三次握手、四次挥手 13 | - 【项目相关】 14 | 15 | **【浙江大华提前批二面】** 16 | 17 | - UDP、TCP 区别 18 | - HTTP 状态码,首部字段 19 | - 讲解一下 HTTP 通信,HTTP 与 TCP 的区别与联系 20 | - HTTP 与 HTTPS 区别 21 | - 二叉树层次遍历非递归【口述流程】 22 | - 链表逆置 23 | 24 | **【浙江大华提前批 hr 面】** 25 | 26 | - 时隔一月收到感谢信 27 | 28 | 29 | **【浙江大华正式批一面:客户端开发工程师】** 30 | 31 | - 自我介绍 32 | - 讲一下在校期间做的项目,Web 服务器,等等 33 | - 项目中开启几个线程,我说 4 个,为什么开启四个,我从 CPU 核数方面讲的 34 | - 项目中开启一个线程和四个线程,具体服务性能有什么提升,emmmm 我回答 webbench 测定服务的吞吐 & 并发量 35 | - 看你项目中使用了线程池,讲一下线程池,我是这么说的,所谓线程池就是用数组保存一组线程,如果没有任务处 36 | 理就用条件变量,让其休眠,等到有任务来临的时候就去唤醒任务队列中的线程,还说了使用线程池的优点 37 | - 你项目中哪个地方使用到了异步,我说的是异步日志,使用的好处是什么 38 | - 你项目中线程具体是怎么设计的,我回答一个 master 线程搭配四个 worker 线程 39 | - 在项目中你遇到了什么困难,我说了 coredump 的解决方案,和任务队列工作线程的优先级设定 40 | - 用过 STL 没,讲一下 vector、list、map 41 | - 什么是平衡二叉树 42 | - 一个场景题(面试官写了一个模块)其目的是问深拷贝 43 | - 操作系统了解不,说一些分页内存 44 | - 手撕单例模式(要求写懒汉) 45 | - go to 语句是什么,如何解决 go to 使用语句出现的问题,我只说了我了解的,然后面试官给我讲了一下底层,面试官说常见的 for、while 循环 46 | 普遍都是限制了一个代码块,而 go to 有可能跳转整个代码模块,说了程序在内存的载入换出什么什么的【让等二面】 47 | 48 | **【浙江大华正式批二面】** 49 | 50 | - 聊人生 51 | - 说一下学校中的事情(我说了数学建模比赛和社会实践团) 52 | - 数据库知道多少说多少 53 | - 你这项目这用到了 select、epoll,讲一下底层【至此二面结束,让我等 HR】 54 | 55 | **【浙江大华 HR 面】** 56 | 57 | - 谈一下薪资,说一下工作地点 58 | - 为什么做客户端,你的优势在哪里 59 | - 你有什么要问我的 60 | -------------------------------------------------------------------------------- /blog/2020秋招面经/有赞面经.md: -------------------------------------------------------------------------------- 1 | **【有赞一面:运维岗】** 2 | 3 | - 自我介绍 4 | - 项目介绍【20分钟吧】 5 | - 项目中的 eventfd 是什么,等等由项目引出的问题 6 | - C++ 11 Thread、Mutex 讲一下【还好之前学习了这些,不然凉凉】 7 | - 讲一下 std::bind() 8 | - sed、awk【之前看到比特 16 届大佬的博客学习了下,刚好用上 9 | - 模式空间与保持空间【电话一面结束】 10 | 11 | **【有赞二面:视频】** 12 | 13 | - TCP 三次握手、四次挥手,TIME_WAIT 作用 14 | - 讲一下你所了解的 HTTP、HTTPS 15 | - Web 请求页面全过程【尽可能详细】 16 | - uname、df、network、ethtool、gdb 等等 17 | - 01 背包有价值问题【手撕代码】 18 | - 二叉树层次遍历【手撕】,还好两个手撕都简单,哈哈哈哈 19 | - 谈一下你对运维的看法 20 | 21 | **【有赞 hr 面】** 22 | 23 | - hr 面也就那样,当场发意向书 24 | -------------------------------------------------------------------------------- /blog/2020秋招面经/滴滴面经.md: -------------------------------------------------------------------------------- 1 | **【滴滴提前批一面】** 2 | 3 | - 说一下 static 4 | - 三次握手、四次挥手,为什么三次、四次 5 | - 三次握手服务端不调用 accept 会怎么样 6 | - 进程间通信方式 7 | - 讲一下 Linux 下的 malloc 8 | - 讲一下虚拟内存 9 | - C++ 多态是怎么样的、如何用 C 语言实现多态,析构函数为虚的好处是什么,虚函数是怎么实现多态的 10 | - 项目相关【具体不多说】 11 | 12 | **【滴滴提前批二面】** 13 | 14 | - 三数之和等于 target 的所有序列【要求不断优化】【在这里共享屏幕,现场 Debug】 15 | - 完全背包有价值问题【优化为一维空间】 16 | - 马踏棋盘【属实不会,面试官当场说这是最基本的算法,你都不会,各种嘲讽】【二面挂】 17 | 18 | **【总结】属实自己确实菜,算法真的菜鸡** 19 | -------------------------------------------------------------------------------- /blog/2020秋招面经/百度面经.md: -------------------------------------------------------------------------------- 1 | **【百度正式批一面】** 2 | 3 | - 自我介绍 4 | - 讲一下你所做的项目 5 | - 你在做这个项目时遇到的困难,有没有遇到 coredump,具体是怎么解决的 6 | - 由于我写了异步日志实时监控服务器运行状态,面试官询问你写的日志级别都有哪些,日志是从哪里产生的 7 | - 如果你的 Web 服务器 coredump 了你会怎么解决,使用 gdb 调试 + 日志排查 8 | - 手撕两数之和等于给定 target,优化 9 | - 暴力求解 O(n^2); 10 | - 辅助数组记录 target - nums[i],时间复杂度 O(n),空间复杂度 O(n); 11 | - 手撕 strcpy, 12 | - 为什么要返回 `char*`; 13 | - 为什么要用 tmp 指针保存 dst 14 | - 手撕单利模式【优化】 15 | - 懒汉,非线程安全 16 | - 双重校验锁实现线程安全(多线程环境下仍然不能保证线程安全); 17 | - pthread_once(once),借鉴 muduo 中的实现思路 18 | -------------------------------------------------------------------------------- /blog/2020秋招面经/网易有道面经.md: -------------------------------------------------------------------------------- 1 | **【网易有道算法一面:提前批】** 2 | 3 | - 寒暄介绍 4 | - 讲项目,花了好长时间 5 | - 讲数学建模【隔热服设计,我担任编程,emmm 问我用的什么库解决偏微分方程的】 6 | - 由于是被捞起来的,在面试之前面试官发了一份试题,面试期间让我讲 7 | - 手撕快排,emmm,完全背包方案数问题,套模板 8 | - kmp 9 | - TCP 可靠性保证,拥塞窗口,滑动窗口 10 | - 讲一下你理解的 http,emmmm 我从协议状态码,首部字段,报文格式,数据传输格式方面说了一下,还说了与https的区别等等 11 | - 数据结构相关,最坑的就是 avl 旋转,气死老子竟然不是红黑树旋转,真不会 12 | - 本轮面试就到这了,你有什么想问的吗,emmmm 您是做什么的,具体在什么岗位,项目组担任的职位,具体干什么,还有,算法教研就是给小学生讲课的,受不liao 13 | -------------------------------------------------------------------------------- /blog/2020秋招面经/腾讯面经.md: -------------------------------------------------------------------------------- 1 | **【腾讯提前批一面]** 2 | 3 | - 自我介绍哈哈哈哈哈 4 | - 继承体系中的虚析构函数作用?如果不会怎样 5 | - 讲一下 STL 中的内存分配原理? 6 | - Linux 下 malloc 一块内存,暂时不谢写入数据,那么这块内存会分配在哪里,物理块?虚存 7 | - Linux 下内存分配原理?进程虚拟内存分配原理 break指针,mmap 映射emmmm 8 | - Linux 各种命令【小 case】 9 | - 四次挥手 10 | - 当我们用户下载文件时,一般情况下初始下载速度很小,逐渐变大,解释这种现象?emmm我回答的是 TCP 拥塞控制,滑动窗口 11 | 反问?如果下载上传文件用的不是 TCP 协议呢,属实不会哈,比如 p2p, udp实在解释不了 12 | -------------------------------------------------------------------------------- /blog/2020秋招面经/艾为面经.md: -------------------------------------------------------------------------------- 1 | **【艾为一面:现场面】** 2 | 3 | - 自我介绍 4 | - 讲一下你所知道的 Linux 命令 5 | - vim:vim 全局替换 `:%s/object/res/g` 6 | - vim:退出不保存和退出保存 7 | - gdb 调试命令 8 | - 文件加大小:du -lh、df -h 9 | - ps、network、ethtool、netstat、ifconfig、git 等等 10 | - C++ 多态是怎么实现的,构造函数可以为虚函数吗,析构函数呢 11 | - 虚函数是怎么实现多态的 12 | - 什么是二叉树 13 | - 排序算法时间复杂度及稳定性 14 | - vector 底层,和与 list 的区别 15 | - STL 你经常使用哪个,讲一下 16 | - 三次握手、如果是两次会怎么样,四次挥手【一面结束】 17 | 18 | **【艾为 hr 面】** 19 | 20 | - 老套路,询问各种问题各种场景下作为求职者会怎么应对 21 | - 谈薪资 22 | - 工作地点哈哈哈哈,没了 23 | -------------------------------------------------------------------------------- /blog/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 应战秋招指南! 3 | --- 4 | 5 | # 2020 届秋招笔经面经及个人总结 6 | 7 | [![](https://img.shields.io/badge/notes-protect-blue)](https://github.com/Apriluestc/2020/blob/master/README.md) 8 | [![](https://img.shields.io/badge/build-passing-brightgreen)](https://github.com/Apriluestc/2020/blob/master/README.md) 9 | [![](https://img.shields.io/badge/build-issue-brightgreen)](https://github.com/Apriluestc/2020/issues) 10 | 11 | ## [Apriluestc](http://39.107.70.253:20000/) 12 | 13 | - [2020 届秋招笔经](https://github.com/Apriluestc/2020/blob/master/blog/2020%E7%A7%8B%E6%8B%9B%E7%AC%94%E7%BB%8F/README.md) 14 | 15 | - [2020 届秋招面经](https://github.com/Apriluestc/2020/blob/master/blog/2020%E7%A7%8B%E6%8B%9B%E9%9D%A2%E7%BB%8F/README.md) 16 | 17 | - [剑指 offer 题集](https://github.com/Apriluestc/2020/tree/master/blog/%E5%89%91%E6%8C%87offer) 18 | 19 | - [2020 届秋招笔试编程题集](https://github.com/Apriluestc/2020/blob/master/blog/2020%E5%B1%8A%E7%A7%8B%E6%8B%9B%E7%AC%94%E8%AF%95%E7%BC%96%E7%A8%8B%E9%A2%98%E9%9B%86/README.md) 20 | 21 | - [C / C++](https://github.com/Apriluestc/2020/blob/master/blog/doc/C%2B%2B/README.md) 22 | 23 | - [算法数据结构](https://github.com/Apriluestc/2020/blob/master/blog/doc/%E7%AE%97%E6%B3%95%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/README.md) 24 | 25 | - [计算机网络](https://github.com/Apriluestc/2020/blob/master/blog/doc/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/README.md) 26 | 27 | - [操作系统](https://github.com/Apriluestc/2020/tree/master/blog/doc/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F) 28 | 29 | - [数据库](https://github.com/Apriluestc/2020/blob/master/blog/doc/MySQL/README.md) 30 | 31 | - [shell](https://github.com/Apriluestc/2020/blob/master/blog/doc/shell/README.md) 32 | 33 | - [项目](https://github.com/Apriluestc/2020/blob/master/blog/%E9%A1%B9%E7%9B%AE/README.md) 34 | 35 | ## 致谢 36 | 37 | 欢迎大家 star . Thanks 38 | 39 | ## 贡献 40 | 41 | - [如何贡献请参考](https://github.com/Apriluestc/2020/blob/master/blog/fork.md) 42 | 43 | ## 友情链接 44 | 45 | - [技术面试必备基础知识 @Cyc2018](https://github.com/CyC2018/CS-Notes) 46 | 47 | - [C/C++ 技术面试基础知识总结 @huitui](https://github.com/huihut/interview) 48 | 49 | - [SGI-STL 源码剖析 @steveLauwh](https://github.com/steveLauwh/SGI-STL) 50 | 51 | - [leetcode 题解 $java 语言版 @MisterBooo](https://github.com/MisterBooo/LeetCodeAnimation) 52 | 53 | - [leetcode 题解 $go 语言版 @aQuaYi](https://github.com/aQuaYi/LeetCode-in-Go) 54 | 55 | - [极客博客首选 PS:对于搭建个人博客、网页素材首选 @kitian616](https://github.com/kitian616/jekyll-TeXt-theme) 56 | -------------------------------------------------------------------------------- /blog/cpp/LRU.h: -------------------------------------------------------------------------------- 1 | class LRU { 2 | public: 3 | LRU(); 4 | int get(); 5 | int put(); 6 | private: 7 | }; 8 | -------------------------------------------------------------------------------- /blog/cpp/LevelOrder.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | struct TreeNode { 5 | int val; 6 | TreeNode* left; 7 | TreeNode* right; 8 | TreeNode(int rhx = 0) 9 | :val(rhx), 10 | left(nullptr), 11 | right(nullptr) 12 | {} 13 | }; 14 | 15 | class Solution { 16 | public: 17 | void LevelOrder(TreeNode* root) { 18 | if (root == nullptr) { 19 | return; 20 | } 21 | std::queue q; 22 | // 根节点入队 23 | q.push(root); 24 | while (!q.empty()) { 25 | TreeNode* tmp = root; 26 | // 输出根节点 27 | std::cout << tmp->val; 28 | q.pop(); 29 | if (tmp->left) { 30 | q.push(tmp->left); 31 | } 32 | if (tmp->right) { 33 | q.push(tmp->right); 34 | } 35 | } 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /blog/cpp/SharePtr.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class SharedPtr { 4 | public: 5 | SharedPtr(int* ptr) { 6 | ptr_ = ptr; 7 | map_.insert(std::make_pair(ptr_, 1)); 8 | } 9 | SharedPtr(const SharedPtr& rhx) { 10 | ptr_ = rhx.ptr_; 11 | map_[ptr_]++; 12 | } 13 | SharedPtr& operator=(const SharedPtr& rhx) { 14 | if (ptr_ == rhx.ptr_) { 15 | return *this; 16 | } 17 | if (--map_[ptr_] <= 0 && ptr_ != nullptr) { 18 | delete ptr_; 19 | ptr_ = nullptr; 20 | map_.erase(ptr_); 21 | } 22 | ptr_ = rhx.ptr_; 23 | map_[ptr_]++; 24 | return *this; 25 | } 26 | int& operator*() { 27 | return *ptr_; 28 | } 29 | int* operator->() { 30 | return ptr_; 31 | } 32 | ~SharedPtr() { 33 | if (--map_[ptr_] <= 0 && ptr_ != nullptr) { 34 | delete ptr_; 35 | ptr_ = nullptr; 36 | map_.erase(ptr_); 37 | } 38 | } 39 | static std::map map_; 40 | private: 41 | int* ptr_; 42 | }; 43 | 44 | std::map SharedPtr::map_; 45 | -------------------------------------------------------------------------------- /blog/cpp/Singleton.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class SingletonLz { 5 | public: 6 | static SingletonLz& getInstance() { 7 | if (Singleton_ == nullptr) { 8 | Singleton_ = new SingletonLz(); 9 | } 10 | return *Singleton_; 11 | } 12 | private: 13 | SingletonLz(); 14 | ~SingletonLz(); 15 | static SingletonLz* Singleton_; 16 | }; 17 | 18 | SingletonLz* SingletonLz::Singleton_ = nullptr; 19 | 20 | class SingletonDCL { 21 | public: 22 | static SingletonDCL& getInstance() { 23 | if (singleton_ == nullptr) { 24 | mutex_.lock(); 25 | if (singleton_ == nullptr) { 26 | singleton_ = new SingletonDCL(); 27 | } 28 | } 29 | return *singleton_; 30 | } 31 | private: 32 | SingletonDCL(); 33 | ~SingletonDCL(); 34 | static SingletonDCL* singleton_; 35 | static std::mutex mutex_; 36 | }; 37 | -------------------------------------------------------------------------------- /blog/cpp/Sort.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class Sort { 5 | public: 6 | void quickSort(std::vector& nums) { 7 | if (nums.size() == 0) { 8 | return; 9 | } 10 | if (nums.size() <= 10) { 11 | insertSort(nums); 12 | } 13 | quickSort(nums, 0, nums.size() - 1); 14 | } 15 | void headSort(std::vector& nums) { 16 | int size = nums.size(); 17 | createHeap(nums, size); 18 | int end = size; 19 | while (end > 0) { 20 | std::swap(nums[0], nums[end-1]); 21 | end--; 22 | AdjustDown(nums, 0, end); 23 | } 24 | } 25 | private: 26 | void createHeap(std::vector& nums, int size) { 27 | for (int i = (size - 1) / 2; i >= 0; i--) { 28 | AdjustDown(nums, i, size); 29 | } 30 | } 31 | void AdjustDown(std::vector& nums, int root, int size) { 32 | int parent = root; 33 | int child = 2 * parent + 1; 34 | while (child < size) { 35 | if (child < size - 1 && nums[child] < nums[child + 1]) { 36 | child++; 37 | } 38 | if (nums[parent] < nums[child]) { 39 | std::swap(nums[parent], nums[child]); 40 | parent = child; 41 | child = 2 * parent + 1; 42 | } 43 | } 44 | } 45 | void quickSort(std::vector& nums, int begin, int end) { 46 | if (begin >= end) { 47 | return; 48 | } 49 | int left = begin; 50 | int right = end; 51 | int base = nums[left]; 52 | // int index = random(left, right); 53 | // int base = nums[index]; 54 | while (left < right) { 55 | while (left < right && nums[right] >= base) { 56 | right--; 57 | } 58 | if (left < right) { 59 | nums[left++] = nums[right]; 60 | } 61 | while (left < right && nums[left] <= base) { 62 | left++; 63 | } 64 | if (left < right) { 65 | nums[right--] = nums[left]; 66 | } 67 | nums[left] = base; 68 | } 69 | quickSort(nums, begin, left - 1); 70 | quickSort(nums, left + 1, end); 71 | } 72 | int random(int left, int right) { 73 | return rand() % (right - left + 1) + left; 74 | } 75 | void insertSort(std::vector& nums) { 76 | if (nums.size() == 0) { 77 | return; 78 | } 79 | insertSort(nums, nums.size()); 80 | } 81 | void insertSort(std::vector& nums, int size) { 82 | for (int i = 1; i < size; i++) { 83 | if (nums[i] < nums[i-1]) { 84 | int j = i - 1; 85 | int temp = nums[i]; 86 | while (j >= 0 && temp < nums[j]) { 87 | nums[j+1] = nums[j]; 88 | j--; 89 | } 90 | nums[j+1] = temp; 91 | } 92 | } 93 | } 94 | }; 95 | -------------------------------------------------------------------------------- /blog/cpp/String.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class String { 4 | public: 5 | String(char* rhx = nullptr) { 6 | if (rhx == nullptr) { 7 | c_ = new char[1]; 8 | *c_ = '\0'; 9 | } else { 10 | int len = strlen(rhx); 11 | c_ = new char[len + 1]; 12 | strcpy(c_, rhx); 13 | } 14 | ++count_; 15 | } 16 | String(const String& rhx) { 17 | int len = strlen(rhx.c_); 18 | c_ = new char[len + 1]; 19 | strcpy(c_, rhx.c_); 20 | ++count_; 21 | } 22 | String& operator=(const String& rhx) { 23 | if (this != &rhx) { 24 | delete [] c_; 25 | int len = strlen(rhx.c_); 26 | c_ = new char[len + 1]; 27 | strcpy(c_, rhx.c_); 28 | } 29 | return *this; 30 | } 31 | ~String() { 32 | if (c_) { 33 | delete [] c_; 34 | } 35 | } 36 | static int count_; 37 | private: 38 | char* c_; 39 | }; 40 | 41 | int String::count_; 42 | -------------------------------------------------------------------------------- /blog/cpp/cpp/GetNext.h: -------------------------------------------------------------------------------- 1 | struct TreeNode { 2 | int val; 3 | TreeNode* left; 4 | TreeNode* right; 5 | TreeNode* next; 6 | TreeNode(int x) 7 | :val(x), 8 | left(nullptr), 9 | right(nullptr), 10 | next(nullptr) 11 | {} 12 | }; 13 | 14 | TreeNode* GetNext(TreeNode* pNode) { 15 | if (pNode == nullptr) { 16 | return nullptr; 17 | } 18 | if (pNode->right != nullptr) { 19 | while (pNode->left != nullptr) { 20 | pNode = pNode->left; 21 | } 22 | return pNode; 23 | } 24 | while (pNode->next != nullptr) { 25 | if (pNode->next->left == pNode) { 26 | return pNode; 27 | } 28 | pNode = pNode->next; 29 | } 30 | return nullptr; 31 | } 32 | -------------------------------------------------------------------------------- /blog/cpp/cpp/IsPopOrder.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std; 5 | 6 | bool IsPopOrder(vector& input, vector& output) { 7 | if (input.size() != output.size()) { 8 | return false; 9 | } 10 | stack s; 11 | for (size_t i = 0, j = 0; i < input.size(); ++i) { 12 | s.push(input[i]); 13 | while (j < output.size() && s.top() == output[i]) { 14 | ++j; 15 | s.pop(); 16 | } 17 | } 18 | return s.empty(); 19 | } 20 | -------------------------------------------------------------------------------- /blog/cpp/cpp/MatchBrackets.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | int main() 6 | { 7 | string str; 8 | while (cin >> str) { 9 | bool flag = false; 10 | stack s; 11 | for (size_t i = 0; i < str.size(); ++i) { 12 | if (str[i] == '[' || str[i] == '(') { 13 | s.push(str[i]); 14 | } 15 | if (str[i] == ']') { 16 | if (s.empty()) { 17 | flag = false; 18 | break; 19 | } else { 20 | char c = s.top(); 21 | s.pop(); 22 | if (c == '[') { 23 | flag = true; 24 | } else { 25 | flag = false; 26 | break; 27 | } 28 | } 29 | } 30 | if (str[i] == ')') { 31 | if (s.empty()) { 32 | flag = false; 33 | break; 34 | } else { 35 | char c = s.top(); 36 | s.pop(); 37 | if (c == '(') { 38 | flag = true; 39 | } else { 40 | flag = false; 41 | break; 42 | } 43 | } 44 | } 45 | } 46 | if (!s.empty()) { 47 | flag = false; 48 | } 49 | if (flag) { 50 | cout << "true" << endl; 51 | } else { 52 | cout << "false" << endl; 53 | } 54 | } 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /blog/cpp/cpp/String.h: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDE_STRING_H 2 | #define INCLUDE_STRING_H 3 | 4 | #include 5 | #include 6 | 7 | template 8 | class String { 9 | public: 10 | String(char* rhx = nullptr) { 11 | if (rhx == nullptr) { 12 | str_ = new char [1]; 13 | *str_ = '\0'; 14 | } else { 15 | int len = strlen(rhx); 16 | str_ = new char [len+1]; 17 | strcpy(str_, rhx); 18 | } 19 | } 20 | String(const String& rhx) { 21 | int len = strlen(rhx.str_); 22 | str_ = new char [len+1]; 23 | strcpy(str_, rhx.str_); 24 | } 25 | String& operator=(const String& rhx) { 26 | if (this != &rhx) { 27 | delete [] str_; 28 | int len = strlen(rhx.str_); 29 | str_ = new char [len+1]; 30 | strcpy(str_, rhx.str_); 31 | } 32 | return *this; 33 | } 34 | String& operator+=(const String& rhx) { 35 | int len = strlen(rhx.str_); 36 | char* tmp = new char [len+1]; 37 | strcpy(tmp, rhx.str_); 38 | strcat(tmp, rhx.str_); 39 | delete [] str_; 40 | str_ = nullptr; 41 | str_ = tmp; 42 | return *this; 43 | } 44 | ~String() { 45 | if (str_) { 46 | delete [] str_; 47 | } 48 | } 49 | size_t Size() const; 50 | private: 51 | char* str_; 52 | }; 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /blog/cpp/cpp/TwoQueueImplementAStack.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std; 5 | 6 | template 7 | class Solution { 8 | public: 9 | void Push(const T& val) { 10 | if (q1.empty() && q2.empty()) { 11 | q1.push(val); 12 | } else if (q1.empty()) { 13 | q2.push(val); 14 | } else { 15 | q1.push(val); 16 | } 17 | } 18 | T Pop() { 19 | if (q1.empty()) { 20 | if (q2.empty()) { 21 | throw "stack is empty"; 22 | } else { 23 | if (q2.size() == 1) { 24 | T result = q2.front(); 25 | q2.pop(); 26 | return result; 27 | } else { 28 | while (q2.size() != 1) { 29 | q1.push(q2.front()); 30 | q2.pop(); 31 | } 32 | T result = q2.front(); 33 | q2.pop(); 34 | return result; 35 | } 36 | } 37 | } else { 38 | if (q1.size() == 1) { 39 | T result = q1.front(); 40 | q1.pop(); 41 | return result; 42 | } else { 43 | while (q1.size() != 1) { 44 | q2.push(q1.front()); 45 | q1.pop(); 46 | } 47 | T result = q1.front(); 48 | q1.pop(); 49 | return result; 50 | } 51 | } 52 | } 53 | private: 54 | queue q1; 55 | queue q2; 56 | }; 57 | -------------------------------------------------------------------------------- /blog/cpp/cpp/TwoStackImplementAQueue.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std; 5 | 6 | template 7 | class Solution { 8 | public: 9 | void Push(const T& val) { 10 | s1.push(val); 11 | } 12 | T Pop() { 13 | if (s2.empty()) { 14 | while (s1.size() > 0) { 15 | int data = s1.top(); 16 | s1.pop(); 17 | s2.push(data); 18 | } 19 | } 20 | int pop_element = s2.top(); 21 | s2.pop(); 22 | return pop_element; 23 | } 24 | private: 25 | stack s1; 26 | stack s2; 27 | }; 28 | -------------------------------------------------------------------------------- /blog/cpp/cpp/Vector.h: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDE_VECTOR_H 2 | #define INCLUDE_VECTOR_H 3 | 4 | #include 5 | 6 | template 7 | class Vector { 8 | public: 9 | Vector() 10 | :start_(nullptr), 11 | finish_(nullptr), 12 | endOfStorage_(nullptr) 13 | {} 14 | Vector(T* array, size_t size) 15 | :start_(new T[size]) 16 | { 17 | for (size_t i = 0; i < size; ++i) { 18 | start_[i] = array[i]; 19 | } 20 | finish_ = start_ + size; 21 | endOfStorage_ = start_ + size; 22 | } 23 | Vector(const Vector& rhx) { 24 | int size = rhx.Size(); 25 | for (int i = 0; i < size; ++i) { 26 | start_[i] = rhx.start_[i]; 27 | } 28 | finish_ = rhx.finish_; 29 | endOfStorage_ = rhx.endOfStorage_; 30 | } 31 | Vector& operator=(const Vector& rhx) { 32 | int size = rhx.Size(); 33 | if (&rhx != this) { 34 | for (int i = 0; i < size; ++i) { 35 | start_[i] = rhx.start_[i]; 36 | } 37 | finish_ = rhx.finish_; 38 | endOfStorage_ = rhx.endOfStorage_; 39 | } 40 | } 41 | ~Vector() { 42 | if (start_) { 43 | delete start_; 44 | start_ = finish_ = endOfStorage_ = nullptr; 45 | } 46 | } 47 | void PushBack(const T& val) { 48 | (*finish_) = val; 49 | ++finish_; 50 | } 51 | void PopBack() { 52 | --finish_; 53 | } 54 | void Insert(size_t index, const T& val) { 55 | assert(index < Size()); 56 | for (size_t i = Size(); i >= index; --i) { 57 | start_[i] = start_[i-1]; 58 | } 59 | start_[index] = val; 60 | ++finish_; 61 | } 62 | void Erase(size_t index) { 63 | assert(index < Size()); 64 | for (size_t i = index + 1; i < Size(); ++i) { 65 | start_[i-1] = start_[i]; 66 | } 67 | --finish_; 68 | } 69 | size_t Size() const { 70 | return finish_ - start_; 71 | } 72 | size_t Capacity() const { 73 | return endOfStorage_ - start_; 74 | } 75 | bool Empty() { 76 | return start_ == finish_; 77 | } 78 | T& operator[](size_t index) { 79 | assert(index < Size()); 80 | return start_[index]; 81 | } 82 | T& Back() { 83 | return start_[Size()-1]; 84 | } 85 | T& Front() { 86 | return start_[0]; 87 | } 88 | private: 89 | // 指向空间的起始地址 90 | T* start_; 91 | // 指向空间最后一个元素的后面 92 | T* finish_; 93 | // 标记空间的容量,指向空间尾 94 | T* endOfStorage_; 95 | }; 96 | 97 | #endif 98 | -------------------------------------------------------------------------------- /blog/cpp/epoll.md: -------------------------------------------------------------------------------- 1 | ```cpp 2 | /* 3 | * fs/eventpoll.c (Efficient event retrieval implementation) 4 | * Copyright (C) 2001,...,2009 Davide Libenzi 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | */ 12 | /* 13 | * 在深入了解epoll的实现之前, 先来了解内核的3个方面. 14 | * 1. 等待队列 waitqueue 15 | * 我们简单解释一下等待队列: 16 | * 队列头(wait_queue_head_t)往往是资源生产者, 17 | * 队列成员(wait_queue_t)往往是资源消费者, 18 | * 当头的资源ready后, 会逐个执行每个成员指定的回调函数, 19 | * 来通知它们资源已经ready了, 等待队列大致就这个意思. 20 | * 2. 内核的poll机制 21 | * 被Poll的fd, 必须在实现上支持内核的Poll技术, 22 | * 比如fd是某个字符设备,或者是个socket, 它必须实现 23 | * file_operations中的poll操作, 给自己分配有一个等待队列头. 24 | * 主动poll fd的某个进程必须分配一个等待队列成员, 添加到 25 | * fd的对待队列里面去, 并指定资源ready时的回调函数. 26 | * 用socket做例子, 它必须有实现一个poll操作, 这个Poll是 27 | * 发起轮询的代码必须主动调用的, 该函数中必须调用poll_wait(), 28 | * poll_wait会将发起者作为等待队列成员加入到socket的等待队列中去. 29 | * 这样socket发生状态变化时可以通过队列头逐个通知所有关心它的进程. 30 | * 这一点必须很清楚的理解, 否则会想不明白epoll是如何 31 | * 得知fd的状态发生变化的. 32 | * 3. epollfd本身也是个fd, 所以它本身也可以被epoll, 33 | * 可以猜测一下它是不是可以无限嵌套epoll下去... 34 | * 35 | * epoll基本上就是使用了上面的1,2点来完成. 36 | * 可见epoll本身并没有给内核引入什么特别复杂或者高深的技术, 37 | * 只不过是已有功能的重新组合, 达到了超过select的效果. 38 | */ 39 | /* 40 | * 相关的其它内核知识: 41 | * 1. fd我们知道是文件描述符, 在内核态, 与之对应的是struct file结构, 42 | * 可以看作是内核态的文件描述符. 43 | * 2. spinlock, 自旋锁, 必须要非常小心使用的锁, 44 | * 尤其是调用spin_lock_irqsave()的时候, 中断关闭, 不会发生进程调度, 45 | * 被保护的资源其它CPU也无法访问. 这个锁是很强力的, 所以只能锁一些 46 | * 非常轻量级的操作. 47 | * 3. 引用计数在内核中是非常重要的概念, 48 | * 内核代码里面经常有些release, free释放资源的函数几乎不加任何锁, 49 | * 这是因为这些函数往往是在对象的引用计数变成0时被调用, 50 | * 既然没有进程在使用在这些对象, 自然也不需要加锁. 51 | * struct file 是持有引用计数的. 52 | */ 53 | /* --- epoll相关的数据结构 --- */ 54 | /* 55 | * This structure is stored inside the "private_data" member of the file 56 | * structure and rapresent the main data sructure for the eventpoll 57 | * interface. 58 | */ 59 | /* 每创建一个epollfd, 内核就会分配一个eventpoll与之对应, 可以说是 60 | * 内核态的epollfd. */ 61 | struct eventpoll { 62 | /* Protect the this structure access */ 63 | spinlock_t lock; 64 | /* 65 | * This mutex is used to ensure that files are not removed 66 | * while epoll is using them. This is held during the event 67 | * collection loop, the file cleanup path, the epoll file exit 68 | * code and the ctl operations. 69 | */ 70 | /* 添加, 修改或者删除监听fd的时候, 以及epoll_wait返回, 向用户空间 71 | * 传递数据时都会持有这个互斥锁, 所以在用户空间可以放心的在多个线程 72 | * 中同时执行epoll相关的操作, 内核级已经做了保护. */ 73 | struct mutex mtx; 74 | /* Wait queue used by sys_epoll_wait() */ 75 | /* 调用epoll_wait()时, 我们就是"睡"在了这个等待队列上... */ 76 | wait_queue_head_t wq; 77 | /* Wait queue used by file->poll() */ 78 | /* 这个用于epollfd本事被poll的时候... */ 79 | wait_queue_head_t poll_wait; 80 | /* List of ready file descriptors */ 81 | /* 所有已经ready的epitem都在这个链表里面 */ 82 | struct list_head rdllist; 83 | /* RB tree root used to store monitored fd structs */ 84 | /* 所有要监听的epitem都在这里 */ 85 | struct rb_root rbr; 86 | /* 87 | 这是一个单链表链接着所有的struct epitem当event转移到用户空间时 88 | */ 89 | * This is a single linked list that chains all the "struct epitem" that 90 | * happened while transfering ready events to userspace w/out 91 | * holding ->lock. 92 | */ 93 | struct epitem *ovflist; 94 | /* The user that created the eventpoll descriptor */ 95 | /* 这里保存了一些用户变量, 比如fd监听数量的最大值等等 */ 96 | struct user_struct *user; 97 | }; 98 | /* 99 | * Each file descriptor added to the eventpoll interface will 100 | * have an entry of this type linked to the "rbr" RB tree. 101 | */ 102 | /* epitem 表示一个被监听的fd */ 103 | struct epitem { 104 | /* RB tree node used to link this structure to the eventpoll RB tree */ 105 | /* rb_node, 当使用epoll_ctl()将一批fds加入到某个epollfd时, 内核会分配 106 | * 一批的epitem与fds们对应, 而且它们以rb_tree的形式组织起来, tree的root 107 | * 保存在epollfd, 也就是struct eventpoll中. 108 | * 在这里使用rb_tree的原因我认为是提高查找,插入以及删除的速度. 109 | * rb_tree对以上3个操作都具有O(lgN)的时间复杂度 */ 110 | struct rb_node rbn; 111 | /* List header used to link this structure to the eventpoll ready list */ 112 | /* 链表节点, 所有已经ready的epitem都会被链到eventpoll的rdllist中 */ 113 | struct list_head rdllink; 114 | /* 115 | * Works together "struct eventpoll"->ovflist in keeping the 116 | * single linked chain of items. 117 | */ 118 | /* 这个在代码中再解释... */ 119 | struct epitem *next; 120 | /* The file descriptor information this item refers to */ 121 | /* epitem对应的fd和struct file */ 122 | struct epoll_filefd ffd; 123 | /* Number of active wait queue attached to poll operations */ 124 | int nwait; 125 | /* List containing poll wait queues */ 126 | struct list_head pwqlist; 127 | /* The "container" of this item */ 128 | /* 当前epitem属于哪个eventpoll */ 129 | struct eventpoll *ep; 130 | /* List header used to link this item to the "struct file" items list */ 131 | struct list_head fllink; 132 | /* The structure that describe the interested events and the source fd */ 133 | /* 当前的epitem关系哪些events, 这个数据是调用epoll_ctl时从用户态传递过来 */ 134 | struct epoll_event event; 135 | }; 136 | struct epoll_filefd { 137 | struct file *file; 138 | int fd; 139 | }; 140 | /* poll所用到的钩子Wait structure used by the poll hooks */ 141 | struct eppoll_entry { 142 | /* List header used to link this structure to the "struct epitem" */ 143 | struct list_head llink; 144 | /* The "base" pointer is set to the container "struct epitem" */ 145 | struct epitem *base; 146 | /* 147 | * Wait queue item that will be linked to the target file wait 148 | * queue head. 149 | */ 150 | wait_queue_t wait; 151 | /* The wait queue head that linked the "wait" wait queue item */ 152 | wait_queue_head_t *whead; 153 | }; 154 | /* Wrapper struct used by poll queueing */ 155 | struct ep_pqueue { 156 | poll_table pt; 157 | struct epitem *epi; 158 | }; 159 | /* Used by the ep_send_events() function as callback private data */ 160 | struct ep_send_events_data { 161 | int maxevents; 162 | struct epoll_event __user *events; 163 | }; 164 | 165 | /* --- 代码注释 --- */ 166 | /* 你没看错, 这就是epoll_create()的真身, 基本啥也不干直接调用epoll_create1了, 167 | * 另外你也可以发现, size这个参数其实是没有任何用处的... */ 168 | SYSCALL_DEFINE1(epoll_create, int, size) 169 | { 170 | if (size <= 0) 171 | return -EINVAL; 172 | return sys_epoll_create1(0); 173 | } 174 | /* 这才是真正的epoll_create啊~~ */ 175 | SYSCALL_DEFINE1(epoll_create1, int, flags) 176 | { 177 | int error; 178 | struct eventpoll *ep = NULL;//主描述符 179 | /* Check the EPOLL_* constant for consistency. */ 180 | /* 这句没啥用处... */ 181 | BUILD_BUG_ON(EPOLL_CLOEXEC != O_CLOEXEC); 182 | /* 对于epoll来讲, 目前唯一有效的FLAG就是CLOEXEC */ 183 | if (flags & ~EPOLL_CLOEXEC) 184 | return -EINVAL; 185 | /* 186 | * Create the internal data structure ("struct eventpoll"). 187 | */ 188 | /* 分配一个struct eventpoll, 分配和初始化细节我们随后深聊~ */ 189 | error = ep_alloc(&ep); 190 | if (error < 0) 191 | return error; 192 | /* 193 | * Creates all the items needed to setup an eventpoll file. That is, 194 | * a file structure and a free file descriptor. 195 | */ 196 | /* 这里是创建一个匿名fd, 说起来就话长了...长话短说: 197 | * epollfd本身并不存在一个真正的文件与之对应, 所以内核需要创建一个 198 | * "虚拟"的文件, 并为之分配真正的struct file结构, 而且有真正的fd. 199 | * 这里2个参数比较关键: 200 | * eventpoll_fops, fops就是file operations, 就是当你对这个文件(这里是虚拟的)进行操作(比如读)时, 201 | * fops里面的函数指针指向真正的操作实现, 类似C++里面虚函数和子类的概念. 202 | * epoll只实现了poll和release(就是close)操作, 其它文件系统操作都有VFS全权处理了. 203 | * ep, ep就是struct epollevent, 它会作为一个私有数据保存在struct file的private指针里面. 204 | * 其实说白了, 就是为了能通过fd找到struct file, 通过struct file能找到eventpoll结构. 205 | * 如果懂一点Linux下字符设备驱动开发, 这里应该是很好理解的, 206 | * 推荐阅读 207 | */ 208 | error = anon_inode_getfd("[eventpoll]", &eventpoll_fops, ep, 209 | O_RDWR | (flags & O_CLOEXEC)); 210 | if (error < 0) 211 | ep_free(ep); 212 | return error; 213 | } 214 | /* 215 | * 创建好epollfd后, 接下来我们要往里面添加fd咯 216 | * 来看epoll_ctl 217 | * epfd 就是epollfd 218 | * op ADD,MOD,DEL 219 | * fd 需要监听的描述符 220 | * event 我们关心的events 221 | */ 222 | SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd, 223 | struct epoll_event __user *, event) 224 | { 225 | int error; 226 | struct file *file, *tfile; 227 | struct eventpoll *ep; 228 | struct epitem *epi; 229 | struct epoll_event epds; 230 | error = -EFAULT; 231 | /* 232 | * 错误处理以及从用户空间将epoll_event结构copy到内核空间. 233 | */ 234 | if (ep_op_has_event(op) && 235 | copy_from_user(&epds, event, sizeof(struct epoll_event))) 236 | goto error_return; 237 | /* Get the "struct file *" for the eventpoll file */ 238 | /* 取得struct file结构, epfd既然是真正的fd, 那么内核空间 239 | * 就会有与之对于的一个struct file结构 240 | * 这个结构在epoll_create1()中, 由函数anon_inode_getfd()分配 */ 241 | error = -EBADF; 242 | file = fget(epfd); 243 | if (!file) 244 | goto error_return; 245 | /* Get the "struct file *" for the target file */ 246 | /* 我们需要监听的fd, 它当然也有个struct file结构, 上下2个不要搞混了哦 */ 247 | tfile = fget(fd); 248 | if (!tfile) 249 | goto error_fput; 250 | /* The target file descriptor must support poll */ 251 | error = -EPERM; 252 | /* 如果监听的文件不支持poll, 那就没辙了. 253 | * 你知道什么情况下, 文件会不支持poll吗? 254 | */ 255 | if (!tfile->f_op || !tfile->f_op->poll) 256 | goto error_tgt_fput; 257 | /* 258 | * We have to check that the file structure underneath the file descriptor 259 | * the user passed to us _is_ an eventpoll file. And also we do not permit 260 | * adding an epoll file descriptor inside itself. 261 | */ 262 | error = -EINVAL; 263 | /* epoll不能自己监听自己... */ 264 | if (file == tfile || !is_file_epoll(file)) 265 | goto error_tgt_fput; 266 | /* 267 | * At this point it is safe to assume that the "private_data" contains 268 | * our own data structure. 269 | */ 270 | /* 取到我们的eventpoll结构, 来自与epoll_create1()中的分配 */ 271 | ep = file->private_data; 272 | /* 接下来的操作有可能修改数据结构内容, 锁之~ */ 273 | mutex_lock(&ep->mtx); 274 | /* 275 | * Try to lookup the file inside our RB tree, Since we grabbed "mtx" 276 | * above, we can be sure to be able to use the item looked up by 277 | * ep_find() till we release the mutex. 278 | */ 279 | /* 对于每一个监听的fd, 内核都有分配一个epitem结构, 280 | * 而且我们也知道, epoll是不允许重复添加fd的, 281 | * 所以我们首先查找该fd是不是已经存在了. 282 | * ep_find()其实就是RBTREE查找, 跟C++STL的map差不多一回事, O(lgn)的时间复杂度. 283 | */ 284 | epi = ep_find(ep, tfile, fd); 285 | error = -EINVAL; 286 | switch (op) { 287 | /* 首先我们关心添加 */ 288 | case EPOLL_CTL_ADD: 289 | if (!epi) { 290 | /* 之前的find没有找到有效的epitem, 证明是第一次插入, 接受! 291 | * 这里我们可以知道, POLLERR和POLLHUP事件内核总是会关心的 292 | * */ 293 | epds.events |= POLLERR | POLLHUP; 294 | /* rbtree插入, 详情见ep_insert()的分析 295 | * 其实我觉得这里有insert的话, 之前的find应该 296 | * 是可以省掉的... */ 297 | error = ep_insert(ep, &epds, tfile, fd); 298 | } else 299 | /* 找到了!? 重复添加! */ 300 | error = -EEXIST; 301 | break; 302 | /* 删除和修改操作都比较简单 */ 303 | case EPOLL_CTL_DEL: 304 | if (epi) 305 | error = ep_remove(ep, epi); 306 | else 307 | error = -ENOENT; 308 | break; 309 | case EPOLL_CTL_MOD: 310 | if (epi) { 311 | epds.events |= POLLERR | POLLHUP; 312 | error = ep_modify(ep, epi, &epds); 313 | } else 314 | error = -ENOENT; 315 | break; 316 | } 317 | mutex_unlock(&ep->mtx); 318 | error_tgt_fput: 319 | fput(tfile); 320 | error_fput: 321 | fput(file); 322 | error_return: 323 | return error; 324 | } 325 | /* 分配一个eventpoll结构 */ 326 | static int ep_alloc(struct eventpoll **pep) 327 | { 328 | int error; 329 | struct user_struct *user; 330 | struct eventpoll *ep; 331 | /* 获取当前用户的一些信息, 比如是不是root啦, 最大监听fd数目啦 */ 332 | user = get_current_user(); 333 | error = -ENOMEM; 334 | ep = kzalloc(sizeof(*ep), GFP_KERNEL); 335 | if (unlikely(!ep)) 336 | goto free_uid; 337 | /* 这些都是初始化啦 */ 338 | spin_lock_init(&ep->lock); 339 | mutex_init(&ep->mtx); 340 | init_waitqueue_head(&ep->wq);//初始化自己睡在的等待队列 341 | init_waitqueue_head(&ep->poll_wait);//初始化 342 | INIT_LIST_HEAD(&ep->rdllist);//初始化就绪链表 343 | ep->rbr = RB_ROOT; 344 | ep->ovflist = EP_UNACTIVE_PTR; 345 | ep->user = user; 346 | *pep = ep; 347 | return 0; 348 | free_uid: 349 | free_uid(user); 350 | return error; 351 | } 352 | /* 353 | * Must be called with "mtx" held. 354 | */ 355 | /* 356 | * ep_insert()在epoll_ctl()中被调用, 完成往epollfd里面添加一个监听fd的工作 357 | * tfile是fd在内核态的struct file结构 358 | */ 359 | static int ep_insert(struct eventpoll *ep, struct epoll_event *event, 360 | struct file *tfile, int fd) 361 | { 362 | int error, revents, pwake = 0; 363 | unsigned long flags; 364 | struct epitem *epi; 365 | struct ep_pqueue epq; 366 | /* 查看是否达到当前用户的最大监听数 */ 367 | if (unlikely(atomic_read(&ep->user->epoll_watches) >= 368 | max_user_watches)) 369 | return -ENOSPC; 370 | /* 从著名的slab中分配一个epitem */ 371 | if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL))) 372 | return -ENOMEM; 373 | /* Item initialization follow here ... */ 374 | /* 这些都是相关成员的初始化... */ 375 | INIT_LIST_HEAD(&epi->rdllink); 376 | INIT_LIST_HEAD(&epi->fllink); 377 | INIT_LIST_HEAD(&epi->pwqlist); 378 | epi->ep = ep; 379 | /* 这里保存了我们需要监听的文件fd和它的file结构 */ 380 | ep_set_ffd(&epi->ffd, tfile, fd); 381 | epi->event = *event; 382 | epi->nwait = 0; 383 | /* 这个指针的初值不是NULL哦... */ 384 | epi->next = EP_UNACTIVE_PTR; 385 | /* Initialize the poll table using the queue callback */ 386 | /* 好, 我们终于要进入到poll的正题了 */ 387 | epq.epi = epi; 388 | /* 初始化一个poll_table 389 | * 其实就是指定调用poll_wait(注意不是epoll_wait!!!)时的回调函数,和我们关心哪些events, 390 | * ep_ptable_queue_proc()就是我们的回调啦, 初值是所有event都关心 */ 391 | init_poll_funcptr(&epq.pt, ep_ptable_queue_proc); 392 | /* 393 | * Attach the item to the poll hooks and get current event bits. 394 | * We can safely use the file* here because its usage count has 395 | * been increased by the caller of this function. Note that after 396 | * this operation completes, the poll callback can start hitting 397 | * the new item. 398 | */ 399 | /* 这一部很关键, 也比较难懂, 完全是内核的poll机制导致的... 400 | * 首先, f_op->poll()一般来说只是个wrapper, 它会调用真正的poll实现, 401 | * 拿UDP的socket来举例, 这里就是这样的调用流程: f_op->poll(), sock_poll(), 402 | * udp_poll(), datagram_poll(), sock_poll_wait(), 最后调用到我们上面指定的 403 | * ep_ptable_queue_proc()这个回调函数...(好深的调用路径...). 404 | * 完成这一步, 我们的epitem就跟这个socket关联起来了, 当它有状态变化时, 405 | * 会通过ep_poll_callback()来通知. 406 | * 最后, 这个函数还会查询当前的fd是不是已经有啥event已经ready了, 有的话 407 | * 会将event返回. */ 408 | revents = tfile->f_op->poll(tfile, &epq.pt); 409 | /* 410 | * We have to check if something went wrong during the poll wait queue 411 | * install process. Namely an allocation for a wait queue failed due 412 | * high memory pressure. 413 | */ 414 | error = -ENOMEM; 415 | if (epi->nwait < 0) 416 | goto error_unregister; 417 | /* Add the current item to the list of active epoll hook for this file */ 418 | /* 这个就是每个文件会将所有监听自己的epitem链起来 */ 419 | spin_lock(&tfile->f_lock); 420 | list_add_tail(&epi->fllink, &tfile->f_ep_links); 421 | spin_unlock(&tfile->f_lock); 422 | /* 423 | * Add the current item to the RB tree. All RB tree operations are 424 | * protected by "mtx", and ep_insert() is called with "mtx" held. 425 | */ 426 | /* 都搞定后, 将epitem插入到对应的eventpoll中去 */ 427 | ep_rbtree_insert(ep, epi); 428 | /* We have to drop the new item inside our item list to keep track of it */ 429 | spin_lock_irqsave(&ep->lock, flags); 430 | /* If the file is already "ready" we drop it inside the ready list */ 431 | /* 到达这里后, 如果我们监听的fd已经有事件发生, 那就要处理一下 */ 432 | if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) { 433 | /* 将当前的epitem加入到ready list中去 */ 434 | list_add_tail(&epi->rdllink, &ep->rdllist); 435 | /* Notify waiting tasks that events are available */ 436 | /* 谁在epoll_wait, 就唤醒它... */ 437 | if (waitqueue_active(&ep->wq)) 438 | wake_up_locked(&ep->wq); 439 | /* 谁在epoll当前的epollfd, 也唤醒它... */ 440 | if (waitqueue_active(&ep->poll_wait)) 441 | pwake++; 442 | } 443 | spin_unlock_irqrestore(&ep->lock, flags); 444 | atomic_inc(&ep->user->epoll_watches); 445 | /* We have to call this outside the lock */ 446 | if (pwake) 447 | ep_poll_safewake(&ep->poll_wait); 448 | return 0; 449 | error_unregister: 450 | ep_unregister_pollwait(ep, epi); 451 | /* 452 | * We need to do this because an event could have been arrived on some 453 | * allocated wait queue. Note that we don't care about the ep->ovflist 454 | * list, since that is used/cleaned only inside a section bound by "mtx". 455 | * And ep_insert() is called with "mtx" held. 456 | */ 457 | spin_lock_irqsave(&ep->lock, flags); 458 | if (ep_is_linked(&epi->rdllink)) 459 | list_del_init(&epi->rdllink); 460 | spin_unlock_irqrestore(&ep->lock, flags); 461 | kmem_cache_free(epi_cache, epi); 462 | return error; 463 | } 464 | /* 465 | * This is the callback that is used to add our wait queue to the 466 | * target file wakeup lists. 467 | */ 468 | /* 469 | * 该函数在调用f_op->poll()时会被调用. 470 | * 也就是epoll主动poll某个fd时, 用来将epitem与指定的fd关联起来的. 471 | * 关联的办法就是使用等待队列(waitqueue) 472 | */ 473 | static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead, 474 | poll_table *pt) 475 | { 476 | struct epitem *epi = ep_item_from_epqueue(pt); 477 | struct eppoll_entry *pwq; 478 | if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) { 479 | /* 初始化等待队列, 指定ep_poll_callback为唤醒时的回调函数, 480 | * 当我们监听的fd发生状态改变时, 也就是队列头被唤醒时, 481 | * 指定的回调函数将会被调用. */ 482 | init_waitqueue_func_entry(&pwq->wait, ep_poll_callback); 483 | pwq->whead = whead; 484 | pwq->base = epi; 485 | /* 将刚分配的等待队列成员加入到头中, 头是由fd持有的 */ 486 | add_wait_queue(whead, &pwq->wait); 487 | list_add_tail(&pwq->llink, &epi->pwqlist); 488 | /* nwait记录了当前epitem加入到了多少个等待队列中, 489 | * 我认为这个值最大也只会是1... */ 490 | epi->nwait++; 491 | } else { 492 | /* We have to signal that an error occurred */ 493 | epi->nwait = -1; 494 | } 495 | } 496 | /* 497 | * This is the callback that is passed to the wait queue wakeup 498 | * machanism. It is called by the stored file descriptors when they 499 | * have events to report. 500 | */ 501 | /* 502 | * 这个是关键性的回调函数, 当我们监听的fd发生状态改变时, 它会被调用. 503 | * 参数key被当作一个unsigned long整数使用, 携带的是events. 504 | */ 505 | static int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key) 506 | { 507 | int pwake = 0; 508 | unsigned long flags; 509 | struct epitem *epi = ep_item_from_wait(wait);//从等待队列获取epitem.需要知道哪个进程挂载到这个设备 510 | struct eventpoll *ep = epi->ep;//获取 511 | spin_lock_irqsave(&ep->lock, flags); 512 | /* 513 | * If the event mask does not contain any poll(2) event, we consider the 514 | * descriptor to be disabled. This condition is likely the effect of the 515 | * EPOLLONESHOT bit that disables the descriptor when an event is received, 516 | * until the next EPOLL_CTL_MOD will be issued. 517 | */ 518 | if (!(epi->event.events & ~EP_PRIVATE_BITS)) 519 | goto out_unlock; 520 | /* 521 | * Check the events coming with the callback. At this stage, not 522 | * every device reports the events in the "key" parameter of the 523 | * callback. We need to be able to handle both cases here, hence the 524 | * test for "key" != NULL before the event match test. 525 | */ 526 | /* 没有我们关心的event... */ 527 | if (key && !((unsigned long) key & epi->event.events)) 528 | goto out_unlock; 529 | /* 530 | * If we are trasfering events to userspace, we can hold no locks 531 | * (because we're accessing user memory, and because of linux f_op->poll() 532 | * semantics). All the events that happens during that period of time are 533 | * chained in ep->ovflist and requeued later on. 534 | */ 535 | /* 536 | * 这里看起来可能有点费解, 其实干的事情比较简单: 537 | * 如果该callback被调用的同时, epoll_wait()已经返回了, 538 | * 也就是说, 此刻应用程序有可能已经在循环获取events, 539 | * 这种情况下, 内核将此刻发生event的epitem用一个单独的链表 540 | * 链起来, 不发给应用程序, 也不丢弃, 而是在下一次epoll_wait 541 | * 时返回给用户. 542 | */ 543 | if (unlikely(ep->ovflist != EP_UNACTIVE_PTR)) { 544 | if (epi->next == EP_UNACTIVE_PTR) { 545 | epi->next = ep->ovflist; 546 | ep->ovflist = epi; 547 | } 548 | goto out_unlock; 549 | } 550 | /* If this file is already in the ready list we exit soon */ 551 | /* 将当前的epitem放入ready list */ 552 | if (!ep_is_linked(&epi->rdllink)) 553 | list_add_tail(&epi->rdllink, &ep->rdllist); 554 | /* 555 | * Wake up ( if active ) both the eventpoll wait list and the ->poll() 556 | * wait list. 557 | */ 558 | /* 唤醒epoll_wait... */ 559 | if (waitqueue_active(&ep->wq)) 560 | wake_up_locked(&ep->wq); 561 | /* 如果epollfd也在被poll, 那就唤醒队列里面的所有成员. */ 562 | if (waitqueue_active(&ep->poll_wait)) 563 | pwake++; 564 | out_unlock: 565 | spin_unlock_irqrestore(&ep->lock, flags); 566 | /* We have to call this outside the lock */ 567 | if (pwake) 568 | ep_poll_safewake(&ep->poll_wait); 569 | return 1; 570 | } 571 | /* 572 | * Implement the event wait interface for the eventpoll file. It is the kernel 573 | * part of the user space epoll_wait(2). 574 | */ 575 | SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events, 576 | int, maxevents, int, timeout) 577 | { 578 | int error; 579 | struct file *file; 580 | struct eventpoll *ep; 581 | /* The maximum number of event must be greater than zero */ 582 | if (maxevents <= 0 || maxevents > EP_MAX_EVENTS) 583 | return -EINVAL; 584 | /* Verify that the area passed by the user is writeable */ 585 | /* 这个地方有必要说明一下: 586 | * 内核对应用程序采取的策略是"绝对不信任", 587 | * 所以内核跟应用程序之间的数据交互大都是copy, 不允许(也时候也是不能...)指针引用. 588 | * epoll_wait()需要内核返回数据给用户空间, 内存由用户程序提供, 589 | * 所以内核会用一些手段来验证这一段内存空间是不是有效的. 590 | */ 591 | if (!access_ok(VERIFY_WRITE, events, maxevents * sizeof(struct epoll_event))) { 592 | error = -EFAULT; 593 | goto error_return; 594 | } 595 | /* Get the "struct file *" for the eventpoll file */ 596 | error = -EBADF; 597 | /* 获取epollfd的struct file, epollfd也是文件嘛 */ 598 | file = fget(epfd); 599 | if (!file) 600 | goto error_return; 601 | /* 602 | * We have to check that the file structure underneath the fd 603 | * the user passed to us _is_ an eventpoll file. 604 | */ 605 | error = -EINVAL; 606 | /* 检查一下它是不是一个真正的epollfd... */ 607 | if (!is_file_epoll(file)) 608 | goto error_fput; 609 | /* 610 | * At this point it is safe to assume that the "private_data" contains 611 | * our own data structure. 612 | */ 613 | /* 获取eventpoll结构 */ 614 | ep = file->private_data; 615 | /* Time to fish for events ... */ 616 | /* OK, 睡觉, 等待事件到来~~ */ 617 | error = ep_poll(ep, events, maxevents, timeout); 618 | error_fput: 619 | fput(file); 620 | error_return: 621 | return error; 622 | } 623 | /* 这个函数真正将执行epoll_wait的进程带入睡眠状态... */ 624 | static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events, 625 | int maxevents, long timeout) 626 | { 627 | int res, eavail; 628 | unsigned long flags; 629 | long jtimeout; 630 | wait_queue_t wait;//等待队列 631 | /* 632 | * Calculate the timeout by checking for the "infinite" value (-1) 633 | * and the overflow condition. The passed timeout is in milliseconds, 634 | * that why (t * HZ) / 1000. 635 | */ 636 | /* 计算睡觉时间, 毫秒要转换为HZ */ 637 | jtimeout = (timeout < 0 || timeout >= EP_MAX_MSTIMEO) ? 638 | MAX_SCHEDULE_TIMEOUT : (timeout * HZ + 999) / 1000; 639 | retry: 640 | spin_lock_irqsave(&ep->lock, flags); 641 | res = 0; 642 | /* 如果ready list不为空, 就不睡了, 直接干活... */ 643 | if (list_empty(&ep->rdllist)) { 644 | /* 645 | * We don't have any available event to return to the caller. 646 | * We need to sleep here, and we will be wake up by 647 | * ep_poll_callback() when events will become available. 648 | */ 649 | /* OK, 初始化一个等待队列, 准备直接把自己挂起, 650 | * 注意current是一个宏, 代表当前进程 */ 651 | init_waitqueue_entry(&wait, current);//初始化等待队列,wait表示当前进程 652 | __add_wait_queue_exclusive(&ep->wq, &wait);//挂载到ep结构的等待队列 653 | for (;;) { 654 | /* 655 | * We don't want to sleep if the ep_poll_callback() sends us 656 | * a wakeup in between. That's why we set the task state 657 | * to TASK_INTERRUPTIBLE before doing the checks. 658 | */ 659 | /* 将当前进程设置位睡眠, 但是可以被信号唤醒的状态, 660 | * 注意这个设置是"将来时", 我们此刻还没睡! */ 661 | set_current_state(TASK_INTERRUPTIBLE); 662 | /* 如果这个时候, ready list里面有成员了, 663 | * 或者睡眠时间已经过了, 就直接不睡了... */ 664 | if (!list_empty(&ep->rdllist) || !jtimeout) 665 | break; 666 | /* 如果有信号产生, 也起床... */ 667 | if (signal_pending(current)) { 668 | res = -EINTR; 669 | break; 670 | } 671 | /* 啥事都没有,解锁, 睡觉... */ 672 | spin_unlock_irqrestore(&ep->lock, flags); 673 | /* jtimeout这个时间后, 会被唤醒, 674 | * ep_poll_callback()如果此时被调用, 675 | * 那么我们就会直接被唤醒, 不用等时间了... 676 | * 再次强调一下ep_poll_callback()的调用时机是由被监听的fd 677 | * 的具体实现, 比如socket或者某个设备驱动来决定的, 678 | * 因为等待队列头是他们持有的, epoll和当前进程 679 | * 只是单纯的等待... 680 | **/ 681 | jtimeout = schedule_timeout(jtimeout);//睡觉 682 | spin_lock_irqsave(&ep->lock, flags); 683 | } 684 | __remove_wait_queue(&ep->wq, &wait); 685 | /* OK 我们醒来了... */ 686 | set_current_state(TASK_RUNNING); 687 | } 688 | /* Is it worth to try to dig for events ? */ 689 | eavail = !list_empty(&ep->rdllist) || ep->ovflist != EP_UNACTIVE_PTR; 690 | spin_unlock_irqrestore(&ep->lock, flags); 691 | /* 692 | * Try to transfer events to user space. In case we get 0 events and 693 | * there's still timeout left over, we go trying again in search of 694 | * more luck. 695 | */ 696 | /* 如果一切正常, 有event发生, 就开始准备数据copy给用户空间了... */ 697 | if (!res && eavail && 698 | !(res = ep_send_events(ep, events, maxevents)) && jtimeout) 699 | goto retry; 700 | return res; 701 | } 702 | /* 这个简单, 我们直奔下一个... */ 703 | static int ep_send_events(struct eventpoll *ep, 704 | struct epoll_event __user *events, int maxevents) 705 | { 706 | struct ep_send_events_data esed; 707 | esed.maxevents = maxevents; 708 | esed.events = events; 709 | return ep_scan_ready_list(ep, ep_send_events_proc, &esed); 710 | } 711 | /** 712 | * ep_scan_ready_list - Scans the ready list in a way that makes possible for 713 | * the scan code, to call f_op->poll(). Also allows for 714 | * O(NumReady) performance. 715 | * 716 | * @ep: Pointer to the epoll private data structure. 717 | * @sproc: Pointer to the scan callback. 718 | * @priv: Private opaque data passed to the @sproc callback. 719 | * 720 | * Returns: The same integer error code returned by the @sproc callback. 721 | */ 722 | static int ep_scan_ready_list(struct eventpoll *ep, 723 | int (*sproc)(struct eventpoll *, 724 | struct list_head *, void *), 725 | void *priv) 726 | { 727 | int error, pwake = 0; 728 | unsigned long flags; 729 | struct epitem *epi, *nepi; 730 | LIST_HEAD(txlist); 731 | /* 732 | * We need to lock this because we could be hit by 733 | * eventpoll_release_file() and epoll_ctl(). 734 | */ 735 | mutex_lock(&ep->mtx); 736 | /* 737 | * Steal the ready list, and re-init the original one to the 738 | * empty list. Also, set ep->ovflist to NULL so that events 739 | * happening while looping w/out locks, are not lost. We cannot 740 | * have the poll callback to queue directly on ep->rdllist, 741 | * because we want the "sproc" callback to be able to do it 742 | * in a lockless way. 743 | */ 744 | spin_lock_irqsave(&ep->lock, flags); 745 | /* 这一步要注意, 首先, 所有监听到events的epitem都链到rdllist上了, 746 | * 但是这一步之后, 所有的epitem都转移到了txlist上, 而rdllist被清空了, 747 | * 要注意哦, rdllist已经被清空了! */ 748 | list_splice_init(&ep->rdllist, &txlist); 749 | /* ovflist, 在ep_poll_callback()里面我解释过, 此时此刻我们不希望 750 | * 有新的event加入到ready list中了, 保存后下次再处理... */ 751 | ep->ovflist = NULL; 752 | spin_unlock_irqrestore(&ep->lock, flags); 753 | /* 754 | * Now call the callback function. 755 | */ 756 | /* 在这个回调函数里面处理每个epitem 757 | * sproc 就是 ep_send_events_proc, 下面会注释到. */ 758 | error = (*sproc)(ep, &txlist, priv); 759 | spin_lock_irqsave(&ep->lock, flags); 760 | /* 761 | * During the time we spent inside the "sproc" callback, some 762 | * other events might have been queued by the poll callback. 763 | * We re-insert them inside the main ready-list here. 764 | */ 765 | /* 现在我们来处理ovflist, 这些epitem都是我们在传递数据给用户空间时 766 | * 监听到了事件. */ 767 | for (nepi = ep->ovflist; (epi = nepi) != NULL; 768 | nepi = epi->next, epi->next = EP_UNACTIVE_PTR) { 769 | /* 770 | * We need to check if the item is already in the list. 771 | * During the "sproc" callback execution time, items are 772 | * queued into ->ovflist but the "txlist" might already 773 | * contain them, and the list_splice() below takes care of them. 774 | */ 775 | /* 将这些直接放入readylist */ 776 | if (!ep_is_linked(&epi->rdllink)) 777 | list_add_tail(&epi->rdllink, &ep->rdllist); 778 | } 779 | /* 780 | * We need to set back ep->ovflist to EP_UNACTIVE_PTR, so that after 781 | * releasing the lock, events will be queued in the normal way inside 782 | * ep->rdllist. 783 | */ 784 | ep->ovflist = EP_UNACTIVE_PTR; 785 | /* 786 | * Quickly re-inject items left on "txlist". 787 | */ 788 | /* 上一次没有处理完的epitem, 重新插入到ready list */ 789 | list_splice(&txlist, &ep->rdllist); 790 | /* ready list不为空, 直接唤醒... */ 791 | if (!list_empty(&ep->rdllist)) { 792 | /* 793 | * Wake up (if active) both the eventpoll wait list and 794 | * the ->poll() wait list (delayed after we release the lock). 795 | */ 796 | if (waitqueue_active(&ep->wq)) 797 | wake_up_locked(&ep->wq); 798 | if (waitqueue_active(&ep->poll_wait)) 799 | pwake++; 800 | } 801 | spin_unlock_irqrestore(&ep->lock, flags); 802 | mutex_unlock(&ep->mtx); 803 | /* We have to call this outside the lock */ 804 | if (pwake) 805 | ep_poll_safewake(&ep->poll_wait); 806 | return error; 807 | } 808 | /* 该函数作为callbakc在ep_scan_ready_list()中被调用 809 | * head是一个链表, 包含了已经ready的epitem, 810 | * 这个不是eventpoll里面的ready list, 而是上面函数中的txlist. 811 | */ 812 | static int ep_send_events_proc(struct eventpoll *ep, struct list_head *head, 813 | void *priv) 814 | { 815 | struct ep_send_events_data *esed = priv; 816 | int eventcnt; 817 | unsigned int revents; 818 | struct epitem *epi; 819 | struct epoll_event __user *uevent; 820 | /* 821 | * We can loop without lock because we are passed a task private list. 822 | * Items cannot vanish during the loop because ep_scan_ready_list() is 823 | * holding "mtx" during this call. 824 | */ 825 | /* 扫描整个链表... */ 826 | for (eventcnt = 0, uevent = esed->events; 827 | !list_empty(head) && eventcnt < esed->maxevents;) { 828 | /* 取出第一个成员 */ 829 | epi = list_first_entry(head, struct epitem, rdllink); 830 | /* 然后从链表里面移除 */ 831 | list_del_init(&epi->rdllink); 832 | /* 读取events, 833 | * 注意events我们ep_poll_callback()里面已经取过一次了, 为啥还要再取? 834 | * 1. 我们当然希望能拿到此刻的最新数据, events是会变的~ 835 | * 2. 不是所有的poll实现, 都通过等待队列传递了events, 有可能某些驱动压根没传 836 | * 必须主动去读取. */ 837 | revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL) & 838 | epi->event.events; 839 | if (revents) { 840 | /* 将当前的事件和用户传入的数据都copy给用户空间, 841 | * 就是epoll_wait()后应用程序能读到的那一堆数据. */ 842 | if (__put_user(revents, &uevent->events) || 843 | __put_user(epi->event.data, &uevent->data)) { 844 | list_add(&epi->rdllink, head); 845 | return eventcnt ? eventcnt : -EFAULT; 846 | } 847 | eventcnt++; 848 | uevent++; 849 | if (epi->event.events & EPOLLONESHOT) 850 | epi->event.events &= EP_PRIVATE_BITS; 851 | else if (!(epi->event.events & EPOLLET)) { 852 | /* 嘿嘿, EPOLLET和非ET的区别就在这一步之差呀~ 853 | * 如果是ET, epitem是不会再进入到readly list, 854 | * 除非fd再次发生了状态改变, ep_poll_callback被调用. 855 | * 如果是非ET, 不管你还有没有有效的事件或者数据, 856 | * 都会被重新插入到ready list, 再下一次epoll_wait 857 | * 时, 会立即返回, 并通知给用户空间. 当然如果这个 858 | * 被监听的fds确实没事件也没数据了, epoll_wait会返回一个0, 859 | * 空转一次. 860 | */ 861 | list_add_tail(&epi->rdllink, &ep->rdllist); 862 | } 863 | } 864 | } 865 | return eventcnt; 866 | } 867 | /* ep_free在epollfd被close时调用, 868 | * 释放一些资源而已, 比较简单 */ 869 | static void ep_free(struct eventpoll *ep) 870 | { 871 | struct rb_node *rbp; 872 | struct epitem *epi; 873 | /* We need to release all tasks waiting for these file */ 874 | if (waitqueue_active(&ep->poll_wait)) 875 | ep_poll_safewake(&ep->poll_wait); 876 | /* 877 | * We need to lock this because we could be hit by 878 | * eventpoll_release_file() while we're freeing the "struct eventpoll". 879 | * We do not need to hold "ep->mtx" here because the epoll file 880 | * is on the way to be removed and no one has references to it 881 | * anymore. The only hit might come from eventpoll_release_file() but 882 | * holding "epmutex" is sufficent here. 883 | */ 884 | mutex_lock(&epmutex); 885 | /* 886 | * Walks through the whole tree by unregistering poll callbacks. 887 | */ 888 | for (rbp = rb_first(&ep->rbr); rbp; rbp = rb_next(rbp)) { 889 | epi = rb_entry(rbp, struct epitem, rbn); 890 | ep_unregister_pollwait(ep, epi); 891 | } 892 | /* 893 | * Walks through the whole tree by freeing each "struct epitem". At this 894 | * point we are sure no poll callbacks will be lingering around, and also by 895 | * holding "epmutex" we can be sure that no file cleanup code will hit 896 | * us during this operation. So we can avoid the lock on "ep->lock". 897 | */ 898 | /* 之所以在关闭epollfd之前不需要调用epoll_ctl移除已经添加的fd, 899 | * 是因为这里已经做了... */ 900 | while ((rbp = rb_first(&ep->rbr)) != NULL) { 901 | epi = rb_entry(rbp, struct epitem, rbn); 902 | ep_remove(ep, epi); 903 | } 904 | mutex_unlock(&epmutex); 905 | mutex_destroy(&ep->mtx); 906 | free_uid(ep->user); 907 | kfree(ep); 908 | } 909 | /* File callbacks that implement the eventpoll file behaviour */ 910 | static const struct file_operations eventpoll_fops = { 911 | .release = ep_eventpoll_release, 912 | .poll = ep_eventpoll_poll 913 | }; 914 | /* Fast test to see if the file is an evenpoll file */ 915 | static inline int is_file_epoll(struct file *f) 916 | { 917 | return f->f_op == &eventpoll_fops; 918 | } 919 | /* OK, eventpoll我认为比较重要的函数都注释完了... */ 920 | ``` 921 | -------------------------------------------------------------------------------- /blog/cpp/md/C++对象内存分布.md: -------------------------------------------------------------------------------- 1 | # C++ 对象内存分布 2 | 3 | [详见此处](https://www.jianshu.com/p/9fb37bb3094f) 4 | 5 | - 虚表在前 6 | - 基类非静态成员 7 | - 派生类非静态成员 8 | -------------------------------------------------------------------------------- /blog/cpp/md/Makefile: -------------------------------------------------------------------------------- 1 | test : bfs.cpp 2 | g++ $^ -o $@ -g 3 | 4 | .PHONY : 5 | 6 | clean : 7 | rm test 8 | -------------------------------------------------------------------------------- /blog/cpp/md/SharedPtr.h: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDE_SHARED_PTR_H 2 | #define INCLUDE_SHARED_PTR_H 3 | 4 | #include 5 | #include 6 | 7 | class SharedPtr { 8 | public: 9 | // 构造函数 10 | SharedPtr(int* ptr) { 11 | _ptr = ptr; 12 | g_map.insert(std::pair(_ptr, 1)); 13 | } 14 | 15 | // 拷贝构造函数 16 | SharedPtr(const SharedPtr& rhx) { 17 | _ptr = rhx._ptr; 18 | g_map[_ptr]++; 19 | } 20 | 21 | // 赋值运算符重载 22 | SharedPtr& operator=(const SharedPtr& rhx) { 23 | // 赋值运算符重载 24 | // 如果参数传进来是当前对象 25 | // 直接返回 *this 26 | if (_ptr == rhx._ptr) { 27 | return *this; 28 | } 29 | 30 | // 如果传进来的不是当前对象 31 | // 这里要格外注意 32 | if (g_map[_ptr] <= 0 && _ptr != nullptr) { 33 | delete _ptr; 34 | _ptr = nullptr; 35 | g_map.erase(_ptr); 36 | } 37 | _ptr = rhx._ptr; 38 | g_map[_ptr]++; 39 | return *this; 40 | } 41 | 42 | // 解引用重载 43 | int& operator*() { 44 | return *_ptr; 45 | } 46 | 47 | // 指针运算符重载 48 | int* operator->() { 49 | return this->_ptr; 50 | } 51 | 52 | // 析构函数 53 | ~SharedPtr() { 54 | // 什么时候析构 55 | // 在引用计数为 0 时,并且指针不为空才可以进行 delete 56 | if (g_map[_ptr] <= 0 && _ptr != nullptr) { 57 | delete _ptr; 58 | _ptr = nullptr; 59 | g_map.erase(_ptr); 60 | } 61 | } 62 | static std::map g_map; 63 | private: 64 | int* _ptr; 65 | }; 66 | 67 | // 静态成员初始化 68 | std::map SharedPtr::g_map; 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /blog/cpp/md/String.h: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDE_STRING_H 2 | #define INCLUDE_STRING_H 3 | 4 | #include 5 | 6 | class String { 7 | public: 8 | String(char* rhx = nullptr) { 9 | if (rhx == nullptr) { 10 | _str = new char [1]; 11 | *_str = '\0'; 12 | } else { 13 | int len = strlen(rhx); 14 | _str = new char [len + 1]; 15 | strcpy(_str, rhx); 16 | } 17 | } 18 | String(const String& rhx) { 19 | int len = strlen(rhx._str); 20 | _str = new char [len + 1]; 21 | strcpy(_str, rhx._str); 22 | } 23 | String& operator=(const String& rhx) { 24 | if (_str == rhx._str) { 25 | return *this; 26 | } else { 27 | delete [] _str; 28 | int len = strlen(rhx._str); 29 | _str = new char [len + 1]; 30 | strcpy(_str, rhx._str); 31 | return *this; 32 | } 33 | } 34 | ~String() { 35 | if (_str != nullptr) { 36 | delete [] _str; 37 | _str = nullptr; 38 | } 39 | } 40 | private: 41 | char* _str; 42 | }; 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /blog/cpp/md/TreeOrder.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std; 5 | 6 | struct TreeNode { 7 | int _val; 8 | TreeNode* _left; 9 | TreeNode* _right; 10 | TreeNode(int x) 11 | :_val(x), 12 | _left(nullptr), 13 | _right(nullptr) 14 | {} 15 | }; 16 | 17 | // 先序遍历,根 左 右 18 | void preOrder(const TreeNode* root) { 19 | if (root == nullptr) { 20 | return ; 21 | } 22 | cout << root->_val; 23 | preOrder(root->_left); 24 | preOrder(root->_right); 25 | } 26 | 27 | // 中序遍历,左 根 右 28 | void inOrder(const TreeNode* root) { 29 | if (root == nullptr) { 30 | return ; 31 | } 32 | inOrder(root->_left); 33 | cout << root->_val; 34 | inOrder(root->_right); 35 | } 36 | 37 | // 后续遍历,左 右 根 38 | void posOrder(const TreeNode* root) { 39 | if (root == nullptr) { 40 | return ; 41 | } 42 | posOrder(root->_left); 43 | posOrder(root->_right); 44 | cout << root->_val; 45 | } 46 | -------------------------------------------------------------------------------- /blog/cpp/md/TreeOrderLoop.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | 7 | struct TreeNode { 8 | int _val; 9 | TreeNode* _left; 10 | TreeNode* _right; 11 | TreeNode(int x) 12 | :_val(x), 13 | _left(nullptr), 14 | _right(nullptr) 15 | {} 16 | }; 17 | 18 | void preOrderLoop(TreeNode* root) { 19 | stack s; 20 | TreeNode* cur = root; 21 | s.push(cur); 22 | while (!s.empty()) { 23 | cur = s.top(); 24 | s.pop(); 25 | cout << cur->_val; 26 | if (cur->_left) { 27 | s.push(cur->_left); 28 | } 29 | if (cur->_right) { 30 | s.push(cur->_right); 31 | } 32 | } 33 | } 34 | 35 | void levelOrder(TreeNode* root) { 36 | if (root) { 37 | queue q; 38 | TreeNode* cur = root; 39 | q.push(cur); 40 | while (!q.empty()) { 41 | cur = q.front(); 42 | q.pop(); 43 | cout << cur->_val; 44 | if (cur->_left) { 45 | q.push(cur->_left); 46 | } 47 | if (cur->_right) { 48 | q.push(cur->_right); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /blog/cpp/md/bfs.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | 7 | struct Graph { 8 | vector vexs; 9 | vector > arcs; 10 | int v, e; 11 | }; 12 | 13 | int locateVex(Graph& g, int v) { 14 | int res = 0; 15 | for (int i = 0; i < g.v; i++) { 16 | if (g.vexs[i] == v) { 17 | res = i; 18 | break; 19 | } 20 | } 21 | return res; 22 | } 23 | 24 | void createGraph(Graph& g) { 25 | cout << "请输入图的顶点和边" << endl; 26 | cin >> g.v >> g.e; 27 | cout << "请输入图的顶点" << endl; 28 | for (int i = 0; i < g.v; i++) { 29 | cin >> g.vexs[i]; 30 | } 31 | cout << "请输入两顶点之间边的权值" << endl; 32 | for (int k = 0; k < g.e; k++) { 33 | int v1, v2, weight; 34 | cin >> v1 >> v2 >> weight; 35 | g.arcs[locateVex(g, v1)][locateVex(g, v2)] = weight; 36 | } 37 | for (int i = 0; i < g.v; i++) { 38 | for (int j = 0; j < g.v; j++) { 39 | g.arcs[i][j] = 0; 40 | } 41 | } 42 | } 43 | 44 | int main() 45 | { 46 | Graph g; 47 | createGraph(g); 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /blog/cpp/md/const关键字.md: -------------------------------------------------------------------------------- 1 | # const 关键字 2 | 3 | const 修饰一个变量为可读,意味着该变量里的数据只能被访问,而不能被修改,const 离谁近意味着谁就不能被修改 4 | 5 | ### const 作用 6 | 7 | [const 详解](https://blog.csdn.net/Eric_Jo/article/details/4138548) 8 | 9 | - const 可以用来定义常量,修饰函数参数、修饰函数返回值、且被 const 修饰的东西,都受到强制保护,可以预防其他代码无意识的进行修改,从而 10 | 提高了程序的健壮性 11 | - 是编译器保护那些不希望被修改的参数,防止无意识的修改,减少 Bug 12 | - 提高程序可读性,声明一个参数,是为了告诉用户这个参数的应用目的 13 | - const 修饰的函数只能调用 const 修饰的成员函数,被 const 修饰的成员函数不能修改对象的数据成员且不能调用非 const 函数 14 | - 修饰指针,指向的内容不可更改或者指针本身的指向不能修改 15 | - 修饰引用,引用的对象不可修改 16 | - 修饰类,只能调用该类的const成员函数,不能修改该类的任何成员 17 | - 修饰成员函数,不能修改该类的任何成员,只能调用该类的const成员函数 18 | 19 | ### 举个栗子 20 | 21 | ```cpp 22 | const int *p; 修饰 int 即 *p 所指向的内容无法改变 23 | int const *p; 修饰 int 即 *p 所指向的内容无法改变 24 | int *const p; 修饰 int* 即 *p 不能改变指向 25 | const int *const p; 修饰 int 和 int* 指向和所指向的内容都不能改变 26 | 27 | class A { 28 | public: 29 | const void func(const int m_value) const; 30 | }; 31 | ``` 32 | -------------------------------------------------------------------------------- /blog/cpp/md/core.14799: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apriluestc/2020/2a7b8e1db4ca98eb0ebc31ca27323a6466d98676/blog/cpp/md/core.14799 -------------------------------------------------------------------------------- /blog/cpp/md/core.15022: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apriluestc/2020/2a7b8e1db4ca98eb0ebc31ca27323a6466d98676/blog/cpp/md/core.15022 -------------------------------------------------------------------------------- /blog/cpp/md/list详解.md: -------------------------------------------------------------------------------- 1 | # list 详解 2 | 3 | - STL list 是一个双向链表,迭代器必须具备前移、后移的能力,环状双向链表,对于任何位置的元素插入删除永远是常数时间 4 | - 只要刻意在环状链表的尾端加上一个空白节点,便符合 STL 规范之前闭后开的原则 5 | 6 | ### list 结点结构 7 | 8 | ```cpp 9 | struct _List_node_base { 10 | _List_node_base* _M_next; 11 | _List_node_base* _M_prev; 12 | }; 13 | 14 | template 15 | struct _List_node : public _List_node_base { 16 | _Tp _M_data; 17 | }; 18 | ``` 19 | 20 | ### list 的基本操作 21 | 22 | - push_front:插入一个节点,作为头节点 23 | - push_back:将新元素插入到 list 尾端 24 | - pop_front:移除头节点 25 | - pop_back:移除尾部结点 26 | - insert:插入是指插入之前,插入完成后,新节点将位于标识出插入所指结点的前方 27 | -------------------------------------------------------------------------------- /blog/cpp/md/map和set详解.md: -------------------------------------------------------------------------------- 1 | # map 和 set 2 | 3 | map 和 set 都是 STL 中提供的关联式容器,其底层实现都是红黑树,由于 map 和 set 所开放的各种操作接口,红黑树也都实现了,所以几乎 4 | 所有的 map 和 set 操作行为,都只是间接调用红黑树的操作 5 | 6 | ### map 和 set 的区别 7 | 8 | - map 中的元素是 key-value,关键字起到索引的作用,值则表示与索引相关联的数据,对于 set 而言,其则 9 | 是关键字的集合,set 中每个元素只包含一个关键字 10 | - set 的迭代器是 const 的,不允许修改元素的值,map 允许修改 value,但不允许修改 key,其原因是 map 和 set 是根据 11 | 关键字排序来保证有序性的,如果允许修改 key 之后,那么首先需要删除该键,然后调节平衡,再插入修改后的键值,调节 12 | 平衡,如此一来严重破坏了 map 和 set 的结构,导致 iterator 失效,不知道应该指向改变前的位置还是改变后的位置,所以 STL 将 13 | set 迭代器设置成 const,不允许修改迭代器的值,而 map 的迭代器则不允许修改 key 值,允许修改 value 值 14 | - map支持下标操作,set不支持下标操作。map可以用key做下标,map的下标运算符[ ]将关键码作为下标去执行查找,如果关 15 | 键码不存在,则插入一个具有该关键码和mapped_type类型默认值的元素至map中,因此下标运算符[]在map应用 16 | 中需要慎用,const_map不能用,只希望确定某一个关键值是否存在而不希望 17 | 插入元素时也不应该使用,mapped_type类型没有默认值也不应该使用。如果find能解决需要, 18 | 尽可能用find。 19 | 20 | 21 | -------------------------------------------------------------------------------- /blog/cpp/md/static关键字.md: -------------------------------------------------------------------------------- 1 | # static 关键字 2 | 3 | ### static 关键字主要有一下使用场景 4 | 5 | - static 修饰全局变量 6 | 7 | 静态全局变量存放在 .data 段,静态全局变量只在本文件内可见,外部文件无法访问,只初始化一次,而 8 | 静态局部变量只在定义的作用于范围内可见,但是它们的生命周期都是整个程序的运行时期 9 | 10 | - static 修饰函数 11 | 12 | static 修饰的函数为静态函数,静态函数主要是起到隐藏的作用,static 修饰的函数只允许在当前文件中使用,其他文件无法找到该函数的地址, 13 | 14 | - static 修饰成员变量 15 | 16 | static 修饰的成员变量属于类的组成部分,static 修饰的成员变量不在栈上分配,而在 17 | 数据区分配内存,static 修饰的成员变量不能通过调用构造函数来进行初始化,因为 static 修饰的成员变量必须在类外进行初始化且只初始化一次 18 | 19 | - static 修饰成员函数 20 | 21 | static 修饰的成员函数‘为静态成员函数,静态成员函数在类内声明,static 成员函数没有 this 指针,所以 22 | 不能直接引用非 static 数据成员或调用类的非 static 成员函数,只能调用类的 static 成员函数和静态数据变量,static 成员函数 23 | 也是类的组成部分,不属于对象,static 成员函数不能声明为 const,因为 const 只限定该类的对象,static 成员函数也不能声明为虚函数 24 | 25 | static 成员函数不能声明为 const,且不能为虚函数 26 | -------------------------------------------------------------------------------- /blog/cpp/md/struct和class.md: -------------------------------------------------------------------------------- 1 | # struct 和 class 区别 2 | 3 | ### 默认的访问权限 4 | 5 | - class 中默认的访问权限是 private 的,而 struct 中则是 public 的 6 | 7 | ### 默认的继承权限 8 | 9 | - 默认继承权限。如果不明确指定,来自class的继承按照private继承处理,来自struct的继承按照public继承处理 10 | 11 | ### 大括号初始化问题 12 | 13 | - 在 C 语言中,我们知道 struct 是一种数据类型,只能定义数据成员,不能定义函数,这是因为 C 是面向过程,面向过程认为 14 | 数据和操作是分开的,所以 C 语言中可以看到这样的情况 15 | 16 | ```cpp 17 | struct test { 18 | int a; 19 | int b; 20 | }; 21 | 22 | test A = {1, 2}; // 完全可以 23 | ``` 24 | 25 | - 在 C++ 中 struct 的功能进行了扩展,struct 可以被继承,可以包含成员函数,也可以实现多态,用大括号初始化 26 | 时需注意 27 | - 当 struct 和 class 中都定义了构造函数就不能使用大括号进行初始化 28 | - 若没有定义构造函数,struct 可以用 {} 进行初始化,而只有当 class 的所有数据成员集函数为 public 时才可以使用 {} 初始化 29 | -------------------------------------------------------------------------------- /blog/cpp/md/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | 7 | int main() 8 | { 9 | map mapStudent; 10 | mapStudent.insert(pair(1, "student_one")); 11 | mapStudent.insert(pair(2, "student_two")); 12 | mapStudent.insert(pair(3, "student_three")); 13 | map::iterator iter; 14 | iter = mapStudent.find(1); 15 | if(iter != mapStudent.end()) 16 | cout << "Find, the value is " << iter->second << endl; 17 | else 18 | cout << "Do not Find" << endl; 19 | map _hash; 20 | _hash.insert(pair(1, 2)); 21 | _hash.insert(pair(2, 3)); 22 | _hash.insert(pair(8, 1)); 23 | _hash.insert(pair(5, 1)); 24 | map::iterator iterp; 25 | for (iterp = _hash.begin(); iterp != _hash.end(); iterp++) { 26 | cout << iterp->first << " "; 27 | } 28 | cout << endl; 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /blog/cpp/md/vector详解.md: -------------------------------------------------------------------------------- 1 | # vector 详解 2 | 3 | - vector 维护的是一段连续线性空间,所以不论其元素类型为何,普通指针都可作为 vector 的迭代器而满足所有必要条件 4 | - 增加新元素,如果超出当前容量,则容量会扩充至两倍 5 | 6 | ### vector 上常见的操作复杂度 7 | 8 | - 随机访问、尾插、尾删 O(1) 9 | - 插入或者移除元素 O(n) 10 | 11 | ### vector 的迭代器类型 12 | 13 | - vector 的迭代器涵盖了原生指针的算术能力,同时 vector 支持随机存取,所以 vector 提供的是随机访问迭代器 14 | - 迭代器函数 15 | - begin():获取首元素位置,起始位置 16 | - end():返回最后一个元素的下一个位置,vector 实现遵循区间左开右闭原则 17 | 18 | ### vector 的扩容原理,动态扩容 19 | 20 | - 所谓动态扩容,并不是在原空间之后接后续的新空间,而是另外配置一块大空间,然后将原内容拷贝过来,接着就是在原内容之后构造新元素,并释放原来空间,因此 21 | vector 的空间一旦重新配置,指向原 vector 的所有迭代器均会失效 22 | - 配置一块更大的空间 23 | - 将原内容拷贝过去 24 | - 释放原空间 25 | 26 | ### vector 的数据结构 27 | 28 | ```cpp 29 | template 30 | class vector { 31 | public: 32 | typedef T value_type; 33 | typedef value_typr* iterator; 34 | protected: 35 | iterator start; // 表示目前使用空间的头 36 | iterator finish;// 表示目前使用空间的尾 37 | iterator end_of_storage; // 表示目前可用空间的尾 38 | }; 39 | ``` 40 | 41 | ### 与 capicaty、size 有关的函数 42 | 43 | - resize() 44 | - resize 可以改变有效空间的大小 45 | - resize 可以改变默认值,但其改变的范围是有限制的,这个范围是当前设定的 size_tmp 范围 46 | - 当新设定的 size_tmp 大于之前的 size 时,默认参数只作用于多开辟的那些空间 47 | - 当 resize 的 size 改变时,capacity 也会随着改变,但是当 reserve 改变 capacity 的大小后,size 的大小并不会随之改变,他仍然是原来有效元素的个数 48 | - reserve():直接区别就是 reserve 只有一个参数 49 | - reserve 中的参数 n 改变的是总空间的大小,它开辟出来的 n 个空间并不代表的都是有效空间只有 object,size() 才表示有效空间 50 | - push_back() 51 | - push_back 作用是尾插,他可能引起 vector 扩容 52 | 53 | ### vector 的基本操作 54 | 55 | - push_back:尾部插入元素 56 | - pop_back:尾部删除元素 57 | - erase:清除 [first,last] 元素,或删除某个位置上的元素 58 | - insert:从某个位置插入 n 个元素,元素初值 x 59 | - clear:清除所有元素 60 | 61 | ### STL 迭代器失效 62 | 63 | - 增加新元素到容器后 64 | - 从容器删除元素 65 | -------------------------------------------------------------------------------- /blog/cpp/md/智能指针.md: -------------------------------------------------------------------------------- 1 | # 智能指针、原理、注意事项、大坑 2 | 3 | ### 使用智能指针应该注意的问题 4 | 5 | - 不要把一个原生的指针给多个 shared_ptr 或者 unique_ptr 管理 6 | 7 | ```cpp 8 | int* ptr = new int(2); 9 | shared_ptr p1(ptr); 10 | shared_ptr p2(ptr); 11 | // p1 p2 析构的时候都会释放 ptr,同一内存被释放多次 12 | ``` 13 | 14 | 我们知道,在使用原生指针对智能指针初始化的时候,智能指针对象都视原生指针为自己管理的资源。换句话意 15 | 思就说:初始化多个智能指针之后,这些智能指针都担负起释放内存的作用。那么就会导致该原生指针会被释放多次!!! 16 | 17 | - 不要把 this 指针交给智能指针管理 18 | 19 | ```cpp 20 | class Test { 21 | public: 22 | void Do() { 23 | m_sp = shared_ptr(this); 24 | } 25 | private: 26 | shared_ptr m_sp; 27 | }; 28 | 29 | int main() 30 | { 31 | Test* t = new Test; 32 | shared_ptr p(t); 33 | p->Do(); 34 | return 0; 35 | } 36 | ``` 37 | 38 | 以上代码发生了什么事情呢?还是同样的错误。把原生指针 this 同时交付给了 m_sp 和 p 管理,这样 39 | 会导致 this 指针被 delete 两次。 40 | 41 | 这里值得注意的是:以上所说的交付给m_sp 和 p 管理不对,并不是指不能多个shared_ptr同时占有 42 | 同一类资源。shared_ptr之间的资源共享是通过shared_ptr智能指针拷贝、赋值实现的,因为这样 43 | 可以引起计数器的更新;而如果直接通过原生指针来初始化,就会导致m_sp和p都根本 44 | 不知道对方的存在,然而却两者都管理同一块地方。相当于”一间庙里请了两尊神” 45 | 46 | - 如果不是通过 new 得到的动态资源内存请自定义删除器 47 | 48 | ```cpp 49 | int main() 50 | { 51 | int* p = (int*)malloc(4 * sizeof(int)); 52 | shared_ptr sp(p); 53 | return 0; 54 | } 55 | ``` 56 | 57 | 上述代码试图将 malloc 产生的动态内存交给 shared_ptr 管理,显然是有问题的,delete 和 malloc 牛头不对马嘴,所以我们要自定义删 58 | 除器,[](int* p) {free(p);} 传递给 shared_ptr 59 | 60 | - 尽量不要使用 get() 61 | 62 | ```cpp 63 | int main() 64 | { 65 | shared_ptr sp(new int(4)); 66 | shared_ptr p(sp.get()); 67 | return 0; 68 | } 69 | ``` 70 | 71 | 智能指针设计者之处提供get()接口是为了使得智能指针也能够适配原生指针使用的相关函数。这个设计可 72 | 以说是个好的设计,也可以说是个失败的设计。因为根据封装的封闭原则,我们将原生指针交付给 73 | 智能指针管理,我们就不应该也不能得到原生指针了;因为原生指针唯一的管理者就应该 74 | 是智能指针。而不是客户逻辑区的其他什么代码。 75 | 76 | 所以我们在使用get()的时候要额外小心,禁止使用get()返回的原生指针再去初始化其他智能指 77 | 针或者释放。(只能够被使用,不能够被管理)。而下面这段代码就违反了这个规定: 78 | 79 | - 尽量使用 make_shared,不要把原生指针暴露出来 80 | 81 | 我们在定义shared_ptr智能指针的时候通常有3种方法: 82 | 83 | 1.先动态开辟内存,然后用局部变量接受指针。再把指针用于初始化。 84 | 85 | 2.直接在初始化参数中写new表达式。 86 | 87 | 3.使用make_shared函数。 88 | 89 | 实际应用中提倡使用第3中方法,第1种方法将原生指针暴露出来了,如果在外面的代码中不小心将该 90 | 指针delete或者初始化其他的智能指针就会出现条款4的错误,所以这不是一个比较好的方法。第 91 | 2种方法,直接在用new表达式作为实参,这样原生指针就匿名了。然而当你用new创建一个对象的同 92 | 时创建一个shared_ptr时,这时会发生两次动态申请内存:一次是给使用new申请的对象本 93 | 身的,而另一次则是由shared_ptr的构造函数引发的为资源管理对象分配的。与此相反,当你 94 | 使用make_shared的时候,C++编译器只会一次性分配一个足够大的内存,用来保存这个资源 95 | 管理者和这个新建对象。 96 | 97 | 下面是3种初始化shared_ptr的方法: 98 | 99 | ```cpp 100 | int main() 101 | { 102 | { 103 | int* p = new int(4); 104 | shared_ptr sp(p); 105 | } 106 | { 107 | shared_ptr sp(new int(3)); 108 | } 109 | { 110 | shared_ptr sp = make_shared(3); 111 | } 112 | return 0; 113 | } 114 | ``` 115 | 116 | ### shared_ptr 的实现 117 | 118 | ```cpp 119 | #include 120 | 121 | class SharedPtr { 122 | public: 123 | SharedPtr(int* ptr) { 124 | ptr_ = ptr; 125 | map_.insert(std::make_pair(ptr_, 1)); 126 | } 127 | SharedPtr(const SharedPtr& rhx) { 128 | ptr_ = rhx.ptr_; 129 | map_[ptr_]++; 130 | } 131 | SharedPtr& operator=(const SharedPtr& rhx) { 132 | if (ptr_ == rhx.ptr_) { 133 | return *this; 134 | } 135 | if (--map_[ptr_] <= 0 && ptr_ != nullptr) { 136 | delete ptr_; 137 | ptr_ = nullptr; 138 | map_.erase(ptr_); 139 | } 140 | ptr_ = rhx.ptr_; 141 | map_[ptr_]++; 142 | return *this; 143 | } 144 | int& operator*() { 145 | return *ptr_; 146 | } 147 | int* operator->() { 148 | return ptr_; 149 | } 150 | ~SharedPtr() { 151 | if (--map_[ptr_] <= 0 && ptr_ != nullptr) { 152 | delete ptr_; 153 | ptr_ = nullptr; 154 | map_.erase(ptr_); 155 | } 156 | } 157 | static std::map map_; 158 | private: 159 | int* ptr_; 160 | }; 161 | 162 | std::map SharedPtr::map_; 163 | ``` 164 | 165 | ### 使用 shared_ptr 的注意事项 166 | 167 | - 尽量使用 make_shared 168 | - 线程不安全 169 | - 避免循环引用,循环引用会导致引用计数器无法清零,造成无法释放 170 | 171 | ```cpp 172 | class Test { 173 | public: 174 | shared_ptr pre; 175 | shared_ptr next; 176 | ~Test() { 177 | cout << "~Test()" << this << endl; 178 | } 179 | int data; 180 | }; 181 | 182 | void func() { 183 | shared_ptr sp1(new Test); 184 | shared_ptr sp2(new Test); 185 | sp1->next = sp2; 186 | sp2->pre = sp1; 187 | } 188 | ``` 189 | -------------------------------------------------------------------------------- /blog/cpp/md/深浅拷贝.md: -------------------------------------------------------------------------------- 1 | # 深浅拷贝 2 | 3 | ### 概念 4 | 5 | - 所谓浅拷贝就是值拷贝,一般的默认拷贝构造函数都是浅拷贝 6 | - 涉及到堆内存拷贝的是深拷贝,可通过自定义拷贝构造函数实现 7 | -------------------------------------------------------------------------------- /blog/cpp/md/菱形继承.md: -------------------------------------------------------------------------------- 1 | # 菱形继承 2 | 3 | 使用虚继承解决 4 | 5 | [详见](https://blog.csdn.net/tounaobun/article/details/8443228) 6 | -------------------------------------------------------------------------------- /blog/cpp/md/虚函数实现原理.md: -------------------------------------------------------------------------------- 1 | # 虚函数实现原理 2 | 3 | [虚函数实现详见](https://blog.csdn.net/jiangnanyouzi/article/details/3720807) 4 | 5 | - 使用虚表实现,子类虚表中使用自己的函数重写了父类的虚函数 6 | -------------------------------------------------------------------------------- /blog/cpp/md/面向对象三大特性七大原则.md: -------------------------------------------------------------------------------- 1 | # 面向对象三大特性和七大原则 2 | 3 | - 开闭原则 4 | 5 | 软件实体对扩展是开放的,对修改是关闭的,即在不修改一个软件实体的前提下去扩展功能 6 | 7 | - 单一职责原则 8 | 9 | 类的职责要单一,不能将太多指责放在一个类中 10 | 11 | - 依赖倒置原则 12 | - 高层模块不应该依赖底层模块,两者都应该依赖其抽象 13 | - 抽象不应该依赖其细节 14 | - 细节应该依赖抽象 15 | 16 | 要对抽象层编程,不能对具体类编程 17 | 18 | - 里氏替换原则 19 | 20 | 一个可以接受基类对象的地方必然可以接受一个子类对象 21 | 22 | - 接口隔离原则 23 | 24 | 使用多个专门的接口来替换一个统一的接口 25 | 26 | - 合成复用原则 27 | 28 | 在系统中应该多使用组合和聚合的关系,尽量少使用继承关系 29 | 30 | - 迪米特法则 31 | 32 | 一个软件实体对其他实体的引用越少越好,或者说两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,而通过引入一个第三者发生接交互 33 | -------------------------------------------------------------------------------- /blog/cpp/nowcoder/constructBinaryTree.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | struct TreeNode { 6 | int val; 7 | TreeNode* left; 8 | TreeNode* right; 9 | TreeNode(int x) 10 | :val(x), 11 | left(nullptr), 12 | right(nullptr) 13 | {} 14 | }; 15 | 16 | class Solution { 17 | public: 18 | TreeNode* constructBinaryTree(vector pre, vector in) { 19 | if (pre.size() != in.size()) { 20 | return nullptr; 21 | } 22 | } 23 | private: 24 | }; 25 | -------------------------------------------------------------------------------- /blog/cpp/nowcoder/printFromTailToFrontList.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | 7 | /* 8 | * 从尾到头打印链表 9 | * 10 | * 方案一:修改链表 11 | * 方案二:不修改链表 12 | * */ 13 | 14 | struct ListNode { 15 | int val_; 16 | ListNode* next_; 17 | ListNode(int x) 18 | :val_(x), 19 | next_(nullptr) 20 | {} 21 | }; 22 | 23 | class Solution { 24 | public: 25 | ListNode* reverseList(ListNode* pNode) { 26 | if (pNode == nullptr || pNode->next_ == nullptr) { 27 | return pNode; 28 | } 29 | ListNode* pPre = nullptr; 30 | ListNode* pCur = pNode; 31 | ListNode* pNext = nullptr; 32 | while (pCur) { 33 | pNext = pCur->next_; 34 | pCur->next_ = pPre; 35 | pPre = pCur; 36 | pCur = pNext; 37 | } 38 | return pPre; 39 | } 40 | vector printListFromTailTpHead(ListNode* pNode) { 41 | ListNode* Node = pNode; 42 | stack s; 43 | int count_ = 0; 44 | while (Node) { 45 | s.push(Node->val_); 46 | count_++; 47 | Node = Node->next_; 48 | } 49 | vector res(count_, 0); 50 | for (int i = 0; i < count_; i++) { 51 | res[i] = s.top(); 52 | s.pop(); 53 | } 54 | return res; 55 | } 56 | void printListFromTailToHead(ListNode* pNode, vector& res) { 57 | if (pNode != nullptr) { 58 | printListFromTailToHead(pNode->next_, res); 59 | res.push_back(pNode->val_); 60 | } 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /blog/doc/C++/C++11新特性.md: -------------------------------------------------------------------------------- 1 | ### nullptr 2 | 3 | nullptr 出现的目的是为了替代 NULL。在某种意义上来说,传统 C++ 会把 NULL、0 视为同一种东西,这取决于编译器如 4 | 何定义 NULL,有些编译器会将 NULL 定义为 `((void*)0)`,有些则会直接将其定义为 0。 5 | 6 | C++ 不允许直接将 void * 隐式转换到其他类型,但如果 NULL 被定义为 `((void*)0)`,那么当编译 7 | 8 | ```cpp 9 | char* ch = NULL; 10 | ``` 11 | 时,NULL 只好被定义为 0。而这依然会产生问题,将导致了 C++ 中重载特性会发生混乱,考虑: 12 | 13 | ```cpp 14 | void foo(char*); 15 | void foo(int); 16 | ``` 17 | 18 | 对于这两个函数来说,如果 NULL 又被定义为了 0 那么 foo(NULL); 这个语句将会 19 | 去调用 foo(int),从而导致代码违反直观。 20 | 21 | 为了解决这个问题,C++11 引入了 nullptr 关键字,专门用来区分空指针、0。nullptr 的类 22 | 型为 nullptr_t,能够隐式的转换为任何指针或成员指针的类型,也能和他们进行相等 23 | 或者不等的比较 24 | 25 | ```cpp 26 | #include 27 | 28 | void foo(char *); 29 | void foo(int); 30 | 31 | int main() 32 | { 33 | if (NULL == (void *)0) 34 | std::cout << "NULL == 0" << std::endl; 35 | else 36 | std::cout << "NULL != 0" << std::endl; 37 | foo(0); 38 | // foo(NULL); // 编译无法通过 39 | foo(nullptr); 40 | return 0; 41 | } 42 | 43 | void foo(char *ch) { 44 | std::cout << "call foo(char*)" << std::endl; 45 | } 46 | void foo(int i) { 47 | std::cout << "call foo(int)" << std::endl; 48 | } 49 | 50 | output: 51 | 52 | NULL == 0; 53 | call foo(int); 54 | call foo(char*); 55 | ``` 56 | 57 | ### Lambda 58 | 59 | 60 | -------------------------------------------------------------------------------- /blog/doc/C++/README.md: -------------------------------------------------------------------------------- 1 | # C/C++ 2 | 3 | - [C/C++ 语言基础](https://github.com/Apriluestc/2020/blob/master/blog/doc/C%2B%2B/%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80.md) 4 | - [多态篇](https://github.com/Apriluestc/2020/blob/master/blog/doc/C%2B%2B/%E5%A4%9A%E6%80%81%E7%AF%87.md) 5 | - STL 6 | - [内存管理](https://github.com/Apriluestc/2020/blob/master/blog/doc/C%2B%2B/%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E7%AF%87.md) 7 | - [C++ 11新特性](https://github.com/Apriluestc/2020/blob/master/blog/doc/C%2B%2B/C%2B%2B11%E6%96%B0%E7%89%B9%E6%80%A7.md) 8 | -------------------------------------------------------------------------------- /blog/doc/C++/STL.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apriluestc/2020/2a7b8e1db4ca98eb0ebc31ca27323a6466d98676/blog/doc/C++/STL.md -------------------------------------------------------------------------------- /blog/doc/C++/内存管理篇.md: -------------------------------------------------------------------------------- 1 | * [C++ 内存管理](#c++-内存管理) 2 | * [STL 中的内存分配策略](#stl-中的内存分配策略) 3 | * [Linux 进程分配内存的两种方式 brk 和 mmap](#linux-进程分配内存的两种方式-brk-和-mmap) 4 | * [Linux-malloc 底层实现原理](#linuxmalloc-底层实现原理) 5 | 6 | ### C++ 内存管理 7 | 8 | 在 C++ 中,内存分为:栈、堆、自由存储区、全局静态存储区、常量存储区 9 | 10 | 栈,在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结果结束也是在栈上进行释放 11 | 12 | 堆,就是那些由 malloc 等分配的内存块,用 free 来释放 13 | 14 | ### STL 中的内存分配策略 15 | 16 | ### Linux 进程分配内存的两种方式 brk 和 mmap 17 | 18 | **如何查看进程发生缺页中断的次数** 19 | 20 | 用`ps -o majflt,minflt -C program`命令查看 21 | 22 | 其中,majflt 表示 majflt fault,大错误 23 | 24 | **发生缺页中断后,执行了哪些操作** 25 | 26 | - 检查要访问的虚拟地址是否合法 27 | - 查找/分配一个物理页 28 | - 填充物理页内容(读取磁盘,或者直接置 0,或者啥也不干) 29 | - 建立映射关系(虚拟地址到物理地址) 30 | 31 | 如果第三步需要读取磁盘,那么这次缺页中断就是 majflt,否则就是 minflt 32 | 33 | **内存分配原理** 34 | 35 | - brk 是将数据段的最高地址指针 break 往高地址推(小于 128 K) 36 | - mmap 是在进程的虚拟地址空间中(堆和栈中间,成为文件映射区域的地方)找一块空闲的虚拟内存(大于 128 K) 37 | 38 | 这两种都是分配虚拟内存,没有分配物理内存,在第一次访问已分配的虚拟地 39 | 址空间时,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系 40 | 41 | ### Linux-malloc 底层实现原理 42 | 43 | **前言** 44 | 45 | 首先 malloc 肯定在堆上分配内存,下来我们讨论一下 46 | 47 | 在 32 位系统下,寻址空间 4G,Linux 系统下 0-3G 是用户模式,3-4G 是内核模式,而在用户 48 | 模式下又分为:代码段、数据段、.bss 段、堆、栈,其中 bss 段存放未初始化的全局变量和局部 49 | 静态变量,数据段存放已经初始化的全局变量和局部静态变量,局部变量存放在栈中 50 | 51 | 堆区位于 bss 段下方,Linux 维护一个 break 指针,这个指针指向堆空间的某个地址,从 52 | 堆起始到 break 之间的地址为映射好的,可以供进程使用,而 break 指针往上,是为映射的地址空 53 | 间,如果访问这段空间,程序则会报错,我们利用 malloc 就是从 break 往上进行分配的 54 | 55 | 进程面对的虚拟内存空间,只有按页映射到物理内存,才能真正使用,受物理内存容量限制, 56 | 整个堆虚拟内存空间不可能全部映射到实际的物理内存 57 | 58 | **malloc 描述** 59 | 60 | malloc 函数的实质是它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。 调 61 | 用 malloc() 函数时,它沿着连接表寻找一个大到足以满足用户请求所需要的内 62 | 存块。 然后,将该内存块一分为二(一块的大小与用户申请的大小相等,另一块的大小就是剩 63 | 下来的字节)。 接下来,将分配给用户的那块内存存储区域传给用户,并将剩下的 64 | 那块(如果有的话)返回到连接表上。 调用 free 函数时,它将用户释放的内存块连接 65 | 到空闲链表上。 到最后,空闲链会被切成很多的小内存片段,如果这时用户 66 | 申请一个大的内存片段, 那么空闲链表上可能没有可以满足用户要求的 67 | 片段了。于是,malloc() 函数请求延时,并开始在空闲链表上检查各内存片段,对它们 68 | 进行内存整理,将相邻的小空闲块合并成较大的内存块。 69 | 70 | **malloc 分配内存前的初始化** 71 | 72 | malloc_init 是初始化内存分配程序的函数,它完成三个目的,将分配程序标识为已 73 | 初始化;找到操作系统中最后一个有效的内存地址,然后建立起指向需要管理的内存 74 | 的指针,这里需要用到三个全局变量 75 | 76 | ```cpp 77 | int has_initialized = 0; // 初始化标记 78 | void* mananed_memory_start; // 管理内存的起始地址 79 | void* last_valid_address; // 操作系统中最后一个有效的地址 80 | ``` 81 | 82 | 被映射的内存边界常称为系统中断点或当前中断点,为了指出当前系统中断点,必须 83 | 使用 sbrk(0) 函数,sbrk 函数根据参数中给出的字节数移动当前系统中断点,然后 84 | 返回新的系统中断点,使用参数 0,只是返回当前中断点,Linux 通过 brk 和 sbrk 系 85 | 统调用操作 break 指针,brk 将 break 指针直接设定为某个地址,而 sbrk 将 break 从 86 | 当前位置移动参数所指定的增量,前者成功返回 0,否则 -1 并且 设置 error 为 ENOMEM,sbrk 成功 87 | 返回 break 移动之前所指向的地址,否则 `(void*)-1` 88 | 89 | 这里给出内存初始化 malloc_init 函数 90 | 91 | ```cpp 92 | void malloc_init() { 93 | // 用 sbrk 函数在操作系统中取得最后一个有效地址 94 | last_valid_address = sbrk(0); 95 | // 将最后一个有效地址作为管理内存的起始地址 96 | mananed_memory_start = last_valid_address; 97 | // 初始化成功标记 98 | has_initialized = 1; 99 | } 100 | ``` 101 | 102 | **内存块的获取** 103 | 104 | 我们所要申请的内存是由多个内存块构成的链表 105 | 106 | 内存块的大致结构:每个块由 meta 区和数据区组成,meta 区记录数据块的元信息(数据区大小、空闲标 107 | 标志位、指针等等),数据区是真实分配的内存区域,并且数据区的第一个字节地址即为 malloc 返回的地址 108 | 109 | ```cpp 110 | typedef struct s_block* t_block; 111 | struct s_block { 112 | size_t size; // 数据区大小 113 | t_block next; // 指向下个字节的指针 114 | int free; // 是否为空闲块 115 | int padding; // 填充 4 字节,保证 meta 块长度为 8 的倍数 116 | char data[1]; // 这是一个虚拟字段,标识数据区的第一个字节,长度不计入 meta 117 | }; 118 | ``` 119 | 120 | 现在,为了完全管理内存,我们需要能够追踪要分配和回收哪些内存,在对内存快进行了 free 调用 121 | 之后,我们需要做的是如何把他们标记为未被使用的内存块,并且在调用 malloc 时,我们要 122 | 将能够定位未被使用的内存块,因此,malloc 返回的每块内存的起始都要有如下这个结构 123 | 124 | ```cpp 125 | struct mem_control_block { 126 | int is_available; // 是否空闲 127 | int size; // 内存块大小 128 | }; 129 | ``` 130 | 131 | **寻找合适的 block** 132 | 133 | 上述工作准备完成,我们应该考虑如何选择合适的 block 134 | 135 | - 从头开始,使用第一个数据区大小大于 size 的块 136 | - 从头开始,遍历所有快,使用数据区大小大于 size 并且差值最小的块 137 | 138 | 两种方法各有千秋:前者具有更好的运行效率,后者具有更高的内存使用率 139 | 140 | find_block 从第一个块开始,查找第一个符合要求的 block 并返回其地址,如果找不到就返回 NULL,这 141 | 里在遍历时会更新一个叫 last 的指针,这个指针始终指向当前遍历的 block,这是为了如果找不到合适的块而开辟新的 block 使用的 142 | 143 | 如果现有的 block 都不满足 size 要求,则需要在链表的最后开辟一个新的 block,开辟新的 block 示意代码, 144 | 145 | ```cpp 146 | #define BLOCK_SIZE 24 // 由于存在虚拟的data字段,sizeof不能正确计算meta长度,这里手工设置 147 | 148 | t_block extend_heap(t_block last, size_t s) { 149 | t_block b; 150 | b = sbrk(0); 151 | if (sbrk(BLOCK_SIZE + s) == (void *)-1) 152 | return NULL; 153 | b->size = s; 154 | b->next = NULL; 155 | if (last) 156 | last->next = b; 157 | b->free = 0; 158 | return b; 159 | ``` 160 | -------------------------------------------------------------------------------- /blog/doc/C++/多态篇.md: -------------------------------------------------------------------------------- 1 | * [什么是多态](#什么是多态) 2 | * [虚函数是如何实现多态的](#虚函数是如何实现多态的) 3 | * [在 virtual 背后究竟发生了什么](#在-virtual-背后究竟发生了什么) 4 | * [类的多态性和函数的多态性](#类的多态性和函数的多态性) 5 | * [析构函数声明为虚函数的好处](#析构函数声明为虚函数的好处) 6 | * [构造函数为什么不能声明为虚函数](#构造函数为什么不能声明为虚函数) 7 | 8 | ### 什么是多态 9 | 10 | 多态是 C++ 编程时的一种特性,多态性即是对一个接口的多种实现,多态可以分为 11 | 静态多态和动态多态,所谓静态多态就是通过函数重载、模板、强制类型转换实现的, 12 | 静态多态是在函数编译阶段就决定调用的机制,即在编译链接截断将函数的入口地址给出,而 13 | 动态多态是在程序运行时刻才决定调用机制,而在 C++ 中动态多态是通过虚函数实现的 14 | 15 | 那么什么又是函数重载、模板、强制类型转换、虚函数呢 16 | 17 | - 【函数重载】所谓函数重载就是一个同名函数可以完成不同的功能,编译系统在编译时期通过**函数参数个数、参数类型不同**来区分该调用哪一个函数,其实现静态多态 18 | - 【模板】模板也是静多态的早绑定是因为模板生成代码的代码,模板特例化出函数之后不同的参数类型就形成了函数的重载,而函数的重载就是早绑定的静多态。因此 19 | 模板为静多态的实质就是函数重载为静多态 20 | - 【虚函数】在类中用 virtual 关键字声明的函数叫虚函数 21 | 22 | ### 虚函数是如何实现多态的 23 | 24 | ```cpp 25 | class Object { 26 | public: 27 | virtual void func(int x = 10) { 28 | cout << "print object x = " << x << endl; 29 | } 30 | }; 31 | 32 | class Test : public Object { 33 | private: 34 | void func(int y = 10) { 35 | cout << "print test y = " << y << endl; 36 | } 37 | }; 38 | 39 | int main() 40 | { 41 | Test t1; 42 | Object* p = &t1; 43 | p->func(); 44 | return 0; 45 | } 46 | 47 | // print Test y = 10 48 | 49 | class Object { 50 | public: 51 | void func(int x = 10) { 52 | cout << "print object = " << x << endl; 53 | } 54 | }; 55 | 56 | class Test : public Object { 57 | private: 58 | void func(int y = 20) { 59 | cout << "print test y = " << y << endl; 60 | } 61 | }; 62 | 63 | int main() 64 | { 65 | Test t1; 66 | Object* p = &t1; 67 | p->func(); 68 | return 0; 69 | } 70 | 71 | print object x = 10 72 | ``` 73 | 74 | - 通过上述代码和两种不同形式下的两种结果,我们能够知道当有 virtual 关键字修饰 75 | 函数时,函数是呈动态性的,p 是基类类型的指针,现在指向了派生类,但是在派生 76 | 类中有两部分,即派生类自己的部分和虚函数部分,此时的 p 是指向派生类中的基类部分, 77 | 所以程序在编译期间程序拿到的是基类中的 func 函数的形参 x = 10,但在运行期间由于 virtual 关键字 78 | 的存在形成了动多态,即动态绑定,在派生类中对基类的 func 函数进行了隐藏, 79 | 如果要调用Object中的fun()的加上作用域,即p->Object::fun() ),但是此时拿到的参数任然为在基类中 80 | 拿到的 10,所以打印的结果为:print Test y = 10。 81 | 82 | - 在不加 virtual 关键字的情况下,我们构造 Test 类的对象时,首先要调用 Object 类 83 | 的构造函数去构造 Object 类的对象,然后才调用Test类的构造函数完成自身部分的构造, 84 | 从而拼接出一个完整的 Object 对象。当我们将 Test 类的对象转换为 Object 类 85 | 型时,该对象就被认为是原对象整个内存模型的上半部分,也就是下图中的 “animal 的对象所占 86 | 内存”。那么当我们利用类型转换后的对象指针去调用它的方法时,当然也就是调用它所在的内存中的 87 | 方法,由于 p 是基类 Object 的指针,所以他调用的是基类中的 fun() 函数。 88 | 89 | ### 在 virtual 背后究竟发生了什么 90 | 91 | - 编- 译器在编译程序的时候,发现类中存在虚函数,此时编译器会为每个包含虚函数的类创建一个虚表(即虚表是属于类的,一个类的 92 | 所有对象共享一个虚表),虚表是一个一维数组,在这个数组中存放着每个虚函数的地址,对于上述程序,Object 和 Test 类都包含了一个虚函数 func,因此 93 | 编译器会为这两个类创建虚表,(即使派生类没有 virtual 函数,但是其棋类里面有,所以派生类类也就有了) 94 | 95 | - 那么如何定位虚表呢?编译器另外还为每个类的对象提供了一个虚表指针(一个对象包含一个),这个指针指向了对象所属类的虚表,在程序运行时, 96 | 根据对象的类型去初始化 vptr,从而让 vptr 正确的指向所属类的虚表,从而在调用虚函数时,就能找到正确的函数,对于上述程序,由于 p 实际指向的类型是 Test,因 97 | 此 vptr 指向的 Test 类的虚表,当调用 p->func() 时,根据虚表中函数地址找到的就是 Test 类的 func() 函数,而在虚表中存有的虚函数指针(即 vptr,每个虚 98 | 函数有一个),虚函数指针就指向对应的虚函数 99 | 100 | - 正是由于每个对象调用的虚函数都是通过虚表指针来进行索引的,也就决定了虚表指针的正确初始化是非常重要的,换句话说,在虚表指针没有正 101 | 确初始化之前,我们不能够去调用虚函数,那么虚表指针在什么时候进行初始化呢,或者它在什么地方进行初始化 102 | 103 | 在构造函数中进行虚表的创建和虚指针的初始化,在构造子类对象时,要先调用基类的构造函数,此时编译器 104 | 只看到了基类,并不知道后面是否还有继承者,它初始化基类对象的虚表指针,该虚表指针指向基类的虚表,当执行 105 | 派生类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表,对于程序来说,当 Test 类的对象构造完毕之后,其内部的虚表指针也就被初始化为指向 Test 类的虚表, 106 | 在类型转换后,调用 p->func(),由于 p 实际指向的是 Test 类的对象,该对象内部的虚表指针指向的是 Test 类的虚表,因此最终调用的是 Test 类 107 | 的 func 函数 108 | 109 | 对于虚函数的调用来说,每一个对象内部都有一个虚表指针,该虚表指针被初始化为本类的虚表,所以在程序中,不管你的对象类型如何转换,但 110 | 该对象内部的虚表指针是固定的,所以呢,才能实现动态的对象函数调用,这就是 C++ 多态性实现原理 111 | 112 | 113 | ### 类的多态性和函数的多态性 114 | 115 | 一般情况下,类的多态性是指用虚函数实现的,函数的多态性是用模板和函数重载实现的 116 | 117 | ### 析构函数声明为虚函数的好处 118 | 119 | 为了防止内存泄漏,基类的析构函数必须声明为虚函数 120 | 121 | - 那么,为什么将基类的析构函数声明为虚函数就可以防止内存泄漏? 122 | 123 | 如果没有将基类的析构函数声明为虚函数,在释放指向派生类对象的基类指针的时候,只会调用基类的析构函数,而派生类的析构函数不会调用,导致属于 124 | 派生类新添加的数据不能释放,从而导致内存泄漏 125 | 126 | 如果将析构函数声明为虚函数,在释放指向派生类对象的基类指针的时候,会调用派生类的析构函数,而派生类的析构函数会自动调用基类的析构函数,从而使放 127 | 所有内存,避免内存泄漏 128 | 129 | - 可是这里会有个问题,为什么将基类析构函数声明为虚函数,在释放指向派生类对象的基类指针时,会调用派生类的析构函数,难道派生类的析构函数重写了基类的析构函数吗?函数名不同啊 130 | 131 | 其实析构函数是一个特殊的函数,编译器在编译时,析构函数的名字同一命名为 destucter 132 | 133 | 所以只要将基类的析构函数声明为虚函数即可,不管派生类的析构函数前面是否有 virtual 关键字,都构成重写,这也就可以解释为什么将基类析构函数声明为虚函数,释放指向派生类对象的基类 134 | 指针时,会调用派生类的析构函数,因为虚表中的函数指针指向的是派生类的析构函数 135 | 136 | ### 构造函数为什么不能声明为虚函数 137 | 138 | 我们知道,在调用虚函数前,需要先访问虚表指针,得到虚表,然后在执行虚表中对应的虚函数,假设 139 | 现在将构造函数声明为虚函数,调用构造函数前,发现构造函数是一个虚函数,然后去访问虚表指针,可是虚表指针是在构造函数中初始化的,而目前构造函数 140 | 还未执行,也就是说,虚表指针还没有初始化,只是一个空值,理所当然,这便找不到构造函数的函数指针,因此无法完成构造任务,所以说,构造函数声明为虚函数是很愚蠢的 141 | -------------------------------------------------------------------------------- /blog/doc/C++/语言基础.md: -------------------------------------------------------------------------------- 1 | * [static 关键字](#static-关键字) 2 | * [头文件中的 #ifndef/#define/#endif 作用](#头文件中的-ifndefdefineendif-作用) 3 | * [#include 和 #include "file.h" 的区别](#include-fileh-和-include-fileh-的区别) 4 | * [extern 关键字和 extern "C" 的作用](#extern-关键字和-extern-c-的作用) 5 | * [struct 和 class 的区别](#struct-和-class-的区别) 6 | * [结构体内存对齐,为什么要内存对齐,结构体大小](#结构体内存对齐为什么要内存对齐结构体大小) 7 | * [const、#define、枚举常量的区别,相比有何优点](#const#define枚举常量的区别相比有何优点) 8 | * [Linux 内部提供了哪些调试宏](#linux-内部提供了哪些调试宏) 9 | * [如何判断一段代码是由 C 编译程序还是 C++ 程序编译](#如何判断一段代码是由-c-编译程序还是-c++-程序编译) 10 | * [什么是引用?声明一个引用应该注意什么?引用和指针有哪些区别?分别在什么场景用](#什么是引用声明一个引用应该注意什么引用和指针有哪些区别分别在什么场景用) 11 | * [C 和 C++ 的区别?C++ 和 java 的区别?](#c-和-c-的区别c-和-java-的区别) 12 | * [volatile 是干嘛的,有什么用](#volatile-是干嘛的有什么用) 13 | * [什么是智能指针?常用的智能指针有哪些?是怎么实现的](#什么是智能指针常用的智能指针有哪些是怎么实现的) 14 | * [四种强制类型转换](#四种强制类型转换) 15 | * [前置 ++ 和后置 ++ 的优缺点,哪个是线程安全的](#前置-++-和后置-++-的优缺点哪个是线程安全的) 16 | * [内联函数和宏函数的区别](#内联函数和宏函数的区别) 17 | * [explicit 是干嘛的](#explicit-是干嘛的) 18 | * [函数实现](#函数实现) 19 | 20 | ### static 关键字 21 | 22 | - static 用法 23 | 24 | - 在函数体内,一个被声明为静态的变量在这一函数调用过程中维持其值不变 25 | - 在模块内(函数体外),一个被声明为静态的变量可以被模块内的所有函数访问,但不能被模块外的其他函数访问,它是一个本地的全局变量 26 | - 在模块内,一个被声明为静态的函数只可以被本模块内的其他函数调用,那就是,这个函数被限制在声明它的模块的本地范围内使用 27 | - 类内的 static 成员变量属于整个类拥有,类内声明,类体外声明 28 | - 类内的 static 成员函数属于整个类拥有,不能包含 this 指针,只能调用 static 成员函数 29 | 30 | - static 全局变量与普通全局变量有什么区别?static 普通函数和普通函数有什么区别?static 局部变量和普通局部变量有什么区别? 31 | 32 | - static 全局变量和普通全局变量的区别:static 全局变量只初始化一次,防止在其他文件单元内被引用 33 | - static 局部变量和普通局部变量的区别:static 局部变量只被初始化一次,下一次依据上一次的结果值 34 | - static 函数与普通函数的区别:static 函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝 35 | 36 | ### 头文件中的 #ifndef/#define/#endif 作用 37 | 38 | 预处理,防止头文件被多次引用,包括 #pragma once 功效相同,但是前者跨平台 39 | 40 | ### #include 和 #include "file.h" 的区别 41 | 42 | 前者 #include 这种类型是从标准库中寻找头文件,而 #include "file.h" 是从当前工作路径中寻找头文件 43 | 44 | ### extern 关键字和 extern "C" 的作用 45 | 46 | **extern C** 47 | 48 | 首先 extern C 是 C/C++ 语言中表明函数和全局变量作用范围的关键字,该关键字告诉编译器,其声明函数和变量可以在本模块中 49 | 使用,通常,在模块的头文件中对本模块提供给其他模块引用的函数和全局变量以 extern 关键字 50 | 进行声明,被 extern C 修饰的变量和函数是按照 C 语言的方式进行编译和连接的 51 | 52 | **C 的函数是这样进行编译的** 53 | 54 | 作为一种面向对象的语言,C++ 支持函数重载,而过程式 C 语言则不支持,函数被 C++ 编译后在符号库的名字与 C 语言的不同 55 | 56 | ```cpp 57 | 假设某个函数原型是:void foo(int x, int y); 58 | 59 | 该函数被 C 编译器修饰后在符号库中的名字是 _foo,而 C++ 编译器则会产生像 _foo_int_int 之类的名字, 60 | 在 C++ 编译器编译后在符号库中的名字包括函数名及参数列表的参数类型以及下划线组合而成 61 | ``` 62 | 63 | - 一般形况下,在 C++ 中引用 C 语言中函数和变量,在包含 C 语言头文件时,需要如下处理: 64 | 65 | ```cpp 66 | extern "C" { 67 | #include 68 | } 69 | ``` 70 | 71 | - 在 C 语言的头文件中,对其外部函数只能指定为 extern 类型,C 语言不支持 extern "C" 的声明 72 | 73 | ### struct 和 class 的区别 74 | 75 | **C 中的 struct** 76 | 77 | 首先,在面向 C 过程中,这里的 struct 是一种数据类型,那么里面肯定不能定义函数,否则报错 78 | 79 | **而 C++ 面向过程认为,数据和数据操作是分开的,C++ 中的 struct 里面可以包含函数,这便是 C 和 C++ 中的 struct 的最大区别** 80 | 81 | **C++ 中的 struct 和 class** 82 | 83 | 首先 C++ 中的 struct 得到了扩充 84 | 85 | - struct 可以包含成员函数 86 | - struct 可以实现继承 87 | - struct 可以实现多态 88 | 89 | **区别** 90 | 91 | - 默认的继承访问权限,class 默认是 private 的,struct 默认是 public 的 92 | - 默认访问权限,struct 作为数据结构的实现体,他默认的数据访问控制是 public 的,而 class 作为对象的实现体,他默认的成员变量访问控制是 private 93 | - struct 和 class 在使用 {} 上的区别 94 | - class 和 struct 如果定义了构造函数的话,都不能使用大括号进行初始化 95 | - 如果没有定义构造函数的话,struct 可以使用 {} 进行初始化 96 | - 如果没有定义构造函数,且所有的成员变量都是 public 的,class 也可以使用 {} 进行初始化 97 | - 考虑到对 C 的兼容,struct 保留了下来 98 | - class 可用于定义模板参数,而 struct 不可以 99 | 100 | ### 结构体内存对齐,为什么要内存对齐,结构体大小 101 | 102 | **struct/class/union 内存对齐原则** 103 | 104 | - 数据成员对齐规则:结构或联合的数据成员,第一个数据放在偏移量为 0 的地方,以后每个数据成员存储的 105 | 起始位置要从该成员大小或者成员子成员的大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍处开始(比如 int 在 32 位为 4 字节对齐, 106 | 则要从 4 的整数倍地址开始存储),基本类型不包括 struct/union/class 107 | - 结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从内部,最宽基本类型成员的整数倍地址开始存储 108 | - 收尾工作:也就是 sizeof 之后的结果,必须是其内部最大成员变量的整数倍,不足要补齐,基本类型不包括 struct/class/union 109 | - sizeof(union),以结构里面 size 最大元素为 union 的size,因为在某一时刻,union 只有一个数据成员真正的存储在该地址 110 | 111 | ```cpp 112 | class Data { 113 | char c; 114 | int a; 115 | }; 116 | 117 | cout << sizeof(Data) << endl; 8 118 | 119 | class Data { 120 | char c; 121 | double a; 122 | }; 123 | 124 | cout << sizeof(Data) << endl; 16 125 | 126 | class Data { 127 | char c; 128 | int a; 129 | char d; 130 | }; 131 | 132 | cout << sizeof(Data) << endl; 12 133 | 134 | class Data { 135 | char c; 136 | char d; 137 | int a; 138 | }; 139 | 140 | cout << sizeof(Data) << endl; 8 141 | 142 | class BigData { 143 | char array[33]; 144 | }; 145 | 146 | class Data { 147 | BigData bd; 148 | int integer; 149 | double d; // 8 150 | }; 151 | 152 | cout << sizeof(Data) << endl; 33 + 4 + 8 = 48 153 | 154 | class BigData { 155 | char array[33]; 156 | }; 157 | 158 | class Data { 159 | BigData bd; 160 | double d; 161 | }; 162 | 163 | cout << sizeof(Data) << endl; 33 + 8 = 48 164 | ``` 165 | 166 | **为什么要进行内存对齐** 167 | 168 | 内存对齐问题,主要存在于 struct/union 等复合结构在内存中的分布情况,许多实际的计算机 169 | 系统,对基本类型数据在内存中存放的位置有限制,他们要求这些数据的首地址是某个数 M ,对于内存对齐 170 | 主要是为了提高程序的性能,数据结构特别是栈,应尽可能的在自然边界上对齐,经对齐后 CPU 的内存访问速度大大提升 171 | 172 | - 平台移植 173 | 174 | - 不是所有的硬件平台都能访问到任意地址上的任意数据 175 | - 某些硬件平台只能在某些地址处去某些特定类型的数据,否则抛出硬件异常 176 | 177 | - 性能原因 178 | 179 | - 数据结构应该尽可能的在自然边界上对齐 180 | - 原因在于,为了访问未对齐的内存,处理器需作两次内存访问,而对齐的内存只需要作依次访问 181 | 182 | ### const、#define、枚举常量的区别,相比有何优点 183 | 184 | - 对于 const 不能将其理解为常数 185 | 186 | - 在定义 const 变量的时候必须初始化 187 | - 指针可以是 const 指针,也可以是指向 const 对象的指针 188 | - 定义为 const 的形参,记载函数内部是不可以被修改的 189 | - 类的成员函数可以被声明为常成员函数,不能修改类的成员变量 190 | - 类的成员函数可以返回的是常对象,即被 const 声明的对象 191 | - 类的成员变量是常成员变量不能再声明的时候初始化,必须的构造函数的列表进行初始化 192 | 193 | ```cpp 194 | 例子: 195 | const int a; // a 是一个常整型数 196 | int const a; // a 是一个常整形数 197 | const int* a; // a 是一个指针,什么指针呢,指向常整型数,这个整型数不可修改,但是指针可修改 198 | int* const a; // a 是一个指针,指向整型数的常指针,指向的整型数可以被修改,但是指针不能修改 199 | int const* a const; // a 是一个指向常整型数的常指针,两者均不可被改变 200 | ``` 201 | 202 | - const 如何做到的只读? 203 | 204 | 这些在编译期间完成,对于内置类型,如 int,编译器使用常数将其替代 205 | 206 | ### Linux 内部提供了哪些调试宏 207 | 208 | - `__FILE__`:表示在哪个文件 209 | - `__LINE__`:表示在当前多少行 210 | - `__FUNCTION__`:表示在执行哪个函数 211 | 212 | ### 如何判断一段代码是由 C 编译程序还是 C++ 程序编译 213 | 214 | ```cpp 215 | #ifdef __cplusplus 216 | cout << "c++" << endl; 217 | #else 218 | cout << "C" << endl; 219 | #endif 220 | ``` 221 | 222 | ### 什么是引用?声明一个引用应该注意什么?引用和指针有哪些区别?分别在什么场景用 223 | 224 | ### C 和 C++ 的区别?C++ 和 java 的区别? 225 | 226 | - C/C++ 开发语言,C 语言更偏向硬件底层开发,C++ 语言是目前为止我们公认为语法最多的一门语言,C/C++ 在执行速度上要快很多,毕竟 227 | 其他类型语言大都是 C 开发的,更多应用于网络编程和嵌入式编程 228 | 229 | ### volatile 是干嘛的,有什么用 230 | 231 | - 访问寄存器比访问内存单元要快,编译器会优化减少内存的读取,可能会脏读数据,声明变量为 volatile,编译器 232 | 不会在对该变量的代码优化,仍然从内存中取值,稳定 233 | 234 | **volatile 关键字影响编译器的结果,用 volatile 声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不再被编译器优化,以免出错** 235 | 236 | - 使用实例(场景) 237 | - 并行设备的硬件寄存器(状态寄存器) 238 | - 一个中断服务子程序会访问到的非自动变量 239 | - 多线程任务中被几个任务共享的变量 240 | 241 | - 一个参数可以既是 const 又可以是 volatile 的吗? 242 | 243 | 可以,只读的状态寄存器,它是 volatile 因为它可能被意想不到的改变,它是 const 因为程序不应该试图修改它 244 | 245 | - 一个指针可以是 volatile 的吗 246 | 247 | 可以,尽管这不常见,当一个中断服务子程序修改一个指向 Buffer 的指针时 248 | 249 | ```cpp 250 | 例子: 251 | int square(volatile int* ptr) { 252 | return *ptr * *ptr; 253 | } 254 | 下面是答案: 255 | 这段代码有点变态。这段代码的目的是用来返指针 *ptr 指向值的平方,但是,由于 *ptr 指向一个 volatile 型参数,编译器将产生类似下面的代码: 256 | int square(volatile int* ptr){ 257 | int a, b; 258 | a = *ptr; 259 | b = *ptr; 260 | return a * b; 261 | 262 | } 263 | 由于 *ptr 的值可能被意想不到地该变,因此 a 和 b 可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下: 264 | long square(volatile int* ptr) { 265 | int a; 266 | a = *ptr; 267 | return a * a; 268 | 269 | } 270 | ``` 271 | 272 | ### 什么是智能指针?常用的智能指针有哪些?是怎么实现的 273 | ### 四种强制类型转换 274 | ### 前置 ++ 和后置 ++ 的优缺点,哪个是线程安全的 275 | 276 | - 前置 ++a 表示取 a 的地址,增加它的内容,然后把值放进寄存器中 277 | - 后置 a++ 表示取 a 的地址,把它的值放入寄存器,然后增加内存中 a 的值 278 | - 前置 ++ 返回的是引用,后置 ++ 返回的是 const,这意味着前置返回的是左值,后置返回的是右值 279 | - ++a 和 a++ 的线程安全分为几种情况: 280 | - 如果 a 是局部变量,那么是线程安全的,局部变量线程私有 281 | - 如果 a 是全局变量,那么是线程不安全的,因为如果是全局变量的话,同一进程中的不同线程都有可能访问到, 282 | 如果有大量的线程执行 a++ 操作,a 变量本身拷贝到每个线程,当同时有两个线程读取线程变量,加入此时是 1 的话 283 | 那么同时执行 a++ 操作,a 将变为 3,因而线程不安全 284 | 285 | ### 内联函数和宏函数的区别 286 | ### explicit 是干嘛的 287 | ### 函数实现 288 | 289 | **strcpy 函数实现** 290 | 291 | - 为什么要返回 `char*` 292 | - 加入考虑 dst 和 src 内存重叠应该怎么实现 293 | 294 | ```cpp 295 | char* strcpy(char* dst, const char* src) { 296 | assert((dst != nullptr) && (src != nullptr)); 297 | char* tmp = dst; 298 | while ((*dst++ = *src++) != '\0'); 299 | return tmp; 300 | } 301 | ``` 302 | 303 | **memcpy 函数实现** 304 | 305 | ```cpp 306 | char* memcpy(char* dst, const char* src, int count) { 307 | assert((dsr != nullptr) && (src != nullptr)); 308 | char* tmp = dst; 309 | // 高地址内存重叠 310 | if (dst >= src && dst <= src + count - 1) { 311 | dst = dst + count - 1; 312 | src = src + count - 1; 313 | while (count--) { 314 | *dst-- = *src--; 315 | } 316 | } else { 317 | while (count--) { 318 | *dst++ = *src++; 319 | } 320 | } 321 | return ret; 322 | } 323 | ``` 324 | 325 | **strlen 函数实现** 326 | 327 | ```cpp 328 | int strlen(const char* str) { 329 | assert(str != nullptr); 330 | int len = 0; 331 | while ((*str++) != '\0') { 332 | len++; 333 | } 334 | return len; 335 | } 336 | ``` 337 | -------------------------------------------------------------------------------- /blog/doc/MySQL/README.md: -------------------------------------------------------------------------------- 1 | # MySQL 2 | 3 | * [三范式](#三范式) 4 | * [Sql 优化](#sql-优化) 5 | * [索引及索引优化](#索引及索引优化) 6 | * [事务](#事务) 7 | * [存储引擎](#存储引擎) 8 | 9 | ### 三范式 10 | 11 | - 第一范式:列的原子性,列不可分割 12 | - 满足第一范式。除此之外,必须有主键,没有包含主键的列必须完全依赖于主键 13 | - 满足第二范式。非主键列必须直接依赖于主键,不能传递依赖 14 | 15 | ### Sql 优化 16 | 17 | - 只返回所需要的数据 18 | - 尽量不写 `select*` 的语句 19 | - 合理写 where 子句,不要写没有 where 的语句 20 | 21 | - 适当建立索引,但一下几点会进行全表扫描 22 | - 左模糊查询`%···` 23 | - 使用了不等操作符 24 | - Or 使用不当,or 两边必须都存在索引 25 | - Where 对字段进行表达式操作 26 | 27 | - 使用 join 代替子查询 28 | 29 | - 使用 union 代替手动创建临时表 30 | ### 索引及索引优化 31 | 32 | - 索引 33 | - B+ Tree 索引 34 | - 哈希索引:哈希索引能以O(1)的时间复杂度进行查找,但是失去了有序性,无法用于排序与分组,只支持精确查找,无法用于部分查找和范围查找,InnoDB 存储引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B+Tree 索引之上再创建一个哈希索引,这样就让 B+Tree 索引具有哈希索引的一些优点,比如快速的哈希查找 35 | - 全文索引:MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。查找条件使用 MATCH AGAINST,而不是普通的 WHERE。全文索引使用倒排索引实现,它记录着关键词到其所在文档的映射 36 | - 空间数据索引:MyISAM 存储引擎支持空间数据索引(R-Tree),可以用于地理数据存储。空间数据索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询 37 | 38 | ### 事务 39 | 40 | - 四大特征:原子性、一致性、隔离性、持久性 41 | - 一致性:指一个事务的执行前后数据库必须处于一致性状态,事务的执行结果必须是是数据库从一个一致性状态变到另一个一致性状态 42 | - 隔离性:一个事务的执行不能干扰其他事物,即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰 43 | 44 | - 如果不考虑事务的隔离性,会发生的问题 45 | - 脏读:指在一个事务处理过程中读取了另一个未提交事务中的数据 46 | - 不可重复度、幻读 47 | - 不可重复度和幻读都是读取了另一条已提交的事务,不可重复度重点在于 update 和 delete,而幻读重点在于 insert 48 | - 在可重复读中,该sql第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复 读了。但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会 发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。需要Serializable隔离级别 ,读用读锁,写用写锁,读锁和写锁互斥,这么做可以有效的避免幻读、不可重复读、脏读等问题,但会极大的降低数据库的并发能力 49 | 50 | - 四种隔离级别 51 | - 串行化 :可避免脏读、不可重复度、幻读的放生 52 | - 可重复度(默认):可避免脏读、不可重复度的发生 53 | - 读已提交:可避免脏读的发生 54 | - 读未提交:最低极别,任何情况都无法保证 55 | 56 | - 锁模式 57 | - 共享锁:(读取)操作创建的锁,其他用户可以并发读取数据,任何事物都不能获取数据上的排他锁,直到已经释放共享锁 58 | - 排他锁(X 锁):对数据 A 加上排他锁以后,则其他事物不能再对 A 添加任何锁,或者排他锁的事务既能读数据又能修改数据 59 | -更新锁(U 锁):更新锁可以防止通常形式的死锁,如果两个事物获得了资源上的共享锁,然后试图更新数据,则两个事物都需要转换共享锁为排他锁,并且每个事物都等待另一个事务释放共享锁,因此阻塞发生死锁 60 | - 若要避免上述问题,请使用更新锁(U 锁),一次只有一个事务可以获得资源的更新锁,如果事务修改资源,则更新锁转换为排他锁,否则转换为共享锁 61 | 62 | - 锁粒度 63 | - 行锁、页锁、表锁、数据库锁 64 | 65 | ### 存储引擎 66 | 67 | **MyISAM 和 Innodb 的区别** 68 | 69 | - InnoDB 支持事务,MyISAM 不支持,对于 InnoDB 每一条 SQL 语句都默认封装成事务提交,这样就会影响 70 | 速度,优化速度的方式是将多条 SQL 语句放在 begin 和 commit 之间,组成一个事务 71 | - InnoDB 支持外键,而 MyISAm 不支持 72 | - InnoDB 不支持全文索引,而 MyISAM 支持,查询效率上 MyISAM 要高 73 | 74 | 如果一个表修改要求比较高的事务处理,可以选择 InnoDB,这个数据库中可以将查询要求比较高的表选择 MyISAM 存储,如果 75 | 该数据库需要一个用于查询的临时表,甚至可以考虑选择 MEMORY 存储引擎 76 | 77 | **存储引擎原理** 78 | 79 | - MyISAM 中 B+ 树的数据结构存储内容是实际的地址值,它的索引和实际数据是分开的,只不过使用索引指向了实际数据,这种索引的模式成为非聚簇索引 80 | 81 | - InnoDB 中 B+ 树的数据结构中存储的都是实际的数据,这种索引又被称为聚簇索引 82 | 83 | **什么是索引呢** 84 | 85 | - 唯一索引:唯一索引不允许两行具有相同的索引值 86 | - 主键索引:为表定义一个主键将自动创建主键索引,主键索引是唯一索引的特殊类型,主键索引要求主键 87 | 中的每个值都是唯一的 88 | - 聚簇索引:表中各行的物理顺序与键值的逻辑顺序相同,每个表只能有一个 89 | - 非聚簇索引:非聚簇索引指定表的逻辑顺序,数据存储在一个位置,索引存储在另外一个位置,索引中 90 | 包含指向数据存储位置的指针,小于 249 91 | 92 | **MyISAM** 93 | 94 | 回到 MyISAM,其索引结构如下图所示,由于 MyISAM 的索引文件仅仅保存数据记录的地址。在 MyISAM 中,主索引和辅助索引(Secondary key 95 | )在结构上没有任何区别: 96 | 97 | MyISAM 中索引检索的算法为首先按照 B+Tree 搜索算法搜索索引,如果指定的 Key 存在,则取出其 data 域的值,然后以 data 域的值为地址,读取相应数据记录。 98 | 99 | 【MyISAM】![](https://github.com/Apriluestc/2020/blob/master/pics/myisam.png) 100 | 101 | **InnoDB** 102 | 103 | 对于 InnoDB 来说,表数据文件本身就是按 B+Tree 组织的一个索引结构,这棵树的叶节点 data 域保存了完整的数据记录。 104 | 105 | 由于 InnoDB 利用的数据库主键作为索引 Key,所以 InnoDB 数据表文件本身就是主索引,且因为 InnoDB 数据文件需要 106 | 按照主键聚集,所以使用 InnoDB 作为数据引擎的表需要有个主键,如果没有显式指定的话 MySQL 会尝试自动选择一个可以 107 | 唯一标识数据的列作为主键,如果无法找到,则会生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形。 108 | 109 | 【InnoDB】![](https://github.com/Apriluestc/2020/blob/master/pics/innodb.png) 110 | 111 | #### b 树和 b+ 树 112 | 113 | 【原理】https://github.com/Apriluestc/2020/blob/blog/master/doc/%E7%AE%97%E6%B3%95%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84.md#b-%E6%A0%91b-%E6%A0%91 114 | -------------------------------------------------------------------------------- /blog/doc/coding/HorseBoard.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | int main() 6 | { 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /blog/doc/shell/Linux下的内置命令.md: -------------------------------------------------------------------------------- 1 | 谈到 Linux 下的内置命令,说实话,在学习 Linux 之前没有听说过,更不知道内置命令和一般命令有什么不同 2 | 3 | 有时候我们会发现在 shell 脚本中执行的命令和与命令行上直接执行命令结果不同,这是为什么呢?当内置命令执行时,内置命令在磁盘上没有对应的可执行程序 4 | 所以 shell 不必再去磁盘中查找,这样就大大加快了执行素的,但是当内置命令在 shell 脚本中执行时,会产生子进程去执行内置命令,而 shell 作为父进程只需等待子进程执行后退出就好,也就是 5 | 说 shell 脚本中执行内置命令创建子进程与 bash 无关,那么会出现上述情况也是情有可原的 6 | 7 | 引用比较书面的说法:内置命令由 shell 程序识别并在 shell 程序中内部完成,通常在 Linux 系统加载到运行时 shell 就被加载并驻留到系统内存中,内部命令是写在 bash 源码里面,其执行速度比外部命令还快,因为解析内部命令 shell 不需要创建子进程 8 | 9 | ### Linux 下常见的内置命令 10 | 11 | - cd ..:回退到上级目录 12 | - help:显示所有内置命令列表,或者显示一个具体命令的用法 13 | - echo:用来显示一行文字,默认自动换行 14 | - printf:显示格式字符串,格式:printf “格式字符串” 参数 15 | - history:查看以往使用过的所有命令 16 | - ::空操作 17 | - . 或者 source:后面加上 ./shell 表示可执行,直接执行 18 | - exit:退出当前进程 19 | - set:列出所有的变量和函数的内容 20 | - read:从标准输入读取一行数据 21 | - time:打印设置命令行的 real user sys 时间 22 | - exec:该命令后面直接跟命令或者程序,执行后取代了原来的 shell 执行环境,也就是执行重定向生效 23 | - bg:把作业放到后台 24 | - bind:显示当前的套接字与函数的绑定情况 25 | - enable:启用或者禁用 shell 内置命令 26 | - dirs:显示当前记录用的目录 27 | - export:设置环境变量 28 | - kill:向由 PID 号指定的进程发送信号 29 | - test:检查文件类型,计算条件表达式 30 | - ulimit:显示或设置进程可用资源的最大限额 31 | - unset:取消指定变量的值或函数的定义 32 | - suspend:终止当前 shell 的运行 33 | - shift:将位置参数左移 N 次 34 | -------------------------------------------------------------------------------- /blog/doc/shell/README.md: -------------------------------------------------------------------------------- 1 | # shell 2 | 3 | - [shell的具体执行过程](https://github.com/Apriluestc/2020/blob/master/blog/doc/shell/shell%E7%9A%84%E5%85%B7%E4%BD%93%E6%89%A7%E8%A1%8C%E8%BF%87%E7%A8%8B.md) 4 | - [shell脚本程序](https://github.com/Apriluestc/2020/blob/master/blog/doc/shell/shell%E8%84%9A%E6%9C%AC%E7%A8%8B%E5%BA%8F.md) 5 | - [shell的内置命令](https://github.com/Apriluestc/2020/blob/master/blog/doc/shell/Linux%E4%B8%8B%E7%9A%84%E5%86%85%E7%BD%AE%E5%91%BD%E4%BB%A4.md) 6 | - [grep工具](https://github.com/Apriluestc/2020/blob/master/blog/doc/shell/grep%E5%B7%A5%E5%85%B7.md) 7 | - [sed工具](https://github.com/Apriluestc/2020/blob/master/blog/doc/shell/sed%E5%B7%A5%E5%85%B7.md) 8 | - [awk工具]() 9 | -------------------------------------------------------------------------------- /blog/doc/shell/grep工具.md: -------------------------------------------------------------------------------- 1 | grep 也被称为行过滤工具,它通常被用来进行模式匹配形式的查找,那么什么是模式 2 | 匹配呢?模式匹配是说不在限定要查找的具体 key 值,而是通过一定的条件来判断匹配(也就是需要正则表达式来匹配) 3 | 4 | ### 简单的添加参数查找匹配串 5 | 6 | - grep -i + 要匹配的内容 + 文件 // 不区分大小写搜索 7 | - grep -l + 要匹配的内容 + 文件 // 列出满足匹配条件的文件名 8 | - grep -w + 要匹配的内容 + 文件 // 列出不满足匹配条件的文件名 9 | - 10 | -------------------------------------------------------------------------------- /blog/doc/shell/sed工具.md: -------------------------------------------------------------------------------- 1 | ### 什么是 sed 2 | 3 | sed 工具也称为流式编辑器,他可以使用作为行过滤(顾名思义 sed 是按照行查找进行操作的),它 4 | 的工作原理可以描述为:把前一个程序的输出引入到 sed 的输入,经过一系列编辑命令转换为另外一种格式输出 5 | 6 | ### sed 常见命令 7 | 8 | ### 9 | -------------------------------------------------------------------------------- /blog/doc/shell/shell的具体执行过程.md: -------------------------------------------------------------------------------- 1 | ### shell 执行命令的步骤 2 | 3 | - 对于内置命令来说,整体的流程大概是这样的:识别命令、内置命令、bash 直接执行后退出,在命令行上输出结果 4 | - 对于非内置命令,首先识别命令、非内置命令、创建子进程、bash(作为所有进程的父进程)等待子进程退出 5 | 6 | ### shell 执行脚本的步骤 7 | 8 | - 对于内置命令来说,控制台 bash、父进程、等待子进程退出(当所有子进程运行时,作为父进程的 bash 会自动回退到后台) 9 | - 对于其他,控制台 fork 出子进程然后 exec 进程程序替换,顺序读取命令行上的参数,子进程执行命令,它的父进程等待 10 | 子进程的退出 11 | 12 | 在这里,有一个知识点必须知道,那就是 shell 的内置命令,内置命令一般直接由 bash 直接执行退出的,这是因为磁盘上没有对应的可执行程序 13 | 供系统执行程序替换,所以自然而然也不会创建子进程,而如果在 shell 脚本下是由 fork 出 14 | 的子进程去执行,于父进程的 bash 无关 15 | -------------------------------------------------------------------------------- /blog/doc/shell/shell脚本程序.md: -------------------------------------------------------------------------------- 1 | ### 求 1~100 所有数字之和 2 | 3 | ```bash 4 | #! /bin/bash 5 | i=0 6 | sum=0 7 | for ((; i<=100; ++i)) 8 | do 9 | let sum+=i 10 | done 11 | echo $sum 12 | ``` 13 | 14 | ### 使用递归的方法求出 1~100 的所有数字之和 15 | 16 | ```bash 17 | #! /bin/bash 18 | function add() { 19 | local tmp1=$1 20 | local tmp2=0 21 | if [$tmp1 -le 1];then 22 | sum=1 23 | else 24 | tmp=$(($tmp1-1)) 25 | add $tmp2 26 | sum=(($tmp1+$tmp2)) 27 | fi 28 | done 29 | echo $sum 30 | } 31 | ``` 32 | 33 | ### 编写一个进度条 34 | 35 | ```bash 36 | #! /bin/bash 37 | function proc_bar() { 38 | rate=0 39 | str=#" 40 | arr=("|" "/" "-" "\\") 41 | while [ $rate -le 100 ] 42 | do 43 | index=rate%4 44 | printf " [%-100s] [%d%%] [%s] \r" "$str" "$rate" "${arr[$index]}" 45 | str=${str}"#" 46 | let rate++ 47 | usleep 10000 48 | done 49 | printf "\n" 50 | } 51 | proc_bar 52 | ``` 53 | -------------------------------------------------------------------------------- /blog/doc/其他/README.md: -------------------------------------------------------------------------------- 1 | - [git 命令](https://github.com/Apriluestc/2020/blob/master/blog/doc/%E5%85%B6%E4%BB%96/git%E5%91%BD%E4%BB%A4.md) 2 | -------------------------------------------------------------------------------- /blog/doc/其他/git命令.md: -------------------------------------------------------------------------------- 1 | ### 常见 git 命令 2 | 3 | **通常我们使用以下三条命令进行上传代码** 4 | 5 | - git add filename // 添加文件 6 | - git commit -m "注释" // 添加注释 7 | - git push // 推送至远程仓库 8 | 9 | **配置用户名和密码** 10 | 11 | - git config --global user.name 12 | - git config --global user.email 13 | 14 | **合并分支、切换分支、删除分支** 15 | 16 | - git checkout master // 切换分支 17 | - git branch -d master // 删除本地分支 18 | - git push origin --delete master // 删除远程分治 19 | - git branch --merged // 合并分支 20 | -------------------------------------------------------------------------------- /blog/doc/操作系统/README.md: -------------------------------------------------------------------------------- 1 | # 操作系统 2 | -------------------------------------------------------------------------------- /blog/doc/操作系统/操作系统基础概念.md: -------------------------------------------------------------------------------- 1 | # 操作系统基础概念 2 | 3 | ### 进程 4 | 5 | 每个正在系统上运行的程序都可以看作是一个进程,每个进程包含一个到多个线程,进程也可能是程序或者部分程序的动态执行 6 | 7 | 进程是对运行程序的封装,是系统进行资源调度和分配的基本单位 8 | 9 | ### 线程 10 | 11 | 线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行,也可以把它理解为代码运行的上下文,所以线程基本上是轻量级的进程,它负责在单个程序里 12 | 执行多个任务,通常由操作系统负责多个线程的调度和执行 13 | 14 | 线程是进程的子任务,是 CPU 调度的基本单位,用于保证程序的实时性,实现进程内部的并发,线程是操作系统可识别的最小执行和调度单位,每个线程都独自占用一个虚拟处理器: 15 | 独自的寄存器组,指令计数器和处理器状态,每个线程完成不同任务,但共享同一地址空间(也就是同样的动态内存、映射文件、目标代码等) 16 | 17 | ### 同步与异步 18 | 19 | 同步与异步是针对应用程序与内核的交互而言的,同步过程中进程触发 I/O 操作并等待或者轮询的去查看 I/O 操作是否完成,异步过程中进程触发 I/O 操作以后,直接返回,做自己的事情,I/O 交给内核来处理,完成后内核通知进程 I/O 完成 20 | 21 | ### 阻塞与非阻塞 22 | 23 | 应用进程 I/O 操作时,如果数据未准备好,如果请求立即返回就是非阻塞,不立即返回就是阻塞,简单来说就是做一件事如果不能立即返回,需要等待,就是阻塞,否则就是非阻塞 24 | 25 | ### 并发 26 | 27 | 并发:指宏观上看起来两个程序同时运行,比如说在单核 CPU 上的多任务,但是从微观上看两个程序的指令是交织着运行的,即互相穿插,这种病发并不能提高计算机性能,智能提高效率 28 | 29 | ### 并行 30 | 31 | 指严格物理意义上的同时运行,比如多核 CPU,两个程序分别运行在两个内核上,两者之间互不影响,单个周期内每个程序都运行了自己的指令, 32 | 也就是运行了两条指令,这样说起来并行确实提高了计算机效率 33 | 34 | ### 死锁 35 | 36 | 死锁指两个或两个以上进程在执行过程中,因争夺资源而造成相互等待的现象 37 | 38 | ### 软连接 39 | 40 | 为了解决文件共享问题,Linux 引入软连接和硬链接,除了为 Linux 解决文件共享问题,还隐藏了路径、增加安全性及节省存储的好处,若一个 Inode 号对应多个文件名则为硬链接, 41 | 即硬链接就是同一个文件使用了不同的别名,若文件用户数据块中存储的是另一个问及那的路径名指向,则该文件是软连接,软连接是一个普通文件,有自己独立的 Inode 42 | 43 | ### 大小端 44 | 45 | 大端是指低字节存储在高地址位,小端则相反,一般根据联合体判断该系统大小端,因为联合体变量总是从低地址到高地址存储 46 | 47 | ### 内存溢出 48 | 49 | 指程序申请内存时,没有足够的内存供申请者使用。内存溢出就是你要的内存空间超过了系统实际分配给你的空间,此时系统相当于没法满足你的需求,就会报内存溢出的错误 50 | 51 | ### 内存泄漏 52 | 53 | 内存泄漏是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存 54 | 的控制,因而造成了内存的浪费。 55 | -------------------------------------------------------------------------------- /blog/doc/算法数据结构/README.md: -------------------------------------------------------------------------------- 1 | # 数据结构 2 | 3 | - [基本概念](https://github.com/Apriluestc/2020/blob/master/blog/doc/%E7%AE%97%E6%B3%95%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84.md) 4 | 5 | **【包括栈、队列、堆、散列表(包括散列冲突及解决方式)、二叉树、二叉查找树、平衡二叉树、b 树 b+ 树、红黑树(性质及 L、R、LR、RL 旋转)】** 6 | 7 | # 算法 8 | 9 | - [基础算法](https://github.com/Apriluestc/2020/blob/master/blog/doc/%E7%AE%97%E6%B3%95%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/%E7%AE%97%E6%B3%95.md) 10 | 11 | **【包括常见排序算法及优化(时间复杂度空间复杂度分析、查找算法、背包问题、并发无锁队列实现方式)】** 12 | 13 | - [高级算法](https://github.com/Apriluestc/2020/blob/master/blog/doc/%E7%AE%97%E6%B3%95%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/%E9%AB%98%E7%BA%A7%E7%AE%97%E6%B3%95.md) 14 | 15 | **【包括动态规划、多重背包问题、回溯算法、贪心算法及分治算法、子序列 & LCS】** 16 | -------------------------------------------------------------------------------- /blog/doc/算法数据结构/数据结构.md: -------------------------------------------------------------------------------- 1 | # 数据结构 2 | 3 | ## 基本概念 4 | 5 | * [堆](#堆) 6 | * [栈](#栈) 7 | * [队列](#队列) 8 | * [散列表](#散列表) 9 | * [二叉树](#二叉树) 10 | * [二叉查找树](#二叉查找树) 11 | * [完全二叉树](#完全二叉树) 12 | * [平衡二叉树](#平衡二叉树) 13 | * [b 树、b+ 树](#b-树b-树) 14 | * [红黑树](#红黑树) 15 | 16 | #### 堆 17 | 18 |         19 | 堆也被称为优先队列,队列中允许的操作是先进先出(FIFO),在队尾插入元素,在队头删除元素,而堆也是一样,在堆低插入元素,在堆顶删除元 20 | 素,二叉树的衍生,有着最小堆最大堆两个概念之分,将根节点大的称为大顶堆,将根节点小的称为小顶堆。常见的堆有二叉堆,斐波那契堆等。有一 21 | 个比较常见的堆排序算法正是基于此数据结构而来 22 | 23 | #### 栈 24 | 25 |         26 | 栈作为一个先进后出的数据结构,它是一种运算受限的线性表,其限制是允许在表的一端进行插入和删除运算,这一端也被称为栈顶,相对地,把另 27 | 一端称为栈底。向一个栈插入新元素又称进栈,它是把新元素放在栈顶元素上面,使之成为新的栈顶元素,从一个栈删除元素又称为出栈,它是把栈 28 | 顶元素删除掉,使其相邻元素成为新的栈顶元素 29 | 30 | #### 队列 31 | 32 |         33 | 队列采用先进先出(FIFO),新元素总是被插入到链表的尾部,而读取的时候总是从链表的头部开始读取。每次读取一个新元素,就会释放一个元 34 | 素,所谓动态创建,动态释放,因而不存在溢出等问题,由于链表由结构体间接而成,遍历也方便 35 | 36 | #### 散列表 37 | 38 |         39 | 哈希表也叫散列表,是根据关键码值而直接进行数据访问的数据结构,也就是说他通过把关键码值映射到表中一个位置来访问记录,以加快查找速度,这 40 | 个映射函数叫做散列函数,存放记录的数组叫做散列表,若结构中存在关键码值 x 的记录,则必定在 hash(x) 的存储位置上,由此,不需比较便可直 41 | 接取得所查记录,成这个对应关系为 hash 函数,即散列函数 42 | 43 | - 散列地址冲突 44 | 45 |         46 | 哈希地址冲突,散列函数是一个压缩映像函数,关键码值比散列表地址集合大得多,因此有可能出现经过散列函数的计算,把不同的关键码值映射到同一 47 | 个散列地址上,这便是散列地址冲突 48 | 49 | - 散列地址冲突解决办法 50 | 51 | - 链地址法,这种基本思想是,将所有的哈希地址为 i 的元素构成一个同义词链表,并将链表的头指针存在哈希地址为 i 的单 52 | 元中,因而查找、删除、插入主要在同义词链表中进行。 53 | 54 | - 开地址法(线性探测再散列),这种基本思想是,当关键码值 key 的哈希地址 H0 = hash(key) 出现冲突时,以 H0 为基础,重新产生一个新的哈希地址 H1 依次类推,直 55 | 到不冲突为止,并将相应元素放入其中,应用这种处理哈希冲突的办法,将出现堆积现象,(散列地址不同的节点争夺同一个后继散列地址而产生的现象称为堆积),这将 56 | 造成不是同义词的节点也将处于同一个探测序列中,从而增加了探测序列的长度,即增加了查找、删除、插入时间 57 | 58 | #### 二叉树 59 | 60 |         61 | 二叉树是每个节点最多有连个节点的树结构,有五种基本状态,一般情况下二叉树的第 i 层节点数目最多为 2^(i-1),深度为 k 的二叉树至多有 2^k - 1 个节点,包含 62 | n 个节点的二叉树高度至少为 log2(n+1),在任意一棵二叉树中,度为 0 的节点数 = 度为 2 的节点数的 2 倍 + 1 63 | 64 | - 二叉树的遍历 65 | 66 | - 前序遍历 67 | ```cpp 68 | void Preorder(TreeNode* root) { 69 | if (root) { 70 | cout << root->val << endl; 71 | Preorder(root->left); 72 | Preorder(root->right); 73 | } 74 | } 75 | 76 | // 二叉树遍历前序(非递归) 77 | void Preorder(root) { 78 | // 定义栈结构 79 | stack s; 80 | 81 | // root 非空,且栈不为空 82 | while (root != nullptr || !s.empty()) { 83 | 84 | // root 不空 85 | if (root) { 86 | 87 | // root 入栈 88 | s.push(root); 89 | 90 | // 打印 root->val 91 | cout << root->val << endl; 92 | root = root->left; 93 | } else { 94 | root = s.top(); 95 | s.pop(); 96 | if (root) { 97 | root = root->right; 98 | } 99 | } 100 | } 101 | } 102 | ``` 103 | - 中序遍历 104 | ```cpp 105 | void Inorder(TreeNode* root) { 106 | if (root) { 107 | Inorder(root->left); 108 | cout << root->val << endl; 109 | Inorder(root->right); 110 | } 111 | } 112 | 113 | // 中序遍历,左根右 114 | void Inorder(TreeNode* root) { 115 | stack s; 116 | while (root) { 117 | s.push(root); 118 | root = root->left; 119 | } 120 | if (!s.empty) { 121 | root = s.top(); 122 | cout << root->val << endl; 123 | s.pop(); 124 | if (root) { 125 | root = root->right; 126 | } 127 | } 128 | } 129 | ``` 130 | - 后序遍历 131 | ```cpp 132 | void Postorder(TreeNode* root) { 133 | if (root) { 134 | Postorder(root->left); 135 | Postorder(root->right); 136 | cout << root->val << endl; 137 | } 138 | } 139 | void Postorder(TreeNode* root) { 140 | TreeNode* cur; 141 | TreeNode* pre = nullptr; 142 | stack s; 143 | if (root) { 144 | s.push(root); 145 | } 146 | while (!s.empty()) { 147 | cur = s.top(); 148 | if (cur->left == nullptr && cur->right == nullptr || pre && (cur->left == pre || cur->right == pre)) { 149 | cout << cur->val << endl; 150 | pre = cur; 151 | s.pop() 152 | } else { 153 | if (cur->right) { 154 | s.push(cur->right); 155 | } 156 | if (cur->left) { 157 | s.push(cur->left); 158 | } 159 | } 160 | } 161 | } 162 | ``` 163 | 164 | - 层次遍历 165 | ```cpp 166 | void Levelorder(TreeNode* root) { 167 | queue q; 168 | if (root) { 169 | q.push(root); 170 | } 171 | int cur = 1; 172 | while (!q.empty()) { 173 | int count = 0; 174 | while (cur) { 175 | TreeNode* tmp = q.front(); 176 | cout << tmp->val << endl; 177 | cur--; 178 | q.pop(); 179 | if (tmp->left) { 180 | q.push(tmp_left); 181 | count++; 182 | } 183 | if (tmp->right) { 184 | q.push(tmp->right) { 185 | count++; 186 | } 187 | } 188 | } 189 | cout << endl; 190 | cur = count; 191 | } 192 | } 193 | ``` 194 | 195 | #### 二叉查找树 196 | 197 |         198 | 二叉查找树,又称为二叉搜索树,即中序遍历有序,在二叉查找树中,若任意节点的左子树不空,则左子树上所有的节点值均小于根节点的值,若右子树不空,则右子树所 199 | 有节点值均大于根节点值,二叉查找树中不存在值相同的节点 200 | 201 | #### 完全二叉树 202 | 203 |         204 | 完全二叉树,在一棵二叉树中,只有最下两层节点的度可以小于 2,并且最下一层的叶节点集中在靠左的若干位置,完全二叉树与满二叉树没有必然的联系 205 | 206 | #### 平衡二叉树 207 | 208 |         209 | 平衡二叉树是一种二叉查找树,其中每一个节点的左子树和右子树的高度差至多等于 1,即在二叉查找树的基础上加上了平衡因子 210 | 211 | # 高级数据结构 212 | 213 | #### b 树、b+ 树 214 | 215 |         216 | 首先 b 树属于多叉树又名平衡多路查找树 217 | 218 | - 其规则是 219 | 220 | - 所有节点关键字是按递增顺序排列,并遵循左小右大规则 221 | - 子节点数,非叶子节点的子节点数大于 1 且小于 M,且 M > 2,空树除外 222 | - 关键字数,枝接点的关键字数量大于等于 ceil(m/2)-1 个且小于等于 M-1 个(注:ceil() 是个朝正无穷方向取整的函数,比如 ceil(1,1) = 2) 223 | - 叶节点的指针为空且叶节点具有相同的深度 224 | 225 | - 而对于 b+ 树,是 b 树的一个升级版,相对于 b 树而言 b+ 树更充分利用了节点的空间,让查询速度更加稳定,其速度完全接近二分查找 226 | 227 | #### 红黑树 228 | 229 |         230 | RB-Tree,又称为红黑树,它是一种特殊的二叉查找树,红黑树的每个节点上都有存储位标识该节点的颜色,非红即黑 231 | 232 | - 红黑树的特性 233 | 234 | - 每个节点不是红色便是黑色 235 | - 根节点是黑色 236 | - 每个叶子节点是黑色 237 | - 如果一个节点是红色的,则它的子节点必须是黑色的 238 | - 从一个节点到该节点的子孙节点的所有路径上包括相同数目的黑节点 239 | 240 | - 红黑树的基本操作 241 | 242 | - 红黑树的基本操作是添加、删除,在对红黑树进行添加删除之后,都会用到旋转方法(之所以要进行旋转,是因为要在插入删除后还必须满足红黑树的性质) 243 | - 左旋 244 | 245 | ![左旋](https://github.com/Apriluestc/2020/blob/master/pics/%E5%B7%A6%E6%97%8B.png) 246 | 247 | - 右旋 248 | 249 | ![右旋](https://github.com/Apriluestc/2020/blob/master/pics/%E5%8F%B3%E6%97%8B.png) 250 | 251 | 由上述可以看出,左旋之于右旋而对称,对于 x 左旋转->将 x 变成一个左节点,右旋转->将 x 变成一个右节点 252 | 253 | - 红黑树的插入 254 | 255 | - 第一步,将红黑树当作一棵二叉查找树,将节点插入 256 | - 第二步,将插入的节点找着色为红色 257 | - 通过一些列旋转和着色操作,使之重新称为一棵红黑树 258 | - 红黑树的插入可分为这么几种情况 259 | - 黑父节点(新插入节点的父节点是黑色),不会影响其平衡性 260 | - 红父节点红叔节点(新插入节点的父节点是红色其叔叔节点为红色),不会影响其平衡性,只需修改颜色即可 261 | - 红父节点黑叔节点(新插入节点的父节点是红色其叔叔节点为黑色),这种情况下需要旋转 262 | - case:1,R 旋转 263 | - case:2,LR 旋转 264 | - case:3,RL 旋转 265 | - case:4,L 旋转 266 | 267 | **红黑树如何进行插入删除?** 268 | 269 | - 插入 270 | 271 | - 如果父节点为黑色,直接插入不处理 272 | - 如果父节点为红色,叔叔节点为红色,则父节点和叔叔节点变为黑色,祖先节点变为红色,将节点操作转换为祖先节点 273 | - 如果当前节点为父亲节点的右节点,则以父亲结点为中心左旋操作 274 | - 如果当前节点为父亲节点的左节点,则父亲节点变为黑色,祖先节点变为红色,以祖先节点为中心右旋操作 275 | 276 | - 删除 277 | 278 | - 先按照排序二叉树的方法,删除当前节点,如果需要转移即转移到下一个节点 279 | - 当前节点,必定为这样的情况:没有左子树。 280 | - 删除为红色节点,不需要处理,直接按照删除二叉树节点一样 281 | - 如果兄弟节点为黑色,兄弟节点的两个子节点为黑色,则将兄弟节点变为红色,将着色转移到父亲节点 282 | - 如果兄弟节点为红色,将兄弟节点设为黑色,父亲结点设为红色节点,对父亲结点进行左旋操作 283 | - 如果兄弟节点为黑色,左孩子为红色,右孩子为黑色,对兄弟节点进行右旋操作 284 | - 如果兄弟节点为黑色,右孩子为红色,则将父亲节点的颜色赋值给兄弟节点,将父亲节点设置为黑色,将兄弟节点的右孩子设为黑色,对父亲节点进行左旋 285 | 286 | **红黑树、b 树、b+ 树区别** 287 | 288 | 红黑树的深度比较大,而B+和B-的深度则相对要小一些,而B+较B-则将数据都保存在叶子节点,同时通过链表的形式将他们连接在一起。 289 | -------------------------------------------------------------------------------- /blog/doc/算法数据结构/算法.md: -------------------------------------------------------------------------------- 1 | # 算法 2 | 3 | * [排序算法](#排序算法) 4 | * [查找算法](#查找算法) 5 | * [背包问题](#背包问题) 6 | * [无锁队列](#无锁队列) 7 | 8 | #### 排序算法 9 | 10 | - 堆排序【nlogn、nlogn、1、不稳定】 11 | 12 | 简单来说,堆排序是一种基于二叉堆结构的排序算法,所谓二叉堆,我们通过完全二叉树来对比 13 | ,只不过相比较完全二叉树而言,二叉堆的所有父节点值都大于或小于它的孩子节点 14 | 15 | 堆排序方法,把大顶堆堆顶的最大数取出,将剩余的堆继续使用向下调整为大顶堆,再次取出堆顶元素,这个过程持 16 | 续到堆里面只有一个元素为止 17 | 18 | - 快速排序【nlogn、n^2、logn、不稳定】 19 | 20 | 在区间中随机挑选一个元素作为基准,将小于基准的元素放到基准之前,大于基准的元素放到基准之后, 21 | **选择一个数作为基准,将比基准小的元素交换到前面,比基准大的元素交换到基准后面,对左右区间重复,直到各区间只有一个数** 22 | 23 | 【快排流程:以 6 为基准】 24 | 25 | | 初始数组 | 6 | 1 | 2 | 7 | 9 | 3 | 4 | 5 | 10 | 8 | 26 | | :------: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | 27 | | 第一次 | 6 | 1 | 2 | 5 | 9 | 3 | 4 | 7 | 10 | 8 | 28 | | 第二次 | 6 | 1 | 2 | 5 | 4 | 3 | 9 | 7 | 10 | 8 | 29 | | 第三次 | 1 | 2 | 5 | 4 | 3 | 6 | 9 | 7 | 10 | 8 | 30 | 31 | 一种简单的快排实现 32 | 33 | ```cpp 34 | void quicksort(vector v, int low, int high) { 35 | if (low >= high) { 36 | return ; 37 | } 38 | // 高低位下标 39 | int left = low, right = high; 40 | // 基准 41 | int base = v[left]; 42 | while (left < right) { 43 | while (left < last && v[right] >= base) { 44 | right--; 45 | } 46 | // 将比基准小的移到前面 47 | if (left < right) { 48 | v[left++] = v[right]; 49 | } 50 | while (left < right && v[left] <= base) { 51 | left++; 52 | } 53 | // 将比基准大的移到后面 54 | if (left < right) { 55 | v[right--] = v[left]; 56 | } 57 | v[left] = base; 58 | quick(v, low, left - 1); 59 | quick(v, left + 1, high); 60 | } 61 | } 62 | ``` 63 | 64 | **【快排的优化】**,固定基准随机化、三数取中、当排序长度达到一定长度后用插入排序、分割一次后将相同数据不做处理、使用并行或者多线程、使用尾递归优化【即将 logn 降解为更低的时间复杂度】 65 | 66 | **【基准随机化算法】**,使用随机数生成函数在一定区间生成一个随机数,范围为 [left, right],并用此随机数为下标对应的元素 v[rand] 作为基准,并与最后一个元素 v[right] 交换,然后进行与选取最后一个元素 67 | 最为基准,继续快排 68 | 69 | 优点;基准随机化,将恶劣情况几率减小,随机化快排能达到 nlogn 的时间复杂度 70 | 71 | ```cpp 72 | int random(int left, int right) { 73 | return rand() % (right - left + 1) + left; 74 | } 75 | ``` 76 | 77 | **【三数取中】**,虽然随机化基准的引入能达到 nlogn,但是最坏情况还是 n^2,为了缓解最坏时间,引入三数取中 78 | 79 | 思路,假设数组被排序的范围为 left 和 right,center =(left+right) / 2,对 a[left]、a[right] 和 a[center] 进 80 | 行适当排序,取中值为中轴,将最小者放 a[left],最大者放在 a[right],把中轴元与 a[right-1]交换,并 81 | 在分割阶段将 i 和 j 初始化为 left+1 和 right-2。然后使用双向描述法,进行快排 82 | 83 | 例子:注意观察 0 4 6 84 | 85 | | 初始数组 | 6 | 1 | 8 | 9 | 4 | 3 | 5 | 2 | 7 | 0 | 86 | | :----------------------: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | 87 | | 选取三个中间数 | 6 | 1 | 8 | 9 | 4 | 3 | 5 | 2 | 7 | 0 | 88 | | 对这三个数排序 | 0 | 1 | 8 | 9 | 4 | 3 | 5 | 3 | 7 | 6 | 89 | | 最后中轴与v[right-1]交换 | 0 | 1 | 8 | 9 | 7 | 3 | 5 | 2 | 4 | 6 | 90 | 91 | 优点: 92 | 93 | 1.将三元素中最小者被分到 a[left]、最大者分到 a[right]是正确的,因为当快排一趟后,比中轴小的放到左边,而比中轴大的放到右边,这样就在分割的时候把它们分到了 94 | 正确的位置,减少了一次比较和交换。 95 | 96 | 2.在前面所说的所有算法中,都有双向扫描时的越界问题,而使用这个分割策略则可以解决这个问题。因为 i 向右扫描时,必然会遇到不小于中轴的数 a[right-1],而 j 在向左扫描时,必然会遇 97 | 到不大于中轴的数 a[left],这样,a[right-1] 和 a[left] 提供了一个警戒标记,所以不需要检查下标越界的问题。 98 | 99 | 分析:最佳的划分是将待排序的序列分成等长的子序列,最佳的状态我们可以使用序列的中间的值,也就是第 N/2 个数。可是,这很难算出来,并 100 | 且会明显减慢快速排序的速度。这样的中值的估计可以通过随机选取三个元素并用它们的中值作为枢纽元而得到。事实上,随机性并没有多大的帮助,因此 101 | 一般的做法是使用左端、右端和中心位置上的三个元素的中值作为枢纽元。显然使用三数中值分割法消除了预排序输入的不好情形,并且减少快排大约14%的 102 | 比较次数 103 | 104 | **【待排序列达到一定长度后用插排,一般长度为 10】**,在数组中如果有相等元素,那么可以减少不必要的划分 105 | 106 | **【尾递归优化】**,优点:如果待排序的序列划分极端不平衡,递归的深度将趋近于 n,而栈的大小是很有限 107 | 的,每次递归调用都会耗费一定的栈空间,函数的参数越多,每次递归耗费的空间也越多。优化后,可以缩减堆栈深度,由原来的 O(n) 缩减为 O(logn),将会 108 | 提高性能 109 | 110 | 【尾递归减少递归深度】 111 | 112 | - 冒泡排序【n^2、n^2、1、稳定】 113 | 114 | 冒泡排序思路,遍历序列,相邻两元素比较且交换 115 | 116 | ```cpp 117 | void bubblesort(vector v, int size) { 118 | bool flags = false; 119 | // 剩余一个元素无需比较且交换 120 | for (int i = 0; i < size - 1 && !flags; ++i) { 121 | flags = true; 122 | for (int j = 0; j < size - 1 - i; ++j) { 123 | if (v[j] > v[j+1]) { 124 | swap(v[j], v[j+1]); 125 | } 126 | } 127 | } 128 | } 129 | ``` 130 | 131 | - 插入排序【n^2、n^2、1、稳定】 132 | 133 | 插入排序思路,**八无序区的第一个元素插入到有序区的合适位置,从第一个元素开始,该元素可以认为已经有序,取出下一个元素,在已排序的元素序列中向后扫描,如 134 | 果该元素大于新元素,将该元素移到下一个位置,重复上述,直到找到已排序的元素小于等于新元素的位置,将新元素插入到该位置后,继续上述步骤** 135 | 136 | ```cpp 137 | void insertsort(vector v, int size) { 138 | 139 | // 无序区的第一个元素 140 | for (int i = 1; i < size; ++i) { 141 | int temp = v[i]; 142 | } 143 | 144 | // 插入到合适位置 145 | for (int j = i - 1; j >= 0; --j) { 146 | if (v[j+1] > temp) { 147 | v[j+1] = v[j]; 148 | v[j] = temp; 149 | } else 150 | break; 151 | } 152 | } 153 | ``` 154 | 155 | - 桶排序【n、n、m、稳定】 156 | 157 | 桶排序思路,将值为 i 的元素放入 i 号桶,最后依次将元素倒出来,**设置一个定量数目的数组当作空桶,遍历序列,并且把元素一个一个放入对应的桶中, 158 | 对每个不是空的桶子进行排序,从不是空的桶子里把元素放回原来的序列中** 159 | 160 | - 归并排序【nlogn、nlogn、n、稳定】 161 | 162 | #### 查找算法 163 | 164 | - 二分查找【logn、1、有序】 165 | 166 | - 顺序查找【n、1、无序或有序】 167 | 168 | - 二叉查找树查找【logn】 169 | 170 | - 哈希查找【1,m、无序或有序】 171 | 172 | #### 背包问题 173 | 174 | 【01 背包无价值问题】、【01 背包有价值问题】、【01 背包方案数问题】、【完全背包】、【多重背包】、【完全背包方案数问题】 175 | 176 | [详见背包问题](https://github.com/Apriluestc/2020/tree/master/2020%E5%B1%8A%E7%A7%8B%E6%8B%9B%E7%AC%94%E8%AF%95%E7%BC%96%E7%A8%8B%E9%A2%98%E9%9B%86) 177 | 178 | #### 无锁队列 179 | 180 | ##### CAS 实现无锁队列 181 | 182 | - 链表实现 183 | 184 | ```cpp 185 | EnQueue(x) { 186 | // 准备新加入的结点数据 187 | q = newrecord(); 188 | q->value = x; 189 | q->next = nullptr; 190 | do { 191 | p = tail; // 取链表尾指针的快照 192 | } while (CAS(p->next, nullptr, q) != TRUE); // 如果没有把结点链上,再试 193 | CAS(tail, p, q); // 置尾结点 194 | 195 | } 196 | 197 | EnQueue(x) { 198 | q = newrecord(); 199 | q->value = x; 200 | q->next = NULL; 201 | p = tail; 202 | oldp = p; 203 | do { 204 | while (p->next != nullptr) 205 | p = p->next; 206 | } while (CAS(p.next, NULL, q) != true); 207 | CAS(tail, oldp, q); 208 | } 209 | 210 | DeQueue() { 211 | do { 212 | p = head; 213 | if (p->next == nullptr) { 214 | return ERR_EMPTY_QUEUE; 215 | } while (CAS(head, p, p->next) != true); 216 | return p->next->value; 217 | } 218 | } 219 | ``` 220 | 221 | ![Add img](https://github.com/Apriluestc/2020/blob/master/pics/cas.png) 222 | 223 | C++ 11 中的 STL 中的 atomic 类函数 224 | 225 | ```cpp 226 | template 227 | boolatomic_compare_exchange_weak( std::atomic* obj, T* expected, T desired ); 228 | template 229 | boolatomic_compare_exchange_weak( volatilestd::atomic* obj, T* expected, T desired ); 230 | ``` 231 | 232 | - CAS 的 ABA 问题 233 | 234 | 所谓 ABA 就是: 235 | 236 | - 进程 P1 在共享变量中读到值为 A 237 | - P1 被抢占了,进程 P2 执行 238 | - P2 把共享变量里的值从 A 改成了 B,再改回到 A,此时被 P1 抢占。 239 | - P1 回来看到共享变量里的值没有被改变,于是继续执行。 240 | 241 | **虽然 P1 以为变量值没有改变,继续执行了,但是这个会引发一些潜在的问题。ABA 问题最容易发生在 lock free 的算 242 | 法中的,CAS 首当其冲,因为 CAS 判断的是指针的地址。如果这个地址被重用了呢,问题就很大了** 243 | 244 | 举个栗子: 245 | 246 | ```html 247 | 你拿着一个装满钱的手提箱在飞机场,此时过来了一个火辣性感的美女,然后她很暖昧地挑逗着你,并趁你不注意的时候,把 248 | 用一个一模一样的手提箱和你那装满钱的箱子调了个包,然后就离开了,你看到你的手提箱还在那,于是就提着 249 | 手提箱去赶飞机去了。 250 | ``` 251 | 252 | 这便是 ABA 问题 253 | 254 | - 解决 ABA 问题 255 | 256 | 使用 double-CAS (双保险的 CAS),例如,在 32 位系统上,我们要检查 64 位的内容 257 | 258 | 1)一次用CAS检查双倍长度的值,前半部是指针,后半部分是一个计数器。 259 | 260 | 2)只有这两个都一样,才算通过检查,要吧赋新的值。并把计数器累加 1 261 | 262 | ```html 263 | 这样一来,ABA 发生时,虽然值一样,但是计数器就不一样(但是在32位的系统上,这个计数器会溢出回来又从 1 开始的,这还是会有 ABA 的问题) 264 | 265 | 当然,我们这个队列的问题就是不想让那个内存重用,这样明确的业务问题比较好解决,论文《Implementing Lock-Free Queues》给出一这么一个方法——使用结点内存引用计数refcnt! 266 | 267 | SafeRead(q) { 268 | loop: 269 | p = q->next; 270 | if (p == nullptr) { 271 | return p; 272 | } 273 | Fetch&Add(p->refcnt, 1); 274 | if (p == q->next) { 275 | return p; 276 | } else { 277 | Release(p); 278 | } 279 | goto loop; 280 | } 281 | 282 | 其中的 Fetch & Add 和 Release 分是是加引用计数和减引用计数,都是原子操作,这样就可以阻止内存被回收了。 283 | 284 | ``` 285 | 286 | - 数组实现 287 | 288 | 使用数组来实现队列是很常见的方法,因为没有内存的分部和释放,一切都会变得简单,实现的思路如下: 289 | 290 | 1)数组队列应该是一个 ring buffer 形式的数组(环形数组) 291 | 292 | 2)数组的元素应该有三个可能的值:HEAD,TAIL,EMPTY(当然,还有实际的数据) 293 | 294 | 3)数组一开始全部初始化成 EMPTY,有两个相邻的元素要初始化成 HEAD 和 TAIL,这代表空队列。 295 | 296 | 4)EnQueue 操作。假设数据x要入队列,定位 TAIL 的位置,使用 double-CAS 方法把(TAIL, EMPTY) 更新成 (x, TAIL)。需要注意,如果找不到(TAIL, EMPTY),则说明队列满了。 297 | 298 | 5)DeQueue 操作。定位 HEAD 的位置,把(HEAD, x)更新成(EMPTY, HEAD),并把x返回。同样需要注意,如果 x 是 TAIL,则说明队列为空。 299 | 300 | 算法的一个关键是——如何定位 HEAD 或 TAIL? 301 | 302 | 1)我们可以声明两个计数器,一个用来计数 EnQueue 的次数,一个用来计数 DeQueue 的次数。 303 | 304 | 2)这两个计算器使用使用 Fetch&ADD 来进行原子累加,在 EnQueue 或 DeQueue 完成的时候累加就好了。 305 | 306 | 3)累加后求个模什么的就可以知道 TAIL 和 HEAD 的位置了。 307 | 308 | **图示:** 309 | 310 | ![Add img](https://github.com/Apriluestc/2020/blob/master/pics/queue.png) 311 | -------------------------------------------------------------------------------- /blog/doc/算法数据结构/高级算法.md: -------------------------------------------------------------------------------- 1 | # 高级算法 2 | 3 | #### 动态规划 4 | 5 | #### 分治算法 6 | 7 | #### 回溯算法 8 | 9 | #### 贪心算法 10 | -------------------------------------------------------------------------------- /blog/doc/计算机网络/README.md: -------------------------------------------------------------------------------- 1 | # 计算机网络 2 | -------------------------------------------------------------------------------- /blog/file: -------------------------------------------------------------------------------- 1 | /********************************************************** 2 | * Author : Apriluestc 3 | * Email : 13669186256@163.com 4 | * Last modified : 2019-07-01 20:41 5 | * Filename : file 6 | * Description : 7 | * *******************************************************/ 8 | 常见文件安装 9 | 10 | ########################################################################### 11 | gcc@ 12 | 13 | $ sudo yum install gcc -y 14 | 15 | centos7-gcc 版本升级至5.3 16 | 17 | $ yum install centos-release-scl 18 | $ yum install devtoolset-4-toolchain 19 | $ scl enable devtoolset-4 bash 20 | 21 | 将 scl enable devtoolset-4 bash 添加至 /etc/profile 22 | 23 | $ gcc --version 24 | ########################################################################### 25 | 26 | ########################################################################## 27 | target : gdb、 git、make、cmake、gcc-c++、zip & unzip、tree 28 | sudo install -y $(target) 29 | 30 | ########################################################################### 31 | vim 配置 32 | Centos 7 33 | 34 | $ git clone https://github.com/chxuan/vimplus.git ~/.vimplus 35 | $ cd ~/.vimplus 36 | $ ./install.sh 37 | 38 | MacOS: 39 | 40 | $ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 41 | $ git clone https://github.com/chxuan/vimplus.git ~/.vimplus 42 | $ cd ~/.vimplus 43 | $ ./install.sh 44 | 45 | ubuntu: 46 | 47 | $ git clone https://github.com/chxuan/vimplus.git ~/.vimplus 48 | $ cd ~/.vimplus 49 | $ ./install.sh 50 | 51 | vim 中添加作者属性:版权、邮箱、文件名、描述 52 | 53 | map ms:call AddAuthor()'s 54 | 55 | function AddAuthor() 56 | let n=1 57 | while n < 5 58 | let line = getline(n) 59 | if line =~'^\s*\*\s*\S*Last\s*modified\s*:\s*\S*.*$' 60 | call UpdateTitle() 61 | return 62 | endif 63 | let n = n + 1 64 | endwhile 65 | call AddTitle() 66 | endfunction 67 | 68 | function UpdateTitle() 69 | normal m' 70 | execute '/* Last modified\s*:/s@:.*$@\=strftime(": %Y-%m-%d %H:%M")@' 71 | normal " 72 | normal mk 73 | execute '/* Filename\s*:/s@:.*$@\=": ".expand("%:t")@' 74 | execute "noh" 75 | normal 'k 76 | echohl WarningMsg | echo "Successful in updating the copy right." | echohl None 77 | endfunction 78 | 79 | function AddTitle() 80 | call append(0,"/**********************************************************") 81 | call append(1," * Author : Apriluestc") 82 | call append(2," * Email : 13669186256@163.com") 83 | call append(3," * Last modified : ".strftime("%Y-%m-%d %H:%M")) 84 | call append(4," * Filename : ".expand("%:t")) 85 | call append(5," * Description : ") 86 | call append(6," * *******************************************************/") 87 | echohl WarningMsg | echo "Successful in adding the copyright." | echohl None 88 | endfunction 89 | 90 | ########################################################################## 91 | 为普通用户添加 sudo 权限 92 | 安装 sudo : 93 | $ yum install -y sudo 94 | $ vim /etc/sudoers 95 | 96 | 找到类似: 97 | 98 | sudoers 99 | 100 | root ALL=(ALL) ALL 101 | user ALL=(ALL) ALL 102 | 103 | 注意在修改 sudoers 文件时,为其添加修改权限 104 | 105 | $ chmod u+w /etc/sudoers 106 | $ chmod u-w /etc/sudoers 107 | 108 | sudo 用户免密码 109 | 110 | $ user ALL=(ALL)NOPASSWD:ALL 111 | ############################################################################### 112 | 113 | 防火墙开启端口号@ 114 | 115 | $ sudo firewall-cmd --zone=public --add-port=80/tcp --permanent 116 | $ sudo firewall-cmd --reload 117 | 118 | 查看防火墙状态: $ sudo systemctl status firewalld 119 | 开启防火墙: $ sudo systemctl start firewalld 120 | 关闭防火墙: $ sudo systemctl stop firewalld 121 | 查看当前防火墙状态: $ sudo firewall-cmd --state 122 | 重启防火墙: $ sudo firewall-cmd --reload 123 | 禁止开机自启动: $ sudo systemctl disable firewalld.service 124 | 永久开启端口号: $ sudo firewall-cmd --zone=public --add-port=80/tcp --permanent 125 | 查看已经开启的端口号: $ sudo firewall-cmd --list-ports 126 | ########################################################################### 127 | 128 | ########################################################################### 129 | 新添加第三方库 130 | 131 | $ cp -r json.h /home/shiny/usr/include/ 132 | $ cp libjsoncpp.so /home/shiny/lib/ 133 | $ sudo vim /etc/profile 134 | $ source /etc/profile 135 | $ cd /usr/include/ 136 | $ sudo ln -s /home/shiny/usr/include/jsoncpp jsoncpp 137 | $ cd /usr/lib/ 138 | $ cp -r /home/shiny/lib/libjsoncpp.so . 139 | 140 | 详见:/etc/profile 141 | 142 | ···· 143 | ok 完成 144 | ########################################################################### 145 | 常见第三方库安装 146 | 147 | jsoncpp、curl、ncurses、ctemplate、httplib 148 | 149 | ########################################################################### 150 | 151 | redis 服务开启 152 | 153 | 先修改配置文件:redis.conf 中的 daemonize no 改为 yes 即支持后台启动(默认后台运行) 154 | sudo redis-server /etc/redis.conf 155 | 开启客户端首先要保证服务端先运行:sudo redis-server /etc/redis.conf 156 | ########################################################################## 157 | C++ 使用 hredis 库 158 | 159 | 1、连接数据库 160 | 1.1 无超时时间,阻塞:redisContext* redisConnect(const char* ip, int port); 161 | 1.2 设置超时时间,阻塞:redisContext* redisConnectWithTimeout(const char* ip, int port, const struct timeval tv); 162 | 1.3 非阻塞立刻返回,也就是无超时:redisContext* redisConnectNoBlock(const char* ip, int port); 163 | 2、执行命令 164 | 2.1 返回值 165 | /* This is the reply object returned by redisCommand() */ 166 | typedef struct redisReply { 167 | int type; /* REDIS_REPLY_* */ 168 | PORT_LONGLONG integer; /* The integer when type is REDIS_REPLY_INTEGER */ 169 | int len; /* Length of string */ 170 | char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */ 171 | size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ 172 | struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */ 173 | } redisReply; 174 | 2.2 返回值类型 175 | #define REDIS_REPLY_STRING 1 //字符串 176 | #define REDIS_REPLY_ARRAY 2 //数组,例如mget返回值 177 | #define REDIS_REPLY_INTEGER 3 //数字类型 178 | #define REDIS_REPLY_NIL 4 //空 179 | #define REDIS_REPLY_STATUS 5 //状态,例如set成功返回的‘OK’ 180 | #define REDIS_REPLY_ERROR 6 //执行失败 181 | 182 | 3、基本命令 183 | 3.1 set 184 | redisReply *r1 = (redisReply*)redisCommand(c, "set k v"); 185 | 3.2 get 186 | redisReply *r2 = (redisReply*)redisCommand(c, "get k"); 187 | 4、存取二进制 188 | char sz[] = { 0,1,2,3,0 }; 189 | redisReply *r3 = (redisReply*)redisCommand(c, "set kb %b",sz,sizeof(sz)); 190 | 存二进制的时候,使用%b,后面需要对应两个参数,指针和长度 191 | redisReply *r4 = (redisReply*)redisCommand(c, "get kb"); 192 | 5、存取多个值 193 | 存取多个值,可以通过拼接字符串 194 | redisReply *r5 = (redisReply*)redisCommand(c, "mset k1 v1 k2 v2"); 195 | 取多个值:redisReply *r6 = (redisReply*)redisCommand(c, "mget k1 k2"); 196 | 197 | ######################################################################### 198 | -------------------------------------------------------------------------------- /blog/fork.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 如何参与贡献? 3 | --- 4 | 5 | >欢迎大家参与讨论,更欢迎大家多多的回馈社区,来开始共享吧! 6 | 7 | ## 贡献流程 8 | 9 | ### 第一步:Fork [https://github.com/Apriluestc/2020](https://github.com/Apriluestc/2020) 项目 10 | 11 | 1. 访问 [https://github.com/Apriluestc/2020](https://github.com/Apriluestc/2020) 12 | 2. 点击 Fork 按钮(顶部右侧),建立基于此的分支; 13 | 14 | ### 第二步:克隆分支到你本地 15 | 16 | ```sh 17 | # Define a local working directory: 18 | $ working_dir=/github.com/Apriluestc 19 | $ user={your github profile name} 20 | $ mkdir -p $working_dir 21 | $ cd $working_dir 22 | $ git clone https://github.com/$user/2020.git 23 | $ cd $working_dir/2020 24 | $ git remote -v 25 | $ git remote add upstream https://github.com/Apriluestc/2020.git 26 | $ git remote -v 27 | # Never push to upstream master since you do not have write access. 28 | $ git remote set-url --push upstream no_push 29 | $ git remote -v 30 | ````` 31 | 32 | ### 第三步:分支 33 | 34 | 让你本地 master 分支保持最新: 35 | 36 | ```sh 37 | $ cd $working_dir/2020 38 | $ git fetch upstream 39 | $ git checkout master 40 | $ git rebase upstream/master 41 | ````` 42 | 43 | 从 master 开分支: 44 | 45 | ```sh 46 | $ git checkout -b myfeature 47 | ````` 48 | 49 | ### 第四步:开发 50 | 51 | #### 编辑代码 52 | 53 | 你现在能在 `myfeature` 分支上编辑代码/文档了。 54 | 55 | 请按照以下一些格式编写: 56 | 57 | `.h`文件命名:`2018-08-03-username-xxx.h`,放到`coding`目录下 58 | `图片`文件的图片:放到 `img` 目录下; 59 | `.md`文件放在`md`目录下 60 | 文件内容:需要明确标题、日期等基本信息; 61 | 62 | ### 第五步:保持分支同步 63 | 64 | ```sh 65 | # While on your myfeature branch. 66 | $ git fetch upstream 67 | $ git rebase upstream/master 68 | ````` 69 | 70 | ### 第六步:提交 71 | 72 | 提交你的修改: 73 | 74 | ```sh 75 | $ git commit 76 | ````` 77 | 78 | ### 第七步:推送 79 | 80 | 准备好审核: 81 | 82 | ```sh 83 | git push -f origin myfeature 84 | ````` 85 | 86 | ### 第八步:创建一个 pull request 87 | 88 | 1. 访问你 fork 的 [https://github.com/${user}/2020](https://github.com/Apriluestc/2020) (替换 $user); 89 | 2. 点击 myfeature 分支旁边的 Compare & pull request 按钮; 90 | 91 | ### 第九步:获取代码审核 92 | 93 | 一旦你的 Pull Request 被打开,它将被分配给审核者。 94 | 这些审核人员将进行彻底的代码审查,寻找正确性,错误,改进机会,文档和评论以及样式。 95 | 96 | ## 如果操作过程中出现问题,可删掉工作目录,或者 CSDN 搜索“如何在`github`加入开源项目” 97 | -------------------------------------------------------------------------------- /blog/g.md: -------------------------------------------------------------------------------- 1 | # 项目 2 | 3 | ### 建立连接 4 | 5 | - 连接的建立比较简单,server端通过socket(),bind(),listen(),并使用 epoll ET 模式监听 listenfd 的 6 | 读请求,当 TCP 连接完成 3 次握手后,会触发 listenfd 的读事件,应用程序调用 accept(),会检查已完 7 | 成的连接队列,如果队列里有连接,就返回这个连接,出错或连接为空时返回 -1。此时,已经可以进行正 8 | 常的读写操作了。 当然,因为是 ET 模式,accept() 要一直循环到就绪连接为空 9 | 10 | ### 处理请求 11 | 12 | - 服务启动,服务器连接建立完成后,开启一个主线程、多个工作线程,主线程负责响应 client 的连接请求,并建立 13 | 连接,在连接建立完成后使用特定的方式将 fd 分配给某个工作线程 14 | - 当主线程把新连接分配给了某个特定的 subreactor 之后,该线程可能正阻塞在多路选择器当中, 15 | - 如何得知新连接的到来呢,这里使用了 eventfd 进行异步唤醒,线程会从 epoll_wait 中醒来,从 epoll 的就绪队列中拿到活跃事件进行处理 16 | - 在 epoll 触发模式上使用了 ET,它与 LT 在 IO 处理上有着很大的区别,ET 模式要比 LT 模式复杂许多,它对用户提出更高的要求,即 17 | 每次读,必须读到不能再读,每次写,写到不能再写的时候 18 | - [ET 和 LT 的区别](https://blog.csdn.net/YMY_mine/article/details/81212731) 19 | 20 | ### 其他 21 | 22 | - [webd](https://github.com/Apriluestc/2020/blob/master/%E9%A1%B9%E7%9B%AE/web.md) 23 | - [对象内存分布](https://github.com/Apriluestc/2020/blob/master/cpp/md/C%2B%2B%E5%AF%B9%E8%B1%A1%E5%86%85%E5%AD%98%E5%88%86%E5%B8%83.md) 24 | - [const](https://github.com/Apriluestc/2020/blob/master/cpp/md/const%E5%85%B3%E9%94%AE%E5%AD%97.md) 25 | - [static](https://github.com/Apriluestc/2020/blob/master/cpp/md/static%E5%85%B3%E9%94%AE%E5%AD%97.md) 26 | - [map & set](https://github.com/Apriluestc/2020/blob/master/cpp/md/map%E5%92%8Cset%E8%AF%A6%E8%A7%A3.md) 27 | - [struct & class](https://github.com/Apriluestc/2020/blob/master/cpp/md/struct%E5%92%8Cclass.md) 28 | - [智能指针](https://github.com/Apriluestc/2020/blob/master/cpp/md/%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88.md) 29 | - [菱形继承](https://github.com/Apriluestc/2020/blob/master/cpp/md/%E8%8F%B1%E5%BD%A2%E7%BB%A7%E6%89%BF.md) 30 | - [虚函数实现原理](https://github.com/Apriluestc/2020/blob/master/cpp/md/%E8%99%9A%E5%87%BD%E6%95%B0%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86.md) 31 | - [面向对象三大特性七大原则](https://github.com/Apriluestc/2020/blob/master/cpp/md/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E4%B8%89%E5%A4%A7%E7%89%B9%E6%80%A7%E4%B8%83%E5%A4%A7%E5%8E%9F%E5%88%99.md) 32 | - [vector](https://github.com/Apriluestc/2020/blob/master/cpp/md/vector%E8%AF%A6%E8%A7%A3.md) 33 | - [shared_ptr](https://github.com/Apriluestc/2020/blob/master/cpp/md/SharedPtr.h) 34 | - [string](https://github.com/Apriluestc/2020/blob/master/cpp/md/String.h) 35 | - [list](https://github.com/Apriluestc/2020/blob/master/cpp/md/list%E8%AF%A6%E8%A7%A3.md) 36 | -------------------------------------------------------------------------------- /blog/issue/贡献名单.md: -------------------------------------------------------------------------------- 1 | # 贡献名单 2 | -------------------------------------------------------------------------------- /blog/km/LevelOrder.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | struct TreeNode { 5 | int val; 6 | TreeNode* left; 7 | TreeNode* right; 8 | TreeNode(int rhx = 0) 9 | :val(rhx), 10 | left(nullptr), 11 | right(nullptr) 12 | {} 13 | }; 14 | 15 | class Solution { 16 | public: 17 | void LevelOrder(TreeNode* root) { 18 | if (root == nullptr) { 19 | return; 20 | } 21 | std::queue q; 22 | // 根节点入队 23 | q.push(root); 24 | while (!q.empty()) { 25 | TreeNode* tmp = root; 26 | // 输出根节点 27 | std::cout << tmp->val; 28 | q.pop(); 29 | if (tmp->left) { 30 | q.push(tmp->left); 31 | } 32 | if (tmp->right) { 33 | q.push(tmp->right); 34 | } 35 | } 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /blog/km/SharePtr.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class SharedPtr { 4 | public: 5 | SharedPtr(int* ptr) { 6 | ptr_ = ptr; 7 | map_.insert(std::make_pair(ptr_, 1)); 8 | } 9 | SharedPtr(const SharedPtr& rhx) { 10 | ptr_ = rhx.ptr_; 11 | map_[ptr_]++; 12 | } 13 | SharedPtr& operator=(const SharedPtr& rhx) { 14 | if (ptr_ == rhx.ptr_) { 15 | return *this; 16 | } 17 | if (--map_[ptr_] <= 0 && ptr_ != nullptr) { 18 | delete ptr_; 19 | ptr_ = nullptr; 20 | map_.erase(ptr_); 21 | } 22 | ptr_ = rhx.ptr_; 23 | map_[ptr_]++; 24 | return *this; 25 | } 26 | int& operator*() { 27 | return *ptr_; 28 | } 29 | int* operator->() { 30 | return ptr_; 31 | } 32 | ~SharedPtr() { 33 | if (--map_[ptr_] <= 0 && ptr_ != nullptr) { 34 | delete ptr_; 35 | ptr_ = nullptr; 36 | map_.erase(ptr_); 37 | } 38 | } 39 | static std::map map_; 40 | private: 41 | int* ptr_; 42 | }; 43 | 44 | std::map SharedPtr::map_; 45 | -------------------------------------------------------------------------------- /blog/km/Singleton.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class SingletonLz { 5 | public: 6 | static SingletonLz& getInstance() { 7 | if (Singleton_ == nullptr) { 8 | Singleton_ = new SingletonLz(); 9 | } 10 | return *Singleton_; 11 | } 12 | private: 13 | SingletonLz(); 14 | ~SingletonLz(); 15 | static SingletonLz* Singleton_; 16 | }; 17 | 18 | SingletonLz* SingletonLz::Singleton_ = nullptr; 19 | 20 | class SingletonDCL { 21 | public: 22 | static SingletonDCL& getInstance() { 23 | if (singleton_ == nullptr) { 24 | mutex_.lock(); 25 | if (singleton_ == nullptr) { 26 | singleton_ = new SingletonDCL(); 27 | } 28 | } 29 | return *singleton_; 30 | } 31 | private: 32 | SingletonDCL(); 33 | ~SingletonDCL(); 34 | static SingletonDCL* singleton_; 35 | static std::mutex mutex_; 36 | }; 37 | -------------------------------------------------------------------------------- /blog/km/Sort.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class Sort { 5 | public: 6 | void quickSort(std::vector& nums) { 7 | if (nums.size() == 0) { 8 | return; 9 | } 10 | if (nums.size() <= 10) { 11 | insertSort(nums); 12 | } 13 | quickSort(nums, 0, nums.size() - 1); 14 | } 15 | void headSort(std::vector& nums) { 16 | int size = nums.size(); 17 | createHeap(nums, size); 18 | int end = size; 19 | while (end > 0) { 20 | std::swap(nums[0], nums[end-1]); 21 | end--; 22 | AdjustDown(nums, 0, end); 23 | } 24 | } 25 | private: 26 | void createHeap(std::vector& nums, int size) { 27 | for (int i = (size - 1) / 2; i >= 0; i--) { 28 | AdjustDown(nums, i, size); 29 | } 30 | } 31 | void AdjustDown(std::vector& nums, int root, int size) { 32 | int parent = root; 33 | int child = 2 * parent + 1; 34 | while (child < size) { 35 | if (child < size - 1 && nums[child] < nums[child + 1]) { 36 | child++; 37 | } 38 | if (nums[parent] < nums[child]) { 39 | std::swap(nums[parent], nums[child]); 40 | parent = child; 41 | child = 2 * parent + 1; 42 | } 43 | } 44 | } 45 | void quickSort(std::vector& nums, int begin, int end) { 46 | if (begin >= end) { 47 | return; 48 | } 49 | int left = begin; 50 | int right = end; 51 | int base = nums[left]; 52 | // int index = random(left, right); 53 | // int base = nums[index]; 54 | while (left < right) { 55 | while (left < right && nums[right] >= base) { 56 | right--; 57 | } 58 | if (left < right) { 59 | nums[left++] = nums[right]; 60 | } 61 | while (left < right && nums[left] <= base) { 62 | left++; 63 | } 64 | if (left < right) { 65 | nums[right--] = nums[left]; 66 | } 67 | nums[left] = base; 68 | } 69 | quickSort(nums, begin, left - 1); 70 | quickSort(nums, left + 1, end); 71 | } 72 | int random(int left, int right) { 73 | return rand() % (right - left + 1) + left; 74 | } 75 | void insertSort(std::vector& nums) { 76 | if (nums.size() == 0) { 77 | return; 78 | } 79 | insertSort(nums, nums.size()); 80 | } 81 | void insertSort(std::vector& nums, int size) { 82 | for (int i = 1; i < size; i++) { 83 | if (nums[i] < nums[i-1]) { 84 | int j = i - 1; 85 | int temp = nums[i]; 86 | while (j >= 0 && temp < nums[j]) { 87 | nums[j+1] = nums[j]; 88 | j--; 89 | } 90 | nums[j+1] = temp; 91 | } 92 | } 93 | } 94 | }; 95 | -------------------------------------------------------------------------------- /blog/km/String.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class String { 4 | public: 5 | String(char* rhx = nullptr) { 6 | if (rhx == nullptr) { 7 | c_ = new char[1]; 8 | *c_ = '\0'; 9 | } else { 10 | int len = strlen(rhx); 11 | c_ = new char[len + 1]; 12 | strcpy(c_, rhx); 13 | } 14 | ++count_; 15 | } 16 | String(const String& rhx) { 17 | int len = strlen(rhx.c_); 18 | c_ = new char[len + 1]; 19 | strcpy(c_, rhx.c_); 20 | ++count_; 21 | } 22 | String& operator=(const String& rhx) { 23 | if (this != &rhx) { 24 | delete [] c_; 25 | int len = strlen(rhx.c_); 26 | c_ = new char[len + 1]; 27 | strcpy(c_, rhx.c_); 28 | } 29 | return *this; 30 | } 31 | ~String() { 32 | if (c_) { 33 | delete [] c_; 34 | } 35 | } 36 | static int count_; 37 | private: 38 | char* c_; 39 | }; 40 | 41 | int String::count_; 42 | -------------------------------------------------------------------------------- /blog/zxing-android-embedded-debug.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apriluestc/2020/2a7b8e1db4ca98eb0ebc31ca27323a6466d98676/blog/zxing-android-embedded-debug.aar -------------------------------------------------------------------------------- /blog/剑指offer/README.md: -------------------------------------------------------------------------------- 1 | # 剑指 offer 真题讲解 2 | 3 | - [二维数组的查找](https://github.com/Apriluestc/2020/blob/master/blog/%E5%89%91%E6%8C%87offer/%E4%BA%8C%E7%BB%B4%E6%95%B0%E7%BB%84%E7%9A%84%E6%9F%A5%E6%89%BE.md) 4 | - [替换空格](https://github.com/Apriluestc/2020/blob/master/blog/%E5%89%91%E6%8C%87offer/%E6%9B%BF%E6%8D%A2%E7%A9%BA%E6%A0%BC.md) 5 | -------------------------------------------------------------------------------- /blog/剑指offer/coding/删除链表中的重复节点.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | struct ListNode { 6 | int val; 7 | ListNode* next; 8 | ListNode(int x) 9 | :val(x), 10 | next(nullptr) 11 | {} 12 | }; 13 | 14 | class Solution { 15 | public: 16 | ListNode* deleteDuplication(ListNode* head) { 17 | 18 | // 链表为空或者只有一个节点不做删除处理 19 | // 不存在重复结点 20 | if (head == nullptr || head->next == nullptr) { 21 | return head; 22 | } 23 | ListNode* cur; 24 | if (head->next->val == head->val) { 25 | int tmp = head->val; 26 | cur = head->next->next; 27 | delete head->next; 28 | delete head; 29 | while (cur != nullptr && cur->val == tmp) { 30 | ListNode* node = cur; 31 | cur = cur->next; 32 | delete node; 33 | } 34 | return deleteDuplication(cur); 35 | } else { 36 | cur = head->next; 37 | head->next = deleteDuplication(cur); 38 | return head; 39 | } 40 | } 41 | }; 42 | 43 | int main() 44 | { 45 | return 0; 46 | } 47 | -------------------------------------------------------------------------------- /blog/剑指offer/二维数组的查找.md: -------------------------------------------------------------------------------- 1 | # 二维数组的查找 2 | 3 | ### 题目描述 4 | 5 | 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的 6 | 顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。 7 | 8 | ### 代码 9 | 10 | ```cpp 11 | class Solution { 12 | public: 13 | // 根据二维数组的性质 14 | /* 15 | nums = { 16 | 1, 2, 3, 17 | 2, 3, 4, 18 | 3, 4, 5, 19 | } 20 | */ 21 | // 从右上角开始查找 22 | bool Find(int target, vector > nums) { 23 | int row = 0; 24 | int col = nums[0].size() - 1; 25 | while (row < nums.size() && col >= 0) { 26 | if (target == nums[row][col]) { 27 | return true; 28 | } else if (target > nums[row][col]) { 29 | row++; 30 | } else { 31 | col--; 32 | } 33 | } 34 | return false; 35 | } 36 | 37 | // 二维数组每行都是递增,每一行用二分查找 38 | bool Find(int target, vector > nums) { 39 | for (int i = 0; i < nums.size(); i++) { 40 | int left = 0; 41 | int right = nums[i].size() - 1; 42 | while (left <= right) { 43 | int mid = left + (right - left) / 2; 44 | if (target == nums[i][mid]) { 45 | return true; 46 | } else if (target > nums[i][mid]) { 47 | left = mid + 1; 48 | } else { 49 | right = mid - 1; 50 | } 51 | } 52 | } 53 | return false; 54 | } 55 | };``` 56 | -------------------------------------------------------------------------------- /blog/剑指offer/替换空格.md: -------------------------------------------------------------------------------- 1 | # 替换空格 2 | 3 | ### 题目描述 4 | 5 | 请实现一个函数,将一个字符串中的每个空格替换成“%20”。 6 | 例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。 7 | 8 | ### 代码 9 | 10 | ```cpp 11 | class Solution { 12 | public: 13 | // 在当前字符串替换,从前往后统计字符串中空格数量,从后往前依次替换 14 | // 这样的话,字符移动的次数就会少很多,从而效率高 15 | void replaceSpace(char* str, int length) { 16 | int count = 0; 17 | for (int i = 0; i < length; i++) { 18 | if (str[i] == ' ') { 19 | count++; 20 | } 21 | } 22 | for (int i = length - 1; i >= 0; i--) { 23 | if (str[i] != ' ') { 24 | str[i + 2 * count] = str[i]; 25 | } else { 26 | count--; 27 | str[i + 2 * count] = '%'; 28 | str[i + 2 * count + 1] = '2'; 29 | str[i + 2 * count + 2] = '0'; 30 | } 31 | } 32 | } 33 | }; 34 | ``` 35 | -------------------------------------------------------------------------------- /blog/项目/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Apriluestc's projects 3 | --- 4 | 5 | ### git 命令 6 | 7 | 包括代码上传,分支管理,用户信息配置 8 | 9 | URL:https://github.com/Apriluestc/2020/blob/master/blog/doc/%E5%85%B6%E4%BB%96/git%E5%91%BD%E4%BB%A4.md 10 | 11 | ### 调试技巧 12 | 13 | VS2017 调试、gdb 调试,包含断点调试、单步调试、查看函数调用栈信息等 14 | 15 | URL:https://github.com/Apriluestc/2020/blob/master/blog/%E9%A1%B9%E7%9B%AE/debug.md 16 | 17 | ### 基于 UDP socket 的聊天室 18 | 19 | URL:https://github.com/Apriluestc/op.im 20 | 21 | 本项目为 C++ 编写的多人聊天室,使用 UDP socket 进行通信,支持多人聊天、支持语音聊天 22 | 23 | **技术特点** 24 | 25 | - C++ STL 26 | - 生产者消费者模型 27 | - 多线程技术,多线程同步原语同步与互斥 28 | - UDP socket 编程 29 | 30 | ### Web 服务器 31 | 32 | URL:https://github.com/Apriluestc/web.d 33 | 34 | 【测试页】[Apriluestc's pub](http://39.107.70.253:20000/) 35 | 36 | **项目介绍** 37 | 38 | 本项目为 C++ 11 编写的高性能 Web 服务器,使用状态机解析了 GET、POST 请求,可静态处理资源,支 39 | 持 HTTP 长连接、支持管线化请求,并实现了异步日志,实时记录服务器运行状态,经 webbench 测试该服务器可达 30000 并发 40 | 41 | **技术特点** 42 | 43 | - 使用 Epoll 边沿触发、非阻塞 IO、Reactor 模式 44 | - 使用多线程技术充分发挥多核 CPU 性能 45 | - 定时器定时关闭超时请求和剔除不活跃连接 46 | - 使用状态机解析了 GET 和 POST 请求 47 | - 主线程负责 accept 请求,并轮询分配给其他 IO 线程,这样一来锁竞争只会出现在主线程和某一特定线程(如:IO 线程、计算县城、工作线程) 48 | - 使用 eventfd 跨线程异步唤醒 49 | - TCP 接收发送环形缓冲区 50 | - 使用智能指针、RAII 机制规避程序中出现内存泄漏的可能 51 | - 异步日志实时记录服务器运行状态(便于 Debug) 52 | - webd 服务以 dameon 进程运行 53 | 54 | ### 文件压缩 55 | 56 | URL:https://github.com/Apriluestc/compress 57 | 58 | 本项目为 C++ 编写的文件压缩工具,使用哈夫曼压缩算法和 lz77 算法实现,支持文件夹压缩、支持文件压缩 59 | 60 | **技术特点** 61 | 62 | - 哈夫曼压缩算法 63 | - lz77 压缩算法 64 | 65 | **压缩测试** 66 | 67 | 【使用方法]`./Jarvis 0001.txt` 68 | 69 | - 哈夫曼压缩测试(txt、png、pdf) 70 | - 文件大小 37M,压缩后 16M,压缩时间 40.04 s,解压缩时间 7.69 s 71 | - 文件大小 47M,压缩后 20M,压缩时间 49.10 s,解压缩时间 8.97 s 72 | - 文件大小 20K,压缩后 19K,压缩时间 0.08 s,解压缩时间 0.02 s 73 | - 文件大小 72K,压缩后 71K,压缩时间 0.2 s,解压缩时间 0.04 s 74 | - 文件大小 74M,压缩后 73M,压缩时间 146.21 s,解压缩时间 23.47 s 75 | 76 | - Lz77 压缩测试(分别为 txt、png、pdf) 77 | - 文件大小 37M,压缩后 34M,压缩时间 80.00 s,解压缩时间 26.28 s 78 | - 文件大小 47M,压缩后 44M,压缩时间 100.45 s,解压缩时间 32.43 s 79 | - 文件大小 42M,压缩后 39M,压缩时间 89.12 s,解压缩时间 29.39 s 80 | - 文件大小 20K,压缩后 20K,压缩时间 0.40 是,解压缩时间 0.00 s 81 | - 文件大小 72K,压缩后 80K,压缩时间 3.25 s,解压缩时间 0.00 s 82 | - 文件大小 1.2M,压缩后 1.4M,压缩时间 49.66 s,解压缩时间 0.11 s 83 | 84 | ### 智能语音 ai 工具 85 | 86 | URL:https://github.com/Apriluestc/Jarvis 87 | 88 | ������ Linux 智能语音 Ai 工具 Jarvis,该项目基于百度语音开源 SDK & 图灵机器 89 | 人搭建的,目前没有接入微信平台(ps:很简单的一个项目,主要用于练习开源库、开源接口的使用) 90 | 91 | **项目功能** 92 | 93 | - 支持语音识别、语音合成 94 | - 支持语音操作指令(如:语音查看内存、等等) 95 | -------------------------------------------------------------------------------- /blog/项目/debug.md: -------------------------------------------------------------------------------- 1 | ### 调试技巧 2 | 3 | #### VS2017 调试技巧 4 | 5 | - 设置断点调试 6 | 7 | 在一行代码的左侧点击即可设置断点,按 F5 即可运行到第一个断点处暂停 8 | 9 | - 逐语句调试 10 | 11 | 按 F11 逐语句调试,即可一步一步执行 12 | 13 | - 逐过程调试 14 | 15 | 按 F10 逐过程调试,此时不会进入函数内部 16 | 17 | - 跳出当前函数 18 | 19 | 在一个函数里面时想要跳出此函数,可以按 Shift + F11 可直接运行完当前函数,直接 return 到函数外面去 20 | 21 | - 运行到光标处 22 | 23 | 在一行代码上右键,选择“运行到光标处”,即可快速运行到当前位置,如果之前有断点则会停在断点处 24 | 25 | - 快速重启 26 | 27 | Ctrl + Shift + F5 28 | 29 | - 添加监视 30 | 31 | 可以在监视窗口添加你想关注的变量,以及对变量进行运算,比如取地址 32 | 33 | - 检查函数调用栈 34 | 35 | 函数调用堆栈窗口显示了当前正在调用函数的顺序,顶层显示的是当前函数,第二行显示的是调用当前函数的函数,越往下越深,依次类推 36 | 37 | 可以在调用堆栈上双击可以看到改代码的位置,有键可以做其他操作,比如运行到光标处 38 | 39 | ![Add image](https://github.com/Apriluestc/2020/blob/master/pics/debug.png) 40 | 41 | #### gdb 调试技巧 42 | 43 | - 简单命令 44 | 45 | - 查看各级函数调用栈信息:bt 46 | - 继续运行到当前函数返回为止,然后停下来等待命令:finish 47 | - 帧编号,选择栈帧:f 48 | - 查看当前栈帧局部变量的值:info 49 | - 列出源代码,接着上次的依次往下列出,每次 10 行:l 或者 list 50 | - 列出从第几行开始的源代码:list + 行号 51 | - 列出从某个函数的源代码:list + 函数名 52 | - 开始执行程序,停在 main 函数第一行语句前面等待命令:start 53 | - 执行下一条语句,即逐过程调试:next 或者 n 54 | - 执行下一条语句,逐语句调试:step 或者 s 55 | - 继续执行到断点位置:run 56 | - 单步执行一条机器指令,通常一条语句由多条机器指令构成,如:i++ 由三条汇编构成:nexti 和 stepi 57 | - 打印表达式的值,通过表达式可以修改变量的值或者调用函数:print 或者 p 58 | - 退出 gdb 调试环境:quit 或者 q 59 | - 修改变量值:set var 60 | - 删除程序中所有断点:clear 61 | - 删除此行断点:clear 行号 62 | - 删除该函数的断点:clear 函数名 63 | - 查看程序是否在运行,进程号被暂停的原因:info program 64 | - 多文件设置断点,在 file.cpp 文件的第 1000 行设置断点:break file.cpp:1000 65 | - 在当前行号的前面或后面停住:break + offset 66 | 67 | - 启动 gdb 方法 68 | 69 | - gdb <可执行程序> 70 | - gdb <可执行程序> core,用 gdb 同时调试一个可执行程序和一个 core 文件,core 是程序非法执行 core dump 产生的文件 71 | - gdb <可执行程序> PID,如果你的程序是一个服务程序,那么你可以指定这个服务程序运行时的 IP,gdb 会自动 attach 上 72 | 73 | - 设置观察点 74 | 75 | - watch 为表达式(变量) expr 设置一个观察点,一旦表达式值有变化时,马上暂停程序 76 | - rwatch 当表达式(变量) expr 被读时,暂停程序 77 | - awatch 当表达式(变量)的值被读或者写时,暂停程序 78 | - info watchpoints 列出当前所有的观察点 79 | - **在使用 watch 时步骤** 80 | - 使用 break 在要观察的变量所在处设置断点 81 | - 使用 run 运行,直到断点 82 | - 使用 watch 设置观察点 83 | - 使用 continue 观察设置的观察点是否有变化 84 | 85 | ```html 86 | watch p 是查看 *(&p), 是p 变量本身。watch (*p) 是 p 所指的内存的内容, 查看地址,一般是我们所需要的。 87 | 我们就是要看莫地址上的数据是怎样变化的,虽然这个地址具体位置只有编译器知道。c. watch 一个数组或内存区间 88 | char buf[128], watch buf, 是对buf 的128个数据进行了监视. 此时不是采用硬件断点,而是软中断实现的。 89 | 软中断方式去检查内存变量是比较耗费cpu资源的。 90 | ``` 91 | 92 | - 设置捕点 93 | 94 | - 当 event 发生时,catch 暂停程序,event 可能是如下 95 | - throw 一个 C++ 抛出的异常 96 | - catch 一个 C++ 捕捉到的异常 97 | - exec 调用系统调用 exec 时 98 | - fork 调用系统调用 fork 时 99 | - vfork 调用系统调用 vfork 时 100 | - 只设置一次捕捉点,当程序停住以后,对应自动删除 101 | 102 | - 一般情况下产生段错误的原因 103 | 104 | - 内存访问越界 105 | - 使用错误的下标,导致数组访问越界 106 | - 搜索字符串时,依靠字符串结束符来判断钱钱钱钱钱钱钱字符串是否结束,但是在这正情况下字符串没有正确使用结束符 107 | - 多线程内使用了不安全的函数 108 | - 多线程读写数据未加锁 109 | - 使用空指针 110 | - 堆栈溢出 111 | - 随意使用指针转换,一个指向一段内存的指针,除非确定这段内存原先就分配为某种结构或类型,或者这种结构或类型的数组,否则 112 | 不要将它转换为这种结构或类型的指针,而应该将这段内存拷贝到一个这种 113 | 结构或类型中,再访问这个结构或类型。这是因为如果这段内存的开始地址不是按照 114 | 这种结构或类型对齐的,那么访问它时就很容易因为 bus error 而 core dump 115 | - 解决这种问题的几个常用命令:l、b、p、r、n、c、q 116 | -------------------------------------------------------------------------------- /blog/项目/web.md: -------------------------------------------------------------------------------- 1 | # Web 2 | 3 | * [介绍以下这个项目](#介绍以下这个项目) 4 | * [定时器怎么实现的?还有什么实现方式?](#定时器怎么实现的还有什么实现方式) 5 | * [实现一个无锁队列](#实现一个无锁队列) 6 | * [eventfd 是什么?有什么好处](#eventfd-是什么有什么好处) 7 | * [异步日志是什么?为什么要这么做?](#异步日志是什么为什么要这么做) 8 | * [什么是优雅关闭连接?](#什么是优雅关闭连接) 9 | * [Epoll 的边沿触发和水平触发有什么区别?](#epoll-的边沿触发和水平触发有什么区别) 10 | * [Epoll 为什么高效,相较于 select、poll?](#epoll-为什么高效相较于-selectpoll) 11 | * [HTTP 报文有哪些字段?](#http-报文有哪些字段) 12 | * [假入服务器要升级,又不想让用户感知这个行为,怎么做?](#假入服务器要升级又不想让用户感知这个行为怎么做) 13 | * [一个请求的到来具体的处理过程是什么样的?](#一个请求的到来具体的处理过程是什么样的) 14 | * [线程池的作用是什么?](#线程池的作用是什么) 15 | * [线程的唤醒方式还有哪些?](#线程的唤醒方式还有哪些) 16 | * [怎么检查内存泄漏?](#怎么检查内存泄漏) 17 | * [用到了哪些智能指针和 RAII 机制,几种锁的区别是什么?什么是 RAII 资源管理?](#用到了哪些智能指针和-raii-机制几种锁的区别是什么什么是-raii-资源管理) 18 | * [任务队列怎么实现的?除了加锁还有什么实现方式?](#任务队列怎么实现的除了加锁还有什么实现方式) 19 | * [死锁的原因?条件?如何预防?如何避免?如何解除?](#死锁的原因条件如何预防如何避免如何解除) 20 | * [怎么进行压测的?](#怎么进行压测的) 21 | * [为什么要用非阻塞 IO?](#为什么要用非阻塞-IO) 22 | * [Reactor 模式是什么?](#reactor-模式是什么) 23 | * [TCP 收发环形缓冲区怎么实现的?](#tcp-收发环形缓冲区怎么实现的) 24 | * [什么是 dameon 进程?dameon 进程创建原理是什么?](#什么是-dameon-进程dameon-进程创建原理是什么) 25 | * [nginx 的并发模型是什么?或者说它是如何处理高并发的?](#nginx-的并发模型是什么或者说它是如何处理高并发的) 26 | * [你做这个项目的初衷是什么?为什么要做这个项目?](#你做这个项目的初衷是什么为什么要做这个项目) 27 | 28 | ### 介绍以下这个项目 29 | 30 | 本项目为 C++ 11 编写的高性能 Web 服务器,使用状态机解析了 GET、POST 请求,可静态处理资源,支 31 | 持 HTTP 长连接、支持管线化请求,并实现了异步日志,实时记录服务器运行状态, 32 | 经 webbench 测试该服务器可达 30000 并发量 33 | 34 | ### 定时器怎么实现的?还有什么实现方式? 35 | 36 | **应用场景分析** 37 | 38 | 首先经典的服务器模型中,都是将事件放在了一个 rbtree 中,比如 nginx、memcached、libevent 这些都是把事件放在了 rbtee 中, 39 | 而 redis 则是放在了链表中(未排序)。 40 | 41 | **定时器** 42 | 43 | Linux 中提供三种定时方法 44 | 45 | - socket 选项 SO_RECVTIMEO 和 SO_SNDTIMEO 46 | - SIGALRM 信号 47 | - I/O 复用系统调用的超时参数 48 | 49 | 场景:客户端发起的网络请求,需要对每个请求做超时检查 50 | 51 | 方案一:一个定时器,一个 mulimap 保存请求超时列表,每次超时检查 mulimap,这样请求的插入时间复杂度是 O(logn), 52 | 便利和删除的时间复杂度为 O(1),但是需要额外的编码 53 | 54 | 方案二:一个请求持有一个定时器,如此便无需额外的开销来保存超时请求,也无序额外的编码,等待超时处理请求即可(请求的信息作为参数给定时器 node 保存),时间复杂度 0 55 | 56 | 但是程序中定时器数量不多的情况下,基于最小堆的定时器一般可以满足要求,且实现简单,对于方案 2 中的应用场景,对定时器要求就比较高了 57 | 58 | **【定时器实现】** 59 | 60 | | 定时器 | StartTimer | StopTimer | PerTickBookkeeping | 61 | | :----------: | :--------: | :-------: | :----------------: | 62 | | 基于小根堆 | logn | 1 | 1 | 63 | | 基于排序链表 | n | 1 | 1 | 64 | | 基于时间轮 | 1 | 1 | 1 | 65 | 66 | **实现** 67 | 68 | 简单时间轮:一个齿轮,每个齿轮保存一个超时的 node 链表,一个齿轮表示一个时间刻度,比如钟表里面的一小格表示一秒,钟表的秒钟没跳一哥,假设一个刻度 69 | 代表 10 ms,则 2^32 格子可表示 1.36 年,2^16 格子可表示 10.9 分钟,当要表示的时间范围较大时,空间复杂度会大大增加 70 | 71 | 分级时间轮:类似于水表,当小轮子里的指针转动满一圈后,上一级的轮子的指针进一格,采用五个轮子每个轮子为一个简单的定时器,大小分别是 2^8,2^6,2^6,2^6,等等 72 | 73 | ### 实现一个无锁队列 74 | 75 | 【详见】https://github.com/Apriluestc/2020/blob/master/doc/%E7%AE%97%E6%B3%95%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/%E7%AE%97%E6%B3%95.md#%E6%97%A0%E9%94%81%E9%98%9F%E5%88%97 76 | 77 | ### eventfd 是什么?有什么好处 78 | 79 | 在 Reactor 模式中,为了能流畅处理多个客户端的连接,通常情况下会有一个主进程和多个工作进程, 80 | 主进程负责 accept 新连接并生成新连接的 socketfd,然后再将这些 fd 分发给数个工作进程,其中主线程 81 | 向子线程(work_thread)分发 fd 的方式在 Linux 平台上常见的有三种,分别是 pipe、eventfd、socketpair, 82 | 跨线程异步唤醒,将生成的 eventfd 绑定到 epoll_fd 上,需要时向这个 eventfd 上写入一个字节,工作线程立即被唤醒, 83 | **eventfd 是 Linux 引入的一种轻量级的 IPC 方式,不同进程可以通过 eventfd 建立起一个共享的计数器,这个计数器由内核维护, 84 | 充当信息的角色,与他关联的进程可以进行读写,从整体上来讲,eventfd 更加有优势,仅仅通过一个文件描述符,便实现了读写功能, 85 | 而 pipe 和 socketpair 都占用两个 fd** 86 | 87 | ```cpp 88 | int eventfd(unsigned int initval, int flags); 89 | ``` 90 | 91 | ### 异步日志是什么?为什么要这么做? 92 | 93 | 首先多线程异步日志需要线程安全的保证,即多个线程可以写日志文件而不发生错乱,简单的线程安全并不难办到,用一个 94 | 全局的 Mutex 对日志的 IO 操作进行保护或者单独写一个日志文件即可,但是前者会造成多个线程竞争锁资源,后者会造成某个业务线程阻塞 95 | 96 | 本项目可行的解决方案就是,用一个背景线程负责收集日志消息并将其写入后端,其他业务线程只负责生成的日志消息并将其传输到日志线程,这便是异步日志 97 | 98 | 日志的实现应用双缓冲区技术,即存在两个 Buffer,日志的实现分为前端和后端, 前端负责向 CurrentBuffer 中写,后端负责 99 | 将其写入文件中,具体来说,当 CurrentBuffer 写满时,先将 CurrentBuffer 中的消息存入 Buffer 中,在交换 CurrentBuffer 和 NextBuffer,这样 100 | 前端就可以继续往 CurrentBuffer 中写新的日志消息, 最后再调用 notify_all 通知后端将其写入文件 101 | 102 | **标准 IO 和文件 IO 的区别** 103 | 104 | 文件 IO:文件 IO 也被称为不带缓存的 IO,不带缓存指的是每个 read、write 都调用内核中的一个系统调用,也就是低级 IO --操作系统提供的 IO 操作,与 OS 绑定。特定与 Linux 和 Unix 平台 105 | 106 | 标准 IO:标准 IO 是一个便准函数包和 stdio.h 头文件的定义,具有一定的可以移植性,有缓存,标准 IO 提供了三种类型的缓存 107 | 108 | - 全缓存:当填满标准 IO 缓冲区才进行实际的 IO 操作 109 | - 行缓存:当输入或输出中遇到换行符时,标准 IO 执行 IO 操作 110 | - 不带缓存:stderr 便是 111 | 112 | **区别** 113 | 114 | 文件 IO 又称为低级磁盘 IO,其进行读写文件时,每次操作都会与系统调用绑定,这样处理的好处是直接读写文件,坏处是系统调用会直接增加系统开销,标准 IO 可以看成是 115 | 在文件 IO 的基础上封装了缓冲机制,先读写缓冲区,必要时再访问实际文件,从而减少系统调用的次数,文件 IO 中用文件描述符表现为一个打开的文件,可以访问不同类型的文件,如普通文件、设备文件等, 116 | 而标准 IO 中用 FILE 流标识一个打开的文件,通常只访问普通文件 117 | 118 | 标准 IO 函数:fopen、fclose、fread、fwrite、fputs等 119 | 120 | 文件 IO 函数:open、close、read、write 121 | 122 | ### 什么是优雅关闭连接? 123 | 124 | 所谓优雅关闭连接就是(就是 read() 到 0,要透明的传递这个行为而不是直接暴力 close()),一般情况下我们使用 close() 来关闭套接字, 125 | 所谓关闭套接字就是,将套接字描述符(或句柄)从内存中清除,之后再也不能使用该套接字,直接调用 close() 会立即向网络中发送 FIN 包, 126 | 不管输出缓冲区是否有数据,所以调用 close() 将丢失缓冲区数据 127 | 128 | 而调用 shutdown() 则会等待输出缓冲区中的数据传输完毕再发送 FIN 包,也就是我们所说的(read 到 0),这便是优雅关闭连接,而不是暴力 close 129 | 130 | ### Epoll 的边沿触发和水平触发有什么区别? 131 | 132 | epoll 有 EPOLLLT 和 EPOLLET 两种触发模式,LT 是默认的模式,ET 是“高速”模式。LT 模式下,只要这个 fd 还有数据可读,每次 epoll_wait 都会返回它的事件,提 133 | 醒用户程序去操作,而在 ET(边缘触发)模式中,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无 论 fd 中是 134 | 否还有数据可读。所以在 ET 模式下,read 一个 fd 的时候一定要把它的 buffer 读光,也就是说一直读到 read 的返回值小于请求值,或者 遇到 EAGAIN 错误 135 | 136 | ### Epoll 为什么高效,相较于 select、poll? 137 | 138 | #### select 139 | 140 | - select 模型:因为一个进程打开的 fd 是有限制的,由 FD_SETSIZE 设置,默认是 1024,因此 select 模型的最大并发数就被相应的限制了 141 | 142 | - 效率问题:select 每次调用都会线性扫描全部 fd 集合,效率呈线性下降,如果将 FD_SETSIZE 改大,可能会造成 fd 超时 143 | 144 | - 内核/用户空间拷贝问题:select 采用了内存拷贝,无论 poll、select、epoll 都要把 fd 消息通知给用户空间,而 select 和 poll 都采用内存拷贝,显然效率低下 145 | 146 | #### poll 147 | 148 | - poll解决了 select 最大并发限制,但是 select 的效率问题和拷贝问题却依然存在 149 | 150 | #### epoll 151 | 152 | - 拷贝问题:epoll 通过内核与用户空间的 mmap 同一块内存实现,这样就避免了大量的 fd 跨空间拷贝,即避免了不必要的拷贝 153 | 154 | - epoll 支持打开大数目 fd,具体数目 `cat /proc/sys/fs/file-max` 155 | 156 | - epoll-API: 157 | 158 | - epoll_create,创建 epoll 句柄 159 | - epoll_ctl,事件注册函数 160 | - epoll_wait,收集 epoll 监控的事件中已发送的事件 161 | 162 | **epoll 为什么高效** 163 | 164 | - 从 API 调用方式来看,select/poll 每次都要传递索要监控的 fd 集合给 select/poll 系统调用(这意味着将所有的 fd 跨空间拷贝,耗时),而每次调用 epoll_wait 时(相当于调用 select/poll), 165 | 不需要传递 fd 给内核,只需要进行增量式操作,所以在 epoll_create 之后,内核已经在内核态开始准备数据结构存放监控的 fd 了,每次调用 epoll_ctl 时只是对这个数据结构进行简单的维护 166 | 167 | - 此外,内核使用了 slab 机制,为 epoll 提供了快速的数据结构,在内核里一切皆文件,所以 epoll 向内核注册了一个文件系统,用于存储被监控的 fd,当调用 epoll_create 时,就会在这个虚拟的 epoll 文件系统里, 168 | 创建一个 file 节点,当然这个 file 不是普通文件,它只服务于 epoll,epoll在被内核初始化时(操作系统启动),同时会开辟出 epoll 自己的内核高速 cache 区,用于安置每一个我们想监控的 fd,这 169 | 些 fd 会以红黑树的形式保存在内核 cache 里,以支持快速的查找、插入、删除,在这个内核高速 cache 区,就是建立连续的物理内存页,然后在之上建立 slab 层,简单地说,就是物理上分配好你想要的 size 的内存对象, 170 | 每次使用时都是使用空闲的已分配好的对象 171 | 172 | - 当我们调用 epoll_ctl 往里塞入百万 fd 时,epoll_wait 仍能飞快的返回,并有效的将发生事件的 fd 给我们用户,这是由于我们在调用 epoll_create 时,内核除了帮我们在 epoll 文件系统里创建 file 节点,在内核 cache 区建 173 | 了个红黑树用于存储 epoll_ctl 传来的 fd,还会建立一个 list 链表,用于存储准备就绪的事件,当 epoll_wait 调用时,仅仅观测 list 有无数据即可,有数据返回,无数据 sleep,等到 timeout 事件页返回,通常情况下我们要监控百万计的 fd, 174 | 大多一次也只返回少量的 fd,所以 epoll_wait 仅需要从内核态拷贝少量 fd 到用户态,那么如何维护这个就绪的 list 链表,当我们执行 epoll_ctl 时,除了把 fd 放到 epoll 文件系统 file 对象对应的红黑树之外,还会给内核中断处理程序注册一个 CallBack, 175 | 告诉内核,如果这个 fd 到了,就把他放到就绪 list 中 176 | 177 | #### 总结 178 | 179 | **如此,一颗红黑树,一张准备就绪的 list 链表,少量的内核 cache,就帮我们解决了高并发下的 fd 处理问题** 180 | 181 | **执行 epoll_create 时创建红黑树和就绪 list 链表** 182 | 183 | **执行 epoll_ctl 时,如果增加 fd,则检查在红黑树中是否存在,存在立即返回,否则添加,然后向内核注册回调函数,用于当中断事件来临时向 list 中插入数据** 184 | 185 | **执行 epoll_wait 时立即返回就绪 list 中的数据** 186 | 187 | ![Add img](https://github.com/Apriluestc/2020/tree/master/pics/epoll.png) 188 | 189 | ### HTTP 报文有哪些字段? 190 | 191 | **通用首部字段** 192 | 193 | - Cache-Control:控制缓存行为 194 | - Connection:控制不在转发给代理的首部字段、管理持久连接 195 | - Date:创建报文时间 196 | - Warning:错误通知 197 | 198 | **请求首部字段** 199 | 200 | - Accept:用户代理可处理的媒体类型 201 | - Authorization:期待 Web 服务器的特定行为 202 | - User-Agent:HTTP 客户端程序信息 203 | 204 | **响应首部字段** 205 | 206 | - Accept-Ranges:是否接受字节范围请求 207 | - ETag:资源匹配信息 208 | - Location:零客户端重定向至特定 URL 209 | 210 | **实体首部字段** 211 | 212 | - Allow:资源可支持的 HTTP 方法 213 | - Content-Length:实体主体的大小 214 | - Content-Location:替代对应资源的 URI 215 | - Last-Modified:资源的最后修改时间 216 | 217 | ### 假入服务器要升级,又不想让用户感知这个行为,怎么做? 218 | 219 | 在我们版本运行之初,可以这么理解,已经将完好的版本发布,现在要对其进行升级,但是又不要用 220 | 户感知自己的服务器的升级行为,我们可参考 nginx 平滑升级 221 | 222 | - nginx 平滑升级 223 | 224 | 当前服务器正在运行 nginx 服务,现对 nginx 进行升级(且跨度不大,容易造成服务崩溃)且在不停止 225 | 服务的情况下进行升级 226 | 227 | **具体过程** 228 | 229 | - 在不停掉旧进程的情况下,启动新进程 230 | - 就进程负责处理仍然没有处理完的请求,但不接收新请求 231 | - 新进程负责接受请求 232 | - 老进程处理完所有请求,关闭所有连接后,服务终止 233 | 234 | ### 一个请求的到来具体的处理过程是什么样的? 235 | 236 | 新连接到来,主线程负责 Accept 新的连接并生成新连接的 socketfd,然后将这个新连接的 fd 以轮询(Round Robin)的方式发送给工作线程,这里涉及到跨线程任务分配,需要加锁,这里的锁 237 | 由某个特定任务的线程中的 loop 创建,制会被该线程和主线程竞争,本项目中主线程向子线程 238 | 分发 fd 采用了 eventfd 方式,来唤醒工作线程,这些工作线程再负责处理这些新连接上的网络 IO 事件(收发数据),每个工作线程持有一个 Timer 用于处理超时请求, 239 | 工作线程都做了(read、write、Decode、Encode 等), 240 | 241 | - 客户发起情况到服务器网卡; 242 | - 服务器网卡接受到请求后转交给内核处理; 243 | - 内核根据请求对应的套接字,将请求交给工作在用户空间的Web服务器进程 244 | - Web 服务器进程根据用户请求,向内核进行系统调用,申请获取相应资源(如index.html) 245 | - 内核发现web服务器进程请求的是一个存放在硬盘上的资源,因此通过驱动程序连接磁盘 246 | - 内核调度磁盘,获取需要的资源 247 | - 内核将资源存放在自己的缓冲区中,并通知Web服务器进程 248 | - Web服务器进程通过系统调用取得资源,并将其复制到进程自己的缓冲区中 249 | - Web服务器进程形成响应,通过系统调用再次发给内核以响应用户请求 250 | - 内核将响应发送至网卡 251 | - 网卡发送响应给用户 252 | 253 | ### 线程池的作用是什么? 254 | 255 | 处理线程高并发,用一个数组保存线程,然后一直放着,如果没用就用条件变量让它休眠,如果加入一个新的任务就唤醒其中一个去执行这个任务 256 | 257 | **pthread_cond_signal 和 pthread_cond_broadcast 的区别?** 258 | 259 | Pthread_cond_signal表示唤醒睡眠线程中的一个【单播,可能按照优先级或者先来后到的原则】 260 | 261 | Pthread_cond_boardcast表示唤醒所有睡眠线程【广播】 262 | 263 | ### 线程的唤醒方式还有哪些? 264 | ### 怎么检查内存泄漏? 265 | 266 | Windows 下,前提 Debug 环境下可以通过 VLD 和 CRT 这两个库本身的内存泄漏函数定位内存泄漏,相对而言简单一些 267 | 268 | Release 环境下: 269 | 270 | - 对象计数 271 | 272 | 在对象构造时计数 ++,析构时 --,每隔一段时间打印对象的数量 273 | 274 | **优点**:没有性能开销,几乎不占内存,定位结果准确 275 | 276 | **缺点**:侵入式方法,需修改现有代码,而且对于第三方库、STL 容器、脚本泄漏等无法修改代码而定位内存泄漏 277 | 278 | - 重载 New / delete 279 | 280 | **优点**:没啥优点 281 | 282 | **缺点**:侵入式方法,需要修改现有代码,需要将头文件加入到大量源文件的头部,以确保重载的宏能够覆盖所有的 New 和 delete,记录分配点需要 283 | 加锁(对于多线程),记录分配电需要大量内存 284 | 285 | Linux 下使用 valgrind 可以很方便的定位内存泄漏 286 | 287 | 最常用的格式:`valgrind --tool = memcheck --leak-check=full ./target` 288 | 289 | 290 | ### 用到了哪些智能指针和 RAII 机制,几种锁的区别是什么?什么是 RAII 资源管理? 291 | 292 | RAII 即资源获取就是初始化,利用对象生命周期来控制程序资源,简单来说就是用过局部变量对象来处理一些资源问题 293 | 294 | auto_ptr:auto_ptr 则是通过权限转移机制实现,auto_ptr 析构时会删除它所拥有的指针,所以使用时避免多个 auto_ptr 对象管理同一个指针,因为多次删除同一对象会导致未定义行为, 295 | 并且一个指针析构,一个指针仍在使用时,会导致空指针访问风险,由于 auto_ptr 内部实现中,析构删除对象 296 | 仍然使用 delete 并不是 delete[] 所以 auto_ptr 不能用来管理数组(避免内存泄漏),auto_ptr 要求对它所拥有的指针完全占有,这一点与引用计数不同,也就是说,一个一般指针不能被两个 auto_ptr 同时 297 | 拥有,auto_ptr 对象被拷贝或赋值之后,失去了对原指针的所有权,再次读取操作不安全 298 | 299 | **auto_ptr 解决方案** 300 | 301 | - 定义赋值运算符,使其进行深复制,这样两个指针将指向不同对象,缺点浪费空间 302 | - 建立所有权,对于特定对象只能是一个指针拥有,这样拥有对象的智能指针的析构函数会删除该对象,然后赋值操操作转移所有权 303 | - 使用引用计数 304 | 305 | shared_ptr:shared_ptr 是一个标准的共享所有权的智能指针,允许多指针指向同一对象,shared_ptr 利用引用计数的方式实现了对管理对象所有权的分享,也被称为 306 | 引用计数型智能指针,shared_ptr 为了解决 auto_ptr 在对象上所有权上的局限性(auto_ptr 时独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针,存在额外开销 307 | 308 | 对于 shared_ptr 使用引用计数实现,在调用 release() 时,当前指针释放资源所有权,引用计数减一,当计数等于 0 时,资源被释放,可使用成员函数 use_count() 查看资源的所有者个数, 309 | 在对象进行构造时,引用计数加 1 310 | 311 | weak_ptr:shared_ptr 的助手,没有重载 `operator*` 和 operator->,它的最大作用在于协同 shared_ptr 工作,仅有观测权, 312 | weak_ptr 可以对 shared_ptr 进行引用,但不改变其引用计数,同时失效 313 | 314 | unique_ptr:替代不安全的 auto_ptr,它持有对对象的独有权,两个 unique_ptr 不能指向同一对象,无法拷贝、智能移动 unique_ptr 即资源管理权转移,release() 释放对对象的所有权 315 | 316 | ### 任务队列怎么实现的?除了加锁还有什么实现方式? 317 | 318 | 条件变量搭配互斥锁、CAS 319 | 320 | ### 死锁的原因?条件?如何预防?如何避免?如何解除? 321 | 322 | 原因:系统资源不足;进程运行推进顺序不合适;资源分配不当 323 | 324 | 条件:互斥;不可剥夺;循环等待;请求与保持 325 | 326 | 预防:破坏任意一个条件 327 | 328 | 避免:银行家算法 329 | 330 | 检测:资源分配图简化 331 | 332 | ### 怎么进行压测的? 333 | 334 | `webbench -t 60 -c 1000 -2 -k 39.107.70.253:20000/` 335 | 336 | ### 为什么要用非阻塞 IO? 337 | 338 | 首先一个输入操作通常包括两个阶段,**等待数据准备好**,**从内核向进程复制数据** 339 | 340 | - 阻塞 IO:在内核数据准备好之前,系统调用会一直等待所有的套接字,默认是阻塞方式 341 | 342 | 举个栗子:A 拿着一支鱼竿在河边钓鱼,并且一直在鱼竿前等,在等的时候不做其他的事情,十分专心。只有鱼上钩的时,才结束掉等的动作,把鱼钓上来。 343 | 344 | - 非阻塞 IO:每次客户询问内核是否有数据准备好,即文件描述符缓冲区是否就绪。当有数据报准备好时,就进行拷贝数据报的操作。当没有数据 345 | 报准备好时,也不阻塞程序,内核直接返回未准备就绪的信号,等待用户程序的下一个轮寻 346 | 347 | 举个栗子:B 也在河边钓鱼,但是 B 不想将自己的所有时间都花费在钓鱼上,在等鱼上钩这个时间段中,B 也在 348 | 做其他的事情(一会看看书,一会读读报纸,一会又去看其他人的钓鱼等),但 B 在做这些事情的时候,每隔一个固定的时间检查鱼是否上钩。一旦检查 349 | 到有鱼上钩,就停下手中的事情,把鱼钓上来。 350 | 351 | 另外多路复用 IO 为何比非阻塞 IO 模型的效率高是因为在非阻塞 IO 中,不断地询问 socket 状态是通 352 | 过用户线程去进行的,而在多路复用 IO 中,轮询每个 socket 状态是内核在 353 | 进行的,这个效率要比用户线程要高的多 354 | 355 | ### Reactor 模式是什么? 356 | 357 | Reactor 模式可以简单概括为,服务器端为了能流畅处理多个客户端连接,一般在某个进程 A 里面 accept 新的 358 | 客户端连接并生成新连接的 socketfd,然后将这些新连接的 socketfd 分发数个工作线程 B1、B2、B3、···,这 359 | 些工作线程监听并处理这些新连接上的网络 IO 事件(即收发数据),同时,还处理系统中的一些事务,这里我们将 A 称为 360 | 主线程,B1、B2、B3、···,等称为工作线程,工作线程的代码框架一般如下: 361 | 362 | ```cpp 363 | while (!m_quit) { 364 | 365 | // 负责通过 epoll()/poll()/select() 去检测 socketfd 上的 IO 事件, 366 | // 若存在这些事件,则下一步 handle_io_events() 来处理这些事件(收发数据),做完之后可 367 | // 能还要做一些系统其他任务,即调用 handle_others_things() 368 | epoll_or_select_func(); 369 | handle_io_events(); 370 | handle_other_things(); 371 | } 372 | ``` 373 | 374 | ### 什么是 dameon 进程?dameon 进程创建原理是什么? 375 | 376 | Linux daemon 是运行于后台常驻内存的一种特殊进程,周期性的执行或者等待 trigger 执行某个任务,与用户交互断开,独 377 | 立于控制终端。 一个守护进程的父进程是 init 进程,它是一个孤儿进程,没有控制终端,所以任何输出,无论是向标 378 | 准输出设备 stdout 还是标准出错设备 stderr 的 输出都被丢到了 /dev/null 中。守护进程一般用作服务 379 | 器进程,如 httpd,syslogd 等。 380 | 381 | **进程、进程组、会话、控制终端之间的关系** 382 | 383 | - 进程组:由一个或者多个进程组成,进程组号(GID),就是这些进程中的进程组长的 PID 384 | 385 | - 会话:又叫会话期,它包括了期间所有的进程组,一般一个会话开始于用户 login,一般 login 的是 shell 的终端,所以 shell 是此次 会话的shoul 首进程,会话一般结束于 logout,对于非进程组长,它可以调用 setid() 创建一个新的会话 386 | 387 | - 控制终端:一般指 shell 的终端,它在会话期可有可没有 388 | 389 | **创建守护进程** 390 | 391 | - 首先让其成为后台进程 392 | 393 | 用 fork 创建进程,父进程退出,子进程称为孤儿进程被 init 进程接管,子进程变为后台进程 394 | 395 | - 脱离父进程控制终端,登录会话和进程组 396 | 397 | 调用 setid() 让子进程称为成为新会话的组长,脱离父进程的会话期,setid() 在调用者是某进程组的组长时 398 | 会失败,但是前提保证调用者不是组长即可 之后子进程变为新会话期的组长 399 | 400 | - 控制进程重新开启控制终端 401 | 402 | 因为会话组的组长有权限重新打开控制终端,所以这里第二次 fork 将子进程结束,留着孙进程,孙进程不是会话组的组长所以没有权利再 403 | 打开控制终端,这样整个程序就与控制终端隔离了 404 | 405 | - 关闭文件描述符 406 | 407 | 进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误 408 | 409 | - 重定向 0,1,2 标准文件描述符 410 | 411 | 将三个标准文件描述符定向到 /dev/null 中 412 | 413 | - 改变工作目录和文件掩码 414 | 415 | 进程活动时,其工作目录所在的文件系统不能卸下(比如工作目录在一个 NFS 中,运行一个 daemon 会导致 umount 无法成功)。一 416 | 般需要将工作目录改变 到根目录。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如 chdir("/tmp"),进程从 417 | 创建它的父进程那里继承 了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:umask(0); 418 | 419 | ### nginx 的并发模型是什么?或者说它是如何处理高并发的? 420 | 421 | 与经典多线程服务器模型相比,nginx 是多进程模型,nginx 启动后以 dameon 进程在后台运行,后台进程包含一个 master 进程和若干个 worker 进程,master 进程主要 422 | 负责管理 worker 进程,主要有 4 个功能**接收来自外界的信号、向各个工作进程发送信号、监控 423 | worker 进程的运行状态、当工作进程推出后,会自动启动新的工作进程**,工作进程 worker 主要用于处理网络 IO 事件,各个 424 | 工作进程之间对等且独立,它们同等竞争来自客户端的请求,一个请求只可能在一个工作进程中处理,数量一般为 CPU 核数 425 | 426 | **使用多进程的优点**,进程之间不共享资源,不需要加锁,省掉了锁开销,采用独立的进程相互之间不受影响,一个进程退出后,服务不会中断,master 进程很快创建一个新的工作进程, 427 | 编程更加容易 428 | 429 | **多线程的瓶颈问题** 430 | 431 | 而多线程在多并发情况下,线程的内存占用大,线程上下文切换造成 CPU 大量的开销 432 | 。想想 apache 的常用工作方式(apache 也有异步非阻塞版本,但因其与自带某些模块冲突,所以 433 | 不常用),每个请求会独占一个工作线程,当并发数上到几千时,就同时有几千的线程 434 | 在处理请求了。这对 操作系统来说,是个不小的挑战,线程带来的内存占用非常大,线程的上下文切换带来的 cpu 开 435 | 销很大,自然性能就上不去了,而这些开销完全是没有意义的 436 | 437 | **异步非阻塞** 438 | 439 | 异步的概念和同步相对的,也就是不是事件之间不是同时发生的。 440 | 441 | 非阻塞的概念是和阻塞对应的,阻塞是事件按顺序执行,每一事件都要等待上一事件的完成,而非阻塞是如果事件没有准备好,这个事件可以直接返回,过一段时间再进行处理询问,这期间可以做其他事情。但是,多次询问也会带来额外的开销。 442 | 443 | 总的来说,Nginx采用异步非阻塞的好处在于: 444 | 445 | - 不需要创建线程,每个请求只占用少量的内存 446 | - 没有上下文切换,事件处理非常轻量 447 | 448 | ### 你做这个项目的初衷是什么?为什么要做这个项目? 449 | 450 | 前情提要,之前看过 TinyHttpd 源码,想自己写一个 Web 服务器,由于当时能力有限, 就从去年推到今年的 1 月份,在脑海里有这个项 451 | 目之前,我们老师曾经推荐好几本书, 我下去看了看,诸如:《C++ 编程思想》、《C 和指针》、《C++ Primer》、《侯捷 STL 源码剖 452 | 析》、《TCP/IP 详解 卷 1》、《高性能服务器编程》、《现代操作系统》、《计算机网络》等,这些书本涵盖了: 453 | 454 | - 多线程编程、Socket 编程、泛型编程 455 | - TCP/IP 协议、通信协议 456 | - IO 相关 457 | - 多线程相关同步原语 458 | - dameon 进程创建原理 459 | - 版本控制 git、Makefile、shell、编程规范 460 | 461 | 做完这个项目刚好可以把握所学的知识在秋招前复习一遍,哈哈哈哈哈哈哈,迫不及待 462 | 463 | 后续项目中进行重构,采用智能指针代替全裸的指针,借鉴 muduo 思想、林亚 写的项目以及陈帅豪项目,把我的 web.d 重构了一番 464 | -------------------------------------------------------------------------------- /pics/cas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apriluestc/2020/2a7b8e1db4ca98eb0ebc31ca27323a6466d98676/pics/cas.png -------------------------------------------------------------------------------- /pics/debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apriluestc/2020/2a7b8e1db4ca98eb0ebc31ca27323a6466d98676/pics/debug.png -------------------------------------------------------------------------------- /pics/epoll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apriluestc/2020/2a7b8e1db4ca98eb0ebc31ca27323a6466d98676/pics/epoll.png -------------------------------------------------------------------------------- /pics/innodb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apriluestc/2020/2a7b8e1db4ca98eb0ebc31ca27323a6466d98676/pics/innodb.png -------------------------------------------------------------------------------- /pics/myisam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apriluestc/2020/2a7b8e1db4ca98eb0ebc31ca27323a6466d98676/pics/myisam.png -------------------------------------------------------------------------------- /pics/queue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apriluestc/2020/2a7b8e1db4ca98eb0ebc31ca27323a6466d98676/pics/queue.png -------------------------------------------------------------------------------- /pics/右旋.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apriluestc/2020/2a7b8e1db4ca98eb0ebc31ca27323a6466d98676/pics/右旋.png -------------------------------------------------------------------------------- /pics/左旋.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apriluestc/2020/2a7b8e1db4ca98eb0ebc31ca27323a6466d98676/pics/左旋.png -------------------------------------------------------------------------------- /zxing-android-embedded-debug.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apriluestc/2020/2a7b8e1db4ca98eb0ebc31ca27323a6466d98676/zxing-android-embedded-debug.aar --------------------------------------------------------------------------------