自学内容网 自学内容网

【Linux】拆分详解 - vim / gcc / makefile


【Linux】vim gcc makefile


前言

本文介绍了 Linux 中与编程强相关的三个工具:文本编辑器,代码编译器,自动化编译器。旨在帮助初学者快速入手使用 Linux 进行代码编写与编译。

一、Linux 编辑器 - vim

0. 简介

vi/vim 的区别简单点来说,它们都是多模式编辑器,不同的是 vim 是 vi 的升级版本,它不仅兼容 vi 的所有指令,而且还有一些新的特性在里面。例如语法加亮,可视化操作不仅可以在终端运行,也可以运行于 x window、 mac os、windows。

1. 多模式切换

在这里插入图片描述

  • 默认模式为命令模式,其余模式能且仅能在命令模式中切换。

    ESC ​键可以由其他模式回到默认模式

2. 各模式常见指令操作

  • 命令模式

    1. 对光标所在行

      ① 复制 yy

      ② 剪贴(删除)dd

      ③ 粘贴 p

      ④ 撤销 u

      撤销上一次的撤销操作 ctrl + R

      • 多行操作 n + yy/ pp/ dd
    2. 光标快速定位

      ① 定位到文本最末尾(第 n 行) [n] shift + g = G

      ② 定位到文本最开始 gg

      ③ 光标局部定位​

      1. 方向控制 左下上右(历史遗留) HJKL
      2. 定位到当前行尾 shift + 4 = $
      3. 定位到当前行首 shift + 6 = ^
      4. 以空格为分割的单词(多字符)作为单位,向后跳转 w
      5. 以空格为分割的单词(多字符)作为单位,向前跳转 b
    3. 光标所在位

      ① 从光标所在位开始(包括光标位) 向后 逐字符删除(指定字符个数)[n] x

      ② ~ (不包括光标位)向前 ~ [n] shift + x = X

      ③ 将光标位的字母进行大小写切换(非字母会向后跳过移动)shift + ~

      ④ 替换(覆盖)[n] r

      • 替换模式(输入的文本会直接覆盖掉原文本)shift + r = R
    4. 查找字符/字符串

      ① 正向查找 /指定字符(串)

      (从前往后),n 键跳转到下一个匹配的位置

      ② 反向查找 ?指定字符(串)

      (从后往前),n 键跳转到上一个匹配的位置

  • 视图模式(批量行操作)

    ctrl + v​ 进入,​HJKL​ 等(使用光标快速定位的相关操作)选中多行

    • shift + i​ 进入插入模式并执行所需修改

      ESC​ 两次,对选中的所有行同步 插入模式执行的修改操作

    • d​ 删除选中行

  • 底行模式

    1. 显示行号
      :set nu

    2. 跳转到指定行
      :数字

    3. 保存,退出

      :q
      退出,如果文件发生了修改,需要使用 !q​ ,意思是强制退出不保存修改

      :wq​ 保存并退出

      ③ 有时候 vim 发生了非正常退出,会临时保存一个副本,再次进入文件时会提示用户是否要恢复/删除该副本,才能继续使用 vim

      在这里插入图片描述
      在这里插入图片描述
      ​​

    4. 使用系统指令(不退出 vim)

      :!指令

    5. 分屏,切换

      分屏 :vs 文件名

      切换操作的文件 ctrl + w 两次

