C++秋招学习随笔
文章平均质量分 60
zsiming
Why do we fall?
展开
-
C++ STL迭代器失效问题
在STL里面的容器往往封装了迭代器。迭代器能让人很方便的拿到容器里面的元素进行操作。然而,在不同结构的容器中,删除一个迭代器可能会引发迭代器失效的问题。下面针对不同的容器类型做一下总结。原创 2022-09-15 22:06:15 · 1780 阅读 · 0 评论 -
设计模式:饿汉式和懒汉式单例模式(C++实现)以及日志系统的实现
在一个项目的日志系统里面,我们常常会发现日志模块的实现是使用单例模式。单例模式的特点和它的名字一样,就是一个类能且只能实例化出一个唯一的对象。那么这样做有什么好处呢?原创 2022-09-09 16:03:30 · 1142 阅读 · 2 评论 -
LRU算法学习笔记:实现以及应用
所谓LRU(Least Recently Used)就是最近最少使用算法。其核心的思想就是根据每个节点的最后一次访问事件来对每个结点进行排序。最近一次使用的排在队头,依次类推。原创 2022-09-07 22:24:40 · 572 阅读 · 0 评论 -
Linux下排除死锁详细教程(基于C++11、GDB)
在实际编写项目的过程中,经常涉及到多线程。多线程的程序编程很大概率会涉及到线程安全的问题,因此往往使用互斥锁来保证线程安全。然而,互斥锁的使用却经常会导致另外一个问题:死锁。所谓死锁,通俗的讲就是有两个共享资源,一个在你手上,一个在我手上。我等着你用完把另一个给我,你等我用完给你,这样相互等待就形成了死锁。所以,在这里,基于Linux的环境,使用C++11提供的多线程编程来模拟死锁,并尝试从“不知情”的角度,使用进行死锁的排查。原创 2022-09-05 14:15:26 · 3051 阅读 · 1 评论 -
设计模式:装饰器模式(C++实现)
首先先上结论:装饰器模式的设计场景是为已有的类增加新的功能。有人可能会觉得有些奇怪:增加新的功能可以通过继承的方式进行实现。例如,我通过public继承把父类的东西全部继承过来,接口也全部继承过来,然后在子类的基础上增加新的接口不就行了?其实理论上确实是可以的,但是这种方式通过继承+增加功能(接口)的方式在某些情况下可能会存在冗余,而装饰器的设计模式可以减少这种冗余。例如,假设现在有两个个汽车的厂家:BWM、Benz。原创 2022-09-04 19:44:09 · 368 阅读 · 0 评论 -
设计模式:适配器模式(C++实现)
那么这时候就需要一个转换器(适配器)来作为中间的桥梁,来将HDMI的信号转换为老的信号,从而被显示器接收工作。比如我们电脑连接投影仪(或者显示屏)的时候,老一点的电脑接口经常是VGA的,而新一点的可以是HDMI的或者TypeC的。这里的 Computer01 是第一代的电脑,比较老,只能输出VGA的信号,而投影仪只能接受一个VGA的老对象,这样工作是没问题的。这个老的一般指的是“接口传入的对象是老的”。VGAToHDMIAdaptor虽然是一个“旧对象”,但是它的构造函数可以接受一个新的对象。原创 2022-09-04 16:12:08 · 461 阅读 · 0 评论 -
web服务器改进:迷你线程池+CAS自旋锁(Demo)
最近有学习一些关于C++11从语法层面上提供的多线程编程。本来考虑使用C++11的互斥锁来实现线程间的互斥,而C++提供的互斥锁是比较简单的,和Linux的使用没有太大区别。另一方面,在面试的时候,会涉及一些无锁的互斥手法。在webserver项目中,请求队列的任务在出队和入队上都需要互斥锁。而互斥锁比较重,需要执行系统调用。而系统调用又需要陷入内核态,调度完切换回用户态又需要时间,因此效率较低。这时候,采用CAS+自旋锁的手法效率会比较高。因此,下面的例子借鉴muduo网络库实现一个迷你版的线程池。...原创 2022-08-30 21:06:00 · 312 阅读 · 0 评论 -
有向图、无向图相关数据结构
最近面试的公司是做地图业务相关的,可能在路径规划、路径搜索方面会涉及图相关的知识,因此趁着这个机会把图相关的知识补充整理一下。如果对图进行大分类的话,主要可以分为有向图和无向图。所谓有向,指的就是从A点到点B这条路径是成立的,而从点B到点A这条路径不一定成立。而无向图的话,从A可以到B的话,意味着也可以从B到A。邻接矩阵、邻接表、十字链表、邻接多重表。1. 邻接矩阵来表示图的话比较直观。可以根据每一行来判断每一个顶点出度对应的终点在哪,根据每一列可以判断每一个顶点的入度的起点是谁。原创 2022-08-20 16:20:18 · 1759 阅读 · 0 评论 -
C++ 优先级队列(运用绑定器实现大小根堆)
但是,对于大根堆来说,每个节点的父节点是需要大于它的孩子节点的。刚插入的、放在叶子节点中的这个节点可能比它的夫节点还要大,因此需要进行。对于出堆,首先需要记录数组的第一个元素(相当于堆顶元素)的值,然后用最后一个值覆盖第一个元素。对于入堆,一开始其实把入堆的元素放到数组的最后面。放在最后的这个元素在二叉树里面相当于就在叶子结点中。这时新的堆顶元素不一定能符合父节点大于两个孩子节点的条件,因此需要进行。中,其实是使用大小根堆实现的(默认是大根堆)。绑定了一根函数对象来自定义的实现大小根堆。...原创 2022-08-18 08:58:55 · 396 阅读 · 0 评论 -
海量数据TopK问题
可以使用大根堆来实现。迭代数据,在迭代过程中,堆里面维护了目前为止的遇到的前K个重复次数最小的元素。迭代完成时,取出即可。大根堆的调整为O(logk)的时间复杂度,k为大根堆的层数。因此是常量时间,可以去掉。只剩下O(n)的遍历元素的时间。从海量数据中找出出现次数最少的前K个值,且算法复杂度为O(n)。...原创 2022-07-23 15:55:34 · 241 阅读 · 0 评论 -
一文详解C++11的三种智能指针
在C++11中出现三种智能指针,分别是unique_ptr、shared_ptr、weak_ptr。这三种智能指针分别有各自的应用场景,同时这些应用场景是有相互联系的。和名字一样,unique_ptr的特点就是“独占”的意思,多个unique_ptr无法指向同一对象。相比于裸指针,unique_ptr的最大优势当然是超出作用域时会自动释放资源。其实unique_ptr解决的并不只是资源自动释放的问题。在C++11之前还有这两种类型的指针,unique_ptr在资源自动释放的同时也假设有下面的代码:在上面的原创 2022-07-14 16:24:36 · 3491 阅读 · 1 评论 -
一致性哈希
这里的哈希结果不变很好理解。通常如果我们的哈希函数采用了除留余数法,比如现在我有三个数100、54、2。如果我想一个哈希函数来把这三个数放进一个连续的数组里面,那么我只要对这些数都模上3即可。所以:那么,多次结果不变有什么用?鲁棒性是什么意思?所以,有没有可能第三台服务器崩了只影响第三台服务器的原本客户,而不影响其他服务器的客户呢?...原创 2022-07-12 22:51:35 · 556 阅读 · 0 评论 -
C++ 手撕红黑树(二):删除操作
前情概述:C++ 手撕红黑树(一):节点的插入当需要删除的节点颜色是黑色时,最基本的情况如下所示:情况1,兄弟是黑色的,而且有一个红色的右孩子:情况2,兄弟是黑色的,但是右孩子是黑色的,而左孩子是红色的:情况三:兄弟的孩子都是黑色的总结一下:3. 整棵红黑树的实现5.测试结果...原创 2022-07-11 21:47:29 · 578 阅读 · 3 评论 -
C++ 手撕红黑树(一):插入操作
红黑树的出现主要是用来解决AVL树旋转次数过多的问题。AVL树在插入节点或者删除节点的过程中(每层递归完回溯时)都需要检查左子树或者右子树之间的高度差是否小于等于1,如果不平衡(大于1)的话需要旋转或者平衡操作来维持平衡,因此在数据量比较大的时候可能旋转了很多次(接近logn次),效率比较差。而红黑树在插入和删除的时候,最多做两次旋转操作。因此旋转次数比较少。然而,值得注意的是,红黑树是一颗BST树但并不是一颗平衡的树。......原创 2022-07-11 11:10:26 · 547 阅读 · 3 评论 -
C++ AVL树手撕代码实现
AVL树是为了解决BST(二叉搜索树)树在一些情况下树的形状退化成线性链表而诞生的一种树。它有着BST树的特点(中序遍历是顺序的),但是有新的特点:因此,AVL树是高度平衡的二叉搜索树。为了保持平衡,在四种情况下,树的结构要进行变形。这四种情况分别为:在发生上面四种情况时,则需要变形操作。变形的操作分为左旋和右旋。下面是四种情况的示意图:2.2 右旋2.3 左平衡(左旋+右旋)2.4 右平衡二、AVL树的实现AVL树的重点在于操作。3.删除操作在找到需要删除的节点后,可以分为三种情况原创 2022-07-01 20:45:54 · 400 阅读 · 0 评论 -
new、express new、operator new、placement new 之间的千丝万缕
第一,编译器帮我们调用了operator new 来 申请一块干净的内存(未经过初始化)。第二,编译器帮我们把得到那块内存用static_cast函数进行类型转换成对应的类型。(C++的四种cast动作详解)第三,编译器帮我们调用了构造函数,在分配的内存中进行对象的构造。 在步骤一中 operator new 接收了一个参数,指明了“我想要多少字节的内存”。而对于后续的步骤二、三来说,从步骤一中得到的内存究竟是从哪儿来的其实它们并不关心,毕竟它们的职责只是构造对象而已。因此,我们就能对某些版本的op原创 2022-06-28 18:16:43 · 382 阅读 · 0 评论 -
Linux下Makefile操作
如:上面的例子一共有两条规则,第二条规则是生成add.o文件。但是,第一条规则生成app文件的过程中,和add.o并没有任何关系,因此第二条规则是不会执行的。如图:执行make命令之后:可以看见,make 命令之后并没有执行第二条规则。而且用ls命令查看文件发现没有add.o文件生成。写Makefile文件的过程中,可以通过定义变量、使用函数的办法来少打一些命令和文件的名称。比如,在上面的Makefile 文件中,很多.c文件都是手打的,因此,我们可以通过函数的方式来获取这些.c文件的文件名原创 2022-06-26 18:38:29 · 5239 阅读 · 0 评论 -
new和 delete 细细碎记录
如图所示,假设我们需要对类Complex申请动态内存,在调试模式下和在非调试模式的如左1,左2所示,其中:左3和左4为String对象申请分配的内存,String对象在设计时往往带有一根char* 的指针指向自己的字符串,因此分配一个String对象只需要4个字节。后面分析同上。与分配单个对象相比,在VC下多了四个字节来存储内存中有几个对象(白色块)。由于这块内存记录了里面究竟有多少个对象的空间,delete[] 时如果调用了delete其实不会造成这块内存的泄露。而因为delete和delete[]相比,原创 2022-06-24 15:51:03 · 134 阅读 · 0 评论 -
插入排序C+++实现
插入排序C+++实现原创 2022-06-13 14:18:05 · 134 阅读 · 0 评论 -
堆排序算法C++实现
堆排序算法原创 2022-06-13 13:59:49 · 166 阅读 · 0 评论 -
C++ 的四种转型(casting)动作
C++ 的四种转型(casting)动作1. const_castconst_cast 是用来将const变量转化为非 const 的一种手段。而且,在四种 casting 手段中,只有 const_cast 有这种能耐!。其最常见于函数的匹配上,比如:void fun(int* ptr);函数 fun 要求传入的参数是一个指向 int 类型的指针,也就是这个指针指向的内容可能是可以改变的。当我们手里只有一个const int 类型的变量 a 时,是没办法传给这个函数的:const int a原创 2022-05-27 16:37:58 · 752 阅读 · 0 评论 -
C++11 NULL和nullptr 的区别
C++11 NULL和nullptr 的区别1. 解决的问题在C语言中, NULL被定义为(void*) 0,也就是将常数0转换为void* 类型的指针。由于在C语言中,支撑void* 类型的指针的隐式转换,因此这种做法是行得通的。然而,在C++中却不支持这种隐式转换了,也就是: int* a = (void*)0; //将void*类型转换成int* 在C++中已经行不通了那怎么办呢?所以在c++中,NULL的定义其实是:# define NULL 0也就是NULL的定义其实原创 2022-05-23 16:32:13 · 132 阅读 · 0 评论 -
C++ allocator 类的使用
C++ allocator 类的使用1. 用来解决的问题allocator类主要是用来解决new的一些缺点的。比如,有时候我们申请了一整块的内存但是又不想初始化的时候,new是没办法做到的。比如:string * ptr = new string[100]new在构造的过程中同时调用了100次string的默认构造函数将将100个string给初始化了。而我们需要的可能是3个string,而且等到需要用的时候再进行初始化,这样效率比较高。因此,产生了allocator类,用来把"内存分配"原创 2022-05-21 17:16:25 · 398 阅读 · 1 评论 -
C++ 之二分查找(指定数、左边界、右边界)模板
C++ 之二分查找(指定数、左边界、右边界)模板1. 查找指定数int findTarget(vector<int>& nums, int target) { int left = 0, right = nums.size() - 1; while (left <= right) { int mid = left + right >> 1; if (nums[mid] == target) { return mid; } else if原创 2022-05-21 10:53:32 · 662 阅读 · 0 评论 -
C++ 之归并排序模板
C++ 之归并排序模板描述: 归并排序其实是分治的思想。先数组多次分成左边区间和右边区间,直到每个区间只有一个元素的时候开始将左区间和右区间合并(排序的过程在合并中),直到恢复成原来的数组。#include <iostream>#include <vector>using namespace std;// 合并(排序函数)void mergeSort(vector<int>& nums, int left, int mid, int right) {原创 2022-05-20 10:36:02 · 327 阅读 · 0 评论 -
C++ lower_bound 的库使用和自己实现
C++ lower_bound 的库使用和自己实现1. 库函数的使用功能:查找一个数组中的第一个大于或等于指定值的数。返回一个迭代器。#include <iostream>#include <vector>#include <algorithm>using namespace std;int main() { vector<int> nums = {1, 4, 5, 5, 8, 9, 3, 2}; sort(nums.begin(), n原创 2022-05-20 10:02:26 · 402 阅读 · 0 评论 -
define 和 typedef 的区别
define 和 typedef 的区别描述definetypedef类型检查无有作用时间预编译期编译期是否分配内存不分配内存,给出的是立即数在静态存储区分配内存,在程序运行的期间只发生了一次拷贝作用域没有作用域的限制只能在定义的作用域内使用易错点://对于typedef,不能简单的进行字符串的替换:typedef char* pstring; const pstring ptr = char* const ptr//因为const对p原创 2022-05-17 14:50:39 · 196 阅读 · 0 评论 -
C++中的内存模型
1.代码段存放可执行的代码。2. 数据段存放已经初始化的静态变量或者全局变量。3. BSS段存放未初始化的静态变量或者全局变量。(存放在这里系统将其初始化为0)4.堆用于申请动态内存,比如new。5. 栈用于存放局部变量。...原创 2022-05-13 22:28:09 · 250 阅读 · 0 评论 -
常量指针 和 指针常量
首先需要搞懂这样的叫法:常量指针,首先是常量然后它才是一个指针。而指针常量,首先它是一个指针,然后指向了一个常量对象。因此,常量指针,既然首先是一个常量,所以,它至始至终指向的对象不会改变: int a = 0; int b = 0; int * const p = &a; p = &b;最后一行试图把这个常量指针改变指向对象,因此是报错的。对于指针常量: const int a = 0; const int*p = &a; int *q = &am原创 2021-09-09 17:09:52 · 136 阅读 · 0 评论