目录
(14)continue让函数从当前位置一直执行至下一个断点处
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
在第23行打断点:
这步操作相当于VS的F9。
输入r运行,就会在23行停下来:
这就相当于VS的F5。
如果给函数名打断点,其实是给函数内容的第一行打了断点:
使用disable可以禁用断点,使用如下命令禁用断点:
disable 断点编号
如下,禁用编号为2的断点,发现断电仅仅是被禁用而已,但是断点还在:
如果不想要某个断点了,可以使用delete删除断点:
删除某一断点:
delete 编号
删除所有断点:
delete
可以使用以下命令查看断点信息:
info b/break
查看可执行程序InsertSort的断点信息:
单步执行代码:
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;
- }