diff --git a/README.md b/README.md deleted file mode 100644 index a57364f760..0000000000 --- a/README.md +++ /dev/null @@ -1,434 +0,0 @@ -

- -

-

- - - - - - -

- -目录: -================= - -* [算法面试思维导图](#算法面试思维导图) -* [B站算法视频讲解](#B站算法视频讲解) -* [LeetCode 刷题攻略](#LeetCode-刷题攻略) -* [算法文章精选](#算法文章精选) -* [算法模板](#算法模板) -* [LeetCode 最强题解](#LeetCode-最强题解) -* [关于作者](#关于作者) - -# 算法面试思维导图 - -![算法面试知识大纲](./pics/算法大纲.png) - -# B站算法视频讲解 - -* [KMP算法(理论篇)](https://www.bilibili.com/video/BV1PD4y1o7nd) -* [KMP算法(代码篇)](https://www.bilibili.com/video/BV1M5411j7Xx) -* [回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM) -* [回溯算法之组合问题(力扣题目:77.组合)](https://www.bilibili.com/video/BV1ti4y1L7cv) -* [组合问题的剪枝操作(对应力扣题目:77.组合)](https://www.bilibili.com/video/BV1wi4y157er) -* [组合总和(对应力扣题目:39.组合总和)](https://www.bilibili.com/video/BV1KT4y1M7HJ/) - -(持续更新中....) - -# LeetCode 刷题攻略 - -> 不少同学和我反应LeetCode 刷题攻略这一栏没有了,首先感谢大家对本仓库的关注!因为我发现一些公众号抄袭我的Github,所以我把刷题攻略隐藏了,但是很多同学就看不到刷题顺序了,为了大家可以继续学习,我把算法精选文章的顺序整理了,和刷题攻略顺序一致的,**文章顺序即刷题顺序**,而且比刷题攻略更全!感谢大家的支持,**这个仓库我每天都会更新的**! - -刷题顺序:建议先从同一类型里题目开始刷起,同一类型里再从简单到中等到困难刷起,题型顺序建议:**数组-> 链表-> 哈希表->字符串->栈与队列->树->回溯->贪心->动态规划->图论**。 - -目前大家可以按照下面的「算法文章精选」顺序来刷,里面都是各个类型的经典题目而且题目顺序都是精心设计的,**初学者可以按照这个顺序来刷题**,算法老手可以按照这个list查缺补漏! - -**同时这份刷题列表也在公众号[「代码随想录」](https://img-blog.csdnimg.cn/20201124161234338.png)左下角的「算法汇总」里,方便大家用手机查看**,用手机看的好处可以看到每篇文章下都有很多录友(代码随想录的朋友们)的留言,录友会总结每篇文章的重点,如果文章有一些笔误的话,留言区也会及时纠正,所以**刷一下文章留言区会对理解知识点非常有帮助**!而且公众号更新要比Github早2-3天。 - -**赶紧去公众号[「代码随想录」](https://img-blog.csdnimg.cn/20201124161234338.png)里看看吧,你会发现相见恨晚!** - -# 算法文章精选 - -* 编程语言 - * [C++面试&C++学习指南知识点整理](https://github.com/youngyangyang04/TechCPP) - -* 编程素养 - * [看了这么多代码,谈一谈代码风格!](https://mp.weixin.qq.com/s/UR9ztxz3AyL3qdHn_zMbqw) - -* 求职 - * [程序员的简历应该这么写!!(附简历模板)](https://mp.weixin.qq.com/s/nCTUzuRTBo1_R_xagVszsA) - * [BAT级别技术面试流程和注意事项都在这里了](https://mp.weixin.qq.com/s/815qCyFGVIxwut9I_7PNFw) - * [深圳原来有这么多互联网公司,你都知道么?](https://mp.weixin.qq.com/s/3VJHF2zNohBwDBxARFIn-Q) - * [北京有这些互联网公司,你都知道么?]() - * [上海有这些互联网公司,你都知道么?]() - * [成都有这些互联网公司,你都知道么?](https://mp.weixin.qq.com/s/Y9Qg22WEsBngs8B-K8acqQ) - * [广州有这些互联网公司,你都知道么?](https://mp.weixin.qq.com/s/Ir_hQP0clbnvHrWzDL-qXg) - - -* 算法性能分析 - * [关于时间复杂度,你不知道的都在这里!](https://mp.weixin.qq.com/s/LWBfehW1gMuEnXtQjJo-sw) - * [O(n)的算法居然超时了,此时的n究竟是多大?](https://mp.weixin.qq.com/s/73ryNsuPFvBQkt6BbhNzLA) - * [通过一道面试题目,讲一讲递归算法的时间复杂度!](https://mp.weixin.qq.com/s/I6ZXFbw09NR31F5CJR_geQ) - * [本周小结!(算法性能分析系列一)](https://mp.weixin.qq.com/s/5m8xDbGUeGgYJsESeg5ITQ) - -* 数组 - * [必须掌握的数组理论知识](https://mp.weixin.qq.com/s/X7R55wSENyY62le0Fiawsg) - * [数组:每次遇到二分法,都是一看就会,一写就废](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q) - * [数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA) - * [数组:滑动窗口拯救了你](https://mp.weixin.qq.com/s/UrZynlqi4QpyLlLhBPglyg) - * [数组:这个循环可以转懵很多人!](https://mp.weixin.qq.com/s/KTPhaeqxbMK9CxHUUgFDmg) - * [数组:总结篇](https://mp.weixin.qq.com/s/LIfQFRJBH5ENTZpvixHEmg) -* 链表 - * [关于链表,你该了解这些!](https://mp.weixin.qq.com/s/ntlZbEdKgnFQKZkSUAOSpQ) - * [链表:听说用虚拟头节点会方便很多?](https://mp.weixin.qq.com/s/slM1CH5Ew9XzK93YOQYSjA) - * [链表:一道题目考察了常见的五个操作!](https://mp.weixin.qq.com/s/Cf95Lc6brKL4g2j8YyF3Mg) - * [链表:听说过两天反转链表又写不出来了?](https://mp.weixin.qq.com/s/pnvVP-0ZM7epB8y3w_Njwg) - * [链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA) - * [链表:总结篇!](https://mp.weixin.qq.com/s/vK0JjSTHfpAbs8evz5hH8A) - -* 哈希表 - * [关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/g8N6WmoQmsCUw3_BaWxHZA) - * [哈希表:可以拿数组当哈希表来用,但哈希值不要太大](https://mp.weixin.qq.com/s/vM6OszkM6L1Mx2Ralm9Dig) - * [哈希表:哈希值太大了,还是得用set](https://mp.weixin.qq.com/s/N9iqAchXreSVW7zXUS4BVA) - * [哈希表:今天你快乐了么?](https://mp.weixin.qq.com/s/G4Q2Zfpfe706gLK7HpZHpA) - * [哈希表:map等候多时了](https://mp.weixin.qq.com/s/uVAtjOHSeqymV8FeQbliJQ) - * [哈希表:其实需要哈希的地方都能找到map的身影](https://mp.weixin.qq.com/s/Ue8pKKU5hw_m-jPgwlHcbA) - * [哈希表:这道题目我做过?](https://mp.weixin.qq.com/s/sYZIR4dFBrw_lr3eJJnteQ) - * [哈希表:解决了两数之和,那么能解决三数之和么?](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A) - * [双指针法:一样的道理,能解决四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g) - * [哈希表:总结篇!(每逢总结必经典)](https://mp.weixin.qq.com/s/1s91yXtarL-PkX07BfnwLg) - - -* 字符串 - * [字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA) - * [字符串:简单的反转还不够!](https://mp.weixin.qq.com/s/XGSk1GyPWhfqj2g7Cb1Vgw) - * [字符串:替换空格](https://mp.weixin.qq.com/s/t0A9C44zgM-RysAQV3GZpg) - * [字符串:花式反转还不够!](https://mp.weixin.qq.com/s/X3qpi2v5RSp08mO-W5Vicw) - * [字符串:反转个字符串还有这个用处?](https://mp.weixin.qq.com/s/PmcdiWSmmccHAONzU0ScgQ) - * [帮你把KMP算法学个通透!(理论篇)B站视频](https://www.bilibili.com/video/BV1PD4y1o7nd) - * [帮你把KMP算法学个通透!(代码篇)B站视频](https://www.bilibili.com/video/BV1M5411j7Xx) - * [字符串:都来看看KMP的看家本领!](https://mp.weixin.qq.com/s/Gk9FKZ9_FSWLEkdGrkecyg) - * [字符串:KMP算法还能干这个!](https://mp.weixin.qq.com/s/lR2JPtsQSR2I_9yHbBmBuQ) - * [字符串:前缀表不右移,难道就写不出KMP了?](https://mp.weixin.qq.com/s/p3hXynQM2RRROK5c6X7xfw) - * [字符串:总结篇!](https://mp.weixin.qq.com/s/gtycjyDtblmytvBRFlCZJg) - -* 双指针法 - * [数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA) - * [字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA) - * [字符串:替换空格](https://mp.weixin.qq.com/s/t0A9C44zgM-RysAQV3GZpg) - * [字符串:花式反转还不够!](https://mp.weixin.qq.com/s/X3qpi2v5RSp08mO-W5Vicw) - * [链表:听说过两天反转链表又写不出来了?](https://mp.weixin.qq.com/s/pnvVP-0ZM7epB8y3w_Njwg) - * [链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA) - * [哈希表:解决了两数之和,那么能解决三数之和么?](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A) - * [双指针法:一样的道理,能解决四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g) - * [双指针法:总结篇!](https://mp.weixin.qq.com/s/_p7grwjISfMh0U65uOyCjA) - -* 栈与队列 - * [栈与队列:来看看栈和队列不为人知的一面](https://mp.weixin.qq.com/s/VZRjOccyE09aE-MgLbCMjQ) - * [栈与队列:我用栈来实现队列怎么样?](https://mp.weixin.qq.com/s/P6tupDwRFi6Ay-L7DT4NVg) - * [栈与队列:用队列实现栈还有点别扭](https://mp.weixin.qq.com/s/yzn6ktUlL-vRG3-m5a8_Yw) - * [栈与队列:系统中处处都是栈的应用](https://mp.weixin.qq.com/s/nLlmPMsDCIWSqAtr0jbrpQ) - * [栈与队列:匹配问题都是栈的强项](https://mp.weixin.qq.com/s/eynAEbUbZoAWrk0ZlEugqg) - * [栈与队列:有没有想过计算机是如何处理表达式的?](https://mp.weixin.qq.com/s/hneh2nnLT91rR8ms2fm_kw) - * [栈与队列:滑动窗口里求最大值引出一个重要数据结构](https://mp.weixin.qq.com/s/8c6l2bO74xyMjph09gQtpA) - * [栈与队列:求前 K 个高频元素和队列有啥关系?](https://mp.weixin.qq.com/s/8hMwxoE_BQRbzCc7CA8rng) - * [栈与队列:总结篇!](https://mp.weixin.qq.com/s/xBcHyvHlWq4P13fzxEtkPg) - -* 二叉树 - * [关于二叉树,你该了解这些!](https://mp.weixin.qq.com/s/_ymfWYvTNd2GvWvC5HOE4A) - * [二叉树:一入递归深似海,从此offer是路人](https://mp.weixin.qq.com/s/PwVIfxDlT3kRgMASWAMGhA) - * [二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg) - * [二叉树:前中后序迭代方式的写法就不能统一一下么?](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg) - * [二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog) - * [二叉树:你真的会翻转二叉树么?](https://mp.weixin.qq.com/s/6gY1MiXrnm-khAAJiIb5Bg) - * [本周小结!(二叉树)](https://mp.weixin.qq.com/s/JWmTeC7aKbBfGx4TY6uwuQ) - * [二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg) - * [二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg) - * [二叉树:看看这些树的最小深度](https://mp.weixin.qq.com/s/BH8-gPC3_QlqICDg7rGSGA) - * [二叉树:我有多少个节点?](https://mp.weixin.qq.com/s/2_eAjzw-D0va9y4RJgSmXw) - * [二叉树:我平衡么?](https://mp.weixin.qq.com/s/isUS-0HDYknmC0Rr4R8mww) - * [二叉树:找我的所有路径?](https://mp.weixin.qq.com/s/Osw4LQD2xVUnCJ-9jrYxJA) - * [还在玩耍的你,该总结啦!(本周小结之二叉树)](https://mp.weixin.qq.com/s/QMBUTYnoaNfsVHlUADEzKg) - * [二叉树:以为使用了递归,其实还隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA) - * [二叉树:做了这么多题目了,我的左叶子之和是多少?](https://mp.weixin.qq.com/s/gBAgmmFielojU5Wx3wqFTA) - * [二叉树:我的左下角的值是多少?](https://mp.weixin.qq.com/s/MH2gbLvzQ91jHPKqiub0Nw) - * [二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg) - * [二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg) - * [二叉树:构造一棵最大的二叉树](https://mp.weixin.qq.com/s/1iWJV6Aov23A7xCF4nV88w) - * [本周小结!(二叉树系列三)](https://mp.weixin.qq.com/s/JLLpx3a_8jurXcz6ovgxtg) - * [二叉树:合并两个二叉树](https://mp.weixin.qq.com/s/3f5fbjOFaOX_4MXzZ97LsQ) - * [二叉树:二叉搜索树登场!](https://mp.weixin.qq.com/s/vsKrWRlETxCVsiRr8v_hHg) - * [二叉树:我是不是一棵二叉搜索树](https://mp.weixin.qq.com/s/8odY9iUX5eSi0eRFSXFD4Q) - * [二叉树:搜索树的最小绝对差](https://mp.weixin.qq.com/s/Hwzml6698uP3qQCC1ctUQQ) - * [二叉树:我的众数是多少?](https://mp.weixin.qq.com/s/KSAr6OVQIMC-uZ8MEAnGHg) - * [二叉树:公共祖先问题](https://mp.weixin.qq.com/s/n6Rk3nc_X3TSkhXHrVmBTQ) - * [本周小结!(二叉树系列四)](https://mp.weixin.qq.com/s/CbdtOTP0N-HIP7DR203tSg) - * [二叉树:搜索树的公共祖先问题](https://mp.weixin.qq.com/s/Ja9dVw2QhBcg_vV-1fkiCg) - * [二叉树:搜索树中的插入操作](https://mp.weixin.qq.com/s/lwKkLQcfbCNX2W-5SOeZEA) - * [二叉树:搜索树中的删除操作](https://mp.weixin.qq.com/s/-p-Txvch1FFk3ygKLjPAKw) - * [二叉树:修剪一棵搜索树](https://mp.weixin.qq.com/s/QzmGfYUMUWGkbRj7-ozHoQ) - * [二叉树:构造一棵搜索树](https://mp.weixin.qq.com/s/sy3ygnouaZVJs8lhFgl9mw) - * [二叉树:搜索树转成累加树](https://mp.weixin.qq.com/s/hZtJh4T5lIGBarY-lZJf6Q) - * [二叉树:总结篇!(需要掌握的二叉树技能都在这里了)](https://mp.weixin.qq.com/s/-ZJn3jJVdF683ap90yIj4Q) - -* 回溯算法 - * [关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw) - * [回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ) - * [回溯算法:组合问题再剪剪枝](https://mp.weixin.qq.com/s/Ri7spcJMUmph4c6XjPWXQA) - * [回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w) - * [回溯算法:电话号码的字母组合](https://mp.weixin.qq.com/s/e2ua2cmkE_vpYjM3j6HY0A) - * [本周小结!(回溯算法系列一)](https://mp.weixin.qq.com/s/m2GnTJdkYhAamustbb6lmw) - * [回溯算法:求组合总和(二)](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw) - * [回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ) - * [回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q) - * [回溯算法:复原IP地址](https://mp.weixin.qq.com/s/v--VmA8tp9vs4bXCqHhBuA) - * [回溯算法:求子集问题!](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA) - * [本周小结!(回溯算法系列二)](https://mp.weixin.qq.com/s/uzDpjrrMCO8DOf-Tl5oBGw) - * [回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ) - * [回溯算法:递增子序列](https://mp.weixin.qq.com/s/ePxOtX1ATRYJb2Jq7urzHQ) - * [回溯算法:排列问题!](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw) - * [回溯算法:排列问题(二)](https://mp.weixin.qq.com/s/9L8h3WqRP_h8LLWNT34YlA) - * [本周小结!(回溯算法系列三)](https://mp.weixin.qq.com/s/tLkt9PSo42X60w8i94ViiA) - * [本周小结!(回溯算法系列三)续集](https://mp.weixin.qq.com/s/kSMGHc_YpsqL2j-jb_E_Ag) - * [视频来了!!带你学透回溯算法(理论篇)](https://mp.weixin.qq.com/s/wDd5azGIYWjbU0fdua_qBg) - * [视频来了!!回溯算法:组合问题](https://mp.weixin.qq.com/s/a_r5JR93K_rBKSFplPGNAA) - * [视频来了!!回溯算法:组合问题的剪枝操作](https://mp.weixin.qq.com/s/CK0kj9lq8-rFajxL4amyEg) - * [视频来了!!回溯算法:组合总和](https://mp.weixin.qq.com/s/4M4Cr04uFOWosRMc_5--gg) - * [回溯算法:重新安排行程](https://mp.weixin.qq.com/s/3kmbS4qDsa6bkyxR92XCTA) - * [回溯算法:N皇后问题](https://mp.weixin.qq.com/s/lU_QwCMj6g60nh8m98GAWg) - * [回溯算法:解数独](https://mp.weixin.qq.com/s/eWE9TapVwm77yW9Q81xSZQ) - * [一篇总结带你彻底搞透回溯算法!](https://mp.weixin.qq.com/s/r73thpBnK1tXndFDtlsdCQ) - -* 贪心算法 - * [关于贪心算法,你该了解这些!](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg) - * [贪心算法:分发饼干](https://mp.weixin.qq.com/s/YSuLIAYyRGlyxbp9BNC1uw) - * [贪心算法:摆动序列](https://mp.weixin.qq.com/s/Xytl05kX8LZZ1iWWqjMoHA) - * [贪心算法:最大子序和](https://mp.weixin.qq.com/s/DrjIQy6ouKbpletQr0g1Fg) - * [本周小结!(贪心算法系列一)](https://mp.weixin.qq.com/s/KQ2caT9GoVXgB1t2ExPncQ) - * [贪心算法:买卖股票的最佳时机II](https://mp.weixin.qq.com/s/VsTFA6U96l18Wntjcg3fcg) - * [贪心算法:跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA) - * [贪心算法:跳跃游戏II](https://mp.weixin.qq.com/s/kJBcsJ46DKCSjT19pxrNYg) - * [贪心算法:K次取反后最大化的数组和](https://mp.weixin.qq.com/s/dMTzBBVllRm_Z0aaWvYazA) - * [本周小结!(贪心算法系列二)](https://mp.weixin.qq.com/s/RiQri-4rP9abFmq_mlXNiQ) - * [贪心算法:加油站](https://mp.weixin.qq.com/s/aDbiNuEZIhy6YKgQXvKELw) - * [贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ) - * [贪心算法:柠檬水找零](https://mp.weixin.qq.com/s/0kT4P-hzY7H6Ae0kjQqnZg) - * [贪心算法:根据身高重建队列](https://mp.weixin.qq.com/s/-2TgZVdOwS-DvtbjjDEbfw) - * [本周小结!(贪心算法系列三)](https://mp.weixin.qq.com/s/JfeuK6KgmifscXdpEyIm-g) - * [贪心算法:根据身高重建队列(续集)](https://mp.weixin.qq.com/s/K-pRN0lzR-iZhoi-1FgbSQ) - - -* 动态规划 - -* 图论 - -* 数论 - -* 高级数据结构经典题目 - * 并查集 - * 最小生成树 - * 线段树 - * 树状数组 - * 字典树 -* 海量数据处理 - -(持续更新中....) - - -# 算法模板 - -[各类基础算法模板](https://github.com/youngyangyang04/leetcode/blob/master/problems/算法模板.md) - -# LeetCode 最强题解: - -|题目 | 类型 | 难度 | 解题方法 | -|---|---| ---| --- | -|[0001.两数之和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0001.两数之和.md) | 数组|简单|**暴力** **哈希**| -|[0015.三数之和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0015.三数之和.md) | 数组 |中等|**双指针** **哈希**| -|[0017.电话号码的字母组合](https://github.com/youngyangyang04/leetcode/blob/master/problems/0017.电话号码的字母组合.md) | 回溯 |中等|**回溯**| -|[0018.四数之和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0018.四数之和.md) | 数组 |中等|**双指针**| -|[0019.删除链表的倒数第N个节点](https://github.com/youngyangyang04/leetcode/blob/master/problems/0019.删除链表的倒数第N个节点.md) | 链表 |中等|**双指针**| -|[0020.有效的括号](https://github.com/youngyangyang04/leetcode/blob/master/problems/0020.有效的括号.md) | 栈 |简单|**栈**| -|[0021.合并两个有序链表](https://github.com/youngyangyang04/leetcode/blob/master/problems/0021.合并两个有序链表.md) |链表 |简单|**模拟** | -|[0024.两两交换链表中的节点](https://github.com/youngyangyang04/leetcode/blob/master/problems/0024.两两交换链表中的节点.md) |链表 |中等|**模拟** | -|[0026.删除排序数组中的重复项](https://github.com/youngyangyang04/leetcode/blob/master/problems/0026.删除排序数组中的重复项.md) |数组 |简单|**暴力** **快慢指针/快慢指针** | -|[0027.移除元素](https://github.com/youngyangyang04/leetcode/blob/master/problems/0027.移除元素.md) |数组 |简单| **暴力** **双指针/快慢指针/双指针**| -|[0028.实现strStr()](https://github.com/youngyangyang04/leetcode/blob/master/problems/0028.实现strStr().md) |字符串 |简单| **KMP** | -|[0031.下一个排列](https://github.com/youngyangyang04/leetcode/blob/master/problems/0031.下一个排列.md) |数组 |中等| **模拟** 这道题目还是有难度的| -|[0034.在排序数组中查找元素的第一个和最后一个位置](https://github.com/youngyangyang04/leetcode/blob/master/problems/0031.下一个排列.md) |数组 |中等| **二分查找**比35.搜索插入位置难一些| -|[0035.搜索插入位置](https://github.com/youngyangyang04/leetcode/blob/master/problems/0035.搜索插入位置.md) |数组 |简单| **暴力** **二分**| -|[0037.解数独](https://github.com/youngyangyang04/leetcode/blob/master/problems/0037.解数独.md) |回溯 |困难| **回溯**| -|[0039.组合总和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0039.组合总和.md) |数组/回溯 |中等| **回溯**| -|[0040.组合总和II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0040.组合总和II.md) |数组/回溯 |中等| **回溯**| -|[0042.接雨水](https://github.com/youngyangyang04/leetcode/blob/master/problems/0042.接雨水.md) |数组/栈/双指针 |困难| **双指针** **单调栈** **动态规划**| -|[0045.跳跃游戏II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0045.跳跃游戏II.md) |贪心 |困难| **贪心**| -|[0046.全排列](https://github.com/youngyangyang04/leetcode/blob/master/problems/0046.全排列.md) |回溯|中等| **回溯**| -|[0047.全排列II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0047.全排列II.md) |回溯|中等| **回溯**| -|[0051.N皇后](https://github.com/youngyangyang04/leetcode/blob/master/problems/0051.N皇后.md) |回溯|困难| **回溯**| -|[0052.N皇后II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0052.N皇后II.md) |回溯|困难| **回溯**| -|[0053.最大子序和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0053.最大子序和.md) |数组 |简单|**暴力** **贪心** 动态规划 分治| -|[0055.跳跃游戏](https://github.com/youngyangyang04/leetcode/blob/master/problems/0053.最大子序和.md) |数组 |中等| **贪心** 经典题目| -|[0056.合并区间](https://github.com/youngyangyang04/leetcode/blob/master/problems/0056.合并区间.md) |数组 |中等| **贪心** 以为是模拟题,其实是贪心| -|[0057.插入区间](https://github.com/youngyangyang04/leetcode/blob/master/problems/0057.插入区间.md) |数组 |困难| **模拟** 是一道数组难题| -|[0059.螺旋矩阵II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0059.螺旋矩阵II.md) |数组 |中等|**模拟**| -|[0062.不同路径](https://github.com/youngyangyang04/leetcode/blob/master/problems/0062.不同路径.md) |数组、动态规划 |中等|**深搜** **动态规划** **数论**| -|[0070.爬楼梯](https://github.com/youngyangyang04/leetcode/blob/master/problems/0070.爬楼梯.md) |动态规划|简单|**动态规划** dp里求排列| -|[0077.组合](https://github.com/youngyangyang04/leetcode/blob/master/problems/0077.组合.md) |回溯 |中等|**回溯**| -|[0078.子集](https://github.com/youngyangyang04/leetcode/blob/master/problems/0078.子集.md) |回溯/数组 |中等|**回溯**| -|[0083.删除排序链表中的重复元素](https://github.com/youngyangyang04/leetcode/blob/master/problems/0083.删除排序链表中的重复元素.md) |链表 |简单|**模拟**| -|[0084.柱状图中最大的矩形](https://github.com/youngyangyang04/leetcode/blob/master/problems/0084.柱状图中最大的矩形.md) |数组 |困难|**单调栈**| -|[0090.子集II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0090.子集II.md) |回溯/数组 |中等|**回溯**| -|[0093.复原IP地址](https://github.com/youngyangyang04/leetcode/blob/master/problems/0093.复原IP地址) |回溯 |中等|**回溯**| -|[0094.二叉树的中序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0094.二叉树的中序遍历.md) |树 |中等|**递归** **迭代/栈**| -|[0098.验证二叉搜索树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0098.验证二叉搜索树.md) |树 |中等|**递归**| -|[0100.相同的树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0100.相同的树.md) |树 |简单|**递归** | -|[0101.对称二叉树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0101.对称二叉树.md) |树 |简单|**递归** **迭代/队列/栈** 和100. 相同的树 相似| -|[0102.二叉树的层序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0102.二叉树的层序遍历.md) |树 |中等|**广度优先搜索/队列**| -|[0104.二叉树的最大深度](https://github.com/youngyangyang04/leetcode/blob/master/problems/0104.二叉树的最大深度.md) |树 |简单|**递归** **迭代/队列/BFS**| -|[0105.从前序与中序遍历序列构造二叉树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0105.从前序与中序遍历序列构造二叉树.md) |二叉树 |中等|**递归**| -|[0106.从中序与后序遍历序列构造二叉树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0106.从中序与后序遍历序列构造二叉树.md) |二叉树 |中等|**递归** 根据数组构造二叉树| -|[0107.二叉树的层次遍历II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0107.二叉树的层次遍历II.md) |树 |简单|**广度优先搜索/队列/BFS**| -|[0108.将有序数组转换为二叉搜索树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0108.将有序数组转换为二叉搜索树.md) |二叉搜索树 |中等|**递归** **迭代** 通过递归函数返回值构造树| -|[0110.平衡二叉树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0110.平衡二叉树.md) |二叉树 |简单|**递归**| -|[0111.二叉树的最小深度](https://github.com/youngyangyang04/leetcode/blob/master/problems/0111.二叉树的最小深度.md) |二叉树 |简单|**递归** **队列/BFS**| -|[0112.路径总和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0112.路径总和.md) |二叉树树 |简单|**深度优先搜索/递归** **回溯** **栈** 思考递归函数什么时候需要返回值| -|[0113.路径总和II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0113.路径总和II.md) |二叉树树 |简单|**深度优先搜索/递归** **回溯** **栈**| -|[0116.填充每个节点的下一个右侧节点指针](https://github.com/youngyangyang04/leetcode/blob/master/problems/0116.填充每个节点的下一个右侧节点指针.md) |二叉树 |中等|**递归** **迭代/广度优先搜索**| -|[0117.填充每个节点的下一个右侧节点指针II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0117.填充每个节点的下一个右侧节点指针II.md) |二叉树 |中等|**递归** **迭代/广度优先搜索**| -|[0122.买卖股票的最佳时机II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0122.买卖股票的最佳时机II.md) |贪心 |简单|**贪心** | -|[0127.单词接龙](https://github.com/youngyangyang04/leetcode/blob/master/problems/0127.单词接龙.md) |广度优先搜索 |中等|**广度优先搜索**| -|[0129.求根到叶子节点数字之和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0129.求根到叶子节点数字之和.md) |二叉树 |中等|**递归/回溯** 递归里隐藏着回溯,和113.路径总和II类似| -|[0131.分割回文串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0131.分割回文串.md) |回溯 |中等|**回溯**| -|[0135.分发糖果](https://github.com/youngyangyang04/leetcode/blob/master/problems/0135.分发糖果.md) |贪心 |困难|**贪心**好题目| -|[0139.单词拆分](https://github.com/youngyangyang04/leetcode/blob/master/problems/0139.单词拆分.md) |动态规划 |中等|**完全背包** **回溯法**| -|[0141.环形链表](https://github.com/youngyangyang04/leetcode/blob/master/problems/0141.环形链表.md) |链表 |简单|**快慢指针/双指针**| -|[0142.环形链表II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0142.环形链表II.md) |链表 |中等|**快慢指针/双指针**| -|[0143.重排链表](https://github.com/youngyangyang04/leetcode/blob/master/problems/0143.重排链表.md) |链表 |中等|**快慢指针/双指针** 也可以用数组,双向队列模拟,考察链表综合操作的好题| -|[0144.二叉树的前序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0144.二叉树的前序遍历.md) |树 |中等|**递归** **迭代/栈**| -|[0145.二叉树的后序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0145.二叉树的后序遍历.md) |树 |困难|**递归** **迭代/栈**| -|[0147.对链表进行插入排序](https://github.com/youngyangyang04/leetcode/blob/master/problems/0147.对链表进行插入排序.md) |链表 |中等|**模拟** 考察链表综合操作| -|[0150.逆波兰表达式求值](https://github.com/youngyangyang04/leetcode/blob/master/problems/0150.逆波兰表达式求值.md) |栈 |中等|**栈**| -|[0151.翻转字符串里的单词](https://github.com/youngyangyang04/leetcode/blob/master/problems/0151.翻转字符串里的单词.md) |字符串 |中等|**模拟/双指针**| -|[0155.最小栈](https://github.com/youngyangyang04/leetcode/blob/master/problems/0155.最小栈.md) |栈 |简单|**栈**| -|[0199.二叉树的右视图](https://github.com/youngyangyang04/leetcode/blob/master/problems/0199.二叉树的右视图.md) |二叉树 |中等|**广度优先遍历/队列**| -|[0202.快乐数](https://github.com/youngyangyang04/leetcode/blob/master/problems/0202.快乐数.md) |哈希表 |简单|**哈希**| -|[0203.移除链表元素](https://github.com/youngyangyang04/leetcode/blob/master/problems/0203.移除链表元素.md) |链表 |简单|**模拟** **虚拟头结点**| -|[0205.同构字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0205.同构字符串.md) |哈希表 |简单| **哈希**| -|[0206.翻转链表](https://github.com/youngyangyang04/leetcode/blob/master/problems/0206.翻转链表.md) |链表 |简单| **双指针法** **递归**| -|[0209.长度最小的子数组](https://github.com/youngyangyang04/leetcode/blob/master/problems/0209.长度最小的子数组.md) |数组 |中等| **暴力** **滑动窗口**| -|[0216.组合总和III](https://github.com/youngyangyang04/leetcode/blob/master/problems/0216.组合总和III.md) |数组/回溯 |中等| **回溯算法**| -|[0219.存在重复元素II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0219.存在重复元素II.md) | 哈希表 |简单| **哈希** | -|[0222.完全二叉树的节点个数](https://github.com/youngyangyang04/leetcode/blob/master/problems/0222.完全二叉树的节点个数.md) | 树 |简单| **递归** | -|[0225.用队列实现栈](https://github.com/youngyangyang04/leetcode/blob/master/problems/0225.用队列实现栈.md) | 队列 |简单| **队列** | -|[0226.翻转二叉树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0226.翻转二叉树.md) |二叉树 |简单| **递归** **迭代**| -|[0232.用栈实现队列](https://github.com/youngyangyang04/leetcode/blob/master/problems/0232.用栈实现队列.md) | 栈 |简单| **栈** | -|[0235.二叉搜索树的最近公共祖先](https://github.com/youngyangyang04/leetcode/blob/master/problems/0235.二叉搜索树的最近公共祖先.md) | 二叉搜索树 |简单| **递归** **迭代** | -|[0236.二叉树的最近公共祖先](https://github.com/youngyangyang04/leetcode/blob/master/problems/0236.二叉树的最近公共祖先.md) | 二叉树 |中等| **递归/回溯** 与其说是递归,不如说是回溯| -|[0237.删除链表中的节点](https://github.com/youngyangyang04/leetcode/blob/master/problems/0237.删除链表中的节点.md) |链表 |简单| **原链表移除** **添加虚拟节点** 递归| -|[0239.滑动窗口最大值](https://github.com/youngyangyang04/leetcode/blob/master/problems/0239.滑动窗口最大值.md) |滑动窗口/队列 |困难| **单调队列**| -|[0242.有效的字母异位词](https://github.com/youngyangyang04/leetcode/blob/master/problems/0242.有效的字母异位词.md) |哈希表 |简单| **哈希**| -|[0257.二叉树的所有路径](https://github.com/youngyangyang04/leetcode/blob/master/problems/0257.二叉树的所有路径.md) |树 |简单| **递归/回溯**| -|[0283.移动零](https://github.com/youngyangyang04/leetcode/blob/master/problems/0283.移动零.md) |数组 |简单| **双指针** 和 27.移除元素 一个套路| -|[0300.最长上升子序列](https://github.com/youngyangyang04/leetcode/blob/master/problems/0300.最长上升子序列.md) |动态规划 |中等| **动态规划**| -|[0316.去除重复字母](https://github.com/youngyangyang04/leetcode/blob/master/problems/0316.去除重复字母.md) |贪心/字符串 |中等| **单调栈** 这道题目处理的情况比较多,属于单调栈中的难题| -|[0332.重新安排行程](https://github.com/youngyangyang04/leetcode/blob/master/problems/0332.重新安排行程.md) |深度优先搜索/回溯 |中等| **深度优先搜索/回溯算法**| -|[0343.整数拆分](https://github.com/youngyangyang04/leetcode/blob/master/problems/0343.整数拆分.md) |动态规划/贪心 |中等| **动态规划**| -|[0344.反转字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0344.反转字符串.md) |字符串 |简单| **双指针**| -|[0347.前K个高频元素](https://github.com/youngyangyang04/leetcode/blob/master/problems/0347.前K个高频元素.md) |哈希/堆/优先级队列 |中等| **哈希/优先级队列**| -|[0349.两个数组的交集](https://github.com/youngyangyang04/leetcode/blob/master/problems/0349.两个数组的交集.md) |哈希表 |简单|**哈希**| -|[0350.两个数组的交集II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0350.两个数组的交集II.md) |哈希表 |简单|**哈希**| -|[0377.组合总和Ⅳ](https://github.com/youngyangyang04/leetcode/blob/master/problems/0377.组合总和Ⅳ.md) |动态规划 |中等|**完全背包** 求排列| -|[0383.赎金信](https://github.com/youngyangyang04/leetcode/blob/master/problems/0383.赎金信.md) |数组 |简单|**暴力** **字典计数** **哈希**| -|[0404.左叶子之和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0404.左叶子之和.md) |树/二叉树 |简单|**递归** **迭代**| -|[0406.根据身高重建队列](https://github.com/youngyangyang04/leetcode/blob/master/problems/0406.根据身高重建队列.md) |树/二叉树 |简单|**递归** **迭代**| -|[0416.分割等和子集](https://github.com/youngyangyang04/leetcode/blob/master/problems/0416.分割等和子集.md) |动态规划 |中等|**背包问题/01背包**| -|[0429.N叉树的层序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0429.N叉树的层序遍历.md) |树 |简单|**队列/广度优先搜索**| -|[0434.字符串中的单词数](https://github.com/youngyangyang04/leetcode/blob/master/problems/0434.字符串中的单词数.md) |字符串 |简单|**模拟**| -|[0435.无重叠区间](https://github.com/youngyangyang04/leetcode/blob/master/problems/0435.无重叠区间.md) |贪心 |中等|**贪心** 经典题目,有点难| -|[0450.删除二叉搜索树中的节点](https://github.com/youngyangyang04/leetcode/blob/master/problems/0450.删除二叉搜索树中的节点.md) |树 |中等|**递归**| -|[0452.用最少数量的箭引爆气球](https://github.com/youngyangyang04/leetcode/blob/master/problems/0452.用最少数量的箭引爆气球.md) |贪心/排序 |中等|**贪心** 经典题目| -|[0454.四数相加II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0454.四数相加II.md) |哈希表 |中等| **哈希**| -|[0455.分发饼干](https://github.com/youngyangyang04/leetcode/blob/master/problems/0455.分发饼干.md) |贪心 |简单| **贪心**| -|[0459.重复的子字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0459.重复的子字符串.md) |字符创 |简单| **KMP**| -|[0473.火柴拼正方形](https://github.com/youngyangyang04/leetcode/blob/master/problems/0473.火柴拼正方形.md) |深度优先搜索|中等| **回溯算法** 和698.划分为k个相等的子集差不多| -|[0474.一和零](https://github.com/youngyangyang04/leetcode/blob/master/problems/0474.一和零.md) |动态规划 |中等| **多重背包** 好题目| -|[0486.预测赢家](https://github.com/youngyangyang04/leetcode/blob/master/problems/0486.预测赢家.md) |动态规划 |中等| **递归** **记忆递归** **动态规划**| -|[0491.递增子序列](https://github.com/youngyangyang04/leetcode/blob/master/problems/0491.递增子序列.md) |深度优先搜索 |中等|**深度优先搜索/回溯算法** 这个去重有意思| -|[0496.下一个更大元素I](https://github.com/youngyangyang04/leetcode/blob/master/problems/0496.下一个更大元素I.md) |栈 |中等|**单调栈** 入门题目,但是两个数组还是有点绕的| -|[0501.二叉搜索树中的众数](https://github.com/youngyangyang04/leetcode/blob/master/problems/0501.二叉搜索树中的众数.md) |二叉树 |简单|**递归/中序遍历**| -|[0509.斐波那契数](https://github.com/youngyangyang04/leetcode/blob/master/problems/0509.斐波那契数.md) |数组 |简单|**递归** **动态规划**| -|[0513.找树左下角的值](https://github.com/youngyangyang04/leetcode/blob/master/problems/0513.找树左下角的值.md) |二叉树 |中等|**递归** **迭代**| -|[0515.在每个树行中找最大值](https://github.com/youngyangyang04/leetcode/blob/master/problems/0515.在每个树行中找最大值.md) |二叉树 |简单|**广度优先搜索/队列**| -|[0518.零钱兑换II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0518.零钱兑换II.md) |动态规划 |中等|**动态规划** dp里求组合| -|[0530.二叉搜索树的最小绝对差](https://github.com/youngyangyang04/leetcode/blob/master/problems/0530.二叉搜索树的最小绝对差.md) |二叉树搜索树 |简单|**递归** **迭代**| -|[0538.把二叉搜索树转换为累加树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0538.把二叉搜索树转换为累加树.md) |二叉搜索树 |简单|**递归** **迭代**| -|[0541.反转字符串II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0541.反转字符串II.md) |字符串 |简单| **模拟**| -|[0559.N叉树的最大深度](https://github.com/youngyangyang04/leetcode/blob/master/problems/0559.N叉树的最大深度.md) |N叉树 |简单| **递归**| -|[0572.另一个树的子树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0572.另一个树的子树.md) |二叉树 |简单| **递归**| -|[0575.分糖果](https://github.com/youngyangyang04/leetcode/blob/master/problems/0575.分糖果.md) |哈希表 |简单|**哈希**| -|[0589.N叉树的前序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0589.N叉树的前序遍历.md) |N叉树 |简单|**递归** **栈/迭代**| -|[0590.N叉树的后序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0590.N叉树的后序遍历.md) |N叉树 |简单|**递归** **栈/迭代**| -|[0617.合并二叉树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0617.合并二叉树.md) |树 |简单|**递归** **迭代**| -|[0637.二叉树的层平均值](https://github.com/youngyangyang04/leetcode/blob/master/problems/0637.二叉树的层平均值.md) |树 |简单|**广度优先搜索/队列**| -|[0649.Dota2参议院](https://github.com/youngyangyang04/leetcode/blob/master/problems/0649.Dota2参议院.md) |贪心 |简单|**贪心算法** 简单的贪心策略但代码实现很有技巧| -|[0654.最大二叉树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0654.最大二叉树.md) |树 |中等|**递归**| -|[0685.冗余连接II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0685.冗余连接II.md) | 并查集/树/图 |困难|**并查集**| -|[0669.修剪二叉搜索树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0669.修剪二叉搜索树.md) | 二叉搜索树/二叉树 |简单|**递归** **迭代**| -|[0698.划分为k个相等的子集](https://github.com/youngyangyang04/leetcode/blob/master/problems/0698.划分为k个相等的子集.md) |回溯算法|中等|动态规划 **回溯算法** 这其实是组合问题,使用了两次递归,好题| -|[0700.二叉搜索树中的搜索](https://github.com/youngyangyang04/leetcode/blob/master/problems/0700.二叉搜索树中的搜索.md) |二叉搜索树 |简单|**递归** **迭代**| -|[0701.二叉搜索树中的插入操作](https://github.com/youngyangyang04/leetcode/blob/master/problems/0701.二叉搜索树中的插入操作.md) |二叉搜索树 |简单|**递归** **迭代**| -|[0705.设计哈希集合](https://github.com/youngyangyang04/leetcode/blob/master/problems/0705.设计哈希集合.md) |哈希表 |简单|**模拟**| -|[0707.设计链表](https://github.com/youngyangyang04/leetcode/blob/master/problems/0707.设计链表.md) |链表 |中等|**模拟**| -|[0714.买卖股票的最佳时机含手续费](https://github.com/youngyangyang04/leetcode/blob/master/problems/0714.买卖股票的最佳时机含手续费.md) |贪心 动态规划 |中等|**贪心** **动态规划** 和122.买卖股票的最佳时机II类似,贪心的思路很巧妙| -|[0763.划分字母区间](https://github.com/youngyangyang04/leetcode/blob/master/problems/0763.划分字母区间.md) |贪心 |中等|**双指针/贪心** 体现贪心尽可能多的思想| -|[0738.单调递增的数字](https://github.com/youngyangyang04/leetcode/blob/master/problems/0738.单调递增的数字.md) |贪心算法 |中等|**贪心算法** 思路不错,贪心好题| -|[0739.每日温度](https://github.com/youngyangyang04/leetcode/blob/master/problems/0739.每日温度.md) |栈 |中等|**单调栈** 适合单调栈入门| -|[0746.使用最小花费爬楼梯](https://github.com/youngyangyang04/leetcode/blob/master/problems/0746.使用最小花费爬楼梯.md) |动态规划 |简单|**动态规划** 入门题目和斐波那契额数列类似| -|[0767.重构字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0767.重构字符串.md) |字符串 |中等|**字符串** + 排序+一点贪心| -|[0841.钥匙和房间](https://github.com/youngyangyang04/leetcode/blob/master/problems/0841.钥匙和房间.md) |孤岛问题 |中等|**bfs** **dfs**| -|[0844.比较含退格的字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0844.比较含退格的字符串.md) |字符串 |简单|**栈** **双指针优化** 使用栈的思路但没有必要使用栈| -|[0860.柠檬水找零](https://github.com/youngyangyang04/leetcode/blob/master/problems/0860.柠檬水找零.md) |贪心算法 |简单|**贪心算法** 基础题目| -|[0925.长按键入](https://github.com/youngyangyang04/leetcode/blob/master/problems/0925.长按键入.md) |字符串 |简单|**双指针/模拟** 是一道模拟类型的题目| -|[0941.有效的山脉数组](https://github.com/youngyangyang04/leetcode/blob/master/problems/0941.有效的山脉数组.md) |数组 |简单|**双指针**| -|[0968.监控二叉树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0968.监控二叉树.md) |二叉树 |困难|**贪心** 贪心与二叉树的结合| -|[0973.最接近原点的K个点](https://github.com/youngyangyang04/leetcode/blob/master/problems/0973.最接近原点的K个点.md) |优先级队列 |中等|**优先级队列**| -|[0977.有序数组的平方](https://github.com/youngyangyang04/leetcode/blob/master/problems/0977.有序数组的平方.md) |数组 |中等|**双指针** 还是比较巧妙的| -|[1002.查找常用字符](https://github.com/youngyangyang04/leetcode/blob/master/problems/1002.查找常用字符.md) |栈 |简单|**栈**| -|[1005.K次取反后最大化的数组和](https://github.com/youngyangyang04/leetcode/blob/master/problems/1005.K次取反后最大化的数组和.md) |贪心/排序 |简单|**贪心算法** 贪心基础题目| -|[1047.删除字符串中的所有相邻重复项](https://github.com/youngyangyang04/leetcode/blob/master/problems/1047.删除字符串中的所有相邻重复项.md) |哈希表 |简单|**哈希表/数组**| -|[1049.最后一块石头的重量II](https://github.com/youngyangyang04/leetcode/blob/master/problems/1049.最后一块石头的重量II.md) |动态规划 |中等|**01背包**| -|[1207.独一无二的出现次数](https://github.com/youngyangyang04/leetcode/blob/master/problems/1207.独一无二的出现次数.md) |哈希表 |简单|**哈希** 两层哈希| -|[1221.分割平衡字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/1221.分割平衡字符串.md) |贪心 |简单|**贪心算法** 基础题目| -|[1356.根据数字二进制下1的数目排序](https://github.com/youngyangyang04/leetcode/blob/master/problems/1356.根据数字二进制下1的数目排序.md) |位运算 |简单|**位运算** 巧妙的计算二进制中1的数量| -|[1365.有多少小于当前数字的数字](https://github.com/youngyangyang04/leetcode/blob/master/problems/1365.有多少小于当前数字的数字.md) |数组、哈希表 |简单|**哈希** 从后遍历的技巧很不错| -|[1382.将二叉搜索树变平衡](https://github.com/youngyangyang04/leetcode/blob/master/problems/1047.删除字符串中的所有相邻重复项.md) |二叉搜索树 |中等|**递归** **迭代** 98和108的组合题目| -|[1403.非递增顺序的最小子序列](https://github.com/youngyangyang04/leetcode/blob/master/problems/1403.非递增顺序的最小子序列.md) | 贪心算法|简单|**贪心算法** 贪心基础题目| -|[1518.换酒问题](https://github.com/youngyangyang04/leetcode/blob/master/problems/1518.换酒问题.md) | 贪心算法|简单|**贪心算法** 贪心基础题目| -|[剑指Offer05.替换空格](https://github.com/youngyangyang04/leetcode/blob/master/problems/剑指Offer05.替换空格.md) |字符串 |简单|**双指针**| -|[ 剑指Offer58-I.翻转单词顺序](https://github.com/youngyangyang04/leetcode/blob/master/problems/剑指Offer05.替换空格.md) |字符串 |简单|**模拟/双指针**| -|[剑指Offer58-II.左旋转字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/剑指Offer58-II.左旋转字符串.md) |字符串 |简单|**反转操作**| -|[剑指Offer59-I.滑动窗口的最大值](https://github.com/youngyangyang04/leetcode/blob/master/problems/剑指Offer59-I.滑动窗口的最大值.md) |滑动窗口/队列 |困难|**单调队列**| -|[面试题02.07.链表相交](https://github.com/youngyangyang04/leetcode/blob/master/problems/面试题02.07.链表相交.md) |链表 |简单|**模拟**| - -持续更新中.... - -# 关于作者 - -大家好,我是程序员Carl,哈工大师兄,ACM 校赛、黑龙江省赛、东北四省赛金牌、亚洲区域赛铜牌获得者,先后在腾讯和百度从事后端技术研发,CSDN博客专家。对算法和C++后端技术有一定的见解,利用工作之余重新刷leetcode。 - -**加我的微信,备注:「个人简单介绍」+「组队刷题」**, 拉你进刷题群,每天一道经典题目分析,而且题目不是孤立的,每一道题目之间都是有关系的,都是由浅入深一脉相承的,所以学习效果最好是每篇连续着看,也许之前你会某些知识点,但是一直没有把知识点串起来,这里每天一篇文章就会帮你把知识点串起来。 - - - - -# 我的公众号 - -更多精彩文章持续更新,微信搜索:「代码随想录」第一时间围观,关注后回复:「简历模板」「二叉树总结」「回溯算法总结」「栈与队列总结」等关键字就可以获得我整理的学习资料。 - -**每天8:35准时为你推送一篇经典面试题目,帮你梳理算法知识体系,轻松学习算法!**,并且公众号里有大量学习资源,也有我自己的学习心得和方法总结,更有很多志同道合的好伙伴在这里打卡学习,来看看就你知道了,相信一定会有所收获! - - - - - diff --git a/TinyWebServer/CGImysql/README.md b/TinyWebServer/CGImysql/README.md new file mode 100644 index 0000000000..985523098b --- /dev/null +++ b/TinyWebServer/CGImysql/README.md @@ -0,0 +1,13 @@ + +CGI & 数据库连接池 +=============== +数据库连接池 +> * 单例模式,保证唯一 +> * list实现连接池 +> * 连接池为静态大小 +> * 互斥锁实现线程安全 + +CGI +> * HTTP请求采用POST方式 +> * 登录用户名和密码校验 +> * 用户注册及多线程注册安全 diff --git a/TinyWebServer/CGImysql/sql_connection_pool.cpp b/TinyWebServer/CGImysql/sql_connection_pool.cpp new file mode 100644 index 0000000000..558db0ea10 --- /dev/null +++ b/TinyWebServer/CGImysql/sql_connection_pool.cpp @@ -0,0 +1,147 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "sql_connection_pool.h" + +using namespace std; + +connection_pool::connection_pool() +{ + this->CurConn = 0; + this->FreeConn = 0; +} + +connection_pool *connection_pool::GetInstance() +{ + static connection_pool connPool; + return &connPool; +} + +//构造初始化 +void connection_pool::init(string url, string User, string PassWord, string DBName, int Port, unsigned int MaxConn) +{ + this->url = url; + this->Port = Port; + this->User = User; + this->PassWord = PassWord; + this->DatabaseName = DBName; + + lock.lock(); + for (int i = 0; i < MaxConn; i++) + { + MYSQL *con = NULL; + con = mysql_init(con); + + if (con == NULL) + { + cout << "Error:" << mysql_error(con); + exit(1); + } + con = mysql_real_connect(con, url.c_str(), User.c_str(), PassWord.c_str(), DBName.c_str(), Port, NULL, 0); + + if (con == NULL) + { + cout << "Error: " << mysql_error(con); + exit(1); + } + connList.push_back(con); + ++FreeConn; + } + + reserve = sem(FreeConn); + + this->MaxConn = FreeConn; + + lock.unlock(); +} + + +//当有请求时,从数据库连接池中返回一个可用连接,更新使用和空闲连接数 +MYSQL *connection_pool::GetConnection() +{ + MYSQL *con = NULL; + + if (0 == connList.size()) + return NULL; + + reserve.wait(); + + lock.lock(); + + con = connList.front(); + connList.pop_front(); + + --FreeConn; + ++CurConn; + + lock.unlock(); + return con; +} + +//释放当前使用的连接 +bool connection_pool::ReleaseConnection(MYSQL *con) +{ + if (NULL == con) + return false; + + lock.lock(); + + connList.push_back(con); + ++FreeConn; + --CurConn; + + lock.unlock(); + + reserve.post(); + return true; +} + +//销毁数据库连接池 +void connection_pool::DestroyPool() +{ + + lock.lock(); + if (connList.size() > 0) + { + list::iterator it; + for (it = connList.begin(); it != connList.end(); ++it) + { + MYSQL *con = *it; + mysql_close(con); + } + CurConn = 0; + FreeConn = 0; + connList.clear(); + + lock.unlock(); + } + + lock.unlock(); +} + +//当前空闲的连接数 +int connection_pool::GetFreeConn() +{ + return this->FreeConn; +} + +connection_pool::~connection_pool() +{ + DestroyPool(); +} + +connectionRAII::connectionRAII(MYSQL **SQL, connection_pool *connPool){ + *SQL = connPool->GetConnection(); + + conRAII = *SQL; + poolRAII = connPool; +} + +connectionRAII::~connectionRAII(){ + poolRAII->ReleaseConnection(conRAII); +} \ No newline at end of file diff --git a/TinyWebServer/CGImysql/sql_connection_pool.h b/TinyWebServer/CGImysql/sql_connection_pool.h new file mode 100644 index 0000000000..b2f68cfa19 --- /dev/null +++ b/TinyWebServer/CGImysql/sql_connection_pool.h @@ -0,0 +1,60 @@ +#ifndef _CONNECTION_POOL_ +#define _CONNECTION_POOL_ + +#include +#include +#include +#include +#include +#include +#include +#include "../lock/locker.h" + +using namespace std; + +class connection_pool +{ +public: + MYSQL *GetConnection(); //获取数据库连接 + bool ReleaseConnection(MYSQL *conn); //释放连接 + int GetFreeConn(); //获取连接 + void DestroyPool(); //销毁所有连接 + + //单例模式 + static connection_pool *GetInstance(); + + void init(string url, string User, string PassWord, string DataBaseName, int Port, unsigned int MaxConn); + + connection_pool(); + ~connection_pool(); + +private: + unsigned int MaxConn; //最大连接数 + unsigned int CurConn; //当前已使用的连接数 + unsigned int FreeConn; //当前空闲的连接数 + +private: + locker lock; + list connList; //连接池 + sem reserve; + +private: + string url; //主机地址 + string Port; //数据库端口号 + string User; //登陆数据库用户名 + string PassWord; //登陆数据库密码 + string DatabaseName; //使用数据库名 +}; + +class connectionRAII{ + +public: + connectionRAII(MYSQL **con, connection_pool *connPool); + ~connectionRAII(); + +private: + MYSQL *conRAII; + connection_pool *poolRAII; +}; + +#endif diff --git a/TinyWebServer/README.md b/TinyWebServer/README.md new file mode 100644 index 0000000000..2e884b16ca --- /dev/null +++ b/TinyWebServer/README.md @@ -0,0 +1,137 @@ +Raw_version文档 +=============== +Linux下C++轻量级Web服务器,助力初学者快速实践网络编程,搭建属于自己的服务器. + +* 使用**线程池 + epoll(ET和LT均实现) + 模拟Proactor模式**的并发模型 +* 使用**状态机**解析HTTP请求报文,支持解析**GET和POST**请求 +* 通过访问服务器数据库实现web端用户**注册、登录**功能,可以请求服务器**图片和视频文件** +* 实现**同步/异步日志系统**,记录服务器运行状态 +* 经Webbench压力测试可以实现**上万的并发连接**数据交换 + +基础测试 +------------ +* 服务器测试环境 + * Ubuntu版本16.04 + * MySQL版本5.7.29 +* 浏览器测试环境 + * Windows、Linux均可 + * Chrome + * FireFox + * 其他浏览器暂无测试 + +* 测试前确认已安装MySQL数据库 + + ```C++ + // 建立yourdb库 + create database yourdb; + + // 创建user表 + USE yourdb; + CREATE TABLE user( + username char(50) NULL, + passwd char(50) NULL + )ENGINE=InnoDB; + + // 添加数据 + INSERT INTO user(username, passwd) VALUES('name', 'passwd'); + ``` + +* 修改main.c中的数据库初始化信息 + + ```C++ + // root root修改为服务器数据库的登录名和密码 + // qgydb修改为上述创建的yourdb库名 + connPool->init("localhost", "root", "root", "yourdb", 3306, 8); + ``` + +* 修改http_conn.cpp中的root路径 + + ```C++ + // 修改为root文件夹所在路径 + const char* doc_root="/home/qgy/TinyWebServer/root"; + ``` + +* 生成server + + ```C++ + make server + ``` + +* 启动server + + ```C++ + ./server port + ``` + +* 浏览器端 + + ```C++ + ip:port + ``` + +个性化测试 +------ + +> * I/O复用方式,listenfd和connfd可以使用不同的触发模式,代码中使用LT + LT模式,可以自由修改与搭配. + +- [x] LT + LT模式 + * listenfd触发模式,关闭main.c中listenfdET,打开listenfdLT + + ```C++ + 26 //#define listenfdET //边缘触发非阻塞 + 27 #define listenfdLT //水平触发阻塞 + ``` + * listenfd触发模式,关闭http_conn.cpp中listenfdET,打开listenfdLT + + ```C++ + 10 //#define listenfdET //边缘触发非阻塞 + 11 #define listenfdLT //水平触发阻塞 + ``` + + * connfd触发模式,关闭http_conn.cpp中connfdET,打开connfdLT + + ```C++ + 7 //#define connfdET //边缘触发非阻塞 + 8 #define connfdLT //水平触发阻塞 + ``` + +- [ ] LT + ET模式 + * listenfd触发模式,关闭main.c中listenfdET,打开listenfdLT + + ```C++ + 26 //#define listenfdET //边缘触发非阻塞 + 27 #define listenfdLT //水平触发阻塞 + ``` + + * listenfd触发模式,关闭http_conn.cpp中listenfdET,打开listenfdLT + + ```C++ + 10 //#define listenfdET //边缘触发非阻塞 + 11 #define listenfdLT //水平触发阻塞 + ``` + + * connfd触发模式,打开http_conn.cpp中connfdET,关闭connfdLT + + ```C++ + 7 #define connfdET //边缘触发非阻塞 + 8 //#define connfdLT //水平触发阻塞 + ``` + +> * 日志写入方式,代码中使用同步日志,可以修改为异步写入. + +- [x] 同步写入日志 + * 关闭main.c中ASYNLOG,打开同步写入SYNLOG + + ```C++ + 25 #define SYNLOG //同步写日志 + 26 //#define ASYNLOG /异步写日志 + ``` + +- [ ] 异步写入日志 + * 关闭main.c中SYNLOG,打开异步写入ASYNLOG + + ```C++ + 25 //#define SYNLOG //同步写日志 + 26 #define ASYNLOG /异步写日志 + ``` +* 选择I/O复用方式或日志写入方式后,按照前述生成server,启动server,即可进行测试. \ No newline at end of file diff --git a/TinyWebServer/http/README.md b/TinyWebServer/http/README.md new file mode 100644 index 0000000000..6092b20224 --- /dev/null +++ b/TinyWebServer/http/README.md @@ -0,0 +1,8 @@ + +http连接处理类 +=============== +根据状态转移,通过主从状态机封装了http连接类。其中,主状态机在内部调用从状态机,从状态机将处理状态和数据传给主状态机 +> * 客户端发出http连接请求 +> * 从状态机读取数据,更新自身状态和接收数据,传给主状态机 +> * 主状态机根据从状态机状态,更新自身状态,决定响应请求还是继续读取 + diff --git a/TinyWebServer/http/http_conn.cpp b/TinyWebServer/http/http_conn.cpp new file mode 100644 index 0000000000..362927eac7 --- /dev/null +++ b/TinyWebServer/http/http_conn.cpp @@ -0,0 +1,716 @@ +#include "http_conn.h" +#include "../log/log.h" +#include +#include +#include + +//#define connfdET //边缘触发非阻塞 +#define connfdLT //水平触发阻塞 + +//#define listenfdET //边缘触发非阻塞 +#define listenfdLT //水平触发阻塞 + +//定义http响应的一些状态信息 +const char *ok_200_title = "OK"; +const char *error_400_title = "Bad Request"; +const char *error_400_form = "Your request has bad syntax or is inherently impossible to staisfy.\n"; +const char *error_403_title = "Forbidden"; +const char *error_403_form = "You do not have permission to get file form this server.\n"; +const char *error_404_title = "Not Found"; +const char *error_404_form = "The requested file was not found on this server.\n"; +const char *error_500_title = "Internal Error"; +const char *error_500_form = "There was an unusual problem serving the request file.\n"; + +//当浏览器出现连接重置时,可能是网站根目录出错或http响应格式出错或者访问的文件中内容完全为空 +const char *doc_root = "/home/qgy/github/TinyWebServer/root"; + +//将表中的用户名和密码放入map +map users; +locker m_lock; + +void http_conn::initmysql_result(connection_pool *connPool) +{ + //先从连接池中取一个连接 + MYSQL *mysql = NULL; + connectionRAII mysqlcon(&mysql, connPool); + + //在user表中检索username,passwd数据,浏览器端输入 + if (mysql_query(mysql, "SELECT username,passwd FROM user")) + { + LOG_ERROR("SELECT error:%s\n", mysql_error(mysql)); + } + + //从表中检索完整的结果集 + MYSQL_RES *result = mysql_store_result(mysql); + + //返回结果集中的列数 + int num_fields = mysql_num_fields(result); + + //返回所有字段结构的数组 + MYSQL_FIELD *fields = mysql_fetch_fields(result); + + //从结果集中获取下一行,将对应的用户名和密码,存入map中 + while (MYSQL_ROW row = mysql_fetch_row(result)) + { + string temp1(row[0]); + string temp2(row[1]); + users[temp1] = temp2; + } +} + +//对文件描述符设置非阻塞 +int setnonblocking(int fd) +{ + int old_option = fcntl(fd, F_GETFL); + int new_option = old_option | O_NONBLOCK; + fcntl(fd, F_SETFL, new_option); + return old_option; +} + +//将内核事件表注册读事件,ET模式,选择开启EPOLLONESHOT +void addfd(int epollfd, int fd, bool one_shot) +{ + epoll_event event; + event.data.fd = fd; + +#ifdef connfdET + event.events = EPOLLIN | EPOLLET | EPOLLRDHUP; +#endif + +#ifdef connfdLT + event.events = EPOLLIN | EPOLLRDHUP; +#endif + +#ifdef listenfdET + event.events = EPOLLIN | EPOLLET | EPOLLRDHUP; +#endif + +#ifdef listenfdLT + event.events = EPOLLIN | EPOLLRDHUP; +#endif + + if (one_shot) + event.events |= EPOLLONESHOT; + epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); + setnonblocking(fd); +} + +//从内核时间表删除描述符 +void removefd(int epollfd, int fd) +{ + epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, 0); + close(fd); +} + +//将事件重置为EPOLLONESHOT +void modfd(int epollfd, int fd, int ev) +{ + epoll_event event; + event.data.fd = fd; + +#ifdef connfdET + event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP; +#endif + +#ifdef connfdLT + event.events = ev | EPOLLONESHOT | EPOLLRDHUP; +#endif + + epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event); +} + +int http_conn::m_user_count = 0; +int http_conn::m_epollfd = -1; + +//关闭连接,关闭一个连接,客户总量减一 +void http_conn::close_conn(bool real_close) +{ + if (real_close && (m_sockfd != -1)) + { + removefd(m_epollfd, m_sockfd); + m_sockfd = -1; + m_user_count--; + } +} + +//初始化连接,外部调用初始化套接字地址 +void http_conn::init(int sockfd, const sockaddr_in &addr) +{ + m_sockfd = sockfd; + m_address = addr; + //int reuse=1; + //setsockopt(m_sockfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse)); + addfd(m_epollfd, sockfd, true); + m_user_count++; + init(); +} + +//初始化新接受的连接 +//check_state默认为分析请求行状态 +void http_conn::init() +{ + mysql = NULL; + bytes_to_send = 0; + bytes_have_send = 0; + m_check_state = CHECK_STATE_REQUESTLINE; + m_linger = false; + m_method = GET; + m_url = 0; + m_version = 0; + m_content_length = 0; + m_host = 0; + m_start_line = 0; + m_checked_idx = 0; + m_read_idx = 0; + m_write_idx = 0; + cgi = 0; + memset(m_read_buf, '\0', READ_BUFFER_SIZE); + memset(m_write_buf, '\0', WRITE_BUFFER_SIZE); + memset(m_real_file, '\0', FILENAME_LEN); +} + +//从状态机,用于分析出一行内容 +//返回值为行的读取状态,有LINE_OK,LINE_BAD,LINE_OPEN +http_conn::LINE_STATUS http_conn::parse_line() +{ + char temp; + for (; m_checked_idx < m_read_idx; ++m_checked_idx) + { + temp = m_read_buf[m_checked_idx]; + if (temp == '\r') + { + if ((m_checked_idx + 1) == m_read_idx) + return LINE_OPEN; + else if (m_read_buf[m_checked_idx + 1] == '\n') + { + m_read_buf[m_checked_idx++] = '\0'; + m_read_buf[m_checked_idx++] = '\0'; + return LINE_OK; + } + return LINE_BAD; + } + else if (temp == '\n') + { + if (m_checked_idx > 1 && m_read_buf[m_checked_idx - 1] == '\r') + { + m_read_buf[m_checked_idx - 1] = '\0'; + m_read_buf[m_checked_idx++] = '\0'; + return LINE_OK; + } + return LINE_BAD; + } + } + return LINE_OPEN; +} + +//循环读取客户数据,直到无数据可读或对方关闭连接 +//非阻塞ET工作模式下,需要一次性将数据读完 +bool http_conn::read_once() +{ + if (m_read_idx >= READ_BUFFER_SIZE) + { + return false; + } + int bytes_read = 0; + +#ifdef connfdLT + + bytes_read = recv(m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0); + m_read_idx += bytes_read; + + if (bytes_read <= 0) + { + return false; + } + + return true; + +#endif + +#ifdef connfdET + while (true) + { + bytes_read = recv(m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0); + if (bytes_read == -1) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + break; + return false; + } + else if (bytes_read == 0) + { + return false; + } + m_read_idx += bytes_read; + } + return true; +#endif +} + +//解析http请求行,获得请求方法,目标url及http版本号 +http_conn::HTTP_CODE http_conn::parse_request_line(char *text) +{ + m_url = strpbrk(text, " \t"); + if (!m_url) + { + return BAD_REQUEST; + } + *m_url++ = '\0'; + char *method = text; + if (strcasecmp(method, "GET") == 0) + m_method = GET; + else if (strcasecmp(method, "POST") == 0) + { + m_method = POST; + cgi = 1; + } + else + return BAD_REQUEST; + m_url += strspn(m_url, " \t"); + m_version = strpbrk(m_url, " \t"); + if (!m_version) + return BAD_REQUEST; + *m_version++ = '\0'; + m_version += strspn(m_version, " \t"); + if (strcasecmp(m_version, "HTTP/1.1") != 0) + return BAD_REQUEST; + if (strncasecmp(m_url, "http://", 7) == 0) + { + m_url += 7; + m_url = strchr(m_url, '/'); + } + + if (strncasecmp(m_url, "https://", 8) == 0) + { + m_url += 8; + m_url = strchr(m_url, '/'); + } + + if (!m_url || m_url[0] != '/') + return BAD_REQUEST; + //当url为/时,显示判断界面 + if (strlen(m_url) == 1) + strcat(m_url, "judge.html"); + m_check_state = CHECK_STATE_HEADER; + return NO_REQUEST; +} + +//解析http请求的一个头部信息 +http_conn::HTTP_CODE http_conn::parse_headers(char *text) +{ + if (text[0] == '\0') + { + if (m_content_length != 0) + { + m_check_state = CHECK_STATE_CONTENT; + return NO_REQUEST; + } + return GET_REQUEST; + } + else if (strncasecmp(text, "Connection:", 11) == 0) + { + text += 11; + text += strspn(text, " \t"); + if (strcasecmp(text, "keep-alive") == 0) + { + m_linger = true; + } + } + else if (strncasecmp(text, "Content-length:", 15) == 0) + { + text += 15; + text += strspn(text, " \t"); + m_content_length = atol(text); + } + else if (strncasecmp(text, "Host:", 5) == 0) + { + text += 5; + text += strspn(text, " \t"); + m_host = text; + } + else + { + //printf("oop!unknow header: %s\n",text); + LOG_INFO("oop!unknow header: %s", text); + Log::get_instance()->flush(); + } + return NO_REQUEST; +} + +//判断http请求是否被完整读入 +http_conn::HTTP_CODE http_conn::parse_content(char *text) +{ + if (m_read_idx >= (m_content_length + m_checked_idx)) + { + text[m_content_length] = '\0'; + //POST请求中最后为输入的用户名和密码 + m_string = text; + return GET_REQUEST; + } + return NO_REQUEST; +} + +// +http_conn::HTTP_CODE http_conn::process_read() +{ + LINE_STATUS line_status = LINE_OK; + HTTP_CODE ret = NO_REQUEST; + char *text = 0; + + while ((m_check_state == CHECK_STATE_CONTENT && line_status == LINE_OK) || ((line_status = parse_line()) == LINE_OK)) + { + text = get_line(); + m_start_line = m_checked_idx; + LOG_INFO("%s", text); + Log::get_instance()->flush(); + switch (m_check_state) + { + case CHECK_STATE_REQUESTLINE: + { + ret = parse_request_line(text); + if (ret == BAD_REQUEST) + return BAD_REQUEST; + break; + } + case CHECK_STATE_HEADER: + { + ret = parse_headers(text); + if (ret == BAD_REQUEST) + return BAD_REQUEST; + else if (ret == GET_REQUEST) + { + return do_request(); + } + break; + } + case CHECK_STATE_CONTENT: + { + ret = parse_content(text); + if (ret == GET_REQUEST) + return do_request(); + line_status = LINE_OPEN; + break; + } + default: + return INTERNAL_ERROR; + } + } + return NO_REQUEST; +} + +http_conn::HTTP_CODE http_conn::do_request() +{ + strcpy(m_real_file, doc_root); + int len = strlen(doc_root); + //printf("m_url:%s\n", m_url); + const char *p = strrchr(m_url, '/'); + + //处理cgi + if (cgi == 1 && (*(p + 1) == '2' || *(p + 1) == '3')) + { + + //根据标志判断是登录检测还是注册检测 + char flag = m_url[1]; + + char *m_url_real = (char *)malloc(sizeof(char) * 200); + strcpy(m_url_real, "/"); + strcat(m_url_real, m_url + 2); + strncpy(m_real_file + len, m_url_real, FILENAME_LEN - len - 1); + free(m_url_real); + + //将用户名和密码提取出来 + //user=123&passwd=123 + char name[100], password[100]; + int i; + for (i = 5; m_string[i] != '&'; ++i) + name[i - 5] = m_string[i]; + name[i - 5] = '\0'; + + int j = 0; + for (i = i + 10; m_string[i] != '\0'; ++i, ++j) + password[j] = m_string[i]; + password[j] = '\0'; + + //同步线程登录校验 + if (*(p + 1) == '3') + { + //如果是注册,先检测数据库中是否有重名的 + //没有重名的,进行增加数据 + char *sql_insert = (char *)malloc(sizeof(char) * 200); + strcpy(sql_insert, "INSERT INTO user(username, passwd) VALUES("); + strcat(sql_insert, "'"); + strcat(sql_insert, name); + strcat(sql_insert, "', '"); + strcat(sql_insert, password); + strcat(sql_insert, "')"); + + if (users.find(name) == users.end()) + { + + m_lock.lock(); + int res = mysql_query(mysql, sql_insert); + users.insert(pair(name, password)); + m_lock.unlock(); + + if (!res) + strcpy(m_url, "/log.html"); + else + strcpy(m_url, "/registerError.html"); + } + else + strcpy(m_url, "/registerError.html"); + } + //如果是登录,直接判断 + //若浏览器端输入的用户名和密码在表中可以查找到,返回1,否则返回0 + else if (*(p + 1) == '2') + { + if (users.find(name) != users.end() && users[name] == password) + strcpy(m_url, "/welcome.html"); + else + strcpy(m_url, "/logError.html"); + } + } + + if (*(p + 1) == '0') + { + char *m_url_real = (char *)malloc(sizeof(char) * 200); + strcpy(m_url_real, "/register.html"); + strncpy(m_real_file + len, m_url_real, strlen(m_url_real)); + + free(m_url_real); + } + else if (*(p + 1) == '1') + { + char *m_url_real = (char *)malloc(sizeof(char) * 200); + strcpy(m_url_real, "/log.html"); + strncpy(m_real_file + len, m_url_real, strlen(m_url_real)); + + free(m_url_real); + } + else if (*(p + 1) == '5') + { + char *m_url_real = (char *)malloc(sizeof(char) * 200); + strcpy(m_url_real, "/picture.html"); + strncpy(m_real_file + len, m_url_real, strlen(m_url_real)); + + free(m_url_real); + } + else if (*(p + 1) == '6') + { + char *m_url_real = (char *)malloc(sizeof(char) * 200); + strcpy(m_url_real, "/video.html"); + strncpy(m_real_file + len, m_url_real, strlen(m_url_real)); + + free(m_url_real); + } + else if (*(p + 1) == '7') + { + char *m_url_real = (char *)malloc(sizeof(char) * 200); + strcpy(m_url_real, "/fans.html"); + strncpy(m_real_file + len, m_url_real, strlen(m_url_real)); + + free(m_url_real); + } + else + strncpy(m_real_file + len, m_url, FILENAME_LEN - len - 1); + + if (stat(m_real_file, &m_file_stat) < 0) + return NO_RESOURCE; + if (!(m_file_stat.st_mode & S_IROTH)) + return FORBIDDEN_REQUEST; + if (S_ISDIR(m_file_stat.st_mode)) + return BAD_REQUEST; + int fd = open(m_real_file, O_RDONLY); + m_file_address = (char *)mmap(0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); + return FILE_REQUEST; +} +void http_conn::unmap() +{ + if (m_file_address) + { + munmap(m_file_address, m_file_stat.st_size); + m_file_address = 0; + } +} + +bool http_conn::write() +{ + int temp = 0; + + if (bytes_to_send == 0) + { + modfd(m_epollfd, m_sockfd, EPOLLIN); + init(); + return true; + } + + while (1) + { + temp = writev(m_sockfd, m_iv, m_iv_count); + + if (temp < 0) + { + if (errno == EAGAIN) + { + modfd(m_epollfd, m_sockfd, EPOLLOUT); + return true; + } + unmap(); + return false; + } + + bytes_have_send += temp; + bytes_to_send -= temp; + if (bytes_have_send >= m_iv[0].iov_len) + { + m_iv[0].iov_len = 0; + m_iv[1].iov_base = m_file_address + (bytes_have_send - m_write_idx); + m_iv[1].iov_len = bytes_to_send; + } + else + { + m_iv[0].iov_base = m_write_buf + bytes_have_send; + m_iv[0].iov_len = m_iv[0].iov_len - bytes_have_send; + } + + if (bytes_to_send <= 0) + { + unmap(); + modfd(m_epollfd, m_sockfd, EPOLLIN); + + if (m_linger) + { + init(); + return true; + } + else + { + return false; + } + } + } +} + +bool http_conn::add_response(const char *format, ...) +{ + if (m_write_idx >= WRITE_BUFFER_SIZE) + return false; + va_list arg_list; + va_start(arg_list, format); + int len = vsnprintf(m_write_buf + m_write_idx, WRITE_BUFFER_SIZE - 1 - m_write_idx, format, arg_list); + if (len >= (WRITE_BUFFER_SIZE - 1 - m_write_idx)) + { + va_end(arg_list); + return false; + } + m_write_idx += len; + va_end(arg_list); + LOG_INFO("request:%s", m_write_buf); + Log::get_instance()->flush(); + return true; +} +bool http_conn::add_status_line(int status, const char *title) +{ + return add_response("%s %d %s\r\n", "HTTP/1.1", status, title); +} +bool http_conn::add_headers(int content_len) +{ + add_content_length(content_len); + add_linger(); + add_blank_line(); +} +bool http_conn::add_content_length(int content_len) +{ + return add_response("Content-Length:%d\r\n", content_len); +} +bool http_conn::add_content_type() +{ + return add_response("Content-Type:%s\r\n", "text/html"); +} +bool http_conn::add_linger() +{ + return add_response("Connection:%s\r\n", (m_linger == true) ? "keep-alive" : "close"); +} +bool http_conn::add_blank_line() +{ + return add_response("%s", "\r\n"); +} +bool http_conn::add_content(const char *content) +{ + return add_response("%s", content); +} +bool http_conn::process_write(HTTP_CODE ret) +{ + switch (ret) + { + case INTERNAL_ERROR: + { + add_status_line(500, error_500_title); + add_headers(strlen(error_500_form)); + if (!add_content(error_500_form)) + return false; + break; + } + case BAD_REQUEST: + { + add_status_line(404, error_404_title); + add_headers(strlen(error_404_form)); + if (!add_content(error_404_form)) + return false; + break; + } + case FORBIDDEN_REQUEST: + { + add_status_line(403, error_403_title); + add_headers(strlen(error_403_form)); + if (!add_content(error_403_form)) + return false; + break; + } + case FILE_REQUEST: + { + add_status_line(200, ok_200_title); + if (m_file_stat.st_size != 0) + { + add_headers(m_file_stat.st_size); + m_iv[0].iov_base = m_write_buf; + m_iv[0].iov_len = m_write_idx; + m_iv[1].iov_base = m_file_address; + m_iv[1].iov_len = m_file_stat.st_size; + m_iv_count = 2; + bytes_to_send = m_write_idx + m_file_stat.st_size; + return true; + } + else + { + const char *ok_string = ""; + add_headers(strlen(ok_string)); + if (!add_content(ok_string)) + return false; + } + } + default: + return false; + } + m_iv[0].iov_base = m_write_buf; + m_iv[0].iov_len = m_write_idx; + m_iv_count = 1; + bytes_to_send = m_write_idx; + return true; +} +void http_conn::process() +{ + HTTP_CODE read_ret = process_read(); + if (read_ret == NO_REQUEST) + { + modfd(m_epollfd, m_sockfd, EPOLLIN); + return; + } + bool write_ret = process_write(read_ret); + if (!write_ret) + { + close_conn(); + } + modfd(m_epollfd, m_sockfd, EPOLLOUT); +} diff --git a/TinyWebServer/http/http_conn.h b/TinyWebServer/http/http_conn.h new file mode 100644 index 0000000000..bd6582f8a2 --- /dev/null +++ b/TinyWebServer/http/http_conn.h @@ -0,0 +1,134 @@ +#ifndef HTTPCONNECTION_H +#define HTTPCONNECTION_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../lock/locker.h" +#include "../CGImysql/sql_connection_pool.h" +class http_conn +{ +public: + static const int FILENAME_LEN = 200; + static const int READ_BUFFER_SIZE = 2048; + static const int WRITE_BUFFER_SIZE = 1024; + enum METHOD + { + GET = 0, + POST, + HEAD, + PUT, + DELETE, + TRACE, + OPTIONS, + CONNECT, + PATH + }; + enum CHECK_STATE + { + CHECK_STATE_REQUESTLINE = 0, + CHECK_STATE_HEADER, + CHECK_STATE_CONTENT + }; + enum HTTP_CODE + { + NO_REQUEST, + GET_REQUEST, + BAD_REQUEST, + NO_RESOURCE, + FORBIDDEN_REQUEST, + FILE_REQUEST, + INTERNAL_ERROR, + CLOSED_CONNECTION + }; + enum LINE_STATUS + { + LINE_OK = 0, + LINE_BAD, + LINE_OPEN + }; + +public: + http_conn() {} + ~http_conn() {} + +public: + void init(int sockfd, const sockaddr_in &addr); + void close_conn(bool real_close = true); + void process(); + bool read_once(); + bool write(); + sockaddr_in *get_address() + { + return &m_address; + } + void initmysql_result(connection_pool *connPool); + +private: + void init(); + HTTP_CODE process_read(); + bool process_write(HTTP_CODE ret); + HTTP_CODE parse_request_line(char *text); + HTTP_CODE parse_headers(char *text); + HTTP_CODE parse_content(char *text); + HTTP_CODE do_request(); + char *get_line() { return m_read_buf + m_start_line; }; + LINE_STATUS parse_line(); + void unmap(); + bool add_response(const char *format, ...); + bool add_content(const char *content); + bool add_status_line(int status, const char *title); + bool add_headers(int content_length); + bool add_content_type(); + bool add_content_length(int content_length); + bool add_linger(); + bool add_blank_line(); + +public: + static int m_epollfd; + static int m_user_count; + MYSQL *mysql; + +private: + int m_sockfd; + sockaddr_in m_address; + char m_read_buf[READ_BUFFER_SIZE]; + int m_read_idx; + int m_checked_idx; + int m_start_line; + char m_write_buf[WRITE_BUFFER_SIZE]; + int m_write_idx; + CHECK_STATE m_check_state; + METHOD m_method; + char m_real_file[FILENAME_LEN]; + char *m_url; + char *m_version; + char *m_host; + int m_content_length; + bool m_linger; + char *m_file_address; + struct stat m_file_stat; + struct iovec m_iv[2]; + int m_iv_count; + int cgi; //是否启用的POST + char *m_string; //存储请求头数据 + int bytes_to_send; + int bytes_have_send; +}; + +#endif diff --git a/TinyWebServer/lock/README.md b/TinyWebServer/lock/README.md new file mode 100644 index 0000000000..f2014fbdf8 --- /dev/null +++ b/TinyWebServer/lock/README.md @@ -0,0 +1,12 @@ + +线程同步机制包装类 +=============== +多线程同步,确保任一时刻只能有一个线程能进入关键代码段. +> * 信号量 +> * 互斥锁 +> * 条件变量 + + + + + diff --git a/TinyWebServer/lock/locker.h b/TinyWebServer/lock/locker.h new file mode 100644 index 0000000000..de38a06eaf --- /dev/null +++ b/TinyWebServer/lock/locker.h @@ -0,0 +1,115 @@ +#ifndef LOCKER_H +#define LOCKER_H + +#include +#include +#include + +class sem +{ +public: + sem() + { + if (sem_init(&m_sem, 0, 0) != 0) + { + throw std::exception(); + } + } + sem(int num) + { + if (sem_init(&m_sem, 0, num) != 0) + { + throw std::exception(); + } + } + ~sem() + { + sem_destroy(&m_sem); + } + bool wait() + { + return sem_wait(&m_sem) == 0; + } + bool post() + { + return sem_post(&m_sem) == 0; + } + +private: + sem_t m_sem; +}; +class locker +{ +public: + locker() + { + if (pthread_mutex_init(&m_mutex, NULL) != 0) + { + throw std::exception(); + } + } + ~locker() + { + pthread_mutex_destroy(&m_mutex); + } + bool lock() + { + return pthread_mutex_lock(&m_mutex) == 0; + } + bool unlock() + { + return pthread_mutex_unlock(&m_mutex) == 0; + } + pthread_mutex_t *get() + { + return &m_mutex; + } + +private: + pthread_mutex_t m_mutex; +}; +class cond +{ +public: + cond() + { + if (pthread_cond_init(&m_cond, NULL) != 0) + { + //pthread_mutex_destroy(&m_mutex); + throw std::exception(); + } + } + ~cond() + { + pthread_cond_destroy(&m_cond); + } + bool wait(pthread_mutex_t *m_mutex) + { + int ret = 0; + //pthread_mutex_lock(&m_mutex); + ret = pthread_cond_wait(&m_cond, m_mutex); + //pthread_mutex_unlock(&m_mutex); + return ret == 0; + } + bool timewait(pthread_mutex_t *m_mutex, struct timespec t) + { + int ret = 0; + //pthread_mutex_lock(&m_mutex); + ret = pthread_cond_timedwait(&m_cond, m_mutex, &t); + //pthread_mutex_unlock(&m_mutex); + return ret == 0; + } + bool signal() + { + return pthread_cond_signal(&m_cond) == 0; + } + bool broadcast() + { + return pthread_cond_broadcast(&m_cond) == 0; + } + +private: + //static pthread_mutex_t m_mutex; + pthread_cond_t m_cond; +}; +#endif diff --git a/TinyWebServer/log/README.md b/TinyWebServer/log/README.md new file mode 100644 index 0000000000..8aa2a70c88 --- /dev/null +++ b/TinyWebServer/log/README.md @@ -0,0 +1,11 @@ + +同步/异步日志系统 +=============== +同步/异步日志系统主要涉及了两个模块,一个是日志模块,一个是阻塞队列模块,其中加入阻塞队列模块主要是解决异步写入日志做准备. +> * 自定义阻塞队列 +> * 单例模式创建日志 +> * 同步日志 +> * 异步日志 +> * 实现按天、超行分类 + + diff --git a/TinyWebServer/log/block_queue.h b/TinyWebServer/log/block_queue.h new file mode 100644 index 0000000000..34c77bd507 --- /dev/null +++ b/TinyWebServer/log/block_queue.h @@ -0,0 +1,212 @@ +/************************************************************* +*循环数组实现的阻塞队列,m_back = (m_back + 1) % m_max_size; +*线程安全,每个操作前都要先加互斥锁,操作完后,再解锁 +**************************************************************/ + +#ifndef BLOCK_QUEUE_H +#define BLOCK_QUEUE_H + +#include +#include +#include +#include +#include "../lock/locker.h" +using namespace std; + +template +class block_queue +{ +public: + block_queue(int max_size = 1000) + { + if (max_size <= 0) + { + exit(-1); + } + + m_max_size = max_size; + m_array = new T[max_size]; + m_size = 0; + m_front = -1; + m_back = -1; + } + + void clear() + { + m_mutex.lock(); + m_size = 0; + m_front = -1; + m_back = -1; + m_mutex.unlock(); + } + + ~block_queue() + { + m_mutex.lock(); + if (m_array != NULL) + delete [] m_array; + + m_mutex.unlock(); + } + //判断队列是否满了 + bool full() + { + m_mutex.lock(); + if (m_size >= m_max_size) + { + + m_mutex.unlock(); + return true; + } + m_mutex.unlock(); + return false; + } + //判断队列是否为空 + bool empty() + { + m_mutex.lock(); + if (0 == m_size) + { + m_mutex.unlock(); + return true; + } + m_mutex.unlock(); + return false; + } + //返回队首元素 + bool front(T &value) + { + m_mutex.lock(); + if (0 == m_size) + { + m_mutex.unlock(); + return false; + } + value = m_array[m_front]; + m_mutex.unlock(); + return true; + } + //返回队尾元素 + bool back(T &value) + { + m_mutex.lock(); + if (0 == m_size) + { + m_mutex.unlock(); + return false; + } + value = m_array[m_back]; + m_mutex.unlock(); + return true; + } + + int size() + { + int tmp = 0; + + m_mutex.lock(); + tmp = m_size; + + m_mutex.unlock(); + return tmp; + } + + int max_size() + { + int tmp = 0; + + m_mutex.lock(); + tmp = m_max_size; + + m_mutex.unlock(); + return tmp; + } + //往队列添加元素,需要将所有使用队列的线程先唤醒 + //当有元素push进队列,相当于生产者生产了一个元素 + //若当前没有线程等待条件变量,则唤醒无意义 + bool push(const T &item) + { + + m_mutex.lock(); + if (m_size >= m_max_size) + { + + m_cond.broadcast(); + m_mutex.unlock(); + return false; + } + + m_back = (m_back + 1) % m_max_size; + m_array[m_back] = item; + + m_size++; + + m_cond.broadcast(); + m_mutex.unlock(); + return true; + } + //pop时,如果当前队列没有元素,将会等待条件变量 + bool pop(T &item) + { + + m_mutex.lock(); + while (m_size <= 0) + { + + if (!m_cond.wait(m_mutex.get())) + { + m_mutex.unlock(); + return false; + } + } + + m_front = (m_front + 1) % m_max_size; + item = m_array[m_front]; + m_size--; + m_mutex.unlock(); + return true; + } + + //增加了超时处理 + bool pop(T &item, int ms_timeout) + { + struct timespec t = {0, 0}; + struct timeval now = {0, 0}; + gettimeofday(&now, NULL); + m_mutex.lock(); + if (m_size <= 0) + { + t.tv_sec = now.tv_sec + ms_timeout / 1000; + t.tv_nsec = (ms_timeout % 1000) * 1000; + if (!m_cond.timewait(m_mutex.get(), t)) + { + m_mutex.unlock(); + return false; + } + } + + if (m_size <= 0) + { + m_mutex.unlock(); + return false; + } + + m_front = (m_front + 1) % m_max_size; + item = m_array[m_front]; + m_size--; + m_mutex.unlock(); + return true; + } + +private: + locker m_mutex; + cond m_cond; + + T *m_array; + int m_size; + int m_max_size; + int m_front; + int m_back; +}; + +#endif diff --git a/TinyWebServer/log/log.cpp b/TinyWebServer/log/log.cpp new file mode 100644 index 0000000000..d9cefceadb --- /dev/null +++ b/TinyWebServer/log/log.cpp @@ -0,0 +1,163 @@ +#include +#include +#include +#include +#include "log.h" +#include +using namespace std; + +Log::Log() +{ + m_count = 0; + m_is_async = false; +} + +Log::~Log() +{ + if (m_fp != NULL) + { + fclose(m_fp); + } +} +//异步需要设置阻塞队列的长度,同步不需要设置 +bool Log::init(const char *file_name, int log_buf_size, int split_lines, int max_queue_size) +{ + //如果设置了max_queue_size,则设置为异步 + if (max_queue_size >= 1) + { + m_is_async = true; + m_log_queue = new block_queue(max_queue_size); + pthread_t tid; + //flush_log_thread为回调函数,这里表示创建线程异步写日志 + pthread_create(&tid, NULL, flush_log_thread, NULL); + } + + m_log_buf_size = log_buf_size; + m_buf = new char[m_log_buf_size]; + memset(m_buf, '\0', m_log_buf_size); + m_split_lines = split_lines; + + time_t t = time(NULL); + struct tm *sys_tm = localtime(&t); + struct tm my_tm = *sys_tm; + + + const char *p = strrchr(file_name, '/'); + char log_full_name[256] = {0}; + + if (p == NULL) + { + snprintf(log_full_name, 255, "%d_%02d_%02d_%s", my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday, file_name); + } + else + { + strcpy(log_name, p + 1); + strncpy(dir_name, file_name, p - file_name + 1); + snprintf(log_full_name, 255, "%s%d_%02d_%02d_%s", dir_name, my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday, log_name); + } + + m_today = my_tm.tm_mday; + + m_fp = fopen(log_full_name, "a"); + if (m_fp == NULL) + { + return false; + } + + return true; +} + +void Log::write_log(int level, const char *format, ...) +{ + struct timeval now = {0, 0}; + gettimeofday(&now, NULL); + time_t t = now.tv_sec; + struct tm *sys_tm = localtime(&t); + struct tm my_tm = *sys_tm; + char s[16] = {0}; + switch (level) + { + case 0: + strcpy(s, "[debug]:"); + break; + case 1: + strcpy(s, "[info]:"); + break; + case 2: + strcpy(s, "[warn]:"); + break; + case 3: + strcpy(s, "[erro]:"); + break; + default: + strcpy(s, "[info]:"); + break; + } + //写入一个log,对m_count++, m_split_lines最大行数 + m_mutex.lock(); + m_count++; + + if (m_today != my_tm.tm_mday || m_count % m_split_lines == 0) //everyday log + { + + char new_log[256] = {0}; + fflush(m_fp); + fclose(m_fp); + char tail[16] = {0}; + + snprintf(tail, 16, "%d_%02d_%02d_", my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday); + + if (m_today != my_tm.tm_mday) + { + snprintf(new_log, 255, "%s%s%s", dir_name, tail, log_name); + m_today = my_tm.tm_mday; + m_count = 0; + } + else + { + snprintf(new_log, 255, "%s%s%s.%lld", dir_name, tail, log_name, m_count / m_split_lines); + } + m_fp = fopen(new_log, "a"); + } + + m_mutex.unlock(); + + va_list valst; + va_start(valst, format); + + string log_str; + m_mutex.lock(); + + //写入的具体时间内容格式 + int n = snprintf(m_buf, 48, "%d-%02d-%02d %02d:%02d:%02d.%06ld %s ", + my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday, + my_tm.tm_hour, my_tm.tm_min, my_tm.tm_sec, now.tv_usec, s); + + int m = vsnprintf(m_buf + n, m_log_buf_size - 1, format, valst); + m_buf[n + m] = '\n'; + m_buf[n + m + 1] = '\0'; + log_str = m_buf; + + m_mutex.unlock(); + + if (m_is_async && !m_log_queue->full()) + { + m_log_queue->push(log_str); + } + else + { + m_mutex.lock(); + fputs(log_str.c_str(), m_fp); + m_mutex.unlock(); + } + + va_end(valst); +} + +void Log::flush(void) +{ + m_mutex.lock(); + //强制刷新写入流缓冲区 + fflush(m_fp); + m_mutex.unlock(); +} diff --git a/TinyWebServer/log/log.h b/TinyWebServer/log/log.h new file mode 100644 index 0000000000..f17b30d805 --- /dev/null +++ b/TinyWebServer/log/log.h @@ -0,0 +1,69 @@ +#ifndef LOG_H +#define LOG_H + +#include +#include +#include +#include +#include +#include "block_queue.h" + +using namespace std; + +class Log +{ +public: + //C++11以后,使用局部变量懒汉不用加锁 + static Log *get_instance() + { + static Log instance; + return &instance; + } + + static void *flush_log_thread(void *args) + { + Log::get_instance()->async_write_log(); + } + //可选择的参数有日志文件、日志缓冲区大小、最大行数以及最长日志条队列 + bool init(const char *file_name, int log_buf_size = 8192, int split_lines = 5000000, int max_queue_size = 0); + + void write_log(int level, const char *format, ...); + + void flush(void); + +private: + Log(); + virtual ~Log(); + void *async_write_log() + { + string single_log; + //从阻塞队列中取出一个日志string,写入文件 + while (m_log_queue->pop(single_log)) + { + m_mutex.lock(); + fputs(single_log.c_str(), m_fp); + m_mutex.unlock(); + } + } + +private: + char dir_name[128]; //路径名 + char log_name[128]; //log文件名 + int m_split_lines; //日志最大行数 + int m_log_buf_size; //日志缓冲区大小 + long long m_count; //日志行数记录 + int m_today; //因为按天分类,记录当前时间是那一天 + FILE *m_fp; //打开log的文件指针 + char *m_buf; + block_queue *m_log_queue; //阻塞队列 + bool m_is_async; //是否同步标志位 + locker m_mutex; +}; + + +#define LOG_DEBUG(format, ...) Log::get_instance()->write_log(0, format, ##__VA_ARGS__) +#define LOG_INFO(format, ...) Log::get_instance()->write_log(1, format, ##__VA_ARGS__) +#define LOG_WARN(format, ...) Log::get_instance()->write_log(2, format, ##__VA_ARGS__) +#define LOG_ERROR(format, ...) Log::get_instance()->write_log(3, format, ##__VA_ARGS__) + +#endif diff --git a/TinyWebServer/main.c b/TinyWebServer/main.c new file mode 100644 index 0000000000..c7154e08fe --- /dev/null +++ b/TinyWebServer/main.c @@ -0,0 +1,370 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "./lock/locker.h" +#include "./threadpool/threadpool.h" +#include "./timer/lst_timer.h" +#include "./http/http_conn.h" +#include "./log/log.h" +#include "./CGImysql/sql_connection_pool.h" + +#define MAX_FD 65536 //最大文件描述符 +#define MAX_EVENT_NUMBER 10000 //最大事件数 +#define TIMESLOT 5 //最小超时单位 + +#define SYNLOG //同步写日志 +//#define ASYNLOG //异步写日志 + +//#define listenfdET //边缘触发非阻塞 +#define listenfdLT //水平触发阻塞 + +//这三个函数在http_conn.cpp中定义,改变链接属性 +extern int addfd(int epollfd, int fd, bool one_shot); +extern int remove(int epollfd, int fd); +extern int setnonblocking(int fd); + +//设置定时器相关参数 +static int pipefd[2]; +static sort_timer_lst timer_lst; +static int epollfd = 0; + +//信号处理函数 +void sig_handler(int sig) +{ + //为保证函数的可重入性,保留原来的errno + int save_errno = errno; + int msg = sig; + send(pipefd[1], (char *)&msg, 1, 0); + errno = save_errno; +} + +//设置信号函数 +void addsig(int sig, void(handler)(int), bool restart = true) +{ + struct sigaction sa; + memset(&sa, '\0', sizeof(sa)); + sa.sa_handler = handler; + if (restart) + sa.sa_flags |= SA_RESTART; + sigfillset(&sa.sa_mask); + assert(sigaction(sig, &sa, NULL) != -1); +} + +//定时处理任务,重新定时以不断触发SIGALRM信号 +void timer_handler() +{ + timer_lst.tick(); + alarm(TIMESLOT); +} + +//定时器回调函数,删除非活动连接在socket上的注册事件,并关闭 +void cb_func(client_data *user_data) +{ + epoll_ctl(epollfd, EPOLL_CTL_DEL, user_data->sockfd, 0); + assert(user_data); + close(user_data->sockfd); + http_conn::m_user_count--; + LOG_INFO("close fd %d", user_data->sockfd); + Log::get_instance()->flush(); +} + +void show_error(int connfd, const char *info) +{ + printf("%s", info); + send(connfd, info, strlen(info), 0); + close(connfd); +} + +int main(int argc, char *argv[]) +{ +#ifdef ASYNLOG + Log::get_instance()->init("ServerLog", 2000, 800000, 8); //异步日志模型 +#endif + +#ifdef SYNLOG + Log::get_instance()->init("ServerLog", 2000, 800000, 0); //同步日志模型 +#endif + + if (argc <= 1) + { + printf("usage: %s ip_address port_number\n", basename(argv[0])); + return 1; + } + + int port = atoi(argv[1]); + + addsig(SIGPIPE, SIG_IGN); + + //创建数据库连接池 + connection_pool *connPool = connection_pool::GetInstance(); + connPool->init("localhost", "root", "root", "qgydb", 3306, 8); + + //创建线程池 + threadpool *pool = NULL; + try + { + pool = new threadpool(connPool); + } + catch (...) + { + return 1; + } + + http_conn *users = new http_conn[MAX_FD]; + assert(users); + + //初始化数据库读取表 + users->initmysql_result(connPool); + + int listenfd = socket(PF_INET, SOCK_STREAM, 0); + assert(listenfd >= 0); + + //struct linger tmp={1,0}; + //SO_LINGER若有数据待发送,延迟关闭 + //setsockopt(listenfd,SOL_SOCKET,SO_LINGER,&tmp,sizeof(tmp)); + + int ret = 0; + struct sockaddr_in address; + bzero(&address, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_ANY); + address.sin_port = htons(port); + + int flag = 1; + setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)); + ret = bind(listenfd, (struct sockaddr *)&address, sizeof(address)); + assert(ret >= 0); + ret = listen(listenfd, 5); + assert(ret >= 0); + + //创建内核事件表 + epoll_event events[MAX_EVENT_NUMBER]; + epollfd = epoll_create(5); + assert(epollfd != -1); + + addfd(epollfd, listenfd, false); + http_conn::m_epollfd = epollfd; + + //创建管道 + ret = socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd); + assert(ret != -1); + setnonblocking(pipefd[1]); + addfd(epollfd, pipefd[0], false); + + addsig(SIGALRM, sig_handler, false); + addsig(SIGTERM, sig_handler, false); + bool stop_server = false; + + client_data *users_timer = new client_data[MAX_FD]; + + bool timeout = false; + alarm(TIMESLOT); + + while (!stop_server) + { + int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1); + if (number < 0 && errno != EINTR) + { + LOG_ERROR("%s", "epoll failure"); + break; + } + + for (int i = 0; i < number; i++) + { + int sockfd = events[i].data.fd; + + //处理新到的客户连接 + if (sockfd == listenfd) + { + struct sockaddr_in client_address; + socklen_t client_addrlength = sizeof(client_address); +#ifdef listenfdLT + int connfd = accept(listenfd, (struct sockaddr *)&client_address, &client_addrlength); + if (connfd < 0) + { + LOG_ERROR("%s:errno is:%d", "accept error", errno); + continue; + } + if (http_conn::m_user_count >= MAX_FD) + { + show_error(connfd, "Internal server busy"); + LOG_ERROR("%s", "Internal server busy"); + continue; + } + users[connfd].init(connfd, client_address); + + //初始化client_data数据 + //创建定时器,设置回调函数和超时时间,绑定用户数据,将定时器添加到链表中 + users_timer[connfd].address = client_address; + users_timer[connfd].sockfd = connfd; + util_timer *timer = new util_timer; + timer->user_data = &users_timer[connfd]; + timer->cb_func = cb_func; + time_t cur = time(NULL); + timer->expire = cur + 3 * TIMESLOT; + users_timer[connfd].timer = timer; + timer_lst.add_timer(timer); +#endif + +#ifdef listenfdET + while (1) + { + int connfd = accept(listenfd, (struct sockaddr *)&client_address, &client_addrlength); + if (connfd < 0) + { + LOG_ERROR("%s:errno is:%d", "accept error", errno); + break; + } + if (http_conn::m_user_count >= MAX_FD) + { + show_error(connfd, "Internal server busy"); + LOG_ERROR("%s", "Internal server busy"); + break; + } + users[connfd].init(connfd, client_address); + + //初始化client_data数据 + //创建定时器,设置回调函数和超时时间,绑定用户数据,将定时器添加到链表中 + users_timer[connfd].address = client_address; + users_timer[connfd].sockfd = connfd; + util_timer *timer = new util_timer; + timer->user_data = &users_timer[connfd]; + timer->cb_func = cb_func; + time_t cur = time(NULL); + timer->expire = cur + 3 * TIMESLOT; + users_timer[connfd].timer = timer; + timer_lst.add_timer(timer); + } + continue; +#endif + } + + else if (events[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) + { + //服务器端关闭连接,移除对应的定时器 + util_timer *timer = users_timer[sockfd].timer; + timer->cb_func(&users_timer[sockfd]); + + if (timer) + { + timer_lst.del_timer(timer); + } + } + + //处理信号 + else if ((sockfd == pipefd[0]) && (events[i].events & EPOLLIN)) + { + int sig; + char signals[1024]; + ret = recv(pipefd[0], signals, sizeof(signals), 0); + if (ret == -1) + { + continue; + } + else if (ret == 0) + { + continue; + } + else + { + for (int i = 0; i < ret; ++i) + { + switch (signals[i]) + { + case SIGALRM: + { + timeout = true; + break; + } + case SIGTERM: + { + stop_server = true; + } + } + } + } + } + + //处理客户连接上接收到的数据 + else if (events[i].events & EPOLLIN) + { + util_timer *timer = users_timer[sockfd].timer; + if (users[sockfd].read_once()) + { + LOG_INFO("deal with the client(%s)", inet_ntoa(users[sockfd].get_address()->sin_addr)); + Log::get_instance()->flush(); + //若监测到读事件,将该事件放入请求队列 + pool->append(users + sockfd); + + //若有数据传输,则将定时器往后延迟3个单位 + //并对新的定时器在链表上的位置进行调整 + if (timer) + { + time_t cur = time(NULL); + timer->expire = cur + 3 * TIMESLOT; + LOG_INFO("%s", "adjust timer once"); + Log::get_instance()->flush(); + timer_lst.adjust_timer(timer); + } + } + else + { + timer->cb_func(&users_timer[sockfd]); + if (timer) + { + timer_lst.del_timer(timer); + } + } + } + else if (events[i].events & EPOLLOUT) + { + util_timer *timer = users_timer[sockfd].timer; + if (users[sockfd].write()) + { + LOG_INFO("send data to the client(%s)", inet_ntoa(users[sockfd].get_address()->sin_addr)); + Log::get_instance()->flush(); + + //若有数据传输,则将定时器往后延迟3个单位 + //并对新的定时器在链表上的位置进行调整 + if (timer) + { + time_t cur = time(NULL); + timer->expire = cur + 3 * TIMESLOT; + LOG_INFO("%s", "adjust timer once"); + Log::get_instance()->flush(); + timer_lst.adjust_timer(timer); + } + } + else + { + timer->cb_func(&users_timer[sockfd]); + if (timer) + { + timer_lst.del_timer(timer); + } + } + } + } + if (timeout) + { + timer_handler(); + timeout = false; + } + } + close(epollfd); + close(listenfd); + close(pipefd[1]); + close(pipefd[0]); + delete[] users; + delete[] users_timer; + delete pool; + return 0; +} diff --git a/TinyWebServer/makefile b/TinyWebServer/makefile new file mode 100644 index 0000000000..5a1ee2b81a --- /dev/null +++ b/TinyWebServer/makefile @@ -0,0 +1,6 @@ +server: main.c ./threadpool/threadpool.h ./http/http_conn.cpp ./http/http_conn.h ./lock/locker.h ./log/log.cpp ./log/log.h ./log/block_queue.h ./CGImysql/sql_connection_pool.cpp ./CGImysql/sql_connection_pool.h + g++ -o server main.c ./threadpool/threadpool.h ./http/http_conn.cpp ./http/http_conn.h ./lock/locker.h ./log/log.cpp ./log/log.h ./CGImysql/sql_connection_pool.cpp ./CGImysql/sql_connection_pool.h -lpthread -lmysqlclient + + +clean: + rm -r server diff --git a/TinyWebServer/root/README.md b/TinyWebServer/root/README.md new file mode 100644 index 0000000000..d6fdb70c5b --- /dev/null +++ b/TinyWebServer/root/README.md @@ -0,0 +1,10 @@ +界面跳转 +=============== +对html中action行为设置标志位,将method设置为POST +> * 0 注册 +> * 1 登录 +> * 2 登录检测 +> * 3 注册检测 +> * 5 请求图片 +> * 6 请求视频 +> * 7 关注我 diff --git a/TinyWebServer/root/fans.html b/TinyWebServer/root/fans.html new file mode 100644 index 0000000000..b7c6179c62 --- /dev/null +++ b/TinyWebServer/root/fans.html @@ -0,0 +1,15 @@ + + + + + awsl + +
+
+
嘿嘿,你来啦,更多资料,请关注 “两猿社” 喔.
+
+ +
+
+
+ diff --git a/TinyWebServer/root/favicon.ico b/TinyWebServer/root/favicon.ico new file mode 100644 index 0000000000..01de9ff379 Binary files /dev/null and b/TinyWebServer/root/favicon.ico differ diff --git a/TinyWebServer/root/frame.jpg b/TinyWebServer/root/frame.jpg new file mode 100644 index 0000000000..1d2b523364 Binary files /dev/null and b/TinyWebServer/root/frame.jpg differ diff --git a/TinyWebServer/root/judge.html b/TinyWebServer/root/judge.html new file mode 100644 index 0000000000..82fc2d7d44 --- /dev/null +++ b/TinyWebServer/root/judge.html @@ -0,0 +1,26 @@ + + + + + WebServer + + +
+
+
欢迎访问
+
+
+
+
+
+
+
+
+
+ + + + + + + diff --git a/TinyWebServer/root/log.html b/TinyWebServer/root/log.html new file mode 100644 index 0000000000..b0d95e0e9e --- /dev/null +++ b/TinyWebServer/root/log.html @@ -0,0 +1,21 @@ + + + + + Sign in + + +
+
+
登录
+
+ + + + diff --git a/TinyWebServer/root/logError.html b/TinyWebServer/root/logError.html new file mode 100644 index 0000000000..a679db85d5 --- /dev/null +++ b/TinyWebServer/root/logError.html @@ -0,0 +1,23 @@ + + + + + Sign in + + +
+
+
登录
+
+ + + + diff --git a/TinyWebServer/root/login.gif b/TinyWebServer/root/login.gif new file mode 100644 index 0000000000..1aa39c2eb6 Binary files /dev/null and b/TinyWebServer/root/login.gif differ diff --git a/TinyWebServer/root/loginnew.gif b/TinyWebServer/root/loginnew.gif new file mode 100644 index 0000000000..9f9e78d2c9 Binary files /dev/null and b/TinyWebServer/root/loginnew.gif differ diff --git a/TinyWebServer/root/picture.gif b/TinyWebServer/root/picture.gif new file mode 100644 index 0000000000..297a434005 Binary files /dev/null and b/TinyWebServer/root/picture.gif differ diff --git a/TinyWebServer/root/picture.html b/TinyWebServer/root/picture.html new file mode 100644 index 0000000000..163b4926f5 --- /dev/null +++ b/TinyWebServer/root/picture.html @@ -0,0 +1,15 @@ + + + + + awsl + +
+
+
你居然想看图,不想关注我
+
+ +
+
+
+ diff --git a/TinyWebServer/root/register.gif b/TinyWebServer/root/register.gif new file mode 100644 index 0000000000..296a3b03c2 Binary files /dev/null and b/TinyWebServer/root/register.gif differ diff --git a/TinyWebServer/root/register.html b/TinyWebServer/root/register.html new file mode 100644 index 0000000000..4fd8b9eabc --- /dev/null +++ b/TinyWebServer/root/register.html @@ -0,0 +1,20 @@ + + + + + Sign up + + +
+
+
注册
+
+ + + diff --git a/TinyWebServer/root/registerError.html b/TinyWebServer/root/registerError.html new file mode 100644 index 0000000000..9b7df516dc --- /dev/null +++ b/TinyWebServer/root/registerError.html @@ -0,0 +1,22 @@ + + + + + Sign up + + +
+
+
注册
+
+ + + + diff --git a/TinyWebServer/root/registernew.gif b/TinyWebServer/root/registernew.gif new file mode 100644 index 0000000000..47d06da053 Binary files /dev/null and b/TinyWebServer/root/registernew.gif differ diff --git a/TinyWebServer/root/test1.jpg b/TinyWebServer/root/test1.jpg new file mode 100644 index 0000000000..856aa8f800 Binary files /dev/null and b/TinyWebServer/root/test1.jpg differ diff --git a/TinyWebServer/root/video.gif b/TinyWebServer/root/video.gif new file mode 100644 index 0000000000..08ec1c6cb3 Binary files /dev/null and b/TinyWebServer/root/video.gif differ diff --git a/TinyWebServer/root/video.html b/TinyWebServer/root/video.html new file mode 100644 index 0000000000..dcc63e02e6 --- /dev/null +++ b/TinyWebServer/root/video.html @@ -0,0 +1,17 @@ + + + + + awsl + +
+
+
你居然想看视频,不想关注我
+
+ +
+
+
+ diff --git a/TinyWebServer/root/welcome.html b/TinyWebServer/root/welcome.html new file mode 100644 index 0000000000..d37d168d81 --- /dev/null +++ b/TinyWebServer/root/welcome.html @@ -0,0 +1,27 @@ + + + + + WebServer + + +
+
+
是时候做出选择了
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + diff --git a/TinyWebServer/root/xxx.jpg b/TinyWebServer/root/xxx.jpg new file mode 100644 index 0000000000..1b118396fd Binary files /dev/null and b/TinyWebServer/root/xxx.jpg differ diff --git a/TinyWebServer/root/xxx.mp4 b/TinyWebServer/root/xxx.mp4 new file mode 100644 index 0000000000..4f4fbba54c Binary files /dev/null and b/TinyWebServer/root/xxx.mp4 differ diff --git a/TinyWebServer/test_presure/README.md b/TinyWebServer/test_presure/README.md new file mode 100644 index 0000000000..f0a2bd3463 --- /dev/null +++ b/TinyWebServer/test_presure/README.md @@ -0,0 +1,36 @@ + + +服务器压力测试 +=============== +Webbench是有名的网站压力测试工具,它是由[Lionbridge](http://www.lionbridge.com)公司开发。 + +> * 测试处在相同硬件上,不同服务的性能以及不同硬件上同一个服务的运行状况。 +> * 展示服务器的两项内容:每秒钟响应请求数和每秒钟传输数据量。 + + + + +测试规则 +------------ +* 测试示例 + + ```C++ + webbench -c 500 -t 30 http://127.0.0.1/phpionfo.php + ``` +* 参数 + +> * `-c` 表示客户端数 +> * `-t` 表示时间 + + +测试结果 +--------- +Webbench对服务器进行压力测试,经压力测试可以实现上万的并发连接. +> * 并发连接总数:10500 +> * 访问服务器时间:5s +> * 每秒钟响应请求数:552852 pages/min +> * 每秒钟传输数据量:1031990 bytes/sec +> * 所有访问均成功 + +
+ diff --git a/TinyWebServer/test_presure/webbench-1.5/COPYRIGHT b/TinyWebServer/test_presure/webbench-1.5/COPYRIGHT new file mode 100644 index 0000000000..9060ce8208 --- /dev/null +++ b/TinyWebServer/test_presure/webbench-1.5/COPYRIGHT @@ -0,0 +1 @@ +debian/copyright \ No newline at end of file diff --git a/TinyWebServer/test_presure/webbench-1.5/ChangeLog b/TinyWebServer/test_presure/webbench-1.5/ChangeLog new file mode 100644 index 0000000000..d526672ce2 --- /dev/null +++ b/TinyWebServer/test_presure/webbench-1.5/ChangeLog @@ -0,0 +1 @@ +debian/changelog \ No newline at end of file diff --git a/TinyWebServer/test_presure/webbench-1.5/Makefile b/TinyWebServer/test_presure/webbench-1.5/Makefile new file mode 100644 index 0000000000..cc60b68284 --- /dev/null +++ b/TinyWebServer/test_presure/webbench-1.5/Makefile @@ -0,0 +1,40 @@ +CFLAGS?= -Wall -ggdb -W -O +CC?= gcc +LIBS?= +LDFLAGS?= +PREFIX?= /usr/local +VERSION=1.5 +TMPDIR=/tmp/webbench-$(VERSION) + +all: webbench tags + +tags: *.c + -ctags *.c + +install: webbench + install -s webbench $(DESTDIR)$(PREFIX)/bin + install -m 644 webbench.1 $(DESTDIR)$(PREFIX)/man/man1 + install -d $(DESTDIR)$(PREFIX)/share/doc/webbench + install -m 644 debian/copyright $(DESTDIR)$(PREFIX)/share/doc/webbench + install -m 644 debian/changelog $(DESTDIR)$(PREFIX)/share/doc/webbench + +webbench: webbench.o Makefile + $(CC) $(CFLAGS) $(LDFLAGS) -o webbench webbench.o $(LIBS) + +clean: + -rm -f *.o webbench *~ core *.core tags + +tar: clean + -debian/rules clean + rm -rf $(TMPDIR) + install -d $(TMPDIR) + cp -p Makefile webbench.c socket.c webbench.1 $(TMPDIR) + install -d $(TMPDIR)/debian + -cp -p debian/* $(TMPDIR)/debian + ln -sf debian/copyright $(TMPDIR)/COPYRIGHT + ln -sf debian/changelog $(TMPDIR)/ChangeLog + -cd $(TMPDIR) && cd .. && tar cozf webbench-$(VERSION).tar.gz webbench-$(VERSION) + +webbench.o: webbench.c socket.c Makefile + +.PHONY: clean install all tar diff --git a/TinyWebServer/test_presure/webbench-1.5/debian/changelog b/TinyWebServer/test_presure/webbench-1.5/debian/changelog new file mode 100644 index 0000000000..292178807c --- /dev/null +++ b/TinyWebServer/test_presure/webbench-1.5/debian/changelog @@ -0,0 +1,56 @@ +webbench (1.5) unstable; urgency=low + + * allow building with both Gnu and BSD make + + -- Radim Kolar Fri, Jun 25 12:00:20 CEST 2004 + +webbench (1.4) unstable; urgency=low + + * check if url is not too long + * report correct program version number + * use yield() when waiting for test start + * corrected error codes + * check availability of test server first + * do not abort test if first request failed + * report when some childrens are dead. + * use alarm, not time() for lower syscal use by bench + * use mode 644 for installed doc + * makefile cleaned for better freebsd ports integration + + -- Radim Kolar Thu, 15 Jan 2004 11:15:52 +0100 + +webbench (1.3) unstable; urgency=low + + * Build fixes for freeBSD + * Default benchmark time 60 -> 30 + * generate tar with subdirectory + * added to freeBSD ports collection + + -- Radim Kolar Mon, 12 Jan 2004 17:00:24 +0100 + +webbench (1.2) unstable; urgency=low + + * Only debian-related bugfixes + * Updated Debian/rules + * Adapted to fit new directory system + * moved from debstd to dh_* + + -- Radim Kolar Fri, 18 Jan 2002 12:33:04 +0100 + +webbench (1.1) unstable; urgency=medium + + * Program debianized + * added support for multiple methods (GET, HEAD, OPTIONS, TRACE) + * added support for multiple HTTP versions (0.9 -- 1.1) + * added long options + * added multiple clients + * wait for start of second before test + * test time can be specified + * better error checking when reading reply from server + * FIX: tests was one second longer than expected + + -- Radim Kolar Thu, 16 Sep 1999 18:48:00 +0200 + +Local variables: +mode: debian-changelog +End: diff --git a/TinyWebServer/test_presure/webbench-1.5/debian/control b/TinyWebServer/test_presure/webbench-1.5/debian/control new file mode 100644 index 0000000000..e1051f2960 --- /dev/null +++ b/TinyWebServer/test_presure/webbench-1.5/debian/control @@ -0,0 +1,14 @@ +Source: webbench +Section: web +Priority: extra +Maintainer: Radim Kolar +Build-Depends: debhelper (>> 3.0.0) +Standards-Version: 3.5.2 + +Package: webbench +Architecture: any +Depends: ${shlibs:Depends} +Description: Simple forking Web benchmark + webbench is very simple program for benchmarking WWW or Proxy servers. + Uses fork() for simulating multiple clients load. Can use HTTP 0.9 - 1.1 + requests, but Keep-Alive connections are not supported. diff --git a/TinyWebServer/test_presure/webbench-1.5/debian/copyright b/TinyWebServer/test_presure/webbench-1.5/debian/copyright new file mode 100644 index 0000000000..7c42aa26f1 --- /dev/null +++ b/TinyWebServer/test_presure/webbench-1.5/debian/copyright @@ -0,0 +1,6 @@ +Webbench was written by Radim Kolar 1997-2004 (hsn@netmag.cz). + +UNIX sockets code (socket.c) taken from popclient 1.5 4/1/94 +public domain code, created by Virginia Tech Computing Center. + +Copyright: GPL (see /usr/share/common-licenses/GPL) diff --git a/TinyWebServer/test_presure/webbench-1.5/debian/dirs b/TinyWebServer/test_presure/webbench-1.5/debian/dirs new file mode 100644 index 0000000000..1e881eda3a --- /dev/null +++ b/TinyWebServer/test_presure/webbench-1.5/debian/dirs @@ -0,0 +1 @@ +usr/bin \ No newline at end of file diff --git a/TinyWebServer/test_presure/webbench-1.5/debian/rules b/TinyWebServer/test_presure/webbench-1.5/debian/rules new file mode 100644 index 0000000000..e6803a1123 --- /dev/null +++ b/TinyWebServer/test_presure/webbench-1.5/debian/rules @@ -0,0 +1,64 @@ +#!/usr/bin/make -f +# Sample debian/rules that uses debhelper. +# GNU copyright 1997 to 1999 by Joey Hess. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +# This is the debhelper compatability version to use. +export DH_COMPAT=3 + +configure: configure-stamp +configure-stamp: + dh_testdir + touch configure-stamp + +build: configure-stamp build-stamp +build-stamp: + dh_testdir + $(MAKE) + touch build-stamp + +clean: + dh_testdir + rm -f build-stamp configure-stamp + + # Add here commands to clean up after the build process. + -$(MAKE) clean + + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + + # Add here commands to install the package into debian/webbench. + $(MAKE) install DESTDIR=$(CURDIR)/debian/webbench + + +# Build architecture-independent files here. +binary-indep: build install +# We have nothing to do by default. + +# Build architecture-dependent files here. +binary-arch: build install + dh_testdir + dh_testroot + dh_installdocs + dh_installman webbench.1 + dh_installchangelogs + dh_link + dh_strip + dh_compress + dh_fixperms +# dh_makeshlibs + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install configure diff --git a/TinyWebServer/test_presure/webbench-1.5/socket.c b/TinyWebServer/test_presure/webbench-1.5/socket.c new file mode 100644 index 0000000000..b7af54a0ee --- /dev/null +++ b/TinyWebServer/test_presure/webbench-1.5/socket.c @@ -0,0 +1,58 @@ +/* $Id: socket.c 1.1 1995/01/01 07:11:14 cthuang Exp $ + * + * This module has been modified by Radim Kolar for OS/2 emx + */ + +/*********************************************************************** + module: socket.c + program: popclient + SCCS ID: @(#)socket.c 1.5 4/1/94 + programmer: Virginia Tech Computing Center + compiler: DEC RISC C compiler (Ultrix 4.1) + environment: DEC Ultrix 4.3 + description: UNIX sockets code. + ***********************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int Socket(const char *host, int clientPort) +{ + int sock; + unsigned long inaddr; + struct sockaddr_in ad; + struct hostent *hp; + + memset(&ad, 0, sizeof(ad)); + ad.sin_family = AF_INET; + + inaddr = inet_addr(host); + if (inaddr != INADDR_NONE) + memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr)); + else + { + hp = gethostbyname(host); + if (hp == NULL) + return -1; + memcpy(&ad.sin_addr, hp->h_addr, hp->h_length); + } + ad.sin_port = htons(clientPort); + + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) + return sock; + if (connect(sock, (struct sockaddr *)&ad, sizeof(ad)) < 0) + return -1; + return sock; +} + diff --git a/TinyWebServer/test_presure/webbench-1.5/tags b/TinyWebServer/test_presure/webbench-1.5/tags new file mode 100644 index 0000000000..b1ab2149c2 --- /dev/null +++ b/TinyWebServer/test_presure/webbench-1.5/tags @@ -0,0 +1,35 @@ +!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/ +!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/ +!_TAG_PROGRAM_AUTHOR Darren Hiebert /dhiebert@users.sourceforge.net/ +!_TAG_PROGRAM_NAME Exuberant Ctags // +!_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/ +!_TAG_PROGRAM_VERSION 5.9~svn20110310 // +METHOD_GET webbench.c 35;" d file: +METHOD_HEAD webbench.c 36;" d file: +METHOD_OPTIONS webbench.c 37;" d file: +METHOD_TRACE webbench.c 38;" d file: +PROGRAM_VERSION webbench.c 39;" d file: +REQUEST_SIZE webbench.c 50;" d file: +Socket socket.c /^int Socket(const char *host, int clientPort)$/;" f +alarm_handler webbench.c /^static void alarm_handler(int signal)$/;" f file: +bench webbench.c /^static int bench(void)$/;" f file: +benchcore webbench.c /^void benchcore(const char *host,const int port,const char *req)$/;" f +benchtime webbench.c /^int benchtime=30;$/;" v +build_request webbench.c /^void build_request(const char *url)$/;" f +bytes webbench.c /^int bytes=0;$/;" v +clients webbench.c /^int clients=1;$/;" v +failed webbench.c /^int failed=0;$/;" v +force webbench.c /^int force=0;$/;" v +force_reload webbench.c /^int force_reload=0;$/;" v +host webbench.c /^char host[MAXHOSTNAMELEN];$/;" v +http10 webbench.c /^int http10=1; \/* 0 - http\/0.9, 1 - http\/1.0, 2 - http\/1.1 *\/$/;" v +long_options webbench.c /^static const struct option long_options[]=$/;" v typeref:struct:option file: +main webbench.c /^int main(int argc, char *argv[])$/;" f +method webbench.c /^int method=METHOD_GET;$/;" v +mypipe webbench.c /^int mypipe[2];$/;" v +proxyhost webbench.c /^char *proxyhost=NULL;$/;" v +proxyport webbench.c /^int proxyport=80;$/;" v +request webbench.c /^char request[REQUEST_SIZE];$/;" v +speed webbench.c /^int speed=0;$/;" v +timerexpired webbench.c /^volatile int timerexpired=0;$/;" v +usage webbench.c /^static void usage(void)$/;" f file: diff --git a/TinyWebServer/test_presure/webbench-1.5/webbench b/TinyWebServer/test_presure/webbench-1.5/webbench new file mode 100644 index 0000000000..f264f24673 Binary files /dev/null and b/TinyWebServer/test_presure/webbench-1.5/webbench differ diff --git a/TinyWebServer/test_presure/webbench-1.5/webbench.1 b/TinyWebServer/test_presure/webbench-1.5/webbench.1 new file mode 100644 index 0000000000..60c3ee65fc --- /dev/null +++ b/TinyWebServer/test_presure/webbench-1.5/webbench.1 @@ -0,0 +1,101 @@ +.TH WEBBENCH 1 "14 Jan 2004" +.\" NAME should be all caps, SECTION should be 1-8, maybe w/ subsection +.\" other parms are allowed: see man(7), man(1) +.SH NAME +webbench \- simple forking web benchmark +.SH SYNOPSIS +.B webbench +.I "[options] URL" +.br +.SH "AUTHOR" +This program and manual page was written by Radim Kolar, +for the +.B Supreme Personality of Godhead +(but may be used by others). +.SH "DESCRIPTION" +.B webbench +is simple program for benchmarking HTTP servers or any +other servers, which can be accessed via HTTP proxy. Unlike others +benchmarks, +.B webbench +uses multiple processes for simulating traffic +generated by multiple users. This allows better operating +on SMP systems and on systems with slow or buggy implementation +of select(). +.SH OPTIONS +The programs follow the usual GNU command line syntax, with long +options starting with two dashes (`-'). +A summary of options are included below. +.TP +.B \-?, \-h, \-\-help +Show summary of options. +.TP +.B \-v, \-\-version +Show version of program. +.TP +.B \-f, \-\-force +Do not wait for any response from server. Close connection after +request is send. This option produce quite a good denial of service +attack. +.TP +.B \-9, \-\-http09 +Use HTTP/0.9 protocol, if possible. +.TP +.B \-1, \-\-http10 +Use HTTP/1.0 protocol, if possible. +.TP +.B \-2, \-\-http11 +Use HTTP/1.1 protocol (without +.I Keep-Alive +), if possible. +.TP +.B \-r, \-\-reload +Forces proxy to reload document. If proxy is not +set, option has no effect. +.TP +.B \-t, \-\-time +Run benchmark for +.I +seconds. Default value is 30. +.TP +.B \-p, \-\-proxy +Send request via proxy server. Needed for supporting others protocols +than HTTP. +.TP +.B \-\-get +Use GET request method. +.TP +.B \-\-head +Use HEAD request method. +.TP +.B \-\-options +Use OPTIONS request method. +.TP +.B \-\-trace +Use TRACE request method. +.TP +.B \-c, \-\-clients +Use +.I +multiple clients for benchmark. Default value +is 1. +.SH "EXIT STATUS" +.TP +0 - sucess +.TP +1 - benchmark failed, can not connect to server +.TP +2 - bad command line argument(s) +.TP +3 - internal error, i.e. fork failed +.SH "TODO" +Include support for using +.I Keep-Alive +HTTP/1.1 connections. +.SH "COPYING" +Webbench is distributed under GPL. Copyright 1997-2004 +Radim Kolar (hsn@netmag.cz). +UNIX sockets code taken from popclient 1.5 4/1/94 +public domain code, created by Virginia Tech Computing Center. +.BR +This man page is public domain. diff --git a/TinyWebServer/test_presure/webbench-1.5/webbench.c b/TinyWebServer/test_presure/webbench-1.5/webbench.c new file mode 100644 index 0000000000..8a5d305efc --- /dev/null +++ b/TinyWebServer/test_presure/webbench-1.5/webbench.c @@ -0,0 +1,452 @@ +/* + * (C) Radim Kolar 1997-2004 + * This is free software, see GNU Public License version 2 for + * details. + * + * Simple forking WWW Server benchmark: + * + * Usage: + * webbench --help + * + * Return codes: + * 0 - sucess + * 1 - benchmark failed (server is not on-line) + * 2 - bad param + * 3 - internal error, fork failed + * + */ +#include "socket.c" +#include +#include +#include +#include +#include +#include +#include + +/* values */ +volatile int timerexpired=0; +int speed=0; +int failed=0; +int bytes=0; +/* globals */ +int http10=1; /* 0 - http/0.9, 1 - http/1.0, 2 - http/1.1 */ +/* Allow: GET, HEAD, OPTIONS, TRACE */ +#define METHOD_GET 0 +#define METHOD_HEAD 1 +#define METHOD_OPTIONS 2 +#define METHOD_TRACE 3 +#define PROGRAM_VERSION "1.5" +int method=METHOD_GET; +int clients=1; +int force=0; +int force_reload=0; +int proxyport=80; +char *proxyhost=NULL; +int benchtime=30; +/* internal */ +int mypipe[2]; +char host[MAXHOSTNAMELEN]; +#define REQUEST_SIZE 2048 +char request[REQUEST_SIZE]; + +static const struct option long_options[]= +{ + {"force",no_argument,&force,1}, + {"reload",no_argument,&force_reload,1}, + {"time",required_argument,NULL,'t'}, + {"help",no_argument,NULL,'?'}, + {"http09",no_argument,NULL,'9'}, + {"http10",no_argument,NULL,'1'}, + {"http11",no_argument,NULL,'2'}, + {"get",no_argument,&method,METHOD_GET}, + {"head",no_argument,&method,METHOD_HEAD}, + {"options",no_argument,&method,METHOD_OPTIONS}, + {"trace",no_argument,&method,METHOD_TRACE}, + {"version",no_argument,NULL,'V'}, + {"proxy",required_argument,NULL,'p'}, + {"clients",required_argument,NULL,'c'}, + {NULL,0,NULL,0} +}; + +/* prototypes */ +static void benchcore(const char* host,const int port, const char *request); +static int bench(void); +static void build_request(const char *url); + +static void alarm_handler(int signal) +{ + timerexpired=1; +} + +static void usage(void) +{ + fprintf(stderr, + "webbench [option]... URL\n" + " -f|--force Don't wait for reply from server.\n" + " -r|--reload Send reload request - Pragma: no-cache.\n" + " -t|--time Run benchmark for seconds. Default 30.\n" + " -p|--proxy Use proxy server for request.\n" + " -c|--clients Run HTTP clients at once. Default one.\n" + " -9|--http09 Use HTTP/0.9 style requests.\n" + " -1|--http10 Use HTTP/1.0 protocol.\n" + " -2|--http11 Use HTTP/1.1 protocol.\n" + " --get Use GET request method.\n" + " --head Use HEAD request method.\n" + " --options Use OPTIONS request method.\n" + " --trace Use TRACE request method.\n" + " -?|-h|--help This information.\n" + " -V|--version Display program version.\n" + ); +}; +int main(int argc, char *argv[]) +{ + int opt=0; + int options_index=0; + char *tmp=NULL; + + if(argc==1) + { + usage(); + return 2; + } + + while((opt=getopt_long(argc,argv,"912Vfrt:p:c:?h",long_options,&options_index))!=EOF ) + { + switch(opt) + { + case 0 : break; + case 'f': force=1;break; + case 'r': force_reload=1;break; + case '9': http10=0;break; + case '1': http10=1;break; + case '2': http10=2;break; + case 'V': printf(PROGRAM_VERSION"\n");exit(0); + case 't': benchtime=atoi(optarg);break; + case 'p': + /* proxy server parsing server:port */ + tmp=strrchr(optarg,':'); + proxyhost=optarg; + if(tmp==NULL) + { + break; + } + if(tmp==optarg) + { + fprintf(stderr,"Error in option --proxy %s: Missing hostname.\n",optarg); + return 2; + } + if(tmp==optarg+strlen(optarg)-1) + { + fprintf(stderr,"Error in option --proxy %s Port number is missing.\n",optarg); + return 2; + } + *tmp='\0'; + proxyport=atoi(tmp+1);break; + case ':': + case 'h': + case '?': usage();return 2;break; + case 'c': clients=atoi(optarg);break; + } + } + + if(optind==argc) { + fprintf(stderr,"webbench: Missing URL!\n"); + usage(); + return 2; + } + + if(clients==0) clients=1; + if(benchtime==0) benchtime=60; + /* Copyright */ + fprintf(stderr,"Webbench - Simple Web Benchmark "PROGRAM_VERSION"\n" + "Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.\n" + ); + build_request(argv[optind]); + /* print bench info */ + printf("\nBenchmarking: "); + switch(method) + { + case METHOD_GET: + default: + printf("GET");break; + case METHOD_OPTIONS: + printf("OPTIONS");break; + case METHOD_HEAD: + printf("HEAD");break; + case METHOD_TRACE: + printf("TRACE");break; + } + printf(" %s",argv[optind]); + switch(http10) + { + case 0: printf(" (using HTTP/0.9)");break; + case 2: printf(" (using HTTP/1.1)");break; + } + printf("\n"); + if(clients==1) printf("1 client"); + else + printf("%d clients",clients); + + printf(", running %d sec", benchtime); + if(force) printf(", early socket close"); + if(proxyhost!=NULL) printf(", via proxy server %s:%d",proxyhost,proxyport); + if(force_reload) printf(", forcing reload"); + printf(".\n"); + return bench(); +} + +void build_request(const char *url) +{ + char tmp[10]; + int i; + + bzero(host,MAXHOSTNAMELEN); + bzero(request,REQUEST_SIZE); + + if(force_reload && proxyhost!=NULL && http10<1) http10=1; + if(method==METHOD_HEAD && http10<1) http10=1; + if(method==METHOD_OPTIONS && http10<2) http10=2; + if(method==METHOD_TRACE && http10<2) http10=2; + + switch(method) + { + default: + case METHOD_GET: strcpy(request,"GET");break; + case METHOD_HEAD: strcpy(request,"HEAD");break; + case METHOD_OPTIONS: strcpy(request,"OPTIONS");break; + case METHOD_TRACE: strcpy(request,"TRACE");break; + } + + strcat(request," "); + + if(NULL==strstr(url,"://")) + { + fprintf(stderr, "\n%s: is not a valid URL.\n",url); + exit(2); + } + if(strlen(url)>1500) + { + fprintf(stderr,"URL is too long.\n"); + exit(2); + } + if(proxyhost==NULL) + if (0!=strncasecmp("http://",url,7)) + { fprintf(stderr,"\nOnly HTTP protocol is directly supported, set --proxy for others.\n"); + exit(2); + } + /* protocol/host delimiter */ + i=strstr(url,"://")-url+3; + /* printf("%d\n",i); */ + + if(strchr(url+i,'/')==NULL) { + fprintf(stderr,"\nInvalid URL syntax - hostname don't ends with '/'.\n"); + exit(2); + } + if(proxyhost==NULL) + { + /* get port from hostname */ + if(index(url+i,':')!=NULL && + index(url+i,':')0) + strcat(request,"User-Agent: WebBench "PROGRAM_VERSION"\r\n"); + if(proxyhost==NULL && http10>0) + { + strcat(request,"Host: "); + strcat(request,host); + strcat(request,"\r\n"); + } + if(force_reload && proxyhost!=NULL) + { + strcat(request,"Pragma: no-cache\r\n"); + } + if(http10>1) + strcat(request,"Connection: close\r\n"); + /* add empty line at end */ + if(http10>0) strcat(request,"\r\n"); + // printf("Req=%s\n",request); +} + +/* vraci system rc error kod */ +static int bench(void) +{ + int i,j,k; + pid_t pid=0; + FILE *f; + + /* check avaibility of target server */ + i=Socket(proxyhost==NULL?host:proxyhost,proxyport); + if(i<0) { + fprintf(stderr,"\nConnect to server failed. Aborting benchmark.\n"); + return 1; + } + close(i); + /* create pipe */ + if(pipe(mypipe)) + { + perror("pipe failed."); + return 3; + } + + /* not needed, since we have alarm() in childrens */ + /* wait 4 next system clock tick */ + /* + cas=time(NULL); + while(time(NULL)==cas) + sched_yield(); + */ + + /* fork childs */ + for(i=0;i0) + { + /* fprintf(stderr,"Correcting failed by signal\n"); */ + failed--; + } + return; + } + s=Socket(host,port); + if(s<0) { failed++;continue;} + if(rlen!=write(s,req,rlen)) {failed++;close(s);continue;} + if(http10==0) + if(shutdown(s,1)) { failed++;close(s);continue;} + if(force==0) + { + /* read all available data from socket */ + while(1) + { + if(timerexpired) break; + i=read(s,buf,1500); + /* fprintf(stderr,"%d\n",i); */ + if(i<0) + { + failed++; + close(s); + goto nexttry; + } + else + if(i==0) break; + else + bytes+=i; + } + } + if(close(s)) {failed++;continue;} + speed++; + } +} diff --git a/TinyWebServer/test_presure/webbench-1.5/webbench.o b/TinyWebServer/test_presure/webbench-1.5/webbench.o new file mode 100644 index 0000000000..c4b48507e8 Binary files /dev/null and b/TinyWebServer/test_presure/webbench-1.5/webbench.o differ diff --git a/TinyWebServer/threadpool/README.md b/TinyWebServer/threadpool/README.md new file mode 100644 index 0000000000..f015fb02cd --- /dev/null +++ b/TinyWebServer/threadpool/README.md @@ -0,0 +1,14 @@ + +半同步/半反应堆线程池 +=============== +使用一个工作队列完全解除了主线程和工作线程的耦合关系:主线程往工作队列中插入任务,工作线程通过竞争来取得任务并执行它。 +> * 同步I/O模拟proactor模式 +> * 半同步/半反应堆 +> * 线程池 + + + + + + + diff --git a/TinyWebServer/threadpool/threadpool.h b/TinyWebServer/threadpool/threadpool.h new file mode 100644 index 0000000000..4cc1a46420 --- /dev/null +++ b/TinyWebServer/threadpool/threadpool.h @@ -0,0 +1,108 @@ +#ifndef THREADPOOL_H +#define THREADPOOL_H + +#include +#include +#include +#include +#include "../lock/locker.h" +#include "../CGImysql/sql_connection_pool.h" + +template +class threadpool +{ +public: + /*thread_number是线程池中线程的数量,max_requests是请求队列中最多允许的、等待处理的请求的数量*/ + threadpool(connection_pool *connPool, int thread_number = 8, int max_request = 10000); + ~threadpool(); + bool append(T *request); + +private: + /*工作线程运行的函数,它不断从工作队列中取出任务并执行之*/ + static void *worker(void *arg); + void run(); + +private: + int m_thread_number; //线程池中的线程数 + int m_max_requests; //请求队列中允许的最大请求数 + pthread_t *m_threads; //描述线程池的数组,其大小为m_thread_number + std::list m_workqueue; //请求队列 + locker m_queuelocker; //保护请求队列的互斥锁 + sem m_queuestat; //是否有任务需要处理 + bool m_stop; //是否结束线程 + connection_pool *m_connPool; //数据库 +}; +template +threadpool::threadpool( connection_pool *connPool, int thread_number, int max_requests) : m_thread_number(thread_number), m_max_requests(max_requests), m_stop(false), m_threads(NULL),m_connPool(connPool) +{ + if (thread_number <= 0 || max_requests <= 0) + throw std::exception(); + m_threads = new pthread_t[m_thread_number]; + if (!m_threads) + throw std::exception(); + for (int i = 0; i < thread_number; ++i) + { + //printf("create the %dth thread\n",i); + if (pthread_create(m_threads + i, NULL, worker, this) != 0) + { + delete[] m_threads; + throw std::exception(); + } + if (pthread_detach(m_threads[i])) + { + delete[] m_threads; + throw std::exception(); + } + } +} +template +threadpool::~threadpool() +{ + delete[] m_threads; + m_stop = true; +} +template +bool threadpool::append(T *request) +{ + m_queuelocker.lock(); + if (m_workqueue.size() > m_max_requests) + { + m_queuelocker.unlock(); + return false; + } + m_workqueue.push_back(request); + m_queuelocker.unlock(); + m_queuestat.post(); + return true; +} +template +void *threadpool::worker(void *arg) +{ + threadpool *pool = (threadpool *)arg; + pool->run(); + return pool; +} +template +void threadpool::run() +{ + while (!m_stop) + { + m_queuestat.wait(); + m_queuelocker.lock(); + if (m_workqueue.empty()) + { + m_queuelocker.unlock(); + continue; + } + T *request = m_workqueue.front(); + m_workqueue.pop_front(); + m_queuelocker.unlock(); + if (!request) + continue; + + connectionRAII mysqlcon(&request->mysql, m_connPool); + + request->process(); + } +} +#endif diff --git a/TinyWebServer/timer/README.md b/TinyWebServer/timer/README.md new file mode 100644 index 0000000000..ad1f7b813b --- /dev/null +++ b/TinyWebServer/timer/README.md @@ -0,0 +1,7 @@ + +定时器处理非活动连接 +=============== +由于非活跃连接占用了连接资源,严重影响服务器的性能,通过实现一个服务器定时器,处理这种非活跃连接,释放连接资源。利用alarm函数周期性地触发SIGALRM信号,该信号的信号处理函数利用管道通知主循环执行定时器链表上的定时任务. +> * 统一事件源 +> * 基于升序链表的定时器 +> * 处理非活动连接 diff --git a/TinyWebServer/timer/lst_timer.h b/TinyWebServer/timer/lst_timer.h new file mode 100644 index 0000000000..dbe3fabbc4 --- /dev/null +++ b/TinyWebServer/timer/lst_timer.h @@ -0,0 +1,178 @@ +#ifndef LST_TIMER +#define LST_TIMER + +#include +#include "../log/log.h" + +class util_timer; +struct client_data +{ + sockaddr_in address; + int sockfd; + util_timer *timer; +}; + +class util_timer +{ +public: + util_timer() : prev(NULL), next(NULL) {} + +public: + time_t expire; + void (*cb_func)(client_data *); + client_data *user_data; + util_timer *prev; + util_timer *next; +}; + +class sort_timer_lst +{ +public: + sort_timer_lst() : head(NULL), tail(NULL) {} + ~sort_timer_lst() + { + util_timer *tmp = head; + while (tmp) + { + head = tmp->next; + delete tmp; + tmp = head; + } + } + void add_timer(util_timer *timer) + { + if (!timer) + { + return; + } + if (!head) + { + head = tail = timer; + return; + } + if (timer->expire < head->expire) + { + timer->next = head; + head->prev = timer; + head = timer; + return; + } + add_timer(timer, head); + } + void adjust_timer(util_timer *timer) + { + if (!timer) + { + return; + } + util_timer *tmp = timer->next; + if (!tmp || (timer->expire < tmp->expire)) + { + return; + } + if (timer == head) + { + head = head->next; + head->prev = NULL; + timer->next = NULL; + add_timer(timer, head); + } + else + { + timer->prev->next = timer->next; + timer->next->prev = timer->prev; + add_timer(timer, timer->next); + } + } + void del_timer(util_timer *timer) + { + if (!timer) + { + return; + } + if ((timer == head) && (timer == tail)) + { + delete timer; + head = NULL; + tail = NULL; + return; + } + if (timer == head) + { + head = head->next; + head->prev = NULL; + delete timer; + return; + } + if (timer == tail) + { + tail = tail->prev; + tail->next = NULL; + delete timer; + return; + } + timer->prev->next = timer->next; + timer->next->prev = timer->prev; + delete timer; + } + void tick() + { + if (!head) + { + return; + } + //printf( "timer tick\n" ); + LOG_INFO("%s", "timer tick"); + Log::get_instance()->flush(); + time_t cur = time(NULL); + util_timer *tmp = head; + while (tmp) + { + if (cur < tmp->expire) + { + break; + } + tmp->cb_func(tmp->user_data); + head = tmp->next; + if (head) + { + head->prev = NULL; + } + delete tmp; + tmp = head; + } + } + +private: + void add_timer(util_timer *timer, util_timer *lst_head) + { + util_timer *prev = lst_head; + util_timer *tmp = prev->next; + while (tmp) + { + if (timer->expire < tmp->expire) + { + prev->next = timer; + timer->next = tmp; + tmp->prev = timer; + timer->prev = prev; + break; + } + prev = tmp; + tmp = tmp->next; + } + if (!tmp) + { + prev->next = timer; + timer->prev = prev; + timer->next = NULL; + tail = timer; + } + } + +private: + util_timer *head; + util_timer *tail; +}; + +#endif diff --git a/note/20171017215810378.jpg b/note/20171017215810378.jpg new file mode 100644 index 0000000000..9b0257cee7 Binary files /dev/null and b/note/20171017215810378.jpg differ diff --git a/note/20171017215827877.jpg b/note/20171017215827877.jpg new file mode 100644 index 0000000000..0075b48271 Binary files /dev/null and b/note/20171017215827877.jpg differ diff --git a/note/2018041513242953.png b/note/2018041513242953.png new file mode 100644 index 0000000000..af4b38cb45 Binary files /dev/null and b/note/2018041513242953.png differ diff --git "a/note/Bitmap\347\256\200\344\273\213 - \345\272\237\347\211\251\345\244\247\345\270\210\345\205\204 - \345\215\232\345\256\242\345\233\255.pdf" "b/note/Bitmap\347\256\200\344\273\213 - \345\272\237\347\211\251\345\244\247\345\270\210\345\205\204 - \345\215\232\345\256\242\345\233\255.pdf" new file mode 100644 index 0000000000..0a522e5e10 Binary files /dev/null and "b/note/Bitmap\347\256\200\344\273\213 - \345\272\237\347\211\251\345\244\247\345\270\210\345\205\204 - \345\215\232\345\256\242\345\233\255.pdf" differ diff --git "a/note/C++\346\217\220\351\253\230\347\274\226\347\250\213.md" "b/note/C++\346\217\220\351\253\230\347\274\226\347\250\213.md" new file mode 100644 index 0000000000..95fcfa832e --- /dev/null +++ "b/note/C++\346\217\220\351\253\230\347\274\226\347\250\213.md" @@ -0,0 +1,8417 @@ +[TOC] + + + +# C++提高编程 + + + +* 本阶段主要针对C++==泛型编程==和==STL==技术做详细讲解,探讨C++更深层的使用 + + + + + +## 1 模板 + +### 1.1 模板的概念 + + + +模板就是建立**通用的模具**,大大**提高复用性** + + + +例如生活中的模板 + + + +一寸照片模板: + + + +![1547105026929](assets/1547105026929.png) + + + +PPT模板: + +![1547103297864](assets/1547103297864.png) + + + +![1547103359158](assets/1547103359158.png) + + + + + +模板的特点: + +* 模板不可以直接使用,它只是一个框架 +* 模板的通用并不是万能的 + + + + + + + + + +### 1.2 函数模板 + + + +* C++另一种编程思想称为 ==泛型编程== ,主要利用的技术就是模板 + + +* C++提供两种模板机制:**函数模板**和**类模板** + + + +#### 1.2.1 函数模板语法 + +函数模板作用: + +建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个**虚拟的类型**来代表。 + + + +**语法:** + +```C++ +template +函数声明或定义 +``` + +**解释:** + +template --- 声明创建模板 + +typename --- 表面其后面的符号是一种数据类型,可以用class代替 + +T --- 通用的数据类型,名称可以替换,通常为大写字母 + + + +**示例:** + +```C++ + +//交换整型函数 +void swapInt(int& a, int& b) { + int temp = a; + a = b; + b = temp; +} + +//交换浮点型函数 +void swapDouble(double& a, double& b) { + double temp = a; + a = b; + b = temp; +} + +//利用模板提供通用的交换函数 +template +void mySwap(T& a, T& b) +{ + T temp = a; + a = b; + b = temp; +} + +void test01() +{ + int a = 10; + int b = 20; + + //swapInt(a, b); + + //利用模板实现交换 + //1、自动类型推导 + mySwap(a, b); + + //2、显示指定类型 + mySwap(a, b); + + cout << "a = " << a << endl; + cout << "b = " << b << endl; + +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结: + +* 函数模板利用关键字 template +* 使用函数模板有两种方式:自动类型推导、显示指定类型 +* 模板的目的是为了提高复用性,将类型参数化 + + + + + + + + + +#### 1.2.2 函数模板注意事项 + +注意事项: + +* 自动类型推导,必须推导出一致的数据类型T,才可以使用 + + +* 模板必须要确定出T的数据类型,才可以使用 + + + +**示例:** + +```C++ +//利用模板提供通用的交换函数 +template +void mySwap(T& a, T& b) +{ + T temp = a; + a = b; + b = temp; +} + + +// 1、自动类型推导,必须推导出一致的数据类型T,才可以使用 +void test01() +{ + int a = 10; + int b = 20; + char c = 'c'; + + mySwap(a, b); // 正确,可以推导出一致的T + //mySwap(a, c); // 错误,推导不出一致的T类型 +} + + +// 2、模板必须要确定出T的数据类型,才可以使用 +template +void func() +{ + cout << "func 调用" << endl; +} + +void test02() +{ + //func(); //错误,模板不能独立使用,必须确定出T的类型 + func(); //利用显示指定类型的方式,给T一个类型,才可以使用该模板 +} + +int main() { + + test01(); + test02(); + + system("pause"); + + return 0; +} +``` + +总结: + +* 使用模板时必须确定出通用数据类型T,并且能够推导出一致的类型 + + + + + + + + + + + +#### 1.2.3 函数模板案例 + +案例描述: + +* 利用函数模板封装一个排序的函数,可以对**不同数据类型数组**进行排序 +* 排序规则从大到小,排序算法为**选择排序** +* 分别利用**char数组**和**int数组**进行测试 + + + +示例: + +```C++ +//交换的函数模板 +template +void mySwap(T &a, T&b) +{ + T temp = a; + a = b; + b = temp; +} + + +template // 也可以替换成typename +//利用选择排序,进行对数组从大到小的排序 +void mySort(T arr[], int len) +{ + for (int i = 0; i < len; i++) + { + int max = i; //最大数的下标 + for (int j = i + 1; j < len; j++) + { + if (arr[max] < arr[j]) + { + max = j; + } + } + if (max != i) //如果最大数的下标不是i,交换两者 + { + mySwap(arr[max], arr[i]); + } + } +} +template +void printArray(T arr[], int len) { + + for (int i = 0; i < len; i++) { + cout << arr[i] << " "; + } + cout << endl; +} +void test01() +{ + //测试char数组 + char charArr[] = "bdcfeagh"; + int num = sizeof(charArr) / sizeof(char); + mySort(charArr, num); + printArray(charArr, num); +} + +void test02() +{ + //测试int数组 + int intArr[] = { 7, 5, 8, 1, 3, 9, 2, 4, 6 }; + int num = sizeof(intArr) / sizeof(int); + mySort(intArr, num); + printArray(intArr, num); +} + +int main() { + + test01(); + test02(); + + system("pause"); + + return 0; +} +``` + +总结:模板可以提高代码复用,需要熟练掌握 + + + + + + + + + + + +#### 1.2.4 普通函数与函数模板的区别 + + + +**普通函数与函数模板区别:** + +* 普通函数调用时可以发生自动类型转换(隐式类型转换) +* 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换 +* 如果利用显示指定类型的方式,可以发生隐式类型转换 + + + +**示例:** + +```C++ +//普通函数 +int myAdd01(int a, int b) +{ + return a + b; +} + +//函数模板 +template +T myAdd02(T a, T b) +{ + return a + b; +} + +//使用函数模板时,如果用自动类型推导,不会发生自动类型转换,即隐式类型转换 +void test01() +{ + int a = 10; + int b = 20; + char c = 'c'; + + cout << myAdd01(a, c) << endl; //正确,将char类型的'c'隐式转换为int类型 'c' 对应 ASCII码 99 + + //myAdd02(a, c); // 报错,使用自动类型推导时,不会发生隐式类型转换 + + myAdd02(a, c); //正确,如果用显示指定类型,可以发生隐式类型转换 +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结:建议使用显示指定类型的方式,调用函数模板,因为可以自己确定通用类型T + + + + + + + + + + + +#### 1.2.5 普通函数与函数模板的调用规则 + + + +调用规则如下: + +1. 如果函数模板和普通函数都可以实现,优先调用普通函数 +2. 可以通过空模板参数列表来强制调用函数模板 +3. 函数模板也可以发生重载 +4. 如果函数模板可以产生更好的匹配,优先调用函数模板 + + + + + +**示例:** + +```C++ +//普通函数与函数模板调用规则 +void myPrint(int a, int b) +{ + cout << "调用的普通函数" << endl; +} + +template +void myPrint(T a, T b) +{ + cout << "调用的模板" << endl; +} + +template +void myPrint(T a, T b, T c) +{ + cout << "调用重载的模板" << endl; +} + +void test01() +{ + //1、如果函数模板和普通函数都可以实现,优先调用普通函数 + // 注意 如果告诉编译器 普通函数是有的,但只是声明没有实现,或者不在当前文件内实现,就会报错找不到 + int a = 10; + int b = 20; + myPrint(a, b); //调用普通函数 + + //2、可以通过空模板参数列表来强制调用函数模板 + myPrint<>(a, b); //调用函数模板 + + //3、函数模板也可以发生重载 + int c = 30; + myPrint(a, b, c); //调用重载的函数模板 + + //4、 如果函数模板可以产生更好的匹配,优先调用函数模板 + char c1 = 'a'; + char c2 = 'b'; + myPrint(c1, c2); //调用函数模板 +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结:既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性 + + + + + + + + + + + +#### 1.2.6 模板的局限性 + +**局限性:** + +* 模板的通用性并不是万能的 + + + +**例如:** + +```C++ + template + void f(T a, T b) + { + a = b; + } +``` + +在上述代码中提供的赋值操作,如果传入的a和b是一个数组,就无法实现了 + + + +再例如: + +```C++ + template + void f(T a, T b) + { + if(a > b) { ... } + } +``` + +在上述代码中,如果T的数据类型传入的是像Person这样的自定义数据类型,也无法正常运行 + + + +因此C++为了解决这种问题,提供模板的重载,可以为这些**特定的类型**提供**具体化的模板** + + + +**示例:** + +```C++ +#include +using namespace std; + +#include + +class Person +{ +public: + Person(string name, int age) + { + this->m_Name = name; + this->m_Age = age; + } + string m_Name; + int m_Age; +}; + +//普通函数模板 +template +bool myCompare(T& a, T& b) +{ + if (a == b) + { + return true; + } + else + { + return false; + } +} + + +//具体化,显示具体化的原型和定意思以template<>开头,并通过名称来指出类型 +//具体化优先于常规模板 +template<> bool myCompare(Person &p1, Person &p2) +{ + if ( p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age) + { + return true; + } + else + { + return false; + } +} + +void test01() +{ + int a = 10; + int b = 20; + //内置数据类型可以直接使用通用的函数模板 + bool ret = myCompare(a, b); + if (ret) + { + cout << "a == b " << endl; + } + else + { + cout << "a != b " << endl; + } +} + +void test02() +{ + Person p1("Tom", 10); + Person p2("Tom", 10); + //自定义数据类型,不会调用普通的函数模板 + //可以创建具体化的Person数据类型的模板,用于特殊处理这个类型 + bool ret = myCompare(p1, p2); + if (ret) + { + cout << "p1 == p2 " << endl; + } + else + { + cout << "p1 != p2 " << endl; + } +} + +int main() { + + test01(); + + test02(); + + system("pause"); + + return 0; +} +``` + +总结: + +* 利用具体化的模板,可以解决自定义类型的通用化 +* 学习模板并不是为了写模板,而是在STL能够运用系统提供的模板 + + + + + + + + + +### 1.3 类模板 + +#### 1.3.1 类模板语法 + +类模板作用: + +* 建立一个通用类,类中的成员 数据类型可以不具体制定,用一个**虚拟的类型**来代表。 + + + +**语法:** + +```c++ +template +类 +``` + +**解释:** + +template --- 声明创建模板 + +typename --- 表面其后面的符号是一种数据类型,可以用class代替 + +T --- 通用的数据类型,名称可以替换,通常为大写字母 + + + +**示例:** + +```C++ +#include +//类模板 +template +class Person +{ +public: + Person(NameType name, AgeType age) + { + this->mName = name; + this->mAge = age; + } + void showPerson() + { + cout << "name: " << this->mName << " age: " << this->mAge << endl; + } +public: + NameType mName; + AgeType mAge; +}; + +void test01() +{ + // 指定NameType 为string类型,AgeType 为 int类型 + PersonP1("孙悟空", 999); + P1.showPerson(); +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结:类模板和函数模板语法相似,在声明模板template后面加类,此类称为类模板 + + + + + + + + + + + +#### 1.3.2 类模板与函数模板区别 + + + +类模板与函数模板区别主要有两点: + +1. 类模板没有自动类型推导的使用方式 +2. 类模板在模板参数列表中可以有默认参数 + + + + +**示例:** + +```C++ +#include +//类模板 +template +class Person +{ +public: + Person(NameType name, AgeType age) + { + this->mName = name; + this->mAge = age; + } + void showPerson() + { + cout << "name: " << this->mName << " age: " << this->mAge << endl; + } +public: + NameType mName; + AgeType mAge; +}; + +//1、类模板没有自动类型推导的使用方式 +void test01() +{ + // Person p("孙悟空", 1000); // 错误 类模板使用时候,不可以用自动类型推导 + Person p("孙悟空", 1000); //必须使用显示指定类型的方式,使用类模板 + p.showPerson(); +} + +//2、类模板在模板参数列表中可以有默认参数 +void test02() +{ + Person p("猪八戒", 999); //类模板中的模板参数列表 可以指定默认参数 + p.showPerson(); +} + +int main() { + + test01(); + + test02(); + + system("pause"); + + return 0; +} +``` + +总结: + +* 类模板使用只能用显示指定类型方式 +* 类模板中的模板参数列表可以有默认参数 + + + + + + + + + + + +#### 1.3.3 类模板中成员函数创建时机 + + + +类模板中成员函数和普通类中成员函数创建时机是有区别的: + +* 普通类中的成员函数一开始就可以创建 +* 类模板中的成员函数在调用时才创建 + + + + + +**示例:** + +```C++ +class Person1 +{ +public: + void showPerson1() + { + cout << "Person1 show" << endl; + } +}; + +class Person2 +{ +public: + void showPerson2() + { + cout << "Person2 show" << endl; + } +}; + +template +class MyClass +{ +public: + T obj; + + //类模板中的成员函数,并不是一开始就创建的,而是在模板调用时再生成 + + void fun1() { obj.showPerson1(); } + void fun2() { obj.showPerson2(); } + +}; + +void test01() +{ + MyClass m; + + m.fun1(); + + //m.fun2();//编译会出错,说明函数调用才会去创建成员函数 +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结:类模板中的成员函数并不是一开始就创建的,在调用时才去创建 + + + + + + + + + +#### 1.3.4 类模板对象做函数参数 + +学习目标: + +* 类模板实例化出的对象,向函数传参的方式 + + + +一共有三种传入方式: + +1. 指定传入的类型 --- 直接显示对象的数据类型 +2. 参数模板化 --- 将对象中的参数变为模板进行传递 +3. 整个类模板化 --- 将这个对象类型 模板化进行传递 + + + + + +**示例:** + +```C++ +#include +//类模板 +template +class Person +{ +public: + Person(NameType name, AgeType age) + { + this->mName = name; + this->mAge = age; + } + void showPerson() + { + cout << "name: " << this->mName << " age: " << this->mAge << endl; + } +public: + NameType mName; + AgeType mAge; +}; + +//1、指定传入的类型 +void printPerson1(Person &p) +{ + p.showPerson(); +} +void test01() +{ + Person p("孙悟空", 100); + printPerson1(p); +} + +//2、参数模板化 +template +void printPerson2(Person&p) +{ + p.showPerson(); + cout << "T1的类型为: " << typeid(T1).name() << endl; + cout << "T2的类型为: " << typeid(T2).name() << endl; +} +void test02() +{ + Person p("猪八戒", 90); + printPerson2(p); +} + +//3、整个类模板化 +template +void printPerson3(T & p) +{ + cout << "T的类型为: " << typeid(T).name() << endl; + p.showPerson(); + +} +void test03() +{ + Person p("唐僧", 30); + printPerson3(p); +} + +int main() { + + test01(); + test02(); + test03(); + + system("pause"); + + return 0; +} +``` + +总结: + +* 通过类模板创建的对象,可以有三种方式向函数中进行传参 +* 使用比较广泛是第一种:指定传入的类型 + + + + + + + + + +#### 1.3.5 类模板与继承 + + + +当类模板碰到继承时,需要注意一下几点: + +* 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型 +* 如果不指定,编译器无法给子类分配内存 +* 如果想灵活指定出父类中T的类型,子类也需变为类模板 + + + + +**示例:** + +```C++ +template +class Base +{ + T m; +}; + +//class Son:public Base //错误,c++编译需要给子类分配内存,必须知道父类中T的类型才可以向下继承 +class Son :public Base //必须指定一个类型 +{ +}; +void test01() +{ + Son c; +} + +//类模板继承类模板 ,可以用T2指定父类中的T类型 +template +class Son2 :public Base +{ +public: + Son2() + { + cout << typeid(T1).name() << endl; + cout << typeid(T2).name() << endl; + } +}; + +void test02() +{ + Son2 child1; +} + + +int main() { + + test01(); + + test02(); + + system("pause"); + + return 0; +} +``` + +总结:如果父类是类模板,子类需要指定出父类中T的数据类型 + + + + + + + + + +#### 1.3.6 类模板成员函数类外实现 + + + +学习目标:能够掌握类模板中的成员函数类外实现 + + + +**示例:** + +```C++ +#include + +//类模板中成员函数类外实现 +template +class Person { +public: + //成员函数类内声明 + Person(T1 name, T2 age); + void showPerson(); + +public: + T1 m_Name; + T2 m_Age; +}; + +//构造函数 类外实现 +template +Person::Person(T1 name, T2 age) { + this->m_Name = name; + this->m_Age = age; +} + +//成员函数 类外实现 +template +void Person::showPerson() { + cout << "姓名: " << this->m_Name << " 年龄:" << this->m_Age << endl; +} + +void test01() +{ + Person p("Tom", 20); + p.showPerson(); +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结:类模板中成员函数类外实现时,需要加上模板参数列表 + + + + + + + + + +#### 1.3.7 类模板分文件编写 + +学习目标: + +* 掌握类模板成员函数分文件编写产生的问题以及解决方式 + +问题: + +* 类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到 + + +解决: + +* 解决方式1:直接包含.cpp源文件 +* 解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制 + + + +**示例:** + +person.hpp中代码: + +```C++ +#pragma once +#include +using namespace std; +#include + +template +class Person { +public: + Person(T1 name, T2 age); + void showPerson(); +public: + T1 m_Name; + T2 m_Age; +}; + +//构造函数 类外实现 +template +Person::Person(T1 name, T2 age) { + this->m_Name = name; + this->m_Age = age; +} + +//成员函数 类外实现 +template +void Person::showPerson() { + cout << "姓名: " << this->m_Name << " 年龄:" << this->m_Age << endl; +} +``` + + + +类模板分文件编写.cpp中代码 + +```C++ +#include +using namespace std; + +//#include "person.h" +#include "person.cpp" //解决方式1,包含cpp源文件 + +//解决方式2,将声明和实现写到一起,文件后缀名改为.hpp +#include "person.hpp" +void test01() +{ + Person p("Tom", 10); + p.showPerson(); +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结:主流的解决方式是第二种,将类模板成员函数写到一起,并将后缀名改为.hpp + + + + + + + + + +#### 1.3.8 类模板与友元 + + + +学习目标: + +* 掌握类模板配合友元函数的类内和类外实现 + + + +全局函数类内实现 - 直接在类内声明友元即可 + +全局函数类外实现 - 需要提前让编译器知道全局函数的存在 + + + +**示例:** + +```C++ +#include + +//2、全局函数配合友元 类外实现 - 先做函数模板声明,下方在做函数模板定义,在做友元 +template class Person; + +//如果声明了函数模板,可以将实现写到后面,否则需要将实现体写到类的前面让编译器提前看到 +//template void printPerson2(Person & p); + +//函数模板 +template +void printPerson2(Person & p) +{ + cout << "类外实现 ---- 姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl; +} + +//类模板 +template +class Person +{ + //1、全局函数配合友元 类内实现 + friend void printPerson(Person & p) + { + cout << "姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl; + } + + //全局函数配合友元 类外实现 + friend void printPerson2<>(Person & p); + +public: + + Person(T1 name, T2 age) + { + this->m_Name = name; + this->m_Age = age; + } + +private: + T1 m_Name; + T2 m_Age; + +}; + +//1、全局函数在类内实现 +void test01() +{ + Person p("Tom", 20); + printPerson(p); +} + +//2、全局函数在类外实现 +void test02() +{ + Person p("Jerry", 30); + printPerson2(p); +} + +int main() { + + //test01(); + + test02(); + + system("pause"); + + return 0; +} +``` + +总结:建议全局函数做类内实现,用法简单,而且编译器可以直接识别 + + + + + + + + + + + +#### 1.3.9 类模板案例 + +案例描述: 实现一个通用的数组类,要求如下: + + + +* 可以对内置数据类型以及自定义数据类型的数据进行存储 +* 将数组中的数据存储到堆区 +* 构造函数中可以传入数组的容量 +* 提供对应的拷贝构造函数以及operator=防止浅拷贝问题 +* 提供尾插法和尾删法对数组中的数据进行增加和删除 +* 可以通过下标的方式访问数组中的元素 +* 可以获取数组中当前元素个数和数组的容量 + + + + + +**示例:** + +myArray.hpp中代码 + +```C++ +#pragma once +#include +using namespace std; + +template +class MyArray +{ +public: + + //构造函数 + MyArray(int capacity) + { + this->m_Capacity = capacity; + this->m_Size = 0; + pAddress = new T[this->m_Capacity]; + } + + //拷贝构造 + MyArray(const MyArray & arr) + { + this->m_Capacity = arr.m_Capacity; + this->m_Size = arr.m_Size; + this->pAddress = new T[this->m_Capacity]; + for (int i = 0; i < this->m_Size; i++) + { + //如果T为对象,而且还包含指针,必须需要重载 = 操作符,因为这个等号不是 构造 而是赋值, + // 普通类型可以直接= 但是指针类型需要深拷贝 + this->pAddress[i] = arr.pAddress[i]; + } + } + + //重载= 操作符 防止浅拷贝问题 + MyArray& operator=(const MyArray& myarray) { + + if (this->pAddress != NULL) { + delete[] this->pAddress; + this->m_Capacity = 0; + this->m_Size = 0; + } + + this->m_Capacity = myarray.m_Capacity; + this->m_Size = myarray.m_Size; + this->pAddress = new T[this->m_Capacity]; + for (int i = 0; i < this->m_Size; i++) { + this->pAddress[i] = myarray[i]; + } + return *this; + } + + //重载[] 操作符 arr[0] + T& operator [](int index) + { + return this->pAddress[index]; //不考虑越界,用户自己去处理 + } + + //尾插法 + void Push_back(const T & val) + { + if (this->m_Capacity == this->m_Size) + { + return; + } + this->pAddress[this->m_Size] = val; + this->m_Size++; + } + + //尾删法 + void Pop_back() + { + if (this->m_Size == 0) + { + return; + } + this->m_Size--; + } + + //获取数组容量 + int getCapacity() + { + return this->m_Capacity; + } + + //获取数组大小 + int getSize() + { + return this->m_Size; + } + + + //析构 + ~MyArray() + { + if (this->pAddress != NULL) + { + delete[] this->pAddress; + this->pAddress = NULL; + this->m_Capacity = 0; + this->m_Size = 0; + } + } + +private: + T * pAddress; //指向一个堆空间,这个空间存储真正的数据 + int m_Capacity; //容量 + int m_Size; // 大小 +}; +``` + + + +类模板案例—数组类封装.cpp中 + +```C++ +#include "myArray.hpp" +#include + +void printIntArray(MyArray& arr) { + for (int i = 0; i < arr.getSize(); i++) { + cout << arr[i] << " "; + } + cout << endl; +} + +//测试内置数据类型 +void test01() +{ + MyArray array1(10); + for (int i = 0; i < 10; i++) + { + array1.Push_back(i); + } + cout << "array1打印输出:" << endl; + printIntArray(array1); + cout << "array1的大小:" << array1.getSize() << endl; + cout << "array1的容量:" << array1.getCapacity() << endl; + + cout << "--------------------------" << endl; + + MyArray array2(array1); + array2.Pop_back(); + cout << "array2打印输出:" << endl; + printIntArray(array2); + cout << "array2的大小:" << array2.getSize() << endl; + cout << "array2的容量:" << array2.getCapacity() << endl; +} + +//测试自定义数据类型 +class Person { +public: + Person() {} + Person(string name, int age) { + this->m_Name = name; + this->m_Age = age; + } +public: + string m_Name; + int m_Age; +}; + +void printPersonArray(MyArray& personArr) +{ + for (int i = 0; i < personArr.getSize(); i++) { + cout << "姓名:" << personArr[i].m_Name << " 年龄: " << personArr[i].m_Age << endl; + } + +} + +void test02() +{ + //创建数组 + MyArray pArray(10); + Person p1("孙悟空", 30); + Person p2("韩信", 20); + Person p3("妲己", 18); + Person p4("王昭君", 15); + Person p5("赵云", 24); + + //插入数据 + pArray.Push_back(p1); + pArray.Push_back(p2); + pArray.Push_back(p3); + pArray.Push_back(p4); + pArray.Push_back(p5); + + printPersonArray(pArray); + + cout << "pArray的大小:" << pArray.getSize() << endl; + cout << "pArray的容量:" << pArray.getCapacity() << endl; + +} + +int main() { + + //test01(); + + test02(); + + system("pause"); + + return 0; +} +``` + +总结: + +能够利用所学知识点实现通用的数组 + + + + + +## 2 STL初识 + +### 2.1 STL的诞生 + + + +* 长久以来,软件界一直希望建立一种可重复利用的东西 + +* C++的**面向对象**和**泛型编程**思想,目的就是**复用性的提升** + +* 大多情况下,数据结构和算法都未能有一套标准,导致被迫从事大量重复工作 + +* 为了建立数据结构和算法的一套标准,诞生了**STL** + + + + +### 2.2 STL基本概念 + + + +* STL(Standard Template Library,**标准模板库**) +* STL 从广义上分为: **容器(container) 算法(algorithm) 迭代器(iterator)** +* **容器**和**算法**之间通过**迭代器**进行无缝连接。 +* STL 几乎所有的代码都采用了模板类或者模板函数 + + + + + +### 2.3 STL六大组件 + +STL大体分为六大组件,分别是:**容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器** + + + +1. 容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据。 +2. 算法:各种常用的算法,如sort、find、copy、for_each等 +3. 迭代器:扮演了容器与算法之间的胶合剂。 +4. 仿函数:行为类似函数,可作为算法的某种策略。 +5. 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。 +6. 空间配置器:负责空间的配置与管理。 + + + + + +### 2.4 STL中容器、算法、迭代器 + + + +**容器:**置物之所也 + +STL**容器**就是将运用**最广泛的一些数据结构**实现出来 + +常用的数据结构:数组, 链表,树, 栈, 队列, 集合, 映射表 等 + +这些容器分为**序列式容器**和**关联式容器**两种: + +​ **序列式容器**:强调值的排序,序列式容器中的每个元素均有固定的位置。 +​ **关联式容器**:二叉树结构,各元素之间没有严格的物理上的顺序关系 + + + +**算法:**问题之解法也 + +有限的步骤,解决逻辑或数学上的问题,这一门学科我们叫做算法(Algorithms) + +算法分为:**质变算法**和**非质变算法**。 + +质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝,替换,删除等等 + +非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找、计数、遍历、寻找极值等等 + + + +**迭代器:**容器和算法之间粘合剂 + +提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。 + +每个容器都有自己专属的迭代器 + +迭代器使用非常类似于指针,初学阶段我们可以先理解迭代器为指针 + + + +迭代器种类: + +| 种类 | 功能 | 支持运算 | +| -------------- | -------------------------------------------------------- | --------------------------------------- | +| 输入迭代器 | 对数据的只读访问 | 只读,支持++、==、!= | +| 输出迭代器 | 对数据的只写访问 | 只写,支持++ | +| 前向迭代器 | 读写操作,并能向前推进迭代器 | 读写,支持++、==、!= | +| 双向迭代器 | 读写操作,并能向前和向后操作 | 读写,支持++、--, | +| 随机访问迭代器 | 读写操作,可以以跳跃的方式访问任意数据,功能最强的迭代器 | 读写,支持++、--、[n]、-n、<、<=、>、>= | + +常用的容器中迭代器种类为双向迭代器,和随机访问迭代器 + + + + + + + +### 2.5 容器算法迭代器初识 + + + +了解STL中容器、算法、迭代器概念之后,我们利用代码感受STL的魅力 + +STL中最常用的容器为Vector,可以理解为数组,下面我们将学习如何向这个容器中插入数据、并遍历这个容器 + + + +#### 2.5.1 vector存放内置数据类型 + + + +容器: `vector` + +算法: `for_each` + +迭代器: `vector::iterator` + + + +**示例:** + +```C++ +#include +#include + +void MyPrint(int val) +{ + cout << val << endl; +} + +void test01() { + + //创建vector容器对象,并且通过模板参数指定容器中存放的数据的类型 + vector v; + //向容器中放数据 + v.push_back(10); + v.push_back(20); + v.push_back(30); + v.push_back(40); + + //每一个容器都有自己的迭代器,迭代器是用来遍历容器中的元素 + //v.begin()返回迭代器,这个迭代器指向容器中第一个数据 + //v.end()返回迭代器,这个迭代器指向容器元素的最后一个元素的下一个位置 + //vector::iterator 拿到vector这种容器的迭代器类型 + + vector::iterator pBegin = v.begin(); + vector::iterator pEnd = v.end(); + + //第一种遍历方式: + while (pBegin != pEnd) { + cout << *pBegin << endl; + pBegin++; + } + + + //第二种遍历方式: + for (vector::iterator it = v.begin(); it != v.end(); it++) { + cout << *it << endl; + } + cout << endl; + + //第三种遍历方式: + //使用STL提供标准遍历算法 头文件 algorithm + for_each(v.begin(), v.end(), MyPrint); +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + + + +#### 2.5.2 Vector存放自定义数据类型 + + + +学习目标:vector中存放自定义数据类型,并打印输出 + + + +**示例:** + +```c++ +#include +#include + +//自定义数据类型 +class Person { +public: + Person(string name, int age) { + mName = name; + mAge = age; + } +public: + string mName; + int mAge; +}; +//存放对象 +void test01() { + + vector v; + + //创建数据 + Person p1("aaa", 10); + Person p2("bbb", 20); + Person p3("ccc", 30); + Person p4("ddd", 40); + Person p5("eee", 50); + + v.push_back(p1); + v.push_back(p2); + v.push_back(p3); + v.push_back(p4); + v.push_back(p5); + + for (vector::iterator it = v.begin(); it != v.end(); it++) { + cout << "Name:" << (*it).mName << " Age:" << (*it).mAge << endl; + + } +} + + +//放对象指针 +void test02() { + + vector v; + + //创建数据 + Person p1("aaa", 10); + Person p2("bbb", 20); + Person p3("ccc", 30); + Person p4("ddd", 40); + Person p5("eee", 50); + + v.push_back(&p1); + v.push_back(&p2); + v.push_back(&p3); + v.push_back(&p4); + v.push_back(&p5); + + for (vector::iterator it = v.begin(); it != v.end(); it++) { + Person * p = (*it); + cout << "Name:" << p->mName << " Age:" << (*it)->mAge << endl; + } +} + + +int main() { + + test01(); + + test02(); + + system("pause"); + + return 0; +} +``` + + + +#### 2.5.3 Vector容器嵌套容器 + + + +学习目标:容器中嵌套容器,我们将所有数据进行遍历输出 + + + +**示例:** + +```C++ +#include + +//容器嵌套容器 +void test01() { + + vector< vector > v; + + vector v1; + vector v2; + vector v3; + vector v4; + + for (int i = 0; i < 4; i++) { + v1.push_back(i + 1); + v2.push_back(i + 2); + v3.push_back(i + 3); + v4.push_back(i + 4); + } + + //将容器元素插入到vector v中 + v.push_back(v1); + v.push_back(v2); + v.push_back(v3); + v.push_back(v4); + + + for (vector>::iterator it = v.begin(); it != v.end(); it++) { + + for (vector::iterator vit = (*it).begin(); vit != (*it).end(); vit++) { + cout << *vit << " "; + } + cout << endl; + } + +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + + + + + +## 3 STL- 常用容器 + +### 3.1 string容器 + + + +#### 3.1.1 string基本概念 + +**本质:** + +* string是C++风格的字符串,而string本质上是一个类 + + + +**string和char * 区别:** + +* char * 是一个指针 +* string是一个类,类内部封装了char\*,管理这个字符串,是一个char*型的容器。 + + + +**特点:** + +string 类内部封装了很多成员方法 + +例如:查找find,拷贝copy,删除delete 替换replace,插入insert + +string管理char*所分配的内存,不用担心复制越界和取值越界等,由类内部进行负责 + + + +#### 3.1.2 string构造函数 + +构造函数原型: + +* `string();` //创建一个空的字符串 例如: string str; + `string(const char* s);` //使用字符串s初始化 +* `string(const string& str);` //使用一个string对象初始化另一个string对象 +* `string(int n, char c);` //使用n个字符c初始化 + + + +**示例:** + +```C++ +#include +//string构造 +void test01() +{ + string s1; //创建空字符串,调用无参构造函数 + cout << "str1 = " << s1 << endl; + + const char* str = "hello world"; + string s2(str); //把c_string转换成了string + + cout << "str2 = " << s2 << endl; + + string s3(s2); //调用拷贝构造函数 + cout << "str3 = " << s3 << endl; + + string s4(10, 'a'); + cout << "str3 = " << s3 << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结:string的多种构造方式没有可比性,灵活使用即可 + + + + + + + + + +#### 3.1.3 string赋值操作 + +功能描述: + +* 给string字符串进行赋值 + + + +赋值的函数原型: + +* `string& operator=(const char* s);` //char*类型字符串 赋值给当前的字符串 +* `string& operator=(const string &s);` //把字符串s赋给当前的字符串 +* `string& operator=(char c);` //字符赋值给当前的字符串 +* `string& assign(const char *s);` //把字符串s赋给当前的字符串 +* `string& assign(const char *s, int n);` //把字符串s的前n个字符赋给当前的字符串 +* `string& assign(const string &s);` //把字符串s赋给当前字符串 +* `string& assign(int n, char c);` //用n个字符c赋给当前字符串 + + + + +**示例:** + +```C++ +//赋值 +void test01() +{ + string str1; + str1 = "hello world"; + cout << "str1 = " << str1 << endl; + + string str2; + str2 = str1; + cout << "str2 = " << str2 << endl; + + string str3; + str3 = 'a'; + cout << "str3 = " << str3 << endl; + + string str4; + str4.assign("hello c++"); + cout << "str4 = " << str4 << endl; + + string str5; + str5.assign("hello c++",5); + cout << "str5 = " << str5 << endl; + + + string str6; + str6.assign(str5); + cout << "str6 = " << str6 << endl; + + string str7; + str7.assign(5, 'x'); + cout << "str7 = " << str7 << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结: + +​ string的赋值方式很多,`operator=` 这种方式是比较实用的 + + + + + + + + + +#### 3.1.4 string字符串拼接 + +**功能描述:** + +* 实现在字符串末尾拼接字符串 + + + +**函数原型:** + +* `string& operator+=(const char* str);` //重载+=操作符 +* `string& operator+=(const char c);` //重载+=操作符 +* `string& operator+=(const string& str);` //重载+=操作符 +* `string& append(const char *s); ` //把字符串s连接到当前字符串结尾 +* `string& append(const char *s, int n);` //把字符串s的前n个字符连接到当前字符串结尾 +* `string& append(const string &s);` //同operator+=(const string& str) +* `string& append(const string &s, int pos, int n);`//字符串s中从pos开始的n个字符连接到字符串结尾 + + + + +**示例:** + + +```C++ +//字符串拼接 +void test01() +{ + string str1 = "我"; + + str1 += "爱玩游戏"; + + cout << "str1 = " << str1 << endl; + + str1 += ':'; + + cout << "str1 = " << str1 << endl; + + string str2 = "LOL DNF"; + + str1 += str2; + + cout << "str1 = " << str1 << endl; + + string str3 = "I"; + str3.append(" love "); + str3.append("game abcde", 4); + //str3.append(str2); + str3.append(str2, 4, 3); // 从下标4位置开始 ,截取3个字符,拼接到字符串末尾 + cout << "str3 = " << str3 << endl; +} +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结:字符串拼接的重载版本很多,初学阶段记住几种即可 + + + + + + + +#### 3.1.5 string查找和替换 + +**功能描述:** + +* 查找:查找指定字符串是否存在 +* 替换:在指定的位置替换字符串 + + + +**函数原型:** + +* `int find(const string& str, int pos = 0) const;` //查找str第一次出现位置,从pos开始查找 +* `int find(const char* s, int pos = 0) const; ` //查找s第一次出现位置,从pos开始查找 +* `int find(const char* s, int pos, int n) const; ` //从pos位置查找s的前n个字符第一次位置 +* `int find(const char c, int pos = 0) const; ` //查找字符c第一次出现位置 +* `int rfind(const string& str, int pos = npos) const;` //查找str最后一次位置,从pos开始查找 +* `int rfind(const char* s, int pos = npos) const;` //查找s最后一次出现位置,从pos开始查找 +* `int rfind(const char* s, int pos, int n) const;` //从pos查找s的前n个字符最后一次位置 +* `int rfind(const char c, int pos = 0) const; ` //查找字符c最后一次出现位置 +* `string& replace(int pos, int n, const string& str); ` //替换从pos开始n个字符为字符串str +* `string& replace(int pos, int n,const char* s); ` //替换从pos开始的n个字符为字符串s + + + + +**示例:** + +```C++ +//查找和替换 +void test01() +{ + //查找 + string str1 = "abcdefgde"; + + int pos = str1.find("de"); + + if (pos == -1) + { + cout << "未找到" << endl; + } + else + { + cout << "pos = " << pos << endl; + } + + + pos = str1.rfind("de"); + + cout << "pos = " << pos << endl; + +} + +void test02() +{ + //替换 + string str1 = "abcdefgde"; + str1.replace(1, 3, "1111"); + + cout << "str1 = " << str1 << endl; +} + +int main() { + + //test01(); + //test02(); + + system("pause"); + + return 0; +} +``` + +总结: + +* find查找是从左往后,rfind从右往左 +* find找到字符串后返回查找的第一个字符位置,找不到返回-1 +* replace在替换时,要指定从哪个位置起,多少个字符,替换成什么样的字符串 + + + + + + + + + + + + + + + + +#### 3.1.6 string字符串比较 + +**功能描述:** + +* 字符串之间的比较 + +**比较方式:** + +* 字符串比较是按字符的ASCII码进行对比 + += 返回 0 + +\> 返回 1 + +< 返回 -1 + + + +**函数原型:** + +* `int compare(const string &s) const; ` //与字符串s比较 +* `int compare(const char *s) const;` //与字符串s比较 + + + + + +**示例:** + +```C++ +//字符串比较 +void test01() +{ + + string s1 = "hello"; + string s2 = "aello"; + + int ret = s1.compare(s2); + + if (ret == 0) { + cout << "s1 等于 s2" << endl; + } + else if (ret > 0) + { + cout << "s1 大于 s2" << endl; + } + else + { + cout << "s1 小于 s2" << endl; + } + +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结:字符串对比主要是用于比较两个字符串是否相等,判断谁大谁小的意义并不是很大 + + + + + +#### 3.1.7 string字符存取 + + + +string中单个字符存取方式有两种 + + + +* `char& operator[](int n); ` //通过[]方式取字符 +* `char& at(int n); ` //通过at方法获取字符 + + + + + +**示例:** + +```C++ +void test01() +{ + string str = "hello world"; + + for (int i = 0; i < str.size(); i++) + { + cout << str[i] << " "; + } + cout << endl; + + for (int i = 0; i < str.size(); i++) + { + cout << str.at(i) << " "; + } + cout << endl; + + + //字符修改 + str[0] = 'x'; + str.at(1) = 'x'; + cout << str << endl; + +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结:string字符串中单个字符存取有两种方式,利用 [ ] 或 at + + + + + + + + + +#### 3.1.8 string插入和删除 + +**功能描述:** + +* 对string字符串进行插入和删除字符操作 + +**函数原型:** + +* `string& insert(int pos, const char* s); ` //插入字符串 +* `string& insert(int pos, const string& str); ` //插入字符串 +* `string& insert(int pos, int n, char c);` //在指定位置插入n个字符c +* `string& erase(int pos, int n = npos);` //删除从Pos开始的n个字符 + + + + + +**示例:** + +```C++ +//字符串插入和删除 +void test01() +{ + string str = "hello"; + str.insert(1, "111"); + cout << str << endl; + + str.erase(1, 3); //从1号位置开始3个字符 + cout << str << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +**总结:**插入和删除的起始下标都是从0开始 + + + + + + + + + + + +#### 3.1.9 string子串 + +**功能描述:** + +* 从字符串中获取想要的子串 + + + +**函数原型:** + +* `string substr(int pos = 0, int n = npos) const;` //返回由pos开始的n个字符组成的字符串 + + + + +**示例:** + +```C++ +//子串 +void test01() +{ + + string str = "abcdefg"; + string subStr = str.substr(1, 3); + cout << "subStr = " << subStr << endl; + + string email = "hello@sina.com"; + int pos = email.find("@"); + string username = email.substr(0, pos); + cout << "username: " << username << endl; + +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +**总结:**灵活的运用求子串功能,可以在实际开发中获取有效的信息 + + + + + + + +### 3.2 vector容器 + + + +#### 3.2.1 vector基本概念 + +**功能:** + +* vector数据结构和**数组非常相似**,也称为**单端数组** + + + +**vector与普通数组区别:** + +* 不同之处在于数组是静态空间,而vector可以**动态扩展** + + + +**动态扩展:** + +* 并不是在原空间之后续接新空间,而是找更大的内存空间,然后将原数据拷贝新空间,释放原空间 + + + +![说明: 2015-11-10_151152](assets/clip_image002.jpg) + + + +* vector容器的迭代器是支持随机访问的迭代器 + + + + + +#### 3.2.2 vector构造函数 + + + +**功能描述:** + +* 创建vector容器 + + + +**函数原型:** + +* `vector v; ` //采用模板实现类实现,默认构造函数 +* `vector(v.begin(), v.end()); ` //将v[begin(), end())区间中的元素拷贝给本身。 +* `vector(n, elem);` //构造函数将n个elem拷贝给本身。 +* `vector(const vector &vec);` //拷贝构造函数。 + + + + +**示例:** + + +```C++ +#include + +void printVector(vector& v) { + + for (vector::iterator it = v.begin(); it != v.end(); it++) { + cout << *it << " "; + } + cout << endl; +} + +void test01() +{ + vector v1; //无参构造 + for (int i = 0; i < 10; i++) + { + v1.push_back(i); + } + printVector(v1); + + vector v2(v1.begin(), v1.end()); + printVector(v2); + + vector v3(10, 100); + printVector(v3); + + vector v4(v3); + printVector(v4); +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +**总结:**vector的多种构造方式没有可比性,灵活使用即可 + + + + + + + + + +#### 3.2.3 vector赋值操作 + + + +**功能描述:** + +* 给vector容器进行赋值 + + + +**函数原型:** + +* `vector& operator=(const vector &vec);`//重载等号操作符 + + +* `assign(beg, end);` //将[beg, end)区间中的数据拷贝赋值给本身。 +* `assign(n, elem);` //将n个elem拷贝赋值给本身。 + + + + + +**示例:** + +```C++ +#include + +void printVector(vector& v) { + + for (vector::iterator it = v.begin(); it != v.end(); it++) { + cout << *it << " "; + } + cout << endl; +} + +//赋值操作 +void test01() +{ + vector v1; //无参构造 + for (int i = 0; i < 10; i++) + { + v1.push_back(i); + } + printVector(v1); + + vectorv2; + v2 = v1; + printVector(v2); + + vectorv3; + v3.assign(v1.begin(), v1.end()); + printVector(v3); + + vectorv4; + v4.assign(10, 100); + printVector(v4); +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} + +``` + +总结: vector赋值方式比较简单,使用operator=,或者assign都可以 + + + + + + + +#### 3.2.4 vector容量和大小 + +**功能描述:** + +* 对vector容器的容量和大小操作 + + + +**函数原型:** + +* `empty(); ` //判断容器是否为空 + +* `capacity();` //容器的容量 + +* `size();` //返回容器中元素的个数 + +* `resize(int num);` //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。 + + ​ //如果容器变短,则末尾超出容器长度的元素被删除。 + +* `resize(int num, elem);` //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。 + + ​ //如果容器变短,则末尾超出容器长度的元素被删除 + + + + +**示例:** + + +```C++ +#include + +void printVector(vector& v) { + + for (vector::iterator it = v.begin(); it != v.end(); it++) { + cout << *it << " "; + } + cout << endl; +} + +void test01() +{ + vector v1; + for (int i = 0; i < 10; i++) + { + v1.push_back(i); + } + printVector(v1); + if (v1.empty()) + { + cout << "v1为空" << endl; + } + else + { + cout << "v1不为空" << endl; + cout << "v1的容量 = " << v1.capacity() << endl; + cout << "v1的大小 = " << v1.size() << endl; + } + + //resize 重新指定大小 ,若指定的更大,默认用0填充新位置,可以利用重载版本替换默认填充 + v1.resize(15,10); + printVector(v1); + + //resize 重新指定大小 ,若指定的更小,超出部分元素被删除 + v1.resize(5); + printVector(v1); +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} + +``` + +总结: + +* 判断是否为空 --- empty +* 返回元素个数 --- size +* 返回容器容量 --- capacity +* 重新指定大小 --- resize + + + + + + + + + + + + + +#### 3.2.5 vector插入和删除 + +**功能描述:** + +* 对vector容器进行插入、删除操作 + + + +**函数原型:** + +* `push_back(ele);` //尾部插入元素ele +* `pop_back();` //删除最后一个元素 +* `insert(const_iterator pos, ele);` //迭代器指向位置pos插入元素ele +* `insert(const_iterator pos, int count,ele);`//迭代器指向位置pos插入count个元素ele +* `erase(const_iterator pos);` //删除迭代器指向的元素 +* `erase(const_iterator start, const_iterator end);`//删除迭代器从start到end之间的元素 +* `clear();` //删除容器中所有元素 + + + + + +**示例:** + +```C++ + +#include + +void printVector(vector& v) { + + for (vector::iterator it = v.begin(); it != v.end(); it++) { + cout << *it << " "; + } + cout << endl; +} + +//插入和删除 +void test01() +{ + vector v1; + //尾插 + v1.push_back(10); + v1.push_back(20); + v1.push_back(30); + v1.push_back(40); + v1.push_back(50); + printVector(v1); + //尾删 + v1.pop_back(); + printVector(v1); + //插入 + v1.insert(v1.begin(), 100); + printVector(v1); + + v1.insert(v1.begin(), 2, 1000); + printVector(v1); + + //删除 + v1.erase(v1.begin()); + printVector(v1); + + //清空 + v1.erase(v1.begin(), v1.end()); + v1.clear(); + printVector(v1); +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结: + +* 尾插 --- push_back +* 尾删 --- pop_back +* 插入 --- insert (位置迭代器) +* 删除 --- erase (位置迭代器) +* 清空 --- clear + + + + + + + + + + + + + +#### 3.2.6 vector数据存取 + + + +**功能描述:** + +* 对vector中的数据的存取操作 + + + +**函数原型:** + +* `at(int idx); ` //返回索引idx所指的数据 +* `operator[]; ` //返回索引idx所指的数据 +* `front(); ` //返回容器中第一个数据元素 +* `back();` //返回容器中最后一个数据元素 + + + + + +**示例:** + +```C++ +#include + +void test01() +{ + vectorv1; + for (int i = 0; i < 10; i++) + { + v1.push_back(i); + } + + for (int i = 0; i < v1.size(); i++) + { + cout << v1[i] << " "; + } + cout << endl; + + for (int i = 0; i < v1.size(); i++) + { + cout << v1.at(i) << " "; + } + cout << endl; + + cout << "v1的第一个元素为: " << v1.front() << endl; + cout << "v1的最后一个元素为: " << v1.back() << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结: + +* 除了用迭代器获取vector容器中元素,[ ]和at也可以 +* front返回容器第一个元素 +* back返回容器最后一个元素 + + + + + + + + + + + +#### 3.2.7 vector互换容器 + +**功能描述:** + +* 实现两个容器内元素进行互换 + + + +**函数原型:** + +* `swap(vec);` // 将vec与本身的元素互换 + + + + + +**示例:** + +```C++ +#include + +void printVector(vector& v) { + + for (vector::iterator it = v.begin(); it != v.end(); it++) { + cout << *it << " "; + } + cout << endl; +} + +void test01() +{ + vectorv1; + for (int i = 0; i < 10; i++) + { + v1.push_back(i); + } + printVector(v1); + + vectorv2; + for (int i = 10; i > 0; i--) + { + v2.push_back(i); + } + printVector(v2); + + //互换容器 + cout << "互换后" << endl; + v1.swap(v2); + printVector(v1); + printVector(v2); +} + +void test02() +{ + vector v; + for (int i = 0; i < 100000; i++) { + v.push_back(i); + } + + cout << "v的容量为:" << v.capacity() << endl; + cout << "v的大小为:" << v.size() << endl; + + v.resize(3); + + cout << "v的容量为:" << v.capacity() << endl; + cout << "v的大小为:" << v.size() << endl; + + //收缩内存 + vector(v).swap(v); //匿名对象 + + cout << "v的容量为:" << v.capacity() << endl; + cout << "v的大小为:" << v.size() << endl; +} + +int main() { + + test01(); + + test02(); + + system("pause"); + + return 0; +} + +``` + +总结:swap可以使两个容器互换,可以达到实用的收缩内存效果 + + + + + + + + + +#### 3.2.8 vector预留空间 + +**功能描述:** + +* 减少vector在动态扩展容量时的扩展次数 + + + +**函数原型:** + +* `reserve(int len);`//容器预留len个元素长度,预留位置不初始化,元素不可访问。 + + + +**示例:** + +```C++ +#include + +void test01() +{ + vector v; + + //预留空间 + v.reserve(100000); + + int num = 0; + int* p = NULL; + for (int i = 0; i < 100000; i++) { + v.push_back(i); + if (p != &v[0]) { + p = &v[0]; + num++; + } + } + + cout << "num:" << num << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结:如果数据量较大,可以一开始利用reserve预留空间 + + + + + + + + + + + +### 3.3 deque容器 + +#### 3.3.1 deque容器基本概念 + + + +**功能:** + +* 双端数组,可以对头端进行插入删除操作 + + + +**deque与vector区别:** + +* vector对于头部的插入删除效率低,数据量越大,效率越低 +* deque相对而言,对头部的插入删除速度回比vector快 +* vector访问元素时的速度会比deque快,这和两者内部实现有关 + +![说明: 2015-11-19_204101](assets/clip_image002-1547547642923.jpg) + + + +deque内部工作原理: + +deque内部有个**中控器**,维护每段缓冲区中的内容,缓冲区中存放真实数据 + +中控器维护的是每个缓冲区的地址,使得使用deque时像一片连续的内存空间 + +![clip_image002-1547547896341](assets/clip_image002-1547547896341.jpg) + +* deque容器的迭代器也是支持随机访问的 + + + +#### 3.3.2 deque构造函数 + +**功能描述:** + +* deque容器构造 + + + +**函数原型:** + +* `deque` deqT; //默认构造形式 +* `deque(beg, end);` //构造函数将[beg, end)区间中的元素拷贝给本身。 +* `deque(n, elem);` //构造函数将n个elem拷贝给本身。 +* `deque(const deque &deq);` //拷贝构造函数 + + + + + +**示例:** + +```C++ +#include + +void printDeque(const deque& d) +{ + for (deque::const_iterator it = d.begin(); it != d.end(); it++) { + cout << *it << " "; + + } + cout << endl; +} +//deque构造 +void test01() { + + deque d1; //无参构造函数 + for (int i = 0; i < 10; i++) + { + d1.push_back(i); + } + printDeque(d1); + deque d2(d1.begin(),d1.end()); + printDeque(d2); + + dequed3(10,100); + printDeque(d3); + + dequed4 = d3; + printDeque(d4); +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +**总结:**deque容器和vector容器的构造方式几乎一致,灵活使用即可 + + + + + + + + + +#### 3.3.3 deque赋值操作 + + + +**功能描述:** + +* 给deque容器进行赋值 + + + +**函数原型:** + +* `deque& operator=(const deque &deq); ` //重载等号操作符 + + +* `assign(beg, end);` //将[beg, end)区间中的数据拷贝赋值给本身。 +* `assign(n, elem);` //将n个elem拷贝赋值给本身。 + + + + + +**示例:** + +```C++ +#include + +void printDeque(const deque& d) +{ + for (deque::const_iterator it = d.begin(); it != d.end(); it++) { + cout << *it << " "; + + } + cout << endl; +} +//赋值操作 +void test01() +{ + deque d1; + for (int i = 0; i < 10; i++) + { + d1.push_back(i); + } + printDeque(d1); + + dequed2; + d2 = d1; + printDeque(d2); + + dequed3; + d3.assign(d1.begin(), d1.end()); + printDeque(d3); + + dequed4; + d4.assign(10, 100); + printDeque(d4); + +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结:deque赋值操作也与vector相同,需熟练掌握 + + + + + + + +#### 3.3.4 deque大小操作 + +**功能描述:** + +* 对deque容器的大小进行操作 + + + +**函数原型:** + +* `deque.empty();` //判断容器是否为空 + +* `deque.size();` //返回容器中元素的个数 + +* `deque.resize(num);` //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。 + + ​ //如果容器变短,则末尾超出容器长度的元素被删除。 + +* `deque.resize(num, elem);` //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。 + + ​ //如果容器变短,则末尾超出容器长度的元素被删除。 + + + + + +**示例:** + +```C++ +#include + +void printDeque(const deque& d) +{ + for (deque::const_iterator it = d.begin(); it != d.end(); it++) { + cout << *it << " "; + + } + cout << endl; +} + +//大小操作 +void test01() +{ + deque d1; + for (int i = 0; i < 10; i++) + { + d1.push_back(i); + } + printDeque(d1); + + //判断容器是否为空 + if (d1.empty()) { + cout << "d1为空!" << endl; + } + else { + cout << "d1不为空!" << endl; + //统计大小 + cout << "d1的大小为:" << d1.size() << endl; + } + + //重新指定大小 + d1.resize(15, 1); + printDeque(d1); + + d1.resize(5); + printDeque(d1); +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结: + +* deque没有容量的概念 +* 判断是否为空 --- empty +* 返回元素个数 --- size +* 重新指定个数 --- resize + + + + + + + + + +#### 3.3.5 deque 插入和删除 + +**功能描述:** + +* 向deque容器中插入和删除数据 + + + +**函数原型:** + +两端插入操作: + +- `push_back(elem);` //在容器尾部添加一个数据 +- `push_front(elem);` //在容器头部插入一个数据 +- `pop_back();` //删除容器最后一个数据 +- `pop_front();` //删除容器第一个数据 + +指定位置操作: + +* `insert(pos,elem);` //在pos位置插入一个elem元素的拷贝,返回新数据的位置。 + +* `insert(pos,n,elem);` //在pos位置插入n个elem数据,无返回值。 + +* `insert(pos,beg,end);` //在pos位置插入[beg,end)区间的数据,无返回值。 + +* `clear();` //清空容器的所有数据 + +* `erase(beg,end);` //删除[beg,end)区间的数据,返回下一个数据的位置。 + +* `erase(pos);` //删除pos位置的数据,返回下一个数据的位置。 + + + + + + + +**示例:** + +```C++ +#include + +void printDeque(const deque& d) +{ + for (deque::const_iterator it = d.begin(); it != d.end(); it++) { + cout << *it << " "; + + } + cout << endl; +} +//两端操作 +void test01() +{ + deque d; + //尾插 + d.push_back(10); + d.push_back(20); + //头插 + d.push_front(100); + d.push_front(200); + + printDeque(d); + + //尾删 + d.pop_back(); + //头删 + d.pop_front(); + printDeque(d); +} + +//插入 +void test02() +{ + deque d; + d.push_back(10); + d.push_back(20); + d.push_front(100); + d.push_front(200); + printDeque(d); + + d.insert(d.begin(), 1000); + printDeque(d); + + d.insert(d.begin(), 2,10000); + printDeque(d); + + dequed2; + d2.push_back(1); + d2.push_back(2); + d2.push_back(3); + + d.insert(d.begin(), d2.begin(), d2.end()); + printDeque(d); + +} + +//删除 +void test03() +{ + deque d; + d.push_back(10); + d.push_back(20); + d.push_front(100); + d.push_front(200); + printDeque(d); + + d.erase(d.begin()); + printDeque(d); + + d.erase(d.begin(), d.end()); + d.clear(); + printDeque(d); +} + +int main() { + + //test01(); + + //test02(); + + test03(); + + system("pause"); + + return 0; +} + +``` + +总结: + +* 插入和删除提供的位置是迭代器! +* 尾插 --- push_back +* 尾删 --- pop_back +* 头插 --- push_front +* 头删 --- pop_front + + + + + + + + + + + +#### 3.3.6 deque 数据存取 + + + +**功能描述:** + +* 对deque 中的数据的存取操作 + + + +**函数原型:** + +- `at(int idx); ` //返回索引idx所指的数据 +- `operator[]; ` //返回索引idx所指的数据 +- `front(); ` //返回容器中第一个数据元素 +- `back();` //返回容器中最后一个数据元素 + + + +**示例:** + +```C++ +#include + +void printDeque(const deque& d) +{ + for (deque::const_iterator it = d.begin(); it != d.end(); it++) { + cout << *it << " "; + + } + cout << endl; +} + +//数据存取 +void test01() +{ + + deque d; + d.push_back(10); + d.push_back(20); + d.push_front(100); + d.push_front(200); + + for (int i = 0; i < d.size(); i++) { + cout << d[i] << " "; + } + cout << endl; + + + for (int i = 0; i < d.size(); i++) { + cout << d.at(i) << " "; + } + cout << endl; + + cout << "front:" << d.front() << endl; + + cout << "back:" << d.back() << endl; + +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结: + +- 除了用迭代器获取deque容器中元素,[ ]和at也可以 +- front返回容器第一个元素 +- back返回容器最后一个元素 + + + + + + + + + + + + + +#### 3.3.7 deque 排序 + +**功能描述:** + +* 利用算法实现对deque容器进行排序 + + + +**算法:** + +* `sort(iterator beg, iterator end)` //对beg和end区间内元素进行排序 + + + + + +**示例:** + +```C++ +#include +#include + +void printDeque(const deque& d) +{ + for (deque::const_iterator it = d.begin(); it != d.end(); it++) { + cout << *it << " "; + + } + cout << endl; +} + +void test01() +{ + + deque d; + d.push_back(10); + d.push_back(20); + d.push_front(100); + d.push_front(200); + + printDeque(d); + sort(d.begin(), d.end()); + printDeque(d); + +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结:sort算法非常实用,使用时包含头文件 algorithm即可 + + + + + + + + + + + +### 3.4 案例-评委打分 + + + +#### 3.4.1 案例描述 + +有5名选手:选手ABCDE,10个评委分别对每一名选手打分,去除最高分,去除评委中最低分,取平均分。 + + + +#### 3.4.2 实现步骤 + +1. 创建五名选手,放到vector中 +2. 遍历vector容器,取出来每一个选手,执行for循环,可以把10个评分打分存到deque容器中 +3. sort算法对deque容器中分数排序,去除最高和最低分 +4. deque容器遍历一遍,累加总分 +5. 获取平均分 + + + + + +**示例代码:** + +```C++ +//选手类 +class Person +{ +public: + Person(string name, int score) + { + this->m_Name = name; + this->m_Score = score; + } + + string m_Name; //姓名 + int m_Score; //平均分 +}; + +void createPerson(vector&v) +{ + string nameSeed = "ABCDE"; + for (int i = 0; i < 5; i++) + { + string name = "选手"; + name += nameSeed[i]; + + int score = 0; + + Person p(name, score); + + //将创建的person对象 放入到容器中 + v.push_back(p); + } +} + +//打分 +void setScore(vector&v) +{ + for (vector::iterator it = v.begin(); it != v.end(); it++) + { + //将评委的分数 放入到deque容器中 + dequed; + for (int i = 0; i < 10; i++) + { + int score = rand() % 41 + 60; // 60 ~ 100 + d.push_back(score); + } + + //cout << "选手: " << it->m_Name << " 打分: " << endl; + //for (deque::iterator dit = d.begin(); dit != d.end(); dit++) + //{ + // cout << *dit << " "; + //} + //cout << endl; + + //排序 + sort(d.begin(), d.end()); + + //去除最高和最低分 + d.pop_back(); + d.pop_front(); + + //取平均分 + int sum = 0; + for (deque::iterator dit = d.begin(); dit != d.end(); dit++) + { + sum += *dit; //累加每个评委的分数 + } + + int avg = sum / d.size(); + + //将平均分 赋值给选手身上 + it->m_Score = avg; + } + +} + +void showScore(vector&v) +{ + for (vector::iterator it = v.begin(); it != v.end(); it++) + { + cout << "姓名: " << it->m_Name << " 平均分: " << it->m_Score << endl; + } +} + +int main() { + + //随机数种子 + srand((unsigned int)time(NULL)); + + //1、创建5名选手 + vectorv; //存放选手容器 + createPerson(v); + + //测试 + //for (vector::iterator it = v.begin(); it != v.end(); it++) + //{ + // cout << "姓名: " << (*it).m_Name << " 分数: " << (*it).m_Score << endl; + //} + + //2、给5名选手打分 + setScore(v); + + //3、显示最后得分 + showScore(v); + + system("pause"); + + return 0; +} +``` + +**总结:** 选取不同的容器操作数据,可以提升代码的效率 + + + + + + + +### 3.5 stack容器 + +#### 3.5.1 stack 基本概念 + + + +**概念:**stack是一种**先进后出**(First In Last Out,FILO)的数据结构,它只有一个出口 + + + + + +![说明: 2015-11-15_195707](assets/clip_image002-1547604555425.jpg) + +栈中只有顶端的元素才可以被外界使用,因此栈不允许有遍历行为 + +栈中进入数据称为 --- **入栈** `push` + +栈中弹出数据称为 --- **出栈** `pop` + + + +生活中的栈: + +![img](assets/clip_image002.png) + + + + + +![img](assets/clip_image002-1547605111510.jpg) + + + +#### 3.5.2 stack 常用接口 + +功能描述:栈容器常用的对外接口 + + + +构造函数: + +* `stack stk;` //stack采用模板类实现, stack对象的默认构造形式 +* `stack(const stack &stk);` //拷贝构造函数 + +赋值操作: + +* `stack& operator=(const stack &stk);` //重载等号操作符 + +数据存取: + +* `push(elem);` //向栈顶添加元素 +* `pop();` //从栈顶移除第一个元素 +* `top(); ` //返回栈顶元素 + +大小操作: + +* `empty();` //判断堆栈是否为空 +* `size(); ` //返回栈的大小 + + + + + +**示例:** + +```C++ +#include + +//栈容器常用接口 +void test01() +{ + //创建栈容器 栈容器必须符合先进后出 + stack s; + + //向栈中添加元素,叫做 压栈 入栈 + s.push(10); + s.push(20); + s.push(30); + + while (!s.empty()) { + //输出栈顶元素 + cout << "栈顶元素为: " << s.top() << endl; + //弹出栈顶元素 + s.pop(); + } + cout << "栈的大小为:" << s.size() << endl; + +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结: + +* 入栈 --- push +* 出栈 --- pop +* 返回栈顶 --- top +* 判断栈是否为空 --- empty +* 返回栈大小 --- size + + + + + + + + + + + +### 3.6 queue 容器 + +#### 3.6.1 queue 基本概念 + + + +**概念:**Queue是一种**先进先出**(First In First Out,FIFO)的数据结构,它有两个出口 + + + + + + + +![说明: 2015-11-15_214429](assets/clip_image002-1547606475892.jpg) + +队列容器允许从一端新增元素,从另一端移除元素 + +队列中只有队头和队尾才可以被外界使用,因此队列不允许有遍历行为 + +队列中进数据称为 --- **入队** `push` + +队列中出数据称为 --- **出队** `pop` + + + +生活中的队列: + +![1547606785041](assets/1547606785041.png) + + + + + + + +#### 3.6.2 queue 常用接口 + + + +功能描述:栈容器常用的对外接口 + + + +构造函数: + +- `queue que;` //queue采用模板类实现,queue对象的默认构造形式 +- `queue(const queue &que);` //拷贝构造函数 + +赋值操作: + +- `queue& operator=(const queue &que);` //重载等号操作符 + +数据存取: + +- `push(elem);` //往队尾添加元素 +- `pop();` //从队头移除第一个元素 +- `back();` //返回最后一个元素 +- `front(); ` //返回第一个元素 + +大小操作: + +- `empty();` //判断堆栈是否为空 +- `size(); ` //返回栈的大小 + + + +**示例:** + +```C++ +#include +#include +class Person +{ +public: + Person(string name, int age) + { + this->m_Name = name; + this->m_Age = age; + } + + string m_Name; + int m_Age; +}; + +void test01() { + + //创建队列 + queue q; + + //准备数据 + Person p1("唐僧", 30); + Person p2("孙悟空", 1000); + Person p3("猪八戒", 900); + Person p4("沙僧", 800); + + //向队列中添加元素 入队操作 + q.push(p1); + q.push(p2); + q.push(p3); + q.push(p4); + + //队列不提供迭代器,更不支持随机访问 + while (!q.empty()) { + //输出队头元素 + cout << "队头元素-- 姓名: " << q.front().m_Name + << " 年龄: "<< q.front().m_Age << endl; + + cout << "队尾元素-- 姓名: " << q.back().m_Name + << " 年龄: " << q.back().m_Age << endl; + + cout << endl; + //弹出队头元素 + q.pop(); + } + + cout << "队列大小为:" << q.size() << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结: + +- 入队 --- push +- 出队 --- pop +- 返回队头元素 --- front +- 返回队尾元素 --- back +- 判断队是否为空 --- empty +- 返回队列大小 --- size + + + + + + + + + + + + + + + +### 3.7 list容器 + +#### 3.7.1 list基本概念 + + + +**功能:**将数据进行链式存储 + +**链表**(list)是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的 + + + +链表的组成:链表由一系列**结点**组成 + + + +结点的组成:一个是存储数据元素的**数据域**,另一个是存储下一个结点地址的**指针域** + + + +STL中的链表是一个双向循环链表 + + + +![说明: 2015-11-15_225145](assets/clip_image002-1547608564071.jpg) + +由于链表的存储方式并不是连续的内存空间,因此链表list中的迭代器只支持前移和后移,属于**双向迭代器** + + + +list的优点: + +* 采用动态存储分配,不会造成内存浪费和溢出 +* 链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素 + +list的缺点: + +* 链表灵活,但是空间(指针域) 和 时间(遍历)额外耗费较大 + + + +List有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的。 + + + +总结:STL中**List和vector是两个最常被使用的容器**,各有优缺点 + + + + + +#### 3.7.2 list构造函数 + +**功能描述:** + +* 创建list容器 + + + +**函数原型:** + +* `list lst;` //list采用采用模板类实现,对象的默认构造形式: +* `list(beg,end);` //构造函数将[beg, end)区间中的元素拷贝给本身。 +* `list(n,elem);` //构造函数将n个elem拷贝给本身。 +* `list(const list &lst);` //拷贝构造函数。 + + + + + +**示例:** + +```C++ +#include + +void printList(const list& L) { + + for (list::const_iterator it = L.begin(); it != L.end(); it++) { + cout << *it << " "; + } + cout << endl; +} + +void test01() +{ + listL1; + L1.push_back(10); + L1.push_back(20); + L1.push_back(30); + L1.push_back(40); + + printList(L1); + + listL2(L1.begin(),L1.end()); + printList(L2); + + listL3(L2); + printList(L3); + + listL4(10, 1000); + printList(L4); +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结:list构造方式同其他几个STL常用容器,熟练掌握即可 + + + + + + + + + + + + + +#### 3.7.3 list 赋值和交换 + +**功能描述:** + +* 给list容器进行赋值,以及交换list容器 + +**函数原型:** + +* `assign(beg, end);` //将[beg, end)区间中的数据拷贝赋值给本身。 +* `assign(n, elem);` //将n个elem拷贝赋值给本身。 +* `list& operator=(const list &lst);` //重载等号操作符 +* `swap(lst);` //将lst与本身的元素互换。 + + + +**示例:** + +```C++ +#include + +void printList(const list& L) { + + for (list::const_iterator it = L.begin(); it != L.end(); it++) { + cout << *it << " "; + } + cout << endl; +} + +//赋值和交换 +void test01() +{ + listL1; + L1.push_back(10); + L1.push_back(20); + L1.push_back(30); + L1.push_back(40); + printList(L1); + + //赋值 + listL2; + L2 = L1; + printList(L2); + + listL3; + L3.assign(L2.begin(), L2.end()); + printList(L3); + + listL4; + L4.assign(10, 100); + printList(L4); + +} + +//交换 +void test02() +{ + + listL1; + L1.push_back(10); + L1.push_back(20); + L1.push_back(30); + L1.push_back(40); + + listL2; + L2.assign(10, 100); + + cout << "交换前: " << endl; + printList(L1); + printList(L2); + + cout << endl; + + L1.swap(L2); + + cout << "交换后: " << endl; + printList(L1); + printList(L2); + +} + +int main() { + + //test01(); + + test02(); + + system("pause"); + + return 0; +} +``` + +总结:list赋值和交换操作能够灵活运用即可 + + + + + + + + + + + + + + + +#### 3.7.4 list 大小操作 + +**功能描述:** + +* 对list容器的大小进行操作 + + + +**函数原型:** + +* `size(); ` //返回容器中元素的个数 + +* `empty(); ` //判断容器是否为空 + +* `resize(num);` //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。 + + ​ //如果容器变短,则末尾超出容器长度的元素被删除。 + +* `resize(num, elem); ` //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。 + + ​ //如果容器变短,则末尾超出容器长度的元素被删除。 + + + +**示例:** + +```C++ +#include + +void printList(const list& L) { + + for (list::const_iterator it = L.begin(); it != L.end(); it++) { + cout << *it << " "; + } + cout << endl; +} + +//大小操作 +void test01() +{ + listL1; + L1.push_back(10); + L1.push_back(20); + L1.push_back(30); + L1.push_back(40); + + if (L1.empty()) + { + cout << "L1为空" << endl; + } + else + { + cout << "L1不为空" << endl; + cout << "L1的大小为: " << L1.size() << endl; + } + + //重新指定大小 + L1.resize(10); + printList(L1); + + L1.resize(2); + printList(L1); +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结: + +- 判断是否为空 --- empty +- 返回元素个数 --- size +- 重新指定个数 --- resize + + + + + + + + + + + +#### 3.7.5 list 插入和删除 + +**功能描述:** + +* 对list容器进行数据的插入和删除 + + + +**函数原型:** + +* push_back(elem);//在容器尾部加入一个元素 +* pop_back();//删除容器中最后一个元素 +* push_front(elem);//在容器开头插入一个元素 +* pop_front();//从容器开头移除第一个元素 +* insert(pos,elem);//在pos位置插elem元素的拷贝,返回新数据的位置。 +* insert(pos,n,elem);//在pos位置插入n个elem数据,无返回值。 +* insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值。 +* clear();//移除容器的所有数据 +* erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。 +* erase(pos);//删除pos位置的数据,返回下一个数据的位置。 +* remove(elem);//删除容器中所有与elem值匹配的元素。 + + + + + +**示例:** + +```C++ +#include + +void printList(const list& L) { + + for (list::const_iterator it = L.begin(); it != L.end(); it++) { + cout << *it << " "; + } + cout << endl; +} + +//插入和删除 +void test01() +{ + list L; + //尾插 + L.push_back(10); + L.push_back(20); + L.push_back(30); + //头插 + L.push_front(100); + L.push_front(200); + L.push_front(300); + + printList(L); + + //尾删 + L.pop_back(); + printList(L); + + //头删 + L.pop_front(); + printList(L); + + //插入 + list::iterator it = L.begin(); + L.insert(++it, 1000); + printList(L); + + //删除 + it = L.begin(); + L.erase(++it); + printList(L); + + //移除 + L.push_back(10000); + L.push_back(10000); + L.push_back(10000); + printList(L); + L.remove(10000); + printList(L); + + //清空 + L.clear(); + printList(L); +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结: + +* 尾插 --- push_back +* 尾删 --- pop_back +* 头插 --- push_front +* 头删 --- pop_front +* 插入 --- insert +* 删除 --- erase +* 移除 --- remove +* 清空 --- clear + + + + + + + + + + + + + + + + + +#### 3.7.6 list 数据存取 + +**功能描述:** + +* 对list容器中数据进行存取 + + + +**函数原型:** + +* `front();` //返回第一个元素。 +* `back();` //返回最后一个元素。 + + + + + +**示例:** + +```C++ +#include + +//数据存取 +void test01() +{ + listL1; + L1.push_back(10); + L1.push_back(20); + L1.push_back(30); + L1.push_back(40); + + + //cout << L1.at(0) << endl;//错误 不支持at访问数据 + //cout << L1[0] << endl; //错误 不支持[]方式访问数据 + cout << "第一个元素为: " << L1.front() << endl; + cout << "最后一个元素为: " << L1.back() << endl; + + //list容器的迭代器是双向迭代器,不支持随机访问 + list::iterator it = L1.begin(); + //it = it + 1;//错误,不可以跳跃访问,即使是+1 +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} + +``` + +总结: + +* list容器中不可以通过[]或者at方式访问数据 +* 返回第一个元素 --- front +* 返回最后一个元素 --- back + + + + + + + + + + + +#### 3.7.7 list 反转和排序 + +**功能描述:** + +* 将容器中的元素反转,以及将容器中的数据进行排序 + + + +**函数原型:** + +* `reverse();` //反转链表 +* `sort();` //链表排序 + + + + + +**示例:** + +```C++ +void printList(const list& L) { + + for (list::const_iterator it = L.begin(); it != L.end(); it++) { + cout << *it << " "; + } + cout << endl; +} + +bool myCompare(int val1 , int val2) +{ + return val1 > val2; +} + +//反转和排序 +void test01() +{ + list L; + L.push_back(90); + L.push_back(30); + L.push_back(20); + L.push_back(70); + printList(L); + + //反转容器的元素 + L.reverse(); + printList(L); + + //排序 + L.sort(); //默认的排序规则 从小到大 + printList(L); + + L.sort(myCompare); //指定规则,从大到小 + printList(L); +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结: + +* 反转 --- reverse +* 排序 --- sort (成员函数) + + + + + + + + + + + +#### 3.7.8 排序案例 + +案例描述:将Person自定义数据类型进行排序,Person中属性有姓名、年龄、身高 + +排序规则:按照年龄进行升序,如果年龄相同按照身高进行降序 + + + +**示例:** + +```C++ +#include +#include +class Person { +public: + Person(string name, int age , int height) { + m_Name = name; + m_Age = age; + m_Height = height; + } + +public: + string m_Name; //姓名 + int m_Age; //年龄 + int m_Height; //身高 +}; + + +bool ComparePerson(Person& p1, Person& p2) { + + if (p1.m_Age == p2.m_Age) { + return p1.m_Height > p2.m_Height; + } + else + { + return p1.m_Age < p2.m_Age; + } + +} + +void test01() { + + list L; + + Person p1("刘备", 35 , 175); + Person p2("曹操", 45 , 180); + Person p3("孙权", 40 , 170); + Person p4("赵云", 25 , 190); + Person p5("张飞", 35 , 160); + Person p6("关羽", 35 , 200); + + L.push_back(p1); + L.push_back(p2); + L.push_back(p3); + L.push_back(p4); + L.push_back(p5); + L.push_back(p6); + + for (list::iterator it = L.begin(); it != L.end(); it++) { + cout << "姓名: " << it->m_Name << " 年龄: " << it->m_Age + << " 身高: " << it->m_Height << endl; + } + + cout << "---------------------------------" << endl; + L.sort(ComparePerson); //排序 + + for (list::iterator it = L.begin(); it != L.end(); it++) { + cout << "姓名: " << it->m_Name << " 年龄: " << it->m_Age + << " 身高: " << it->m_Height << endl; + } +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + + + +总结: + +* 对于自定义数据类型,必须要指定排序规则,否则编译器不知道如何进行排序 + + +* 高级排序只是在排序规则上再进行一次逻辑规则制定,并不复杂 + + + + + + + + + + + + + + + + + + + +### 3.8 set/ multiset 容器 + +#### 3.8.1 set基本概念 + +**简介:** + +* 所有元素都会在插入时自动被排序 + + + + + +**本质:** + +* set/multiset属于**关联式容器**,底层结构是用**二叉树**实现。 + + + + + +**set和multiset区别**: + +* set不允许容器中有重复的元素 +* multiset允许容器中有重复的元素 + + + + + +#### 3.8.2 set构造和赋值 + +功能描述:创建set容器以及赋值 + + + +构造: + +* `set st;` //默认构造函数: +* `set(const set &st);` //拷贝构造函数 + +赋值: + +* `set& operator=(const set &st);` //重载等号操作符 + + + +**示例:** + +```C++ +#include + +void printSet(set & s) +{ + for (set::iterator it = s.begin(); it != s.end(); it++) + { + cout << *it << " "; + } + cout << endl; +} + +//构造和赋值 +void test01() +{ + set s1; + + s1.insert(10); + s1.insert(30); + s1.insert(20); + s1.insert(40); + printSet(s1); + + //拷贝构造 + sets2(s1); + printSet(s2); + + //赋值 + sets3; + s3 = s2; + printSet(s3); +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结: + +* set容器插入数据时用insert +* set容器插入数据的数据会自动排序 + + + + + + + + + + + +#### 3.8.3 set大小和交换 + +**功能描述:** + +* 统计set容器大小以及交换set容器 + + + +**函数原型:** + +* `size();` //返回容器中元素的数目 +* `empty();` //判断容器是否为空 +* `swap(st);` //交换两个集合容器 + + + +**示例:** + +```C++ +#include + +void printSet(set & s) +{ + for (set::iterator it = s.begin(); it != s.end(); it++) + { + cout << *it << " "; + } + cout << endl; +} + +//大小 +void test01() +{ + + set s1; + + s1.insert(10); + s1.insert(30); + s1.insert(20); + s1.insert(40); + + if (s1.empty()) + { + cout << "s1为空" << endl; + } + else + { + cout << "s1不为空" << endl; + cout << "s1的大小为: " << s1.size() << endl; + } + +} + +//交换 +void test02() +{ + set s1; + + s1.insert(10); + s1.insert(30); + s1.insert(20); + s1.insert(40); + + set s2; + + s2.insert(100); + s2.insert(300); + s2.insert(200); + s2.insert(400); + + cout << "交换前" << endl; + printSet(s1); + printSet(s2); + cout << endl; + + cout << "交换后" << endl; + s1.swap(s2); + printSet(s1); + printSet(s2); +} + +int main() { + + //test01(); + + test02(); + + system("pause"); + + return 0; +} +``` + +总结: + +* 统计大小 --- size +* 判断是否为空 --- empty +* 交换容器 --- swap + + + + + + + + + + + + + + + + + +#### 3.8.4 set插入和删除 + +**功能描述:** + +* set容器进行插入数据和删除数据 + + + + + +**函数原型:** + +* `insert(elem);` //在容器中插入元素。 +* `clear();` //清除所有元素 +* `erase(pos);` //删除pos迭代器所指的元素,返回下一个元素的迭代器。 +* `erase(beg, end);` //删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。 +* `erase(elem);` //删除容器中值为elem的元素。 + + + + + +**示例:** + +```C++ +#include + +void printSet(set & s) +{ + for (set::iterator it = s.begin(); it != s.end(); it++) + { + cout << *it << " "; + } + cout << endl; +} + +//插入和删除 +void test01() +{ + set s1; + //插入 + s1.insert(10); + s1.insert(30); + s1.insert(20); + s1.insert(40); + printSet(s1); + + //删除 + s1.erase(s1.begin()); + printSet(s1); + + s1.erase(30); + printSet(s1); + + //清空 + //s1.erase(s1.begin(), s1.end()); + s1.clear(); + printSet(s1); +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结: + +* 插入 --- insert +* 删除 --- erase +* 清空 --- clear + + + + + + + + + + + +#### 3.8.5 set查找和统计 + +**功能描述:** + +* 对set容器进行查找数据以及统计数据 + + + +**函数原型:** + +* `find(key);` //查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end(); +* `count(key);` //统计key的元素个数 + + + + + +**示例:** + +```C++ +#include + +//查找和统计 +void test01() +{ + set s1; + //插入 + s1.insert(10); + s1.insert(30); + s1.insert(20); + s1.insert(40); + + //查找 + set::iterator pos = s1.find(30); + + if (pos != s1.end()) + { + cout << "找到了元素 : " << *pos << endl; + } + else + { + cout << "未找到元素" << endl; + } + + //统计 + int num = s1.count(30); + cout << "num = " << num << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结: + +* 查找 --- find (返回的是迭代器) +* 统计 --- count (对于set,结果为0或者1) + + + + + + + + + + + + + + + + + +#### 3.8.6 set和multiset区别 + +**学习目标:** + +* 掌握set和multiset的区别 + + + +**区别:** + +* set不可以插入重复数据,而multiset可以 +* set插入数据的同时会返回插入结果,表示插入是否成功 +* multiset不会检测数据,因此可以插入重复数据 + + + + + +**示例:** + +```C++ +#include + +//set和multiset区别 +void test01() +{ + set s; + pair::iterator, bool> ret = s.insert(10); + if (ret.second) { + cout << "第一次插入成功!" << endl; + } + else { + cout << "第一次插入失败!" << endl; + } + + ret = s.insert(10); + if (ret.second) { + cout << "第二次插入成功!" << endl; + } + else { + cout << "第二次插入失败!" << endl; + } + + //multiset + multiset ms; + ms.insert(10); + ms.insert(10); + + for (multiset::iterator it = ms.begin(); it != ms.end(); it++) { + cout << *it << " "; + } + cout << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结: + +* 如果不允许插入重复数据可以利用set +* 如果需要插入重复数据利用multiset + + + + + + + + + + + +#### 3.8.7 pair对组创建 + +**功能描述:** + +* 成对出现的数据,利用对组可以返回两个数据 + + + + + +**两种创建方式:** + +* `pair p ( value1, value2 );` +* `pair p = make_pair( value1, value2 );` + + + + + +**示例:** + +```C++ +#include + +//对组创建 +void test01() +{ + pair p(string("Tom"), 20); + cout << "姓名: " << p.first << " 年龄: " << p.second << endl; + + pair p2 = make_pair("Jerry", 10); + cout << "姓名: " << p2.first << " 年龄: " << p2.second << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结: + +两种方式都可以创建对组,记住一种即可 + + + + + + + + + + + + + +#### 3.8.8 set容器排序 + +学习目标: + +* set容器默认排序规则为从小到大,掌握如何改变排序规则 + + + +主要技术点: + +* 利用仿函数,可以改变排序规则 + + + + + +**示例一** set存放内置数据类型 + +```C++ +#include + +class MyCompare +{ +public: + bool operator()(int v1, int v2) { + return v1 > v2; + } +}; +void test01() +{ + set s1; + s1.insert(10); + s1.insert(40); + s1.insert(20); + s1.insert(30); + s1.insert(50); + + //默认从小到大 + for (set::iterator it = s1.begin(); it != s1.end(); it++) { + cout << *it << " "; + } + cout << endl; + + //指定排序规则 + set s2; + s2.insert(10); + s2.insert(40); + s2.insert(20); + s2.insert(30); + s2.insert(50); + + for (set::iterator it = s2.begin(); it != s2.end(); it++) { + cout << *it << " "; + } + cout << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结:利用仿函数可以指定set容器的排序规则 + + + +**示例二** set存放自定义数据类型 + +```C++ +#include +#include + +class Person +{ +public: + Person(string name, int age) + { + this->m_Name = name; + this->m_Age = age; + } + + string m_Name; + int m_Age; + +}; +class comparePerson +{ +public: + bool operator()(const Person& p1, const Person &p2) + { + //按照年龄进行排序 降序 + return p1.m_Age > p2.m_Age; + } +}; + +void test01() +{ + set s; + + Person p1("刘备", 23); + Person p2("关羽", 27); + Person p3("张飞", 25); + Person p4("赵云", 21); + + s.insert(p1); + s.insert(p2); + s.insert(p3); + s.insert(p4); + + for (set::iterator it = s.begin(); it != s.end(); it++) + { + cout << "姓名: " << it->m_Name << " 年龄: " << it->m_Age << endl; + } +} +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结: + +对于自定义数据类型,set必须指定排序规则才可以插入数据 + + + + + + + + + + + +### 3.9 map/ multimap容器 + +#### 3.9.1 map基本概念 + +**简介:** + +* map中所有元素都是pair +* pair中第一个元素为key(键值),起到索引作用,第二个元素为value(实值) +* 所有元素都会根据元素的键值自动排序 + + + +**本质:** + +* map/multimap属于**关联式容器**,底层结构是用二叉树实现。 + + + +**优点:** + +* 可以根据key值快速找到value值 + + + +map和multimap**区别**: + +- map不允许容器中有重复key值元素 +- multimap允许容器中有重复key值元素 + + + + +#### 3.9.2 map构造和赋值 + +**功能描述:** + +* 对map容器进行构造和赋值操作 + +**函数原型:** + +**构造:** + +* `map mp;` //map默认构造函数: +* `map(const map &mp);` //拷贝构造函数 + + + +**赋值:** + +* `map& operator=(const map &mp);` //重载等号操作符 + + + +**示例:** + +```C++ +#include + +void printMap(map&m) +{ + for (map::iterator it = m.begin(); it != m.end(); it++) + { + cout << "key = " << it->first << " value = " << it->second << endl; + } + cout << endl; +} + +void test01() +{ + mapm; //默认构造 + m.insert(pair(1, 10)); + m.insert(pair(2, 20)); + m.insert(pair(3, 30)); + printMap(m); + + mapm2(m); //拷贝构造 + printMap(m2); + + mapm3; + m3 = m2; //赋值 + printMap(m3); +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结:map中所有元素都是成对出现,插入数据时候要使用对组 + + + + + + + + + + + +#### 3.9.3 map大小和交换 + +**功能描述:** + +* 统计map容器大小以及交换map容器 + + + + + +函数原型: + +- `size();` //返回容器中元素的数目 +- `empty();` //判断容器是否为空 +- `swap(st);` //交换两个集合容器 + + + + + +**示例:** + +```C++ +#include + +void printMap(map&m) +{ + for (map::iterator it = m.begin(); it != m.end(); it++) + { + cout << "key = " << it->first << " value = " << it->second << endl; + } + cout << endl; +} + +void test01() +{ + mapm; + m.insert(pair(1, 10)); + m.insert(pair(2, 20)); + m.insert(pair(3, 30)); + + if (m.empty()) + { + cout << "m为空" << endl; + } + else + { + cout << "m不为空" << endl; + cout << "m的大小为: " << m.size() << endl; + } +} + + +//交换 +void test02() +{ + mapm; + m.insert(pair(1, 10)); + m.insert(pair(2, 20)); + m.insert(pair(3, 30)); + + mapm2; + m2.insert(pair(4, 100)); + m2.insert(pair(5, 200)); + m2.insert(pair(6, 300)); + + cout << "交换前" << endl; + printMap(m); + printMap(m2); + + cout << "交换后" << endl; + m.swap(m2); + printMap(m); + printMap(m2); +} + +int main() { + + test01(); + + test02(); + + system("pause"); + + return 0; +} +``` + +总结: + +- 统计大小 --- size +- 判断是否为空 --- empty +- 交换容器 --- swap + + + + + + + + + + + +#### 3.9.4 map插入和删除 + +**功能描述:** + +- map容器进行插入数据和删除数据 + + + + + +**函数原型:** + +- `insert(elem);` //在容器中插入元素。 +- `clear();` //清除所有元素 +- `erase(pos);` //删除pos迭代器所指的元素,返回下一个元素的迭代器。 +- `erase(beg, end);` //删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。 +- `erase(key);` //删除容器中值为key的元素。 + + + +**示例:** + +```C++ +#include + +void printMap(map&m) +{ + for (map::iterator it = m.begin(); it != m.end(); it++) + { + cout << "key = " << it->first << " value = " << it->second << endl; + } + cout << endl; +} + +void test01() +{ + //插入 + map m; + //第一种插入方式 + m.insert(pair(1, 10)); + //第二种插入方式 + m.insert(make_pair(2, 20)); + //第三种插入方式 + m.insert(map::value_type(3, 30)); + //第四种插入方式 + m[4] = 40; + printMap(m); + + //删除 + m.erase(m.begin()); + printMap(m); + + m.erase(3); + printMap(m); + + //清空 + m.erase(m.begin(),m.end()); + m.clear(); + printMap(m); +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结: + +* map插入方式很多,记住其一即可 + +- 插入 --- insert +- 删除 --- erase +- 清空 --- clear + + + + + + + + + + + + + +#### 3.9.5 map查找和统计 + +**功能描述:** + +- 对map容器进行查找数据以及统计数据 + + + +**函数原型:** + +- `find(key);` //查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end(); +- `count(key);` //统计key的元素个数 + + + +**示例:** + +```C++ +#include + +//查找和统计 +void test01() +{ + mapm; + m.insert(pair(1, 10)); + m.insert(pair(2, 20)); + m.insert(pair(3, 30)); + + //查找 + map::iterator pos = m.find(3); + + if (pos != m.end()) + { + cout << "找到了元素 key = " << (*pos).first << " value = " << (*pos).second << endl; + } + else + { + cout << "未找到元素" << endl; + } + + //统计 + int num = m.count(3); + cout << "num = " << num << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结: + +- 查找 --- find (返回的是迭代器) +- 统计 --- count (对于map,结果为0或者1) + + + + + + + + + + + + + + + +#### 3.9.6 map容器排序 + +**学习目标:** + +- map容器默认排序规则为 按照key值进行 从小到大排序,掌握如何改变排序规则 + + + + + +**主要技术点:** + +- 利用仿函数,可以改变排序规则 + + + + + +**示例:** + +```C++ +#include + +class MyCompare { +public: + bool operator()(int v1, int v2) { + return v1 > v2; + } +}; + +void test01() +{ + //默认从小到大排序 + //利用仿函数实现从大到小排序 + map m; + + m.insert(make_pair(1, 10)); + m.insert(make_pair(2, 20)); + m.insert(make_pair(3, 30)); + m.insert(make_pair(4, 40)); + m.insert(make_pair(5, 50)); + + for (map::iterator it = m.begin(); it != m.end(); it++) { + cout << "key:" << it->first << " value:" << it->second << endl; + } +} +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结: + +* 利用仿函数可以指定map容器的排序规则 +* 对于自定义数据类型,map必须要指定排序规则,同set容器 + + + + + + + + + + + + + +### 3.10 案例-员工分组 + +#### 3.10.1 案例描述 + +* 公司今天招聘了10个员工(ABCDEFGHIJ),10名员工进入公司之后,需要指派员工在那个部门工作 +* 员工信息有: 姓名 工资组成;部门分为:策划、美术、研发 +* 随机给10名员工分配部门和工资 +* 通过multimap进行信息的插入 key(部门编号) value(员工) +* 分部门显示员工信息 + + + + + +#### 3.10.2 实现步骤 + +1. 创建10名员工,放到vector中 +2. 遍历vector容器,取出每个员工,进行随机分组 +3. 分组后,将员工部门编号作为key,具体员工作为value,放入到multimap容器中 +4. 分部门显示员工信息 + + + + + +**案例代码:** + +```C++ +#include +using namespace std; +#include +#include +#include +#include + +/* +- 公司今天招聘了10个员工(ABCDEFGHIJ),10名员工进入公司之后,需要指派员工在那个部门工作 +- 员工信息有: 姓名 工资组成;部门分为:策划、美术、研发 +- 随机给10名员工分配部门和工资 +- 通过multimap进行信息的插入 key(部门编号) value(员工) +- 分部门显示员工信息 +*/ + +#define CEHUA 0 +#define MEISHU 1 +#define YANFA 2 + +class Worker +{ +public: + string m_Name; + int m_Salary; +}; + +void createWorker(vector&v) +{ + string nameSeed = "ABCDEFGHIJ"; + for (int i = 0; i < 10; i++) + { + Worker worker; + worker.m_Name = "员工"; + worker.m_Name += nameSeed[i]; + + worker.m_Salary = rand() % 10000 + 10000; // 10000 ~ 19999 + //将员工放入到容器中 + v.push_back(worker); + } +} + +//员工分组 +void setGroup(vector&v,multimap&m) +{ + for (vector::iterator it = v.begin(); it != v.end(); it++) + { + //产生随机部门编号 + int deptId = rand() % 3; // 0 1 2 + + //将员工插入到分组中 + //key部门编号,value具体员工 + m.insert(make_pair(deptId, *it)); + } +} + +void showWorkerByGourp(multimap&m) +{ + // 0 A B C 1 D E 2 F G ... + cout << "策划部门:" << endl; + + multimap::iterator pos = m.find(CEHUA); + int count = m.count(CEHUA); // 统计具体人数 + int index = 0; + for (; pos != m.end() && index < count; pos++ , index++) + { + cout << "姓名: " << pos->second.m_Name << " 工资: " << pos->second.m_Salary << endl; + } + + cout << "----------------------" << endl; + cout << "美术部门: " << endl; + pos = m.find(MEISHU); + count = m.count(MEISHU); // 统计具体人数 + index = 0; + for (; pos != m.end() && index < count; pos++, index++) + { + cout << "姓名: " << pos->second.m_Name << " 工资: " << pos->second.m_Salary << endl; + } + + cout << "----------------------" << endl; + cout << "研发部门: " << endl; + pos = m.find(YANFA); + count = m.count(YANFA); // 统计具体人数 + index = 0; + for (; pos != m.end() && index < count; pos++, index++) + { + cout << "姓名: " << pos->second.m_Name << " 工资: " << pos->second.m_Salary << endl; + } + +} + +int main() { + + srand((unsigned int)time(NULL)); + + //1、创建员工 + vectorvWorker; + createWorker(vWorker); + + //2、员工分组 + multimapmWorker; + setGroup(vWorker, mWorker); + + + //3、分组显示员工 + showWorkerByGourp(mWorker); + + ////测试 + //for (vector::iterator it = vWorker.begin(); it != vWorker.end(); it++) + //{ + // cout << "姓名: " << it->m_Name << " 工资: " << it->m_Salary << endl; + //} + + system("pause"); + + return 0; +} +``` + +总结: + +* 当数据以键值对形式存在,可以考虑用map 或 multimap + + + + + + + +## 4 STL- 函数对象 + +### 4.1 函数对象 + +#### 4.1.1 函数对象概念 + +**概念:** + +* 重载**函数调用操作符**的类,其对象常称为**函数对象** +* **函数对象**使用重载的()时,行为类似函数调用,也叫**仿函数** + + + +**本质:** + +函数对象(仿函数)是一个**类**,不是一个函数 + + + +#### 4.1.2 函数对象使用 + +**特点:** + +* 函数对象在使用时,可以像普通函数那样调用, 可以有参数,可以有返回值 +* 函数对象超出普通函数的概念,函数对象可以有自己的状态 +* 函数对象可以作为参数传递 + + + + + +**示例:** + +```C++ +#include + +//1、函数对象在使用时,可以像普通函数那样调用, 可以有参数,可以有返回值 +class MyAdd +{ +public : + int operator()(int v1,int v2) + { + return v1 + v2; + } +}; + +void test01() +{ + MyAdd myAdd; + cout << myAdd(10, 10) << endl; +} + +//2、函数对象可以有自己的状态 +class MyPrint +{ +public: + MyPrint() + { + count = 0; + } + void operator()(string test) + { + cout << test << endl; + count++; //统计使用次数 + } + + int count; //内部自己的状态 +}; +void test02() +{ + MyPrint myPrint; + myPrint("hello world"); + myPrint("hello world"); + myPrint("hello world"); + cout << "myPrint调用次数为: " << myPrint.count << endl; +} + +//3、函数对象可以作为参数传递 +void doPrint(MyPrint &mp , string test) +{ + mp(test); +} + +void test03() +{ + MyPrint myPrint; + doPrint(myPrint, "Hello C++"); +} + +int main() { + + //test01(); + //test02(); + test03(); + + system("pause"); + + return 0; +} +``` + +总结: + +* 仿函数写法非常灵活,可以作为参数进行传递。 + + + + + + + + + + + + + +### 4.2 谓词 + +#### 4.2.1 谓词概念 + + + +**概念:** + +* 返回bool类型的仿函数称为**谓词** +* 如果operator()接受一个参数,那么叫做一元谓词 +* 如果operator()接受两个参数,那么叫做二元谓词 + + + + + +#### 4.2.2 一元谓词 + +**示例:** + +```C++ +#include +#include + +//1.一元谓词 +struct GreaterFive{ + bool operator()(int val) { + return val > 5; + } +}; + +void test01() { + + vector v; + for (int i = 0; i < 10; i++) + { + v.push_back(i); + } + + vector::iterator it = find_if(v.begin(), v.end(), GreaterFive()); + if (it == v.end()) { + cout << "没找到!" << endl; + } + else { + cout << "找到:" << *it << endl; + } + +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结:参数只有一个的谓词,称为一元谓词 + + + + + + + + + + + +#### 4.2.3 二元谓词 + +**示例:** + +```C++ +#include +#include +//二元谓词 +class MyCompare +{ +public: + bool operator()(int num1, int num2) + { + return num1 > num2; + } +}; + +void test01() +{ + vector v; + v.push_back(10); + v.push_back(40); + v.push_back(20); + v.push_back(30); + v.push_back(50); + + //默认从小到大 + sort(v.begin(), v.end()); + for (vector::iterator it = v.begin(); it != v.end(); it++) + { + cout << *it << " "; + } + cout << endl; + cout << "----------------------------" << endl; + + //使用函数对象改变算法策略,排序从大到小 + sort(v.begin(), v.end(), MyCompare()); + for (vector::iterator it = v.begin(); it != v.end(); it++) + { + cout << *it << " "; + } + cout << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结:参数只有两个的谓词,称为二元谓词 + + + + + + + + + + + + + + + + + +### 4.3 内建函数对象 + +#### 4.3.1 内建函数对象意义 + +**概念:** + +* STL内建了一些函数对象 + + + +**分类:** + +* 算术仿函数 + +* 关系仿函数 + +* 逻辑仿函数 + +**用法:** + +* 这些仿函数所产生的对象,用法和一般函数完全相同 +* 使用内建函数对象,需要引入头文件 `#include` + + + + + + + +#### 4.3.2 算术仿函数 + +**功能描述:** + +* 实现四则运算 +* 其中negate是一元运算,其他都是二元运算 + + + +**仿函数原型:** + +* `template T plus` //加法仿函数 +* `template T minus` //减法仿函数 +* `template T multiplies` //乘法仿函数 +* `template T divides` //除法仿函数 +* `template T modulus` //取模仿函数 +* `template T negate` //取反仿函数 + + + +**示例:** + +```C++ +#include +//negate +void test01() +{ + negate n; + cout << n(50) << endl; +} + +//plus +void test02() +{ + plus p; + cout << p(10, 20) << endl; +} + +int main() { + + test01(); + test02(); + + system("pause"); + + return 0; +} +``` + +总结:使用内建函数对象时,需要引入头文件 `#include ` + + + + + + + + + +#### 4.3.3 关系仿函数 + +**功能描述:** + +- 实现关系对比 + + + +**仿函数原型:** + +* `template bool equal_to` //等于 +* `template bool not_equal_to` //不等于 +* `template bool greater` //大于 +* `template bool greater_equal` //大于等于 +* `template bool less` //小于 +* `template bool less_equal` //小于等于 + + + +**示例:** + +```C++ +#include +#include +#include + +class MyCompare +{ +public: + bool operator()(int v1,int v2) + { + return v1 > v2; + } +}; +void test01() +{ + vector v; + + v.push_back(10); + v.push_back(30); + v.push_back(50); + v.push_back(40); + v.push_back(20); + + for (vector::iterator it = v.begin(); it != v.end(); it++) { + cout << *it << " "; + } + cout << endl; + + //自己实现仿函数 + //sort(v.begin(), v.end(), MyCompare()); + //STL内建仿函数 大于仿函数 + sort(v.begin(), v.end(), greater()); + + for (vector::iterator it = v.begin(); it != v.end(); it++) { + cout << *it << " "; + } + cout << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结:关系仿函数中最常用的就是greater<>大于 + + + + + + + + + + + +#### 4.3.4 逻辑仿函数 + +**功能描述:** + +- 实现逻辑运算 + + + +**函数原型:** + +* `template bool logical_and` //逻辑与 +* `template bool logical_or` //逻辑或 +* `template bool logical_not` //逻辑非 + + + +**示例:** + +```C++ +#include +#include +#include +void test01() +{ + vector v; + v.push_back(true); + v.push_back(false); + v.push_back(true); + v.push_back(false); + + for (vector::iterator it = v.begin();it!= v.end();it++) + { + cout << *it << " "; + } + cout << endl; + + //逻辑非 将v容器搬运到v2中,并执行逻辑非运算 + vector v2; + v2.resize(v.size()); + transform(v.begin(), v.end(), v2.begin(), logical_not()); + for (vector::iterator it = v2.begin(); it != v2.end(); it++) + { + cout << *it << " "; + } + cout << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结:逻辑仿函数实际应用较少,了解即可 + + + + + + + +## 5 STL- 常用算法 + + + +**概述**: + +* 算法主要是由头文件`` `` ``组成。 + + + +* ``是所有STL头文件中最大的一个,范围涉及到比较、 交换、查找、遍历操作、复制、修改等等 +* ``体积很小,只包括几个在序列上面进行简单数学运算的模板函数 +* ``定义了一些模板类,用以声明函数对象。 + + + + + +### 5.1 常用遍历算法 + +**学习目标:** + +* 掌握常用的遍历算法 + + + +**算法简介:** + +* `for_each` //遍历容器 +* `transform` //搬运容器到另一个容器中 + + + + + +#### 5.1.1 for_each + +**功能描述:** + +* 实现遍历容器 + +**函数原型:** + +* `for_each(iterator beg, iterator end, _func); ` + + // 遍历算法 遍历容器元素 + + // beg 开始迭代器 + + // end 结束迭代器 + + // _func 函数或者函数对象 + + + +**示例:** + +```C++ +#include +#include + +//普通函数 +void print01(int val) +{ + cout << val << " "; +} +//函数对象 +class print02 +{ + public: + void operator()(int val) + { + cout << val << " "; + } +}; + +//for_each算法基本用法 +void test01() { + + vector v; + for (int i = 0; i < 10; i++) + { + v.push_back(i); + } + + //遍历算法 + for_each(v.begin(), v.end(), print01); + cout << endl; + + for_each(v.begin(), v.end(), print02()); + cout << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +**总结:**for_each在实际开发中是最常用遍历算法,需要熟练掌握 + + + + + + + + + +#### 5.1.2 transform + +**功能描述:** + +* 搬运容器到另一个容器中 + +**函数原型:** + +* `transform(iterator beg1, iterator end1, iterator beg2, _func);` + +//beg1 源容器开始迭代器 + +//end1 源容器结束迭代器 + +//beg2 目标容器开始迭代器 + +//_func 函数或者函数对象 + + + +**示例:** + +```C++ +#include +#include + +//常用遍历算法 搬运 transform + +class TransForm +{ +public: + int operator()(int val) + { + return val; + } + +}; + +class MyPrint +{ +public: + void operator()(int val) + { + cout << val << " "; + } +}; + +void test01() +{ + vectorv; + for (int i = 0; i < 10; i++) + { + v.push_back(i); + } + + vectorvTarget; //目标容器 + + vTarget.resize(v.size()); // 目标容器需要提前开辟空间 + + transform(v.begin(), v.end(), vTarget.begin(), TransForm()); + + for_each(vTarget.begin(), vTarget.end(), MyPrint()); +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +**总结:** 搬运的目标容器必须要提前开辟空间,否则无法正常搬运 + + + + + + + +### 5.2 常用查找算法 + +学习目标: + +- 掌握常用的查找算法 + + + + + +**算法简介:** + +- `find` //查找元素 +- `find_if` //按条件查找元素 +- `adjacent_find` //查找相邻重复元素 +- `binary_search` //二分查找法 +- `count` //统计元素个数 +- `count_if` //按条件统计元素个数 + + + + +#### 5.2.1 find + +**功能描述:** + +* 查找指定元素,找到返回指定元素的迭代器,找不到返回结束迭代器end() + + + +**函数原型:** + +- `find(iterator beg, iterator end, value); ` + + // 按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置 + + // beg 开始迭代器 + + // end 结束迭代器 + + // value 查找的元素 + + + + + +**示例:** + +```C++ +#include +#include +#include +void test01() { + + vector v; + for (int i = 0; i < 10; i++) { + v.push_back(i + 1); + } + //查找容器中是否有 5 这个元素 + vector::iterator it = find(v.begin(), v.end(), 5); + if (it == v.end()) + { + cout << "没有找到!" << endl; + } + else + { + cout << "找到:" << *it << endl; + } +} + +class Person { +public: + Person(string name, int age) + { + this->m_Name = name; + this->m_Age = age; + } + //重载== + bool operator==(const Person& p) + { + if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) + { + return true; + } + return false; + } + +public: + string m_Name; + int m_Age; +}; + +void test02() { + + vector v; + + //创建数据 + Person p1("aaa", 10); + Person p2("bbb", 20); + Person p3("ccc", 30); + Person p4("ddd", 40); + + v.push_back(p1); + v.push_back(p2); + v.push_back(p3); + v.push_back(p4); + + vector::iterator it = find(v.begin(), v.end(), p2); + if (it == v.end()) + { + cout << "没有找到!" << endl; + } + else + { + cout << "找到姓名:" << it->m_Name << " 年龄: " << it->m_Age << endl; + } +} +``` + +总结: 利用find可以在容器中找指定的元素,返回值是**迭代器** + + + + + + + + + + + + + +#### 5.2.2 find_if + +**功能描述:** + +* 按条件查找元素 + +**函数原型:** + +- `find_if(iterator beg, iterator end, _Pred); ` + + // 按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置 + + // beg 开始迭代器 + + // end 结束迭代器 + + // _Pred 函数或者谓词(返回bool类型的仿函数) + + + +**示例:** + +```C++ +#include +#include +#include + +//内置数据类型 +class GreaterFive +{ +public: + bool operator()(int val) + { + return val > 5; + } +}; + +void test01() { + + vector v; + for (int i = 0; i < 10; i++) { + v.push_back(i + 1); + } + + vector::iterator it = find_if(v.begin(), v.end(), GreaterFive()); + if (it == v.end()) { + cout << "没有找到!" << endl; + } + else { + cout << "找到大于5的数字:" << *it << endl; + } +} + +//自定义数据类型 +class Person { +public: + Person(string name, int age) + { + this->m_Name = name; + this->m_Age = age; + } +public: + string m_Name; + int m_Age; +}; + +class Greater20 +{ +public: + bool operator()(Person &p) + { + return p.m_Age > 20; + } + +}; + +void test02() { + + vector v; + + //创建数据 + Person p1("aaa", 10); + Person p2("bbb", 20); + Person p3("ccc", 30); + Person p4("ddd", 40); + + v.push_back(p1); + v.push_back(p2); + v.push_back(p3); + v.push_back(p4); + + vector::iterator it = find_if(v.begin(), v.end(), Greater20()); + if (it == v.end()) + { + cout << "没有找到!" << endl; + } + else + { + cout << "找到姓名:" << it->m_Name << " 年龄: " << it->m_Age << endl; + } +} + +int main() { + + //test01(); + + test02(); + + system("pause"); + + return 0; +} +``` + +总结:find_if按条件查找使查找更加灵活,提供的仿函数可以改变不同的策略 + + + + + + + + + + + + + + + +#### 5.2.3 adjacent_find + +**功能描述:** + +* 查找相邻重复元素 + + + +**函数原型:** + +- `adjacent_find(iterator beg, iterator end); ` + + // 查找相邻重复元素,返回相邻元素的第一个位置的迭代器 + + // beg 开始迭代器 + + // end 结束迭代器 + + + + + +**示例:** + +```C++ +#include +#include + +void test01() +{ + vector v; + v.push_back(1); + v.push_back(2); + v.push_back(5); + v.push_back(2); + v.push_back(4); + v.push_back(4); + v.push_back(3); + + //查找相邻重复元素 + vector::iterator it = adjacent_find(v.begin(), v.end()); + if (it == v.end()) { + cout << "找不到!" << endl; + } + else { + cout << "找到相邻重复元素为:" << *it << endl; + } +} +``` + +总结:面试题中如果出现查找相邻重复元素,记得用STL中的adjacent_find算法 + + + + + + + + + +#### 5.2.4 binary_search + +**功能描述:** + +* 查找指定元素是否存在 + + + +**函数原型:** + +- `bool binary_search(iterator beg, iterator end, value); ` + + // 查找指定的元素,查到 返回true 否则false + + // 注意: 在**无序序列中不可用** + + // beg 开始迭代器 + + // end 结束迭代器 + + // value 查找的元素 + + + + + +**示例:** + +```C++ +#include +#include + +void test01() +{ + vectorv; + + for (int i = 0; i < 10; i++) + { + v.push_back(i); + } + //二分查找 + bool ret = binary_search(v.begin(), v.end(),2); + if (ret) + { + cout << "找到了" << endl; + } + else + { + cout << "未找到" << endl; + } +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +**总结:**二分查找法查找效率很高,值得注意的是查找的容器中元素必须的有序序列 + + + + + + + + + +#### 5.2.5 count + +**功能描述:** + +* 统计元素个数 + + + +**函数原型:** + +- `count(iterator beg, iterator end, value); ` + + // 统计元素出现次数 + + // beg 开始迭代器 + + // end 结束迭代器 + + // value 统计的元素 + + + + + +**示例:** + +```C++ +#include +#include + +//内置数据类型 +void test01() +{ + vector v; + v.push_back(1); + v.push_back(2); + v.push_back(4); + v.push_back(5); + v.push_back(3); + v.push_back(4); + v.push_back(4); + + int num = count(v.begin(), v.end(), 4); + + cout << "4的个数为: " << num << endl; +} + +//自定义数据类型 +class Person +{ +public: + Person(string name, int age) + { + this->m_Name = name; + this->m_Age = age; + } + bool operator==(const Person & p) + { + if (this->m_Age == p.m_Age) + { + return true; + } + else + { + return false; + } + } + string m_Name; + int m_Age; +}; + +void test02() +{ + vector v; + + Person p1("刘备", 35); + Person p2("关羽", 35); + Person p3("张飞", 35); + Person p4("赵云", 30); + Person p5("曹操", 25); + + v.push_back(p1); + v.push_back(p2); + v.push_back(p3); + v.push_back(p4); + v.push_back(p5); + + Person p("诸葛亮",35); + + int num = count(v.begin(), v.end(), p); + cout << "num = " << num << endl; +} +int main() { + + //test01(); + + test02(); + + system("pause"); + + return 0; +} +``` + +**总结:** 统计自定义数据类型时候,需要配合重载 `operator==` + + + + + + + + + + + + + + + + + +#### 5.2.6 count_if + +**功能描述:** + +* 按条件统计元素个数 + +**函数原型:** + +- `count_if(iterator beg, iterator end, _Pred); ` + + // 按条件统计元素出现次数 + + // beg 开始迭代器 + + // end 结束迭代器 + + // _Pred 谓词 + + + +**示例:** + +```C++ +#include +#include + +class Greater4 +{ +public: + bool operator()(int val) + { + return val >= 4; + } +}; + +//内置数据类型 +void test01() +{ + vector v; + v.push_back(1); + v.push_back(2); + v.push_back(4); + v.push_back(5); + v.push_back(3); + v.push_back(4); + v.push_back(4); + + int num = count_if(v.begin(), v.end(), Greater4()); + + cout << "大于4的个数为: " << num << endl; +} + +//自定义数据类型 +class Person +{ +public: + Person(string name, int age) + { + this->m_Name = name; + this->m_Age = age; + } + + string m_Name; + int m_Age; +}; + +class AgeLess35 +{ +public: + bool operator()(const Person &p) + { + return p.m_Age < 35; + } +}; +void test02() +{ + vector v; + + Person p1("刘备", 35); + Person p2("关羽", 35); + Person p3("张飞", 35); + Person p4("赵云", 30); + Person p5("曹操", 25); + + v.push_back(p1); + v.push_back(p2); + v.push_back(p3); + v.push_back(p4); + v.push_back(p5); + + int num = count_if(v.begin(), v.end(), AgeLess35()); + cout << "小于35岁的个数:" << num << endl; +} + + +int main() { + + //test01(); + + test02(); + + system("pause"); + + return 0; +} +``` + +**总结:**按值统计用count,按条件统计用count_if + + + + + + + + + + + + + +### 5.3 常用排序算法 + +**学习目标:** + +- 掌握常用的排序算法 + +**算法简介:** + +- `sort` //对容器内元素进行排序 +- `random_shuffle` //洗牌 指定范围内的元素随机调整次序 +- `merge ` // 容器元素合并,并存储到另一容器中 +- `reverse` // 反转指定范围的元素 + + + + + +#### 5.3.1 sort + +**功能描述:** + +* 对容器内元素进行排序 + + + + + +**函数原型:** + +- `sort(iterator beg, iterator end, _Pred); ` + + // 按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置 + + // beg 开始迭代器 + + // end 结束迭代器 + + // _Pred 谓词 + + + + + +**示例:** + +```c++ +#include +#include + +void myPrint(int val) +{ + cout << val << " "; +} + +void test01() { + vector v; + v.push_back(10); + v.push_back(30); + v.push_back(50); + v.push_back(20); + v.push_back(40); + + //sort默认从小到大排序 + sort(v.begin(), v.end()); + for_each(v.begin(), v.end(), myPrint); + cout << endl; + + //从大到小排序 + sort(v.begin(), v.end(), greater()); + for_each(v.begin(), v.end(), myPrint); + cout << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +**总结:**sort属于开发中最常用的算法之一,需熟练掌握 + + + + + + + + + + + + + +#### 5.3.2 random_shuffle + +**功能描述:** + +* 洗牌 指定范围内的元素随机调整次序 + + + +**函数原型:** + +- `random_shuffle(iterator beg, iterator end); ` + + // 指定范围内的元素随机调整次序 + + // beg 开始迭代器 + + // end 结束迭代器 + + + +**示例:** + +```c++ +#include +#include +#include + +class myPrint +{ +public: + void operator()(int val) + { + cout << val << " "; + } +}; + +void test01() +{ + srand((unsigned int)time(NULL)); + vector v; + for(int i = 0 ; i < 10;i++) + { + v.push_back(i); + } + for_each(v.begin(), v.end(), myPrint()); + cout << endl; + + //打乱顺序 + random_shuffle(v.begin(), v.end()); + for_each(v.begin(), v.end(), myPrint()); + cout << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +**总结:**random_shuffle洗牌算法比较实用,使用时记得加随机数种子 + + + + + + + + + + + + + + + +#### 5.3.3 merge + +**功能描述:** + +* 两个容器元素合并,并存储到另一容器中 + + + +**函数原型:** + +- `merge(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest); ` + + // 容器元素合并,并存储到另一容器中 + + // 注意: 两个容器必须是**有序的** + + // beg1 容器1开始迭代器 + // end1 容器1结束迭代器 + // beg2 容器2开始迭代器 + // end2 容器2结束迭代器 + // dest 目标容器开始迭代器 + + + +**示例:** + +```c++ +#include +#include + +class myPrint +{ +public: + void operator()(int val) + { + cout << val << " "; + } +}; + +void test01() +{ + vector v1; + vector v2; + for (int i = 0; i < 10 ; i++) + { + v1.push_back(i); + v2.push_back(i + 1); + } + + vector vtarget; + //目标容器需要提前开辟空间 + vtarget.resize(v1.size() + v2.size()); + //合并 需要两个有序序列 + merge(v1.begin(), v1.end(), v2.begin(), v2.end(), vtarget.begin()); + for_each(vtarget.begin(), vtarget.end(), myPrint()); + cout << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +**总结:**merge合并的两个容器必须的有序序列 + + + + + + + + + + + +#### 5.3.4 reverse + +**功能描述:** + +* 将容器内元素进行反转 + + + +**函数原型:** + +- `reverse(iterator beg, iterator end); ` + + // 反转指定范围的元素 + + // beg 开始迭代器 + + // end 结束迭代器 + + + +**示例:** + +```c++ +#include +#include + +class myPrint +{ +public: + void operator()(int val) + { + cout << val << " "; + } +}; + +void test01() +{ + vector v; + v.push_back(10); + v.push_back(30); + v.push_back(50); + v.push_back(20); + v.push_back(40); + + cout << "反转前: " << endl; + for_each(v.begin(), v.end(), myPrint()); + cout << endl; + + cout << "反转后: " << endl; + + reverse(v.begin(), v.end()); + for_each(v.begin(), v.end(), myPrint()); + cout << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +**总结:**reverse反转区间内元素,面试题可能涉及到 + + + + + + + + + +### 5.4 常用拷贝和替换算法 + +**学习目标:** + +- 掌握常用的拷贝和替换算法 + +**算法简介:** + +- `copy` // 容器内指定范围的元素拷贝到另一容器中 +- `replace` // 将容器内指定范围的旧元素修改为新元素 +- `replace_if ` // 容器内指定范围满足条件的元素替换为新元素 +- `swap` // 互换两个容器的元素 + + + + +#### 5.4.1 copy + +**功能描述:** + +* 容器内指定范围的元素拷贝到另一容器中 + + + +**函数原型:** + +- `copy(iterator beg, iterator end, iterator dest); ` + + // 按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置 + + // beg 开始迭代器 + + // end 结束迭代器 + + // dest 目标起始迭代器 + + + +**示例:** + +```c++ +#include +#include + +class myPrint +{ +public: + void operator()(int val) + { + cout << val << " "; + } +}; + +void test01() +{ + vector v1; + for (int i = 0; i < 10; i++) { + v1.push_back(i + 1); + } + vector v2; + v2.resize(v1.size()); + copy(v1.begin(), v1.end(), v2.begin()); + + for_each(v2.begin(), v2.end(), myPrint()); + cout << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +**总结:**利用copy算法在拷贝时,目标容器记得提前开辟空间 + + + + + + + + + + + + + + + +#### 5.4.2 replace + +**功能描述:** + +* 将容器内指定范围的旧元素修改为新元素 + + + +**函数原型:** + +- `replace(iterator beg, iterator end, oldvalue, newvalue); ` + + // 将区间内旧元素 替换成 新元素 + + // beg 开始迭代器 + + // end 结束迭代器 + + // oldvalue 旧元素 + + // newvalue 新元素 + + + +**示例:** + +```c++ +#include +#include + +class myPrint +{ +public: + void operator()(int val) + { + cout << val << " "; + } +}; + +void test01() +{ + vector v; + v.push_back(20); + v.push_back(30); + v.push_back(20); + v.push_back(40); + v.push_back(50); + v.push_back(10); + v.push_back(20); + + cout << "替换前:" << endl; + for_each(v.begin(), v.end(), myPrint()); + cout << endl; + + //将容器中的20 替换成 2000 + cout << "替换后:" << endl; + replace(v.begin(), v.end(), 20,2000); + for_each(v.begin(), v.end(), myPrint()); + cout << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +**总结:**replace会替换区间内满足条件的元素 + + + + + + + + + + + + + +#### 5.4.3 replace_if + +**功能描述:** + +* 将区间内满足条件的元素,替换成指定元素 + + + +**函数原型:** + +- `replace_if(iterator beg, iterator end, _pred, newvalue); ` + + // 按条件替换元素,满足条件的替换成指定元素 + + // beg 开始迭代器 + + // end 结束迭代器 + + // _pred 谓词 + + // newvalue 替换的新元素 + + + +**示例:** + +```c++ +#include +#include + +class myPrint +{ +public: + void operator()(int val) + { + cout << val << " "; + } +}; + +class ReplaceGreater30 +{ +public: + bool operator()(int val) + { + return val >= 30; + } + +}; + +void test01() +{ + vector v; + v.push_back(20); + v.push_back(30); + v.push_back(20); + v.push_back(40); + v.push_back(50); + v.push_back(10); + v.push_back(20); + + cout << "替换前:" << endl; + for_each(v.begin(), v.end(), myPrint()); + cout << endl; + + //将容器中大于等于的30 替换成 3000 + cout << "替换后:" << endl; + replace_if(v.begin(), v.end(), ReplaceGreater30(), 3000); + for_each(v.begin(), v.end(), myPrint()); + cout << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +**总结:**replace_if按条件查找,可以利用仿函数灵活筛选满足的条件 + + + + + + + +#### 5.4.4 swap + +**功能描述:** + +* 互换两个容器的元素 + + + +**函数原型:** + +- `swap(container c1, container c2); ` + + // 互换两个容器的元素 + + // c1容器1 + + // c2容器2 + + + +**示例:** + +```c++ +#include +#include + +class myPrint +{ +public: + void operator()(int val) + { + cout << val << " "; + } +}; + +void test01() +{ + vector v1; + vector v2; + for (int i = 0; i < 10; i++) { + v1.push_back(i); + v2.push_back(i+100); + } + + cout << "交换前: " << endl; + for_each(v1.begin(), v1.end(), myPrint()); + cout << endl; + for_each(v2.begin(), v2.end(), myPrint()); + cout << endl; + + cout << "交换后: " << endl; + swap(v1, v2); + for_each(v1.begin(), v1.end(), myPrint()); + cout << endl; + for_each(v2.begin(), v2.end(), myPrint()); + cout << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +**总结:**swap交换容器时,注意交换的容器要同种类型 + + + + + + + + + + + + + +### 5.5 常用算术生成算法 + +**学习目标:** + +- 掌握常用的算术生成算法 + + + +**注意:** + +* 算术生成算法属于小型算法,使用时包含的头文件为 `#include ` + + + +**算法简介:** + +- `accumulate` // 计算容器元素累计总和 + +- `fill` // 向容器中添加元素 + + + +#### 5.5.1 accumulate + +**功能描述:** + +* 计算区间内 容器元素累计总和 + + + +**函数原型:** + +- `accumulate(iterator beg, iterator end, value); ` + + // 计算容器元素累计总和 + + // beg 开始迭代器 + + // end 结束迭代器 + + // value 起始值 + + + +**示例:** + +```c++ +#include +#include +void test01() +{ + vector v; + for (int i = 0; i <= 100; i++) { + v.push_back(i); + } + + int total = accumulate(v.begin(), v.end(), 0); + + cout << "total = " << total << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +**总结:**accumulate使用时头文件注意是 numeric,这个算法很实用 + + + +#### 5.5.2 fill + +**功能描述:** + +* 向容器中填充指定的元素 + + + +**函数原型:** + +- `fill(iterator beg, iterator end, value); ` + + // 向容器中填充元素 + + // beg 开始迭代器 + + // end 结束迭代器 + + // value 填充的值 + + + +**示例:** + +```c++ +#include +#include +#include + +class myPrint +{ +public: + void operator()(int val) + { + cout << val << " "; + } +}; + +void test01() +{ + + vector v; + v.resize(10); + //填充 + fill(v.begin(), v.end(), 100); + + for_each(v.begin(), v.end(), myPrint()); + cout << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +**总结:**利用fill可以将容器区间内元素填充为 指定的值 + + + + + +### 5.6 常用集合算法 + +**学习目标:** + +- 掌握常用的集合算法 + + + +**算法简介:** + +- `set_intersection` // 求两个容器的交集 + +- `set_union` // 求两个容器的并集 + +- `set_difference ` // 求两个容器的差集 + + + + + +#### 5.6.1 set_intersection + +**功能描述:** + +* 求两个容器的交集 + + + +**函数原型:** + +- `set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest); ` + + // 求两个集合的交集 + + // **注意:两个集合必须是有序序列** + + // beg1 容器1开始迭代器 + // end1 容器1结束迭代器 + // beg2 容器2开始迭代器 + // end2 容器2结束迭代器 + // dest 目标容器开始迭代器 + + + +**示例:** + +```C++ +#include +#include + +class myPrint +{ +public: + void operator()(int val) + { + cout << val << " "; + } +}; + +void test01() +{ + vector v1; + vector v2; + for (int i = 0; i < 10; i++) + { + v1.push_back(i); + v2.push_back(i+5); + } + + vector vTarget; + //取两个里面较小的值给目标容器开辟空间 + vTarget.resize(min(v1.size(), v2.size())); + + //返回目标容器的最后一个元素的迭代器地址 + vector::iterator itEnd = + set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin()); + + for_each(vTarget.begin(), itEnd, myPrint()); + cout << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +**总结:** + +* 求交集的两个集合必须的有序序列 +* 目标容器开辟空间需要从**两个容器中取小值** +* set_intersection返回值既是交集中最后一个元素的位置 + + + + + + + + + + + + + +#### 5.6.2 set_union + +**功能描述:** + +* 求两个集合的并集 + + + +**函数原型:** + +- `set_union(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest); ` + + // 求两个集合的并集 + + // **注意:两个集合必须是有序序列** + + // beg1 容器1开始迭代器 + // end1 容器1结束迭代器 + // beg2 容器2开始迭代器 + // end2 容器2结束迭代器 + // dest 目标容器开始迭代器 + + + +**示例:** + +```C++ +#include +#include + +class myPrint +{ +public: + void operator()(int val) + { + cout << val << " "; + } +}; + +void test01() +{ + vector v1; + vector v2; + for (int i = 0; i < 10; i++) { + v1.push_back(i); + v2.push_back(i+5); + } + + vector vTarget; + //取两个容器的和给目标容器开辟空间 + vTarget.resize(v1.size() + v2.size()); + + //返回目标容器的最后一个元素的迭代器地址 + vector::iterator itEnd = + set_union(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin()); + + for_each(vTarget.begin(), itEnd, myPrint()); + cout << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +**总结:** + +- 求并集的两个集合必须的有序序列 +- 目标容器开辟空间需要**两个容器相加** +- set_union返回值既是并集中最后一个元素的位置 + + + + + + + + +#### 5.6.3 set_difference + +**功能描述:** + +* 求两个集合的差集 + + + +**函数原型:** + +- `set_difference(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest); ` + + // 求两个集合的差集 + + // **注意:两个集合必须是有序序列** + + // beg1 容器1开始迭代器 + // end1 容器1结束迭代器 + // beg2 容器2开始迭代器 + // end2 容器2结束迭代器 + // dest 目标容器开始迭代器 + + + +**示例:** + +```C++ +#include +#include + +class myPrint +{ +public: + void operator()(int val) + { + cout << val << " "; + } +}; + +void test01() +{ + vector v1; + vector v2; + for (int i = 0; i < 10; i++) { + v1.push_back(i); + v2.push_back(i+5); + } + + vector vTarget; + //取两个里面较大的值给目标容器开辟空间 + vTarget.resize( max(v1.size() , v2.size())); + + //返回目标容器的最后一个元素的迭代器地址 + cout << "v1与v2的差集为: " << endl; + vector::iterator itEnd = + set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), vTarget.begin()); + for_each(vTarget.begin(), itEnd, myPrint()); + cout << endl; + + + cout << "v2与v1的差集为: " << endl; + itEnd = set_difference(v2.begin(), v2.end(), v1.begin(), v1.end(), vTarget.begin()); + for_each(vTarget.begin(), itEnd, myPrint()); + cout << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +**总结:** + +- 求差集的两个集合必须的有序序列 +- 目标容器开辟空间需要从**两个容器取较大值** +- set_difference返回值既是差集中最后一个元素的位置 \ No newline at end of file diff --git "a/note/C++\346\240\270\345\277\203\347\274\226\347\250\213.md" "b/note/C++\346\240\270\345\277\203\347\274\226\347\250\213.md" new file mode 100644 index 0000000000..0259cddc75 --- /dev/null +++ "b/note/C++\346\240\270\345\277\203\347\274\226\347\250\213.md" @@ -0,0 +1,5050 @@ +[TOC] + + + +# C++核心编程 + +本阶段主要针对C++==面向对象==编程技术做详细讲解,探讨C++中的核心和精髓。 + + + +## 1 内存分区模型 + +C++程序在执行时,将内存大方向划分为**4个区域** + +- 代码区:存放函数体的二进制代码,由操作系统进行管理的 +- 全局区:存放全局变量和静态变量以及常量 +- 栈区:由编译器自动分配释放, 存放函数的参数值,局部变量等 +- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收 + + + + + + + +**内存四区意义:** + +不同区域存放的数据,赋予不同的生命周期, 给我们更大的灵活编程 + + + + + +### 1.1 程序运行前 + +​ 在程序编译后,生成了exe可执行程序,**未执行该程序前**分为两个区域 + +​ **代码区:** + +​ 存放 CPU 执行的机器指令 + +​ 代码区是**共享**的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可 + +​ 代码区是**只读**的,使其只读的原因是防止程序意外地修改了它的指令 + +​ **全局区:** + +​ 全局变量和静态变量存放在此. + +​ 全局区还包含了常量区, 字符串常量和其他常量也存放在此. + +​ ==该区域的数据在程序结束后由操作系统释放==. + + + + + + + + + + + + + +**示例:** + +```c++ +//全局变量 +int g_a = 10; +int g_b = 10; + +//全局常量 +const int c_g_a = 10; +const int c_g_b = 10; + +int main() { + + //局部变量 + int a = 10; + int b = 10; + + //打印地址 + cout << "局部变量a地址为: " << (int)&a << endl; + cout << "局部变量b地址为: " << (int)&b << endl; + + cout << "全局变量g_a地址为: " << (int)&g_a << endl; + cout << "全局变量g_b地址为: " << (int)&g_b << endl; + + //静态变量 + static int s_a = 10; + static int s_b = 10; + + cout << "静态变量s_a地址为: " << (int)&s_a << endl; + cout << "静态变量s_b地址为: " << (int)&s_b << endl; + + cout << "字符串常量地址为: " << (int)&"hello world" << endl; + cout << "字符串常量地址为: " << (int)&"hello world1" << endl; + + cout << "全局常量c_g_a地址为: " << (int)&c_g_a << endl; + cout << "全局常量c_g_b地址为: " << (int)&c_g_b << endl; + + const int c_l_a = 10; + const int c_l_b = 10; + cout << "局部常量c_l_a地址为: " << (int)&c_l_a << endl; + cout << "局部常量c_l_b地址为: " << (int)&c_l_b << endl; + + system("pause"); + + return 0; +} +``` + +打印结果: + +![1545017602518](assets/1545017602518.png) + + + +总结: + +* C++中在程序运行前分为全局区和代码区 +* 代码区特点是共享和只读 +* 全局区中存放全局变量、静态变量、常量 +* 常量区中存放 const修饰的全局常量 和 字符串常量 + + + + + + +### 1.2 程序运行后 + + + +​ **栈区:** + +​ 由编译器自动分配释放, 存放函数的参数值,局部变量等 + +​ 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放 + + + +**示例:** + +```c++ +int * func() +{ + int a = 10; + return &a; +} + +int main() { + + int *p = func(); + + cout << *p << endl; + cout << *p << endl; + + system("pause"); + + return 0; +} +``` + + + + + + + +​ **堆区:** + +​ 由程序员分配释放,若程序员不释放,程序结束时由操作系统回收 + +​ 在C++中主要利用new在堆区开辟内存 + +**示例:** + +```c++ +int* func() +{ + int* a = new int(10); + return a; +} + +int main() { + + int *p = func(); + + cout << *p << endl; + cout << *p << endl; + + system("pause"); + + return 0; +} +``` + + + +**总结:** + +堆区数据由程序员管理开辟和释放 + +堆区数据利用new关键字进行开辟内存 + + + + + + + + + +### 1.3 new操作符 + + + +​ C++中利用==new==操作符在堆区开辟数据 + +​ 堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符 ==delete== + +​ 语法:` new 数据类型` + +​ 利用new创建的数据,会返回该数据对应的类型的指针 + + + +**示例1: 基本语法** + +```c++ +int* func() +{ + int* a = new int(10); + return a; +} + +int main() { + + int *p = func(); + + cout << *p << endl; + cout << *p << endl; + + //利用delete释放堆区数据 + delete p; + + //cout << *p << endl; //报错,释放的空间不可访问 + + system("pause"); + + return 0; +} +``` + + + +**示例2:开辟数组** + +```c++ +//堆区开辟数组 +int main() { + + int* arr = new int[10]; + + for (int i = 0; i < 10; i++) + { + arr[i] = i + 100; + } + + for (int i = 0; i < 10; i++) + { + cout << arr[i] << endl; + } + //释放数组 delete 后加 [] + delete[] arr; + + system("pause"); + + return 0; +} + +``` + + + + + + + + + + + +## 2 引用 + +### 2.1 引用的基本使用 + +**作用: **给变量起别名 + +**语法:** `数据类型 &别名 = 原名` + + + +**示例:** + +```C++ +int main() { + + int a = 10; + int &b = a; + + cout << "a = " << a << endl; + cout << "b = " << b << endl; + + b = 100; + + cout << "a = " << a << endl; + cout << "b = " << b << endl; + + system("pause"); + + return 0; +} +``` + + + + + + + +### 2.2 引用注意事项 + +* 引用必须初始化 +* 引用在初始化后,不可以改变 + +示例: + +```C++ +int main() { + + int a = 10; + int b = 20; + //int &c; //错误,引用必须初始化 + int &c = a; //一旦初始化后,就不可以更改 + c = b; //这是赋值操作,不是更改引用 + + cout << "a = " << a << endl; + cout << "b = " << b << endl; + cout << "c = " << c << endl; + + system("pause"); + + return 0; +} +``` + + + + + + + + + + + +### 2.3 引用做函数参数 + +**作用:**函数传参时,可以利用引用的技术让形参修饰实参 + +**优点:**可以简化指针修改实参 + + + +**示例:** + +```C++ +//1. 值传递 +void mySwap01(int a, int b) { + int temp = a; + a = b; + b = temp; +} + +//2. 地址传递 +void mySwap02(int* a, int* b) { + int temp = *a; + *a = *b; + *b = temp; +} + +//3. 引用传递 +void mySwap03(int& a, int& b) { + int temp = a; + a = b; + b = temp; +} + +int main() { + + int a = 10; + int b = 20; + + mySwap01(a, b); + cout << "a:" << a << " b:" << b << endl; + + mySwap02(&a, &b); + cout << "a:" << a << " b:" << b << endl; + + mySwap03(a, b); + cout << "a:" << a << " b:" << b << endl; + + system("pause"); + + return 0; +} + +``` + + + +> 总结:通过引用参数产生的效果同按地址传递是一样的。引用的语法更清楚简单 + + + + + + + + + + + + + +### 2.4 引用做函数返回值 + + + +作用:引用是可以作为函数的返回值存在的 + + + +注意:**不要返回局部变量引用** + +用法:函数调用作为左值 + + + +**示例:** + +```C++ +//返回局部变量引用 +int& test01() { + int a = 10; //局部变量 + return a; +} + +//返回静态变量引用 +int& test02() { + static int a = 20; + return a; +} + +int main() { + + //不能返回局部变量的引用 + int& ref = test01(); + cout << "ref = " << ref << endl; + cout << "ref = " << ref << endl; + + //如果函数做左值,那么必须返回引用 + int& ref2 = test02(); + cout << "ref2 = " << ref2 << endl; + cout << "ref2 = " << ref2 << endl; + + test02() = 1000; + + cout << "ref2 = " << ref2 << endl; + cout << "ref2 = " << ref2 << endl; + + system("pause"); + + return 0; +} +``` + + + + + +​ + + + + + + + + + +### 2.5 引用的本质 + +本质:**引用的本质在c++内部实现是一个指针常量.** + +讲解示例: + +```C++ +//发现是引用,转换为 int* const ref = &a; +void func(int& ref){ + ref = 100; // ref是引用,转换为*ref = 100 +} +int main(){ + int a = 10; + + //自动转换为 int* const ref = &a; 指针常量是指针指向不可改,也说明为什么引用不可更改 + int& ref = a; + ref = 20; //内部发现ref是引用,自动帮我们转换为: *ref = 20; + + cout << "a:" << a << endl; + cout << "ref:" << ref << endl; + + func(a); + return 0; +} +``` + +结论:C++推荐用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了 + + + + + + + + + + + + + +### 2.6 常量引用 + + + +**作用:**常量引用主要用来修饰形参,防止误操作 + + + +在函数形参列表中,可以加==const修饰形参==,防止形参改变实参 + + + +**示例:** + + + +```C++ +//引用使用的场景,通常用来修饰形参 +void showValue(const int& v) { + //v += 10; + cout << v << endl; +} + +int main() { + + //int& ref = 10; 引用本身需要一个合法的内存空间,因此这行错误 + //加入const就可以了,编译器优化代码,int temp = 10; const int& ref = temp; + const int& ref = 10; + + //ref = 100; //加入const后不可以修改变量 + cout << ref << endl; + + //函数中利用常量引用防止误操作修改实参 + int a = 10; + showValue(a); + + system("pause"); + + return 0; +} +``` + + + + + + + + + +## 3 函数提高 + +### 3.1 函数默认参数 + + + +在C++中,函数的形参列表中的形参是可以有默认值的。 + +语法:` 返回值类型 函数名 (参数= 默认值){}` + + + +**示例:** + +```C++ +int func(int a, int b = 10, int c = 10) { + return a + b + c; +} + +//1. 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值 +//2. 如果函数声明有默认值,函数实现的时候就不能有默认参数 +int func2(int a = 10, int b = 10); +int func2(int a, int b) { + return a + b; +} + +int main() { + + cout << "ret = " << func(20, 20) << endl; + cout << "ret = " << func(100) << endl; + + system("pause"); + + return 0; +} +``` + + + + + + + +### 3.2 函数占位参数 + + + +C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置 + + + +**语法:** `返回值类型 函数名 (数据类型){}` + + + +在现阶段函数的占位参数存在意义不大,但是后面的课程中会用到该技术 + + + +**示例:** + +```C++ +//函数占位参数 ,占位参数也可以有默认参数 +void func(int a, int) { + cout << "this is func" << endl; +} + +int main() { + + func(10,10); //占位参数必须填补 + + system("pause"); + + return 0; +} +``` + + + + + + + + + +### 3.3 函数重载 + +#### 3.3.1 函数重载概述 + + + +**作用:**函数名可以相同,提高复用性 + + + +**函数重载满足条件:** + +* 同一个作用域下 +* 函数名称相同 +* 函数参数**类型不同** 或者 **个数不同** 或者 **顺序不同** + + + +**注意:** 函数的返回值不可以作为函数重载的条件 + + + +**示例:** + +```C++ +//函数重载需要函数都在同一个作用域下 +void func() +{ + cout << "func 的调用!" << endl; +} +void func(int a) +{ + cout << "func (int a) 的调用!" << endl; +} +void func(double a) +{ + cout << "func (double a)的调用!" << endl; +} +void func(int a ,double b) +{ + cout << "func (int a ,double b) 的调用!" << endl; +} +void func(double a ,int b) +{ + cout << "func (double a ,int b)的调用!" << endl; +} + +//函数返回值不可以作为函数重载条件 +//int func(double a, int b) +//{ +// cout << "func (double a ,int b)的调用!" << endl; +//} + + +int main() { + + func(); + func(10); + func(3.14); + func(10,3.14); + func(3.14 , 10); + + system("pause"); + + return 0; +} +``` + + + + + + + + + + + + + +#### 3.3.2 函数重载注意事项 + + + +* 引用作为重载条件 +* 函数重载碰到函数默认参数 + + + + + +**示例:** + +```C++ +//函数重载注意事项 +//1、引用作为重载条件 + +void func(int &a) +{ + cout << "func (int &a) 调用 " << endl; +} + +void func(const int &a) +{ + cout << "func (const int &a) 调用 " << endl; +} + + +//2、函数重载碰到函数默认参数 + +void func2(int a, int b = 10) +{ + cout << "func2(int a, int b = 10) 调用" << endl; +} + +void func2(int a) +{ + cout << "func2(int a) 调用" << endl; +} + +int main() { + + int a = 10; + func(a); //调用无const + func(10);//调用有const + + + //func2(10); //碰到默认参数产生歧义,需要避免 + + system("pause"); + + return 0; +} +``` + + + + + + + +## **4** 类和对象 + + + +C++面向对象的三大特性为:==封装、继承、多态== + + + +C++认为==万事万物都皆为对象==,对象上有其属性和行为 + + + +**例如:** + +​ 人可以作为对象,属性有姓名、年龄、身高、体重...,行为有走、跑、跳、吃饭、唱歌... + +​ 车也可以作为对象,属性有轮胎、方向盘、车灯...,行为有载人、放音乐、放空调... + +​ 具有相同性质的==对象==,我们可以抽象称为==类==,人属于人类,车属于车类 + +### 4.1 封装 + +#### 4.1.1 封装的意义 + +封装是C++面向对象三大特性之一 + +封装的意义: + +* 将属性和行为作为一个整体,表现生活中的事物 +* 将属性和行为加以权限控制 + + + +**封装意义一:** + +​ 在设计类的时候,属性和行为写在一起,表现事物 + +**语法:** `class 类名{ 访问权限: 属性 / 行为 };` + + + +**示例1:**设计一个圆类,求圆的周长 + +**示例代码:** + +```C++ +//圆周率 +const double PI = 3.14; + +//1、封装的意义 +//将属性和行为作为一个整体,用来表现生活中的事物 + +//封装一个圆类,求圆的周长 +//class代表设计一个类,后面跟着的是类名 +class Circle +{ +public: //访问权限 公共的权限 + + //属性 + int m_r;//半径 + + //行为 + //获取到圆的周长 + double calculateZC() + { + //2 * pi * r + //获取圆的周长 + return 2 * PI * m_r; + } +}; + +int main() { + + //通过圆类,创建圆的对象 + // c1就是一个具体的圆 + Circle c1; + c1.m_r = 10; //给圆对象的半径 进行赋值操作 + + //2 * pi * 10 = = 62.8 + cout << "圆的周长为: " << c1.calculateZC() << endl; + + system("pause"); + + return 0; +} +``` + + + + + +**示例2:**设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号 + + + + + +**示例2代码:** + +```C++ +//学生类 +class Student { +public: + void setName(string name) { + m_name = name; + } + void setID(int id) { + m_id = id; + } + + void showStudent() { + cout << "name:" << m_name << " ID:" << m_id << endl; + } +public: + string m_name; + int m_id; +}; + +int main() { + + Student stu; + stu.setName("德玛西亚"); + stu.setID(250); + stu.showStudent(); + + system("pause"); + + return 0; +} + +``` + + + + + + + + + +**封装意义二:** + +类在设计时,可以把属性和行为放在不同的权限下,加以控制 + +访问权限有三种: + + + +1. public 公共权限 +2. protected 保护权限 +3. private 私有权限 + + + + + + + +**示例:** + +```C++ +//三种权限 +//公共权限 public 类内可以访问 类外可以访问 +//保护权限 protected 类内可以访问 类外不可以访问 +//私有权限 private 类内可以访问 类外不可以访问 + +class Person +{ + //姓名 公共权限 +public: + string m_Name; + + //汽车 保护权限 +protected: + string m_Car; + + //银行卡密码 私有权限 +private: + int m_Password; + +public: + void func() + { + m_Name = "张三"; + m_Car = "拖拉机"; + m_Password = 123456; + } +}; + +int main() { + + Person p; + p.m_Name = "李四"; + //p.m_Car = "奔驰"; //保护权限类外访问不到 + //p.m_Password = 123; //私有权限类外访问不到 + + system("pause"); + + return 0; +} +``` + + + + + + + +#### 4.1.2 struct和class区别 + + + +在C++中 struct和class唯一的**区别**就在于 **默认的访问权限不同** + +区别: + +* struct 默认权限为公共 +* class 默认权限为私有 + + + +```C++ +class C1 +{ + int m_A; //默认是私有权限 +}; + +struct C2 +{ + int m_A; //默认是公共权限 +}; + +int main() { + + C1 c1; + c1.m_A = 10; //错误,访问权限是私有 + + C2 c2; + c2.m_A = 10; //正确,访问权限是公共 + + system("pause"); + + return 0; +} +``` + + + + + + + + + + + + + +#### 4.1.3 成员属性设置为私有 + + + +**优点1:**将所有成员属性设置为私有,可以自己控制读写权限 + +**优点2:**对于写权限,我们可以检测数据的有效性 + + + +**示例:** + +```C++ +class Person { +public: + + //姓名设置可读可写 + void setName(string name) { + m_Name = name; + } + string getName() + { + return m_Name; + } + + + //获取年龄 + int getAge() { + return m_Age; + } + //设置年龄 + void setAge(int age) { + if (age < 0 || age > 150) { + cout << "你个老妖精!" << endl; + return; + } + m_Age = age; + } + + //情人设置为只写 + void setLover(string lover) { + m_Lover = lover; + } + +private: + string m_Name; //可读可写 姓名 + + int m_Age; //只读 年龄 + + string m_Lover; //只写 情人 +}; + + +int main() { + + Person p; + //姓名设置 + p.setName("张三"); + cout << "姓名: " << p.getName() << endl; + + //年龄设置 + p.setAge(50); + cout << "年龄: " << p.getAge() << endl; + + //情人设置 + p.setLover("苍井"); + //cout << "情人: " << p.m_Lover << endl; //只写属性,不可以读取 + + system("pause"); + + return 0; +} +``` + + + + + + + + + +**练习案例1:设计立方体类** + +设计立方体类(Cube) + +求出立方体的面积和体积 + +分别用全局函数和成员函数判断两个立方体是否相等。 + + + +![1545533548532](assets/1545533548532.png) + + + + + + + + + + + +**练习案例2:点和圆的关系** + +设计一个圆形类(Circle),和一个点类(Point),计算点和圆的关系。 + + + +![1545533829184](assets/1545533829184.png) + + + + + + + +### 4.2 对象的初始化和清理 + + + +* 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用时候也会删除一些自己信息数据保证安全 +* C++中的面向对象来源于生活,每个对象也都会有初始设置以及 对象销毁前的清理数据的设置。 + + + + + +#### 4.2.1 构造函数和析构函数 + +对象的**初始化和清理**也是两个非常重要的安全问题 + +​ 一个对象或者变量没有初始状态,对其使用后果是未知 + +​ 同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题 + + + +c++利用了**构造函数**和**析构函数**解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。 + +对象的初始化和清理工作是编译器强制要我们做的事情,因此如果**我们不提供构造和析构,编译器会提供** + +**编译器提供的构造函数和析构函数是空实现。** + + + +* 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。 +* 析构函数:主要作用在于对象**销毁前**系统自动调用,执行一些清理工作。 + +0 + + + +**构造函数语法:**`类名(){}` + +1. 构造函数,没有返回值也不写void +2. 函数名称与类名相同 +3. 构造函数可以有参数,因此可以发生重载 +4. 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次 + + + + + +**析构函数语法:** `~类名(){}` + +1. 析构函数,没有返回值也不写void +2. 函数名称与类名相同,在名称前加上符号 ~ +3. 析构函数不可以有参数,因此不可以发生重载 +4. 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次 + + + + + +```C++ +class Person +{ +public: + //构造函数 + Person() + { + cout << "Person的构造函数调用" << endl; + } + //析构函数 + ~Person() + { + cout << "Person的析构函数调用" << endl; + } + +}; + +void test01() +{ + Person p; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + + + + + + + + + + + +#### 4.2.2 构造函数的分类及调用 + +两种分类方式: + +​ 按参数分为: 有参构造和无参构造 + +​ 按类型分为: 普通构造和拷贝构造 + +三种调用方式: + +​ 括号法 + +​ 显示法 + +​ 隐式转换法 + + + +**示例:** + +```C++ +//1、构造函数分类 +// 按照参数分类分为 有参和无参构造 无参又称为默认构造函数 +// 按照类型分类分为 普通构造和拷贝构造 + +class Person { +public: + //无参(默认)构造函数 + Person() { + cout << "无参构造函数!" << endl; + } + //有参构造函数 + Person(int a) { + age = a; + cout << "有参构造函数!" << endl; + } + //拷贝构造函数 + Person(const Person& p) { + age = p.age; + cout << "拷贝构造函数!" << endl; + } + //析构函数 + ~Person() { + cout << "析构函数!" << endl; + } +public: + int age; +}; + +//2、构造函数的调用 +//调用无参构造函数 +void test01() { + Person p; //调用无参构造函数 +} + +//调用有参的构造函数 +void test02() { + + //2.1 括号法,常用 + Person p1(10); + //注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明 + //Person p2(); + + //2.2 显式法 + Person p2 = Person(10); + Person p3 = Person(p2); + //Person(10)单独写就是匿名对象 当前行结束之后,马上析构 + + //2.3 隐式转换法 + Person p4 = 10; // Person p4 = Person(10); + Person p5 = p4; // Person p5 = Person(p4); + + //注意2:不能利用 拷贝构造函数 初始化匿名对象 编译器认为是对象声明 + //Person p5(p4); +} + +int main() { + + test01(); + //test02(); + + system("pause"); + + return 0; +} +``` + + + + + + + + + +#### 4.2.3 拷贝构造函数调用时机 + + + +C++中拷贝构造函数调用时机通常有三种情况 + +* 使用一个已经创建完毕的对象来初始化一个新对象 +* 值传递的方式给函数参数传值 +* 以值方式返回局部对象 + + + +**示例:** + +```C++ +class Person { +public: + Person() { + cout << "无参构造函数!" << endl; + mAge = 0; + } + Person(int age) { + cout << "有参构造函数!" << endl; + mAge = age; + } + Person(const Person& p) { + cout << "拷贝构造函数!" << endl; + mAge = p.mAge; + } + //析构函数在释放内存之前调用 + ~Person() { + cout << "析构函数!" << endl; + } +public: + int mAge; +}; + +//1. 使用一个已经创建完毕的对象来初始化一个新对象 +void test01() { + + Person man(100); //p对象已经创建完毕 + Person newman(man); //调用拷贝构造函数 + Person newman2 = man; //拷贝构造 + + //Person newman3; + //newman3 = man; //不是调用拷贝构造函数,赋值操作 +} + +//2. 值传递的方式给函数参数传值 +//相当于Person p1 = p; +void doWork(Person p1) {} +void test02() { + Person p; //无参构造函数 + doWork(p); +} + +//3. 以值方式返回局部对象 +Person doWork2() +{ + Person p1; + cout << (int *)&p1 << endl; + return p1; +} + +void test03() +{ + Person p = doWork2(); + cout << (int *)&p << endl; +} + + +int main() { + + //test01(); + //test02(); + test03(); + + system("pause"); + + return 0; +} +``` + + + + + +#### 4.2.4 构造函数调用规则 + +默认情况下,c++编译器至少给一个类添加3个函数 + +1.默认构造函数(无参,函数体为空) + +2.默认析构函数(无参,函数体为空) + +3.默认拷贝构造函数,对属性进行值拷贝 + + + +构造函数调用规则如下: + +* 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造 + + +* 如果用户定义拷贝构造函数,c++不会再提供其他构造函数 + + + +示例: + +```C++ +class Person { +public: + //无参(默认)构造函数 + Person() { + cout << "无参构造函数!" << endl; + } + //有参构造函数 + Person(int a) { + age = a; + cout << "有参构造函数!" << endl; + } + //拷贝构造函数 + Person(const Person& p) { + age = p.age; + cout << "拷贝构造函数!" << endl; + } + //析构函数 + ~Person() { + cout << "析构函数!" << endl; + } +public: + int age; +}; + +void test01() +{ + Person p1(18); + //如果不写拷贝构造,编译器会自动添加拷贝构造,并且做浅拷贝操作 + Person p2(p1); + + cout << "p2的年龄为: " << p2.age << endl; +} + +void test02() +{ + //如果用户提供有参构造,编译器不会提供默认构造,会提供拷贝构造 + Person p1; //此时如果用户自己没有提供默认构造,会出错 + Person p2(10); //用户提供的有参 + Person p3(p2); //此时如果用户没有提供拷贝构造,编译器会提供 + + //如果用户提供拷贝构造,编译器不会提供其他构造函数 + Person p4; //此时如果用户自己没有提供默认构造,会出错 + Person p5(10); //此时如果用户自己没有提供有参,会出错 + Person p6(p5); //用户自己提供拷贝构造 +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + + + + + + + + + +#### 4.2.5 深拷贝与浅拷贝 + + + +深浅拷贝是面试经典问题,也是常见的一个坑 + + + +浅拷贝:简单的赋值拷贝操作 + + + +深拷贝:在堆区重新申请空间,进行拷贝操作 + + + +**示例:** + +```C++ +class Person { +public: + //无参(默认)构造函数 + Person() { + cout << "无参构造函数!" << endl; + } + //有参构造函数 + Person(int age ,int height) { + + cout << "有参构造函数!" << endl; + + m_age = age; + m_height = new int(height); + + } + //拷贝构造函数 + Person(const Person& p) { + cout << "拷贝构造函数!" << endl; + //如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题 + m_age = p.m_age; + m_height = new int(*p.m_height); + + } + + //析构函数 + ~Person() { + cout << "析构函数!" << endl; + if (m_height != NULL) + { + delete m_height; + } + } +public: + int m_age; + int* m_height; +}; + +void test01() +{ + Person p1(18, 180); + + Person p2(p1); + + cout << "p1的年龄: " << p1.m_age << " 身高: " << *p1.m_height << endl; + + cout << "p2的年龄: " << p2.m_age << " 身高: " << *p2.m_height << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +> 总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题 + + + + + + + + + +#### 4.2.6 初始化列表 + + + +**作用:** + +C++提供了初始化列表语法,用来初始化属性 + + + +**语法:**`构造函数():属性1(值1),属性2(值2)... {}` + + + +**示例:** + +```C++ +class Person { +public: + + ////传统方式初始化 + //Person(int a, int b, int c) { + // m_A = a; + // m_B = b; + // m_C = c; + //} + + //初始化列表方式初始化 + Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {} + void PrintPerson() { + cout << "mA:" << m_A << endl; + cout << "mB:" << m_B << endl; + cout << "mC:" << m_C << endl; + } +private: + int m_A; + int m_B; + int m_C; +}; + +int main() { + + Person p(1, 2, 3); + p.PrintPerson(); + + + system("pause"); + + return 0; +} +``` + + + + + +#### 4.2.7 类对象作为类成员 + + + +C++类中的成员可以是另一个类的对象,我们称该成员为 对象成员 + + + +例如: + +```C++ +class A {} +class B +{ + A a; +} +``` + + + +B类中有对象A作为成员,A为对象成员 + + + +那么当创建B对象时,A与B的构造和析构的顺序是谁先谁后? + + + +**示例:** + +```C++ +class Phone +{ +public: + Phone(string name) + { + m_PhoneName = name; + cout << "Phone构造" << endl; + } + + ~Phone() + { + cout << "Phone析构" << endl; + } + + string m_PhoneName; + +}; + + +class Person +{ +public: + + //初始化列表可以告诉编译器调用哪一个构造函数 + Person(string name, string pName) :m_Name(name), m_Phone(pName) + { + cout << "Person构造" << endl; + } + + ~Person() + { + cout << "Person析构" << endl; + } + + void playGame() + { + cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 牌手机! " << endl; + } + + string m_Name; + Phone m_Phone; + +}; +void test01() +{ + //当类中成员是其他类对象时,我们称该成员为 对象成员 + //构造的顺序是 :先调用对象成员的构造,再调用本类构造 + //析构顺序与构造相反 + Person p("张三" , "苹果X"); + p.playGame(); + +} + + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + + + + + + + + + + + +#### 4.2.8 静态成员 + +静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员 + +静态成员分为: + + + +* 静态成员变量 + * 所有对象共享同一份数据 + * 在编译阶段分配内存 + * 类内声明,类外初始化 +* 静态成员函数 + * 所有对象共享同一个函数 + * 静态成员函数只能访问静态成员变量 + + + + + + + +**示例1 :**静态成员变量 + +```C++ +class Person +{ + +public: + + static int m_A; //静态成员变量 + + //静态成员变量特点: + //1 在编译阶段分配内存 + //2 类内声明,类外初始化 + //3 所有对象共享同一份数据 + +private: + static int m_B; //静态成员变量也是有访问权限的 +}; +int Person::m_A = 10; +int Person::m_B = 10; + +void test01() +{ + //静态成员变量两种访问方式 + + //1、通过对象 + Person p1; + p1.m_A = 100; + cout << "p1.m_A = " << p1.m_A << endl; + + Person p2; + p2.m_A = 200; + cout << "p1.m_A = " << p1.m_A << endl; //共享同一份数据 + cout << "p2.m_A = " << p2.m_A << endl; + + //2、通过类名 + cout << "m_A = " << Person::m_A << endl; + + + //cout << "m_B = " << Person::m_B << endl; //私有权限访问不到 +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + + + +**示例2:**静态成员函数 + +```C++ +class Person +{ + +public: + + //静态成员函数特点: + //1 程序共享一个函数 + //2 静态成员函数只能访问静态成员变量 + + static void func() + { + cout << "func调用" << endl; + m_A = 100; + //m_B = 100; //错误,不可以访问非静态成员变量 + } + + static int m_A; //静态成员变量 + int m_B; // +private: + + //静态成员函数也是有访问权限的 + static void func2() + { + cout << "func2调用" << endl; + } +}; +int Person::m_A = 10; + + +void test01() +{ + //静态成员变量两种访问方式 + + //1、通过对象 + Person p1; + p1.func(); + + //2、通过类名 + Person::func(); + + + //Person::func2(); //私有权限访问不到 +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + + + + + + + + + +### 4.3 C++对象模型和this指针 + + + +#### 4.3.1 成员变量和成员函数分开存储 + + + +在C++中,类内的成员变量和成员函数分开存储 + +只有非静态成员变量才属于类的对象上 + + + +```C++ +class Person { +public: + Person() { + mA = 0; + } + //非静态成员变量占对象空间 + int mA; + //静态成员变量不占对象空间 + static int mB; + //函数也不占对象空间,所有函数共享一个函数实例 + void func() { + cout << "mA:" << this->mA << endl; + } + //静态成员函数也不占对象空间 + static void sfunc() { + } +}; + +int main() { + + cout << sizeof(Person) << endl; + + system("pause"); + + return 0; +} +``` + + + + + + + +#### 4.3.2 this指针概念 + +通过4.3.1我们知道在C++中成员变量和成员函数是分开存储的 + +每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码 + +那么问题是:这一块代码是如何区分那个对象调用自己的呢? + + + +c++通过提供特殊的对象指针,this指针,解决上述问题。**this指针指向被调用的成员函数所属的对象** + + + +this指针是隐含每一个非静态成员函数内的一种指针 + +this指针不需要定义,直接使用即可 + +. + +this指针的用途: + +* 当形参和成员变量同名时,可用this指针来区分 +* 在类的非静态成员函数中返回对象本身,可使用return *this + +```C++ +class Person +{ +public: + + Person(int age) + { + //1、当形参和成员变量同名时,可用this指针来区分 + this->age = age; + } + + Person& PersonAddPerson(Person p) + { + this->age += p.age; + //返回对象本身 + return *this; + } + + int age; +}; + +void test01() +{ + Person p1(10); + cout << "p1.age = " << p1.age << endl; + + Person p2(10); + p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1); + cout << "p2.age = " << p2.age << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + + + + + + + + + +#### 4.3.3 空指针访问成员函数 + + + +C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针 + + + +如果用到this指针,需要加以判断保证代码的健壮性 + + + +**示例:** + +```C++ +//空指针访问成员函数 +class Person { +public: + + void ShowClassName() { + cout << "我是Person类!" << endl; + } + + void ShowPerson() { + if (this == NULL) { + return; + } + cout << mAge << endl; + } + +public: + int mAge; +}; + +void test01() +{ + Person * p = NULL; + p->ShowClassName(); //空指针,可以调用成员函数 + p->ShowPerson(); //但是如果成员函数中用到了this指针,就不可以了 +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + + + + + + + + + +#### 4.3.4 const修饰成员函数 + + + +**常函数:** + +* 成员函数后加const后我们称为这个函数为**常函数** +* 常函数内不可以修改成员属性 +* 成员属性声明时加关键字mutable后,在常函数中依然可以修改 + + + +**常对象:** + +* 声明对象前加const称该对象为常对象 +* 常对象只能调用常函数 + + + + + + + +**示例:** + +```C++ +class Person { +public: + Person() { + m_A = 0; + m_B = 0; + } + + //this指针的本质是一个指针常量,指针的指向不可修改 + //如果想让指针指向的值也不可以修改,需要声明常函数 + void ShowPerson() const { + //const Type* const pointer; + //this = NULL; //不能修改指针的指向 Person* const this; + //this->mA = 100; //但是this指针指向的对象的数据是可以修改的 + + //const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量 + this->m_B = 100; + } + + void MyFunc() const { + //mA = 10000; + } + +public: + int m_A; + mutable int m_B; //可修改 可变的 +}; + + +//const修饰对象 常对象 +void test01() { + + const Person person; //常量对象 + cout << person.m_A << endl; + //person.mA = 100; //常对象不能修改成员变量的值,但是可以访问 + person.m_B = 100; //但是常对象可以修改mutable修饰成员变量 + + //常对象访问成员函数 + person.MyFunc(); //常对象不能调用const的函数 + +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + + + + + + + + +### 4.4 友元 + + + +生活中你的家有客厅(Public),有你的卧室(Private) + +客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去 + +但是呢,你也可以允许你的好闺蜜好基友进去。 + + + +在程序里,有些私有属性 也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术 + + + +友元的目的就是让一个函数或者类 访问另一个类中私有成员 + + + +友元的关键字为 ==friend== + + + +友元的三种实现 + +* 全局函数做友元 +* 类做友元 +* 成员函数做友元 + + + + + +#### 4.4.1 全局函数做友元 + +```C++ +class Building +{ + //告诉编译器 goodGay全局函数 是 Building类的好朋友,可以访问类中的私有内容 + friend void goodGay(Building * building); + +public: + + Building() + { + this->m_SittingRoom = "客厅"; + this->m_BedRoom = "卧室"; + } + + +public: + string m_SittingRoom; //客厅 + +private: + string m_BedRoom; //卧室 +}; + + +void goodGay(Building * building) +{ + cout << "好基友正在访问: " << building->m_SittingRoom << endl; + cout << "好基友正在访问: " << building->m_BedRoom << endl; +} + + +void test01() +{ + Building b; + goodGay(&b); +} + +int main(){ + + test01(); + + system("pause"); + return 0; +} +``` + + + +#### 4.4.2 类做友元 + + + +```C++ +class Building; +class goodGay +{ +public: + + goodGay(); + void visit(); + +private: + Building *building; +}; + + +class Building +{ + //告诉编译器 goodGay类是Building类的好朋友,可以访问到Building类中私有内容 + friend class goodGay; + +public: + Building(); + +public: + string m_SittingRoom; //客厅 +private: + string m_BedRoom;//卧室 +}; + +Building::Building() +{ + this->m_SittingRoom = "客厅"; + this->m_BedRoom = "卧室"; +} + +goodGay::goodGay() +{ + building = new Building; +} + +void goodGay::visit() +{ + cout << "好基友正在访问" << building->m_SittingRoom << endl; + cout << "好基友正在访问" << building->m_BedRoom << endl; +} + +void test01() +{ + goodGay gg; + gg.visit(); + +} + +int main(){ + + test01(); + + system("pause"); + return 0; +} +``` + + + + + +#### 4.4.3 成员函数做友元 + + + +```C++ + +class Building; +class goodGay +{ +public: + + goodGay(); + void visit(); //只让visit函数作为Building的好朋友,可以发访问Building中私有内容 + void visit2(); + +private: + Building *building; +}; + + +class Building +{ + //告诉编译器 goodGay类中的visit成员函数 是Building好朋友,可以访问私有内容 + friend void goodGay::visit(); + +public: + Building(); + +public: + string m_SittingRoom; //客厅 +private: + string m_BedRoom;//卧室 +}; + +Building::Building() +{ + this->m_SittingRoom = "客厅"; + this->m_BedRoom = "卧室"; +} + +goodGay::goodGay() +{ + building = new Building; +} + +void goodGay::visit() +{ + cout << "好基友正在访问" << building->m_SittingRoom << endl; + cout << "好基友正在访问" << building->m_BedRoom << endl; +} + +void goodGay::visit2() +{ + cout << "好基友正在访问" << building->m_SittingRoom << endl; + //cout << "好基友正在访问" << building->m_BedRoom << endl; +} + +void test01() +{ + goodGay gg; + gg.visit(); + +} + +int main(){ + + test01(); + + system("pause"); + return 0; +} +``` + + + + + + + + + +### 4.5 运算符重载 + + + +运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型 + + + +#### 4.5.1 加号运算符重载 + + + +作用:实现两个自定义数据类型相加的运算 + + + +```C++ +class Person { +public: + Person() {}; + Person(int a, int b) + { + this->m_A = a; + this->m_B = b; + } + //成员函数实现 + 号运算符重载 + Person operator+(const Person& p) { + Person temp; + temp.m_A = this->m_A + p.m_A; + temp.m_B = this->m_B + p.m_B; + return temp; + } + + +public: + int m_A; + int m_B; +}; + +//全局函数实现 + 号运算符重载 +//Person operator+(const Person& p1, const Person& p2) { +// Person temp(0, 0); +// temp.m_A = p1.m_A + p2.m_A; +// temp.m_B = p1.m_B + p2.m_B; +// return temp; +//} + +//运算符重载 可以发生函数重载 +Person operator+(const Person& p2, int val) +{ + Person temp; + temp.m_A = p2.m_A + val; + temp.m_B = p2.m_B + val; + return temp; +} + +void test() { + + Person p1(10, 10); + Person p2(20, 20); + + //成员函数方式 + Person p3 = p2 + p1; //相当于 p2.operaor+(p1) + cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl; + + + Person p4 = p3 + 10; //相当于 operator+(p3,10) + cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl; + +} + +int main() { + + test(); + + system("pause"); + + return 0; +} +``` + + + +> 总结1:对于内置的数据类型的表达式的的运算符是不可能改变的 + +> 总结2:不要滥用运算符重载 + + + + + + + +#### 4.5.2 左移运算符重载 + + + +作用:可以输出自定义数据类型 + + + +```C++ +class Person { + friend ostream& operator<<(ostream& out, Person& p); + +public: + + Person(int a, int b) + { + this->m_A = a; + this->m_B = b; + } + + //成员函数 实现不了 p << cout 不是我们想要的效果 + //void operator<<(Person& p){ + //} + +private: + int m_A; + int m_B; +}; + +//全局函数实现左移重载 +//ostream对象只能有一个 +ostream& operator<<(ostream& out, Person& p) { + out << "a:" << p.m_A << " b:" << p.m_B; + return out; +} + +void test() { + + Person p1(10, 20); + + cout << p1 << "hello world" << endl; //链式编程 +} + +int main() { + + test(); + + system("pause"); + + return 0; +} +``` + + + +> 总结:重载左移运算符配合友元可以实现输出自定义数据类型 + + + + + + + + + + + + + +#### 4.5.3 递增运算符重载 + + + +作用: 通过重载递增运算符,实现自己的整型数据 + + + +```C++ + +class MyInteger { + + friend ostream& operator<<(ostream& out, MyInteger myint); + +public: + MyInteger() { + m_Num = 0; + } + //前置++ + MyInteger& operator++() { + //先++ + m_Num++; + //再返回 + return *this; + } + + //后置++ + MyInteger operator++(int) { + //先返回 + MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++; + m_Num++; + return temp; + } + +private: + int m_Num; +}; + + +ostream& operator<<(ostream& out, MyInteger myint) { + out << myint.m_Num; + return out; +} + + +//前置++ 先++ 再返回 +void test01() { + MyInteger myInt; + cout << ++myInt << endl; + cout << myInt << endl; +} + +//后置++ 先返回 再++ +void test02() { + + MyInteger myInt; + cout << myInt++ << endl; + cout << myInt << endl; +} + +int main() { + + test01(); + //test02(); + + system("pause"); + + return 0; +} +``` + + + +> 总结: 前置递增返回引用,后置递增返回值 + + + + + + + + + + + + + +#### 4.5.4 赋值运算符重载 + + + +c++编译器至少给一个类添加4个函数 + +1. 默认构造函数(无参,函数体为空) +2. 默认析构函数(无参,函数体为空) +3. 默认拷贝构造函数,对属性进行值拷贝 +4. 赋值运算符 operator=, 对属性进行值拷贝 + + + + + +如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题 + + + + + +**示例:** + +```C++ +class Person +{ +public: + + Person(int age) + { + //将年龄数据开辟到堆区 + m_Age = new int(age); + } + + //重载赋值运算符 + Person& operator=(Person &p) + { + if (m_Age != NULL) + { + delete m_Age; + m_Age = NULL; + } + //编译器提供的代码是浅拷贝 + //m_Age = p.m_Age; + + //提供深拷贝 解决浅拷贝的问题 + m_Age = new int(*p.m_Age); + + //返回自身 + return *this; + } + + + ~Person() + { + if (m_Age != NULL) + { + delete m_Age; + m_Age = NULL; + } + } + + //年龄的指针 + int *m_Age; + +}; + + +void test01() +{ + Person p1(18); + + Person p2(20); + + Person p3(30); + + p3 = p2 = p1; //赋值操作 + + cout << "p1的年龄为:" << *p1.m_Age << endl; + + cout << "p2的年龄为:" << *p2.m_Age << endl; + + cout << "p3的年龄为:" << *p3.m_Age << endl; +} + +int main() { + + test01(); + + //int a = 10; + //int b = 20; + //int c = 30; + + //c = b = a; + //cout << "a = " << a << endl; + //cout << "b = " << b << endl; + //cout << "c = " << c << endl; + + system("pause"); + + return 0; +} +``` + + + + + + + + + +#### 4.5.5 关系运算符重载 + + + +**作用:**重载关系运算符,可以让两个自定义类型对象进行对比操作 + + + +**示例:** + +```C++ +class Person +{ +public: + Person(string name, int age) + { + this->m_Name = name; + this->m_Age = age; + }; + + bool operator==(Person & p) + { + if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) + { + return true; + } + else + { + return false; + } + } + + bool operator!=(Person & p) + { + if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) + { + return false; + } + else + { + return true; + } + } + + string m_Name; + int m_Age; +}; + +void test01() +{ + //int a = 0; + //int b = 0; + + Person a("孙悟空", 18); + Person b("孙悟空", 18); + + if (a == b) + { + cout << "a和b相等" << endl; + } + else + { + cout << "a和b不相等" << endl; + } + + if (a != b) + { + cout << "a和b不相等" << endl; + } + else + { + cout << "a和b相等" << endl; + } +} + + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + + + + + +#### 4.5.6 函数调用运算符重载 + + + +* 函数调用运算符 () 也可以重载 +* 由于重载后使用的方式非常像函数的调用,因此称为仿函数 +* 仿函数没有固定写法,非常灵活 + + + +**示例:** + +```C++ +class MyPrint +{ +public: + void operator()(string text) + { + cout << text << endl; + } + +}; +void test01() +{ + //重载的()操作符 也称为仿函数 + MyPrint myFunc; + myFunc("hello world"); +} + + +class MyAdd +{ +public: + int operator()(int v1, int v2) + { + return v1 + v2; + } +}; + +void test02() +{ + MyAdd add; + int ret = add(10, 10); + cout << "ret = " << ret << endl; + + //匿名对象调用 + cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl; +} + +int main() { + + test01(); + test02(); + + system("pause"); + + return 0; +} +``` + + + + + + + + + +### 4.6 继承 + +**继承是面向对象三大特性之一** + +有些类与类之间存在特殊的关系,例如下图中: + +![1544861202252](assets/1544861202252.png) + +我们发现,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。 + +这个时候我们就可以考虑利用继承的技术,减少重复代码 + + + +#### 4.6.1 继承的基本语法 + + + +例如我们看到很多网站中,都有公共的头部,公共的底部,甚至公共的左侧列表,只有中心内容不同 + +接下来我们分别利用普通写法和继承的写法来实现网页中的内容,看一下继承存在的意义以及好处 + + + +**普通实现:** + +```C++ +//Java页面 +class Java +{ +public: + void header() + { + cout << "首页、公开课、登录、注册...(公共头部)" << endl; + } + void footer() + { + cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl; + } + void left() + { + cout << "Java,Python,C++...(公共分类列表)" << endl; + } + void content() + { + cout << "JAVA学科视频" << endl; + } +}; +//Python页面 +class Python +{ +public: + void header() + { + cout << "首页、公开课、登录、注册...(公共头部)" << endl; + } + void footer() + { + cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl; + } + void left() + { + cout << "Java,Python,C++...(公共分类列表)" << endl; + } + void content() + { + cout << "Python学科视频" << endl; + } +}; +//C++页面 +class CPP +{ +public: + void header() + { + cout << "首页、公开课、登录、注册...(公共头部)" << endl; + } + void footer() + { + cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl; + } + void left() + { + cout << "Java,Python,C++...(公共分类列表)" << endl; + } + void content() + { + cout << "C++学科视频" << endl; + } +}; + +void test01() +{ + //Java页面 + cout << "Java下载视频页面如下: " << endl; + Java ja; + ja.header(); + ja.footer(); + ja.left(); + ja.content(); + cout << "--------------------" << endl; + + //Python页面 + cout << "Python下载视频页面如下: " << endl; + Python py; + py.header(); + py.footer(); + py.left(); + py.content(); + cout << "--------------------" << endl; + + //C++页面 + cout << "C++下载视频页面如下: " << endl; + CPP cp; + cp.header(); + cp.footer(); + cp.left(); + cp.content(); + +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + + + +**继承实现:** + +```C++ +//公共页面 +class BasePage +{ +public: + void header() + { + cout << "首页、公开课、登录、注册...(公共头部)" << endl; + } + + void footer() + { + cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl; + } + void left() + { + cout << "Java,Python,C++...(公共分类列表)" << endl; + } + +}; + +//Java页面 +class Java : public BasePage +{ +public: + void content() + { + cout << "JAVA学科视频" << endl; + } +}; +//Python页面 +class Python : public BasePage +{ +public: + void content() + { + cout << "Python学科视频" << endl; + } +}; +//C++页面 +class CPP : public BasePage +{ +public: + void content() + { + cout << "C++学科视频" << endl; + } +}; + +void test01() +{ + //Java页面 + cout << "Java下载视频页面如下: " << endl; + Java ja; + ja.header(); + ja.footer(); + ja.left(); + ja.content(); + cout << "--------------------" << endl; + + //Python页面 + cout << "Python下载视频页面如下: " << endl; + Python py; + py.header(); + py.footer(); + py.left(); + py.content(); + cout << "--------------------" << endl; + + //C++页面 + cout << "C++下载视频页面如下: " << endl; + CPP cp; + cp.header(); + cp.footer(); + cp.left(); + cp.content(); + + +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + + + +**总结:** + +继承的好处:==可以减少重复的代码== + +class A : public B; + +A 类称为子类 或 派生类 + +B 类称为父类 或 基类 + + + +**派生类中的成员,包含两大部分**: + +一类是从基类继承过来的,一类是自己增加的成员。 + +从基类继承过过来的表现其共性,而新增的成员体现了其个性。 + + + + + + + + + +#### 4.6.2 继承方式 + + + +继承的语法:`class 子类 : 继承方式 父类` + + + +**继承方式一共有三种:** + +* 公共继承 +* 保护继承 +* 私有继承 + + + + + +![img](assets/clip_image002.png) + + + + + +**示例:** + +```C++ +class Base1 +{ +public: + int m_A; +protected: + int m_B; +private: + int m_C; +}; + +//公共继承 +class Son1 :public Base1 +{ +public: + void func() + { + m_A; //可访问 public权限 + m_B; //可访问 protected权限 + //m_C; //不可访问 + } +}; + +void myClass() +{ + Son1 s1; + s1.m_A; //其他类只能访问到公共权限 +} + +//保护继承 +class Base2 +{ +public: + int m_A; +protected: + int m_B; +private: + int m_C; +}; +class Son2:protected Base2 +{ +public: + void func() + { + m_A; //可访问 protected权限 + m_B; //可访问 protected权限 + //m_C; //不可访问 + } +}; +void myClass2() +{ + Son2 s; + //s.m_A; //不可访问 +} + +//私有继承 +class Base3 +{ +public: + int m_A; +protected: + int m_B; +private: + int m_C; +}; +class Son3:private Base3 +{ +public: + void func() + { + m_A; //可访问 private权限 + m_B; //可访问 private权限 + //m_C; //不可访问 + } +}; +class GrandSon3 :public Son3 +{ +public: + void func() + { + //Son3是私有继承,所以继承Son3的属性在GrandSon3中都无法访问到 + //m_A; + //m_B; + //m_C; + } +}; +``` + + + + + + + + + +#### 4.6.3 继承中的对象模型 + + + +**问题:**从父类继承过来的成员,哪些属于子类对象中? + + + +**示例:** + +```C++ +class Base +{ +public: + int m_A; +protected: + int m_B; +private: + int m_C; //私有成员只是被隐藏了,但是还是会继承下去 +}; + +//公共继承 +class Son :public Base +{ +public: + int m_D; +}; + +void test01() +{ + cout << "sizeof Son = " << sizeof(Son) << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + + + + + +利用工具查看: + + + +![1545881904150](assets/1545881904150.png) + + + +打开工具窗口后,定位到当前CPP文件的盘符 + +然后输入: cl /d1 reportSingleClassLayout查看的类名 所属文件名 + + + +效果如下图: + + + +![1545882158050](assets/1545882158050.png) + + + +> 结论: 父类中私有成员也是被子类继承下去了,只是由编译器给隐藏后访问不到 + + + + + + + + + + + + + + + + + + + +#### 4.6.4 继承中构造和析构顺序 + + + +子类继承父类后,当创建子类对象,也会调用父类的构造函数 + + + +问题:父类和子类的构造和析构顺序是谁先谁后? + + + +**示例:** + +```C++ +class Base +{ +public: + Base() + { + cout << "Base构造函数!" << endl; + } + ~Base() + { + cout << "Base析构函数!" << endl; + } +}; + +class Son : public Base +{ +public: + Son() + { + cout << "Son构造函数!" << endl; + } + ~Son() + { + cout << "Son析构函数!" << endl; + } + +}; + + +void test01() +{ + //继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反 + Son s; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + + + +> 总结:继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反 + + + + + + + + + + + +#### 4.6.5 继承同名成员处理方式 + + + +问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢? + + + +* 访问子类同名成员 直接访问即可 +* 访问父类同名成员 需要加作用域 + + + +**示例:** + +```C++ +class Base { +public: + Base() + { + m_A = 100; + } + + void func() + { + cout << "Base - func()调用" << endl; + } + + void func(int a) + { + cout << "Base - func(int a)调用" << endl; + } + +public: + int m_A; +}; + + +class Son : public Base { +public: + Son() + { + m_A = 200; + } + + //当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数 + //如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域 + void func() + { + cout << "Son - func()调用" << endl; + } +public: + int m_A; +}; + +void test01() +{ + Son s; + + cout << "Son下的m_A = " << s.m_A << endl; + cout << "Base下的m_A = " << s.Base::m_A << endl; + + s.func(); + s.Base::func(); + s.Base::func(10); + +} +int main() { + + test01(); + + system("pause"); + return EXIT_SUCCESS; +} +``` + +总结: + +1. 子类对象可以直接访问到子类中同名成员 +2. 子类对象加作用域可以访问到父类同名成员 +3. 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数 + + + + + + + + + + + + + +#### 4.6.6 继承同名静态成员处理方式 + + + +问题:继承中同名的静态成员在子类对象上如何进行访问? + + + +静态成员和非静态成员出现同名,处理方式一致 + + + +- 访问子类同名成员 直接访问即可 +- 访问父类同名成员 需要加作用域 + + + +**示例:** + +```C++ +class Base { +public: + static void func() + { + cout << "Base - static void func()" << endl; + } + static void func(int a) + { + cout << "Base - static void func(int a)" << endl; + } + + static int m_A; +}; + +int Base::m_A = 100; + +class Son : public Base { +public: + static void func() + { + cout << "Son - static void func()" << endl; + } + static int m_A; +}; + +int Son::m_A = 200; + +//同名成员属性 +void test01() +{ + //通过对象访问 + cout << "通过对象访问: " << endl; + Son s; + cout << "Son 下 m_A = " << s.m_A << endl; + cout << "Base 下 m_A = " << s.Base::m_A << endl; + + //通过类名访问 + cout << "通过类名访问: " << endl; + cout << "Son 下 m_A = " << Son::m_A << endl; + cout << "Base 下 m_A = " << Son::Base::m_A << endl; +} + +//同名成员函数 +void test02() +{ + //通过对象访问 + cout << "通过对象访问: " << endl; + Son s; + s.func(); + s.Base::func(); + + cout << "通过类名访问: " << endl; + Son::func(); + Son::Base::func(); + //出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问 + Son::Base::func(100); +} +int main() { + + //test01(); + test02(); + + system("pause"); + + return 0; +} +``` + +> 总结:同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象 和 通过类名) + + + + + + + + + + + + + +#### 4.6.7 多继承语法 + + + +C++允许**一个类继承多个类** + + + +语法:` class 子类 :继承方式 父类1 , 继承方式 父类2...` + + + +多继承可能会引发父类中有同名成员出现,需要加作用域区分 + + + +**C++实际开发中不建议用多继承** + + + + + + + +**示例:** + +```C++ +class Base1 { +public: + Base1() + { + m_A = 100; + } +public: + int m_A; +}; + +class Base2 { +public: + Base2() + { + m_A = 200; //开始是m_B 不会出问题,但是改为mA就会出现不明确 + } +public: + int m_A; +}; + +//语法:class 子类:继承方式 父类1 ,继承方式 父类2 +class Son : public Base2, public Base1 +{ +public: + Son() + { + m_C = 300; + m_D = 400; + } +public: + int m_C; + int m_D; +}; + + +//多继承容易产生成员同名的情况 +//通过使用类名作用域可以区分调用哪一个基类的成员 +void test01() +{ + Son s; + cout << "sizeof Son = " << sizeof(s) << endl; + cout << s.Base1::m_A << endl; + cout << s.Base2::m_A << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + + + +> 总结: 多继承中如果父类中出现了同名情况,子类使用时候要加作用域 + + + +#### 4.6.8 菱形继承 + + + +**菱形继承概念:** + +​ 两个派生类继承同一个基类 + +​ 又有某个类同时继承者两个派生类 + +​ 这种继承被称为菱形继承,或者钻石继承 + + + +**典型的菱形继承案例:** + + + +![IMG_256](assets/clip_image002.jpg) + + + +**菱形继承问题:** + + + +1. 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。 + +2. 草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。 + + + +**示例:** + +```C++ +class Animal +{ +public: + int m_Age; +}; + +//继承前加virtual关键字后,变为虚继承 +//此时公共的父类Animal称为虚基类 +class Sheep : virtual public Animal {}; +class Tuo : virtual public Animal {}; +class SheepTuo : public Sheep, public Tuo {}; + +void test01() +{ + SheepTuo st; + st.Sheep::m_Age = 100; + st.Tuo::m_Age = 200; + + cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl; + cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl; + cout << "st.m_Age = " << st.m_Age << endl; +} + + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + + + +总结: + +* 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义 +* 利用虚继承可以解决菱形继承问题 + +### 4.7 多态 + +#### 4.7.1 多态的基本概念 + + + +**多态是C++面向对象三大特性之一** + +多态分为两类 + +* 静态多态: 函数重载 和 运算符重载、模板属于静态多态,复用函数名 +* 动态多态: 派生类和虚函数实现运行时多态 + + + +静态多态和动态多态区别: + +* 静态多态的函数地址早绑定 - 编译阶段确定函数地址 +* 动态多态的函数地址晚绑定 - 运行阶段确定函数地址 + + + +下面通过案例进行讲解多态 + + + +```C++ +class Animal +{ +public: + //Speak函数就是虚函数 + //函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。 + virtual void speak() + { + cout << "动物在说话" << endl; + } +}; + +class Cat :public Animal +{ +public: + void speak() + { + cout << "小猫在说话" << endl; + } +}; + +class Dog :public Animal +{ +public: + + void speak() + { + cout << "小狗在说话" << endl; + } + +}; +//我们希望传入什么对象,那么就调用什么对象的函数 +//如果函数地址在编译阶段就能确定,那么静态联编 +//如果函数地址在运行阶段才能确定,就是动态联编 + +void DoSpeak(Animal & animal) +{ + animal.speak(); +} +// +//多态满足条件: +//1、有继承关系 +//2、子类重写父类中的虚函数,重写:函数返回值类型 函数名 参数列表 完全相同 +//多态使用: +//父类指针或引用指向子类对象 + +void test01() +{ + Cat cat; + DoSpeak(cat); + + + Dog dog; + DoSpeak(dog); +} + + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结: + +多态满足条件 + +* 有继承关系 +* 子类重写父类中的虚函数 + +多态使用条件 + +* 父类指针或引用指向子类对象 + +重写:函数返回值类型 函数名 参数列表 完全一致称为重写 + + + + + + + + + +#### 4.7.2 多态案例一-计算器类 + + + +案例描述: + +分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类 + + + +多态的优点: + +* 代码组织结构清晰 +* 可读性强 +* 利于前期和后期的扩展以及维护 + + + +**示例:** + +```C++ +//普通实现 +class Calculator { +public: + int getResult(string oper) + { + if (oper == "+") { + return m_Num1 + m_Num2; + } + else if (oper == "-") { + return m_Num1 - m_Num2; + } + else if (oper == "*") { + return m_Num1 * m_Num2; + } + //如果要提供新的运算,需要修改源码 + } +public: + int m_Num1; + int m_Num2; +}; + +void test01() +{ + //普通实现测试 + Calculator c; + c.m_Num1 = 10; + c.m_Num2 = 10; + cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult("+") << endl; + + cout << c.m_Num1 << " - " << c.m_Num2 << " = " << c.getResult("-") << endl; + + cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResult("*") << endl; +} + + + +//多态实现 +//抽象计算器类 +//多态优点:代码组织结构清晰,可读性强,利于前期和后期的扩展以及维护 +class AbstractCalculator +{ +public : + + virtual int getResult() + { + return 0; + } + + int m_Num1; + int m_Num2; +}; + +//加法计算器 +class AddCalculator :public AbstractCalculator +{ +public: + int getResult() + { + return m_Num1 + m_Num2; + } +}; + +//减法计算器 +class SubCalculator :public AbstractCalculator +{ +public: + int getResult() + { + return m_Num1 - m_Num2; + } +}; + +//乘法计算器 +class MulCalculator :public AbstractCalculator +{ +public: + int getResult() + { + return m_Num1 * m_Num2; + } +}; + + +void test02() +{ + //创建加法计算器 + AbstractCalculator *abc = new AddCalculator; + abc->m_Num1 = 10; + abc->m_Num2 = 10; + cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl; + delete abc; //用完了记得销毁 + + //创建减法计算器 + abc = new SubCalculator; + abc->m_Num1 = 10; + abc->m_Num2 = 10; + cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl; + delete abc; + + //创建乘法计算器 + abc = new MulCalculator; + abc->m_Num1 = 10; + abc->m_Num2 = 10; + cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl; + delete abc; +} + +int main() { + + //test01(); + + test02(); + + system("pause"); + + return 0; +} +``` + +> 总结:C++开发提倡利用多态设计程序架构,因为多态优点很多 + + + + + + + + + + + + + + + + + +#### 4.7.3 纯虚函数和抽象类 + + + +在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容 + + + +因此可以将虚函数改为**纯虚函数** + + + +纯虚函数语法:`virtual 返回值类型 函数名 (参数列表)= 0 ;` + + + +当类中有了纯虚函数,这个类也称为==抽象类== + + + +**抽象类特点**: + + * 无法实例化对象 + * 子类必须重写抽象类中的纯虚函数,否则也属于抽象类 + + + + + +**示例:** + +```C++ +class Base +{ +public: + //纯虚函数 + //类中只要有一个纯虚函数就称为抽象类 + //抽象类无法实例化对象 + //子类必须重写父类中的纯虚函数,否则也属于抽象类 + virtual void func() = 0; +}; + +class Son :public Base +{ +public: + virtual void func() + { + cout << "func调用" << endl; + }; +}; + +void test01() +{ + Base * base = NULL; + //base = new Base; // 错误,抽象类无法实例化对象 + base = new Son; + base->func(); + delete base;//记得销毁 +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + + + + + + + + + + + + + + + +#### 4.7.4 多态案例二-制作饮品 + +**案例描述:** + +制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 + + + +利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶 + + + +![1545985945198](assets/1545985945198.png) + + + +**示例:** + +```C++ +//抽象制作饮品 +class AbstractDrinking { +public: + //烧水 + virtual void Boil() = 0; + //冲泡 + virtual void Brew() = 0; + //倒入杯中 + virtual void PourInCup() = 0; + //加入辅料 + virtual void PutSomething() = 0; + //规定流程 + void MakeDrink() { + Boil(); + Brew(); + PourInCup(); + PutSomething(); + } +}; + +//制作咖啡 +class Coffee : public AbstractDrinking { +public: + //烧水 + virtual void Boil() { + cout << "煮农夫山泉!" << endl; + } + //冲泡 + virtual void Brew() { + cout << "冲泡咖啡!" << endl; + } + //倒入杯中 + virtual void PourInCup() { + cout << "将咖啡倒入杯中!" << endl; + } + //加入辅料 + virtual void PutSomething() { + cout << "加入牛奶!" << endl; + } +}; + +//制作茶水 +class Tea : public AbstractDrinking { +public: + //烧水 + virtual void Boil() { + cout << "煮自来水!" << endl; + } + //冲泡 + virtual void Brew() { + cout << "冲泡茶叶!" << endl; + } + //倒入杯中 + virtual void PourInCup() { + cout << "将茶水倒入杯中!" << endl; + } + //加入辅料 + virtual void PutSomething() { + cout << "加入枸杞!" << endl; + } +}; + +//业务函数 +void DoWork(AbstractDrinking* drink) { + drink->MakeDrink(); + delete drink; +} + +void test01() { + DoWork(new Coffee); + cout << "--------------" << endl; + DoWork(new Tea); +} + + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + + + + + + + + + + + + + + + + + + + +#### 4.7.5 虚析构和纯虚析构 + + + +多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码 + + + +解决方式:将父类中的析构函数改为**虚析构**或者**纯虚析构** + + + +虚析构和纯虚析构共性: + +* 可以解决父类指针释放子类对象 +* 都需要有具体的函数实现 + +虚析构和纯虚析构区别: + +* 如果是纯虚析构,该类属于抽象类,无法实例化对象 + + + +虚析构语法: + +`virtual ~类名(){}` + +纯虚析构语法: + +` virtual ~类名() = 0;` + +`类名::~类名(){}` + +(构造函数的虚函数不会调用子类重写的虚函数,是静态绑定。 + +子类的构造函数是先调用父类的构造函数。add by HL) + +**示例:** + +```C++ +class Animal { +public: + + Animal() + { + cout << "Animal 构造函数调用!" << endl; + } + virtual void Speak() = 0; + + //析构函数加上virtual关键字,变成虚析构函数 + //virtual ~Animal() + //{ + // cout << "Animal虚析构函数调用!" << endl; + //} + + + virtual ~Animal() = 0; +}; + +Animal::~Animal() +{ + cout << "Animal 纯虚析构函数调用!" << endl; +} + +//和包含普通纯虚函数的类一样,包含了纯虚析构函数的类也是一个抽象类。不能够被实例化。 + +class Cat : public Animal { +public: + Cat(string name) + { + cout << "Cat构造函数调用!" << endl; + m_Name = new string(name); + } + virtual void Speak() + { + cout << *m_Name << "小猫在说话!" << endl; + } + ~Cat() + { + cout << "Cat析构函数调用!" << endl; + if (this->m_Name != NULL) { + delete m_Name; + m_Name = NULL; + } + } + +public: + string *m_Name; +}; + +void test01() +{ + Animal *animal = new Cat("Tom"); + animal->Speak(); + + //通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏 + //怎么解决?给基类增加一个虚析构函数 + //虚析构函数就是用来解决通过父类指针释放子类对象 + delete animal; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + + + +总结: + +​ 1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象 + +​ 2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构 + +​ 3. 拥有纯虚析构函数的类也属于抽象类 + + + + + + + + + + + + + + + +#### 4.7.6 多态案例三-电脑组装 + + + +**案例描述:** + + + +电脑主要组成部件为 CPU(用于计算),显卡(用于显示),内存条(用于存储) + +将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例如Intel厂商和Lenovo厂商 + +创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口 + +测试时组装三台不同的电脑进行工作 + + + + + +**示例:** + +```C++ +#include +using namespace std; + +//抽象CPU类 +class CPU +{ +public: + //抽象的计算函数 + virtual void calculate() = 0; +}; + +//抽象显卡类 +class VideoCard +{ +public: + //抽象的显示函数 + virtual void display() = 0; +}; + +//抽象内存条类 +class Memory +{ +public: + //抽象的存储函数 + virtual void storage() = 0; +}; + +//电脑类 +class Computer +{ +public: + Computer(CPU * cpu, VideoCard * vc, Memory * mem) + { + m_cpu = cpu; + m_vc = vc; + m_mem = mem; + } + + //提供工作的函数 + void work() + { + //让零件工作起来,调用接口 + m_cpu->calculate(); + + m_vc->display(); + + m_mem->storage(); + } + + //提供析构函数 释放3个电脑零件 + ~Computer() + { + + //释放CPU零件 + if (m_cpu != NULL) + { + delete m_cpu; + m_cpu = NULL; + } + + //释放显卡零件 + if (m_vc != NULL) + { + delete m_vc; + m_vc = NULL; + } + + //释放内存条零件 + if (m_mem != NULL) + { + delete m_mem; + m_mem = NULL; + } + } + +private: + + CPU * m_cpu; //CPU的零件指针 + VideoCard * m_vc; //显卡零件指针 + Memory * m_mem; //内存条零件指针 +}; + +//具体厂商 +//Intel厂商 +class IntelCPU :public CPU +{ +public: + virtual void calculate() + { + cout << "Intel的CPU开始计算了!" << endl; + } +}; + +class IntelVideoCard :public VideoCard +{ +public: + virtual void display() + { + cout << "Intel的显卡开始显示了!" << endl; + } +}; + +class IntelMemory :public Memory +{ +public: + virtual void storage() + { + cout << "Intel的内存条开始存储了!" << endl; + } +}; + +//Lenovo厂商 +class LenovoCPU :public CPU +{ +public: + virtual void calculate() + { + cout << "Lenovo的CPU开始计算了!" << endl; + } +}; + +class LenovoVideoCard :public VideoCard +{ +public: + virtual void display() + { + cout << "Lenovo的显卡开始显示了!" << endl; + } +}; + +class LenovoMemory :public Memory +{ +public: + virtual void storage() + { + cout << "Lenovo的内存条开始存储了!" << endl; + } +}; + + +void test01() +{ + //第一台电脑零件 + CPU * intelCpu = new IntelCPU; + VideoCard * intelCard = new IntelVideoCard; + Memory * intelMem = new IntelMemory; + + cout << "第一台电脑开始工作:" << endl; + //创建第一台电脑 + Computer * computer1 = new Computer(intelCpu, intelCard, intelMem); + computer1->work(); + delete computer1; + + cout << "-----------------------" << endl; + cout << "第二台电脑开始工作:" << endl; + //第二台电脑组装 + Computer * computer2 = new Computer(new LenovoCPU, new LenovoVideoCard, new LenovoMemory);; + computer2->work(); + delete computer2; + + cout << "-----------------------" << endl; + cout << "第三台电脑开始工作:" << endl; + //第三台电脑组装 + Computer * computer3 = new Computer(new LenovoCPU, new IntelVideoCard, new LenovoMemory);; + computer3->work(); + delete computer3; + +} +``` + + + + + + + + + + + + + +## 5 文件操作 + + + +程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放 + +通过**文件可以将数据持久化** + +C++中对文件操作需要包含头文件 ==< fstream >== + + + +文件类型分为两种: + +1. **文本文件** - 文件以文本的**ASCII码**形式存储在计算机中 +2. **二进制文件** - 文件以文本的**二进制**形式存储在计算机中,用户一般不能直接读懂它们 + + + +操作文件的三大类: + +1. ofstream:写操作 +2. ifstream: 读操作 +3. fstream : 读写操作 + + + +### 5.1文本文件 + +#### 5.1.1写文件 + + 写文件步骤如下: + +1. 包含头文件 + + \#include + +2. 创建流对象 + + ofstream ofs; + +3. 打开文件 + + ofs.open("文件路径",打开方式); + +4. 写数据 + + ofs << "写入的数据"; + +5. 关闭文件 + + ofs.close(); + + + +文件打开方式: + +| 打开方式 | 解释 | +| ----------- | -------------------------- | +| ios::in | 为读文件而打开文件 | +| ios::out | 为写文件而打开文件 | +| ios::ate | 初始位置:文件尾 | +| ios::app | 追加方式写文件 | +| ios::trunc | 如果文件存在先删除,再创建 | +| ios::binary | 二进制方式 | + +**注意:** 文件打开方式可以配合使用,利用|操作符 + +**例如:**用二进制方式写文件 `ios::binary | ios:: out` + + + + + +**示例:** + +```C++ +#include + +void test01() +{ + ofstream ofs; + ofs.open("test.txt", ios::out); + + ofs << "姓名:张三" << endl; + ofs << "性别:男" << endl; + ofs << "年龄:18" << endl; + + ofs.close(); +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结: + +* 文件操作必须包含头文件 fstream +* 读文件可以利用 ofstream ,或者fstream类 +* 打开文件时候需要指定操作文件的路径,以及打开方式 +* 利用<<可以向文件中写数据 +* 操作完毕,要关闭文件 + + + + + + + + + + + + + + + + + +#### 5.1.2读文件 + + + +读文件与写文件步骤相似,但是读取方式相对于比较多 + + + +读文件步骤如下: + +1. 包含头文件 + + \#include + +2. 创建流对象 + + ifstream ifs; + +3. 打开文件并判断文件是否打开成功 + + ifs.open("文件路径",打开方式); + +4. 读数据 + + 四种方式读取 + +5. 关闭文件 + + ifs.close(); + + + +**示例:** + +```C++ +#include +#include +void test01() +{ + ifstream ifs; + ifs.open("test.txt", ios::in); + + if (!ifs.is_open()) + { + cout << "文件打开失败" << endl; + return; + } + + //第一种方式 + //char buf[1024] = { 0 }; + //while (ifs >> buf) + //{ + // cout << buf << endl; + //} + + //第二种 + //char buf[1024] = { 0 }; + //while (ifs.getline(buf,sizeof(buf))) + //{ + // cout << buf << endl; + //} + + //第三种 + //string buf; + //while (getline(ifs, buf)) + //{ + // cout << buf << endl; + //} + + char c; + while ((c = ifs.get()) != EOF) + { + cout << c; + } + + ifs.close(); + + +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结: + +- 读文件可以利用 ifstream ,或者fstream类 +- 利用is_open函数可以判断文件是否打开成功 +- close 关闭文件 + + + + + + + + + + + + + + + +### 5.2 二进制文件 + +以二进制的方式对文件进行读写操作 + +打开方式要指定为 ==ios::binary== + + + +#### 5.2.1 写文件 + +二进制方式写文件主要利用流对象调用成员函数write + +函数原型 :`ostream& write(const char * buffer,int len);` + +参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数 + + + +**示例:** + +```C++ +#include +#include + +class Person +{ +public: + char m_Name[64]; + int m_Age; +}; + +//二进制文件 写文件 +void test01() +{ + //1、包含头文件 + + //2、创建输出流对象 + ofstream ofs("person.txt", ios::out | ios::binary); + + //3、打开文件 + //ofs.open("person.txt", ios::out | ios::binary); + + Person p = {"张三" , 18}; + + //4、写文件 + ofs.write((const char *)&p, sizeof(p)); + + //5、关闭文件 + ofs.close(); +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + +总结: + +* 文件输出流对象 可以通过write函数,以二进制方式写数据 + + + + + + + + + + + +#### 5.2.2 读文件 + +二进制方式读文件主要利用流对象调用成员函数read + +函数原型:`istream& read(char *buffer,int len);` + +参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数 + +示例: + +```C++ +#include +#include + +class Person +{ +public: + char m_Name[64]; + int m_Age; +}; + +void test01() +{ + ifstream ifs("person.txt", ios::in | ios::binary); + if (!ifs.is_open()) + { + cout << "文件打开失败" << endl; + } + + Person p; + ifs.read((char *)&p, sizeof(p)); + + cout << "姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl; +} + +int main() { + + test01(); + + system("pause"); + + return 0; +} +``` + + + +- 文件输入流对象 可以通过read函数,以二进制方式读数据 + +N + + \ No newline at end of file diff --git a/note/CPP-Design-Patterns-master/.gitignore b/note/CPP-Design-Patterns-master/.gitignore new file mode 100644 index 0000000000..25faeb23f6 --- /dev/null +++ b/note/CPP-Design-Patterns-master/.gitignore @@ -0,0 +1,3 @@ +# Created by .ignore support plugin (hsz.mobi) +.DS_Store +.idea/ \ No newline at end of file diff --git "a/note/CPP-Design-Patterns-master/01_\350\257\276\344\273\266_\344\273\213\347\273\215/\344\273\213\347\273\215.pdf" "b/note/CPP-Design-Patterns-master/01_\350\257\276\344\273\266_\344\273\213\347\273\215/\344\273\213\347\273\215.pdf" new file mode 100644 index 0000000000..f2e356fdca Binary files /dev/null and "b/note/CPP-Design-Patterns-master/01_\350\257\276\344\273\266_\344\273\213\347\273\215/\344\273\213\347\273\215.pdf" differ diff --git "a/note/CPP-Design-Patterns-master/01_\350\257\276\344\273\266_\344\273\213\347\273\215/\344\273\243\347\240\201/MyPaint1/MainForm1.cpp" "b/note/CPP-Design-Patterns-master/01_\350\257\276\344\273\266_\344\273\213\347\273\215/\344\273\243\347\240\201/MyPaint1/MainForm1.cpp" new file mode 100644 index 0000000000..b0a912a219 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/01_\350\257\276\344\273\266_\344\273\213\347\273\215/\344\273\243\347\240\201/MyPaint1/MainForm1.cpp" @@ -0,0 +1,87 @@ + +class MainForm : public Form { +private: + Point p1; + Point p2; + + vector lineVector; + vector rectVector; + //改变 + vector circleVector; + +public: + MainForm(){ + //... + } +protected: + + virtual void OnMouseDown(const MouseEventArgs& e); + virtual void OnMouseUp(const MouseEventArgs& e); + virtual void OnPaint(const PaintEventArgs& e); +}; + + +void MainForm::OnMouseDown(const MouseEventArgs& e){ + p1.x = e.X; + p1.y = e.Y; + + //... + Form::OnMouseDown(e); +} + +void MainForm::OnMouseUp(const MouseEventArgs& e){ + p2.x = e.X; + p2.y = e.Y; + + if (rdoLine.Checked){ + Line line(p1, p2); + lineVector.push_back(line); + } + else if (rdoRect.Checked){ + int width = abs(p2.x - p1.x); + int height = abs(p2.y - p1.y); + Rect rect(p1, width, height); + rectVector.push_back(rect); + } + //改变 + else if (...){ + //... + circleVector.push_back(circle); + } + + //... + this->Refresh(); + + Form::OnMouseUp(e); +} + +void MainForm::OnPaint(const PaintEventArgs& e){ + + //针对直线 + for (int i = 0; i < lineVector.size(); i++){ + e.Graphics.DrawLine(Pens.Red, + lineVector[i].start.x, + lineVector[i].start.y, + lineVector[i].end.x, + lineVector[i].end.y); + } + + //针对矩形 + for (int i = 0; i < rectVector.size(); i++){ + e.Graphics.DrawRectangle(Pens.Red, + rectVector[i].leftUp, + rectVector[i].width, + rectVector[i].height); + } + + //改变 + //针对圆形 + for (int i = 0; i < circleVector.size(); i++){ + e.Graphics.DrawCircle(Pens.Red, + circleVector[i]); + } + + //... + Form::OnPaint(e); +} + diff --git "a/note/CPP-Design-Patterns-master/01_\350\257\276\344\273\266_\344\273\213\347\273\215/\344\273\243\347\240\201/MyPaint1/Shape1.h" "b/note/CPP-Design-Patterns-master/01_\350\257\276\344\273\266_\344\273\213\347\273\215/\344\273\243\347\240\201/MyPaint1/Shape1.h" new file mode 100644 index 0000000000..38068f0d51 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/01_\350\257\276\344\273\266_\344\273\213\347\273\215/\344\273\243\347\240\201/MyPaint1/Shape1.h" @@ -0,0 +1,39 @@ +class Point{ +public: + int x; + int y; +}; + +class Line{ +public: + Point start; + Point end; + + Line(const Point& start, const Point& end){ + this->start = start; + this->end = end; + } + +}; + +class Rect{ +public: + Point leftUp; + int width; + int height; + + Rect(const Point& leftUp, int width, int height){ + this->leftUp = leftUp; + this->width = width; + this->height = height; + } + +}; + +//增加 +class Circle{ + + +}; + + diff --git "a/note/CPP-Design-Patterns-master/01_\350\257\276\344\273\266_\344\273\213\347\273\215/\344\273\243\347\240\201/MyPaint1/_desktop.ini" "b/note/CPP-Design-Patterns-master/01_\350\257\276\344\273\266_\344\273\213\347\273\215/\344\273\243\347\240\201/MyPaint1/_desktop.ini" new file mode 100644 index 0000000000..3cc33a84ae --- /dev/null +++ "b/note/CPP-Design-Patterns-master/01_\350\257\276\344\273\266_\344\273\213\347\273\215/\344\273\243\347\240\201/MyPaint1/_desktop.ini" @@ -0,0 +1 @@ +2007/8/3 \ No newline at end of file diff --git "a/note/CPP-Design-Patterns-master/01_\350\257\276\344\273\266_\344\273\213\347\273\215/\344\273\243\347\240\201/MyPaint2/MainForm2.cpp" "b/note/CPP-Design-Patterns-master/01_\350\257\276\344\273\266_\344\273\213\347\273\215/\344\273\243\347\240\201/MyPaint2/MainForm2.cpp" new file mode 100644 index 0000000000..042521c06d --- /dev/null +++ "b/note/CPP-Design-Patterns-master/01_\350\257\276\344\273\266_\344\273\213\347\273\215/\344\273\243\347\240\201/MyPaint2/MainForm2.cpp" @@ -0,0 +1,64 @@ +class MainForm : public Form { +private: + Point p1; + Point p2; + + //针对所有形状 + vector shapeVector; + +public: + MainForm(){ + //... + } +protected: + + virtual void OnMouseDown(const MouseEventArgs& e); + virtual void OnMouseUp(const MouseEventArgs& e); + virtual void OnPaint(const PaintEventArgs& e); +}; + + +void MainForm::OnMouseDown(const MouseEventArgs& e){ + p1.x = e.X; + p1.y = e.Y; + + //... + Form::OnMouseDown(e); +} + +void MainForm::OnMouseUp(const MouseEventArgs& e){ + p2.x = e.X; + p2.y = e.Y; + + if (rdoLine.Checked){ + shapeVector.push_back(new Line(p1,p2)); + } + else if (rdoRect.Checked){ + int width = abs(p2.x - p1.x); + int height = abs(p2.y - p1.y); + shapeVector.push_back(new Rect(p1, width, height)); + } + //改变 + else if (...){ + //... + shapeVector.push_back(circle); + } + + //... + this->Refresh(); + + Form::OnMouseUp(e); +} + +void MainForm::OnPaint(const PaintEventArgs& e){ + + //针对所有形状 + for (int i = 0; i < shapeVector.size(); i++){ + + shapeVector[i]->Draw(e.Graphics); //多态调用,各负其责 + } + + //... + Form::OnPaint(e); +} + diff --git "a/note/CPP-Design-Patterns-master/01_\350\257\276\344\273\266_\344\273\213\347\273\215/\344\273\243\347\240\201/MyPaint2/Shape2.h" "b/note/CPP-Design-Patterns-master/01_\350\257\276\344\273\266_\344\273\213\347\273\215/\344\273\243\347\240\201/MyPaint2/Shape2.h" new file mode 100644 index 0000000000..2610d41d25 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/01_\350\257\276\344\273\266_\344\273\213\347\273\215/\344\273\243\347\240\201/MyPaint2/Shape2.h" @@ -0,0 +1,67 @@ +class Shape{ +public: + virtual void Draw(const Graphics& g)=0; + virtual ~Shape() { } +}; + + +class Point{ +public: + int x; + int y; +}; + +class Line: public Shape{ +public: + Point start; + Point end; + + Line(const Point& start, const Point& end){ + this->start = start; + this->end = end; + } + + //实现自己的Draw,负责画自己 + virtual void Draw(const Graphics& g){ + g.DrawLine(Pens.Red, + start.x, start.y,end.x, end.y); + } + +}; + +class Rect: public Shape{ +public: + Point leftUp; + int width; + int height; + + Rect(const Point& leftUp, int width, int height){ + this->leftUp = leftUp; + this->width = width; + this->height = height; + } + + //实现自己的Draw,负责画自己 + virtual void Draw(const Graphics& g){ + g.DrawRectangle(Pens.Red, + leftUp,width,height); + } + +}; + +//增加 +class Circle : public Shape{ +public: + //实现自己的Draw,负责画自己 + virtual void Draw(const Graphics& g){ + g.DrawCircle(Pens.Red, + ...); + } + +}; + + + + + + diff --git "a/note/CPP-Design-Patterns-master/01_\350\257\276\344\273\266_\344\273\213\347\273\215/\344\273\243\347\240\201/MyPaint2/_desktop.ini" "b/note/CPP-Design-Patterns-master/01_\350\257\276\344\273\266_\344\273\213\347\273\215/\344\273\243\347\240\201/MyPaint2/_desktop.ini" new file mode 100644 index 0000000000..3cc33a84ae --- /dev/null +++ "b/note/CPP-Design-Patterns-master/01_\350\257\276\344\273\266_\344\273\213\347\273\215/\344\273\243\347\240\201/MyPaint2/_desktop.ini" @@ -0,0 +1 @@ +2007/8/3 \ No newline at end of file diff --git "a/note/CPP-Design-Patterns-master/02_\350\257\276\344\273\266_\351\235\242\345\220\221\345\257\271\350\261\241\350\256\276\350\256\241\345\216\237\345\210\231/\351\235\242\345\220\221\345\257\271\350\261\241\350\256\276\350\256\241\345\216\237\345\210\231.pdf" "b/note/CPP-Design-Patterns-master/02_\350\257\276\344\273\266_\351\235\242\345\220\221\345\257\271\350\261\241\350\256\276\350\256\241\345\216\237\345\210\231/\351\235\242\345\220\221\345\257\271\350\261\241\350\256\276\350\256\241\345\216\237\345\210\231.pdf" new file mode 100644 index 0000000000..34db87aafa Binary files /dev/null and "b/note/CPP-Design-Patterns-master/02_\350\257\276\344\273\266_\351\235\242\345\220\221\345\257\271\350\261\241\350\256\276\350\256\241\345\216\237\345\210\231/\351\235\242\345\220\221\345\257\271\350\261\241\350\256\276\350\256\241\345\216\237\345\210\231.pdf" differ diff --git "a/note/CPP-Design-Patterns-master/03_\350\257\276\344\273\266_Template Method\346\250\241\346\235\277\346\226\271\346\263\225/Template Method_\346\250\241\346\235\277\346\226\271\346\263\225.pdf" "b/note/CPP-Design-Patterns-master/03_\350\257\276\344\273\266_Template Method\346\250\241\346\235\277\346\226\271\346\263\225/Template Method_\346\250\241\346\235\277\346\226\271\346\263\225.pdf" new file mode 100644 index 0000000000..ab2c433ff0 Binary files /dev/null and "b/note/CPP-Design-Patterns-master/03_\350\257\276\344\273\266_Template Method\346\250\241\346\235\277\346\226\271\346\263\225/Template Method_\346\250\241\346\235\277\346\226\271\346\263\225.pdf" differ diff --git "a/note/CPP-Design-Patterns-master/03_\350\257\276\344\273\266_Template Method\346\250\241\346\235\277\346\226\271\346\263\225/\344\273\243\347\240\201/template1_app.cpp" "b/note/CPP-Design-Patterns-master/03_\350\257\276\344\273\266_Template Method\346\250\241\346\235\277\346\226\271\346\263\225/\344\273\243\347\240\201/template1_app.cpp" new file mode 100644 index 0000000000..ffa8b8465b --- /dev/null +++ "b/note/CPP-Design-Patterns-master/03_\350\257\276\344\273\266_Template Method\346\250\241\346\235\277\346\226\271\346\263\225/\344\273\243\347\240\201/template1_app.cpp" @@ -0,0 +1,30 @@ +//应用程序开发人员 +class Application{ +public: + bool Step2(){ + //... + } + + void Step4(){ + //... + } +}; + +int main() +{ + Library lib=Library(); + Application app=Application(); + + lib.Step1(); + + if (app.Step2()){ + lib.Step3(); + } + + for (int i = 0; i < 4; i++){ + app.Step4(); + } + + lib.Step5(); + +} diff --git "a/note/CPP-Design-Patterns-master/03_\350\257\276\344\273\266_Template Method\346\250\241\346\235\277\346\226\271\346\263\225/\344\273\243\347\240\201/template1_lib.cpp" "b/note/CPP-Design-Patterns-master/03_\350\257\276\344\273\266_Template Method\346\250\241\346\235\277\346\226\271\346\263\225/\344\273\243\347\240\201/template1_lib.cpp" new file mode 100644 index 0000000000..5260e21f14 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/03_\350\257\276\344\273\266_Template Method\346\250\241\346\235\277\346\226\271\346\263\225/\344\273\243\347\240\201/template1_lib.cpp" @@ -0,0 +1,16 @@ +//程序库开发人员 +class Library{ + +public: + void Step1(){ + //... + } + + void Step3(){ + //... + } + + void Step5(){ + //... + } +}; \ No newline at end of file diff --git "a/note/CPP-Design-Patterns-master/03_\350\257\276\344\273\266_Template Method\346\250\241\346\235\277\346\226\271\346\263\225/\344\273\243\347\240\201/template2_app.cpp" "b/note/CPP-Design-Patterns-master/03_\350\257\276\344\273\266_Template Method\346\250\241\346\235\277\346\226\271\346\263\225/\344\273\243\347\240\201/template2_app.cpp" new file mode 100644 index 0000000000..94da2302c3 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/03_\350\257\276\344\273\266_Template Method\346\250\241\346\235\277\346\226\271\346\263\225/\344\273\243\347\240\201/template2_app.cpp" @@ -0,0 +1,27 @@ +//应用程序开发人员 +class Application : public Library { +protected: + virtual bool Step2(){ + //... 子类重写实现 + } + + virtual void Step4() { + //... 子类重写实现 + } +}; + + + + +int main() + { + Library* lib=new Application(); + lib->Run(); + + delete lib; + } +} + + + + diff --git "a/note/CPP-Design-Patterns-master/03_\350\257\276\344\273\266_Template Method\346\250\241\346\235\277\346\226\271\346\263\225/\344\273\243\347\240\201/template2_lib.cpp" "b/note/CPP-Design-Patterns-master/03_\350\257\276\344\273\266_Template Method\346\250\241\346\235\277\346\226\271\346\263\225/\344\273\243\347\240\201/template2_lib.cpp" new file mode 100644 index 0000000000..3933f6ab7d --- /dev/null +++ "b/note/CPP-Design-Patterns-master/03_\350\257\276\344\273\266_Template Method\346\250\241\346\235\277\346\226\271\346\263\225/\344\273\243\347\240\201/template2_lib.cpp" @@ -0,0 +1,36 @@ +//程序库开发人员 +class Library{ +public: + //稳定 template method + void Run(){ + + Step1(); + + if (Step2()) { //支持变化 ==> 虚函数的多态调用 + Step3(); + } + + for (int i = 0; i < 4; i++){ + Step4(); //支持变化 ==> 虚函数的多态调用 + } + + Step5(); + + } + virtual ~Library(){ } + +protected: + + void Step1() { //稳定 + //..... + } + void Step3() {//稳定 + //..... + } + void Step5() { //稳定 + //..... + } + + virtual bool Step2() = 0;//变化 + virtual void Step4() =0; //变化 +}; \ No newline at end of file diff --git "a/note/CPP-Design-Patterns-master/04_\350\257\276\344\273\266_\347\255\226\347\225\245\346\250\241\345\274\217/\344\273\243\347\240\201/strategy1.cpp" "b/note/CPP-Design-Patterns-master/04_\350\257\276\344\273\266_\347\255\226\347\225\245\346\250\241\345\274\217/\344\273\243\347\240\201/strategy1.cpp" new file mode 100644 index 0000000000..ac3077d08c --- /dev/null +++ "b/note/CPP-Design-Patterns-master/04_\350\257\276\344\273\266_\347\255\226\347\225\245\346\250\241\345\274\217/\344\273\243\347\240\201/strategy1.cpp" @@ -0,0 +1,30 @@ +enum TaxBase { + CN_Tax, + US_Tax, + DE_Tax, + FR_Tax //更改 +}; + +class SalesOrder{ + TaxBase tax; +public: + double CalculateTax(){ + //... + + if (tax == CN_Tax){ + //CN*********** + } + else if (tax == US_Tax){ + //US*********** + } + else if (tax == DE_Tax){ + //DE*********** + } + else if (tax == FR_Tax){ //更改 + //... + } + + //.... + } + +}; diff --git "a/note/CPP-Design-Patterns-master/04_\350\257\276\344\273\266_\347\255\226\347\225\245\346\250\241\345\274\217/\344\273\243\347\240\201/strategy2.cpp" "b/note/CPP-Design-Patterns-master/04_\350\257\276\344\273\266_\347\255\226\347\225\245\346\250\241\345\274\217/\344\273\243\347\240\201/strategy2.cpp" new file mode 100644 index 0000000000..39280e7f93 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/04_\350\257\276\344\273\266_\347\255\226\347\225\245\346\250\241\345\274\217/\344\273\243\347\240\201/strategy2.cpp" @@ -0,0 +1,75 @@ + +class TaxStrategy{ +public: + virtual double Calculate(const Context& context)=0; + virtual ~TaxStrategy(){} +}; + + +class CNTax : public TaxStrategy{ +public: + virtual double Calculate(const Context& context){ + //*********** + } +}; + +class USTax : public TaxStrategy{ +public: + virtual double Calculate(const Context& context){ + //*********** + } +}; + +class DETax : public TaxStrategy{ +public: + virtual double Calculate(const Context& context){ + //*********** + } +}; + + + +//扩展 +//********************************* +class FRTax : public TaxStrategy{ +public: + virtual double Calculate(const Context& context){ + //......... + } +}; + +//Ӣ�� +class EN_Tax:public TaxStrategy{ //�仯 +public: + virtual double Calculate(const Context& context){ + //......... + } +} + +//���� +class SalesOrder{ +private: + TaxStrategy* strategy;//����ָ��ָ������ + +public: + SalesOrder(StrategyFactory* strategyFactory){ + this->strategy = strategyFactory->NewStrategy();// ����һ�� �������� + } + ~SalesOrder(){ + delete this->strategy; + } + + public double CalculateTax(){ + //... + Context context(); + + double val = + strategy->Calculate(context); //��̬���� + //... + } + +}; + + + + diff --git "a/note/CPP-Design-Patterns-master/04_\350\257\276\344\273\266_\347\255\226\347\225\245\346\250\241\345\274\217/\347\255\226\347\225\245\346\250\241\345\274\217.pdf" "b/note/CPP-Design-Patterns-master/04_\350\257\276\344\273\266_\347\255\226\347\225\245\346\250\241\345\274\217/\347\255\226\347\225\245\346\250\241\345\274\217.pdf" new file mode 100644 index 0000000000..383dde89cf Binary files /dev/null and "b/note/CPP-Design-Patterns-master/04_\350\257\276\344\273\266_\347\255\226\347\225\245\346\250\241\345\274\217/\347\255\226\347\225\245\346\250\241\345\274\217.pdf" differ diff --git "a/note/CPP-Design-Patterns-master/05_\350\257\276\344\273\266_\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217/\344\273\243\347\240\201/FileSplitter1.cpp" "b/note/CPP-Design-Patterns-master/05_\350\257\276\344\273\266_\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217/\344\273\243\347\240\201/FileSplitter1.cpp" new file mode 100644 index 0000000000..73913eac1f --- /dev/null +++ "b/note/CPP-Design-Patterns-master/05_\350\257\276\344\273\266_\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217/\344\273\243\347\240\201/FileSplitter1.cpp" @@ -0,0 +1,36 @@ +class Ipgross{ + virtual void dogross(float value)=0; + ~Ipgross(){ + } +}; + +class FileSplitter +{ + string m_filePath; + int m_fileNumber; + ProgressBar* m_progressBar;//����֪ͨ�ؼ� + +public: + FileSplitter(const string& filePath, int fileNumber, ProgressBar* progressBar) : + m_filePath(filePath), + m_fileNumber(fileNumber), + m_progressBar(progressBar){ + + } + + void split(){ + + //1.读取大文件 + + //2.分批次向小文件中写入 + for (int i = 0; i < m_fileNumber; i++){ + //... + float progressValue = m_fileNumber; + progressValue = (i + 1) / progressValue; + m_progressBar->dogross(progressValue); + } + + } + + +}; diff --git "a/note/CPP-Design-Patterns-master/05_\350\257\276\344\273\266_\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217/\344\273\243\347\240\201/FileSplitter2.cpp" "b/note/CPP-Design-Patterns-master/05_\350\257\276\344\273\266_\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217/\344\273\243\347\240\201/FileSplitter2.cpp" new file mode 100644 index 0000000000..f657db634c --- /dev/null +++ "b/note/CPP-Design-Patterns-master/05_\350\257\276\344\273\266_\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217/\344\273\243\347\240\201/FileSplitter2.cpp" @@ -0,0 +1,58 @@ +class IProgress{ +public: + virtual void DoProgress(float value)=0; + virtual ~IProgress(){} +}; + + +class FileSplitter +{ + string m_filePath; + int m_fileNumber; + + List m_iprogressList; // ����֪ͨ���ƣ�֧�ֶ���۲��� + +public: + FileSplitter(const string& filePath, int fileNumber) : + m_filePath(filePath), + m_fileNumber(fileNumber){ + + } + + + void split(){ + + //1.��ȡ���ļ� + + //2.��������С�ļ���д�� + for (int i = 0; i < m_fileNumber; i++){ + //... + + float progressValue = m_fileNumber; + progressValue = (i + 1) / progressValue; + onProgress(progressValue);//发送通知 + } + + } + + + void addIProgress(IProgress* iprogress){ + m_iprogressList.push_back(iprogress); + } + + void removeIProgress(IProgress* iprogress){ + m_iprogressList.remove(iprogress); + } + + +protected: + virtual void onProgress(float value){ + + List::iterator itor=m_iprogressList.begin(); + + while (itor != m_iprogressList.end() ) + (*itor)->DoProgress(value); //更新进度条 + itor++; + } + } +}; diff --git "a/note/CPP-Design-Patterns-master/05_\350\257\276\344\273\266_\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217/\344\273\243\347\240\201/MainForm1.cpp" "b/note/CPP-Design-Patterns-master/05_\350\257\276\344\273\266_\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217/\344\273\243\347\240\201/MainForm1.cpp" new file mode 100644 index 0000000000..0489fb8ad6 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/05_\350\257\276\344\273\266_\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217/\344\273\243\347\240\201/MainForm1.cpp" @@ -0,0 +1,25 @@ +class MainForm : public Form,public Ipgross +{ + TextBox* txtFilePath; + TextBox* txtFileNumber; + ProgressBar* m_progressBar; + +public: + void Button1_Click(){ + + string filePath = txtFilePath->getText(); + int number = atoi(txtFileNumber->getText().c_str()); + + FileSplitter splitter(filePath, number, this); + + splitter.split(); + + } + + virtual void dogross(float value){ + m_progressBar->setvalue(value); + } + + +}; + diff --git "a/note/CPP-Design-Patterns-master/05_\350\257\276\344\273\266_\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217/\344\273\243\347\240\201/MainForm2.cpp" "b/note/CPP-Design-Patterns-master/05_\350\257\276\344\273\266_\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217/\344\273\243\347\240\201/MainForm2.cpp" new file mode 100644 index 0000000000..1130303236 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/05_\350\257\276\344\273\266_\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217/\344\273\243\347\240\201/MainForm2.cpp" @@ -0,0 +1,38 @@ +class MainForm : public Form, public IProgress +{ + TextBox* txtFilePath; + TextBox* txtFileNumber; + + ProgressBar* progressBar; + +public: + void Button1_Click(){ + + string filePath = txtFilePath->getText(); + int number = atoi(txtFileNumber->getText().c_str()); + + ConsoleNotifier cn; + + FileSplitter splitter(filePath, number); + + splitter.addIProgress(this); //订阅通知 + splitter.addIProgress(&cn); //订阅通知 + + splitter.split(); + + splitter.removeIProgress(this); + + } + + virtual void DoProgress(float value){ + progressBar->setValue(value); + } +}; + +class ConsoleNotifier : public IProgress { +public: + virtual void DoProgress(float value){ + cout << "."; + } +}; + diff --git "a/note/CPP-Design-Patterns-master/05_\350\257\276\344\273\266_\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217/\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.pdf" "b/note/CPP-Design-Patterns-master/05_\350\257\276\344\273\266_\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217/\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.pdf" new file mode 100644 index 0000000000..eb7e09a84c Binary files /dev/null and "b/note/CPP-Design-Patterns-master/05_\350\257\276\344\273\266_\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217/\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.pdf" differ diff --git "a/note/CPP-Design-Patterns-master/06_\350\257\276\344\273\266_\350\243\205\351\245\260\346\250\241\345\274\217/\344\273\243\347\240\201/decorator1.cpp" "b/note/CPP-Design-Patterns-master/06_\350\257\276\344\273\266_\350\243\205\351\245\260\346\250\241\345\274\217/\344\273\243\347\240\201/decorator1.cpp" new file mode 100644 index 0000000000..d8a2b8b3f4 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/06_\350\257\276\344\273\266_\350\243\205\351\245\260\346\250\241\345\274\217/\344\273\243\347\240\201/decorator1.cpp" @@ -0,0 +1,163 @@ +//业务操作 +class Stream{ +public: + virtual char Read(int number)=0; + virtual void Seek(int position)=0; + virtual void Write(char data)=0; + + virtual ~Stream(){} +}; + +//主体类 +class FileStream: public Stream{ +public: + virtual char Read(int number){ + //读文件流 + } + virtual void Seek(int position){ + //定位文件流 + } + virtual void Write(char data){ + //写文件流 + } + +}; + +class NetworkStream :public Stream{ +public: + virtual char Read(int number){ + //读网络流 + } + virtual void Seek(int position){ + //定位网络流 + } + virtual void Write(char data){ + //写网络流 + } + +}; + +class MemoryStream :public Stream{ +public: + virtual char Read(int number){ + //读内存流 + } + virtual void Seek(int position){ + //定位内存流 + } + virtual void Write(char data){ + //写内存流 + } + +}; + +//扩展操作 +class CryptoFileStream :public FileStream{ +public: + virtual char Read(int number){ + + //额外的加密操作... + FileStream::Read(number);//读文件流 + + } + virtual void Seek(int position){ + //额外的加密操作... + FileStream::Seek(position);//定位文件流 + //额外的加密操作... + } + virtual void Write(byte data){ + //额外的加密操作... + FileStream::Write(data);//写文件流 + //额外的加密操作... + } +}; + +class CryptoNetworkStream : :public NetworkStream{ +public: + virtual char Read(int number){ + + //额外的加密操作... + NetworkStream::Read(number);//读网络流 + } + virtual void Seek(int position){ + //额外的加密操作... + NetworkStream::Seek(position);//定位网络流 + //额外的加密操作... + } + virtual void Write(byte data){ + //额外的加密操作... + NetworkStream::Write(data);//写网络流 + //额外的加密操作... + } +}; + +class CryptoMemoryStream : public MemoryStream{ +public: + virtual char Read(int number){ + + //额外的加密操作... + MemoryStream::Read(number);//读内存流 + } + virtual void Seek(int position){ + //额外的加密操作... + MemoryStream::Seek(position);//定位内存流 + //额外的加密操作... + } + virtual void Write(byte data){ + //额外的加密操作... + MemoryStream::Write(data);//写内存流 + //额外的加密操作... + } +}; + +class BufferedFileStream : public FileStream{ + //... +}; + +class BufferedNetworkStream : public NetworkStream{ + //... +}; + +class BufferedMemoryStream : public MemoryStream{ + //... +} + + + + +class CryptoBufferedFileStream :public FileStream{ +public: + virtual char Read(int number){ + + //额外的加密操作... + //额外的缓冲操作... + FileStream::Read(number);//读文件流 + } + virtual void Seek(int position){ + //额外的加密操作... + //额外的缓冲操作... + FileStream::Seek(position);//定位文件流 + //额外的加密操作... + //额外的缓冲操作... + } + virtual void Write(byte data){ + //额外的加密操作... + //额外的缓冲操作... + FileStream::Write(data);//写文件流 + //额外的加密操作... + //额外的缓冲操作... + } +}; + + + +void Process(){ + + //编译时装配 + CryptoFileStream *fs1 = new CryptoFileStream(); + + BufferedFileStream *fs2 = new BufferedFileStream(); + + CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream(); + +} \ No newline at end of file diff --git "a/note/CPP-Design-Patterns-master/06_\350\257\276\344\273\266_\350\243\205\351\245\260\346\250\241\345\274\217/\344\273\243\347\240\201/decorator2.cpp" "b/note/CPP-Design-Patterns-master/06_\350\257\276\344\273\266_\350\243\205\351\245\260\346\250\241\345\274\217/\344\273\243\347\240\201/decorator2.cpp" new file mode 100644 index 0000000000..0fe30b1fd6 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/06_\350\257\276\344\273\266_\350\243\205\351\245\260\346\250\241\345\274\217/\344\273\243\347\240\201/decorator2.cpp" @@ -0,0 +1,114 @@ +//业务操作 +class Stream{ + +public: + virtual char Read(int number)=0; + virtual void Seek(int position)=0; + virtual void Write(char data)=0; + + virtual ~Stream(){} +}; + +//主体类 +class FileStream: public Stream{ +public: + virtual char Read(int number){ + //读文件流 + } + virtual void Seek(int position){ + //定位文件流 + } + virtual void Write(char data){ + //写文件流 + } + +}; + +class NetworkStream :public Stream{ +public: + virtual char Read(int number){ + //读网络流 + } + virtual void Seek(int position){ + //定位网络流 + } + virtual void Write(char data){ + //写网络流 + } + +}; + +class MemoryStream :public Stream{ +public: + virtual char Read(int number){ + //读内存流 + } + virtual void Seek(int position){ + //定位内存流 + } + virtual void Write(char data){ + //写内存流 + } + +}; + +//扩展操作 + + +class CryptoStream: public Stream { + + Stream* stream;//... + +public: + CryptoStream(Stream* stm):stream(stm){ + + } + + + virtual char Read(int number){ + + //额外的加密操作... + stream->Read(number);//读文件流 + } + virtual void Seek(int position){ + //额外的加密操作... + stream::Seek(position);//定位文件流 + //额外的加密操作... + } + virtual void Write(byte data){ + //额外的加密操作... + stream::Write(data);//写文件流 + //额外的加密操作... + } +}; + + + +class BufferedStream : public Stream{ + + Stream* stream;//... + +public: + BufferedStream(Stream* stm):stream(stm){ + + } + //... +}; + + + + + +void Process(){ + + //运行时装配 + FileStream* s1=new FileStream(); + CryptoStream* s2=new CryptoStream(s1); + + BufferedStream* s3=new BufferedStream(s1); + + BufferedStream* s4=new BufferedStream(s2); + + + +} \ No newline at end of file diff --git "a/note/CPP-Design-Patterns-master/06_\350\257\276\344\273\266_\350\243\205\351\245\260\346\250\241\345\274\217/\344\273\243\347\240\201/decorator3.cpp" "b/note/CPP-Design-Patterns-master/06_\350\257\276\344\273\266_\350\243\205\351\245\260\346\250\241\345\274\217/\344\273\243\347\240\201/decorator3.cpp" new file mode 100644 index 0000000000..15a3006c44 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/06_\350\257\276\344\273\266_\350\243\205\351\245\260\346\250\241\345\274\217/\344\273\243\347\240\201/decorator3.cpp" @@ -0,0 +1,122 @@ +//业务操作 +class Stream{ + +public: + virtual char Read(int number)=0; + virtual void Seek(int position)=0; + virtual void Write(char data)=0; + + virtual ~Stream(){} +}; + +//主体类 +class FileStream: public Stream{ +public: + virtual char Read(int number){ + //读文件流 + } + virtual void Seek(int position){ + //定位文件流 + } + virtual void Write(char data){ + //写文件流 + } + +}; + +class NetworkStream :public Stream{ +public: + virtual char Read(int number){ + //读网络流 + } + virtual void Seek(int position){ + //定位网络流 + } + virtual void Write(char data){ + //写网络流 + } + +}; + +class MemoryStream :public Stream{ +public: + virtual char Read(int number){ + //读内存流 + } + virtual void Seek(int position){ + //定位内存流 + } + virtual void Write(char data){ + //写内存流 + } + +}; + +//扩展操作 + +DecoratorStream: public Stream{ +protected: + Stream* stream;//... + + DecoratorStream(Stream * stm):stream(stm){ + + } + +}; + +class CryptoStream: public DecoratorStream { + + +public: + CryptoStream(Stream* stm):DecoratorStream(stm){ + + } + + + virtual char Read(int number){ + + //额外的加密操作... + stream->Read(number);//读文件流 + } + virtual void Seek(int position){ + //额外的加密操作... + stream::Seek(position);//定位文件流 + //额外的加密操作... + } + virtual void Write(byte data){ + //额外的加密操作... + stream::Write(data);//写文件流 + //额外的加密操作... + } +}; + + + +class BufferedStream : public DecoratorStream{ + + Stream* stream;//... + +public: + BufferedStream(Stream* stm):DecoratorStream(stm){ + + } + //... +}; + + + + +void Process(){ + + //运行时装配 + FileStream* s1=new FileStream(); + + CryptoStream* s2=new CryptoStream(s1); + + BufferedStream* s3=new BufferedStream(s1); + + BufferedStream* s4=new BufferedStream(s2); + + + +} \ No newline at end of file diff --git "a/note/CPP-Design-Patterns-master/06_\350\257\276\344\273\266_\350\243\205\351\245\260\346\250\241\345\274\217/\350\243\205\351\245\260\346\250\241\345\274\217.pdf" "b/note/CPP-Design-Patterns-master/06_\350\257\276\344\273\266_\350\243\205\351\245\260\346\250\241\345\274\217/\350\243\205\351\245\260\346\250\241\345\274\217.pdf" new file mode 100644 index 0000000000..667495368e Binary files /dev/null and "b/note/CPP-Design-Patterns-master/06_\350\257\276\344\273\266_\350\243\205\351\245\260\346\250\241\345\274\217/\350\243\205\351\245\260\346\250\241\345\274\217.pdf" differ diff --git "a/note/CPP-Design-Patterns-master/07_\350\257\276\344\273\266_\346\241\245\346\250\241\345\274\217/\344\273\243\347\240\201/bridge1.cpp" "b/note/CPP-Design-Patterns-master/07_\350\257\276\344\273\266_\346\241\245\346\250\241\345\274\217/\344\273\243\347\240\201/bridge1.cpp" new file mode 100644 index 0000000000..392e8f8882 Binary files /dev/null and "b/note/CPP-Design-Patterns-master/07_\350\257\276\344\273\266_\346\241\245\346\250\241\345\274\217/\344\273\243\347\240\201/bridge1.cpp" differ diff --git "a/note/CPP-Design-Patterns-master/07_\350\257\276\344\273\266_\346\241\245\346\250\241\345\274\217/\344\273\243\347\240\201/bridge2.cpp" "b/note/CPP-Design-Patterns-master/07_\350\257\276\344\273\266_\346\241\245\346\250\241\345\274\217/\344\273\243\347\240\201/bridge2.cpp" new file mode 100644 index 0000000000..051ac921ac Binary files /dev/null and "b/note/CPP-Design-Patterns-master/07_\350\257\276\344\273\266_\346\241\245\346\250\241\345\274\217/\344\273\243\347\240\201/bridge2.cpp" differ diff --git "a/note/CPP-Design-Patterns-master/07_\350\257\276\344\273\266_\346\241\245\346\250\241\345\274\217/\346\241\245\346\250\241\345\274\217.pdf" "b/note/CPP-Design-Patterns-master/07_\350\257\276\344\273\266_\346\241\245\346\250\241\345\274\217/\346\241\245\346\250\241\345\274\217.pdf" new file mode 100644 index 0000000000..8f726b4c64 Binary files /dev/null and "b/note/CPP-Design-Patterns-master/07_\350\257\276\344\273\266_\346\241\245\346\250\241\345\274\217/\346\241\245\346\250\241\345\274\217.pdf" differ diff --git "a/note/CPP-Design-Patterns-master/08_\350\257\276\344\273\266_Factory Method\345\267\245\345\216\202\346\226\271\346\263\225/Factory Method_\345\267\245\345\216\202\346\226\271\346\263\225.pdf" "b/note/CPP-Design-Patterns-master/08_\350\257\276\344\273\266_Factory Method\345\267\245\345\216\202\346\226\271\346\263\225/Factory Method_\345\267\245\345\216\202\346\226\271\346\263\225.pdf" new file mode 100644 index 0000000000..c0402af4d9 Binary files /dev/null and "b/note/CPP-Design-Patterns-master/08_\350\257\276\344\273\266_Factory Method\345\267\245\345\216\202\346\226\271\346\263\225/Factory Method_\345\267\245\345\216\202\346\226\271\346\263\225.pdf" differ diff --git "a/note/CPP-Design-Patterns-master/08_\350\257\276\344\273\266_Factory Method\345\267\245\345\216\202\346\226\271\346\263\225/\344\273\243\347\240\201/FileSplitter1.cpp" "b/note/CPP-Design-Patterns-master/08_\350\257\276\344\273\266_Factory Method\345\267\245\345\216\202\346\226\271\346\263\225/\344\273\243\347\240\201/FileSplitter1.cpp" new file mode 100644 index 0000000000..910cd922b0 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/08_\350\257\276\344\273\266_Factory Method\345\267\245\345\216\202\346\226\271\346\263\225/\344\273\243\347\240\201/FileSplitter1.cpp" @@ -0,0 +1,22 @@ +class ISplitter{ +public: + virtual void split()=0; + virtual ~ISplitter(){} +}; + +class BinarySplitter : public ISplitter{ + +}; + +class TxtSplitter: public ISplitter{ + +}; + +class PictureSplitter: public ISplitter{ + +}; + +class VideoSplitter: public ISplitter{ + +}; + diff --git "a/note/CPP-Design-Patterns-master/08_\350\257\276\344\273\266_Factory Method\345\267\245\345\216\202\346\226\271\346\263\225/\344\273\243\347\240\201/FileSplitter2.cpp" "b/note/CPP-Design-Patterns-master/08_\350\257\276\344\273\266_Factory Method\345\267\245\345\216\202\346\226\271\346\263\225/\344\273\243\347\240\201/FileSplitter2.cpp" new file mode 100644 index 0000000000..22f2d192f8 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/08_\350\257\276\344\273\266_Factory Method\345\267\245\345\216\202\346\226\271\346\263\225/\344\273\243\347\240\201/FileSplitter2.cpp" @@ -0,0 +1,48 @@ + + +//具体类 +class BinarySplitter : public ISplitter{ + +}; + +class TxtSplitter: public ISplitter{ + +}; + +class PictureSplitter: public ISplitter{ + +}; + +class VideoSplitter: public ISplitter{ + +}; + +//具体工厂 +class BinarySplitterFactory: public SplitterFactory{ +public: + virtual ISplitter* CreateSplitter(){ + return new BinarySplitter(); + } +}; + +class TxtSplitterFactory: public SplitterFactory{ +public: + virtual ISplitter* CreateSplitter(){ + return new TxtSplitter(); + } +}; + +class PictureSplitterFactory: public SplitterFactory{ +public: + virtual ISplitter* CreateSplitter(){ + return new PictureSplitter(); + } +}; + +class VideoSplitterFactory: public SplitterFactory{ +public: + virtual ISplitter* CreateSplitter(){ + return new VideoSplitter(); + } +}; + diff --git "a/note/CPP-Design-Patterns-master/08_\350\257\276\344\273\266_Factory Method\345\267\245\345\216\202\346\226\271\346\263\225/\344\273\243\347\240\201/ISplitterFactory.cpp" "b/note/CPP-Design-Patterns-master/08_\350\257\276\344\273\266_Factory Method\345\267\245\345\216\202\346\226\271\346\263\225/\344\273\243\347\240\201/ISplitterFactory.cpp" new file mode 100644 index 0000000000..e132c9fd15 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/08_\350\257\276\344\273\266_Factory Method\345\267\245\345\216\202\346\226\271\346\263\225/\344\273\243\347\240\201/ISplitterFactory.cpp" @@ -0,0 +1,18 @@ + +//抽象类 +class ISplitter{ +public: + virtual void split()=0; + virtual ~ISplitter(){} +}; + + +//工厂基类 +class SplitterFactory{ +public: + virtual ISplitter* CreateSplitter()=0; + virtual ~SplitterFactory(){} +}; + + + diff --git "a/note/CPP-Design-Patterns-master/08_\350\257\276\344\273\266_Factory Method\345\267\245\345\216\202\346\226\271\346\263\225/\344\273\243\347\240\201/MainForm1.cpp" "b/note/CPP-Design-Patterns-master/08_\350\257\276\344\273\266_Factory Method\345\267\245\345\216\202\346\226\271\346\263\225/\344\273\243\347\240\201/MainForm1.cpp" new file mode 100644 index 0000000000..5d2e9adeb9 Binary files /dev/null and "b/note/CPP-Design-Patterns-master/08_\350\257\276\344\273\266_Factory Method\345\267\245\345\216\202\346\226\271\346\263\225/\344\273\243\347\240\201/MainForm1.cpp" differ diff --git "a/note/CPP-Design-Patterns-master/08_\350\257\276\344\273\266_Factory Method\345\267\245\345\216\202\346\226\271\346\263\225/\344\273\243\347\240\201/MainForm2.cpp" "b/note/CPP-Design-Patterns-master/08_\350\257\276\344\273\266_Factory Method\345\267\245\345\216\202\346\226\271\346\263\225/\344\273\243\347\240\201/MainForm2.cpp" new file mode 100644 index 0000000000..552f810005 Binary files /dev/null and "b/note/CPP-Design-Patterns-master/08_\350\257\276\344\273\266_Factory Method\345\267\245\345\216\202\346\226\271\346\263\225/\344\273\243\347\240\201/MainForm2.cpp" differ diff --git "a/note/CPP-Design-Patterns-master/09_\350\257\276\344\273\266_Abstract Factory\346\212\275\350\261\241\345\267\245\345\216\202/Abstract Factory_\346\212\275\350\261\241\345\267\245\345\216\202.pdf" "b/note/CPP-Design-Patterns-master/09_\350\257\276\344\273\266_Abstract Factory\346\212\275\350\261\241\345\267\245\345\216\202/Abstract Factory_\346\212\275\350\261\241\345\267\245\345\216\202.pdf" new file mode 100644 index 0000000000..ffc20616de Binary files /dev/null and "b/note/CPP-Design-Patterns-master/09_\350\257\276\344\273\266_Abstract Factory\346\212\275\350\261\241\345\267\245\345\216\202/Abstract Factory_\346\212\275\350\261\241\345\267\245\345\216\202.pdf" differ diff --git "a/note/CPP-Design-Patterns-master/09_\350\257\276\344\273\266_Abstract Factory\346\212\275\350\261\241\345\267\245\345\216\202/\344\273\243\347\240\201/EmployeeDAO1.cpp" "b/note/CPP-Design-Patterns-master/09_\350\257\276\344\273\266_Abstract Factory\346\212\275\350\261\241\345\267\245\345\216\202/\344\273\243\347\240\201/EmployeeDAO1.cpp" new file mode 100644 index 0000000000..1685ed3360 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/09_\350\257\276\344\273\266_Abstract Factory\346\212\275\350\261\241\345\267\245\345\216\202/\344\273\243\347\240\201/EmployeeDAO1.cpp" @@ -0,0 +1,21 @@ + +class EmployeeDAO{ + +public: + vector GetEmployees(){ + SqlConnection* connection = + new SqlConnection(); + connection->ConnectionString = "..."; + + SqlCommand* command = + new SqlCommand(); + command->CommandText="..."; + command->SetConnection(connection); + + SqlDataReader* reader = command->ExecuteReader(); + while (reader->Read()){ + + } + + } +}; diff --git "a/note/CPP-Design-Patterns-master/09_\350\257\276\344\273\266_Abstract Factory\346\212\275\350\261\241\345\267\245\345\216\202/\344\273\243\347\240\201/EmployeeDAO2.cpp" "b/note/CPP-Design-Patterns-master/09_\350\257\276\344\273\266_Abstract Factory\346\212\275\350\261\241\345\267\245\345\216\202/\344\273\243\347\240\201/EmployeeDAO2.cpp" new file mode 100644 index 0000000000..4020c8e038 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/09_\350\257\276\344\273\266_Abstract Factory\346\212\275\350\261\241\345\267\245\345\216\202/\344\273\243\347\240\201/EmployeeDAO2.cpp" @@ -0,0 +1,92 @@ + +//数据库访问有关的基类 +class IDBConnection{ + +}; +class IDBConnectionFactory{ +public: + virtual IDBConnection* CreateDBConnection()=0; +}; + + +class IDBCommand{ + +}; +class IDBCommandFactory{ +public: + virtual IDBCommand* CreateDBCommand()=0; +}; + + +class IDataReader{ + +}; +class IDataReaderFactory{ +public: + virtual IDataReader* CreateDataReader()=0; +}; + + +//支持SQL Server +class SqlConnection: public IDBConnection{ + +}; +class SqlConnectionFactory:public IDBConnectionFactory{ + +}; + + +class SqlCommand: public IDBCommand{ + +}; +class SqlCommandFactory:public IDBCommandFactory{ + +}; + + +class SqlDataReader: public IDataReader{ + +}; +class SqlDataReaderFactory:public IDataReaderFactory{ + +}; + +//支持Oracle +class OracleConnection: public IDBConnection{ + +}; + +class OracleCommand: public IDBCommand{ + +}; + +class OracleDataReader: public IDataReader{ + +}; + + + +class EmployeeDAO{ + IDBConnectionFactory* dbConnectionFactory; + IDBCommandFactory* dbCommandFactory; + IDataReaderFactory* dataReaderFactory; + + +public: + vector GetEmployees(){ + IDBConnection* connection = + dbConnectionFactory->CreateDBConnection(); + connection->ConnectionString("..."); + + IDBCommand* command = + dbCommandFactory->CreateDBCommand(); + command->CommandText("..."); + command->SetConnection(connection); //关联性 + + IDBDataReader* reader = command->ExecuteReader(); //关联性 + while (reader->Read()){ + + } + + } +}; diff --git "a/note/CPP-Design-Patterns-master/09_\350\257\276\344\273\266_Abstract Factory\346\212\275\350\261\241\345\267\245\345\216\202/\344\273\243\347\240\201/EmployeeDAO3.cpp" "b/note/CPP-Design-Patterns-master/09_\350\257\276\344\273\266_Abstract Factory\346\212\275\350\261\241\345\267\245\345\216\202/\344\273\243\347\240\201/EmployeeDAO3.cpp" new file mode 100644 index 0000000000..319a4eb136 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/09_\350\257\276\344\273\266_Abstract Factory\346\212\275\350\261\241\345\267\245\345\216\202/\344\273\243\347\240\201/EmployeeDAO3.cpp" @@ -0,0 +1,80 @@ + +//数据库访问有关的基类 +class IDBConnection{ + +}; + +class IDBCommand{ + +}; + +class IDataReader{ + +}; + + +class IDBFactory{ +public: + virtual IDBConnection* CreateDBConnection()=0; + virtual IDBCommand* CreateDBCommand()=0; + virtual IDataReader* CreateDataReader()=0; + +}; + + +//支持SQL Server +class SqlConnection: public IDBConnection{ + +}; +class SqlCommand: public IDBCommand{ + +}; +class SqlDataReader: public IDataReader{ + +}; + + +class SqlDBFactory:public IDBFactory{ +public: + virtual IDBConnection* CreateDBConnection()=0; + virtual IDBCommand* CreateDBCommand()=0; + virtual IDataReader* CreateDataReader()=0; + +}; + +//支持Oracle +class OracleConnection: public IDBConnection{ + +}; + +class OracleCommand: public IDBCommand{ + +}; + +class OracleDataReader: public IDataReader{ + +}; + + + +class EmployeeDAO{ + IDBFactory* dbFactory; + +public: + vector GetEmployees(){ + IDBConnection* connection = + dbFactory->CreateDBConnection(); + connection->ConnectionString("..."); + + IDBCommand* command = + dbFactory->CreateDBCommand(); + command->CommandText("..."); + command->SetConnection(connection); //关联性 + + IDBDataReader* reader = command->ExecuteReader(); //关联性 + while (reader->Read()){ + + } + + } +}; diff --git "a/note/CPP-Design-Patterns-master/10_\350\257\276\344\273\266_prototype/\344\273\243\347\240\201/Client.cpp" "b/note/CPP-Design-Patterns-master/10_\350\257\276\344\273\266_prototype/\344\273\243\347\240\201/Client.cpp" new file mode 100644 index 0000000000..83ecf747b3 Binary files /dev/null and "b/note/CPP-Design-Patterns-master/10_\350\257\276\344\273\266_prototype/\344\273\243\347\240\201/Client.cpp" differ diff --git "a/note/CPP-Design-Patterns-master/10_\350\257\276\344\273\266_prototype/\344\273\243\347\240\201/ConcretePrototype.cpp" "b/note/CPP-Design-Patterns-master/10_\350\257\276\344\273\266_prototype/\344\273\243\347\240\201/ConcretePrototype.cpp" new file mode 100644 index 0000000000..cc4e6a2847 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/10_\350\257\276\344\273\266_prototype/\344\273\243\347\240\201/ConcretePrototype.cpp" @@ -0,0 +1,32 @@ + + +//具体类 +class BinarySplitter : public ISplitter{ +public: + virtual ISplitter* clone(){ + return new BinarySplitter(*this); + } +}; + +class TxtSplitter: public ISplitter{ +public: + virtual ISplitter* clone(){ + return new TxtSplitter(*this); + } +}; + +class PictureSplitter: public ISplitter{ +public: + virtual ISplitter* clone(){ + return new PictureSplitter(*this); + } +}; + +class VideoSplitter: public ISplitter{ +public: + virtual ISplitter* clone(){ + return new VideoSplitter(*this); + } +}; + + diff --git "a/note/CPP-Design-Patterns-master/10_\350\257\276\344\273\266_prototype/\344\273\243\347\240\201/Prototype.cpp" "b/note/CPP-Design-Patterns-master/10_\350\257\276\344\273\266_prototype/\344\273\243\347\240\201/Prototype.cpp" new file mode 100644 index 0000000000..e35e3ea28b --- /dev/null +++ "b/note/CPP-Design-Patterns-master/10_\350\257\276\344\273\266_prototype/\344\273\243\347\240\201/Prototype.cpp" @@ -0,0 +1,13 @@ + +//抽象类 +class ISplitter{ +public: + virtual void split()=0; + virtual ISplitter* clone()=0; //通过克隆自己来创建对象 + + virtual ~ISplitter(){} + +}; + + + diff --git "a/note/CPP-Design-Patterns-master/11_\350\257\276\344\273\266_builder\346\236\204\345\273\272\345\231\250/builder_\346\236\204\345\273\272\345\231\250.pdf" "b/note/CPP-Design-Patterns-master/11_\350\257\276\344\273\266_builder\346\236\204\345\273\272\345\231\250/builder_\346\236\204\345\273\272\345\231\250.pdf" new file mode 100644 index 0000000000..4349421c31 Binary files /dev/null and "b/note/CPP-Design-Patterns-master/11_\350\257\276\344\273\266_builder\346\236\204\345\273\272\345\231\250/builder_\346\236\204\345\273\272\345\231\250.pdf" differ diff --git "a/note/CPP-Design-Patterns-master/11_\350\257\276\344\273\266_builder\346\236\204\345\273\272\345\231\250/\344\273\243\347\240\201/builder.cpp" "b/note/CPP-Design-Patterns-master/11_\350\257\276\344\273\266_builder\346\236\204\345\273\272\345\231\250/\344\273\243\347\240\201/builder.cpp" new file mode 100644 index 0000000000..d4a0c66f06 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/11_\350\257\276\344\273\266_builder\346\236\204\345\273\272\345\231\250/\344\273\243\347\240\201/builder.cpp" @@ -0,0 +1,82 @@ + +class House{ + //.... +}; + +class HouseBuilder { +public: + House* GetResult(){ + return pHouse; + } + virtual ~HouseBuilder(){} +protected: + + House* pHouse; + virtual void BuildPart1()=0; + virtual void BuildPart2()=0; + virtual void BuildPart3()=0; + virtual void BuildPart4()=0; + virtual void BuildPart5()=0; + +}; + +class StoneHouse: public House{ + +}; + +class StoneHouseBuilder: public HouseBuilder{ +protected: + + virtual void BuildPart1(){ + //pHouse->Part1 = ...; + } + virtual void BuildPart2(){ + + } + virtual void BuildPart3(){ + + } + virtual void BuildPart4(){ + + } + virtual void BuildPart5(){ + + } + +}; + + +class HouseDirector{ + +public: + HouseBuilder* pHouseBuilder; + + HouseDirector(HouseBuilder* pHouseBuilder){ + this->pHouseBuilder=pHouseBuilder; + } + + House* Construct(){ + + pHouseBuilder->BuildPart1(); + + for (int i = 0; i < 4; i++){ + pHouseBuilder->BuildPart2(); + } + + bool flag=pHouseBuilder->BuildPart3(); + + if(flag){ + pHouseBuilder->BuildPart4(); + } + + pHouseBuilder->BuildPart5(); + + return pHouseBuilder->GetResult(); + } +}; + + + + + + diff --git "a/note/CPP-Design-Patterns-master/12_\350\257\276\344\273\266_Singleton/Singleton.pdf" "b/note/CPP-Design-Patterns-master/12_\350\257\276\344\273\266_Singleton/Singleton.pdf" new file mode 100644 index 0000000000..6c84394479 Binary files /dev/null and "b/note/CPP-Design-Patterns-master/12_\350\257\276\344\273\266_Singleton/Singleton.pdf" differ diff --git "a/note/CPP-Design-Patterns-master/12_\350\257\276\344\273\266_Singleton/\344\273\243\347\240\201/Singleton.cpp" "b/note/CPP-Design-Patterns-master/12_\350\257\276\344\273\266_Singleton/\344\273\243\347\240\201/Singleton.cpp" new file mode 100644 index 0000000000..a69ca22fea --- /dev/null +++ "b/note/CPP-Design-Patterns-master/12_\350\257\276\344\273\266_Singleton/\344\273\243\347\240\201/Singleton.cpp" @@ -0,0 +1,88 @@ + + +class Singleton{ +private: + Singleton(); + Singleton(const Singleton& other); //构造函数私有化,无法被继承 +public: + static Singleton* getInstance(); + static Singleton* m_instance; +}; + +Singleton* Singleton::m_instance=nullptr; + +//线程非安全版本 +Singleton* Singleton::getInstance() { + if (m_instance == nullptr) { + m_instance = new Singleton(); + } + return m_instance; +} + + + + + + +//线程安全版本,但锁的代价过高 +Singleton* Singleton::getInstance() { + Lock lock; + if (m_instance == nullptr) { + m_instance = new Singleton(); + } + return m_instance; +} + + + + + + + + + +//双检查锁,但由于内存读写reorder不安全 +Singleton* Singleton::getInstance() { + + if(m_instance==nullptr){ + Lock lock; + if (m_instance == nullptr) { + m_instance = new Singleton(); + } + } + return m_instance; +} + + + + + + + + +//C++ 11版本之后的跨平台实现 (volatile) +std::atomic Singleton::m_instance; +std::mutex Singleton::m_mutex; + +Singleton* Singleton::getInstance() { + Singleton* tmp = m_instance.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence + if (tmp == nullptr) { + std::lock_guard lock(m_mutex); + tmp = m_instance.load(std::memory_order_relaxed); + if (tmp == nullptr) { + tmp = new Singleton; + std::atomic_thread_fence(std::memory_order_release);//释放内存fence + m_instance.store(tmp, std::memory_order_relaxed); + } + } + return tmp; +} + + + + + + + + diff --git "a/note/CPP-Design-Patterns-master/13_\350\257\276\344\273\266_Flyweight\344\272\253\345\205\203\346\250\241\345\274\217/Flyweight_\344\272\253\345\205\203\346\250\241\345\274\217.pdf" "b/note/CPP-Design-Patterns-master/13_\350\257\276\344\273\266_Flyweight\344\272\253\345\205\203\346\250\241\345\274\217/Flyweight_\344\272\253\345\205\203\346\250\241\345\274\217.pdf" new file mode 100644 index 0000000000..d612fa96b2 Binary files /dev/null and "b/note/CPP-Design-Patterns-master/13_\350\257\276\344\273\266_Flyweight\344\272\253\345\205\203\346\250\241\345\274\217/Flyweight_\344\272\253\345\205\203\346\250\241\345\274\217.pdf" differ diff --git "a/note/CPP-Design-Patterns-master/13_\350\257\276\344\273\266_Flyweight\344\272\253\345\205\203\346\250\241\345\274\217/\344\273\243\347\240\201/flyweight.cpp" "b/note/CPP-Design-Patterns-master/13_\350\257\276\344\273\266_Flyweight\344\272\253\345\205\203\346\250\241\345\274\217/\344\273\243\347\240\201/flyweight.cpp" new file mode 100644 index 0000000000..7902836e94 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/13_\350\257\276\344\273\266_Flyweight\344\272\253\345\205\203\346\250\241\345\274\217/\344\273\243\347\240\201/flyweight.cpp" @@ -0,0 +1,41 @@ + +class Font { +private: + + //unique object key + string key; + + //object state + //.... + +public: + Font(const string& key){ + //... + } +}; +ß + +class FontFactory{ +private: + map fontPool; + +public: + Font* GetFont(const string& key){ + + map::iterator item=fontPool.find(key); + + if(item!=footPool.end()){ + return fontPool[key]; + } + else{ + Font* font = new Font(key); + fontPool[key]= font; + return font; + } + + } + + void clear(){ + //... + } +}; \ No newline at end of file diff --git "a/note/CPP-Design-Patterns-master/14_\350\257\276\344\273\266_Facade\351\227\250\351\235\242\346\250\241\345\274\217/Facade_\351\227\250\351\235\242\346\250\241\345\274\217.pdf" "b/note/CPP-Design-Patterns-master/14_\350\257\276\344\273\266_Facade\351\227\250\351\235\242\346\250\241\345\274\217/Facade_\351\227\250\351\235\242\346\250\241\345\274\217.pdf" new file mode 100644 index 0000000000..71de792271 Binary files /dev/null and "b/note/CPP-Design-Patterns-master/14_\350\257\276\344\273\266_Facade\351\227\250\351\235\242\346\250\241\345\274\217/Facade_\351\227\250\351\235\242\346\250\241\345\274\217.pdf" differ diff --git "a/note/CPP-Design-Patterns-master/15_\350\257\276\344\273\266_Proxy/Proxy.pdf" "b/note/CPP-Design-Patterns-master/15_\350\257\276\344\273\266_Proxy/Proxy.pdf" new file mode 100644 index 0000000000..90c60b47fd Binary files /dev/null and "b/note/CPP-Design-Patterns-master/15_\350\257\276\344\273\266_Proxy/Proxy.pdf" differ diff --git "a/note/CPP-Design-Patterns-master/15_\350\257\276\344\273\266_Proxy/\344\273\243\347\240\201/client.cpp" "b/note/CPP-Design-Patterns-master/15_\350\257\276\344\273\266_Proxy/\344\273\243\347\240\201/client.cpp" new file mode 100644 index 0000000000..17f6392d6b --- /dev/null +++ "b/note/CPP-Design-Patterns-master/15_\350\257\276\344\273\266_Proxy/\344\273\243\347\240\201/client.cpp" @@ -0,0 +1,30 @@ +class ISubject{ +public: + virtual void process(); +}; + + +class RealSubject: public ISubject{ +public: + virtual void process(){ + //.... + } +}; + +class ClientApp{ + + ISubject* subject; + +public: + + ClientApp(){ + subject=new RealSubject(); + } + + void DoTask(){ + //... + subject->process(); + + //.... + } +}; \ No newline at end of file diff --git "a/note/CPP-Design-Patterns-master/15_\350\257\276\344\273\266_Proxy/\344\273\243\347\240\201/proxy.cpp" "b/note/CPP-Design-Patterns-master/15_\350\257\276\344\273\266_Proxy/\344\273\243\347\240\201/proxy.cpp" new file mode 100644 index 0000000000..ff449e86c7 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/15_\350\257\276\344\273\266_Proxy/\344\273\243\347\240\201/proxy.cpp" @@ -0,0 +1,34 @@ +class ISubject{ +public: + virtual void process(); +}; + + +//Proxy的设计 +class SubjectProxy: public ISubject{ + + +public: + virtual void process(){ + //对RealSubject的一种间接访问 + //.... + } +}; + +class ClientApp{ + + ISubject* subject; + +public: + + ClientApp(){ + subject=new SubjectProxy(); + } + + void DoTask(){ + //... + subject->process(); + + //.... + } +}; \ No newline at end of file diff --git "a/note/CPP-Design-Patterns-master/16_\350\257\276\344\273\266_Adapter\351\200\202\351\205\215\345\231\250/Adapter_\351\200\202\351\205\215\345\231\250.pdf" "b/note/CPP-Design-Patterns-master/16_\350\257\276\344\273\266_Adapter\351\200\202\351\205\215\345\231\250/Adapter_\351\200\202\351\205\215\345\231\250.pdf" new file mode 100644 index 0000000000..d7b49e6666 Binary files /dev/null and "b/note/CPP-Design-Patterns-master/16_\350\257\276\344\273\266_Adapter\351\200\202\351\205\215\345\231\250/Adapter_\351\200\202\351\205\215\345\231\250.pdf" differ diff --git "a/note/CPP-Design-Patterns-master/16_\350\257\276\344\273\266_Adapter\351\200\202\351\205\215\345\231\250/\344\273\243\347\240\201/Adapter.cpp" "b/note/CPP-Design-Patterns-master/16_\350\257\276\344\273\266_Adapter\351\200\202\351\205\215\345\231\250/\344\273\243\347\240\201/Adapter.cpp" new file mode 100644 index 0000000000..a6c39683c8 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/16_\350\257\276\344\273\266_Adapter\351\200\202\351\205\215\345\231\250/\344\273\243\347\240\201/Adapter.cpp" @@ -0,0 +1,76 @@ +//目标接口(新接口) +class ITarget{ +public: + virtual void process()=0; +}; + +//遗留接口(老接口) +class IAdaptee{ +public: + virtual void foo(int data)=0; + virtual int bar()=0; +}; + +//遗留类型 +class OldClass: public IAdaptee{ + //.... +}; + +//对象适配器 +class Adapter: public ITarget{ //继承 +protected: + IAdaptee* pAdaptee;//组合 + +public: + + Adapter(IAdaptee* pAdaptee){ + this->pAdaptee=pAdaptee; + } + + virtual void process(){ + int data=pAdaptee->bar(); + pAdaptee->foo(data); + + } + + +}; + + +//类适配器 +class Adapter: public ITarget, + protected OldClass{ //多继承 + + +} + + +int main(){ + IAdaptee* pAdaptee=new OldClass(); + + + ITarget* pTarget=new Adapter(pAdaptee); + pTarget->process(); + + +} + + +class stack{ + deqeue container; + +}; + +class queue{ + deqeue container; + +}; + + + + + + + + + diff --git "a/note/CPP-Design-Patterns-master/17_\350\257\276\344\273\266_Mediator\344\270\255\344\273\213\350\200\205/Mediator_\344\270\255\344\273\213\350\200\205.pdf" "b/note/CPP-Design-Patterns-master/17_\350\257\276\344\273\266_Mediator\344\270\255\344\273\213\350\200\205/Mediator_\344\270\255\344\273\213\350\200\205.pdf" new file mode 100644 index 0000000000..b8941b498c Binary files /dev/null and "b/note/CPP-Design-Patterns-master/17_\350\257\276\344\273\266_Mediator\344\270\255\344\273\213\350\200\205/Mediator_\344\270\255\344\273\213\350\200\205.pdf" differ diff --git "a/note/CPP-Design-Patterns-master/18_\350\257\276\344\273\266_State/State.pdf" "b/note/CPP-Design-Patterns-master/18_\350\257\276\344\273\266_State/State.pdf" new file mode 100644 index 0000000000..cd37873831 Binary files /dev/null and "b/note/CPP-Design-Patterns-master/18_\350\257\276\344\273\266_State/State.pdf" differ diff --git "a/note/CPP-Design-Patterns-master/18_\350\257\276\344\273\266_State/\344\273\243\347\240\201/state1.cpp" "b/note/CPP-Design-Patterns-master/18_\350\257\276\344\273\266_State/\344\273\243\347\240\201/state1.cpp" new file mode 100644 index 0000000000..5eab2cb47b --- /dev/null +++ "b/note/CPP-Design-Patterns-master/18_\350\257\276\344\273\266_State/\344\273\243\347\240\201/state1.cpp" @@ -0,0 +1,57 @@ +enum NetworkState +{ + Network_Open, + Network_Close, + Network_Connect, +}; + +class NetworkProcessor{ + + NetworkState state; + +public: + + void Operation1(){ + if (state == Network_Open){ + + //********** + state = Network_Close; + } + else if (state == Network_Close){ + + //.......... + state = Network_Connect; + } + else if (state == Network_Connect){ + + //$$$$$$$$$$ + state = Network_Open; + } + } + + public void Operation2(){ + + if (state == Network_Open){ + + //********** + state = Network_Connect; + } + else if (state == Network_Close){ + + //..... + state = Network_Open; + } + else if (state == Network_Connect){ + + //$$$$$$$$$$ + state = Network_Close; + } + + } + + public void Operation3(){ + + } +}; + + diff --git "a/note/CPP-Design-Patterns-master/18_\350\257\276\344\273\266_State/\344\273\243\347\240\201/state2.cpp" "b/note/CPP-Design-Patterns-master/18_\350\257\276\344\273\266_State/\344\273\243\347\240\201/state2.cpp" new file mode 100644 index 0000000000..3d52c75eec --- /dev/null +++ "b/note/CPP-Design-Patterns-master/18_\350\257\276\344\273\266_State/\344\273\243\347\240\201/state2.cpp" @@ -0,0 +1,83 @@ +class NetworkState{ + +public: + NetworkState* pNext; + virtual void Operation1()=0; + virtual void Operation2()=0; + virtual void Operation3()=0; + + virtual ~NetworkState(){} +}; + + +class OpenState :public NetworkState{ + + static NetworkState* m_instance; +public: + static NetworkState* getInstance(){ + if (m_instance == nullptr) { + m_instance = new OpenState(); + } + return m_instance; + } + + void Operation1(){ + + //********** + pNext = CloseState::getInstance(); + } + + void Operation2(){ + + //.......... + pNext = ConnectState::getInstance(); + } + + void Operation3(){ + + //$$$$$$$$$$ + pNext = OpenState::getInstance(); + } + + +}; + +class CloseState:public NetworkState{ } +//... + + +class NetworkProcessor{ + + NetworkState* pState; + +public: + + NetworkProcessor(NetworkState* pState){ + + this->pState = pState; + } + + void Operation1(){ + //... + pState->Operation1(); + pState = pState->pNext; + //... + } + + void Operation2(){ + //... + pState->Operation2(); + pState = pState->pNext; + //... + } + + void Operation3(){ + //... + pState->Operation3(); + pState = pState->pNext; + //... + } + +}; + + diff --git "a/note/CPP-Design-Patterns-master/19_\350\257\276\344\273\266_Memento\345\244\207\345\277\230\345\275\225/Memento_\345\244\207\345\277\230\345\275\225.pdf" "b/note/CPP-Design-Patterns-master/19_\350\257\276\344\273\266_Memento\345\244\207\345\277\230\345\275\225/Memento_\345\244\207\345\277\230\345\275\225.pdf" new file mode 100644 index 0000000000..6f96c7587e Binary files /dev/null and "b/note/CPP-Design-Patterns-master/19_\350\257\276\344\273\266_Memento\345\244\207\345\277\230\345\275\225/Memento_\345\244\207\345\277\230\345\275\225.pdf" differ diff --git "a/note/CPP-Design-Patterns-master/19_\350\257\276\344\273\266_Memento\345\244\207\345\277\230\345\275\225/\344\273\243\347\240\201/memento.cpp" "b/note/CPP-Design-Patterns-master/19_\350\257\276\344\273\266_Memento\345\244\207\345\277\230\345\275\225/\344\273\243\347\240\201/memento.cpp" new file mode 100644 index 0000000000..61b818c7f0 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/19_\350\257\276\344\273\266_Memento\345\244\207\345\277\230\345\275\225/\344\273\243\347\240\201/memento.cpp" @@ -0,0 +1,45 @@ + +class Memento +{ + string state; + //.. +public: + Memento(const string & s) : state(s) {} + string getState() const { return state; } + void setState(const string & s) { state = s; } +}; + + + +class Originator +{ + string state; + //.... +public: + Originator() {} + Memento createMomento() { + Memento m(state); + return m; + } + void setMomento(const Memento & m) { + state = m.getState(); + } +}; + + + +int main() +{ + Originator orginator; + + //捕获对象状态,存储到备忘录 + Memento mem = orginator.createMomento(); + + //... 改变orginator状态 + + //从备忘录中恢复 + orginator.setMomento(memento); + + + +} \ No newline at end of file diff --git "a/note/CPP-Design-Patterns-master/20_\350\257\276\344\273\266_Composite/Composite.pdf" "b/note/CPP-Design-Patterns-master/20_\350\257\276\344\273\266_Composite/Composite.pdf" new file mode 100644 index 0000000000..bb1fcae247 Binary files /dev/null and "b/note/CPP-Design-Patterns-master/20_\350\257\276\344\273\266_Composite/Composite.pdf" differ diff --git "a/note/CPP-Design-Patterns-master/20_\350\257\276\344\273\266_Composite/\344\273\243\347\240\201/composite.cpp" "b/note/CPP-Design-Patterns-master/20_\350\257\276\344\273\266_Composite/\344\273\243\347\240\201/composite.cpp" new file mode 100644 index 0000000000..1aed1c49ac --- /dev/null +++ "b/note/CPP-Design-Patterns-master/20_\350\257\276\344\273\266_Composite/\344\273\243\347\240\201/composite.cpp" @@ -0,0 +1,84 @@ +#include +#include +#include +#include + +using namespace std; + +class Component +{ +public: + virtual void process() = 0; + virtual ~Component(){} +}; + +//树节点 +class Composite : public Component{ + + string name; + list elements; +public: + Composite(const string & s) : name(s) {} + + void add(Component* element) { + elements.push_back(element); + } + void remove(Component* element){ + elements.remove(element); + } + + void process(){ + + //1. process current node + + + //2. process leaf nodes + for (auto &e : elements) + e->process(); //多态调用 + + } +}; + +//叶子节点 +class Leaf : public Component{ + string name; +public: + Leaf(string s) : name(s) {} + + void process(){ + //process current node + } +}; + + +void Invoke(Component & c){ + //... + c.process(); + //... +} + + +int main() +{ + + Composite root("root"); + Composite treeNode1("treeNode1"); + Composite treeNode2("treeNode2"); + Composite treeNode3("treeNode3"); + Composite treeNode4("treeNode4"); + Leaf leat1("left1"); + Leaf leat2("left2"); + + root.add(&treeNode1); + treeNode1.add(&treeNode2); + treeNode2.add(&leaf1); + + root.add(&treeNode3); + treeNode3.add(&treeNode4); + treeNode4.add(&leaf2); + + process(root); + process(leaf2); + process(treeNode3); + +} \ No newline at end of file diff --git "a/note/CPP-Design-Patterns-master/21_\350\257\276\344\273\266_Iterator\350\277\255\344\273\243\345\231\250/Iterator_\350\277\255\344\273\243\345\231\250.pdf" "b/note/CPP-Design-Patterns-master/21_\350\257\276\344\273\266_Iterator\350\277\255\344\273\243\345\231\250/Iterator_\350\277\255\344\273\243\345\231\250.pdf" new file mode 100644 index 0000000000..fee3bb6b11 Binary files /dev/null and "b/note/CPP-Design-Patterns-master/21_\350\257\276\344\273\266_Iterator\350\277\255\344\273\243\345\231\250/Iterator_\350\277\255\344\273\243\345\231\250.pdf" differ diff --git "a/note/CPP-Design-Patterns-master/21_\350\257\276\344\273\266_Iterator\350\277\255\344\273\243\345\231\250/\344\273\243\347\240\201/Iterator.cpp" "b/note/CPP-Design-Patterns-master/21_\350\257\276\344\273\266_Iterator\350\277\255\344\273\243\345\231\250/\344\273\243\347\240\201/Iterator.cpp" new file mode 100644 index 0000000000..35ee7bb030 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/21_\350\257\276\344\273\266_Iterator\350\277\255\344\273\243\345\231\250/\344\273\243\347\240\201/Iterator.cpp" @@ -0,0 +1,62 @@ +template +class Iterator +{ +public: + virtual void first() = 0; + virtual void next() = 0; + virtual bool isDone() const = 0; + virtual T& current() = 0; +}; + + + +template +class MyCollection{ + +public: + + Iterator GetIterator(){ + //... + } + +}; + +template +class CollectionIterator : public Iterator{ + MyCollection mc; +public: + + CollectionIterator(const MyCollection & c): mc(c){ } + + void first() override { + + } + void next() override { + + } + bool isDone() const override{ + + } + T& current() override{ + + } +}; + +void MyAlgorithm() +{ + MyCollection mc; + + Iterator iter= mc.GetIterator(); + + for (iter.first(); !iter.isDone(); iter.next()){ + cout << iter.current() << endl; + } + +} + + + + + + + diff --git "a/note/CPP-Design-Patterns-master/22_\350\257\276\344\273\266_\350\201\214\350\264\243\351\223\276/\344\273\243\347\240\201/ChainofResposibility.cpp" "b/note/CPP-Design-Patterns-master/22_\350\257\276\344\273\266_\350\201\214\350\264\243\351\223\276/\344\273\243\347\240\201/ChainofResposibility.cpp" new file mode 100644 index 0000000000..41921d95f3 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/22_\350\257\276\344\273\266_\350\201\214\350\264\243\351\223\276/\344\273\243\347\240\201/ChainofResposibility.cpp" @@ -0,0 +1,95 @@ +#include +#include + +using namespace std; + +enum class RequestType +{ + REQ_HANDLER1, + REQ_HANDLER2, + REQ_HANDLER3 +}; + +class Reqest +{ + string description; + RequestType reqType; +public: + Reqest(const string & desc, RequestType type) : description(desc), reqType(type) {} + RequestType getReqType() const { return reqType; } + const string& getDescription() const { return description; } +}; + +class ChainHandler{ + + ChainHandler *nextChain; + void sendReqestToNextHandler(const Reqest & req) + { + if (nextChain != nullptr) + nextChain->handle(req); + } +protected: + virtual bool canHandleRequest(const Reqest & req) = 0; + virtual void processRequest(const Reqest & req) = 0; +public: + ChainHandler() { nextChain = nullptr; } + void setNextChain(ChainHandler *next) { nextChain = next; } + + + void handle(const Reqest & req) + { + if (canHandleRequest(req)) + processRequest(req); + else + sendReqestToNextHandler(req); + } +}; + + +class Handler1 : public ChainHandler{ +protected: + bool canHandleRequest(const Reqest & req) override + { + return req.getReqType() == RequestType::REQ_HANDLER1; + } + void processRequest(const Reqest & req) override + { + cout << "Handler1 is handle reqest: " << req.getDescription() << endl; + } +}; + +class Handler2 : public ChainHandler{ +protected: + bool canHandleRequest(const Reqest & req) override + { + return req.getReqType() == RequestType::REQ_HANDLER2; + } + void processRequest(const Reqest & req) override + { + cout << "Handler2 is handle reqest: " << req.getDescription() << endl; + } +}; + +class Handler3 : public ChainHandler{ +protected: + bool canHandleRequest(const Reqest & req) override + { + return req.getReqType() == RequestType::REQ_HANDLER3; + } + void processRequest(const Reqest & req) override + { + cout << "Handler3 is handle reqest: " << req.getDescription() << endl; + } +}; + +int main(){ + Handler1 h1; + Handler2 h2; + Handler3 h3; + h1.setNextChain(&h2); + h2.setNextChain(&h3); + + Reqest req("process task ... ", RequestType::REQ_HANDLER3); + h1.handle(req); + return 0; +} \ No newline at end of file diff --git "a/note/CPP-Design-Patterns-master/22_\350\257\276\344\273\266_\350\201\214\350\264\243\351\223\276/\350\201\214\350\264\243\351\223\276.pdf" "b/note/CPP-Design-Patterns-master/22_\350\257\276\344\273\266_\350\201\214\350\264\243\351\223\276/\350\201\214\350\264\243\351\223\276.pdf" new file mode 100644 index 0000000000..ce03fb3036 Binary files /dev/null and "b/note/CPP-Design-Patterns-master/22_\350\257\276\344\273\266_\350\201\214\350\264\243\351\223\276/\350\201\214\350\264\243\351\223\276.pdf" differ diff --git "a/note/CPP-Design-Patterns-master/23_\350\257\276\344\273\266_Command\345\221\275\344\273\244\346\250\241\345\274\217/Command_\345\221\275\344\273\244\346\250\241\345\274\217.pdf" "b/note/CPP-Design-Patterns-master/23_\350\257\276\344\273\266_Command\345\221\275\344\273\244\346\250\241\345\274\217/Command_\345\221\275\344\273\244\346\250\241\345\274\217.pdf" new file mode 100644 index 0000000000..ea1442e7b1 Binary files /dev/null and "b/note/CPP-Design-Patterns-master/23_\350\257\276\344\273\266_Command\345\221\275\344\273\244\346\250\241\345\274\217/Command_\345\221\275\344\273\244\346\250\241\345\274\217.pdf" differ diff --git "a/note/CPP-Design-Patterns-master/23_\350\257\276\344\273\266_Command\345\221\275\344\273\244\346\250\241\345\274\217/\344\273\243\347\240\201/Command.cpp" "b/note/CPP-Design-Patterns-master/23_\350\257\276\344\273\266_Command\345\221\275\344\273\244\346\250\241\345\274\217/\344\273\243\347\240\201/Command.cpp" new file mode 100644 index 0000000000..5637d2b64b --- /dev/null +++ "b/note/CPP-Design-Patterns-master/23_\350\257\276\344\273\266_Command\345\221\275\344\273\244\346\250\241\345\274\217/\344\273\243\347\240\201/Command.cpp" @@ -0,0 +1,64 @@ +#include +#include +#include +using namespace std; + + +class Command +{ +public: + virtual void execute() = 0; +}; + +class ConcreteCommand1 : public Command +{ + string arg; +public: + ConcreteCommand1(const string & a) : arg(a) {} + void execute() override + { + cout<< "#1 process..."< commands; +public: + void addCommand(Command *c) { commands.push_back(c); } + void execute() override + { + for (auto &c : commands) + { + c->execute(); + } + } +}; + + + +int main() +{ + + ConcreteCommand1 command1(receiver, "Arg ###"); + ConcreteCommand2 command2(receiver, "Arg $$$"); + + MacroCommand macro; + macro.addCommand(&command1); + macro.addCommand(&command2); + + macro.execute(); + +} \ No newline at end of file diff --git "a/note/CPP-Design-Patterns-master/24_\350\257\276\344\273\266_Vistor\350\256\277\351\227\256\345\231\250/Vistor_\350\256\277\351\227\256\345\231\250.pdf" "b/note/CPP-Design-Patterns-master/24_\350\257\276\344\273\266_Vistor\350\256\277\351\227\256\345\231\250/Vistor_\350\256\277\351\227\256\345\231\250.pdf" new file mode 100644 index 0000000000..7816121431 Binary files /dev/null and "b/note/CPP-Design-Patterns-master/24_\350\257\276\344\273\266_Vistor\350\256\277\351\227\256\345\231\250/Vistor_\350\256\277\351\227\256\345\231\250.pdf" differ diff --git "a/note/CPP-Design-Patterns-master/24_\350\257\276\344\273\266_Vistor\350\256\277\351\227\256\345\231\250/\344\273\243\347\240\201/Visitor1.cpp" "b/note/CPP-Design-Patterns-master/24_\350\257\276\344\273\266_Vistor\350\256\277\351\227\256\345\231\250/\344\273\243\347\240\201/Visitor1.cpp" new file mode 100644 index 0000000000..41205dcde9 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/24_\350\257\276\344\273\266_Vistor\350\256\277\351\227\256\345\231\250/\344\273\243\347\240\201/Visitor1.cpp" @@ -0,0 +1,83 @@ +#include +using namespace std; + +class Visitor; + + +class Element +{ +public: + virtual void accept(Visitor& visitor) = 0; //第一次多态辨析 + + virtual ~Element(){} +}; + +class ElementA : public Element +{ +public: + void accept(Visitor &visitor) override { + visitor.visitElementA(*this); + } + + +}; + +class ElementB : public Element +{ +public: + void accept(Visitor &visitor) override { + visitor.visitElementB(*this); //第二次多态辨析 + } + +}; + + +class Visitor{ +public: + virtual void visitElementA(ElementA& element) = 0; + virtual void visitElementB(ElementB& element) = 0; + + virtual ~Visitor(){} +}; + +//================================== + +//扩展1 +class Visitor1 : public Visitor{ +public: + void visitElementA(ElementA& element) override{ + cout << "Visitor1 is processing ElementA" << endl; + } + + void visitElementB(ElementB& element) override{ + cout << "Visitor1 is processing ElementB" << endl; + } +}; + +//扩展2 +class Visitor2 : public Visitor{ +public: + void visitElementA(ElementA& element) override{ + cout << "Visitor2 is processing ElementA" << endl; + } + + void visitElementB(ElementB& element) override{ + cout << "Visitor2 is processing ElementB" << endl; + } +}; + + + + +int main() +{ + Visitor2 visitor; + ElementB elementB; + elementB.accept(visitor);// double dispatch + + ElementA elementA; + elementA.accept(visitor); + + + return 0; +} \ No newline at end of file diff --git "a/note/CPP-Design-Patterns-master/24_\350\257\276\344\273\266_Vistor\350\256\277\351\227\256\345\231\250/\344\273\243\347\240\201/Visitor2.cpp" "b/note/CPP-Design-Patterns-master/24_\350\257\276\344\273\266_Vistor\350\256\277\351\227\256\345\231\250/\344\273\243\347\240\201/Visitor2.cpp" new file mode 100644 index 0000000000..ebe1a8e334 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/24_\350\257\276\344\273\266_Vistor\350\256\277\351\227\256\345\231\250/\344\273\243\347\240\201/Visitor2.cpp" @@ -0,0 +1,49 @@ +#include +using namespace std; + +class Visitor; + + +class Element +{ +public: + virtual void Func1() = 0; + + virtual void Func2(int data)=0; + virtual void Func3(int data)=0; + //... + + virtual ~Element(){} +}; + +class ElementA : public Element +{ +public: + void Func1() override{ + //... + } + + void Func2(int data) override{ + //... + } + +}; + +class ElementB : public Element +{ +public: + void Func1() override{ + //*** + } + + void Func2(int data) override { + //*** + } + +}; + + + + + + diff --git "a/note/CPP-Design-Patterns-master/25_\350\257\276\344\273\266_Interpreter/Interpreter.pdf" "b/note/CPP-Design-Patterns-master/25_\350\257\276\344\273\266_Interpreter/Interpreter.pdf" new file mode 100644 index 0000000000..94a622c6c2 Binary files /dev/null and "b/note/CPP-Design-Patterns-master/25_\350\257\276\344\273\266_Interpreter/Interpreter.pdf" differ diff --git "a/note/CPP-Design-Patterns-master/25_\350\257\276\344\273\266_Interpreter/\344\273\243\347\240\201/main.cpp" "b/note/CPP-Design-Patterns-master/25_\350\257\276\344\273\266_Interpreter/\344\273\243\347\240\201/main.cpp" new file mode 100644 index 0000000000..da6a6c3777 --- /dev/null +++ "b/note/CPP-Design-Patterns-master/25_\350\257\276\344\273\266_Interpreter/\344\273\243\347\240\201/main.cpp" @@ -0,0 +1,136 @@ + +#include +#include +#include + +using namespace std; + +class Expression { +public: + virtual int interpreter(map var)=0; + virtual ~Expression(){} +}; + +//变量表达式 +class VarExpression: public Expression { + + char key; + +public: + VarExpression(const char& key) + { + this->key = key; + } + + int interpreter(map var) override { + return var[key]; + } + +}; + +//符号表达式 +class SymbolExpression : public Expression { + + // 运算符左右两个参数 +protected: + Expression* left; + Expression* right; + +public: + SymbolExpression( Expression* left, Expression* right): + left(left),right(right){ + + } + +}; + +//加法运算 +class AddExpression : public SymbolExpression { + +public: + AddExpression(Expression* left, Expression* right): + SymbolExpression(left,right){ + + } + int interpreter(map var) override { + return left->interpreter(var) + right->interpreter(var); + } + +}; + +//减法运算 +class SubExpression : public SymbolExpression { + +public: + SubExpression(Expression* left, Expression* right): + SymbolExpression(left,right){ + + } + int interpreter(map var) override { + return left->interpreter(var) - right->interpreter(var); + } + +}; + + + +Expression* analyse(string expStr) { + + stack expStack; + Expression* left = nullptr; + Expression* right = nullptr; + for(int i=0; i var; + var.insert(make_pair('a',5)); + var.insert(make_pair('b',2)); + var.insert(make_pair('c',1)); + var.insert(make_pair('d',6)); + var.insert(make_pair('e',10)); + + + Expression* expression= analyse(expStr); + + int result=expression->interpreter(var); + + cout< * 单例模式,保证唯一 +> * list实现连接池 +> * 连接池为静态大小 +> * 互斥锁实现线程安全 + +校验 +> * HTTP请求采用POST方式 +> * 登录用户名和密码校验 +> * 用户注册及多线程注册安全 diff --git a/note/TinyWebServer-master/CGImysql/sql_connection_pool.cpp b/note/TinyWebServer-master/CGImysql/sql_connection_pool.cpp new file mode 100644 index 0000000000..f4109b79cc --- /dev/null +++ b/note/TinyWebServer-master/CGImysql/sql_connection_pool.cpp @@ -0,0 +1,143 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "sql_connection_pool.h" + +using namespace std; + +connection_pool::connection_pool() +{ + m_CurConn = 0; + m_FreeConn = 0; +} + +connection_pool *connection_pool::GetInstance() +{ + static connection_pool connPool; + return &connPool; +} + +//构造初始化 +void connection_pool::init(string url, string User, string PassWord, string DBName, int Port, int MaxConn, int close_log) +{ + m_url = url; + m_Port = Port; + m_User = User; + m_PassWord = PassWord; + m_DatabaseName = DBName; + m_close_log = close_log; + + for (int i = 0; i < MaxConn; i++) + { + MYSQL *con = NULL; + con = mysql_init(con); + + if (con == NULL) + { + LOG_ERROR("MySQL Error"); + exit(1); + } + con = mysql_real_connect(con, url.c_str(), User.c_str(), PassWord.c_str(), DBName.c_str(), Port, NULL, 0); + + if (con == NULL) + { + LOG_ERROR("MySQL Error"); + exit(1); + } + connList.push_back(con); + ++m_FreeConn; + } + + reserve = sem(m_FreeConn); + + m_MaxConn = m_FreeConn; +} + + +//当有请求时,从数据库连接池中返回一个可用连接,更新使用和空闲连接数 +MYSQL *connection_pool::GetConnection() +{ + MYSQL *con = NULL; + + if (0 == connList.size()) + return NULL; + + reserve.wait(); + + lock.lock(); + + con = connList.front(); + connList.pop_front(); + + --m_FreeConn; + ++m_CurConn; + + lock.unlock(); + return con; +} + +//释放当前使用的连接 +bool connection_pool::ReleaseConnection(MYSQL *con) +{ + if (NULL == con) + return false; + + lock.lock(); + + connList.push_back(con); + ++m_FreeConn; + --m_CurConn; + + lock.unlock(); + + reserve.post(); + return true; +} + +//销毁数据库连接池 +void connection_pool::DestroyPool() +{ + + lock.lock(); + if (connList.size() > 0) + { + list::iterator it; + for (it = connList.begin(); it != connList.end(); ++it) + { + MYSQL *con = *it; + mysql_close(con); + } + m_CurConn = 0; + m_FreeConn = 0; + connList.clear(); + } + + lock.unlock(); +} + +//当前空闲的连接数 +int connection_pool::GetFreeConn() +{ + return this->m_FreeConn; +} + +connection_pool::~connection_pool() +{ + DestroyPool(); +} + +connectionRAII::connectionRAII(MYSQL **SQL, connection_pool *connPool){ + *SQL = connPool->GetConnection(); + + conRAII = *SQL; + poolRAII = connPool; +} + +connectionRAII::~connectionRAII(){ + poolRAII->ReleaseConnection(conRAII); +} \ No newline at end of file diff --git a/note/TinyWebServer-master/CGImysql/sql_connection_pool.h b/note/TinyWebServer-master/CGImysql/sql_connection_pool.h new file mode 100644 index 0000000000..00d285bef0 --- /dev/null +++ b/note/TinyWebServer-master/CGImysql/sql_connection_pool.h @@ -0,0 +1,60 @@ +#ifndef _CONNECTION_POOL_ +#define _CONNECTION_POOL_ + +#include +#include +#include +#include +#include +#include +#include +#include "../lock/locker.h" +#include "../log/log.h" + +using namespace std; + +class connection_pool +{ +public: + MYSQL *GetConnection(); //获取数据库连接 + bool ReleaseConnection(MYSQL *conn); //释放连接 + int GetFreeConn(); //获取连接 + void DestroyPool(); //销毁所有连接 + + //单例模式 + static connection_pool *GetInstance(); + + void init(string url, string User, string PassWord, string DataBaseName, int Port, int MaxConn, int close_log); + +private: + connection_pool(); + ~connection_pool(); + + int m_MaxConn; //最大连接数 + int m_CurConn; //当前已使用的连接数 + int m_FreeConn; //当前空闲的连接数 + locker lock; + list connList; //连接池 + sem reserve; + +public: + string m_url; //主机地址 + string m_Port; //数据库端口号 + string m_User; //登陆数据库用户名 + string m_PassWord; //登陆数据库密码 + string m_DatabaseName; //使用数据库名 + int m_close_log; //日志开关 +}; + +class connectionRAII{ + +public: + connectionRAII(MYSQL **con, connection_pool *connPool); + ~connectionRAII(); + +private: + MYSQL *conRAII; + connection_pool *poolRAII; +}; + +#endif diff --git a/note/TinyWebServer-master/LICENSE b/note/TinyWebServer-master/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/note/TinyWebServer-master/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/note/TinyWebServer-master/README.md b/note/TinyWebServer-master/README.md new file mode 100644 index 0000000000..dc7a95e7dd --- /dev/null +++ b/note/TinyWebServer-master/README.md @@ -0,0 +1,246 @@ + + +TinyWebServer +=============== +Linux下C++轻量级Web服务器,助力初学者快速实践网络编程,搭建属于自己的服务器. + +* 使用 **线程池 + 非阻塞socket + epoll(ET和LT均实现) + 事件处理(Reactor和Proactor均实现)** 的并发模型 +* 使用**状态机**解析HTTP请求报文,支持解析**GET和POST**请求 +* 访问服务器数据库实现web端用户**注册、登录**功能,可以请求服务器**图片和视频文件** +* 实现**同步/异步日志系统**,记录服务器运行状态 +* 经Webbench压力测试可以实现**上万的并发连接**数据交换 + +目录 +----- + +| [概述](#概述) | [框架](#框架) | [Demo演示](#Demo演示) | [压力测试](#压力测试) |[更新日志](#更新日志) |[源码下载](#源码下载) | [快速运行](#快速运行) | [个性化运行](#个性化运行) | [庖丁解牛](#庖丁解牛) | [致谢](#致谢) | +|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:| + + +概述 +---------- + +> * C/C++ +> * B/S模型 +> * [线程同步机制包装类](https://github.com/qinguoyi/TinyWebServer/tree/master/lock) +> * [http连接请求处理类](https://github.com/qinguoyi/TinyWebServer/tree/master/http) +> * [半同步/半反应堆线程池](https://github.com/qinguoyi/TinyWebServer/tree/master/threadpool) +> * [定时器处理非活动连接](https://github.com/qinguoyi/TinyWebServer/tree/master/timer) +> * [同步/异步日志系统 ](https://github.com/qinguoyi/TinyWebServer/tree/master/log) +> * [数据库连接池](https://github.com/qinguoyi/TinyWebServer/tree/master/CGImysql) +> * [同步线程注册和登录校验](https://github.com/qinguoyi/TinyWebServer/tree/master/CGImysql) +> * [简易服务器压力测试](https://github.com/qinguoyi/TinyWebServer/tree/master/test_presure) + + +框架 +------------- +
+ +Demo演示 +---------- +> * 注册演示 + +
+ +> * 登录演示 + +
+ +> * 请求图片文件演示(6M) + +
+ +> * 请求视频文件演示(39M) + +
+ + +压力测试 +------------- +在关闭日志后,使用Webbench对服务器进行压力测试,对listenfd和connfd分别采用ET和LT模式,均可实现上万的并发连接,下面列出的是两者组合后的测试结果. + +> * Proactor,LT + LT,93251 QPS + +
+ +> * Proactor,LT + ET,97459 QPS + +
+ +> * Proactor,ET + LT,80498 QPS + +
+ +> * Proactor,ET + ET,92167 QPS + +
+ +> * Reactor,LT + ET,69175 QPS + +
+ +> * 并发连接总数:10500 +> * 访问服务器时间:5s +> * 所有访问均成功 + +**注意:** 使用本项目的webbench进行压测时,若报错显示webbench命令找不到,将可执行文件webbench删除后,重新编译即可。 + +更新日志 +------- +- [x] 解决请求服务器上大文件的Bug +- [x] 增加请求视频文件的页面 +- [x] 解决数据库同步校验内存泄漏 +- [x] 实现非阻塞模式下的ET和LT触发,并完成压力测试 +- [x] 完善`lock.h`中的封装类,统一使用该同步机制 +- [x] 改进代码结构,更新局部变量懒汉单例模式 +- [x] 优化数据库连接池信号量与代码结构 +- [x] 使用RAII机制优化数据库连接的获取与释放 +- [x] 优化代码结构,封装工具类以减少全局变量 +- [x] 编译一次即可,命令行进行个性化测试更加友好 +- [x] main函数封装重构 +- [x] 新增命令行日志开关,关闭日志后更新压力测试结果 +- [x] 改进编译方式,只配置一次SQL信息即可 +- [x] 新增Reactor模式,并完成压力测试 + +源码下载 +------- +目前有两个版本,版本间的代码结构有较大改动,文档和代码运行方法也不一致。重构版本更简洁,原始版本(raw_version)更大保留游双代码的原汁原味,从原始版本更容易入手. + +如果遇到github代码下载失败,或访问太慢,可以从以下链接下载,与Github最新提交同步. + +* 重构版本下载地址 : [BaiduYun](https://pan.baidu.com/s/1dFdDR3QDbIXX71C2OJ-x2g) + * 提取码 : hr8n +* 原始版本(raw_version)下载地址 : [BaiduYun](https://pan.baidu.com/s/1-icglZGmz8cYqGCZhsN78A) + * 提取码 : 496d + * 原始版本运行请参考[原始文档](https://github.com/qinguoyi/TinyWebServer/tree/raw_version) + +快速运行 +------------ +* 服务器测试环境 + * Ubuntu版本16.04 + * MySQL版本5.7.29 +* 浏览器测试环境 + * Windows、Linux均可 + * Chrome + * FireFox + * 其他浏览器暂无测试 + +* 测试前确认已安装MySQL数据库 + + ```C++ + // 建立yourdb库 + create database yourdb; + + // 创建user表 + USE yourdb; + CREATE TABLE user( + username char(50) NULL, + passwd char(50) NULL + )ENGINE=InnoDB; + + // 添加数据 + INSERT INTO user(username, passwd) VALUES('name', 'passwd'); + ``` + +* 修改main.cpp中的数据库初始化信息 + + ```C++ + //数据库登录名,密码,库名 + string user = "root"; + string passwd = "root"; + string databasename = "yourdb"; + ``` + +* build + + ```C++ + sh ./build.sh + ``` + +* 启动server + + ```C++ + ./server + ``` + +* 浏览器端 + + ```C++ + ip:9006 + ``` + +个性化运行 +------ + +```C++ +./server [-p port] [-l LOGWrite] [-m TRIGMode] [-o OPT_LINGER] [-s sql_num] [-t thread_num] [-c close_log] [-a actor_model] +``` + +温馨提示:以上参数不是非必须,不用全部使用,根据个人情况搭配选用即可. + +* -p,自定义端口号 + * 默认9006 +* -l,选择日志写入方式,默认同步写入 + * 0,同步写入 + * 1,异步写入 +* -m,listenfd和connfd的模式组合,默认使用LT + LT + * 0,表示使用LT + LT + * 1,表示使用LT + ET + * 2,表示使用ET + LT + * 3,表示使用ET + ET +* -o,优雅关闭连接,默认不使用 + * 0,不使用 + * 1,使用 +* -s,数据库连接数量 + * 默认为8 +* -t,线程数量 + * 默认为8 +* -c,关闭日志,默认打开 + * 0,打开日志 + * 1,关闭日志 +* -a,选择反应堆模型,默认Proactor + * 0,Proactor模型 + * 1,Reactor模型 + +测试示例命令与含义 + +```C++ +./server -p 9007 -l 1 -m 0 -o 1 -s 10 -t 10 -c 1 -a 1 +``` + +- [x] 端口9007 +- [x] 异步写入日志 +- [x] 使用LT + LT组合 +- [x] 使用优雅关闭连接 +- [x] 数据库连接池内有10条连接 +- [x] 线程池内有10条线程 +- [x] 关闭日志 +- [x] Reactor反应堆模型 + +庖丁解牛 +------------ +近期版本迭代较快,以下内容多以旧版本(raw_version)代码为蓝本进行详解. + +* [小白视角:一文读懂社长的TinyWebServer](https://huixxi.github.io/2020/06/02/%E5%B0%8F%E7%99%BD%E8%A7%86%E8%A7%92%EF%BC%9A%E4%B8%80%E6%96%87%E8%AF%BB%E6%87%82%E7%A4%BE%E9%95%BF%E7%9A%84TinyWebServer/#more) +* [最新版Web服务器项目详解 - 01 线程同步机制封装类](https://mp.weixin.qq.com/s?__biz=MzAxNzU2MzcwMw==&mid=2649274278&idx=3&sn=5840ff698e3f963c7855d702e842ec47&chksm=83ffbefeb48837e86fed9754986bca6db364a6fe2e2923549a378e8e5dec6e3cf732cdb198e2&scene=0&xtrack=1#rd) +* [最新版Web服务器项目详解 - 02 半同步半反应堆线程池(上)](https://mp.weixin.qq.com/s?__biz=MzAxNzU2MzcwMw==&mid=2649274278&idx=4&sn=caa323faf0c51d882453c0e0c6a62282&chksm=83ffbefeb48837e841a6dbff292217475d9075e91cbe14042ad6e55b87437dcd01e6d9219e7d&scene=0&xtrack=1#rd) +* [最新版Web服务器项目详解 - 03 半同步半反应堆线程池(下)](https://mp.weixin.qq.com/s/PB8vMwi8sB4Jw3WzAKpWOQ) +* [最新版Web服务器项目详解 - 04 http连接处理(上)](https://mp.weixin.qq.com/s/BfnNl-3jc_x5WPrWEJGdzQ) +* [最新版Web服务器项目详解 - 05 http连接处理(中)](https://mp.weixin.qq.com/s/wAQHU-QZiRt1VACMZZjNlw) +* [最新版Web服务器项目详解 - 06 http连接处理(下)](https://mp.weixin.qq.com/s/451xNaSFHxcxfKlPBV3OCg) +* [最新版Web服务器项目详解 - 07 定时器处理非活动连接(上)](https://mp.weixin.qq.com/s/mmXLqh_NywhBXJvI45hchA) +* [最新版Web服务器项目详解 - 08 定时器处理非活动连接(下)](https://mp.weixin.qq.com/s/fb_OUnlV1SGuOUdrGrzVgg) +* [最新版Web服务器项目详解 - 09 日志系统(上)](https://mp.weixin.qq.com/s/IWAlPzVDkR2ZRI5iirEfCg) +* [最新版Web服务器项目详解 - 10 日志系统(下)](https://mp.weixin.qq.com/s/f-ujwFyCe1LZa3EB561ehA) +* [最新版Web服务器项目详解 - 11 数据库连接池](https://mp.weixin.qq.com/s?__biz=MzAxNzU2MzcwMw==&mid=2649274326&idx=1&sn=5af78e2bf6552c46ae9ab2aa22faf839&chksm=83ffbe8eb4883798c3abb82ddd124c8100a39ef41ab8d04abe42d344067d5e1ac1b0cac9d9a3&token=1450918099&lang=zh_CN#rd) +* [最新版Web服务器项目详解 - 12 注册登录](https://mp.weixin.qq.com/s?__biz=MzAxNzU2MzcwMw==&mid=2649274431&idx=4&sn=7595a70f06a79cb7abaebcd939e0cbee&chksm=83ffb167b4883871ce110aeb23e04acf835ef41016517247263a2c3ab6f8e615607858127ea6&token=1686112912&lang=zh_CN#rd) +* [最新版Web服务器项目详解 - 13 踩坑与面试题](https://mp.weixin.qq.com/s?__biz=MzAxNzU2MzcwMw==&mid=2649274431&idx=1&sn=2dd28c92f5d9704a57c001a3d2630b69&chksm=83ffb167b48838715810b27b8f8b9a576023ee5c08a8e5d91df5baf396732de51268d1bf2a4e&token=1686112912&lang=zh_CN#rd) +* 已更新完毕 + +
+ +致谢 +------------ +Linux高性能服务器编程,游双著. + +感谢以下朋友的PR和帮助: [@RownH](https://github.com/RownH),[@mapleFU](https://github.com/mapleFU),[@ZWiley](https://github.com/ZWiley),[@zjuHong](https://github.com/zjuHong),[@mamil](https://github.com/mamil),[@byfate](https://github.com/byfate),[@MaJun827](https://github.com/MaJun827),[@BBLiu-coder](https://github.com/BBLiu-coder),[@smoky96](https://github.com/smoky96),[@yfBong](https://github.com/yfBong),[@liuwuyao](https://github.com/liuwuyao),[@Huixxi](https://github.com/Huixxi). diff --git a/note/TinyWebServer-master/build.sh b/note/TinyWebServer-master/build.sh new file mode 100644 index 0000000000..a39ce15285 --- /dev/null +++ b/note/TinyWebServer-master/build.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +make server \ No newline at end of file diff --git a/note/TinyWebServer-master/config.cpp b/note/TinyWebServer-master/config.cpp new file mode 100644 index 0000000000..f16a69ecfb --- /dev/null +++ b/note/TinyWebServer-master/config.cpp @@ -0,0 +1,86 @@ +#include "config.h" + +Config::Config(){ + //端口号,默认9006 + PORT = 9006; + + //日志写入方式,默认同步 + LOGWrite = 0; + + //触发组合模式,默认listenfd LT + connfd LT + TRIGMode = 0; + + //listenfd触发模式,默认LT + LISTENTrigmode = 0; + + //connfd触发模式,默认LT + CONNTrigmode = 0; + + //优雅关闭链接,默认不使用 + OPT_LINGER = 0; + + //数据库连接池数量,默认8 + sql_num = 8; + + //线程池内的线程数量,默认8 + thread_num = 8; + + //关闭日志,默认不关闭 + close_log = 0; + + //并发模型,默认是proactor + actor_model = 0; +} + +void Config::parse_arg(int argc, char*argv[]){ + int opt; + const char *str = "p:l:m:o:s:t:c:a:"; + while ((opt = getopt(argc, argv, str)) != -1) + { + switch (opt) + { + case 'p': + { + PORT = atoi(optarg); + break; + } + case 'l': + { + LOGWrite = atoi(optarg); + break; + } + case 'm': + { + TRIGMode = atoi(optarg); + break; + } + case 'o': + { + OPT_LINGER = atoi(optarg); + break; + } + case 's': + { + sql_num = atoi(optarg); + break; + } + case 't': + { + thread_num = atoi(optarg); + break; + } + case 'c': + { + close_log = atoi(optarg); + break; + } + case 'a': + { + actor_model = atoi(optarg); + break; + } + default: + break; + } + } +} \ No newline at end of file diff --git a/note/TinyWebServer-master/config.h b/note/TinyWebServer-master/config.h new file mode 100644 index 0000000000..d57db57ea2 --- /dev/null +++ b/note/TinyWebServer-master/config.h @@ -0,0 +1,47 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include "webserver.h" + +using namespace std; + +class Config +{ +public: + Config(); + ~Config(){}; + + void parse_arg(int argc, char*argv[]); + + //端口号 + int PORT; + + //日志写入方式 + int LOGWrite; + + //触发组合模式 + int TRIGMode; + + //listenfd触发模式 + int LISTENTrigmode; + + //connfd触发模式 + int CONNTrigmode; + + //优雅关闭链接 + int OPT_LINGER; + + //数据库连接池数量 + int sql_num; + + //线程池内的线程数量 + int thread_num; + + //是否关闭日志 + int close_log; + + //并发模型选择 + int actor_model; +}; + +#endif \ No newline at end of file diff --git a/note/TinyWebServer-master/http/README.md b/note/TinyWebServer-master/http/README.md new file mode 100644 index 0000000000..9f9d21d41e --- /dev/null +++ b/note/TinyWebServer-master/http/README.md @@ -0,0 +1,7 @@ + +http连接处理类 +=============== +根据状态转移,通过主从状态机封装了http连接类。其中,主状态机在内部调用从状态机,从状态机将处理状态和数据传给主状态机 +> * 客户端发出http连接请求 +> * 从状态机读取数据,更新自身状态和接收数据,传给主状态机 +> * 主状态机根据从状态机状态,更新自身状态,决定响应请求还是继续读取 \ No newline at end of file diff --git a/note/TinyWebServer-master/http/http_conn.cpp b/note/TinyWebServer-master/http/http_conn.cpp new file mode 100644 index 0000000000..a359ab4ecc --- /dev/null +++ b/note/TinyWebServer-master/http/http_conn.cpp @@ -0,0 +1,702 @@ +#include "http_conn.h" + +#include +#include + +//定义http响应的一些状态信息 +const char *ok_200_title = "OK"; +const char *error_400_title = "Bad Request"; +const char *error_400_form = "Your request has bad syntax or is inherently impossible to staisfy.\n"; +const char *error_403_title = "Forbidden"; +const char *error_403_form = "You do not have permission to get file form this server.\n"; +const char *error_404_title = "Not Found"; +const char *error_404_form = "The requested file was not found on this server.\n"; +const char *error_500_title = "Internal Error"; +const char *error_500_form = "There was an unusual problem serving the request file.\n"; + +locker m_lock; +map users; + +void http_conn::initmysql_result(connection_pool *connPool) +{ + //先从连接池中取一个连接 + MYSQL *mysql = NULL; + connectionRAII mysqlcon(&mysql, connPool); + + //在user表中检索username,passwd数据,浏览器端输入 + if (mysql_query(mysql, "SELECT username,passwd FROM user")) + { + LOG_ERROR("SELECT error:%s\n", mysql_error(mysql)); + } + + //从表中检索完整的结果集 + MYSQL_RES *result = mysql_store_result(mysql); + + //返回结果集中的列数 + int num_fields = mysql_num_fields(result); + + //返回所有字段结构的数组 + MYSQL_FIELD *fields = mysql_fetch_fields(result); + + //从结果集中获取下一行,将对应的用户名和密码,存入map中 + while (MYSQL_ROW row = mysql_fetch_row(result)) + { + string temp1(row[0]); + string temp2(row[1]); + users[temp1] = temp2; + } +} + +//对文件描述符设置非阻塞 +int setnonblocking(int fd) +{ + int old_option = fcntl(fd, F_GETFL); + int new_option = old_option | O_NONBLOCK; + fcntl(fd, F_SETFL, new_option); + return old_option; +} + +//将内核事件表注册读事件,ET模式,选择开启EPOLLONESHOT +void addfd(int epollfd, int fd, bool one_shot, int TRIGMode) +{ + epoll_event event; + event.data.fd = fd; + + if (1 == TRIGMode) + event.events = EPOLLIN | EPOLLET | EPOLLRDHUP; + else + event.events = EPOLLIN | EPOLLRDHUP; + + if (one_shot) + event.events |= EPOLLONESHOT; + epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); + setnonblocking(fd); +} + +//从内核时间表删除描述符 +void removefd(int epollfd, int fd) +{ + epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, 0); + close(fd); +} + +//将事件重置为EPOLLONESHOT +void modfd(int epollfd, int fd, int ev, int TRIGMode) +{ + epoll_event event; + event.data.fd = fd; + + if (1 == TRIGMode) + event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP; + else + event.events = ev | EPOLLONESHOT | EPOLLRDHUP; + + epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event); +} + +int http_conn::m_user_count = 0; +int http_conn::m_epollfd = -1; + +//关闭连接,关闭一个连接,客户总量减一 +void http_conn::close_conn(bool real_close) +{ + if (real_close && (m_sockfd != -1)) + { + printf("close %d\n", m_sockfd); + removefd(m_epollfd, m_sockfd); + m_sockfd = -1; + m_user_count--; + } +} + +//初始化连接,外部调用初始化套接字地址 +void http_conn::init(int sockfd, const sockaddr_in &addr, char *root, int TRIGMode, + int close_log, string user, string passwd, string sqlname) +{ + m_sockfd = sockfd; + m_address = addr; + + addfd(m_epollfd, sockfd, true, m_TRIGMode); + m_user_count++; + + //当浏览器出现连接重置时,可能是网站根目录出错或http响应格式出错或者访问的文件中内容完全为空 + doc_root = root; + m_TRIGMode = TRIGMode; + m_close_log = close_log; + + strcpy(sql_user, user.c_str()); + strcpy(sql_passwd, passwd.c_str()); + strcpy(sql_name, sqlname.c_str()); + + init(); +} + +//初始化新接受的连接 +//check_state默认为分析请求行状态 +void http_conn::init() +{ + mysql = NULL; + bytes_to_send = 0; + bytes_have_send = 0; + m_check_state = CHECK_STATE_REQUESTLINE; + m_linger = false; + m_method = GET; + m_url = 0; + m_version = 0; + m_content_length = 0; + m_host = 0; + m_start_line = 0; + m_checked_idx = 0; + m_read_idx = 0; + m_write_idx = 0; + cgi = 0; + m_state = 0; + timer_flag = 0; + improv = 0; + + memset(m_read_buf, '\0', READ_BUFFER_SIZE); + memset(m_write_buf, '\0', WRITE_BUFFER_SIZE); + memset(m_real_file, '\0', FILENAME_LEN); +} + +//从状态机,用于分析出一行内容 +//返回值为行的读取状态,有LINE_OK,LINE_BAD,LINE_OPEN +http_conn::LINE_STATUS http_conn::parse_line() +{ + char temp; + for (; m_checked_idx < m_read_idx; ++m_checked_idx) + { + temp = m_read_buf[m_checked_idx]; + if (temp == '\r') + { + if ((m_checked_idx + 1) == m_read_idx) + return LINE_OPEN; + else if (m_read_buf[m_checked_idx + 1] == '\n') + { + m_read_buf[m_checked_idx++] = '\0'; + m_read_buf[m_checked_idx++] = '\0'; + return LINE_OK; + } + return LINE_BAD; + } + else if (temp == '\n') + { + if (m_checked_idx > 1 && m_read_buf[m_checked_idx - 1] == '\r') + { + m_read_buf[m_checked_idx - 1] = '\0'; + m_read_buf[m_checked_idx++] = '\0'; + return LINE_OK; + } + return LINE_BAD; + } + } + return LINE_OPEN; +} + +//循环读取客户数据,直到无数据可读或对方关闭连接 +//非阻塞ET工作模式下,需要一次性将数据读完 +bool http_conn::read_once() +{ + if (m_read_idx >= READ_BUFFER_SIZE) + { + return false; + } + int bytes_read = 0; + + //LT读取数据 + if (0 == m_TRIGMode) + { + bytes_read = recv(m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0); + m_read_idx += bytes_read; + + if (bytes_read <= 0) + { + return false; + } + + return true; + } + //ET读数据 + else + { + while (true) + { + bytes_read = recv(m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0); + if (bytes_read == -1) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + break; + return false; + } + else if (bytes_read == 0) + { + return false; + } + m_read_idx += bytes_read; + } + return true; + } +} + +//解析http请求行,获得请求方法,目标url及http版本号 +http_conn::HTTP_CODE http_conn::parse_request_line(char *text) +{ + m_url = strpbrk(text, " \t"); + if (!m_url) + { + return BAD_REQUEST; + } + *m_url++ = '\0'; + char *method = text; + if (strcasecmp(method, "GET") == 0) + m_method = GET; + else if (strcasecmp(method, "POST") == 0) + { + m_method = POST; + cgi = 1; + } + else + return BAD_REQUEST; + m_url += strspn(m_url, " \t"); + m_version = strpbrk(m_url, " \t"); + if (!m_version) + return BAD_REQUEST; + *m_version++ = '\0'; + m_version += strspn(m_version, " \t"); + if (strcasecmp(m_version, "HTTP/1.1") != 0) + return BAD_REQUEST; + if (strncasecmp(m_url, "http://", 7) == 0) + { + m_url += 7; + m_url = strchr(m_url, '/'); + } + + if (strncasecmp(m_url, "https://", 8) == 0) + { + m_url += 8; + m_url = strchr(m_url, '/'); + } + + if (!m_url || m_url[0] != '/') + return BAD_REQUEST; + //当url为/时,显示判断界面 + if (strlen(m_url) == 1) + strcat(m_url, "judge.html"); + m_check_state = CHECK_STATE_HEADER; + return NO_REQUEST; +} + +//解析http请求的一个头部信息 +http_conn::HTTP_CODE http_conn::parse_headers(char *text) +{ + if (text[0] == '\0') + { + if (m_content_length != 0) + { + m_check_state = CHECK_STATE_CONTENT; + return NO_REQUEST; + } + return GET_REQUEST; + } + else if (strncasecmp(text, "Connection:", 11) == 0) + { + text += 11; + text += strspn(text, " \t"); + if (strcasecmp(text, "keep-alive") == 0) + { + m_linger = true; + } + } + else if (strncasecmp(text, "Content-length:", 15) == 0) + { + text += 15; + text += strspn(text, " \t"); + m_content_length = atol(text); + } + else if (strncasecmp(text, "Host:", 5) == 0) + { + text += 5; + text += strspn(text, " \t"); + m_host = text; + } + else + { + LOG_INFO("oop!unknow header: %s", text); + } + return NO_REQUEST; +} + +//判断http请求是否被完整读入 +http_conn::HTTP_CODE http_conn::parse_content(char *text) +{ + if (m_read_idx >= (m_content_length + m_checked_idx)) + { + text[m_content_length] = '\0'; + //POST请求中最后为输入的用户名和密码 + m_string = text; + return GET_REQUEST; + } + return NO_REQUEST; +} + +http_conn::HTTP_CODE http_conn::process_read() +{ + LINE_STATUS line_status = LINE_OK; + HTTP_CODE ret = NO_REQUEST; + char *text = 0; + + while ((m_check_state == CHECK_STATE_CONTENT && line_status == LINE_OK) || ((line_status = parse_line()) == LINE_OK)) + { + text = get_line(); + m_start_line = m_checked_idx; + LOG_INFO("%s", text); + switch (m_check_state) + { + case CHECK_STATE_REQUESTLINE: + { + ret = parse_request_line(text); + if (ret == BAD_REQUEST) + return BAD_REQUEST; + break; + } + case CHECK_STATE_HEADER: + { + ret = parse_headers(text); + if (ret == BAD_REQUEST) + return BAD_REQUEST; + else if (ret == GET_REQUEST) + { + return do_request(); + } + break; + } + case CHECK_STATE_CONTENT: + { + ret = parse_content(text); + if (ret == GET_REQUEST) + return do_request(); + line_status = LINE_OPEN; + break; + } + default: + return INTERNAL_ERROR; + } + } + return NO_REQUEST; +} + +http_conn::HTTP_CODE http_conn::do_request() +{ + strcpy(m_real_file, doc_root); + int len = strlen(doc_root); + //printf("m_url:%s\n", m_url); + const char *p = strrchr(m_url, '/'); + + //处理cgi + if (cgi == 1 && (*(p + 1) == '2' || *(p + 1) == '3')) + { + + //根据标志判断是登录检测还是注册检测 + char flag = m_url[1]; + + char *m_url_real = (char *)malloc(sizeof(char) * 200); + strcpy(m_url_real, "/"); + strcat(m_url_real, m_url + 2); + strncpy(m_real_file + len, m_url_real, FILENAME_LEN - len - 1); + free(m_url_real); + + //将用户名和密码提取出来 + //user=123&passwd=123 + char name[100], password[100]; + int i; + for (i = 5; m_string[i] != '&'; ++i) + name[i - 5] = m_string[i]; + name[i - 5] = '\0'; + + int j = 0; + for (i = i + 10; m_string[i] != '\0'; ++i, ++j) + password[j] = m_string[i]; + password[j] = '\0'; + + if (*(p + 1) == '3') + { + //如果是注册,先检测数据库中是否有重名的 + //没有重名的,进行增加数据 + char *sql_insert = (char *)malloc(sizeof(char) * 200); + strcpy(sql_insert, "INSERT INTO user(username, passwd) VALUES("); + strcat(sql_insert, "'"); + strcat(sql_insert, name); + strcat(sql_insert, "', '"); + strcat(sql_insert, password); + strcat(sql_insert, "')"); + + if (users.find(name) == users.end()) + { + m_lock.lock(); + int res = mysql_query(mysql, sql_insert); + users.insert(pair(name, password)); + m_lock.unlock(); + + if (!res) + strcpy(m_url, "/log.html"); + else + strcpy(m_url, "/registerError.html"); + } + else + strcpy(m_url, "/registerError.html"); + } + //如果是登录,直接判断 + //若浏览器端输入的用户名和密码在表中可以查找到,返回1,否则返回0 + else if (*(p + 1) == '2') + { + if (users.find(name) != users.end() && users[name] == password) + strcpy(m_url, "/welcome.html"); + else + strcpy(m_url, "/logError.html"); + } + } + + if (*(p + 1) == '0') + { + char *m_url_real = (char *)malloc(sizeof(char) * 200); + strcpy(m_url_real, "/register.html"); + strncpy(m_real_file + len, m_url_real, strlen(m_url_real)); + + free(m_url_real); + } + else if (*(p + 1) == '1') + { + char *m_url_real = (char *)malloc(sizeof(char) * 200); + strcpy(m_url_real, "/log.html"); + strncpy(m_real_file + len, m_url_real, strlen(m_url_real)); + + free(m_url_real); + } + else if (*(p + 1) == '5') + { + char *m_url_real = (char *)malloc(sizeof(char) * 200); + strcpy(m_url_real, "/picture.html"); + strncpy(m_real_file + len, m_url_real, strlen(m_url_real)); + + free(m_url_real); + } + else if (*(p + 1) == '6') + { + char *m_url_real = (char *)malloc(sizeof(char) * 200); + strcpy(m_url_real, "/video.html"); + strncpy(m_real_file + len, m_url_real, strlen(m_url_real)); + + free(m_url_real); + } + else if (*(p + 1) == '7') + { + char *m_url_real = (char *)malloc(sizeof(char) * 200); + strcpy(m_url_real, "/fans.html"); + strncpy(m_real_file + len, m_url_real, strlen(m_url_real)); + + free(m_url_real); + } + else + strncpy(m_real_file + len, m_url, FILENAME_LEN - len - 1); + + if (stat(m_real_file, &m_file_stat) < 0) + return NO_RESOURCE; + + if (!(m_file_stat.st_mode & S_IROTH)) + return FORBIDDEN_REQUEST; + + if (S_ISDIR(m_file_stat.st_mode)) + return BAD_REQUEST; + + int fd = open(m_real_file, O_RDONLY); + m_file_address = (char *)mmap(0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); + return FILE_REQUEST; +} +void http_conn::unmap() +{ + if (m_file_address) + { + munmap(m_file_address, m_file_stat.st_size); + m_file_address = 0; + } +} +bool http_conn::write() +{ + int temp = 0; + + if (bytes_to_send == 0) + { + modfd(m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode); + init(); + return true; + } + + while (1) + { + temp = writev(m_sockfd, m_iv, m_iv_count); + + if (temp < 0) + { + if (errno == EAGAIN) + { + modfd(m_epollfd, m_sockfd, EPOLLOUT, m_TRIGMode); + return true; + } + unmap(); + return false; + } + + bytes_have_send += temp; + bytes_to_send -= temp; + if (bytes_have_send >= m_iv[0].iov_len) + { + m_iv[0].iov_len = 0; + m_iv[1].iov_base = m_file_address + (bytes_have_send - m_write_idx); + m_iv[1].iov_len = bytes_to_send; + } + else + { + m_iv[0].iov_base = m_write_buf + bytes_have_send; + m_iv[0].iov_len = m_iv[0].iov_len - bytes_have_send; + } + + if (bytes_to_send <= 0) + { + unmap(); + modfd(m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode); + + if (m_linger) + { + init(); + return true; + } + else + { + return false; + } + } + } +} +bool http_conn::add_response(const char *format, ...) +{ + if (m_write_idx >= WRITE_BUFFER_SIZE) + return false; + va_list arg_list; + va_start(arg_list, format); + int len = vsnprintf(m_write_buf + m_write_idx, WRITE_BUFFER_SIZE - 1 - m_write_idx, format, arg_list); + if (len >= (WRITE_BUFFER_SIZE - 1 - m_write_idx)) + { + va_end(arg_list); + return false; + } + m_write_idx += len; + va_end(arg_list); + + LOG_INFO("request:%s", m_write_buf); + + return true; +} +bool http_conn::add_status_line(int status, const char *title) +{ + return add_response("%s %d %s\r\n", "HTTP/1.1", status, title); +} +bool http_conn::add_headers(int content_len) +{ + return add_content_length(content_len) && add_linger() && + add_blank_line(); +} +bool http_conn::add_content_length(int content_len) +{ + return add_response("Content-Length:%d\r\n", content_len); +} +bool http_conn::add_content_type() +{ + return add_response("Content-Type:%s\r\n", "text/html"); +} +bool http_conn::add_linger() +{ + return add_response("Connection:%s\r\n", (m_linger == true) ? "keep-alive" : "close"); +} +bool http_conn::add_blank_line() +{ + return add_response("%s", "\r\n"); +} +bool http_conn::add_content(const char *content) +{ + return add_response("%s", content); +} +bool http_conn::process_write(HTTP_CODE ret) +{ + switch (ret) + { + case INTERNAL_ERROR: + { + add_status_line(500, error_500_title); + add_headers(strlen(error_500_form)); + if (!add_content(error_500_form)) + return false; + break; + } + case BAD_REQUEST: + { + add_status_line(404, error_404_title); + add_headers(strlen(error_404_form)); + if (!add_content(error_404_form)) + return false; + break; + } + case FORBIDDEN_REQUEST: + { + add_status_line(403, error_403_title); + add_headers(strlen(error_403_form)); + if (!add_content(error_403_form)) + return false; + break; + } + case FILE_REQUEST: + { + add_status_line(200, ok_200_title); + if (m_file_stat.st_size != 0) + { + add_headers(m_file_stat.st_size); + m_iv[0].iov_base = m_write_buf; + m_iv[0].iov_len = m_write_idx; + m_iv[1].iov_base = m_file_address; + m_iv[1].iov_len = m_file_stat.st_size; + m_iv_count = 2; + bytes_to_send = m_write_idx + m_file_stat.st_size; + return true; + } + else + { + const char *ok_string = ""; + add_headers(strlen(ok_string)); + if (!add_content(ok_string)) + return false; + } + } + default: + return false; + } + m_iv[0].iov_base = m_write_buf; + m_iv[0].iov_len = m_write_idx; + m_iv_count = 1; + bytes_to_send = m_write_idx; + return true; +} +void http_conn::process() +{ + HTTP_CODE read_ret = process_read(); + if (read_ret == NO_REQUEST) + { + modfd(m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode); + return; + } + bool write_ret = process_write(read_ret); + if (!write_ret) + { + close_conn(); + } + modfd(m_epollfd, m_sockfd, EPOLLOUT, m_TRIGMode); +} diff --git a/note/TinyWebServer-master/http/http_conn.h b/note/TinyWebServer-master/http/http_conn.h new file mode 100644 index 0000000000..b36d36524a --- /dev/null +++ b/note/TinyWebServer-master/http/http_conn.h @@ -0,0 +1,152 @@ +#ifndef HTTPCONNECTION_H +#define HTTPCONNECTION_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../lock/locker.h" +#include "../CGImysql/sql_connection_pool.h" +#include "../timer/lst_timer.h" +#include "../log/log.h" + +class http_conn +{ +public: + static const int FILENAME_LEN = 200; + static const int READ_BUFFER_SIZE = 2048; + static const int WRITE_BUFFER_SIZE = 1024; + enum METHOD + { + GET = 0, + POST, + HEAD, + PUT, + DELETE, + TRACE, + OPTIONS, + CONNECT, + PATH + }; + enum CHECK_STATE + { + CHECK_STATE_REQUESTLINE = 0, + CHECK_STATE_HEADER, + CHECK_STATE_CONTENT + }; + enum HTTP_CODE + { + NO_REQUEST, + GET_REQUEST, + BAD_REQUEST, + NO_RESOURCE, + FORBIDDEN_REQUEST, + FILE_REQUEST, + INTERNAL_ERROR, + CLOSED_CONNECTION + }; + enum LINE_STATUS + { + LINE_OK = 0, + LINE_BAD, + LINE_OPEN + }; + +public: + http_conn() {} + ~http_conn() {} + +public: + void init(int sockfd, const sockaddr_in &addr, char *, int, int, string user, string passwd, string sqlname); + void close_conn(bool real_close = true); + void process(); + bool read_once(); + bool write(); + sockaddr_in *get_address() + { + return &m_address; + } + void initmysql_result(connection_pool *connPool); + int timer_flag; + int improv; + + +private: + void init(); + HTTP_CODE process_read(); + bool process_write(HTTP_CODE ret); + HTTP_CODE parse_request_line(char *text); + HTTP_CODE parse_headers(char *text); + HTTP_CODE parse_content(char *text); + HTTP_CODE do_request(); + char *get_line() { return m_read_buf + m_start_line; }; + LINE_STATUS parse_line(); + void unmap(); + bool add_response(const char *format, ...); + bool add_content(const char *content); + bool add_status_line(int status, const char *title); + bool add_headers(int content_length); + bool add_content_type(); + bool add_content_length(int content_length); + bool add_linger(); + bool add_blank_line(); + +public: + static int m_epollfd; + static int m_user_count; + MYSQL *mysql; + int m_state; //读为0, 写为1 + +private: + int m_sockfd; + sockaddr_in m_address; + char m_read_buf[READ_BUFFER_SIZE]; + int m_read_idx; + int m_checked_idx; + int m_start_line; + char m_write_buf[WRITE_BUFFER_SIZE]; + int m_write_idx; + CHECK_STATE m_check_state; + METHOD m_method; + char m_real_file[FILENAME_LEN]; + char *m_url; + char *m_version; + char *m_host; + int m_content_length; + bool m_linger; + char *m_file_address; + struct stat m_file_stat; + struct iovec m_iv[2]; + int m_iv_count; + int cgi; //是否启用的POST + char *m_string; //存储请求头数据 + int bytes_to_send; + int bytes_have_send; + char *doc_root; + + map m_users; + int m_TRIGMode; + int m_close_log; + + char sql_user[100]; + char sql_passwd[100]; + char sql_name[100]; +}; + +#endif diff --git a/note/TinyWebServer-master/lock/README.md b/note/TinyWebServer-master/lock/README.md new file mode 100644 index 0000000000..5b3a59b706 --- /dev/null +++ b/note/TinyWebServer-master/lock/README.md @@ -0,0 +1,11 @@ + +线程同步机制包装类 +=============== +多线程同步,确保任一时刻只能有一个线程能进入关键代码段. +> * 信号量 +> * 互斥锁 +> * 条件变量 + + + + diff --git a/note/TinyWebServer-master/lock/locker.h b/note/TinyWebServer-master/lock/locker.h new file mode 100644 index 0000000000..de38a06eaf --- /dev/null +++ b/note/TinyWebServer-master/lock/locker.h @@ -0,0 +1,115 @@ +#ifndef LOCKER_H +#define LOCKER_H + +#include +#include +#include + +class sem +{ +public: + sem() + { + if (sem_init(&m_sem, 0, 0) != 0) + { + throw std::exception(); + } + } + sem(int num) + { + if (sem_init(&m_sem, 0, num) != 0) + { + throw std::exception(); + } + } + ~sem() + { + sem_destroy(&m_sem); + } + bool wait() + { + return sem_wait(&m_sem) == 0; + } + bool post() + { + return sem_post(&m_sem) == 0; + } + +private: + sem_t m_sem; +}; +class locker +{ +public: + locker() + { + if (pthread_mutex_init(&m_mutex, NULL) != 0) + { + throw std::exception(); + } + } + ~locker() + { + pthread_mutex_destroy(&m_mutex); + } + bool lock() + { + return pthread_mutex_lock(&m_mutex) == 0; + } + bool unlock() + { + return pthread_mutex_unlock(&m_mutex) == 0; + } + pthread_mutex_t *get() + { + return &m_mutex; + } + +private: + pthread_mutex_t m_mutex; +}; +class cond +{ +public: + cond() + { + if (pthread_cond_init(&m_cond, NULL) != 0) + { + //pthread_mutex_destroy(&m_mutex); + throw std::exception(); + } + } + ~cond() + { + pthread_cond_destroy(&m_cond); + } + bool wait(pthread_mutex_t *m_mutex) + { + int ret = 0; + //pthread_mutex_lock(&m_mutex); + ret = pthread_cond_wait(&m_cond, m_mutex); + //pthread_mutex_unlock(&m_mutex); + return ret == 0; + } + bool timewait(pthread_mutex_t *m_mutex, struct timespec t) + { + int ret = 0; + //pthread_mutex_lock(&m_mutex); + ret = pthread_cond_timedwait(&m_cond, m_mutex, &t); + //pthread_mutex_unlock(&m_mutex); + return ret == 0; + } + bool signal() + { + return pthread_cond_signal(&m_cond) == 0; + } + bool broadcast() + { + return pthread_cond_broadcast(&m_cond) == 0; + } + +private: + //static pthread_mutex_t m_mutex; + pthread_cond_t m_cond; +}; +#endif diff --git a/note/TinyWebServer-master/log/README.md b/note/TinyWebServer-master/log/README.md new file mode 100644 index 0000000000..d6dec7185a --- /dev/null +++ b/note/TinyWebServer-master/log/README.md @@ -0,0 +1,9 @@ + +同步/异步日志系统 +=============== +同步/异步日志系统主要涉及了两个模块,一个是日志模块,一个是阻塞队列模块,其中加入阻塞队列模块主要是解决异步写入日志做准备. +> * 自定义阻塞队列 +> * 单例模式创建日志 +> * 同步日志 +> * 异步日志 +> * 实现按天、超行分类 diff --git a/note/TinyWebServer-master/log/block_queue.h b/note/TinyWebServer-master/log/block_queue.h new file mode 100644 index 0000000000..34c77bd507 --- /dev/null +++ b/note/TinyWebServer-master/log/block_queue.h @@ -0,0 +1,212 @@ +/************************************************************* +*循环数组实现的阻塞队列,m_back = (m_back + 1) % m_max_size; +*线程安全,每个操作前都要先加互斥锁,操作完后,再解锁 +**************************************************************/ + +#ifndef BLOCK_QUEUE_H +#define BLOCK_QUEUE_H + +#include +#include +#include +#include +#include "../lock/locker.h" +using namespace std; + +template +class block_queue +{ +public: + block_queue(int max_size = 1000) + { + if (max_size <= 0) + { + exit(-1); + } + + m_max_size = max_size; + m_array = new T[max_size]; + m_size = 0; + m_front = -1; + m_back = -1; + } + + void clear() + { + m_mutex.lock(); + m_size = 0; + m_front = -1; + m_back = -1; + m_mutex.unlock(); + } + + ~block_queue() + { + m_mutex.lock(); + if (m_array != NULL) + delete [] m_array; + + m_mutex.unlock(); + } + //判断队列是否满了 + bool full() + { + m_mutex.lock(); + if (m_size >= m_max_size) + { + + m_mutex.unlock(); + return true; + } + m_mutex.unlock(); + return false; + } + //判断队列是否为空 + bool empty() + { + m_mutex.lock(); + if (0 == m_size) + { + m_mutex.unlock(); + return true; + } + m_mutex.unlock(); + return false; + } + //返回队首元素 + bool front(T &value) + { + m_mutex.lock(); + if (0 == m_size) + { + m_mutex.unlock(); + return false; + } + value = m_array[m_front]; + m_mutex.unlock(); + return true; + } + //返回队尾元素 + bool back(T &value) + { + m_mutex.lock(); + if (0 == m_size) + { + m_mutex.unlock(); + return false; + } + value = m_array[m_back]; + m_mutex.unlock(); + return true; + } + + int size() + { + int tmp = 0; + + m_mutex.lock(); + tmp = m_size; + + m_mutex.unlock(); + return tmp; + } + + int max_size() + { + int tmp = 0; + + m_mutex.lock(); + tmp = m_max_size; + + m_mutex.unlock(); + return tmp; + } + //往队列添加元素,需要将所有使用队列的线程先唤醒 + //当有元素push进队列,相当于生产者生产了一个元素 + //若当前没有线程等待条件变量,则唤醒无意义 + bool push(const T &item) + { + + m_mutex.lock(); + if (m_size >= m_max_size) + { + + m_cond.broadcast(); + m_mutex.unlock(); + return false; + } + + m_back = (m_back + 1) % m_max_size; + m_array[m_back] = item; + + m_size++; + + m_cond.broadcast(); + m_mutex.unlock(); + return true; + } + //pop时,如果当前队列没有元素,将会等待条件变量 + bool pop(T &item) + { + + m_mutex.lock(); + while (m_size <= 0) + { + + if (!m_cond.wait(m_mutex.get())) + { + m_mutex.unlock(); + return false; + } + } + + m_front = (m_front + 1) % m_max_size; + item = m_array[m_front]; + m_size--; + m_mutex.unlock(); + return true; + } + + //增加了超时处理 + bool pop(T &item, int ms_timeout) + { + struct timespec t = {0, 0}; + struct timeval now = {0, 0}; + gettimeofday(&now, NULL); + m_mutex.lock(); + if (m_size <= 0) + { + t.tv_sec = now.tv_sec + ms_timeout / 1000; + t.tv_nsec = (ms_timeout % 1000) * 1000; + if (!m_cond.timewait(m_mutex.get(), t)) + { + m_mutex.unlock(); + return false; + } + } + + if (m_size <= 0) + { + m_mutex.unlock(); + return false; + } + + m_front = (m_front + 1) % m_max_size; + item = m_array[m_front]; + m_size--; + m_mutex.unlock(); + return true; + } + +private: + locker m_mutex; + cond m_cond; + + T *m_array; + int m_size; + int m_max_size; + int m_front; + int m_back; +}; + +#endif diff --git a/note/TinyWebServer-master/log/log.cpp b/note/TinyWebServer-master/log/log.cpp new file mode 100644 index 0000000000..65e8ad46b8 --- /dev/null +++ b/note/TinyWebServer-master/log/log.cpp @@ -0,0 +1,164 @@ +#include +#include +#include +#include +#include "log.h" +#include +using namespace std; + +Log::Log() +{ + m_count = 0; + m_is_async = false; +} + +Log::~Log() +{ + if (m_fp != NULL) + { + fclose(m_fp); + } +} +//异步需要设置阻塞队列的长度,同步不需要设置 +bool Log::init(const char *file_name, int close_log, int log_buf_size, int split_lines, int max_queue_size) +{ + //如果设置了max_queue_size,则设置为异步 + if (max_queue_size >= 1) + { + m_is_async = true; + m_log_queue = new block_queue(max_queue_size); + pthread_t tid; + //flush_log_thread为回调函数,这里表示创建线程异步写日志 + pthread_create(&tid, NULL, flush_log_thread, NULL); + } + + m_close_log = close_log; + m_log_buf_size = log_buf_size; + m_buf = new char[m_log_buf_size]; + memset(m_buf, '\0', m_log_buf_size); + m_split_lines = split_lines; + + time_t t = time(NULL); + struct tm *sys_tm = localtime(&t); + struct tm my_tm = *sys_tm; + + + const char *p = strrchr(file_name, '/'); + char log_full_name[256] = {0}; + + if (p == NULL) + { + snprintf(log_full_name, 255, "%d_%02d_%02d_%s", my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday, file_name); + } + else + { + strcpy(log_name, p + 1); + strncpy(dir_name, file_name, p - file_name + 1); + snprintf(log_full_name, 255, "%s%d_%02d_%02d_%s", dir_name, my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday, log_name); + } + + m_today = my_tm.tm_mday; + + m_fp = fopen(log_full_name, "a"); + if (m_fp == NULL) + { + return false; + } + + return true; +} + +void Log::write_log(int level, const char *format, ...) +{ + struct timeval now = {0, 0}; + gettimeofday(&now, NULL); + time_t t = now.tv_sec; + struct tm *sys_tm = localtime(&t); + struct tm my_tm = *sys_tm; + char s[16] = {0}; + switch (level) + { + case 0: + strcpy(s, "[debug]:"); + break; + case 1: + strcpy(s, "[info]:"); + break; + case 2: + strcpy(s, "[warn]:"); + break; + case 3: + strcpy(s, "[erro]:"); + break; + default: + strcpy(s, "[info]:"); + break; + } + //写入一个log,对m_count++, m_split_lines最大行数 + m_mutex.lock(); + m_count++; + + if (m_today != my_tm.tm_mday || m_count % m_split_lines == 0) //everyday log + { + + char new_log[256] = {0}; + fflush(m_fp); + fclose(m_fp); + char tail[16] = {0}; + + snprintf(tail, 16, "%d_%02d_%02d_", my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday); + + if (m_today != my_tm.tm_mday) + { + snprintf(new_log, 255, "%s%s%s", dir_name, tail, log_name); + m_today = my_tm.tm_mday; + m_count = 0; + } + else + { + snprintf(new_log, 255, "%s%s%s.%lld", dir_name, tail, log_name, m_count / m_split_lines); + } + m_fp = fopen(new_log, "a"); + } + + m_mutex.unlock(); + + va_list valst; + va_start(valst, format); + + string log_str; + m_mutex.lock(); + + //写入的具体时间内容格式 + int n = snprintf(m_buf, 48, "%d-%02d-%02d %02d:%02d:%02d.%06ld %s ", + my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday, + my_tm.tm_hour, my_tm.tm_min, my_tm.tm_sec, now.tv_usec, s); + + int m = vsnprintf(m_buf + n, m_log_buf_size - 1, format, valst); + m_buf[n + m] = '\n'; + m_buf[n + m + 1] = '\0'; + log_str = m_buf; + + m_mutex.unlock(); + + if (m_is_async && !m_log_queue->full()) + { + m_log_queue->push(log_str); + } + else + { + m_mutex.lock(); + fputs(log_str.c_str(), m_fp); + m_mutex.unlock(); + } + + va_end(valst); +} + +void Log::flush(void) +{ + m_mutex.lock(); + //强制刷新写入流缓冲区 + fflush(m_fp); + m_mutex.unlock(); +} diff --git a/note/TinyWebServer-master/log/log.h b/note/TinyWebServer-master/log/log.h new file mode 100644 index 0000000000..64972af402 --- /dev/null +++ b/note/TinyWebServer-master/log/log.h @@ -0,0 +1,69 @@ +#ifndef LOG_H +#define LOG_H + +#include +#include +#include +#include +#include +#include "block_queue.h" + +using namespace std; + +class Log +{ +public: + //C++11以后,使用局部变量懒汉不用加锁 + static Log *get_instance() + { + static Log instance; + return &instance; + } + + static void *flush_log_thread(void *args) + { + Log::get_instance()->async_write_log(); + } + //可选择的参数有日志文件、日志缓冲区大小、最大行数以及最长日志条队列 + bool init(const char *file_name, int close_log, int log_buf_size = 8192, int split_lines = 5000000, int max_queue_size = 0); + + void write_log(int level, const char *format, ...); + + void flush(void); + +private: + Log(); + virtual ~Log(); + void *async_write_log() + { + string single_log; + //从阻塞队列中取出一个日志string,写入文件 + while (m_log_queue->pop(single_log)) + { + m_mutex.lock(); + fputs(single_log.c_str(), m_fp); + m_mutex.unlock(); + } + } + +private: + char dir_name[128]; //路径名 + char log_name[128]; //log文件名 + int m_split_lines; //日志最大行数 + int m_log_buf_size; //日志缓冲区大小 + long long m_count; //日志行数记录 + int m_today; //因为按天分类,记录当前时间是那一天 + FILE *m_fp; //打开log的文件指针 + char *m_buf; + block_queue *m_log_queue; //阻塞队列 + bool m_is_async; //是否同步标志位 + locker m_mutex; + int m_close_log; //关闭日志 +}; + +#define LOG_DEBUG(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(0, format, ##__VA_ARGS__); Log::get_instance()->flush();} +#define LOG_INFO(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(1, format, ##__VA_ARGS__); Log::get_instance()->flush();} +#define LOG_WARN(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(2, format, ##__VA_ARGS__); Log::get_instance()->flush();} +#define LOG_ERROR(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(3, format, ##__VA_ARGS__); Log::get_instance()->flush();} + +#endif diff --git a/note/TinyWebServer-master/main.cpp b/note/TinyWebServer-master/main.cpp new file mode 100644 index 0000000000..19a4fff723 --- /dev/null +++ b/note/TinyWebServer-master/main.cpp @@ -0,0 +1,41 @@ +#include "config.h" + +int main(int argc, char *argv[]) +{ + //需要修改的数据库信息,登录名,密码,库名 + string user = "root"; + string passwd = "root"; + string databasename = "qgydb"; + + //命令行解析 + Config config; + config.parse_arg(argc, argv); + + WebServer server; + + //初始化 + server.init(config.PORT, user, passwd, databasename, config.LOGWrite, + config.OPT_LINGER, config.TRIGMode, config.sql_num, config.thread_num, + config.close_log, config.actor_model); + + + //日志 + server.log_write(); + + //数据库 + server.sql_pool(); + + //线程池 + server.thread_pool(); + + //触发模式 + server.trig_mode(); + + //监听 + server.eventListen(); + + //运行 + server.eventLoop(); + + return 0; +} \ No newline at end of file diff --git a/note/TinyWebServer-master/makefile b/note/TinyWebServer-master/makefile new file mode 100644 index 0000000000..f3e877431d --- /dev/null +++ b/note/TinyWebServer-master/makefile @@ -0,0 +1,15 @@ +CXX ?= g++ + +DEBUG ?= 1 +ifeq ($(DEBUG), 1) + CXXFLAGS += -g +else + CXXFLAGS += -O2 + +endif + +server: main.cpp ./timer/lst_timer.cpp ./http/http_conn.cpp ./log/log.cpp ./CGImysql/sql_connection_pool.cpp webserver.cpp config.cpp + $(CXX) -o server $^ $(CXXFLAGS) -lpthread -lmysqlclient + +clean: + rm -r server diff --git a/note/TinyWebServer-master/root/README.md b/note/TinyWebServer-master/root/README.md new file mode 100644 index 0000000000..d6fdb70c5b --- /dev/null +++ b/note/TinyWebServer-master/root/README.md @@ -0,0 +1,10 @@ +界面跳转 +=============== +对html中action行为设置标志位,将method设置为POST +> * 0 注册 +> * 1 登录 +> * 2 登录检测 +> * 3 注册检测 +> * 5 请求图片 +> * 6 请求视频 +> * 7 关注我 diff --git a/note/TinyWebServer-master/root/fans.html b/note/TinyWebServer-master/root/fans.html new file mode 100644 index 0000000000..b7c6179c62 --- /dev/null +++ b/note/TinyWebServer-master/root/fans.html @@ -0,0 +1,15 @@ + + + + + awsl + +
+
+
嘿嘿,你来啦,更多资料,请关注 “两猿社” 喔.
+
+ +
+
+
+ diff --git a/note/TinyWebServer-master/root/favicon.ico b/note/TinyWebServer-master/root/favicon.ico new file mode 100644 index 0000000000..01de9ff379 Binary files /dev/null and b/note/TinyWebServer-master/root/favicon.ico differ diff --git a/note/TinyWebServer-master/root/frame.jpg b/note/TinyWebServer-master/root/frame.jpg new file mode 100644 index 0000000000..1d2b523364 Binary files /dev/null and b/note/TinyWebServer-master/root/frame.jpg differ diff --git a/note/TinyWebServer-master/root/judge.html b/note/TinyWebServer-master/root/judge.html new file mode 100644 index 0000000000..82fc2d7d44 --- /dev/null +++ b/note/TinyWebServer-master/root/judge.html @@ -0,0 +1,26 @@ + + + + + WebServer + + +
+
+
欢迎访问
+
+
+
+
+
+
+
+
+
+ + + + + + + diff --git a/note/TinyWebServer-master/root/log.html b/note/TinyWebServer-master/root/log.html new file mode 100644 index 0000000000..b0d95e0e9e --- /dev/null +++ b/note/TinyWebServer-master/root/log.html @@ -0,0 +1,21 @@ + + + + + Sign in + + +
+
+
登录
+
+ + + + diff --git a/note/TinyWebServer-master/root/logError.html b/note/TinyWebServer-master/root/logError.html new file mode 100644 index 0000000000..a679db85d5 --- /dev/null +++ b/note/TinyWebServer-master/root/logError.html @@ -0,0 +1,23 @@ + + + + + Sign in + + +
+
+
登录
+
+ + + + diff --git a/note/TinyWebServer-master/root/login.gif b/note/TinyWebServer-master/root/login.gif new file mode 100644 index 0000000000..1aa39c2eb6 Binary files /dev/null and b/note/TinyWebServer-master/root/login.gif differ diff --git a/note/TinyWebServer-master/root/loginnew.gif b/note/TinyWebServer-master/root/loginnew.gif new file mode 100644 index 0000000000..9f9e78d2c9 Binary files /dev/null and b/note/TinyWebServer-master/root/loginnew.gif differ diff --git a/note/TinyWebServer-master/root/picture.gif b/note/TinyWebServer-master/root/picture.gif new file mode 100644 index 0000000000..297a434005 Binary files /dev/null and b/note/TinyWebServer-master/root/picture.gif differ diff --git a/note/TinyWebServer-master/root/picture.html b/note/TinyWebServer-master/root/picture.html new file mode 100644 index 0000000000..163b4926f5 --- /dev/null +++ b/note/TinyWebServer-master/root/picture.html @@ -0,0 +1,15 @@ + + + + + awsl + +
+
+
你居然想看图,不想关注我
+
+ +
+
+
+ diff --git a/note/TinyWebServer-master/root/register.gif b/note/TinyWebServer-master/root/register.gif new file mode 100644 index 0000000000..296a3b03c2 Binary files /dev/null and b/note/TinyWebServer-master/root/register.gif differ diff --git a/note/TinyWebServer-master/root/register.html b/note/TinyWebServer-master/root/register.html new file mode 100644 index 0000000000..4fd8b9eabc --- /dev/null +++ b/note/TinyWebServer-master/root/register.html @@ -0,0 +1,20 @@ + + + + + Sign up + + +
+
+
注册
+
+ + + diff --git a/note/TinyWebServer-master/root/registerError.html b/note/TinyWebServer-master/root/registerError.html new file mode 100644 index 0000000000..9b7df516dc --- /dev/null +++ b/note/TinyWebServer-master/root/registerError.html @@ -0,0 +1,22 @@ + + + + + Sign up + + +
+
+
注册
+
+ + + + diff --git a/note/TinyWebServer-master/root/registernew.gif b/note/TinyWebServer-master/root/registernew.gif new file mode 100644 index 0000000000..47d06da053 Binary files /dev/null and b/note/TinyWebServer-master/root/registernew.gif differ diff --git a/note/TinyWebServer-master/root/test1.jpg b/note/TinyWebServer-master/root/test1.jpg new file mode 100644 index 0000000000..856aa8f800 Binary files /dev/null and b/note/TinyWebServer-master/root/test1.jpg differ diff --git a/note/TinyWebServer-master/root/video.gif b/note/TinyWebServer-master/root/video.gif new file mode 100644 index 0000000000..08ec1c6cb3 Binary files /dev/null and b/note/TinyWebServer-master/root/video.gif differ diff --git a/note/TinyWebServer-master/root/video.html b/note/TinyWebServer-master/root/video.html new file mode 100644 index 0000000000..dcc63e02e6 --- /dev/null +++ b/note/TinyWebServer-master/root/video.html @@ -0,0 +1,17 @@ + + + + + awsl + +
+
+
你居然想看视频,不想关注我
+
+ +
+
+
+ diff --git a/note/TinyWebServer-master/root/welcome.html b/note/TinyWebServer-master/root/welcome.html new file mode 100644 index 0000000000..d37d168d81 --- /dev/null +++ b/note/TinyWebServer-master/root/welcome.html @@ -0,0 +1,27 @@ + + + + + WebServer + + +
+
+
是时候做出选择了
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + diff --git a/note/TinyWebServer-master/root/xxx.jpg b/note/TinyWebServer-master/root/xxx.jpg new file mode 100644 index 0000000000..1b118396fd Binary files /dev/null and b/note/TinyWebServer-master/root/xxx.jpg differ diff --git a/note/TinyWebServer-master/root/xxx.mp4 b/note/TinyWebServer-master/root/xxx.mp4 new file mode 100644 index 0000000000..4f4fbba54c Binary files /dev/null and b/note/TinyWebServer-master/root/xxx.mp4 differ diff --git a/note/TinyWebServer-master/test_presure/README.md b/note/TinyWebServer-master/test_presure/README.md new file mode 100644 index 0000000000..5885d1d2e2 --- /dev/null +++ b/note/TinyWebServer-master/test_presure/README.md @@ -0,0 +1,33 @@ +服务器压力测试 +=============== +Webbench是有名的网站压力测试工具,它是由[Lionbridge](http://www.lionbridge.com)公司开发。 + +> * 测试处在相同硬件上,不同服务的性能以及不同硬件上同一个服务的运行状况。 +> * 展示服务器的两项内容:每秒钟响应请求数和每秒钟传输数据量。 + + + + +测试规则 +------------ +* 测试示例 + + ```C++ + webbench -c 500 -t 30 http://127.0.0.1/phpionfo.php + ``` +* 参数 + +> * `-c` 表示客户端数 +> * `-t` 表示时间 + + +测试结果 +--------- +Webbench对服务器进行压力测试,经压力测试可以实现上万的并发连接. +> * 并发连接总数:10500 +> * 访问服务器时间:5s +> * 每秒钟响应请求数:552852 pages/min +> * 每秒钟传输数据量:1031990 bytes/sec +> * 所有访问均成功 + +
diff --git a/note/TinyWebServer-master/test_presure/webbench-1.5/COPYRIGHT b/note/TinyWebServer-master/test_presure/webbench-1.5/COPYRIGHT new file mode 100644 index 0000000000..9060ce8208 --- /dev/null +++ b/note/TinyWebServer-master/test_presure/webbench-1.5/COPYRIGHT @@ -0,0 +1 @@ +debian/copyright \ No newline at end of file diff --git a/note/TinyWebServer-master/test_presure/webbench-1.5/ChangeLog b/note/TinyWebServer-master/test_presure/webbench-1.5/ChangeLog new file mode 100644 index 0000000000..d526672ce2 --- /dev/null +++ b/note/TinyWebServer-master/test_presure/webbench-1.5/ChangeLog @@ -0,0 +1 @@ +debian/changelog \ No newline at end of file diff --git a/note/TinyWebServer-master/test_presure/webbench-1.5/Makefile b/note/TinyWebServer-master/test_presure/webbench-1.5/Makefile new file mode 100644 index 0000000000..cc60b68284 --- /dev/null +++ b/note/TinyWebServer-master/test_presure/webbench-1.5/Makefile @@ -0,0 +1,40 @@ +CFLAGS?= -Wall -ggdb -W -O +CC?= gcc +LIBS?= +LDFLAGS?= +PREFIX?= /usr/local +VERSION=1.5 +TMPDIR=/tmp/webbench-$(VERSION) + +all: webbench tags + +tags: *.c + -ctags *.c + +install: webbench + install -s webbench $(DESTDIR)$(PREFIX)/bin + install -m 644 webbench.1 $(DESTDIR)$(PREFIX)/man/man1 + install -d $(DESTDIR)$(PREFIX)/share/doc/webbench + install -m 644 debian/copyright $(DESTDIR)$(PREFIX)/share/doc/webbench + install -m 644 debian/changelog $(DESTDIR)$(PREFIX)/share/doc/webbench + +webbench: webbench.o Makefile + $(CC) $(CFLAGS) $(LDFLAGS) -o webbench webbench.o $(LIBS) + +clean: + -rm -f *.o webbench *~ core *.core tags + +tar: clean + -debian/rules clean + rm -rf $(TMPDIR) + install -d $(TMPDIR) + cp -p Makefile webbench.c socket.c webbench.1 $(TMPDIR) + install -d $(TMPDIR)/debian + -cp -p debian/* $(TMPDIR)/debian + ln -sf debian/copyright $(TMPDIR)/COPYRIGHT + ln -sf debian/changelog $(TMPDIR)/ChangeLog + -cd $(TMPDIR) && cd .. && tar cozf webbench-$(VERSION).tar.gz webbench-$(VERSION) + +webbench.o: webbench.c socket.c Makefile + +.PHONY: clean install all tar diff --git a/note/TinyWebServer-master/test_presure/webbench-1.5/debian/changelog b/note/TinyWebServer-master/test_presure/webbench-1.5/debian/changelog new file mode 100644 index 0000000000..292178807c --- /dev/null +++ b/note/TinyWebServer-master/test_presure/webbench-1.5/debian/changelog @@ -0,0 +1,56 @@ +webbench (1.5) unstable; urgency=low + + * allow building with both Gnu and BSD make + + -- Radim Kolar Fri, Jun 25 12:00:20 CEST 2004 + +webbench (1.4) unstable; urgency=low + + * check if url is not too long + * report correct program version number + * use yield() when waiting for test start + * corrected error codes + * check availability of test server first + * do not abort test if first request failed + * report when some childrens are dead. + * use alarm, not time() for lower syscal use by bench + * use mode 644 for installed doc + * makefile cleaned for better freebsd ports integration + + -- Radim Kolar Thu, 15 Jan 2004 11:15:52 +0100 + +webbench (1.3) unstable; urgency=low + + * Build fixes for freeBSD + * Default benchmark time 60 -> 30 + * generate tar with subdirectory + * added to freeBSD ports collection + + -- Radim Kolar Mon, 12 Jan 2004 17:00:24 +0100 + +webbench (1.2) unstable; urgency=low + + * Only debian-related bugfixes + * Updated Debian/rules + * Adapted to fit new directory system + * moved from debstd to dh_* + + -- Radim Kolar Fri, 18 Jan 2002 12:33:04 +0100 + +webbench (1.1) unstable; urgency=medium + + * Program debianized + * added support for multiple methods (GET, HEAD, OPTIONS, TRACE) + * added support for multiple HTTP versions (0.9 -- 1.1) + * added long options + * added multiple clients + * wait for start of second before test + * test time can be specified + * better error checking when reading reply from server + * FIX: tests was one second longer than expected + + -- Radim Kolar Thu, 16 Sep 1999 18:48:00 +0200 + +Local variables: +mode: debian-changelog +End: diff --git a/note/TinyWebServer-master/test_presure/webbench-1.5/debian/control b/note/TinyWebServer-master/test_presure/webbench-1.5/debian/control new file mode 100644 index 0000000000..e1051f2960 --- /dev/null +++ b/note/TinyWebServer-master/test_presure/webbench-1.5/debian/control @@ -0,0 +1,14 @@ +Source: webbench +Section: web +Priority: extra +Maintainer: Radim Kolar +Build-Depends: debhelper (>> 3.0.0) +Standards-Version: 3.5.2 + +Package: webbench +Architecture: any +Depends: ${shlibs:Depends} +Description: Simple forking Web benchmark + webbench is very simple program for benchmarking WWW or Proxy servers. + Uses fork() for simulating multiple clients load. Can use HTTP 0.9 - 1.1 + requests, but Keep-Alive connections are not supported. diff --git a/note/TinyWebServer-master/test_presure/webbench-1.5/debian/copyright b/note/TinyWebServer-master/test_presure/webbench-1.5/debian/copyright new file mode 100644 index 0000000000..7c42aa26f1 --- /dev/null +++ b/note/TinyWebServer-master/test_presure/webbench-1.5/debian/copyright @@ -0,0 +1,6 @@ +Webbench was written by Radim Kolar 1997-2004 (hsn@netmag.cz). + +UNIX sockets code (socket.c) taken from popclient 1.5 4/1/94 +public domain code, created by Virginia Tech Computing Center. + +Copyright: GPL (see /usr/share/common-licenses/GPL) diff --git a/note/TinyWebServer-master/test_presure/webbench-1.5/debian/dirs b/note/TinyWebServer-master/test_presure/webbench-1.5/debian/dirs new file mode 100644 index 0000000000..1e881eda3a --- /dev/null +++ b/note/TinyWebServer-master/test_presure/webbench-1.5/debian/dirs @@ -0,0 +1 @@ +usr/bin \ No newline at end of file diff --git a/note/TinyWebServer-master/test_presure/webbench-1.5/debian/rules b/note/TinyWebServer-master/test_presure/webbench-1.5/debian/rules new file mode 100644 index 0000000000..e6803a1123 --- /dev/null +++ b/note/TinyWebServer-master/test_presure/webbench-1.5/debian/rules @@ -0,0 +1,64 @@ +#!/usr/bin/make -f +# Sample debian/rules that uses debhelper. +# GNU copyright 1997 to 1999 by Joey Hess. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +# This is the debhelper compatability version to use. +export DH_COMPAT=3 + +configure: configure-stamp +configure-stamp: + dh_testdir + touch configure-stamp + +build: configure-stamp build-stamp +build-stamp: + dh_testdir + $(MAKE) + touch build-stamp + +clean: + dh_testdir + rm -f build-stamp configure-stamp + + # Add here commands to clean up after the build process. + -$(MAKE) clean + + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + + # Add here commands to install the package into debian/webbench. + $(MAKE) install DESTDIR=$(CURDIR)/debian/webbench + + +# Build architecture-independent files here. +binary-indep: build install +# We have nothing to do by default. + +# Build architecture-dependent files here. +binary-arch: build install + dh_testdir + dh_testroot + dh_installdocs + dh_installman webbench.1 + dh_installchangelogs + dh_link + dh_strip + dh_compress + dh_fixperms +# dh_makeshlibs + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install configure diff --git a/note/TinyWebServer-master/test_presure/webbench-1.5/socket.c b/note/TinyWebServer-master/test_presure/webbench-1.5/socket.c new file mode 100644 index 0000000000..b7af54a0ee --- /dev/null +++ b/note/TinyWebServer-master/test_presure/webbench-1.5/socket.c @@ -0,0 +1,58 @@ +/* $Id: socket.c 1.1 1995/01/01 07:11:14 cthuang Exp $ + * + * This module has been modified by Radim Kolar for OS/2 emx + */ + +/*********************************************************************** + module: socket.c + program: popclient + SCCS ID: @(#)socket.c 1.5 4/1/94 + programmer: Virginia Tech Computing Center + compiler: DEC RISC C compiler (Ultrix 4.1) + environment: DEC Ultrix 4.3 + description: UNIX sockets code. + ***********************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int Socket(const char *host, int clientPort) +{ + int sock; + unsigned long inaddr; + struct sockaddr_in ad; + struct hostent *hp; + + memset(&ad, 0, sizeof(ad)); + ad.sin_family = AF_INET; + + inaddr = inet_addr(host); + if (inaddr != INADDR_NONE) + memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr)); + else + { + hp = gethostbyname(host); + if (hp == NULL) + return -1; + memcpy(&ad.sin_addr, hp->h_addr, hp->h_length); + } + ad.sin_port = htons(clientPort); + + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) + return sock; + if (connect(sock, (struct sockaddr *)&ad, sizeof(ad)) < 0) + return -1; + return sock; +} + diff --git a/note/TinyWebServer-master/test_presure/webbench-1.5/tags b/note/TinyWebServer-master/test_presure/webbench-1.5/tags new file mode 100644 index 0000000000..b1ab2149c2 --- /dev/null +++ b/note/TinyWebServer-master/test_presure/webbench-1.5/tags @@ -0,0 +1,35 @@ +!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/ +!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/ +!_TAG_PROGRAM_AUTHOR Darren Hiebert /dhiebert@users.sourceforge.net/ +!_TAG_PROGRAM_NAME Exuberant Ctags // +!_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/ +!_TAG_PROGRAM_VERSION 5.9~svn20110310 // +METHOD_GET webbench.c 35;" d file: +METHOD_HEAD webbench.c 36;" d file: +METHOD_OPTIONS webbench.c 37;" d file: +METHOD_TRACE webbench.c 38;" d file: +PROGRAM_VERSION webbench.c 39;" d file: +REQUEST_SIZE webbench.c 50;" d file: +Socket socket.c /^int Socket(const char *host, int clientPort)$/;" f +alarm_handler webbench.c /^static void alarm_handler(int signal)$/;" f file: +bench webbench.c /^static int bench(void)$/;" f file: +benchcore webbench.c /^void benchcore(const char *host,const int port,const char *req)$/;" f +benchtime webbench.c /^int benchtime=30;$/;" v +build_request webbench.c /^void build_request(const char *url)$/;" f +bytes webbench.c /^int bytes=0;$/;" v +clients webbench.c /^int clients=1;$/;" v +failed webbench.c /^int failed=0;$/;" v +force webbench.c /^int force=0;$/;" v +force_reload webbench.c /^int force_reload=0;$/;" v +host webbench.c /^char host[MAXHOSTNAMELEN];$/;" v +http10 webbench.c /^int http10=1; \/* 0 - http\/0.9, 1 - http\/1.0, 2 - http\/1.1 *\/$/;" v +long_options webbench.c /^static const struct option long_options[]=$/;" v typeref:struct:option file: +main webbench.c /^int main(int argc, char *argv[])$/;" f +method webbench.c /^int method=METHOD_GET;$/;" v +mypipe webbench.c /^int mypipe[2];$/;" v +proxyhost webbench.c /^char *proxyhost=NULL;$/;" v +proxyport webbench.c /^int proxyport=80;$/;" v +request webbench.c /^char request[REQUEST_SIZE];$/;" v +speed webbench.c /^int speed=0;$/;" v +timerexpired webbench.c /^volatile int timerexpired=0;$/;" v +usage webbench.c /^static void usage(void)$/;" f file: diff --git a/note/TinyWebServer-master/test_presure/webbench-1.5/webbench b/note/TinyWebServer-master/test_presure/webbench-1.5/webbench new file mode 100644 index 0000000000..f264f24673 Binary files /dev/null and b/note/TinyWebServer-master/test_presure/webbench-1.5/webbench differ diff --git a/note/TinyWebServer-master/test_presure/webbench-1.5/webbench.1 b/note/TinyWebServer-master/test_presure/webbench-1.5/webbench.1 new file mode 100644 index 0000000000..60c3ee65fc --- /dev/null +++ b/note/TinyWebServer-master/test_presure/webbench-1.5/webbench.1 @@ -0,0 +1,101 @@ +.TH WEBBENCH 1 "14 Jan 2004" +.\" NAME should be all caps, SECTION should be 1-8, maybe w/ subsection +.\" other parms are allowed: see man(7), man(1) +.SH NAME +webbench \- simple forking web benchmark +.SH SYNOPSIS +.B webbench +.I "[options] URL" +.br +.SH "AUTHOR" +This program and manual page was written by Radim Kolar, +for the +.B Supreme Personality of Godhead +(but may be used by others). +.SH "DESCRIPTION" +.B webbench +is simple program for benchmarking HTTP servers or any +other servers, which can be accessed via HTTP proxy. Unlike others +benchmarks, +.B webbench +uses multiple processes for simulating traffic +generated by multiple users. This allows better operating +on SMP systems and on systems with slow or buggy implementation +of select(). +.SH OPTIONS +The programs follow the usual GNU command line syntax, with long +options starting with two dashes (`-'). +A summary of options are included below. +.TP +.B \-?, \-h, \-\-help +Show summary of options. +.TP +.B \-v, \-\-version +Show version of program. +.TP +.B \-f, \-\-force +Do not wait for any response from server. Close connection after +request is send. This option produce quite a good denial of service +attack. +.TP +.B \-9, \-\-http09 +Use HTTP/0.9 protocol, if possible. +.TP +.B \-1, \-\-http10 +Use HTTP/1.0 protocol, if possible. +.TP +.B \-2, \-\-http11 +Use HTTP/1.1 protocol (without +.I Keep-Alive +), if possible. +.TP +.B \-r, \-\-reload +Forces proxy to reload document. If proxy is not +set, option has no effect. +.TP +.B \-t, \-\-time +Run benchmark for +.I +seconds. Default value is 30. +.TP +.B \-p, \-\-proxy +Send request via proxy server. Needed for supporting others protocols +than HTTP. +.TP +.B \-\-get +Use GET request method. +.TP +.B \-\-head +Use HEAD request method. +.TP +.B \-\-options +Use OPTIONS request method. +.TP +.B \-\-trace +Use TRACE request method. +.TP +.B \-c, \-\-clients +Use +.I +multiple clients for benchmark. Default value +is 1. +.SH "EXIT STATUS" +.TP +0 - sucess +.TP +1 - benchmark failed, can not connect to server +.TP +2 - bad command line argument(s) +.TP +3 - internal error, i.e. fork failed +.SH "TODO" +Include support for using +.I Keep-Alive +HTTP/1.1 connections. +.SH "COPYING" +Webbench is distributed under GPL. Copyright 1997-2004 +Radim Kolar (hsn@netmag.cz). +UNIX sockets code taken from popclient 1.5 4/1/94 +public domain code, created by Virginia Tech Computing Center. +.BR +This man page is public domain. diff --git a/note/TinyWebServer-master/test_presure/webbench-1.5/webbench.c b/note/TinyWebServer-master/test_presure/webbench-1.5/webbench.c new file mode 100644 index 0000000000..8a5d305efc --- /dev/null +++ b/note/TinyWebServer-master/test_presure/webbench-1.5/webbench.c @@ -0,0 +1,452 @@ +/* + * (C) Radim Kolar 1997-2004 + * This is free software, see GNU Public License version 2 for + * details. + * + * Simple forking WWW Server benchmark: + * + * Usage: + * webbench --help + * + * Return codes: + * 0 - sucess + * 1 - benchmark failed (server is not on-line) + * 2 - bad param + * 3 - internal error, fork failed + * + */ +#include "socket.c" +#include +#include +#include +#include +#include +#include +#include + +/* values */ +volatile int timerexpired=0; +int speed=0; +int failed=0; +int bytes=0; +/* globals */ +int http10=1; /* 0 - http/0.9, 1 - http/1.0, 2 - http/1.1 */ +/* Allow: GET, HEAD, OPTIONS, TRACE */ +#define METHOD_GET 0 +#define METHOD_HEAD 1 +#define METHOD_OPTIONS 2 +#define METHOD_TRACE 3 +#define PROGRAM_VERSION "1.5" +int method=METHOD_GET; +int clients=1; +int force=0; +int force_reload=0; +int proxyport=80; +char *proxyhost=NULL; +int benchtime=30; +/* internal */ +int mypipe[2]; +char host[MAXHOSTNAMELEN]; +#define REQUEST_SIZE 2048 +char request[REQUEST_SIZE]; + +static const struct option long_options[]= +{ + {"force",no_argument,&force,1}, + {"reload",no_argument,&force_reload,1}, + {"time",required_argument,NULL,'t'}, + {"help",no_argument,NULL,'?'}, + {"http09",no_argument,NULL,'9'}, + {"http10",no_argument,NULL,'1'}, + {"http11",no_argument,NULL,'2'}, + {"get",no_argument,&method,METHOD_GET}, + {"head",no_argument,&method,METHOD_HEAD}, + {"options",no_argument,&method,METHOD_OPTIONS}, + {"trace",no_argument,&method,METHOD_TRACE}, + {"version",no_argument,NULL,'V'}, + {"proxy",required_argument,NULL,'p'}, + {"clients",required_argument,NULL,'c'}, + {NULL,0,NULL,0} +}; + +/* prototypes */ +static void benchcore(const char* host,const int port, const char *request); +static int bench(void); +static void build_request(const char *url); + +static void alarm_handler(int signal) +{ + timerexpired=1; +} + +static void usage(void) +{ + fprintf(stderr, + "webbench [option]... URL\n" + " -f|--force Don't wait for reply from server.\n" + " -r|--reload Send reload request - Pragma: no-cache.\n" + " -t|--time Run benchmark for seconds. Default 30.\n" + " -p|--proxy Use proxy server for request.\n" + " -c|--clients Run HTTP clients at once. Default one.\n" + " -9|--http09 Use HTTP/0.9 style requests.\n" + " -1|--http10 Use HTTP/1.0 protocol.\n" + " -2|--http11 Use HTTP/1.1 protocol.\n" + " --get Use GET request method.\n" + " --head Use HEAD request method.\n" + " --options Use OPTIONS request method.\n" + " --trace Use TRACE request method.\n" + " -?|-h|--help This information.\n" + " -V|--version Display program version.\n" + ); +}; +int main(int argc, char *argv[]) +{ + int opt=0; + int options_index=0; + char *tmp=NULL; + + if(argc==1) + { + usage(); + return 2; + } + + while((opt=getopt_long(argc,argv,"912Vfrt:p:c:?h",long_options,&options_index))!=EOF ) + { + switch(opt) + { + case 0 : break; + case 'f': force=1;break; + case 'r': force_reload=1;break; + case '9': http10=0;break; + case '1': http10=1;break; + case '2': http10=2;break; + case 'V': printf(PROGRAM_VERSION"\n");exit(0); + case 't': benchtime=atoi(optarg);break; + case 'p': + /* proxy server parsing server:port */ + tmp=strrchr(optarg,':'); + proxyhost=optarg; + if(tmp==NULL) + { + break; + } + if(tmp==optarg) + { + fprintf(stderr,"Error in option --proxy %s: Missing hostname.\n",optarg); + return 2; + } + if(tmp==optarg+strlen(optarg)-1) + { + fprintf(stderr,"Error in option --proxy %s Port number is missing.\n",optarg); + return 2; + } + *tmp='\0'; + proxyport=atoi(tmp+1);break; + case ':': + case 'h': + case '?': usage();return 2;break; + case 'c': clients=atoi(optarg);break; + } + } + + if(optind==argc) { + fprintf(stderr,"webbench: Missing URL!\n"); + usage(); + return 2; + } + + if(clients==0) clients=1; + if(benchtime==0) benchtime=60; + /* Copyright */ + fprintf(stderr,"Webbench - Simple Web Benchmark "PROGRAM_VERSION"\n" + "Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.\n" + ); + build_request(argv[optind]); + /* print bench info */ + printf("\nBenchmarking: "); + switch(method) + { + case METHOD_GET: + default: + printf("GET");break; + case METHOD_OPTIONS: + printf("OPTIONS");break; + case METHOD_HEAD: + printf("HEAD");break; + case METHOD_TRACE: + printf("TRACE");break; + } + printf(" %s",argv[optind]); + switch(http10) + { + case 0: printf(" (using HTTP/0.9)");break; + case 2: printf(" (using HTTP/1.1)");break; + } + printf("\n"); + if(clients==1) printf("1 client"); + else + printf("%d clients",clients); + + printf(", running %d sec", benchtime); + if(force) printf(", early socket close"); + if(proxyhost!=NULL) printf(", via proxy server %s:%d",proxyhost,proxyport); + if(force_reload) printf(", forcing reload"); + printf(".\n"); + return bench(); +} + +void build_request(const char *url) +{ + char tmp[10]; + int i; + + bzero(host,MAXHOSTNAMELEN); + bzero(request,REQUEST_SIZE); + + if(force_reload && proxyhost!=NULL && http10<1) http10=1; + if(method==METHOD_HEAD && http10<1) http10=1; + if(method==METHOD_OPTIONS && http10<2) http10=2; + if(method==METHOD_TRACE && http10<2) http10=2; + + switch(method) + { + default: + case METHOD_GET: strcpy(request,"GET");break; + case METHOD_HEAD: strcpy(request,"HEAD");break; + case METHOD_OPTIONS: strcpy(request,"OPTIONS");break; + case METHOD_TRACE: strcpy(request,"TRACE");break; + } + + strcat(request," "); + + if(NULL==strstr(url,"://")) + { + fprintf(stderr, "\n%s: is not a valid URL.\n",url); + exit(2); + } + if(strlen(url)>1500) + { + fprintf(stderr,"URL is too long.\n"); + exit(2); + } + if(proxyhost==NULL) + if (0!=strncasecmp("http://",url,7)) + { fprintf(stderr,"\nOnly HTTP protocol is directly supported, set --proxy for others.\n"); + exit(2); + } + /* protocol/host delimiter */ + i=strstr(url,"://")-url+3; + /* printf("%d\n",i); */ + + if(strchr(url+i,'/')==NULL) { + fprintf(stderr,"\nInvalid URL syntax - hostname don't ends with '/'.\n"); + exit(2); + } + if(proxyhost==NULL) + { + /* get port from hostname */ + if(index(url+i,':')!=NULL && + index(url+i,':')0) + strcat(request,"User-Agent: WebBench "PROGRAM_VERSION"\r\n"); + if(proxyhost==NULL && http10>0) + { + strcat(request,"Host: "); + strcat(request,host); + strcat(request,"\r\n"); + } + if(force_reload && proxyhost!=NULL) + { + strcat(request,"Pragma: no-cache\r\n"); + } + if(http10>1) + strcat(request,"Connection: close\r\n"); + /* add empty line at end */ + if(http10>0) strcat(request,"\r\n"); + // printf("Req=%s\n",request); +} + +/* vraci system rc error kod */ +static int bench(void) +{ + int i,j,k; + pid_t pid=0; + FILE *f; + + /* check avaibility of target server */ + i=Socket(proxyhost==NULL?host:proxyhost,proxyport); + if(i<0) { + fprintf(stderr,"\nConnect to server failed. Aborting benchmark.\n"); + return 1; + } + close(i); + /* create pipe */ + if(pipe(mypipe)) + { + perror("pipe failed."); + return 3; + } + + /* not needed, since we have alarm() in childrens */ + /* wait 4 next system clock tick */ + /* + cas=time(NULL); + while(time(NULL)==cas) + sched_yield(); + */ + + /* fork childs */ + for(i=0;i0) + { + /* fprintf(stderr,"Correcting failed by signal\n"); */ + failed--; + } + return; + } + s=Socket(host,port); + if(s<0) { failed++;continue;} + if(rlen!=write(s,req,rlen)) {failed++;close(s);continue;} + if(http10==0) + if(shutdown(s,1)) { failed++;close(s);continue;} + if(force==0) + { + /* read all available data from socket */ + while(1) + { + if(timerexpired) break; + i=read(s,buf,1500); + /* fprintf(stderr,"%d\n",i); */ + if(i<0) + { + failed++; + close(s); + goto nexttry; + } + else + if(i==0) break; + else + bytes+=i; + } + } + if(close(s)) {failed++;continue;} + speed++; + } +} diff --git a/note/TinyWebServer-master/test_presure/webbench-1.5/webbench.o b/note/TinyWebServer-master/test_presure/webbench-1.5/webbench.o new file mode 100644 index 0000000000..c4b48507e8 Binary files /dev/null and b/note/TinyWebServer-master/test_presure/webbench-1.5/webbench.o differ diff --git a/note/TinyWebServer-master/threadpool/README.md b/note/TinyWebServer-master/threadpool/README.md new file mode 100644 index 0000000000..f015fb02cd --- /dev/null +++ b/note/TinyWebServer-master/threadpool/README.md @@ -0,0 +1,14 @@ + +半同步/半反应堆线程池 +=============== +使用一个工作队列完全解除了主线程和工作线程的耦合关系:主线程往工作队列中插入任务,工作线程通过竞争来取得任务并执行它。 +> * 同步I/O模拟proactor模式 +> * 半同步/半反应堆 +> * 线程池 + + + + + + + diff --git a/note/TinyWebServer-master/threadpool/threadpool.h b/note/TinyWebServer-master/threadpool/threadpool.h new file mode 100644 index 0000000000..b2848a6121 --- /dev/null +++ b/note/TinyWebServer-master/threadpool/threadpool.h @@ -0,0 +1,152 @@ +#ifndef THREADPOOL_H +#define THREADPOOL_H + +#include +#include +#include +#include +#include "../lock/locker.h" +#include "../CGImysql/sql_connection_pool.h" + +template +class threadpool +{ +public: + /*thread_number是线程池中线程的数量,max_requests是请求队列中最多允许的、等待处理的请求的数量*/ + threadpool(int actor_model, connection_pool *connPool, int thread_number = 8, int max_request = 10000); + ~threadpool(); + bool append(T *request, int state); + bool append_p(T *request); + +private: + /*工作线程运行的函数,它不断从工作队列中取出任务并执行之*/ + static void *worker(void *arg); + void run(); + +private: + int m_thread_number; //线程池中的线程数 + int m_max_requests; //请求队列中允许的最大请求数 + pthread_t *m_threads; //描述线程池的数组,其大小为m_thread_number + std::list m_workqueue; //请求队列 + locker m_queuelocker; //保护请求队列的互斥锁 + sem m_queuestat; //是否有任务需要处理 + connection_pool *m_connPool; //数据库 + int m_actor_model; //模型切换 +}; +template +threadpool::threadpool( int actor_model, connection_pool *connPool, int thread_number, int max_requests) : m_actor_model(actor_model),m_thread_number(thread_number), m_max_requests(max_requests), m_threads(NULL),m_connPool(connPool) +{ + if (thread_number <= 0 || max_requests <= 0) + throw std::exception(); + m_threads = new pthread_t[m_thread_number]; + if (!m_threads) + throw std::exception(); + for (int i = 0; i < thread_number; ++i) + { + if (pthread_create(m_threads + i, NULL, worker, this) != 0) + { + delete[] m_threads; + throw std::exception(); + } + if (pthread_detach(m_threads[i])) + { + delete[] m_threads; + throw std::exception(); + } + } +} +template +threadpool::~threadpool() +{ + delete[] m_threads; +} +template +bool threadpool::append(T *request, int state) +{ + m_queuelocker.lock(); + if (m_workqueue.size() >= m_max_requests) + { + m_queuelocker.unlock(); + return false; + } + request->m_state = state; + m_workqueue.push_back(request); + m_queuelocker.unlock(); + m_queuestat.post(); + return true; +} +template +bool threadpool::append_p(T *request) +{ + m_queuelocker.lock(); + if (m_workqueue.size() >= m_max_requests) + { + m_queuelocker.unlock(); + return false; + } + m_workqueue.push_back(request); + m_queuelocker.unlock(); + m_queuestat.post(); + return true; +} +template +void *threadpool::worker(void *arg) +{ + threadpool *pool = (threadpool *)arg; + pool->run(); + return pool; +} +template +void threadpool::run() +{ + while (true) + { + m_queuestat.wait(); + m_queuelocker.lock(); + if (m_workqueue.empty()) + { + m_queuelocker.unlock(); + continue; + } + T *request = m_workqueue.front(); + m_workqueue.pop_front(); + m_queuelocker.unlock(); + if (!request) + continue; + if (1 == m_actor_model) + { + if (0 == request->m_state) + { + if (request->read_once()) + { + request->improv = 1; + connectionRAII mysqlcon(&request->mysql, m_connPool); + request->process(); + } + else + { + request->improv = 1; + request->timer_flag = 1; + } + } + else + { + if (request->write()) + { + request->improv = 1; + } + else + { + request->improv = 1; + request->timer_flag = 1; + } + } + } + else + { + connectionRAII mysqlcon(&request->mysql, m_connPool); + request->process(); + } + } +} +#endif diff --git a/note/TinyWebServer-master/timer/README.md b/note/TinyWebServer-master/timer/README.md new file mode 100644 index 0000000000..ad1f7b813b --- /dev/null +++ b/note/TinyWebServer-master/timer/README.md @@ -0,0 +1,7 @@ + +定时器处理非活动连接 +=============== +由于非活跃连接占用了连接资源,严重影响服务器的性能,通过实现一个服务器定时器,处理这种非活跃连接,释放连接资源。利用alarm函数周期性地触发SIGALRM信号,该信号的信号处理函数利用管道通知主循环执行定时器链表上的定时任务. +> * 统一事件源 +> * 基于升序链表的定时器 +> * 处理非活动连接 diff --git a/note/TinyWebServer-master/timer/lst_timer.cpp b/note/TinyWebServer-master/timer/lst_timer.cpp new file mode 100644 index 0000000000..7efba9bd49 --- /dev/null +++ b/note/TinyWebServer-master/timer/lst_timer.cpp @@ -0,0 +1,224 @@ +#include "lst_timer.h" +#include "../http/http_conn.h" + +sort_timer_lst::sort_timer_lst() +{ + head = NULL; + tail = NULL; +} +sort_timer_lst::~sort_timer_lst() +{ + util_timer *tmp = head; + while (tmp) + { + head = tmp->next; + delete tmp; + tmp = head; + } +} + +void sort_timer_lst::add_timer(util_timer *timer) +{ + if (!timer) + { + return; + } + if (!head) + { + head = tail = timer; + return; + } + if (timer->expire < head->expire) + { + timer->next = head; + head->prev = timer; + head = timer; + return; + } + add_timer(timer, head); +} +void sort_timer_lst::adjust_timer(util_timer *timer) +{ + if (!timer) + { + return; + } + util_timer *tmp = timer->next; + if (!tmp || (timer->expire < tmp->expire)) + { + return; + } + if (timer == head) + { + head = head->next; + head->prev = NULL; + timer->next = NULL; + add_timer(timer, head); + } + else + { + timer->prev->next = timer->next; + timer->next->prev = timer->prev; + add_timer(timer, timer->next); + } +} +void sort_timer_lst::del_timer(util_timer *timer) +{ + if (!timer) + { + return; + } + if ((timer == head) && (timer == tail)) + { + delete timer; + head = NULL; + tail = NULL; + return; + } + if (timer == head) + { + head = head->next; + head->prev = NULL; + delete timer; + return; + } + if (timer == tail) + { + tail = tail->prev; + tail->next = NULL; + delete timer; + return; + } + timer->prev->next = timer->next; + timer->next->prev = timer->prev; + delete timer; +} +void sort_timer_lst::tick() +{ + if (!head) + { + return; + } + + time_t cur = time(NULL); + util_timer *tmp = head; + while (tmp) + { + if (cur < tmp->expire) + { + break; + } + tmp->cb_func(tmp->user_data); + head = tmp->next; + if (head) + { + head->prev = NULL; + } + delete tmp; + tmp = head; + } +} + +void sort_timer_lst::add_timer(util_timer *timer, util_timer *lst_head) +{ + util_timer *prev = lst_head; + util_timer *tmp = prev->next; + while (tmp) + { + if (timer->expire < tmp->expire) + { + prev->next = timer; + timer->next = tmp; + tmp->prev = timer; + timer->prev = prev; + break; + } + prev = tmp; + tmp = tmp->next; + } + if (!tmp) + { + prev->next = timer; + timer->prev = prev; + timer->next = NULL; + tail = timer; + } +} + +void Utils::init(int timeslot) +{ + m_TIMESLOT = timeslot; +} + +//对文件描述符设置非阻塞 +int Utils::setnonblocking(int fd) +{ + int old_option = fcntl(fd, F_GETFL); + int new_option = old_option | O_NONBLOCK; + fcntl(fd, F_SETFL, new_option); + return old_option; +} + +//将内核事件表注册读事件,ET模式,选择开启EPOLLONESHOT +void Utils::addfd(int epollfd, int fd, bool one_shot, int TRIGMode) +{ + epoll_event event; + event.data.fd = fd; + + if (1 == TRIGMode) + event.events = EPOLLIN | EPOLLET | EPOLLRDHUP; + else + event.events = EPOLLIN | EPOLLRDHUP; + + if (one_shot) + event.events |= EPOLLONESHOT; + epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); + setnonblocking(fd); +} + +//信号处理函数 +void Utils::sig_handler(int sig) +{ + //为保证函数的可重入性,保留原来的errno + int save_errno = errno; + int msg = sig; + send(u_pipefd[1], (char *)&msg, 1, 0); + errno = save_errno; +} + +//设置信号函数 +void Utils::addsig(int sig, void(handler)(int), bool restart) +{ + struct sigaction sa; + memset(&sa, '\0', sizeof(sa)); + sa.sa_handler = handler; + if (restart) + sa.sa_flags |= SA_RESTART; + sigfillset(&sa.sa_mask); + assert(sigaction(sig, &sa, NULL) != -1); +} + +//定时处理任务,重新定时以不断触发SIGALRM信号 +void Utils::timer_handler() +{ + m_timer_lst.tick(); + alarm(m_TIMESLOT); +} + +void Utils::show_error(int connfd, const char *info) +{ + send(connfd, info, strlen(info), 0); + close(connfd); +} + +int *Utils::u_pipefd = 0; +int Utils::u_epollfd = 0; + +class Utils; +void cb_func(client_data *user_data) +{ + epoll_ctl(Utils::u_epollfd, EPOLL_CTL_DEL, user_data->sockfd, 0); + assert(user_data); + close(user_data->sockfd); + http_conn::m_user_count--; +} diff --git a/note/TinyWebServer-master/timer/lst_timer.h b/note/TinyWebServer-master/timer/lst_timer.h new file mode 100644 index 0000000000..5a64c7fa8e --- /dev/null +++ b/note/TinyWebServer-master/timer/lst_timer.h @@ -0,0 +1,102 @@ +#ifndef LST_TIMER +#define LST_TIMER + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "../log/log.h" + +class util_timer; + +struct client_data +{ + sockaddr_in address; + int sockfd; + util_timer *timer; +}; + +class util_timer +{ +public: + util_timer() : prev(NULL), next(NULL) {} + +public: + time_t expire; + + void (* cb_func)(client_data *); + client_data *user_data; + util_timer *prev; + util_timer *next; +}; + +class sort_timer_lst +{ +public: + sort_timer_lst(); + ~sort_timer_lst(); + + void add_timer(util_timer *timer); + void adjust_timer(util_timer *timer); + void del_timer(util_timer *timer); + void tick(); + +private: + void add_timer(util_timer *timer, util_timer *lst_head); + + util_timer *head; + util_timer *tail; +}; + +class Utils +{ +public: + Utils() {} + ~Utils() {} + + void init(int timeslot); + + //对文件描述符设置非阻塞 + int setnonblocking(int fd); + + //将内核事件表注册读事件,ET模式,选择开启EPOLLONESHOT + void addfd(int epollfd, int fd, bool one_shot, int TRIGMode); + + //信号处理函数 + static void sig_handler(int sig); + + //设置信号函数 + void addsig(int sig, void(handler)(int), bool restart = true); + + //定时处理任务,重新定时以不断触发SIGALRM信号 + void timer_handler(); + + void show_error(int connfd, const char *info); + +public: + static int *u_pipefd; + sort_timer_lst m_timer_lst; + static int u_epollfd; + int m_TIMESLOT; +}; + +void cb_func(client_data *user_data); + +#endif diff --git a/note/TinyWebServer-master/webserver.cpp b/note/TinyWebServer-master/webserver.cpp new file mode 100644 index 0000000000..a9dfae0b18 --- /dev/null +++ b/note/TinyWebServer-master/webserver.cpp @@ -0,0 +1,434 @@ +#include "webserver.h" + +WebServer::WebServer() +{ + //http_conn类对象 + users = new http_conn[MAX_FD]; + + //root文件夹路径 + char server_path[200]; + getcwd(server_path, 200); + char root[6] = "/root"; + m_root = (char *)malloc(strlen(server_path) + strlen(root) + 1); + strcpy(m_root, server_path); + strcat(m_root, root); + + //定时器 + users_timer = new client_data[MAX_FD]; +} + +WebServer::~WebServer() +{ + close(m_epollfd); + close(m_listenfd); + close(m_pipefd[1]); + close(m_pipefd[0]); + delete[] users; + delete[] users_timer; + delete m_pool; +} + +void WebServer::init(int port, string user, string passWord, string databaseName, int log_write, + int opt_linger, int trigmode, int sql_num, int thread_num, int close_log, int actor_model) +{ + m_port = port; + m_user = user; + m_passWord = passWord; + m_databaseName = databaseName; + m_sql_num = sql_num; + m_thread_num = thread_num; + m_log_write = log_write; + m_OPT_LINGER = opt_linger; + m_TRIGMode = trigmode; + m_close_log = close_log; + m_actormodel = actor_model; +} + +void WebServer::trig_mode() +{ + //LT + LT + if (0 == m_TRIGMode) + { + m_LISTENTrigmode = 0; + m_CONNTrigmode = 0; + } + //LT + ET + else if (1 == m_TRIGMode) + { + m_LISTENTrigmode = 0; + m_CONNTrigmode = 1; + } + //ET + LT + else if (2 == m_TRIGMode) + { + m_LISTENTrigmode = 1; + m_CONNTrigmode = 0; + } + //ET + ET + else if (3 == m_TRIGMode) + { + m_LISTENTrigmode = 1; + m_CONNTrigmode = 1; + } +} + +void WebServer::log_write() +{ + if (0 == m_close_log) + { + //初始化日志 + if (1 == m_log_write) + Log::get_instance()->init("./ServerLog", m_close_log, 2000, 800000, 800); + else + Log::get_instance()->init("./ServerLog", m_close_log, 2000, 800000, 0); + } +} + +void WebServer::sql_pool() +{ + //初始化数据库连接池 + m_connPool = connection_pool::GetInstance(); + m_connPool->init("localhost", m_user, m_passWord, m_databaseName, 3306, m_sql_num, m_close_log); + + //初始化数据库读取表 + users->initmysql_result(m_connPool); +} + +void WebServer::thread_pool() +{ + //线程池 + m_pool = new threadpool(m_actormodel, m_connPool, m_thread_num); +} + +void WebServer::eventListen() +{ + //网络编程基础步骤 + m_listenfd = socket(PF_INET, SOCK_STREAM, 0); + assert(m_listenfd >= 0); + + //优雅关闭连接 + if (0 == m_OPT_LINGER) + { + struct linger tmp = {0, 1}; + setsockopt(m_listenfd, SOL_SOCKET, SO_LINGER, &tmp, sizeof(tmp)); + } + else if (1 == m_OPT_LINGER) + { + struct linger tmp = {1, 1}; + setsockopt(m_listenfd, SOL_SOCKET, SO_LINGER, &tmp, sizeof(tmp)); + } + + int ret = 0; + struct sockaddr_in address; + bzero(&address, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_ANY); + address.sin_port = htons(m_port); + + int flag = 1; + setsockopt(m_listenfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)); + ret = bind(m_listenfd, (struct sockaddr *)&address, sizeof(address)); + assert(ret >= 0); + ret = listen(m_listenfd, 5); + assert(ret >= 0); + + utils.init(TIMESLOT); + + //epoll创建内核事件表 + epoll_event events[MAX_EVENT_NUMBER]; + m_epollfd = epoll_create(5); + assert(m_epollfd != -1); + + utils.addfd(m_epollfd, m_listenfd, false, m_LISTENTrigmode); + http_conn::m_epollfd = m_epollfd; + + ret = socketpair(PF_UNIX, SOCK_STREAM, 0, m_pipefd); + assert(ret != -1); + utils.setnonblocking(m_pipefd[1]); + utils.addfd(m_epollfd, m_pipefd[0], false, 0); + + utils.addsig(SIGPIPE, SIG_IGN); + utils.addsig(SIGALRM, utils.sig_handler, false); + utils.addsig(SIGTERM, utils.sig_handler, false); + + alarm(TIMESLOT); + + //工具类,信号和描述符基础操作 + Utils::u_pipefd = m_pipefd; + Utils::u_epollfd = m_epollfd; +} + +void WebServer::timer(int connfd, struct sockaddr_in client_address) +{ + users[connfd].init(connfd, client_address, m_root, m_CONNTrigmode, m_close_log, m_user, m_passWord, m_databaseName); + + //初始化client_data数据 + //创建定时器,设置回调函数和超时时间,绑定用户数据,将定时器添加到链表中 + users_timer[connfd].address = client_address; + users_timer[connfd].sockfd = connfd; + util_timer *timer = new util_timer; + timer->user_data = &users_timer[connfd]; + timer->cb_func = cb_func; + time_t cur = time(NULL); + timer->expire = cur + 3 * TIMESLOT; + users_timer[connfd].timer = timer; + utils.m_timer_lst.add_timer(timer); +} + +//若有数据传输,则将定时器往后延迟3个单位 +//并对新的定时器在链表上的位置进行调整 +void WebServer::adjust_timer(util_timer *timer) +{ + time_t cur = time(NULL); + timer->expire = cur + 3 * TIMESLOT; + utils.m_timer_lst.adjust_timer(timer); + + LOG_INFO("%s", "adjust timer once"); +} + +void WebServer::deal_timer(util_timer *timer, int sockfd) +{ + timer->cb_func(&users_timer[sockfd]); + if (timer) + { + utils.m_timer_lst.del_timer(timer); + } + + LOG_INFO("close fd %d", users_timer[sockfd].sockfd); +} + +bool WebServer::dealclinetdata() +{ + struct sockaddr_in client_address; + socklen_t client_addrlength = sizeof(client_address); + if (0 == m_LISTENTrigmode) + { + int connfd = accept(m_listenfd, (struct sockaddr *)&client_address, &client_addrlength); + if (connfd < 0) + { + LOG_ERROR("%s:errno is:%d", "accept error", errno); + return false; + } + if (http_conn::m_user_count >= MAX_FD) + { + utils.show_error(connfd, "Internal server busy"); + LOG_ERROR("%s", "Internal server busy"); + return false; + } + timer(connfd, client_address); + } + + else + { + while (1) + { + int connfd = accept(m_listenfd, (struct sockaddr *)&client_address, &client_addrlength); + if (connfd < 0) + { + LOG_ERROR("%s:errno is:%d", "accept error", errno); + break; + } + if (http_conn::m_user_count >= MAX_FD) + { + utils.show_error(connfd, "Internal server busy"); + LOG_ERROR("%s", "Internal server busy"); + break; + } + timer(connfd, client_address); + } + return false; + } + return true; +} + +bool WebServer::dealwithsignal(bool &timeout, bool &stop_server) +{ + int ret = 0; + int sig; + char signals[1024]; + ret = recv(m_pipefd[0], signals, sizeof(signals), 0); + if (ret == -1) + { + return false; + } + else if (ret == 0) + { + return false; + } + else + { + for (int i = 0; i < ret; ++i) + { + switch (signals[i]) + { + case SIGALRM: + { + timeout = true; + break; + } + case SIGTERM: + { + stop_server = true; + break; + } + } + } + } + return true; +} + +void WebServer::dealwithread(int sockfd) +{ + util_timer *timer = users_timer[sockfd].timer; + + //reactor + if (1 == m_actormodel) + { + if (timer) + { + adjust_timer(timer); + } + + //若监测到读事件,将该事件放入请求队列 + m_pool->append(users + sockfd, 0); + + while (true) + { + if (1 == users[sockfd].improv) + { + if (1 == users[sockfd].timer_flag) + { + deal_timer(timer, sockfd); + users[sockfd].timer_flag = 0; + } + users[sockfd].improv = 0; + break; + } + } + } + else + { + //proactor + if (users[sockfd].read_once()) + { + LOG_INFO("deal with the client(%s)", inet_ntoa(users[sockfd].get_address()->sin_addr)); + + //若监测到读事件,将该事件放入请求队列 + m_pool->append_p(users + sockfd); + + if (timer) + { + adjust_timer(timer); + } + } + else + { + deal_timer(timer, sockfd); + } + } +} + +void WebServer::dealwithwrite(int sockfd) +{ + util_timer *timer = users_timer[sockfd].timer; + //reactor + if (1 == m_actormodel) + { + if (timer) + { + adjust_timer(timer); + } + + m_pool->append(users + sockfd, 1); + + while (true) + { + if (1 == users[sockfd].improv) + { + if (1 == users[sockfd].timer_flag) + { + deal_timer(timer, sockfd); + users[sockfd].timer_flag = 0; + } + users[sockfd].improv = 0; + break; + } + } + } + else + { + //proactor + if (users[sockfd].write()) + { + LOG_INFO("send data to the client(%s)", inet_ntoa(users[sockfd].get_address()->sin_addr)); + + if (timer) + { + adjust_timer(timer); + } + } + else + { + deal_timer(timer, sockfd); + } + } +} + +void WebServer::eventLoop() +{ + bool timeout = false; + bool stop_server = false; + + while (!stop_server) + { + int number = epoll_wait(m_epollfd, events, MAX_EVENT_NUMBER, -1); + if (number < 0 && errno != EINTR) + { + LOG_ERROR("%s", "epoll failure"); + break; + } + + for (int i = 0; i < number; i++) + { + int sockfd = events[i].data.fd; + + //处理新到的客户连接 + if (sockfd == m_listenfd) + { + bool flag = dealclinetdata(); + if (false == flag) + continue; + } + else if (events[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) + { + //服务器端关闭连接,移除对应的定时器 + util_timer *timer = users_timer[sockfd].timer; + deal_timer(timer, sockfd); + } + //处理信号 + else if ((sockfd == m_pipefd[0]) && (events[i].events & EPOLLIN)) + { + bool flag = dealwithsignal(timeout, stop_server); + if (false == flag) + LOG_ERROR("%s", "dealclientdata failure"); + } + //处理客户连接上接收到的数据 + else if (events[i].events & EPOLLIN) + { + dealwithread(sockfd); + } + else if (events[i].events & EPOLLOUT) + { + dealwithwrite(sockfd); + } + } + if (timeout) + { + utils.timer_handler(); + + LOG_INFO("%s", "timer tick"); + + timeout = false; + } + } +} \ No newline at end of file diff --git a/note/TinyWebServer-master/webserver.h b/note/TinyWebServer-master/webserver.h new file mode 100644 index 0000000000..7381a13e76 --- /dev/null +++ b/note/TinyWebServer-master/webserver.h @@ -0,0 +1,82 @@ +#ifndef WEBSERVER_H +#define WEBSERVER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "./threadpool/threadpool.h" +#include "./http/http_conn.h" + +const int MAX_FD = 65536; //最大文件描述符 +const int MAX_EVENT_NUMBER = 10000; //最大事件数 +const int TIMESLOT = 5; //最小超时单位 + +class WebServer +{ +public: + WebServer(); + ~WebServer(); + + void init(int port , string user, string passWord, string databaseName, + int log_write , int opt_linger, int trigmode, int sql_num, + int thread_num, int close_log, int actor_model); + + void thread_pool(); + void sql_pool(); + void log_write(); + void trig_mode(); + void eventListen(); + void eventLoop(); + void timer(int connfd, struct sockaddr_in client_address); + void adjust_timer(util_timer *timer); + void deal_timer(util_timer *timer, int sockfd); + bool dealclinetdata(); + bool dealwithsignal(bool& timeout, bool& stop_server); + void dealwithread(int sockfd); + void dealwithwrite(int sockfd); + +public: + //基础 + int m_port; + char *m_root; + int m_log_write; + int m_close_log; + int m_actormodel; + + int m_pipefd[2]; + int m_epollfd; + http_conn *users; + + //数据库相关 + connection_pool *m_connPool; + string m_user; //登陆数据库用户名 + string m_passWord; //登陆数据库密码 + string m_databaseName; //使用数据库名 + int m_sql_num; + + //线程池相关 + threadpool *m_pool; + int m_thread_num; + + //epoll_event相关 + epoll_event events[MAX_EVENT_NUMBER]; + + int m_listenfd; + int m_OPT_LINGER; + int m_TRIGMode; + int m_LISTENTrigmode; + int m_CONNTrigmode; + + //定时器相关 + client_data *users_timer; + Utils utils; +}; +#endif diff --git "a/note/TinyWebServer-master/web\346\234\215\345\212\241\345\231\250\346\241\206\346\236\266\345\233\276.gif" "b/note/TinyWebServer-master/web\346\234\215\345\212\241\345\231\250\346\241\206\346\236\266\345\233\276.gif" new file mode 100644 index 0000000000..0cea67cdef Binary files /dev/null and "b/note/TinyWebServer-master/web\346\234\215\345\212\241\345\231\250\346\241\206\346\236\266\345\233\276.gif" differ diff --git "a/note/webserver_\347\254\224\350\256\260.md" "b/note/webserver_\347\254\224\350\256\260.md" new file mode 100644 index 0000000000..f533ec1dfe --- /dev/null +++ "b/note/webserver_\347\254\224\350\256\260.md" @@ -0,0 +1,110 @@ +# Raw_version文档 + +![](C:\Users\38032\Desktop\note\web服务器框架图.gif) + +Linux下C++轻量级Web服务器,助力初学者快速实践网络编程,搭建属于自己的服务器. + +- 1、使用**线程池 + epoll(ET和LT均实现) + 模拟Proactor模式**的并发模型 +- 2、使用**状态机**解析HTTP请求报文,支持解析**GET和POST**请求 +- 3、通过访问服务器数据库实现web端用户**注册、登录**功能,可以请求服务器**图片和视频文件** +- 4、实现**同步/异步日志系统**,记录服务器运行状态 +- 5、定时器处理非活动连接 +- 6、经Webbench压力测试可以实现**上万的并发连接**数据交换 + + + +## 1、使用**线程池 + epoll(ET和LT均实现) + 模拟Proactor模式**的并发模型 + +半同步/半反应堆线程池 + +**===============** + +使用一个**工作队列**完全解除了**主线程**和**工作线程**的耦合关系:主线程往工作队列中插入任务,工作线程通过**竞争**来取得任务并执行它。 + +\> * 同步I/O模拟proactor模式 + +\> * 半同步/半反应堆 + +\> * 线程池(8个工作线程)内含数据库连接池,当有读、写事件时,工作线程竞争取得任务,执行process()函数,同时从数据库连接池中取出一个连接以执行数据库的查询,插入操作。 + +\> * list实现工作队列存放http_conn类 + +## 2、使用**状态机**解析HTTP请求报文,支持解析**GET和POST**请求 + +http连接处理类 + +**===============** + +根据状态转移,通过**主从状态机**封装了**http连接类**。其中,主状态机在内部调用从状态机,从状态机将处理状态和数据传给主状态机 + +\> * 客户端发出http连接请求 + +\> * 从状态机读取数据,更新自身状态和接收数据,传给主状态机 + +\> * 主状态机根据从状态机状态,更新自身状态,决定响应请求还是继续读取 + +## 3、通过访问服务器数据库实现web端用户**注册、登录**功能,可以请求服务器**图片和视频文件** + +CGI & 数据库连接池 + +**===============** + +**数据库连接池** + +\> * 单例模式,保证唯一 + +\> * list实现连接池 + +\> * 连接池为**静态大小** + +\> * **互斥锁**实现线程安全 + + + +**CGI** + +\> * HTTP请求采用POST方式 + +\> * 登录用户名和密码校验 + +\> * 用户注册及多线程注册安全 + + + +## 4.实现**同步/异步日志系统**,记录服务器运行状态 + +所谓同步日志,即当输出日志时,必须等待日志输出语句执行完毕后,才能执行后面的业务逻辑语句。 + +| | **日志输出方式** | +| -------------- | ------------------------------------------------------------ | +| sync | 同步打印日志,日志输出与业务逻辑在同一线程内,当日志输出完毕,才能进行后续业务逻辑操作 | +| Async Appender | 异步打印日志,内部采用ArrayBlockingQueue,对每个AsyncAppender创建一个线程用于处理日志输出。 | +| Async Logger | 异步打印日志,采用了高性能并发框架Disruptor,创建一个线程用于处理日志输出。 | + +**同步/异步日志系统** + +**===============** + +同步/异步日志系统主要涉及了两个模块,一个是日志模块,一个是阻塞队列模块,其中加入阻塞队列模块主要是解决异步写入日志做准备. + +\> * 自定义阻塞队列 + +\> * 单例模式创建日志 + +\> * 同步日志 + +\> * 异步日志 + +\> * 实现按天、超行分类 + +## 5.定时器处理非活动连接 + +**===============** + +由于非活跃连接占用了连接资源,严重影响服务器的性能,通过实现一个服务器定时器,处理这种非活跃连接,释放连接资源。利用alarm函数周期性(5 s 产生一次SIGALRM 信号 )地触发SIGALRM信号,该信号的信号处理函数利用管道通知主循环执行定时器链表上的定时任务,检测过期的定时器并关闭非活跃连接. + +\> * 统一事件源 + +\> * 基于升序的双向链表的定时器 + +\> * 处理非活跃连接 \ No newline at end of file diff --git "a/note/\345\207\275\346\225\260\347\254\224\350\256\260.md" "b/note/\345\207\275\346\225\260\347\254\224\350\256\260.md" new file mode 100644 index 0000000000..b7b3f430a8 --- /dev/null +++ "b/note/\345\207\275\346\225\260\347\254\224\350\256\260.md" @@ -0,0 +1,1366 @@ +[TOC] + +# 1.C-stirng 函数 + +## 1.1 itoa + +```c++ +#include +#include +int main(void) +{ + int number=12345; + char string[25]; + itoa(number,string,10);//按十进制转换 + printf("integer=%dstring=%s\n",number,string); + + itoa(number,string,16);//按16进制转换 + printf("integer=%dstring=%s\n",number,string); + + int num=8;char str[100]; + itoa(num,str,2); //2代表转换为二进制。 + printf("原数=%d,二进制数=%s\n",num,str); + + return 0; +} +``` + +## 1.2 atoi ,stoi + + 1) 将string字符串转换为int类型,只能转换为十进制;atoi函数不会对string字符串进行范围检查[-2147483648,2147483647],超过这个界限,不会报错,只会进行相应的转换,遇到非法字符会停止,不会报错;头文件为cstdlib + + 2) 将string字符串转换为int类型,只能转换为十进制;atoi函数会对字符串进行检查,如果超过范围,会报错,遇到非法字符同样会停下来,不会报错;头文件为string + +```c++ +#include + +using namespace std; + +int main() +{ + + string s="1234"; + + const char *p=s.c_str();//c_str()转换到第一个非数字字符为止 + + int n=atoi(p); + + cout< // std::cout +#include // std::iota + +int main () { + int numbers[10]; + + std::iota (numbers,numbers+10,100); + + std::cout << "numbers:"; + for (int& i:numbers) std::cout << ' ' << i; + std::cout << '\n'; + + return 0; +} +ouput: +numbers: 100 101 102 103 104 105 106 107 108 109 + +``` + + + +## 1.4 reverse + +```c++ +#include +#include +#include +using namespace std; +int main() +{ + char a[101]; + cin.getline(a,sizeof(a)); + int m=strlen(a); + reverse(a,a+m); + puts(a); +} + +/////////// + void reverse(vector& nums, int start, int end) { + while (start < end) { + swap(nums[start], nums[end]); + start += 1; + end -= 1; + } + } +/////// +``` + +# 2.C++ + +## 2.1 内联函数 + +为了消除函数调用的时空开销,C++ 提供一种提高效率的方法,即在编译时将函数调用处用函数体替换,类似于C语言中的宏展开。这种在函数调用处直接嵌入函数体的函数称为内联函数(Inline Function),又称内嵌函数或者内置函数。 + +指定内联函数的方法很简单,只需要在函数定义处增加 inline 关键字。请看下面的例子: + +```c++ +#include +using namespace std; + +//内联函数,交换两个数的值 +inline void swap(int *a, int *b){ + int temp; + temp = *a; + *a = *b; + *b = temp; +} + +int main(){ + int m, n; + cin>>m>>n; + cout< +#include//注意头文件 +using namespace std; +int main() +{ + int a[]={4,2,3,8,5}; + int len=sizeof(a)/sizeof(int); + //len=5; + cout<<*max_element(a,a+len)<> 运算符来输入字符串,但它可能会导致一些需要注意的问题。 + +当 cin 读取数据时,它会传递并忽略任何前导白色空格字符(空格、制表符或换行符)。一旦它接触到第一个非空格字符即开始阅读,当它读取到下一个空白字符时,它将停止读取。以下面的语句为例: + +``` +cin >> namel; +``` + +可以输入 "Mark" 或 "Twain",但不能输入 "Mark Twain",因为 cin 不能输入包含嵌入空格的字符串。 + +为了解决这个问题,可以使用一个叫做 getline 的 [C++](http://c.biancheng.net/cplus/) 函数。此函数可读取整行,包括前导和嵌入的空格,并将其存储在字符串对象中。 + +getline 函数如下所示: + +``` +getline(cin, inputLine); +``` + +其中 cin 是正在读取的输入流,而 inputLine 是接收输入字符串的 string 变量的名称。下面的程序演示了 getline 函数的应用: + +```c++ +// This program illustrates using the getline function +//to read character data into a string object. +#include +#include // Header file needed to use string objects +using namespace std; + +int main() +{ + string name;//string 类 + string city; + cout << "Please enter your name: "; + getline(cin, name); + cout << "Enter the city you live in: "; + getline(cin, city); + cout << "Hello, " << name << endl; + cout << "You live in " << city << endl; + return 0; +} + +#include +#include +#include +using namespace std; +int main() +{ + char a[101];//char 型数组 + cin.getline(a,sizeof(a)); + int m=strlen(a); + reverse(a,a+m); + puts(a); +} +``` + +## 2.4 C++ STL之multimap + +```c++ +#include +#include +#include +#include + +using namespace std; +//multimap按关键字大小排序 +multimapmp;//定义map容器 + +int main() +{ + //数组方式插入 + mp.insert(pair(2,'a')); +// mp.emplace(1,'b'); +// mp.emplace(1,'c'); +// mp.emplace(1,'a'); + mp.insert(pair(1,'c')); + mp.insert(pair(1,'b')); + mp.insert(multimap::value_type(3, 'c')); +// cout<<"根据key值输出对应的value值 "<::iterator it1; + + cout<<"遍历输出map中的元素 "<first<<" "<second<first<<" "<second<first<<" "; + cout<second<first<<" "; + cout<second<::iterator it; + //pair p=mp.equal_range(5); + //等价于: + it pfirst=mp.lower_bound(1); + it psecond=mp.upper_bound(1); + + it i=mp.find(1); + cout<<"key="<first<<"有"<first<<" "<second< + for(auto it1:mp){ + cout<first<<" "<second< +#include + +using namespace std; + +//升序队列 +priority_queue ,greater > q1;//小顶堆 +//降序队列 +priority_queue ,less >q2;//大顶堆 + +priority_queueq3; //默认大顶堆 + +/*greater和less是std实现的两个仿函数(就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了)*/ + +struct cmp{//重写仿函数 + bool operator()(pair&a,pair&b)const{ + if(a.first>b.first) return true; + else return a.second>b.second; + } +}; + +int main(){ + q1.push(5);q1.push(6);q1.push(3);q1.push(4); + q2.push(5);q2.push(6);q2.push(3);q2.push(4); + while(!q1.empty()){ + cout<,vector>,cmp> a;//pari的比较,先比较第一个元素,第一个相等比较第二个 + pair b(1, 2); + pair c(3, 1); + pair d(2, 5); + a.push(d); + a.push(c); + a.push(b); + while (!a.empty()) + { + cout << a.top().first << ' ' << a.top().second << '\n'; + a.pop(); + } +} +``` + +```c++ +#include +#include +using namespace std; + +//方法1 +struct tmp1 //运算符重载< +{ + int x; + tmp1(int a) {x = a;} + bool operator<(const tmp1& a) const + { + return a.x>x; //大顶堆 + } +}; + +//方法2 +struct tmp2 //重写仿函数 +{ + bool operator() (tmp1 a, tmp1 b) + { + return b.x>a.x; //大顶堆 + } +}; + +int main() +{ + tmp1 a(1); + tmp1 b(2); + tmp1 c(3); + priority_queue > d;//结构体内实现了 比较函数 + d.push(b); + d.push(c); + d.push(a); + while (!d.empty()) + { + cout << d.top().x << '\n'; + d.pop(); + } + cout << endl; + + priority_queue, tmp2> f;//重写仿函数 + f.push(c); + f.push(b); + f.push(a); + while (!f.empty()) + { + cout << f.top().x << '\n'; + f.pop(); + } + cout<, greater >f1;//小顶堆 + priority_queue, less >f1;//大顶堆 + f1.push(1); + f1.push(2); + f1.push(4); + f1.push(6); + f1.push(3); + f1.push(1); + while (!f1.empty()) + { + cout << f1.top()<< '\n'; + f1.pop(); + } +} + +``` + +## 2.6 memset初始化内存注意事项 + +第一:memset函数按字节对内存块进行初始化,所以不能用它将int数组初始化为0和-1之外的其他值(除非该值高字节和低字节相同)。 + +第二:memset(void *s, int ch,size_t n);中ch实际范围应该在0~255,因为该函数只能取ch的后八位赋值给你所输入的范围的每个字节,比如int a[5]赋值memset(a,-1,sizeof(int )*5)与memset(a,511,sizeof(int )*5) 所赋值的结果是一样的都为-1;因为-1的二进制码为(11111111 11111111 11111111 11111111)而511的二进制码为(00000000 00000000 00000001 11111111)后八位都为(11111111),所以数组中每个字节,如a[0]含四个字节都被赋值为(11111111),其结果为a[0](11111111 11111111 11111111 11111111),即a[0]=-1,因此无论ch多大只有后八位二进制有效,而后八位[二进制](https://baike.baidu.com/item/二进制)的范围在(0~255)中改。而对字符数组操作时则取后八位赋值给字符数组,其八位值作为[ASCII](https://baike.baidu.com/item/ASCII)码。 + +第三: 搞反了 ch 和 n 的位置. + +一定要记住如果要把一个char a[20]清零,一定是 memset(a,0,20*sizeof(char)); + +而不是 memset(a,20*sizeof(char),0); + +第四: 过度使用memset. + +```c++ +char buffer[4]; +memset(buffer,0,sizeof(char)*4); +strcpy(buffer,"123"); +//"123"中最后隐藏的'\0'占一位,总长4位。 +``` + +这里的memset是多余的. 因为这块内存马上就被全部覆盖,清零没有意义. + +另:以下情况并不多余,因某些[编译器](https://baike.baidu.com/item/编译器)分配空间时,内存中默认值并不为0: + +``` +char buffer[20]; +memset(buffer,0,sizeof(char)*20); +memcpy(buffer,"123",3); +//这一条的memset并不多余,memcpy并没把buffer全部覆盖,如果没有memset, +//用printf打印buffer会有乱码甚至会出现段错误。 +//如果此处是strcpy(buffer,"123");便不用memset, +//strcpy虽然不会覆盖buffer但是会拷贝字符串结束符 +``` + +第五: + +``` +int some_func(struct something *a) +{ +… +… +memset(a,0,sizeof(a)); +… +} +``` + +其实这个错误严格来讲不能算用错memset,但是它经常在使用memset的场合出现。这里错误的原因是VC函数传参过程中的[指针](https://baike.baidu.com/item/指针)降级,导致[sizeof](https://baike.baidu.com/item/sizeof)(a),返回的是一个[something](https://baike.baidu.com/item/something/6666793)*指针类型大小的的[字节](https://baike.baidu.com/item/字节)数,如果是32位,就是4字节。 + +## 2.7 accumulate(累计求和)函数 + +该算法函数在numeric头文件中定义:`#include` +目前知道的有几个功能:1.求和;2.求连乘; 3.string合并 + +直接举例说明: + +```c++ +#include +#include//accumulate函数在这个库中定义 +#include//包含这个库,可以直接输出字符串 +#include//vector是向量类型,可容纳许多类型的数据,因此也被称为容器 +using namespace std; +int main() { +//功能一:求和 + int list[10] = { 1,2,3,4,5,6,7,8,9,10 }; + int sum= accumulate(list, list+10, 0) ; + cout <<"和:"<()); + cout<<"连乘积:"<a{"1","-2345","+6"}; + string a_sum=accumulate(a.begin(), a.end(),string("out: ")); +cout<<"string合并后输出:"<()) ;` +得出sum=6. +如果`con_product= accumulate(list, list+3, 10, multiplies()) ;` +得出sum=60. +**求string合并**, + +## 2.8 is_sorted()函数 + +is_sorted() 函数有 2 种语法格式,分别是: + +```c++ +//判断 [first, last) 区域内的数据是否符合 std::less 排序规则,即是否为升序序列 +bool is_sorted (ForwardIterator first, ForwardIterator last); +//判断 [first, last) 区域内的数据是否符合 comp 排序规则 +bool is_sorted (ForwardIterator first, ForwardIterator last, Compare comp); +``` + +```c++ +#include // std::cout +#include // std::is_sorted +#include // std::array +#include // std::list +using namespace std; +//以普通函数的方式自定义排序规则 +bool mycomp1(int i, int j) { + return (i > j); +} +//以函数对象的方式自定义排序规则 +class mycomp2 { +public: + bool operator() (int i, int j) { + return (i > j); + } +}; + +int main() { + vector myvector{ 3,1,2,4 }; + list mylist{ 1,2,3,4 }; + //调用第 2 种语法格式的 is_sorted() 函数,该判断语句会得到执行 + if (!is_sorted(myvector.begin(), myvector.end(),mycomp2())) { + cout << "开始对 myvector 容器排序" << endl; + //对 myvector 容器做降序排序 + sort(myvector.begin(), myvector.end(),mycomp2()); + //输出 myvector 容器中的元素 + for (auto it = myvector.begin(); it != myvector.end(); ++it) { + cout << *it << " "; + } + } + + //调用第一种语法格式的 is_sorted() 函数,该判断语句得不到执行 + if (!is_sorted(mylist.begin(), mylist.end())) { + cout << "开始对 mylist 排序" << endl; + //...... + } + return 0; +} +``` + +程序执行结果为: + +开始对 myvector 容器排序 +4 3 2 1 + +结合输出结果可以看到,虽然 myvector 容器中的数据为降序序列,但我们需要的是升序序列。因此第 22 行代码中 is_sorted() 函数的返回值为 false,而 !false 即 true,所以此 if 判断语句会得到执行。 + +同样在 33 行代码中,mylist 容器中存储的数据为升序序列,和 is_sorted() 函数的要求相符,因此该函数的返回值为 true,而 !true 即 false,所以此 if 判断语句将无法得到执行。 + +> C++标准库官方网站给出了 is_sorted() 函数底层实现的等效代码,感兴趣的读者可自行前往查看。 + +## 2.9 is_sorted_until()函数 + + + +```c++ +//排序规则为默认的升序排序 +ForwardIterator is_sorted_until (ForwardIterator first, ForwardIterator last); +//排序规则是自定义的 comp 规则 +ForwardIterator is_sorted_until (ForwardIterator first, + ForwardIterator last, + Compare comp); +其中,first 和 last 都为正向迭代器(这意味着该函数适用于大部分容器),[first, last) 用于指定要检测的序列;comp 用于指定自定义的排序规则。 +注意,如果使用默认的升序排序规则,则 [first, last) 指定区域内的元素必须支持使用 < 小于运算符做比较;同样,如果指定排序规则为 comp,也要保证 [first, last) 区域内的元素支持该规则内部使用的比较运算符。 + +可以看到,该函数会返回一个正向迭代器。对于第一种语法格式来说,该函数返回的是指向序列中第一个破坏升序规则的元素;对于第二种语法格式来说,该函数返回的是指向序列中第一个破坏 comp 排序规则的元素。注意,如果 [first, last) 指定的序列完全满足默认排序规则或者 comp 排序规则的要求,则该函数将返回一个和 last 迭代器指向相同的正向迭代器。 +``` + +```c++ +#include // std::cout +#include // std::is_sorted_until +#include // std::array +#include // std::list +using namespace std; +//以普通函数的方式自定义排序规则 +bool mycomp1(int i, int j) { + return (i > j); +} +//以函数对象的方式自定义排序规则 +class mycomp2 { +public: + bool operator() (int i, int j) { + return (i > j); + } +}; + +int main() { + vector myvector{ 3,1,2,4 }; + list mylist{ 1,2,3,4 }; + //如果返回值为 myvector.end(),则表明 myvector 容器中的序列符合 mycomp2() 规则 + if (is_sorted_until(myvector.begin(), myvector.end(),mycomp2()) != myvector.end()) { + cout << "开始对 myvector 容器排序" << endl; + //对 myvector 容器做降序排序 + sort(myvector.begin(), myvector.end(),mycomp2()); + //输出 myvector 容器中的元素 + for (auto it = myvector.begin(); it != myvector.end(); ++it) { + cout << *it << " "; + } + } + + //该判断语句得不到执行 + if (is_sorted_until(mylist.begin(), mylist.end()) != mylist.end()) { + cout << "开始对 mylist 排序" << endl; + //...... + } + return 0; +} +/*程序执行结果为: +开始对 myvector 容器排序 +4 3 2 1*/ +``` + +## 2.10 向上取整运算技巧 + +经过分析,x如果是n的整倍数,(x+n-1)/n 的结果就是x/n 。如果不是(有余数)(x+n-1)/n=x/n +1 ; 所以公式: result=(x+n-1)/n ; 完全可以表示来个整数相除(x>=n的情况) 结果向上取整; + +## 2.11 字符转数字 + +``` + int a=static_cast(‘9’-'0'); //数字9 + char b=static_cast(9+'0');//字符‘9' + ASCII 码表 + DEC char + 48 ‘0’ + 65 ‘A’ + 97 ‘a’ + +``` + +## 2.12 常量指针和指针常量 + +**指针常量和常量指针的区别:** + +**指针常量**就是指针本身是常量,换句话说,就是指针里面所存储的内容(内存地址)是常量,不能改变。但是,内存地址所对应的内容是可以通过指针改变的。 + +**常量指针**就是指向常量的指针,换句话说,就是指针指向的是常量,它指向的内容不能发生改变,不能通过指针来修改它指向的内容。但是,指针自身不是常量, + +它自身的值可以改变,从而指向另一个常量。 + +**指针常量与常量指针的声明** + +指针常量的声明:数据类型 * const 指针变量。 + +常量指针的声明:数据类型 const * 指针变量 或者 const 数据类型 *指针变量。 + +常量指针常量的声明:数据类型 const * const 指针变量 或者 const 数据类型 * const 指针变量。 + +**指针常量与常量指针的使用** + +1) 指针常量的例子 + +```c++ +int a,b; + +int *const p; + +p=&a;//true + +p=&b;//false + +*p=20;//true +``` + +指针常量声明的时候必须赋初始值。使用指针常量可以增加代码的可靠性和执行效率。 + +2 ) 常量指针的例子 + +```c++ +int a,b; + +const int *p1 ; + +int const *p2; + +p1=&a;//正确 + +p1=&b;//正确 + +*p1=20;//错误,不能通过指针去修改这个地址存储的值 +``` + +## 2.13 智能指针 + +**1)auto_ptr** + +auto_ptr有两个版本,一个是pc版,指针涉及所有权问题;一个是gcc版本,指针不涉及所有权问题。 + +1、auto_ptr存储的指针应该为NULL或者指向动态分配的内存块。 + +2、auto_ptr存储的指针应该指向单一物件(是new出来的,而不是new[]出来的)。 + +3、两个auto_ptr对象不会同时指向同一块内存块。要明白2个auto_ptr对象赋值会发生什么。 + +4、千万不要把auto_ptr对象放在容器中。 + +5、当将auto_ptr作为函数参数时,最好声明为const auto_ptr&(by const ref).当函数返回值可以简单的传值(by value). + +**2) unique_ptr(替换auto_ptr)** + +u n i q u e _ p t r 实 现 独 占 式 拥 有 或 严 格 拥 有 概 念 , 保 证 同 一 时 间 内 只 有 一 个 智 能 指 针 可 以 指 向 该 对 象 。 它 对 于 避 免 资 源 泄 露 ( 例 如 “ 以 n e w 创 建 对 象 后 因 为 发 生 异 常 而 忘 记 调 用 d e l e t e ” ) 特 别 有 用 。 + +**3)shared_ptr** + +1、 在遗留代码上如果要引入shared_ptr要谨慎!shared_ptr带来的不确定性可能要比带来的便利性大的多。 + +2、 使用shared_ptr并不是意味着能偷懒。反而你更需要了解用shared_ptr管理的对象的生命周期应该是什么样子的,是不是有环形引用,是不是有线程安全问题,是不是会在某个地方意外的被某个东西hold住了。 + +3、 一个对象如何使用shared_ptr管理那么最好全部使用shared_ptr来管理,必要的时候可以使用weak_ptr。千万不要raw ptr和智能指针混用 + +3、 不要以传递指针的形式传递shared_ptr,最好按值传递。 + +4、 多线程读写同一个shared_ptr的时候,可以先加锁拷贝一份出来,然后解锁即可。 + +**4) weak_ptr** + +w e a k _ p t r 是 用 来 解 决 s h a r e d _ p t r 相 互 引 用 时 的 死 锁 问 题 , 如 果 说 两 个 s h a r e d _ p t r 相 互 引 用 , 那 么 这 两 个 指 针 的 引 用 计 数 永 远 不 可 能 下 降 为 0 , 资 源 永 远 不 会 释 放 。 它 是 对 对 象 的 一 种 弱 引 用 , 不 会 增 加 对 象 的 引 用 计 数 , 和 s h a r e d _ p t r 之 间 可 以 相 互 转 化 , s h a r e d _ p t r 可 以 直 接 赋 值 给 它 , 它 可 以 通 过 调 用 l o c k 函 数 来 获 得 s h a r e d _ p t r 。 + + + +总结: + +1. C++没有垃圾收集,资源管理需要自己来做。 +2. 智能指针可以部分解决资源管理的工作,但是不是万能的。 +3. 使用智能指针的时候,每个shared_ptr对象都应该有一个名字;也就是避免在一个表达式内做多个资源的初始化; +4. 避免shared_ptr的交叉引用;使用weak_ptr打破交叉; +5. **使用enable_shared_from_this机制来把this从类内部传递出来;** +6. 资源管理保持统一风格,要么使用智能指针,要么就全部自己管理裸指针 + +## 2.14 const成员函数及其权限 + +1.const对象,不能调用非const成员函数,const对象,可以调用const成员函数 + +2.非const对象,可以调用非const成员函数,非const对象,可以调用const成员函数 + +权限可以放大但不能缩小,const相当于只拥有读权限,非const拥有读写的权限 + +3. const成员函数内可以调用其它的const成员函数,不能调用非const成员函数 +4. 非const成员函数内可以调用其它的const成员函数非const成员函数 + + + + +2.15 equal_range/ lower_bound + +```c++ +auto it = equal_range(nums.begin(),nums.end(),target); + +auto left = it.first; //等于target的第一个位置 等价于 lower_bound(); + +auto right = it.second; //大于target的第一个位置 等价于 upper_bound(); + + +auto t1 = lower_bound(nums.begin(),nums.end(),8); +``` + + + +# 3.数据结构与算法 + +## 3.1 D算法-指定端到其他端的最短路径算法 + +``` +/* +D算法对有向图也适用 +也可适用于端点有权的情况 +D算法不适用于边的权有正有负的情况 +2020-12-17 written by HL +*/ +#include +using namespace std; +#define MAX_VERTEX_NUM 100 //最大矩阵维度 +typedef struct MGRAPH +{ + int n;//图中顶点数目 + int e;//图中边的数目 + int vertex[MAX_VERTEX_NUM]; + int edge[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; +} MGraph; + +MGraph* CreateMGraph( ) +{ + MGraph *G=new MGraph ; + int i,j,k,w; + printf("请输入顶点数目和边的数目:"); + scanf("%d%d",&G->n,&G->e); + //初始化邻接矩阵 + for(i=0; in; i++) + for(j=0; jn; j++) + G->edge[i][j]=INT_MAX; + printf("请输入%d条边和对应的权值:",G->e); + for(k=0; ke; k++) + { + scanf("%d%d%d",&i,&j,&w);//输入边的信息 + G->edge[i][j]=w; + G->edge[j][i]=w; + } + return G; +} + +//输出从顶点 v出发的所有最短路径 +void Dispath(MGraph*G,int dis[],int path[],int S[],int v) +{ + int i,j,k; + int apath[MAX_VERTEX_NUM],d;//存放一条最短路径(逆向存放)及其顶点个数 + for(i=0; in; i++) + { + if(S[i]==1 && i!=v ) + { + printf("顶点 %d 到顶点 %d 的路径长度:%d\t路径:",v,i,dis[i]); + d=0; + apath[d]=i; + k=path[i]; + if(k==-1) printf("无路径\n"); + else + { + while(k!=v) + { + d++; + apath[d]=k; + k=path[k]; + } + d++; + apath[d]=v;//添加路径上的起点 + printf("%d",apath[d]);//先输出起点 + for(j=d-1; j>=0; j--) + { + printf(",%d",apath[j]); + } + printf("\n"); + } + } + } +} + +void Dijkstra(MGraph *G,int v) +{ + int dis[MAX_VERTEX_NUM];//路径长度数组 + int path[MAX_VERTEX_NUM]; + int S[MAX_VERTEX_NUM]; + int mindis,i,j,u; + for(i=0; in; i++) + { + dis[i]=G->edge[v][i];//初始化路径长度数组 + S[i]=0; + if(G->edge[v][i]n; i++) + { + mindis=INT_MAX; + for(int j=0; jn; j++) + { + if(S[j]==0&&dis[j]n; j++) + { + if(S[j]==0) + { + if(G->edge[u][j]edge[u][j]edge[u][j]; + path[j]=u; + } + } + } + } + Dispath(G,dis,path,S,v); +} + +int main() +{ + MGraph *G=CreateMGraph(); + Dijkstra(G,0); + system("pause"); + return 0; +} + + +``` + + + +# 4.STL源码阅读笔记 + +2021/5-2 + +## 4.1 第二章 空间配置器 + +1.malloc、free底层实现 + +![](C:\Users\38032\Desktop\note\malloc,free.png) + +*** 1)\*小于\**\**128K\**\**内存分配\**** + +​ malloc小于128k的内存,使用brk分配内存,将_edata往高地址推(只分配虚拟空间,不对应物理内存(因此没有初始化),第一次读/写数据时,引起内核缺页中断,内核才分配对应的物理内存,然后虚拟地址空间建立映射关系),如下图(32位系统): + +![](C:\Users\38032\Desktop\note\20171017215810378.jpg) + +***\2)*大于\**\**128K\**\**内存分配\**** + +malloc大于128k的内存,使用mmap分配内存,在堆和栈之间找一块空闲内存分配(对应独立内存,而且初始化为0),如下图: + +![](C:\Users\38032\Desktop\note\20171017215827877.jpg) + + + +```c++ + +//分配程序初始化函数 +void malloc_init() +{ + + last_valid_address = sbrk(0); + + managed_memory_start = last_valid_address; + + has_initialized = 1; +} + +/*调用free之后,指针p存储的地址,依旧是源地址,但*p的值为随机值。*/ + +/**内存控制块数据结构,用于管理所有的内存块 +* is_available: 标志着该块是否可用。1表示可用,0表示不可用 +* size: 该块的大小 +**/ +struct mem_control_block { + int is_available; + int size; +}; + +/**在实现malloc时要用到linux下的全局变量 +*managed_memory_start:该指针指向进程的堆底,也就是堆中的第一个内存块 +*last_valid_address:该指针指向进程的堆顶,也就是堆中最后一个内存块的末地址 +**/ +void *managed_memory_start; +void *last_valid_address; + +/**malloc()功能是动态的分配一块满足参数要求的内存块 +*numbytes:该参数表明要申请多大的内存空间 +*返回值:函数执行结束后将返回满足参数要求的内存块首地址,要是没有分配成功则返回NULL +**/ +void *malloc(size_t numbytes) { + //游标,指向当前的内存块 + void *current_location; + //保存当前内存块的内存控制结构 + struct mem_control_block *current_location_mcb; + //保存满足条件的内存块的地址用于函数返回 + void *memory_location; + memory_location = NULL; + //计算内存块的实际大小,也就是函数参数指定的大小+内存控制块的大小 + numbytes = numbytes + sizeof(struct mem_control_block); + //利用全局变量得到堆中的第一个内存块的地址 + current_location = managed_memory_start; + + //对堆中的内存块进行遍历,找合适的内存块 + while (current_location != last_valid_address) //检查是否遍历到堆顶了 + { + //取得当前内存块的内存控制结构 + current_location_mcb = (struct mem_control_block*)current_location; + //判断该块是否可用 + if (current_location_mcb->is_available) + //检查该块大小是否满足 + if (current_location_mcb->size >= numbytes) + { + //满足的块将其标志为不可用 + current_location_mcb->is_available = 0; + //得到该块的地址,结束遍历 + memory_location = current_location; + break; + } + //取得下一个内存块 + current_location = current_location + current_location_mcb->size; + } + + //在堆中已有的内存块中没有找到满足条件的内存块时执行下面的函数 + if (!memory_location) + { + //向操作系统申请新的内存块 + if (sbrk(numbytes) == -1) + return NULL;//申请失败,说明系统没有可用内存 + memory_location = last_valid_address; + last_valid_address = last_valid_address + numbytes; + current_location_mcb = (struct mem_control_block)memory_location; + current_location_mcb->is_available = 0; + current_location_mcb->size = numbytes; + } + //到此已经得到所要的内存块,现在要做的是越过内存控制块返回内存块的首地址 + memory_location = memory_location + sizeof(struct mem_control_block); + return memory_location; +} + + + + +``` + +## 4.2 第三章 迭代器与traits编程技巧 + +1.主要利用内嵌型别和自动类型转换。 + + + + + +## 4.3 第四章 序列式容器 包括(array(build-in))、vector、heap、priority-queue、list、slist、deque、stack(配接器)、queue(配接器) + +所谓序列式容器,其中的元素都可序,但未必有序。 + +### 1、array-数组 + +​ array 是静态空间,一旦配置,不能改变大小。 + +### 2、vector(动态数组) + +``` +template + +class vector{ + + ... + + protected: + + iterator start; + + iterator finish; + + iterator end_of_storage; + ... + +} +``` + +1)vector的迭代器是普通指针。 + +2)vector的扩容机制:增加新元素s时,若超过当时容量,则容量会扩充至原来的两倍。如果两倍还是不够,则扩充至足够大的容量。容量的扩充必须经历“重新配置,元素移动,释放原空间”等过程。 + +3)对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器均失效。 + +### 3、list (双向链表) + +```c++ +template + +struct _list_node{ + + typedef void* void_pointer; + + void_pointer prev;//型别void* ,可设为 _list_node* + + void_poniter next; + + T data; + +} +``` + +1) list 是一个环状双向链表,尾端为一个空白节点,符合STL对于 “ 前闭后开 ” 的区间要求。 + +2)list的push_back()函数内部调用insert函数。 + +``` +void push_back(const T& x){ + + insert(end(),x); + +} +``` + +3) insert 函数 :在指定位置,插入元素。新节点位于插入点之前。 + +4)插入前所有迭代器在插入操作之后都仍然有效。 + +### 4、deque (双端队列) + +1)一种双向开口的连续线性空间,可以在头尾两端做元素的插入和删除操作。 + +2)deque没有容量的概念,因为它是动态地以分段连续空间组合而成,随时可以新增一段连续空间并连接起来。 + +3)尽可能选择使用vector,而非deque。其迭代器实现复杂度不可与vector同日而语。 + +4)对deque的排序操作,为了最高效率,可将deque完整复制到vector,将vector调用STL sort算法,再复制回deque。 + + + +5)deque采用中控器(指针数组,p145-deque结构图)来连接各个缓冲区。 + +6)deque迭代器 (p146)。 + +### 5、stack(栈-配接器) + +stack是一种先进后出的数据结构。**SGI STL中,如果没有指定底层实现的话,默认是以deque为缺省情况下栈的低层结构。** + +1) 缺省情况下以deque为底部结构并封闭其头端开口,便轻而易举地形成了一个stack。因此,SGI STL便以deque作为缺省情况下的stack底部结构。 + +2)以list作为stack的底层容器,封闭其头端开口。 + +3)**stack没有迭代器。** + +4) 栈的底层实现可以是vector,deque,list 都是可以的, 主要就是数组和链表的底层实现。 + +```c++ +std::stack > third; // 使用vector为底层容器的栈 +``` + +### 6、queue(队列-配接器) + +queue是一种先进先出的数据结构。 + +1) 缺省情况下以deque为底部结构,便轻而易举地形成了一个stack。因此,SGI STL便以deque作为缺省情况下的queue底部结构。 + +2)以list作为stack的底层容器,并封闭其某些接口。 + +3)**queue没有迭代器。** + +``` +std::queue> third; // 定义以list为底层容器的队列 +``` + +### 7、 heap (堆) + +1) 适合作为priority queue的底层实现。 + +2)若完全二叉树中的某个节点位于array的i处(i起始为0),其左子节点为2i+1,右子节点为2i+2。 + +3)STL提供的是max-heap。 + +### 7、priority_queue (优先队列-配接器) + +1)priority_queueq;//默认大顶堆 + +```c++ +//example 1 +#include +#include + +using namespace std; + +//升序队列 + +priority_queue ,greater > q1;//小顶堆 +//降序队列 +priority_queue ,less >q2;//大顶堆 + +priority_queueq3; //默认大顶堆 + +//greater和less是std实现的两个仿函数(就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了) + +struct cmp{//重写仿函数 + bool operator()(pair&a,pair&b)const{ + if(a.first>b.first) return true; + else return a.second>b.second; + } +}; + +int main(){ + q1.push(5);q1.push(6);q1.push(3);q1.push(4); + q2.push(5);q2.push(6);q2.push(3);q2.push(4); + while(!q1.empty()){ + cout<,vector>,cmp> a;//pari的比较,先比较第一个元素,第一个相等比较第二个 + pair b(1, 1); + pair c(1, 3); + pair d(2, 2); + a.push(d); + a.push(c); + a.push(b); + while (!a.empty()) + { + cout << a.top().first << ' ' << a.top().second << '\n'; + a.pop(); + } +} + +//example 2 +#include +#include +using namespace std; + +//方法1 +struct tmp1 //运算符重载< +{ + int x; + tmp1(int a) {x = a;} + bool operator<(const tmp1& a) const + { + return a.x>x; //大顶堆 + } +}; + +//方法2 +struct tmp2 //重写仿函数 ,重载()运算符 +{ + bool operator() (const tmp1 &a,const tmp1 &b) + { + return b.x>a.x; //大顶堆 + } +}; + +int main() +{ + tmp1 a(1); + tmp1 b(2); + tmp1 c(3); + priority_queue > d;//结构体内实现了 比较函数 + d.push(b); + d.push(c); + d.push(a); + while (!d.empty()) + { + cout << d.top().x << '\n'; + d.pop(); + } + cout << endl; + + priority_queue, tmp2> f;//重写仿函数 + f.push(c); + f.push(b); + f.push(a); + while (!f.empty()) + { + cout << f.top().x << '\n'; + f.pop(); + } + cout<, greater >f1;//小顶堆 + priority_queue, less >f1;//大顶堆 + f1.push(1); + f1.push(2); + f1.push(4); + f1.push(6); + f1.push(3); + f1.push(1); + while (!f1.empty()) + { + cout << f1.top()<< '\n'; + f1.pop(); + } + return 0; +} + +``` + +### 8、slist(单向链表) + +1)由于slist没有任何方便的办法可以找到前一个位置,因此在slist调用insert函数时必须从头找起,这在除slist起点附近的区域之外来说,采用erase和insert都不是一个明智之举,因此,slist特别提供了insert_after和erase_after 供灵活选择。 + +2) 只提供push_front(),没有push_back()。即只提供头插。 + +## 第五章 关联式容器 + +### 1、set -底层实现 RB-tree + +1)set::iterator 被定义为底层RB-tree的const_iterator,杜绝写入操作。 + +2) set 与list一样,进行元素的新增或删除操作后,操作之前的所有迭代器咋,在操作完成之后依然有效。 + +### 2、map -底层 RB-tree + +1)键值key不能修改,实值value不能修改。 + +2)map与list一样,进行元素的新增或删除操作后,操作之前的所有迭代器咋,在操作完成之后依然有效。 + +### 3、multiset -底层 RB-tree + +multiset的特性以及用法和set完全相同,唯一的差别在于它允许键值重复,因此它的插入操作采用的是底层机制RB-tree的insert_equal()而非insert_unique()。 + +### 4、multimap -底层 RB-tree + +multimap的特性以及用法和map完全相同,唯一的差别在于它允许键值重复,因此它的插入操作采用的是底层机制RB-tree的insert_equal()而非insert_unique()。 + +### 5、hashtable -散列表 + +1)解决哈希值碰撞问题 + +线性探测、二次探测、开链法、创建公共溢出区。 + +### 6、hashset - 底层 -hashtable + +1)RB-tree 有自动排序功能,hashtable没有。 + +### 7、hashmap - 底层 hashtable + +1)RB-tree 有自动排序功能,hashtable没有。 + +### 8、hash_multiset- 底层 hashtable + +1)hash_multiset的特性与multiset完全相同,唯一的差别在于底层实现是hash_table。因此hash_multiset的元素不会被自动排序。 + +2)hash_multiset和hash_set实现上的唯一差别是,前者的元素插入操作采用底层机制hash_table的insert_equal(), + +后者则是采用insert_unique()。 + +### 9、hash_multimap- 底层 hashtable + +1)hash_multimap的特性与multimap完全相同,唯一的差别在于底层实现是hash_table。因此hash_multimap的元素不会被自动排序。 + +2)hash_multimap和hash_map实现上的唯一差别是,前者的元素插入操作采用底层机制hash_table的insert_equal(),后者则是采用insert_unique()。 + +注:C++11把unordered_set、unordered_map、unordered_multiset、unordered_multimap添加到标准库中。 + +​ 最初的 C++ 标准库中没有类似 hash_map 的实现,但不同实现者自己提供了非标准的 hash_map。 因为这些实现不是遵循标准编写的,所以它们在功能和性能保证方面都有细微差别。 + +从 C++ 11 开始,hash_map 实现已被添加到标准库中。但为了防止与已开发的代码存在冲突,决定使用替代名称 unordered_map。这个名字其实更具描述性,因为它暗示了该类元素的无序性。 +———————————————— + +## 第六章 算法 + +1)自写仿函数 + +```c++ +template + +struct display{ + + operator()(const T& x)const + + { cout<val == val ){ + ListNode* tmp = head; + head = head->next; + delete tmp; + } + //删除非头结点 + ListNode* cur = head; + while( cur != nullptr && cur->next != nullptr){ + if( cur->next->val == val ){ + ListNode* tmp = cur->next; + cur->next = cur->next->next; + delete tmp; + } + else{ + cur = cur->next; + } + } + return head; + } +}; + + +``` + +```c++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode() : val(0), next(nullptr) {} + * ListNode(int x) : val(x), next(nullptr) {} + * ListNode(int x, ListNode *next) : val(x), next(next) {} + * }; + */ +class Solution { +public: + ListNode* removeElements(ListNode* head, int val) { + //设置虚拟头节点 + ListNode* dumhead = new ListNode(0); + dumhead->next = head; + ListNode* cur = dumhead; + while( cur->next !=nullptr ){ + if(cur->next->val == val){ + ListNode* tmp = cur->next; + cur->next = cur->next->next; + delete tmp; + } + else{ + cur = cur->next; + } + } + return dumhead->next; + } +}; +``` + +## 2. 707 设计链表 + +### 1) 设计单链表 + +```c++ +class MyLinkedList { +private: + struct Node{ + int val; + Node* next; + Node():val(0),next(nullptr){} + Node(int _val):val(_val),next(nullptr){} + }; + Node* dumhead; + int size; +public: + /** Initialize your data structure here. */ + MyLinkedList() { + dumhead = new Node(0); + size = 0; + } + + /** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */ + int get(int index) { + Node* cur = dumhead->next; + if( index >= 0 && index < size){ + while( index-- ){ + cur = cur->next; + } + return cur->val; + } + else return -1; + } + + /** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */ + void addAtHead(int val) { + Node* node = new Node(val); + if(size > 0) node->next = dumhead->next; + dumhead->next = node; + size++; + } + + /** Append a node of value val to the last element of the linked list. */ + void addAtTail(int val) { + Node* cur = dumhead; + Node* node = new Node(val); + while( cur->next != nullptr ){ + cur = cur->next; + } + cur->next = node; + size++; + } + + /** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */ + void addAtIndex(int index, int val) { + if( index > size ) return; + Node* cur = dumhead; + while( index-- ){ + cur = cur->next; + } + Node* node = new Node(val); + node->next = cur->next; + cur->next = node; + size++; + } + + /** Delete the index-th node in the linked list, if the index is valid. */ + void deleteAtIndex(int index) { + if( index >= 0 && index < size){ + Node* cur = dumhead; + while( index-- ){ + cur = cur->next; + } + Node* tmp = cur->next; + cur->next = cur->next->next; + size--; + delete tmp; + } + } +}; + +/** + * Your MyLinkedList object will be instantiated and called as such: + * MyLinkedList* obj = new MyLinkedList(); + * int param_1 = obj->get(index); + * obj->addAtHead(val); + * obj->addAtTail(val); + * obj->addAtIndex(index,val); + * obj->deleteAtIndex(index); + */ +``` + +### 2) 设计双链表 + +```c++ +class MyLinkedList { +private: + struct Node{ + int val; + Node* next; + Node* pre; + Node():val(0),next(nullptr),pre(nullptr){} + Node(int _val):val(_val),next(nullptr),pre(nullptr){} + }; + Node* dumhead; + Node* dumtail; + int size; +public: + /** Initialize your data structure here. */ + MyLinkedList() { + dumhead = new Node(0); + dumtail = new Node(0); + dumhead->next = dumtail; + dumtail->pre = dumhead; + size = 0; + } + + /** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */ + int get(int index) { + if( index < 0 || index >= size ) return -1; + if( index < size/2 ){ + Node* cur = dumhead; + while( index-- ) cur = cur->next; + cur = cur->next; + return cur->val; + } + else{ + Node* cur = dumtail; + index = size - index; + while( index-- ) cur = cur->pre; + return cur->val; + } + } + + /** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */ + void addAtHead(int val) { + Node* node = new Node(val); + node->next = dumhead->next; + node->pre = dumhead; + dumhead->next->pre = node; + dumhead->next = node; + size++; + } + + /** Append a node of value val to the last element of the linked list. */ + void addAtTail(int val) { + Node* node = new Node(val); + node->next = dumtail; + node->pre = dumtail->pre; + dumtail->pre->next = node; + dumtail->pre = node; + size++; + } + + /** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */ + void addAtIndex(int index, int val) { + if( index > size ) return; + Node* cur; + if( index < size/2 ){ + cur = dumhead; + while( index-- ) cur = cur->next; + } + else{ + cur = dumtail; + index = size - index + 1; + while( index-- ) cur = cur->pre; + } + Node* node = new Node(val); + node->pre = cur; + node->next = cur->next; + cur->next->pre = node; + cur->next = node; + size++; + } + + /** Delete the index-th node in the linked list, if the index is valid. */ + void deleteAtIndex(int index) { + if( index < 0 || index >= size ) return; + Node* cur; + if( index < size/2 ){ + cur = dumhead; + while( index-- ) cur = cur->next; + } + else{ + cur = dumtail; + index = size - index + 1; + while( index-- ) cur = cur->pre; + } + Node* tmp = cur->next; + cur->next->next->pre = cur; + cur->next = cur->next->next; + delete tmp; + size--; + } +}; + +/** + * Your MyLinkedList object will be instantiated and called as such: + * MyLinkedList* obj = new MyLinkedList(); + * int param_1 = obj->get(index); + * obj->addAtHead(val); + * obj->addAtTail(val); + * obj->addAtIndex(index,val); + * obj->deleteAtIndex(index); + */ +``` + +## 3. 206 反转链表 + +```c++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode() : val(0), next(nullptr) {} + * ListNode(int x) : val(x), next(nullptr) {} + * ListNode(int x, ListNode *next) : val(x), next(next) {} + * }; + */ +class Solution { +public: + ListNode* reverseList(ListNode* head) { + ListNode* cur = head; + ListNode* pre = nullptr; + while( cur != nullptr ){ + ListNode* tmp = cur->next; + cur->next = pre; + pre = cur; + cur = tmp; + } + return pre; + } +}; +``` + +## 4. 两两交换链表中的节点 + +```c++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode() : val(0), next(nullptr) {} + * ListNode(int x) : val(x), next(nullptr) {} + * ListNode(int x, ListNode *next) : val(x), next(next) {} + * }; + */ +class Solution { +public: + // exampale : dumhead->1->2->3->4->NULL + ListNode* swapPairs(ListNode* head) { + ListNode* dumhead = new ListNode(0); + dumhead->next = head; + ListNode* cur = dumhead; + while( cur->next != nullptr && cur->next->next != nullptr ){ + ListNode* tmp = cur->next; //1 + ListNode* tmp1 = cur->next->next->next;// 3 + cur->next = cur->next->next; // cur->2 步骤1 + tmp->next = tmp1; // 1->3 步骤2 + cur->next->next = tmp; // 2->1 步骤3 + cur = tmp; + } + return dumhead->next; + } +}; +``` + +## 5. 删除链表倒数第N个节点 + +```c++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode() : val(0), next(nullptr) {} + * ListNode(int x) : val(x), next(nullptr) {} + * ListNode(int x, ListNode *next) : val(x), next(next) {} + * }; + */ +class Solution { +public: + ListNode* removeNthFromEnd(ListNode* head, int n) { + ListNode* dumhead = new ListNode(0); + dumhead->next = head; + ListNode* fast = dumhead; + ListNode* slow = dumhead; + while( n-- ) fast = fast->next; + while( fast->next != nullptr ){ + fast = fast->next; + slow = slow->next; + } + ListNode* tmp = slow->next; + slow->next = slow->next->next; + delete tmp; + return dumhead->next; + } +}; +``` + +## 6. 链表相交 + +```c++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode(int x) : val(x), next(NULL) {} + * }; + */ +//思路: A + B , B + A 注意 (A == NULL && B != NULL) 时, A跳转至 headB; +class Solution { +public: + ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { + ListNode* A = headA; + ListNode* B = headB; + while( A != NULL && B != NULL ){ + if(A == B ) return A; + A = A->next; + B = B->next; + if(A == NULL && B != NULL) A = headB; + if(B == NULL && A != NULL) B = headA; + } + return NULL; + } +}; +``` + +## 7. 寻找链表环入口-[142. 环形链表 II](https://leetcode-cn.com/problems/linked-list-cycle-ii/) + +如下图所示,设链表中环外部分的长度为 a。**slow** 指针进入环后,又走了 b的距离与**fast** 相遇。此时,**fast** 指针已经走完了环的 n 圈,因此**fast**走过的总距离为 **a+n(b+c)+b=*a*+(*n*+1)*b*+*n*c**。 + +![](C:\Users\38032\Desktop\note\寻找链表环入口示意图.png) + +```c++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode(int x) : val(x), next(NULL) {} + * }; + */ +// 思路: x = (y+z)(n-1)+z; 1.快慢指针第一次相遇。 2. 定义cur指针到头节点,同时移动cur与slow相遇,即为入口。 +class Solution { +public: + ListNode *detectCycle(ListNode *head) { + ListNode* fast = head; + ListNode* slow = head; + while( fast != NULL && fast->next != NULL ){ + fast = fast->next->next; + slow = slow->next; + if(fast == slow){ + ListNode* cur = head; + while( cur != slow ){ + cur = cur->next; + slow = slow->next; + } + return cur; + } + } + return NULL; + } +}; +``` + +# 2.哈希表篇 + +## 2. 242 有效的字母异位词 + +```c++ +//思路:哈希表计数 +class Solution { +public: + bool isAnagram(string s, string t) { + int record[26] = {0}; + for(auto c : s){ + record[c-'a']++; + } + for(auto c : t){ + record[c-'a']--; + } + for(int i = 0;i < 26;i++ ){ + if( record[i] != 0) + return false; + } + return true; + } +}; +``` + +## 3. 349 两个有序数组的交集 + +```c++ +//思路 排序+双指针 + +class Solution{ +public: + vector intersection(vectornums1,vectornums2){ + sort(nums1.begin(),nums1.end()); + sort(nums2.begin(),nums2.end()); + int lenth1 = nums1.size(),lenth2 = nums2.size(); + int index1 = 0,index2 = 0; + vector ans; + while( index1 < lenth1 && index2 < lenth2){ + int num1 = nums1[index1],num2 = nums2[index2]; + if( num1 == num2 ){ + if( ans.empty() || num1 != ans.back() ) + ans.push_back(num1); + index1++; + index2++; + } + else if( num1 < num2 ){ + index1++; + } + else index2++; + } + return ans; + } +}; +``` + +## 4. 202 快乐数 + +```c++ +/* 思路: 最终会得到 1,最终会进入循环。*/ +class Solution { +public: + int getnext(int n){ + int ans = 0; + while(n > 0){ + ans += pow(n%10,2); + n /= 10; + } + return ans; + } + + bool isHappy(int n) { + unordered_set s; + while( n != 1 && s.find(n) == s.end() ){ + s.emplace(n); + n = getnext(n); + } + return n == 1; + } +}; +``` + +## 5. 1 两数之和、三数之和、四数之和 + +```c++ +// 哈希表 +class Solution { +public: + vector twoSum(vector& nums, int target) { + unordered_mapmp; + for(int i = 0;i < nums.size();i++ ){ + int tmp = target - nums[i]; + if( mp.find(tmp) != mp.end() && mp[tmp] != i ){ + return vector{mp[tmp],i}; + } + else mp.emplace(nums[i],i); + } + return {}; + } +}; +``` + +```c++ +//三数之和, 思路:当我们需要枚举数组中的两个元素时,如果我们发现随着第一个元素的递增,第二个元素是递减的,那么就可以使用双指针的方法,将枚举的时间复杂度从 O(N^2)减少至 O(N)。 + +class Solution { +public: + vector> threeSum(vector& nums) { + vector> ans; + sort(nums.begin(),nums.end()); + int n = nums.size(); + for(int first = 0;first < n;first++ ){ + if ( first != 0 && nums[first] == nums[first - 1] ) continue; //避免枚举重复元素 + int third = n - 1; + int target = -nums[first]; + for(int second = first + 1;second < n;second++ ){ + if ( second != first + 1 && nums[second] == nums[second - 1] ) continue;//避免枚举重复元素 + while( second < third && nums[second] + nums[first] + nums[third] > 0 ){ + third--; + } + if( second == third ) break; + if( nums[second] + nums[third] == target ){ + ans.push_back({nums[first],nums[second],nums[third]}); + } + } + } + return ans; + } +}; +``` + +```c++ +//四数之和 ,思路类似于三数之和 +class Solution { +public: + vector> fourSum(vector& nums, int target) { + vector> ans; + sort(nums.begin(),nums.end()); + int n = nums.size(); + if(n < 4 ) return ans; + for(int first = 0;first < n - 3;first++ ){ + if(first > 0 && nums[first] == nums[first - 1]) continue; + if (nums[first] + nums[first + 1] + nums[first + 2] + nums[first + 3] > target) break; + if (nums[first] + nums[n - 3] + nums[n - 2] + nums[n - 1] < target) continue; + for(int second = first + 1;second < n - 2;second++ ){ + if(second > first + 1 && nums[second] == nums[second - 1]) continue; + if (nums[first] + nums[second] + nums[second + 1] + nums[second + 2] > target) break; + if (nums[first] + nums[second] + nums[n - 2] + nums[n - 1] < target) continue; + int third = second + 1; + int four = n - 1; + while( third < four ){ + int sum = nums[first] + nums[second] + nums[third] + nums[four]; + if( sum == target){ + ans.push_back({nums[first],nums[second],nums[third],nums[four]}); + while( third < four && nums[third] == nums[third+1] ) third++; + third++; + while( third < four && nums[four] == nums[four-1] ) four--; + four--; + } + else if( sum > target ) four--; + else third++; + } + } + } + return ans; + } +}; +``` + +## 6. [981. 基于时间的键值存储](https://leetcode-cn.com/problems/time-based-key-value-store/) + +```go +class TimeMap { + unordered_map>> m; + +public: + TimeMap() {} + + void set(string key, string value, int timestamp) { + m[key].emplace_back(timestamp, value); + } + + string get(string key, int timestamp) { + auto &pairs = m[key]; + // 使用一个大于所有 value 的字符串,以确保在 pairs 中含有 timestamp 的情况下也返回大于 timestamp 的位置 + pair p = {timestamp, string({127})}; + auto i = upper_bound(pairs.begin(), pairs.end(), p); + if (i != pairs.begin()) { + return (i - 1)->second; + } + return ""; + } +}; + +``` + + + +# 3.数组 + +## 1. 704 二分查找 + +```c++ +//输入数组:严格单调递增 +class Solution { +public: + int search(vector& nums, int target) { + int end = nums.size()-1; + int begin = 0; + while( begin <= end ){ + int mid = begin + (end - begin)/2; + if( nums[mid] < target ) begin = mid + 1; + else if( nums[mid] > target ) end = mid - 1; + else if( nums[mid] == target) return mid; + } + return -1; + } +}; +``` + +## 2. 34 在排序数组中查找元素的第一个和最后一个位置 + +```c++ +class Solution { +public: + int lower_bound(vector& nums,int target){ + int r = nums.size() - 1; + int l = 0; + while( l <= r ){ + int mid = l + (r - l)/2; + if( nums[mid] < target ) l = mid + 1; + else if( nums[mid] > target ) r = mid - 1; + else if( nums[mid] == target ) r = mid - 1; + } + //注意处理边界问题 + if( l >= nums.size() || nums[l] != target ) return -1; + return l; + } + + int upper_bound(vector& nums,int target){ + int r = nums.size() - 1; + int l = 0; + while( l <= r ){ + int mid = l + (r - l)/2; + if( nums[mid] < target ) l = mid + 1; + else if( nums[mid] > target ) r = mid - 1; + else if( nums[mid] == target ) l = mid + 1; + } + //注意处理边界问题 + if( r < 0 || nums[r] != target ) return -1; + return r; + } + + vector searchRange(vector& nums, int target) { + if(nums.size() == 0) return {-1,-1}; + return vector{lower_bound(nums,target),upper_bound(nums,target)}; + } +}; +``` + +## 3. 27 移除元素 + +给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 + +不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 + +元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。 + +```c++ +class Solution { +public: + int removeElement(vector& nums, int val) { + int left = 0, right = nums.size(); + while (left < right) { + if (nums[left] == val) { + nums[left] = nums[right - 1]; + right--; + } else { + left++; + } + } + return left; + } +}; + +``` + +## 4. 977 有序数组的平方 + +```c++ +//双指针,指向数组两端,计算平方值,在比较。 +class Solution { +public: + vector sortedSquares(vector& nums) { + int n = nums.size(); + if( n == 0) return {}; + if( n == 1) return vector{nums[0]*nums[0]}; + vectorans(n,0); + + int pos = 0; + int l = 0,r = 1; + for(int i = 0;i < n-1; i++){ + if(nums[i] < 0 ){ + l = i; + r = i+1; + } + else break; + } + while( l >= 0 || r < n){ + if( l < 0){ + ans[pos++] = nums[r] * nums[r]; + r++; + } + else if( r == n){ + ans[pos++] = nums[l] * nums[l]; + l--; + } + else if( nums[l] * nums[l] < nums[r] * nums[r] ){ + ans[pos++] = nums[l] * nums[l]; + l--; + } + else if( nums[l] * nums[l] >= nums[r] * nums[r] ){ + ans[pos++] = nums[r] * nums[r]; + r++; + } + } + return ans; + } +}; +``` + +## 5. 长度最小的子数组 + +```c++ +class Solution { +public: + int minSubArrayLen(int target, vector& nums) { + int n = nums.size(); + if(n == 0) return 0; + int l = 0; + int r = -1; + int sum = 0; + int ans = INT_MAX; + while( l <= r || r == -1 ){ + while( sum < target && r < n -1){ + r++; + sum += nums[r]; + } + if( sum >= target ){ + int tmp = r - l + 1; + if( ans > tmp ) ans = tmp; + } + sum -= nums[l]; + l++; + } + return ans == INT_MAX? 0:ans; + } +}; +``` + +```c++ +class Solution { +public: + int minSubArrayLen(int s, vector& nums) { + int n = nums.size(); + if (n == 0) { + return 0; + } + int ans = INT_MAX; + int start = 0, end = 0; + int sum = 0; + while (end < n) { + sum += nums[end]; + while (sum >= s) { + ans = min(ans, end - start + 1); + sum -= nums[start]; + start++; + } + end++; + } + return ans == INT_MAX ? 0 : ans; + } +}; + +``` + +## 6. 59 螺旋矩阵 + +```c++ +class Solution { +public: + vector> generateMatrix(int n) { + vector> res(n, vector(n, 0)); // 使用vector定义一个二维数组 + int startx = 0, starty = 0; // 定义每循环一个圈的起始位置 + int loop = n / 2; // 每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理 + int mid = n / 2; // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2) + int count = 1; // 用来给矩阵中每一个空格赋值 + int offset = 1; // 每一圈循环,需要控制每一条边遍历的长度 + int i,j; + while (loop --) { + i = startx; + j = starty; + + // 下面开始的四个for就是模拟转了一圈 + // 模拟填充上行从左到右(左闭右开) + for (j = starty; j < starty + n - offset; j++) { + res[startx][j] = count++; + } + // 模拟填充右列从上到下(左闭右开) + for (i = startx; i < startx + n - offset; i++) { + res[i][j] = count++; + } + // 模拟填充下行从右到左(左闭右开) + for (; j > starty; j--) { + res[i][j] = count++; + } + // 模拟填充左列从下到上(左闭右开) + for (; i > startx; i--) { + res[i][j] = count++; + } + + // 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1) + startx++; + starty++; + + // offset 控制每一圈里每一条边遍历的长度 + offset += 2; + } + + // 如果n为奇数的话,需要单独给矩阵最中间的位置赋值 + if (n % 2) { + res[mid][mid] = count; + } + return res; + } +}; + +``` + + + + + +# 4.字符串 + +## 1. 大数加减法 + +```c++ + +//"123456789123456789123456789-1234567891456789" +// 1234567891456789 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define ll long long +using namespace std; + +string addstring(string s1, string s2) { + int i = s1.length() - 1; + int j = s2.length() - 1; + int add = 0; + string ss = ""; + while (i >= 0 || j >= 0|| add != 0) { + if (i >= 0) + add = add + s1[i--] - '0'; + if (j >= 0) + add = add + s2[j--] - '0'; + ss = ss + to_string(add % 10); + add = add / 10; + } + reverse(ss.begin(), ss.end()); + return ss; +} + +//大数减法 +string sub(string s1,string s2){ + bool minus = false; + if( s1 < s2 ){ + swap(s1,s2); + minus = true; + } + int i = s1.size() - 1; + int j = s2.size() - 1; + int flag = 0; + string ans = ""; + while( i >= 0 && j >= 0 ){ + int tmp = tmp - flag; + if( s1[i] >= s2[j] ){ + tmp = s1[i] - s2[j]; + ans += to_string(tmp); + flag = 0; + } + else{ + tmp = s1[i] - s2[j] + 10; + ans += to_string(tmp); + flag = 1; + } + i--; + j--; + } + // 处理较大数的剩余部分 + while( i >= 0 ){ + if(flag == 0){ + ans += s1[i]; + } + else{ + int tmp = s1[i] - '0' - flag; + ans += to_string(tmp); + flag = 0; + } + i--; + } + // 翻转 + reverse(ans.begin(),ans.end()); + //去除前导 0 + int k = 0; + while( k < ans.size() && ans[k] == '0') k++; + if( k == ans.size() ){ + ans = "0"; + } + else ans = ans.substr(k); + // 结果是否为负 + return minus?"-" + ans:ans; +} + +int main() { + string s1, s2; + cin >> s1 >> s2; + cout << sub(s1, s2) << endl; + system("pause"); + return 0; +} +``` + +## 2. 大数乘法 + +```c++ +//使用大小为 n + m 的数组,num1的第i位乘以num2的第j位,结果对应存放在数组的i+j+1的位置。 +class Solution { +public: + string multiply(string num1, string num2) { + int n = num1.size(); + int m = num2.size(); + vector result(n + m,0);//123 * 123 + for(int i = n - 1; i >= 0; i--){ + for(int j = m - 1;j >= 0; j--){ + int tmp = (num1[i] - '0') * (num2[j] - '0'); + tmp += result[i + j + 1]; + result[i + j] += tmp / 10; //注意 这里是 += + result[i + j + 1] = tmp % 10; + } + } + int i = 0; + while( i < n + m && result[i] == 0 ){ + i++; + } + string ans; + for(; i < n + m; i++){ + ans.push_back(result[i] + '0'); + } + return ans.size() == 0?"0":ans; + } +}; +``` + +## 3. 翻转字符串 + +```c++ +class Solution { +public: + void reverseString(vector& s) { + int end = s.size() - 1; + int begin = 0; + while( begin < end ){ + char c = s[begin]; + s[begin] = s[end]; + s[end] = c; + begin++; + end--; + } + } +}; +``` + +## 4. 翻转字符串 II + +```c++ +/*给定一个字符串 s 和一个整数 k,你需要对从字符串开头算起的每隔 2k 个字符的前 k 个字符进行反转。 +如果剩余字符少于 k 个,则将剩余字符全部反转。 +如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。 +*/ +class Solution { +public: + void reverse(string &s,int begin,int end){ + while ( begin < end ){ + swap(s[begin],s[end]); + begin++; + end--; + } + } + + string reverseStr(string s, int k) { + int n = s.size() - 1; + int begin = 0; + while ( 1 ){ + if( begin > n ) break; + int len = n - begin + 1; + if( len < k ) reverse(s,begin,n); + else reverse(s,begin,begin + k -1); + begin += 2*k; + } + return s; + } +}; +``` + +## 5. 替换空格 + +```c++ +/*思路:首先扩充数组到每个空格替换成"%20"之后的大小。 + +然后从后向前替换空格,也就是双指针法,过程如下: + +i指向新长度的末尾,j指向旧长度的末尾。*/ + +class Solution { +public: + string replaceSpace(string s) { + int len = s.size(); + int cnt = 0; + for(auto &c : s ){ + if( c == ' '){ + cnt++; + } + } + s.resize(len + 2 * cnt); + for(int i = len - 1,j = s.size() - 1; i < j; i--,j--){ + if(s[i] != ' '){ + s[j] = s[i]; + } + else{ + s[j - 2] = '%'; + s[j - 1] = '2'; + s[j] = '0'; + j -= 2; + } + } + return s; + } +}; +``` + +## 6. 翻转字符串里的单词 + +```c++ +//思路:字符串s整体反转,再逐个移位,反转单词。 +class Solution { +public: + string reverseWords(string s) { + reverse(s.begin(),s.end()); + int pos = 0; + int n = s.size(); + for(int start = 0; start < n; start++){ + if( s[start] != ' ' ){ + int end = start; + // 填一个空白字符然后将pos移动到下一个单词的开头位置 + if( pos != 0) s[pos++] = ' '; + // 循环遍历至单词的末尾 + while( end < n && s[end] != ' ') s[pos++] = s[end++]; + // 反转整个单词 + reverse(s.begin() + pos - (end - start),s.begin() + pos); + // 更新start,去找下一个单词 + start = end ; + } + } + //删除多余位置 + s.erase(s.begin() + pos,s.end()); + return s; + } +}; +``` + +## 7. 左旋转字符串 + +```c++ +//思路:先局部反转前n个字符串和剩余字符串,再整体反转。 +class Solution { +public: + string reverseLeftWords(string s, int n) { + //反转前n个字符串 + reverse(s.begin(),s.begin() + n); + //反转剩余字符串 + reverse(s.begin() + n,s.end()); + //整体反转 + reverse(s.begin(),s.end()); + return s; + } +}; +``` + +## 8. 28 实现 strStr()-KMP算法 + +字符串的**前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串**;**后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串**。 + +**//前缀表统一减一 C++代码实现** + +```c++ +/*思路:利用前缀表即next数组 +前缀表有什么作用呢? +前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。 + +复杂度分析 +时间复杂度:O(n+m),其中 n是字符串haystack 的长度,m 是字符串needle 的长度。我们至多需要遍历两字符串一次。 +空间复杂度:O(m),其中 m是字符串needle 的长度。我们只需要保存字符串needle 的前缀函数。*/ + +class Solution { +public: + void getnext(int *next,const string &s){ + int j = -1; + int n = s.size(); + next[0] = j;// 此行容易忘记 + for (int i = 1; i < n; i++){ + //前后缀不同 + while (j >= 0 && s[ i ] != s[j + 1]){ + j = next[j];//回退 + } + //找到相同前后缀 + if (s[i] == s[j + 1]){ + j++; + } + next[i] = j; + } + } + + int strStr(string haystack, string needle) { + if(needle.size() == 0) return 0; + int next[needle.size()]; + getnext(next,needle); + int j = -1; + for (int i = 0 ;i < haystack.size(); i++){ + //不匹配 + while(j >= 0 && haystack[i] != needle[j + 1]){ + j = next[j];//寻找之后的匹配 + } + //匹配,i,j同时后移 + if( haystack[i] == needle[j + 1]){ + j++;//i的增加在for循环里面 + } + if(j == needle.size() -1){// j移动到了模式串的末尾,文本串s里出现了模式串t + return i - j;//当前在文本串匹配模式串的位置i 减去 模式串的长度,就是文本串字符串中出现模式串的第一个位置 + } + } + return -1; + } +}; +``` + +**前缀表(不减一)C++实现** + +那么前缀表就不减一了,也不右移的,到底行不行呢?行! + +我之前说过,这仅仅是KMP算法实现上的问题,如果就直接使用前缀表可以换一种回退方式,找j=next[j-1] 来进行回退。 + +主要就是j=next[x]这一步最为关键! + +```c++ +class Solution{ +public: + void getnext(int *next,const string &s){ + int j = 0; + next[0] = j;// 此行容易忘记 + for (int i = 1; i < s.size(); i++){ + while( j >= 1 && s[i] != s[j]){ + j = next[j - 1]; + } + if(s[i] == s[j]){ + j++; + } + next[i] = j; + } + } + + int strStr(string haystack,string needle){ + if(needle.size() == 0) return 0; + int j = 0; + int next[needle.size()]; + getnext(next,needle); + for(int i = 0; i < haystack.size(); i++){ + while(j >= 1 && haystack[i] != needle[j]){ + j = next[j - 1]; + } + if(haystack[i] == needle[j]){ + j++; + } + if(j == needle.size() ){ + return i - j + 1 ; + } + } + return -1; + } +}; +``` + +## 9. 459重复的子字符串 + +```c++ +//思路:,求出前缀表,然后数组长度减去最长相同前后缀的长度相当于是第一个周期的长度,也就是一个周期的长度,如果这个周期可以被整除,就说明整个数组就是这个周期的循环。 +class Solution { +public: + void getnext(int *next,const string&s){ + int j = -1; + next[0] = j;//起始位置为-1,此行容易忘记 + for(int i = 1; i < s.size(); i++){ + while (j >= 0 && s[i] != s[j + 1]){ + j = next[j]; + } + if (s[i] == s[j + 1]){ + j++; + } + next[i] = j; + } + } + + bool repeatedSubstringPattern(string s) { + int len = s.size(); + int next[len]; + getnext(next,s); + if(next[len - 1] != -1 && len % (len - (next[len - 1] + 1)) == 0){ + return true; + } + return false; + } +}; +``` + +## [10. 字符串转换整数 (atoi)](https://leetcode-cn.com/problems/string-to-integer-atoi/) + +```c++ +class Solution { +public: + int myAtoi(string s) { + long long res = 0; + int flag = 1; + int i = 0; + while( s[i] == ' ') i++; + if( s[i] == '-' ) flag = -1; + if( s[i] == '-' || s[i] == '+' ) i++; + int n = s.size(); + while ( i < n && isdigit(s[i]) ) { + int tmp = s[i] - '0'; + if( res > INT_MAX / 10 || ( res == INT_MAX / 10 && tmp > 7 ) ) + return flag == 1?INT_MAX:INT_MIN; + res = res * 10 + tmp; + i++; + } + return flag == 1?res:-res; + } +}; +``` + + + +# 5.栈与队列 + +## 1. [232. 用栈实现队列](https://leetcode-cn.com/problems/implement-queue-using-stacks/) + +```c++ +//思路:两个栈,一个Stackin,另一个Stackout +class MyQueue { +private: + stacks1; + stacks2; +public: + /** Initialize your data structure here. */ + MyQueue() { + + } + + /** Push element x to the back of queue. */ + void push(int x) { + s1.push(x); + } + + /** Removes the element from in front of queue and returns that element. */ + int pop() { + if(s2.empty()){ + while (!s1.empty()){ + int tmp = s1.top(); + s1.pop(); + s2.push(tmp); + } + } + int tmp = s2.top(); + s2.pop(); + return tmp; + } + + /** Get the front element. */ + int peek() { + //调用自身pop() + int tmp = this->top(); + s2.push(tmp); + return tmp; + } + + /** Returns whether the queue is empty. */ + bool empty() { + return s2.empty() && s1.empty(); + } +}; + +/** + * Your MyQueue object will be instantiated and called as such: + * MyQueue* obj = new MyQueue(); + * obj->push(x); + * int param_2 = obj->pop(); + * int param_3 = obj->peek(); + * bool param_4 = obj->empty(); + */ +``` + +## 2. [225 用队列实现栈](https://leetcode-cn.com/problems/implement-stack-using-queues/) + +```c++ +//两个队列,q1存放栈元素,q2为辅助栈 +class MyStack { +public: + queue q1; + queue q2; + /** Initialize your data structure here. */ + MyStack() { + + } + + /** Push element x onto stack. */ + void push(int x) { + q2.push(x); + while (!q1.empty()){ + q2.push(q1.front()); + q1.pop(); + } + swap(q1,q2); + } + + /** Removes the element on top of the stack and returns that element. */ + int pop() { + int tmp = q1.front(); + q1.pop(); + return tmp; + } + + /** Get the top element. */ + int top() { + return q1.front(); + } + + /** Returns whether the stack is empty. */ + bool empty() { + return q1.empty(); + } +}; + +/** + * Your MyStack object will be instantiated and called as such: + * MyStack* obj = new MyStack(); + * obj->push(x); + * int param_2 = obj->pop(); + * int param_3 = obj->top(); + * bool param_4 = obj->empty(); + */ +``` + +```c++ +//思路:一个队列实现 +class MyStack { +public: + queue q; + /** Initialize your data structure here. */ + MyStack() { + + } + + /** Push element x onto stack. */ + void push(int x) { + q.push(x); + } + + /** Removes the element on top of the stack and returns that element. */ + int pop() { + int n = q.size(); + n--; + while (n--){ + int tmp = q.front(); + q.pop(); + q.push(tmp); + } + int ans = q.front(); + q.pop(); + return ans; + } + + /** Get the top element. */ + int top() { + return q.back(); + } + + /** Returns whether the stack is empty. */ + bool empty() { + return q.empty(); + } +}; + +/** + * Your MyStack object will be instantiated and called as such: + * MyStack* obj = new MyStack(); + * obj->push(x); + * int param_2 = obj->pop(); + * int param_3 = obj->top(); + * bool param_4 = obj->empty(); + */ +``` + +## [3. 20. 有效的括号](https://leetcode-cn.com/problems/valid-parentheses/) + +```c++ +//思路:使用栈,左括号入栈,右括号匹配左括号 +class Solution { +public: + bool is_ok(char a,char b){ + if( (a=='[' && b == ']' ) || ( a=='(' && b == ')' ) || (a=='{' && b == '}' ) ) return true; + return false; + } + + bool isValid(string s) { + stack stk; + for (int i = 0; i < s.size(); i++){ + if (s[i] == ')' || s[i] == '}' || s[i] == ']'){ + if(stk.empty()) return false; + char tmp = stk.top(); + if ( is_ok(tmp,s[i]) ) stk.pop(); + else return false; + } + else{ + stk.push(s[i]); + } + } + return stk.empty(); + } +}; + +``` + +## [4.1047 删除字符串中的所有相邻重复项](https://leetcode-cn.com/problems/remove-all-adjacent-duplicates-in-string/) + +```c++ +//string 替代 栈的作用 +class Solution { +public: + string removeDuplicates(string s) { + string result; + for(char c : s) { + if(result.empty() || result.back() != c) { + result.push_back(c); + } + else { + result.pop_back(); + } + } + return result; + } +}; +``` + +```c++ +//使用栈 +class Solution { +public: + string removeDuplicates(string s) { + stack stk; + for(auto &c : s){ + if(stk.empty() || c != stk.top()){ + stk.push(c); + } + else stk.pop(); + } + string ans; + while(!stk.empty()){ + ans += stk.top(); + stk.pop(); + } + reverse(ans.begin(),ans.end()); + return ans; + } +}; +``` + +## 5.[150. 逆波兰表达式求值](https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/) + +```c++ +//思路:利用栈 +class Solution { +public: + int evalRPN(vector& tokens) { + stack stk; + for(auto &str : tokens){ + if(str == "+" || str == "-" || str == "*" || str == "/"){ + int n1 = stk.top();stk.pop(); + int n2 = stk.top();stk.pop(); + if( str == "+" ){ + stk.push(n2 + n1); + } + else if(str == "-" ){ + stk.push(n2 - n1); + } + else if(str == "*"){ + stk.push(n2 * n1); + } + else stk.push(n2 / n1); + } + else{ + stk.push(atoi(str.c_str());//使用库函数 atoi() c_str() stoi() string -> int + } + } + return stk.top(); + } +}; +``` + +## 6.239 滑动窗口最大值 + +```c++ +/*思路: + 当滑动窗口向右移动时,我们需要把一个新的元素放入队列中。为了保持队列的性质,我们会不断地将新的元素与队尾的元素相比较,如果前者大于等于后者,那么队尾的元素就可以被永久地移除,我们将其弹出队列。我们需要不断地进行此项操作,直到队列为空或者新的元素小于队尾的元素。 + 由于队列中下标对应的元素是严格单调递减的,因此此时队首下标对应的元素就是滑动窗口中的最大值。但与方法一中相同的是,此时的最大值可能在滑动窗口左边界的左侧,并且随着窗口向右移动,它永远不可能出现在滑动窗口中了。因此我们还需要不断从队首弹出元素,直到队首元素在窗口中为止。 + 为了可以同时弹出队首和队尾的元素,我们需要使用双端队列。满足这种单调性的双端队列一般称作「单调队列」。*/ +class Solution { +public: + vector maxSlidingWindow(vector& nums, int k) { + int n = nums.size(); + deque q; + for (int i = 0; i < k; i++){ + while ( !q.empty() && nums[i] >= nums[q.back()] ){ + q.pop_back(); + } + q.push_back(i); + } + vectorans; + ans.push_back(nums[q.front()]); + for (int i = k; i < n; i++){ + while ( !q.empty() && nums[i] >= nums[q.back()] ){ + q.pop_back(); + } + q.push_back(i); + while ( !q.empty() && q.front() <= i - k ){ + q.pop_front(); + } + ans.push_back(nums[q.front()]); + } + return ans; + } +}; + +//时空复杂度:o(n) +``` + + + +```c++ +class Solution { +private: + class MyQueue { //单调队列(从大到小) + public: + deque que; // 使用deque来实现单调队列 + // 每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。 + // 同时pop之前判断队列当前是否为空。 + void pop(int value) { + if (!que.empty() && value == que.front()) { + que.pop_front(); + } + } + // 如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。 + // 这样就保持了队列里的数值是单调从大到小的了。 + void push(int value) { + while (!que.empty() && value > que.back()) { + que.pop_back(); + } + que.push_back(value); + + } + // 查询当前队列里的最大值 直接返回队列前端也就是front就可以了。 + int front() { + return que.front(); + } + }; +public: + vector maxSlidingWindow(vector& nums, int k) { + MyQueue que; + vector result; + for (int i = 0; i < k; i++) { // 先将前k的元素放进队列 + que.push(nums[i]); + } + result.push_back(que.front()); // result 记录前k的元素的最大值 + for (int i = k; i < nums.size(); i++) { + que.pop(nums[i - k]); // 滑动窗口移除最前面元素 + que.push(nums[i]); // 滑动窗口前加入最后面的元素 + result.push_back(que.front()); // 记录对应的最大值 + } + return result; + } +}; + +``` + +## [7. 347. 前 K 个高频元素](https://leetcode-cn.com/problems/top-k-frequent-elements/) + +```c++ +//哈希表 + 优先队列 +/* +大家对这个比较运算在建堆时是如何应用的,为什么左大于右就会建立小顶堆,反之建立大顶堆比较困惑。 + +确实 例如我们在写快排的cmp函数的时候,return left>right 就是从大到小,return leftm; + static bool cmp(pair&a,pair&b){ + return a.second>b.second; + } + + vector topKFrequent(vector& nums, int k) { + priority_queue, vector>, decltype(&cmp)> q(cmp); + for(auto i:nums){ + m[i]++; + } + for(auto it=m.begin();it!=m.end();it++){ + if(q.size()==k){ + if(q.top().second < it->second){ + q.pop(); + q.emplace(it->first,it->second); + } + } + else{ + q.emplace(it->first,it->second); + } + } + vectorv; + while(!q.empty()){ + v.push_back(q.top().first); + q.pop(); + } + return v; + } +}; +``` + +```c++ +class Solution { +public: + unordered_mapmp; + + struct cmp{//重写仿函数 + bool operator()(pair&a,pair&b)const{ + return a.second > b.second; + } + }; + vector topKFrequent(vector& nums, int k) { + priority_queue,vector>,cmp> q; + for (int i = 0; i < nums.size(); i++){ + mp[nums[i]]++; + } + + for (unordered_map::iterator it = mp.begin(); it != mp.end(); it++){ + if(q.size() < k) + q.push(pair(it->first,it->second)); + else{ + if( q.top().second < it->second ){ + q.pop(); + q.push(pair(it->first,it->second)); + } + } + } + vector ans; + while(!q.empty()){ + ans.push_back(q.top().first); + q.pop(); + } + return ans; + } +}; +``` + +# 6.二叉树 + +## 1 . [144. 二叉树的前序遍历](https://leetcode-cn.com/problems/binary-tree-preorder-traversal/) + +```c++ +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode() : val(0), left(nullptr), right(nullptr) {} + * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} + * }; + */ +class Solution{ +public: + + vector preorderTraversal(TreeNode* root) { + vectorans; + if(root == nullptr) return ans; + stack stk; + TreeNode* tmp = root; + while( !stk.empty() || tmp !=nullptr ){ + //入栈 + while( tmp!= nullptr ){ + ans.push_back(tmp->val); + stk.push(tmp); + tmp = tmp->left; + } + //出栈 + tmp = stk.top(); + stk.pop(); + tmp = tmp->right; + } + return ans; + } +}; + +// 第二种 +class Solution { +public: + vector preorderTraversal(TreeNode* root) { + vector result; + stack st; + if (root != NULL) st.push(root); + while (!st.empty()) { + TreeNode* node = st.top(); + if (node != NULL) { + st.pop(); + if (node->right) st.push(node->right); // 右 + if (node->left) st.push(node->left); // 左 + st.push(node); // 中 + st.push(NULL); + } + else { + st.pop(); + node = st.top(); + st.pop(); + result.push_back(node->val); + } + } + return result; + } +}; + +``` + +## 2. [94. 二叉树的中序遍历](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/) + +```c++ +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode() : val(0), left(nullptr), right(nullptr) {} + * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} + * }; + */ +class Solution { +public: + vector inorderTraversal(TreeNode* root) { + vectorans; + if( root == nullptr) return ans; + stackstk; + TreeNode* node = root; + while( !stk.empty() || node != nullptr ){ + while( node != nullptr){ + stk.push(node); + node = node->left; + } + node = stk.top(); + stk.pop(); + ans.push_back(node->val); + node = node->right; + } + return ans; + } +}; +// 第二种 +class Solution { +public: + vector inorderTraversal(TreeNode* root) { + vector result; + stack st; + if (root != NULL) st.push(root); + while (!st.empty()) { + TreeNode* node = st.top(); + if (node != NULL) { + st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中 + if (node->right) st.push(node->right); // 添加右节点(空节点不入栈) + + st.push(node); // 添加中节点 + st.push(NULL); // 中节点访问过,但是还没有处理,加入空节点做为标记。 + + if (node->left) st.push(node->left); // 添加左节点(空节点不入栈) + } + else { // 只有遇到空节点的时候,才将下一个节点放进结果集 + st.pop(); // 将空节点弹出 + node = st.top(); // 重新取出栈中元素 + st.pop(); + result.push_back(node->val); // 加入到结果集 + } + } + return result; + } +}; + +``` + +## 3. [145. 二叉树的后序遍历](https://leetcode-cn.com/problems/binary-tree-postorder-traversal/) + +```c++ +// 第二种 +class Solution { +public: + vector postorderTraversal(TreeNode* root) { + vector result; + stack st; + if (root != NULL) st.push(root); + while (!st.empty()) { + TreeNode* node = st.top(); + if (node != NULL) { + st.pop(); + st.push(node); // 中 + st.push(NULL); + + if (node->right) st.push(node->right); // 右 + if (node->left) st.push(node->left); // 左 + + } + else { + st.pop(); + node = st.top(); + st.pop(); + result.push_back(node->val); + } + } + return result; + } +}; + +``` + +## 4. [226. 翻转二叉树](https://leetcode-cn.com/problems/invert-binary-tree/) + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ + +//go版本 +func invertTree(root *TreeNode) *TreeNode { + if root == nil{ + return nil + } + tmp := root.Left + root.Left = root.Right + root.Right = tmp + invertTree(root.Left) + invertTree(root.Right) + return root +} +``` + +## 5.[101. 对称二叉树](https://leetcode-cn.com/problems/symmetric-tree/) + +```c++ +class Solution { +public: + bool check(TreeNode *u, TreeNode *v) { + queue q; + q.push(u); q.push(v); + while (!q.empty()) { + u = q.front(); q.pop(); + v = q.front(); q.pop(); + if (!u && !v) continue; + if ((!u || !v) || (u->val != v->val)) return false; + + q.push(u->left); + q.push(v->right); + + q.push(u->right); + q.push(v->left); + } + return true; + } + + bool isSymmetric(TreeNode* root) { + return check(root, root); + } +}; +``` + +```go +func isSymmetric(root * TreeNode) bool { + u,v := root,root + q := []*TreeNode{} + q = append(q,u) + q = append(q,v) + for len(q) > 0 { + u,v := q[0],q[1] + q = q[2:] + if u == nil && v == nil { + continue + } + if u == nil || v == nil { + return false + } + if u.val != v.val { + return false + } + q = append(q,u.Left) + q = append(q,v.Right) + + q = append(q,u.Right) + q = append(q,v.Left) + } + return true +} +``` + +## 6. [104. 二叉树的最大深度](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +// go 语言 + +func maxDepth(root *TreeNode) int { + if root == nil{ + return 0 + } + return max(maxDepth(root.Left) , maxDepth(root.Right)) + 1 +} + +func max(a,b int) int { + if a > b{ + return a + }else{ + return b + } +} +``` + +## 7. [111. 二叉树的最小深度](https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/) + +```c++ +class Solution { +public: + int minDepth(TreeNode* root) { + if(root == nullptr) return 0; + if(root->left== nullptr && root->right == nullptr) return 1; + int mindeep = INT_MAX; + if(root->left) mindeep = min(minDepth(root->left)+1,mindeep); + if(root->right) mindeep = min(minDepth(root->right)+1,mindeep); + return mindeep; + } +}; +``` + +## 8. [222. 完全二叉树的节点个数](https://leetcode-cn.com/problems/count-complete-tree-nodes/) + +```c++ +/* 二分查找 + 位运算 + 等比数列求和 Sn = a1*(1-q^n)/(1-q) ;对于最大层数为 h 的完全二叉树,节点个数一定在 [2^h,2^(h+1)-1]的范围内,可以在该范围内通过二分查找的方式得到完全二叉树的节点个数。 +*/ +class Solution { +public: + int countNodes(TreeNode* root) { + if (root == nullptr) { + return 0; + } + int level = 0; + TreeNode* node = root; + while (node->left != nullptr) { + level++; + node = node->left; + } + int low = 1 << level, high = (1 << (level + 1)) - 1; + while (low < high) { + int mid = (high - low + 1) / 2 + low; + if (exists(root, level, mid)) { + low = mid; + } else { + high = mid - 1; + } + } + return low; + } + + bool exists(TreeNode* root, int level, int k) { + int bits = 1 << (level - 1); + TreeNode* node = root; + while (node != nullptr && bits > 0) { + if (!(bits & k)) { + node = node->left; + } else { + node = node->right; + } + bits >>= 1; + } + return node != nullptr; + } +}; + +``` + +## [9. 110. 平衡二叉树](https://leetcode-cn.com/problems/balanced-binary-tree/) + +```c++ +class Solution { +public: + int deep(TreeNode* root){ + if(root == nullptr) return 0; + int deep_left = deep(root->left) ; + int deep_right = deep(root->right) ; + if( deep_left == -1 || deep_right == -1 || abs(deep_left - deep_right) > 1 ) { + return -1; + } + return max(deep_left,deep_right) + 1; + } + + bool isBalanced(TreeNode* root) { + return deep(root) != -1; + } +}; +``` + +## 10. [257. 二叉树的所有路径](https://leetcode-cn.com/problems/binary-tree-paths/) + +```c++ +class Solution { +public: + void dfs(TreeNode*root,string path,vector& ans){ + if(root == nullptr) return; + path += to_string(root->val); + if(root->left == nullptr && root->right == nullptr){ + ans.push_back(path); + return; + } + dfs(root->left,path + "->",ans); + dfs(root->right,path + "->",ans); + } + + vector binaryTreePaths(TreeNode* root) { + vector ans; + dfs(root,"",ans); + return ans; + } +}; +``` + +## 11. [404. 左叶子之和](https://leetcode-cn.com/problems/sum-of-left-leaves/) + +```go +//二叉树左叶子节点之和 +var sum int + +func dfs(root * TreeNode,flag int) { + if root == nil{ + return + } + if flag == 1 && root.Left == nil && root.Right == nil { + sum += root.Val + } + dfs(root.Left,1); + dfs(root.Right,0); +} + +func sumOfLeftLeaves(root *TreeNode) int { + if root == nil { + return 0 + } + sum = 0 + dfs(root,0) + return sum +} +``` + +## 12 [513. 找树左下角的值](https://leetcode-cn.com/problems/find-bottom-left-tree-value/) + +```go +func dfs(root *TreeNode,deep int,ans,high *int){ + if root == nil { + return + } + if root.Left == nil && root.Right == nil { + if deep > *high { + *ans = root.Val + *high = deep + return + } + } + dfs(root.Left,deep + 1,ans,high) + dfs(root.Right,deep + 1,ans,high) +} + +func findBottomLeftValue(root *TreeNode) int { + if root.Left == nil && root.Right == nil{ + return root.Val + } + ans ,high := 0,0 + dfs(root,0,&ans,&high) + return ans +} +``` + +## 13. [112. 路径总和](https://leetcode-cn.com/problems/path-sum/) + +```c++ +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode() : val(0), left(nullptr), right(nullptr) {} + * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} + * }; + */ +//判断路径总和是否等于tatgetsum +class Solution { +public: + + bool dfs(TreeNode* root,int targetSum,int sum){ + if( root == nullptr ) return false; + sum += root->val; + if(!root->left && !root->right && targetSum == sum){ + return true; + } + if( dfs(root->left,targetSum,sum) || dfs(root->right,targetSum,sum ) ){ + return true; + } + return false; + } + + bool hasPathSum(TreeNode* root, int targetSum) { + + if( root == nullptr ) return false; + + return dfs(root,targetSum,0); + + } +}; + +//迭代解法 +class Solution { +public: + bool hasPathSum(TreeNode *root, int sum) { + if (root == nullptr) { + return false; + } + queue que_node; + queue que_val; + que_node.push(root); + que_val.push(root->val); + while (!que_node.empty()) { + TreeNode *now = que_node.front(); + int temp = que_val.front(); + que_node.pop(); + que_val.pop(); + if (now->left == nullptr && now->right == nullptr) { + if (temp == sum) { + return true; + } + continue; + } + if (now->left != nullptr) { + que_node.push(now->left); + que_val.push(now->left->val + temp); + } + if (now->right != nullptr) { + que_node.push(now->right); + que_val.push(now->right->val + temp); + } + } + return false; + } +}; + +``` + + + +## 7. [501. 二叉搜索树中的众数](https://leetcode-cn.com/problems/find-mode-in-binary-search-tree/) + +```c++ +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode() : val(0), left(nullptr), right(nullptr) {} + * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} + * }; + */ +class Solution { +public: + vector ans; + int base, count, maxCount; + + void update(int x) { + if (x == base) { + ++count; + } else { + count = 1; + base = x; + } + if (count == maxCount) { + ans.push_back(base); + } + if (count > maxCount) { + maxCount = count; + ans = vector {base}; + } + } + vector findMode(TreeNode* root) { + if(!root) return {}; + stack q; + q.push(root); + while (!q.empty()) { + TreeNode* node = q.top(); + if(node){ + q.pop(); + if(node->right) q.push(node->right); + q.push(node); + q.push(nullptr); + if(node->left) q.push(node->left); + } + else{ + q.pop(); + node = q.top(); + q.pop(); + update(node->val); + } + } + return ans; + } +}; +``` + +# 7.动态规划 + +#### 1 [152. 乘积最大子数组](https://leetcode-cn.com/problems/maximum-product-subarray/) + +```c++ +//根据正负性讨论 +class Solution { +public: + int maxProduct(vector& nums) { + int n = nums.size(); + int maxF = nums[0],minF = nums[0],ans = nums[0]; + for (int i = 1;i < n;i++) { + int mi = minF; + int mn = maxF; + maxF = max(mn * nums[i], max(mi * nums[i], nums[i] ) ); + minF = min(mi * nums[i], min(mn * nums[i], nums[i] ) ); + ans = max(ans,maxF); + } + return ans; + } +}; +``` + +#### [2. 1143. 最长公共子序列](https://leetcode-cn.com/problems/longest-common-subsequence/) + +```c++ +// afece +//ace +//dp[i][j] 表示 {text}_1[0:i] 和 {text}_2[0:j]的最长公共子序列的长度。 + +class Solution { +public: + int longestCommonSubsequence(string text1, string text2) { + int n=text1.size(); + int m=text2.size(); + vector< vector >dp(n+1,vector(m+1,0)); + for(int i = 1;i < n+1;i++){ + for(int j = 1;j < m+1;j++){ + if(text1[i - 1] == text2[j - 1] ) + dp[i][j] = dp[i - 1][j - 1] + 1; + else + dp[i][j] = max(dp[i - 1][j],dp[i][j - 1]); + } + } + return dp[n][m]; + } +}; +``` + +#### [3. 300. 最长递增子序列](https://leetcode-cn.com/problems/longest-increasing-subsequence/) + +```c++ +//动态规划 dp[i] 表示前i+1个元素的最长递增子序列,包含位置0 +class Solution { +public: + int lengthOfLIS(vector& nums) { + int n = (int)nums.size(); + if (n == 0) { + return 0; + } + vector dp(n, 0); + for (int i = 0; i < n; ++i) { + dp[i] = 1; + for (int j = 0; j < i; ++j) { + if (nums[j] < nums[i]) { + dp[i] = max(dp[i], dp[j] + 1); + } + } + } + return *max_element(dp.begin(), dp.end()); + } +}; + +//贪心+二分 +class Solution { +public: + int lengthOfLIS(vector& nums) { + int n = nums.size(); + vectord(n + 1,0); + if ( n == 0) return 0; + int len = 1; + d[len] = nums[0]; + for (int i = 1;i < n;i++) { + if ( d[len] < nums[i]) { + d[++len] = nums[i]; + } + else { + int l = 1, r = len; + int pos = 0; + while ( l <= r ) { + int mid = (l+r)>>1; + if ( d[mid] < nums[i] ) { + l = mid + 1; + pos = mid; + } + else { + r = mid - 1; + } + } + d[pos + 1] = nums[i]; + } + } + return len; + } +}; + +``` + +#### 4 . [516. 最长回文子序列](https://leetcode-cn.com/problems/longest-palindromic-subsequence/) + +```c++ +/* +给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。 + +子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。 + +来源:力扣(LeetCode) +链接:https://leetcode-cn.com/problems/longest-palindromic-subsequence +著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 +*/ +//用dp[i][j] 表示字符串s的下标范围[i,j] 内的最长回文子序列的长度 +class Solution { +public: + int longestPalindromeSubseq(string s) { + int n = s.size(); + vector>dp(n,vector(n,0)); + for(int i = n - 1;i >= 0 ;i-- ) { + dp[i][i] = 1; + for(int j = i + 1;j < n;j++ ) { + if(s[i] == s[j]) { + dp[i][j] = dp[i + 1][j - 1] + 2; + } + else { + dp[i][j] = max(dp[i + 1][j],dp[i][j - 1]); + } + } + } + return dp[0][n - 1]; + } +}; +``` + diff --git "a/note/\345\257\273\346\211\276\351\223\276\350\241\250\347\216\257\345\205\245\345\217\243\347\244\272\346\204\217\345\233\276.png" "b/note/\345\257\273\346\211\276\351\223\276\350\241\250\347\216\257\345\205\245\345\217\243\347\244\272\346\204\217\345\233\276.png" new file mode 100644 index 0000000000..c329bd2a4a Binary files /dev/null and "b/note/\345\257\273\346\211\276\351\223\276\350\241\250\347\216\257\345\205\245\345\217\243\347\244\272\346\204\217\345\233\276.png" differ diff --git "a/note/\347\275\221\347\273\234\347\274\226\347\250\213.md" "b/note/\347\275\221\347\273\234\347\274\226\347\250\213.md" new file mode 100644 index 0000000000..3f54c43d94 --- /dev/null +++ "b/note/\347\275\221\347\273\234\347\274\226\347\250\213.md" @@ -0,0 +1,2894 @@ +[TOC] + + + +# 杂项: + +## 1 .c – UNIX系统编程中的S_IFMT是什么? + +S_IFMT是文件类型的位掩码(参见man stat) + +直接与mystat.st_mode(mystat.st_mode& S_IFMT)进行逐位AND运算意味着只考虑所涉及的位来确定文件类型(常规文件,套接字,块或字符设备等) + +使用按位否定位掩码(mystat.st_mode& ~S_IFMT)执行mystat.st_mode的按位AND意味着忽略上面解释的位,只保留需要确定文件权限的位(该命令下面的9行). + +```C +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char* argv[]) +{ + DIR *mydir; + struct dirent *myfile; + struct stat mystat; + + mydir = opendir(argv[1]); + char buf[512]; + while((myfile = readdir(mydir)) != NULL) + { + struct tm *time_stamp=localtime(&mystat.st_mtime); + sprintf(buf, "%s/%s", argv[1], myfile->d_name); + stat(buf, &mystat); + //stat(myfile->d_name, &mystat); + mode_t val; + + val=(mystat.st_mode & ~S_IFMT); + (val & S_IRUSR) ? printf("r") : printf("-"); + (val & S_IWUSR) ? printf("w") : printf("-"); + (val & S_IXUSR) ? printf("x") : printf("-"); + (val & S_IRGRP) ? printf("r") : printf("-"); + (val & S_IWGRP) ? printf("w") : printf("-"); + (val & S_IXGRP) ? printf("x") : printf("-"); + (val & S_IROTH) ? printf("r") : printf("-"); + (val & S_IWOTH) ? printf("w") : printf("-"); + (val & S_IXOTH) ? printf("x") : printf("-"); + printf("\t%d",mystat.st_nlink); + printf("\t%d",mystat.st_uid); + printf("\t%d",mystat.st_gid); + printf("\t%d",mystat.st_size); + char buffer[80]; + strftime(buffer,10,"%b",time_stamp); + + printf("\t%4d %s %2d ", time_stamp->tm_year+1900,buffer,time_stamp->tm_mday); + printf(" %s\n", myfile->d_name); + } + closedir(mydir); +} +``` + +## 2.大端模式和小端模式的区别是什么? + +大端模式中字数据的高字节存储在低地址中,而字数据的低字节则存放在高地址中;而与大端存储模式相反,在小端存储模式中,低地址中存放的是字数据的低字节,高地址存放的是字数据的高字节。 + +## 3.生成core file 查看错误(如:核心已转储) + +命令行键入: + +1) ulimit -a 查看core file size 是否为unlimited + +2)改变 :ulimit -c unlimited + +3) 重新编译 gcc main.c -o main -g + +4) 生成可执行文件,执行后,生成core文件 + +5) 进入gdb调试:gdb main + +6)输入core-file core 回车,可查看结果 + + + +# 1.基本文件I/O操作 + +## 1.open、close 函数 + +```C +/* + #include + #include + #include + + // 打开一个已经存在的文件 + int open(const char *pathname, int flags); + 参数: + - pathname:要打开的文件路径 + - flags:对文件的操作权限设置还有其他的设置 + O_RDONLY, O_WRONLY, O_RDWR 这三个设置是互斥的 + 返回值:返回一个新的文件描述符,如果调用失败,返回-1 + + errno:属于Linux系统函数库,库里面的一个全局变量,记录的是最近的错误号。 + + #include + void perror(const char *s);作用:打印errno对应的错误描述 + s参数:用户描述,比如hello,最终输出的内容是 hello:xxx(实际的错误描述) + + + // 创建一个新的文件 + int open(const char *pathname, int flags, mode_t mode); + + //关闭不再使用的文件 + #include + int close(int fd); + 参数:fd -文件描述符 + 返回值:0 -执行成功 + -1 执行失败 + +*/ +#include +#include +#include +#include +#include + +int main() { + // 打开一个文件 + int fd = open("a.txt", O_RDONLY); + if(fd == -1) { + perror("open"); + } + // 读写操作 + // 关闭 + close(fd); + return 0; + +} +``` + +```C +/* + #include + #include + #include + + int open(const char *pathname, int flags, mode_t mode); + 参数: + - pathname:要创建的文件的路径 + - flags:对文件的操作权限和其他的设置 + - 必选项:O_RDONLY, O_WRONLY, O_RDWR 这三个之间是互斥的 + - 可选项:O_CREAT 文件不存在,创建新文件 + - mode:八进制的数,表示创建出的新的文件的操作权限,比如:0775 + 最终的权限是:mode & ~umask + 0777 -> 111111111 + & 0775 -> 111111101 + ---------------------------- + 111111101 + 按位与:0和任何数都为0 + umask的作用就是抹去某些权限。 + + flags参数是一个int类型的数据,占4个字节,32位。 + flags 32个位,每一位就是一个标志位。 + +*/ +#include +#include +#include +#include +#include + +int main() { + // 创建一个新的文件 + int fd = open("create.txt", O_RDWR | O_CREAT, 0777); + if(fd == -1) { + perror("open"); + } + // 关闭 + close(fd); + return 0; +} +``` + +## 2.read、write函数 + +```c +/* + #include + ssize_t read(int fd, void *buf, size_t count); + 参数: + - fd:文件描述符,open得到的,通过这个文件描述符操作某个文件 + - buf:需要读取数据存放的地方,数组的地址(传出参数) + - count:指定的数组的大小 + 返回值: + - 成功: + >0: 返回实际的读取到的字节数 + =0:文件已经读取完了 + - 失败:-1 ,并且设置errno + + #include + ssize_t write(int fd, const void *buf, size_t count); + 参数: + - fd:文件描述符,open得到的,通过这个文件描述符操作某个文件 + - buf:要往磁盘写入的数据,数据 + - count:要写的数据的实际的大小 + 返回值: + 成功:实际写入的字节数 + 失败:返回-1,并设置errno +*/ +#include +#include +#include +#include +#include + +int main() { + // 1.通过open打开english.txt文件 + int srcfd = open("english.txt", O_RDONLY); + if(srcfd == -1) { + perror("open"); + return -1; + } + // 2.创建一个新的文件(拷贝文件) + int destfd = open("cpy.txt", O_WRONLY | O_CREAT, 0664); + if(destfd == -1) { + perror("open"); + return -1; + } + // 3.频繁的读写操作 + char buf[1024] = {0}; + int len = 0; + while((len = read(srcfd, buf, sizeof(buf))) > 0) { + write(destfd, buf, len); + } + // 4.关闭文件 + close(destfd); + close(srcfd); + return 0; +} + +``` + +## 3.fseek、lseek函数 + +```c +/* + 标准C库的函数 + #include + int fseek(FILE *stream, long offset, int whence); + + Linux系统函数 + #include + #include + off_t lseek(int fd, off_t offset, int whence); + 参数: + - fd:文件描述符,通过open得到的,通过这个fd操作某个文件 + - offset:偏移量 + - whence: + SEEK_SET + 设置文件指针的偏移量 + SEEK_CUR + 设置偏移量:当前位置 + 第二个参数offset的值 + SEEK_END + 设置偏移量:文件大小 + 第二个参数offset的值 + 返回值:返回文件指针的位置 + + + 作用: + 1.移动文件指针到文件头 + lseek(fd, 0, SEEK_SET); + + 2.获取当前文件指针的位置 + lseek(fd, 0, SEEK_CUR); + + 3.获取文件长度 + lseek(fd, 0, SEEK_END); + + 4.拓展文件的长度,当前文件10b, 110b, 增加了100个字节 + lseek(fd, 100, SEEK_END) + 注意:需要写一次数据 + +*/ + +#include +#include +#include +#include +#include + +int main() { + + int fd = open("hello.txt", O_RDWR); + + if(fd == -1) { + perror("open"); + return -1; + } + + // 扩展文件的长度 + int ret = lseek(fd, 100, SEEK_END); + if(ret == -1) { + perror("lseek"); + return -1; + } + + // 写入一个空数据 + write(fd, " ", 1); + + // 关闭文件 + close(fd); + + return 0; +} + +``` + +## 4.stat、lstat函数 + +```c +/* + #include + #include + #include + + int stat(const char *pathname, struct stat *statbuf); + 作用:获取一个文件相关的一些信息 + 参数: + - pathname:操作的文件的路径 + - statbuf:结构体变量,传出参数,用于保存获取到的文件的信息 + 返回值: + 成功:返回0 + 失败:返回-1 设置errno + + int lstat(const char *pathname, struct stat *statbuf); + 参数: + - pathname:操作的文件的路径 + - statbuf:结构体变量,传出参数,用于保存获取到的文件的信息 + 返回值: + 成功:返回0 + 失败:返回-1 设置errno + +*/ + +#include +#include +#include +#include + +int main() { + + struct stat statbuf; + + int ret = stat("a.txt", &statbuf); + + if(ret == -1) { + perror("stat"); + return -1; + } + + printf("size: %ld\n", statbuf.st_size); + + return 0; +} +``` + +```c + +#include +#include +#include +#include +#include +#include +#include +#include + +// 模拟实现 ls -l 指令 +// -rw-rw-r-- 1 nowcoder nowcoder 12 12月 3 15:48 a.txt +int main(int argc, char * argv[]) { + + // 判断输入的参数是否正确 + if(argc < 2) { + printf("%s filename\n", argv[0]); + return -1; + } + + // 通过stat函数获取用户传入的文件的信息 + struct stat st; + int ret = stat(argv[1], &st); + if(ret == -1) { + perror("stat"); + return -1; + } + + // 获取文件类型和文件权限 + char perms[11] = {0}; // 用于保存文件类型和文件权限的字符串 + + switch(st.st_mode & S_IFMT) { + case S_IFLNK: + perms[0] = 'l'; + break; + case S_IFDIR: + perms[0] = 'd'; + break; + case S_IFREG: + perms[0] = '-'; + break; + case S_IFBLK: + perms[0] = 'b'; + break; + case S_IFCHR: + perms[0] = 'c'; + break; + case S_IFSOCK: + perms[0] = 's'; + break; + case S_IFIFO: + perms[0] = 'p'; + break; + default: + perms[0] = '?'; + break; + } + + // 判断文件的访问权限 + + // 文件所有者 + perms[1] = (st.st_mode & S_IRUSR) ? 'r' : '-'; + perms[2] = (st.st_mode & S_IWUSR) ? 'w' : '-'; + perms[3] = (st.st_mode & S_IXUSR) ? 'x' : '-'; + + // 文件所在组 + perms[4] = (st.st_mode & S_IRGRP) ? 'r' : '-'; + perms[5] = (st.st_mode & S_IWGRP) ? 'w' : '-'; + perms[6] = (st.st_mode & S_IXGRP) ? 'x' : '-'; + + // 其他人 + perms[7] = (st.st_mode & S_IROTH) ? 'r' : '-'; + perms[8] = (st.st_mode & S_IWOTH) ? 'w' : '-'; + perms[9] = (st.st_mode & S_IXOTH) ? 'x' : '-'; + + // 硬连接数 + int linkNum = st.st_nlink; + + // 文件所有者 + char * fileUser = getpwuid(st.st_uid)->pw_name; + + // 文件所在组 + char * fileGrp = getgrgid(st.st_gid)->gr_name; + + // 文件大小 + long int fileSize = st.st_size; + + // 获取修改的时间 + char * time = ctime(&st.st_mtime); + + char mtime[512] = {0}; + strncpy(mtime, time, strlen(time) - 1); + + char buf[1024]; + sprintf(buf, "%s %d %s %s %ld %s %s", perms, linkNum, fileUser, fileGrp, fileSize, mtime, argv[1]); + + printf("%s\n", buf); + + return 0; +} +``` + +## 2.文件属性操作函数 + +### 5.truncate函数 + +```C +/* + #include + #include + int truncate(const char *path, off_t length); + 作用:缩减或者扩展文件的尺寸至指定的大小 + 参数: + - path: 需要修改的文件的路径 + - length: 需要最终文件变成的大小 + 返回值: + 成功返回0, 失败返回-1 +*/ + +#include +#include +#include + +int main() { + + int ret = truncate("b.txt", 5); + + if(ret == -1) { + perror("truncate"); + return -1; + } + + return 0; +} +``` + +### 6.chmod函数 + +```c +/* + #include + int chmod(const char *pathname, mode_t mode); + 修改文件的权限 + 参数: + - pathname: 需要修改的文件的路径 + - mode:需要修改的权限值,八进制的数 + 返回值:成功返回0,失败返回-1 + +*/ +#include +#include +int main() { + + int ret = chmod("a.txt", 0777); + + if(ret == -1) { + perror("chmod"); + return -1; + } + + return 0; +} +``` + +### 7.access函数 + +```c +/* + #include + int access(const char *pathname, int mode); + 作用:判断某个文件是否有某个权限,或者判断文件是否存在 + 参数: + - pathname: 判断的文件路径 + - mode: + R_OK: 判断是否有读权限 + W_OK: 判断是否有写权限 + X_OK: 判断是否有执行权限 + F_OK: 判断文件是否存在 + 返回值:成功返回0, 失败返回-1 +*/ + +#include +#include + +int main() { + + int ret = access("a.txt", F_OK); + if(ret == -1) { + perror("access"); + } + + printf("文件存在!!!\n"); + + return 0; +} +``` + +## 3.目录操作函数 + +### 8.rename 函数 + +```c +/* + #include + int rename(const char *oldpath, const char *newpath); + +*/ +#include + +int main() { + + int ret = rename("aaa", "bbb"); + + if(ret == -1) { + perror("rename"); + return -1; + } + + return 0; +} +``` + +### 9.mkdir函数 + +```c +/* + #include + #include + int mkdir(const char *pathname, mode_t mode); + 作用:创建一个目录 + 参数: + pathname: 创建的目录的路径 + mode: 权限,八进制的数 + 返回值: + 成功返回0, 失败返回-1 +*/ + +#include +#include +#include + +int main() { + + int ret = mkdir("aaa", 0777); + + if(ret == -1) { + perror("mkdir"); + return -1; + } + + return 0; +} +``` + +### 10.chdir、getcwd函数 + +```C +/* + + #include + int chdir(const char *path); + 作用:修改进程的工作目录 + 比如在/home/nowcoder 启动了一个可执行程序a.out, 进程的工作目录 /home/nowcoder + 参数: + path : 需要修改的工作目录 + + #include + char *getcwd(char *buf, size_t size); + 作用:获取当前工作目录 + 参数: + - buf : 存储的路径,指向的是一个数组(传出参数) + - size: 数组的大小 + 返回值: + 返回的指向的一块内存,这个数据就是第一个参数 + +*/ +#include +#include +#include +#include +#include + +int main() { + + // 获取当前的工作目录 + char buf[128]; + getcwd(buf, sizeof(buf)); + printf("当前的工作目录是:%s\n", buf); + + // 修改工作目录 + int ret = chdir("/home/nowcoder/Linux/lesson13"); + if(ret == -1) { + perror("chdir"); + return -1; + } + + // 创建一个新的文件 + int fd = open("chdir.txt", O_CREAT | O_RDWR, 0664); + if(fd == -1) { + perror("open"); + return -1; + } + + close(fd); + + // 获取当前的工作目录 + char buf1[128]; + getcwd(buf1, sizeof(buf1)); + printf("当前的工作目录是:%s\n", buf1); + + return 0; +} +``` + +## 4.目录遍历函数 + +```C +/* + // 打开一个目录 + #include + #include + DIR *opendir(const char *name); + 参数: + - name: 需要打开的目录的名称 + 返回值: + DIR * 类型,理解为目录流 + 错误返回NULL + + + // 读取目录中的数据 + #include + struct dirent *readdir(DIR *dirp); + - 参数:dirp是opendir返回的结果 + - 返回值: + struct dirent,代表读取到的文件的信息 + 读取到了末尾或者失败了,返回NULL + + // 关闭目录 + #include + #include + int closedir(DIR *dirp); + +*/ +#include +#include +#include +#include +#include + +int getFileNum(const char * path); + +// 读取某个目录下所有的普通文件的个数 +int main(int argc, char * argv[]) { + + if(argc < 2) { + printf("%s path\n", argv[0]); + return -1; + } + + int num = getFileNum(argv[1]); + + printf("普通文件的个数为:%d\n", num); + + return 0; +} + +// 用于获取目录下所有普通文件的个数 +int getFileNum(const char * path) { + + // 1.打开目录 + DIR * dir = opendir(path); + + if(dir == NULL) { + perror("opendir"); + exit(0); + } + + struct dirent *ptr; + + // 记录普通文件的个数 + int total = 0; + + while((ptr = readdir(dir)) != NULL) { + + // 获取名称 + char * dname = ptr->d_name; + + // 忽略掉. 和.. + if(strcmp(dname, ".") == 0 || strcmp(dname, "..") == 0) { + continue; + } + + // 判断是否是普通文件还是目录 + if(ptr->d_type == DT_DIR) { + // 目录,需要继续读取这个目录 + char newpath[256]; + sprintf(newpath, "%s/%s", path, dname); + total += getFileNum(newpath); + } + + if(ptr->d_type == DT_REG) { + // 普通文件 + total++; + } + + } + + // 关闭目录 + closedir(dir); + + return total; +} +``` + +### 11.dup、dup2函数 + +```c +/* + #include + int dup(int oldfd); + 作用:复制一个新的文件描述符 + fd=3, int fd1 = dup(fd), + fd指向的是a.txt, fd1也是指向a.txt + 从空闲的文件描述符表中找一个最小的,作为新的拷贝的文件描述符 +*/ + +#include +#include +#include +#include +#include +#include + +int main() { + + int fd = open("a.txt", O_RDWR | O_CREAT, 0664); + + int fd1 = dup(fd); + + if(fd1 == -1) { + perror("dup"); + return -1; + } + + printf("fd : %d , fd1 : %d\n", fd, fd1); + + close(fd); + + char * str = "hello,world"; + int ret = write(fd1, str, strlen(str)); + if(ret == -1) { + perror("write"); + return -1; + } + + close(fd1); + + return 0; +} + +``` + +```c +/* + #include + int dup2(int oldfd, int newfd); + 作用:重定向文件描述符 + oldfd 指向 a.txt, newfd 指向 b.txt + 调用函数成功后:newfd 和 b.txt 做close, newfd 指向了 a.txt + oldfd 必须是一个有效的文件描述符 + oldfd和newfd值相同,相当于什么都没有做 +*/ +#include +#include +#include +#include +#include +#include + +int main() { + + int fd = open("1.txt", O_RDWR | O_CREAT, 0664); + if(fd == -1) { + perror("open"); + return -1; + } + + int fd1 = open("2.txt", O_RDWR | O_CREAT, 0664); + if(fd1 == -1) { + perror("open"); + return -1; + } + + printf("fd : %d, fd1 : %d\n", fd, fd1); + + int fd2 = dup2(fd, fd1); + if(fd2 == -1) { + perror("dup2"); + return -1; + } + + // 通过fd1去写数据,实际操作的是1.txt,而不是2.txt + char * str = "hello, dup2"; + int len = write(fd1, str, strlen(str)); + + if(len == -1) { + perror("write"); + return -1; + } + + printf("fd : %d, fd1 : %d, fd2 : %d\n", fd, fd1, fd2); + + close(fd); + close(fd1); + + return 0; +} +``` + +### 12.fcntl函数(阻塞和非阻塞) + +```c +/* + + #include + #include + + int fcntl(int fd, int cmd, ...); + 参数: + fd : 表示需要操作的文件描述符 + cmd: 表示对文件描述符进行如何操作 + - F_DUPFD : 复制文件描述符,复制的是第一个参数fd,得到一个新的文件描述符(返回值) + int ret = fcntl(fd, F_DUPFD); + + - F_GETFL : 获取指定的文件描述符文件状态flag + 获取的flag和我们通过open函数传递的flag是一个东西。 + + - F_SETFL : 设置文件描述符文件状态flag + 必选项:O_RDONLY, O_WRONLY, O_RDWR 不可以被修改 + 可选性:O_APPEND, O_NONBLOCK + O_APPEND 表示追加数据 + O_NONBLOCK 设置成非阻塞 + + 阻塞和非阻塞:描述的是函数调用的行为。 +*/ + +#include +#include +#include +#include + +int main() { + + // 1.复制文件描述符 + // int fd = open("1.txt", O_RDONLY); + // int ret = fcntl(fd, F_DUPFD); + + // 2.修改或者获取文件状态flag + int fd = open("1.txt", O_RDWR); + if(fd == -1) { + perror("open"); + return -1; + } + + // 获取文件描述符状态flag + int flag = fcntl(fd, F_GETFL); + if(flag == -1) { + perror("fcntl"); + return -1; + } + flag |= O_APPEND; // flag = flag | O_APPEND + + // 修改文件描述符状态的flag,给flag加入O_APPEND这个标记 + int ret = fcntl(fd, F_SETFL, flag); + if(ret == -1) { + perror("fcntl"); + return -1; + } + + char * str = "nihao"; + write(fd, str, strlen(str)); + + close(fd); + + return 0; +} +``` + +### 13.setsockopt函数 + +```c++ +#include +#include +// 设置套接字的属性(不仅仅能设置端口复用) +int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t +optlen); +参数: +- sockfd : 要操作的文件描述符 +- level : 级别 - SOL_SOCKET (端口复用的级别) +- optname : 选项的名称 +- SO_REUSEADDR +- SO_REUSEPORT +- optval : 端口复用的值(整形) +- 1 : 可以复用 +- 0 : 不可以复用 +- optlen : optval参数的大小 +端口复用,设置的时机是在服务器绑定端口之前。 +setsockopt(); +bind(); +``` + +### 14 opendir函数 + +```c +/* + // 打开一个目录 + #include + #include + DIR *opendir(const char *name); + 参数: + - name: 需要打开的目录的名称 + 返回值: + DIR * 类型,理解为目录流 + 错误返回NULL + + + // 读取目录中的数据 + #include + struct dirent *readdir(DIR *dirp); + - 参数:dirp是opendir返回的结果 + - 返回值: + struct dirent,代表读取到的文件的信息 + 读取到了末尾或者失败了,返回NULL + + // 关闭目录 + #include + #include + int closedir(DIR *dirp); + +*/ +#include +#include +#include +#include +#include + +int getFileNum(const char * path); + +// 读取某个目录下所有的普通文件的个数 +int main(int argc, char * argv[]) { + + if(argc < 2) { + printf("%s path\n", argv[0]); + return -1; + } + + int num = getFileNum(argv[1]); + + printf("普通文件的个数为:%d\n", num); + + return 0; +} + +// 用于获取目录下所有普通文件的个数 +int getFileNum(const char * path) { + + // 1.打开目录 + DIR * dir = opendir(path); + + if(dir == NULL) { + perror("opendir"); + exit(0); + } + + struct dirent *ptr; + + // 记录普通文件的个数 + int total = 0; + + while((ptr = readdir(dir)) != NULL) { + + // 获取名称 + char * dname = ptr->d_name; + + // 忽略掉. 和.. + if(strcmp(dname, ".") == 0 || strcmp(dname, "..") == 0) { + continue; + } + + // 判断是否是普通文件还是目录 + if(ptr->d_type == DT_DIR) { + // 目录,需要继续读取这个目录 + char newpath[256]; + sprintf(newpath, "%s/%s", path, dname); + total += getFileNum(newpath); + } + + if(ptr->d_type == DT_REG) { + // 普通文件 + total++; + } + + } + + // 关闭目录 + closedir(dir); + + return total; +} +``` + +## 5.execl函数族 + +### 15.execl 函数 + +```c +/* + #include + int execl(const char *path, const char *arg, ...); + - 参数: + - path:需要指定的执行的文件的路径或者名称 + a.out /home/nowcoder/a.out 推荐使用绝对路径 + ./a.out hello world + + - arg:是执行可执行文件所需要的参数列表 + 第一个参数一般没有什么作用,为了方便,一般写的是执行的程序的名称 + 从第二个参数开始往后,就是程序执行所需要的的参数列表。 + 参数最后需要以NULL结束(哨兵) + + - 返回值: + 只有当调用失败,才会有返回值,返回-1,并且设置errno + 如果调用成功,没有返回值。 + +*/ +#include +#include +int main() { + // 创建一个子进程,在子进程中执行exec函数族中的函数 + pid_t pid = fork(); + + if(pid > 0) { + // 父进程 + printf("i am parent process, pid : %d\n",getpid()); + sleep(1); + }else if(pid == 0) { + // 子进程 + // execl("hello","hello",NULL); + + execl("/bin/ps", "ps", "aux", NULL); + perror("execl"); + printf("i am child process, pid : %d\n", getpid()); + + } + + for(int i = 0; i < 3; i++) { + printf("i = %d, pid = %d\n", i, getpid()); + } + + return 0; +} +``` + +### 16.execlp函数 + +```c +/* + #include + int execlp(const char *file, const char *arg, ... ); + - 会到环境变量中查找指定的可执行文件,如果找到了就执行,找不到就执行不成功。 + - 参数: + - file:需要执行的可执行文件的文件名 + a.out + ps + + - arg:是执行可执行文件所需要的参数列表 + 第一个参数一般没有什么作用,为了方便,一般写的是执行的程序的名称 + 从第二个参数开始往后,就是程序执行所需要的的参数列表。 + 参数最后需要以NULL结束(哨兵) + + - 返回值: + 只有当调用失败,才会有返回值,返回-1,并且设置errno + 如果调用成功,没有返回值。 + + + int execv(const char *path, char *const argv[]); + argv是需要的参数的一个字符串数组 + char * argv[] = {"ps", "aux", NULL}; + execv("/bin/ps", argv); + + int execve(const char *filename, char *const argv[], char *const envp[]); + char * envp[] = {"/home/nowcoder", "/home/bbb", "/home/aaa"}; + + +*/ +#include +#include + +int main() { + + + // 创建一个子进程,在子进程中执行exec函数族中的函数 + pid_t pid = fork(); + + if(pid > 0) { + // 父进程 + printf("i am parent process, pid : %d\n",getpid()); + sleep(1); + }else if(pid == 0) { + // 子进程 + execlp("ps", "ps", "aux", NULL); + + printf("i am child process, pid : %d\n", getpid()); + + } + + for(int i = 0; i < 3; i++) { + printf("i = %d, pid = %d\n", i, getpid()); + } + + + return 0; +} +``` + +## 6.进程资源回收 -相关函数 + +### 17.exit函数 + +```c +/* + #include + void exit(int status); + + #include + void _exit(int status); + + status参数:是进程退出时的一个状态信息。父进程回收子进程资源的时候可以获取到。 +*/ +#include +#include +#include + +int main() { + + printf("hello\n"); + printf("world"); + + // exit(0); + _exit(0); + + return 0; +} +``` + +### 18.wait函数 + +```c +/* + #include + #include + pid_t wait(int *wstatus); + 功能:等待任意一个子进程结束,如果任意一个子进程结束了,此函数会回收子进程的资源。 + 参数:int *wstatus + 进程退出时的状态信息,传入的是一个int类型的地址,传出参数。 + 返回值: + - 成功:返回被回收的子进程的id + - 失败:-1 (所有的子进程都结束,调用函数失败) + + 调用wait函数的进程会被挂起(阻塞),直到它的一个子进程退出或者收到一个不能被忽略的信号时才被唤醒(相当于继续往下执行) + 如果没有子进程了,函数立刻返回,返回-1;如果子进程都已经结束了,也会立即返回,返回-1. + +*/ +#include +#include +#include +#include +#include + + +int main() { + + // 有一个父进程,创建5个子进程(兄弟) + pid_t pid; + + // 创建5个子进程 + for(int i = 0; i < 5; i++) { + pid = fork(); + if(pid == 0) { + break; + } + } + + if(pid > 0) { + // 父进程 + while(1) { + printf("parent, pid = %d\n", getpid()); + + // int ret = wait(NULL); + int st; + int ret = wait(&st); + + if(ret == -1) { + break; + } + + if(WIFEXITED(st)) { + // 是不是正常退出 + printf("退出的状态码:%d\n", WEXITSTATUS(st)); + } + if(WIFSIGNALED(st)) { + // 是不是异常终止 + printf("被哪个信号干掉了:%d\n", WTERMSIG(st)); + } + printf("child die, pid = %d\n", ret); + + sleep(1); + } + } else if (pid == 0){ + // 子进程 + while(1) { + printf("child, pid = %d\n",getpid()); + sleep(1); + } + exit(0); + } + return 0; // exit(0) +} +``` + +### 19.waitpid函数 + +```c +/* + #include + #include + pid_t waitpid(pid_t pid, int *wstatus, int options); + 功能:回收指定进程号的子进程,可以设置是否阻塞。 + 参数: + - pid: + pid > 0 : 某个子进程的pid + pid = 0 : 回收当前进程组的所有子进程 + pid = -1 : 回收所有的子进程,相当于 wait() (最常用) + pid < -1 : 某个进程组的组id的绝对值,回收指定进程组中的子进程 + - options:设置阻塞或者非阻塞 + 0 : 阻塞 + WNOHANG : 非阻塞 + - 返回值: + > 0 : 返回子进程的id + = 0 : options=WNOHANG, 表示还有子进程或者 + = -1 :错误,或者没有子进程了 +*/ +#include +#include +#include +#include +#include + +int main() { + + // 有一个父进程,创建5个子进程(兄弟) + pid_t pid; + + // 创建5个子进程 + for(int i = 0; i < 5; i++) { + pid = fork(); + if(pid == 0) { + break; + } + } + + if(pid > 0) { + // 父进程 + while(1) { + printf("parent, pid = %d\n", getpid()); + sleep(1); + + int st; + // int ret = waitpid(-1, &st, 0); + int ret = waitpid(-1, &st, WNOHANG); + + if(ret == -1) { + break; + } else if(ret == 0) { + // 说明还有子进程存在 + continue; + } else if(ret > 0) { + + if(WIFEXITED(st)) { + // 是不是正常退出 + printf("退出的状态码:%d\n", WEXITSTATUS(st)); + } + if(WIFSIGNALED(st)) { + // 是不是异常终止 + printf("被哪个信号干掉了:%d\n", WTERMSIG(st)); + } + + printf("child die, pid = %d\n", ret); + } + + } + + } else if (pid == 0){ + // 子进程 + while(1) { + printf("child, pid = %d\n",getpid()); + sleep(1); + } + exit(0); + } + + return 0; +} +``` + + + +# 进程间通信方式 + +## 1.管道 + +管道的读写特点: +使用管道时,需要注意以下几种特殊的情况(假设都是阻塞I/O操作) +1.所有的指向管道写端的文件描述符都关闭了(管道写端引用计数为0),有进程从管道的读端 +读数据,那么管道中剩余的数据被读取以后,再次read会返回0,就像读到文件末尾一样。 + +2.如果有指向管道写端的文件描述符没有关闭(管道的写端引用计数大于0),而持有管道写端的进程 +也没有往管道中写数据,这个时候有进程从管道中读取数据,那么管道中剩余的数据被读取后, +再次read会阻塞,直到管道中有数据可以读了才读取数据并返回。 + +3.如果所有指向管道读端的文件描述符都关闭了(管道的读端引用计数为0),这个时候有进程 +向管道中写数据,那么该进程会收到一个信号SIGPIPE, 通常会导致进程异常终止。 + +4.如果有指向管道读端的文件描述符没有关闭(管道的读端引用计数大于0),而持有管道读端的进程 +也没有从管道中读数据,这时有进程向管道中写数据,那么在管道被写满的时候再次write会阻塞, +直到管道中有空位置才能再次写入数据并返回。 + +总结: + 读管道: + 管道中有数据,read返回实际读到的字节数。 + 管道中无数据: + 写端被全部关闭,read返回0(相当于读到文件的末尾) + 写端没有完全关闭,read阻塞等待 + + 写管道: + 管道读端全部被关闭,进程异常终止(进程收到SIGPIPE信号) + 管道读端没有全部关闭: + 管道已满,write阻塞 + 管道没有满,write将数据写入,并返回实际写入的字节数 + +```c +//匿名管道 +/* + #include + int pipe(int pipefd[2]); + 功能:创建一个匿名管道,用来进程间通信。 + 参数:int pipefd[2] 这个数组是一个传出参数。 + pipefd[0] 对应的是管道的读端 + pipefd[1] 对应的是管道的写端 + 返回值: + 成功 0 + 失败 -1 + + 管道默认是阻塞的:如果管道中没有数据,read阻塞,如果管道满了,write阻塞 + + 注意:匿名管道只能用于具有关系的进程之间的通信(父子进程,兄弟进程) +*/ + +// 子进程发送数据给父进程,父进程读取到数据输出 +#include +#include +#include +#include +#include + +int main() { + + // 在fork之前创建管道 + int pipefd[2]; + int ret = pipe(pipefd); + if(ret == -1) { + perror("pipe"); + exit(0); + } + + // 创建子进程 + pid_t pid = fork(); + if(pid > 0) { + // 父进程 + printf("i am parent process, pid : %d\n", getpid()); + + // 关闭写端 + close(pipefd[1]); + + // 从管道的读取端读取数据 + char buf[1024] = {0}; + while(1) { + int len = read(pipefd[0], buf, sizeof(buf)); + printf("parent recv : %s, pid : %d\n", buf, getpid()); + + // 向管道中写入数据 + //char * str = "hello,i am parent"; + //write(pipefd[1], str, strlen(str)); + //sleep(1); + } + + } else if(pid == 0){ + // 子进程 + printf("i am child process, pid : %d\n", getpid()); + // 关闭读端 + close(pipefd[0]); + char buf[1024] = {0}; + while(1) { + // 向管道中写入数据 + char * str = "hello,i am child"; + write(pipefd[1], str, strlen(str)); + //sleep(1); + + // int len = read(pipefd[0], buf, sizeof(buf)); + // printf("child recv : %s, pid : %d\n", buf, getpid()); + // bzero(buf, 1024); + } + + } + return 0; +} + +``` + +```c +/* + 有名管道 + 创建fifo文件 + 1.通过命令: mkfifo 名字 + 2.通过函数:int mkfifo(const char *pathname, mode_t mode); + + #include + #include + int mkfifo(const char *pathname, mode_t mode); + 参数: + - pathname: 管道名称的路径 + - mode: 文件的权限 和 open 的 mode 是一样的 + 是一个八进制的数 + 返回值:成功返回0,失败返回-1,并设置错误号 + +*/ + +#include +#include +#include +#include +#include + +int main() { + + // 判断文件是否存在 + int ret = access("fifo1", F_OK); + if(ret == -1) { + printf("管道不存在,创建管道\n"); + + ret = mkfifo("fifo1", 0664); + + if(ret == -1) { + perror("mkfifo"); + exit(0); + } + } + return 0; +} +``` + +```c +//read.c +#include +#include +#include +#include +#include +#include + +// 从管道中读取数据 +int main() { + + // 1.打开管道文件 + int fd = open("test", O_RDONLY); + if(fd == -1) { + perror("open"); + exit(0); + } + + // 读数据 + while(1) { + char buf[1024] = {0}; + int len = read(fd, buf, sizeof(buf)); + if(len == 0) { + printf("写端断开连接了...\n"); + break; + } + printf("recv buf : %s\n", buf); + } + + close(fd); + + return 0; +} +``` + +```c +// write.c +#include +#include +#include +#include +#include +#include +#include + +// 向管道中写数据 +/* + 有名管道的注意事项: + 1.一个为只读而打开一个管道的进程会阻塞,直到另外一个进程为只写打开管道 + 2.一个为只写而打开一个管道的进程会阻塞,直到另外一个进程为只读打开管道 + + 读管道: + 管道中有数据,read返回实际读到的字节数 + 管道中无数据: + 管道写端被全部关闭,read返回0,(相当于读到文件末尾) + 写端没有全部被关闭,read阻塞等待 + + 写管道: + 管道读端被全部关闭,进行异常终止(收到一个SIGPIPE信号) + 管道读端没有全部关闭: + 管道已经满了,write会阻塞 + 管道没有满,write将数据写入,并返回实际写入的字节数。 +*/ +int main() { + + // 1.判断文件是否存在 + int ret = access("test", F_OK); + if(ret == -1) { + printf("管道不存在,创建管道\n"); + + // 2.创建管道文件 + ret = mkfifo("test", 0664); + + if(ret == -1) { + perror("mkfifo"); + exit(0); + } + + } + + // 3.以只写的方式打开管道 + int fd = open("test", O_WRONLY); + if(fd == -1) { + perror("open"); + exit(0); + } + + // 写数据 + for(int i = 0; i < 100; i++) { + char buf[1024]; + sprintf(buf, "hello, %d\n", i); + printf("write data : %s\n", buf); + write(fd, buf, strlen(buf)); + sleep(1); + } + + close(fd); + + return 0; +} +``` + + + +## 2.内存映射 + +```c +/* + #include + void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset); + - 功能:将一个文件或者设备的数据映射到内存中 + - 参数: + - void *addr: NULL, 由内核指定 + - length : 要映射的数据的长度,这个值不能为0。建议使用文件的长度。 + 获取文件的长度:stat lseek + - prot : 对申请的内存映射区的操作权限 + -PROT_EXEC :可执行的权限 + -PROT_READ :读权限 + -PROT_WRITE :写权限 + -PROT_NONE :没有权限 + 要操作映射内存,必须要有读的权限。 + PROT_READ、PROT_READ|PROT_WRITE + - flags : + - MAP_SHARED : 映射区的数据会自动和磁盘文件进行同步,进程间通信,必须要设置这个选项 + - MAP_PRIVATE :不同步,内存映射区的数据改变了,对原来的文件不会修改,会重新创建一个新的文件。(copy on write) + - fd: 需要映射的那个文件的文件描述符 + - 通过open得到,open的是一个磁盘文件 + - 注意:文件的大小不能为0,open指定的权限不能和prot参数有冲突。 + prot: PROT_READ open:只读/读写 + prot: PROT_READ | PROT_WRITE open:读写 + - offset:偏移量,一般不用。必须指定的是4k的整数倍,0表示不偏移。 + - 返回值:返回创建的内存的首地址 + 失败返回MAP_FAILED,(void *) -1 + + int munmap(void *addr, size_t length); + - 功能:释放内存映射 + - 参数: + - addr : 要释放的内存的首地址 + - length : 要释放的内存的大小,要和mmap函数中的length参数的值一样。 +*/ + +/* + 使用内存映射实现进程间通信: + 1.有关系的进程(父子进程) + - 还没有子进程的时候 + - 通过唯一的父进程,先创建内存映射区 + - 有了内存映射区以后,创建子进程 + - 父子进程共享创建的内存映射区 + + 2.没有关系的进程间通信 + - 准备一个大小不是0的磁盘文件 + - 进程1 通过磁盘文件创建内存映射区 + - 得到一个操作这块内存的指针 + - 进程2 通过磁盘文件创建内存映射区 + - 得到一个操作这块内存的指针 + - 使用内存映射区通信 + + 注意:内存映射区通信,是非阻塞。 +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +// 作业:使用内存映射实现没有关系的进程间的通信。 +int main() { + + // 1.打开一个文件 + int fd = open("test.txt", O_RDWR); + int size = lseek(fd, 0, SEEK_END); // 获取文件的大小 + + // 2.创建内存映射区,返回 类型为 void 的指针 + void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if(ptr == MAP_FAILED) { + perror("mmap"); + exit(0); + } + + // 3.创建子进程 + pid_t pid = fork(); + if(pid > 0) { + wait(NULL);//回收子进程 + // 父进程 + char buf[64]; + strcpy(buf, (char *)ptr); + printf("read data : %s\n", buf); + + }else if(pid == 0){ + // 子进程 + strcpy((char *)ptr, "nihao a, son!!!"); + } + + // 关闭内存映射区 + munmap(ptr, size); + + return 0; +} +``` + +## 3.共享内存 + +```c +共享内存相关的函数 +#include +#include + +int shmget(key_t key, size_t size, int shmflg); + - 功能:创建一个新的共享内存段,或者获取一个既有的共享内存段的标识。 + 新创建的内存段中的数据都会被初始化为0 + - 参数: + - key : key_t类型是一个整形,通过这个找到或者创建一个共享内存。 + 一般使用16进制表示,非0值 + - size: 共享内存的大小 + - shmflg: 属性 + - 访问权限 + - 附加属性:创建/判断共享内存是不是存在 + - 创建:IPC_CREAT + - 判断共享内存是否存在: IPC_EXCL , 需要和IPC_CREAT一起使用 + IPC_CREAT | IPC_EXCL | 0664 + - 返回值: + 失败:-1 并设置错误号 + 成功:>0 返回共享内存的引用的ID,后面操作共享内存都是通过这个值。 + + +void *shmat(int shmid, const void *shmaddr, int shmflg); + - 功能:和当前的进程进行关联 + - 参数: + - shmid : 共享内存的标识(ID),由shmget返回值获取 + - shmaddr: 申请的共享内存的起始地址,指定NULL,内核指定 + - shmflg : 对共享内存的操作 + - 读 : SHM_RDONLY, 必须要有读权限 + - 读写: 0 + - 返回值: + 成功:返回共享内存的首(起始)地址。 失败(void *) -1 + + +int shmdt(const void *shmaddr); + - 功能:解除当前进程和共享内存的关联 + - 参数: + shmaddr:共享内存的首地址 + - 返回值:成功 0, 失败 -1 + +int shmctl(int shmid, int cmd, struct shmid_ds *buf); + - 功能:对共享内存进行操作。删除共享内存,共享内存要删除才会消失,创建共享内存的进行被销毁了对共享内存是没有任何影响。 + - 参数: + - shmid: 共享内存的ID + - cmd : 要做的操作 + - IPC_STAT : 获取共享内存的当前的状态 + - IPC_SET : 设置共享内存的状态 + - IPC_RMID: 标记共享内存被销毁 + - buf:需要设置或者获取的共享内存的属性信息 + - IPC_STAT : buf存储数据 + - IPC_SET : buf中需要初始化数据,设置到内核中 + - IPC_RMID : 没有用,NULL + +key_t ftok(const char *pathname, int proj_id); + - 功能:根据指定的路径名,和int值,生成一个共享内存的key + - 参数: + - pathname:指定一个存在的路径 + /home/nowcoder/Linux/a.txt + + - proj_id: int类型的值,但是这系统调用只会使用其中的1个字节 + 范围 : 0-255 一般指定一个字符 'a' + + +问题1:操作系统如何知道一块共享内存被多少个进程关联? + - 共享内存维护了一个结构体struct shmid_ds 这个结构体中有一个成员 shm_nattch + - shm_nattach 记录了关联的进程个数 + +问题2:可不可以对共享内存进行多次删除 shmctl + - 可以的 + - 因为shmctl 标记删除共享内存,不是直接删除 + - 什么时候真正删除呢? + 当和共享内存关联的进程数为0的时候,就真正被删除 + - 当共享内存的key为0的时候,表示共享内存被标记删除了 + 如果一个进程和共享内存取消关联,那么这个进程就不能继续操作这个共享内存。也不能进行关联。 + + 共享内存和内存映射的区别 + 1.共享内存可以直接创建,内存映射需要磁盘文件(匿名映射除外) + 2.共享内存效果更高 + 3.内存 + 所有的进程操作的是同一块共享内存。 + 内存映射,每个进程在自己的虚拟地址空间中有一个独立的内存。 + 4.数据安全 + - 进程突然退出 + 共享内存还存在 + 内存映射区消失 + - 运行进程的电脑死机,宕机了 + 数据存在在共享内存中,没有了 + 内存映射区的数据 ,由于磁盘文件中的数据还在,所以内存映射区的数据还存在。 + + 5.生命周期 + - 内存映射区:进程退出,内存映射区销毁 + - 共享内存:进程退出,共享内存还在,标记删除(所有的关联的进程数为0),或者关机 + 如果一个进程退出,会自动和共享内存进行取消关联。 + +//read_shm.c +#include +#include +#include +#include + +int main() { + + // 1.获取一个共享内存 + int shmid = shmget(100, 0, IPC_CREAT); + printf("shmid : %d\n", shmid); + + // 2.和当前进程进行关联 + void * ptr = shmat(shmid, NULL, 0); + + // 3.读数据 + printf("%s\n", (char *)ptr); + + printf("按任意键继续\n"); + getchar(); + + // 4.解除关联 + shmdt(ptr); + + // 5.删除共享内存 + shmctl(shmid, IPC_RMID, NULL); + + return 0; +} + +//write_shm.c +#include +#include +#include +#include + +int main() { + + // 1.创建一个共享内存 + int shmid = shmget(100, 4096, IPC_CREAT|0664); + printf("shmid : %d\n", shmid); + + // 2.和当前进程进行关联 + void * ptr = shmat(shmid, NULL, 0); + + char * str = "helloworld"; + + // 3.写数据 + memcpy(ptr, str, strlen(str) + 1); + + printf("按任意键继续\n"); + getchar(); + + // 4.解除关联 + shmdt(ptr); + + // 5.删除共享内存 + shmctl(shmid, IPC_RMID, NULL); + + return 0; +} + + + +``` + + + +# 线程API + +## 1.pthread_create + +```C +/* + 一般情况下,main函数所在的线程我们称之为主线程(main线程),其余创建的线程 + 称之为子线程。 + 程序中默认只有一个进程,fork()函数调用,2进行 + 程序中默认只有一个线程,pthread_create()函数调用,2个线程。 + + #include + int pthread_create(pthread_t *thread, const pthread_attr_t *attr, + void *(*start_routine) (void *), void *arg); + + - 功能:创建一个子线程 + - 参数: + - thread:传出参数,线程创建成功后,子线程的线程ID被写到该变量中。 + - attr : 设置线程的属性,一般使用默认值,NULL + - start_routine : 函数指针,这个函数是子线程需要处理的逻辑代码 + - arg : 给第三个参数使用,传参 + - 返回值: + 成功:0 + 失败:返回错误号。这个错误号和之前errno不太一样。 + 获取错误号的信息: char * strerror(int errnum); + +*/ +#include +#include +#include +#include + +void * callback(void * arg) { + printf("child thread...\n"); + printf("arg value: %d\n", *(int *)arg); + return NULL; +} + +int main() { + + pthread_t tid; + + int num = 10; + + // 创建一个子线程 + int ret = pthread_create(&tid, NULL, callback, (void *)&num); + + if(ret != 0) { + char * errstr = strerror(ret); + printf("error : %s\n", errstr); + } + + for(int i = 0; i < 5; i++) { + printf("%d\n", i); + } + + sleep(1); + + return 0; // exit(0); +} +``` + +## 2.pthread_cancel + +```c +/* + #include + int pthread_cancel(pthread_t thread); + - 功能:取消线程(让线程终止) + 取消某个线程,可以终止某个线程的运行, + 但是并不是立马终止,而是当子线程执行到一个取消点,线程才会终止。 + 取消点:系统规定好的一些系统调用,我们可以粗略的理解为从用户区到内核区的切换,这个位置称之为取消点。 +*/ + +#include +#include +#include +#include + +void * callback(void * arg) { + printf("chid thread id : %ld\n", pthread_self()); + for(int i = 0; i < 5; i++) { + printf("child : %d\n", i); + } + return NULL; +} + +int main() { + + // 创建一个子线程 + pthread_t tid; + + int ret = pthread_create(&tid, NULL, callback, NULL); + if(ret != 0) { + char * errstr = strerror(ret); + printf("error1 : %s\n", errstr); + } + + // 取消线程 + pthread_cancel(tid); + + for(int i = 0; i < 5; i++) { + printf("%d\n", i); + } + + // 输出主线程和子线程的id + printf("tid : %ld, main thread id : %ld\n", tid, pthread_self()); + + pthread_exit(NULL); + + return 0; +} +``` + +## 3.pthread_detach + +```c +/* + #include + int pthread_detach(pthread_t thread); + - 功能:分离一个线程。被分离的线程在终止的时候,会自动释放资源返回给系统。 + 1.不能多次分离,会产生不可预料的行为。 + 2.不能去连接一个已经分离的线程,会报错。 + - 参数:需要分离的线程的ID + - 返回值: + 成功:0 + 失败:返回错误号 +*/ +#include +#include +#include +#include + +void * callback(void * arg) { + printf("chid thread id : %ld\n", pthread_self()); + return NULL; +} + +int main() { + + // 创建一个子线程 + pthread_t tid; + + int ret = pthread_create(&tid, NULL, callback, NULL); + if(ret != 0) { + char * errstr = strerror(ret); + printf("error1 : %s\n", errstr); + } + + // 输出主线程和子线程的id + printf("tid : %ld, main thread id : %ld\n", tid, pthread_self()); + + // 设置子线程分离,子线程分离后,子线程结束时对应的资源就不需要主线程释放 + ret = pthread_detach(tid); + if(ret != 0) { + char * errstr = strerror(ret); + printf("error2 : %s\n", errstr); + } + + // 设置分离后,对分离的子线程进行连接 pthread_join() + // ret = pthread_join(tid, NULL); + // if(ret != 0) { + // char * errstr = strerror(ret); + // printf("error3 : %s\n", errstr); + // } + + pthread_exit(NULL); + + return 0; +} +``` + +## 4.pthread_exit + +```c +/* + + #include + void pthread_exit(void *retval); + 功能:终止一个线程,在哪个线程中调用,就表示终止哪个线程 + 参数: + retval:需要传递一个指针,作为一个返回值,可以在pthread_join()中获取到。 + + pthread_t pthread_self(void); + 功能:获取当前的线程的线程ID + + int pthread_equal(pthread_t t1, pthread_t t2); + 功能:比较两个线程ID是否相等 + 不同的操作系统,pthread_t类型的实现不一样,有的是无符号的长整型,有的 + 是使用结构体去实现的。 +*/ +#include +#include +#include + +void * callback(void * arg) { + printf("child thread id : %ld\n", pthread_self()); + return NULL; // pthread_exit(NULL); +} + +int main() { + + // 创建一个子线程 + pthread_t tid; + int ret = pthread_create(&tid, NULL, callback, NULL); + + if(ret != 0) { + char * errstr = strerror(ret); + printf("error : %s\n", errstr); + } + + // 主线程 + for(int i = 0; i < 5; i++) { + printf("%d\n", i); + } + + printf("tid : %ld, main thread id : %ld\n", tid ,pthread_self()); + + // 让主线程退出,当主线程退出时,不会影响其他正常运行的线程。 + pthread_exit(NULL); + + printf("main thread exit\n"); + + return 0; // exit(0); +} +``` + +## 5.pthread_join + +```c +/* + #include + int pthread_join(pthread_t thread, void **retval); + - 功能:和一个已经终止的线程进行连接 + 回收子线程的资源 + 这个函数是阻塞函数,调用一次只能回收一个子线程 + 一般在主线程中使用 + - 参数: + - thread:需要回收的子线程的ID + - retval: 接收子线程退出时的返回值 + - 返回值: + 0 : 成功 + 非0 : 失败,返回的错误号 +*/ + +#include +#include +#include +#include + +int value = 10; + +void * callback(void * arg) { + printf("child thread id : %ld\n", pthread_self()); + // sleep(3); + // return NULL; + // int value = 10; // 局部变量 + pthread_exit((void *)&value); // return (void *)&value; +} + +int main() { + + // 创建一个子线程 + pthread_t tid; + int ret = pthread_create(&tid, NULL, callback, NULL); + + if(ret != 0) { + char * errstr = strerror(ret); + printf("error : %s\n", errstr); + } + + // 主线程 + for(int i = 0; i < 5; i++) { + printf("%d\n", i); + } + + printf("tid : %ld, main thread id : %ld\n", tid ,pthread_self()); + + // 主线程调用pthread_join()回收子线程的资源 + int * thread_retval; + ret = pthread_join(tid, (void **)&thread_retval); + + if(ret != 0) { + char * errstr = strerror(ret); + printf("error : %s\n", errstr); + } + + printf("exit data : %d\n", *thread_retval); + + printf("回收子线程资源成功!\n"); + + // 让主线程退出,当主线程退出时,不会影响其他正常运行的线程。 + pthread_exit(NULL); + + return 0; +} + +``` + +## 6.pthread_attr_... + +```c +/* + int pthread_attr_init(pthread_attr_t *attr); + - 初始化线程属性变量 + + int pthread_attr_destroy(pthread_attr_t *attr); + - 释放线程属性的资源 + + int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate); + - 获取线程分离的状态属性 + + int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); + - 设置线程分离的状态属性 +*/ + +#include +#include +#include +#include + +void * callback(void * arg) { + printf("chid thread id : %ld\n", pthread_self()); + return NULL; +} + +int main() { + + // 创建一个线程属性变量 + pthread_attr_t attr; + // 初始化属性变量 + pthread_attr_init(&attr); + + // 设置属性 + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + // 创建一个子线程 + pthread_t tid; + + int ret = pthread_create(&tid, &attr, callback, NULL); + if(ret != 0) { + char * errstr = strerror(ret); + printf("error1 : %s\n", errstr); + } + + // 获取线程的栈的大小 + size_t size; + pthread_attr_getstacksize(&attr, &size); + printf("thread stack size : %ld\n", size); + + // 输出主线程和子线程的id + printf("tid : %ld, main thread id : %ld\n", tid, pthread_self()); + + // 释放线程属性资源 + pthread_attr_destroy(&attr); + + pthread_exit(NULL); + + return 0; +} +``` + +# 线程同步 + + ◼ 线程的主要优势在于,能够通过全局变量来共享信息。不过,这种便捷的共享是有代价 的:必须确保多个线程不会同时修改同一变量,或者某一线程不会读取正在由其他线程 修改的变量。 + + ◼ 临界区是指访问某一共享资源的代码片段,并且这段代码的执行应为原子操作,也就是 同时访问同一共享资源的其他线程不应终端该片段的执行。 + + ◼ 线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进 行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作,而其他线程则处 于等待状态。 + +## 1.互斥量 + + ◼ 为避免线程更新共享变量时出现问题,可以使用互斥量(mutex 是 mutual exclusion 的缩写)来确保同时仅有一个线程可以访问某项共享资源。可以使用互斥量来保证对任意共 享资源的原子访问。 + + ◼ 互斥量有两种状态:已锁定(locked)和未锁定(unlocked)。任何时候,至多只有一 个线程可以锁定该互斥量。试图对已经锁定的某一互斥量再次加锁,将可能阻塞线程或者报 错失败,具体取决于加锁时使用的方法。 + + ◼ 一旦线程锁定互斥量,随即成为该互斥量的所有者,只有所有者才能给互斥量解锁。一般情 况下,对每一共享资源(可能由多个相关变量组成)会使用不同的互斥量,每一线程在访问 同一资源时将采用如下协议: + + ⚫ 针对共享资源锁定互斥量 + + ⚫ 访问共享资源 + + ⚫ 对互斥量解锁 + +## 1.互斥锁 + +```c +/* + 互斥锁的类型 pthread_mutex_t + 1) int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); + - 初始化互斥量 + - 参数 : + - mutex : 需要初始化的互斥量变量 + - attr : 互斥量相关的属性,NULL + - restrict : C语言的关键字,被修饰的指针,不能由另外的一个指针进行操作。 + pthread_mutex_t *restrict mutex = xxx; + pthread_mutex_t * mutex1 = mutex; + + 2) int pthread_mutex_destroy(pthread_mutex_t *mutex); + - 释放互斥量的资源 + + 3) int pthread_mutex_lock(pthread_mutex_t *mutex); + - 加锁,阻塞的,如果有一个线程加锁了,那么其他的线程只能阻塞等待 + + 4) int pthread_mutex_trylock(pthread_mutex_t *mutex); + - 尝试加锁,如果加锁失败,不会阻塞,会直接返回。 + + 5) int pthread_mutex_unlock(pthread_mutex_t *mutex); + - 解锁 +*/ +#include +#include +#include + +// 全局变量,所有的线程都共享这一份资源。 +int tickets = 1000; + +// 创建一个互斥量 +pthread_mutex_t mutex; + +void * sellticket(void * arg) { + + // 卖票 + while(1) { + + // 加锁 + pthread_mutex_lock(&mutex); + + if(tickets > 0) { + usleep(6000);//6000 微秒 + printf("%ld 正在卖第 %d 张门票\n", pthread_self(), tickets); + tickets--; + }else { + // 解锁 + pthread_mutex_unlock(&mutex); + break; + } + + // 解锁 + pthread_mutex_unlock(&mutex); + } + + + + return NULL; +} + +int main() { + + // 初始化互斥量 + pthread_mutex_init(&mutex, NULL); + + // 创建3个子线程 + pthread_t tid1, tid2, tid3; + pthread_create(&tid1, NULL, sellticket, NULL); + pthread_create(&tid2, NULL, sellticket, NULL); + pthread_create(&tid3, NULL, sellticket, NULL); + + // 回收子线程的资源,阻塞 + pthread_join(tid1, NULL); + pthread_join(tid2, NULL); + pthread_join(tid3, NULL); + + pthread_exit(NULL); // 退出主线程 + + // 释放互斥量资源 + pthread_mutex_destroy(&mutex); + + return 0; +} +``` + +## 2.死锁 + +定义:当两个或两个以上的进程在执行过程中,因争夺系统资源而造成的一种相互等待的现象,若无外力作用,它们都将无法推进下渠,此时称系统处于死锁状态。 + +◼ 有时,一个线程需要同时访问两个或更多不同的共享资源,而每个资源又都由不同的互 斥量管理。当超过一个线程加锁同一组互斥量时,就有可能发生死锁。 + +◼ 两个或两个以上的进程在执行过程中,因争夺共享资源而造成的一种互相等待的现象, 若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。 + +◼ 死锁的几种场景:  忘记释放锁  重复加锁  多线程多锁,抢占锁资源 + +(更多可见CDSN收藏夹) + +## 3.读写锁 + +◼ 当有一个线程已经持有互斥锁时,互斥锁将所有试图进入临界区的线程都阻塞住。但是考 虑一种情形,当前持有互斥锁的线程只是要读访问共享资源,而同时有其它几个线程也想 读取这个共享资源,但是由于互斥锁的排它性,所有其它线程都无法获取锁,也就无法读 访问共享资源了,但是实际上多个线程同时读访问共享资源并不会导致问题。 + +◼ 在对数据的读写操作中,更多的是读操作,写操作较少,例如对数据库数据的读写应用。 为了满足当前能够允许多个读出,但只允许一个写入的需求,线程提供了读写锁来实现。 + +◼ 读写锁的特点: + + 如果有其它线程读数据,则允许其它线程执行读操作,但不允许写操作。 + + 如果有其它线程写数据,则其它线程都不允许读、写操作。 + + 写是独占的,写的优先级高 + +```c +/* + 读写锁的类型 pthread_rwlock_t + int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); + int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); + int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); + int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); + int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); + int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); + int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); + + 案例:8个线程操作同一个全局变量。 + 3个线程不定时写这个全局变量,5个线程不定时的读这个全局变量 +*/ + +#include +#include +#include + +// 创建一个共享数据 +int num = 1; +// pthread_mutex_t mutex; +pthread_rwlock_t rwlock; + +void * writeNum(void * arg) { + + while(1) { + pthread_rwlock_wrlock(&rwlock); + num++; + printf("++write, tid : %ld, num : %d\n", pthread_self(), num); + pthread_rwlock_unlock(&rwlock); + usleep(100); + } + + return NULL; +} + +void * readNum(void * arg) { + + while(1) { + pthread_rwlock_rdlock(&rwlock); + printf("===read, tid : %ld, num : %d\n", pthread_self(), num); + pthread_rwlock_unlock(&rwlock); + usleep(100); + } + + return NULL; +} + +int main() { + + pthread_rwlock_init(&rwlock, NULL); + + // 创建3个写线程,5个读线程 + pthread_t wtids[3], rtids[5]; + for(int i = 0; i < 3; i++) { + pthread_create(&wtids[i], NULL, writeNum, NULL); + } + + for(int i = 0; i < 5; i++) { + pthread_create(&rtids[i], NULL, readNum, NULL); + } + + // 设置线程分离 + for(int i = 0; i < 3; i++) { + pthread_detach(wtids[i]); + } + + for(int i = 0; i < 5; i++) { + pthread_detach(rtids[i]); + } + + pthread_exit(NULL); + + pthread_rwlock_destroy(&rwlock); + + return 0; +} +``` + +## 4.自旋锁、挂起等待锁 + +1)自旋锁是在发生获取不到锁的时候,会直接等待,不会被CPU直接调度走,而是会一直等到获取到锁,因为此锁是一直的在等待,所以不会有调度的开销,故此锁的效率比挂起等待锁的效率高,但是此锁会因不停的查看锁的释放情况,故会浪费更多的CPU资源。 + +优点:效率高,避免了线程之间调度的开销 缺点:浪费CPU资源 + +2)挂起等待锁是当某线程在执行临界区的代码时,那其他线程只能挂起等待,此时这些线程会被CPU调度走,等到锁释放(即就是临界区的代码被之前的那个线程已经执行完毕),而且被CPU调度的线程只有被调度回来才可以执行临界区的代码 。 + 挂起等待锁是在发生获取不到锁的时候,他会被CPU调度走,去做别的事,但是会时不时的去查看锁有没有被释放 +ps:线程想执行临界区的代码的条件: +(1)锁释放; +(2)被CPU调度回来 + +优点:不会浪费CPU的资源,比较灵活 缺点:效率不高,很可能会使临界区的代码不被任何线程执行,因为可能会是线程被 CPU调度走了但是却没有被调度回来 + +———————————————— + +## 5.生产者消费者模型 + +归根结底来说,生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。 + +在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这种生产消费能力不均衡的问题,所以便有了生产者和消费者模式。 +———————————————— +版权声明:本文为CSDN博主「snow_5288」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 +原文链接:https://blog.csdn.net/snow_5288/article/details/72794306 + +```c +/* + 生产者消费者模型(粗略的版本) +*/ +#include +#include +#include +#include + +// 创建一个互斥量 +pthread_mutex_t mutex; + +struct Node{ + int num; + struct Node *next; +}; + +// 头结点 +struct Node * head = NULL; + +void * producer(void * arg) { + + // 不断的创建新的节点,添加到链表中,头插法 + while(1) { + pthread_mutex_lock(&mutex); + struct Node * newNode = (struct Node *)malloc(sizeof(struct Node)); + newNode->next = head; + head = newNode; + newNode->num = rand() % 1000; + printf("add node, num : %d, tid : %ld\n", newNode->num, pthread_self()); + pthread_mutex_unlock(&mutex); + usleep(100); + } + + return NULL; +} + +void * customer(void * arg) { + + while(1) { + pthread_mutex_lock(&mutex); + // 保存头结点的指针 + struct Node * tmp = head; + + // 判断是否有数据 + if(head != NULL) { + // 有数据 + head = head->next; + printf("del node, num : %d, tid : %ld\n", tmp->num, pthread_self()); + free(tmp); + pthread_mutex_unlock(&mutex); + usleep(100); + } else { + // 没有数据 + pthread_mutex_unlock(&mutex); + } + } + return NULL; +} + +int main() { + + pthread_mutex_init(&mutex, NULL); + + // 创建5个生产者线程,和5个消费者线程 + pthread_t ptids[5], ctids[5]; + + for(int i = 0; i < 5; i++) { + pthread_create(&ptids[i], NULL, producer, NULL); + pthread_create(&ctids[i], NULL, customer, NULL); + } + + for(int i = 0; i < 5; i++) { + pthread_detach(ptids[i]); + pthread_detach(ctids[i]); + } + + while(1) { + sleep(10); + } + + pthread_mutex_destroy(&mutex); + + pthread_exit(NULL); + + return 0; +} +``` + +## 6.条件变量 (配合互斥量实现线程同步) + +条件变量提供了一种线程间的通知机制:当某个共享数据达到某个值的时候,唤醒等待这个共享数据的线程。 + +如果说互斥锁是用于同步线程对共享数据的访问的话,那么条件变量则是用于在线程之间同步共享数据的值。 + +```c +/* + 条件变量的类型 pthread_cond_t + int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); + int pthread_cond_destroy(pthread_cond_t *cond); + + int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); + - 等待,调用了该函数,线程会阻塞。当这个函数调用阻塞的时候,会对互斥锁进行解锁,当不阻塞的,继续向下执行,会重新加锁。 + int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime); + - 等待多长时间,调用了这个函数,线程会阻塞,直到指定的时间结束。 + int pthread_cond_signal(pthread_cond_t *cond); + - 唤醒一个或者多个等待的线程 + int pthread_cond_broadcast(pthread_cond_t *cond); + - 唤醒所有的等待的线程 +*/ +#include +#include +#include +#include + +// 创建一个互斥量 +pthread_mutex_t mutex; +// 创建条件变量 +pthread_cond_t cond; + +struct Node{ + int num; + struct Node *next; +}; + +// 头结点 +struct Node * head = NULL; + +void * producer(void * arg) { + + // 不断的创建新的节点,添加到链表中 + while(1) { + pthread_mutex_lock(&mutex); + struct Node * newNode = (struct Node *)malloc(sizeof(struct Node)); + newNode->next = head; + head = newNode; + newNode->num = rand() % 1000; + printf("add node, num : %d, tid : %ld\n", newNode->num, pthread_self()); + + // 只要生产了一个,就通知消费者消费,唤醒一个或者多个等待的线程 + pthread_cond_signal(&cond); + + pthread_mutex_unlock(&mutex); + usleep(100); + } + + return NULL; +} + +void * customer(void * arg) { + + while(1) { + pthread_mutex_lock(&mutex); + // 保存头结点的指针 + struct Node * tmp = head; + // 判断是否有数据 + if(head != NULL) { + // 有数据 + head = head->next; + printf("del node, num : %d, tid : %ld\n", tmp->num, pthread_self()); + free(tmp); + pthread_mutex_unlock(&mutex); + usleep(100); + } else { + // 没有数据,需要等待 + // 当这个函数调用阻塞的时候,会对互斥锁进行解锁,然后等待生产者生产数据,当不阻塞,继续向下执行,会重新加锁。 + pthread_cond_wait(&cond, &mutex); + pthread_mutex_unlock(&mutex); + } + } + return NULL; +} + +int main() { + + pthread_mutex_init(&mutex, NULL); + pthread_cond_init(&cond, NULL); + + // 创建5个生产者线程,和5个消费者线程 + pthread_t ptids[5], ctids[5]; + + for(int i = 0; i < 5; i++) { + pthread_create(&ptids[i], NULL, producer, NULL); + pthread_create(&ctids[i], NULL, customer, NULL); + } + + for(int i = 0; i < 5; i++) { + pthread_detach(ptids[i]); + pthread_detach(ctids[i]); + } + + while(1) { + sleep(10); + } + + pthread_mutex_destroy(&mutex); + pthread_cond_destroy(&cond); + + pthread_exit(NULL); + + return 0; +} +``` + +## 7.信号量 + +```c +/* + 信号量的类型 sem_t + int sem_init(sem_t *sem, int pshared, unsigned int value); + - 初始化信号量 + - 参数: + - sem : 信号量变量的地址 + - pshared : 0 用在线程间 ,非0 用在进程间 + - value : 信号量中的值 + + int sem_destroy(sem_t *sem); + - 释放资源 + + int sem_wait(sem_t *sem); + - 对信号量加锁,调用一次对信号量的值-1,如果值为0,就阻塞,直到这个量具有非零值 + int sem_post(sem_t *sem); + - 对信号量解锁,调用一次对信号量的值+1 + + int sem_trywait(sem_t *sem); + - sem_trywait与sem_wait 函数相似,不过它始终立即返回,而不论被操作的信号量是否具有非0值,相当于sem_wait的非阻塞版本。当信号量的值非О时,sem_trywait对信号量执行减1操作。当信号量的值为0时,它将返回-1并设置errno为EAGAIN; + + int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); + + int sem_getvalue(sem_t *sem, int *sval); + + sem_t psem; + sem_t csem; + init(psem, 0, 8); + init(csem, 0, 0); + + producer() { + sem_wait(&psem); + sem_post(&csem) + } + + customer() { + sem_wait(&csem); + sem_post(&psem) + } + +*/ + +#include +#include +#include +#include +#include + +// 创建一个互斥量 +pthread_mutex_t mutex; +// 创建两个信号量 +sem_t psem; //可生产数 +sem_t csem;//可消费数 + +struct Node{ + int num; + struct Node *next; +}; + +// 头结点 +struct Node * head = NULL; + +void * producer(void * arg) { + + // 不断的创建新的节点,添加到链表中 + while(1) { + sem_wait(&psem);//值>0生产,再减1,=0 阻塞 + pthread_mutex_lock(&mutex); + struct Node * newNode = (struct Node *)malloc(sizeof(struct Node)); + newNode->next = head; + head = newNode; + newNode->num = rand() % 1000; + printf("add node, num : %d, tid : %ld\n", newNode->num, pthread_self()); + pthread_mutex_unlock(&mutex); + sem_post(&csem);//解锁,开灯,+1 + } + + return NULL; +} + +void * customer(void * arg) { + + while(1) { + sem_wait(&csem);//值 >0 消费,再-1, =0 阻塞 + pthread_mutex_lock(&mutex); + // 保存头结点的指针 + struct Node * tmp = head; + head = head->next; + printf("del node, num : %d, tid : %ld\n", tmp->num, pthread_self()); + free(tmp); + pthread_mutex_unlock(&mutex); + sem_post(&psem);// + + } + return NULL; +} + +int main() { + + pthread_mutex_init(&mutex, NULL); + sem_init(&psem, 0, 8); //生成8个,容量为8 + sem_init(&csem, 0, 0); + + // 创建5个生产者线程,和5个消费者线程 + pthread_t ptids[5], ctids[5]; + + for(int i = 0; i < 5; i++) { + pthread_create(&ptids[i], NULL, producer, NULL); + pthread_create(&ctids[i], NULL, customer, NULL); + } + + for(int i = 0; i < 5; i++) { + pthread_detach(ptids[i]); + pthread_detach(ctids[i]); + } + + while(1) { + sleep(10); + } + + pthread_mutex_destroy(&mutex); + sem_destroy(&psem); + sem_destroy(&csem); + pthread_exit(NULL); + + return 0; +} +``` + + + + + +# I/O多路复用(参考IO多路复用.PDF) + +select缺点: 1.每次调用select,都需要把fd集合从用 户态拷贝到内核态,这个开销在fd很多时 会很大 2.同时每次调用select都需要在内核遍历 传递进来的所有fd,这个开销在fd很多时也很大 3.select支持的文件描述符数量太小了, 默认是1024 4.fds集合不能重用,每次都需要重置 + +poll 缺点:1.每次调用select,都需要把fd集合从用 户态拷贝到内核态,这个开销在fd很多时 会很大 2.同时每次调用select都需要在内核遍历 传递进来的所有fd,这个开销在fd很多时也很大 + + + +# 常看网络相关信息的命令 + +## 1. netstat 参数 + +``` +-a 所有的socket + +-p 显示正在使用socket的程序的名称 + +-n 直接使用IP地址,而不通过域名服务 +``` + +## 2.实时显示进程动态 + +```c +实时显示进程动态 +top +可以在使用 top 命令时加上 -d 来指定显示信息更新的时间间隔,在 top 命令 +执行后,可以按以下按键对显示的结果进行排序: +⚫ M 根据内存使用量排序 +⚫ P 根据 CPU 占有率排序 +⚫ T 根据进程运行时间长短排序 +⚫ U 根据用户名来筛选进程 +⚫ K 输入指定的 PID 杀死进程 +``` + +## 3.查看进程 ps aux / ajx + +``` +查看进程 +ps aux / ajx +a:显示终端上的所有进程,包括其他用户的进程 +u:显示进程的详细信息 +x:显示没有控制终端的进程 +j:列出与作业控制相关的信息 +``` + diff --git "a/note/\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/note/\350\256\276\350\256\241\346\250\241\345\274\217.md" new file mode 100644 index 0000000000..f28ada9bb4 --- /dev/null +++ "b/note/\350\256\276\350\256\241\346\250\241\345\274\217.md" @@ -0,0 +1,137 @@ +# 面向对象设计原则 + +1.依赖倒置原则(DIP) + + 高层模块(稳定)不应该依赖于底层模块(变化),二者都应该依赖于抽象(稳定)。 + + 抽象(稳定)不应该依赖于实现细节(变化),实现细节应该依赖于抽象(稳定)。 + +2.开放封闭原则(OCP) + +​ 对扩展开放,对修改改封闭。 + +​ 类模块应该是可扩展的,但是不可修改。 + +3.单一职责原则(SRP) + +​ 一个类应该仅有一个引起它变化的原因。 + +​ 变化的方向隐含着类的责任。 + +4.Liskov替换原则(LSP) + +​ 子类必须能够替换它们的基类(IS-A) + +​ 继承表达类型抽象 + +5.接口隔离原则(ISP) + +6.优先使用对象组合,而不是类继承 + +7.封装变化点 + +8.针对接口编程,而不是针对实现编程 + +推荐图书: + +![image-20210302163910412](C:\Users\38032\AppData\Roaming\Typora\typora-user-images\image-20210302163910412.png) + +# 模式分类 + +![image-20210305134253112](C:\Users\38032\AppData\Roaming\Typora\typora-user-images\image-20210305134253112.png) + +## 组件协作 + +### 1.Template Method -模板方法 + +定义一个操作中的算法的骨架 (稳定),而将一些步骤延迟 (变化)到子类中。Template Method使得子类可以不改变 (复用)一个算法的结构即可重定义(override 重写)该算法的 某些特定步骤。 + +![image-20210302170723865](C:\Users\38032\AppData\Roaming\Typora\typora-user-images\image-20210302170723865.png) + + + +### 2.策略模式 + +定义一系列算法,把它们一个个封装起来,并且使它们可互相替换(变化)。该模式使得算法可独立于使用它的客户程序(稳定)而变化(扩展,子类化)。 + +![image-20210302173240664](C:\Users\38032\AppData\Roaming\Typora\typora-user-images\image-20210302173240664.png) + +红色圈是稳定部分,蓝色圈是变化部分 + +### 3.观察者模式 + +定义对象间的一种一对多(变化)的依赖关系,以便当一个 对象(Subject)的状态发生改变时,所有依赖于它的对象都 得到通知并自动更新。 + + 注: C++不推荐使用多继承,但有一种情况可以,一个主的继承类,其他都是接口或者抽象基类 + +![image-20210302213759225](C:\Users\38032\AppData\Roaming\Typora\typora-user-images\image-20210302213759225.png) + +## 单一职责 + +### 4.装饰模式 + +动态(组合)地给一个对象增加一些额外的职责。就增加功 能而言,Decorator模式比生成子类(继承)更为灵活(消 除重复代码 & 减少子类个数)。 + +![image-20210304225325501](C:\Users\38032\AppData\Roaming\Typora\typora-user-images\image-20210304225325501.png) + +要点总结 + +1.通过采用组合而非继承的手法, Decorator模式实现了在运行时 动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免 了使用继承带来的“灵活性差”和“多子类衍生问题” 。 + + 2.Decorator类在接口上表现为is-a Component的继承关系,即 Decorator类继承了Component类所具有的接口。但在实现上又 表现为has-a Component的组合关系,即Decorator类又使用了 另外一个Component类。 + +3.Decorator模式的目的并非解决“多子类衍生的多继承”问题, Decorator模式应用的要点在于解决“主体类在多个方向上的扩展 功能”——是为“装饰”的含义。 + +### 5.桥模式 + +![image-20210305111055265](C:\Users\38032\AppData\Roaming\Typora\typora-user-images\image-20210305111055265.png) + +## 对象创建 + +### 6.工厂方法 + +![image-20210305151406036](C:\Users\38032\AppData\Roaming\Typora\typora-user-images\image-20210305151406036.png) + +### 7.抽象工厂 + +提供一个接口,让该接口负责创建一系列“相关或者相互依 赖的对象”,无需指定它们具体的类。 + +![image-20210305165855408](C:\Users\38032\AppData\Roaming\Typora\typora-user-images\image-20210305165855408.png) + +注:红色表示稳定部分,蓝色和绿色是可变的。 + +### 8.原型模式 + +使用原型实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象。 + +![image-20210306102701881](C:\Users\38032\AppData\Roaming\Typora\typora-user-images\image-20210306102701881.png) + +### 9.构建器-Builder 模式 + +要点总结: + +1.主要用于“分步骤构建一个复杂的对象”。在这其中“分步骤”是一个稳定的算法,而复杂对象的各个部分则经常变化。 + +2.变化点在哪里,封装哪里-Builder模式主要在于应对“复杂对象各个部分”的频繁需求变动。其缺点在于难以应对“分步骤构建算法”的需求变动。 + +3.在Builder模式中,要注意不同语言中构造器内调用虚函数的差别(C++ vs ,C#) + +![image-20210306103426194](C:\Users\38032\AppData\Roaming\Typora\typora-user-images\image-20210306103426194.png) + +## 对象性能 + +### 10.单例模式 + +保证一个类仅有一个实例,并提供一个该实例的全局访问点。 + +要点总结: + +1.Singleleton 模式中的实例构造器可以设置为protected以允许子类派生。 + +2.Singleton模式一般不要支持拷贝构造函数和clone接口,因为这有可能导致多个对象实例,与singleton模式的初衷违背。 + +3.如何实现多线程环境下安全的Singleton?注意对双检查锁的正确实现。 + +![image-20210306111725931](C:\Users\38032\AppData\Roaming\Typora\typora-user-images\image-20210306111725931.png) + +### 11.Flyweight_享元模式 \ No newline at end of file diff --git "a/note/\350\266\205\345\205\250\351\235\242\347\232\204\345\220\216\347\253\257\345\274\200\345\217\221C_C++\351\235\242\347\273\217\346\225\264\347\220\206\345\210\206\344\272\253\345\220\253\345\217\202\350\200\203\347\255\224\346\241\210 \347\256\200\345\216\206\345\210\206\344\272\253_\347\254\224\347\273\217\351\235\242\347\273\217_\347\211\233\345\256\242\347\275\221.pdf" "b/note/\350\266\205\345\205\250\351\235\242\347\232\204\345\220\216\347\253\257\345\274\200\345\217\221C_C++\351\235\242\347\273\217\346\225\264\347\220\206\345\210\206\344\272\253\345\220\253\345\217\202\350\200\203\347\255\224\346\241\210 \347\256\200\345\216\206\345\210\206\344\272\253_\347\254\224\347\273\217\351\235\242\347\273\217_\347\211\233\345\256\242\347\275\221.pdf" new file mode 100644 index 0000000000..dee248fa9e Binary files /dev/null and "b/note/\350\266\205\345\205\250\351\235\242\347\232\204\345\220\216\347\253\257\345\274\200\345\217\221C_C++\351\235\242\347\273\217\346\225\264\347\220\206\345\210\206\344\272\253\345\220\253\345\217\202\350\200\203\347\255\224\346\241\210 \347\256\200\345\216\206\345\210\206\344\272\253_\347\254\224\347\273\217\351\235\242\347\273\217_\347\211\233\345\256\242\347\275\221.pdf" differ diff --git "a/note/\350\277\221 40 \345\274\240\345\233\276\350\247\243\350\242\253\351\227\256\345\215\203\347\231\276\351\201\215\347\232\204 TCP \344\270\211\346\254\241\346\217\241\346\211\213\345\222\214\345\233\233\346\254\241\346\214\245\346\211\213\351\235\242\350\257\225\351\242\230.pdf" "b/note/\350\277\221 40 \345\274\240\345\233\276\350\247\243\350\242\253\351\227\256\345\215\203\347\231\276\351\201\215\347\232\204 TCP \344\270\211\346\254\241\346\217\241\346\211\213\345\222\214\345\233\233\346\254\241\346\214\245\346\211\213\351\235\242\350\257\225\351\242\230.pdf" new file mode 100644 index 0000000000..bccbf01e1f Binary files /dev/null and "b/note/\350\277\221 40 \345\274\240\345\233\276\350\247\243\350\242\253\351\227\256\345\215\203\347\231\276\351\201\215\347\232\204 TCP \344\270\211\346\254\241\346\217\241\346\211\213\345\222\214\345\233\233\346\254\241\346\214\245\346\211\213\351\235\242\350\257\225\351\242\230.pdf" differ diff --git "a/note/\351\235\242\347\273\217\346\225\264\347\220\206\345\217\221\345\270\203\347\211\210.md" "b/note/\351\235\242\347\273\217\346\225\264\347\220\206\345\217\221\345\270\203\347\211\210.md" new file mode 100644 index 0000000000..a8fec78206 --- /dev/null +++ "b/note/\351\235\242\347\273\217\346\225\264\347\220\206\345\217\221\345\270\203\347\211\210.md" @@ -0,0 +1,2705 @@ +[TOC] + +# 1. 语言基础 (C/C++) + +#### (0) 指针和引用的区别 +* 指针是一个新的变量,指向另一个变量的地址,我们可以通过访问这个地址来修改另一个变量;而引用是一个别名,对引用的操作就是对变量的本身进行操作, +* 指针可以有多级,引用只有一级 +* 传参的时候,使用指针的话需要解引用才能对参数进行修改,而使用引用可以直接对参数进行修改 +* 指针的大小一般是4个字节,引用的大小取决于被引用对象的大小 +* 指针可以为空,引用不可以。 +* 本质:**引用的本质在c++内部实现是一个指针常量,int * const ptr.** + +#### (1)在函数参数传递的时候,什么时候使用指针,什么时候使用引用? +* 需要返回函数内局部静态变量的内存的时候用指针。使用指针传参需要开辟内存,用完要记得释放指针,不然会内存泄漏。而返回局部变量的引用是没有意义的 +* 对栈空间大小比较敏感(比如递归)的时候使用引用。使用引用传递不需要创建临时变量,开销要更小 +* 类对象作为参数传递的时候使用引用,这是C++类对象传递的标准方式 +#### (2) 堆和栈有什么区别 +* 从定义上:堆是由new和malloc开辟的一块内存,由程序员手动管理,栈是编译器自动管理的内存,存放函数的参数和局部变量。 +* 堆空间因为会有频繁的分配释放操作,会产生内存碎片 +* 堆的生长空间向上,地址越来越大,栈的生长空间向下,地址越来越小 +#### (3)堆快一点还是栈快一点?(字节提前批一面) +栈快一点。因为操作系统会在底层对栈提供支持,会分配专门的寄存器存放栈的地址,栈的入栈出栈操作也十分简单,并且有专门的指令执行,所以栈的效率比较高也比较快。而堆的操作是由C/C++函数库提供的,在分配堆内存的时候需要一定的算法寻找合适大小的内存。并且获取堆的内容需要两次访问,第一次访问指针,第二次根据指针保存的地址访问内存,因此堆比较慢。 +#### (4) new和delete是如何实现的,new 与 malloc的异同处 +在new一个对象的时候,**首先会调用malloc为对象分配内存空间,然后调用对象的构造函数**。**delete会调用对象的析构函数,然后调用free回收内存**。 + +new与malloc都会分配空间,但是new还会调用对象的构造函数进行初始化,malloc需要给定空间大小,而new只需要对象名 +#### (5)既然有了malloc/free,C++中为什么还需要new/delete呢? +https://blog.csdn.net/leikun153/article/details/80612130 + +* malloc/free和new/delete都是用来申请内存和回收内存的。 +* 在对非基本数据类型的对象使用的时候,对象创建的时候还需要执行构造函数,销毁的时候要执行析构函数。而malloc/free是库函数,是已经编译的代码,所以不能把构造函数和析构函数的功能强加给malloc/free。 +#### (6) C和C\+\+的区别 +包括但不限于: +* C是面向过程的语言,C\+\+是面向对象的语言,C\+\+有“封装,继承和多态”的特性。封装隐藏了实现细节,使得代码模块化。继承通过子类继承父类的方法和属性,实现了代码重用。多态则是“一个接口,多个实现”,通过子类重写父类的虚函数,实现了接口重用。 +* C和C\+\+内存管理的方法不一样,C使用malloc/free,C\+\+除此之外还用new/delete +* C\+\+中还有函数重载和引用等概念,C中没有 +> #### (7)delete和delete\[\]的区别 +> + +* delete只会调用一次析构函数,而delete\[\]会调用每个成员的析构函数 + +* 用new分配的内存用delete释放,用new\[\]分配的内存用delete\[\]释放 + +> #### (8) C++、Java的联系与区别,包括语言特性、垃圾回收、应用场景等(java的垃圾回收机制) + +包括但不限于: +* C\+\+ 和Java都是面向对象的语言,C\+\+是编译成可执行文件直接运行的,JAVA是编译之后在JAVA虚拟机上运行的,因此JAVA有良好的跨平台特性,但是执行效率没有C\+\+ 高。 +* C\+\+的内存管理由程序员手动管理,JAVA的内存管理是由Java虚拟机完成的,它的垃圾回收使用的是标记-回收算法 +* C\+\+有指针,Java没有指针,只有引用 +* JAVA和C\+\+都有构造函数,但是C\+\+有析构函数但是Java没有 +> #### (9)C++和python的区别 + +包括但不限于: +1. python是一种脚本语言,是解释执行的,而C\+\+是编译语言,是需要编译后在特定平台运行的。python可以很方便的跨平台,但是效率没有C\+\+高。 +2. python使用缩进来区分不同的代码块,C\+\+使用花括号来区分 +3. C\+\+中需要事先定义变量的类型,而python不需要,python的基本数据类型只有数字,布尔值,字符串,列表,元组等等 +4. python的库函数比C\+\+的多,调用起来很方便 +#### (10) Struct和class的区别 +* 使用struct时,它的成员的访问权限默认是public的,而class的成员默认是private的 +* struct的继承默认是public继承,而class的继承默认是private继承 +* class可以用作模板,而struct不能 +#### (11) define 和const的联系与区别(编译阶段、安全性、内存占用等) + 联系:它们都是定义常量的一种方法。 + + 区别: +* define定义的常量没有类型,只是进行了简单的替换,可能会有多个拷贝,占用的内存空间大,const定义的常量是有类型的,存放在静态存储区,只有一个拷贝,占用的内存空间小。 +* define定义的常量是在预处理阶段进行替换,而const在编译阶段确定它的值。 +* define不会进行类型安全检查,而const会进行类型安全检查,安全性更高。 +* const可以定义函数而define不可以。 +#### (12) 在C\+\+中const的用法(定义,用途) +* const修饰类的成员变量时,表示常量不能被修改 +* const修饰类的成员函数,表示该函数不会修改类中的数据成员,不会调用其他非const的成员函数 + +#### (13) C++中的static用法和意义 +static的意思是静态的,可以用来修饰变量,函数和类成员。 +* 变量:被static修饰的变量就是静态变量,它会在程序运行过程中一直存在,会被放在静态存储区。局部静态变量的作用域在函数体中,全局静态变量的作用域在这个文件里。 +* 函数:被static修饰的函数就是静态函数,静态函数只能在本文件中使用,不能被其他文件调用,也不会和其他文件中的同名函数冲突。 +* 类:(1)而在类中,被static修饰的成员变量是类静态成员,这个静态成员会被类的多个对象共用。(2)被static修饰的成员函数也属于静态成员,不是属于某个对象的,**访问这个静态函数不需要引用对象名,而是通过引用类名来访问**。 + +**【note】**静态成员函数要访问非静态成员时,要用过对象来引用。局部静态变量在函数调用结束后也不会被回收,会一直在程序内存中,直到该函数再次被调用,它的值还是保持上一次调用结束后的值。 + +注意和const的区别。const强调值不能被修改,而static强调唯一的拷贝,对所有类的对象都共用。 +#### (14) 计算下面几个类的大小: +```C++ +class A {}; +int main(){ + cout< +```C++ +class A { static int a; }; +int main(){ + cout<用程序调用mmap(),磁盘上的数据会通过DMA被拷贝的内核缓冲区,接着操作系统会把这段内核缓冲区与应用程序共享,这样就不需要把内核缓冲区的内容往用户空间拷贝。应用程序再调用write(),操作系统直接将内核缓冲区的内容拷贝到socket缓冲区中,这一切都发生在内核态,最后,socket缓冲区再把数据发到网卡去。 + +#### (35) 介绍C++所有的构造函数 +C\+\+中的构造函数主要有三种类型:默认构造函数、重载构造函数和拷贝构造函数 +* 默认构造函数是当类没有实现自己的构造函数时,编译器默认提供的一个构造函数。 +* 重载构造函数也称为一般构造函数,一个类可以有多个重载构造函数,但是需要参数类型或个数不相同。可以在重载构造函数中自定义类的初始化方式。 +* 拷贝构造函数是在发生对象复制的时候调用的。 +#### (36) 什么情况下会调用拷贝构造函数(三种情况) +* 对象以值传递的方式传入函数参数 + + >如 ` void func(Dog dog){};` +* 对象以值传递的方式从函数返回 + + >如 ` Dog func(){ Dog d; return d;}` +* 对象需要通过另外一个对象进行初始化 + +详见:[C++拷贝构造函数详解](https://blog.csdn.net/lwbeyond/article/details/6202256) +#### (37) 结构体内存对齐方式和为什么要进行内存对齐? +因为结构体的成员可以有不同的数据类型,所占的大小也不一样。同时,由于CPU读取数据是按块读取的,内存对齐可以使得CPU一次就可以将所需的数据读进来。 + +对齐规则: +* 第一个成员在与结构体变量偏移量为0的地址 +* 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 +* 对齐数=编译器默认的一个对齐数 与结构体中最宽数据类型的较小值。 +* linux 中默认为4 +* vs 中的默认值为8 +结构体总大小为最大对齐数的整数倍(每个成员变量除了第一个成员都有一个对齐数) + +#### (38) 内存泄露的定义,如何检测与避免? +动态分配内存所开辟的空间,在使用完毕后未手动释放,导致一直占据该内存,即为内存泄漏。 + +造成内存泄漏的几种原因: + +1)类的构造函数和析构函数中new和delete没有配套 + +2)在释放对象数组时没有使用delete\[\],使用了delete + +3)没有将基类的析构函数定义为虚函数,当基类指针指向子类对象时,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确释放,因此造成内存泄露 + +4)没有正确的清楚嵌套的对象指针 + +避免方法: +1. malloc/free要配套 +2. 使用智能指针; +3. 将基类的析构函数设为虚函数; +#### (39) C++的智能指针有哪些 +C++中的智能指针有auto_ptr,shared_ptr,weak_ptr和unique_ptr。智能指针其实是将指针进行了封装,可以像普通指针一样进行使用,同时可以自行进行释放,避免忘记释放指针指向的内存地址造成内存泄漏。 +* auto_ptr是较早版本的智能指针,在进行指针拷贝和赋值的时候,新指针直接接管旧指针的资源并且将旧指针指向空,但是这种方式在需要访问旧指针的时候,就会出现问题。 +* unique_ptr是auto_ptr的一个改良版,不能赋值也不能拷贝,保证一个对象同一时间只有一个智能指针。 +* shared_ptr可以使得一个对象可以有多个智能指针,当这个对象所有的智能指针被销毁时就会自动进行回收。(内部使用计数机制进行维护) +* weak_ptr是为了协助shared_ptr而出现的。它不能访问对象,只能观测shared_ptr的引用计数,防止出现死锁。 +* weak _ ptr是 一 种 不 控 制 对 象 生 命 周 期 的 智 能 指 针 , 它 指 向 一 个 shared_ptr 管 理 的 对 象 . 进 行 该 对 象 的 内 存 管 理 的 是 那 个 强 引 用 的 shared_ptr. weak_ptr 只 是 提 供 了 对 管 理 对 象 的 一 个 访 问 手 段 。 weak_ptr 设 计 的 目 的 是 为 配 合 shared_ptr 而 引 入 的 一 种 智 能 指 针 来 协 助 shared_ptr 工 作 , 它 只 可 以 从 一 个 shared_ptr 或 另 一 个 weak_ptr 对 象 构 造 , 它 的 构 造 和 析 构 不 会 引 起 引 用 记 数 的 增 加 或 减 少 。 **weak_ptr 是 用 来 解 决 shared_ptr 相 互 引 用 时 的 死 锁 问 题** , 如 果 说 两 个 shared_ ptr 相 互 引 用 , 那 么 这 两 个 指 针 的 引 用 计 数 永 远 不 可 能 下 降 为 0 , 资 源 永 远 不 会 释 放 。**weak_ptr** 它 是 对 对 象 的 一 种 弱 引 用 , 不 会 增 加 对 象 的 引 用 计 数 , 和 shared_ptr 之 间 可 以 相 互 转 化 , shared_ptr 可 以 直 接 赋 值 给 它 , 它 可 以 通 过 调 用 l o c k 函 数 来 获 得 shared_ptr 。 +* 详见 C语言C++常见面试题(含答案).PDF 第38点 +#### (40) 调试程序的方法 +* 通过设置断点进行调试 +* 打印log进行调试 +* 打印中间结果进行调试 +#### (41) 遇到coredump要怎么调试 +coredump是程序由于异常或者bug在运行时异常退出或者终止,在一定的条件下生成的一个叫做core的文件,这个core文件会记录程序在运行时的内存,寄存器状态,内存指针和函数堆栈信息等等。对这个文件进行分析可以定位到程序异常的时候对应的堆栈调用信息。 + +* 使用gdb命令对core文件进行调试 + +以下例子在Linux上编写一段代码并导致segment fault 并产生core文件 +``` +mkdir coredumpTest +vim coredumpTest.cpp +``` +在编辑器内键入 +``` +#include +int main(){ + int i; + scanf("%d",i);//正确的应该是&i,这里使用i会导致segment fault + printf("%d\n",i); + return 0; +} +``` +编译 +``` +g++ coredumpTest.cpp -g -o coredumpTest +``` +运行 +``` +./coredumpTest +``` +使用gdb调试coredump +``` +gdb [可执行文件名] [core文件名] +``` + + + +3.生成core file 查看错误(如:核心已转储) + +命令行键入: + +1) ulimit -a 查看core file size 是否为unlimited + +2)改变 :ulimit -c unlimited + +3) 重新编译 gcc main.c -g -o main + +4) 生成可执行文件,执行后,生成core文件 + +5) 进入gdb调试:gdb main + +6)输入core-file core 回车,可查看结果 + + + + + +#### (42) inline关键字说一下 和宏定义有什么区别 + +inline是内联的意思,可以定义比较小的函数。因为函数频繁调用会占用很多的栈空间,进行入栈出栈操作也耗费计算资源,所以可以用inline关键字修饰频繁调用的小函数。编译器会在编译阶段将代码体嵌入内联函数的调用语句块中。 + +1、内联函数在编译时展开,而宏在预编译时展开 + +2、在编译的时候,内联函数直接被嵌入到目标代码中去,而宏只是一个简单的文本替换。 + +3、内联函数可以进行诸如类型安全检查、语句是否正确等编译功能,宏不具有这样的功能。 + +4、宏不是函数,而inline是函数 + +5、宏在定义时要小心处理宏参数,一般用括号括起来,否则容易出现二义性。而内联函数不会出现二义性。 + +6、inline可以不展开,宏一定要展开。因为inline指示对编译器来说,只是一个建议,编译器可以选择忽略该建议,不对该函数进行展开。 + +7、宏定义在形式上类似于一个函数,但在使用它时,仅仅只是做预处理器符号表中的简单替换,因此它不能进行参数有效性的检测,也就不能享受C++编译器严格类型检查的好处,另外它的返回值也不能被强制转换为可转换的合适的类型,这样,它的使用就存在着一系列的隐患和局限性。 + +#### (43) 模板的用法与适用场景 实现原理 +用template \关键字进行声明,接下来就可以进行模板函数和模板类的编写了 + +编译器会对函数模板进行两次编译:在声明的地方对模板代码本身进行编译,这次编译只会进行一个语法检查,并不会生成具体的代码。在运行时对代码进行参数替换后再进行编译,生成具体的函数代码。 +#### (44) 成员初始化列表的概念,为什么用成员初始化列表会快一些(性能优势)? +成员初始化列表就是在类或者结构体的构造函数中,在参数列表后以冒号开头,逗号进行分隔的一系列初始化字段。如下: +``` +class A{ +int id; +string name; +FaceImage face; +A(int& inputID,string& inputName,FaceImage& inputFace):id(inputID),name(inputName),face(inputFace){} // 成员初始化列表 +}; +``` +因为使用成员初始化列表进行初始化的话,会**直接使用传入参数的拷贝构造函数**进行初始化,省去了一次执行传入参数的默认构造函数的过程,否则会调用一次传入参数的默认构造函数。所以使用成员初始化列表效率会高一些。
+另外,有三种情况是必须使用成员初始化列表进行初始化的: + +* 常量成员的初始化,因为常量成员只能初始化不能赋值 +* 引用类型 +* 没有默认构造函数的对象必须使用成员初始化列表的方式进行初始化 + +详见[C++ 初始化列表](https://www.cnblogs.com/graphics/archive/2010/07/04/1770900.html) + +```c++ +struct Test1{ + Test1(){ + cout<<"无参构造"<a = p.a; + cout<<"拷贝构造"<a = p.a; + return *this; + cout<<"赋值运算符"<返回值类型{函数体}` +* thread类和mutex类 +* 新的智能指针 unique_ptr和shared_ptr +* 移动语义,移动构造函数 + + +* 更多详见:https://blog.csdn.net/caogenwangbaoqiang/article/details/79438279 +* c++17,绑定,optional +#### (46) C++的调用惯例(简单一点C++函数调用的压栈过程) +函数的调用过程: + +1)从栈空间分配存储空间 + +2)从实参的存储空间复制值到形参栈空间 + +3)进行运算 + +形参在函数未调用之前都是没有分配存储空间的,在函数调用结束之后,形参弹出栈空间,清除形参空间。 + +数组作为参数的函数调用方式是地址传递,形参和实参都指向相同的内存空间,调用完成后,形参指针被销毁,但是所指向的内存空间依然存在,不能也不会被销毁。 + +当函数有多个返回值的时候,不能用普通的 return 的方式实现,需要通过传回地址的形式进行,即地址/指针传递。 +#### (47) C++的四种强制转换 +四种强制类型转换操作符分别为:static_cast、dynamic_cast、const_cast、reinterpret_cast + +* 1)static_cast : +用于各种隐式转换。具体的说,就是用户各种基本数据类型之间的转换,比如把int换成char,float换成int等。以及派生类(子类)的指针转换成基类(父类)指针的转换。 + >特性与要点: + 1. 它没有运行时类型检查,所以是有安全隐患的。 + 2. 在派生类指针转换到基类指针时,是没有任何问题的,在基类指针转换到派生类指针的时候,会有安全问题。 + 3. static_cast不能转换const,volatile等属性 +* 2)dynamic_cast: +用于动态类型转换。具体的说,就是在基类指针到派生类指针,或者派生类到基类指针的转换。 +dynamic_cast能够提供运行时类型检查,只用于含有虚函数的类。 +dynamic_cast如果不能转换返回NULL。 +* 3)const_cast: +用于去除const常量属性,使其可以修改 ,也就是说,原本定义为const的变量在定义后就不能进行修改的,但是使用const_cast操作之后,可以通过这个指针或变量进行修改; 另外还有volatile属性的转换。 +* 4)reinterpret_cast +几乎什么都可以转,用在任意的指针之间的转换,引用之间的转换,指针和足够大的int型之间的转换,整数到指针的转换等。但是不够安全。 +#### (48)string的底层实现 +string继承自basic_string,其实是对char\*进行了封装,封装的string包含了char\*数组,容量,长度等等属性。 + +**string可以进行动态扩展,在每次扩展的时候另外申请一块原空间大小两倍的空间**(2^n),然后将原字符串拷贝过去,并加上新增的内容。 + +#### (49)一个函数或者可执行文件的生成过程或者编译过程是怎样的 +预处理,编译,汇编,链接 + +* 预处理: 对预处理命令进行替换等预处理操作 +* 编译:代码优化和生成汇编代码 +* 汇编:将汇编代码转化为机器语言 +* 链接:将目标文件彼此链接起来 +#### (50)set,map和vector的插入复杂度 +set,map的插入复杂度就是红黑树的插入复杂度,是log(N)。 + +unordered_set,unordered_map的插入复杂度是常数,最坏是O(N). + +vector的插入复杂度是O(N),最坏的情况下(从头插入)就要对所有其他元素进行移动,或者扩容重新拷贝 +#### (51)定义和声明的区别 +* 声明是告诉编译器变量的类型和名字,不会为变量分配空间 + +* 定义就是对这个变量和函数进行内存分配和初始化。需要分配空间,同一个变量可以被声明多次,但是只能被定义一次 +#### (52)typdef和define区别 + +#define是预处理命令,在预处理是执行简单的替换,不做正确性的检查 + +typedef是在编译时处理的,它是在自己的作用域内给已经存在的类型一个别名 + +#### (53)被free回收的内存是立即返还给操作系统吗?为什么 +https://blog.csdn.net/YMY_mine/article/details/81180168 + +不是的,**被free回收的内存会首先被ptmalloc使用双链表保存起来**,当用户下一次申请内存的时候,会尝试从这些内存中寻找合适的返回。这样就避免了频繁的系统调用,占用过多的系统资源。同时ptmalloc也会尝试对小块内存进行合并,避免过多的内存碎片。 + +#### (54)引用作为函数参数以及返回值的好处 + +对比值传递,引用传参的好处: + +1)在函数内部可以对此参数进行修改 + +2)提高函数调用和运行的效率(因为没有了传值和生成副本的时间和空间消耗) + +如果函数的参数实质就是形参,不过这个形参的作用域只是在函数体内部,也就是说实参和形参是两个不同的东西,要想形参代替实参,肯定有一个值的传递。函数调用时,值的传递机制是通过“形参=实参”来对形参赋值达到传值目的,产生了一个实参的副本。即使函数内部有对参数的修改,也只是针对形参,也就是那个副本,实参不会有任何更改。函数一旦结束,形参生命也宣告终结,做出的修改一样没对任何变量产生影响。 + +用引用作为返回值最大的好处就是在内存中不产生被返回值的副本。 + +但是有以下的限制: + +1)不能返回局部变量的引用。因为函数返回以后局部变量就会被销毁 + +2)不能返回函数内部new分配的内存的引用。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一 个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak + +3)可以返回类成员的引用,但是最好是const。因为如果其他对象可以获得该属性的非常量的引用,那么对该属性的单纯赋值就会破坏业务规则的完整性。 + +#### (55)友元函数和友元类 +https://www.cnblogs.com/zhuguanhao/p/6286145.html + +友元提供了不同类的成员函数之间、类的成员函数和一般函数之间进行数据共享的机制。通过友元,一个不同函数或者另一个类中的成员函数可以访问类中的私有成员和保护成员。友元的正确使用能提高程序的运行效率,但同时也破坏了类的封装性和数据的隐藏性,导致程序可维护性变差。 + +1)友元函数 + +有元函数是定义在类外的普通函数,不属于任何类,可以访问其他类的私有成员。但是需要在类的定义中声明所有可以访问它的友元函数。 + +``` +#include + +using namespace std; + +class A +{ +public: + friend void set_show(int x, A &a); //该函数是友元函数的声明 +private: + int data; +}; + +void set_show(int x, A &a) //友元函数定义,为了访问类A中的成员 +{ + a.data = x; + cout << a.data << endl; +} +int main(void) +{ + class A a; + + set_show(1, a); + + return 0; +} +``` + +一个函数可以是多个类的友元函数,但是每个类中都要声明这个函数。 + +2)友元类 + +友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。 +但是另一个类里面也要相应的进行声明 + + ``` + #include + +using namespace std; + +class A +{ +public: + friend class C; //这是友元类的声明 +private: + int data; +}; + +class C //友元类定义,为了访问类A中的成员 +{ +public: + void set_show(int x, A &a) { a.data = x; cout< ssthresh 时),停止使用慢开始算法而改用拥塞避免算法) + +(2)**拥塞避免**(线性增加)。拥塞避免算法的思路是让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍。 + +(3)**快重传**。当发送端连续收到三个重复的ack时,表示该数据段已经丢失,需要重发。此时慢启动阈值ssth变为原来一半,(然后执行拥塞避免算法)拥塞窗口cwnd变为ssth+3,然后+1+1的发(每一轮rtt+1)。 + +(4)**快恢复**。(**出现超时,使用慢开始算法**)当超过设定的时间没有收到某个报文段的ack时,表示网络拥塞,慢启动阈值ssth变为原来一半,拥塞窗口cwnd=1,进入慢启动阶段。 + +书上定义:当发送端连续收到三个重复的ack时(**使用拥塞避免算法**),此时慢启动阈值ssth变为原来一半,然后执行拥塞避免算法,拥塞窗口cwnd变为ssth+3,然后+1+1的发(每一轮rtt+1)。 + +注:在采用快恢复算法时,慢开始算法只是在**TCP连接建立**时和**网络出现超时**时才使用。 + +#### (21) http协议与TCP的区别与联系 +联系:Http协议是建立在TCP协议基础之上的,当浏览器需要从服务器获取网页数据的时候,会发出一次Http请求。Http会通过TCP建立起一个到服务器的连接通道,当本次请求需要的数据传输完毕后,Http会立即将TCP连接断开,这个过程是很短的。 + +区别:HTTP和TCP位于不同的网络分层。TCP是传输层的协议,定义的是数据传输和连接的规范,而HTTP是应用层的,定义的是数据的内容的规范。 +建立一个TCP请求需要进行三次握手,而由于http是建立在tcp连接之上的,建立一个http请求通常包含请求和响应两个步骤。 +#### (22) http/1.0和http/1.1的区别 +HTTP 协议老的标准是 HTTP/1.0 ,目前最通用的标准是 HTTP/1.1 。 +HTTP1.0 只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个 TCP 连接,但是最新的http/1.0加入了长连接,只需要在客户端给服务器发送的http报文头部加入Connection:keep-alive +HTTP 1.1 支持持久连接,默认进行持久连接,在一个 TCP 连接上可以传送多个 HTTP 请求和响应,减少了建立和关闭连接的消耗和延迟。 + +#### (23) http的请求方法有哪些?get和post的区别。 +HTTP的请求方法包括GET,POST,PUT,DELETE四种基本方法。(四种方法中只有POST不是操作幂等性的) + +get和post的区别: +1. get方法不会修改服务器上的资源,它的查询是没有副作用的,而post有可能会修改服务器上的资源 +2. get可以保存为书签,可以用缓存来优化,而post不可以 +3. get把请求附在url上,而post把参数附在http包的包体中 +4. 浏览器和服务器一般对get方法所提交的url长度有限制,一般是1k或者2k,而对post方法所传输的参数大小限制为80k到4M不等 +5. post可以传输二进制编码的信息,get的参数一般只支持ASCII +#### (24) http的状态码 403 201等等是什么意思 +详见 [HTTP状态码的含义](https://blog.csdn.net/u011630575/article/details/46636535) + +常见的状态码有: +>* 200 - 请求成功 +>* 301 - 资源(网页等)被永久转移到其它URL +>* 404 - 请求的资源(网页等)不存在 +>* 500 - 内部服务器错误 +>* 400 - 请求无效 +>* 403 - 禁止访问 +#### (25) http和https的区别,由http升级为https需要做哪些操作 +http 是超文本传输协议,信息是明文传输, https 则是具有安全性的 ssl 加密传输协议 +http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80 ,后者是 443 +http 的连接很简单,是无状态的; HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比http 协议安全。 +https 协议需要到 ca 申请证书,一般免费证书较少,因而需要一定费用 +https://www.cnblogs.com/wqhwe/p/5407468.html + +#### (26) https的具体实现,怎么确保安全性 +**SSL是传输层的协议** + +https包括非对称加密和对称加密两个阶段,在客户端与服务器建立连接的时候使用非对称加密,连接建立以后使用的是对称加密。 + +1. 客户使用https的URL访问Web服务器,要求与Web服务器建立SSL连接 +2. Web服务器收到客户端请求后,会将网站的公钥传送一份给客户端,私钥自己保存。 +3. 客户端的浏览器根据双方同意的安全等级,生成对称加密使用的密钥,称为会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站 +4. Web服务器利用自己的私钥解密出会话密钥。 +5. Web服务器利用会话密钥加密与客户端之间的通信,这个过程是对称加密的过程。 + +服务器**第一次传给客户端的公钥其实是CA对网站信息进行加密的数字证书** + +客户端的对称加密密钥其实是三个随机数的哈希(1. 客户端第一次给服务端发送请求时附带的随机数 2. 服务器返回时的随机数 3. 客户端收到返回时的随机数) +#### (27) TCP三次握手时的第一次的seq序号是怎样产生的 +第一次的序号是随机序号,但也不是完全随机,它是使用一个ISN算法得到的。 + +seq = C + H (源IP地址,目的IP地址,源端口,目的端口)。其中,C是一个计时器,每隔一段时间值就会变大,H是消息摘要算法,输入是一个四元组(源IP地址,目的IP地址,源端口,目的端口)。 +#### (28) 一个机器能够使用的端口号上限是多少,为什么?可以改变吗?那如果想要用的端口超过这个限制怎么办? +65536.因为TCP的报文头部中源端口号和目的端口号的长度是16位,也就是可以表示2^16=65536个不同端口号,因此TCP可供识别的端口号最多只有65536个。但是由于0到1023是知名服务端口,所以实际上还要少1024个端口号。 + +而对于服务器来说,可以开的端口号与65536无关,其实是受限于Linux可以打开的文件数量,并且可以通过MaxUserPort来进行配置。 +#### (29) 对称密码和非对称密码体系 +https://blog.csdn.net/qq_29689487/article/details/81634057 + +* 对称加密:加密和解密使用的密钥是同一个 + * 优点:计算量小,算法速度快,加密效率高 缺点:密钥容易泄漏。不同的会话需要不同的密钥,管理起来很费劲 + * 常用算法:DES,3DES,IDEA,CR4,CR5,CR6,AES +* 非对称加密:需要公钥和私钥,公钥用来加密,私钥用来解密 + * 优点:安全,不怕泄漏 缺点:速度慢 + + * 常用算法:RSA,ECC,DSA + + + + Hash算法(摘要算法) + + Hash算法特别的地方在于它是一种单向算法,用户可以通过hash算法对目标信息生成一段特定长度的唯一hash值,却不能通过这个hash值重新获得目标信息。因此Hash算法常用在不可还原的密码存储、信息完整性校验等。 + + 常见的摘要算法有: MD2、MD4、MD5、HAVAL、SHA + +#### (30) 数字证书的了解(高频) +![fig/数字证书.jpg](fig/数字证书.jpg) + +1)权威CA使用私钥将网站A的信息和消息摘要(签名S)进行加密打包形成数字证书。发送给客户端。 + +2)网站A将自己的信息和数字证书发给客户端,客户端用CA的公钥对数字证书进行解密,得到签名S,与手动将网站的信息进行消息摘要得到的结果S\*进行对比,如果签名一致就证明网站A可以信任。 + +#### (31) 服务器出现大量close_wait的连接的原因以及解决方法 +close_wait状态是在TCP四次挥手的时候收到FIN但是没有发送自己的FIN时出现的,服务器出现大量close_wait状态的原因有两种: +* 服务器内部业务处理占用了过多时间,都没能处理完业务;或者还有数据需要发送;或者服务器的业务逻辑有问题,没有执行close()方法 +* 服务器的父进程派生出子进程,子进程继承了socket,收到FIN的时候子进程处理但父进程没有处理该信号,导致socket的引用不为0无法回收 + +处理方法: +* 停止应用程序 +* 修改程序里的bug +#### (32) 消息摘要算法列举一下,介绍MD5算法,为什么MD5是不可逆的,有什么办法可以加强消息摘要算法的安全性让它不那么容易被破解呢?(百度安全一面) +* 消息摘要算法有MD家族(MD2,MD4,MD5),SHA家族(SHA-1,SHA-256)和CRC家族(CRC8,CRC16,CRC32)等等 + +* MD5算法介绍: +MD5以512位分组来处理输入的信息,且每一分组又被划分为若干个小分组(16个32位子分组),经过一些列的处理后,算法输出由四个散列值(32位分组组成的128位散列值。) + +1. MD5首先将输入的信息分成若干个512字节长度的分组,如果不够就填充1和若干个0。 +2. 对每个512字节的分组进行循环运算。使用四个幻数对第一个分组的数据进行四轮变换,得到四个变量。 +3. 接下来对其中三个使用线性函数进行计算,与剩下一个相加,并赋值给其中某个变量,得到新的四个变量,重复16次这个过程,得到的四个变量作为幻数,与下一个分组进行相似的计算。 +4. 遍历所有分组后得到的四个变量即为结果。 + +详见:https://blog.csdn.net/weixin_39640298/article/details/84555814 + +* 为什么不可逆:因为MD5在进行消息摘要的过程中,数据与原始数据相比发生了丢失,所以不能由结果进行恢复。 + +* 加强安全性:加盐(加随机数) +#### (33) 单条记录高并发访问的优化 +服务器端: +* 使用缓存,如redis等 +* 使用分布式架构进行处理 +* 将静态页面和静态资源存储在静态资源服务器,需要处理的数据使用服务器进行计算后返回 +* 将静态资源尽可能在客户端进行缓存 +* 采用ngnix进行负载均衡 (nginx读作恩静埃克斯 = Engine X) + +数据库端: +* 数据库采用主从赋值,读写分离措施 +* 建立适当的索引 +* 分库分表 +#### (34) 介绍一下ping的过程,分别用到了哪些协议 +详见:[Ping原理与ICMP协议](https://www.cnblogs.com/Akagi201/archive/2012/03/26/2418475.html) + +ping是使用ICMP协议来进行工作的。 ICMP:网络控制报文协议 + +* 首先,ping命令会构建一个ICMP请求数据包,然后由ICMP协议将这个数据包连同目的IP地址源IP地址一起交给IP协议。 +* 然后IP协议就会构建一个IP数据报,并且在映射表中查找目的IP对应的mac地址,将其交给数据链路层。 +* 然后数据链路层就会构建一个数据帧,附上源mac地址和目的mac地址发送出去。 + +目的主机接收到数据帧后,就会检查包上的mac地址与本机mac是否相符,如果相符,就接收并把其中的信息提取出来交给IP协议,IP协议就会将其中的信息提取出来交给ICMP协议。然后构建一个ICMP应答包,用相同的过程发送回去。 +#### (35) TCP/IP的粘包与避免介绍一下 +因为TCP为了减少额外开销,采取的是流式传输,所以接收端在一次接收的时候有可能一次接收多个包。而TCP粘包就是发送方的若干个数据包到达接收方的时候粘成了一个包。多个包首尾相接,无法区分。 + +导致TCP粘包的原因有三方面: +* 发送端等待缓冲区满才进行发送,造成粘包 +* 接收方来不及接收缓冲区内的数据,造成粘包 +* 由于TCP协议在发送较小的数据包的时候,会将几个包合成一个包后发送 + +避免粘包的措施: +* 通过编程,强制使TCP发生数据传送,不必等到缓冲区满 +* 优化接收方接收数据的过程,使其来得及接收数据包,包括提高接收进程优先级等 +* 设置固定长度的报文或者设置报文头部指示报文的长度。 + +#### (36) 说一下TCP的封包和拆包 +因为TCP是无边界的流传输,所以需要对TCP进行封包和拆包,确保发送和接收的数据不粘连。 +* 封包:封包就是在发送数据报的时候为每个TCP数据包加上一个包头,将数据报分为包头和包体两个部分。包头是一个固定长度的结构体,里面包含该数据包的总长度。 +* 拆包:接收方在接收到报文后提取包头中的长度信息进行截取。 +#### (37) 一个ip配置多个域名,靠什么识别? +* 靠host主机名区分 +* 靠端口号区分 +#### (38) 服务器攻击(DDos攻击) +#### (39)DNS的工作过程和原理 +![](fig/DNS查询图解.png) +DNS解析有两种方式:递归查询和迭代查询 +* 递归查询 用户先向本地域名服务器查询,如果本地域名服务器的缓存没有IP地址映射记录,就向根域名服务器查询,根域名服务器就会向顶级域名服务器查询,顶级域名服务器向权限域名服务器查询,查到结果后依次返回。 +* 迭代查询 用户向本地域名服务器查询,如果没有缓存,本地域名服务器会向根域名服务器查询,根域名服务器返回顶级域名服务器的地址,本地域名服务器再向顶级域名服务器查询,得到权限域名服务器的地址,本地域名服务器再向权限域名服务器查询得到结果 +#### (41)OSA七层协议和五层协议,分别有哪些 +OSI七层协议模型主要是:应用层(Application)、表示层(Presentation)、会话层(Session)、传输层(Transport)、网络层(Network)、数据链路层(Data Link)、物理层(Physical)。 + +五层体系结构包括:应用层、传输层、网络层、数据链路层和物理层。 + +![(fig/网络协议层.png](fig/网络协议层.png) + +#### (42)IP寻址和MAC寻址有什么不同,怎么实现的 +通过MAC地址寻找主机是MAC地址寻址,通过IP地址寻找主机叫IP地址寻址。它们适用于不同的协议层,IP寻址是网络层,Mac寻址是数据链路层。 + +http://c.biancheng.net/view/6388.html + +https://blog.csdn.net/wxy_nick/article/details/9190693 + +IP寻址的过程(ARP协议):主机A想通过IP地址寻找到目标主机,首先分析IP地址确定目标主机与自己是否为同一网段。如果是则查看ARP缓存,或者使用ARP协议发送广播。如果不是,则寻找网关发送ARP数据包 + +#### (43)cookie和session + +1,session 在服务器端,cookie 在客户端(浏览器) + 2,session 默认被存在在服务器的一个文件里(不是内存) + 3,session 的运行依赖 session id,而 session id 是存在 cookie 中的,也就是说,如果浏览器禁用了 cookie ,同时 session 也会失效(但是可以通过其它方式实现,比如在 url 中传递 session_id) + 4,session 可以放在 文件、数据库、或内存中都可以。 + 5,用户验证这种场合一般会用 session + + + + + +# 3. 数据库 +#### (1) 关系型和非关系型数据库的区别(低频) +* 关系型数据库的优点 + 1. 容易理解。因为它采用了关系模型来组织数据。 + 2. 可以保持数据的一致性。 + 3. 数据更新的开销比较小。 + 4. 支持复杂查询(带where子句的查询) +* 非关系型数据库的优点 + 1. 不需要经过sql层的解析,读写效率高。 + 2. 基于键值对,数据的扩展性很好。 + 3. 可以支持多种类型数据的存储,如图片,文档等等。 +#### (2) 什么是非关系型数据库(低频) +非关系型数据库也叫nosql,采用键值对的形式进行存储。它的读写性能很高,易于扩展。例如Redis,Mongodb,hbase等等。 + +适合使用非关系型数据库的场景: +* 日志系统 +* 地理位置存储 +* 数据量巨大 +* 高可用 +#### (3) 说一下 MySQL 执行一条查询语句的内部执行过程? +* 连接器:客户端先通过连接器连接到 MySQL 服务器。 +* 缓存:连接器权限验证通过之后,先查询是否有查询缓存,如果有缓存(之前执行过此语句)则直接返回缓存数据,如果没有缓存则进入分析器。 +* 分析器:分析器会对查询语句进行语法分析和词法分析,判断 SQL 语法是否正确,如果查询语法错误会直接返回给客户端错误信息,如果语法正确则进入优化器。 +* 优化器:优化器是对查询语句进行优化处理,例如一个表里面有多个索引,优化器会判别哪个索引性能更好。 +* 执行器:优化器执行完就进入执行器,执行器就开始执行语句进行查询比对了,直到查询到满足条件的所有数据,然后进行返回。 +#### (4) 数据库的索引类型 +数据库的索引类型分为逻辑分类和物理分类
+逻辑分类: +* 主键索引 当关系表中定义主键时会自动创建主键索引。每张表中的主键索引只能有一个,要求主键中的每个值都唯一,即不可重复,也不能有空值。 +* 唯一索引 数据列不能有重复,可以有空值。一张表可以有多个唯一索引,但是每个唯一索引只能有一列。如身份证,卡号等。 +* 普通索引 一张表可以有多个普通索引,可以重复可以为空值 +* 全文索引 可以加快模糊查询,不常用 + +物理分类: +* 聚集索引(聚簇索引) 数据在物理存储中的顺序跟索引中数据的逻辑顺序相同,比如以ID建立聚集索引,数据库中id从小到大排列,那么物理存储中该数据的内存地址值也按照从小到大存储。**一般是表中的主键索引**,如果没有主键索引就会以第一个非空的唯一索引作为聚集索引。**一张表只能有一个聚集索引**。 +* 非聚集索引 数据在物理存储中的顺序跟索引中数据的逻辑顺序不同。非聚集索引因为无法定位数据所在的行,所以需要扫描两遍索引树。第一遍扫描非聚集索引的索引树,确定该数据的主键ID,然后到主键索引(聚集索引)中寻找相应的数据。 +#### (5) 说一下事务是怎么实现的 +https://blog.csdn.net/u013256816/article/details/103966510 + +https://www.cnblogs.com/takumicx/p/9998844.html + +事务就是一组逻辑操作的集合。实现事务就是要保证可靠性和并发隔离,或者说,能够满足ACID特性的机制。而这些主要是靠日志恢复和并发控制实现的。 + +* 日志恢复:数据库里有两个日志,一个是redo log,一个是undo log。redo log记录的是已经成功提交的事务操作信息,用来恢复数据,保证事务的**持久性**。undo log记录的是事务修改之前的数据信息,用来回滚数据,保证事务的**原子性**。 +* 并发控制:并发控制主要靠读写锁和MVCC(多版本并发控制)来实现。读写锁包括共享锁和排他锁,保证事务的**隔离性**。MVCC通过为数据添加时间戳来实现。 + +#### (6) MySQL怎么建立索引,怎么建立主键索引,怎么删除索引? +MySQL建立索引有两种方式:用alter table或者create index。 +``` +alter table table_name add primary key(column_list) #添加一个主键索引 +alter table table_name add index (column_list) #添加一个普通索引 +alter table table_name add unique (column_list) #添加一个唯一索引 +``` +``` +create index index_name on table_name (column_list) #创建一个普通索引 +create unique index_name on table_name (column_list) #创建一个唯一索引 +``` + +Mysql删除索引同样也有两种方式:alter table 和 drop index +``` +alter table table_name drop index index_name #删除一个普通索引 +alter table table_name drop primary key #删除一个主键索引 +``` +``` +drop index index_name on table table_name +``` +#### (7) 索引的优缺点,什么时候使用索引,什么时候不能使用索引(重点) +https://www.cnblogs.com/wezheng/p/8399305.html +* 经常搜索的列上建索引 +* 作为主键的列上要建索引 +* 经常需要连接(where子句)的列上 +* 经常需要排序的列 +* 经常需要范围查找的列 + +哪些列不适合建索引? +* 很少查询的列 +* 更新很频繁的列 +* 数据值的取值比较少的列(比如性别) +#### (8) 索引的底层实现(重点) +数据库的索引是使用B+树来实现的。 + +(为什么要用B+树,为什么不用红黑树和B树)
+B+树是一种特殊的平衡多路树,是B树的优化改进版本,它把所有的数据都存放在叶节点上,中间节点保存的是索引。这样一来相对于B树来说, + +1) 减少了数据对中间节点的空间占用,使得中间节点可以存放更多的指针,使得树变得更矮,深度更小,从而减少查询的磁盘IO次数,提高查询效率。 + +2) 另一个是由于叶节点之间有指针连接,所以可以进行范围查询,方便区间访问。 + +3) 而红黑树是二叉的,它的深度相对B+树来说更大,更大的深度意味着查找次数更多,更频繁的磁盘IO,所以红黑树更适合在内存中进行查找。 + +#### (9) B树和B+树的区别(重点) +![./fig/Bptree.png](./fig/Bptree.png) + +这都是由于B+树和B具有不同的存储结构所造成的区别,以一个m阶树为例。 +1. 关键字的数量不同;B+树中分支结点有m个关键字,其叶子结点也有m个,其关键字只是起到了一个索引的作用,但是B树虽然也有m个子结点,但是其只拥有m-1个关键字。 +2. 存储的位置不同;B+树中的数据都存储在叶子结点上,也就是其所有叶子结点的数据组合起来就是完整的数据,但是B树的数据存储在每一个结点中,并不仅仅存储在叶子结点上。 +3. 分支结点的构造不同;B+树的分支结点仅仅存储着关键字信息和儿子的指针(这里的指针指的是磁盘块的偏移量),也就是说内部结点仅仅包含着索引信息。 +4. 查询不同;B树在找到具体的数值以后,则结束,而B+树则需要通过索引找到叶子结点中的数据才结束,也就是说B+树的搜索过程中走了一条从根结点到叶子结点的路径。 + +B+树优点:由于B+树的数据都存储在叶子结点中,分支结点均为索引,方便扫库,只需要扫一遍叶子结点即可,但是B树因为其分支结点同样存储着数据,我们要找到具体的数据,需要进行一次中序遍历按序来扫,所以B+树更加适合在区间查询的情况,所以通常B+树用于数据库索引,而B树则常用于文件索引。 +#### (10) 索引最左前缀/最左匹配 +假如我们对a b c三个字段建立了联合索引,**在联合索引中,从最左边的字段开始,任何连续的索引都能匹配上,当遇到范围查询的时候停止**。比如**对于联合索引index(a,b,c),能匹配a,ab,abc三组索引。并且对查询时字段的顺序没有限制,也就是a,b,c; b,a,c; c,a,b; c,b,a都可以匹配。** +#### (11) Mysql的优化(高频,索引优化,性能优化) +高频访问: +* 分表分库:将数据库表进行水平拆分,减少表的长度 +* 增加缓存: 在web和DB之间加上一层缓存层 +* 增加数据库的索引:在合适的字段加上索引,解决高频访问的问题 + + +并发优化: +* 主从读写分离:只在主服务器上写,从服务器上读 +* 负载均衡集群:通过集群或者分布式的方式解决并发压力 +#### (12) MYSQL数据库引擎介绍,innodb和myisam的特点与区别 +* InnoDB : InnoDB是mysql的默认引擎,支持事务和外键,支持容灾恢复。适合更新频繁和多并发的表 行级锁 + +* MyISAM : 插入和查询速度比较高,支持大文件,但是不支持事务,适合在web和数据仓库场景下使用 表级锁. + + 读取速度优越,常用于高读取的应用场景数据库,支持三种不同类型的存储结构:静态型、动态型、压缩型。 不支持事物和外键。 + +* MEMORY : memory将表中的数据保存在内存里,适合数据比较小而且频繁访问的场景 + +* **InnoDB**: 提供ACID事务,系统崩溃修复能力和多版本并发控制的行级锁,支持自增长列(auto_increment),支持外键(foregin key),mysql 5.5之后默认数据库引擎。 + +* **MyISAM**: 读取速度优越,常用于高读取的应用场景数据库,支持三种不同类型的存储结构:静态型、动态型、压缩型。 不支持事物和外键。 + +* **CSV**: 使用该引擎的MySQL数据库表会在MySQL安装目录data文件夹中的和该表所在数据库名相同的目录中生成一个.CSV文件,这种文件是一种普通文本文件,把数据以逗号分隔的格式存储在文本文件中,每个数据行占用一个文本行。该种类型的存储引擎不支持索引,即使用该种类型的表没有主键列;另外也不允许表中的字段为null。 + +* **MEMORY**: 该存储引擎通过在内存中创建临时表来存储数据。每个基于该存储引擎的表实际对应一个磁盘文件,该文件的文件名和表名是相同的,类型为.frm。该磁盘文件只存储表的结构,数据在内存中,所以其数据具有不稳定性,比如如果mysqld进程发生异常、重启或计算机关机等等都会造成这些数据的消失,但是表结构还是存在磁盘上的。而因为其数据存储在内存中,所以使用该种引擎的表拥有极高的插入、更新和查询效率。这种存储引擎默认使用哈希(HASH)索引,其速度比使用B-+Tree型要快,但也可以使用B树型索引。 + +* **BLACKHOLE**: 看着名字就知道了,黑洞引擎,该引擎接受但不存储数据,并且检索总是返回一个空集。支持事务,而且支持mvcc的行级锁,主要用于做日志记录或同步归档的中继存储,一般不用。 + +* **MRG_MYISAM**: 该引擎将一定数量的MyISAM表联合而成一个整体。 + +* **ARCHIVE**: 该引擎提供了压缩功能,拥有高效的插入速度,但是这种引擎不支持索引,所以查询性能较差一些。非常适合存储大量独立的、作为历史记录的数据。 + +* **PERFORMANCE_SCHEMA**: 该引擎主要用于收集数据库服务器性能参数。这种引擎提供以下功能:提供进程等待的详细信息,包括锁、互斥变量、文件信息;保存历史的事件汇总信息,为提供MySQL服务器性能做出详细的判断;对于新增和删除监控事件点都非常容易,并可以随意改变mysql服务器的监控周期,例如(CYCLE、MICROSECOND)。 + +* **FEDERATED**: 该存储引擎可以将不同的Mysql服务器联合起来(远程连接),逻辑上组成一个完整的数据库。这种存储引擎非常适合数据库分布式应用。 + +#### (13) 数据库中事务的ACID(四大特性都要能够举例说明,理解透彻,比如原子性和一致性的关联,隔离性不好会出现的问题) +数据库事务是指逻辑上对数据的一种操作,这个事务要么全部成功,要么全部失败。 + +**A: atom 原子性**
+数据库事务的原子性是指:事务是一个不可分割的工作单位,这组操作要么全部发生,要么全部不发生。 + +**C: consistency 一致性**
+数据库事务的一致性是指:在事务开始以前,数据库中的数据有一个一致的状态。在事务完成后,数据库中的事务也应该保持这种一致性。事务应该将数据从一个一致性状态转移到另一个一致性状态。 +比如在银行转账操作后两个账户的总额应当不变。 + +**I: isolation 隔离性**
+数据库事务的隔离性要求数据库中的事务不会受另一个并发执行的事务的影响,对于数据库中同时执行的每个事务来说,其他事务要么还没开始执行,要么已经执行结束,它都感觉不到还有别的事务正在执行。 + +**D:durability 持久性**
+数据库事务的持久性要求事务对数据库的改变是永久的,哪怕数据库发生损坏都不会影响到已发生的事务。 +如果事务没有完成,数据库因故断电了,那么重启后也应该是没有执行事务的状态,如果事务已经完成后数据库断电了,那么重启后就应该是事务执行完成后的状态。 +#### (14)什么是脏读,不可重复读和幻读? +详见[数据库的事务隔离级别总结](https://blog.csdn.net/fuzhongmin05/article/details/91126936) +* 脏读:脏读是指一个事务在处理过程中读取了另一个还没提交的事务的数据。 + + > 比如A向B转账100,A的账户减少了100,而B的账户还没来得及修改,此时一个并发的事务访问到了B的账户,就是脏读 +* 不可重复读:不可重复读是对于数据库中的某一个字段,一个事务多次查询却返回了不同的值,这是由于在查询的间隔中,该字段被另一个事务修改并提交了。 + > 比如A第一次查询自己的账户有1000元,此时另一个事务给A的账户增加了1000元,所以A再次读取他的账户得到了2000的结果,跟第一次读取的不一样。 + > 不可重复读与脏读的不同之处在于,脏读是读取了另一个事务没有提交的脏数据,不可重复读是读取了已经提交的数据,实际上并不是一个异常现象。 +* 幻读:事务多次读取同一个范围的时候,查询结果的记录数不一样,这是由于在查询的间隔中,另一个事务新增或删除了数据。 + > 比如A公司一共有100个人,第一次查询总人数得到100条记录,此时另一个事务新增了一个人,所以下一次查询得到101条记录。 + > 不可重复度和幻读的不同之处在于,幻读是多次读取的结果行数不同,不可重复度是读取结果的值不同。 + +避免不可重复读需要锁行,避免幻读则需要锁表。 + +脏读,不可重复读和幻读都是数据库的读一致性问题,是在并行的过程中出现的问题,必须采用一定的隔离级别解决。 +详见[脏读、不可重复读和幻读的区别](https://www.cnblogs.com/Hakuna-Matata/p/7772794.html) + +#### (15) 数据库的隔离级别,mysql和Oracle的隔离级别分别是什么(重点) +详见[数据库的事务隔离级别总结](https://blog.csdn.net/fuzhongmin05/article/details/91126936)和[数据库隔离级别](https://blog.csdn.net/fg2006/article/details/6937413) + +为了保证数据库事务一致性,解决脏读,不可重复读和幻读的问题,数据库的隔离级别一共有四种隔离级别: +* 读未提交 Read Uncommitted: 最低级别的隔离,不能解决以上问题 +* 读已提交 Read committed: 可以避免脏读的发生 +* 可重复读 Reapeatable read: 确保事务可以多次从一个字段中读取相同的值,在该事务执行期间,禁止其他事务对此字段的更新,可以避免脏读和不可重复读。 通过锁行来实现 +* 串行化 Serializaion 最严格的事务隔离机制,要求所有事务被串行执行,可以避免以上所有问题。 通过锁表来实现 + +Oracle的默认隔离级别是**读已提交**,实现了四种隔离级别中的读已提交和串行化隔离级别 + +MySQL的默认隔离级别是**可重复读**,并且实现了所有四种隔离级别 +#### (16) 数据库连接池的作用 +#### (17) Mysql的表空间方式,各自特点 +* 共享表空间:指的是数据库的所有的表数据,索引文件全部放在一个文件中,默认这个共享表空间的文件路径在 data 目录下。 +* 独立表空间:每一个表都将会生成以独立的文件方式来进行存储。 优点:当表被删除时这部分空间可以被回收;可以更快的恢复和备份单个表;将单个表复制到另一个实例会很方便; 缺点:mysqld会维持很多文件句柄,表太多会影响性能。如果很多表都增长会导致碎片问题 +#### (18) 分布式事务 +#### (19) 数据库的范式 +https://www.cnblogs.com/linjiqin/archive/2012/04/01/2428695.html + +* **第一范式(确保每列保持原子性)**
+第一范式是最基本的范式。如果**数据库表中的所有字段值都是不可分解的原子值**,就说明该数据库表满足了第一范式。 + +>比如 学生 选课(包括很多课程) 就不符合第一范式,“地址”这个属性重新拆分为省份、城市、详细地址等多个部分进行存储 +* **第二范式(确保表中的每列都和主键相关)**
+在满足第一范式的前提下,(主要针对联合主键而言)第二范式需要确保数据库表中的每一列都和主键的所有成员直接相关,由整个主键才能唯一确定,而不能只与主键的某一部分相关或者不相关。 + +>比如一张学生信息表,由主键(学号)可以唯一确定一个学生的姓名,班级,年龄等信息。但是主键 (学号,班级) 与列 姓名,班主任,教室 就不符合第二范式,因为班主任跟部分主键(班级)是依赖关系 +* **第三范式(确保非主键的列没有传递依赖)**
+在满足第二范式的前提下,第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。非主键的列不能确定其他列,列与列之间不能出现传递依赖。 + +>比如一张学生信息表,主键是(学号)列包括 姓名,班级,班主任 就不符合第三范式,因为非主键的列中 班主任 依赖于 班级 +* **BCNF范式(确保主键之间没有传递依赖)**
+主键有可能是由多个属性组合成的复合主键,那么多个主键之间不能有传递依赖。也就是复合主键之间谁也不能决定谁,相互之间没有关系。 +#### (20) 数据的锁的种类,加锁的方式 +以MYSQL为例, +* 按照类型来分有乐观锁和悲观锁 + +* 根据粒度来分有行级锁,页级锁,表级锁(粒度一个比一个大) (仅BDB,Berkeley Database支持页级锁) + +* 根据作用来分有共享锁(读锁)和排他锁(写锁)。 + +* **表级锁**: 每次操作锁住整张表。开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低; + + **行级锁**: 每次操作锁住一行数据。开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高; +#### (21) 什么是共享锁和排他锁 +* 共享锁是读操作的时候创建的锁,一个事务对数据加上共享锁之后,其他事务只能对数据再加共享锁,不能进行写操作直到释放所有共享锁。 +* 排他锁是写操作时创建的锁,事务对数据加上排他锁之后其他任何事务都不能对数据加任何的锁(即其他事务不能再访问该数据) + + https://blog.csdn.net/qq_42743933/article/details/81236658 +#### (22) 分库分表的理解和简介(CSDN收藏夹) +#### (23) 缓存异常 + +**1)缓存雪崩** +缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。 + +解决方案 + +1) 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。 +2) 一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。 +3) 给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。 + +**2)缓存穿透** +缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。 + +解决方案 + +1) 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截; + +2) 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击。 + +3) 采用布隆过滤器,将所有可能存在的数据哈希值放到一个足够大的 bitmap(位图) 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力。 + +附加 + +对于空间的利用到达了一种极致,那就是Bitmap和布隆过滤器(Bloom Filter)。 +Bitmap: 典型的就是哈希表 +缺点是,Bitmap对于每个元素只能记录1bit信息,如果还想完成额外的功能,恐怕只能靠牺牲更多的空间、时间来完成了。 + +布隆过滤器(推荐) + +就是引入了k(k>1)k(k>1)个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过程。 +它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。 +Bloom-Filter算法的核心思想就是利用多个不同的Hash函数来解决“冲突”。 +Hash存在一个冲突(碰撞)的问题,用同一个Hash得到的两个URL的值有可能相同。为了减少冲突,我们可以多引入几个Hash,如果通过其中的一个Hash值我们得出某元素不在集合中,那么该元素肯定不在集合中。只有在所有的Hash函数告诉我们该元素在集合中时,才能确定该元素存在于集合中。这便是Bloom-Filter的基本思想。 +Bloom-Filter一般用于在大数据量的集合中判定某元素是否存在。 + +**3) 缓存击穿** +1) 缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。 + +和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。 + +解决方案 + +1) 设置热点数据永远不过期。 +2) 加互斥锁,互斥锁 + + + +#### (24)数据库高并发的解决方案 +1. 在web服务框架中加入缓存。在服务器与数据库层之间加入缓存层,将高频访问的数据存入缓存中,减少数据库的读取负担。 +2. 增加数据库索引。提高查询速度。(不过索引太多会导致速度变慢,并且数据库的写入会导致索引的更新,也会导致速度变慢) +3. 主从读写分离,让主服务器负责写,从服务器负责读。 +4. 将数据库进行拆分,使得数据库的表尽可能小,提高查询的速度。 +5. 使用分布式架构,分散计算压力。 +#### (25)什么是乐观锁与悲观锁 +一般的数据库都会支持并发操作,在并发操作中为了避免数据冲突,所以需要对数据上锁,乐观锁和悲观锁就是两种不同的上锁方式。 + +1) 悲观锁假设数据在并发操作中一定会发生冲突,所以在数据开始读取的时候就把数据锁住。 + +2) 乐观锁则假设数据一般情况下不会发生冲突,所以在数据提交更新的时候,才会检测数据是否有冲突。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚。 + +#### (26)乐观锁与悲观锁是怎么实现的 +悲观锁有行级锁和页级锁两种形式。行级锁对正在使用的单条数据进行锁定,事务完成后释放该行数据,而页级锁则对整张表进行锁定,事务正在对该表进行访问的时候不允许其他事务并行访问。 + +悲观锁要求在整个过程中一直与数据库有一条连接,因为上一个事务完成后才能让下一个事务执行,这个过程是串行的。 + +乐观锁有三种常用的实现形式: +* 一种是在执行事务时把整个数据都拷贝到应用中,在数据更新提交的时候比较数据库中的数据与新数据,如果两个数据一摸一样则表示没有冲突可以直接提交,如果有冲突就要交给业务逻辑去解决。 +* 一种是使用**版本戳**来对数据进行标记,数据每发生一次修改,版本号就增加1。某条数据在提交的时候,如果数据库中的版本号与自己的一致,就说明数据没有发生修改,否则就认为是过期数据需要处理。 +* 最后一种采用**时间戳**对数据最后修改的时间进行标记。与上一种类似。 + +#### (27)对数据库目前最新技术有什么了解吗 + +#### (28)解释一下MVCC + + + + +# 4. Linux +#### (1) Linux的I/O模型介绍以及同步异步阻塞非阻塞的区别(超级重要) +https://blog.csdn.net/sqsltr/article/details/92762279 + +https://www.cnblogs.com/euphie/p/6376508.html + + +(IO过程包括两个阶段:(1)内核从IO设备读写数据和(2)进程从内核复制数据) + +**同步**表示A向B请求调用一个网络IO接口时(或者调用某个业务逻辑API接口时),数据的读写都是 由请求方A自己来完成的(不管是阻塞还是非阻塞); + +**异步**表示A向B请求调用一个网络IO接口时 (或者调用某个业务逻辑API接口时),向B传入请求的事件以及事件发生时通知的方式,A就可以 处理其它逻辑了,当B监听到事件处理完成后,会用事先约定好的通知方式,通知A处理结果。 + +* 阻塞:调用IO操作的时候,如果缓冲区空或者满了,调用的进程或者线程就会处于阻塞状态直到IO可用并完成数据拷贝。 +* 非阻塞:调用IO操作的时候,内核会马上返回结果,如果IO不可用,会返回错误,这种方式下进程需要不断轮询直到IO可用为止,但是当进程从内核拷贝数据时是阻塞的。 +* IO多路复用是一种同步IO模型,实现一个线程同时监听多个文件描述符,一旦某个描述符IO就绪(读就绪或者写就绪),就能够通知进程进行相应的IO操作,否则就将进程阻塞在select或者epoll语句上。 + + +* 同步IO:同步IO模型包括阻塞IO,非阻塞IO和IO多路复用,信号驱动IO模型。特点就是当进程从内核复制数据的时候都是阻塞的。 +* 异步IO:在检测IO是否可用和进程拷贝数据的两个阶段都是不阻塞的,进程可以做其他事情,当IO完成后内核会给进程发送一个信号。 +#### (2) 文件系统的理解(EXT4,XFS,BTRFS) +#### (3) EPOLL的介绍和了解 +https://zhuanlan.zhihu.com/p/56486633 + +https://www.jianshu.com/p/397449cadc9a + +https://blog.csdn.net/davidsguo008/article/details/73556811 + + +Epoll是Linux进行IO多路复用的一种方式,用于在一个线程里监听多个IO源,在IO源可用的时候返回并进行操作。它的特点是基于事件驱动,性能很高。 + +epoll将文件描述符拷贝到内核空间后使用红黑树进行维护,同时向内核注册每个文件描述符的回调函数,当某个文件描述符可读可写的时候,将这个文件描述符加入到就绪链表里,并唤起进程,返回就绪链表到用户空间,由用户程序进行处理。 + +Epoll有三个系统调用:epoll_create(),epoll_ctl()和epoll_wait()。 + +* eoll_create()函数在内核中初始化一个eventpoll对象,同时初始化红黑树和就绪链表。 + +* epoll_ctl()用来对监听的文件描述符进行管理。将文件描述符插入红黑树,或者从红黑树中删除,这个过程的时间复杂度是log(N)。同时向内核注册文件描述符的回调函数。 + +* epoll_wait()会将进程放到eventpoll的等待队列中,将进程阻塞,当某个文件描述符IO可用时,内核通过回调函数将该文件描述符放到就绪链表里,epoll_wait()会将就绪链表里的文件描述符返回到用户空间。 +#### (4) IO复用的三种方法(select,poll,epoll)深入理解,包括三者区别,内部原理实现? +(1)select的方法介绍:select把所有监听的文件描述符拷贝到内核中,挂起进程。当某个文件描述符可读或可写的时候,中断程序唤起进程,select将监听的文件描述符再次拷贝到用户空间,然select后遍历这些文件描述符找到IO可用的文件。下次监控的时候需要再次拷贝这些文件描述符到内核空间。select支持监听的描述符最大数量是1024. +![select](fig/select.png) +(2)poll使用链表保存文件描述符,其他的跟select没有什么不同。 + +(3)epoll将文件描述符拷贝到内核空间后使用红黑树进行维护,同时向内核注册每个文件描述符的回调函数,当某个文件描述符可读可写的时候,将这个文件描述符加入到就绪链表里,并唤起进程,返回就绪链表到用户空间。 +![epoll](fig/epoll.png) +详见 https://www.cnblogs.com/Anker/p/3265058.html + +#### **(4.1) IO多路复用用来解决什么问题** + +​ 当多个客户端与服务器通信时,若服务器阻塞在其中一个客户的read(sockfd1,…),当另一个客户数据到达sockfd2时,服务器无法及时处理,此时需要用到IO多路复用。即同时监听n个客户,当其中有一个发来消息时就从select的阻塞中返回,然后调用read读取收到消息的sockfd,然后又循环回select阻塞。这样就解决了阻塞在一个消息而无法处理其它的。即用来解决对多个I/O监听时,一个I/O阻塞影响其他I/O的问题。 +———————————————— +版权声明:本文为CSDN博主「Snippers」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 +原文链接:https://blog.csdn.net/Snippers/article/details/113359006 + +#### (5) Epoll的ET模式和LT模式(ET的非阻塞) +* ET是边缘触发模式,在这种模式下,只有当描述符从未就绪变成就绪时,内核才会通过epoll进行通知。然后直到下一次变成就绪之前,不会再次重复通知。也就是说,如果一次就绪通知之后不对这个描述符进行IO操作导致它变成未就绪,内核也不会再次发送就绪通知。优点就是只通知一次,减少内核资源浪费,效率高。缺点就是不能保证数据的完整,有些数据来不及读可能就会无法取出。 + +* LT是水平触发模式,在这个模式下,如果文件描述符IO就绪,内核就会进行通知,如果不对它进行IO操作,只要还有未操作的数据,内核都会一直进行通知。优点就是可以确保数据可以完整输出。缺点就是由于内核会一直通知,会不停从内核空间切换到用户空间,资源浪费严重。 + + +- epoll有EPOLLLT和EPOLLET两种触发模式,LT是默认的模式,ET是“高速”模式。 +- LT模式下,只要这个fd还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作 +- ET模式下,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无论fd中是否还有数据可读。所以在ET模式下,read一个fd的时候一定要把它的buffer读完,或者遇到EAGAIN错误 + +#### (6) 查询进程占用CPU的命令(注意要了解到used,buf,代表意义) +详见:https://blog.csdn.net/qq_36357820/article/details/76606113 +1. top命令查看linux负载: +2. uptime查看linux负载 +3. w查看linux负载: +4. vmstat查看linux负载 +#### (7) linux的其他常见命令(kill,find,cp等等) +#### (8) shell脚本用法 +#### (9) 硬连接和软连接的区别 +#### (10) 文件权限怎么看(rwx) +#### (11) 文件的三种时间(mtime, atime,ctime),分别在什么时候会改变 +#### (12) Linux监控网络带宽的命令,查看特定进程的占用网络资源情况命令 +#### (13)Linux中线程的同步方式有哪些? +#### (14)怎么修改一个文件的权限 +chmod 777 (177 277 477 等,权限组合是 1 2 4,分别代表r x w ) +#### (15)查看文件内容常用命令 +详见: http://blog.sina.com.cn/s/blog_7b4ce6b101018l8l.html +1. cat 与 tac +``` +cat的功能是将文件从第一行开始连续的将内容输出在屏幕上。当文件大,行数比较多时,屏幕无法全部容下时,只能看到一部分内容。所以通常使用重定向的方式,输出满足指定格式的内容 + +cat语法:cat [-n] 文件名 (-n : 显示时,连行号一起输出) + +tac的功能是将文件从最后一行开始倒过来将内容数据输出到屏幕上。我们可以发现,tac实际上是cat反过来写。这个命令不常用。 + +tac语法:tac 文件名。 +``` +2. more和less(常用) +``` +more的功能是将文件从第一行开始,根据输出窗口的大小,适当的输出文件内容。当一页无法全部输出时,可以用“回车键”向下翻行,用“空格键”向下翻页。退出查看页面,请按“q”键。另外,more还可以配合管道符“|”(pipe)使用,例如:ls -al | more + +more的语法:more 文件名 + +Enter 向下n行,需要定义,默认为1行; + +Ctrl f 向下滚动一屏; + +空格键 向下滚动一屏; + +Ctrl b 返回上一屏; + += 输出当前行的行号; + +:f 输出文件名和当前行的行号; + +v 调用vi编辑器; + +! 命令 调用Shell,并执行命令; + +q 退出more + + +less的功能和more相似,但是使用more无法向前翻页,只能向后翻。 + +less可以使用【pageup】和【pagedown】键进行前翻页和后翻页,这样看起来更方便。 + +less的语法:less 文件名 +``` +3. head和tail +``` +head和tail通常使用在只需要读取文件的前几行或者后几行的情况下使用。head的功能是显示文件的前几行内容 + +head的语法:head [n number] 文件名 (number 显示行数) + +tail的功能恰好和head相反,只显示最后几行内容 + +tail的语法:tail [-n number] 文件名 +``` +4. nl +``` +nl的功能和cat -n一样,同样是从第一行输出全部内容,并且把行号显示出来 + +nl的语法:nl 文件名 +``` +5. vim + +这个用的太普遍了,主要是用于编辑。 + +#### (16)怎么找出含有关键字的前后4行 +#### (17)Linux的GDB调试 +#### (18)coredump是什么 怎么才能coredump +coredump是程序由于异常或者bug在运行时异常退出或者终止,在一定的条件下生成的一个叫做core的文件,这个core文件会记录程序在运行时的内存,寄存器状态,内存指针和函数堆栈信息等等。对这个文件进行分析可以定位到程序异常的时候对应的堆栈调用信息。 + +coredump产生的条件 +1. shell资源控制限制,使用 ulimit -c 命令查看shell执行程序时的资源 ,如果为0,则不会产生coredump。可以用ulimit -c unlimited设置为不限大小。 +2. 读写越界,包括:数组访问越界,指针指向错误的内存,字符串读写越界 +3. 使用了线程不安全的函数,读写未加锁保护 +4. 错误使用指针转换 +5. 堆栈溢出 +#### (19)tcpdump常用命令 +用简单的话来定义tcpdump,就是:dump the traffic on a network,根据使用者的定义对网络上的数据包进行截获的包分析工具。 tcpdump可以将网络中传送的数据包的“头”完全截获下来提供分析。它支持针对网络层、协议、主机、网络或端口的过滤,并提供and、or、not等逻辑语句来帮助你去掉无用的信息。 + +实用命令实例 + +将某端口收发的数据包保存到文件
+`sudo tcpdump -i any port 端口 -w 文件名.cap` + + +打印请求到屏幕
+`sudo tcpdump -i any port 端口 -Xnlps0` + +默认启动
+`tcpdump` +普通情况下,直接启动tcpdump将监视第一个网络接口上所有流过的数据包。 +监视指定网络接口的数据包
+`tcpdump -i eth1` +如果不指定网卡,默认tcpdump只会监视第一个网络接口,一般是eth0,下面的例子都没有指定网络接口。  +#### (20) crontab命令 +详见:https://www.cnblogs.com/peida/archive/2013/01/08/2850483.html + +corntab命令是用来指定用户计划任务的。用户将需要定时执行的任务写入crontab文件中,提交给crond进程定期执行。 + +* crontab命令用来对crontab文件进行管理 +``` +1.命令格式: +crontab [-u user] file +crontab [-u user] [ -e | -l | -r ] +2.命令功能: +通过crontab 命令,我们可以在固定的间隔时间执行指定的系统指令或 shell script脚本。时间间隔的单位可以是分钟、小时、日、月、周及以上的任意组合。这个命令非常设合周期性的日志分析或数据备份等工作。 +3.命令参数: +-u user:用来设定某个用户的crontab服务,例如,“-u ixdba”表示设定ixdba用户的crontab服务,此参数一般有root用户来运行。 +file:file是命令文件的名字,表示将file做为crontab的任务列表文件并载入crontab。如果在命令行中没有指定这个文件,crontab命令将接受标准输入(键盘)上键入的命令,并将它们载入crontab。 +-e:编辑某个用户的crontab文件内容。如果不指定用户,则表示编辑当前用户的crontab文件。 +-l:显示某个用户的crontab文件内容,如果不指定用户,则表示显示当前用户的crontab文件内容。 +-r:从/var/spool/cron目录中删除某个用户的crontab文件,如果不指定用户,则默认删除当前用户的crontab文件。 +-i:在删除用户的crontab文件时给确认提示。 +``` + +* crontab文件内容 + +crond是Linux下的周期性执行系统任务的守护进程,他会根据/etc下的crontab配置文件的内容执行。用户需要将计划任务写入crontab文件中才能执行。 + +用户所建立的crontab文件中,每一行都代表一项任务,每行的每个字段代表一项设置,它的格式共分为六个字段,前五段是时间设定段,第六段是要执行的命令段,格式如下: +``` +minute hour day month week command + +其中: +minute: 表示分钟,可以是从0到59之间的任何整数。 +hour:表示小时,可以是从0到23之间的任何整数。 +day:表示日期,可以是从1到31之间的任何整数。 +month:表示月份,可以是从1到12之间的任何整数。 +week:表示星期几,可以是从0到7之间的任何整数,这里的0或7代表星期日。 +command:要执行的命令,可以是系统命令,也可以是自己编写的脚本文件。 +在以上各个字段中,还可以使用以下特殊字符: +星号(*):代表所有可能的值,例如month字段如果是星号,则表示在满足其它字段的制约条件后每月都执行该命令操作。 +逗号(,):可以用逗号隔开的值指定一个列表范围,例如,“1,2,5,7,8,9” +中杠(-):可以用整数之间的中杠表示一个整数范围,例如“2-6”表示“2,3,4,5,6” +正斜线(/):可以用正斜线指定时间的间隔频率,例如“0-23/2”表示每两小时执行一次。同时正斜线可以和星号一起使用,例如*/10,如果用在minute字段,表示每十分钟执行一次。 +``` +#### (21) 查看后台进程 +* jobs + +查看当前控制台的后台进程 + +想要停止后台进程,使用jobs命令查看其进程号(比如为num),然后kill %num即可 + +* ps + +查看后台进程 + +* top + +查看所有进程和资源使用情况,类似Windows中的任务管理器 + +停止进程:界面是交互式的,在窗口输入k 之后输入PID,会提示输入停止进程模式 有SIGTERM和 SIGKILL 如果留空不输入,就是SIGTERM(优雅停止) + +退出top:输入q即可 + +#### 22)静态库动态库的区别 + + + +# 5. 操作系统 +#### (1) 进程与线程的区别和联系(重点) +* 区别 +1. 进程是对运行时程序的封装,是系统进行资源分配和调度的基本单元,而线程是进程的子任务,是CPU分配和调度的基本单元。 +2. 一个进程可以有多个线程,但是一个线程只能属于一个进程。 +3. 进程的创建需要系统分配内存和CPU,文件句柄等资源,销毁时也要进行相应的回收,所以进程的管理开销很大;但是线程的管理开销则很小。 +4. 进程之间不会相互影响;而一个线程崩溃会导致进程崩溃,从而影响同个进程里面的其他线程。 + +* 联系 进程与线程之间的关系:线程是存在进程的内部,一个进程中可以有多个线程,一个线程只能存在一个进程中。 +#### (2) Linux理论上最多可以创建多少个进程?一个进程可以创建多少线程,和什么有关 +答:32768. 因为进程的pid是用pid_t来表示的,pid_t的最大值是32768.所以理论上最多有32768个进程。 + +至于线程。进程最多可以创建的线程数是根据分配给调用栈的大小,以及操作系统(32位和64位不同)共同决定的。Linux32位下是300多个。 +#### (3) 冯诺依曼结构有哪几个模块?分别对应现代计算机的哪几个部分?(百度安全一面) +* 存储器:内存 +* 控制器:南桥北桥 +* 运算器:CPU +* 输入设备:键盘 +* 输出设备:显示器、网卡 +#### (4) 进程之间的通信方法有哪几种 (重点) +进程之间的通信方式主要有六种,包括**管道,信号量,消息队列,信号,共享内存,套接字**。 + +* 管道:管道是半双工的,双方需要通信的时候,需要建立两个管道。管道的实质是一个内核缓冲区,进程以先进先出的方式从缓冲区存取数据:管道一端的进程顺序地将进程数据写入缓冲区,另一端的进程则顺序地读取数据,该缓冲区可以看做一个循环队列,读和写的位置都是自动增加的,一个数据只能被读一次,读出以后再缓冲区都不复存在了。当缓冲区读空或者写满时,有一定的规则控制相应的读进程或写进程是否进入等待队列,当空的缓冲区有新数据写入或慢的缓冲区有数据读出时,就唤醒等待队列中的进程继续读写。管道是最容易实现的 +![fig/管道通信.png](fig/管道通信.png) + + 匿名管道pipe和命名管道除了建立,打开,删除的方式不同外,其余都是一样的。匿名管道只允许有亲缘关系的进程之间通信,也就是父子进程之间的通信,命名管道允许具有非亲缘关系的进程间通信。 + + 管道的底层实现 https://segmentfault.com/a/1190000009528245 + +* 信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。信号量只有等待和发送两种操作。等待(P(sv))就是将其值减一或者挂起进程,发送(V(sv))就是将其值加一或者将进程恢复运行。 + +* 信号:信号是Linux系统中用于进程之间通信或操作的一种机制,信号可以在任何时候发送给某一进程,而无须知道该进程的状态。如果该进程并未处于执行状态,则该信号就由内核保存起来,知道该进程恢复执行并传递给他为止。如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。 信号是开销最小的 + +* 共享内存:共享内存允许两个或多个进程共享一个给定的存储区,这一段存储区可以被两个或两个以上的进程映射至自身的地址空间中,就像由malloc()分配的内存一样使用。一个进程写入共享内存的信息,可以被其他使用这个共享内存的进程,通过一个简单的内存读取读出,从而实现了进程间的通信。共享内存的效率最高,缺点是没有提供同步机制,需要使用锁等其他机制进行同步。 + +* 消息队列:消息队列就是一个消息的链表,是一系列保存在内核中消息的列表。用户进程可以向消息队列添加消息,也可以向消息队列读取消息。 +消息队列与管道通信相比,其优势是对每个消息指定特定的消息类型,接收的时候不需要按照队列次序,而是可以根据自定义条件接收特定类型的消息。 +可以把消息看做一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向消息队列中按照一定的规则添加新消息,对消息队列有读权限的进程可以从消息队列中读取消息。 + +* 套接字:套接口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同设备及其间的进程通信。 +#### (5) 进程调度方法详细介绍 +https://blog.csdn.net/u011080472/article/details/51217754 + +https://blog.csdn.net/leex_brave/article/details/51638300 + +* 先来先服务 (FCFS first come first serve):按照作业到达任务队列的顺序调度 FCFS是非抢占式的,易于实现,效率不高,性能不好,有利于长作业(CPU繁忙性)而不利于短作业(I/O繁忙性)。 +* 短作业优先 (SHF short job first):每次从队列里选择预计时间最短的作业运行。SJF是非抢占式的,优先照顾短作业,具有很好的性能,降低平均等待时间,提高吞吐量。但是不利于长作业,长作业可能一直处于等待状态,出现饥饿现象;完全未考虑作业的优先紧迫程度,不能用于实时系统。 +* 最短剩余时间优先 该算法首先按照作业的服务时间挑选最短的作业运行,在该作业运行期间,一旦有新作业到达系统,并且该新作业的服务时间比当前运行作业的剩余服务时间短,则发生抢占;否则,当前作业继续运行。该算法确保一旦新的短作业或短进程进入系统,能够很快得到处理。 +* 高响应比优先调度算法(Highest Reponse Ratio First, HRRF)是非抢占式的,主要用于作业调度。基本思想:每次进行作业调度时,先计算后备作业队列中每个作业的响应比,挑选最高的作业投入系统运行。响应比 = (等待时间 + 服务时间) / 服务时间 = 等待时间 / 服务时间 + 1。因为每次都需要计算响应比,所以比较耗费系统资源。 +* 时间片轮转 用于分时系统的进程调度。基本思想:系统将CPU处理时间划分为若干个时间片(q),进程按照到达先后顺序排列。每次调度选择队首的进程,执行完1个时间片q后,计时器发出时钟中断请求,该进程移至队尾。以后每次调度都是如此。该算法能在给定的时间内响应所有用户的而请求,达到分时系统的目的。 +* 多级反馈队列(Multilevel Feedback Queue) +#### (6) 进程的执行过程是什么样的,执行一个进程需要做哪些工作? +进程的执行需要经过三大步骤:编译,链接和装入。 +* 编译:将源代码编译成若干模块 +* 链接:将编译后的模块和所需要的库函数进行链接。链接包括三种形式:静态链接,装入时动态链接(将编译后的模块在链接时一边链接一边装入),运行时动态链接(在执行时才把需要的模块进行链接) +* 装入:将模块装入内存运行 + +https://blog.csdn.net/qq_38623623/article/details/78306498 + +将进程装入内存时,通常使用分页技术,将内存分成固定大小的页,进程分为固定大小的块,加载时将进程的块装入页中,并使用页表记录。减少外部碎片。 + +通常操作系统还会使用虚拟内存的技术将磁盘作为内存的扩充。 +#### (6) 操作系统的内存管理说一下 +https://www.cnblogs.com/peterYong/p/6556619.html + +https://zhuanlan.zhihu.com/p/141602175 + +操作系统的内存管理包括物理内存管理和虚拟内存管理 +* 物理内存管理包括交换与覆盖,分页管理,分段管理和段页式管理等; +* 虚拟内存管理包括虚拟内存的概念,页面置换算法,页面分配策略等; + +(面试官这样问的时候,其实是希望你能讲讲虚拟内存) + +**虚拟内存**是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。 + +#### (7) 实现一个LRU算法 +用到两个数据结构:哈希+双向链表 +``` +unordered_map > > cache ;// 存放键,迭代器 +list> auxlist; // 存放 <键,值> +``` +```c++ +class LRUCache { + int cap; + list> l;// front:new back:old 存放值 新的放前面,因为前面的可以取得有效的迭代器 + map >::iterator > cache;// 存放键,迭代器 +public: + LRUCache(int capacity) { + cap=capacity; + } + + int get(int key) { + auto mapitera = cache.find(key); + if(mapitera==cache.end()){ + return -1; + }else{// found + list>::iterator listItera = mapitera->second; + int value = (*listItera).second; + + l.erase(listItera); + l.push_front({key,value}); + cache[key]=l.begin(); + + return value; + } + } + + void put(int key, int value) { + auto itera = cache.find(key); + if(itera!=cache.end()){// exist + list>::iterator listItera = itera->second; + + l.erase(listItera); + l.push_front({key,value}); + cache[key]=l.begin(); + + }else{// not exist + if(cache.size()>=cap){ + pair oldpair = l.back(); + l.pop_back(); + cache.erase(oldpair.first); + } + l.push_front({key,value}); + cache[key]=l.begin(); + } + } +}; + +/** + * Your LRUCache object will be instantiated and called as such: + * LRUCache* obj = new LRUCache(capacity); + * int param_1 = obj->get(key); + * obj->put(key,value); + */ +``` +#### (8) 死锁产生的必要条件(怎么检测死锁,解决死锁问题) +(1) 互斥:一个资源每次只能被一个进程使用。
+(2) 占有并请求:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
+(3) 不可剥夺:进程已获得的资源,在末使用完之前,不能强行剥夺。
+(4) 循环等待:若干进程之间形成一种头尾相接的循环等待资源关系。
+ +产生死锁的原因主要是:
+(1) 因为系统资源不足。
+(2) 进程运行推进的顺序不合适。
+(3) 资源分配不当等。
+#### (8) 死锁的恢复 +1. 重新启动:是最简单、最常用的死锁消除方法,但代价很大,因为在此之前所有进程已经完成的计算工作都将付之东流,不仅包括死锁的全部进程,也包括未参与死锁的全部进程。 +2. 终止进程(process termination):终止参与死锁的进程并回收它们所占资源。 + (1) 一次性全部终止;(2) 逐步终止(优先级,代价函数) +3. 剥夺资源(resource preemption):剥夺死锁进程所占有的全部或者部分资源。 + (1) 逐步剥夺:一次剥夺死锁进程所占有的一个或一组资源,如果死锁尚未解除再继续剥夺,直至死锁解除为止。 + (2) 一次剥夺:一次性地剥夺死锁进程所占有的全部资源。 +4. 进程回退(rollback):让参与死锁的进程回退到以前没有发生死锁的某个点处,并由此点开始继续执行,希望进程交叉执行时不再发生死锁。但是系统开销很大: + (1) 要实现“回退”,必须“记住”以前某一点处的现场,而现场随着进程推进而动态变化,需要花费大量时间和空间。 + (2) 一个回退的进程应当“挽回”它在回退点之间所造成的影响,如修改某一文件,给其它进程发送消息等,这些在实现时是难以做到的 +#### (8)什么是饥饿 +饥饿是由于资源分配策略不公引起的,当进程或线程无法访问它所需要的资源而不能继续执行时,就会发生饥饿现象。 +#### (9) 如果要你实现一个mutex互斥锁你要怎么实现? +https://blog.csdn.net/kid551/article/details/84338619 + +实现mutex最重要的就是实现它的lock()方法和unlock()方法。我们保存一个全局变量flag,flag=1表明该锁已经锁住,flag=0表明锁没有锁住。 +实现lock()时,使用一个while循环不断检测flag是否等于1,如果等于1就一直循环。然后将flag设置为1;unlock()方法就将flag置为0; +```C++ +static int flag=0; + +void lock(){ + while(TestAndSet(&flag,1)==1); + //flag=1; +} +void unlock(){ + flag=0; +} +``` +因为while有可能被重入,所以可以用TestandSet()方法。 +```C++ +int TestAndSet(int *ptr, int new) { + int old = *ptr; + *ptr = new; + return old; +} +``` +#### (10)线程之间的通信方式有哪些? 进程之间的同步方式又哪些? +线程之间通信: +* 使用全局变量 +* 使用信号机制 +* 使用事件 + +进程之间同步: +https://www.cnblogs.com/sonic4x/archive/2011/07/05/2098036.html + +* 信号量 +* 管程 +#### (13) 什么时候用多进程,什么时候用多线程 +https://blog.csdn.net/yu876876/article/details/82810178 + +* 频繁修改:需要频繁创建和销毁的优先使用**多线程** +* 计算量:需要大量计算的优先使用**多线程** 因为需要消耗大量CPU资源且切换频繁,所以多线程好一点 +* 相关性:任务间相关性比较强的用**多线程**,相关性比较弱的用多进程。因为线程之间的数据共享和同步比较简单。 +* 多分布:可能要扩展到多机分布的用**多进程**,多核分布的用**多线程**。 + +但是实际中更常见的是进程加线程的结合方式,并不是非此即彼的。 +#### (14) 文件读写使用的系统调用 +#### (15) 孤儿进程和僵尸进程分别是什么,怎么形成的? +https://www.cnblogs.com/Anker/p/3271773.html + +* 孤儿进程是父进程退出后它的子进程还在执行,这时候这些子进程就成为孤儿进程。孤儿进程会被init进程收养并完成状态收集。 +* 僵尸进程是指子进程完成并退出后父进程没有使用wait()或者waitpid()对它们进行状态收集,这些子进程的进程描述符仍然会留在系统中。这些子进程就成为僵尸进程。 +#### (16) 说一下PCB/说一下进程地址空间/ +https://blog.csdn.net/qq_38499859/article/details/80057427 + +PCB就是进程控制块,是操作系统中的一种数据结构,用于表示进程状态,操作系统通过PCB对进程进行管理。 + +PCB中包含有:进程标识符,处理器状态,进程调度信息,进程控制信息。 + +![](https://img-blog.csdn.net/20140904215636015?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhhbmd6aGVianV0/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) + +进程地址空间内有: +* 代码段text:存放程序的二进制代码 +* 初始化的数据Data:已经初始化的变量和数据 +* 未初始化的数据BSS:还没有初始化的数据 +* 栈 +* 堆 +#### (17) 内核空间和用户空间是怎样区分的 +在Linux中虚拟地址空间范围为0到4G,最高的1G地址(0xC0000000到0xFFFFFFFF)供内核使用,称为内核空间,低的3G空间(0x00000000到0xBFFFFFFF)供各个进程使用,就是用户空间。 + +内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。 +#### (18) 多线程是如何同步的(尤其是如果项目中用到了多线程,很大可能会结合讨论) +https://blog.csdn.net/s_lisheng/article/details/74278765 + +* 临界区 +* 信号量 +* 事件 +* 互斥量 +#### (19) 同一个进程内的线程会共享什么资源? +* 该进程的地址空间 +* 全局变量 +* 堆空间 + +线程的栈空间是自己独有的 +#### (20) 异常和中断的区别 + +- **异常是由于执行了现行指令所引起的。由于系统调用引起的中断属于异常。** + +- **中断则是由于系统中某事件引起的,该事件与现行指令无关。** + +- **中断和异常** + + **相同点:**都是**CPU**对**系统**发生的某**个事情**做出的**一种反应**。 + + **区别:\**中断\****由**外因引起**,**异常**由**CPU本身**原因引起。 + + ![image-20210610221723734](C:\Users\38032\AppData\Roaming\Typora\typora-user-images\image-20210610221723734.png) + +#### (21) 一般情况下在Linux/windows平台下栈空间的大小 +在Linux下栈空间通常是8M,Windows下是1M +#### (22)虚拟内存的了解 +https://www.cnblogs.com/Przz/p/6876988.html + +在运行一个进程的时候,它所需要的内存空间可能大于系统的物理内存容量。通常一个进程会有4G的空间,但是物理内存并没有这么大,所以这些空间都是虚拟内存,它的地址都是逻辑地址,每次在访问的时候都需要映射成物理地址。 +当进程访问某个逻辑地址的时候,会去查看页表,如果页表中没有相应的物理地址,说明内存中没有这页的数据,发生缺页异常,这时候进程需要把数据从磁盘拷贝到物理内存中。如果物理内存已经满了,就需要覆盖已有的页,如果这个页曾经被修改过,那么还要把它写回磁盘。 + +#### (23)服务器高并发的解决方案 +1. 应用数据与静态资源分离 +将静态资源(图片,视频,js,css等)单独保存到专门的静态资源服务器中,在客户端访问的时候从静态资源服务器中返回静态资源,从主服务器中返回应用数据。 + +2. 客户端缓存 +因为效率最高,消耗资源最小的就是纯静态的html页面,所以可以把网站上的页面尽可能用静态的来实现,在页面过期或者有数据更新之后再将页面重新缓存。或者先生成静态页面,然后用ajax异步请求获取动态数据。 + +3. 集群和分布式 +(集群是所有的服务器都有相同的功能,请求哪台都可以,主要起分流作用)
+(分布式是将不同的业务放到不同的服务器中,处理一个请求可能需要使用到多台服务器,起到加快请求处理的速度。)
+可以使用服务器集群和分布式架构,使得原本属于一个服务器的计算压力分散到多个服务器上。同时加快请求处理的速度。 + +4. 反向代理 +在访问服务器的时候,服务器通过别的服务器获取资源或结果返回给客户端。 +#### (24)协程了解吗(高频) +协程和微线程是一个东西。 + +协程就是子程序在执行时中断并转去执行别的子程序,在适当的时候又返回来执行。 +这种子程序间的跳转不是函数调用,也不是多线程执行,所以省去了线程切换的开销,效率很高,并且不需要多线程间的锁机制,不会发生变量写冲突。 + +#### (25)那协程的底层是怎么实现的,怎么使用协程? +协程进行中断跳转时将函数的上下文存放在其他位置中,而不是存放在函数堆栈里,当处理完其他事情跳转回来的时候,取回上下文继续执行原来的函数。 +#### (23)进程的状态以及转换图 +* 三态模型 + 三态模型包括三种状态: + 1. 执行:进程分到CPU时间片,可以执行 + 2. 就绪:进程已经就绪,只要分配到CPU时间片,随时可以执行 + 3. 阻塞:有IO事件或者等待其他资源 + ![](fig/三态模型.png) +* 五态模型 + 1. 新建态:进程刚刚创建。 + 2. 就绪态: + 3. 运行态: + 4. 等待态:出现等待事件 + 5. 终止态:进程结束 + ![](fig/五态模型.png) + +* 七态模型 + 1. 新建态 + 2. 就绪挂起态 + 3. 就绪态 + 4. 运行态 + 5. 等待态 + 6. 挂起等待态 + 7. 终止态 +![](fig/七态模型.png) + +#### (24)在执行malloc申请内存的时候,操作系统是怎么做的?/内存分配的原理说一下/malloc函数底层是怎么实现的?/进程是怎么分配内存的? +https://blog.csdn.net/yusiguyuan/article/details/39496057 + +从操作系统层面上看,malloc是通过两个系统调用来实现的: brk和mmap +* brk是将进程数据段(.data)的最高地址指针向高处移动,这一步可以扩大进程在运行时的堆大小 +* mmap是在进程的虚拟地址空间中寻找一块空闲的虚拟内存,这一步可以获得一块可以操作的堆内存。 + +通常,分配的内存小于128k时,使用brk调用来获得虚拟内存,大于128k时就使用mmap来获得虚拟内存。 + +进程先通过这两个系统调用获取或者扩大进程的虚拟内存,获得相应的虚拟地址,在访问这些虚拟地址的时候,通过缺页中断,让内核分配相应的物理内存,这样内存分配才算完成。 +#### (25)什么是字节序?怎么判断是大端还是小端?有什么用? +https://www.cnblogs.com/broglie/p/5645200.html + +字节序即为多字节对象存储在内存中的字节顺序,大端即为最高有效位在前面,小端即为最低有效位在前面。 +判断大小端的方法:使用一个union数据结构 + +大端:0x12345678 + +小端:0x78563412 + +主机字节序取决于不同主机。 + +网络字节序为大端。 + +```C++ +union{ + short s; + char c[2]; // sizeof(short)=2; +}un; +un.s=0x0102; +if(un.c[0]==1 and un.c[1]==2) cout<<"大端"; +if(un.c[0]==2 and un.c[1]==1) cout<<"小端"; +``` +在网络编程中不同字节序的机器发送和接收的顺序不同。 + + +# 6. 场景题/算法题 +#### (0) leetcode hot100至少刷两遍,剑指offer至少刷两遍 重中之重!! +面试中90%的算法题都从leetcode hot100和剑指offer中出 刷两遍非常有必要 +#### (1) 介绍熟悉的设计模式(单例,简单工厂模式) +#### (2) 写单例模式,线程安全版本 +```C++ version +class Singleton{ + private: + static Singleton* instance; + Singleton(){ + // initialize + } + public: + static Singleton* getInstance(){ + if(instance==nullptr) instance=new Singleton(); + return instance; + } +}; +``` +#### (3) 写三个线程交替打印ABC +```C++ +#include +#include +#include +#include +using namespace std; + +mutex mymutex; +condition_variable cv; +int flag=0; + +void printa(){ + unique_lock lk(mymutex); + int count=0; + while(count<10){ + while(flag!=0) cv.wait(lk); + cout<<"thread 1: a"< lk(mymutex); + for(int i=0;i<10;i++){ + while(flag!=1) cv.wait(lk); + cout<<"thread 2: b"< lk(mymutex); + for(int i=0;i<10;i++){ + while(flag!=2) cv.wait(lk); + cout<<"thread 3: c"<& vec,int a,int b){ + vec[a]=vec[a]^vec[b]; + vec[b]=vec[a]^vec[b]; + vec[a]=vec[a]^vec[b]; +} +int partition(vector& vec,int start,int end){ + int pivot=vec[start+(end-start)/2]; + while(startpivot) end--; + if(start& vec,int start,int end){ + if(start>end) return; + int pivot=partition(vec,start,end); + quickSort(vec,start,pivot-1); + + + quickSort(vec,pivot+1,end); +} +``` +#### (8) 实现一个堆排序 +堆排序的基本过程: +* 将n个元素的序列构建一个大顶堆或小顶堆 +* 将堆顶的元素放到序列末尾 +* 将前n-1个元素重新构建大顶堆或小顶堆,重复这个过程,直到所有元素都已经排序 + +整体时间复杂度为nlogn +```C++ +#include +#include +using namespace std; +void swap(vector& arr, int a,int b){ + arr[a]=arr[a]^arr[b]; + arr[b]=arr[a]^arr[b]; + arr[a]=arr[a]^arr[b]; +} +void adjust(vector& arr,int len,int index){ + int maxid=index; + // 计算左右子节点的下标 left=2*i+1 right=2*i+2 parent=(i-1)/2 + int left=2*index+1,right=2*index+2; + + // 寻找当前以index为根的子树中最大/最小的元素的下标 + if(left&arr,int len){ + // 初次构建堆,i要从最后一个非叶子节点开始,所以是(len-1-1)/2,0这个位置要加等号 + for(int i=(len-1-1)/2;i>=0;i--){ + adjust(arr,len,i); + } + + // 从最后一个元素的下标开始往前遍历,每次将堆顶元素交换至当前位置,并且缩小长度(i为长度),从0处开始adjust + for(int i=len-1;i>0;i--){ + swap(arr,0,i); + adjust(arr,i,0);// 注意每次adjust是从根往下调整,所以这里index是0! + } +} +int main(){ + vector arr={3,4,2,1,5,8,7,6}; + + cout<<"before: "<& nums){ + int len=nums.size(); + for(int i=1;i=0 and nums[j]>key){ + nums[j+1]=nums[j]; + j--; + } + nums[j+1]=key; + } +} +``` + +#### (9) 快排存在的问题,如何优化 +* 3 种快排基准选择方法: + +随机(rand函数)、固定(队首、队尾)、三数取中(队首、队中和队尾的中间数) + +* 4种优化方式: + +优化1:当待排序序列的长度分割到一定大小后,使用插入排序 + +优化2:在一次分割结束后,可以把与Key相等的元素聚在一起,继续下次分割时,不用再对与key相等元素分割 + +优化3:优化递归操作 + +优化4:使用并行或多线程处理子序列 +#### (10) 反转一个链表(招银网络二面) +```C++ +ListNode* reverse(ListNode* root){ + ListNode* pre=nullptr,cur=root,nxt; + while(cur!=nullptr){ + nxt=cur->next; + cur->next=pre; + pre=cur;cur=nxt; + } + return pre; +} +``` +#### (11) Top K问题(可以采取的方法有哪些,各自优点?)(重点) +*Top K 问题的常见形式:* +>给定10000个整数,找第K大(第K小)的数
+ 给定10000个整数,找出最大(最小)的前K个数
+给定100000个单词,求前K词频的单词
+ +*解决Top K问题若干种方法* + +* 使用最大最小堆。求最大的数用最小堆,求最小的数用最大堆。 +* Quick Select算法。使用类似快排的思路,根据pivot划分数组。 +* 使用排序方法,排序后再寻找top K元素。 +* 使用选择排序的思想,对前K个元素部分排序。 +* 将1000.....个数分成m组,每组寻找top K个数,得到m×K个数,在这m×k个数里面找top K个数。 + +1. 使用最大最小堆的思路 (以top K 最大元素为例)
+按顺序扫描这10000个数,先取出K个元素构建一个大小为K的最小堆。每扫描到一个元素,如果这个元素大于堆顶的元素(这个堆最小的一个数),就放入堆中,并删除堆顶的元素,同时整理堆。如果这个元素小于堆顶的元素,就直接pass。最后堆中剩下的元素就是最大的前Top K个元素,最右的叶节点就是Top 第K大的元素。 + +>note:最小堆的插入时间复杂度为log(n),n为堆中元素个数,在这里是K。最小堆的初始化时间复杂度是nlog(n) + +C++中的最大最小堆要用标准库的priority_queue来实现。 +```C++ +struct Node { + int value; + int idx; + Node (int v, int i): value(v), idx(i) {} + friend bool operator < (const struct Node &n1, const struct Node &n2) ; +}; + +inline bool operator < (const struct Node &n1, const struct Node &n2) { + return n1.value < n2.value; +} + +priority_queue pq; // 此时pq为最大堆 +``` + +2. 使用Quick Select的思路(以寻找第K大的元素为例)
+Quick Select脱胎于快速排序,提出这两个算法的都是同一个人。算法的过程是这样的: +首先选取一个枢轴,然后将数组中小于该枢轴的数放到左边,大于该枢轴的数放到右边。 +此时,如果左边的数组中的元素个数大于等于K,则第K大的数肯定在左边数组中,继续对左边数组执行相同操作; +如果左边的数组元素个数等于K-1,则第K大的数就是pivot; +如果左边的数组元素个数小于K,则第K大的数肯定在右边数组中,对右边数组执行相同操作。 + +这个算法与快排最大的区别是,每次划分后只处理左半边或者右半边,而快排在划分后对左右半边都继续排序。 +``` +//此为Java实现 +public int findKthLargest(int[] nums, int k) { + return quickSelect(nums, k, 0, nums.length - 1); +} + +// quick select to find the kth-largest element +public int quickSelect(int[] arr, int k, int left, int right) { + if (left == right) return arr[right]; + int index = partition(arr, left, right); + if (index - left + 1 > k) + return quickSelect(arr, k, left, index - 1); + else if (index - left + 1 == k) + return arr[index]; + else + return quickSelect(arr, k - (index - left + 1), index + 1, right); + +} +``` + +3. 使用选择排序的思想对前K个元素排序 ( 以寻找前K大个元素为例)
+扫描一遍数组,选出最大的一个元素,然后再扫描一遍数组,找出第二大的元素,再扫描一遍数组,找出第三大的元素。。。。。以此类推,找K个元素,时间复杂度为O(N*K) +#### (12) 8G的int型数据,计算机的内存只有2G,怎么对它进行排序?(外部排序)(百度一面) +我们可以使用外部排序来对它进行处理。首先将整个文件分成许多份,比如说m份,划分的依据就是使得每一份的大小都能放到内存里。然后我们用快速排序或者堆排序等方法对每一份数据进行一个内部排序,变成有序子串。接着对这m份有序子串进行m路归并排序。取这m份数据的最小元素,进行排序,输出排序后最小的元素到结果中,同时从该元素所在子串中读入一个元素,直到所有数据都被输出到结果中为止。 + +https://blog.csdn.net/ailunlee/article/details/84548950 + +#### (13) 自己构建一棵二叉树,使用带有null标记的前序遍历序列 +在写二叉树相关算法的时候,如果需要自己构造测试用例(自己构造一棵二叉树),往往是一件很麻烦的事情,我们可以用一个带有null标记的前序遍历序列来进行构造。 **需要注意的是vec2tree()参数中的start是引用传递,而不是简单的参数值传递**。 +```C++ +#include +#include +#include +using namespace std; + +struct treeNode{ + string val; + treeNode* left,*right; + treeNode(string val):val(val){ + left=nullptr; + right=nullptr; + } +}; + +treeNode* vec2tree(vector& vec,int& start){ + treeNode* root; + if(vec[start]=="null"){ + start+=1; + root=nullptr; + }else{ + root=new treeNode(vec[start]); + start+=1; + root->left=vec2tree(vec,start); + root->right=vec2tree(vec,start); + } + return root; +} + +void tree2vec(treeNode *root,vector& vec){ + if(root==nullptr){ + vec.push_back("null"); + }else{ + vec.push_back(root->val); + tree2vec(root->left,vec); + tree2vec(root->right,vec); + } +} + +int main(){ + vector vec={"2","4","5","7","null","null","null","null","3","6","null","null","2","null","null"}; + int index=0,&start=index; + treeNode* root=vec2tree(vec,start); + //displaytree(root); + vector mvec; + tree2vec(root,mvec); + for(string item:mvec) cout<>5
+N%32就是求N的后5位:N& 0x1F (0x1F = 00011111)
+模32然后相应位置置为1: a[i] |= 1<< N & 0x1F
+ +所以总的公式为: a[ N>>5 ] |= 1<< N & 0x1F
+ +**BitMap算法评价** + +* 优点: + 1. 运算效率高,不进行比较和移位; + 2. 占用内存少,比如最大的数MAX=10000000;只需占用内存为MAX/8=1250000Byte=1.25M。 +* 缺点: + 1. 所有的数据不能重复,即不可对重复的数据进行排序。(少量重复数据查找还是可以的,用2-bitmap)。 + 2. 所需要的空间随着最大元素的增大而增大,当数据类似(1,1000,10万)只有3个数据的时候,用bitmap时间复杂度和空间复杂度相当大,只有当数据比较密集时才有优势。 + +#### (26) 布隆过滤器原理与优点 +布隆过滤器是一个比特向量或者比特数组,它本质上是一种概率型数据结构,用来查找一个元素是否在集合中,支持高效插入和查询某条记录。常作为针对超大数据量下高效查找数据的一种方法。 + +**它的具体工作过程是这样子的:** +假设布隆过滤器的大小为m(比特向量的长度为m),有k个哈希函数,它对每个数据用这k个哈希函数计算哈希,得到k个哈希值,然后将向量中相应的位设为1。在查询某个数据是否存在的时候,对这个数据用k个哈希函数得到k个哈希值,再在比特向量中相应的位查找是否为1,如果某一个相应的位不为1,那这个数据就肯定不存在。但是如果全找到了,则这个数据有可能存在。 + +**为什么说有可能存在呢?** +因为不同的数据经过哈希后可能有相同的哈希值,在比特向量上某个位置查找到1也可能是由于某个另外的数据映射得到的。 + +**支持删除操作吗** +目前布隆过滤器只支持插入和查找操作,不支持删除操作,如果要支持删除,就要另外使用一个计数变量,每次将相应的位置为1则计数加一,删除则减一。 + +布隆过滤器中哈希函数的个数需要选择。如果太多则很快所有位都置为1,如果太少会容易误报。 + +**布隆过滤器的大小以及哈希函数的个数怎么选择?** +k 为哈希函数个数,m 为布隆过滤器长度,n 为插入的元素个数,p 为误报率 +![](fig/布隆过滤器.png) +#### (27) 布隆过滤器处理大规模问题时的持久化,包括内存大小受限、磁盘换入换出问题 +#### (28)实现一个队列,并且使它支持多线程,队列有什么应用场景(阿里三面) +```C++ +//评测题目: +class FIFOQueue +{ +vector vec(initCap,0); +int start=0,end=0; +condition_variable cv; +mutex m; +bool flag=false;// isFull + bool enqueue(int v) { + unique_lock lk(m); + while(flag==true) cv.wait(lk); + end=(end+1)%initCap; + vec[end]=v; + cv.notifyall(); + return true; + } + } + int dequeue() { + unique_lock lk(m); + if(start!=end){ + int val = vec[start]; + start=(start+1)%initCap; + flag=false; + cv.notifyall(); + return val; + }else{ + flag=false; + cv.notifyall(); + return -1; + } + } +} +``` +以上代码是面试时写的,并没有运行,也许有错误,请客观参考 +# 7. 智力题 +#### (1) 100层楼,只有2个鸡蛋,想要判断出那一层刚好让鸡蛋碎掉,给出策略(滴滴笔试中两个铁球跟这个是一类题) +* (给定了楼层数和鸡蛋数的情况)二分法+线性查找 从100/2=50楼扔起,如果破了就用另一个从0扔起直到破。如果没破就从50/2=25楼扔起,重复。 +* 动态规划 +#### (2) 毒药问题,1000瓶水,其中有一瓶可以无限稀释的毒药,要快速找出哪一瓶有毒,需要几只小白鼠 +用二进制的思路解决问题。2的十次方是1024,使用十只小鼠喝一次即可。方法是先将每瓶水编号,同时10个小鼠分别表示二进制中的一个位。将每瓶水混合到水瓶编号中二进制为1的小鼠对应的水中。喝完后统计,将死亡小鼠对应的位置为1,没死的置为0,根据死亡小鼠的编号确定有毒的是哪瓶水,如0000001010表示10号水有毒。 +#### (3) +#### (4) 先手必胜策略问题:100本书,每次能够拿1-5本,怎么拿能保证最后一次是你拿 +寻找每个回合固定的拿取模式。最后一次是我拿,那么上个回合最少剩下6本。那么只要保持每个回合结束后都剩下6的倍数,并且在这个回合中我拿的和对方拿的加起来为6(这样这个回合结束后剩下的还是6的倍数),就必胜。关键是第一次我必须先手拿(100%6=4)本(这不算在第一回合里面)。 +#### (5) 放n只蚂蚁在一条树枝上,蚂蚁与蚂蚁之间碰到就各自往反方向走,问总距离或者时间。 +碰到就当没发生,继续走,相当于碰到的两个蚂蚁交换了一下身体。其实就是每个蚂蚁从当前位置一直走直到停止的总距离或者时间。 +#### (6) 瓶子换饮料问题:1000瓶饮料,3个空瓶子能够换1瓶饮料,问最多能喝几瓶 +拿走3瓶,换回1瓶,相当于减少2瓶。但是最后剩下4瓶的时候例外,这时只能换1瓶。所以我们计算1000减2能减多少次,直到剩下4.(1000-4=996,996/2=498)所以1000减2能减498次直到剩下4瓶,最后剩下的4瓶还可以换一瓶,所以总共是1000+498+1=1499瓶。 +#### (7)在24小时里面时针分针秒针可以重合几次 +24小时中时针走2圈,而分针走24圈,时针和分针重合24-2=22次,而只要时针和分针重合,秒针一定有机会重合,所以总共重合22次 +#### (8) 有一个天平,九个砝码,一个轻一些,用天平至少几次能找到轻的? +至少2次:第一次,一边3个,哪边轻就在哪边,一样重就是剩余的3个; +第二次,一边1个,哪边轻就是哪个,一样重就是剩余的那个; +#### (9) 有十组砝码每组十个,每个砝码重10g,其中一组每个只有9g,有能显示克数的秤最少几次能找到轻的那一组砝码? +砝码分组1~10,第一组拿一个,第二组拿两个以此类推。。第十组拿十个放到秤上称出克数x,则y = 550 - x,第y组就是轻的那组 +#### (10)生成随机数问题:给定生成1到5的随机数Rand5(),如何得到生成1到7的随机数函数Rand7()? +思路:由大的生成小的容易,比如由Rand7()生成Rand5(),所以我们先构造一个大于7的随机数生成函数。 +记住下面这个式子: +``` +RandNN= N( RandN()-1 ) + RandN() ;// 生成1到N^2之间的随机数 +可以看作是在数轴上撒豆子。N是跨度/步长,是RandN()生成的数的范围长度,RandN()-1的目的是生成0到N-1的数,是跳数。后面+RandN()的目的是填满中间的空隙 +``` +比如` Rand25= 5( Rand5()-1 ) + Rand5()`可以生成1到25之间的随机数。我们可以只要1到21(3*7)之间的数字,所以可以这么写 +``` +int rand7(){ + int x=INT_MAX; + while(x>21){ + x=5*(rand5()-1)+rand5(); + } + return x%7+1; +} +``` +#### 赛马:有25匹马,每场比赛只能赛5匹,至少要赛多少场才能找到最快的3匹马? +* 第一次,分成5个赛道ABCDE,每个赛道5匹马,每个赛道比赛一场,每个赛道的第12345名记为 A1,A2,A3,A4,A5 B1,B2,B3,B4,B5等等,这一步要赛5场。 +* 第二次,我们将每个赛道的前三名,共15匹。分成三组,然后每组进行比赛。这一步要赛3场。 +* 第三次,我们取每组的前三名。共9匹,第一名赛道的马编号为1a,1b,1c,第二名赛道的马编号为2a,2b,2c,第三名赛道的马编号为3a,3b,3c。这时进行分析,1a表示第一名里面的第一名,绝对是所有马中的第一,所以不用再比了。2c表示第二名的三匹里头的最后一匹,3b和3c表示第三名里面的倒数两匹,不可能是所有马里面的前三名,所以也直接排除,剩下1b,1c,2a,2b,,3a,共5匹,再赛跑一次取第一第二名,加上刚筛选出来的1a就是所有马里面的最快3匹了。这一步要赛1场。 +* 所以一共是5+3+1=9场。 +#### 烧 香/绳子/其他 确定时间问题:有两根不均匀的香,燃烧完都需要一个小时,问怎么确定15分钟的时长? +(说了求15分钟,没说开始的15分钟还是结束的15分钟,这里是可以求最后的15分钟)点燃一根A,同时点燃另一根B的两端,当另一根B烧完的时候就是半小时,这是再将A的另一端也点燃,从这时到A燃烧完就正好15分钟。 + +#### 掰巧克力问题 N*M块巧克力,每次掰一块的一行或一列,掰成1*1的巧克力需要多少次?(1000个人参加辩论赛,1V1,输了就退出,需要安排多少场比赛) +每次拿起一块巧克力,掰一下(无论横着还是竖着)都会变成两块,因为所有的巧克力共有N\*M块,所以要掰N\*M-1次,-1是因为最开始的一块是不用算进去的。 + +每一场辩论赛参加两个人,消失一个人,所以可以看作是每一场辩论赛减少一个人,直到最后剩下1个人,所以是1000-1=999场。 +# 8. 大数据 +#### 1. 介绍一下Hadoop +Hadoop是一套大数据解决方案,提供了一套分布式的系统基础架构,包括HDFS,MapReduce和YARN。 +* HDFS提供分布式的数据存储 +* MapReduce负责进行数据运算 +* YARN负责任务调度 + +HDFS是主从架构的,包括namenode,secondarynamenode和datanode。datanode负责存储数据,namenode负责管理HDFS的目录树和文件元信息。
+MapReduce包括jobtracker,tasktracker和client。Jobtracker负责进行资源调度和作业监控。tasktracker会周期性的通过心跳向jobtracker汇报资源使用情况。 +#### 2. 说一下MapReduce的运行机制 +MapReduce包括输入分片、map阶段、combine阶段、shuffle阶段和reduce阶段。分布式计算框架包括client,jobtracker和tasktracker和调度器。 +* 输入分片阶段,mapreduce会根据输入文件计算分片,每个分片对应一个map任务 +* map阶段会根据mapper方法的业务逻辑进行计算,映射成键值对 +* combine阶段是在节点本机进行一个reduce,减少传输结果对带宽的占用 +* shuffle阶段是对map阶段的结果进行分区,排序,溢出然后写入磁盘。将map端输出的无规则的数据整理成为有一定规则的数据,方便reduce端进行处理,有点像洗牌的逆过程。 https://blog.csdn.net/ASN_forever/article/details/81233547 +* reduce阶段是根据reducer方法的业务逻辑进行计算,最终结果会存在hdfs上。 + +#### 3. 介绍一下kafka +https://blog.csdn.net/qq_29186199/article/details/80827085 + +https://blog.csdn.net/student__software/article/details/81486431 + +kafka是一个分布式消息队列,包括producer、broker和consumer。kafka会对每个消息根据topic进行归类,每个topic又会分成多个partition,消息会根据先进先出的方式存储。消费者通过offset进行消费。 + +kafka的特点是吞吐量高,可以进行持久化,高可用。 +#### 4. 为什么kafka吞吐量高?/介绍一下零拷贝 +kafka吞吐量高是因为一个利用了磁盘顺序读写的特性,速度比随机读写要快很多,另一个是使用了零拷贝,数据直接在内核进行输入和输出,减少了用户空间和内核空间的切换。 + +零拷贝:传统文件读取并发送至网络的步骤是:先将文件从磁盘拷贝到内核空间,然后内核空间拷贝到用户空间的缓冲区,再从用户空间拷贝到内核空间的socket缓冲区,最后拷贝到网卡并发送。而零拷贝技术是先将文件从磁盘空间拷贝到内核缓冲区,然后直接拷贝至网卡进行发送,减少了重复拷贝操作。 +#### 5. 介绍一下spark +https://blog.csdn.net/u011204847/article/details/51010205 + +spark是一个通用内存并行计算框架。它可以在内存中对数据进行计算,效率很高,spark的数据被抽象成RDD(弹性分布式数据集)并且拥有DAG执行引擎,兼容性和通用性很好。可以和Hadoop协同工作。 +#### 6. 介绍一下spark-streaming +https://blog.csdn.net/yu0_zhang0/article/details/80569946 + +spark-streaming是spark的核心组件之一。主要提供高效的流计算能力。spark-streaming的原理是将输入数据流以时间片进行拆分,然后经过spark引擎以类似批处理的方式处理每个时间片数据。 + +spark-streaming将输入根据时间片划分成一段一段的Dstream(也就是离散数据流),然后将每一段数据转换成RDD进行操作。 + +#### 7. spark的transformation和action有什么区别 +spark的算子分成transformation和action两类 +* transformation是变换算子,这类算子不会触发提交,是延迟执行的。也就是说执行到transformation算子的时候数据并没有马上进行计算,只是记住了对RDD的逻辑操作 +* action算子是执行算子,会出发spark提交作业,并将数据输出到spark +#### 8. spark常用的算子说几个 +spark的算子分为两类:transformation和action + +常用的transformation算子: +```scala +// union 求并集 +val rdd8 = rdd6.union(rdd7) + +// intersection 求交集 +val rdd9 = rdd6.intersection(rdd7) + +// join 将rdd进行聚合连接,类似数据库的join +val rdd3 = rdd1.join(rdd2) + +// map flatMap mapPartition 传入一个函数对数据集中的每一个数据进行操作 +val arr1 = Array(1,2,3,4,5) +val arr2 = rdd1.map(_+1) + +// countByKey reduceByKey partitionByKey 统计每个key有多少个键值对 +``` +常用的action算子 +```scala +// reduce 按照一定的方法将元素进行合并 +val rdd2 = rdd1.reduce(_+_) + +// collect 将RDD转换为数组 +rdd1.collect + +// top 返回最大的k个元素 +rdd1.top(2) +``` +#### 9. 如何保证kafka的消息不丢失 +https://blog.csdn.net/liudashuang2017/article/details/88576274 + +我们可以从三个方面保证kafka不丢失消息 +* 首先从producer生产者方面,为send()方法注册一个回调函数,可以得知消息发送有没有成功;将重试次数retrie设置为3;设置acks参数为all,当消息被写入所有同步副本之后才算发送成功。 +* 在consumer消费者方面,关闭自动提交; +* 在broker集群方面,设置复制系数replica.factor为大于等于3 +#### 10. kafka如何选举leader +首先启动的broker在zookeeper中创建一个临时节点并让自己称为leader,其他的节点会创建watch对象进行监听并成为follower,当broker宕机的时候,其他follower会尝试创建这个临时节点,但是只有一个能够创建成功,创建成功的broker就会成为leader。 + + +#### 11. 说下spark中的宽依赖和窄依赖 +https://blog.csdn.net/a1043498776/article/details/54889922 + +* 宽依赖:指子RDD的分区依赖于父RDD的所有分区,举例:groupbykey,join +* 窄依赖:指父RDD的每个分区被最多一个子RDD的分区所依赖,举例:map,filter +![](https://img-blog.csdn.net/20170206221148299?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYTEwNDM0OTg3NzY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) +#### 12. 说下spark中stage是依照什么划分的 +https://zhuanlan.zhihu.com/p/57124273 + +spark中的stage其实是一组并行的任务,spark会将多个RDD根据依赖关系划分成有向无环图DAG,DAG会被划分成多个stage,划分的依据是RDD之间的宽窄依赖。遇到宽依赖就划分stage。因为宽依赖与窄依赖的区别之一就是宽依赖会发生shuffle操作,所以也可以说stage的划分依据是是否发生shuffle操作。 + +#### 13. spark的内存管理是怎样的 +https://www.jianshu.com/p/4f1e551553ae + +https://www.cnblogs.com/wzj4858/p/8204282.html + +spark的内存包括静态内存管理和统一内存管理两种机制。静态内存管理中存储和执行两块内存区域是分开的,统一内存管理中两块内存之间可以相互借用
+* 静态内存管理:静态内存管理机制下堆内内存包括安全内存,存储内存,shuffle内存和unroll内存 + +![](fig/spark内存一.png) + * 统一内存管理:统一内存管理机制下内存分为spark内存,用户内存和保留内存三部分。用户内存存放用户代码逻辑和自定义数据结构等,保留内存存放的是spark的内部对象和逻辑。 + ![](https://upload-images.jianshu.io/upload_images/195230-f119edabb5683f38.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp) + +#### 14. spark的容错机制是什么样的 +https://blog.csdn.net/dengxing1234/article/details/73613484 + +spark的容错机制是通过血统(lineage)和checkpoint来实现的 。 + +* RDD的lineage可以看作是一个重做日志(redo log)记录的是它粗粒度上的transformation操作。当rdd的分区数据丢失时,它可以根据lineage重新计算来恢复数据。 在窄依赖上可以直接计算父RDD的节点数据进行恢复,在宽依赖上则要等到父RDD所有数据计算完后并将结果shuffle到子RDD上才能完成恢复。 +* 如果DAG中的lineage过长,或者在宽依赖上进行checkpoint的收益更大,就会使用checkpoint进行容错,将RDD写入磁盘进行持久化存储,如果节点数据丢失,就从磁盘读取数据进行恢复。 + + + + + + + +# 9. HR面 +#### 1. 自我介绍 +(HR面试的自我介绍可以侧重软实力部分,项目技术方面介绍可以适当少一些) +#### 2. 项目中遇到的最大难点 +* 在项目中曾经遇到了新的框架不知道该如何上手的问题,以及面对新的概念,新的技术不知道从何学起。解决的办法是在官网寻找说明文档和demo,按照说明文档上的内容一步步了解,以及咨询身边有用过这个框架的同学,或者在CSDN上寻找相关博客。 + +* 项目的时间比较紧迫,没有那么多的时间可以用。解决方法是把还没有完成的项目分一个轻重缓急,在有限的时间里,先做重要而且紧急的,然后完成紧急的,再做重要的。利用轻重缓急做一个取舍。 +#### 3. 项目中的收获 +一个是了解了相关框架的使用方法(比如Dataframe的使用,xgboost的使用等等),这些框架或者技术可以在以后的开发中使用到。和对自己开发能力的锻炼。 + +一个是锻炼了与他人的交流能力,因为在团队项目里经常会跟别人汇报自己的想法和进度,同时也会跟其他成员沟通模块之间的交互,所以在这个过程中对自己的表达能力和理解能力都是一个很大的提升。 +#### 4. 可以实习的时间,实习时长 +一定要往长了说!半年起步,最好七八个月,因为实习生是可以随时跑路的。而且实习时间越长HR越青睐。 +#### 5. 哪里人 +#### 6. 说一下自己的性格 +我是比较内向谨慎的人,平时做的多说的少。比较善于总结,在与人交流的时候更倾向于倾听别人的意见后才发言。并且别人都说我办事认真靠谱。 +#### 7. 你的优缺点是什么 +我的缺点是容易在一些细节的地方花费太多的时间,有时候过分追求细节。并且我的实习经验比较缺乏,对于实际项目的业务流程和工作流程不是很了解。(所以我打算通过实习来熟悉实际的软件开发的流程和技术。) + +我的优点是责任心比较强,做事比较负责,在校期间我负责的大创项目进展很顺利,我经常组织组员们进行讨论和推进项目的开发,最后这个项目得到了92的评分,在同级别里面是比较高的。 +#### 8. 有什么兴趣爱好,画的怎么样/球打的如何/游戏打的怎么样 +平时的爱好是画画打游戏,在CSDN写写博客,还有就是看书,我很喜欢学到新知识掌握新技能的感觉。 +#### 9. 看过最好的一本书是什么 +技术类:编程之美 机器学习西瓜书 STL源码剖析 剑指offer C++primer plus + +非技术类:明朝那些事儿 香水(聚斯金德) 解忧杂货店 人类简史 沉默的大多数 与时间做朋友(李笑来) 千年历史千年诗 +#### 10. 学习技术中有什么难点 +#### 11. 怎么看待加班 +我觉得 任何一家单位都有可能要加班。如果自己的工作没有按时完成,那自觉加班是理所当然的,当然,自己要不断提高工作效率,避免这种原因导致的加班。如果遇到紧急任务或者突发状况时,为了顺利配合团队完成任务,我会尽自己所能加班共同完成。 +#### 12. 觉得深圳怎么样(或者其他地点) +#### 13. 遇见过最大的挫折是什么,怎么解决的 + +#### 14. 职业规划 +在工作的第一个阶段,先尽快适应工作的环境,包括开发环境开发工具和工作流程等,把自己负责的部分快速的完成,不能出差错。第二个阶段要熟悉整个项目的业务流程,所有模块的结构和依赖关系,知道每个模块为什么要这么设计,以及它们的实现细节。第三个阶段要培养独立设计一个项目的能力,可以独立或者在别人的协作下设计项目的模块分工和架构。 + +在工作和项目中多写博客或者笔记,积累技术影响力,将经验总结成文档。同时与同事搞好关系,尝试培养领导能力和组织能力。 + +#### 15. 目前的offer情况 +可以如实说 +#### 16. 你最大的优势和劣势是什么 +* 优势:做事情有主动性,不拖沓,有责任心。举个例子:在做论文课题的时候,几乎都是我自己找老师汇报进度和找老师讨论问题,很少有被老师催的时候。每一次跟老师讨论之后都会将讨论的内容和老师提出的意见进行详细记录。在中软杯的比赛中,主动承担答辩ppt的制作,并且每次排练之后都迅速对ppt的修改意见进行落实修改,前前后后改了十几版。 +* 劣势:有时候做事情比较急躁,容易导致粗心。 +#### 17. 介绍在项目里面充当的角色 +#### 18. 介绍一下本科获得的全国赛奖项的情况 +#### 19. 最有成就感的事情/最骄傲的一件事情 +* 本科的时候跟优秀的队友们一起参加中国软件杯比赛努力了四个月,最后获得了该赛题的第一名和全国一等奖的好成绩 +* 保研夏令营拿到了四个学校的offer +#### 20. 在实验室中担任什么角色,参加的XXX能聊聊吗 +#### 22. 用两个词来形容自己 +踏实 认真 + +# **10.Redis** + +https://blog.csdn.net/ThinkWon/article/details/103522351 + +# 1 .Redis相比其他缓存,有一个非常大的优势,就是支持多种数据类型。 + +数据类型说明string字符串,最简单的k-v存储hashhash格式,value为field和value,适合ID-Detail这样的场景。list简单的list,顺序列表,支持首位或者末尾插入数据set无序list,查找速度快,适合交集、并集、差集处理sorted set有序的set + +其实,通过上面的数据类型的特性,基本就能想到合适的应用场景了。 + +string——适合最简单的k-v存储,类似于memcached的存储结构,短信验证码,配置信息等,就用这种类型来存储。 + +hash——一般key为ID或者唯一标示,value对应的就是详情了。如商品详情,个人信息详情,新闻详情等。 + +list——因为list是有序的,比较适合存储一些有序且数据相对固定的数据。如省市区表、字典表等。因为list是有序的,适合根据写入的时间来排序,如:最新的***,消息队列等。 + +set——可以简单的理解为ID-List的模式,如微博中一个人有哪些好友,set最牛的地方在于,可以对两个set提供交集、并集、差集操作。例如:查找两个人共同的好友等。 + +Sorted Set——是set的增强版本,增加了一个score参数,自动会根据score的值进行排序。比较适合类似于top 10等不根据插入的时间来排序的数据。 + +如上所述,虽然Redis不像关系数据库那么复杂的数据结构,但是,也能适合很多场景,比一般的缓存数据结构要多。了解每种数据结构适合的业务场景,不仅有利于提升开发效率,也能有效利用Redis的性能。 + + + +# 2.LRU和LFU的区别: + +LRU是最近最少使用页面置换算法(Least Recently Used),也就是首先淘汰最长时间未被使用的页面! + +LFU是最近最不常用页面置换算法(Least Frequently Used),也就是淘汰一定时期内被访问次数最少的页! + +# 3.分布式的CAP理论 + +告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。”所以,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。 + +# 4.正向代理、反向代理 + +1)当一个代理服务器能够代理外部网络上的主机,访问内部网络时,这种代理服务的方式称为**反向代理**服务。此时代理服务器对外就表现为一个Web服务器,外部网络就可以简单把它当作一个标准的Web服务器而不需要特定的配置。(对方提供) + + + +2)正向代理:是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。(客户端提供) + diff --git "a/webserver_\347\254\224\350\256\260.md" "b/webserver_\347\254\224\350\256\260.md" new file mode 100644 index 0000000000..f533ec1dfe --- /dev/null +++ "b/webserver_\347\254\224\350\256\260.md" @@ -0,0 +1,110 @@ +# Raw_version文档 + +![](C:\Users\38032\Desktop\note\web服务器框架图.gif) + +Linux下C++轻量级Web服务器,助力初学者快速实践网络编程,搭建属于自己的服务器. + +- 1、使用**线程池 + epoll(ET和LT均实现) + 模拟Proactor模式**的并发模型 +- 2、使用**状态机**解析HTTP请求报文,支持解析**GET和POST**请求 +- 3、通过访问服务器数据库实现web端用户**注册、登录**功能,可以请求服务器**图片和视频文件** +- 4、实现**同步/异步日志系统**,记录服务器运行状态 +- 5、定时器处理非活动连接 +- 6、经Webbench压力测试可以实现**上万的并发连接**数据交换 + + + +## 1、使用**线程池 + epoll(ET和LT均实现) + 模拟Proactor模式**的并发模型 + +半同步/半反应堆线程池 + +**===============** + +使用一个**工作队列**完全解除了**主线程**和**工作线程**的耦合关系:主线程往工作队列中插入任务,工作线程通过**竞争**来取得任务并执行它。 + +\> * 同步I/O模拟proactor模式 + +\> * 半同步/半反应堆 + +\> * 线程池(8个工作线程)内含数据库连接池,当有读、写事件时,工作线程竞争取得任务,执行process()函数,同时从数据库连接池中取出一个连接以执行数据库的查询,插入操作。 + +\> * list实现工作队列存放http_conn类 + +## 2、使用**状态机**解析HTTP请求报文,支持解析**GET和POST**请求 + +http连接处理类 + +**===============** + +根据状态转移,通过**主从状态机**封装了**http连接类**。其中,主状态机在内部调用从状态机,从状态机将处理状态和数据传给主状态机 + +\> * 客户端发出http连接请求 + +\> * 从状态机读取数据,更新自身状态和接收数据,传给主状态机 + +\> * 主状态机根据从状态机状态,更新自身状态,决定响应请求还是继续读取 + +## 3、通过访问服务器数据库实现web端用户**注册、登录**功能,可以请求服务器**图片和视频文件** + +CGI & 数据库连接池 + +**===============** + +**数据库连接池** + +\> * 单例模式,保证唯一 + +\> * list实现连接池 + +\> * 连接池为**静态大小** + +\> * **互斥锁**实现线程安全 + + + +**CGI** + +\> * HTTP请求采用POST方式 + +\> * 登录用户名和密码校验 + +\> * 用户注册及多线程注册安全 + + + +## 4.实现**同步/异步日志系统**,记录服务器运行状态 + +所谓同步日志,即当输出日志时,必须等待日志输出语句执行完毕后,才能执行后面的业务逻辑语句。 + +| | **日志输出方式** | +| -------------- | ------------------------------------------------------------ | +| sync | 同步打印日志,日志输出与业务逻辑在同一线程内,当日志输出完毕,才能进行后续业务逻辑操作 | +| Async Appender | 异步打印日志,内部采用ArrayBlockingQueue,对每个AsyncAppender创建一个线程用于处理日志输出。 | +| Async Logger | 异步打印日志,采用了高性能并发框架Disruptor,创建一个线程用于处理日志输出。 | + +**同步/异步日志系统** + +**===============** + +同步/异步日志系统主要涉及了两个模块,一个是日志模块,一个是阻塞队列模块,其中加入阻塞队列模块主要是解决异步写入日志做准备. + +\> * 自定义阻塞队列 + +\> * 单例模式创建日志 + +\> * 同步日志 + +\> * 异步日志 + +\> * 实现按天、超行分类 + +## 5.定时器处理非活动连接 + +**===============** + +由于非活跃连接占用了连接资源,严重影响服务器的性能,通过实现一个服务器定时器,处理这种非活跃连接,释放连接资源。利用alarm函数周期性(5 s 产生一次SIGALRM 信号 )地触发SIGALRM信号,该信号的信号处理函数利用管道通知主循环执行定时器链表上的定时任务,检测过期的定时器并关闭非活跃连接. + +\> * 统一事件源 + +\> * 基于升序的双向链表的定时器 + +\> * 处理非活跃连接 \ No newline at end of file diff --git "a/\345\210\206\347\261\273\345\210\267\351\242\230\347\254\224\350\256\260.md" "b/\345\210\206\347\261\273\345\210\267\351\242\230\347\254\224\350\256\260.md" new file mode 100644 index 0000000000..e93923000f --- /dev/null +++ "b/\345\210\206\347\261\273\345\210\267\351\242\230\347\254\224\350\256\260.md" @@ -0,0 +1,2001 @@ +[TOC] + + + +# 1.链表篇 + +## 1. 203 移除链表元素 + +```c++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode() : val(0), next(nullptr) {} + * ListNode(int x) : val(x), next(nullptr) {} + * ListNode(int x, ListNode *next) : val(x), next(next) {} + * }; + */ +// 不使用伪头 ,删除头结点和非头结点 分开写 +class Solution { +public: + ListNode* removeElements(ListNode* head, int val) { + //删除头结点 + while( head != nullptr && head->val == val ){ + ListNode* tmp = head; + head = head->next; + delete tmp; + } + //删除非头结点 + ListNode* cur = head; + while( cur != nullptr && cur->next != nullptr){ + if( cur->next->val == val ){ + ListNode* tmp = cur->next; + cur->next = cur->next->next; + delete tmp; + } + else{ + cur = cur->next; + } + } + return head; + } +}; + + +``` + +```c++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode() : val(0), next(nullptr) {} + * ListNode(int x) : val(x), next(nullptr) {} + * ListNode(int x, ListNode *next) : val(x), next(next) {} + * }; + */ +class Solution { +public: + ListNode* removeElements(ListNode* head, int val) { + //设置虚拟头节点 + ListNode* dumhead = new ListNode(0); + dumhead->next = head; + ListNode* cur = dumhead; + while( cur->next !=nullptr ){ + if(cur->next->val == val){ + ListNode* tmp = cur->next; + cur->next = cur->next->next; + delete tmp; + } + else{ + cur = cur->next; + } + } + return dumhead->next; + } +}; +``` + +## 2. 707 设计链表 + +### 1) 设计单链表 + +```c++ +class MyLinkedList { +private: + struct Node{ + int val; + Node* next; + Node():val(0),next(nullptr){} + Node(int _val):val(_val),next(nullptr){} + }; + Node* dumhead; + int size; +public: + /** Initialize your data structure here. */ + MyLinkedList() { + dumhead = new Node(0); + size = 0; + } + + /** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */ + int get(int index) { + Node* cur = dumhead->next; + if( index >= 0 && index < size){ + while( index-- ){ + cur = cur->next; + } + return cur->val; + } + else return -1; + } + + /** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */ + void addAtHead(int val) { + Node* node = new Node(val); + if(size > 0) node->next = dumhead->next; + dumhead->next = node; + size++; + } + + /** Append a node of value val to the last element of the linked list. */ + void addAtTail(int val) { + Node* cur = dumhead; + Node* node = new Node(val); + while( cur->next != nullptr ){ + cur = cur->next; + } + cur->next = node; + size++; + } + + /** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */ + void addAtIndex(int index, int val) { + if( index > size ) return; + Node* cur = dumhead; + while( index-- ){ + cur = cur->next; + } + Node* node = new Node(val); + node->next = cur->next; + cur->next = node; + size++; + } + + /** Delete the index-th node in the linked list, if the index is valid. */ + void deleteAtIndex(int index) { + if( index >= 0 && index < size){ + Node* cur = dumhead; + while( index-- ){ + cur = cur->next; + } + Node* tmp = cur->next; + cur->next = cur->next->next; + size--; + delete tmp; + } + } +}; + +/** + * Your MyLinkedList object will be instantiated and called as such: + * MyLinkedList* obj = new MyLinkedList(); + * int param_1 = obj->get(index); + * obj->addAtHead(val); + * obj->addAtTail(val); + * obj->addAtIndex(index,val); + * obj->deleteAtIndex(index); + */ +``` + +### 2) 设计双链表 + +```c++ +class MyLinkedList { +private: + struct Node{ + int val; + Node* next; + Node* pre; + Node():val(0),next(nullptr),pre(nullptr){} + Node(int _val):val(_val),next(nullptr),pre(nullptr){} + }; + Node* dumhead; + Node* dumtail; + int size; +public: + /** Initialize your data structure here. */ + MyLinkedList() { + dumhead = new Node(0); + dumtail = new Node(0); + dumhead->next = dumtail; + dumtail->pre = dumhead; + size = 0; + } + + /** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */ + int get(int index) { + if( index < 0 || index >= size ) return -1; + if( index < size/2 ){ + Node* cur = dumhead; + while( index-- ) cur = cur->next; + cur = cur->next; + return cur->val; + } + else{ + Node* cur = dumtail; + index = size - index; + while( index-- ) cur = cur->pre; + return cur->val; + } + } + + /** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */ + void addAtHead(int val) { + Node* node = new Node(val); + node->next = dumhead->next; + node->pre = dumhead; + dumhead->next->pre = node; + dumhead->next = node; + size++; + } + + /** Append a node of value val to the last element of the linked list. */ + void addAtTail(int val) { + Node* node = new Node(val); + node->next = dumtail; + node->pre = dumtail->pre; + dumtail->pre->next = node; + dumtail->pre = node; + size++; + } + + /** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */ + void addAtIndex(int index, int val) { + if( index > size ) return; + Node* cur; + if( index < size/2 ){ + cur = dumhead; + while( index-- ) cur = cur->next; + } + else{ + cur = dumtail; + index = size - index + 1; + while( index-- ) cur = cur->pre; + } + Node* node = new Node(val); + node->pre = cur; + node->next = cur->next; + cur->next->pre = node; + cur->next = node; + size++; + } + + /** Delete the index-th node in the linked list, if the index is valid. */ + void deleteAtIndex(int index) { + if( index < 0 || index >= size ) return; + Node* cur; + if( index < size/2 ){ + cur = dumhead; + while( index-- ) cur = cur->next; + } + else{ + cur = dumtail; + index = size - index + 1; + while( index-- ) cur = cur->pre; + } + Node* tmp = cur->next; + cur->next->next->pre = cur; + cur->next = cur->next->next; + delete tmp; + size--; + } +}; + +/** + * Your MyLinkedList object will be instantiated and called as such: + * MyLinkedList* obj = new MyLinkedList(); + * int param_1 = obj->get(index); + * obj->addAtHead(val); + * obj->addAtTail(val); + * obj->addAtIndex(index,val); + * obj->deleteAtIndex(index); + */ +``` + +## 3. 206 反转链表 + +```c++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode() : val(0), next(nullptr) {} + * ListNode(int x) : val(x), next(nullptr) {} + * ListNode(int x, ListNode *next) : val(x), next(next) {} + * }; + */ +class Solution { +public: + ListNode* reverseList(ListNode* head) { + ListNode* cur = head; + ListNode* pre = nullptr; + while( cur != nullptr ){ + ListNode* tmp = cur->next; + cur->next = pre; + pre = cur; + cur = tmp; + } + return pre; + } +}; +``` + +## 4. 两两交换链表中的节点 + +```c++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode() : val(0), next(nullptr) {} + * ListNode(int x) : val(x), next(nullptr) {} + * ListNode(int x, ListNode *next) : val(x), next(next) {} + * }; + */ +class Solution { +public: + // exampale : dumhead->1->2->3->4->NULL + ListNode* swapPairs(ListNode* head) { + ListNode* dumhead = new ListNode(0); + dumhead->next = head; + ListNode* cur = dumhead; + while( cur->next != nullptr && cur->next->next != nullptr ){ + ListNode* tmp = cur->next; //1 + ListNode* tmp1 = cur->next->next->next;// 3 + cur->next = cur->next->next; // cur->2 步骤1 + tmp->next = tmp1; // 1->3 步骤2 + cur->next->next = tmp; // 2->1 步骤3 + cur = tmp; + } + return dumhead->next; + } +}; +``` + +## 5. 删除链表倒数第N个节点 + +```c++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode() : val(0), next(nullptr) {} + * ListNode(int x) : val(x), next(nullptr) {} + * ListNode(int x, ListNode *next) : val(x), next(next) {} + * }; + */ +class Solution { +public: + ListNode* removeNthFromEnd(ListNode* head, int n) { + ListNode* dumhead = new ListNode(0); + dumhead->next = head; + ListNode* fast = dumhead; + ListNode* slow = dumhead; + while( n-- ) fast = fast->next; + while( fast->next != nullptr ){ + fast = fast->next; + slow = slow->next; + } + ListNode* tmp = slow->next; + slow->next = slow->next->next; + delete tmp; + return dumhead->next; + } +}; +``` + +## 6. 链表相交 + +```c++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode(int x) : val(x), next(NULL) {} + * }; + */ +//思路: A + B , B + A 注意 (A == NULL && B != NULL) 时, A跳转至 headB; +class Solution { +public: + ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { + ListNode* A = headA; + ListNode* B = headB; + while( A != NULL && B != NULL ){ + if(A == B ) return A; + A = A->next; + B = B->next; + if(A == NULL && B != NULL) A = headB; + if(B == NULL && A != NULL) B = headA; + } + return NULL; + } +}; +``` + +## 7. 寻找链表环入口-[142. 环形链表 II](https://leetcode-cn.com/problems/linked-list-cycle-ii/) + +如下图所示,设链表中环外部分的长度为 a。**slow** 指针进入环后,又走了 b的距离与**fast** 相遇。此时,**fast** 指针已经走完了环的 n 圈,因此**fast**走过的总距离为 **a+n(b+c)+b=*a*+(*n*+1)*b*+*n*c**。 + +![](C:\Users\38032\Desktop\note\寻找链表环入口示意图.png) + +```c++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode(int x) : val(x), next(NULL) {} + * }; + */ +// 思路: x = (y+z)(n-1)+z; 1.快慢指针第一次相遇。 2. 定义cur指针到头节点,同时移动cur与slow相遇,即为入口。 +class Solution { +public: + ListNode *detectCycle(ListNode *head) { + ListNode* fast = head; + ListNode* slow = head; + while( fast != NULL && fast->next != NULL ){ + fast = fast->next->next; + slow = slow->next; + if(fast == slow){ + ListNode* cur = head; + while( cur != slow ){ + cur = cur->next; + slow = slow->next; + } + return cur; + } + } + return NULL; + } +}; +``` + +# 2.哈希表篇 + +## 2. 242 有效的字母异位词 + +```c++ +//思路:哈希表计数 +class Solution { +public: + bool isAnagram(string s, string t) { + int record[26] = {0}; + for(auto c : s){ + record[c-'a']++; + } + for(auto c : t){ + record[c-'a']--; + } + for(int i = 0;i < 26;i++ ){ + if( record[i] != 0) + return false; + } + return true; + } +}; +``` + +## 3. 349 两个有序数组的交集 + +```c++ +//思路 排序+双指针 + +class Solution{ +public: + vector intersection(vectornums1,vectornums2){ + sort(nums1.begin(),nums1.end()); + sort(nums2.begin(),nums2.end()); + int lenth1 = nums1.size(),lenth2 = nums2.size(); + int index1 = 0,index2 = 0; + vector ans; + while( index1 < lenth1 && index2 < lenth2){ + int num1 = nums1[index1],num2 = nums2[index2]; + if( num1 == num2 ){ + if( ans.empty() || num1 == ans.back() ) + ans.push_back(num1); + index1++; + index2++; + } + else if( num1 < num2 ){ + index1++; + } + else index2++; + } + return ans; + } +}; +``` + +## 4. 202 快乐数 + +```c++ +/* 思路: 最终会得到 1,最终会进入循环。*/ +class Solution { +public: + int getnext(int n){ + int ans = 0; + while(n > 0){ + ans += pow(n%10,2); + n /= 10; + } + return ans; + } + + bool isHappy(int n) { + unordered_set s; + while( n != 1 && s.find(n) == s.end() ){ + s.emplace(n); + n = getnext(n); + } + return n == 1; + } +}; +``` + +## 5. 1 两数之和、三数之和、四数之和 + +```c++ +// 哈希表 +class Solution { +public: + vector twoSum(vector& nums, int target) { + unordered_mapmp; + for(int i = 0;i < nums.size();i++ ){ + int tmp = target - nums[i]; + if( mp.find(tmp) != mp.end() && mp[tmp] != i ){ + return vector{mp[tmp],i}; + } + else mp.emplace(nums[i],i); + } + return {}; + } +}; +``` + +```c++ +//三数之和, 思路:当我们需要枚举数组中的两个元素时,如果我们发现随着第一个元素的递增,第二个元素是递减的,那么就可以使用双指针的方法,将枚举的时间复杂度从 O(N^2)减少至 O(N)。 + +class Solution { +public: + vector> threeSum(vector& nums) { + vector> ans; + sort(nums.begin(),nums.end()); + int n = nums.size(); + for(int first = 0;first < n;first++ ){ + if ( first != 0 && nums[first] == nums[first - 1] ) continue; //避免枚举重复元素 + int third = n - 1; + int target = -nums[first]; + for(int second = first + 1;second < n;second++ ){ + if ( second != first + 1 && nums[second] == nums[second - 1] ) continue;//避免枚举重复元素 + while( second < third && nums[second] + nums[first] + nums[third] > 0 ){ + third--; + } + if( second == third ) break; + if( nums[second] + nums[third] == target ){ + ans.push_back({nums[first],nums[second],nums[third]}); + } + } + } + return ans; + } +}; +``` + +```c++ +//四数之和 ,思路类似于三数之和 +class Solution { +public: + vector> fourSum(vector& nums, int target) { + vector> ans; + sort(nums.begin(),nums.end()); + int n = nums.size(); + if(n < 4 ) return ans; + for(int first = 0;first < n - 3;first++ ){ + if(first > 0 && nums[first] == nums[first - 1]) continue; + if (nums[first] + nums[first + 1] + nums[first + 2] + nums[first + 3] > target) break; + if (nums[first] + nums[n - 3] + nums[n - 2] + nums[n - 1] < target) continue; + for(int second = first + 1;second < n - 2;second++ ){ + if(second > first + 1 && nums[second] == nums[second - 1]) continue; + if (nums[first] + nums[second] + nums[second + 1] + nums[second + 2] > target) break; + if (nums[first] + nums[second] + nums[n - 2] + nums[n - 1] < target) continue; + int third = second + 1; + int four = n - 1; + while( third < four ){ + int sum = nums[first] + nums[second] + nums[third] + nums[four]; + if( sum == target){ + ans.push_back({nums[first],nums[second],nums[third],nums[four]}); + while( third < four && nums[third] == nums[third+1] ) third++; + third++; + while( third < four && nums[four] == nums[four-1] ) four--; + four--; + } + else if( sum > target ) four--; + else third++; + } + } + } + return ans; + } +}; +``` + +## 6. [981. 基于时间的键值存储](https://leetcode-cn.com/problems/time-based-key-value-store/) + +```go +class TimeMap { + unordered_map>> m; + +public: + TimeMap() {} + + void set(string key, string value, int timestamp) { + m[key].emplace_back(timestamp, value); + } + + string get(string key, int timestamp) { + auto &pairs = m[key]; + // 使用一个大于所有 value 的字符串,以确保在 pairs 中含有 timestamp 的情况下也返回大于 timestamp 的位置 + pair p = {timestamp, string({127})}; + auto i = upper_bound(pairs.begin(), pairs.end(), p); + if (i != pairs.begin()) { + return (i - 1)->second; + } + return ""; + } +}; + +``` + + + +# 3.数组 + +## 1. 704 二分查找 + +```c++ +//输入数组:严格单调递增 +class Solution { +public: + int search(vector& nums, int target) { + int end = nums.size()-1; + int begin = 0; + while( begin <= end ){ + int mid = begin + (end - begin)/2; + if( nums[mid] < target ) begin = mid + 1; + else if( nums[mid] > target ) end = mid - 1; + else if( nums[mid] == target) return mid; + } + return -1; + } +}; +``` + +## 2. 34 在排序数组中查找元素的第一个和最后一个位置 + +```c++ +class Solution { +public: + int lower_bound(vector& nums,int target){ + int r = nums.size() - 1; + int l = 0; + while( l <= r ){ + int mid = l + (r - l)/2; + if( nums[mid] < target ) l = mid + 1; + else if( nums[mid] > target ) r = mid - 1; + else if( nums[mid] == target ) r = mid - 1; + } + //注意处理边界问题 + if( l >= nums.size() || nums[l] != target ) return -1; + return l; + } + + int upper_bound(vector& nums,int target){ + int r = nums.size() - 1; + int l = 0; + while( l <= r ){ + int mid = l + (r - l)/2; + if( nums[mid] < target ) l = mid + 1; + else if( nums[mid] > target ) r = mid - 1; + else if( nums[mid] == target ) l = mid + 1; + } + //注意处理边界问题 + if( r < 0 || nums[r] != target ) return -1; + return r; + } + + vector searchRange(vector& nums, int target) { + if(nums.size() == 0) return {-1,-1}; + return vector{lower_bound(nums,target),upper_bound(nums,target)}; + } +}; +``` + +## 3. 27 移除元素 + +给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 + +不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 + +元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。 + +```c++ +class Solution { +public: + int removeElement(vector& nums, int val) { + int left = 0, right = nums.size(); + while (left < right) { + if (nums[left] == val) { + nums[left] = nums[right - 1]; + right--; + } else { + left++; + } + } + return left; + } +}; + +``` + +## 4. 977 有序数组的平方 + +```c++ +//双指针,指向数组两端,计算平方值,在比较。 +class Solution { +public: + vector sortedSquares(vector& nums) { + int n = nums.size(); + if( n == 0) return {}; + if( n == 1) return vector{nums[0]*nums[0]}; + vectorans(n,0); + + int pos = 0; + int l = 0,r = 1; + for(int i = 0;i < n-1; i++){ + if(nums[i] < 0 ){ + l = i; + r = i+1; + } + else break; + } + while( l >= 0 || r < n){ + if( l < 0){ + ans[pos++] = nums[r] * nums[r]; + r++; + } + else if( r == n){ + ans[pos++] = nums[l] * nums[l]; + l--; + } + else if( nums[l] * nums[l] < nums[r] * nums[r] ){ + ans[pos++] = nums[l] * nums[l]; + l--; + } + else if( nums[l] * nums[l] >= nums[r] * nums[r] ){ + ans[pos++] = nums[r] * nums[r]; + r++; + } + } + return ans; + } +}; +``` + +## 5. 长度最小的子数组 + +```c++ +class Solution { +public: + int minSubArrayLen(int target, vector& nums) { + int n = nums.size(); + if(n == 0) return 0; + int l = 0; + int r = -1; + int sum = 0; + int ans = INT_MAX; + while( l <= r || r == -1 ){ + while( sum < target && r < n -1){ + r++; + sum += nums[r]; + } + if( sum >= target ){ + int tmp = r - l + 1; + if( ans > tmp ) ans = tmp; + } + sum -= nums[l]; + l++; + } + return ans == INT_MAX? 0:ans; + } +}; +``` + +```c++ +class Solution { +public: + int minSubArrayLen(int s, vector& nums) { + int n = nums.size(); + if (n == 0) { + return 0; + } + int ans = INT_MAX; + int start = 0, end = 0; + int sum = 0; + while (end < n) { + sum += nums[end]; + while (sum >= s) { + ans = min(ans, end - start + 1); + sum -= nums[start]; + start++; + } + end++; + } + return ans == INT_MAX ? 0 : ans; + } +}; + +``` + +## 6. 59 螺旋矩阵 + +```c++ +class Solution { +public: + vector> generateMatrix(int n) { + vector> res(n, vector(n, 0)); // 使用vector定义一个二维数组 + int startx = 0, starty = 0; // 定义每循环一个圈的起始位置 + int loop = n / 2; // 每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理 + int mid = n / 2; // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2) + int count = 1; // 用来给矩阵中每一个空格赋值 + int offset = 1; // 每一圈循环,需要控制每一条边遍历的长度 + int i,j; + while (loop --) { + i = startx; + j = starty; + + // 下面开始的四个for就是模拟转了一圈 + // 模拟填充上行从左到右(左闭右开) + for (j = starty; j < starty + n - offset; j++) { + res[startx][j] = count++; + } + // 模拟填充右列从上到下(左闭右开) + for (i = startx; i < startx + n - offset; i++) { + res[i][j] = count++; + } + // 模拟填充下行从右到左(左闭右开) + for (; j > starty; j--) { + res[i][j] = count++; + } + // 模拟填充左列从下到上(左闭右开) + for (; i > startx; i--) { + res[i][j] = count++; + } + + // 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1) + startx++; + starty++; + + // offset 控制每一圈里每一条边遍历的长度 + offset += 2; + } + + // 如果n为奇数的话,需要单独给矩阵最中间的位置赋值 + if (n % 2) { + res[mid][mid] = count; + } + return res; + } +}; + +``` + + + + + +# 4.字符串 + +## 1. 大数加减法 + +```c++ + +//"123456789123456789123456789-1234567891456789" +// 1234567891456789 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define ll long long +using namespace std; + +string addstring(string s1, string s2) { + int i = s1.length() - 1; + int j = s2.length() - 1; + int add = 0; + string ss = ""; + while (i >= 0 || j >= 0|| add != 0) { + if (i >= 0) + add = add + s1[i--] - '0'; + if (j >= 0) + add = add + s2[j--] - '0'; + ss = ss + to_string(add % 10); + add = add / 10; + } + reverse(ss.begin(), ss.end()); + return ss; +} + +//大数减法 +string sub(string s1,string s2){ + bool minus = false; + if( s1 < s2 ){ + swap(s1,s2); + minus = true; + } + int i = s1.size() - 1; + int j = s2.size() - 1; + int flag = 0; + string ans = ""; + while( i >= 0 && j >= 0 ){ + int tmp = tmp - flag; + if( s1[i] >= s2[j] ){ + tmp = s1[i] - s2[j]; + ans += to_string(tmp); + flag = 0; + } + else{ + tmp = s1[i] - s2[j] + 10; + ans += to_string(tmp); + flag = 1; + } + i--; + j--; + } + // 处理较大数的剩余部分 + while( i >= 0 ){ + if(flag == 0){ + ans += s1[i]; + } + else{ + int tmp = s1[i] - '0' - flag; + ans += to_string(tmp); + flag = 0; + } + i--; + } + // 翻转 + reverse(ans.begin(),ans.end()); + //去除前导 0 + int k = 0; + while( k < ans.size() && ans[k] == '0') k++; + if( k == ans.size() ){ + ans = "0"; + } + else ans = ans.substr(k); + // 结果是否为负 + return minus?"-" + ans:ans; +} + +int main() { + string s1, s2; + cin >> s1 >> s2; + cout << sub(s1, s2) << endl; + system("pause"); + return 0; +} +``` + +## 2. 大数乘法 + +```c++ +//使用大小为 n + m 的数组,num1的第i位乘以num2的第j位,结果 +对应存放在数组的i+j+1的位置。 +class Solution { +public: + string multiply(string num1, string num2) { + int n = num1.size(); + int m = num2.size(); + vector result(n + m,0); + for(int i = n - 1; i >= 0; i--){ + for(int j = m - 1;j >= 0; j--){ + int tmp = (num1[i] - '0') * (num2[j] - '0'); + tmp += result[i + j + 1]; + result[i + j] += tmp / 10; //注意 这里是 += + result[i + j + 1] = tmp % 10; + } + } + int i = 0; + while( i < n + m && result[i] == 0 ){ + i++; + } + string ans; + for(; i < n + m; i++){ + ans.push_back(result[i] + '0'); + } + return ans.size() == 0?"0":ans; + } +}; +``` + +## 3. 翻转字符串 + +```c++ +class Solution { +public: + void reverseString(vector& s) { + int end = s.size() - 1; + int begin = 0; + while( begin < end ){ + char c = s[begin]; + s[begin] = s[end]; + s[end] = c; + begin++; + end--; + } + } +}; +``` + +## 4. 翻转字符串 II + +```c++ +/*给定一个字符串 s 和一个整数 k,你需要对从字符串开头算起的每隔 2k 个字符的前 k 个字符进行反转。 +如果剩余字符少于 k 个,则将剩余字符全部反转。 +如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。 +*/ +class Solution { +public: + void reverse(string &s,int begin,int end){ + while ( begin < end ){ + swap(s[begin],s[end]); + begin++; + end--; + } + } + + string reverseStr(string s, int k) { + int n = s.size() - 1; + int begin = 0; + while ( 1 ){ + if( begin > n ) break; + int len = n - begin + 1; + if( len < k ) reverse(s,begin,n); + else reverse(s,begin,begin + k -1); + begin += 2*k; + } + return s; + } +}; +``` + +## 5. 替换空格 + +```c++ +/*思路:首先扩充数组到每个空格替换成"%20"之后的大小。 + +然后从后向前替换空格,也就是双指针法,过程如下: + +i指向新长度的末尾,j指向旧长度的末尾。*/ + +class Solution { +public: + string replaceSpace(string s) { + int len = s.size(); + int cnt = 0; + for(auto &c : s ){ + if( c == ' '){ + cnt++; + } + } + s.resize(len + 2 * cnt); + for(int i = len - 1,j = s.size() - 1; i < j; i--,j--){ + if(s[i] != ' '){ + s[j] = s[i]; + } + else{ + s[j - 2] = '%'; + s[j - 1] = '2'; + s[j] = '0'; + j -= 2; + } + } + return s; + } +}; +``` + +## 6. 翻转字符串里的单词 + +```c++ +//思路:字符串s整体反转,再逐个移位,反转单词。 +class Solution { +public: + string reverseWords(string s) { + reverse(s.begin(),s.end()); + int pos = 0; + int n = s.size(); + for(int start = 0; start < n; start++){ + if( s[start] != ' ' ){ + int end = start; + // 填一个空白字符然后将pos移动到下一个单词的开头位置 + if( pos != 0) s[pos++] = ' '; + // 循环遍历至单词的末尾 + while( end < n && s[end] != ' ') s[pos++] = s[end++]; + // 反转整个单词 + reverse(s.begin() + pos - (end - start),s.begin() + pos); + // 更新start,去找下一个单词 + start = end ; + } + } + //删除多余位置 + s.erase(s.begin() + pos,s.end()); + return s; + } +}; +``` + +## 7. 左旋转字符串 + +```c++ +//思路:先局部反转前n个字符串和剩余字符串,再整体反转。 +class Solution { +public: + string reverseLeftWords(string s, int n) { + //反转前n个字符串 + reverse(s.begin(),s.begin() + n); + //反转剩余字符串 + reverse(s.begin() + n,s.end()); + //整体反转 + reverse(s.begin(),s.end()); + return s; + } +}; +``` + +## 8. 28 实现 strStr()-KMP算法 + +字符串的**前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串**;**后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串**。 + +**//前缀表统一减一 C++代码实现** + +```c++ +/*思路:利用前缀表即next数组 +前缀表有什么作用呢? +前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。 + +复杂度分析 +时间复杂度:O(n+m),其中 n是字符串haystack 的长度,m 是字符串needle 的长度。我们至多需要遍历两字符串一次。 +空间复杂度:O(m),其中 m是字符串needle 的长度。我们只需要保存字符串needle 的前缀函数。*/ + +class Solution { +public: + void getnext(int *next,const string &s){ + int j = -1; + int n = s.size(); + next[0] = j;// 此行容易忘记 + for (int i = 1; i < n; i++){ + //前后缀不同 + while (j >= 0 && s[ i ] != s[j + 1]){ + j = next[j];//回退 + } + //找到相同前后缀 + if (s[i] == s[j + 1]){ + j++; + } + next[i] = j; + } + } + + int strStr(string haystack, string needle) { + if(needle.size() == 0) return 0; + int next[needle.size()]; + getnext(next,needle); + int j = -1; + for (int i = 0 ;i < haystack.size(); i++){ + //不匹配 + while(j >= 0 && haystack[i] != needle[j + 1]){ + j = next[j];//寻找之后的匹配 + } + //匹配,i,j同时后移 + if( haystack[i] == needle[j + 1]){ + j++;//i的增加在for循环里面 + } + if(j == needle.size() -1){// j移动到了模式串的末尾,文本串s里出现了模式串t + return i - j;//当前在文本串匹配模式串的位置i 减去 模式串的长度,就是文本串字符串中出现模式串的第一个位置 + } + } + return -1; + } +}; +``` + +**前缀表(不减一)C++实现** + +那么前缀表就不减一了,也不右移的,到底行不行呢?行! + +我之前说过,这仅仅是KMP算法实现上的问题,如果就直接使用前缀表可以换一种回退方式,找j=next[j-1] 来进行回退。 + +主要就是j=next[x]这一步最为关键! + +```c++ +class Solution{ +public: + void getnext(int *next,const string &s){ + int j = 0; + next[0] = j;// 此行容易忘记 + for (int i = 1; i < s.size(); i++){ + while( j >= 1 && s[i] != s[j]){ + j = next[j - 1]; + } + if(s[i] == s[j]){ + j++; + } + next[i] = j; + } + } + + int strStr(string haystack,string needle){ + if(needle.size() == 0) return 0; + int j = 0; + int next[needle.size()]; + getnext(next,needle); + for(int i = 0; i < haystack.size(); i++){ + while(j >= 1 && haystack[i] != needle[j]){ + j = next[j - 1]; + } + if(haystack[i] == needle[j]){ + j++; + } + if(j == needle.size() ){ + return i - j + 1 ; + } + } + return -1; + } +}; +``` + +## 9. 459重复的子字符串 + +```c++ +//思路:,求出前缀表,然后数组长度减去最长相同前后缀的长度相当于是第一个周期的长度,也就是一个周期的长度,如果这个周期可以被整除,就说明整个数组就是这个周期的循环。 +class Solution { +public: + void getnext(int *next,const string&s){ + int j = -1; + next[0] = j;//起始位置为-1,此行容易忘记 + for(int i = 1; i < s.size(); i++){ + while (j >= 0 && s[i] != s[j + 1]){ + j = next[j]; + } + if (s[i] == s[j + 1]){ + j++; + } + next[i] = j; + } + } + + bool repeatedSubstringPattern(string s) { + int len = s.size(); + int next[len]; + getnext(next,s); + if(next[len - 1] != -1 && len % (len - (next[len - 1] + 1)) == 0){ + return true; + } + return false; + } +}; +``` + +# 5.栈与队列 + +## 1. [232. 用栈实现队列](https://leetcode-cn.com/problems/implement-queue-using-stacks/) + +```c++ +//思路:两个栈,一个Stackin,另一个Stackout +class MyQueue { +private: + stacks1; + stacks2; +public: + /** Initialize your data structure here. */ + MyQueue() { + + } + + /** Push element x to the back of queue. */ + void push(int x) { + s1.push(x); + } + + /** Removes the element from in front of queue and returns that element. */ + int pop() { + if(s2.empty()){ + while (!s1.empty()){ + int tmp = s1.top(); + s1.pop(); + s2.push(tmp); + } + } + int tmp = s2.top(); + s2.pop(); + return tmp; + } + + /** Get the front element. */ + int peek() { + //调用自身pop() + int tmp = this->top(); + s2.push(tmp); + return tmp; + } + + /** Returns whether the queue is empty. */ + bool empty() { + return s2.empty() && s1.empty(); + } +}; + +/** + * Your MyQueue object will be instantiated and called as such: + * MyQueue* obj = new MyQueue(); + * obj->push(x); + * int param_2 = obj->pop(); + * int param_3 = obj->peek(); + * bool param_4 = obj->empty(); + */ +``` + +## 2. [225 用队列实现栈](https://leetcode-cn.com/problems/implement-stack-using-queues/) + +```c++ +//两个队列,q1存放栈元素,q2为辅助栈 +class MyStack { +public: + queue q1; + queue q2; + /** Initialize your data structure here. */ + MyStack() { + + } + + /** Push element x onto stack. */ + void push(int x) { + q2.push(x); + while (!q1.empty()){ + q2.push(q1.front()); + q1.pop(); + } + swap(q1,q2); + } + + /** Removes the element on top of the stack and returns that element. */ + int pop() { + int tmp = q1.front(); + q1.pop(); + return tmp; + } + + /** Get the top element. */ + int top() { + return q1.front(); + } + + /** Returns whether the stack is empty. */ + bool empty() { + return q1.empty(); + } +}; + +/** + * Your MyStack object will be instantiated and called as such: + * MyStack* obj = new MyStack(); + * obj->push(x); + * int param_2 = obj->pop(); + * int param_3 = obj->top(); + * bool param_4 = obj->empty(); + */ +``` + +```c++ +//思路:一个队列实现 +class MyStack { +public: + queue q; + /** Initialize your data structure here. */ + MyStack() { + + } + + /** Push element x onto stack. */ + void push(int x) { + q.push(x); + } + + /** Removes the element on top of the stack and returns that element. */ + int pop() { + int n = q.size(); + n--; + while (n--){ + int tmp = q.front(); + q.pop(); + q.push(tmp); + } + int ans = q.front(); + q.pop(); + return ans; + } + + /** Get the top element. */ + int top() { + return q.back(); + } + + /** Returns whether the stack is empty. */ + bool empty() { + return q.empty(); + } +}; + +/** + * Your MyStack object will be instantiated and called as such: + * MyStack* obj = new MyStack(); + * obj->push(x); + * int param_2 = obj->pop(); + * int param_3 = obj->top(); + * bool param_4 = obj->empty(); + */ +``` + +## [3. 20. 有效的括号](https://leetcode-cn.com/problems/valid-parentheses/) + +```c++ +//思路:使用栈,左括号入栈,右括号匹配左括号 +class Solution { +public: + bool is_ok(char a,char b){ + if( (a=='[' && b == ']' ) || ( a=='(' && b == ')' ) || (a=='{' && b == '}' ) ) return true; + return false; + } + + bool isValid(string s) { + stack stk; + for (int i = 0; i < s.size(); i++){ + if (s[i] == ')' || s[i] == '}' || s[i] == ']'){ + if(stk.empty()) return false; + char tmp = stk.top(); + if ( is_ok(tmp,s[i]) ) stk.pop(); + else return false; + } + else{ + stk.push(s[i]); + } + } + return stk.empty(); + } +}; + +``` + +## [4.1047 删除字符串中的所有相邻重复项](https://leetcode-cn.com/problems/remove-all-adjacent-duplicates-in-string/) + +```c++ +//string 替代 栈的作用 +class Solution { +public: + string removeDuplicates(string s) { + string result; + for(char c : s) { + if(result.empty() || result.back() != c) { + result.push_back(c); + } + else { + result.pop_back(); + } + } + return result; + } +}; +``` + +```c++ +//使用栈 +class Solution { +public: + string removeDuplicates(string s) { + stack stk; + for(auto &c : s){ + if(stk.empty() || c != stk.top()){ + stk.push(c); + } + else stk.pop(); + } + string ans; + while(!stk.empty()){ + ans += stk.top(); + stk.pop(); + } + reverse(ans.begin(),ans.end()); + return ans; + } +}; +``` + +## 5.[150. 逆波兰表达式求值](https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/) + +```c++ +//思路:利用栈 +class Solution { +public: + int evalRPN(vector& tokens) { + stack stk; + for(auto &str : tokens){ + if(str == "+" || str == "-" || str == "*" || str == "/"){ + int n1 = stk.top();stk.pop(); + int n2 = stk.top();stk.pop(); + if( str == "+" ){ + stk.push(n2 + n1); + } + else if(str == "-" ){ + stk.push(n2 - n1); + } + else if(str == "*"){ + stk.push(n2 * n1); + } + else stk.push(n2 / n1); + } + else{ + stk.push(atoi(str.c_str());//使用库函数 atoi() c_str() stoi() string -> int + } + } + return stk.top(); + } +}; +``` + +## 6.239 滑动窗口最大值 + +```c++ +/*思路: + 当滑动窗口向右移动时,我们需要把一个新的元素放入队列中。为了保持队列的性质,我们会不断地将新的元素与队尾的元素相比较,如果前者大于等于后者,那么队尾的元素就可以被永久地移除,我们将其弹出队列。我们需要不断地进行此项操作,直到队列为空或者新的元素小于队尾的元素。 + 由于队列中下标对应的元素是严格单调递减的,因此此时队首下标对应的元素就是滑动窗口中的最大值。但与方法一中相同的是,此时的最大值可能在滑动窗口左边界的左侧,并且随着窗口向右移动,它永远不可能出现在滑动窗口中了。因此我们还需要不断从队首弹出元素,直到队首元素在窗口中为止。 + 为了可以同时弹出队首和队尾的元素,我们需要使用双端队列。满足这种单调性的双端队列一般称作「单调队列」。*/ +class Solution { +public: + vector maxSlidingWindow(vector& nums, int k) { + int n = nums.size(); + deque q; + for (int i = 0; i < k; i++){ + while ( !q.empty() && nums[i] >= nums[q.back()] ){ + q.pop_back(); + } + q.push_back(i); + } + vectorans; + ans.push_back(nums[q.front()]); + for (int i = k; i < n; i++){ + while ( !q.empty() && nums[i] >= nums[q.back()] ){ + q.pop_back(); + } + q.push_back(i); + while ( !q.empty() && q.front() <= i - k ){ + q.pop_front(); + } + ans.push_back(nums[q.front()]); + } + return ans; + } +}; + +//时空复杂度:o(n) +``` + + + +```c++ +class Solution { +private: + class MyQueue { //单调队列(从大到小) + public: + deque que; // 使用deque来实现单调队列 + // 每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。 + // 同时pop之前判断队列当前是否为空。 + void pop(int value) { + if (!que.empty() && value == que.front()) { + que.pop_front(); + } + } + // 如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。 + // 这样就保持了队列里的数值是单调从大到小的了。 + void push(int value) { + while (!que.empty() && value > que.back()) { + que.pop_back(); + } + que.push_back(value); + + } + // 查询当前队列里的最大值 直接返回队列前端也就是front就可以了。 + int front() { + return que.front(); + } + }; +public: + vector maxSlidingWindow(vector& nums, int k) { + MyQueue que; + vector result; + for (int i = 0; i < k; i++) { // 先将前k的元素放进队列 + que.push(nums[i]); + } + result.push_back(que.front()); // result 记录前k的元素的最大值 + for (int i = k; i < nums.size(); i++) { + que.pop(nums[i - k]); // 滑动窗口移除最前面元素 + que.push(nums[i]); // 滑动窗口前加入最后面的元素 + result.push_back(que.front()); // 记录对应的最大值 + } + return result; + } +}; + +``` + +## [7. 347. 前 K 个高频元素](https://leetcode-cn.com/problems/top-k-frequent-elements/) + +```c++ +//哈希表 + 优先队列 +/* +大家对这个比较运算在建堆时是如何应用的,为什么左大于右就会建立小顶堆,反之建立大顶堆比较困惑。 + +确实 例如我们在写快排的cmp函数的时候,return left>right 就是从大到小,return leftm; + static bool cmp(pair&a,pair&b){ + return a.second>b.second; + } + + vector topKFrequent(vector& nums, int k) { + priority_queue, vector>, decltype(&cmp)> q(cmp); + for(auto i:nums){ + m[i]++; + } + for(auto it=m.begin();it!=m.end();it++){ + if(q.size()==k){ + if(q.top().second < it->second){ + q.pop(); + q.emplace(it->first,it->second); + } + } + else{ + q.emplace(it->first,it->second); + } + } + vectorv; + while(!q.empty()){ + v.push_back(q.top().first); + q.pop(); + } + return v; + } +}; +``` + +```c++ +class Solution { +public: + unordered_mapmp; + + struct cmp{//重写仿函数 + bool operator()(pair&a,pair&b)const{ + return a.second > b.second; + } + }; + vector topKFrequent(vector& nums, int k) { + priority_queue,vector>,cmp> q; + for (int i = 0; i < nums.size(); i++){ + mp[nums[i]]++; + } + + for (unordered_map::iterator it = mp.begin(); it != mp.end(); it++){ + if(q.size() < k) + q.push(pair(it->first,it->second)); + else{ + if( q.top().second < it->second ){ + q.pop(); + q.push(pair(it->first,it->second)); + } + } + } + vector ans; + while(!q.empty()){ + ans.push_back(q.top().first); + q.pop(); + } + return ans; + } +}; +``` + +# 6.二叉树 + +## 1 . [144. 二叉树的前序遍历](https://leetcode-cn.com/problems/binary-tree-preorder-traversal/) + +```c++ +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode() : val(0), left(nullptr), right(nullptr) {} + * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} + * }; + */ +class Solution{ +public: + + vector preorderTraversal(TreeNode* root) { + vectorans; + if(root == nullptr) return ans; + stack stk; + TreeNode* tmp = root; + while( !stk.empty() || tmp !=nullptr ){ + //入栈 + while( tmp!= nullptr ){ + ans.push_back(tmp->val); + stk.push(tmp); + tmp = tmp->left; + } + //出栈 + tmp = stk.top(); + stk.pop(); + tmp = tmp->right; + } + return ans; + } +}; +// 第二种 +class Solution { +public: + vector preorderTraversal(TreeNode* root) { + vector result; + stack st; + if (root != NULL) st.push(root); + while (!st.empty()) { + TreeNode* node = st.top(); + if (node != NULL) { + st.pop(); + if (node->right) st.push(node->right); // 右 + if (node->left) st.push(node->left); // 左 + st.push(node); // 中 + st.push(NULL); + } + else { + st.pop(); + node = st.top(); + st.pop(); + result.push_back(node->val); + } + } + return result; + } +}; + +``` + +## 2. [94. 二叉树的中序遍历](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/) + +```c++ +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode() : val(0), left(nullptr), right(nullptr) {} + * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} + * }; + */ +class Solution { +public: + vector inorderTraversal(TreeNode* root) { + vectorans; + if( root == nullptr) return ans; + stackstk; + TreeNode* node = root; + while( !stk.empty() || node != nullptr ){ + while( node != nullptr){ + stk.push(node); + node = node->left; + } + node = stk.top(); + stk.pop(); + ans.push_back(node->val); + node = node->right; + } + return ans; + } +}; +// 第二种 +class Solution { +public: + vector inorderTraversal(TreeNode* root) { + vector result; + stack st; + if (root != NULL) st.push(root); + while (!st.empty()) { + TreeNode* node = st.top(); + if (node != NULL) { + st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中 + if (node->right) st.push(node->right); // 添加右节点(空节点不入栈) + + st.push(node); // 添加中节点 + st.push(NULL); // 中节点访问过,但是还没有处理,加入空节点做为标记。 + + if (node->left) st.push(node->left); // 添加左节点(空节点不入栈) + } + else { // 只有遇到空节点的时候,才将下一个节点放进结果集 + st.pop(); // 将空节点弹出 + node = st.top(); // 重新取出栈中元素 + st.pop(); + result.push_back(node->val); // 加入到结果集 + } + } + return result; + } +}; + +``` + +## 3. [145. 二叉树的后序遍历](https://leetcode-cn.com/problems/binary-tree-postorder-traversal/) + +```c++ +// 第二种 +class Solution { +public: + vector postorderTraversal(TreeNode* root) { + vector result; + stack st; + if (root != NULL) st.push(root); + while (!st.empty()) { + TreeNode* node = st.top(); + if (node != NULL) { + st.pop(); + st.push(node); // 中 + st.push(NULL); + + if (node->right) st.push(node->right); // 右 + if (node->left) st.push(node->left); // 左 + + } + else { + st.pop(); + node = st.top(); + st.pop(); + result.push_back(node->val); + } + } + return result; + } +}; + +``` + +## 4.[226. 翻转二叉树](https://leetcode-cn.com/problems/invert-binary-tree/) + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ + +//go版本 +func invertTree(root *TreeNode) *TreeNode { + if root == nil{ + return nil + } + tmp := root.Left + root.Left = root.Right + root.Right = tmp + invertTree(root.Left) + invertTree(root.Right) + return root +} +``` + +## 5.[101. 对称二叉树](https://leetcode-cn.com/problems/symmetric-tree/) + +```c++ +class Solution { +public: + bool check(TreeNode *u, TreeNode *v) { + queue q; + q.push(u); q.push(v); + while (!q.empty()) { + u = q.front(); q.pop(); + v = q.front(); q.pop(); + if (!u && !v) continue; + if ((!u || !v) || (u->val != v->val)) return false; + + q.push(u->left); + q.push(v->right); + + q.push(u->right); + q.push(v->left); + } + return true; + } + + bool isSymmetric(TreeNode* root) { + return check(root, root); + } +}; +``` + +```go +func isSymmetric(root * TreeNode) bool { + u,v := root,root + q := []*TreeNode{} + q = append(q,u) + q = append(q,v) + for len(q) > 0 { + u,v := q[0],q[1] + q = q[2:] + if u == nil && v == nil { + continue + } + if u == nil || v == nil { + return false + } + if u.val != v.val { + return false + } + q = append(q,u.Left) + q = append(q,v.Right) + + q = append(q,u.Right) + q = append(q,v.Left) + } + return true +} +``` + +## 6. [104. 二叉树的最大深度](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +// go 语言 + +func maxDepth(root *TreeNode) int { + if root == nil{ + return 0 + } + return max(maxDepth(root.Left) , maxDepth(root.Right)) + 1 +} + +func max(a,b int) int { + if a > b{ + return a + }else{ + return b + } +} +``` + diff --git "a/\351\235\242\347\273\217\346\225\264\347\220\206\345\217\221\345\270\203\347\211\210.md" "b/\351\235\242\347\273\217\346\225\264\347\220\206\345\217\221\345\270\203\347\211\210.md" new file mode 100644 index 0000000000..f8c97fe961 --- /dev/null +++ "b/\351\235\242\347\273\217\346\225\264\347\220\206\345\217\221\345\270\203\347\211\210.md" @@ -0,0 +1,2515 @@ +[TOC] + +# 1. 语言基础 (C/C++) + +#### (0) 指针和引用的区别 +* 指针是一个新的变量,指向另一个变量的地址,我们可以通过访问这个地址来修改另一个变量;而引用是一个别名,对引用的操作就是对变量的本身进行操作, +* 指针可以有多级,引用只有一级 +* 传参的时候,使用指针的话需要解引用才能对参数进行修改,而使用引用可以直接对参数进行修改 +* 指针的大小一般是4个字节,引用的大小取决于被引用对象的大小 +* 指针可以为空,引用不可以。 +* 本质:**引用的本质在c++内部实现是一个指针常量.** + +#### (1)在函数参数传递的时候,什么时候使用指针,什么时候使用引用? +* 需要返回函数内局部静态变量的内存的时候用指针。使用指针传参需要开辟内存,用完要记得释放指针,不然会内存泄漏。而返回局部变量的引用是没有意义的 +* 对栈空间大小比较敏感(比如递归)的时候使用引用。使用引用传递不需要创建临时变量,开销要更小 +* 类对象作为参数传递的时候使用引用,这是C++类对象传递的标准方式 +#### (2) 堆和栈有什么区别 +* 从定义上:堆是由new和malloc开辟的一块内存,由程序员手动管理,栈是编译器自动管理的内存,存放函数的参数和局部变量。 +* 堆空间因为会有频繁的分配释放操作,会产生内存碎片 +* 堆的生长空间向上,地址越来越大,栈的生长空间向下,地址越来越小 +#### (3)堆快一点还是栈快一点?(字节提前批一面) +栈快一点。因为操作系统会在底层对栈提供支持,会分配专门的寄存器存放栈的地址,栈的入栈出栈操作也十分简单,并且有专门的指令执行,所以栈的效率比较高也比较快。而堆的操作是由C/C++函数库提供的,在分配堆内存的时候需要一定的算法寻找合适大小的内存。并且获取堆的内容需要两次访问,第一次访问指针,第二次根据指针保存的地址访问内存,因此堆比较慢。 +#### (4) new和delete是如何实现的,new 与 malloc的异同处 +在new一个对象的时候,首先会调用malloc为对象分配内存空间,然后调用对象的构造函数。delete会调用对象的析构函数,然后调用free回收内存。 + +new与malloc都会分配空间,但是new还会调用对象的构造函数进行初始化,malloc需要给定空间大小,而new只需要对象名 +#### (5)既然有了malloc/free,C++中为什么还需要new/delete呢? +https://blog.csdn.net/leikun153/article/details/80612130 + +* malloc/free和new/delete都是用来申请内存和回收内存的。 +* 在对非基本数据类型的对象使用的时候,对象创建的时候还需要执行构造函数,销毁的时候要执行析构函数。而malloc/free是库函数,是已经编译的代码,所以不能把构造函数和析构函数的功能强加给malloc/free。 +#### (6) C和C\+\+的区别 +包括但不限于: +* C是面向过程的语言,C\+\+是面向对象的语言,C\+\+有“封装,继承和多态”的特性。封装隐藏了实现细节,使得代码模块化。继承通过子类继承父类的方法和属性,实现了代码重用。多态则是“一个接口,多个实现”,通过子类重写父类的虚函数,实现了接口重用。 +* C和C\+\+内存管理的方法不一样,C使用malloc/free,C\+\+除此之外还用new/delete +* C\+\+中还有函数重载和引用等概念,C中没有 +> #### (7)delete和delete\[\]的区别 +> + +* delete只会调用一次析构函数,而delete\[\]会调用每个成员的析构函数 + +* 用new分配的内存用delete释放,用new\[\]分配的内存用delete\[\]释放 + +> #### (8) C++、Java的联系与区别,包括语言特性、垃圾回收、应用场景等(java的垃圾回收机制) + +包括但不限于: +* C\+\+ 和Java都是面向对象的语言,C\+\+是编译成可执行文件直接运行的,JAVA是编译之后在JAVA虚拟机上运行的,因此JAVA有良好的跨平台特性,但是执行效率没有C\+\+ 高。 +* C\+\+的内存管理由程序员手动管理,JAVA的内存管理是由Java虚拟机完成的,它的垃圾回收使用的是标记-回收算法 +* C\+\+有指针,Java没有指针,只有引用 +* JAVA和C\+\+都有构造函数,但是C\+\+有析构函数但是Java没有 +> #### (9)C++和python的区别 + +包括但不限于: +1. python是一种脚本语言,是解释执行的,而C\+\+是编译语言,是需要编译后在特定平台运行的。python可以很方便的跨平台,但是效率没有C\+\+高。 +2. python使用缩进来区分不同的代码块,C\+\+使用花括号来区分 +3. C\+\+中需要事先定义变量的类型,而python不需要,python的基本数据类型只有数字,布尔值,字符串,列表,元组等等 +4. python的库函数比C\+\+的多,调用起来很方便 +#### (10) Struct和class的区别 +* 使用struct时,它的成员的访问权限默认是public的,而class的成员默认是private的 +* struct的继承默认是public继承,而class的继承默认是private继承 +* class可以用作模板,而struct不能 +#### (11) define 和const的联系与区别(编译阶段、安全性、内存占用等) + 联系:它们都是定义常量的一种方法。 + + 区别: +* define定义的常量没有类型,只是进行了简单的替换,可能会有多个拷贝,占用的内存空间大,const定义的常量是有类型的,存放在静态存储区,只有一个拷贝,占用的内存空间小。 +* define定义的常量是在预处理阶段进行替换,而const在编译阶段确定它的值。 +* define不会进行类型安全检查,而const会进行类型安全检查,安全性更高。 +* const可以定义函数而define不可以。 +#### (12) 在C\+\+中const的用法(定义,用途) +* const修饰类的成员变量时,表示常量不能被修改 +* const修饰类的成员函数,表示该函数不会修改类中的数据成员,不会调用其他非const的成员函数 + +#### (13) C++中的static用法和意义 +static的意思是静态的,可以用来修饰变量,函数和类成员。 +* 变量:被static修饰的变量就是静态变量,它会在程序运行过程中一直存在,会被放在静态存储区。局部静态变量的作用域在函数体中,全局静态变量的作用域在这个文件里。 +* 函数:被static修饰的函数就是静态函数,静态函数只能在本文件中使用,不能被其他文件调用,也不会和其他文件中的同名函数冲突。 +* 类:(1)而在类中,被static修饰的成员变量是类静态成员,这个静态成员会被类的多个对象共用。(2)被static修饰的成员函数也属于静态成员,不是属于某个对象的,访问这个静态函数不需要引用对象名,而是通过引用类名来访问。 + +【note】静态成员函数要访问非静态成员时,要用过对象来引用。局部静态变量在函数调用结束后也不会被回收,会一直在程序内存中,直到该函数再次被调用,它的值还是保持上一次调用结束后的值。 + +注意和const的区别。const强调值不能被修改,而static强调唯一的拷贝,对所有类的对象都共用。 +#### (14) 计算下面几个类的大小: +```C++ +class A {}; +int main(){ + cout< +```C++ +class A { static int a; }; +int main(){ + cout<用程序调用mmap(),磁盘上的数据会通过DMA被拷贝的内核缓冲区,接着操作系统会把这段内核缓冲区与应用程序共享,这样就不需要把内核缓冲区的内容往用户空间拷贝。应用程序再调用write(),操作系统直接将内核缓冲区的内容拷贝到socket缓冲区中,这一切都发生在内核态,最后,socket缓冲区再把数据发到网卡去。 + +#### (35) 介绍C++所有的构造函数 +C\+\+中的构造函数主要有三种类型:默认构造函数、重载构造函数和拷贝构造函数 +* 默认构造函数是当类没有实现自己的构造函数时,编译器默认提供的一个构造函数。 +* 重载构造函数也称为一般构造函数,一个类可以有多个重载构造函数,但是需要参数类型或个数不相同。可以在重载构造函数中自定义类的初始化方式。 +* 拷贝构造函数是在发生对象复制的时候调用的。 +#### (36) 什么情况下会调用拷贝构造函数(三种情况) +* 对象以值传递的方式传入函数参数 + + >如 ` void func(Dog dog){};` +* 对象以值传递的方式从函数返回 + + >如 ` Dog func(){ Dog d; return d;}` +* 对象需要通过另外一个对象进行初始化 + +详见:[C++拷贝构造函数详解](https://blog.csdn.net/lwbeyond/article/details/6202256) +#### (37) 结构体内存对齐方式和为什么要进行内存对齐? +因为结构体的成员可以有不同的数据类型,所占的大小也不一样。同时,由于CPU读取数据是按块读取的,内存对齐可以使得CPU一次就可以将所需的数据读进来。 + +对齐规则: +* 第一个成员在与结构体变量偏移量为0的地址 +* 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 +* 对齐数=编译器默认的一个对齐数 与 该成员大小的较小值。 +* linux 中默认为4 +* vs 中的默认值为8 +结构体总大小为最大对齐数的整数倍(每个成员变量除了第一个成员都有一个对齐数) + +#### (38) 内存泄露的定义,如何检测与避免? +动态分配内存所开辟的空间,在使用完毕后未手动释放,导致一直占据该内存,即为内存泄漏。 + +造成内存泄漏的几种原因: + +1)类的构造函数和析构函数中new和delete没有配套 + +2)在释放对象数组时没有使用delete\[\],使用了delete + +3)没有将基类的析构函数定义为虚函数,当基类指针指向子类对象时,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确释放,因此造成内存泄露 + +4)没有正确的清楚嵌套的对象指针 + +避免方法: +1. malloc/free要配套 +2. 使用智能指针; +3. 将基类的析构函数设为虚函数; +#### (39) C++的智能指针有哪些 +C++中的智能指针有auto_ptr,shared_ptr,weak_ptr和unique_ptr。智能指针其实是将指针进行了封装,可以像普通指针一样进行使用,同时可以自行进行释放,避免忘记释放指针指向的内存地址造成内存泄漏。 +* auto_ptr是较早版本的智能指针,在进行指针拷贝和赋值的时候,新指针直接接管旧指针的资源并且将旧指针指向空,但是这种方式在需要访问旧指针的时候,就会出现问题。 +* unique_ptr是auto_ptr的一个改良版,不能赋值也不能拷贝,保证一个对象同一时间只有一个智能指针。 +* shared_ptr可以使得一个对象可以有多个智能指针,当这个对象所有的智能指针被销毁时就会自动进行回收。(内部使用计数机制进行维护) +* weak_ptr是为了协助shared_ptr而出现的。它不能访问对象,只能观测shared_ptr的引用计数,防止出现死锁。 +* weak _ ptr是 一 种 不 控 制 对 象 生 命 周 期 的 智 能 指 针 , 它 指 向 一 个 shared_ptr 管 理 的 对 象 . 进 行 该 对 象 的 内 存 管 理 的 是 那 个 强 引 用 的 shared_ptr. weak_ptr 只 是 提 供 了 对 管 理 对 象 的 一 个 访 问 手 段 。 weak_ptr 设 计 的 目 的 是 为 配 合 shared_ptr 而 引 入 的 一 种 智 能 指 针 来 协 助 shared_ptr 工 作 , 它 只 可 以 从 一 个 shared_ptr 或 另 一 个 weak_ptr 对 象 构 造 , 它 的 构 造 和 析 构 不 会 引 起 引 用 记 数 的 增 加 或 减 少 。 weak_ptr 是 用 来 解 决 shared_ptr 相 互 引 用 时 的 死 锁 问 题 , 如 果 说 两 个 shared_ ptr 相 互 引 用 , 那 么 这 两 个 指 针 的 引 用 计 数 永 远 不 可 能 下 降 为 0 , 资 源 永 远 不 会 释 放 。 它 是 对 对 象 的 一 种 弱 引 用 , 不 会 增 加 对 象 的 引 用 计 数 , 和 shared_ptr 之 间 可 以 相 互 转 化 , shared_ptr 可 以 直 接 赋 值 给 它 , 它 可 以 通 过 调 用 l o c k 函 数 来 获 得 shared_ptr 。 +* 详见 C语言C++常见面试题(含答案).PDF 第38点 +#### (40) 调试程序的方法 +* 通过设置断点进行调试 +* 打印log进行调试 +* 打印中间结果进行调试 +#### (41) 遇到coredump要怎么调试 +coredump是程序由于异常或者bug在运行时异常退出或者终止,在一定的条件下生成的一个叫做core的文件,这个core文件会记录程序在运行时的内存,寄存器状态,内存指针和函数堆栈信息等等。对这个文件进行分析可以定位到程序异常的时候对应的堆栈调用信息。 + +* 使用gdb命令对core文件进行调试 + +以下例子在Linux上编写一段代码并导致segment fault 并产生core文件 +``` +mkdir coredumpTest +vim coredumpTest.cpp +``` +在编辑器内键入 +``` +#include +int main(){ + int i; + scanf("%d",i);//正确的应该是&i,这里使用i会导致segment fault + printf("%d\n",i); + return 0; +} +``` +编译 +``` +g++ coredumpTest.cpp -g -o coredumpTest +``` +运行 +``` +./coredumpTest +``` +使用gdb调试coredump +``` +gdb [可执行文件名] [core文件名] +``` + + + +## 3.生成core file 查看错误(如:核心已转储) + +命令行键入: + +1) ulimit -a 查看core file size 是否为unlimited + +2)改变 :ulimit -c unlimited + +3) 重新编译 gcc main.c -g -o main + +4) 生成可执行文件,执行后,生成core文件 + +5) 进入gdb调试:gdb main + +6)输入core-file core 回车,可查看结果 + + + + + +#### (42) inline关键字说一下 和宏定义有什么区别 + +inline是内联的意思,可以定义比较小的函数。因为函数频繁调用会占用很多的栈空间,进行入栈出栈操作也耗费计算资源,所以可以用inline关键字修饰频繁调用的小函数。编译器会在编译阶段将代码体嵌入内联函数的调用语句块中。 + +1、内联函数在编译时展开,而宏在预编译时展开 + +2、在编译的时候,内联函数直接被嵌入到目标代码中去,而宏只是一个简单的文本替换。 + +3、内联函数可以进行诸如类型安全检查、语句是否正确等编译功能,宏不具有这样的功能。 + +4、宏不是函数,而inline是函数 + +5、宏在定义时要小心处理宏参数,一般用括号括起来,否则容易出现二义性。而内联函数不会出现二义性。 + +6、inline可以不展开,宏一定要展开。因为inline指示对编译器来说,只是一个建议,编译器可以选择忽略该建议,不对该函数进行展开。 + +7、宏定义在形式上类似于一个函数,但在使用它时,仅仅只是做预处理器符号表中的简单替换,因此它不能进行参数有效性的检测,也就不能享受C++编译器严格类型检查的好处,另外它的返回值也不能被强制转换为可转换的合适的类型,这样,它的使用就存在着一系列的隐患和局限性。 + +#### (43) 模板的用法与适用场景 实现原理 +用template \关键字进行声明,接下来就可以进行模板函数和模板类的编写了 + +编译器会对函数模板进行两次编译:在声明的地方对模板代码本身进行编译,这次编译只会进行一个语法检查,并不会生成具体的代码。在运行时对代码进行参数替换后再进行编译,生成具体的函数代码。 +#### (44) 成员初始化列表的概念,为什么用成员初始化列表会快一些(性能优势)? +成员初始化列表就是在类或者结构体的构造函数中,在参数列表后以冒号开头,逗号进行分隔的一系列初始化字段。如下: +``` +class A{ +int id; +string name; +FaceImage face; +A(int& inputID,string& inputName,FaceImage& inputFace):id(inputID),name(inputName),face(inputFace){} // 成员初始化列表 +}; +``` +因为使用成员初始化列表进行初始化的话,会直接使用传入参数的拷贝构造函数进行初始化,省去了一次执行传入参数的默认构造函数的过程,否则会调用一次传入参数的默认构造函数。所以使用成员初始化列表效率会高一些。
+另外,有三种情况是必须使用成员初始化列表进行初始化的: + +* 常量成员的初始化,因为常量成员只能初始化不能赋值 +* 引用类型 +* 没有默认构造函数的对象必须使用成员初始化列表的方式进行初始化 + +详见[C++ 初始化列表](https://www.cnblogs.com/graphics/archive/2010/07/04/1770900.html) +#### (45) 用过C11吗,知道C11新特性吗?(有面试官建议熟悉C11) +* 自动类型推导auto:auto的自动类型推导用于从初始化表达式中推断出变量的数据类型。通过auto的自动类型推导,可以大大简化我们的编程工作 +* nullptr +:nullptr是为了解决原来C\+\+中NULL的二义性问题而引进的一种新的类型,因为NULL实际上代表的是0,而nullptr是void*类型的 + +* lambda表达式:它类似Javascript中的闭包,它可以用于创建并定义匿名的函数对象,以简化编程工作。Lambda的语法如下: +`[函数对象参数](操作符重载函数参数)mutable或exception声明->返回值类型{函数体}` +* thread类和mutex类 +* 新的智能指针 unique_ptr和shared_ptr + + +* 更多详见:https://blog.csdn.net/caogenwangbaoqiang/article/details/79438279 +#### (46) C++的调用惯例(简单一点C++函数调用的压栈过程) +函数的调用过程: + +1)从栈空间分配存储空间 + +2)从实参的存储空间复制值到形参栈空间 + +3)进行运算 + +形参在函数未调用之前都是没有分配存储空间的,在函数调用结束之后,形参弹出栈空间,清除形参空间。 + +数组作为参数的函数调用方式是地址传递,形参和实参都指向相同的内存空间,调用完成后,形参指针被销毁,但是所指向的内存空间依然存在,不能也不会被销毁。 + +当函数有多个返回值的时候,不能用普通的 return 的方式实现,需要通过传回地址的形式进行,即地址/指针传递。 +#### (47) C++的四种强制转换 +四种强制类型转换操作符分别为:static_cast、dynamic_cast、const_cast、reinterpret_cast + +* 1)static_cast : +用于各种隐式转换。具体的说,就是用户各种基本数据类型之间的转换,比如把int换成char,float换成int等。以及派生类(子类)的指针转换成基类(父类)指针的转换。 + >特性与要点: + 1. 它没有运行时类型检查,所以是有安全隐患的。 + 2. 在派生类指针转换到基类指针时,是没有任何问题的,在基类指针转换到派生类指针的时候,会有安全问题。 + 3. static_cast不能转换const,volatile等属性 +* 2)dynamic_cast: +用于动态类型转换。具体的说,就是在基类指针到派生类指针,或者派生类到基类指针的转换。 +dynamic_cast能够提供运行时类型检查,只用于含有虚函数的类。 +dynamic_cast如果不能转换返回NULL。 +* 3)const_cast: +用于去除const常量属性,使其可以修改 ,也就是说,原本定义为const的变量在定义后就不能进行修改的,但是使用const_cast操作之后,可以通过这个指针或变量进行修改; 另外还有volatile属性的转换。 +* 4)reinterpret_cast +几乎什么都可以转,用在任意的指针之间的转换,引用之间的转换,指针和足够大的int型之间的转换,整数到指针的转换等。但是不够安全。 +#### (48)string的底层实现 +string继承自basic_string,其实是对char\*进行了封装,封装的string包含了char\*数组,容量,长度等等属性。 + +string可以进行动态扩展,在每次扩展的时候另外申请一块原空间大小两倍的空间(2^n),然后将原字符串拷贝过去,并加上新增的内容。 +#### (49)一个函数或者可执行文件的生成过程或者编译过程是怎样的 +预处理,编译,汇编,链接 + +* 预处理: 对预处理命令进行替换等预处理操作 +* 编译:代码优化和生成汇编代码 +* 汇编:将汇编代码转化为机器语言 +* 链接:将目标文件彼此链接起来 +#### (50)set,map和vector的插入复杂度 +set,map的插入复杂度就是红黑树的插入复杂度,是log(N)。 + +unordered_set,unordered_map的插入复杂度是常数,最坏是O(N). + +vector的插入复杂度是O(N),最坏的情况下(从头插入)就要对所有其他元素进行移动,或者扩容重新拷贝 +#### (51)定义和声明的区别 +* 声明是告诉编译器变量的类型和名字,不会为变量分配空间 + +* 定义就是对这个变量和函数进行内存分配和初始化。需要分配空间,同一个变量可以被声明多次,但是只能被定义一次 +#### (52)typdef和define区别 + +#define是预处理命令,在预处理是执行简单的替换,不做正确性的检查 + +typedef是在编译时处理的,它是在自己的作用域内给已经存在的类型一个别名 + +#### (53)被free回收的内存是立即返还给操作系统吗?为什么 +https://blog.csdn.net/YMY_mine/article/details/81180168 + +不是的,被free回收的内存会首先被ptmalloc使用双链表保存起来,当用户下一次申请内存的时候,会尝试从这些内存中寻找合适的返回。这样就避免了频繁的系统调用,占用过多的系统资源。同时ptmalloc也会尝试对小块内存进行合并,避免过多的内存碎片。 + +#### (54)引用作为函数参数以及返回值的好处 + +对比值传递,引用传参的好处: + +1)在函数内部可以对此参数进行修改 + +2)提高函数调用和运行的效率(因为没有了传值和生成副本的时间和空间消耗) + +如果函数的参数实质就是形参,不过这个形参的作用域只是在函数体内部,也就是说实参和形参是两个不同的东西,要想形参代替实参,肯定有一个值的传递。函数调用时,值的传递机制是通过“形参=实参”来对形参赋值达到传值目的,产生了一个实参的副本。即使函数内部有对参数的修改,也只是针对形参,也就是那个副本,实参不会有任何更改。函数一旦结束,形参生命也宣告终结,做出的修改一样没对任何变量产生影响。 + +用引用作为返回值最大的好处就是在内存中不产生被返回值的副本。 + +但是有以下的限制: + +1)不能返回局部变量的引用。因为函数返回以后局部变量就会被销毁 + +2)不能返回函数内部new分配的内存的引用。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一 个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak + +3)可以返回类成员的引用,但是最好是const。因为如果其他对象可以获得该属性的非常量的引用,那么对该属性的单纯赋值就会破坏业务规则的完整性。 +#### (55)友元函数和友元类 +https://www.cnblogs.com/zhuguanhao/p/6286145.html + +友元提供了不同类的成员函数之间、类的成员函数和一般函数之间进行数据共享的机制。通过友元,一个不同函数或者另一个类中的成员函数可以访问类中的私有成员和保护成员。友元的正确使用能提高程序的运行效率,但同时也破坏了类的封装性和数据的隐藏性,导致程序可维护性变差。 + +1)友元函数 + +有元函数是定义在类外的普通函数,不属于任何类,可以访问其他类的私有成员。但是需要在类的定义中声明所有可以访问它的友元函数。 + +``` +#include + +using namespace std; + +class A +{ +public: + friend void set_show(int x, A &a); //该函数是友元函数的声明 +private: + int data; +}; + +void set_show(int x, A &a) //友元函数定义,为了访问类A中的成员 +{ + a.data = x; + cout << a.data << endl; +} +int main(void) +{ + class A a; + + set_show(1, a); + + return 0; +} +``` + +一个函数可以是多个类的友元函数,但是每个类中都要声明这个函数。 + +2)友元类 + +友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。 +但是另一个类里面也要相应的进行声明 + + ``` + #include + +using namespace std; + +class A +{ +public: + friend class C; //这是友元类的声明 +private: + int data; +}; + +class C //友元类定义,为了访问类A中的成员 +{ +public: + void set_show(int x, A &a) { a.data = x; cout< ssthresh 时),停止使用慢开始算法而改用拥塞避免算法) + +(2)**拥塞避免**(线性增加)。拥塞避免算法的思路是让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍。 + +(3)**快重传**。当发送端连续收到三个重复的ack时,表示该数据段已经丢失,需要重发。此时慢启动阈值ssth变为原来一半,(然后执行拥塞避免算法)拥塞窗口cwnd变为ssth+3,然后+1+1的发(每一轮rtt+1)。 + +(4)**快恢复**。(**出现超时,使用慢开始算法**)当超过设定的时间没有收到某个报文段的ack时,表示网络拥塞,慢启动阈值ssth变为原来一半,拥塞窗口cwnd=1,进入慢启动阶段。 + +书上定义:当发送端连续收到三个重复的ack时(**使用拥塞避免算法**),此时慢启动阈值ssth变为原来一半,然后执行拥塞避免算法,拥塞窗口cwnd变为ssth+3,然后+1+1的发(每一轮rtt+1)。 + +注:在采用快恢复算法时,慢开始算法只是在**TCP连接建立**时和**网络出现超时**时才使用。 + +#### (21) http协议与TCP的区别与联系 +联系:Http协议是建立在TCP协议基础之上的,当浏览器需要从服务器获取网页数据的时候,会发出一次Http请求。Http会通过TCP建立起一个到服务器的连接通道,当本次请求需要的数据传输完毕后,Http会立即将TCP连接断开,这个过程是很短的。 + +区别:HTTP和TCP位于不同的网络分层。TCP是传输层的协议,定义的是数据传输和连接的规范,而HTTP是应用层的,定义的是数据的内容的规范。 +建立一个TCP请求需要进行三次握手,而由于http是建立在tcp连接之上的,建立一个http请求通常包含请求和响应两个步骤。 +#### (22) http/1.0和http/1.1的区别 +HTTP 协议老的标准是 HTTP/1.0 ,目前最通用的标准是 HTTP/1.1 。 +HTTP1.0 只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个 TCP 连接,但是最新的http/1.0加入了长连接,只需要在客户端给服务器发送的http报文头部加入Connection:keep-alive +HTTP 1.1 支持持久连接,默认进行持久连接,在一个 TCP 连接上可以传送多个 HTTP 请求和响应,减少了建立和关闭连接的消耗和延迟。 + +#### (23) http的请求方法有哪些?get和post的区别。 +HTTP的请求方法包括GET,POST,PUT,DELETE四种基本方法。(四种方法中只有POST不是操作幂等性的) + +get和post的区别: +1. get方法不会修改服务器上的资源,它的查询是没有副作用的,而post有可能会修改服务器上的资源 +2. get可以保存为书签,可以用缓存来优化,而post不可以 +3. get把请求附在url上,而post把参数附在http包的包体中 +4. 浏览器和服务器一般对get方法所提交的url长度有限制,一般是1k或者2k,而对post方法所传输的参数大小限制为80k到4M不等 +5. post可以传输二进制编码的信息,get的参数一般只支持ASCII +#### (24) http的状态码 403 201等等是什么意思 +详见 [HTTP状态码的含义](https://blog.csdn.net/u011630575/article/details/46636535) + +常见的状态码有: +>* 200 - 请求成功 +>* 301 - 资源(网页等)被永久转移到其它URL +>* 404 - 请求的资源(网页等)不存在 +>* 500 - 内部服务器错误 +>* 400 - 请求无效 +>* 403 - 禁止访问 +#### (25) http和https的区别,由http升级为https需要做哪些操作 +http 是超文本传输协议,信息是明文传输, https 则是具有安全性的 ssl 加密传输协议 +http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80 ,后者是 443 +http 的连接很简单,是无状态的; HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比http 协议安全。 +https 协议需要到 ca 申请证书,一般免费证书较少,因而需要一定费用 +https://www.cnblogs.com/wqhwe/p/5407468.html + +#### (26) https的具体实现,怎么确保安全性 +**SSL是传输层的协议** + +https包括非对称加密和对称加密两个阶段,在客户端与服务器建立连接的时候使用非对称加密,连接建立以后使用的是对称加密。 + +1. 客户使用https的URL访问Web服务器,要求与Web服务器建立SSL连接 +2. Web服务器收到客户端请求后,会将网站的公钥传送一份给客户端,私钥自己保存。 +3. 客户端的浏览器根据双方同意的安全等级,生成对称加密使用的密钥,称为会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站 +4. Web服务器利用自己的私钥解密出会话密钥。 +5. Web服务器利用会话密钥加密与客户端之间的通信,这个过程是对称加密的过程。 + +服务器第一次传给客户端的公钥其实是CA对网站信息进行加密的数字证书 + +客户端的对称加密密钥其实是三个随机数的哈希(1. 客户端第一次给服务端发送请求时附带的随机数 2. 服务器返回时的随机数 3. 客户端收到返回时的随机数) +#### (27) TCP三次握手时的第一次的seq序号是怎样产生的 +第一次的序号是随机序号,但也不是完全随机,它是使用一个ISN算法得到的。 + +seq = C + H (源IP地址,目的IP地址,源端口,目的端口)。其中,C是一个计时器,每隔一段时间值就会变大,H是消息摘要算法,输入是一个四元组(源IP地址,目的IP地址,源端口,目的端口)。 +#### (28) 一个机器能够使用的端口号上限是多少,为什么?可以改变吗?那如果想要用的端口超过这个限制怎么办? +65536.因为TCP的报文头部中源端口号和目的端口号的长度是16位,也就是可以表示2^16=65536个不同端口号,因此TCP可供识别的端口号最多只有65536个。但是由于0到1023是知名服务端口,所以实际上还要少1024个端口号。 + +而对于服务器来说,可以开的端口号与65536无关,其实是受限于Linux可以打开的文件数量,并且可以通过MaxUserPort来进行配置。 +#### (29) 对称密码和非对称密码体系 +https://blog.csdn.net/qq_29689487/article/details/81634057 + +* 对称加密:加密和解密使用的密钥是同一个 + * 优点:计算量小,算法速度快,加密效率高 缺点:密钥容易泄漏。不同的会话需要不同的密钥,管理起来很费劲 + * 常用算法:DES,3DES,IDEA,CR4,CR5,CR6,AES +* 非对称加密:需要公钥和私钥,公钥用来加密,私钥用来解密 + * 优点:安全,不怕泄漏 缺点:速度慢 + * 常用算法:RSA,ECC,DSA +#### (30) 数字证书的了解(高频) +![fig/数字证书.jpg](fig/数字证书.jpg) + +权威CA使用私钥将网站A的信息和消息摘要(签名S)进行加密打包形成数字证书。公钥给客户端。 + +网站A将自己的信息和数字证书发给客户端,客户端用CA的公钥对数字证书进行解密,得到签名S,与手动将网站的信息进行消息摘要得到的结果S\*进行对比,如果签名一致就证明网站A可以信任。 +#### (31) 服务器出现大量close_wait的连接的原因以及解决方法 +close_wait状态是在TCP四次挥手的时候收到FIN但是没有发送自己的FIN时出现的,服务器出现大量close_wait状态的原因有两种: +* 服务器内部业务处理占用了过多时间,都没能处理完业务;或者还有数据需要发送;或者服务器的业务逻辑有问题,没有执行close()方法 +* 服务器的父进程派生出子进程,子进程继承了socket,收到FIN的时候子进程处理但父进程没有处理该信号,导致socket的引用不为0无法回收 + +处理方法: +* 停止应用程序 +* 修改程序里的bug +#### (32) 消息摘要算法列举一下,介绍MD5算法,为什么MD5是不可逆的,有什么办法可以加强消息摘要算法的安全性让它不那么容易被破解呢?(百度安全一面) +* 消息摘要算法有MD家族(MD2,MD4,MD5),SHA家族(SHA-1,SHA-256)和CRC家族(CRC8,CRC16,CRC32)等等 + +* MD5算法介绍: +MD5以512位分组来处理输入的信息,且每一分组又被划分为若干个小分组(16个32位子分组),经过一些列的处理后,算法输出由四个散列值(32位分组组成的128位散列值。) + +1. MD5首先将输入的信息分成若干个512字节长度的分组,如果不够就填充1和若干个0。 +2. 对每个512字节的分组进行循环运算。使用四个幻数对第一个分组的数据进行四轮变换,得到四个变量。 +3. 接下来对其中三个使用线性函数进行计算,与剩下一个相加,并赋值给其中某个变量,得到新的四个变量,重复16次这个过程,得到的四个变量作为幻数,与下一个分组进行相似的计算。 +4. 遍历所有分组后得到的四个变量即为结果。 + +详见:https://blog.csdn.net/weixin_39640298/article/details/84555814 + +* 为什么不可逆:因为MD5在进行消息摘要的过程中,数据与原始数据相比发生了丢失,所以不能由结果进行恢复。 + +* 加强安全性:加盐(加随机数) +#### (33) 单条记录高并发访问的优化 +服务器端: +* 使用缓存,如redis等 +* 使用分布式架构进行处理 +* 将静态页面和静态资源存储在静态资源服务器,需要处理的数据使用服务器进行计算后返回 +* 将静态资源尽可能在客户端进行缓存 +* 采用ngnix进行负载均衡 (nginx读作恩静埃克斯 = Engine X) + +数据库端: +* 数据库采用主从赋值,读写分离措施 +* 建立适当的索引 +* 分库分表 +#### (34) 介绍一下ping的过程,分别用到了哪些协议 +详见:[Ping原理与ICMP协议](https://www.cnblogs.com/Akagi201/archive/2012/03/26/2418475.html) + +ping是使用ICMP协议来进行工作的。 ICMP:网络控制报文协议 + +* 首先,ping命令会构建一个ICMP请求数据包,然后由ICMP协议将这个数据包连同目的IP地址源IP地址一起交给IP协议。 +* 然后IP协议就会构建一个IP数据报,并且在映射表中查找目的IP对应的mac地址,将其交给数据链路层。 +* 然后数据链路层就会构建一个数据帧,附上源mac地址和目的mac地址发送出去。 + +目的主机接收到数据帧后,就会检查包上的mac地址与本机mac是否相符,如果相符,就接收并把其中的信息提取出来交给IP协议,IP协议就会将其中的信息提取出来交给ICMP协议。然后构建一个ICMP应答包,用相同的过程发送回去。 +#### (35) TCP/IP的粘包与避免介绍一下 +因为TCP为了减少额外开销,采取的是流式传输,所以接收端在一次接收的时候有可能一次接收多个包。而TCP粘包就是发送方的若干个数据包到达接收方的时候粘成了一个包。多个包首尾相接,无法区分。 + +导致TCP粘包的原因有三方面: +* 发送端等待缓冲区满才进行发送,造成粘包 +* 接收方来不及接收缓冲区内的数据,造成粘包 +* 由于TCP协议在发送较小的数据包的时候,会将几个包合成一个包后发送 + +避免粘包的措施: +* 通过编程,强制使TCP发生数据传送,不必等到缓冲区满 +* 优化接收方接收数据的过程,使其来得及接收数据包,包括提高接收进程优先级等 +* 设置固定长度的报文或者设置报文头部指示报文的长度。 + +#### (36) 说一下TCP的封包和拆包 +因为TCP是无边界的流传输,所以需要对TCP进行封包和拆包,确保发送和接收的数据不粘连。 +* 封包:封包就是在发送数据报的时候为每个TCP数据包加上一个包头,将数据报分为包头和包体两个部分。包头是一个固定长度的结构体,里面包含该数据包的总长度。 +* 拆包:接收方在接收到报文后提取包头中的长度信息进行截取。 +#### (37) 一个ip配置多个域名,靠什么识别? +* 靠host主机名区分 +* 靠端口号区分 +#### (38) 服务器攻击(DDos攻击) +#### (39)DNS的工作过程和原理 +![](fig/DNS查询图解.png) +DNS解析有两种方式:递归查询和迭代查询 +* 递归查询 用户先向本地域名服务器查询,如果本地域名服务器的缓存没有IP地址映射记录,就向根域名服务器查询,根域名服务器就会向顶级域名服务器查询,顶级域名服务器向权限域名服务器查询,查到结果后依次返回。 +* 迭代查询 用户向本地域名服务器查询,如果没有缓存,本地域名服务器会向根域名服务器查询,根域名服务器返回顶级域名服务器的地址,本地域名服务器再向顶级域名服务器查询,得到权限域名服务器的地址,本地域名服务器再向权限域名服务器查询得到结果 +#### (41)OSA七层协议和五层协议,分别有哪些 +OSI七层协议模型主要是:应用层(Application)、表示层(Presentation)、会话层(Session)、传输层(Transport)、网络层(Network)、数据链路层(Data Link)、物理层(Physical)。 + +五层体系结构包括:应用层、传输层、网络层、数据链路层和物理层。 + +![(fig/网络协议层.png](fig/网络协议层.png) + +#### (42)IP寻址和MAC寻址有什么不同,怎么实现的 +通过MAC地址寻找主机是MAC地址寻址,通过IP地址寻找主机叫IP地址寻址。它们适用于不同的协议层,IP寻址是网络层,Mac寻址是数据链路层。 + +http://c.biancheng.net/view/6388.html + +https://blog.csdn.net/wxy_nick/article/details/9190693 + +IP寻址的过程(ARP协议):主机A想通过IP地址寻找到目标主机,首先分析IP地址确定目标主机与自己是否为同一网段。如果是则查看ARP缓存,或者使用ARP协议发送广播。如果不是,则寻找网关发送ARP数据包 + +# 3. 数据库 +#### (1) 关系型和非关系型数据库的区别(低频) +* 关系型数据库的优点 + 1. 容易理解。因为它采用了关系模型来组织数据。 + 2. 可以保持数据的一致性。 + 3. 数据更新的开销比较小。 + 4. 支持复杂查询(带where子句的查询) +* 非关系型数据库的优点 + 1. 不需要经过sql层的解析,读写效率高。 + 2. 基于键值对,数据的扩展性很好。 + 3. 可以支持多种类型数据的存储,如图片,文档等等。 +#### (2) 什么是非关系型数据库(低频) +非关系型数据库也叫nosql,采用键值对的形式进行存储。它的读写性能很高,易于扩展。例如Redis,Mongodb,hbase等等。 + +适合使用非关系型数据库的场景: +* 日志系统 +* 地理位置存储 +* 数据量巨大 +* 高可用 +#### (3) 说一下 MySQL 执行一条查询语句的内部执行过程? +* 连接器:客户端先通过连接器连接到 MySQL 服务器。 +* 缓存:连接器权限验证通过之后,先查询是否有查询缓存,如果有缓存(之前执行过此语句)则直接返回缓存数据,如果没有缓存则进入分析器。 +* 分析器:分析器会对查询语句进行语法分析和词法分析,判断 SQL 语法是否正确,如果查询语法错误会直接返回给客户端错误信息,如果语法正确则进入优化器。 +* 优化器:优化器是对查询语句进行优化处理,例如一个表里面有多个索引,优化器会判别哪个索引性能更好。 +* 执行器:优化器执行完就进入执行器,执行器就开始执行语句进行查询比对了,直到查询到满足条件的所有数据,然后进行返回。 +#### (4) 数据库的索引类型 +数据库的索引类型分为逻辑分类和物理分类
+逻辑分类: +* 主键索引 当关系表中定义主键时会自动创建主键索引。每张表中的主键索引只能有一个,要求主键中的每个值都唯一,即不可重复,也不能有空值。 +* 唯一索引 数据列不能有重复,可以有空值。一张表可以有多个唯一索引,但是每个唯一索引只能有一列。如身份证,卡号等。 +* 普通索引 一张表可以有多个普通索引,可以重复可以为空值 +* 全文索引 可以加快模糊查询,不常用 + +物理分类: +* 聚集索引(聚簇索引) 数据在物理存储中的顺序跟索引中数据的逻辑顺序相同,比如以ID建立聚集索引,数据库中id从小到大排列,那么物理存储中该数据的内存地址值也按照从小到大存储。一般是表中的主键索引,如果没有主键索引就会以第一个非空的唯一索引作为聚集索引。一张表只能有一个聚集索引。 +* 非聚集索引 数据在物理存储中的顺序跟索引中数据的逻辑顺序不同。非聚集索引因为无法定位数据所在的行,所以需要扫描两遍索引树。第一遍扫描非聚集索引的索引树,确定该数据的主键ID,然后到主键索引(聚集索引)中寻找相应的数据。 +#### (5) 说一下事务是怎么实现的 +https://blog.csdn.net/u013256816/article/details/103966510 + +https://www.cnblogs.com/takumicx/p/9998844.html + +事务就是一组逻辑操作的集合。实现事务就是要保证可靠性和并发隔离,或者说,能够满足ACID特性的机制。而这些主要是靠日志恢复和并发控制实现的。 + +* 日志恢复:数据库里有两个日志,一个是redo log,一个是undo log。redo log记录的是已经成功提交的事务操作信息,用来恢复数据,保证事务的**持久性**。undo log记录的是事务修改之前的数据信息,用来回滚数据,保证事务的**原子性**。 +* 并发控制:并发控制主要靠读写锁和MVCC(多版本并发控制)来实现。读写锁包括共享锁和排他锁,保证事务的**隔离性**。MVCC通过为数据添加时间戳来实现。 + +#### (6) MySQL怎么建立索引,怎么建立主键索引,怎么删除索引? +MySQL建立索引有两种方式:用alter table或者create index。 +``` +alter table table_name add primary key(column_list) #添加一个主键索引 +alter table table_name add index (column_list) #添加一个普通索引 +alter table table_name add unique (column_list) #添加一个唯一索引 +``` +``` +create index index_name on table_name (column_list) #创建一个普通索引 +create unique index_name on table_name (column_list) #创建一个唯一索引 +``` + +Mysql删除索引同样也有两种方式:alter table 和 drop index +``` +alter table table_name drop index index_name #删除一个普通索引 +alter table table_name drop primary key #删除一个主键索引 +``` +``` +drop index index_name on table table_name +``` +#### (7) 索引的优缺点,什么时候使用索引,什么时候不能使用索引(重点) +https://www.cnblogs.com/wezheng/p/8399305.html +* 经常搜索的列上建索引 +* 作为主键的列上要建索引 +* 经常需要连接(where子句)的列上 +* 经常需要排序的列 +* 经常需要范围查找的列 + +哪些列不适合建索引? +* 很少查询的列 +* 更新很频繁的列 +* 数据值的取值比较少的列(比如性别) +#### (8) 索引的底层实现(重点) +数据库的索引是使用B+树来实现的。 + +(为什么要用B+树,为什么不用红黑树和B树)
+B+树是一种特殊的平衡多路树,是B树的优化改进版本,它把所有的数据都存放在叶节点上,中间节点保存的是索引。这样一来相对于B树来说, + +1) 减少了数据对中间节点的空间占用,使得中间节点可以存放更多的指针,使得树变得更矮,深度更小,从而减少查询的磁盘IO次数,提高查询效率。 + +2) 另一个是由于叶节点之间有指针连接,所以可以进行范围查询,方便区间访问。 + +3) 而红黑树是二叉的,它的深度相对B+树来说更大,更大的深度意味着查找次数更多,更频繁的磁盘IO,所以红黑树更适合在内存中进行查找。 + +#### (9) B树和B+树的区别(重点) +![./fig/Bptree.png](./fig/Bptree.png) + +这都是由于B+树和B具有不同的存储结构所造成的区别,以一个m阶树为例。 +1. 关键字的数量不同;B+树中分支结点有m个关键字,其叶子结点也有m个,其关键字只是起到了一个索引的作用,但是B树虽然也有m个子结点,但是其只拥有m-1个关键字。 +2. 存储的位置不同;B+树中的数据都存储在叶子结点上,也就是其所有叶子结点的数据组合起来就是完整的数据,但是B树的数据存储在每一个结点中,并不仅仅存储在叶子结点上。 +3. 分支结点的构造不同;B+树的分支结点仅仅存储着关键字信息和儿子的指针(这里的指针指的是磁盘块的偏移量),也就是说内部结点仅仅包含着索引信息。 +4. 查询不同;B树在找到具体的数值以后,则结束,而B+树则需要通过索引找到叶子结点中的数据才结束,也就是说B+树的搜索过程中走了一条从根结点到叶子结点的路径。 + +B+树优点:由于B+树的数据都存储在叶子结点中,分支结点均为索引,方便扫库,只需要扫一遍叶子结点即可,但是B树因为其分支结点同样存储着数据,我们要找到具体的数据,需要进行一次中序遍历按序来扫,所以B+树更加适合在区间查询的情况,所以通常B+树用于数据库索引,而B树则常用于文件索引。 +#### (10) 索引最左前缀/最左匹配 +假如我们对a b c三个字段建立了联合索引,在联合索引中,从最左边的字段开始,任何连续的索引都能匹配上,当遇到范围查询的时候停止。比如对于联合索引index(a,b,c),能匹配a,ab,abc三组索引。并且对查询时字段的顺序没有限制,也就是a,b,c; b,a,c; c,a,b; c,b,a都可以匹配。 +#### (11) Mysql的优化(高频,索引优化,性能优化) +高频访问: +* 分表分库:将数据库表进行水平拆分,减少表的长度 +* 增加缓存: 在web和DB之间加上一层缓存层 +* 增加数据库的索引:在合适的字段加上索引,解决高频访问的问题 + + +并发优化: +* 主从读写分离:只在主服务器上写,从服务器上读 +* 负载均衡集群:通过集群或者分布式的方式解决并发压力 +#### (12) MYSQL数据库引擎介绍,innodb和myisam的特点与区别 +* InnoDB : InnoDB是mysql的默认引擎,支持事务和外键,支持容灾恢复。适合更新频繁和多并发的表 行级锁 + +* MyISAM : 插入和查询速度比较高,支持大文件,但是不支持事务,适合在web和数据仓库场景下使用 表级锁. + + 读取速度优越,常用于高读取的应用场景数据库,支持三种不同类型的存储结构:静态型、动态型、压缩型。 不支持事物和外键。 + +* MEMORY : memory将表中的数据保存在内存里,适合数据比较小而且频繁访问的场景 + +* **InnoDB**: 提供ACID事务,系统崩溃修复能力和多版本并发控制的行级锁,支持自增长列(auto_increment),支持外键(foregin key),mysql 5.5之后默认数据库引擎。 + +* **MyISAM**: 读取速度优越,常用于高读取的应用场景数据库,支持三种不同类型的存储结构:静态型、动态型、压缩型。 不支持事物和外键。 + +* **CSV**: 使用该引擎的MySQL数据库表会在MySQL安装目录data文件夹中的和该表所在数据库名相同的目录中生成一个.CSV文件,这种文件是一种普通文本文件,把数据以逗号分隔的格式存储在文本文件中,每个数据行占用一个文本行。该种类型的存储引擎不支持索引,即使用该种类型的表没有主键列;另外也不允许表中的字段为null。 + +* **MEMORY**: 该存储引擎通过在内存中创建临时表来存储数据。每个基于该存储引擎的表实际对应一个磁盘文件,该文件的文件名和表名是相同的,类型为.frm。该磁盘文件只存储表的结构,数据在内存中,所以其数据具有不稳定性,比如如果mysqld进程发生异常、重启或计算机关机等等都会造成这些数据的消失,但是表结构还是存在磁盘上的。而因为其数据存储在内存中,所以使用该种引擎的表拥有极高的插入、更新和查询效率。这种存储引擎默认使用哈希(HASH)索引,其速度比使用B-+Tree型要快,但也可以使用B树型索引。 + +* **BLACKHOLE**: 看着名字就知道了,黑洞引擎,该引擎接受但不存储数据,并且检索总是返回一个空集。支持事务,而且支持mvcc的行级锁,主要用于做日志记录或同步归档的中继存储,一般不用。 + +* **MRG_MYISAM**: 该引擎将一定数量的MyISAM表联合而成一个整体。 + +* **ARCHIVE**: 该引擎提供了压缩功能,拥有高效的插入速度,但是这种引擎不支持索引,所以查询性能较差一些。非常适合存储大量独立的、作为历史记录的数据。 + +* **PERFORMANCE_SCHEMA**: 该引擎主要用于收集数据库服务器性能参数。这种引擎提供以下功能:提供进程等待的详细信息,包括锁、互斥变量、文件信息;保存历史的事件汇总信息,为提供MySQL服务器性能做出详细的判断;对于新增和删除监控事件点都非常容易,并可以随意改变mysql服务器的监控周期,例如(CYCLE、MICROSECOND)。 + +* **FEDERATED**: 该存储引擎可以将不同的Mysql服务器联合起来(远程连接),逻辑上组成一个完整的数据库。这种存储引擎非常适合数据库分布式应用。 + +#### (13) 数据库中事务的ACID(四大特性都要能够举例说明,理解透彻,比如原子性和一致性的关联,隔离性不好会出现的问题) +数据库事务是指逻辑上对数据的一种操作,这个事务要么全部成功,要么全部失败。 + +**A: atom 原子性**
+数据库事务的原子性是指:事务是一个不可分割的工作单位,这组操作要么全部发生,要么全部不发生。 + +**C: consistency 一致性**
+数据库事务的一致性是指:在事务开始以前,数据库中的数据有一个一致的状态。在事务完成后,数据库中的事务也应该保持这种一致性。事务应该将数据从一个一致性状态转移到另一个一致性状态。 +比如在银行转账操作后两个账户的总额应当不变。 + +**I: isolation 隔离性**
+数据库事务的隔离性要求数据库中的事务不会受另一个并发执行的事务的影响,对于数据库中同时执行的每个事务来说,其他事务要么还没开始执行,要么已经执行结束,它都感觉不到还有别的事务正在执行。 + +**D:durability 持久性**
+数据库事务的持久性要求事务对数据库的改变是永久的,哪怕数据库发生损坏都不会影响到已发生的事务。 +如果事务没有完成,数据库因故断电了,那么重启后也应该是没有执行事务的状态,如果事务已经完成后数据库断电了,那么重启后就应该是事务执行完成后的状态。 +#### (14)什么是脏读,不可重复读和幻读? +详见[数据库的事务隔离级别总结](https://blog.csdn.net/fuzhongmin05/article/details/91126936) +* 脏读:脏读是指一个事务在处理过程中读取了另一个还没提交的事务的数据。 + + > 比如A向B转账100,A的账户减少了100,而B的账户还没来得及修改,此时一个并发的事务访问到了B的账户,就是脏读 +* 不可重复读:不可重复读是对于数据库中的某一个字段,一个事务多次查询却返回了不同的值,这是由于在查询的间隔中,该字段被另一个事务修改并提交了。 + > 比如A第一次查询自己的账户有1000元,此时另一个事务给A的账户增加了1000元,所以A再次读取他的账户得到了2000的结果,跟第一次读取的不一样。 + > 不可重复读与脏读的不同之处在于,脏读是读取了另一个事务没有提交的脏数据,不可重复读是读取了已经提交的数据,实际上并不是一个异常现象。 +* 幻读:事务多次读取同一个范围的时候,查询结果的记录数不一样,这是由于在查询的间隔中,另一个事务新增或删除了数据。 + > 比如A公司一共有100个人,第一次查询总人数得到100条记录,此时另一个事务新增了一个人,所以下一次查询得到101条记录。 + > 不可重复度和幻读的不同之处在于,幻读是多次读取的结果行数不同,不可重复度是读取结果的值不同。 + +避免不可重复读需要锁行,避免幻读则需要锁表。 + +脏读,不可重复读和幻读都是数据库的读一致性问题,是在并行的过程中出现的问题,必须采用一定的隔离级别解决。 +详见[脏读、不可重复读和幻读的区别](https://www.cnblogs.com/Hakuna-Matata/p/7772794.html) + +#### (15) 数据库的隔离级别,mysql和Oracle的隔离级别分别是什么(重点) +详见[数据库的事务隔离级别总结](https://blog.csdn.net/fuzhongmin05/article/details/91126936)和[数据库隔离级别](https://blog.csdn.net/fg2006/article/details/6937413) + +为了保证数据库事务一致性,解决脏读,不可重复读和幻读的问题,数据库的隔离级别一共有四种隔离级别: +* 读未提交 Read Uncommitted: 最低级别的隔离,不能解决以上问题 +* 读已提交 Read committed: 可以避免脏读的发生 +* 可重复读 Reapeatable read: 确保事务可以多次从一个字段中读取相同的值,在该事务执行期间,禁止其他事务对此字段的更新,可以避免脏读和不可重复读。 通过锁行来实现 +* 串行化 Serializaion 最严格的事务隔离机制,要求所有事务被串行执行,可以避免以上所有问题。 通过锁表来实现 + +Oracle的默认隔离级别是**读已提交**,实现了四种隔离级别中的读已提交和串行化隔离级别 + +MySQL的默认隔离级别是**可重复读**,并且实现了所有四种隔离级别 +#### (16) 数据库连接池的作用 +#### (17) Mysql的表空间方式,各自特点 +* 共享表空间:指的是数据库的所有的表数据,索引文件全部放在一个文件中,默认这个共享表空间的文件路径在 data 目录下。 +* 独立表空间:每一个表都将会生成以独立的文件方式来进行存储。 优点:当表被删除时这部分空间可以被回收;可以更快的恢复和备份单个表;将单个表复制到另一个实例会很方便; 缺点:mysqld会维持很多文件句柄,表太多会影响性能。如果很多表都增长会导致碎片问题 +#### (18) 分布式事务 +#### (19) 数据库的范式 +https://www.cnblogs.com/linjiqin/archive/2012/04/01/2428695.html + +* **第一范式(确保每列保持原子性)**
+第一范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值,就说明该数据库表满足了第一范式。 + +>比如 学生 选课(包括很多课程) 就不符合第一范式 +* **第二范式(确保表中的每列都和主键相关)**
+在满足第一范式的前提下,(主要针对联合主键而言)第二范式需要确保数据库表中的每一列都和主键的所有成员直接相关,由整个主键才能唯一确定,而不能只与主键的某一部分相关或者不相关。 + +>比如一张学生信息表,由主键(学号)可以唯一确定一个学生的姓名,班级,年龄等信息。但是主键 (学号,班级) 与列 姓名,班主任,教室 就不符合第二范式,因为班主任跟部分主键(班级)是依赖关系 +* **第三范式(确保非主键的列没有传递依赖)**
+在满足第二范式的前提下,第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。非主键的列不能确定其他列,列与列之间不能出现传递依赖。 + +>比如一张学生信息表,主键是(学号)列包括 姓名,班级,班主任 就不符合第三范式,因为非主键的列中 班主任 依赖于 班级 +* **BCNF范式(确保主键之间没有传递依赖)**
+主键有可能是由多个属性组合成的复合主键,那么多个主键之间不能有传递依赖。也就是复合主键之间谁也不能决定谁,相互之间没有关系。 +#### (20) 数据的锁的种类,加锁的方式 +以MYSQL为例, +* 按照类型来分有乐观锁和悲观锁 + +* 根据粒度来分有行级锁,页级锁,表级锁(粒度一个比一个大) (仅BDB,Berkeley Database支持页级锁) + +* 根据作用来分有共享锁(读锁)和排他锁(写锁)。 + +* **表级锁**: 每次操作锁住整张表。开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低; + + **行级锁**: 每次操作锁住一行数据。开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高; +#### (21) 什么是共享锁和排他锁 +* 共享锁是读操作的时候创建的锁,一个事务对数据加上共享锁之后,其他事务只能对数据再加共享锁,不能进行写操作直到释放所有共享锁。 +* 排他锁是写操作时创建的锁,事务对数据加上排他锁之后其他任何事务都不能对数据加任何的锁(即其他事务不能再访问该数据) + + https://blog.csdn.net/qq_42743933/article/details/81236658 +#### (22) 分库分表的理解和简介(CSDN收藏夹) +#### (23) 缓存异常 + +**1)缓存雪崩** +缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。 + +解决方案 + +1) 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。 +2) 一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。 +3) 给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。 + +**2)缓存穿透** +缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。 + +解决方案 + +1) 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截; + +2) 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击。 + +3) 采用布隆过滤器,将所有可能存在的数据哈希值放到一个足够大的 bitmap(位图) 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力。 + +附加 + +对于空间的利用到达了一种极致,那就是Bitmap和布隆过滤器(Bloom Filter)。 +Bitmap: 典型的就是哈希表 +缺点是,Bitmap对于每个元素只能记录1bit信息,如果还想完成额外的功能,恐怕只能靠牺牲更多的空间、时间来完成了。 + +布隆过滤器(推荐) + +就是引入了k(k>1)k(k>1)个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过程。 +它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。 +Bloom-Filter算法的核心思想就是利用多个不同的Hash函数来解决“冲突”。 +Hash存在一个冲突(碰撞)的问题,用同一个Hash得到的两个URL的值有可能相同。为了减少冲突,我们可以多引入几个Hash,如果通过其中的一个Hash值我们得出某元素不在集合中,那么该元素肯定不在集合中。只有在所有的Hash函数告诉我们该元素在集合中时,才能确定该元素存在于集合中。这便是Bloom-Filter的基本思想。 +Bloom-Filter一般用于在大数据量的集合中判定某元素是否存在。 + +**3) 缓存击穿** +1) 缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。 + +和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。 + +解决方案 + +1) 设置热点数据永远不过期。 +2) 加互斥锁,互斥锁 + + + +#### (24)数据库高并发的解决方案 +1. 在web服务框架中加入缓存。在服务器与数据库层之间加入缓存层,将高频访问的数据存入缓存中,减少数据库的读取负担。 +2. 增加数据库索引。提高查询速度。(不过索引太多会导致速度变慢,并且数据库的写入会导致索引的更新,也会导致速度变慢) +3. 主从读写分离,让主服务器负责写,从服务器负责读。 +4. 将数据库进行拆分,使得数据库的表尽可能小,提高查询的速度。 +5. 使用分布式架构,分散计算压力。 +#### (25)什么是乐观锁与悲观锁 +一般的数据库都会支持并发操作,在并发操作中为了避免数据冲突,所以需要对数据上锁,乐观锁和悲观锁就是两种不同的上锁方式。 + +1) 悲观锁假设数据在并发操作中一定会发生冲突,所以在数据开始读取的时候就把数据锁住。 + +2) 乐观锁则假设数据一般情况下不会发生冲突,所以在数据提交更新的时候,才会检测数据是否有冲突。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚。 + +#### (26)乐观锁与悲观锁是怎么实现的 +悲观锁有行级锁和页级锁两种形式。行级锁对正在使用的单条数据进行锁定,事务完成后释放该行数据,而页级锁则对整张表进行锁定,事务正在对该表进行访问的时候不允许其他事务并行访问。 + +悲观锁要求在整个过程中一直与数据库有一条连接,因为上一个事务完成后才能让下一个事务执行,这个过程是串行的。 + +乐观锁有三种常用的实现形式: +* 一种是在执行事务时把整个数据都拷贝到应用中,在数据更新提交的时候比较数据库中的数据与新数据,如果两个数据一摸一样则表示没有冲突可以直接提交,如果有冲突就要交给业务逻辑去解决。 +* 一种是使用版本戳来对数据进行标记,数据每发生一次修改,版本号就增加1。某条数据在提交的时候,如果数据库中的版本号与自己的一致,就说明数据没有发生修改,否则就认为是过期数据需要处理。 +* 最后一种采用时间戳对数据最后修改的时间进行标记。与上一种类似。 + +#### (27)对数据库目前最新技术有什么了解吗 + +#### (28)解释一下MVCC + + + + +# 4. Linux +#### (1) Linux的I/O模型介绍以及同步异步阻塞非阻塞的区别(超级重要) +https://blog.csdn.net/sqsltr/article/details/92762279 + +https://www.cnblogs.com/euphie/p/6376508.html + + +(IO过程包括两个阶段:(1)内核从IO设备读写数据和(2)进程从内核复制数据) + + + +* 阻塞:调用IO操作的时候,如果缓冲区空或者满了,调用的进程或者线程就会处于阻塞状态直到IO可用并完成数据拷贝。 +* 非阻塞:调用IO操作的时候,内核会马上返回结果,如果IO不可用,会返回错误,这种方式下进程需要不断轮询直到IO可用为止,但是当进程从内核拷贝数据时是阻塞的。 +* IO多路复用就是同时监听多个描述符,一旦某个描述符IO就绪(读就绪或者写就绪),就能够通知进程进行相应的IO操作,否则就将进程阻塞在select或者epoll语句上。 + + +* 同步IO:同步IO模型包括阻塞IO,非阻塞IO和IO多路复用。特点就是当进程从内核复制数据的时候都是阻塞的。 +* 异步IO:在检测IO是否可用和进程拷贝数据的两个阶段都是不阻塞的,进程可以做其他事情,当IO完成后内核会给进程发送一个信号。 +#### (2) 文件系统的理解(EXT4,XFS,BTRFS) +#### (3) EPOLL的介绍和了解 +https://zhuanlan.zhihu.com/p/56486633 + +https://www.jianshu.com/p/397449cadc9a + +https://blog.csdn.net/davidsguo008/article/details/73556811 + + +Epoll是Linux进行IO多路复用的一种方式,用于在一个线程里监听多个IO源,在IO源可用的时候返回并进行操作。它的特点是基于事件驱动,性能很高。 + +epoll将文件描述符拷贝到内核空间后使用红黑树进行维护,同时向内核注册每个文件描述符的回调函数,当某个文件描述符可读可写的时候,将这个文件描述符加入到就绪链表里,并唤起进程,返回就绪链表到用户空间,由用户程序进行处理。 + +Epoll有三个系统调用:epoll_create(),epoll_ctl()和epoll_wait()。 + +* eoll_create()函数在内核中初始化一个eventpoll对象,同时初始化红黑树和就绪链表。 + +* epoll_ctl()用来对监听的文件描述符进行管理。将文件描述符插入红黑树,或者从红黑树中删除,这个过程的时间复杂度是log(N)。同时向内核注册文件描述符的回调函数。 + +* epoll_wait()会将进程放到eventpoll的等待队列中,将进程阻塞,当某个文件描述符IO可用时,内核通过回调函数将该文件描述符放到就绪链表里,epoll_wait()会将就绪链表里的文件描述符返回到用户空间。 +#### (4) IO复用的三种方法(select,poll,epoll)深入理解,包括三者区别,内部原理实现? +(1)select的方法介绍:select把所有监听的文件描述符拷贝到内核中,挂起进程。当某个文件描述符可读或可写的时候,中断程序唤起进程,select将监听的文件描述符再次拷贝到用户空间,然select后遍历这些文件描述符找到IO可用的文件。下次监控的时候需要再次拷贝这些文件描述符到内核空间。select支持监听的描述符最大数量是1024. +![select](fig/select.png) +(2)poll使用链表保存文件描述符,其他的跟select没有什么不同。 + +(3)epoll将文件描述符拷贝到内核空间后使用红黑树进行维护,同时向内核注册每个文件描述符的回调函数,当某个文件描述符可读可写的时候,将这个文件描述符加入到就绪链表里,并唤起进程,返回就绪链表到用户空间。 +![epoll](fig/epoll.png) +详见 https://www.cnblogs.com/Anker/p/3265058.html +#### (5) Epoll的ET模式和LT模式(ET的非阻塞) +* ET是边缘触发模式,在这种模式下,只有当描述符从未就绪变成就绪时,内核才会通过epoll进行通知。然后直到下一次变成就绪之前,不会再次重复通知。也就是说,如果一次就绪通知之后不对这个描述符进行IO操作导致它变成未就绪,内核也不会再次发送就绪通知。优点就是只通知一次,减少内核资源浪费,效率高。缺点就是不能保证数据的完整,有些数据来不及读可能就会无法取出。 +* LT是水平触发模式,在这个模式下,如果文件描述符IO就绪,内核就会进行通知,如果不对它进行IO操作,只要还有未操作的数据,内核都会一直进行通知。优点就是可以确保数据可以完整输出。缺点就是由于内核会一直通知,会不停从内核空间切换到用户空间,资源浪费严重。 +#### (6) 查询进程占用CPU的命令(注意要了解到used,buf,代表意义) +详见:https://blog.csdn.net/qq_36357820/article/details/76606113 +1. top命令查看linux负载: +2. uptime查看linux负载 +3. w查看linux负载: +4. vmstat查看linux负载 +#### (7) linux的其他常见命令(kill,find,cp等等) +#### (8) shell脚本用法 +#### (9) 硬连接和软连接的区别 +#### (10) 文件权限怎么看(rwx) +#### (11) 文件的三种时间(mtime, atime,ctime),分别在什么时候会改变 +#### (12) Linux监控网络带宽的命令,查看特定进程的占用网络资源情况命令 +#### (13)Linux中线程的同步方式有哪些? +#### (14)怎么修改一个文件的权限 +chmod 777 (177 277 477 等,权限组合是 1 2 4,分别代表r x w ) +#### (15)查看文件内容常用命令 +详见: http://blog.sina.com.cn/s/blog_7b4ce6b101018l8l.html +1. cat 与 tac +``` +cat的功能是将文件从第一行开始连续的将内容输出在屏幕上。当文件大,行数比较多时,屏幕无法全部容下时,只能看到一部分内容。所以通常使用重定向的方式,输出满足指定格式的内容 + +cat语法:cat [-n] 文件名 (-n : 显示时,连行号一起输出) + +tac的功能是将文件从最后一行开始倒过来将内容数据输出到屏幕上。我们可以发现,tac实际上是cat反过来写。这个命令不常用。 + +tac语法:tac 文件名。 +``` +2. more和less(常用) +``` +more的功能是将文件从第一行开始,根据输出窗口的大小,适当的输出文件内容。当一页无法全部输出时,可以用“回车键”向下翻行,用“空格键”向下翻页。退出查看页面,请按“q”键。另外,more还可以配合管道符“|”(pipe)使用,例如:ls -al | more + +more的语法:more 文件名 + +Enter 向下n行,需要定义,默认为1行; + +Ctrl f 向下滚动一屏; + +空格键 向下滚动一屏; + +Ctrl b 返回上一屏; + += 输出当前行的行号; + +:f 输出文件名和当前行的行号; + +v 调用vi编辑器; + +! 命令 调用Shell,并执行命令; + +q 退出more + + +less的功能和more相似,但是使用more无法向前翻页,只能向后翻。 + +less可以使用【pageup】和【pagedown】键进行前翻页和后翻页,这样看起来更方便。 + +less的语法:less 文件名 +``` +3. head和tail +``` +head和tail通常使用在只需要读取文件的前几行或者后几行的情况下使用。head的功能是显示文件的前几行内容 + +head的语法:head [n number] 文件名 (number 显示行数) + +tail的功能恰好和head相反,只显示最后几行内容 + +tail的语法:tail [-n number] 文件名 +``` +4. nl +``` +nl的功能和cat -n一样,同样是从第一行输出全部内容,并且把行号显示出来 + +nl的语法:nl 文件名 +``` +5. vim + +这个用的太普遍了,主要是用于编辑。 + +#### (16)怎么找出含有关键字的前后4行 +#### (17)Linux的GDB调试 +#### (18)coredump是什么 怎么才能coredump +coredump是程序由于异常或者bug在运行时异常退出或者终止,在一定的条件下生成的一个叫做core的文件,这个core文件会记录程序在运行时的内存,寄存器状态,内存指针和函数堆栈信息等等。对这个文件进行分析可以定位到程序异常的时候对应的堆栈调用信息。 + +coredump产生的条件 +1. shell资源控制限制,使用 ulimit -c 命令查看shell执行程序时的资源 ,如果为0,则不会产生coredump。可以用ulimit -c unlimited设置为不限大小。 +2. 读写越界,包括:数组访问越界,指针指向错误的内存,字符串读写越界 +3. 使用了线程不安全的函数,读写未加锁保护 +4. 错误使用指针转换 +5. 堆栈溢出 +#### (19)tcpdump常用命令 +用简单的话来定义tcpdump,就是:dump the traffic on a network,根据使用者的定义对网络上的数据包进行截获的包分析工具。 tcpdump可以将网络中传送的数据包的“头”完全截获下来提供分析。它支持针对网络层、协议、主机、网络或端口的过滤,并提供and、or、not等逻辑语句来帮助你去掉无用的信息。 + +实用命令实例 + +将某端口收发的数据包保存到文件
+`sudo tcpdump -i any port 端口 -w 文件名.cap` + + +打印请求到屏幕
+`sudo tcpdump -i any port 端口 -Xnlps0` + +默认启动
+`tcpdump` +普通情况下,直接启动tcpdump将监视第一个网络接口上所有流过的数据包。 +监视指定网络接口的数据包
+`tcpdump -i eth1` +如果不指定网卡,默认tcpdump只会监视第一个网络接口,一般是eth0,下面的例子都没有指定网络接口。  +#### (20) crontab命令 +详见:https://www.cnblogs.com/peida/archive/2013/01/08/2850483.html + +corntab命令是用来指定用户计划任务的。用户将需要定时执行的任务写入crontab文件中,提交给crond进程定期执行。 + +* crontab命令用来对crontab文件进行管理 +``` +1.命令格式: +crontab [-u user] file +crontab [-u user] [ -e | -l | -r ] +2.命令功能: +通过crontab 命令,我们可以在固定的间隔时间执行指定的系统指令或 shell script脚本。时间间隔的单位可以是分钟、小时、日、月、周及以上的任意组合。这个命令非常设合周期性的日志分析或数据备份等工作。 +3.命令参数: +-u user:用来设定某个用户的crontab服务,例如,“-u ixdba”表示设定ixdba用户的crontab服务,此参数一般有root用户来运行。 +file:file是命令文件的名字,表示将file做为crontab的任务列表文件并载入crontab。如果在命令行中没有指定这个文件,crontab命令将接受标准输入(键盘)上键入的命令,并将它们载入crontab。 +-e:编辑某个用户的crontab文件内容。如果不指定用户,则表示编辑当前用户的crontab文件。 +-l:显示某个用户的crontab文件内容,如果不指定用户,则表示显示当前用户的crontab文件内容。 +-r:从/var/spool/cron目录中删除某个用户的crontab文件,如果不指定用户,则默认删除当前用户的crontab文件。 +-i:在删除用户的crontab文件时给确认提示。 +``` + +* crontab文件内容 + +crond是Linux下的周期性执行系统任务的守护进程,他会根据/etc下的crontab配置文件的内容执行。用户需要将计划任务写入crontab文件中才能执行。 + +用户所建立的crontab文件中,每一行都代表一项任务,每行的每个字段代表一项设置,它的格式共分为六个字段,前五段是时间设定段,第六段是要执行的命令段,格式如下: +``` +minute hour day month week command + +其中: +minute: 表示分钟,可以是从0到59之间的任何整数。 +hour:表示小时,可以是从0到23之间的任何整数。 +day:表示日期,可以是从1到31之间的任何整数。 +month:表示月份,可以是从1到12之间的任何整数。 +week:表示星期几,可以是从0到7之间的任何整数,这里的0或7代表星期日。 +command:要执行的命令,可以是系统命令,也可以是自己编写的脚本文件。 +在以上各个字段中,还可以使用以下特殊字符: +星号(*):代表所有可能的值,例如month字段如果是星号,则表示在满足其它字段的制约条件后每月都执行该命令操作。 +逗号(,):可以用逗号隔开的值指定一个列表范围,例如,“1,2,5,7,8,9” +中杠(-):可以用整数之间的中杠表示一个整数范围,例如“2-6”表示“2,3,4,5,6” +正斜线(/):可以用正斜线指定时间的间隔频率,例如“0-23/2”表示每两小时执行一次。同时正斜线可以和星号一起使用,例如*/10,如果用在minute字段,表示每十分钟执行一次。 +``` +#### (21) 查看后台进程 +* jobs + +查看当前控制台的后台进程 + +想要停止后台进程,使用jobs命令查看其进程号(比如为num),然后kill %num即可 + +* ps + +查看后台进程 + +* top + +查看所有进程和资源使用情况,类似Windows中的任务管理器 + +停止进程:界面是交互式的,在窗口输入k 之后输入PID,会提示输入停止进程模式 有SIGTERM和 SIGKILL 如果留空不输入,就是SIGTERM(优雅停止) + +退出top:输入q即可 + +#### 22)静态库动态库的区别 + + + +# 5. 操作系统 +#### (1) 进程与线程的区别和联系(重点) +* 区别 +1. 进程是对运行时程序的封装,是系统进行资源分配和调度的基本单元,而线程是进程的子任务,是CPU分配和调度的基本单元。 +2. 一个进程可以有多个线程,但是一个线程只能属于一个进程。 +3. 进程的创建需要系统分配内存和CPU,文件句柄等资源,销毁时也要进行相应的回收,所以进程的管理开销很大;但是线程的管理开销则很小。 +4. 进程之间不会相互影响;而一个线程崩溃会导致进程崩溃,从而影响同个进程里面的其他线程。 + +* 联系 进程与线程之间的关系:线程是存在进程的内部,一个进程中可以有多个线程,一个线程只能存在一个进程中。 +#### (2) Linux理论上最多可以创建多少个进程?一个进程可以创建多少线程,和什么有关 +答:32768. 因为进程的pid是用pid_t来表示的,pid_t的最大值是32768.所以理论上最多有32768个进程。 + +至于线程。进程最多可以创建的线程数是根据分配给调用栈的大小,以及操作系统(32位和64位不同)共同决定的。Linux32位下是300多个。 +#### (3) 冯诺依曼结构有哪几个模块?分别对应现代计算机的哪几个部分?(百度安全一面) +* 存储器:内存 +* 控制器:南桥北桥 +* 运算器:CPU +* 输入设备:键盘 +* 输出设备:显示器、网卡 +#### (4) 进程之间的通信方法有哪几种 (重点) +进程之间的通信方式主要有六种,包括**管道,信号量,消息队列,信号,共享内存,套接字**。 + +* 管道:管道是半双工的,双方需要通信的时候,需要建立两个管道。管道的实质是一个内核缓冲区,进程以先进先出的方式从缓冲区存取数据:管道一端的进程顺序地将进程数据写入缓冲区,另一端的进程则顺序地读取数据,该缓冲区可以看做一个循环队列,读和写的位置都是自动增加的,一个数据只能被读一次,读出以后再缓冲区都不复存在了。当缓冲区读空或者写满时,有一定的规则控制相应的读进程或写进程是否进入等待队列,当空的缓冲区有新数据写入或慢的缓冲区有数据读出时,就唤醒等待队列中的进程继续读写。管道是最容易实现的 +![fig/管道通信.png](fig/管道通信.png) + + 匿名管道pipe和命名管道除了建立,打开,删除的方式不同外,其余都是一样的。匿名管道只允许有亲缘关系的进程之间通信,也就是父子进程之间的通信,命名管道允许具有非亲缘关系的进程间通信。 + + 管道的底层实现 https://segmentfault.com/a/1190000009528245 + +* 信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。信号量只有等待和发送两种操作。等待(P(sv))就是将其值减一或者挂起进程,发送(V(sv))就是将其值加一或者将进程恢复运行。 + +* 信号:信号是Linux系统中用于进程之间通信或操作的一种机制,信号可以在任何时候发送给某一进程,而无须知道该进程的状态。如果该进程并未处于执行状态,则该信号就由内核保存起来,知道该进程恢复执行并传递给他为止。如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。 信号是开销最小的 + +* 共享内存:共享内存允许两个或多个进程共享一个给定的存储区,这一段存储区可以被两个或两个以上的进程映射至自身的地址空间中,就像由malloc()分配的内存一样使用。一个进程写入共享内存的信息,可以被其他使用这个共享内存的进程,通过一个简单的内存读取读出,从而实现了进程间的通信。共享内存的效率最高,缺点是没有提供同步机制,需要使用锁等其他机制进行同步。 + +* 消息队列:消息队列就是一个消息的链表,是一系列保存在内核中消息的列表。用户进程可以向消息队列添加消息,也可以向消息队列读取消息。 +消息队列与管道通信相比,其优势是对每个消息指定特定的消息类型,接收的时候不需要按照队列次序,而是可以根据自定义条件接收特定类型的消息。 +可以把消息看做一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向消息队列中按照一定的规则添加新消息,对消息队列有读权限的进程可以从消息队列中读取消息。 + +* 套接字:套接口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同设备及其间的进程通信。 +#### (5) 进程调度方法详细介绍 +https://blog.csdn.net/u011080472/article/details/51217754 + +https://blog.csdn.net/leex_brave/article/details/51638300 + +* 先来先服务 (FCFS first come first serve):按照作业到达任务队列的顺序调度 FCFS是非抢占式的,易于实现,效率不高,性能不好,有利于长作业(CPU繁忙性)而不利于短作业(I/O繁忙性)。 +* 短作业优先 (SHF short job first):每次从队列里选择预计时间最短的作业运行。SJF是非抢占式的,优先照顾短作业,具有很好的性能,降低平均等待时间,提高吞吐量。但是不利于长作业,长作业可能一直处于等待状态,出现饥饿现象;完全未考虑作业的优先紧迫程度,不能用于实时系统。 +* 最短剩余时间优先 该算法首先按照作业的服务时间挑选最短的作业运行,在该作业运行期间,一旦有新作业到达系统,并且该新作业的服务时间比当前运行作业的剩余服务时间短,则发生抢占;否则,当前作业继续运行。该算法确保一旦新的短作业或短进程进入系统,能够很快得到处理。 +* 高响应比优先调度算法(Highest Reponse Ratio First, HRRF)是非抢占式的,主要用于作业调度。基本思想:每次进行作业调度时,先计算后备作业队列中每个作业的响应比,挑选最高的作业投入系统运行。响应比 = (等待时间 + 服务时间) / 服务时间 = 等待时间 / 服务时间 + 1。因为每次都需要计算响应比,所以比较耗费系统资源。 +* 时间片轮转 用于分时系统的进程调度。基本思想:系统将CPU处理时间划分为若干个时间片(q),进程按照到达先后顺序排列。每次调度选择队首的进程,执行完1个时间片q后,计时器发出时钟中断请求,该进程移至队尾。以后每次调度都是如此。该算法能在给定的时间内响应所有用户的而请求,达到分时系统的目的。 +* 多级反馈队列(Multilevel Feedback Queue) +#### (6) 进程的执行过程是什么样的,执行一个进程需要做哪些工作? +进程的执行需要经过三大步骤:编译,链接和装入。 +* 编译:将源代码编译成若干模块 +* 链接:将编译后的模块和所需要的库函数进行链接。链接包括三种形式:静态链接,装入时动态链接(将编译后的模块在链接时一边链接一边装入),运行时动态链接(在执行时才把需要的模块进行链接) +* 装入:将模块装入内存运行 + +https://blog.csdn.net/qq_38623623/article/details/78306498 + +将进程装入内存时,通常使用分页技术,将内存分成固定大小的页,进程分为固定大小的块,加载时将进程的块装入页中,并使用页表记录。减少外部碎片。 + +通常操作系统还会使用虚拟内存的技术将磁盘作为内存的扩充。 +#### (6) 操作系统的内存管理说一下 +https://www.cnblogs.com/peterYong/p/6556619.html + +https://zhuanlan.zhihu.com/p/141602175 + +操作系统的内存管理包括物理内存管理和虚拟内存管理 +* 物理内存管理包括交换与覆盖,分页管理,分段管理和段页式管理等; +* 虚拟内存管理包括虚拟内存的概念,页面置换算法,页面分配策略等; + +(面试官这样问的时候,其实是希望你能讲讲虚拟内存) +#### (7) 实现一个LRU算法 +用到两个数据结构:哈希+双向链表 +``` +unordered_map > > cache ;// 存放键,迭代器 +list> auxlist; // 存放 <键,值> +``` +``` +class LRUCache { + int cap; + list> l;// front:new back:old 存放值 新的放前面,因为前面的可以取得有效的迭代器 + map >::iterator > cache;// 存放键,迭代器 +public: + LRUCache(int capacity) { + cap=capacity; + } + + int get(int key) { + auto mapitera = cache.find(key); + if(mapitera==cache.end()){ + return -1; + }else{// found + list>::iterator listItera = mapitera->second; + int value = (*listItera).second; + + l.erase(listItera); + l.push_front({key,value}); + cache[key]=l.begin(); + + return value; + } + } + + void put(int key, int value) { + auto itera = cache.find(key); + if(itera!=cache.end()){// exist + list>::iterator listItera = itera->second; + + l.erase(listItera); + l.push_front({key,value}); + cache[key]=l.begin(); + + }else{// not exist + if(cache.size()>=cap){ + pair oldpair = l.back(); + l.pop_back(); + cache.erase(oldpair.first); + } + l.push_front({key,value}); + cache[key]=l.begin(); + } + } +}; + +/** + * Your LRUCache object will be instantiated and called as such: + * LRUCache* obj = new LRUCache(capacity); + * int param_1 = obj->get(key); + * obj->put(key,value); + */ +``` +#### (8) 死锁产生的必要条件(怎么检测死锁,解决死锁问题) +(1) 互斥:一个资源每次只能被一个进程使用。
+(2) 占有并请求:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
+(3) 不可剥夺:进程已获得的资源,在末使用完之前,不能强行剥夺。
+(4) 循环等待:若干进程之间形成一种头尾相接的循环等待资源关系。
+ +产生死锁的原因主要是:
+(1) 因为系统资源不足。
+(2) 进程运行推进的顺序不合适。
+(3) 资源分配不当等。
+#### (8) 死锁的恢复 +1. 重新启动:是最简单、最常用的死锁消除方法,但代价很大,因为在此之前所有进程已经完成的计算工作都将付之东流,不仅包括死锁的全部进程,也包括未参与死锁的全部进程。 +2. 终止进程(process termination):终止参与死锁的进程并回收它们所占资源。 + (1) 一次性全部终止;(2) 逐步终止(优先级,代价函数) +3. 剥夺资源(resource preemption):剥夺死锁进程所占有的全部或者部分资源。 + (1) 逐步剥夺:一次剥夺死锁进程所占有的一个或一组资源,如果死锁尚未解除再继续剥夺,直至死锁解除为止。 + (2) 一次剥夺:一次性地剥夺死锁进程所占有的全部资源。 +4. 进程回退(rollback):让参与死锁的进程回退到以前没有发生死锁的某个点处,并由此点开始继续执行,希望进程交叉执行时不再发生死锁。但是系统开销很大: + (1) 要实现“回退”,必须“记住”以前某一点处的现场,而现场随着进程推进而动态变化,需要花费大量时间和空间。 + (2) 一个回退的进程应当“挽回”它在回退点之间所造成的影响,如修改某一文件,给其它进程发送消息等,这些在实现时是难以做到的 +#### (8)什么是饥饿 +饥饿是由于资源分配策略不公引起的,当进程或线程无法访问它所需要的资源而不能继续执行时,就会发生饥饿现象。 +#### (9) 如果要你实现一个mutex互斥锁你要怎么实现? +https://blog.csdn.net/kid551/article/details/84338619 + +实现mutex最重要的就是实现它的lock()方法和unlock()方法。我们保存一个全局变量flag,flag=1表明该锁已经锁住,flag=0表明锁没有锁住。 +实现lock()时,使用一个while循环不断检测flag是否等于1,如果等于1就一直循环。然后将flag设置为1;unlock()方法就将flag置为0; +```C++ +static int flag=0; + +void lock(){ + while(TestAndSet(&flag,1)==1); + //flag=1; +} +void unlock(){ + flag=0; +} +``` +因为while有可能被重入,所以可以用TestandSet()方法。 +```C++ +int TestAndSet(int *ptr, int new) { + int old = *ptr; + *ptr = new; + return old; +} +``` +#### (10)线程之间的通信方式有哪些? 进程之间的同步方式又哪些? +线程之间通信: +* 使用全局变量 +* 使用信号机制 +* 使用事件 + +进程之间同步: +https://www.cnblogs.com/sonic4x/archive/2011/07/05/2098036.html + +* 信号量 +* 管程 +#### (13) 什么时候用多进程,什么时候用多线程 +https://blog.csdn.net/yu876876/article/details/82810178 + +* 频繁修改:需要频繁创建和销毁的优先使用**多线程** +* 计算量:需要大量计算的优先使用**多线程** 因为需要消耗大量CPU资源且切换频繁,所以多线程好一点 +* 相关性:任务间相关性比较强的用**多线程**,相关性比较弱的用多进程。因为线程之间的数据共享和同步比较简单。 +* 多分布:可能要扩展到多机分布的用**多进程**,多核分布的用**多线程**。 + +但是实际中更常见的是进程加线程的结合方式,并不是非此即彼的。 +#### (14) 文件读写使用的系统调用 +#### (15) 孤儿进程和僵尸进程分别是什么,怎么形成的? +https://www.cnblogs.com/Anker/p/3271773.html + +* 孤儿进程是父进程退出后它的子进程还在执行,这时候这些子进程就成为孤儿进程。孤儿进程会被init进程收养并完成状态收集。 +* 僵尸进程是指子进程完成并退出后父进程没有使用wait()或者waitpid()对它们进行状态收集,这些子进程的进程描述符仍然会留在系统中。这些子进程就成为僵尸进程。 +#### (16) 说一下PCB/说一下进程地址空间/ +https://blog.csdn.net/qq_38499859/article/details/80057427 + +PCB就是进程控制块,是操作系统中的一种数据结构,用于表示进程状态,操作系统通过PCB对进程进行管理。 + +PCB中包含有:进程标识符,处理器状态,进程调度信息,进程控制信息 + +![](https://img-blog.csdn.net/20140904215636015?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhhbmd6aGVianV0/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) + +进程地址空间内有: +* 代码段text:存放程序的二进制代码 +* 初始化的数据Data:已经初始化的变量和数据 +* 未初始化的数据BSS:还没有初始化的数据 +* 栈 +* 堆 +#### (17) 内核空间和用户空间是怎样区分的 +在Linux中虚拟地址空间范围为0到4G,最高的1G地址(0xC0000000到0xFFFFFFFF)供内核使用,称为内核空间,低的3G空间(0x00000000到0xBFFFFFFF)供各个进程使用,就是用户空间。 + +内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。 +#### (18) 多线程是如何同步的(尤其是如果项目中用到了多线程,很大可能会结合讨论) +https://blog.csdn.net/s_lisheng/article/details/74278765 + +* 临界区 +* 信号量 +* 事件 +* 互斥量 +#### (19) 同一个进程内的线程会共享什么资源? +* 该进程的地址空间 +* 全局变量 +* 堆空间 + +线程的栈空间是自己独有的 +#### (20) 异常和中断的区别 + +- **异常是由于执行了现行指令所引起的。由于系统调用引起的中断属于异常。** + +- **中断则是由于系统中某事件引起的,该事件与现行指令无关。** + +- **中断和异常** + + **相同点:**都是**CPU**对**系统**发生的某**个事情**做出的**一种反应**。 + + **区别:\**中断\****由**外因引起**,**异常**由**CPU本身**原因引起。 + + ![image-20210610221723734](C:\Users\38032\AppData\Roaming\Typora\typora-user-images\image-20210610221723734.png) + +#### (21) 一般情况下在Linux/windows平台下栈空间的大小 +在Linux下栈空间通常是8M,Windows下是1M +#### (22)虚拟内存的了解 +https://www.cnblogs.com/Przz/p/6876988.html + +在运行一个进程的时候,它所需要的内存空间可能大于系统的物理内存容量。通常一个进程会有4G的空间,但是物理内存并没有这么大,所以这些空间都是虚拟内存,它的地址都是逻辑地址,每次在访问的时候都需要映射成物理地址。 +当进程访问某个逻辑地址的时候,会去查看页表,如果页表中没有相应的物理地址,说明内存中没有这页的数据,发生缺页异常,这时候进程需要把数据从磁盘拷贝到物理内存中。如果物理内存已经满了,就需要覆盖已有的页,如果这个页曾经被修改过,那么还要把它写回磁盘。 + +#### (23)服务器高并发的解决方案 +1. 应用数据与静态资源分离 +将静态资源(图片,视频,js,css等)单独保存到专门的静态资源服务器中,在客户端访问的时候从静态资源服务器中返回静态资源,从主服务器中返回应用数据。 + +2. 客户端缓存 +因为效率最高,消耗资源最小的就是纯静态的html页面,所以可以把网站上的页面尽可能用静态的来实现,在页面过期或者有数据更新之后再将页面重新缓存。或者先生成静态页面,然后用ajax异步请求获取动态数据。 + +3. 集群和分布式 +(集群是所有的服务器都有相同的功能,请求哪台都可以,主要起分流作用)
+(分布式是将不同的业务放到不同的服务器中,处理一个请求可能需要使用到多台服务器,起到加快请求处理的速度。)
+可以使用服务器集群和分布式架构,使得原本属于一个服务器的计算压力分散到多个服务器上。同时加快请求处理的速度。 + +4. 反向代理 +在访问服务器的时候,服务器通过别的服务器获取资源或结果返回给客户端。 +#### (24)协程了解吗(高频) +协程和微线程是一个东西。 + +协程就是子程序在执行时中断并转去执行别的子程序,在适当的时候又返回来执行。 +这种子程序间的跳转不是函数调用,也不是多线程执行,所以省去了线程切换的开销,效率很高,并且不需要多线程间的锁机制,不会发生变量写冲突。 + +#### (25)那协程的底层是怎么实现的,怎么使用协程? +协程进行中断跳转时将函数的上下文存放在其他位置中,而不是存放在函数堆栈里,当处理完其他事情跳转回来的时候,取回上下文继续执行原来的函数。 +#### (23)进程的状态以及转换图 +* 三态模型 + 三态模型包括三种状态: + 1. 执行:进程分到CPU时间片,可以执行 + 2. 就绪:进程已经就绪,只要分配到CPU时间片,随时可以执行 + 3. 阻塞:有IO事件或者等待其他资源 + ![](fig/三态模型.png) +* 五态模型 + 1. 新建态:进程刚刚创建。 + 2. 就绪态: + 3. 运行态: + 4. 等待态:出现等待事件 + 5. 终止态:进程结束 + ![](fig/五态模型.png) + +* 七态模型 + 1. 新建态 + 2. 就绪挂起态 + 3. 就绪态 + 4. 运行态 + 5. 等待态 + 6. 挂起等待态 + 7. 终止态 +![](fig/七态模型.png) + +#### (24)在执行malloc申请内存的时候,操作系统是怎么做的?/内存分配的原理说一下/malloc函数底层是怎么实现的?/进程是怎么分配内存的? +https://blog.csdn.net/yusiguyuan/article/details/39496057 + +从操作系统层面上看,malloc是通过两个系统调用来实现的: brk和mmap +* brk是将进程数据段(.data)的最高地址指针向高处移动,这一步可以扩大进程在运行时的堆大小 +* mmap是在进程的虚拟地址空间中寻找一块空闲的虚拟内存,这一步可以获得一块可以操作的堆内存。 + +通常,分配的内存小于128k时,使用brk调用来获得虚拟内存,大于128k时就使用mmap来获得虚拟内存。 + +进程先通过这两个系统调用获取或者扩大进程的虚拟内存,获得相应的虚拟地址,在访问这些虚拟地址的时候,通过缺页中断,让内核分配相应的物理内存,这样内存分配才算完成。 +#### (25)什么是字节序?怎么判断是大端还是小端?有什么用? +https://www.cnblogs.com/broglie/p/5645200.html + +字节序是对象在内存中存储的方式,大端即为最高有效位在前面,小端即为最低有效位在前面。 +判断大小端的方法:使用一个union数据结构 +```C++ +union{ + short s; + char c[2]; // sizeof(short)=2; +}un; +un.s=0x0102; +if(un.c[0]==1 and un.c[1]==2) cout<<"大端"; +if(un.c[0]==2 and un.c[1]==1) cout<<"小端"; +``` +在网络编程中不同字节序的机器发送和接收的顺序不同。 + + +# 6. 场景题/算法题 +#### (0) leetcode hot100至少刷两遍,剑指offer至少刷两遍 重中之重!! +面试中90%的算法题都从leetcode hot100和剑指offer中出 刷两遍非常有必要 +#### (1) 介绍熟悉的设计模式(单例,简单工厂模式) +#### (2) 写单例模式,线程安全版本 +```C++ version +class Singleton{ + private: + static Singleton* instance; + Singleton(){ + // initialize + } + public: + static Singleton* getInstance(){ + if(instance==nullptr) instance=new Singleton(); + return instance; + } +}; +``` +#### (3) 写三个线程交替打印ABC +```C++ +#include +#include +#include +#include +using namespace std; + +mutex mymutex; +condition_variable cv; +int flag=0; + +void printa(){ + unique_lock lk(mymutex); + int count=0; + while(count<10){ + while(flag!=0) cv.wait(lk); + cout<<"thread 1: a"< lk(mymutex); + for(int i=0;i<10;i++){ + while(flag!=1) cv.wait(lk); + cout<<"thread 2: b"< lk(mymutex); + for(int i=0;i<10;i++){ + while(flag!=2) cv.wait(lk); + cout<<"thread 3: c"<& vec,int a,int b){ + vec[a]=vec[a]^vec[b]; + vec[b]=vec[a]^vec[b]; + vec[a]=vec[a]^vec[b]; +} +int partition(vector& vec,int start,int end){ + int pivot=vec[start+(end-start)/2]; + while(startpivot) end--; + if(start& vec,int start,int end){ + if(start>end) return; + int pivot=partition(vec,start,end); + quickSort(vec,start,pivot-1); + + + quickSort(vec,pivot+1,end); +} +``` +#### (8) 实现一个堆排序 +堆排序的基本过程: +* 将n个元素的序列构建一个大顶堆或小顶堆 +* 将堆顶的元素放到序列末尾 +* 将前n-1个元素重新构建大顶堆或小顶堆,重复这个过程,直到所有元素都已经排序 + +整体时间复杂度为nlogn +```C++ +#include +#include +using namespace std; +void swap(vector& arr, int a,int b){ + arr[a]=arr[a]^arr[b]; + arr[b]=arr[a]^arr[b]; + arr[a]=arr[a]^arr[b]; +} +void adjust(vector& arr,int len,int index){ + int maxid=index; + // 计算左右子节点的下标 left=2*i+1 right=2*i+2 parent=(i-1)/2 + int left=2*index+1,right=2*index+2; + + // 寻找当前以index为根的子树中最大/最小的元素的下标 + if(left&arr,int len){ + // 初次构建堆,i要从最后一个非叶子节点开始,所以是(len-1-1)/2,0这个位置要加等号 + for(int i=(len-1-1)/2;i>=0;i--){ + adjust(arr,len,i); + } + + // 从最后一个元素的下标开始往前遍历,每次将堆顶元素交换至当前位置,并且缩小长度(i为长度),从0处开始adjust + for(int i=len-1;i>0;i--){ + swap(arr,0,i); + adjust(arr,i,0);// 注意每次adjust是从根往下调整,所以这里index是0! + } +} +int main(){ + vector arr={3,4,2,1,5,8,7,6}; + + cout<<"before: "<& nums){ + int len=nums.size(); + for(int i=1;i=0 and nums[j]>key){ + nums[j+1]=nums[j]; + j--; + } + nums[j+1]=key; + } +} +``` + +#### (9) 快排存在的问题,如何优化 +* 3 种快排基准选择方法: + +随机(rand函数)、固定(队首、队尾)、三数取中(队首、队中和队尾的中间数) + +* 4种优化方式: + +优化1:当待排序序列的长度分割到一定大小后,使用插入排序 + +优化2:在一次分割结束后,可以把与Key相等的元素聚在一起,继续下次分割时,不用再对与key相等元素分割 + +优化3:优化递归操作 + +优化4:使用并行或多线程处理子序列 +#### (10) 反转一个链表(招银网络二面) +```C++ +ListNode* reverse(ListNode* root){ + ListNode* pre=nullptr,cur=root,nxt; + while(cur!=nullptr){ + nxt=cur->next; + cur->next=pre; + pre=cur;cur=nxt; + } + return pre; +} +``` +#### (11) Top K问题(可以采取的方法有哪些,各自优点?)(重点) +*Top K 问题的常见形式:* +>给定10000个整数,找第K大(第K小)的数
+ 给定10000个整数,找出最大(最小)的前K个数
+给定100000个单词,求前K词频的单词
+ +*解决Top K问题若干种方法* + +* 使用最大最小堆。求最大的数用最小堆,求最小的数用最大堆。 +* Quick Select算法。使用类似快排的思路,根据pivot划分数组。 +* 使用排序方法,排序后再寻找top K元素。 +* 使用选择排序的思想,对前K个元素部分排序。 +* 将1000.....个数分成m组,每组寻找top K个数,得到m×K个数,在这m×k个数里面找top K个数。 + +1. 使用最大最小堆的思路 (以top K 最大元素为例)
+按顺序扫描这10000个数,先取出K个元素构建一个大小为K的最小堆。每扫描到一个元素,如果这个元素大于堆顶的元素(这个堆最小的一个数),就放入堆中,并删除堆顶的元素,同时整理堆。如果这个元素小于堆顶的元素,就直接pass。最后堆中剩下的元素就是最大的前Top K个元素,最右的叶节点就是Top 第K大的元素。 + +>note:最小堆的插入时间复杂度为log(n),n为堆中元素个数,在这里是K。最小堆的初始化时间复杂度是nlog(n) + +C++中的最大最小堆要用标准库的priority_queue来实现。 +```C++ +struct Node { + int value; + int idx; + Node (int v, int i): value(v), idx(i) {} + friend bool operator < (const struct Node &n1, const struct Node &n2) ; +}; + +inline bool operator < (const struct Node &n1, const struct Node &n2) { + return n1.value < n2.value; +} + +priority_queue pq; // 此时pq为最大堆 +``` + +2. 使用Quick Select的思路(以寻找第K大的元素为例)
+Quick Select脱胎于快速排序,提出这两个算法的都是同一个人。算法的过程是这样的: +首先选取一个枢轴,然后将数组中小于该枢轴的数放到左边,大于该枢轴的数放到右边。 +此时,如果左边的数组中的元素个数大于等于K,则第K大的数肯定在左边数组中,继续对左边数组执行相同操作; +如果左边的数组元素个数等于K-1,则第K大的数就是pivot; +如果左边的数组元素个数小于K,则第K大的数肯定在右边数组中,对右边数组执行相同操作。 + +这个算法与快排最大的区别是,每次划分后只处理左半边或者右半边,而快排在划分后对左右半边都继续排序。 +``` +//此为Java实现 +public int findKthLargest(int[] nums, int k) { + return quickSelect(nums, k, 0, nums.length - 1); +} + +// quick select to find the kth-largest element +public int quickSelect(int[] arr, int k, int left, int right) { + if (left == right) return arr[right]; + int index = partition(arr, left, right); + if (index - left + 1 > k) + return quickSelect(arr, k, left, index - 1); + else if (index - left + 1 == k) + return arr[index]; + else + return quickSelect(arr, k - (index - left + 1), index + 1, right); + +} +``` + +3. 使用选择排序的思想对前K个元素排序 ( 以寻找前K大个元素为例)
+扫描一遍数组,选出最大的一个元素,然后再扫描一遍数组,找出第二大的元素,再扫描一遍数组,找出第三大的元素。。。。。以此类推,找K个元素,时间复杂度为O(N*K) +#### (12) 8G的int型数据,计算机的内存只有2G,怎么对它进行排序?(外部排序)(百度一面) +我们可以使用外部排序来对它进行处理。首先将整个文件分成许多份,比如说m份,划分的依据就是使得每一份的大小都能放到内存里。然后我们用快速排序或者堆排序等方法对每一份数据进行一个内部排序,变成有序子串。接着对这m份有序子串进行m路归并排序。取这m份数据的最小元素,进行排序,输出排序后最小的元素到结果中,同时从该元素所在子串中读入一个元素,直到所有数据都被输出到结果中为止。 + +https://blog.csdn.net/ailunlee/article/details/84548950 + +#### (13) 自己构建一棵二叉树,使用带有null标记的前序遍历序列 +在写二叉树相关算法的时候,如果需要自己构造测试用例(自己构造一棵二叉树),往往是一件很麻烦的事情,我们可以用一个带有null标记的前序遍历序列来进行构造。 **需要注意的是vec2tree()参数中的start是引用传递,而不是简单的参数值传递**。 +```C++ +#include +#include +#include +using namespace std; + +struct treeNode{ + string val; + treeNode* left,*right; + treeNode(string val):val(val){ + left=nullptr; + right=nullptr; + } +}; + +treeNode* vec2tree(vector& vec,int& start){ + treeNode* root; + if(vec[start]=="null"){ + start+=1; + root=nullptr; + }else{ + root=new treeNode(vec[start]); + start+=1; + root->left=vec2tree(vec,start); + root->right=vec2tree(vec,start); + } + return root; +} + +void tree2vec(treeNode *root,vector& vec){ + if(root==nullptr){ + vec.push_back("null"); + }else{ + vec.push_back(root->val); + tree2vec(root->left,vec); + tree2vec(root->right,vec); + } +} + +int main(){ + vector vec={"2","4","5","7","null","null","null","null","3","6","null","null","2","null","null"}; + int index=0,&start=index; + treeNode* root=vec2tree(vec,start); + //displaytree(root); + vector mvec; + tree2vec(root,mvec); + for(string item:mvec) cout<>5
+N%32就是求N的后5位:N& 0x1F (0x1F = 00011111)
+模32然后相应位置置为1: a[i] |= 1<< N & 0x1F
+ +所以总的公式为: a[ N>>5 ] |= 1<< N & 0x1F
+ +**BitMap算法评价** +* 优点: + 1. 运算效率高,不进行比较和移位; + 2. 占用内存少,比如最大的数MAX=10000000;只需占用内存为MAX/8=1250000Byte=1.25M。 +* 缺点: + 1. 所有的数据不能重复,即不可对重复的数据进行排序。(少量重复数据查找还是可以的,用2-bitmap)。 + 2. 所需要的空间随着最大元素的增大而增大,当数据类似(1,1000,10万)只有3个数据的时候,用bitmap时间复杂度和空间复杂度相当大,只有当数据比较密集时才有优势。 + +#### (26) 布隆过滤器原理与优点 +布隆过滤器是一个比特向量或者比特数组,它本质上是一种概率型数据结构,用来查找一个元素是否在集合中,支持高效插入和查询某条记录。常作为针对超大数据量下高效查找数据的一种方法。 + +**它的具体工作过程是这样子的:** +假设布隆过滤器的大小为m(比特向量的长度为m),有k个哈希函数,它对每个数据用这k个哈希函数计算哈希,得到k个哈希值,然后将向量中相应的位设为1。在查询某个数据是否存在的时候,对这个数据用k个哈希函数得到k个哈希值,再在比特向量中相应的位查找是否为1,如果某一个相应的位不为1,那这个数据就肯定不存在。但是如果全找到了,则这个数据有可能存在。 + +**为什么说有可能存在呢?** +因为不同的数据经过哈希后可能有相同的哈希值,在比特向量上某个位置查找到1也可能是由于某个另外的数据映射得到的。 + +**支持删除操作吗** +目前布隆过滤器只支持插入和查找操作,不支持删除操作,如果要支持删除,就要另外使用一个计数变量,每次将相应的位置为1则计数加一,删除则减一。 + +布隆过滤器中哈希函数的个数需要选择。如果太多则很快所有位都置为1,如果太少会容易误报。 + +**布隆过滤器的大小以及哈希函数的个数怎么选择?** +k 为哈希函数个数,m 为布隆过滤器长度,n 为插入的元素个数,p 为误报率 +![](fig/布隆过滤器.png) +#### (27) 布隆过滤器处理大规模问题时的持久化,包括内存大小受限、磁盘换入换出问题 +#### (28)实现一个队列,并且使它支持多线程,队列有什么应用场景(阿里三面) +```C++ +//评测题目: +class FIFOQueue +{ +vector vec(initCap,0); +int start=0,end=0; +condition_variable cv; +mutex m; +bool flag=false;// isFull + bool enqueue(int v) { + unique_lock lk(m); + while(flag==true) cv.wait(lk); + end=(end+1)%initCap; + vec[end]=v; + cv.notifyall(); + return true; + } + } + int dequeue() { + unique_lock lk(m); + if(start!=end){ + int val = vec[start]; + start=(start+1)%initCap; + flag=false; + cv.notifyall(); + return val; + }else{ + flag=false; + cv.notifyall(); + return -1; + } + } +} +``` +以上代码是面试时写的,并没有运行,也许有错误,请客观参考 +# 7. 智力题 +#### (1) 100层楼,只有2个鸡蛋,想要判断出那一层刚好让鸡蛋碎掉,给出策略(滴滴笔试中两个铁球跟这个是一类题) +* (给定了楼层数和鸡蛋数的情况)二分法+线性查找 从100/2=50楼扔起,如果破了就用另一个从0扔起直到破。如果没破就从50/2=25楼扔起,重复。 +* 动态规划 +#### (2) 毒药问题,1000瓶水,其中有一瓶可以无限稀释的毒药,要快速找出哪一瓶有毒,需要几只小白鼠 +用二进制的思路解决问题。2的十次方是1024,使用十只小鼠喝一次即可。方法是先将每瓶水编号,同时10个小鼠分别表示二进制中的一个位。将每瓶水混合到水瓶编号中二进制为1的小鼠对应的水中。喝完后统计,将死亡小鼠对应的位置为1,没死的置为0,根据死亡小鼠的编号确定有毒的是哪瓶水,如0000001010表示10号水有毒。 +#### (3) +#### (4) 先手必胜策略问题:100本书,每次能够拿1-5本,怎么拿能保证最后一次是你拿 +寻找每个回合固定的拿取模式。最后一次是我拿,那么上个回合最少剩下6本。那么只要保持每个回合结束后都剩下6的倍数,并且在这个回合中我拿的和对方拿的加起来为6(这样这个回合结束后剩下的还是6的倍数),就必胜。关键是第一次我必须先手拿(100%6=4)本(这不算在第一回合里面)。 +#### (5) 放n只蚂蚁在一条树枝上,蚂蚁与蚂蚁之间碰到就各自往反方向走,问总距离或者时间。 +碰到就当没发生,继续走,相当于碰到的两个蚂蚁交换了一下身体。其实就是每个蚂蚁从当前位置一直走直到停止的总距离或者时间。 +#### (6) 瓶子换饮料问题:1000瓶饮料,3个空瓶子能够换1瓶饮料,问最多能喝几瓶 +拿走3瓶,换回1瓶,相当于减少2瓶。但是最后剩下4瓶的时候例外,这时只能换1瓶。所以我们计算1000减2能减多少次,直到剩下4.(1000-4=996,996/2=498)所以1000减2能减498次直到剩下4瓶,最后剩下的4瓶还可以换一瓶,所以总共是1000+498+1=1499瓶。 +#### (7)在24小时里面时针分针秒针可以重合几次 +24小时中时针走2圈,而分针走24圈,时针和分针重合24-2=22次,而只要时针和分针重合,秒针一定有机会重合,所以总共重合22次 +#### (8) 有一个天平,九个砝码,一个轻一些,用天平至少几次能找到轻的? +至少2次:第一次,一边3个,哪边轻就在哪边,一样重就是剩余的3个; +第二次,一边1个,哪边轻就是哪个,一样重就是剩余的那个; +#### (9) 有十组砝码每组十个,每个砝码重10g,其中一组每个只有9g,有能显示克数的秤最少几次能找到轻的那一组砝码? +砝码分组1~10,第一组拿一个,第二组拿两个以此类推。。第十组拿十个放到秤上称出克数x,则y = 550 - x,第y组就是轻的那组 +#### (10)生成随机数问题:给定生成1到5的随机数Rand5(),如何得到生成1到7的随机数函数Rand7()? +思路:由大的生成小的容易,比如由Rand7()生成Rand5(),所以我们先构造一个大于7的随机数生成函数。 +记住下面这个式子: +``` +RandNN= N( RandN()-1 ) + RandN() ;// 生成1到N^2之间的随机数 +可以看作是在数轴上撒豆子。N是跨度/步长,是RandN()生成的数的范围长度,RandN()-1的目的是生成0到N-1的数,是跳数。后面+RandN()的目的是填满中间的空隙 +``` +比如` Rand25= 5( Rand5()-1 ) + Rand5()`可以生成1到25之间的随机数。我们可以只要1到21(3*7)之间的数字,所以可以这么写 +``` +int rand7(){ + int x=INT_MAX; + while(x>21){ + x=5*(rand5()-1)+rand5(); + } + return x%7+1; +} +``` +#### 赛马:有25匹马,每场比赛只能赛5匹,至少要赛多少场才能找到最快的3匹马? +* 第一次,分成5个赛道ABCDE,每个赛道5匹马,每个赛道比赛一场,每个赛道的第12345名记为 A1,A2,A3,A4,A5 B1,B2,B3,B4,B5等等,这一步要赛5场。 +* 第二次,我们将每个赛道的前三名,共15匹。分成三组,然后每组进行比赛。这一步要赛3场。 +* 第三次,我们取每组的前三名。共9匹,第一名赛道的马编号为1a,1b,1c,第二名赛道的马编号为2a,2b,2c,第三名赛道的马编号为3a,3b,3c。这时进行分析,1a表示第一名里面的第一名,绝对是所有马中的第一,所以不用再比了。2c表示第二名的三匹里头的最后一匹,3b和3c表示第三名里面的倒数两匹,不可能是所有马里面的前三名,所以也直接排除,剩下1b,1c,2a,2b,,3a,共5匹,再赛跑一次取第一第二名,加上刚筛选出来的1a就是所有马里面的最快3匹了。这一步要赛1场。 +* 所以一共是5+3+1=9场。 +#### 烧 香/绳子/其他 确定时间问题:有两根不均匀的香,燃烧完都需要一个小时,问怎么确定15分钟的时长? +(说了求15分钟,没说开始的15分钟还是结束的15分钟,这里是可以求最后的15分钟)点燃一根A,同时点燃另一根B的两端,当另一根B烧完的时候就是半小时,这是再将A的另一端也点燃,从这时到A燃烧完就正好15分钟。 + +#### 掰巧克力问题 N*M块巧克力,每次掰一块的一行或一列,掰成1*1的巧克力需要多少次?(1000个人参加辩论赛,1V1,输了就退出,需要安排多少场比赛) +每次拿起一块巧克力,掰一下(无论横着还是竖着)都会变成两块,因为所有的巧克力共有N\*M块,所以要掰N\*M-1次,-1是因为最开始的一块是不用算进去的。 + +每一场辩论赛参加两个人,消失一个人,所以可以看作是每一场辩论赛减少一个人,直到最后剩下1个人,所以是1000-1=999场。 +# 8. 大数据 +#### 1. 介绍一下Hadoop +Hadoop是一套大数据解决方案,提供了一套分布式的系统基础架构,包括HDFS,MapReduce和YARN。 +* HDFS提供分布式的数据存储 +* MapReduce负责进行数据运算 +* YARN负责任务调度 + +HDFS是主从架构的,包括namenode,secondarynamenode和datanode。datanode负责存储数据,namenode负责管理HDFS的目录树和文件元信息。
+MapReduce包括jobtracker,tasktracker和client。Jobtracker负责进行资源调度和作业监控。tasktracker会周期性的通过心跳向jobtracker汇报资源使用情况。 +#### 2. 说一下MapReduce的运行机制 +MapReduce包括输入分片、map阶段、combine阶段、shuffle阶段和reduce阶段。分布式计算框架包括client,jobtracker和tasktracker和调度器。 +* 输入分片阶段,mapreduce会根据输入文件计算分片,每个分片对应一个map任务 +* map阶段会根据mapper方法的业务逻辑进行计算,映射成键值对 +* combine阶段是在节点本机进行一个reduce,减少传输结果对带宽的占用 +* shuffle阶段是对map阶段的结果进行分区,排序,溢出然后写入磁盘。将map端输出的无规则的数据整理成为有一定规则的数据,方便reduce端进行处理,有点像洗牌的逆过程。 https://blog.csdn.net/ASN_forever/article/details/81233547 +* reduce阶段是根据reducer方法的业务逻辑进行计算,最终结果会存在hdfs上。 + +#### 3. 介绍一下kafka +https://blog.csdn.net/qq_29186199/article/details/80827085 + +https://blog.csdn.net/student__software/article/details/81486431 + +kafka是一个分布式消息队列,包括producer、broker和consumer。kafka会对每个消息根据topic进行归类,每个topic又会分成多个partition,消息会根据先进先出的方式存储。消费者通过offset进行消费。 + +kafka的特点是吞吐量高,可以进行持久化,高可用。 +#### 4. 为什么kafka吞吐量高?/介绍一下零拷贝 +kafka吞吐量高是因为一个利用了磁盘顺序读写的特性,速度比随机读写要快很多,另一个是使用了零拷贝,数据直接在内核进行输入和输出,减少了用户空间和内核空间的切换。 + +零拷贝:传统文件读取并发送至网络的步骤是:先将文件从磁盘拷贝到内核空间,然后内核空间拷贝到用户空间的缓冲区,再从用户空间拷贝到内核空间的socket缓冲区,最后拷贝到网卡并发送。而零拷贝技术是先将文件从磁盘空间拷贝到内核缓冲区,然后直接拷贝至网卡进行发送,减少了重复拷贝操作。 +#### 5. 介绍一下spark +https://blog.csdn.net/u011204847/article/details/51010205 + +spark是一个通用内存并行计算框架。它可以在内存中对数据进行计算,效率很高,spark的数据被抽象成RDD(弹性分布式数据集)并且拥有DAG执行引擎,兼容性和通用性很好。可以和Hadoop协同工作。 +#### 6. 介绍一下spark-streaming +https://blog.csdn.net/yu0_zhang0/article/details/80569946 + +spark-streaming是spark的核心组件之一。主要提供高效的流计算能力。spark-streaming的原理是将输入数据流以时间片进行拆分,然后经过spark引擎以类似批处理的方式处理每个时间片数据。 + +spark-streaming将输入根据时间片划分成一段一段的Dstream(也就是离散数据流),然后将每一段数据转换成RDD进行操作。 + +#### 7. spark的transformation和action有什么区别 +spark的算子分成transformation和action两类 +* transformation是变换算子,这类算子不会触发提交,是延迟执行的。也就是说执行到transformation算子的时候数据并没有马上进行计算,只是记住了对RDD的逻辑操作 +* action算子是执行算子,会出发spark提交作业,并将数据输出到spark +#### 8. spark常用的算子说几个 +spark的算子分为两类:transformation和action + +常用的transformation算子: +```scala +// union 求并集 +val rdd8 = rdd6.union(rdd7) + +// intersection 求交集 +val rdd9 = rdd6.intersection(rdd7) + +// join 将rdd进行聚合连接,类似数据库的join +val rdd3 = rdd1.join(rdd2) + +// map flatMap mapPartition 传入一个函数对数据集中的每一个数据进行操作 +val arr1 = Array(1,2,3,4,5) +val arr2 = rdd1.map(_+1) + +// countByKey reduceByKey partitionByKey 统计每个key有多少个键值对 +``` +常用的action算子 +```scala +// reduce 按照一定的方法将元素进行合并 +val rdd2 = rdd1.reduce(_+_) + +// collect 将RDD转换为数组 +rdd1.collect + +// top 返回最大的k个元素 +rdd1.top(2) +``` +#### 9. 如何保证kafka的消息不丢失 +https://blog.csdn.net/liudashuang2017/article/details/88576274 + +我们可以从三个方面保证kafka不丢失消息 +* 首先从producer生产者方面,为send()方法注册一个回调函数,可以得知消息发送有没有成功;将重试次数retrie设置为3;设置acks参数为all,当消息被写入所有同步副本之后才算发送成功。 +* 在consumer消费者方面,关闭自动提交; +* 在broker集群方面,设置复制系数replica.factor为大于等于3 +#### 10. kafka如何选举leader +首先启动的broker在zookeeper中创建一个临时节点并让自己称为leader,其他的节点会创建watch对象进行监听并成为follower,当broker宕机的时候,其他follower会尝试创建这个临时节点,但是只有一个能够创建成功,创建成功的broker就会成为leader。 + + +#### 11. 说下spark中的宽依赖和窄依赖 +https://blog.csdn.net/a1043498776/article/details/54889922 + +* 宽依赖:指子RDD的分区依赖于父RDD的所有分区,举例:groupbykey,join +* 窄依赖:指父RDD的每个分区被最多一个子RDD的分区所依赖,举例:map,filter +![](https://img-blog.csdn.net/20170206221148299?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYTEwNDM0OTg3NzY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) +#### 12. 说下spark中stage是依照什么划分的 +https://zhuanlan.zhihu.com/p/57124273 + +spark中的stage其实是一组并行的任务,spark会将多个RDD根据依赖关系划分成有向无环图DAG,DAG会被划分成多个stage,划分的依据是RDD之间的宽窄依赖。遇到宽依赖就划分stage。因为宽依赖与窄依赖的区别之一就是宽依赖会发生shuffle操作,所以也可以说stage的划分依据是是否发生shuffle操作。 + +#### 13. spark的内存管理是怎样的 +https://www.jianshu.com/p/4f1e551553ae + +https://www.cnblogs.com/wzj4858/p/8204282.html + +spark的内存包括静态内存管理和统一内存管理两种机制。静态内存管理中存储和执行两块内存区域是分开的,统一内存管理中两块内存之间可以相互借用
+* 静态内存管理:静态内存管理机制下堆内内存包括安全内存,存储内存,shuffle内存和unroll内存 + +![](fig/spark内存一.png) + * 统一内存管理:统一内存管理机制下内存分为spark内存,用户内存和保留内存三部分。用户内存存放用户代码逻辑和自定义数据结构等,保留内存存放的是spark的内部对象和逻辑。 + ![](https://upload-images.jianshu.io/upload_images/195230-f119edabb5683f38.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp) + +#### 14. spark的容错机制是什么样的 +https://blog.csdn.net/dengxing1234/article/details/73613484 + +spark的容错机制是通过血统(lineage)和checkpoint来实现的 。 + +* RDD的lineage可以看作是一个重做日志(redo log)记录的是它粗粒度上的transformation操作。当rdd的分区数据丢失时,它可以根据lineage重新计算来恢复数据。 在窄依赖上可以直接计算父RDD的节点数据进行恢复,在宽依赖上则要等到父RDD所有数据计算完后并将结果shuffle到子RDD上才能完成恢复。 +* 如果DAG中的lineage过长,或者在宽依赖上进行checkpoint的收益更大,就会使用checkpoint进行容错,将RDD写入磁盘进行持久化存储,如果节点数据丢失,就从磁盘读取数据进行恢复。 + + + + + + + +# 9. HR面 +#### 1. 自我介绍 +(HR面试的自我介绍可以侧重软实力部分,项目技术方面介绍可以适当少一些) +#### 2. 项目中遇到的最大难点 +* 在项目中曾经遇到了新的框架不知道该如何上手的问题,以及面对新的概念,新的技术不知道从何学起。解决的办法是在官网寻找说明文档和demo,按照说明文档上的内容一步步了解,以及咨询身边有用过这个框架的同学,或者在CSDN上寻找相关博客。 + +* 项目的时间比较紧迫,没有那么多的时间可以用。解决方法是把还没有完成的项目分一个轻重缓急,在有限的时间里,先做重要而且紧急的,然后完成紧急的,再做重要的。利用轻重缓急做一个取舍。 +#### 3. 项目中的收获 +一个是了解了相关框架的使用方法(比如Dataframe的使用,xgboost的使用等等),这些框架或者技术可以在以后的开发中使用到。和对自己开发能力的锻炼。 + +一个是锻炼了与他人的交流能力,因为在团队项目里经常会跟别人汇报自己的想法和进度,同时也会跟其他成员沟通模块之间的交互,所以在这个过程中对自己的表达能力和理解能力都是一个很大的提升。 +#### 4. 可以实习的时间,实习时长 +一定要往长了说!半年起步,最好七八个月,因为实习生是可以随时跑路的。而且实习时间越长HR越青睐。 +#### 5. 哪里人 +#### 6. 说一下自己的性格 +我是比较内向谨慎的人,平时做的多说的少。比较善于总结,在与人交流的时候更倾向于倾听别人的意见后才发言。并且别人都说我办事认真靠谱。 +#### 7. 你的优缺点是什么 +我的缺点是容易在一些细节的地方花费太多的时间,有时候过分追求细节。并且我的实习经验比较缺乏,对于实际项目的业务流程和工作流程不是很了解。(所以我打算通过实习来熟悉实际的软件开发的流程和技术。) + +我的优点是责任心比较强,做事比较负责,在校期间我负责的大创项目进展很顺利,我经常组织组员们进行讨论和推进项目的开发,最后这个项目得到了92的评分,在同级别里面是比较高的。 +#### 8. 有什么兴趣爱好,画的怎么样/球打的如何/游戏打的怎么样 +平时的爱好是画画打游戏,在CSDN写写博客,还有就是看书,我很喜欢学到新知识掌握新技能的感觉。 +#### 9. 看过最好的一本书是什么 +技术类:编程之美 机器学习西瓜书 STL源码剖析 剑指offer C++primer plus + +非技术类:明朝那些事儿 香水(聚斯金德) 解忧杂货店 人类简史 沉默的大多数 与时间做朋友(李笑来) 千年历史千年诗 +#### 10. 学习技术中有什么难点 +#### 11. 怎么看待加班 +我觉得 任何一家单位都有可能要加班。如果自己的工作没有按时完成,那自觉加班是理所当然的,当然,自己要不断提高工作效率,避免这种原因导致的加班。如果遇到紧急任务或者突发状况时,为了顺利配合团队完成任务,我会尽自己所能加班共同完成。 +#### 12. 觉得深圳怎么样(或者其他地点) +#### 13. 遇见过最大的挫折是什么,怎么解决的 + +#### 14. 职业规划 +在工作的第一个阶段,先尽快适应工作的环境,包括开发环境开发工具和工作流程等,把自己负责的部分快速的完成,不能出差错。第二个阶段要熟悉整个项目的业务流程,所有模块的结构和依赖关系,知道每个模块为什么要这么设计,以及它们的实现细节。第三个阶段要培养独立设计一个项目的能力,可以独立或者在别人的协作下设计项目的模块分工和架构。 + +在工作和项目中多写博客或者笔记,积累技术影响力,将经验总结成文档。同时与同事搞好关系,尝试培养领导能力和组织能力。 + +#### 15. 目前的offer情况 +可以如实说 +#### 16. 你最大的优势和劣势是什么 +* 优势:做事情有主动性,不拖沓,有责任心。举个例子:在做论文课题的时候,几乎都是我自己找老师汇报进度和找老师讨论问题,很少有被老师催的时候。每一次跟老师讨论之后都会将讨论的内容和老师提出的意见进行详细记录。在中软杯的比赛中,主动承担答辩ppt的制作,并且每次排练之后都迅速对ppt的修改意见进行落实修改,前前后后改了十几版。 +* 劣势:有时候做事情比较急躁,容易导致粗心。 +#### 17. 介绍在项目里面充当的角色 +#### 18. 介绍一下本科获得的全国赛奖项的情况 +#### 19. 最有成就感的事情/最骄傲的一件事情 +* 本科的时候跟优秀的队友们一起参加中国软件杯比赛努力了四个月,最后获得了该赛题的第一名和全国一等奖的好成绩 +* 保研夏令营拿到了四个学校的offer +#### 20. 在实验室中担任什么角色,参加的XXX能聊聊吗 +#### 22. 用两个词来形容自己 +踏实 认真 \ No newline at end of file