四,基础开发工具(下)
- 4.5自动构建make/Makefile
- 4.5.1基本使用
- 1示例
- 2进一步解释
- 3实践
- 4最佳实践
- 4.6练习:进度条
- 4.6.1倒计时
- 4.6.2进度条version1
- 4.6.2进度条version2
- 4.7版本控制器Git
- 4.7.1git操作
- 1操作一次,以后不愁
- 2经典"三件套"
- 3常用
- 4版本回退
- 4.7.2小结
4.5自动构建make/Makefile
make是⼀条命令,Makefile是⼀个文件,两个搭配使用,完成项目自动化构建。
makefile带来的好处就是⸺“自动化编译”,⼀旦写好,只需要⼀个make命令,整个⼯程完全自动编译,极⼤的提高了软件开发的效率。
make是⼀个命令⼯具,是⼀个解释makefile中指令的命令⼯具,⼀般来说,⼤多数的IDE都有这个命令,⽐如:Delphi的make,Visual C++的nmake,Linux下GNU的make。
make命令和makefile文件——依赖关系和依赖方法
4.5.1基本使用
1示例
简述:创建Makefile/makefile文件,写入依赖关系和依赖方法,通过make命令执行。
touch test.c
touch Makefile//test.c
#include<stdio.h>
int main()
{printf("hello world!\n");return 0;
}//Makefile
test.exe:test.c //依赖关系,test.exe依赖test.cgcc -o test.exe test.c //依赖方法,test.c形成test.exemake
./test.c
2进一步解释
//Makefile
test.exe:test.cgcc -o test.exe test.c
test.exe是目标文件
test.c等是依赖文件列表
依赖关系,test.exe依赖test.c
依赖方法,test.c形成test.exe
二者共同构建可执行目标文件的语义。
形成可执行文件的过程:
//Makefile
test.exe:test.ogcc test.o -o test.exe
test.o:test.sgcc -c test.s -o test.o
test.s:test.igcc -S test.i -o test.s
test.i:test.cgcc -E test.c -o test.i//执行make后
gcc -E test.c -o test.i
gcc -S test.i -o test.s
gcc -c test.s -o test.o
gcc test.o -o test.exe
make会解析Makefile文件,形成推导依赖关系的推导栈(依赖方法的集合)
依赖方法不存在就入栈,推导成功就执行对应命令出栈
(其实实现过程类似于函数递归的过程)
3实践
//构建
test.exe:test.c@gcc -o test.exe test.c//加上@会禁止命令回显//清理
.PHONY:clean
clean:@rm -f test.exe test.i test.s test.o
PHONY:假的,伪造的
.PHONY:
用来修饰目标文件是一个伪目标,.PHONY:
修饰的伪目标总是被执行的。
总是被执行的原理是什么?为什么gcc无法二次编译旧代码?
首先我们知道可执行文件是文件,拥有修改时间的属性,而源文件是否需要被重新编译,需要判断源文件修改时间和可执行文件的修改时间谁更新,更接近当前时间;而**.PHONY:
可以让gcc或者对应的命令,忽略MOD时间对比新旧。**
关于文件时间:
stat 文件名
(简记ACM时间)
access:文件访问时间
modify:文件内容更改时间
change:文件属性更改时间
了解:
更改文件内容不仅会更新文件内容的时间,还会更新文件属性的时间,因为Mod时间也属于文件属性。
在对文件进行访问时,并不是每次都会更新access时间。原因是:查看文件会更新时间,接着更新文件属性,然后过程会刷新到磁盘(磁盘属于外设,效率比较低),如果每当文件被访问时都会更新时间进行以上过程,会导致OS整体的效率低下。所以为了提高效率会设置为当访问次数达到一定次数时(10,20次等)才会更新时间。
结论
1,依赖关系必须存在,依赖文件列表可以为空
2,依赖方法可以是任何shell命令
3,clean目标,只是利用make的自动推导能力,执行了rm命令,在构建工程的视角,拦起来就是清理项目,即删除不需要的临时建立的文件。
4,make命令,后面可以跟目标名,之后解析推导对应的依赖关系和依赖方法,
默认情况下make只会推导一条完整的推导链路,默认只会推导第一个依赖关系对应的推导链。
5,源文件发生更改才会重新编译!
4最佳实践
//其实类似于宏替换
BIN=test.exe
SRC=test.c$(BIN):$(SRC)@echo "编译开始"@gcc -o $@ $^ @echo "编译完成"
.PHONY:clean
clean:@echo "清理开始..."@rm -f $(BIN)@echo "清理完成..."
问题:如果有多个文件,怎么高效编译?
touch src{1..100}.c
该命令意义是创建100个.c后缀文件命名为src1.c、src14.c等
rm src{1..100}.c
删除
BIN=test.exe
//SRC=$(shell ls *.c)
SRC=$(wildcard *c) //wildcard函数获取当前目录下的源文件
OBJ=$(SRC:.c=.o) //.c文件全部替换为.o文件
CC=gcc //编译器
Echo=echo
Rm=rm -f$(BIN):$(OBJ) //.o文件链接形成可执行文件@$(CC) -o $@ $^
%.o:%.c@$(CC) -c $<.PHONY:clean
clean:@$(Rm) $(OBJ) $(BIN).PHONY:test
test:@$(Echo) "------"@$(Echo) $(SRC) @$(Echo) "------"@$(Echo) $(OBJ) @$(Echo) "------"
至此我们就完成了基本可以通用的Makefile文件。
4.6练习:进度条
4.6.1倒计时
回车和换行是两个不同的操作。
回车\r
换行\n
printf("hello world\r\n");
sleep(2);
//现象:先打印后休眠printf("hello world");
sleep(2);
//现象:先休眠后打印
在sleep期间,print函数已经执行完了,hello world此时被缓存到内存空间中了,没有打印到显示器,等到程序结束时,会自动刷新。刷新方式,以行为方式刷新,\r\n或者\n。
到此我们就可以实现一个简单的倒计时
#include<stdio.h>#define COUNT 15int main()
{int cnt = COUNT;while(cnt >= 0){printf("%-2d\r",cnt);fflush(stdout); sleep(1);--cnt;}printf("\n");return 0;
}
4.6.2进度条version1
首先展示一下最后的效果图:
实现的主要步骤与刚才练习的倒计时是相似的,主要是回车以及刷新缓冲区的操作。
cat progress.h
#pragma once
#include<stdio.h>
void progress_v1();
cat progress.c
#include"progress.h"
#include<string.h>
#include<unistd.h>#define NUM 101
#define STYLE '-'
const char *lable="|/-\\";void progress_v1()
{char buffer[NUM];memset(buffer,0,sizeof(buffer));int cnt = 0;while(cnt <= 100){int len = strlen(lable);printf("[%-100s][%d%%][%c]\r",buffer,cnt,lable[cnt%len]);fflush(stdout);buffer[cnt] = STYLE;++cnt;usleep(500000);}
}
cat main.c
#include"progress.h"int main()
{progress_v1();return 0;
}
4.6.2进度条version2
cat progress.h
#include<stdio.h>
#include<string.h>
#include<unistd.h>void progress(double total, double current);
cat progress.c
#include"progress.h"#define NUM 101
#define STYLE '-'void progress(double total, double current)
{if(current > total)current = total;char buffer[NUM];memset(buffer,0,sizeof(buffer));const char *lable = "|/-\\";int len = strlen(lable);static int index = 0;double rate = current / total * 100;int num = (int)(rate);int i = 0;for(; i < num; i++){buffer[i] = STYLE;}printf("[%-100s][%.1lf%%][%c]\r", buffer, rate, lable[index++]);index %= len;fflush(stdout);if(current >= total){//printf("\n");printf("[%-100s][%.1lf%%][%c]\r", buffer, 100.0, lable[index++]);}
}
cat main.c
#include"progress.h"double total = 800.0;
double speed = 3.4;void DownLoad()
{double current = 0;while(current <= total){progress(total, current);usleep(3000);current += speed;}// 循环结束后,确保显示100%progress(total, total);printf("\ndownload %.2lfMB Done\n", current);
}int main()
{DownLoad();//progress(total, current);return 0;
}
4.7版本控制器Git
Git可以控制电脑上所有格式的文件,例如doc、excel、dwg、dgn、rvt等等。对于我们开发人员来说,Git最重要的就是可以帮助我们管理软件开发项目中的源代码文件!
4.7.1git操作
1操作一次,以后不愁
安装:yum install -y git
查看版本:git --version
初始化本地仓库:git init
注意:千万不要在一个git仓库里面再执行 git init
,这会创建嵌套的仓库,会导致各种问题。
配置本地仓库:name和email
git config user.name “用户名”
git config user.email “邮箱名”
免密码push:执行git config --global credential.helper store
之后进行一次push操作,输入用户名和密码即可
查看配置项:git config -l
删除配置项:git config --unset user.name
所有的git仓库设置git config --global user.name "用户名"
2经典"三件套"
添加指定文件:git add 文件名
添加所有文件到暂存区:git add .
提交本地:git commit -m "提交日志"
同步远端:git push
3常用
查看历史提交记录:git log
git log --pretty=oneline
查看git状态:git status
远端同步到本地:git pull
git pull的本质:提交自己的代码之前,必须把别人历史提交的代码同步到本地。
克隆现有的远程仓库到本地:git clone 远程仓库URL
查看远程仓库名:git remote
查看远程仓库详细信息:git remote -v
git diff
命令用于比较 Git 管理的代码在不同版本、不同区域之间的差异。它会以行-by-行的形式告诉你哪些内容被添加(+)、删减(-)或修改了。
4版本回退
工作区 | 暂存区 | 版本库 | 指令 | |
---|---|---|---|---|
是否回退 | 否 | 否 | 是 | git reset --soft |
是否回退 | 否 | 是 | 是 | git reset --mixed(默认选项) |
是否回退 | 是 | 是 | 是 | git reset --hard(谨慎使用) |
原因:hard选项会回退所有版本包括工作区未提交的代码,所以需要谨慎使用!!
将工作区回退到最近一次add的版本:git checkout -- 文件名
工作区 | 暂存区 | 版本库 | 解决方式 |
---|---|---|---|
xxx code | 1,手动撤销(麻烦易出错)2,git checkout – file_name | ||
xxx code | xxx code | ||
xxx code | xxx code | xxx code |
4.7.2小结
1,git进行版本控制时,通过同步记录"变化"信息来进行版本控制,可以减少空间的消耗。
2,git是一个去中心化的、分布式的版本控制器,但是使用时还是以中心化的模式为主。