Linux系统编程(三)--Linux环境基础开发工具

在这里插入图片描述



前言

本篇博客主要介绍如何在Linux环境下进行开发,写代码,进行编译器的安装。


1.软件包的管理

1.1 Linux下安装软件的方式

在Linux下安装软件的方法大概有以下三种:
1)下载到程序的源代码,自行进行编译,得到可执行程序。
2)获取rpm安装包,通过rpm命令进行安装。(未解决软件的依赖关系)
3)通过yum进行安装软件。(常用)

1.2 什么是软件包(yum)

  • 在Linux下安装软件,⼀个通常的办法是下载到程序的源代码,并进⾏编译,得到可执⾏程序.
  • 但是这样太⿇烦了,于是有些⼈把⼀些常⽤的软件提前编译好,做成软件包(可以理解成windows上的安装程序)放在⼀个服务器上,通过包管理器可以很⽅便的获取到这个编译好的软件包,直接进⾏安装.
  • 软件包和软件包管理器,就好⽐"App"和"应⽤商店"这样的关系.
  • yum(YellowdogUpdater,Modified)是Linux下⾮常常⽤的⼀种包管理器.主要应⽤在Fedora,RedHat, Centos等发⾏版上.
  • Ubuntu:主要使⽤apt(AdvancedPackageTool)作为其包管理器。apt同样提供了⾃动解决依赖关系、下载和安装软件包的功能

在这里插入图片描述

1.3 yum具体操作

使用 yum list指令,罗列出可下载的所有软件

[root@VM-8-17-centos ~]# yum list

在这里插入图片描述
罗列出XXX相关的软件

[root@VM-8-17-centos ~]# yum list | grep XXX

在这里插入图片描述
说明一下:
1)软件包名称:主版本号.次版本号.源程序发行号-软件包的发行号.主机平台.cpu架构。
2)"x86_64"后缀表示64位系统的安装包,"i686"后缀表示32位系统安装包,选择包时要和系统匹配。
3)"el7"表示操作系统发行版的版本,“el7"表示的是"centos7/redhat7”,“el6"表示"centos6/redhat6”。
4)最后一列表示的是“软件源”的名称,类似于“小米应用商店”,“华为应用商店”这样的概念。

安装软件
指令: yum install 软件名
普通用户需要提权:: sudo yum install 软件名

[root@VM-8-17-centos ~]# yum install sl

yum会自动找到都有哪些软件包需要下载,这时候敲“y”确认安装,当出现“complete”字样时,说明安装完成。
在这里插入图片描述

注意事项:
1)安装软件时由于需要向系统目录中写入内容,一般需要sudo或者切换到root账户下才能完成。
2)yum安装软件只能一个装完了再装另一个,正在使用yum安装一个软件的过程中,如果再尝试用yum安装另外一个软件,yum会报错。
卸载软件
卸载指令:yum remove 软件名
普通用户需提权:sudo yum remove 软件名

[root@VM-8-17-centos ~]# yum remove sl

yum会自动卸载该软件,这时候敲“y”确认卸载,当出现“complete”字样时,说明卸载完成。

2. 编辑器vim

2.1 vim的基本概念

vim在我们做开发的时候,主要解决我们编写代码的问题,本质上就是一个多模式的文本编辑器。

我们这里主要介绍vim最常用的三种模式:命令模式、插入模式、底行模式。
1、命令模式(Normal mode)。
在命令模式下,我们可以控制屏幕光标的移动,字符、字或行的删除,复制粘贴,剪贴等操作。
2、插入模式(Insert mode)。
只有在插入模式下才能进行文字输入,该模式是我们使用最频繁的编辑模式。
3、底行模式(Command mode)。
在底行模式下,我们可以将文件保存或退出,也可以进行查找字符串等操作。在底行模式下我们还可以直接输入vim help-modes查看当前vim的所有模式。

2.2 vim下各模式的切换

指令: vim 文件名

