目录
一、yum
1.了解yum
(1)RPM
(2)yum
2.yum使用
(1)查看软件包
(2)安装软件
(3)卸载软件
二.Linux编辑器-vim
1. vim概念
(1)命令模式
(2)插入模式
(3)底行模式
2.vim模式切换
(1)命令模式切换成插入模式
(2)命令模式切换成底行模式
(3)插入模式和底行模式切换为命令模式
3.命令模式操作
(1)移动
(2)翻页
(3)删除
(4)复制粘贴
(5)剪切
(6)替换
(7)撤销
(8)更改
(9)跳转行
4.底行模式操作
(1)列出行号
(2)跳到某行
(3)查找字符
(4)拷贝文件
(5)保存文件
(6)退出vim
5.配置vim
(1)公共配置
(2)用户私有配置
三、Linux编译器-gcc/g++
1.gcc/g++作用
2.gcc/g++编译过程
(1)预处理--选项-E 生成 .i
(2)编译-选项-S 生成.s
(3)汇编-选项-c 生成.o
(4)链接-选项-o 生成可执行程序
3.静态库和动态库
(1)静态库
(2)动态库
四、gdb调试
1.gdb简介
2.gdb调试
(1)r运行程序
(2)start启动程序
(3)l列出代码
(4)b设置断点
(5)disable禁用断点
(6)delete删除断点
(7)info b查看断点信息
(8)s逐语句执行
(9)n逐过程执行
(10)display显示跟踪查看变量值
(11)undisplay取消跟踪显示变量值
(12)finish退出函数
(13)info查看当前设置了哪些断点
(14)continue让函数从当前位置一直执行至下一个断点处
(15)until跳至某行
(16)bt调用堆栈
五、make/Makefile
1.了解make/Makefile
(1)make
(2)Makefile
2.如何写一个Makefile
六、进度条小程序
1.回车和换行
2. 实现进度条小程序
linux下的软件安装一般有以下3种:
· 源码安装,下载源代码,编译好后,生成可执行文件进行安装(会有点麻烦,毕竟需要解决编译能否通过的问题)。
· rpm安装,rpm已经编译好,打包好,直接安装即可
· yum安装
以上的第二种和第三种安装方法有什么区别呢?
在了解yum之前,先了解RPM,RPM在linux中是一种用于互联网下载包的打包及安装工具,它能够进行打包、安装、查询、升级、卸载、校验、数据库管理。一个RPM包中的应用程序,除了自身所带的附加文件保证其正常以外,还需要其他特定版本文件,这就是软件包的依赖关系,程序与程序之间的依赖关系比较复杂,而RPM无法解决软件包的依赖关系。
yum(Yellow dog Updater, Modified)是Shell前端软件包管理器,即linux下进行软件安装的客户端,能够从服务器自动下载RPM包并安装,能够自动解决RPM所面临的软件包依赖关系,并且一次安装所有依赖的软件包,无须繁琐地一次次下载、安装,能够更方便地添加、删除、更新RPM包,便于管理系统更新。
yum在服务器端存有所有的RPM包,并把各个包之间的依赖关系记录在文件中,当使用yum安装RPM包时,yum会先从服务器端下载包的依赖,通过分析依赖文件从服务器端一次性下载所有相关的RPM包并进行安装。
这就好比手机应用市场APP是一个客户端,服务器上有手机应用市场App对用的应用服务,当用户搜索某款应用时,应用市场APP是作为客户端就会把请求发送到对应的应用服务,应用服务就会把结果返回给用户。当用户下载某款应用时,应用市场APP就会把下载请求发送给应用服务,让应用服务把软件下载下来发给用户,下载完成之后再安装。
同样,yum也是安装在linux上的一个客户端,在服务器上找到找到yum对应的服务,并且把软件信息下载下来,而且会根据该软件对应的依赖关系把相关软件下载下来并安装好。
yum要从服务器下载RPM包及其依赖,因此所有操作必须联网。可用通过ping命令ping百度来判断是否连上互联网,像如下状态,网络正常:
且同一时刻同一服务器只允许安装一个软件,因此用yum安装软件包只能一个一个进行安装。
命令:
yum list
用该命令可以罗列出当前有哪些软件包:
软件包很多,可以结合grep命令过滤:
对于yum列表:
· 软件包名称: 主版本号.次版本号.源程序发行号-软件包的发行号.主机平台.cpu架构.
· "x86_64" 后缀表示64位系统的安装包, "i686" 后缀表示32位系统安装包. 选择包时要和系统匹配.
· "el7" 表示操作系统发行版的版本. "el7" 表示的是 centos7/redhat7. "el6" 表示 centos6/redhat6.
· 最后一列, base 表示的是 "软件源" 的名称, 类似于 "小米应用商店", "华为应用商店" 这样的概念.
安装软件必须以root权限进行软件安装,命令:
sudo yum install 软件名
sudo是以root权限使用yum进行软件安装:
假如使用sudo安装软件,出现以下情况:
这说明创建了delia用户以后,没有对应修改sudoers文件或者group文件,可以按照如下方式修改sudoers文件:
① 以root用户身份打开/etc/sudoers文件
再按Esc键,输入wq!保存退出。此时再去执行yum的安装命令就OK了。
安装的lrzsz这个软件包有什么用呢?
lrzsz是一款在linux里可代替ftp上传和下载的程序。其中,rz、sz是Linux/Unix同Windows进行ZModem文件传输的命令行工具:
上传windows文件到云服务器,命令:
rz -E
如:
将云服务器文件下载到windows,命令:
sz 文件名
同样卸载软件也要在root权限下执行卸载软件命令:
sudo yum remove 软件名
如要卸载lrzsz软件,需要输入'y' 进行确认:
提示successed和Complete,表明卸载成功:
vim是从vi发展出来的文本编辑器,不过vim是vi的升级版本,它不仅兼容vi的所有指令,还有新特性,比如语法加亮,可视化操作,不仅可以在终端运行,也可以在x window、 mac os、windows上运行。
vim主要有3种模式:命令模式、插入模式、底行模式,这三种模式可以切换,每次切换,文本最下面一行就有不同的模式。
控制屏幕光标的移动,字符、字或行的删除,可以移动、复制、剪切、粘贴文本。
只有在插入模式下才可以输入文字,编辑时使用较频繁。
保存文件、退出文件、替换文件,找字符串,列出行号等操作。可使用vim help-modes查看当前vim的所有模式。
使用命令:
vim 文件名
比如:
进入vim命令模式,同时也是全屏幕编辑画面:
现在还不能编辑file.c文件,因为必须在插入模式下才能编辑文件。
命令模式切换成插入模式有i、a、o 三种:
i:从光标当前位置开始输入文件
a:从光标所在位置的下一个位置开始输入文字
o:插入新的一行,从行首开始输入文字
输入英文冒号:
:
输入键盘Esc:
Esc
在按「Esc」后,切换到命令模式,才能做以下操作 :
移动光标有以下多种常用操作:
「h」:左
「l」:右
「k」:上
「j」:下
「^」:移动到光标所在行的“行首”
「$」:移动到光标所在行的“行尾”
「w」:光标跳到下个字的开头
「e」:光标跳到下个字的字尾
「b」:光标回到上个字的开头
「nl」:光标移到该行的第n个位置,如:5l,56l
[gg]:跳到第一行行首
「G」:跳到最后一行行首
[shift+g]:进入文本末端
翻页有以下常用操作:
「ctrl+b」:上翻一页
「ctrl+f」:下翻一页
「ctrl+u」:上翻半页
「ctrl+d」:下翻半页
删除文本有以下几种常用操作:
「x」:每按一次,删除光标所在位置的一个字符
「nx」:例如,「6x」表示删除光标所在位置的“后面(包含自己在内)”6个字符
「X」:大写的X,每按一次,删除光标所在位置的“前面”一个字符
「nX」:例如,「20X」表示删除光标所在位置的“前面”20个字符
「dd」:删除光标所在行
「ndd」:从光标所在行开始删除#行
复制粘贴文本有以下几种常用操作:
「yy」:复制光标所在行到缓冲区
「nyy」:例如,「6yy」表示拷贝从光标所在的该行“往下数”6行文字
「yw」:复制将标所在之处到行尾的字符到缓冲区中
「nyw」:复制n个字符到缓冲区
「p」:将缓冲区内的字符粘贴到下一行
「np」:将缓冲区内的字符粘贴到下n行
注意:所有与“y”有关的复制命令都必须与“p”配合才能完成复制与粘贴功能
剪切文本有以下几种常用操作:
dd:剪切光标所在行
ndd:剪切从光标所在行开始往下的n行
p:将剪切的字符粘贴到下一行
np:将剪切的字符粘贴到下n行
替换文本有以下几种常用操作:
「r」:替换光标所在处的字符。
「R」:替换光标所到之处的字符,直到按下「ESC」键为止。
撤销有以下常用操作:
「u」:误执行一个命令时,马上按「u」,回到上一个操作。按多次“u”可以执行多次恢复。
「ctrl + r」: 撤销恢复
更改有以下常用操作:
「cw」:更改光标所在处的字到字尾处
「cnw」:例如,「c3w」表示更改3个字
跳转行有以下常用操作:
「ctrl」+「g」列出光标所在行的行号。
「nG」:例如,「15G」,表示移动光标至文章的第15行行首
在按「Esc」后,按「:」进入底行模式,才能做以下操作:
「set nu」:列出所有行行号
「set nonu」:隐藏所有行行号
「n」:n代表第n行,输入n,再按「enter」就跳转到第n行了
「/字符」:先输入/,再输入字符,再按enter,就会高亮显示该字符,如查找到多个字符,可以按键盘「n」向后跳转,光标会处于查找之前的行
「?字符」:先输入?,再输入字符,再按enter,就会高亮显示该字符,如查找到多个字符,可以按键盘「n」向后跳转,光标会处于第一行行首
取消查找,底行模式:
:/nohl
「vs 文件名」:双屏编辑同一文件
「ctrl+w+w」:光标在两个双屏文件中跳转
「w」:在冒号后面输入w
「q」:退出
「q!」:强制退出vim
「wq」:保存后退出vim
「wq!」:保存后强制退出vim
vim的系统公共配置文件位于/etc下,名为vimrc, 对所有用户都有效:
/etc/vimrc
如切换为root后, 在/etc下就能看到vimrc文件:
每个用户的主目录,都可以建立自己的私有配置文件,命名为.vimrc
su 用户名
cd ~
vim .vimrc
在.vimrc文件中,可以配置下列配置选项:
syntax on //设置语法高亮:
set nu //显示行号:
set shiftwidth=4 //设置缩进的空格数为4
还有许多配置,包括当前行增加下划线,自动补全,等等配置,需要一个一个去配,有些配置还需要插件,比较麻烦。因此可以使用下面的命令直接配置(不要动公共配置,所以不要在root下执行,只能在普通用户家目录下执行):
curl -sLf https://gitee.com/HGtz2222/VimForCpp/raw/master/install.sh -o ./install.sh && bash ./install.sh
gcc和g++是由GNU开发的编程语言编译器,其中,gcc用来编译c程序,g++用来编译c++程序。编译程序时,都要经历以下4个阶段:
使用以下命令进行编译:
gcc 【选项]】 源文件 【选项】 目标文件
常用选项:
-E 只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面
-S 编译到汇编语言不进行汇编和链接
-c 编译到目标代码
-o 文件输出到 文件
-static 此选项对生成的文件采用静态链接
-g 生成调试信息。GNU 调试器可利用该信息。
-shared 此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库.
-O0
-O1
-O2
-O3 编译器的优化选项的4个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高
-w 不生成任何警告信息。
-Wall 生成所有警告信息。
对于文件HelloWorld.c
#include
#define NUMBER 2
int main()
{
#if NUMBER
printf("NUMBER=%d\n",NUMBER);//NUMBER被定义,就打印NUMBER的值,否则打印error
#else
printf("error");
#endif
return 0;
}
执行命令:
gcc -o HelloWorld.c -o HelloWorld.i
这就生成了HelloWorld.i文件,cat一下HelloWorld.i文件:
继续执行以下命令:
gcc -S HelloWorld.i -o HelloWorld.s
生成HelloWorld.s文件后,cat一下汇编代码HelloWorld.s:
使用以下命令:
gcc -c HelloWorld.s -o HelloWorld.o
生成HelloWorld.o文件,由于.o文件是二进制,因此cat的时候是乱码:
执行以下命令:
gcc HelloWrld.o -o HelloWorld
生成HelloWorld可执行文件,使用cat查看可执行文件:
静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为“.a” 。
优点:可移植性好
缺点:体积大
先使用以下命令安装lib.c:
sudo yum install glibc-static
再使用静态链接生成可执行文件:
gcc 源文件 -o 目标文件 -static
如:
动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为“.so”。gcc 在编译时默认使用动态库(编译时,系统默认给了.so文件,gcc会默认优先使用 .so动态库进行编译,当没有.so才会找.a静态库,没有.a静态库就会报错)。完成了链接之后,gcc 就可以生成可执行文件。
优点:体积小,轻量,节省内存和硬盘空间,下载传输方便
缺点:库代码没了,程序无法运行
使用
gcc HelloWorld.c -o HelloWorld
生成可执行程序后:
可以使用指令
file 可执行文件名
查看可执行文件HelloWorld的默认链接方式,发现是动态链接库:
gdb是GNU开源组织发布的一个强大的UNIX下的程序调试工具,是命令行调试工具。一般来说,gdb主要完成如下四个功能:
gcc 源文件 -o 目标文件 -g
这就生成了debug版本的可执行程序:
可以使用命令:
readelf -S 可执行文件 | grep -i debug
查看debug信息。
通过debug版本的和release版本的可执行程序对比发现,debug版本的可执行程序比release版本的可执行程序文件大小要大一些,debug的可执行程序大小为10048B,release版本的可执行程序大小为8512B:
因此,也可以看出gcc/g++编译出来的可执行程序是release版本的。
以插入排序的代码InsertSort.c为例:
#include
#include
//打印
void Print(int* a, int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
printf("\n");
}
//直接插入排序
void InsertSort(int* a, int n)
{
//多趟排序
for (int i = 0; i < n - 1; i++)
{
//把temp插入到数组的[0,end]有序区间中
int end = i;
int temp = a[end + 1];
while (end >= 0)
{
if (temp < a[end])
{
a[end + 1] = a[end];
end--;
}
else
{
break;
}
}
a[end + 1] = temp;
}
}
void TestInsertSort()
{
int arr[] = { 9,1,2,5,7,4,8,6,3,5 };
InsertSort(arr, sizeof(arr) / sizeof(arr[0]));
Print(arr, sizeof(arr)/sizeof(arr[0]));
}
int main()
{
TestInsertSort();
return 0;
}
来了解gdb调试。
r/run:运行程序
运行程序之前,要先以debug模式生成可执行程序,再进入debug模式,然后运行程序:
默认情况下,run 指令会一直执行程序,直到执行结束。如果程序中手动设置有断点,则 run 指令会执行程序至第一个断点处。
start 指令会执行程序至 main函数的起始位置,即在 main() 函数的第一行语句处停止执行(该行代码尚未执行)。
而后要调试可以直接执行s或n。
从第n行开始列出10行代码:
l/list n
从第1行开始,列出10行代码:
继续输入l,会跟着上面的代码继续显示,如下面显示11-20行,21-20行:
在第n行打断点:
b n
这步操作相当于VS的F9。
输入r运行,就会在23行停下来:
如果给函数名打断点,其实是给函数内容的第一行打了断点:
使用disable可以禁用断点,使用如下命令禁用断点:
disable 断点编号
如下,禁用编号为2的断点,发现断电仅仅是被禁用而已,但是断点还在:
如果不想要某个断点了,可以使用delete删除断点:
删除某一断点:
delete 编号
删除所有断点:
delete
可以使用以下命令查看断点信息:
info b/break
单步执行代码:
s/step
执行main函数代码,执行完45行,再执行46行,再进入InsertSort函数内部执行,如果遇到断点则会停下:
相当于VS的F11。
逐过程执行:
n/next
执行main函数代码,直接将main函数执行完毕,中途不会进入main里面的子函数,遇到断点会停下:
假如将23行设置为断点,那么执行到断点处会停下:
相当于VS的F10。
跟踪查看一个变量,每次停下来都显示它的值:
dispaly 变量名
如跟踪变量temp的值:
也可以查看它的地址:
对于使用display的每一个变量,系统都会分配一个编号,如果不想显示这个变量值,可以使用如下命令:
undisplay 编号
如果想取消跟踪显示所有变量,可以直接使用:
undisplay
假如想退出某个函数,比如已经确认了bug不在该函数内,可以使用finish退出该函数:
finish
使用info命令查看当前设置了哪些断点:
i/info b
显示当前设置了哪些断点:
从当前位置开始连续而非单步执行程序:
c/continue
先运行到第一个断点处,从第一个断点处执行c,就运行到第二个断点处:
在一个函数内直接执行到第n行:
until n
从第45行直接执行到第47行:
调试时可以使用finish和continue快速确定在哪个函数崩了,再用until确定再哪一行崩了,比如从第一行until到第100行没有报错,但是从第100行until到第200行报错了,那么错误就在第100行至第200行。
可以使用如下命令查看函数调用的栈帧:
bt
查看执行到当前代码行时,调用的堆栈:
make是一个解释makefile中指令的命令工具。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中。
Makefile是Linux项目自动化构建工具,Makefile规定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。编译的安排就叫做构建。构建规则都写在Makefile文件里面,要学会如何Make命令,就必须学会如何编写Makefile文件。makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率,makefile成为了一种在工程方面的编译方法。
make是一条命令,makefile是一个文件,两者搭配使用,形成可执行程序,完成项目自动化构建。
与源文件同级,创建一个Makefile文件:
将编译InsertSort.c的命令写入Makefile中。其中,第一行叫做依赖关系,第二行叫做依赖方法:
InsertSort:InsertSort.c #依赖关系
gcc InsertSort.c -o InsertSort -std=c99 #依赖方法
第一行开始的InsertSort叫做目标,InsertSort.c叫做前置条件,gcc InsertSort.c -o InsertSort -std=c99是命令,命令前有Tab键:
执行make命令后,会自动执行Makefile文件中的命令:
但是此时想再make一下,会发现不让make了:
因为可执行文件已经是最新了。Makefile默认的目标是文件,如果目标不是真实存在的文件,而是一个命令呢,那么这个目标就是伪目标,伪目标没有依赖关系,只有依赖方法,如下clean就是一个伪目标,make就不会去检查是否存在一个叫做clean的文件,而是每次运行都执行对应的命令:
InsertSort:InsertSort.c
gcc InsertSort.c -o InsertSort -std=c99
.PHONY:clean #伪目标
clean:
rm -f InsertSort
其中:
由于make扫描Makefile文件时,默认只会形成一个目标依赖关系,一般是第一个,因此,现在执行make,并不能达到删除InsertSort可执行文件的目的,因为make会生成第一个目标也就是生成InsertSort可执行程序:
此时要执行非第一个目标时,需要make加上目标:
当把两个目标的顺序交换一下,会发现,执行make时就先执行clean命令:
这下直接make时,就不是编译生成可执行文件了, 而是执行clean命令,如果要生成可执行程序,必须执行make 加上目标:
也可以用$@表示目标文件,$^表示上一行的冒号后面的依赖文件列表:
例如,如下代码,当执行打印后,需要等待3秒才结束程序:
enter.c
#include
int main()
{
printf("33333333\n");
sleep(3);
return 0;
}
Makefile
enter:enter.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f enter
那么打印内容会在屏幕上出现3秒以后程序才结束:
如果不加\n,那么一开始就不会打印字符串,等待3秒后,才会打印出字符串:
这两个程序运行结果并不代表sleep先于printf执行,其实只是printf已经执行,但是数据没有被立即刷新到显示器上而已。当没有\n时,字符串会暂时保存到用户C语言级别的缓冲区当中,显示器设备的刷新策略就是行刷新(\n),即立即刷新。
如果想在不带\n的情况下,让数据立即刷新出来, 可以调用fflush接口,不过fflush的参数是文件流:
那么对于这个程序,应该给fflush传什么参数呢?
由于在C语言中的文件操作,系统会默认地帮我们把对应的文件以C语言的方式打开,并且会默认打开3个输入输出流:
为什么程序会默认打开着3个输入输出流呢?因为程序为了帮我们计算数据,就有数据源和数据结果,C语言为了避免我们麻烦,默认给我们打开了这3个输入输出流,供我们读写。
对于刚刚的程序,想让代码立即刷新出来,我们借助fflush函数,因为printf是向stdout打印,那么刷新也是向stdout刷新,所以将stdout作为fflush的参数:
再执行make clean;make,会发现先刷新字符串,但是再等了3秒程序才结束:
对于\r,仅回车,行不变,列回到当前行的最开始,由于没有\n,所以每次循环的打印结果不会被显示出来:
#include
int main()
{
int count = 5;
while(count)
{
printf("%d\r",count);
count--;
sleep(1);
}
return 0;
}
那么为了让打印结果显示出来,可以使用fflush:
但是结果就变成在这个位置倒计时:
假如将count初始化为10,执行结果就更离谱:
这是因为显示器是字符设备,凡是显示到显示器上面的内容都是字符,凡是从键盘读取的内容也都是字符。所以上面第一次打印的count值为10并不是数字10,而是字符'1'和字符'0'。再对count--时,count的值只有一个字符,比如9,所以只能覆盖到一个字符,因此字符'0'永远都不会动。要解决这个问题,可以输出2个字符,这样就会把字符'0'给覆盖了:
执行结果:
假如实现进度条小程序,在同一行打印'#',每次刷新就多打印一个'#',以下代码是实现不了的,因为显示器是行刷新,没有\n就刷新不出来进度条:
#include
#include
#include
#define NUM 100
int main()
{
char bar[NUM+1];
memset(bar,'\0',sizeof(bar));
int i = 0;
while(i <= 200)
{
printf("%s",bar);//没有\n,在显示器上显示不出来‘#’
bar[i] = '#';
i++;
sleep(1);
}
return 0;
}
那么加了\n以后呢?虽然'#'可以打印出来,但是打印却变成了不在同一行显示'#',这不符合我们的要求:
所以考虑到使用fflush:
但是发现运行结果,是每隔1秒自动多打印n个'#':
这不符合我们的需求,我们需要每次增加一个'#'。可以加上\r
现在每次刷新增加一个#":
这下达到了我们的要求,但是有没有发现好像刷新的太慢了,那就把刷新间隔缩短一些:
这下快一些了 :
但是i的上限是200,程序要执行好久才能看到最终的执行结果,而且还刷屏,不好观察,将i的上限改为20:
执行结果发现,执行完毕时提示符[delia@VM-8-17-centos progressBar]$ 和进度条在同一行呢:
能不能把提示符去掉?可以在程序执行结束时,换行:
如果想让进度条被[ ]包起来呢?
发现进度条从右向左显示:
加上'-'就从左向右显示了:
想加上数字百分比呢?
执行结果如下:
想在结尾增加光标旋转呢?
假如想更换进度条颜色呢?比如换成绿色:
执行结果如下:
好啦,进度条做好啦。完整代码段:
#include
#include
#include
#define NUM 100
int main()
{
char bar[NUM+1];
memset(bar,'\0',sizeof(bar));
const char *token = "|/-\\";
int i = 0;
while(i <= 100)
{
printf("\033[32m[%-100s][%d%%] [%c]\r",bar,i,token[i%4]);
fflush(stdout);
bar[i] = '#';
i++;
usleep(50000);
}
printf("\n");
return 0;
}