【Linux】拆分详解 - vim / gcc / makefile
文章目录
- 【Linux】vim gcc makefile
- 前言
- 一、Linux 编辑器 - vim
- 0. 简介
- 1. 多模式切换
- 2. 各模式常见指令操作
- 二、gcc 使用
- 三、项目自动化构建工具 - make/Makefile
- 1. 简介
- 2. 语法
- 3. 特性
- 4. 综合案例 - 进度条
- 4.1 预备知识
- 4.2 代码实现
- 总结
【Linux】vim gcc makefile
前言
本文介绍了 Linux 中与编程强相关的三个工具:文本编辑器,代码编译器,自动化编译器。旨在帮助初学者快速入手使用 Linux 进行代码编写与编译。
一、Linux 编辑器 - vim
0. 简介
vi/vim 的区别简单点来说,它们都是多模式编辑器,不同的是 vim 是 vi 的升级版本,它不仅兼容 vi 的所有指令,而且还有一些新的特性在里面。例如语法加亮,可视化操作不仅可以在终端运行,也可以运行于 x window、 mac os、windows。
1. 多模式切换
默认模式为命令模式,其余模式能且仅能在命令模式中切换。
ESC
键可以由其他模式回到默认模式
2. 各模式常见指令操作
命令模式
对光标所在行
① 复制
yy
② 剪贴(删除)
dd
③ 粘贴
p
④ 撤销
u
撤销上一次的撤销操作
ctrl + R
- 多行操作
n + yy/ pp/ dd
光标快速定位
① 定位到文本最末尾(第 n 行)
[n] shift + g = G
② 定位到文本最开始
gg
③ 光标局部定位
- 方向控制 左下上右(历史遗留)
HJKL
- 定位到当前行尾
shift + 4 = $
- 定位到当前行首
shift + 6 = ^
- 以空格为分割的单词(多字符)作为单位,向后跳转
w
- 以空格为分割的单词(多字符)作为单位,向前跳转
b
光标所在位
① 从光标所在位开始(包括光标位) 向后 逐字符删除(指定字符个数)
[n] x
② ~ (不包括光标位)向前 ~
[n] shift + x = X
③ 将光标位的字母进行大小写切换(非字母会向后跳过移动)
shift + ~
④ 替换(覆盖)
[n] r
- 替换模式(输入的文本会直接覆盖掉原文本)
shift + r = R
查找字符/字符串
① 正向查找
/指定字符(串)
(从前往后),n 键跳转到下一个匹配的位置
② 反向查找
?指定字符(串)
(从后往前),n 键跳转到上一个匹配的位置
视图模式(批量行操作)
ctrl + v
进入,HJKL
等(使用光标快速定位的相关操作)选中多行
shift + i
进入插入模式并执行所需修改
ESC
两次,对选中的所有行同步 插入模式执行的修改操作
d
删除选中行底行模式
显示行号
:set nu
跳转到指定行
:数字
保存,退出
①
:q
退出,如果文件发生了修改,需要使用!q
,意思是强制退出不保存修改②
:wq
保存并退出③ 有时候 vim 发生了非正常退出,会临时保存一个副本,再次进入文件时会提示用户是否要恢复/删除该副本,才能继续使用 vim
使用系统指令(不退出 vim)
:!指令
分屏,切换
分屏
:vs 文件名
切换操作的文件
ctrl + w 两次
二、gcc 使用
语法
gcc [选项] 源文件 [选项] 目标文件
分步编译
gcc -o 目标文件 源文件
一步生成编译的四个阶段
gcc -E code.c -o code.i # 1. 预处理 gcc -S code.i -o code.s # 2. 编译 gcc -c code.s -o code.o # 3. 编译+汇编(注意-c是小写) gcc -o code code.o # 4. 链接 gcc -o code code.c #直接生成可执行程序
动 / 静态库
动/静态链接
动态链接,如头文件,链接到外部的公共库
静态链接,相当于将头文件直接拷贝到可执行程序内部,不依赖外部
编译器自举
当一款新的语言诞生时,是没有相应的编译器的,拿汇编语言为例,当汇编诞生时,第一款编译器必须由开发者使用二进制指令写一款编译器(因为机器只认识二进制),而第二款和以后的更新就可以使用汇编语言编写了,只要使用第一款编译器翻译成二进制指令即可。而 C 语言也是一样的,初版 C 编译器必须使用汇编语言编写(也可以用二进制写,但是前人已经写好了汇编编译器,为什么不用呢?),而第二版及以后的所有更新就可以使用 C 语言编写了。
简而言之,就是一款新语言的初版编译器必须使用上一代语言或者二进制编写,而后的更新才可以使用新语言编写,这称之为编译器的自举过程
三、项目自动化构建工具 - make/Makefile
1. 简介
- 目的:自动化、局部化编译文件
- 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile 定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
- makefile 带来的好处就是——“自动化编译”,一旦写好,只需要一个 make 命令,整个工程完全自动编译,极大的提高了软件开发的效率。
- make 是一个命令工具,是一个解释 makefile 中指令的命令工具,一般来说,大多数的 IDE 都有这个命令,比如:Delphi 的 make,Visual C++ 的 nmake,Linux 下 GNU 的 make。可见,makefile 都成为了一种在工程方面的编译方法。
- make 是一条命令,makefile 是一个文件,两个搭配使用,完成项目自动化构建。
2. 语法
# 目标文件 : 依赖文件列表(一整行称为依赖关系)
# 依赖方法
proc:proc.c
gcc -o proc proc.c
基本概念
定义
本质上 makefile 就是依赖关系和依赖方法的集合
依赖关系
proc:proc.c
称为依赖关系,由目标文件和依赖文件列表两部分组成依赖方法
gcc -o proc proc.c
称为依赖方法,可以是任意指令(的集合)
以 Tab 键开头,不能是空格
@(指令名):关闭默认的指令回显,如
@gcc -o proc proc.c
特殊符号的使用
%
:Linux 中的通配符号
%.c
:匹配当前目录下所有.c 文件
$
:替换符号
$(变量名)
:将$()
整体替换为 变量存储的内容
$^
:替换为所有依赖文件列表
$@
:替换为所有目标文件proc.o:proc.c gcc -c proc.o proc.c #将当前目录下所有.c文件 通过gcc编译成 一个个.o文件 %.o:%.c gcc -c $< #将右侧的依赖文件一个一个交给gcc选项
变量
makefile 中的变量类似于 C 语言中的宏,没有类型,只用于存储内容替换。但需要配合替换符
$
才能达成替换效果proc:proc.c gcc -o proc proc.c # 定义变量 bin,src 下方代码等同于上方代码 bin = proc src = proc.c $(bin):$(src) gcc $^ -o $@ # 将proc.c 编译成 proc
3. 特性
make
- 直接
make
,默认形成 makefile 中自上而下 第一个目标文件(依据依赖关系,执行对应的依赖方法,生成目标文件)- 如果要形成其他目标文件(执行其他依赖关系和方法),则
make 目标文件名
执行 gcc 命令时,如果出现编译错误,会直接终止推导过程
目标文件的修改时间晚于源文件时,make 会报错不执行
原因:当目标文件的修改时间早于源文件时,说明源文件没有被修改过,那生成的目标文件不会有任何变化,make 此时认为没有必要去执行
ACM 时间
Access
:原意指最新的访问时间(但是考虑到性能消耗问题,实际上为积累到一定访问次数才更新该时间)
Change
:文件属性最新被修改的时间
Modify
:文件内容最新被修改的时间
注意:有时候 Modify 改变 会引发 Change 同步变化,这是因为修改文件内容可能会造成文件大小变化,而文件大小 是文件属性的一部分!
反向推导(栈结构)
make makefile 时,如果遇到 某依赖关系中的依赖文件不存在,则不会执行该依赖关系对应的依赖方法,跳过然后向下继续推导执行其他依赖关系,如果依赖文件又不存在,则继续跳过,直到遇到依赖文件存在,再逆向执行所有依赖方法
这里只是为了演示反向推导的规则,我们一般没什么大病不会这样写编译过程,而是直接一步到位
proc:proc.c
gcc -o proc proc.c
4. 综合案例 - 进度条
4.1 预备知识
回车与换行
回车:将光标移到当前行的最前方(横向移动)
换行:将光标原地向下移动到下一行(纵向)
平时我们说的换行,大部分情况都是指回车和换行,即先回车再换行
\r
回车
\n
换行(c 语言中等同于\r\n
)缓冲区
概念和作用:
我们的程序是运行在内存中的,printf 输出字符串时不会直接将内容写入到显示器文件中,而是先将其存入缓冲区,等到一定量后再一次性刷新到显示器,这样可以减少消耗。
示例:
sleep 执行前 printf 已经完成执行了,此时字符串被输入到缓冲区,等到程序结束时会强制刷新缓冲区,我们就可以看到打印出来的内容。
强制刷新:
\n
printf 中使用换行符会强制刷新缓冲区
\r
不会刷新程序结束,也会强制刷新缓冲区
使用库函数刷新标准输出流
fflush(stdout);
4.2 代码实现
倒计时
使用
\r
+fflush(stdout)
强制刷新在同一行不断覆盖原先打印内容,达成倒计时效果
%-2d
控制字符宽度和左对齐
显示器没有类型的概念,都是打印一个一个的字符
所以 printf 的格式化输出,实际上是把各种类型格式化为字符交给显示器(让我们看起来像各种类型数,实际上输出的是一个一个的字符)
所以打印 10,实际上是打印字符 ‘1’ ‘0’,必须控制至少占据两个宽度,否则会有’0‘残留在第二个位置上
printf 默认为右对齐,
-
改为左对齐
#include <stdio.h>
#include <unistd.h>
int main()
{
int count = 10;
while(count >= 0)
{
printf("%-2d\r", count); // \r回车,但是没有换行,也就没有刷新
fflush(stdout);
count--;
sleep(1);
}
printf("\n"); //换行,防止命令行提示符覆盖掉最后的0
return 0;
}
真实的进度条
- 函数指针回调,可以使代码复用,只需传入不同的进度条刷新函数,就可以达成不同的进度条效果
process:main.c process.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f process
// --- process.h --- //
#pragma once
void FlushProcess(double total, double current);
// --- process.c --- //
#include "process.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <string.h>
#define NUM 101 //预留100个插入空间,剩下一个给‘/0’
#define STYLE '='
#define POINT '.'
#define SPACE ' '
const int pnum = 6;
// version 2:真实的进度条,应该根据具体的比如下载的量,来动态刷新进度
void FlushProcess(double total, double current)
{
// 1. 更新当前进度的百分比
double rate = (current/total)*100;
// 2. 更新进度条主体
char bar[NUM]; // 我们认为,1% 更新一个等号
memset(bar, '\0', sizeof(bar)); //全部设置为\0,如有新内容插入就直接覆盖
// 可以使得printf打印完插入的内容就停止打印
for(int i = 0; i < (int)rate; i++)
{
bar[i] = STYLE;
}
// 3. 更新旋转光标或者是其他风格
static int num = 0;
num++;
num %= pnum; // %= 控制始终为 pnum长度的光标循环
char points[pnum+1];
memset(points, '\0', sizeof(points));
for(int i = 0; i < pnum; i++)
{
if(i < num) points[i] = POINT;
else points[i] = SPACE;
}
// 4. test && printf
//单行动态显示 + 预留空间显示
printf("[%-100s][%.1lf%%]%s\r", bar, rate, points);
fflush(stdout);
//sleep(1);
}
// --- main.c --- //
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include "process.h"
typedef void (*flush_t)(double total, double current);// 这是一个刷新的函数指针类型
const int base = 100;
double total = 2048.0; // 2048MB
double once = 0.1; // 0.5MB
// 进度条的调用方式
void download(flush_t f)
{
double current = 0.0;
while(current < total)
{
// 模拟下载行为
int r = rand() % base + 1; // [1, 10]
double speed = r * once;
current += speed;
if(current >= total) current = total;
usleep(10000);
// 更新除了本次新的下载量
// 根据真实的应用场景,进行动态刷新
//Process(total, 1.0);
f(total, current);
//printf("test: %.1lf/%.1lf\r", current, total);
//fflush(stdout);
}
printf("\n");
}
int main()
{
srand(time(NULL));
download(FlushProcess);
return 0;
}
总结
本文介绍了 Linux 中与编程强相关的三个工具:文本编辑器,代码编译器,自动化编译器。旨在帮助初学者快速入手使用 Linux 进行代码编写与编译。
尽管文章修正了多次,但由于水平有限,难免有不足甚至错误之处,敬请各位读者来评论区批评指正