【Linux基础】03 Linux环境基础开发工具使用

1. yum ——软件包管理器

yum 是我们 Linux 预装的一个指令,搜索、下载、、安装对应的软件
yum 相当于 Linux 的应用商店!

安装与卸载

  1. yum list | grep command
    通过 yum list 命令可以罗列出当前一共有哪些软件包. 由于包的数目可能非常之多, 这里我们需要使用 grep 命令只 筛选出我们关注的包.
  2. yum install [-y] command // sudo or root
    安装,直至出现 complete
  3. yum remove command // sudo or root
    卸载

2. vim——Linux 编辑器

vim 是一款多模式的编辑器,vim 里面有很多子命令来进行代码的编写操作。

2.1 三种模式

  • 普通模式/命令模式(Normal mode):用 vim 命令打开文件就是默认为命令模式。控制屏幕光标移动,字符或行的删除,移动复制某区段及进入插入模式或末行模式下;
  • 插入/输入模式(Insert mode):在命令模式下按 i,进入命令模式。只有在此模式下才能进行文字编写。按下 esc 键回到命令行模式;
  • 末行模式(Last line mode):文件保存或退出,也可以进行文件替换,查找字符串,显示行号等操作。

基本操作

  • 命令模式->插入模式:键入aio;
  • 插入模式->命令模式:按下esc
  • 命令模式->末行模式:键入

image.png
一开始用 vim 就是命令模式,无论处于哪种模式,你都可以按 Esc 就进入命令模式

2.2 命令模式指令集

以下的 n 代表数字 num
移动光标
shift+$:光标定位在当前行的最右侧结尾处锚点
shift+^:光标定位在当前行的最左侧开头处锚点
shift+g:光标定位到文本的最结尾(的开头)
n+shift+g:光标定位到第 n 行
gg:光标定位到文本的最开始
h:左,最左侧,左
j:下
k:上 king
l:右,最右侧,右
w:按照”单词”在行内进行移动,后
b:按照”单词“在行内进行移动,前

修改操作
n+ yy:复制当前行/多行(复制从光标起n 行)
n+p:在下一行进行粘贴/可以多次粘贴 (粘贴 n 次)
u:撤销编辑操作,undo
ctrl+r:对撤销进行撤销
n + dd:删除/剪切当前行
shift+'=~:大小写快速切换

替换模式
n+r:替换当前光标所在字符 (替换 n 个)
shift+r=R:替换模式
n+x:删除光标所在字符 (往后删 n 个)
n+shirt+x=X:向左侧进行局部删除 (往前删 n 个)
小写 x 是光标往后,包括自身。
大写 X 是光标往前
shift+3=#:高亮要查找的函数名
n:下一个查找到的函数/字符串名

2.3 末行模式指令集

列出行号
:set nu

保存与退出
:w/q/wq/!/q!/wq!
w 是保存文件,q 是退出,wq 是保存并退出,! 是强制执行

多窗口模式
vs filename
光标在哪一个界面,我们就正在编辑哪一个界面,底行也是一样
image.png

光标多终端切换
ctrl+ww:
可以切换文件,进行不同的操作
可以在 A 文件复制内容并且拷贝到 B 文件

跳到文件中的某一行
:n,再按回车就跳到该行

检索字符
「/关键字」:先按「/」键,再输入您想寻找的字符,如果第一次找的关键字不是您想要的,可以一直按「n」会往后寻找到您要的关键字为止;
「? 关键字」:先按「?」键,再输入您想寻找的字符,如果第一次找的关键字不是您想要的,可以一直按「N」会往前寻找到您要的关键字为止。
区别:前者向下查找,后者向前查找。
前者搭配 n 使用,后者搭配 N 使用。

2.4 视图模式

在正常模式下,输入 ctrl + v

批量注释
j、k 选中要注释的行,选好后按 shift+i 进入插入模式,再输入 // 后按 ESC 退出,此时代码就被注释掉了

批量去注释
进入视图模式,使用 hjkl 选中要取消注释的区域,==注意这里要选中前两列,==否则只会删除 // 的第一个 /,选好后输入 d,即可删除

实例
批量注释.gif|300 批量去注释.gif|300

3. gcc/g++的使用——Linux 编译器

补充扩展:
语言和编译器的自举过程

3.1 编译的过程