[root@VM-8-17-centos gcc]# vim test.c

进入vim后默认为命令模式(普通模式),要输入文字需切换到插入模式。
在这里插入图片描述
【命令模式】切换至【插入模式】
1)输入「i」:在当前光标处进入插入模式。
2)输入「a」:在当前光标的后一位置进入插入模式。
3)输入「o」:在当前光标处新起一行进入插入模式。

【命令模式】切换至【底行模式】
1)输入「Shift+;」即可,实际上就是输入「:」。

【插入模式】或【底行模式】切换至【命令模式】
1)插入模式或是底行模式切换至命令模式都是直接按一下「Esc」键即可。

退出vim及保存⽂件,在【正常模式】下,按⼀下「:」冒号键进⼊「Lastlinemode」,例如:

1)输入「w」:保存当前⽂件)。
2)输⼊「wq」:存盘并退出vim。
3)输入「q!」:不存盘强制退出vim。

vim命令模式各命令汇总

  1. 移动光标
    vim可以直接用键盘上的光标来上下左右移动,但正规的vim是用小写英文字母「h」、「j」、「k」、「l」,分别控制光标左、下、上、右移一格

两边为左右,中间为上下 j(jump中文意思为跳,因此为下移),k(king中文意思为皇,因此为上移)

按「shift + g = G」:移动到文章的最后。
按「shift + 4 = $ 」:移动到光标所在行的“行尾”。
按「shift + 6 = ^」:移动到光标所在行的“行首”。
按「w」:光标跳到下个字的开头。
按「e」:光标跳到下个字的字尾。
按「b」:光标回到上个字的开头。
按「#l」:光标移到该行的第#个位置,如:5l,56l
按[gg]:进入到文本开始。
按「ctrl + b」:屏幕往“后”移动一页
按「ctrl + f 」:屏幕往“前”移动一页
按「ctrl + u」:屏幕往“后”移动半页
按「ctrl + d」:屏幕往“前”移动半页

  1. 删除文字
    「x」:每按一次,删除光标所在位置的一个字符。
    「#x」:例如,「6x」表示删除光标所在位置的“后面(包含自己在内)”6个字符。
    「shift + x = X」:大写的X,每按一次,删除光标所在位置的“前面”一个字符。
    「#X」:例如,「20X」表示删除光标所在位置的“前面”20个字符。
    「dd」:删除光标所在行。
    「#dd」:从光标所在行开始删除#行。

  2. 复制
    「yw」:将光标所在之处到字尾的字符复制到缓冲区中。
    「#yw」:复制#个字到缓冲区。
    「yy」:复制光标所在行到缓冲区。
    「#yy」:例如,「6yy」表示拷贝从光标所在的该行“往下数”6行文字。
    「p」:将缓冲区内的字符贴到光标所在位置。

注意:所有与“y”有关的复制命令都必须与“p”配合才能完成复制与粘贴功能。

  1. 替换

「r」:替换光标所在处的字符。
「shift + r = R」:替换光标所到之处的字符,直到按下「ESC」键为止。

  1. 撤销上一次操作
    「u」:如果您误执行一个命令,可以马上按下「u」,回到上一个操作。按多次“u”可以执行多次回复。
    「ctrl + r」: 撤销的恢复。

  2. 更改
    「cw」:更改光标所在处的字到字尾处。
    「c#w」:例如,「c3w」表示更改3个字。

  3. 跳至指定的行
    「ctrl + g」: 列出光标所在行的行号。
    「#G」:例如,「15G」,表示移动光标至文章的第15行行首。

  4. 其他操作

