Linux《进度条》

Linux《进度条》

在之前的Linux基础开发工具当中我们已经了解了vim、gcc、makefile等基本的开发工具,那么有了这些开发工具我们就可以来实现我们Linux旅程当中的第一个程序——进度条。相信通过该项目的实现能让你对vim等开发工具更加的熟悉。一起加油吧!!!


1.补充知识回车与换行 

在实现进度条的项目之前我们先要来了解关于回车和换行的基础补充知识,在了解之后才能让接下来的项目的编写更加的顺畅。

首先我们要了解的是回车和换行有什么区别,此时你可能就会疑惑这两个实现的效果不是一样的吗?我们在点击回车的时候不就是实现了换行了吗?

其实这两个实现的效果是不同的,只不过在现代的计算机当中基本将这两个概念不进行区分了,但在上个实际使用的老式打字机上换行自是将对应的纸向下移动,而要将实现回车还需要将指针移动回起始位置。

那通过以上的示例就可以理解在现代的计算机当中回车其实是 换行+回车 的,通过以下的老式键盘就可以看出这一特点

其实在之前在C语言当中使用的\n就是本质上就是实现了换行+回车的功能,\r实现的就是回车的功能。 


2.练手小程序——倒计时

以上我们了解了回车的本质,那么接下来就来通过一个倒计时的小程序来作为实现进度条项目之前的联手项目,不过在实现之前先要通过以下的代码得出一个规则。

首先来看以下的代码:

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

在以上的代码当中运行会出现什么样的现象呢?

通过运行形成可执行程序以上的代码输出的效果如下所示:

 

此时就可以看出首先会在显示器当中打印出hello world,之后在休眠5妙再让程序结束

再看以下的代码会有什么现象:

#include<stdio.h> #include <unistd.h> int main() { printf("hello world"); sleep(5); return 0; } 

以上的代码相比原来的代码只是少了一个换行符\n,此时该程序运行的结果会和之前的一样吗?

通过将该程序形成可执行程序运行之后就可以看出和之前不同,该程序一开始没有将hello world输出到显示器上,而是到程序结束的时候才打印到显示器上,那么为什么会有这样的现象呢? 


 

要解答以上的问题就需要了解C语言当中进行输出的流程是怎么样的,其实在将对应的信息输出到显示器之前是会将数据先存储到缓冲区,之后再统一的将缓冲区内的数据刷新到显示器上,因此以上的两份代码再执行完printf("hello world\n")与printf("hello world")之后都是将执行完的结果存储到缓冲区当中,那么为什么第一份代码能直接将结果显示到显示器上呢?

这其实和其实和显示器行刷新的策略有关,当使用\n之后会将缓冲区当中的数据刷新到显示器上,而第二份代码当中未使用\n就使得程序缓冲区当中的数据需要在程序结束的时候才刷新到显示器上,这也是第二份代码形成的可执行程序在最后才会显示出来。

那么在第二份代码当中不使用\n但要使其与第一份代码执行出相同的效果,要使用什么样的方式呢?

在此就需要了解一个库函数fflush

在此fflush的作用就是将缓冲区当中的数据进行强制刷新。我们知道在C语言的程序启动时默认会打开stdin、stdout、stderr三个标准输入输出流,那么此时就可以将缓冲区当中的数据强制刷新到标准输出流stdout上。

改进的代码如下所示:

#include<stdio.h> #include <unistd.h> int main() { printf("hello world"); fflush(stdout); sleep(5); return 0; } 

以上代码形成可执行程序之后输出结果如下所示:


了解了缓冲区之后接下来就来尝试编写倒计时程序的代码,在此要求是从10开是倒计时,倒计时的数字在同一行内一直进行刷新,直到最后数字未0时结束 

那么接下来就很容易的能实现出以下的代码

#include<stdio.h> #include<unistd.h> int main() { int cnt= 10; while(cnt>=0) { printf("%d\r",cnt); cnt--; fflush(stdout); sleep(1); } printf("\n"); return 0; } 

以上的代码就是使用了以上我们学习的fflush在进行每一次的printf之后就进行强制的刷新,并且在每次输出一个数字之后使用\r进行回车,这样就可以使得每一秒显示器上会出现一个数字且在同一行内输出。但是以后的代码还有一个问题,我们来通过运行看看。

通过以下的输出就可以看出问题的所在是在输出时一开始打印的10是两位数,之后再进行打印的是一位数,这就会使得打印出的结果变为了90、80……

这个问题形成的原因是由于printf在输出时默认的是右对齐,此时要修改为左对齐只需要在printf的占位符%之后加上一个-2即可,这样就可以限定最小宽度为2

修改之后的代码如下所示:

#include<stdio.h> #include<unistd.h> int main() { int cnt= 10; while(cnt>=0) { printf("%-2d\r",cnt); cnt--; fflush(stdout); sleep(1); } printf("\n"); return 0; } 

