前言
- 第一个Linux编程——进度条
- git的简单使用
一、第一个Linux编程——进度条
在写进度条之前我们需要两个基础知识:
- 回车换行
- 缓冲区
1. 回车换行
- 首先我们需要知道回车换行它是两个概念,回车是回车,换行是换行
- 换行:光标从上往下,直接到下一行(例如光标现在在当前行的第5个位置,换行之后光标直接挪到下一行的第5个位置,并没有在下一行的最左侧)
回车:把光标挪到当前行的最开始(最左侧),还在当前行- 换行一般不单独使用,一般换行回车一起用,所以在C语言中用\n表示换行(注意:\n是换行+回车)
- 回车,我们用转义字符\r表示
2. 行缓冲区
(1)引入行缓冲区
在讲解之间我们先来学习一个系统函数——sleep
- 我们通过man手册,查看sleep的使用
- 调用sleep,休眠指定的秒数
现在我们来看下面的两个代码:
两段代码唯一的区别就是代码1打印完之后换行,代码2打印完之后不换行,如下图:
分别编译运行这两段代码,运行结果是怎么样的呢?
- 代码1:先打印hello linux!和换行,随后休眠2秒,结束运行
- 代码2:先休眠2秒,随后打印hello linux!,结束运行
问题:我们学过C语言的都知道,C代码一定是按照顺序从上往下执行的,无论是代码1还是代码2都应该是先执行A处再执行B处,但是为什么代码2的运行效果好像是先执行了B处的代码,随后才执行的A处?
从C语言的执行顺序,我们知道一定是先执行printf,随后再执行sleep
在sleep的时候,printf已经执行了,只不过信息没有打印出来
那在我sleep的时候,信息“hello linux!”在哪里?——他一定是被保存起来了!——保存在缓冲区
缓冲区就是由C语言维护的一段内存!
缓冲区的刷新策略:行刷新
- 行刷新:缓冲区他要看你输出的字符串是不是一个完整行
- 如果是一个完整行,缓冲区就立刻将存在缓冲区的内容刷新出来
- 如果不是一个完整行,缓冲区就不刷新,直到程序退出或者遇到换行符才将存在缓冲区的内容刷新出来
- 完整行的标准:是否有‘\n’换行符
了解了缓冲区和缓冲区的刷新策略,现在我们就知道为什么代码2会先休眠再输出了,因为代码2没有‘\n’换行符,不是一个完整行,所以直到程序退出才从缓冲区刷新出来
(2)怎样强制刷新缓冲区
我们先来认识两个知识点:
- C程序默认会帮我们打开三个输入输出流,分别叫做标准输入、标准输出、标准错误。今天我们只考虑标准输出。
C语言默认会帮我们打开三个输入输出文件,输出文件就是显示器,显示器文件对应C语言的stdout。
#include<stdio.h>
extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;
- 使用fflush刷新文件流,即使缓冲区不是一个完整行,也可以使用fflush强制将缓冲区中的内容马上刷新出来
(3)倒计时
在实现进度条之前,我们可以先来实现一个简易的倒计时程序。
实现倒计时需要注意以下几个问题:
- 倒计时需要将之前的数字覆盖掉
- 光标不能向后移动——》解决方案:回车‘\r’
- 光标不能换行且要立马将缓冲区数据刷新出来——》fflush
- 不想让最后的结果被bash覆盖——》在程序的最后换行
- 从大到小倒计时,随着倒计时的运行会导致不能完全覆盖之前的数字,例如9覆盖10,反而变成90——》-numd%:左对齐num个字符
#include<stdio.h>
#include<unistd.h>
int main()
{
int cnt = 10;
while(cnt >= 0)
{
printf("%-2d\r", cnt);
fflush(stdout);
sleep(1);
cnt--;
}
printf("\n");
//printf("hello linux!"); //A
//fflush(stdout); //强制刷新文件流,将缓冲区中的内容马上刷新出来
// sleep(2); //B
return 0;
}
3. 进度条
(1)准备工作
- 首先mkdir processbar创建processbar目录,用于保存进度条代码
- 在processbar目录下,touch processbar.c processbar.h main.c三个文件,分别用于进度条的实现、进度条的声明、进度条的调用
- 再touch Makefile,用于项目的自动化构建
(2)实现简单的进度条样式
- 编写Makefile文件,通过make命令生成可执行程序和清理项目
processbar:processBar.c main.c # 可执行目标文件依赖多个源文件的话,使用空格分隔即可
gcc $^ -o $@
.PHONY:clean
clean:
rm -f processbar
- processbar.h:进度条声明
//防止头文件被重复包含
#pragma once
//声明常用的头文件
#include<stdio.h>
#include<string.h>
#include<unistd.h>
//声明函数
extern void processbar(int speed);
//进度条的长度是一个常量,所以我们将其定义为一个宏
#define NUM 101//进度条长度为100,但因为C字符串以'\0'结尾,所以将其定义为101
//进度条的主体字符格式也是一个常量,所以我们也将其定义为一个宏
#define BODY '='
//进度条推进是右边有一个字符'>'表示在推进
#define RIGHT '>'
- processbar.c:进度条样式的实现
1、如何实现进度条推进的效果
答:循环——通过每一次多输出一个字符,并将前一次输出的字符覆盖,这样不断循环,从左向右依次增加,实现进度条推进的效果
2、进度条的样式
- 预留100的长度,并左对齐——%-100s
- 显示进度条推进的进度,即百分比(注意‘%’是一个特殊字符,一般通过两个‘%%’才能输出)
- 进度条工作时字符’|'顺时针旋转表示在工作
#include"processBar.h"
//进度条工作时字符'|'顺时针旋转表示在工作
const char* lable = "|/-\\"; //'\'是特殊字符
//实现进度条推进的样式,参数speed—由调用者决定每一次推进的速度
void processbar(int speed)
{
//定义进度条
char bar[NUM];
//初始化进度条
memset(bar, '\0', sizeof(bar));
int len = strlen(lable);
//循环:通过每一次多输出一个字符,并将前一次输出的字符覆盖,这样不断循环实现进度条推进的效果
//进度条长度为NUM-1,从长度为0开始循环,需要循环NUM次—》[0,NUM-1]
int cnt = 0;//计数器
while(cnt <= NUM - 1)
{
//这里我们不使用'\n',使用'\r'回车将前一次的进度条覆盖掉
printf("[%-100s][%d%%][%c]\r", bar, cnt, lable[cnt%len]);
//没有'\n',缓冲区就没有立即刷新,因为显示器默认行刷新
//所以这里我们使用fflush函数立马把缓冲区的内容刷新出来
fflush(stdout);
//每一次输出休眠0.1秒——usleep
usleep(speed);
//迭代:每一次比上一次多一个字符
bar[cnt++] = BODY;
//注意:临界情况,即当cnt=NUM-1时就不能再执行了
if(cnt < NUM - 1)
{
bar[cnt] = RIGHT;
}
}
//进度条打印完之后,为了不让bash命令行把我们的进度条覆盖掉,所以这里换行
printf("\n");
}
- main.c:进度条样式的调用(测试)
#include"processBar.h"
int main()
{
//每一次推进,休眠0.1秒
processbar(100000);
return 0;
}
(3)进度条调用的场景
- 在实际中进度条的进度不应该有我们循环模拟实现,而是由调用者决定的
- 在实际中进度条调用时,调用者进行着某种下载任务,每下载1%就调用进度条打印一次进度,不断重复下载,直到下载任务完成
- processbar.c:进度条样式的实现
//进度条工作时字符'|'顺时针旋转表示在工作
const char* lable = "|/-\\"; //'\'是特殊字符
//定义进度条
char bar[NUM];
//参数rate为进度条的进度
//processbar每一次调用进度+1
void processbar(int rate)
{
//当进度条小于0和大于100时,直接退出
if(rate < 0 || rate > 100) return;
int len = strlen(lable);
printf("[%-100s][%d%%][%c]\r", bar, rate, lable[rate%len]);
fflush(stdout);
bar[rate++] = BODY;
//注意:临界情况,即当rate=NUM-1时就不能再执行了
if(rate < NUM - 1) bar[rate] = RIGHT;
}
//下载完毕之后调用initbar初始化进度条
void initbar()
{
memset(bar,'\0',sizeof(bar));
}
- main.c:进度条样式的调用(测试)
#include"processBar.h"
typedef void (*callback_t)(int);//函数指针类型
//模拟一种安装或下载
void downLoad(callback_t cb)
{
int total = 1000;//需要下载1000MB
int curr = 0;//一开始还没下载为0MB
//当curr>total即下载完成
while(curr <= total)
{
//进行某种下载任务
usleep(10000);//模拟下载的时间
int rate = curr*100/total;//更新进度
cb(rate);//通过回调,展示进度
curr += 10;//迭代:循环一次下载10M
}
printf("\n");
}
int main()
{
downLoad(processbar);
initbar();
downLoad(processbar);
initbar();
downLoad(processbar);
initbar();
downLoad(processbar);
initbar();
return 0;
}
二、简单使用git
1. git是什么
- git是一个版本控制器
- git的核心工作主要是两个:
- 使用git来进行版本管理
- 使用git多人协作开发
- git是client和Server一体的(例如:本地仓库可以传到远端,远端仓库也可以拉取到本地)
- git是一个开源的软件,基于git搞了一些商业版的网站——github&&gitee
- 理解什么是版本管理?
例如:在写毕业论文的时候,我们需要不断的修改,在修改的时候就会产生不同的版本,这个时候我们需要将其保存在同一个文件夹下,也可将这个文件夹称作仓库。这个仓库就是做毕业论文的版本管理的!
- 理解git
我们怕电脑中的本地仓库被我们弄丢,所以我们可以将其传到远程仓库git中!
2. git的简单使用
在这里我们只学习git的简单使用,把本地仓库的代码传上远端仓库即可
(1)新建仓库
在菜单栏右侧有一个‘+’,新建仓库:
现阶段我们创建的仓库,填写好仓库名和仓库介绍即可
- tip:.gitignore文件是筛选从本地上传到远端仓库的文件的,只要在.gitignore文件有的文件就不能上传到远端仓库!
(2)将远端仓库拉取到本地
- 先将仓库的url复制一份:
- 将远端仓库拉取到本地:
注意:在拉取之前先检测Linux是否下载了git
git --help#检测git是否安装好,简写为--h也可以
git --version#也可检查是否安装好
出现如下图所示,即安装好了
如果没有安装,粘贴如下命令安装:
sudo yum install git
将远端仓库拉取到本地,指令如下:
git clone [url]#这里的url就是刚在远端仓库复制的url
(3)本地仓库
- 我们需要把本地仓库的代码上传到远端仓库,那本地仓库在哪里呢?
本地仓库 .git 在我们拉取到本地的目录中,注意它隐藏的
- 为什么我们的本地仓库是隐藏的——就是因为它不想让我们对其修改
(4)三板斧第一招:git add
- 先把我们要上传的文件拷贝到刚才拉取的克隆本地当中
- 注意:这个文件虽然拷贝到了本地当中,但是它还没有被仓库管理起来
- 所以git add第一板斧将新拷贝到本地的文件添加到本地仓库
git add [文件名]
(5)三板斧第二招:git commit
- 其实git add只是将其添加到仓库的暂存区里了,还需要git commit第二板斧将其真正提交到本地仓库中!
- git commit提交的时候还应该注明提交日志,描述改动的详细内容
- 注意:提交日志不能乱写,它是给我们查看的——即通过日志了解提交的文件具体做了什么事
git commit -m "提交日志信息"
- 首次使用git commit的时候,可能会报错,因为Git需要知道你的用户名和电子邮件地址,以便在每次提交时记录这些信息,但你还没有设置全局的用户名和邮箱。(其本质是为了对代码进行溯源)
- 完成设置后,你应该能够成功地进行Git提交了。再次尝试运行你的提交命令
tip:如果这里设置的邮箱与你git上的邮箱不一致,可能会导致在Gitee没有提交的小绿点。
(6)三板斧第三招:git push
- 将本地仓库同步到远端仓库(即远端服务器)上
git push
- push时需要填写用户名和密码
- 同步成功后,刷新Gitee就可以看到你提交的文件了
(7)补充:git log&&git status
- git log:查看历史提交记录
- git status:查看提交状态
到这里我们就学完了git的基本使用