技术标签: 运维 linux 服务器 # Linux基础入门篇
今天,就让我们利用前面所学习的知识,在Linux上写一个小程序,来检验一下自己掌握的程度
【成品展示】
在写进度条之间呢,我们要理清两个概念,首先来看看什么是
\r\n
在C语言中呢,有很多的字符,大致分为【可显字符】和【控制字符】两大类
在我们日常写代码,写文章的过程中,写完一行后若是没有自动换行就需要敲下键盘中的Enter
键来达到换行的效果。可是对于这个按键,实际上它在计算机内部做了两件事 —— 【换行】+【回车】
\n
—— 新起一行,光标位于行末【换行】\r
—— 回到当前文本行的首部【回车】在我们使用的键盘中,看到的Enter
回车键,莫过于下面这两种,第一种出现在台式机多一些,第二种出现在笔记本多一些。不过就这么看来,还是这种老式键盘符合【换行】+【回车】的这么一个概念,也就是新起一行,然后在回到当前行的行首
\n
就可以起到【换行】+【回车】的功能呢,其实这是语言本身的范畴所决定的,在C语言中便自动解释成了这个意思,不过在其他地方可能只能起到【换行】的功能,\r
需要我们手动加上\r\n
了解了
\r\n
的概念之后,我们继续来谈谈【缓冲区】的概念
在这之前先普及两个Linux下的库函数
<unistd.h>
中,我们通过【man 3 sleep】来进行查看int fflush(FILE *stream);
stdin
】、【标准输入stdout
】、【标准错误stderror
】,一般用来刷新输出流即stdout接下去我们通过五段代码来逐步理解行缓冲的概念
代码1
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("hello linux!\n");
sleep(3);
return 0;
}
现象观察
sleep()
函数,相当于在打印输出完之后让程序睡上3秒,然后才会显示【命令提示符】代码2
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("hello linux!");
sleep(3);
return 0;
}
现象观察
\n
给去除了,可以看出,我们要输出的【hello linux】并没有在第一时间打印,而是在睡眠3秒后和【命令提示符】一同进行打印,这是为何呢?代码3
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("hello Makefile!");
fflush(stdout);
sleep(3);
return 0;
}
现象观察
fflush()
这个函数,将其放在sleep()
函数之前,也就相当于是优先刷新了一下缓冲流,此时就可以看到【hello linux】立马先被打印了出来,等上3秒后才显示的【命令提示符】代码4
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("hello linux!\r");
sleep(3);
return 0;
}
现象观察
\r
,当执行程序后便开始睡眠, 然后在3秒睡眠后便直接打印出了【命令提示符】,这是为何呢?我们原本要打印的数据去哪里了呢?代码5
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("hello linux!\r");
fflush(stdout);
sleep(3);
return 0;
}
现象观察
fflush()
刷新流,我们提前显示了一下需要打印的数据,此时就可以看得很清楚,其实我们原本要打印的数据是在的,只是被【命令提示符】覆盖了而已,因为这个光标回到了行首通过观察上面5个代码段以及它们所产生的现象,我们可以提出这样的问题
\n
时为何会先睡眠再打印?\n
后数据会立马先显示出来,睡眠后才显示提示符?\r
后为什么看不到我们要的数据?刷新一下就有了呢?接下去我们就正式地来谈谈【缓冲区】的概念。文字居多、都是概念,还望理解
当我们编写完代码的时候,要将数据进行输出,此时在内存中会为其预留了一块空间,用来缓冲输入或输出的数据,这个保留的空间被称为缓冲区
fflush()
函数当不加换行符\n
时为何会先睡眠再打印?
sleep()
函数的缘故,导致这个缓冲区没有被刷新而已,所以它并没有丢失为何带上\n
后数据会立马先显示出来,睡眠后才显示提示符?
\n
,数据都会被保存在缓冲区里。缓冲区有很多自己的刷新策略【往显示器上打印就是行缓冲】,只要碰到了换行符,就会把换行符之前的所有内容全部显示出来在加上回车\r
后为什么看不到我们要的数据?刷新一下就有了呢?
上面我们有谈到\r
与\n
的特点,知道了对于前者来说会回到当前行的行首,那是什么回到行首呢?通过观察动图可以发现是光标。当我们在输入一个字符时,光标就会后移,也就会移动到下一个要输入字符的位置。因此在我们什么都不加的时候便会顺着打印【命令提示符】
那其实就可以说得通了,因为\r
的原因,光标回到了行首,因此在3秒的等待后shell输出了【命令提示符】,便覆盖了我们原本所想要输出的内容
好,看完行缓冲之后,也解答了遗留下来的疑难问题,我们来对其进行一个总结
slepp()
函数导致缓冲区延迟刷新,我们一时就看不到想要输出的内容。可以等待系统睡眠后自动刷新,也可以手动使用fflush()
自动刷新通过前面学习的有关
\r\n
以及缓冲区的知识,相信解开了你一直以来的很多困惑,现在我们先利用前面所学的一些知识,去实现一个数字倒计时的功能
sleep()
函数睡眠一秒 1 #include <stdio.h>
2 #include <unistd.h>
3
4 int main(void)
5 {
6 int i = 9;
7 while(i >= 0)
8 {
9 printf("%d\n", i);
10 sleep(1);
11 i--;
12 }
13 return 0;
14 }
\r
回车 ——> printf("%d\r", i);
slepp()
睡眠前将缓冲区刷新一下即可,保证在倒计时完不被命令行覆盖可以在最后加个换行再加个码,若是我从10开始倒计时,该怎么修改程序呢?你可试着先自己写写看
i
修改一下从10开始的话,就会出现下面这样的情况,只有前面的数字会被覆盖,10后面的这个0会始终被保留下来,最后倒计时结束后便成了00
那要如何去修改呢?关于这点我要先普及一下有关显示器打印的原理
printf("%d\n", 123);
就是打印了一下123这个整数,但是在计算机内部进行处理后显示在屏幕上可不是这样,因为所有显示在屏幕上的内容都是字符1
、2
、3
这三个字符,进行显示,只是看上去像是数字一样。在计算机内部,会将你输入的一些整型数字首先转换为字符串的形式,然后去遍历这个字符串,用putc()
这个函数将字符一一地打印在显示器上所以我们一般把【键盘】【显示器】这些称做为字符设备
知道了显示器字符打印的原理,接下去我们就可以对上面的代码做一个修改
10
就算是有2个字符,所以我们每次在打印只需要预留出2个字符的位置就可以了,这样第一次打印10
刚好占满两个字符的空间,后面在打印9 8 7...
的时候虽然只有一个字符,但是还有一个预留的空间,所以就会把上一次打印的内容进行一个覆盖printf("%2d\r", i); //C语言中的格式化占位符
好,终于来到了我们心心念念的【进度条】了,有关我为什么要将前面的这些知识做铺垫,你看完本模块就知道了
proc.h
proc.c
main.c
vim
】来进行多文件编写如下图所示make/Makefile
】进行自动化构建.c
的文件一起进行编译proc.h
吗?为何不进行一起编译呢?gcc
】的时候也有提到过,我们在进行多文件编译时候是不需要考虑【头文件】的,因为在预处理阶段头文件就会在它被包含的.c
源文件中进行展开,因此我们加了和没加一样,你想加的话也可以加上,不会有问题,不过一般我们是不加的准备工作做好之后,我们来看看需要实现的进度条需要是一个什么样子
[ ]
进行囊括,中间用==>
这样的符号进行一个慢慢地推进50%
[|][/][-][\]
这就是进度条的大致形状,要先有个数 [=======================>][100%][/]
首先要实现的是主体进度条的推进时间,先简单地实现一下这个进度条不断变长的过程(๑•̀ㅂ•́)و✧
下面是详细介绍,想直接看整体代码的可以拉到最后面
0% ~ 100%
的进度条扩展,因此就需要一个长度为101的字符数组,这里首先使用#define
进行一个宏定义#define SIZE 101
memset
,为什么要使用它呢?因为我相当于是使用一个循环的方式,每次都去修改这个字符数组当前位置上的字符,将其变为=
,然后每次去打印的时候下一个位置就会多出来一个=
,此时在视觉上看其实就相当于是一个进度条在慢慢推进的样子,不过在最后不要忘记带上sleep()
这个睡眠函数,让进度条每过一秒钟向后前进一个单位memset(bar, '\0', sizeof(bar));
//初始化整个bar字符串均为\0,sizeof(数组名)表示取到这个数组的字节大小
\0
,因此我们每次在添加完当前位置的=
后,还要在其后面添加上一个\0
才行,这会显得很麻烦,于是我就想到了在一开始就将整个字符串的内容初始化为\0
,对于sizeof(bar)
来说也就是sizeof(数组名),可以取到这个数组的整个字节大小,若是不懂的可以看看C语言数组章节下面是代码
1 #include "proc.h"
2
3 #define SIZE 101
4
5 void progress()
6 {
7 char bar[SIZE];
8 memset(bar, '\0', sizeof(bar));
9 //初始化整个bar字符串均为\0,sizeof(数组名)表示取到这个数组的字节大小
10
11 int i = 0;
12 while(i <= 100)
13 {
14 printf("[%s]\n", bar);
15 bar[i] = '=';
16 i++;
17 sleep(1);
18 }
19 }
然后来看一下上面这段代码的演示过程
\n
去掉,让光标每次回到行首,再打印这个字符串printf("[%s]\r", bar);
不过可以看到,并没有显示出任何东西,此时相信你一定可以回答出来了,那就是:缓冲区没刷新!
fflush(stdout);
sleep(1)
每次睡眠一秒,使得进度条一个不断推进的效果不过可以观察到这个进度条推进得很慢,那有没有办法使它快一点呢
usleep()
,我们可以通过【man】命令来查看一下它对应的手册sleep()
函数进行对比可以发现它的单位不是【秒】,而是【微秒】,对应单位转换也就是106秒
//sleep(1); 1秒
// usleep(1000000); 1秒
usleep(100000); //0.1秒
printf("[%100s]\r", bar);
-
printf("[%-100s]\r", bar);
0% ~ 100%
1 #include "proc.h"
2
3 #define SIZE 51
4
5 void progress()
6 {
7 char bar[SIZE];
8 memset(bar, '\0', sizeof(bar));
9 //初始化整个bar字符串均为\0,sizeof(数组名)表示取到这个数组的字节大小
10
11 int i = 0;
12 while(i <= 50)
13 {
14 printf("[%-50s]\r", bar);
15 bar[i++] = '=';
16 fflush(stdout);
17 // usleep(1000000); 1秒
18 usleep(100000); //0.1秒
19 }
20 printf("\n");
21 }
>
。重新定义一下#define STYLE '='
#define ARR '>'
99%
之前最后一个字符呈现【>】,而最后到100%
为【=】,我们此处就需要再循环内部做一个判断若是其i != 50
,就一直追加【>】,最后到达100%
时便为【=】#define SIZE 52
if(i != 50) bar[i] = ARR;
此时来看的话我们进度条的主体部分就做完了
接下去我们来实现一下百分比递增这块
i
即可,但是我们修改了进度条的长度,所以需要使用i * 2
,也可以实现一个0% ~ 100%
的过程printf("[%-50s][%d]\r", bar, i * 2);
.bat
中的批处理程序,此时我们需要写上【%%】才算是一个【%】,你也可以理解为我们在写文件路径时的【\】当做【\】,防止出现转义字符的歧义
printf("[%-50s][%d%%]\r", bar, i * 2);
接下去来实现最后的一个旋转字符的轮回,已到达图形化界面中的缓冲功能
const
是防止里面的内容被修改。而最后一个\\
也就是我上面说到的【转义字符歧义】,其实它就是【\】const char* label = "|/-\\";
%
的操作,每次打印的都是【0 ~ 3】的倍数printf("[%-50s][%d%%][%c]\r", bar, i * 2, label[i % 4]);
最后,就实现了一个完整的进度条
void processBar(int speed)
usleep(speed);
#define TOP 100
#define BODY '='
#define RIGHT '>'
最后,再将我们上面所写的代码展示一下
processBar.h
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <string.h>
4
5 #define TOP 100
6 #define BODY '='
7 #define RIGHT '>'
8
9 extern void processBar(int speed);
processBar.c
1 #include "processBar.h"
2
3 const char* label = "|/-\\";
4
5 void processBar(int speed)
6 {
7 char bar[TOP];
8 int len = strlen(label);
9 int cnt = 0;
10
11 memset(bar, '\0', sizeof(bar));
12 //初始化整个bar字符串均为\0,sizeof(数组名)表示取到这个数组的字节大小
13 while(cnt <= 100)
14 {
15 printf("[%-100s][%d%%][%c]\r", bar, cnt, label[cnt % len]);
16 fflush(stdout); // 刷新缓冲区
17
18 bar[cnt++] = BODY;
19 if(cnt < 100) bar[cnt] = RIGHT;
20 usleep(speed);
21 }
22 printf("\n");
23 }
main.c
1 #include "processBar.h"
2
3 int main(void)
4 {
5 processBar(100000);
6 return 0;
7 }
好,接下去我们做一些进阶的训练,主要对一些基础好的同学,如果看不懂只掌握上面的即可
1 #include "processBar.h"
2
3 const char* label = "|/-\\";
4 char bar[TOP];
5
6 // 单次的进度推进
7 void processBar(int rate)
8 {
9 if(rate < 0 || rate > 100) return;
10
11 int len = strlen(label);
12 printf("[%-100s][%d%%][%c]\r", bar, rate, label[rate % len]);
13 fflush(stdout); // 刷新缓冲区
14 bar[rate++] = BODY;
15 if(rate < 100) bar[rate] = RIGHT;
16 }
rate
,那随着进度的推进,在这一块我们一般会去执行一些下载任务,不过现在没有真实的业务场景,让cur++
即可 1 #include "processBar.h"
2
3 int main(void)
4 {
5 int total = 1000;
6 int curr = 0;
7
8 while(curr <= total)
9 {
10 processBar(curr * 100 / total);
11 // 进行某种下载任务
12 curr += 10;
13 usleep(100000);
14 }
15 printf("\n");
16 return 0;
17 }
好,接下去我们再做一个提升,展现一点真实的业务场景
typedef void (callback_t)(int rate);
void downLoad(callback_t cb)
6 // 模拟一种安装或下载的场景(回调函数)
7 void downLoad(callback_t cb)
8 {
9 int total = 1000; // 1000B
10 int curr = 0; // 0B
11
12 while(curr <= total)
13 {
14 /* 进行某种下载任务 */
15 usleep(50000); // 模拟下载时间
16
17 // 计算下载速率
18 int rate = curr * 100 / total;
19 cb(rate); // 通过函数指针去调用对应的函数
20
21 // 循环下载了一部分
22 curr += 10;
23 }
24 printf("\n");
25 }
downLoad(processBar);
main.c
这个文件外,其他地方都无需修改了,最后来看一下执行的结果不过呢这样还彰显不出回调函数的功能,我们尝试传入多次试试,模拟一下通过回调函数去调用不同的函数场景
27 int main(void)
28 {
29 // 将所需要的调用的函数地址传递给回调函数
30 printf("download 1:\n");
31 downLoad(processBar);
32
33 printf("download 2:\n");
34 downLoad(processBar);
35
36 printf("download 3:\n");
37 downLoad(processBar);
38
39 printf("download 4:\n");
40 downLoad(processBar);
41 return 0;
42 }
memset
void initBar()
{
memset(bar, '\0', sizeof(bar));
}
rate
到达100的时候去做一个清空的操作if(rate < 100)
bar[rate] = RIGHT;
else
initBar();
我们来看一下最终效果
processBar.h
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <string.h>
4
5 #define TOP 100
6 #define BODY '='
7 #define RIGHT '>'
8
9 extern void processBar(int rate);
10 extern void initBar();
processBar.c
1 #include "processBar.h"
2
3 const char* label = "|/-\\";
4 char bar[TOP];
5
6 void initBar()
7 {
8 memset(bar, '\0', sizeof(bar));
9 }
10
11 // 单次的进度推进
12 void processBar(int rate)
13 {
14 if(rate < 0 || rate > 100) return;
15
16 int len = strlen(label);
17 printf("[%-100s][%d%%][%c]\r", bar, rate, label[rate % len]);
18 fflush(stdout); // 刷新缓冲区
19 bar[rate++] = BODY;
20 if(rate < 100)
21 bar[rate] = RIGHT;
22 else
23 initBar();
24 }
main.c
1 #include "processBar.h"
2
3 // 函数指针类型
4 typedef void (callback_t)(int rate);
5
6 // 模拟一种安装或下载的场景(回调函数)
7 void downLoad(callback_t cb)
8 {
9 int total = 1000; // 1000B
10 int curr = 0; // 0B
11
12 while(curr <= total)
13 {
14 /* 进行某种下载任务 */
15 usleep(50000); // 模拟下载时间
16
17 // 计算下载速率
18 int rate = curr * 100 / total;
19 cb(rate); // 通过函数指针去调用对应的函数
20
21 // 循环下载了一部分
22 curr += 10;
23 }
24 printf("\n");
25 }
26
27 int main(void)
28 {
29 // 将所需要的调用的函数地址传递给回调函数
30 printf("download 1:\n");
31 downLoad(processBar);
32
33 printf("download 2:\n");
34 downLoad(processBar);
35
36 printf("download 3:\n");
37 downLoad(processBar);
38 }
好,我们来总结一下本文所学习的内容
\n
】与【\r
】,知道了它们分别有什么作用以上就是本文所要阐述的所有内容,感谢您的阅读
文章浏览阅读3.8k次,点赞9次,收藏28次。直接上一个工作中碰到的问题,另外一个系统开启多线程调用我这边的接口,然后我这边会开启多线程批量查询第三方接口并且返回给调用方。使用的是两三年前别人遗留下来的方法,放到线上后发现确实是可以正常取到结果,但是一旦调用,CPU占用就直接100%(部署环境是win server服务器)。因此查看了下相关的老代码并使用JProfiler查看发现是在某个while循环的时候有问题。具体项目代码就不贴了,类似于下面这段代码。while(flag) {//your code;}这里的flag._main函数使用while(1)循环cpu占用99
文章浏览阅读347次。idea shift f6 快捷键无效_idea shift +f6快捷键不生效
文章浏览阅读135次。Ecmacript 中没有DOM 和 BOM核心模块Node为JavaScript提供了很多服务器级别,这些API绝大多数都被包装到了一个具名和核心模块中了,例如文件操作的 fs 核心模块 ,http服务构建的http 模块 path 路径操作模块 os 操作系统信息模块// 用来获取机器信息的var os = require('os')// 用来操作路径的var path = require('path')// 获取当前机器的 CPU 信息console.log(os.cpus._node模块中有很多核心模块,以下不属于核心模块,使用时需下载的是
文章浏览阅读10w+次,点赞435次,收藏3.4k次。SPSS 22 下载安装过程7.6 方差分析与回归分析的SPSS实现7.6.1 SPSS软件概述1 SPSS版本与安装2 SPSS界面3 SPSS特点4 SPSS数据7.6.2 SPSS与方差分析1 单因素方差分析2 双因素方差分析7.6.3 SPSS与回归分析SPSS回归分析过程牙膏价格问题的回归分析_化工数学模型数据回归软件
文章浏览阅读7.5k次。如何利用hutool工具包实现邮件发送功能呢?1、首先引入hutool依赖<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.19</version></dependency>2、编写邮件发送工具类package com.pc.c..._hutool发送邮件
文章浏览阅读867次,点赞2次,收藏2次。docker安装elasticsearch,elasticsearch-head,kibana,ik分词器安装方式基本有两种,一种是pull的方式,一种是Dockerfile的方式,由于pull的方式pull下来后还需配置许多东西且不便于复用,个人比较喜欢使用Dockerfile的方式所有docker支持的镜像基本都在https://hub.docker.com/docker的官网上能找到合..._docker安装kibana连接elasticsearch并且elasticsearch有密码
文章浏览阅读1.3w次,点赞57次,收藏92次。整理 | 郑丽媛出品 | CSDN(ID:CSDNnews)近年来,随着机器学习的兴起,有一门编程语言逐渐变得火热——Python。得益于其针对机器学习提供了大量开源框架和第三方模块,内置..._beeware
文章浏览阅读7.9k次。//// ViewController.swift// Day_10_Timer//// Created by dongqiangfei on 2018/10/15.// Copyright 2018年 飞飞. All rights reserved.//import UIKitclass ViewController: UIViewController { ..._swift timer 暂停
文章浏览阅读986次,点赞2次,收藏2次。1.硬性等待让当前线程暂停执行,应用场景:代码执行速度太快了,但是UI元素没有立马加载出来,造成两者不同步,这时候就可以让代码等待一下,再去执行找元素的动作线程休眠,强制等待 Thread.sleep(long mills)package com.example.demo;import org.junit.jupiter.api.Test;import org.openqa.selenium.By;import org.openqa.selenium.firefox.Firefox.._元素三大等待
文章浏览阅读3k次,点赞4次,收藏14次。Java软件工程师职位分析_java岗位分析
文章浏览阅读2k次。Java:Unreachable code的解决方法_java unreachable code
文章浏览阅读1w次。1、html中设置标签data-*的值 标题 11111 222222、点击获取当前标签的data-url的值$('dd').on('click', function() { var urlVal = $(this).data('ur_如何根据data-*属性获取对应的标签对象