目录
1. 一个C/C++程序的构建过程,程序从源代码到可执行文件必须经历四个阶段
1. git clone拉取远端仓库克隆到本地(目录克隆到本地)
2. git add . 把需要提交的目录(项目)复制或剪切到本地仓库
3. git commit -m ‘提交日志’ 修改之后的代码添加进去提交本地库
4. git push 推送到远程仓库,这才是把本地仓库的代码推送至远程仓库
5. 如果在远端我们的代码修改了,git status 查看 git 的状态
⑨ display 常显示变量的值、undisplay取消常显示
一、编译器——gcc/g++的使用
gcc/g++的安装
gcc的安装:
yum install -y gcc
g++的安装:
yum install -y gcc-c++ libstdc++-devel
gcc/g++的基本使用
我们在使用 gcc/g++ 进行编译的时候一般都带选项: -o
在
gcc -o test test.c
这个命令中,gcc
编译器会对test.c
这个源文件进行编译和链接操作。正常情况下,如果没有-o
选项,gcc
会生成一个默认名称的可执行文件(在 Linux 等系统下通常是a.out
)。但是当使用
-o test
时,编译器会将生成的可执行文件命名为test
,而不是默认的a.out
。
注意:gcc -o test test.c
和gcc test.c -o test
这两种写法都是正确的。在这两种情况下,gcc
编译器都能够理解-o test
是要将编译生成的可执行文件命名为test
,而test.c
是要编译的源文件。
gcc的使用
g++的使用
动态链接与静态链接
我们在学习动态链接与静态链接的时候需要对程序的翻译过程进行复习一下,这都是我们C语言阶段就学习过的内容。
程序的翻译过程
1. 一个C/C++程序的构建过程,程序从源代码到可执行文件必须经历四个阶段
- 预处理:头文件的展开、宏替换、条件编译、去掉注释
- 编译:检查语法错误,将预处理后的源代码生成为汇编代码
- 汇编:汇编代码转换成二进制的机器码
- 链接:多个目标文件的文件链接成一个可执行文件
2. 理解选项的含义
当生成了一个可执行文件,但是我们并不想让他直接编译完成,编译的过程是一下就走完了,而接下来我们想看他的过程,就在编译的时候带上选项就可以查看每一个阶段的过程。
注意:C++同样是这种过程。
在测试之前我们重新在 test.c 中编写一个用于测试的代码
#include <stdio.h>
#define N 20
int main()
{
//测试去掉注释
printf("hello wolrd 1\n");
//printf("hello wolrd 2\n");
//printf("hello wolrd 3\n");
//printf("hello wolrd 4\n");
//printf("hello wolrd 5\n");
//printf("hello wolrd 6\n");
//printf("hello wolrd 7\n");
//printf("hello wolrd 8\n");
//printf("hello wolrd 9\n");
//printf("hello wolrd 10\n");
//测试条件编译
#ifdef CONDITION
printf("condition\n");
#else
printf("no condition\n");
#endif
//测试宏替换
printf("宏:%d\n", N);
return 0;
}
第一步预处理:
选项 -E :从现在开始,进行程序的翻译,当你将预处理做完,就停下来。
如果去掉 –o 预处理这么多内容打印到显示器上特别不好看,选项 -o: 指明形成的临时文件名称 .i, 使用 .i 作为预处理文件的扩展名是一种比较常见的约定,就像c语言文件名后缀是 .c一样。
观察下面的代码,我们可以看到多了800多行代码,这就是头文件的展开,把库中的代码拷贝过来了。
然后就进行了宏替换,预处理阶段就已经做了的。
最后只剩hello world 1,这就是去掉注释。
条件编译这里把这个没有的也裁掉了,这就是条件编译。
注意:我们要用的头文件、必须在C语言库中存在,平时在某个平台写代码的时候,头文件必须在该系统中存在 , Linux中库头文件在 /usr/include/stdio.h 目录下,平时我们安装VS2022,同时也会把头文件库文件安装到v2022所对应的目录下,这也就是我们为什么写代码可以自动补齐的原因。
gcc -E test.c -o test.i
我们不定义CONDITION也可以通过传参来定义
第二步编译:
编译:把c语言代码变成汇编代码。
选项 -S:从现在开始,进行程序的翻译,做完编译工作,变成汇编之后,就停下来。
编译之后Linux下通常以 .s后缀结尾作为存储汇编代码的文件
gcc -S test.i -o test.s
第三步汇编:
汇编语言不能被计算机直接执行,把汇编变成二进制目标文件
选项 -c:将汇编代码转成二进制目标文件就停下来,没有进行链接。
vim test.o 进入该文件我们看不懂,但是可以 od test.o进行阅读该二进制文件。
test.o虽然是二进制文件,但是不能被执行,我们可以加上权限,发现还是不能被执行,原因在于少了一步链接,这三个步骤只在编译你写的代码,比如你在c语言上的printf的函数是你写吗,你只是调用了printf,printf的实现你没有写。
gcc -c test.s -o test.o
我们执行该目标文件发现不能被执行,原因是因为缺少一步链接
第四步链接:
我们把前面三个步骤完成之后就可以进行链接操作了,链接是将多个目标文件以及所需的库文件整合在一起的关键过程。
把你写的代码和 C标准库中的代码合起来:这就是链接的过程,可以不带任何选项
gcc test.o –o mytest
链接就形成了可执行程序,形成可执行的二进制程序(库+你的代码)。
gcc test.o -o mytest
动静态链接的认识
我们要明白自己编写的代码与库中的代码是不同的。像 C 标准库,是他人事先准备好以供我们直接使用的。比如我们在代码里写了像
printf
这类库函数的调用语句,实际上我们只是进行了调用,并没有去实现这些函数。只有在进行链接这一环节时,库函数对应的具体实现才会和我们写的代码关联起来。而链接的本质,核心就是解决我们调用库函数时,如何与标准库(或其他相关库)建立关联的问题,这其中就涉及到选择动态链接还是静态链接的方式。动态链接是在程序运行时按需加载共享库来获取函数实现,而静态链接则是在链接阶段直接把库函数的代码复制到可执行文件当中。
- 动态链接
- 优点
- 资源节省:可执行程序体积小,因为不包含库代码,运行时才加载共享库,节省内存、磁盘和网络资源。例如多个程序共享动态库内存实例。
- 更新方便:库更新时,只要接口兼容,程序无需重新编译就能使用新功能。
- 缺点
- 运行依赖:依赖外部环境,缺少库或版本不匹配时程序无法运行。
- 性能损耗:启动要加载库,运行中调用库函数有系统开销,影响性能和启动速度。
- 静态链接
- 优点
- 独立稳定:不依赖外部库,库升级、删除或移动不影响程序运行,程序有很好的稳定性。
- 行为可预测:运行行为由自身代码决定,便于在不同环境部署和调试。
- 缺点
- 资源占用:可执行程序体积大,占用更多磁盘和内存空间,多个程序用相同静态库会有代码冗余。
- 更新不便:库更新时,程序要重新编译才能使用新功能,大型项目中耗时耗资源。
动静态库可以类比成摄影爱好者与照片滤镜库
假设你是一个摄影爱好者,你有一部相机(相当于程序本身),你平时会拍摄各种各样的照片(程序执行各种任务)。
动态库的情况
现在有一个非常流行的在线照片滤镜应用(相当于动态库)。当你拍完照片后,你想要给照片添加一些特殊的滤镜效果,但是你的相机本身没有这些滤镜功能(就像程序本身没有这个功能的实现)。
你打开相机中的一个连接功能(相当于程序中的函数调用接口),连接到这个在线滤镜应用。每次你想要使用一个滤镜的时候,相机就会通过网络(相当于运行时环境)向这个在线滤镜应用发送照片和滤镜请求(就像程序运行时请求动态库中的功能,这就是动态链接)。这个在线滤镜应用会处理你的照片,添加滤镜后再把处理后的照片发送回你的相机(就像动态库提供功能实现并返回结果)。
很多摄影爱好者都可以使用这个在线滤镜应用(多个程序共享动态库)。而且,如果这个滤镜应用更新了新的滤镜效果或者优化了滤镜算法(动态库更新),只要相机和滤镜应用之间的连接方式(接口)不变,你的相机依然可以使用这些新功能来处理照片。
静态库的情况
后来,你发现每次连接到在线滤镜应用很麻烦,而且有时候网络不好就没办法使用滤镜。于是你购买了一个带有大量滤镜功能的存储卡(相当于静态库),这个存储卡可以插入你的相机。
当你把存储卡插入相机后,相机就把存储卡里的滤镜功能(就像静态库中的代码)复制到了相机内部的可用功能列表中(就像静态链接把库代码复制到可执行文件,这就是静态链接)。现在你拍摄完照片后,想要添加滤镜,相机就可以直接使用存储卡里已经复制过来的滤镜功能来处理照片,不需要再连接到外部的在线滤镜应用(不依赖外部库运行)。即使这个在线滤镜应用因为某些原因停止服务(比如公司倒闭等情况,就像动态库不可用),你依然可以在相机里使用这些滤镜功能来处理照片。
验证我们所写的程序是动态链接还是静态链接
file 可执行文件
ldd 指令
ldd 可执行文件 用于查看程序(可执行文件或共享库)依赖的动态共享库的工具。当你运行
ldd
命令并跟上一个可执行文件的名称时,它会显示该可执行文件在运行时需要加载的动态库的路径和名称等相关信息。
静态库的安装
默认情况下都是动态链接的,如果我们想要静态链接需要怎么办呢?
ls /lib64/libc.a 不一定存在静态库,静态库不存在需要自己安装。
C语言安装静态库:
yum install -y glibc-static
C++安装静态库:
yum install -y libstdc++-static
静态链接
在系统中,C 动态库(也称为共享库)是被多个使用该库的程序所共享的。尽管使用 C 动态库的程序众多,但通常系统中只存在一份该动态库的物理副本。这体现了动态库的共享特性,即多个程序在运行时可以同时访问并使用这同一个动态库,避免了库代码在内存中的重复存储,从而节省了系统的内存资源。例如,标准 C 库(如
libc.so
)被众多的 C 语言程序所依赖,在系统运行时,这些程序通过动态链接的方式引用libc.so
,而无需各自拥有一份独立的libc.so
代码副本。静态链接并不是拷贝动态库内部的代码。静态链接是在程序编译阶段,将所需的库代码(通常是静态库,以
.a
结尾)直接复制到可执行文件中,使其成为可执行文件的一部分,从而在运行时不再依赖外部的库(包括动态库)。所以,静态链接与动态库的机制是相互独立的,静态链接不会直接操作动态库的代码。Windows动静态链接的原理是一样的,动态库通常以.dll为后缀,静态库一般是以.lib为后缀
我们也可以用C++进行静态链接
二、项目自动化构建工具——make/Makefile
make与Makefile的概念
make 是一个命令,Makefile是一个文件。
make与Makefile存在的意义:
Makefile 所带来的好处集中体现在自动化编译方面。这并不意味着完全无需人工干预,而是在初始阶段,开发人员需要根据项目的结构和需求编写 Makefile 文件,精心规划好源文件与目标文件的依赖关系以及相应的编译规则。一旦 Makefile 文件编写完成,后续使用 Make 命令时,它会自动读取 Makefile 中的内容,按照既定的规则和依赖关系,有条不紊地对程序或项目进行编译,无需开发人员再次手动调整编译过程。这使得在项目开发过程中,无论是对单个源文件进行修改后重新编译,还是对整个项目进行全新构建,都能够通过简单地执行 Make 命令快速、准确地完成,从而实现了高效、稳定的自动化构建过程,为软件开发的迭代和维护提供了有力支持。
注意: Makefile的 m 可以大写也可以小写。
我们可以来使用一下Makefile,下面会说明Makefile
就是不用再手写gcc了,直接make就可以了,看起有点弱,其实慢慢就会发现这个Makefile很强
Makefile的原理
Makefile 必须包含依赖关系和依赖方法这两个关键要素,其存在意义在于为构建项目提供自动化支持,使项目能够依据文件间的依赖关系和生成方法,有条不紊地完成编译、链接等构建流程,避免手动操作的繁琐与易错性,高效达成项目构建这一目标。
Makefile 的运行就如同生活中的一个场景。比如,当你打电话说 “爸,我是你儿子”,此时这仅仅建立了一种依赖关系,即你依赖于自己的父亲,但仅凭这一句,父亲并不清楚你到底想要做什么,事情自然无法推进,这是因为缺少依赖方法。再比如,你不可能去找室友的爸爸借钱,原因是你和室友的爸爸之间不存在任何依赖关系。只有当你既明确了依赖关系,像打电话跟自己的爸爸表明身份,又清晰地阐述依赖方法,例如接着说 “我没钱吃饭了,给我打点钱吧”,这样依赖于父亲打钱这件事情才能够完成。在 Makefile 中也是如此,只有同时具备依赖关系和依赖方法这两个关键要素,项目构建这件事情才能够顺利实现。
Makefile的语法
依赖关系和依赖方法
第一行整体我们称为:依赖关系。
mytest : test.c
目标文件(可执行程序/临时文件) : 依赖文件列表, 这里只有一个依赖文件
这里表示 mytest 依赖于test.c第二行整体我们称为:依赖方法
写完了Makefile,我们make一下就会生成可执行文件,然后运行该文件就可以了
项目清理
随着项目的不断编译和构建,会产生许多可执行程序以及一些诸如目标文件(
.o
文件)、中间编译文件等临时文件。有时候,我们可能想要重新编译整个项目,或者不想保留这些已生成的可执行程序和临时文件了(比如它们占用了过多磁盘空间,或者之前的编译结果出现了一些未知问题想要重新开始等情况),这时候就需要一种方法来清理它们。如何清理:
vim Makefile 进入Makefile之后接着往下输入
clean : 这里可以为空(依赖关系可以为空,也就说clean不依赖与任何文件)
在下来 tab 开头的就是依赖方法
我们再用 .PHONY 修改 clean
红色为伪目标 .PHONY 修饰的是一个伪目标
为什么make 和 make mytest 也是可以,而 clean 要 make clean
第一个遇到的可以省略名称,仅此而已,默认情况下只形成一个目标文件(可执行目标文件),不指名默认就是第一个。
如何理解 .PHONY 修饰的是一个伪目标
当我们不断 make 的时候,会发现不让你编译了,因为是最新的,没有修改没必要重新编译
可是当我们再不断调用 make clean 的时候总是能执行 rm –f mytest
我们可以知道被.PHONY修饰的对象总可以被执行
但是我们一般不会这样做,将我们生成的可执行程序用.PHONY修饰, 但是这都不是重要的,
最重要的是 gcc 如何得知 mytest 是最新呢???
我们再改回原来的Makefile
gcc 如何得知 mytest 是最新呢
肯定跟时间有关系,在我们学习指令的时候,就接触过一个指令 stat,stat命令是一个用于查看文件或文件系统状态信息的工具。
Access(访问时间):这是文件最后一次被访问(如查看内容)的时间。
Modify(修改时间):记录文件内容最后一次修改的时间。
Change(改变时间):是文件元数据(属性,如权限、所有者等)最后一次改变的时间。
为什么change会随着modify的改变而改变呢???
当我修改了文件内容,modify修改了,change也会随着修改,因为文件的大小变化了,也意味着文件的属性也跟着变化,所以change会改变。
Change 和 Modify 的时间变化能够理解,但是为什么Access是指我们cat访问/vim查看该文件的时间,为什么没有任何的变化???
访问文件不一定改内容,改内容一定访问,如果每次访问都修改时间,导致Access修改的频率太多了,就需要在磁盘进行多次的 IO(输入 / 输出)操作,别人设置文件让别人阅读,就不在过于关心访问时间,如果别人修改了文件时间立马更新Modify时间,所以操作系统采用的是你访问次数达到多少次就更新一次Access的时间,这种策略,没有什么规律,我们主要关注 Change 和 Modify。
我们现在知道了这么多时间,但是和 gcc 有什么关系??? 它怎么知道我们文件是最新的???
先有源文件 test.c,再有可执行程序 mytest
test.c 的 Modify 的时间 一定比 mytest 的 Modify 的时间要更早
如果 test.c 里面有bug改了一下里面的内容,然后就会更新源文件test.c 的 Modify的时间,这个时候,test.c 里面的时间就是新的,比 mytest 的时间新了,就是比较两个时间的新旧。
如何证明 gcc 根据修改的时间得知 mytest 是最新
我们知道一个指令touch,除了创建文件还可以修改时间
touch 可以更新文件的时间
综上所述:凡是用.PHONY修饰的就是不要拿时间作对比了,你每次都执行一下,不要遵守时间对比的规则,每次都要执行依赖方法,而没有使用 .PHONY修饰的就是对比源文件的时间和可执行的时间变化来确定是不是需要进行编译。
Makefile的推导规则
程序的翻译过程从预处理、编译、汇编、链接
文件从 test.i -> test.s -> test.o
我们的可执行文件 mytest 依赖于 test.o
而 test.o 依赖于 test.s
而 test.s 依赖于test.i我们在日常写Makefile中我们直接一步到位,直接写依赖于源文件就可以了。
三、版本控制器——git
今天,就让我们一起来学习一下个人使用 git 提交代码的基本操作流程吧,后续更复杂的冲突处理、分支协作等内容我们可以再逐步深入学习。
git 是什么
git 就像是一个超级智能的代码 “时光机”。假设你在写代码,就好像在创作一幅复杂的画作。每次你对代码进行修改,无论是添加新功能、修复小错误还是优化性能,git 都能帮你记录下这个 “画作”(代码)的不同状态。
比如说你写一个 C++ 程序,最开始只有一个简单的打印 “Hello, World!” 的功能,这是你代码的最初版本,git 就像一个相册,把这个初始版本保存了下来。之后你添加了一个计算两个数字相加的函数,git 又会把这个有新功能的版本记录下来,就好像在相册里新增了一张带有新内容的照片,你还可以找到原来记录下的版本(可以回顾不同的版本)。
git 的操作
1. 进入gitte官网,先新建一个仓库
gitte官网:Gitee - 基于 Git 的代码托管和研发协作平台
进入官网之后我们先创建一个仓库
初始化仓库,设置仓库模板,选择分支,我们先这样进行创建,后续需要深入学习git
远端就会形成一个这个仓库
2、Linux中提交代码操作
git 的安装:
sudo yum install -y git
准备我们要提交的代码文件
1. git clone拉取远端仓库克隆到本地(目录克隆到本地)
我们暂时先使用HTPS,因为比较简单如果用SSH,还需要进行配置
先复制好,然后进入Linux指令行中输入 git clone 粘贴刚刚复制的内容
git clone:把远端仓库克隆到本地
当我们克隆到本地的时候,有时候可能需要输入自己gitee的账号和密码
Linux中进入该仓库,而远端隐藏了.git文件
注意点: 当我们首次安装好了git之后系统会提示你进行配置 用户名 和 邮箱
要将码云(Gitee)的仓库与 Linux 系统关联起来
配置 Git 用户名和邮箱(如果还没配置的话)---我们在提交的过程中会出现提示
git config --global user.name "你的gitte用户名"
git config --global user.email "你的邮箱"
2. git add . 把需要提交的目录(项目)复制或剪切到本地仓库
git add . 相当于添加到临时仓库(还没提交到本地库),相当于一个 "准备区"
3. git commit -m ‘提交日志’ 修改之后的代码添加进去提交本地库
git commit -m ‘提交日志信息’ 这个日志必须写,也不要乱写。
这才是提交到了本地仓库 但是远端仓库还没没有提交,所以我们需要推送到远端仓库
4. git push 推送到远程仓库,这才是把本地仓库的代码推送至远程仓库
我们查看远端仓库发现多了一个我们提交的文件
5. 如果在远端我们的代码修改了,git status 查看 git 的状态
如果远程库和本地库没有同步的话,我们就 git pull 先把远端的拉到本地,同步一下,然后再进行 push
pull 的本质就是强制我们每一个人必须和远端仓库保持一致,不一致了就pull一下。
git pull 同步远程仓库,本地库与远程仓库保持一致
如果你本地要删除这个文件都在前面加上 git
6. git log 查看提交历史记录
四、调试器——gdb
- 在调试思路上,无论是在 Windows 还是 Linux 系统下,目的都是为了找出代码中的错误。都需要关注变量的初始化、赋值、变化过程,检查函数的参数传递是否正确、函数内部逻辑是否正确,以及循环结构是否按照预期执行(如循环次数是否正确、循环体中的逻辑是否正确等)。
- 比如,在检查一个计算数组元素总和的函数时,都需要考虑是否正确地遍历了数组,是否正确地累加了元素的值,思路都是从程序的输入、中间处理过程到输出,逐步验证代码的正确性。
调试之前我们先创建文件以及准备调试代码
test.c 文件
#include <stdio.h>
// 函数用于统计给定范围内能被divisor整除的数的个数及它们的和
void countAndSum(int start, int end, int divisor, int *count, int *sum) {
*count = 0;
*sum = 0;
int i = 0;
for (i = start; i < end; i++) { // 这里循环条件有错误,少包含了边界值end,应该是i <= end,导致可能少统计一些数
if (i % divisor == 0) {
(*count)++;
*sum += i;
}
}
}
int main() {
int start = 1;
int end = 18;
int divisor = 3;
int count;
int sum;
countAndSum(start, end, divisor, &count, &sum);
printf("在 %d 到 %d 范围内,能被 %d 整除的数有 %d 个,它们的和是 %d\n", start, end, divisor, count, sum);
return 0;
}
编译成debug版本
在学习C语言的时候我们就知道编译存在两种版本:一种是release发布版,目的:主要用于向用户发布最终产品,另一种是debug调试版,目的:专为开发人员在开发和调试阶段使用。
要用 gdb 调试,变成debug版本才可以调试,首先要进行给编译条件添加 -g,表示生成debug 版本
gdb 的使用
我们要调试,直接 gdb 可执行程序,发现我们还没有gdb,所以需要安装
gdb的安装
sudo yum install -y gdb
基本使用
① l 查看代码
l 的全称是 list
l 0 从0行显示,想再往后显示不需要输指令了,直接回车可以继续往下显示代码。
② b 打断点、info查看断点、d 删除断点
打断点:b 全称 breakPoint b 20 b 23都是打断点
查看断点: info b
去掉断点:d 全称 deletePoint d 24这样删除不了断点,删除是删除断点编号,添加断点之后就有了断点编号。
③ r 开始调试,调试起来
r 程序跑起来,在断点处停下来,r 全称run
r 相当于 Visual Studio下的 F5开始调试,VS 下 F5开始调试:有断点就在断点处停下来,没有断点就直接跑完了,到另外一个断点还是F5。
④ n 逐过程、s 逐语句
启动调试之后下一步就可以逐过程 F10,这里是 n 全称next 一行一行往下走
想进入这个函数逐语句 F11,这里是 s 全称 step,可以进入某个函数
⑤ c 运行到下一个断点处
c 全称continue运行到下一个断点 可以一直c运行到下一个断点处
⑥ bt 查看调用堆栈
s进去一个函数后, bt 查看函数的调用堆栈,出了函数后还可以继续查看调用堆栈
⑦ finish 运行完当前函数
通过这个finish可以确定这个函数是否存在错误
⑧ p 查看变量值
p 只能临时查看,我们要边看边调试变量的值。
⑨ display 常显示变量的值、undisplay取消常显示
常显示可以显示变量值和地址,也可以取消变量值和地址,但是取消的时候也是取消编号。
取消常显示
⑩ until 跳出循环
如果问题不在循环里面我们要跳出这个循环,直接until,然后继续 n 往下走
⑪ info locals 查看特定区域的临时变量
五、Linux小程序——进度条的实现
基本概念
fflush的使用
这个代码会先出现 hello world,然后休眠 2 秒。
如果我们去掉\n就没有打印内容,两秒之后内容才打印出来,这里先执行的是printf,然后执行的是sleep,因为我们的代码是顺序结构。
先执行printf不等于数据先显示,printf已经执行完了,只不过数据在缓冲区里面,没有显示出来 ,缓冲区我们不知道是什么,我们目前只需要知道它是一块内存就够了,如果我们想要这个内容刷新出来,可以使用 fflush(stdout)。而 \n 刷新的是行缓冲。
fflush(stdout)
刷新缓冲区
回车换行的理解
有一组概念回车换行,回车换行是两个概念,回车是回到当前行的开始,换行是换到下一行 \r 回车
\n 换行
\r\n 回车换行
注意:语言层面 \n 就是回车换行
倒计时程序的实现
有了这两个基础铺垫,我们就可以完成一个简单的倒计时了。
反斜杠n就是回车换行,我们不能使用回车换行,我们就用反斜杠 r
fflush(stdout)
刷新缓冲区
进度条的实现
有了这些基础之后,我们实现这个进度条就很容易上手了。
进度条一定是循环的,头文件不需要在Makefile中写,因为会在源文件中展开。
注意点:\是C语言中的特殊字符,如果我们要显示 \ 就需要\\
模上4之后范围就在0-3
我们先把文件建立好,写好之后先简单的测试一下Makefile是否正确
process.h
#pragma once
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#define NUM 101
//#define STYLE '#'
#define S_NUM 5
//函数的声明
extern void ProcessOn();
process.c
#include "process.h"
//函数的实现
void ProcessOn()
{
int cnt = 0;
//字符数组
char bar[NUM];
//进度条样式
char STYLE[S_NUM] = {'+','#','@','*','&'};
//初始化bar
memset(bar,'\0',sizeof(bar));
//旋转光标
const char* lable = "|/-\\";
while(cnt <= 100)
{
//控制格式
printf("[%-100s][%d%%][%c]\r",bar,cnt,lable[cnt%4]);
fflush(stdout);
E> bar[cnt++] = STYLE[N];
usleep(50000); //1秒 = 10000毫秒,3秒跑完
}
printf("\n");
}
main.c
#include "process.h"
int main()
{
ProcessOn();
return 0;
}
Makefile
ProcessOn:main.c process.c
gcc main.c process.c -o ProcessOn -DN=1 #-D定义宏
.PHONY:clean
clean:
rm -rf ProcessOn