输出结果如下所示:

3.进度条项目实现

通过以上的倒计时小程序我们完成了在实现进度条项目之前的练手,那么接下来就可以开始实现我们Linux学习当中的第一个项目,在此会实现两个版本的进度条项目,其中第一个版本的代码较为简单,但是不具有实用性,第二份的代码才具有实用性。

在实现代码之前首先要来明确我们的实现的项目的要求是什么,在此实现出的效果需大致如下所示:

首先是有一个进度条进行进度的推进直到进度条满为止,在进度条之后有一个显示当前进度的百分比数,之后有一个旋转的光标来表示当前进度条是在推进的。

 

3.1 v1版本

在实现该进度条项目时分为三个文件,在process.h内进行实现函数的声明,在process.c内实现对应的函数,在main.c使用实现的函数来实现进度条

那么接下来就先来实现第一个版本的进度条,在此创建一个函数process_v1,在该函数内实现对应的代码。在该版本当中的进度条只需要每秒使得进度条向后推进指定的进度即可,并且在进度条之后的百分数也显示对应的进度值。

 但在实现process_v1内的函数代码之前我们先要将该程序对应的makefile进行构建

makefile内的内容如下所示:

BIN=process.exe SRC=$(wildcard *.c) OBJ=$(SRC:.c=.o) CC=gcc $(BIN):$(OBJ) $(CC) -o $@ $^ %.o:%.c $(CC) -c $< .PHYNO:clean clean: rm -f $(OBJ) $(BIN) 

接下来就来实现第一个版本的进度条

首先在实现进度条要从0到100,那么就创建一个大小为101的字符数组来存储对应的进度数据,使用'#'来表示进度元素,接下来使用memset函数将数组buffer内的元素都初始化为0。 

由于要在进度条的最后使用一个动态的翻转来表明进度条是在运行的,那么在此就创建一个数组tmp,该数组内存储对应的字符,进度条每增长一位就将对应的状态字符改变,

在此字符串内使用\\是因为两个\\才会被转译为\

接下来创建一个变量cnt来表示当前进度条的进度值,使用while喜欢来实现0到100进度。在此过程中每次打印完字符串、进度条百分比、状态元素之后都要回车并且进行强制刷新。 

每次打印完之后将buffer数组内对应cnt下标位置元素修改为'#',再让cnt++

注:在此每次状态元素都使用cnt%len,这样就可以使得得到的下标一直在tmp字符串的范围内

以上使用的函数usleep和sleep类似也是进行休眠,不过单位是毫秒

完整代码如下所示:

#include"process.h" //函数实现 #define MAX 101 #define STYLE '#' void process_v1() { char buffer[MAX]; memset(buffer,0,sizeof(buffer)); const char* tmp="|/-\\"; int len=strlen(tmp); int cnt=0; while(cnt<=100) { printf("[%-100s][%d%%][%c]\r",buffer,cnt,tmp[cnt%len]); fflush(stdout); usleep(50000); buffer[cnt]=STYLE; cnt++; } printf("\n"); } 

编写完process_v1的代码之后接下来就使用make命令生成对应的可执行程序process.exe


 

执行process.exe效果如下所示: 

3.2 v2版本

在上述内容中,我们已经实现了进度条的第一个版本。尽管上述进度条程序确实能够运行,但问题在于我们上面实现的进度条速度是均匀的。这在现实中几乎是不可能的情况。更多时候,进度条会呈现出速度的变化。例如,在下载文件时,由于网速的影响,网速快时进度条移动得快,网速慢时进度条移动得慢。因此,接下来实现的第二个版本的进度条将更符合实际情况。

在此创建一个process_v2来表示该版本进度条的实现

该函数的参数有两个分别为总的数据值以及当前完成任务的数据值。在该函数的内部就需要实现cur占totle总量的百分比进度条,在此数据的变化就不在该函数内实现。

首先和之前的process_v1函数类似也是先创建一个大小为101的数组buffer来存储进度条的内容,再创建一个数组tmp来存储动态变化的字符。

之后和之前实现函数不同的是使用cnt变量的值来确定对应状态数组的下标,创建一个浮点数的变量count来统计当前进度条的百分比,再使用变量sum来统计进度条内#的个数

 

完整代码如下所示:

#define MAX 101 #define STYLE '#' void process_v2(double totle,double cur) { char buffer[MAX]; memset(buffer,0,sizeof(buffer)); const char* tmp="|/-\\"; int len=strlen(tmp); static int cnt=0; double count=cur*100/totle; int sum=(int)count; int i=0; for(;i<=sum;i++) { buffer[i]=STYLE; } printf("[%-100s][%.1lf%%][%c]\r",buffer,count,tmp[cnt]); fflush(stdout); cnt++; cnt%=len; } 