3.1.1 预处理

  • 头文件包含
    在此过程中,将头文件内容拷贝至源文件。将 #include 包含的头文件内容直接插入到源代码中。

  • 宏替换
    在此过程中,将用 #define 定义的宏替换为相应的值。

  • 条件编译
    根据条件编译指令(如 #ifdef#ifndef)来选择性地编译代码段。

    比如:我们在 code.h

    #ifdef _CODE_H_
    

#define CODE_H
#endif
```
只有在定义了 code. h 的情况下,这段代码才会编。 这也就是为什么可以防止头文件被重复包含的原因。

  • 删除注释
    预处理器会将所有的注释删除,以简化代码。

输出:
预处理后的结果是一个纯文本文件,它是原始源代码经过宏替换、头文件展开、条件编译后的结果,通常以 .i 文件形式保存。

gcc -E code.c -o code.i

-E 选项告诉编译器只进行预处理,不进行进一步的编译步骤
-o 选项是指目标文件
.i 文件为已经过预处理的C原始程序。
image.png

3.1.2 编译

编译 是将预处理后的源代码(如 C 语言) 转换为目标机器的汇编代码的过程。编译器会将高级语言(C/C++)的语法结构翻译为与特定处理器架构相关的汇编指令。

编译步骤:

  • 语法检查:编译器会对预处理后的代码进行语法和语义分析,确保代码符合语言规范。
  • 生成中间代码:编译器会将代码转化为中间形式,这种形式与平台无关,可以优化。
  • 优化:编译器可能会进行一些优化,比如减少不必要的代码、优化循环和条件语句等。
  • 生成汇编代码:最后,编译器会生成与目标架构相关的汇编代码。汇编代码是一种低级别的、人类可读的机器语言。

输出:
编译的结果是汇编代码,通常保存为 .s 文件。

gcc -S code.i -o code.s

-S 选项让编译器停止在生成汇编代码的步骤,不继续到汇编和链接阶段。
image.png

3.1.3 汇编

汇编 是将汇编代码转换为目标机器可以直接理解的机器指令(机器码)的过程。汇编器会将汇编语言转化为二进制形式的机器码,这个过程是将低级别的汇编指令直接映射为 CPU 可以执行的指令。

汇编步骤:

  • 指令翻译:每条汇编指令被转化为对应的机器码。
  • 符号解析:将汇编代码中的符号(如变量和函数名)转化为具体的内存地址或偏移量。

输出:
汇编后的输出是目标文件(即可重定位目标二进制文件,是不能执行的),通常以 .o(在 Linux 上)或 .obj(在 Windows 上)为后缀。目标文件包含了编译后的二进制代码,但尚未与其他库或文件链接。

gcc -c code.s -o code.o

-c 选项指示编译器只进行到汇编阶段,不进行链接。

3.1.4 链接

链接 是将多个目标文件和库文件合并为一个最终的可执行文件的过程。程序通常由多个源文件组成,编译器会单独编译这些文件生成各自的目标文件,最后由链接器将它们链接在一起,生成最终的可执行文件。

链接步骤:

  • 符号解析:链接器会解析所有目标文件中的符号,将各个文件中引用的函数和变量正确地连接起来。比如,如果 main.c 调用了 foo.c 中的一个函数,链接器会找到 foo.c 中对应的函数定义并将它们链接起来。
  • 库的链接:如果程序使用了外部库(如标准 C 库 libc 或用户自定义的库),链接器会把这些库中的符号与程序中的引用进行匹配。可以是静态链接(库代码被复制到可执行文件中)或动态链接(在运行时加载库)。
  • 生成可执行文件:最后,链接器会将所有解析后的二进制代码合并为一个可执行文件。

输出:
链接的输出是最终的可执行文件,通常没有文件扩展名(如 a.out),或有 .exe(在 Windows 上)。

gcc hello.o -o hello

默认的指令就可以了。

3.2 动、静态函数库

动态库(动态链接)

  • 在程序执行时,由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为 .so,如 libc.so.6 就是动态库。gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件。
    gcc code.c -o code
    
  • gcc 默认生成的二进制程序是动态链接,可用 file 指令验证
    优点:比较节省资源,不会出现太多的重复代码
    资源:磁盘、内存、网络等。
    缺点:对库的依赖性比较强,一旦库丢失,所有使用这个库的程序无法运行

静态库(静态链接)
静态库是指编译链接时, 把库文件的代码全部拷贝到可执行文件中, 因此生成的文件比较大, 但在运行时也就不再需要库文件了。其后缀名一般为“. a”
优点:不依赖库,同类型平台中都可以直接运行使用
缺点:可执行程序体积比较大,比较浪费资源(资源同上)

总结:
允许你拷贝的库就是静态库
允许你关联的库就是动态库

4. makefile ——Linux项目自动化构建工具

gcc 一条指令只能编译(删除)一个或多个源文件,而面对不计其数的项目文件(根据类型、功能等分散在各处)就显得十分低效,所以就有了 makefile 定义了一系列的规则指定文件的编译先后顺序等甚至更复杂的功能操作。
为了提高效率,人们把gcc、g++这些指令都集合在一个文件 Makefile 中,只需编辑一次,有新需求再对其修改,通过工具 make,自动化执行指令。——一键自动化构建

4.1 make、依赖关系及方法

make 是一条命令,makefile 是一个特定格式的文件(需要自己创建),两个搭配使用,完成项目自动化构建。

target: dependencies
    command
  • target:表示需要构建的目标文件
  • dependencies:表示构建目标文件所需要的依赖文件。
  • command:表示构建目标文件的命令。

你创建了一个 mytest.c,然后下面你创建了 Makefile 或者 makefile 文件

mybin: mytest.c #这就是依赖关系。
    gcc -o mybin mytest.c #这就是依赖方法

.PHONY: clean # 修饰 clean 目标为伪目标,总是被执行
clean:
    rm -f mybin

image.png

4.1.1 依赖关系与依赖方法、自动化构建

目标 mybin

  • 第一行定义了一个名为 mybin 的目标,它依赖于 mytest.c 文件。
    这就是依赖关系。
  • 当你运行 make 命令时,make 会找到第一个目标 mybin,并检查其依赖项(即 mytest.c)。如果 mytest.c 发生了改变make 会执行 gcc -o mybin mytest.c 来编译生成可执行文件 mybin
    这就是依赖方法

依赖关系
Makefile 中的依赖关系表示了目标文件和依赖文件之间的关系。如果依赖文件发生更改,或者目标文件不存在,make 将执行规则中的构建命令,以确保目标文件是最新的
image.png
即:如果文件是最新的,make 就不会执行。
那么怎么判断文件是否最新呢?详见第五节。[[03 Linux环境基础开发工具使用#4.3. 文件的三个属性]]

自动化构建make 工具的一个主要优势是它能够自动检测哪些文件需要重新构建,以减少不必要的编译和链接操作。它只会重新构建已更改或丢失的文件,而不是整个项目。

4.1.2 默认行为

默认行为:——默认形成的是第一个目标文件,且默认只形成一个!

  • 默认情况下,执行 make 时,从上到下扫描 Makefile,构建第一个目标 mybin 。如果没有其他目标的指定,make 只会创建 mybin,即使 Makefile 中有多个目标(比如 clean)。
    当我们将 cleanmybin 的位置颠倒,使用 make,不会执行 gcc,但却直接执行 clean
    image.png|475

4.2、make clean 和 .PHONY

伪目标 clean

  • 使用 .PHONY: clean 指定 clean 是一个伪目标,这意味着它不会与文件系统中的实际文件冲突。无论何时你运行 make clean,都会执行其下的命令。
  • clean 目标用于清理生成的可执行文件,通过运行 make clean 来删除 mybin 文件。
    image.png

.PHONY 是一个特殊的目标,用于告诉 make 哪些目标是伪目标(不表示实际的文件),以防止 make 误解这些目标。通常,伪目标在 Makefile 中不会表示实际的文件,而是用于执行特定的操作或任务(比如clean清理数据)

伪对象的作用有两个
1. 使目标对象无论如何都要重新生成
2. 并不生成目标文件,而是为了执行一些指令

4.3. 文件的三个属性

那么系统怎么判断文件是否最新呢?

文件第一次编写、编译代码后,此时源文件的修改时间一定是早于生成的可执行文件的修改时间。所以,第二次 make 时,系统会提示我们 mybin is up to date
当我们修改文件后,此时源文件修改时间晚于可执行程序文件的修改时间,所以系统会执行 make

查看文件的属性

stat 文件名


Access 是最近访问文件的时间
看文件是最频繁的,所以 access 时间并不是实时更新
Modify 是最近对文件内容做修改的时间
Change 是最近对文件属性做修改的时间

总结:
==文件 = 内容 + 属性 ==
修改文件的内容可能会影响文件属性 ,因为增加或减少代码会影响文件的大小
然而修改文件的属性不会影响文件内容,比如修改文件的拥有者,修改文件权限等

4.4 makefile推导能力和变量


这段代码的意思就是
首先,mybin依赖的是.o文件,但是系统中此时没有.o文件,就会执行下一条指令,但是.o文件依赖的是.s文件,系统中此时没有.s文件又会跳到下一条指令,以此往复直到找到系统中存在的.c文件,再倒推回去
这就是 makefile 的推导能力。(简单了解)

makefile 中可以定义变量

#原来的代码
mybin:mytest.c
    gcc -o mybin mytest.c
.PHONY:clean # 修饰mybin目标文件,成为一个伪目标,总是被执行
clean:                      
    rm -f mybin

在要替换的内容前加上 $() 即可
像C语言定义变量一样编辑代码

#定义变量的代码
target = mybin            
cc = gcc 
src = mytest.c   
$(target):$(src) 
    $(cc) $(src) -o $(target)
.PHONY:clean 
clean: 
    rm -rf $(target)

并且,可以用$^代表依赖关系的左边
可以用 $@ 代表依赖关系的右边
有种关键字 auto 的感觉

mybin:mytest.c
     gcc $@ -o $^
.PHONY:clean  
clean: 
     rm -rf mybin

5. Linux第一个小程序——进度条

#include <stdio.h>
#include<unistd.h>

int main()
{
	printf("you can see me.");//注意这里没有`\n`
	sleep(3);
	return 0;
}
该程序在三秒后才显示出`you can see me.`——缓冲区

5.1 缓冲区

临时存储输出数据的内存区域,数据在输出时会先存储在缓冲区中。
在 I/O 操作中,数据一般不会立即直接写入目标。

类型

  • 全缓冲:在数据达到一定量时,才会进行输出(如大文件输出到文件)。
  • 行缓冲:每当遇到换行符(\n)时,会将缓冲区的数据输出。
    如果加了 \n —— printf("you can see me.\n"); 便会立刻输出在显示屏上。
    由于标准输出是行缓冲的,字符串会被存储在缓冲区中,直到遇到换行符或缓冲区满时,才会被输出到终端。
  • 无缓冲:数据会立即写入目标,没有缓冲行为。

5.2 回车 \r

  • 定义:回车符是 ASCII 控制字符,其值为 13(十进制),表示将光标移动到当前行的开头。
  • 表现:在终端中,回车符的效果是将光标移到行首,而不换行。例如,printf("Hello\rWorld"); 会输出 World,覆盖 Hello,结果只显示 World

5.3 换行 \n

  • 定义:换行符是 ASCII 控制字符,其值为 10(十进制),表示将光标移动到下一行的开头。(即,并换行)
  • 表现:在终端中,换行符的效果是将光标移到下一行。例如,printf("Hello\nWorld"); 会在 Hello 后换行,接着输出 World,结果显示为:

根据以上的知识点写出的

#include <stdio.h>
#include<unistd.h>

int main()
{
    int cnt = 10;                                                         
    while(cnt >= 0)
    {
        printf("%-2d\r",cnt);//不满2位数就打印出一位数
        fflush(stdout);// 强制输出到终端
        cnt--;
        sleep(1);//停顿
    }
    printf("\n");
    return 0;
}

fflush(stdout) 确保数字 cnt 会立即显示在终端上,而不是等待程序结束或缓冲区满时才显示。

5.4 进度条

5.4.1 Version 1 (死板固定的进度条)

#pragma once
#include <stdio.h>

#define NUM 102
#define S '#' 
//version1
void process(); 
#include "processbar.h"
#include <string.h>
#include <unistd.h>

const char *label = "|/-\\";
//version 1
void process()
{
    char buffer[NUM];
    memset(buffer,'\0',sizeof(buffer));

    int cnt = 0;
    int n = strlen(label);
    while(cnt <= 100)
    {
        printf("[%-100s][%3d%%][%c]\r",buffer,cnt,label[cnt%n]);                  fflush(stdout);
        buffer[cnt++] = S;
        usleep(50000);
    }
    printf("\n");
}
#include "processbar.h"                                                   
int main()
{
    process();
    return 0;
}

进度条.gif|800

5.4.2 Version 2 (模拟真实情况)

#pragma once
#include <stdio.h>

#define NUM 103
#define Body '='
#define Head '>'

typedef void (*callback_t)(double)//回调函数

void process_flush(double rate)           
#include "processbar.h"
#include <string.h>
#include <unistd.h>

const char *label = "|/-\\";

char buffer[NUM] = {0};
void process_flush(double rate)
{
    static int cnt = 0;                                                                                                                                    
    int n = strlen(label);
    if(rate <= 1.0) buffer[0] = Head;
    printf("[\033[4;37;41m%-100s\033[0m][%.1f%%][%c]\r", buffer, rate, label[cnt%n]);
    //只是,增添了颜色。printf("[%-100s][%3d%%][%c]\r",buffer,cnt,label[cnt%n]);
    fflush(stdout);

    buffer[(int)rate] = Body;
    if((int)rate+1 < 100) buffer[(int)(rate+1)] = Head;
    if(rate>=100.0) printf("\n");

    cnt++;
    cnt%=n;
}

#include "processbar.h"
#include <time.h>
#include <stdlib.h>
#include <unistd.h>

#define FILESIZE 1024*1024*1024

//模拟一种场景,表示一种下载的任务
void download(callback_t cb) // 回调函数的形式
{
    srand(time(NULL)^1023);
    //- 首先调用 `srand` 来初始化随机数生成器。
    //在下载循环中,`rand()` 用于生成一个随机下载量(`one`),使得每次循环下载的量都是不固定的,模拟真实下载情况。		
    int total = FILESIZE;
    while(total){
        usleep(10000); //下载动作
        int one = rand()%(1024*1024*10);
        total -= one;
        if(total < 0) total = 0;

        // 当前的进度是多少?
        int download = FILESIZE - total;
        double rate = (download*1.0/(FILESIZE))*100.0; // 0 23.4 35.6, 56.6
        cb(rate);
        //process_flush(rate);
        //printf("download: %f\n", rate); // rate出来了,应该让进度条刷新
    }   
}

int main()
{
    //process();
    download(process_flush);
    return 0;
}

进度条01.gif

6. 版本管理工具——git

Git 是一个强大的版本控制系统,通过一系列指令和操作,可以轻松管理项目的历史、协作开发和代码发布。

Git 工作流程

  1. 克隆或初始化项目:使用 git clone 克隆远程仓库,或使用 git init 创建本地仓库。
  2. 创建分支:创建新分支进行开发(git branch <branch_name>)。
  3. 修改代码并提交:在分支中修改代码,使用 git add 暂存更改,使用 git commit 提交更改。
  4. 推送更改:将本地分支的更改推送到远程仓库(git push)。
  5. 合并分支:当开发完成后,将分支合并到主分支(git merge

6.1 常用指令

sudo yum install git
记得在系统里安装 git 工具。

git clone
下载项目到本地,创建好一个放置代码的目录。

git clone [url]
这里的 url 就是刚刚建立好的仓库链接

image.png

git add

  • git add <file>:将指定文件的更改添加到暂存区。
  • git add .:将当前目录下的所有更改添加到暂存区。
    image.png|525

git status
显示当前工作目录中已修改但未提交的文件,以及与远程仓库的不同步情况。

git commit

git commit -m "Commit message"

提交暂存区中的文件到本地仓库,-m 后面跟提交的描述信息。
image.png|500

git log
显示当前仓库的提交历史,包含提交 ID、作者、日期和提交信息。

git push
将本地分支的提交推送到远程仓库。
image.png|500
用户名是你 gitee 主页@后面的字符串

6.2 .gitignore 文件

git 仓库只要管理源代码(源文件+头文件+配置文件等)
本地仓库提交的文件进行过滤的,文件后缀在 ign 文件列表中,不会被本地和远端托管
注意

  1. 不要忽略 *.xxx
  2. 修改是及时生效的
    image.png

7. gdb——Linux 调试器

程序的发布方式有两种,debug模式和release模式
前者较于后者,形成的可执行程序会给我们添加调试信息

gcc/g++编译时默认是 release 模式。

要使用gdb调试,必须在源代码生成二进制程序的时候, 加上 -g 选项

mybin:mytest.c
     gcc -o $@  $^ -g
.PHONY:clean  
clean: 
     rm -rf mybin

7.1 调试的目的

只有知道代码逻辑,调试起来才会相对容易。

  1. 找到问题(通过查找,范围查找,局部逐行查找等)
  2. 解决问题(联系上下文代码)

7.2 调试过程及 gdb指令

尽管各平台(如:微软的visual studio )调试的形式不同,但本质是相似的。

Windows 基本的调试功能

  1. 运行起来才能调试
  2. 要看得到代码
  3. 打断点
  4. 局部调试(如:函数内)
  5. 看到局部变量的内容和地址
  6. 看到调用栈
  7. 单步
  8. 进入函数

l 行号:显示指定行之后的代码(gdb 自动记录最近一条指令)
b 行号/函数名/file:行号:对指定位置打断点
info b:査看我们所打的断点
d 断点编号:删除断点
disable/enable 断点编号:使能(禁用/开启)断点

n:逐过程–F10
s:逐语句–F11(进入函数)
p:显示变量的内容和地址
display 变量名/取地址:常显示变量的内容和地址
undisplay 编号:取消常显示变量的内容和地址

c:从一个断点运行到下一个断点(可用于问题范围查找)
finish:将一个函数运行结束,就停下来(可用于问题范围查找)
until:在一个范围内,直接运行到指定行(可用于问题范围查找),不能跳过函数调用,一般用于跳过循环
bt:查看调用堆栈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值