1.Linux 软件包管理器
2.Linux开发工具
3.Linux编译器-gcc/g++使用
4.Linux项目自动化构建工具-make/Makefile
5.Linux第一个小程序-进度条
- 在Linux下安装软件, 一个通常的办法是下载到程序的源代码, 并进行编译, 得到可执行程序.
- 但是这样太麻烦了, 于是有些人把一些常用的软件提前编译好, 做成软件包(可以理解成windows上的安装程序)放在一个服务器上, 通过包管理器可以很方便的获取到这个编译好的软件包, 直接进行安装.
- 软件包和软件包管理器, 就好比 "App" 和 "应用商店" 这样的关系.
- yum(Yellow dog Updater, Modified)是Linux下非常常用的一种包管理器. 主要应用在Fedora, RedHat, Centos等发行版上.

这个工具用于 windows 机器和远端的 Linux 机器通过 XShell 传输文件.
安装完毕之后可以通过拖拽的方式将文件上传过去.
注意事项
关于 yum 的所有操作必须保证主机(虚拟机)网络畅通!!!
可以通过 ping 指令验证
ping www.baidu.com
通过 yum list 命令可以罗列出当前一共有哪些软件包. 由于包的数目可能非常之多, 这里我们需要使用 grep 命令只 筛选出我们关注的包. 例如:
yum list | grep lrzsz
结果如下:

注意事项:
- 软件包名称: 主版本号.次版本号.源程序发行号-软件包的发行号.主机平台.cpu架构.
- "x86_64" 后缀表示64位系统的安装包, "i686" 后缀表示32位系统安装包. 选择包时要和系统匹配.
- "el7" 表示操作系统发行版的版本. "el7" 表示的是 centos7/redhat7. "el6" 表示 centos6/redhat6.
- 最后一列, base 表示的是 "软件源" 的名称, 类似于 "小米应用商店", "华为应用商店" 这样的概念.
通过 yum, 我们可以通过很简单的一条命令完成 gcc 的安装.
sudo yum install lrzsz
yum 会自动找到都有哪些软件包需要下载, 这时候敲 "y" 确认安装. 如果上面指令加上-y选项,此时不需要敲 "y" 确认安装. 出现 "complete" 字样, 说明安装完成.
注意事项:
- 安装软件时由于需要向系统目录中写入内容, 一般需要 sudo 或者切到 root 账户下才能完成.
- yum安装软件只能一个装完了再装另一个. 正在yum安装一个软件的过程中, 如果再尝试用yum安装另外 一个软件, yum会报错.
- 如果 yum 报错, 请自行百度.
安装几个有意思的指令
yum install -y sl
仍然是一条命令:
sudo yum remove lrzsz
问题:yum如何知道目标服务器的地址和下载链接呢?比如我们在手机的应用商店上下载一个抖音,应用商店是怎么知道目标服务器的地址呢?它怎么知道要去哪里下载呢?
当用户决定下载应用时,应用商店会生成一个下载请求,将该请求发送到应用商店的服务器。应用商店的服务器会根据下载请求检索应用的信息,包括下载链接。同样的LInux下yum也是这样,yum会生成一个下载请求(请求路径由配置文件提供),将该请求发送到应yum的远端的指令仓库,这个指令仓库会根据下载请求检索应用的信息,包括下载链接。
liunx通过配置文件的提供的地址去寻找yum的远端的指令仓库,查看配置文件:ls /etc/yum.repos.d/,CentOs-Base.repo就是Linux下的配置文件。

指令仓库中包含了各个软件的下载链接。

如果我们访问的yum源是国外的,我们访问起来速度会有点慢,因此我们可以更替换我们的Linux下的配置文件CentOs-Base.repo,更新我们的yum源。
1.更新yum源之前,需要备份当前的yum源,以便出现问题时可以恢复,可以通过以下命令备份:
cp /etc/yum.repos.d/Centos-Base.repo /etc/yum.repos.d/Centos-Base.repo.backup
2.下载新的yum源文件:这里下载的时阿里云镜像站点的Centos 7 yum源文件
wget -O /etc/yum.repos.d/Centos-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo

此时我们打开保存的yum源:vim Centos-7.repo,此时就更新了我们的yum源