二、gcc 使用

  1. 语法

    gcc [选项] 源文件 [选项] 目标文件​ 分步编译

    gcc -o 目标文件 源文件​ 一步生成

  2. 编译的四个阶段

    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 #直接生成可执行程序
    
  3. 动 / 静态库

    1. 动/静态链接

      动态链接,如头文件,链接到外部的公共库

      静态链接,相当于将头文件直接拷贝到可执行程序内部,不依赖外部
      在这里插入图片描述
      在这里插入图片描述
      ​​

    2. 编译器自举

      当一款新的语言诞生时,是没有相应的编译器的,拿汇编语言为例,当汇编诞生时,第一款编译器必须由开发者使用二进制指令写一款编译器(因为机器只认识二进制),而第二款和以后的更新就可以使用汇编语言编写了,只要使用第一款编译器翻译成二进制指令即可。而 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 
  1. 基本概念

    • 定义

      本质上 makefile 就是依赖关系和依赖方法的集合

    • 依赖关系

      proc:proc.c​ 称为依赖关系,由目标文件和依赖文件列表两部分组成

    • 依赖方法

      gcc -o proc proc.c​ 称为依赖方法,可以是任意指令(的集合)

      • 以 Tab 键开头,不能是空格

      • @(指令名):关闭默认的指令回显,如 @gcc -o proc proc.c

  2. 特殊符号的使用

    • %​ :Linux 中的通配符号

      %.c​ :匹配当前目录下所有.c 文件

    • $​ :替换符号

      $(变量名)​ :将 $()​ ​整体替换为 变量存储的内容

      $^​ :替换为所有依赖文件列表

      $@​ :替换为所有目标文件

    proc.o:proc.c
    gcc -c proc.o proc.c 
    
    #将当前目录下所有.c文件 通过gcc编译成 一个个.o文件
    %.o:%.c
    gcc -c $< #将右侧的依赖文件一个一个交给gcc选项
    

    在这里插入图片描述

  3. 变量

    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. 特性

  1. make

    • 直接 make​ ,默认形成 makefile 中自上而下 第一个目标文件(依据依赖关系,执行对应的依赖方法,生成目标文件)
    • 如果要形成其他目标文件(执行其他依赖关系和方法),则 make 目标文件名
  2. 执行 gcc 命令时,如果出现编译错误,会直接终止推导过程

  3. 目标文件的修改时间晚于源文件时,make 会报错不执行

    • 原因:当目标文件的修改时间早于源文件时,说明源文件没有被修改过,那生成的目标文件不会有任何变化,make 此时认为没有必要去执行

    • ACM 时间

      Access​ :原意指最新的访问时间

      (但是考虑到性能消耗问题,实际上为积累到一定访问次数才更新该时间)

      Change​ :文件属性最新被修改的时间

      Modify​ :文件内容最新被修改的时间

      在这里插入图片描述

      注意:有时候 Modify 改变 会引发 Change 同步变化,这是因为修改文件内容可能会造成文件大小变化,而文件大小 是文件属性的一部分!

  4. 反向推导(栈结构)

    make makefile 时,如果遇到 某依赖关系中的依赖文件不存在,则不会执行该依赖关系对应的依赖方法,跳过然后向下继续推导执行其他依赖关系,如果依赖文件又不存在,则继续跳过,直到遇到依赖文件存在,再逆向执行所有依赖方法

    在这里插入图片描述
    在这里插入图片描述

这里只是为了演示反向推导的规则,我们一般没什么大病不会这样写编译过程,而是直接一步到位

proc:proc.c
gcc -o proc proc.c

4. 综合案例 - 进度条

4.1 预备知识

  1. 回车与换行

    回车:将光标移到当前行的最前方(横向移动)

    换行:将光标原地向下移动到下一行(纵向)

    • 平时我们说的换行,大部分情况都是指回车和换行,即先回车再换行

      \r​ 回车

      \n​ 换行(c 语言中等同于 \r\n​ )

  2. 缓冲区

    • 概念和作用:

      我们的程序是运行在内存中的,printf 输出字符串时不会直接将内容写入到显示器文件中,而是先将其存入缓冲区,等到一定量后再一次性刷新到显示器,这样可以减少消耗。

    在这里插入图片描述

    • 示例:

      在这里插入图片描述

      sleep 执行前 printf 已经完成执行了,此时字符串被输入到缓冲区,等到程序结束时会强制刷新缓冲区,我们就可以看到打印出来的内容。

      • 强制刷新:

        1. \n​ printf 中使用换行符会强制刷新缓冲区

          \r​ 不会刷新

        2. 程序结束,也会强制刷新缓冲区

        3. 使用库函数刷新标准输出流 fflush(stdout);

4.2 代码实现

  1. 倒计时

    1. 使用 \r​ + fflush(stdout)​ 强制刷新

      在同一行不断覆盖原先打印内容,达成倒计时效果

    2. %-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;
}
  1. 真实的进度条

    • 函数指针回调,可以使代码复用,只需传入不同的进度条刷新函数,就可以达成不同的进度条效果
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 进行代码编写与编译。

尽管文章修正了多次,但由于水平有限,难免有不足甚至错误之处,敬请各位读者来评论区批评指正


原文地址:https://blog.csdn.net/bubble_shadow/article/details/142851265

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!