实现了process_v2函数之后接下来就需要在main.c当中实现一个具体的场景,假设在此实现的是文件的下载,下载的总量是1024mb,下载的速度是0.1mb每秒,那么接下来实现的函数就如下所示:

#include"process.h" double totle=1024.0; double speed=1.0; void DownLoad() { double cur=0; while(cur<=totle) { process_v2(totle,cur); usleep(10000); cur+=speed; } } int main() { //函数使用 //process_v1(); DownLoad(); return 0; } 

执行以上代码make之后形成的可执行程序,输出结果如下所示:

 

以上虽然实现的进度条能执行下去,但是以上还是匀速的,那么此时要使得呈现出来的进度条快慢是变化就需要使用随机数。

并且还将下载总的数据量的大小由mai函数内调用DownLoad时传参。

在此之前先在process.h内引用对应的头文件

#include"process.h" void DownLoad(double totle) { srand(time(NULL)); double speed=rand()%51; double cur=0; while(cur<=totle) { process_v2(totle,cur); usleep(10000); cur+=speed; } printf("\n"); } int main() { //函数使用 //process_v1(); DownLoad(10240); return 0; } 

以上代码此时就会存在一个问题就是最终cur的值可能会无法和totle正好匹配,那么此时最后进度条就无法显示到100%,因此接下来就要对这种情况进行处理。

在此只需要按照以上的方式处理即可实现最终的进度条到100%。 

以上实现的进度条以及基本满足我们的预期了,接下来就是对以上代码进行优化,在以上代码中DownLoad和process_v2的耦合度较高,在此我们可以修改为回调函数的方式来实现函数的调用。

并且在process_v2函数当中再添加一个变量来表示当中进度进行的操作是什么。

这样就可以实现上传等其他的操作

完整代码如下所示:

process.h

#pragma once #include<stdio.h> #include<string.h> #include<unistd.h> #include<time.h> #include<stdlib.h> //函数声明 void process_v1(); void process_v2(const char* s,double totle,double cur); 


process.c

#include"process.h" //函数实现 #define MAX 101 #define STYLE '#' void process_v2(const char* s,double totle,double cur) { char buffer[MAX]; memset(buffer,0,sizeof(buffer)); const char* tmp="|/-\\"; int len=strlen(tmp); static int cnt=0; double count=cur*100/totle; int sum=(int)count; int i=0; for(;i<=sum;i++) { buffer[i]=STYLE; } printf("[%s][%-100s][%.1lf%%][%c]\r",s,buffer,count,tmp[cnt]); fflush(stdout); cnt++; cnt%=len; } void process_v1() { char buffer[MAX]; memset(buffer,0,sizeof(buffer)); const char* tmp="|/-\\"; int len=strlen(tmp); int cnt=0; while(cnt<=100) { printf("[%-100s][%d%%][%c]\r",buffer,cnt,tmp[cnt%len]); fflush(stdout); usleep(50000); buffer[cnt]=STYLE; cnt++; } printf("\n"); } 

main.c

#include"process.h" typedef void(*call_t)(const char*,double,double); void DownLoad(double totle,call_t cb) { srand(time(NULL)); double speed=rand()%51; double cur=0; while(cur<=totle) { cb("下载中:",totle,cur); if(cur>=totle)break; usleep(1000); cur+=speed; if(cur>totle)cur=totle; } printf("\n"); } void UPLoad(double totle,call_t cb) { srand(time(NULL)); double speed=rand()%51; double cur=0; while(cur<=totle) { cb("上传中:",totle,cur); if(cur>=totle)break; usleep(1000); cur+=speed; if(cur>totle)cur=totle; } printf("\n"); } int main() { //函数使用 //process_v1() printf("下载总量:%d\n",10000); DownLoad(10000,process_v2); printf("下载总量:%d\n",222222); DownLoad(222222,process_v2); printf("下载总量:%d\n",99); DownLoad(99,process_v2); printf("下载总量:%d\n",5555); DownLoad(5555,process_v2); printf("下载总量:%d\n",1); DownLoad(1,process_v2); printf("上传总量:%d\n",20000); UPLoad(20000,process_v2); return 0; } 

 

以上代码运行结果如下所示:


 

以上就是本篇的全部内容了,希望通过本篇的学习能让你对这些基础的开发工具有更深的认识

Read more

【C++深学日志】C++“类”的完全指南--从基础到实践(一)

【C++深学日志】C++“类”的完全指南--从基础到实践(一)