3.清除yum缓存:更新yum源后,需要清除yum缓存,以便系统能够识别新的yum源文件
yum clean all
4.更新yum缓存:清除yum缓存后,需要更新yum源后,以便系统能够识别新的yum源文件中的软件包信息
yum makecache
5.测试新的yum源
yum list
我们该如何使用这个yum源呢?我们需要将这个yum源更改成我们的配置文件
mv Centos-7.repo CentOS-Base.repo
然后我们再以root身份执行:yum install -y epel-release,看看 cd /etc/yum.repo/有没有epel.repo文件

上面的CentOS-Base.repo是基础软件源,内部软件都比较成熟稳定,epel.repo是扩展软件源是新增的软件,待新增的软件稳定后移入到新增的软件基础软件源中,基础软件源再把一些淘汰的软件删掉,这样就能保持基础软件源的更新,同时新增的不稳定软件不放人基础软件源,保护基础软件源的安全。所以以后下载软件可以选择epel.repo。

vi/vim的区别简单点来说,它们都是多模式编辑器,不同的是vim是vi的升级版本,它不仅兼容vi的所有指令,而且还有一些新的特性在里面。例如语法加亮,可视化操作不仅可以在终端运行,也可以运行于x window、 mac os、 windows。我们课堂上,统一按照vim来进行讲解。
vim是一个多模式的编辑器,vim里面还有很多的字命令,来进行代码的编写操作。vim的三种模式(其实有很多模式,目前掌握这3种即可),分别是命令模式(command mode)、插入模式(Insert mode)和底行模式(last line mode),各模式的功能区分如下:
控制屏幕光标的移动,字符、字或行的删除,移动复制某区段及进入Insert mode下,或者到 last line mode。此模式下的输入都当作命令来看待,除非误触了模式切换的命令。
只有在Insert mode下,才可以做文字输入,按「ESC」键可回到命令行模式。该模式是我们后面用的最频繁的编辑模式。
文件保存或退出,也可以进行文件替换,找字符串,列出行号等操作。 在命令模式下,shift + :(等价于: )即可进入该模式。
要查看你的所有模式:打开vim,底行模式直接输入
:help vim-modes
这里一共有12种模式:six BASIC modes和six ADDITIONAL modes.
进入vim,在系统提示符号输入vim及文件名称后,就进入vim全屏幕编辑画面:
[正常模式]切换至[插入模式]
[插入模式]切换至[正常模式]
[正常模式]切换至[末行模式]
[末行模式]切换至[正常模式]
退出vim及保存文件,在[正常模式]下,按一下「:」冒号键进入「Last line mode」,例如:
插入模式
从插入模式切换为命令模式
移动光标
删除文字
复制
大小写快速切换
替换
撤销上一次操作
更改
跳至指定的行
「shift + 3 = #」:高亮要查找的多次使用的函数名(批量选中),「n」:下一个要查找的函数名
在使用末行模式之前,请记住先按「ESC」键确定您已经处于正常模式,再按「:」冒号即可进入末行模式。
列出行号
跳到文件中的某一行
查找字符
保存文件
离开vim
配置文件的位置

