Linux:基础开发工具
1.软件包管理器
1.1什么是软件包
在linux中我们安装软件一共有三种常见方式:
1.源代码安装:我们直接下载源代码到linux系统中,然后编译,得到可执行程序2.rpm安装:先把源代码在目标系统环境中编译,然后打包发送给linux系统,这与源代码安装的区别就是不用在安装后自己编译一次,而是可以直接执行程序
3.工具级别安装:我们可以使用apt/yum等软件包管理工具来帮助我们安装程序
ps:
1.软件之间存在依赖关系,代码和库之间也存在依赖环境:比如说我们的软件依赖关系如下a->b->c(a依赖于b,b依赖于c)此时若我们的a软件下载之后没有b软件做支撑,那么我们的a软件也无法正常运行
而此时我们的安装方法1,2就会存在路径依赖问题,因为他们是用户直接对源码/安装包进行操作的
2.软件包类似于应用,软件包管理器类似于应用商店:我们利用apt/yum等工具下载软件就是以一种很简单的操作方式进行下载
apt是ubuntu用的工具,yum是centos用的工具
1.2linux的软件包生态
1.下载软件的流程
引用开发:先将跨平台的源代码写好,然后分别编译给不同的操作系统,上传到不同的软件包服务器上
用户下载:通过访问软件包服务器去找到需要下载的软件包,然后利用软件包管理器(apt/yum)将软件包下载到我们的linux操作系统上
图示:
补充:如果我们要在操作系统中去访问软件包服务器,那么我们需要先拥有对应软件的地址,而其实我们的操作系统就是有地址内置的
2.评价操作系统的指标
由于我们在使用操作系统的时候可能会对系统不熟悉需要学习,所以官方文档是很重要的。而遇到了问题需要解决时我们又需要有人给我们解答,此时一个高活跃的论坛就很重要了。
还有目标用户群体等等要素也很重要...
图示:
3.镜像
由于linux的操作系统是国外开发的,所以内置的链接都是国外的软件包服务器的地址,而我国不允许访问国外网址,所以我们直接下载软件是无法做到的。此时我们就需要使用镜像网站来解决这个问题,从而下载软件
镜像的原理:就是先访问国外的网站,将对应文件下载到国内镜像网站中,从而确保国内镜像网站也可以获取到完全一样的软件。且国内镜像软件也会定期/实时更新国外的软件内容到国内镜像网站中给用户下载
图示:
4.扩展软件源
扩展软件源就是软件包服务器中还没有那么稳定的软件上传的区域,这部分区域也提供给用户下载,可以给用户体验新功能,但是有兼容性风险。
图示:
2.vim编辑器
vim是一种多模式编辑器。
模式1:命令模式(进入时默认模式)
模式2:插入模式
插入模式左下角有insert关键词
模式3:底行模式
底行模式左下角是一个冒号
情况1:写了部分信息,想退出文件会有三种路径
1.保存并退出:冒号+wq
2.直接退出不保存:q+!
3.保存:冒号+w情况2:我们当前在插入模式,想要退出vim编辑器
1.先回退到命令模式,然后再进入底行模式,输入命令退出vim。因为insert模式中任何按键操作都是当成数据输入,所以无法用冒号+q退出
模式回退:esc
情况3:命令模式的快速文本编辑
光标快速定位:
(1)上下定位
1.首位置定位:gg
2.任意位置定位:n + shift + g
这里我们是直接用了6+shift+g,所以定位到了第六行
3.末尾位置定位:shift + g
(2)左右定位
1.跳转行首:^
2.跳转行尾:$
3.hjkl:局部精细移动光标
h:左 j:下
k:上 l:右
4.行内快速移动光标
(n)+ w:向右以单词为单位移动
(n)+ b: 向左以单词为单位移动
5.直接进入指定行
vim code.c +n
我们通过在命令后面加数字即可直接让光标进入指定行
文本变动操作:
1.复制:yy
2.粘贴:(n)+ p
例1:对return 0;代码进行复制粘贴操作
这里用的命令是yy + p
例2:对return 0;代码进行粘贴10行操作
使用了yy + 10p命令
3.剪切:dd + p
例1:初始状态
使用dd命令
使用p命令
4.删除:
行删除:(n)+ dd
单字符右删除: (n)+ x
单字符左删除: shift +(n)+ x
5.大小写转换: ~
按一次~符号就会对光标所在行进行一个字符的大小写转换操作
6.修改
(1)局部修改
修改光标所在字符:(n)+r+字符
修改模式:shift + r
(2)区块修改
先进入视图块模式(ctrl+v)
然后利用hkll选取区域
再退回insert模式(shift+i)
进行手动第一个操作
最后按esc即可批量进行操作
(3)区块删除
进入视图块模式后选中对应区域按d即可删除
7.查找
(倒序)选中+查找:shift + # + n
(顺序):进入底行模式,然后输入/查找字符串,再按n顺序查找
8.批量替换
光标行替换:s/被替换字符/需要换成的字符
全局替换:%s/被替换字符/需要换成的字符/g
9.分屏操作
分屏:底行模式中输入vs 文件名
光标跨屏移动:ctrl + ww
撤销操作:
1.撤回操作:u
按下u之后会对最近一次修改进行撤回
2.撤销撤回操作:ctrl + r
使用ctrl+r后会将最近一次撤回操作撤销
3.gcc/g++
gcc是用于进行代码编译的工具,就像我们在vs中按下的f5一样,gcc可以将源代码经过预处理,编译,汇编,链接操作变成可执行程序。
注意:c语言的代码使用gcc,c++的使用g++
错误演示:
由于我们使用的头文件是c++的,所以属于c++文件,用gcc就无法进行编译。
当我们使用g++的时候就可以进行正确编译了
3.1操作演示:
快速记忆:-ESc表示前三个过程,-iso表示对应的文件后缀
(1)预处理:g++ -E code.c -o code.i
(2)编译:g++ -S code.i -o code.s
(3)汇编: g++ -c code.s -o code.o
如果不加-o就会将操作过后的文件信息打印在屏幕上
汇编产生的文件是可重定位目标文件,由于原本的代码中是只有库函数的声明的,没有库函数的实现,所以我们需要去库中调用该函数。可重定位指的是可以将需要使用的库中的函数的地址找到,然后便于链接库函数
(4)链接:g++ code.o -o code.exe
3.2库的分类:动态库与静态库
在windows系统中:静态库文件后缀(.lib),动态库文件后缀(.dll)
在linux中:静态库文件后缀(.a),动态库文件后缀(.so)
理解动态库与静态库:
场景:假设小明和同学现在都要去打电脑游戏,他们有两种选择,一种是去网吧打,一种是用自己的电脑打。
网吧相当于动态库,他不占用学校的内部空间,而是在学校外
优点:占用空间小,修改代码方便(不用在每一个文件中修改,只需修改库文件内容),资源利用率高
缺点:申请速度较慢,库出现问题会导致所有使用该库的程序无法运行
去网吧打游戏的时候不止小明一个人可以去,所有同学都可以去,所以动态库又叫共享库
自己的电脑相当于静态库,他占用学校内部空间
优点:不会因为某个库出问题导致全部使用该库的程序无法运行,申请速度快
缺点:代码体量大,修改代码较麻烦,资源利用率较低
由于动静态库自身的特性,所以我们的文件一般默认使用动态库
这是我们前面进行编译的newfile文件,通过file指令查看,我们发现使用的是
dynamically linked(动态链接),也就是默认使用的是动态库
如果我们需要使用静态库,可以用-static命令进行
4.自动化构建make/Makefile
例子1:make与Makefile的使用方法
make是一个命令,用于启动Makefile中的指令的一个命令,而Makefile是我们自己去编写的一个文件。
这里我们就在Makefile中写了将code.c编译出code的命令,使用make启动后,执行该命令
Makefile书写解释:
第一行左侧是目标文件,右侧是依赖文件,整个第一行叫依赖关系
第二行左侧有一个Tab,然后是需要执行的语句,第二行又叫依赖方法
例子2:执行逻辑与伪目标
这里我们添加了一条指令,具体含义我们先不管。
我们看看此时make的执行结果
正常执行了第一条指令,但是我们明明还有第二条指令,他却没有执行,为什么?
因为Makefile中是默认从上往下进行扫描,然后执行第一个遇到的指令。方法一:假如我们把两条命令换个上下关系,那么会执行的就是clean命令
将顺序调换之后我们执行的就是clean命令了
方法二:将clean指定在make后面
疑问:我们新添加的指令中.PHONY到底是什么意思?
.PHONY就是用来声明伪目标的,在这里我们将clean声明为伪目标,伪目标是始终执行的目标
为什么说伪目标是始终执行的目标?
始终执行并不意味着每次使用make都会执行,而是说该目标可以多次执行。我们先看看普通目标的情况
这里我们执行了两次make,第一次成功执行指令,但是第二次却说code已经存在且为最新状态,然后不执行我们的指令。这就是普通目标的不可重复执行的特性。
不可重复执行的好处:可以减少大项目编译的时间成本
因为我们一般在项目中进行修改不会对所有文件进行修改,只会修改其中一小部分,且只有这一小部分的代码需要重新编译,如果我们所有的文件都要重新编译会浪费很多时间
编译器判断我们文件是否需要重新编译的方法:
查看文件的属性信息,看exe文件和源文件的modify(内容修改时间)和change(属性修改时间)属性
若exe的修改时间比源文件修改时间新,说明文件被编译为最新,不执行编译指令
反之,说明源文件被修改后还没有编译过,执行编译指令
接下来我们看看伪目标的始终执行特性如何体现
这里我们重复执行了clean命令,并没有像前面一样阻止我们重复执行clean,这就是伪目标的始终执行的意思
补充:一般情况下可执行文件都是通过集体链接.o文件来产出的,而.o文件又是.c文件一一对应生成的,所以Makefile中代码应该如下所示
而当我们直接用-c编译的时候会自动生成.o结尾同名文件,不用在后面接-o code,o
例子3:通用写法
我们可以通过变量式写法写出通用Makefile,就像定义宏的时候一样更改宏变量值就可以更改代码中所有宏的值
在makefile中我们也有变量的概念,而这里的变量其实就相当于宏的意思,是可以用于进行替换功能的
这里的BIN就是变量,名字是自定义的BIN的意思是目标文件,$(BIN)的意思就是将BIN变量的内容替换掉,所以这里我们写的是回显BIN变量的名字。
假如我们不需要回显执行过的指令信息,我们可以在makefile文件中对应指令开头处添加@符号
添加完之后就没有指令回显了
上述图片我们将所有的指令都通用化了,只需更改变量后面的东西就可以达到复用的目的
这里实际上我们就将变量都替换掉了
接下来我们尝试根据实际情况的依赖写法来写
这里是分两步编译可执行文件,第一步是将源文件编译为.o文件,第二步是将所有.o文件集体链接为可执行文件
$@:表示目标文件 $^:表示依赖文件(有一个就是一个,有很多就是他们全部)
例子4:多文件通用写法
其实我们的可执行文件是能由多个源文件编译而来的,所以为了应对多个源文件的编译情况,我们需要有特殊的通用写法
SRC部分的含义:将当前目录的所有以.c结尾的文件依次排列出来(中间用空格隔开)以字符串形式赋值给SRC(特别注意wildcard后面有一个空格)
OBJ部分的含义:找到当前目录中所有.c结尾文件并将他们的后缀替换为.o结尾并以字符串形式排列赋值给OBJ
%.o和%.c:前面的%表示通配符,可以匹配对应后缀的文件
$<:表示将依赖文件和目标文件一一对应的执行指令,有多少个目标-依赖文件对就执行多少次命令
补充程序:进度条模拟实现
首先我们先确定需要实现的进度条基本功能:包含进度条本体,百分比显示,旋转下载光标
图示:
前置内容补充:
1.回车与换行
其实回车和换行表示两种截然不同的功能,他们并不是一样的概念。
回车(/r)指的是将光标移动到当前行的开头处,而换行(/n)指的是将光标移动到下一行
我们如果直接使用/n其实是包含了回车和换行两个功能,也就是先将光标退回当前行开头,然后将光标移动到下一行
而其实我们也可以通过使用回车+换行达到相同目的:/r+/n
2.用户缓冲区问题
我们先观察如下代码及其执行情况
(1)
这里我们加上了换行符\n,在执行的时候完成了回车换行,且等待一秒是在输出了demo信息之后才进行的
(2)
这里我们没加上\n,没有回车换行操作,且是等了一秒后demo才打印出来
疑问:为什么在不加\n情况中会让sleep先执行?难道是先识别了sleep指令那一行然后才识别printf那一行吗?
首先我们需要明确代码的识别顺序一定是从上到下的,所以不加\n的代码中先执行的一定是printf指令
而之所以在显示器上看到是sleep先执行,这是因为printf函数是一个复制函数,他先将显示器文件打开,然后将需要输出的信息写入到了显示器文件缓冲区中
文件缓冲区有行刷新,关闭文件流刷新,显式刷新,程序正常结束刷新等刷新方式
这里使用的刷新方式就是程序结束刷新,所以看起来才像是先执行了sleep函数
使用了\n的属于行刷新,使用flush函数等手动刷新属于显式刷新
接下来我们看看显式刷新
这里我使用了fflush这个函数来显式刷新标准输出流,那么printf写入到缓冲区的信息就会立刻刷新出来而不是等程序结束再刷新,然后才执行sleep函数
文件结构:
在头文件声明process函数,在源文件process.c中实现,然后在main函数中直接调用
(1)实现数字10的倒计时,为进度条倒计时做准备
我们的倒计时有几个要求
1.左对齐的数字
由于我们需要左对齐,所以我们在格式化字符串前面加上负号
2.每次显示出一个数字后不换行刷新继续输出下一个数字
不换行那么我们需要光标移到最左侧也就是使用回车操作,而在多位数情况下为了回车后仍然可以将前面的所有数字都覆盖掉,我们需要设置数字的占位为最高位。
不进行行刷新的情况下,我们使用fflush进行显式刷新
3.要有一定间隔方便看清
利用包含在unistd下的sleep函数,单位为秒
(2)实现进度条可视化
我们这里实现的效果如下图所示:
视觉效果:每过固定的时间就在中括号框内从左到右多打印一个#,且中括号的间隔是始终不变的
接下来看看代码实现:
(1)初始状态:中括号占据足够位置且间隔不再变动
将打印内容设置为一个占据长度恒为100的字符数组bar
其中将bar长度设置为101是需要额外容纳一个\0作为字符串结尾
(2)加载状态:从左到右每一段时间就多打印一个#
要保证从左到右就需要让字符串左对齐加上负号即可,隔段时间打印用usleep控制。每次多打印一个#,每次循环结束后在bar字符数组中加一个#
由于不是行刷新,所以我们在printf中加上回车符号\r,然后显式刷新缓存区
(3)初级版本进度条实现
我们在进度条可视化的基础上添加了百分比进度显示和光标旋转
1.百分比进度显示:
由于最多是100%三位数,所以我们格式化字符串要为3d,而且我们是左对齐所以要加负号,最后百分号需要转义重复写一次
2.旋转光标:
我们利用 \ | / -四个字符轮流显示来模拟光标的旋转。此时我们就创建一个字符指针常量(方便后续类似数组的访问),指向包含这四个字符的字符串,然后通过cnt与字符串长度的取模来不断变化访问元素索引,从而实现光标旋转
(4)模拟实际场景版本进度条
由于实际下载的时候,进度条是根据下载实际进度来跑的,而不是固定隔段时间就跑,所以我们现在需要模拟实际下载场景,升级一下进度条代码
1.main.c代码
main函数构建:
我们封装一个专门的下载函数根据下载实际进度更新调用进度条,所以只需要在main中执行一个叫download的函数即可,该函数需要的参数是下载文件大小和下载速度
download函数构建:
首先我们规定目标文件的大小以及我们文件的下载速度(规定目标文件大小不是速度的整数倍,以适用于更多情况)。
执行逻辑:当前下载进度不够时,等待以当前下载速度下载一次的时间后将进度更新,然后进行进度条刷新
注意:
1.由于没有开始下载的时候我们也需要显示进度为0%,所以我们的刷新进度条要放在最前面
2.当速度和目标文件大小不是整数倍关系的时候一定会出现进度条显示到99%进度的时候current_size>size,此时我们就用if语句将其提前捕捉,进行进度刷新。
同理是整数倍关系的时候一定有current_size==size
2.process.h代码
该头文件是需要包含给process.c和main.c的,所以我们将需要用到的库文件都先包含进去,然后对进度条刷新函数FlushProcess进行函数声明
3.process.c代码
进度条刷新需要的参数是文件总大小以及当前下载大小,这样子才知道是百分之多少进度
1.进度条本体:
需要知道打印在屏幕上有多少个#,所以我们需要先将百分比cur计算出来,然后进行整形强制类型转换,从而知道cnt是多少,进行bar数组的#添加
2.进度百分比:
我们不需要太高精度,所以只要保存小数点后一位即可
3.光标旋转:
和前面基础版本一样
5.git
git是一个用于进行源代码与头文件版本管理的工具
场景:
假设我们正在进行项目开发,产品经理要我们修改代码,实现更多的功能以满足用户实际需求,我们不断的修改,最终改出来了,可是因为产品稳定性下降,测试难以通过。产品经理决定用以前较稳定的版本进行发布。
那么此时,如果我们没有对代码版本进行管理,我们将无法满足产品经理的要求上交成品,这会导致巨大的利益损失。
而git就是一个广泛应用与这种场景的代码托管平台,我们可以通过建立本地仓库本地编写代码,然后与远端git管理的仓库进行合并,从而实现代码托管与协同开发
(1)在使用git之前,我们需要先将git下载到linux中,以及创建gitee账号
命令:apt install git
(2)接下来进行git用户名和邮箱的数据录入,我们在linux中使用git命令前需要配置好这两个信息
# 设置全局用户名 git config --global user.name "你的用户名"# 设置全局邮箱 git config --global user.email "你的邮箱"
(3)然后我们就要在远端(gitee)进行仓库创建
补充:仓库
仓库其实就是一个包含.git管理文件的文件夹,本地仓库是本地的文件夹,远端仓库是远端文件夹。
本地仓库包含两个部分:缓存区和本地仓库目录
在本地仓库文件夹内进行了修改的文件都可以加入缓存区中,当然也可以选择需要上传的文件进入缓存区
进入缓存区之后如果需要提交修改到本地仓库中,我们就需要进行commit操作,进行该操作的时候需要同时写日志,也就是要附上当前commit的文件进行了什么修改
.git文件包含提交历史,日志信息,分支信息,远端url,配置信息等等,正是有了.git文件,所以才让本地仓库有了git的版本管理功能
(4)将远端仓库创建完成后,通过clone的方式建立本地仓库
clone就是克隆命令,可以将远端的仓库同步到当前目录下,而同步过来后的目录就是我们的本地仓库了,只有在本地仓库文件夹内部的文件可以进行git相关操作
命令:git clone URL
(5)然后我们将在仓库中进行了修改的文件提交到缓存区
命令:git add 文件名
如果需要将所有修改过的文件都添加,使用 ' . '替换文件名即可
(6)将缓存区内容提交到本地仓库
命令:git commit -m "日志内容"
(7)将本地仓库内容与远端同步(多人协作时很关键)
命令:git pull
同步的目的是为了防止本地与远端存在冲突,从而推送失败,通过拉取远端仓库,我们可以把本地仓库的内容扩展至包含远端仓库所有内容
(8)最后将本地仓库信息推送至远端即可
命令:git push
推送的时候需要输入用户名和用户登录密码
6.gdb/cgdb调试器使用
gdb是一个用于进行代码调试的工具,类似与vs中的调试功能
其中gdb是真正进行代码调试的引擎,所有调试命令最终都是让gdb执行。但是gdb查看调试代码的时候很不方便,所以有了视觉优化过的cgdb工具,而cgdb工具是包含gdb的,它更像是一个外壳,这个外壳实现了更好的视觉效果方便我们调试,底层里还是使用gdb进行操作
日常%90的场景我们可以使用cgdb,以更好的调试体验进行本地代码调试,而如果需要远程调试的时候就只能使用gdb进行调试
接下来我们尝试开始使用gdb进行调试
当我们查看文件信息的时候,我们发现文件中没有调试信息,我们无法进行代码调试。
这是因为文件分为release版本和debug版本,程序员用来调试代码的都是debug版本,而我们默认编译出来的都是release版本。
编译debug版本命令: gcc mycmd -o mycmd -g
使用-g后,编译出来的就是debug程序,附带了调试信息
后面我们都使用cgdb进行调试
命令: cgdb ./mycmd(文件名)
这是cgdb的进入加载界面,我们直接按c就可以进入了
如果要查看代码,我们直接按esc确保光标在上面的代码,然后进行上下翻动
如果要输入命令,按i进入下方gdb命令行进行命令输入
(1)源代码查看: l num
我们使用l + 数字的命令可以查看以给出数字为屏幕中心的代码块
eg:l 11命令
我们看到11出现在十行显示代码的中心
但是也会有特殊情况,比如l 1
正常来说我们会将1显示在十行代码中心,但是由于代码行数都是大于0的,所以会自动补出后面的九行,从而将第一行放在最上面
(2)断点
打断点命令:b 行号
使用b 17表示对第17行打上断点,那么此时上方源代码17行的行号就被标记为红色了
给函数打断点:b 函数名
给指定文件的指定行打断点:b 文件名:行号
查看断点信息:info b
在一个调试周期中,断点编号是不会重复出现且线性递增的,也就是一个断点对应一个编号
删除断点:d 断点编号
删除所有断点:d breakpoints
禁用断点:disable 断点编号
这里我们现在main函数(14行)和15行打了断点,然后用disable禁用了14行的断点,那么此时有效断点就只有15行的
禁用断点而不是删除断点的好处:保留调试痕迹
恢复断点:enable 断点编号
我们使用enable将断点恢复,那么之后就可以正常使用断点了
条件断点:b 行号 if 条件语句
我们成功将断点的启动条件设置为当n等于5050的时候
这种断点只有满足条件的时候才有打断的作用,不过这种断点的条件想要去除只能直接删掉断点,然后重新设置成普通断点
我们也给断点添加条件,将其变为条件断点
命令:condition 行号 条件(不用加if)
我们给第15行的语句添加了条件变成了条件断点,不过想要修改这个断点的条件信息我们也可以直接用condition修改,然后取消条件就是condition 编号 [空]即可
(3)调试启动:r
如果没有打断点的话其实就是让程序运行直到运行结束,而如果打了断点就会在断点处停止
(4)逐过程/语句调试
逐过程:n
逐过程进行代码运行的时候是不会进入调用的函数的,而是直接跳过函数内部
这里我们在sum调用
逐语句:s
逐语句运行的时候会进入调用的函数内部一行一行的运行,不过在没有调用函数的部分逐过程和逐语句没有任何区别
这里我们按s就进入了sum函数内部,进入函数内部进行逐行运行
(5)变量显示
打印变量命令:p 变量名
这里只能显示一次,而如果要常显示我们需要用到另一个命令
常显示变量:display 变量名
这里我们将i变量常显示出来,那么在存在i的作用域内就会一直刷新显示i变量
而如果不在作用域内就不会再显示
取消常显示变量:undisplay 变量名
(6)代码区域式执行
执行跳转到某一行:until 行号
一开始是在第8行,但我们如果确定了这个for循环是没问题的,想要跳出去,那么我们就可以使用until命令直接跳转执行到指定行号
我们看到使用了命令后直接就跳转到了第10行,出了for循环
区域执行:打断点+c(continue)
我们当前给14,17,18行都打了断点,然后我们直接输入r启动调试,中间我们想要区段跳越,所以我们直接按c即可
执行完当前所处函数:finish
我们当前处于sum函数中,然后使用finish命令后直接执行完了sum函数,退出了sum函数
(7)查看调用堆栈信息
命令:bt
我们看到运行在14行的时候调用的堆栈是main的
(8)查看当前局部变量
命令:info locals
(9)查看当前正在调试的程序信息
命令:info i
(10)监视发生变化的变量
命令:watch 变量名
其实watch result就是去监视result这个变量的值,当他发生变化的时候就会在屏幕上打印
比如这里我们进入了Sum这个函数,然后用s逐行运行,当result的值发生变化的时候就在屏幕上打印信息,包括原数据值和信数据值
应用场景:当我们需要有变量是保持不变的时候,我们可以使用watch该变量来判断是否是该变量变化导致的程序错误
(11)更改指定变量的值
命令:set var 变量名=数值
应用场景:比如说我们在排查代码的时候发现是某个变量的数值问题导致出错,此时我们就可以在调试的时候改变该变量的值,看看是否问题解决。
我们一开始将result错误的初始化为了-1,而正常来说应该是0才对,所以此时我们在result赋值为1之后将result改为0,接着执行就没问题了