- 博客(51)
- 收藏
- 关注
原创 权限及权限操作
这就造成了上面我们所看到的权限的。我们的权限中的4表示r权限,2表示w权限,1表示x权限,其他的都是这三个数字组合形式,比如6 就是 r 和w权限。我们不要将上面的权限的去除简单理解为减法,实际的运算是这样的。
2024-05-12 21:05:33 895
原创 Linux 基础命令
如果我们想要提取中间的几行打印应该怎么办呢?比如我们要打印 990-1000这十行,该怎么办呢?我们可以用head和tail指令配合管道来进行head 默认是把前n行打印在我们的显示器上,而如果后面是管道 | 的话,就相当于将提取出来的数据重定向到管道中,然后tail 再从管道中去提取后 x 行,而 tail后面没有接管道了,意思就是到了管道的出口,于是就打印到他的默认的位置也就是显示器中了。
2024-05-08 20:00:46 1157
原创 string 模拟实现
string是用来存储字符类型数据的容器,由于string比STL出现的早,所以在C++官方网string并不在container分类中,但是我们也可以称它为容器。string有两个类模板,我们主要学习的是 basic_string这个类模板实例化出来的 string (basic_string<char>的重命名)类。我们在C语言中学的字符都是以ASCII码的形式来表示和存储的,但是ASCII码只能表示128个字符。
2024-05-03 20:02:22 7
原创 C++模板初阶
而在main.o中,由于没有函数的定义,虽然它调用了模板函数,但是在他的文件里,只有模板函数的声明,没有定义,所以编译器在编译的时候,虽然能够推演出具体的类型,但是他只能替换声明中的T,所以在main.o中,相当于只有函数的声明,而没有实现,所以他的符号表中这个函数关联的是一个无效的地址。在模板上是不能写明具体的类型的,如果写明具体的类型,这就不是模板而是具体的函数了,要做到与类型无关,就需要用一个虚拟类型,而class 和typename 就是虚拟的类型,他么们不是具体的类型。这就是我们下面的类模板。
2024-04-28 22:30:34 803
原创 C++ 内存管理
目录1.内存区域划分2.C语言内存管理方式3.C++的内存管理方式4.operator new 和operator new[ ]5.operator delete 和operator delete[ ] 6.定位new表达式7.内存泄漏在C语言中,我们了解了内存是分区域使用的,栈区存储局部变量,动态开辟内存在堆区,静态和全局变量在静态区,而常量则存储在常量区代码段中的可执行代码是什么呢?我们写了这么多的代码,首先我们要知道我们写的程序平常都是存在文件中的,出现经过编译连接之后形成一个可执行程序,他也是存在磁
2024-04-27 20:50:28 829
原创 类与对象(四)
首先要理解的一点就是,类中的所有成员都会走初始化列表,不是显式就是隐式,所有成员的变量的初始化都是在初始化列表中完成的。这很容易理解,就拿我们上面的日期类来说,我们并没有写初始化列表,但是在进入构造函数体之前所有成员变量都已经初始化了。对于自定义类型的成员变量,我们如果不显式写在初始化列表中对其初始化的话,就会调用它的默认构造进行初始化。
2024-04-26 22:19:11 1009 2
原创 类与对象(三) 拷贝构造与赋值运算符重载
那么对于我们的Date类,编译器自动生成的拷贝构造就能用了,那么我在什么情况下需要我们自己去写拷贝构造呢?我们前面的 Stack 和MyQueue这两个类对于Stack类,如果我们直接用编译器自动生成的拷贝构造会不会出现问题呢?
2024-04-24 22:45:21 1104
原创 类与对象(二) 构造函数与析构函数
为什么编译器对内置类型不做处理呢?因为编译器也不清楚这些变量是用来做什么的,不知道要给他们什么样的初始值才合适,不管怎么样总有情况不会满意,所以编译器也不敢处理内置类型。所以说,自动生成的默认构造函数还是会做事情的,只是对内置类型不处理。对于自定义类型,自动生成的构造函数会去调用该类型的构造函数,这个怎么理解呢?我们可以拿之前的用栈实现队列来举例,我们的队列使用两个栈来完成的。
2024-04-23 20:21:56 1254
原创 C++类与对象(一)
虽然C++中兼容C语言的结构体,但是对于大多数人来说,对于类的定义我们还是更喜欢用class来定义,class是C++中用来定义类的关键字,//类体:类成员函数 + 类成员变量在类的定义里面,没有规定成员函数和成员变量的位置,我们可以按任意顺序定义变量和函数,因为类是一个整体,编译器在搜索类的时候是不分上下前后顺序的,会直接搜索整个类。但是我们更建议把函数定义在一起,变量定义在一起,这样我们去找成员变量的时候更加方便。assert(_a);_size = 0;_size++;
2024-04-22 22:27:09 853
原创 C++基础入门
C语言是结构化和模块化的语言,适合处理较小规模的程序。对于复杂的问题,规模较大的程序,需要高度的抽象和建模时,C语言则不适合,为了解决软件危机,20世纪80年代,计算机界提出了OOP(object oriented programming):面向对象思想,支持面向对象的程序设计语言应运而生1982年,Bajarne Stroustrup博士在C语言的基础上引入并扩充了面向对象的概念,发明了一种新的程序语言。为了表示该语言与C语言的渊源关系,命名为C++。因此:C++是基于C语言产生的,
2024-04-21 20:50:09 1064
原创 排序(五)——非比较排序+排序总结
我们前面学了几种比较排序,这里我们对他们进行一个总结时间复杂度直接插入排序:O(N^2)希尔排序: O(N^1.3)选择排序: O(N^2)堆排序: O(NlogN)冒泡排序: O(N^2)快排: 最好O(NlogN),最坏O(N^2),但是三数取中优化之后一般不会出现最坏归并排序: O(NlogN)空间复杂度直接插入排序:O(1)希尔排序: O(1)选择排序: O(1)
2024-04-19 07:00:00 892
原创 排序(四)——归并排序 + 外排序
目录1.归并排序递归实现代码2.归并排序非递归代码3.比较快排、归并和堆排序4.归并排序实现外排序我们之前对两个有序数组进行排序就用到了归并的思想,对于两个有序数组,我们分别取他们首元素比较大小,取小的插入到一个新开辟的数组中,归并排序的前提就是要有两个有序序列,而且还要开辟一块新的空间来存放新的有序的数组。那么对于一个无序的数组该如何使用归并排序呢这一整个数组我们可以把它分成左右两个区间来看,一个是 [0,4] ,一个是 [5,9] ,而只要这两个区间有序了,我们就有办法通过控制下标来进行归并排序。怎么使
2024-04-18 07:00:00 1169
原创 排序(三)——快速排序(递归以及栈和队列实现非递归)超详细
相遇有两种情况,一种是 R 移动与 L 相遇,一种时 L 移动与 R 相遇,他们相遇点的数据一定是比key小的值,第一种情况 R 移动与 L 相遇说明 R 走过的都是比key大的值,而 L 的位置要不就是key,要不就是上一次交换完的比key小的值。第二种情况 L 移动与 R 相遇,L 走过的都是比 key 小的值,L 在移动说明 R 已经停在了比 key 小的值上了,这时候像雨点也是比key小的值了。
2024-04-17 08:00:00 2656
原创 排序(二)选择排序
但是就算这样优化了,冒泡排序还是最慢的排序算法之一,跟直接选择排序和直接插入排序半斤八两,唯一的优势就是对有序的数据排序,但是这个条件太苛刻了。而直接选择排序除了正常的数据排序效率低下之外,如果数据本来就有序,选择排序也不会管,他还是会遍历这么多次数组,然后选数进行交换。堆排序的本质上也是一种选择排序,在之前二叉树的章节中我们讲解了堆排序的实现,我们使用就是使用几个函数,建堆,交换,向下调整。直接选择排序是一种非常简单粗暴,也是效率非常低的一种排序,直接选的排序的基本思想就是:。
2024-04-16 08:00:00 432
原创 排序(一)——插入排序 希尔排序
试想,当原数组的数据是接近逆序的时候,他的大的数据在前面,而小的数据在后面,如果采用直接排序的话就需要一个位置一个位置交换,十分的耗时,当我们用预排序的话,他不是一次只移动一个位置,而是一次移动gap个位置,这样一来能让小的数据更快地来到数组的前面,大的数据也能更快的调换到后面,使得原本逆序的数组变得更接近有序,更有利于插入排序。同时,希尔排序一般是用于排序数据量很大的时候,这时候也能让更大的数据更快的到后面,小的数据更快的到前面,这样优化了直接插入排序。我们可以认为,只有一个数据的时候他就是有序的。
2024-04-15 18:40:51 602
原创 顺序表和链表
而我们写的这些数据结构的程序都是存储在内存中的,也就是上图中的主存,而代码经过编译之后是要到我买电脑的cpu中去执行的,代码运行过程中呢,cpu就会去访问我们的数据,由于 cpu 的访问速度很快,而如果cpu是直接去内存中访问的话,内存的访问速度又相对而言慢,这时候cpu的性能就没发挥出来,程序运行速度就会很慢。因为顺序表是连续存储的,所以加载顺序表的数据到缓存中时不知会把这一个数据加载进来,而是会加载多个数据(具体取决于cpu的字长),再访问后面的数据时可能就直接能在缓存中查找到了。
2024-04-08 17:57:50 1161
原创 带头双向循环链表实现
虽然带头双向循环链表的结构比单链表要复杂的多,但是他的结构带来的优势就是增删的操作都十分简单,而且头插尾插能完全复用插入函数,头删尾删也能完全复用删除函数,代码量变小了很多,只要我们理解了指针之间的逻辑关系,这种结构使用起来就十分方便了。
2024-04-07 22:44:15 840
原创 单链表经典oj题 (一) 简单
我们不能去遍历链表,但是我们能找到要删除的节点的下一个节点,这个题的要求是删除那个数据,并不是说要删除这个节点,那么我们就可以将要删除的节点的数据与下一个结点的数据进行调换,这样问题就转换为了删除下一个节点了,怎样处理就很简单了。首先我们是可以求出两个链表的节点的个数的,也就是链表的长度,当链表相交之后后半段的长度是相等的,所以两个链表的长度差就差在相交之前的部分。那么我们是不是可以让长的链表先走 他们的长度差 的步数,如何在两个指针一起走,这样他们相遇的节点就是相交的起始节点。
2024-04-07 06:00:00 673
原创 无头单向非循环链表的实现
以上就是单链表的实现,它的优势就是头插头删很方便,效率很高,但是单链表除了头部操作,其他的操作都很不适合,想要做到任意位置的高效的插入删除还是要看后面要学的带头双向循环链表。
2024-04-06 19:23:17 1317
原创 顺序表的实现
define INIT_CAPACITY 10//初始的容量和每次增长的容量//维护动态数组//数据个数//数组容量}SL;这就是顺序表的简单实现了。
2024-04-05 22:55:10 928
原创 复杂度的讲解
我们可以将上面的每一个方块看成一个函数栈帧,但是我们要记住一点,我们调用fib(n-1)+fib(n-2)时,不是同时为这两个函数创建栈帧,而是会在fib(n-1)返回后再去调用fib(n-2),所以计算这个递归的空间复杂度看的不是调用的次数,而是递归的深度,同时,因为每次创建栈帧时栈帧的空间就已经是固定的了,也就是一个常数,在大O的渐进表示法中,加法的常数和系数的常数都是会删掉的,所以这个算法的空间复杂度就是O(N)。在写算法的时候,我们尤其要注意算法的时间复杂度,必要时我们要采取以空间换时间的策略。
2024-04-04 21:37:51 809
原创 程序环境和预处理
下面的一些与定义符号是C语言内置的。__FILE__ //进行编译的源文件__LINE__ //文件当前的行号__DATE__ //文件被编译的日期__TIME__ //文件被编译的时间__STDC__ //如果编译器村寻ANSI C ,其值为1,否则未定义我们可以将这些值打印出来观察。首先我们可以发现vs编译器中STDC是未定义的,这说明vs不遵循标准C。我们将其他的值打印出来就能很清楚的知道他们所代表的信息。
2024-04-03 05:00:00 1147
原创 通讯录改造———文件版本
上一篇文章我们详细讲了文件操作,这时候我们就可以把通讯录保存到文件中,这样即使程序退出了,联系人的信息也还是保存着,下一次启动程序时我们就可以把文件中的数据读取到程序中来使用。
2024-04-02 02:00:00 707
原创 C语言文件操作
简单来说,硬盘上的文件就是文件。但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件。(以文件功能的角度来分类)程序文件包括源文件(.c)、目标文件(.obj)、可执行程序(.exe)数据文件:文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据(把文件的内容读到内存中),或者输出内容的文件(把内存中的数据写到文件中)。我们在这篇文章里讲的就是数据文件这么多文件怎么使用呢?这就涉及到了文件的唯一标识:文件名。
2024-04-01 20:19:52 903
原创 通讯录改进———动态版本
首先我们将联系人列表初始化一定的空间,假设我们设定初始最大联系人数量为3,我们可以在头文件中用#define来定义,方便后续修改.与此同时,我们也可以用#define来定义每次扩容的空间。要将其改为动态版本主要是两件事,首先初始化的时候我们要动态开辟data的空间,光动态开辟还不够,我们还要增加一个变量来记录当前联系人列表的大小,空间使用完之后要记得释放。在上一篇博客中讲完了动态内存分配,这时候我们就可以改进之前写的通讯录了,可以将其升级为动态内存的版本,既不用担心联系人满了,也不用担心内存浪费太大。
2024-03-29 20:49:06 379
原创 C语言--动态内存管理
为什么存在动态内存管理?在之前我们讲到的类型创建的变量再空间开辟好之后就不能再改变了,但是有时候我们希望能够自由地为某个变量分配空间,就比如上一篇文章中的通讯录,在没有动态内存管理情况下,我们就只能为通讯录开辟一点的空间,开辟之后就不能再更改了,如果一开始开辟的空间过大,可能会造成很大的浪费,如果一开始开辟的空间很小,那就可能不够用,当我们能够做到有几个联系人就开辟多大的空间时,空间利用率就会大大提升。这就是我们要讲的动态内存管理。
2024-03-28 20:10:02 1089
原创 自定义类型
结构体有两种定义形式,一种是在声明时直接在变量列表中定义,另外一种是用这个结构体类型去创建变量,这两种多余的时候都能直接对变量初始化。在对结构体初始化的时候要用一个大括号,与数组一样,对多个元素同时赋值要用大括号括起来。如果一个结构体内部包含另一个结构体,在初始化的时候,内部的结构体的初始化也要用一个大括号括起来。int score;
2024-03-26 21:10:28 1178
原创 内存函数认识和模拟实现 二
memcpy函数有三个参数,第一个是要拷贝的空间的起始地址,第二个是被拷贝的内容的起始地址,num是要拷贝的字节个数。但是当我们使用vs中的memcpy函数时会发现它可以完成重叠内存的拷贝,这是因为vs编译器库里的memcpy的实现考虑到了这种情况,属于是超额完成任务,因为我们本身并没有要求他能实现这个功能。而我们要模拟的memcpy只用于两块独立内存的拷贝。memcmp函数是要比较两个指针开始往后的num个字节的内容的大小,这个函数是逐字节比较,所以比较时也要把指针临时强制转换成char* 类型来比较。
2024-03-25 19:40:21 339 1
原创 模拟库函数qsort实现不同类型元素的冒泡排序
这里的base是 void* 类型的,而void* 可以接受任意类型的指针变量,且 void* 类型的指针不可被解引用,简单来说,void* 是一个泛型指针,我们在传参的时候可以传任意类型的指针,base都能接受,等到要用的时候再把它强制转换成所需要的类型的指针来解引用。在介绍中说了,这个比较函数会在qsort中重复调用来比较两个数据,这个函数的返回类型为int,当p1 的内容大于p2的内容时,返回一个大于零的数,当p1的内容等于p2的内容时,返回零,否则返回小于零的数。这样就能得到降序了。
2024-03-21 19:48:31 876 1
原创 数据在内存中的的存储
内置类型long 类型的大小是 4 / 8 个字节,元素C语言规定 sizeof(long)>= sizeof(int)就行。在32位平台上,long为4个字节,在64位平台上,long位8个字节。类型的意义1.使用这个类型开辟内存空间的大小(内存大小决定了适用范围)2.如何看待内存的视角。(数据存储到内存中和从内存中读取的方式)在上面的代码中,我们发现在内存中的数据好像是以字节为单位反着存的。大小端介绍 ,以十六进制数 0x 11 22 33 44为例。
2024-03-19 20:47:04 1015 1
原创 初阶结构体
我们可以在声明的时候定义结构体全局变量,也可以在函数内部定义结构体局部变量;结构体初始化可以用一个大括号,在大括号里面对成员变量依次赋初值;结构体嵌套是初始化也是用大括号赋初值,里面的结构体再用一个大括号赋初值。结构体初始化也可以不完全初始化,比如只对前几个成员变量赋初值。int main()struct stu s1 = { "zhangsan",13,"男","111111111111" };
2024-03-17 20:03:45 346 1
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人