常用配置选项,用来测试
- inoremap ( ()
i "设置(自动补全 - inoremap [ []
i " 设置[自动补全 - inoremap { {}
i "设置{自动补全 - inoremap < <>
i " 设置<自动补全 - inoremap ' ''
i "设置' 自动补全 - inoremap " ""
i " 设置"自动补全 - set nu "设置显示行号
- set tabstop=2 "设置tab健的长度为2
- set ruler "设置标尺
- set ai "设置文本高亮
- set autoindent "设置自动缩进(与上一行的缩进相同)
使用插件
要配置好看的vim,原生的配置可能功能不全,可以选择安装插件来完善配置,保证用户是你要配置的用户,接下来:
在 shell 中执行指令(想在哪个用户下让vim配置生效, 就在哪个用户下执行这个指令. 强烈 "不推荐" 直接在 root 下执行):curl -sLf https://gitee.com/HGtz2222/VimForCpp/raw/master/install.sh -o ./install.sh && bash ./install.sh,需要按照提示输入 root 密码. 您的 root 密码不会被上传, 请放心输入.

参考资料
Vim从入门到牛逼(vim from zero to hero)
历史问题:当我们用sudo进行指令提权为什么还是不能执行?

我们可以想一下,我们上面对指令进行sudo提权,是输入自己的密码,输入自己的密码就可以执行root的指令,这样就会有很大的风险。所以我们在创建用户的时候默认在./etc/sudoers/下没有添加到白名单中,我们可以切换root的身份,vim打开./etc/sudoers/文件,在光标处下添加当前用户

然后我们再执行sudo进行指令提权

C语言使用gcc(推荐)/g++编译,C++只能使用g++编译。
格式 gcc [选项] 要编译的文件 [选项] [目标文件]
我们先来写一段代码,方便后续的观察


读者在此可使用选项“-c”就可看到汇编代码已转化为“.o”的二进制目标代码了

问题:拿汇编语言举例,先有的汇编编程语言还是先有的汇编程编译器?
这个问题似乎和先有蛋还是现有鸡的问题一样,其实不然,早期我们使用二进制写程序的,由于二进制写起来比较麻烦,所以出现了汇编语言,那么此时汇编语言由谁编译呢?肯定是汇编语言编译器,但是这个汇编语言编译器不会是用汇编语言写的,所以汇编语言编译器是用二进制去写的,汇编语言使用二进制编写的编译器去编译的,从而最后形成软件。编译器也是软件,后续就有人用汇编语言写了一个汇编语言编译器通过二进制编写的编译器去编译,最后才出现了我们的汇编语言编译器。对于先有的汇编编程语言还是先有的编程编译器,我们可以确定的是一定先有二进制编写的编译器。




动态库的优点:所有人都去网咖共享一台电脑,比较节省资源,不会出现太多的重复代码 --- 节省的是磁盘空间,内存和网络等资源。缺点:一旦网咖因为运营而关闭,所有人都不能使用了,对库的依赖性比较强,一旦库丢失,所有使用这个库的程序都无法运行。
静态库的优点:不依赖库,同类型平台中都可以直接使用。缺点:可执行程序体积比较大,比较浪费资源 --- 磁盘空间,内存和网络等资源。
gcc默认生成的二进制程序,是动态链接的,这点可以通过 file 命令验证。ldd可以查询一个可执行程序所依赖的库文件。

我们也可以通过指令对可执行程序进行静态链接:gcc -o mybin-static mytest.c -static

这里我们也可以发现静态链接形成的可执行程序大小非常大,静态链接是把库中得代码拷贝过来。

但是我们的云服务器默认是没有安装静态库的,所以我们执行:gcc -o mybin-static mytest.c -static 是会报错的
上面报错信息中ld是我们的链接器,-l是gcc的另外一个选项,c就是说明我们缺少一个c标准库,我们库的名称是去掉前缀lib和后缀.so.6。所以我们是需要安装我们的静态库的,执行指令:
sudo yum install -y glibc-static libstdc++-static
- -E 只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面
- -S 编译到汇编语言不进行汇编和链接
- -c 编译到目标代码
- -o 文件输出到 文件
- -static 此选项对生成的文件采用静态链接
- -g 生成调试信息。GNU 调试器可利用该信息。
- -shared 此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库.
- -O0 -O1 -O2 -O3 编译器的优化选项的4个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高
- -w 不生成任何警告信息。
- -Wall 生成所有警告信息。
我们先来创建一个makefile文件,然后vim打开makefile文件,输出内容:

然后我们在输入make指令就形成了可执行程序mybin。

这里通过一个小故事了解一下它们之间的关系:
月末了你已经没有生活费了,此时的你就会向你老爸要生活费,然后你向你老爸说了一句我是你的儿子,然后又说月末了我没有生活费了。对老爸说我是你的儿子这句话就是表面依赖关系,解释的是为什么我要帮你,月末了我没有生活费这句话就是具体的依赖方法,解释的是如何帮你。

如果我们再次输入make呢?

此时显示该文件不需要再编译,当前的mybin已经是最新文件了,我们并没有对源文件test.c进行过任何修改,所以源文件没有变化,我们的可执行程序再编译的结果是一样的,所以gcc也就不再编译了。如果我们改一下源文件,那么make会让我们再编译嘛?答案是可以滴。

这里提一个问题,make和makefile怎么知道当前文件时最新的呢?首先我们要清楚,如果这个源文件我们一直没有修改,就算过了一万年,他仍然是最新的,仍然是不可编译的,所以这个肯定是与我们现在所处的时间概念无关的。但是它肯定与时间有关,但是这个时间是对比出来的,常识告诉我们,改一个源文件就需要打开该源文件,修改之后该文件的修改时间就会被改变,只要可执行程序的最近修改时间比所有源文件最近的修改时间新,说明它就是最新的!
我们可以通过stat+filename查看文件的时间信息

当我们再次vim打开该文件进行修改,再次执行stat+filename

此时我们发现文件的Modify时间就发生了变化,然后我们再make指令编译一下源文件,再次执行stat+filename

我们可以发现mybin的时间永远是源文件test.c更新。通过stat我们可以查看到Modify时间,那么Change时间是什么呢?我们之前提到文件=内容+属性。Modify是对于文件内容而言的,而Change是对于文件属性来说的。通过ll指令查看的就是文件的属性:权限,拥有者和所属组等等。

通过给other去掉读权限,我们可以看到文件的Change时间发生变化

随后我们再对文件内容进行修改,此时会发生什么呢?

我们会发现此时的Modify时间改变了,但是Change时间也发生了改变,因为我们对源文件修改的时候,可能增加或者删除了代码,此时我们文件的大小就可能发生变化,所以Change时间也发生了改变。我们再来了解一下Access时间,它是文件的访问时间。

然后我们多次执行访问文件,我们会发现Access时间没有改变,这是因为Access不是实时更新的,而是访问到一定的次数或者是在系统内部做了一定的访问操作,当系统认为可以时间可以更新便会自动更新。

结论:关于对比可执行程序和我们的源文件哪一个更新的时候,我们通常以Modify时间为准。
如果我们想在不改变源文件内容的时候,我们可以通过修改文件的Modify时间去让make能够重新编译。我们可以使用touch命令,如果文件不存在,touch帮我们创建文件,如果文件存在,touch就会帮我们更新文件。

我们还可以打开vim mytest.c,然后将它设置为伪目标,用 .PHONY 修饰,伪目标的特性是,总是被执行的。

此后我们再输入make编译,此时就不再显示文件是最新的而不可被编译

不过我们一般不将我们的目标文件设置成伪文件,而是将clean设置成伪文件,因为清理工作总是要执行的。

我们也可以将上面的makefile这样写:@表示目标文件,^表示源文件列表。

或者也可以这样写,makefile是支持变量定义的,我们可以将它理解为宏,完成的任务是替换。

这里我们还需要了解一个问题mybin是直接依赖我们的test.c吗?根据我们上面提到滴,并不是,我们写的代码test.c需要经过预处理、编译、汇编和链接才可以形成可执行程序mybin。

上面的这个依赖关系和依赖方法是类似于栈的,依赖关系先依次入栈,然后再出栈于相应的依赖方法相匹配。
1. make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
2. 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“mybin”这个文件, 并把这个文件作为最终的目标文件。
3. 如果mybin文件不存在,或是mybin所依赖的后面的test.o文件的文件修改时间要比mybin这个文件新(可以用 touch 测试),那么,他就会执行后面所定义的命令来生成mybin这个文件。
4. 如果mybin所依赖的test.o文件不存在,那么make会在当前文件中找目标为test.o文件的依赖性,如果找到则再根据那一个规则生成test.o文件。(这有点像一个堆栈的过程)
5. 当然,你的C文件和H文件是存在的啦,于是make会生成 test.o 文件,然后再用 test.o 文件声明 make的终极任务,也就是执行文件mybin了。
6. 这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。
7. 在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错, 而对于所定义的命令的错误,或是编译不成功,make根本不理。
8. make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起, 我就不工作啦。
怎么清理呢?vim打开Makefile

即命令——“make clean”,以此来清除所有的目标文件,以便重编译。

我们再来改一下vim里面的顺序

1.Makefile和make形成目标文件的时候,默认是从上到下扫描makefile文件的,默认形成的是第一个目标文件。
2.有多个依赖关系和依赖方法,默认只形成一个。
首先我们将makefile和源文件搭建好

然后我们验证一下我们写的是否有问题

然后我们验证两个代码,观看它们的现象。
- #include
- int main()
- {
- printf("hello Makefile!\n");
- sleep(3);
- return 0;
- }
我们可以发现上面代码的运行结果是先输出printf,然后等待三秒

- #include
- int main()
- {
- printf("hello Makefile!");
- sleep(3);
- return 0;
- }
我们发现上代码的运行结果是先等待三秒,然后再输出printf

但是我们不要被上面的现象所迷惑,C语言代码是遵守顺序结构的,所以我们可以知道上面的代码永远是先执行printf("hello Makefile!");然后在执行sleep(3);但是为什么会出现上面的想象呢?

这是因为我们的C语言有一个缓冲区,当sleep没有执行完的时候,printf的字符串被保存到缓冲区了,当sleep执行完的时候,此时就要求刷新缓冲区,于是此时才输出printf语句,而第一个代码直接输出是因为'\n'字符会立刻刷新缓冲区,于是就先输出了printf语句。
- #include
- int main()
- {
- printf("hello Makefile!");
- fflush(stdout);
- sleep(3);
- return 0;
- }
我们可以通过手动刷新缓冲区,先输出printf内容。


所以准备工作已经完成了,现在开始写我们的进度条小程序。

但是此时我们发现make编译后运行我们的程序没有任何结果

因为我们上面的printf内容被保存到缓存区了,所以没有输出我们想要的结果,所以我们就要使用fflush进行强制刷新缓冲区。

然后我们再运行我们的代码。

这样就实现了我们的倒计时小程序,不过我们上面只能从9倒计时,如果我们想从10倒计时呢?

我们发现我们的程序不能完成从10倒计时,输出的时候后面多了一个0,为什么呢?

当我们向显示器打印10的时候,打印的是字符'1'和'0',当我们后面打印的时候,字符只依次覆盖'1'字符,并没有覆盖'0'字符。怎么解决呢?-2d:表示输出这个整数的时候预留两个字符的空间并左对齐输出这个整数

输出结果:

现在我们就来写一下我们的进度体条小程序,首先学习一下usleep函数,按照微秒的时间进行休眠。

我们先来建立一下多个文件并写上一个简单的打印输出工作


然后我们先验证一下make能否编译

接下来就可以写我们的进度条了。

我们的程序输出的进度条主要在processbar.c中实现的,字符数组设置为102是因为在我们的程序中,循环共循环了101次,bar[100]='#',如果设置数组大小101,此时下标最大就是100,而100下标处存放了bar[100]='#',没有位置存放'\0',这样程字符串就没有结束的标识,程序就达不到预期的结果。这里要注意最后一次,下标的位置是bar[100]='#',那从0开始到100不就有101个'#'符号吧吗?并不是,我们自己看我们的程序,当下标为0的时候,我们的程序时不打印'#'的,下标为1的时候才打印'#'的。
- #include
- using namespace std;
- int main()
- {
- int cnt = 0;
- char bar[5];
- memset(bar, '\0', sizeof(bar));
- while (cnt <= 3)//下标为0,1,2,3
- {
- printf("[%s]", bar);
- bar[cnt++] = '#';
- }
- return 0;
- }
运行结果:

所以此时小程序下标就是100,即可输出100个字符'#",结果运行:

但是我们平常的进度条不是#,我们期待能出现一个箭头的形式,我们可以在0下标处设置为字符'<',后续用字符'='覆盖前面的字符'<',我们来看一下程序的运行结果:

我们发现上面的程序输出了101个字符,我们期待最后一个字符应该是'=',我们来看一下我们的程序,当i=100的时候,此时bar[100]='>',循环再次进入的时候,最后一个元素输出是'>',然后i++,bar[100]位置被修改为'=',然后i++变为101,此时不再进入循环,虽然bar[100]位置被修改为'=',但是此时没有执行打印输出工作,所以最后一个仍然是'>'字符。
- #include
- using namespace std;
- int main()
- {
- int cnt = 0;
- char bar[6];
- memset(bar, '\0', sizeof(bar));
- bar[0] = '>';
- while (cnt <= 3)//下标是0,1,2,3
- {
- printf("[%s]", bar);
- bar[cnt++] = '=';
- if (cnt <= 3)
- bar[cnt] = '>';
- }
- return 0;
- }
运行结果:

监视窗口可以看到bar最后一个元素是'=',只不过输出的时候没有打印出来。


因此我们可以修改一下我们的程序,当i<100的时候就不要再执行修改'>'了。

但是实际上进度条并不是一个独立的程序,而是依附于其他应用的,比如我们下载一个文件就要进度条,进度条应该表现为下载量除以文件大小。接下来我们模拟一个下载的场景。

运行结果:

因此我们可以改善一下进度条。



上面的程序我们使用了我们的函数指针,通过回调函数可以调用函数,并且我们修改旋转光标不和rate进度条相关,并且给printf输出加上颜色打印,使我们的进度条更加美观。
进度条:

本章结束啦!!!