「 shift + ` = ~ 」:大小写快速切换。

「shift + zz = ZZ」: 保存并退出

vim末行命令集合

在使用末行模式之前,请记住先按「ESC」键确定您已经处于正常模式,再按「:」冒号即可进入末行模式。

  1. 列出行号
    「set nu」: 输入「set nu」后,会在文件中的每一行前面列出行号。

  2. 跳到文件中的某一行
    「#」:「#」号表示一个数字,在冒号后输入一个数字,再按回车键就会跳到该行了,如输入数字15,再回车,就会跳到文章的第15行。

  3. 查找字符
    「/关键字」: 先按「/」键,再输入您想寻找的字符,如果第一次找的关键字不是您想要的,可以一直按「n」会往后寻找到您要的关键字为止。
    「?关键字」:先按「?」键,再输入您想寻找的字符,如果第一次找的关键字不是您想要的,可以一直
    按「n」会往前寻找到您要的关键字为止。
    问题:/ 和 ?查找有和区别?

/关键字 会高亮显示光标后匹配的第一个字符串,回车后光标移到该字符串的第一个字母;
?string 会高亮显示光标前匹配的第一个字符串,回车后光标移到该字符串的第一个字母。
在回车之后,按n键同方向转到下一个匹配的字符串,按N键反方向转到上一个匹配的字符串。

  1. 保存文件
    「w」: 在冒号输入字母「w」就可以将文件保存起来

  2. 离开vim
    「q」:按「q」就是退出,如果无法离开vim,可以在「q」后跟一个「!」强制离开vim。
    「wq」:一般建议离开时,搭配「w」一起使用,这样在退出的时候还可以保存文件。

2.4批量化注释和批量化去注释

批量化注释
在这里插入图片描述
第一步完成之后,如果你想选择快速注释的行号,比如:你现在在3行,你想注释到30行,可以在第一步完成之后,按下30,再按shift+ g(G),接下来继续完成后面的步骤。

批量化去注释
在这里插入图片描述

2.5 vim配置

使用im编辑器的时候好多功能需要我们自己配置,比如关键字的提升,行数的显示,换行的空格。

在这里插入图片描述
进去之后是一个vim的编辑界面,这些配置命令可以从网上搜,但是比较麻烦。所以这里给一个链接,大家可以之间复制上去,点击回车,进行配置。

curl -sLf https://gitee.com/HGtz2222/VimForCpp/raw/master/install.sh -o ./install.sh && bash ./install.sh

注意:一定要在自己当前的目录下
在这里插入图片描述
在这里插入图片描述
配置完成后就可以达到这样的效果:
在这里插入图片描述

2.6 普通用户使用sudo提权

在上一篇博客中有好多地方只能使用超级权限才能做到,但是如果我们把普通用户提权之后,也可以使用root账户的权限。
只有使用root账户才可以给普通账户提权,所以我要先切换到root账户进行提权。
步骤一:输入vim /etc/sudoers 命令
步骤二:在这个界面找到这个位置,以root的格式加入普通用户)
在这里插入图片描述
操作如下![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/641b436ff7904767bd71f52d6925b45b.png
步骤三:在底行输入wq!,强转退出,提权成功。

3. 编译器gcc/g++

3.1 gcc/g++的作用

gcc和g++分别是GNU的C和C++的编译器,gcc和g++在执行编译的时候一般有以下四个步骤:
1)预处理(头文件展开、去注释、宏替换、条件编译)。
2)编译(C代码翻译成汇编语言)。
3)汇编(汇编代码转为二进制目标代码)。
4)链接(将汇编过程产生的二进制代码进行链接)。

在这里插入图片描述

3.2 gcc/g++语法

语法: gcc/g++ 选项 文件
常用选项:
1)-E 只进行预处理,这个不生成文件,你需要把他重定向到一个输出文件里面(否则将把预处理后的结果打印到屏幕上)。
2)-S 编译到汇编语言,不进行汇编和链接,即只进行预处理和编译。
3)-c 编译到目标代码
4)-o 将处理结果输出到指定文件,该选项后需紧跟输出文件名。
5)-static 此选项对生成的文件采用静态链接。
6)-g 生成调试信息(若不携带该选项则默认生成release版本)。
7)-shared 此选项将尽量使用动态库,生成文件较小。
8)-w 不生成任何警告信息。
9)Wall 生成所有警告信息。
10)-O0/-O1/-O2/-O3 编译器优化选项的四个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高。

3.3 编译选项

格式:gcc [选项] 要编译的文件 [选项] [目标文件]

预处理

  • 预处理功能主要包括宏定义,⽂件包含,条件编译,去注释等。
  • 预处理指令是以#号开头的代码⾏。
  • 选项“-E”,该选项的作⽤是让gcc在预处理结束后停⽌编译过程。
  • 选项“-o”是指⽬标⽂件,“.i”⽂件为已经过预处理的C原始程序。
gcc -E test.c -o test.i

在这里插入图片描述

编译(生成汇编)

  • 在这个阶段中,gcc⾸先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的⼯作,在检查⽆误后,gcc把代码翻译成汇编语⾔。
  • ⽤⼾可以使⽤“-S”选项来进⾏查看,该选项只进⾏编译⽽不进⾏汇编,⽣成汇编代码。
gcc -S test.i -o test.s

在这里插入图片描述

汇编 (生成机器可识别代码)

  • 汇编阶段是把编译阶段⽣成的“.s”⽂件转成⽬标⽂件
  • 读者在此可使⽤选项“-c”就可看到汇编代码已转化为“.o”的⼆进制⽬标代码了
gcc -c test.s -o test.o

在这里插入图片描述

链接

  • 在成功完成以上步骤之后,就进入了链接阶段。
  • 链接的主要任务就是将生成的各个“xxx.o”文件进行链接,生成可执行文件。
  • gcc/g++不带-E、-S、-c选项时,就默认生成预处理、编译、汇编、链接全过程后的文件。
  • 若不用-o选项指定生成文件的文件名,则默认生成的可执行文件名为a.out。
    在这里插入图片描述
    图中三个绿色的文件都是用 -o指定生成的可执行文件。
    在这里插入图片描述

3.4 动态链接和静态链接

在我们的实际开发中,不可能将所有代码放在⼀个源⽂件中,所以会出现多个源⽂件,⽽且多个源⽂件之间不是独⽴的,⽽会存在多种依赖关系,如⼀个源⽂件可能要调⽤另⼀个源⽂件中定义的函数,但是每个源⽂件都是独⽴编译的,即每个*.c⽂件会形成⼀个*.o⽂件,为了满⾜前⾯说的依赖关系,则需要将这些源⽂件产⽣的⽬标⽂件进⾏链接,从⽽形成⼀个可以执⾏的程序。这个链接的过程就是静态链接。静态链接的缺点很明显:

  • 浪费空间:因为每个可执⾏程序中对所有需要的⽬标⽂件都要有⼀份副本,所以如果多个程序对同⼀个⽬标⽂件都有依赖,如多个程序中都调⽤了printf()函数,则这多个程序中都含有
    printf.o,所以同⼀个⽬标⽂件都在内存存在多个副本;

  • 更新⽐较困难:因为每当库函数的代码修改了,这个时候就需要重新进⾏编译链接形成可执⾏程序。但是静态链接的优点就是,在可执⾏程序中已经具备了所有执⾏程序所需要的任何东西,在执⾏的时候运⾏速度快。

动态链接的出现解决了静态链接中提到问题。动态链接的基本思想是把程序按照模块拆分成各个相对独⽴部分,在程序运⾏时才将它们链接在⼀起形成⼀个完整的程序,⽽不是像静态链接⼀样把所有程序模块都链接成⼀个单独的可执⾏⽂件。
动态链接其实远⽐静态链接要常⽤得多。⽐如我们查看下test这个可执⾏程序依赖的动态库,会发
现它就⽤到了⼀个c动态链接库:
在这里插入图片描述

在这⾥涉及到⼀个重要的概念:库

  • 我们的C程序中,并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该
    函数的声明,⽽没有定义函数的实现,那么,是在哪⾥实“printf”函数的呢? •
  • 最后的答案是:系统把这些函数实现都被做到名为libc.so.6的库⽂件中去了,在没有特别指定
    时,gcc会到系统默认的搜索路径“/usr/lib”下进⾏查找,也就是链接到libc.so.6库函数中去,这样
    就能实现函数“printf”了,⽽这也就是链接的作⽤ gcc和g++默认生成的二进制程序是动态链接的,我们可以使用file指令进行查看。

在这里插入图片描述
虽然gcc和g++默认采用的是动态链接,但如果我们需要使用静态链接,带上-static选项即可。

⼀般我们的云服务器,C/C++的静态库并没有安装,可以采⽤如下⽅法安装。

sudo yum install glibc-static libstdc++-static -y

在这里插入图片描述
这里我们可以发现动态链接比较节省空间,而静态链接比较浪费空间。

动态库和静态链接优缺点:
动态链接:
 优点:省空间(磁盘的空间,内存的空间),bin体积小,加载速度快。
 缺点:依赖动态库,程序可移植性较差。
静态链接:
 优点:不依赖第三方库,程序的可移植性较高。
 缺点:浪费空间。

4.⾃动化构建-make/Makefile

4.1 makefile基本介绍

  • 会不会写makefile,从⼀个侧⾯说明了⼀个⼈是否具备完成⼤型⼯程的能⼒
  • ⼀个⼯程中的源⽂件不计数,其按类型、功能、模块分别放在若⼲个⽬录中,makefile定义了⼀
    系列的规则来指定,哪些⽂件需要先编译,哪些⽂件需要后编译,哪些⽂件需要重新编译,甚⾄于进⾏更复杂的功能操作
  • makefile带来的好处就是⸺“⾃动化编译”,⼀旦写好,只需要⼀个make命令,整个⼯程完全
    ⾃动编译,极⼤的提⾼了软件开发的效率。
  • make是⼀个命令⼯具,是⼀个解释makefile中指令的命令⼯具,⼀般来说,⼤多数的IDE都有这
    个命令,⽐如:Delphi的make,VisualC++的nmake,Linux下GNU的make。可⻅,makefile
    都成为了⼀种在⼯程⽅⾯的编译⽅法。
  • make是⼀条命令,makefile是⼀个⽂件,两个搭配使⽤,完成项⽬⾃动化构建。

4.2 依赖关系和依赖方法

在使用make/Makefile前我们首先应该理解各个文件之间的依赖关系以及它们之间的依赖方法。

依赖关系: 文件A的变更会影响到文件B,那么就称文件B依赖于文件A。

例如,test.o文件是由test.c文件通过预处理、编译以及汇编之后生成的文件,所以test.c文件的改变会影响test.o,所以说test.o文件依赖于test.c文件。
依赖方法: 如果文件B依赖于文件A,那么通过文件A得到文件B的方法,就是文件B依赖于文件A的依赖方法。

例如,test.o依赖于test.c,而test.c通过gcc -c test.c -o
test.o指令就可以得到test.o,那么test.o依赖于test.c的依赖方法就是gcc -c test.c -o test.o。

在这里插入图片描述

4.3 基本使用

先创建一个test.c文件和名字为Makefile的文件,使用vim对Makefile进行编辑,如下:
在这里插入图片描述
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/aea04d288ce34e35aa61359bfed1325f.png
注意:

  • makefile 的文件名必须是 makefile/Makefile,不能是其他名称,否则 make 识别不了。
  • 依赖文件可以有多个,也可以没有。
  • 依赖方法必须以 [Tab] 键开头,特别注意不能是四个空格。

用命令查看效果
在这里插入图片描述

4.3 项目清理

在这里插入图片描述

⼯程是需要被清理的,像clean这种,没有被第⼀个⽬标⽂件直接或间接关联,那么它后⾯所定义的命令将不会被⾃动执⾏,不过,我们可以显⽰要make执⾏。即命令⸺“make clean”,以此来清除所有的⽬标⽂件,以便重编译。但是⼀般我们这种clean的⽬标⽂件,我们将它设置为伪⽬标,⽤.PHINY修饰,伪目标的特性是总是被执⾏的。

什么叫做总是被执行的

在这里插入图片描述
如果我们之前已经make过了,那么我们这里再次make,就会显示上图。
这表示用户在尝试编译名为 mytest 的项目或文件。
由于没有检测到源文件的更改,make 命令没有执行任何操作,因为编译结果已经是最新的。

文件 = 内容+属性
在这里插入图片描述
明白了上面的一些时间的意义,就可以知道是通过什么来确定为什么不能编译了。
在这里插入图片描述
但是我们的make clean依然可以执行。
在这里插入图片描述
总结:

. PHONY可以让make忽略源⽂件和可执⾏⽬标⽂件的Modify时间对⽐

4.3 make原理

在这里插入图片描述

  1. make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
  2. 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到 myproc 这个文件,并把这个文件作为最终的目标文件。
  3. 如果 myproc 文件不存在,或是 myproc 所依赖的后面的 myproc.o 文件的文件修改时间要比 myproc 这个文件新(可以用 touch 测试),那么,他就会执行后面所定义的命令来生成 myproc 这个文件。
  4. 如果 myproc 所依赖的 myproc.o 文件不存在,那么 make 会在当前文件中找目标为 myproc.o 文件的依赖性,如果找到则再根据那一个规则生成 myproc.o 文件。(这有点像一个堆栈的过程)
  5. 当然,你的C文件和H文件是存在的啦,于是 make 会生成 myproc.o 文件,然后再用 myproc.o 文件声明 make 的终极任务,也就是执行文件 hello 了。
  6. 这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。
  7. 在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。
  8. make只管⽂件的依赖性,即,如果在我找了依赖关系之后,冒号后⾯⽂件还是不在,那么对不起,我就不⼯作啦。

4.4Makefile语法扩展

如果我们有多个 .c文件,那么我们将他们编译时在Makefile里面可能要敲很多行gcc的代码,所以在这里Makefile提供一种类似C语言中宏定义的功能。

$(变量名):使用该变量,这个变量是替换了一个目标的,下面就是BIN替换了mytest
@ 加(执行的命令): 去掉命令的显示
%:通配符,%.c即展开目录下所有的.c文件
$^:代表依赖文件列表

下列代码示例:

# 定义变量
BIN = mytest
CC = gcc
SRC = $(wildcard *.c)  # 使用 wildcard 函数获取当前所有 .c 文件名
OBJ = $(SRC:.c=.o)     # 将 SRC 的所有同名 .c 替换成为 .o 形成目标文件列表
LFLAGS = -o            # 链接选项
FLAGS = -c             # 编译选项
RM = rm -f             # 引入命令

# 目标文件的生成规则
$(BIN): $(OBJ)
	@$(CC) $(LFLAGS) $@ $^          #$@代表目标文件名
	@echo "linking ... $^ to $@"

# 依赖文件的生成规则
%.o: %.c                           # %表示通配符,
	@$(CC) $(FLAGS) $<             # <对展开的依赖.c文件,一个一个的交给对应的命令(gcc)生成.o
	@echo "compiling ... $< to $@" # 加上@就不会回显

# 伪目标
.PHONY: clean test

# 清理生成的文件
clean:
	$(RM) $(OBJ) $(BIN)

# 测试目标
test:
	@echo $(SRC)
	@echo $(OBJ)

5.进度条

5.1 换行和回车

感受一下缓冲区的存在。

#include <stdio.h>
 int main()
 {
 printf("hello world!\n");
 sleep(3);
 return 0;
 }
#include <stdio.h>
 int main()
 {
 printf("hello world!");
 sleep(3);
 return 0;
 }

可以看到代码中仅仅删除了字符串后面的’\n’,那么代码的运行结果还与之前相同吗?答案否定的,该代码的运行结果是:先休眠3秒,然后打印字符串hello world之后结束运行。该现象就证明了行缓冲区的存在。

显示器对应的是行刷新,即当缓冲区当中遇到’\n’或是缓冲区被写满才会被打印出来,而在第二份代码当中并没有’\n’,所以字符串hello world先被写到缓冲区当中去了,然后休眠3秒后,直到程序运行结束时才将hello world打印到显示器当中。

\r和\n
\r: 回车,使光标回到本行行首。
\n: 换行,使光标下移一格。
而我们键盘上的Enter键实际上就等价于\n+\r
既然是\r是使光标回到本行行首,那么如果我们向显示器上写了一个数之后再让光标回到本行行首,然后再写一个数,不就相当于将前面一个数字覆盖了吗?
但这里有一个问题:不使用’\n’进行换行怎么将缓冲区当中数据打印出来?
这里我们可以使用fflush函数,该函数可以刷新缓冲区,即将缓冲区当中的数据刷新当显示器当中。

5.2 进度条

知道了\r这个概念我们就可以实现一个简单的进度条了。

首先我们创建四个文件:

process.c : 进度条函数的实现。

process.h : 进度条函数的声明,头文件包含。

main.c : 调用.h文件中的方法 。

Makefile : 自动化编译。

Makefile

  1 BIN=process
  2 SRC=$(wildcard *.c)
  3 OBJ=$(SRC:.c=.o)
  4 CC=gcc
  5 RM=rm -f
  6 
  7 $(BIN):$(OBJ)
  8   @$(CC) $^ -o $@
  9   @echo "链接 $^ 成 $@"
 10 %.o:%.c
 11   @$(CC) -c $<                                                                                                              
 12   @echo "编译 ... $< 成 $@"
 13 
 14 .PHONY:clean
 15 clean:
 16   @$(RM) $(OBJ) $(BIN)
 17 
 18 .PHONY:test
 19 test:
 20   @echo $(BIN)
 21   @echo $(SRC)
 22   @echo $(OBJ)

process.h

#pragma once

#include <stdio.h>

//v1
void process();

//v2
void FlushProcess(const char*, double total, double current);

process.c:v1是基础版本,v2是进阶版本

#include "process.h"
#include <string.h>
#include <unistd.h>

#define SIZE 101
#define STYLE '='

//v2: 根据进度,动态刷新一次进度条
void FlushProcess(const char *tips, double total, double current)
{
    const char *lable = "|/-\\";
    int len = strlen(lable);
    static int index = 0;
    char buffer[SIZE];
    memset(buffer, 0, sizeof(buffer));

    double rate = current*100.0/total;
    int num = (int)rate;

    int i = 0;
    for(; i < num; i++)
        buffer[i] = STYLE;

    printf("%s...[%-100s][%.1lf%%][%c]\r", tips, buffer, rate, lable[index++]);
    fflush(stdout);
    index %= len;

    if(num >= 100)printf("\n");
}

// v1: 展示进度条基本功能
void process()
{
    int rate = 0;
    char buffer[SIZE];
    memset(buffer, 0, sizeof(buffer));
    const char *lable = "|/-\\";
    int len = strlen(lable);

    while(rate <= 100)
    {
        printf("[%-100s][%d%%][%c]\r", buffer, rate, lable[rate%len]);
        fflush(stdout);
        buffer[rate] = STYLE;
        rate++;
        usleep(10000);
    }

    printf("\n");
}

main.c

#include "process.h"
#include <unistd.h>
#include <time.h>
#include <stdlib.h>

//函数指针类型
typedef void (*call_t)(const char*,double,double);

double total = 1024.0;
//double speed = 1.0;
double speed[] = {1.0, 0.5, 0.3, 0.02, 0.1, 0.01};

//回调函数
void download(int total, call_t cb)
{
    srand(time(NULL));
    double current = 0.0;
    while(current <= total)
    {
        cb("下载中", total, current); // 进行回调
        if(current>=total) break;
        // 下载代码
        int random = rand()%6;
        usleep(5000);
        current += speed[random];
        if(current>=total) current = total;
    }
}

void uploadload(int total, call_t cb)
{
    srand(time(NULL));
    double current = 0.0;
    while(current <= total)
    {
        cb("上传中", total, current); // 进行回调
        if(current>=total) break;
        // 下载代码
        int random = rand()%6;
        usleep(5000);
        current += speed[random];
        if(current>=total) current = total;
    }
}

int main()
{
    download(1024.0, FlushProcess);
    printf("download 1024.0MB done\n");
    download(512.0, FlushProcess);
    printf("download 512.0MB done\n");
    download(256.0,FlushProcess);
    printf("download 256.0MB done\n");
    download(128.0,FlushProcess);
    printf("download 128.0MB done\n");
    download(64.0,FlushProcess);
    printf("download 64.0MB done\n");
    uploadload(500.0, FlushProcess);
    return 0;
}

6. Linux环境下代码调试器-gdb

gdb使用须知
程序发布方式:
 1、debug版本:程序本身会被加入更多的调试信息,以便于进行调试。
 2、release版本:不会添加任何调试信息,是不可调试的。

在Linux当中gcc/g++默认生成的可执行程序是release版本的,是不可被调试的。如果想生成debug版本,就需要在使用gcc/g++生成可执行程序时加上-g选项。
在这里插入图片描述
对同一份源代码分别生成其release版本和debug版本的可执行程序,并通过ll指令可以看到,debug版本发布的可执行程序的大小比release版本发布的可执行程序的大小要大一点,其原因就是以debug版本发布的可执行程序当中包含了更多的调试信息。
在这里插入图片描述
gdb命令汇总
【进入gdb】
指令: gdb 文件名
在这里插入图片描述
gdb进行调试的时候我们看不到代码,所以调试起来可能有点不便,我们这里安装一个cgdb

  • Ubuntu:sudo apt-get install -y cgdb
  • Centos: sudo yum install -y cgdb
    指令:
    cgdb 目标文件名
    在这里插入图片描述
    这样就可以看到代码进行调试了,接下来就是一些调试的指令了。

【调试】
1)「run/r」:运行代码(启动调试)。
2)「next/n」:逐过程调试。
3)「step/s」:逐语句调试。
4)「until 行号」:跳转至指定行。
5)「finish」:执行完当前正在调用的函数后停下来(不能是主函数)。
6)「continue/c」:运行到下一个断点处。
7)「set var 变量=x」:修改变量的值为x。

【显示】
1)「list/l n」:显示从第n行开始的源代码,每次显示10行,若n未给出则默认从上次的位置往下显示.。
2)「list/l 函数名」:显示该函数的源代码。
3)「print/p 变量」:打印变量的值。
4)「print/p &变量」:打印变量的地址。
5)「print/p 表达式」:打印表达式的值,通过表达式可以修改变量的值。
6)「display 变量」:将变量加入常显示(每次停下来都显示它的值)。
7)「display &变量」:将变量的地址加入常显示。
8)「undisplay 编号」:取消指定编号变量的常显示。
9)「bt」:查看各级函数调用及参数。
10)「info/i locals」:查看当前栈帧当中局部变量的值。

【断点】
1)「break/b n」:在第n行设置断点。
2)「break/b 函数名」:在某函数体内第一行设置断点。
3)「info breakpoint/b」:查看已打断点信息。
4)「delete/d 编号」:删除指定编号的断点。
5)「disable 编号」:禁用指定编号的断点。
6)「enable 编号」:启用指定编号的断点。

【退出gdb】
1)「quit/q」:退出gdb

i:光标跳转的命令行
Esc:进入代码页面


本篇博客到此结束,欢迎评论区留言~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值