假想一下,你是一个顶级汽车设计师,你的任务不是亲自拧紧每一个螺丝,而是要设计出一幅“汽车蓝图”,你在图纸上设计了一辆汽车所需的一切:车轮、车灯、V8发动机、方向盘等,你手上这份设计好的蓝图就相当于我们今天要讲的C++中的“类”,它规定了汽车的属性(例如:离合器)和方法(功能:换挡),它本身并不是一辆真正的汽车,只是你的一份设计规划,后续你交付给工厂,工厂按照你的设计蓝图,生产出了一辆汽车,这就是实例化,后续工厂有根据你的蓝图设计了一条流水线,每一辆从流水线上生产下来的车辆,都是里这个蓝图(类)的一个对象,他们都有蓝图定义的属性和功能。在C++中类就充当着蓝图的作用,它定义了对象拥有哪些属性,那么就和我一起来揭开这份“蓝图”的面纱吧。 1.类 1.1.类的定义 类的基本思想是数据抽象和封装,数据抽象是一种依赖于接口和实现的分离式编程技术,类的接口包括用户所能执行的操作,类的实现则是包括类的数据成员、负责接口实现的函数以及定义类所需的各种私有函数。封装实现了类的接口和实现的分离,封装后的类隐藏了他的视线细节,也就是说,

By Ne0inhk
Qt步进电机上位机控制程序源代码:跨平台C/C++编写,支持多种端口类型与详细注释

Qt步进电机上位机控制程序源代码:跨平台C/C++编写,支持多种端口类型与详细注释

Qt步进电机上位机控制程序源代码Qt跨平台C/C++语言编写 支持串口Tcp网口Udp网络三种端口类型 提供,提供详细注释和人工讲解 1.功能介绍: 可控制步进电机的上位机程序源代码,基于Qt库,采用C/C++语言编写。 支持串口、Tcp网口、Udp网络三种端口类型,带有调试显示窗口,接收数据可实时显示。 带有配置自动保存功能,用户的配置数据会自动存储,带有超时提醒功能,如果不回复则弹框提示。 其中三个端口,采用了类的继承与派生方式编写,对外统一接口,实现多态功能,具备较强的移植性。 2.环境说明: 开发环境是Qt5.10.1,使用Qt自带的QSerialPort,使用网络的Socket编程。 源代码中包含详细注释,使用说明,设计文档等。 请将源码放到纯英文路径下再编译。 3.使用介绍: 可直接运行在可执行程序里的exe文件,操作并了解软件运行流程。 本代码产品特点: 1、尽量贴合实际应用,细节考虑周到。 2、注释完善,讲解详细,还有相关扩展知识点介绍。

By Ne0inhk
【C++】深入拆解二叉搜索树:从递归与非递归双视角,彻底掌握STL容器的基石

【C++】深入拆解二叉搜索树:从递归与非递归双视角,彻底掌握STL容器的基石

【C++】深入拆解二叉搜索树:从递归与非递归双视角,彻底掌握STL容器的基石 * 摘要 * 目录 * 一、概念 * 二、 性能分析 * 三、key结构非递归模拟实现 * 1. 二叉搜索树的插入 * 2. 二叉搜索树的查找 * 3. 二叉搜索树的删除 * 4. 二叉搜索树的中序遍历 * 四、key结构递归的模拟实现 * 1. 递归与非递归二叉搜索树核心操作的对比 * 2. 递归插入 * 3. 递归查找 * 4. 递归删除 * 总结 摘要 二叉搜索树(BST)是一种重要的数据结构,它通过"左子树所有节点值小于根节点,右子树所有节点值大于根节点"的特性实现高效的元素组织。本文详细解析了BST的核心概念、性能特点,并分别通过非递归和递归两种方式完整实现了插入、查找、删除等关键操作,深入探讨了指针引用在递归实现中的巧妙应用,以及两种实现方式在时间复杂度、空间复杂度和适用场景上的差异。 目录

By Ne0inhk
【C++】 —— 笔试刷题day_28

【C++】 —— 笔试刷题day_28

一、游游的重组偶数 题目解析 这道题,有q组数据,每一次输入一个正整数x,让我们将这个数进行重排,变成一个偶数,然后返回(如果x本身就是一个偶数那可以直接返回x); 如果不存在合法解,就是x通过重排后,无法变成一个偶数,就输出-1; 算法思路 这道题,总体来说还是比较简单的; 对于正整数x,我们可以把它当作一个字符串进行输入;(如果按照整数输入,我们还要将这个数x的每一位变换成对应数组) 我们知道,如果一个数是偶数,那最低位一定是一个偶数,这样我们只需判断字符串的最后一位即可知道这个数是否是偶数;如果这个数是偶数,那就直接输出即可;如果最后一位不是偶数,那就从第一位开始向后找,找到一位是偶数,然后把它交换到最后一位;然后输出即可;如果遍历完这个字符串,还没找到一位是偶数的,那就表示这个数x通过重拍无法变成偶数,输出-1即可。 题目解析 #include<iostream>usingnamespace std; string func(){ string str; cin >>

By Ne0inhk