目录
五、Linux项目自动化构建工具—make/Makefile
一、Linux软件包管理器yum
我们在Linux下有一般有三种软件安装方式
1、源码安装
2、rpm安装(安装包)
3、yum安装
我们一般都推荐yum安装,因为前者安装比较麻烦,可能存在大量的软件之间的依赖关系,(比如说,我们软件需要更新管理,想要下载新版本的就很麻烦)
什么是yum?
说简单明了:就是Linux下的应用市场
英文缩写:yum=Yellow dog Updater, Modified 。
主要功能是更方便的添加/删除/更新RPM包,它能自动解决包的倚赖性问题,它能便于管理大量系统的更新问题
注意事项:
关于yum的所有操作必须保证网络畅通,可以通过ping指令验证
[sjj@VM-20-15-centos ~]$ ping www.baidu.com
现在处于联网的状态了!!!
查看软件包
通过yum list我们可以查看一共有哪些软件包,可以利用grep筛选想要的软件包
yum list | grep lrzsz
查看结果:
注意事项:
1、软件包名称: 主版本号.次版本号.源程序发行号-软件包的发行号.主机平台.cpu架构.
2、"x86_64" 后缀表示64位系统的安装包, "i686" 后缀表示32位系统安装包. 选择包时要和系统匹配.
3、"el7" 表示操作系统发行版的版本. "el7" 表示的是 centos7/redhat7. "el6" 表示 centos6/redhat6.
4、最后一列, os 表示的是 "软件源" 的名称, 类似于 "小米应用商店", "华为应用商店" 这样的概念.
如何安装软件?
通过yum,我们就可以通过很简单的一条命令自动完成安装,需要sudo提升临时权限
sudo yum install lrzsz
yum会自动找到都有哪些软件包需要下载,这时候敲击“y”确认安装,出现“complete”说明安装完成
注意事项:
1、因为一般安装时需要向系统目录写入内容,一般需要sudo或者切换到root用户
2、yum一次只能安装一款软件,如果同时安装多个软件,会发生报错
如何卸载软件?
任然是yum命令,同样需要提升权限
sudo yum remove lrzsz
Linux与windows之间文件传送
Windows 上传到 Linux : rz + Enter(回车)
Linux 下载到 Windows : sz + 文件名 + Enter(回车)
二、Linux编辑器——vim的使用
vim是一个Linux下的文本编辑器,可以类比Windows下的记事本
1、vim的三个常用模式
①命令模式(Normal mode)
②插入模式(Insert mode)
③底行模式(Last line mode)
2、vim的基本操作
①进入vim
输入vim后跟文件名,进入vim的命令模式,需要输入指令切换模式
vim test.c
②正常模式切换至插入模式
刚刚进入就可以敲击a,i,o切换
a
i
o
③插入模式切换至命令模式
处于插入模式时,就只能一直输入文字,像我们的记事本一样在里面输入。我们想要回退到命令模式,需要按左上角的Esc键
④命令模式切换至底行模式
按shift+:
⑤退出vim及保存文件
在命令模式,进入底行模式,输入
: w(保存当前文件)
: wq (输入wq,存盘并退出vim)
: q! (输入q!,不存盘强制退出vim)
3、命令模式下的各种命令
插入模式
①按「i」切换进入插入模式「insert mode」,按“i”进入插入模式后是从光标当前位置开始输入文件;
②按「a」进入插入模式后,是从目前光标所在位置的下一个位置开始输入文字;
③按「o」进入插入模式后,是插入新的一行,从行首开始输入文字。
从插入模式切换到命令模式
按「ESC」键。
移动光标
①vim可以直接用键盘上的光标来上下左右移动,但正规的vim是用小写英文字母「h」、「j」、「k」、 「l」,分别控制光标左、下、上、右移一格
②按「G」:移动到文章的最后
③按「 $ 」:移动到光标所在行的“行尾”
④按「^」:移动到光标所在行的“行首”
⑤按「w」:光标跳到下个字的开头
⑥按「e」:光标跳到下个字的字尾
⑦按「b」:光标回到上个字的开头
⑧按「#l」:光标移到该行的第#个位置,如:5l,56l
⑨按[gg]:进入到文本开始
⑩按[shift+g]:进入文本末端
⑪按「ctrl」+「b」:屏幕往“后”移动一页
⑫按「ctrl」+「f」:屏幕往“前”移动一页
⑬按「ctrl」+「u」:屏幕往“后”移动半页
⑭按「ctrl」+「d」:屏幕往“前”移动半页
删除文字
①「x」:每按一次,删除光标所在位置的一个字符
②「#x」:例如,「6x」表示删除光标所在位置的“后面(包含自己在内)”6个字符
③「X」:大写的X,每按一次,删除光标所在位置的“前面”一个字符
④「#X」:例如,「20X」表示删除光标所在位置的“前面”20个字符
⑤「dd」:删除光标所在行
⑥「#dd」:从光标所在行开始删除#行
复制
①「yw」:将光标所在之处到字尾的字符复制到缓冲区中。
②「#yw」:复制#个字到缓冲区
③「yy」:复制光标所在行到缓冲区。
④「#yy」:例如,「6yy」表示拷贝从光标所在的该行“往下数”6行文字。
⑤「p」:将缓冲区内的字符贴到光标所在位置。注意:所有与“y”有关的复制命令都必须与“p”配合才能完成复制与粘贴功能
替换
①「r」:替换光标所在处的字符。
②「R」:替换光标所到之处的字符,直到按下「ESC」键为止。
撤销上一次操作
①「u」:如果您误执行一个命令,可以马上按下「u」,回到上一个操作。按多次“u”可以执行多次回复。
②「ctrl + r」: 撤销的恢复
更改
①「cw」:更改光标所在处的字到字尾处
②「c#w」:例如,「c3w」表示更改3个字
跳至指定的行
①「ctrl」+「g」列出光标所在行的行号。
②「#G」:例如,「15G」,表示移动光标至文章的第15行行首。
4、底行模式命令
在使用底行模式前,请一定记住先按Esc确认你已经处于命令模式中,再按shift+:进入底行模式
列出行号
「set nu」: 输入「set nu」后,会在文件中的每一行前面列出行号。
跳到文件中的某一行
「#」:「#」号表示一个数字,在冒号后输入一个数字,再按回车键就会跳到该行了,如输入数字15,再回车,就会跳到文章的第15行。
查找字符
「/关键字」: 先按「/」键,再输入您想寻找的字符,如果第一次找的关键字不是您想要的,可以一直按 「n」会往后寻找到您要的关键字为止。
「?关键字」:先按「?」键,再输入您想寻找的字符,如果第一次找的关键字不是您想要的,可以一直 按「n」会往前寻找到您要的关键字为止。
保存文件
「w」: 在冒号输入字母「w」就可以将文件保存起来
离开vim
「q」:按「q」就是退出,如果无法离开vim,可以在「q」后跟一个「!」强制离开vim。
「wq」:一般建议离开时,搭配「w」一起使用,这样在退出的时候还可以保存文件。
实用操作——批量注释与取消
批量注释:
在命令模式下,Ctrl + v 进入块选择模式,然后移动光标选中你要注释的行,再按大写的 I (可以按住shift键加小写的i)进入行首插入模式输入注释符号如 // 或 #,输入完毕之后,按两下 ESC,Vim 会自动将你选中的所有行首都加上注释,保存退出完成注释
取消注释:
在命令模式下,Ctrl + v 进入块选择模式,选中你要删除的行首的注释符号,注意 // 要选中两个,选好之后按 d 即可删除注释,ESC 保存退出。
三、Linux编译器——gcc/g++的使用
gcc/g++使用的翻译场景
1、预处理(头文件的展开,宏替换,去注释)
2、编译(生成汇编代码)
3、汇编(生成二进制指令)
4、链接(生成可执行的文件或者库文件)
gcc/g++语法格式
gcc [选项] 要编译的文件名 [选项] 目标文件名
常用选项:
①-E,开始进行程序的翻译,完成预处理后,停下来,你需要把它生成的内容重定向到一个输出文件里面(否则将把预处理后的结果打印到屏幕上)。
②-S,开始进行程序的翻译,完成编译后,停下来
③-c,开始进行程序的翻译,完成汇编后,停下来
④-o,将处理结果输出到指定文件,该选项后需紧跟输出文件名
我们可以用一个已经写好的c源文件,来详细解读
预处理
gcc -E mytest.c -o mytest.i
预处理功能主要包括宏定义,文件包含,条件编译,去注释等。
预处理指令是以#号开头的代码行。
选项“-E”,该选项的作用是让 gcc 在预处理结束后停止编译过程。
选项“-o”是指目标文件,“.i”文件为已经过预处理的C原始程序
编译
gcc -S mytest.i -o mytest.s
在这个阶段中,gcc 首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做工作,在检查无误后,gcc 把代码翻译成汇编语言。
用户可以使用“-S”选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码。
汇编
gcc -c mytest.s -o mytest.o
汇编阶段是把编译阶段生成的“.s”文件转成目标文件
读者在此可使用选项“-c”就可看到汇编代码已转化为“.o”的二进制目标代码了
链接
gcc mytest.o -o mytest
实际使用中并不用四步都执行,太繁琐了,咱们只需要执行第四步,编译器会自动帮我们完成前三个步骤:
gcc test.c -o hhh(名字可以自定)
在这里涉及到了一个重要的概念:函数库
问题:我们的C程序中,并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实“printf”函数的呢?
答案:系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函数“printf”了,而这也就是链接的作用
语言也是有库的!!!一般由一套头文件+一套库文件组成(libc.a,libc.so)
需要链接来将我们自己写的代码中的函数调用起来,将外部数据和库关联起来
函数库一般分为静态库和动态库
①静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为“.a”
②动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为“.so”,如前面所述的 libc.so.6 就是动态库。gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件
③gcc默认生成的二进制程序,是动态链接的,这点可以通过 file 命令验证
可以通过file命令来查看文件的类型:
可以通过ldd命令来查看依赖的第三方库:
虽然gcc默认是动态链接的,但是我们可以加上-static选项,来让其静态链接
gcc mytest.c -o mytest_static -static
我们同样可以用file和ldd命令来查看是否是静态链接的:
总结一下动静态链接的优缺点
静态库:
优点是,在编译后的执行程序不再需要外部的函数库支持,运行速度相对快些;
缺点是,如果所使用的静态库发生更新改变,你的程序必须重新编译。
动态库 :
优点是,动态库的改变并不影响你的程序,所以动态函数库升级比较方便;
缺点是,因为函数库并没有整合进程序,所以程序的运行环境必须提供相应的库
gcc的常用参数
选项 | 含义 |
-I directory (大写的 i) | 指定 include 包含文件的搜索目录 |
-g | 在编译的时候,生成调试信息,该程序可以被调试器调试 |
-l 小写的l | 在程序编译的时候,指定使用的库 |
-L | 指定编译的时候,搜索的库的路径。 |
-fPIC/fpic | 生成与位置无关的代码 |
-shared | 生成共享目标文件。通常用在建立共享库时 |
-std | 指定 C 方言,如:-std=c99,gcc 默认的方言是 GNU C |
-I的使用
我们现在把head.h放到一个include目录下:
gcc和g++的区别
1、在代码编译阶段(第二个阶段):
后缀为 .c 的,gcc 把它当作是 C 程序,而 g++ 当作是 C++ 程序
后缀为.cpp 的,两者都会认为是 C++ 程序,C++ 的语法规则更加严谨一些
g++ 会调用 gcc,对于 C++ 代码,两者是等价的,也就是说 gcc 和 g++ 都可以编译 C/C++ 代码
2、在链接阶段(最后一个阶段):
gcc 和 g++ 都可以自动链接到标准 C 库
g++ 可以自动链接到标准 C++ 库,gcc 如果要链接到标准 C++ 库需要加参数 -lstdc++
3、关于 __cplusplus 宏的定义
g++ 会自动定义__cplusplus 宏,但是这个不影响它去编译 C 程序
gcc 需要根据文件后缀判断是否需要定义 __cplusplus 宏 (规则参考第一条)
四、Linux调试器——gdb的使用
1、背景
在Windows下的vs编译器中,程序的发布方式一般有两种,debug和release两个模式
因为Linux下gcc/g++翻译出来的是二进制的程序,默认是release模式,所以要使用gdb调试,必须在生成的源代码中生成二进制的时候,加入一些调试信息,我们需要加上 -g选项
我们可以敲击readelf -S mytest来查看程序的段构成:
段构成其实就是我们程序是由哪些数据段组成的,是由堆区上的数据,还是常量区的,还是栈区等等区域,我们通过翻阅查找,并没有看到debug相关的信息!!
我们知道如果一个程序可以被调试,该程序中的二进制文件一定加入了一些debug信息!而release中则没有debug信息!
所以我们需要在生成可执行程序的时候加入一些debug信息(这里我们只需要加入了可调试的信息,不用具体深究到底是什么)
[sjj@VM-20-15-centos 2022_3_18]$ gcc mytest.c -o mytest -g
我们再次查看程序的段结构:
[sjj@VM-20-15-centos 2022_3_18]$ readelf -S mytest | grep -i debug
总结: 所以gdb要调试程序必须是debug方式发布的,也就是要加gcc -g选项
2、开始使用
语法:gdb +可执行程序名 退出:ctrl+d或者quit
3、常用调试命令:
①list/l 行号:显示binFile源代码,接着上次的位置往下列,每次列10行。
②r或run:运行程序。
③n 或 next:单条执行。
④s或step:进入函数调用
⑤break(b) 行号:在某一行设置断点
⑥info break :查看断点信息。
⑦print(p):打印表达式的值,通过表达式可以修改变量的值或者调用函数
⑧p 变量:打印变量值。
⑨set var:修改变量的值
⑩continue(或c):从当前位置开始连续而非单步执行程序
⑪run(或r):从开始连续而非单步执行程序
⑫delete breakpoints n:删除序号为n的断点
⑬disable breakpoints:禁用断点
⑭enable breakpoints:启用断点
⑮info(或i) breakpoints:参看当前设置了哪些断点
⑯display 变量名:跟踪查看一个变量,每次停下来都显示它的值
⑰until X行号:跳至X行
⑱quit:退出gdb
五、Linux项目自动化构建工具—make/Makefile
背景
1、make是一个命令,而Makefile是一个文件
2、我们在写一些大型的项目或者工程时候,上百个源文件、头文件等都在不同的目录中放着,我们想要把这些文件组织起来就相当的麻烦,难道我们要将他们一个一个的gcc编译成可执行程序,删除的时候在一个一个的删除?万一中途不小心打错一个?这样显得太过于繁琐了
3、Makefile里面包含依赖关系和依赖方法,一起配合就可以实现自动化编译,一旦将命令写好,只需要一个make命令,整个工程完全自动化编译,删除的时候,直接一个make clean命令,就直接删除了,不用重复敲击rm...命令,极大的提高了软件开发的效率!!!
依赖关系和依赖方法
依赖关系
我们知道mytest可执行程序的生成,必须要先有mytest.c这个源程序,这里我们就可以说mytest依赖于mytest.c
依赖方法
就是生成mytest可执行程序的方法,我们就称为依赖方法,例如:
[sjj@VM-20-15-centos 2022_3_18]$ gcc mytest.c -o mytest
C代码示例:
先创建一个名为Makefile的文件
再在里面编写依赖关系和依赖方法
我们现在可以直接敲击make命令,执行依赖方法
我们可以直接 ./ 运行一下可执行程序,观察效果
问题来了:
问题一:为什么我们敲击make就可以直接执行,而清理需要make+clean呢?
原因:make扫描Makefile文件时,默认只会形成一个目标依赖关系,一般是第一个
问题二:为什么要加一个 .PHONY 这样的符号?
原因:修饰对应的符号,这是一个伪目标的概念,(其实就是总是可以执行的意思)
简化写法:
其中的$@ 就是目标文件(冒号左边的),$^就是依赖的文件(冒号右边的),敲击make运行时会自动转换为上面的文件
其实make和make clean可以类比vs中的生成和清理解决方案:
六、Linux小程序——进度条
回车换行(\n)的概念
回车(\r):就是回到当前行的首位置
换行:列不变,换到下面一行
我们先来观察两段代码:有什么现象呢?
#include <stdio.h>
int main()
{
printf("hello\n");
sleep(3);
return 0;
}
#include <stdio.h>
int main()
{
printf("hello");
sleep(3);
return 0;
}
我们会发现,带了\n的会立即将hello打印出来然后等待3秒钟结束程序,而不带\n的则需要等待3秒才能显示出来,那么问题是不是sleep函数执行先于printf函数呢?其实不是,printf函数已经执行,但是数据存放起来了,并没有立即刷新到屏幕上面去
缓冲区刷新机制
所以我们需要介绍我们的缓冲区机制(遇到\n进行刷新)
但是我们现在不想带上\n却也想将缓冲区给刷新,我们就要用到fflush函数了
int fflush(FILE *stream);
#include <stdio.h>
#include<unistd.h>
int main()
{
printf("hello");
fflush(stdout);
sleep(3);
return 0;
}
这样程序直接就打印在了屏幕上了
有了这些前置基础,就可以开始写进度条程序了。
编写进度条
特点:我们的目标就是在屏幕上面,肉眼可见的不断变长的进度条,顺便带上些许提示。
#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
int i = 0;
char bar[102];//0到100的一个字符串,最后加个\0
memset(bar, 0, sizeof(bar));
const char* lable = "|/-\\";//提示加载标签
while (i <= 100)
{
printf("[%-100s][%d%%][%c]\r", bar, i, lable[i % 4]);
fflush(stdout);
bar[i] = '#';
i++;
usleep(50000);
}
printf("\n");
return 0;
}
我们设置了一个字符数组,来存放我们的'#',通过将其不断的加长,每次将这个字符串打印在屏幕上,不换行,只回车,来实现我们的进度条:
为了美观,可以适当的改变字体的颜色,甚至加一些个性化的设置,可以百度搜索更多!