赞
踩
目录
三、Linux项目自动化构建工具-make/Makefile
vim仅仅是一个编辑器,就是只能编写代码。
正常/普通/命令模式(Normal mode)
控制屏幕光标的移动,字符、字或行的删除,移动复制某区段及进入Insert mode下,或者到 last line mode
插入模式(Insert mode)
只有在Insert mode下,才可以做文字输入,按「ESC」键可回到命令行模式。该模式是我们后面用的最频繁的编辑模式。
底行模式(last line mode)
文件保存或退出,也可以进行文件替换,找字符串,列出行号等操作。 在命令模式下,shift+: 即可进入该模式。要查看你的所有模式:打开vim,底行模式直接输入vim是一款多模式的编辑器
vim刚打开时的模式为命令模式,
输入i的时候,就变成了插入模式,就可以愉快编写代码了。
这时我们再按一个esc就能重新回到我们的命令模式。
退出vim的话需要在命令模式的情况下按下"shift"+":",进入到我们的底行模式。
如果想要插入模式进入底行模式,需要先esc进入命令模式,再i,进入插入模式
: w (保存当前文件)
: wq (输入「wq」,存盘并退出vim)
: q! (输入q!,不存盘强制退出vim)(感叹号就是强制退出的意思)
这里我们创建一个mycode.c的文件,然后使用vim mycode.c打开我们的文件。
输入i转换为insert模式之后,输入我们的代码,然后按下esc,转换为命令模式,再按下shift+:,进入底行模式,输入wq,存盘并且退出
使用cat mycode.c就可以查看我们刚刚编写的代码了。
yy:将当前光标所在的行赋值
p:将我们对应的复制行进行粘贴
u:撤销
ctrl+r:撤销u操作
同时yy和p也支持nyy或者np,这里的n表示具体的数字。yy100p就是复制,然后粘贴100次。10yyp就是以下十行复制,然后粘贴一次
shift+g:直接将光标定位文件的结尾
gg:按两下g光标直接移动到文件的最开始
n+shift+g:n为具体的数组,能够让光标定义到文件的第n行
shift+4($):直接将光标移动到当前行的末尾
shift+6(^):直接将光标移动到当前行的首位置。
w:以单词为单位向后移动(nw向后移动n个单词)
b:以单词为单位向前移动(nb向前移动n个单词)
最早的老式键盘没有上下左右键,所以使用hjkl进行移动
h:光标向左移动一个字符
j:光标向下移动一行
k:光标向上移动一行
l:光标向右移动一个字符
dd:将当前光标所在的行进行剪切或者删除,支持ndd,删除n行,如果这时候再p以下,就会粘贴刚刚删除掉的内容
shift+~:大小写切换
shift+r:进入替换模式
r:替换光标所在的字符,支持nr,4rw就是将当前位置的后四个字符全部变成w
x:从左向右删除
shift+x:从右向左删
set nu:开启索引行
set nonu:关闭索引行
ctrl+ww或者control+ww(mac):切换到另一个屏幕
vs 文件名:分屏操作
vs mycode2.c左边是我们的mycode2.c,右边是mycode.c
不退出vim执行对应的命令
:!cmd:之后加上对应的命令就可以执行对应的命令
1.我们不关心,因为它比较麻烦
2.需要有一个不错的配置,需要了解一下vim配置的原理
推荐直接使用下面的vim配置 ,在终端输入下面的代码
curl -sLf https://gitee.com/HGtz2222/VimForCpp/raw/master/install.sh -o ./install.sh && bash ./install.sh
操作完之后输入下面的代码 让配置生效
sourse ~/.bashrc
接着我们打开的vim就是下面的样子了。
程序翻译的过程
文本的c-计算机二进制
1.预处理(a.去注释 b.宏替换 c.头文件展开 d.条件编译……)
2.编译(c-->汇编)
3.汇编(汇编-->可重定向二进制目标文件)
4.链接(链接->将多个.o,.obj->合并形成一个可执行).exe
gcc/g++需要遵守机器翻译的顺序。
首先使用esc进入命令模式
然后按control+v(mac)
ctrl+v(windows)
成功进入我们的视图模式V-BLOCK(virsual block)
然后使用hjkl来选中我们想要选中的区域
h向左,j向下,k向上,l向右
扩充或者减少我们的选区
按下shift+a也就是A,也可以先点击capslock再点击一下A,这是又会被切换为insert模式
光标移动到行首,输入//
再按下键盘左上角的esc键,就发现刚刚我们标记的那几行全部都被注释了
按住ctrl+u重新进入我们的视图模式,然后使用hjkl,选中我们想要取消注释的区域,注意,只需要像下面一样,将注释的//全部选中即可
输入d删除我们刚刚的注释
:s(substitute)命令用来查找和替换字符串。
语法:
:{作用范围}s/{目标}/{替换}/{替换标志}
我们想将下面的Makefile中的exec全部替换成myshell
按左上角的esc,进入命令行模式,然后再按:进入底行模式
这里我们就是将exec全部替换为myshell,最后一个参数g是global的意思,也就是全局的exec全部都替换掉
%s/exec/myshell/g
然后按下回车,进行全部替换
这样就全部将exec替换成了myshell
选区,在Visual模式下选择区域后输入:,
(进入视图模式,control+v(mac),ctrl+v(windows))
Vim即可自动补全为 :'<,'>。
选中要替换的范围
:'<,'>s/Shells/helloworld/g
同样是在视图模式下
2-11行 :2,12s/helloworld/lalala/g
0 : 数字零, 移动到行开头
^ : 到本行第一个不是空字符的位置(所谓空字符就是空格, tab,换行,回车等)
$ : 到本行行尾
g_ (字母g+下划线): 到本行最后一个不是blank字符的位置
NG : 到第 N 行 (注意命令中的G是大写的)
gg: 到第一行。(相当于1G,或 :1)
G: 到最后一行。% : 匹配括号移动,包括 (, {, [. (需要把光标先移到括号上)
* 和 #: 匹配光标当前所在的单词,移动光标到下一个(或上一个)匹配单词
(*是下一个, #是上一个)
/shell: 搜索 shell 的字符串(如果搜索出多个匹配,可按n键到下一个)
(按住n切换下一个)
gcc只能用来编译c语言,g++可以用来编译c语言,也可以编译g++
一般的Linux都会内置gcc,g++则不一定,使用下面的代码就可以安装
sudo yum install -y gcc-c++
使用下面的代码可以查看gcc/g++的版本
gcc -v
g++ -v
先编写我们的程序
gcc hello.c -o zhuyuan
在上面的代码中会直接生成我们编译完成的文件,但是我们应该如何查看分布的编译过程呢?(上面代码就是使用gcc编译我们的hello.c文件,并且将生成的文件写入名为zhuyuan的文件中)
-E:从现在开始进行程序的翻译,如果预处理完成,就停下来
-o:将我们生成的文件存入mytest.i中
gcc -E hello.c -o mytest.i
使用vim打开我们预处理过之后的代码,我们发现我们的代码变长了很多
预处理:相当于根据预处理指令组装新的C/C++程序。经过预处理,会产生一个没有头文件(都已经被展开了)、宏定义(都已经替换了),没有条件编译指令(该屏蔽的都屏蔽掉了),没有特殊符号的输出文件,这个文件的含义同原本的文件无异,只是内容上有所不同。
使用下面的代码进行汇编文件
-S:从现在开始进行程序的翻译,如果编译完成,就停下来。
gcc -S mytest.i -o mytest.s
编译:将预处理完的文件逐一进行一系列词法分析、语法分析、语义分析及优化后,产生相应的汇编代码文件。编译是针对单个文件编译的,只校验本文件的语法是否有问题,不负责寻找实体。
-c:从现在开始进行程序的翻译,如果汇编完成,就停下来
gcc -c mytest.s -o mytest.o
mytest.o是可重定位目标文件,就是在经过汇编之后,我们的文件已经变成了一个二进制文件。但是当前的二进制文件是无法运行的。
汇编:将我们的汇编语言翻译为机器才知道怎么运行的二进制文件
执行下面的代码,进行链接操作,链接进行了什么呢?就是将我们所写的代码与它所调用的库文件连接起来。
gcc mytest.o -o mytest
链接:通过链接器将一个个目标文件(或许还会有库文件)链接在一起生成一个完整的可执行程序。 链接程序的主要工作就是将有关的目标文件彼此相连接,也就是将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。在此过程中会发现被调用的函数未被定义。需要注意的是,链接阶段只会链接调用了的函数/全局变量,如果存在一个不存在实体的声明(函数声明、全局变量的外部声明),但没有被调用,依然是可以正常编译执行的。
将我们上面的操作的选项连起来就是ESc,我们的程序后缀依次对应的是iso,可以使用我们键盘左上角的esc键来方便记忆,iso可以通过镜像文件的后缀来辅助记忆。
-E 只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面
-S 编译到汇编语言不进行汇编和链接
-c 编译到目标代码
-o 文件输出到 文件
-static 此选项对生成的文件采用静态链接
-g 生成调试信息。GNU 调试器可利用该信息。
-shared 此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库.
-O0
-O1
-O2
-O3 编译器的优化选项的4个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高
-w 不生成任何警告信息。
-Wall 生成所有警告信息
使用下面代码可以查看程序链接的库
ldd mytest
其中lib64/libc.so.6也就是我们c语言的动态库
使用下面的代码可以查看我们程序的相关文件
file mytest
我们的C程序中,并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实“printf”函数的呢?
最后的答案是:系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函数“printf”了,而这也就是链接的作用
函数库一般分为静态库和动态库两种。
静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为“.a”
动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为“.so”,如前面所述的 libc.so.6 就是动态库。gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件,如下所示。gcc hello.o –o hello
gcc默认生成的二进制程序,是动态链接的,这点可以通过 file 命令验证
使用下面的代码可以查看stdio.h的文件
头文件提供c语言的方法列表,方法声明
vim /usr/include/stdio.h
当然也可以仅仅是查看一下而不打开
ls /usr/include/stdio.h
在include下面可以看到我们的全部的头文件
ls /usr/include
#下面的c语言库提供c语言的方法实现
ls /lib64/libc-2.17.so -l
以上的库和文件和我们自身写的文件结合在一起就形成了我们的exe文件
在Linux中以.so结尾的动态库,以.a结尾的为静态库
在windows中以.dll结尾的为动态库,以.lib结尾的为静态库
在运行时需要跳转到库中进行运行,在编译时需要将库中的代码(地址链接到我们写的文件)连接到我的自身的写的代码的就称为动态库 ,如果这个动态库没了,我们的代码就跑不动了
打个比方:我们打游戏需要去网吧,有一个朋友告诉你网吧的位置,你就去那个地方的网吧上网,这就好比是链接,网吧就好比是我们的动态库,有一天,这个网吧被查封了,我们就没办法打游戏了,也就是没办法跑代码了。
在运行的时候不需要依赖我们的库,将库中我们要的方法拷贝到我们的运行程序中,这个库就称为静态库,如果这个静态库没了,我们的代码依旧可以跑
打个比方:我们自己买了一台电脑,当网吧关了之后,我们不需要再去网吧,我们也能打电动,
总结:
动态链接的程序非常依赖动态库,静态链接的程序就不怎么依赖静态库。
静态库的优点就是运行不依赖静态库。但是,如果我们系统中有100个文件,都使用了c语言中的printf函数,那么就会造成大量的重复代码,就会非常占用我们的系统资源。
动态链接的缺点是运行时依赖动态库,并且相对静态库占有系统资源较少
gcc默认使用动态连接(我们查看到使用ldd之后查看我们之前生成的代码,它所链接的库是以.so结尾的,所以链接的是一个动态库)
(注意下面mytest后面的-d或者是-s是和mytest连在一起的,没有空格,只是起了不同的名字而已,不是调用不同的选项)
gcc hello.c -o mytest-d
-static:表名使用静态链接的方式生成我们的程序(从下面的测试代码的运行结构看,statically linked就说明是静态链接的)
gcc hello.c -o mytest-s -static
动态链接必须使用.so动态库文件
静态库链接必须使用.a静态库文件
一般我们的云服务器没有安装静态库,我们可以使用下面的代码进行安装
#安装c语言的静态库 sudo yum install -y glibc-static
#安装c++的静态库 sudo yum install -y libstdc++-static
make是一个命令
makefile是一个文件
一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建
我们编写makefile的目的是为了编写我们的项目,我们编写这个相许需要用到依赖关系和依赖方法
a.依赖关系
例子:月底,你生活费没了,你的目的是要钱。你打电话给你爸,说爸我是你儿子,然后挂断电话,在这个过程中你对你爸就是依赖关系,但是你并没有说你要钱,所以你没有要到钱。
b.依赖方法
例子:你打电话给你爸,说说爸我是你儿子,我没钱了,你爸打给你钱,你要到钱了,这就是依赖方法。
使用touch makefile在我们的当前目录创建一个Makefile文件
第一行为我们的依赖关系
hello:hello.c
第二行为我们的依赖方法
(切换到第二行的时候一定要按下tab键)
gcc -o hello hello.c
用.PHONY修饰的clean称为伪目标,就是说在输入make的时候并不会马上就执行
只有输入make clean才会将我们的hello清除掉
makefile只会从上到下扫描到的第一个目标
make clean就是执行clean对应的方法
什么是伪目标?
伪目标总是被执行的
.PHONY修饰的方法就是在调用make的时候,每一次先不会执行。
总是会依据依赖关系,执行依赖方法
我们习惯总是给clear带上.PHONT修饰
对于上面的操作,我们还可以使用先touch mytest.c,然后直接使用ls >Makefile的形式将我们的文件名导入到Makefile当中
makefile是如何知道我们的文件的新的还是老的?
是根据我们文件的修改时间来知道的
当我们再进行一次make的时候,我们发现linux提示我们我们的mytest已经是最新的了
stat test.c
Access:代表最近文件被访问的时间,只有累计到一定的时间才会发生修改。因为我们对于我们的读取是比较频繁的操作,如果我们一直实时更新,就会占用大量的系统资源。
Modify:代表文件被修改的时间
Change:文件=内容+属性,Modify代表的是内容的改变,Change代表的是属性的改变,例如就是我们文件权限的改变。
生成可执行的文件一定比源文件生成的时间更晚,如果我们源文件的修改时间比可执行文件晚就代表这是我们的可执行文件并不是最新的。如果我们的可执行文件的修改时间比我们的源文件更晚,就代表着此时我们的可执行文件是最新的。
首先创建一个Makefile文件,写入以下代码,其中第一行是说明我们的mytest文件依赖于mytest.cpp
第二行是用g++编译我们的mytest.cpp,并且将生成的文件放到mytest中。用.PHONY指明在每一次make的时候不执行clean,只有当make clean的时候再执行清理操作。
保存过makefile文件之后,编写好我们的mytest.cpp文件,然后在终端make一下,再输入./mytest,就能执行我们的c++代码了。
这里我们写一个小程序来实现我们多个文件之间的代码编写
这是我们的再test.h文件中写的内容。
这是我们的test.c中声明的内容
这是我们在main.c中声明的内容
按照我们的老方法,我们可以这样编写我们的代码
实际上结合我们的链接,我们可以使用makefile来实现我们刚才的内容
我们其实当编译过后就不再需要关注头文件了,所以我们需要将我们编译过之后的xxx.o文件也就是main.o和test.o再加上我们的动态库文件libc.so合并链接就可以形成我们的可执行文件了。
在我们的Makefile中声明下列依赖关系。注意我们第一行中的test.o和main.o并不是一开始就有的,我们的编译器找不到这两个文件就会继续向下查找,直到返现我们的test.o是从我们的test.c文件编译过来的,我们的main.o文件是从我们的main.c文件编译过来的。就相当于是一个栈的结构,没有找到该结构就入栈,找到了对应的结构就出栈。
这时,我们再make一下,就会自动生成我们的项目文件。
当然我们还可以编写我们的清理代码,在下面的代码中,我们将所有的.o结尾的文件全部都删除,并且最后将我们的执行文件mytest也一并删除。
使用make clean就能够清理掉我们全部的.o结尾的文件么mytest
首先我们可以设定我们Makefile中的依赖关系。这里我们要编译的文件为proc.c
我们写一个proc文件,并且编写一个简单的hello world来确定我们的makefile依赖关系是正确的。
ok我们发现我们的程序被正确的执行了。所以以后我们在编写代码的时候大多采用这种方式,只要在声明了正确的依赖关系之后,只需要make一下就能够生成我们的项目,make clean以下就能将我们生成的多余文件清理掉。
下面我们就正式进行我们小练习二的编写
这里我们引入unistd.h头文件,导入我们的sleep方法
我们再运行我们刚刚的代码,就是先make编译一下,然后再./proc.c执行我们的可执行文件,我们就会发现我们的hello world打印出来过了一秒钟之后我们才结束我们的代码的运行
那如果我们的\n去掉呢?
这里我们将\n去掉之后我们发现等了几秒我们的打印的内容才出现。为什么我们的程序会出现先等待再输出的现象呢?
这是因为我们的printf早就已经执行完来了,但是信息没有被立马显示出来。
C语言是会给我们提供输出缓冲区的,根据特定的刷新策略,来进行刷新。我们的printf打印出来的内容被放到了输出缓冲区里面(就是c语言给我们提供的一段内存空间)
显示器设备,一般的刷新策略是行刷新,就是碰到\n,就把\n之前的全部字符都给显示出来。
如果我们需要立即刷新的话需要使用fflush指令
fflush(stdout);l立刻将我们的缓冲区中的内容刷新到屏幕上
使用了上面的代码之后我们就会发现我们的hello world先被打印了出来,然后等待了几秒,我们的程序才运行结束。
(stdin stdout stderr是我们的三种流,其中stdout是将其内容输出到屏幕上)
那如果我们把/n换成/r呢?
再次执行我们的程序我们会发现我们的hello world在被打印出来了之后我们的光标又回到了这一行的开始,然后我们的hello world就被覆盖掉了。这是怎么回事呢?我们在此就需要了解回车和换行之间的区别
屏幕录制2022-07-27 19.14.36
回车换行是一回事吗?
回车和换行并不是一个概念。
换行:一行写完再新启一行比方说我们从一行五列,换行到了二行五列\n
回车:将我们写入的位置回到最开始叫做回车,比方说从二行五列回到了二行一列叫做回车。\r
现在我们的c语言中的\n已经被当成了回车换行了。
因为我们每一次打印都只是执行了\r也就是回车,并没有将我们输出结果从缓冲区中输出,仅仅是将我们的光标移动到了初始的位置,所以没有任何东西
fflush(stdout)
利用上面的这种特性,我们就可以编写一个代码实现进度条。在下面的代码中我们使用buf作为我们整根进度条的长度,然后使用sign作为我们旋转的光标
然后从0到100遍历,每次我们都将原来数组中的后一个位置的空白变成#,然后指定我们打印的类型,%-100s就是左对齐的一百个字符位用来打印字符串,%d%%表示的是我们进度条的进度打印使用小数,sing[i%4]就是轮换着从我们光标旋转的图表中选取。
fflush就是将我们缓冲区中的内容打印到屏幕上。
usleep的睡眠时间是以微妙为单位的。下面的时间就是0.1秒打印一次。
屏幕录制2022-07-27 19.36.30
为什么我们在敲代码的时候会自动出现匹配项?
是因为我们在敲代码的时候我们的vim自动地在c语言的头文件中检索你包含的头文件中有没有以它开头的。就依据你所写的代码的头文件展开的,从而能够实现代码的匹配。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。