目录
1.Linux软件包管理器-yum
2.Linux编辑器-vim
2.1.vim的引入
2.2.vim的基本概念
2.3.vim的基本操作
2.4.vim正常模式命令集
2.5.简单vim配置
3.Linux编译器-gcc/g++
3.1.背景知识
3.2.gcc/g++介绍
4.Linux调试器-gdb
5.Linux项目自动化构建工具-make/Makefile
5.1.背景知识
5.2.make/Makefile介绍
6.Linux第一个小程序-进度条
6.1.补充知识
6.2.进度条小程序
7.git
7.1.什么是版本控制
7.2.git、gitee和github
7.3.git如何使用
centos 7中安装软件的方式:
(1)源码安装
(2)rpm包安装
(3)yum安装
yum安装的好处:
(1)不用编译源码
(2)不用解决软件的依赖关系
例如:我们的Linux操作系统里面没有sl指令,如下图所示
在root用户下使用命令yum install sl。如果在普通用户下,需要使用命令sudo yum install sl,sudo是使用root身份执行该命令(sudo命令现在如果无法使用没关系,后面会讲),如下图所示,进行安装
如果想要卸载sl指令,在root用户下使用命令yum remove sl。如果在普通用户下,需要使用命令sudo yum remove sl,如下图所示,进行卸载
从上面可知,如果我知道要安装什么软件,用yum可以很简单
无论在什么系统下,我们要安装别人的软件,需要经历两步:
第一步:需要别人先把代码给我编译成为可执行程序
第二步:需要有人将编好的软件放在用户能下载的地方(官网、应用软件市场)
Linux也是一样的
常规下载的过程我们举个例子,以应用市场APP为例:
在手机(客户端)上的应用市场APP中我们要下载抖音,点击下载,那么下载的请求就会通过网络发送给公司的应用市场服务器,服务器内部有APP列表,服务器找到抖音软件发送给手机(客户端),在手机上下载好抖音软件并安装就可以使用了
Linux也是一样的,Linux上有专门下载的软件yum(类似于手机上的应用市场APP),有Linux服务器,里面存放有常见的Linux工具软件,Linux机器里面的yum软件会根据你的要求,向Linux服务器发送软件请求,Linux服务器将软件发送给Linux机器,Linux机器进行下载安装
因此yum可以理解为类似于手机上的应用市场APP
在Linux上搜索安装卸载软件:
1.软件搜索:yum list | grep sl
yum list的作用是获取服务器所有的软件名,grep sl是搜索所有名字里面包含sl的软件,yum list | grep sl就得到了所有名字里面有sl的软件,如下图所示,其中第一列就是软件的名字
搜索结果以下图为例,介绍各部分内容
◆ 软件包名称: 主版本号.次版本号.源程序发行号-软件包的发行号.主机平台.cpu架构.
◆ "x86_64" 后缀表示64位系统的安装包, "i686" 后缀表示32位系统安装包. 选择包时要和系统匹配.◆ "el7" 表示操作系统发行版的版本. "el7" 表示的是 centos7/redhat7. "el6" 表示 centos6/redhat6.◆ 最后一列, base 表示的是 "软件源" 的名称, 类似于 "小米应用商店", "华为应用商店" 这样的概念2.软件安装:yum install 软件名 / sudo yum install 软件名
如下图所示,因为我们已经安装过sl了,所以下面提示什么也没有做
注:安装的时候可以使用-y选项,系统就不会再问你是否确认安装了
3.软件卸载:yum remove 软件名 / sudo yum remove 软件名
手机上的应用市场APP和Linux上使用的yum下载软件,是怎么知道去哪个服务器下载软件呢?
我们手机上的应用市场APP一定内置了很多下载链接或者网址,Linux也一样。yum在下载的时候会去/etc/yum.repos.d路径下找 Centos-Base.repo yum源。
yum源有很多,我们也可以自己配置yum源,其中yum在找下载链接的时候是在Centos-Base.repo yum这个源里面找的,如下图所示
使用命令nano Centos-Base.repo打开该这个yum源可以看见里面有很多网址,如下图所示
不是所有人的Linux上面的yum源都是国内的链接,如果不是国内的,或者安装软件特别慢,建议更新yum源(更新yum源很简单,自己在网上找教程即可)(注意做任何配置,绝对不要先rm删除,一定是先mv备份)
建议安装两个软件
第一个软件:rzsz工具
这个工具用于 windows 机器和远端的 Linux 机器通过 XShell 传输文件。 安装完毕之后可以通过拖拽的方式将文件上传过去查找命令:yum list | grep lrzsz / sudo yum list | grep lrzsz
下载命令:yum install -y lrzsz.x86_64 / sudo yum install -y lrzsz.x86_64
第二个软件:epel-release扩展源
epel-release是一个扩展源,一些软件在正式的官方服务器上没有,epel-release是一个准官方的服务器列表
下载命令:yum install -y epel-release / sudo yum install -y lrzsz.x86_64
vim下载命令:yum install -y vim / sudo yum install -y vim
引入:
在windows下,像vs2019这种编译器是集编写、编译、链接、运行、调试于一体的集成开发环境。在Linux下就不再是集成环境了,Linux下是一个个分散的工具,比如说vim,vim是一个编辑器,只能用来写代码,但是vim本身的功能十分强大,是多模式的编辑器
为什么要学习vim?
vim是Linux上自带的编辑器,有些情况下,我们需要在生产环境(上线环境)下快速定位问题,甚至需要快速修改代码,此时我们就需要使用vim
vi和vim的区别:vi/vim 的区别简单点来说,它们都是多模式编辑器,不同的是 vim 是 vi 的升级版本,它不仅兼容 vi 的所有指令,而且还有一些新的特性在里面。例如语法加亮,可视化操作不仅可以在终端运行,也可以运行于x window 、 mac os 、windows
我们讲解vim的三种模式(其实有好多模式,目前掌握这3种即可),分别是命令模式(command mode)、插入模式(Insert mode)和底行模式(last line mode),各模式的功能区分如下(1)正常/普通/命令模式(Normal mode)控制屏幕光标的移动,字符、字或行的删除,移动复制某区段及进入Insert mode下,或者到 last line mode(2)插入模式(Insert mode)只有在Insert mode下,才可以做文字输入,按「ESC」键可回到命令行模式。该模式是我们后面用的最频繁的编辑模式。(3)底行模式(last line mode)文件保存或退出,也可以进行文件替换,找字符串,列出行号等操作。 在命令模式下 shift+: 即可进入该模式。注:要查看你的所有模式:打开vim,底行模式直接输入:help vim-modes
创建一个文件,用vim指令打开该文件,如下图所示
使用vim命令打开文件,此时的模式叫做命令模式,此时键盘上的很多按键是用不了的,我们进行shift :(shift :其实就是输入冒号,因为单敲冒号键输入的是分号,要输入冒号,必须摁住shift然后敲冒号键)操作,我们发现左下角有一个冒号,这种模式我们称为底行模式
如果我们想退出vim,在底行模式下输入q,如果想保存退出vim,在底行模式下输入wq
在命令模式下,进行 i 操作即可进入插入模式,插入模式下就可以写代码了。命令模式进入插入模式,除了i键还有a键和o键,a、i、o三个键的区别是:
a:光标向右移动一格并进入插入模式
i:光标不移动直接进入插入模式
o:光标向下移动一格并进入插入模式
这里其实我们只需要记住i就可以了
要想退出插入模式,点击Esc就会进入命令模式。任何模式想进入命令模式无脑Esc即可
如果想从底行模式回到命令模式,点击Esc即可
总结:三种模式的转换,快捷键如下图所示
使用vim编辑器写代码,并进行编译运行,如下图所示
命令模式下的文本批量化操作:
1.复制行内容
◆ yy:复制当前行 ◆ p:粘贴到当前行的后面
将光标放到要复制的行上输入yy, 输入yy后该行代码就被复制了,然后光标放到要粘贴的前一行位置输入p,整行代码就被粘贴过来了,如果想连续粘贴n次就输入np(例如如果想粘贴5次,就输入5p)
如果想复制连续的n行,光标放在这n行中的第一行,输入nyy(例如如果想复制2行,将光标放在第1行输入3yy)即可,然后光标放到要粘贴的位置输入p,n行代码就被粘贴过来了
2.剪切行内容
◆ dd:剪切(删除)当前行
◆ p:粘贴到当前行的后面
将光标放到要剪切的行上输入dd,输入dd后该行代码就被剪切了,然后光标放到要粘贴的前一行位置输入p,整行代码就被粘贴过来了,如果想连续粘贴n次就输入np(例如如果想粘贴5次,就输入5p)
如果想剪切连续的n行,光标放在这n行中的第一行,输入ndd(例如如果想剪切2行,将光标放在第1行输入3dd)即可,然后光标放到要粘贴的位置输入p,n行代码就被粘贴过来了
注:dd可以当作删除连续的一行或n行来使用
3.撤销操作
◆ u:撤销刚刚的操作
◆ ctrl+r:针对u操作,再次进行撤销
4.快速将光标移动到某一行
◆ shift g(本质上输入是G):光标快速定位到文本末尾
◆ gg:光标快速定位到文本开始
◆ n + shift g:光标快速定位到文本第n行
5.快速将光标移动到文本行的首尾部
◆ shift $(本质上输入是$):光标快速定位到文本行的末尾
◆ shift ^(本质上是^):光标快速定位到文本行的开始
我们称$和^为锚点
6.以单词为单位进行前后光标的移动
◆ w:以单词为单位后移光标
◆ b:以单词为单位前移光标
7.光标上下左右的移动
◆ h:左移光标
◆ j:下移光标
◆ k:上移光标
◆ l:右移光标
建议熟悉hjkl移动光标而不是上下左右键移动光标,因为后面一些组合命令是不支持上下左右键的。选用hjkl移动光标是因为老式键盘的上下左右是复用在这四个键里的,如下图所示。
快速记住hjkl如何对应上下左右:h在最左边所以是左,l在最右边所以是右,k大j小所以k上j下
8.光标处字母大小写变换
◆ shift ~(本质上是~):大小写快速切换
9.字符替换
◆ r + [替换的字符]:将光标处的字符进行替换
◆ n + r + [替换的字符]:将光标及其后n个字符进行替换
◆ shift r(本质上是R):进入replace模式,接下来输入的内容依次覆盖光标及其后面的内容(Esc退出replace模式)
10.删除字符
◆ x:删除光标处的字符
◆ n+x:删除光标及其后n个字符
11.批量化注释和去注释
注释:小写状态使用ctrl v进入块视图模式,然后使用上k下j左h右l批量化选中区域,选好之后输入大写的I,然后输入注释符//,最后点击Esc即可多行注释。
去注释:小写状态使用ctrl v进入块视图模式,然后使用上k下j左h右l批量化选中注释部分,选好之后输入小写的d即可。
注:从上面这些文本批量化操作可以看出,vim更适合处理大型项目或文件
快速配置:
在gitee网站上搜索vimforcpp,打开下图一所示插件,复制下图二所示这一段命令内容。在shell里登录你的普通用户(不建议在root用户下执行),粘贴命令运行并输入root密码即可安装,如下图三所示。
安装好之后输入source~/.bashrc命令使vim配置生效,如下图四所示,这样我们登录的普通用户的vim就完成了配置(只对该普通用户的vim进行了配置)
注:该方法只支持centos 7
程序翻译过程:程序(文本)-> 机器语言(二进制)1. 预处理(进行宏替换)2. 编译(生成汇编)3. 汇编(生成机器可识别代码)4. 链接(生成可执行文件或库文件)
复习回顾:
c语言代码在Linux上运行过程如下图所示。首先创建test.c文件,使用vim打开test.c文件,如下图一所示,在test.c文件中写入c语言代码,如下图二所示,gcc编译test.c文件生成a.out可执行文件,运行a.out可执行文件,得到代码运行结果如下图三所示。
c++代码在Linux上运行过程如下图所示。首先创建test.cpp文件,使用vim打开test.cpp文件,如下图一所示,在test.c文件中写入c语言代码,如下图二所示,gcc编译test.c文件生成a.out可执行文件,运行a.out可执行文件,得到代码运行结果如下图三所示。
注:
1.如果g++编译器没有安装,可以使用命令yum install -y gcc-g++进行安装。
2.gcc只能用来编译后缀为.c的c语言文件,g++既可以编译后缀为.c的c语言文件也可以编译后缀为.cpp的c++文件。
程序翻译过程也就是程序(文本)转换为机器语言(二进制)的过程。程序需要转换为二进制的机器语言才能通过计算机执行。问题:计算机为什么只认识二进制?答:其实并不是计算机只认识二进制,而是组成计算机的各种组件(例如CPU、内存、磁盘等)只认识二进制,二进制可以简化硬件设计。
gcc和g++的全部编译过程和选项完全一样,本小节统一使用gcc作为讲解,g++同理。
语法:gcc [选项] 要编译的文件 [选项] [目标文件]注:1.首先创建test.c文件,使用vim打开test.c文件,如下图一所示,在test.c文件中写入c语言代码,如下图二所示,gcc编译test.c文件使用命令gcc test.c -o mytest生成名称为mytest的可执行文件,运行mytest可执行文件,得到代码运行结果如下图三所示。注:
1.如果经过编译不想生成a.out可执行文件,而是要自定义生成的可执行文件名称,使用命令gcc test.c -o mytest即可生成名称为mytest的对应可执行文件。
2.上面的演示一步到位,直接将程序编译成了机器认识的可执行程序。下面我们分预处理、编译、汇编、链接四步,在配合对应命令的基础上详细介绍该过程。
1.预处理指令:gcc -E 后缀为.c或.cpp的原文件 -o 后缀为.i的目标文件指令所用选项解析:-E:从现在开始进行程序的翻译,当预处理完成就停下来。-o:把处理的结果写到后面自定义的临时文件中。功能:(1)宏替换(2)头文件展开(3)去注释(4)条件编译注:1.预处理对应指令如果不写-o选项和后面自定义的目标文件,那么处理的结果会直接显示在显示器上不方便查看,建议写-o选项和后面自定义的目标文件将预处理结果写在自定义文件中。2.gcc预处理test.c文件使用命令gcc -E test.c -o test.i生成名称为test.i的目标文件,如下图一所示。vim打开test.c原文件,进入底行模式输入vs test.i并回车,此时将test.c文件代码和test.i文件代码进行了分屏显示,如下图二所示。此时可以对比原代码和预处理后代码的区别。在分屏显示多个文件代码的情况下,想让光标移动到其他文件的代码上,使用ctrl+ww即可。3.在test.i预处理文件中头文件会被展开,可以看出c语言的标准头文件在下图一所示的/usr/include/stdio.h路径中,使用命令ls /usr/include如下图二所示,可以看到所有的头文件都在/usr/include路径下。
这里可以看出原代码#include
经过预处理后对应头文件的路径,因此我们可以得出结论,编译器内部都必须通过一定的方式,知道原代码包含的头文件所在路径。Linux下gcc编译器也有自己默认的搜索路径。 4.c语言原代码经过预处理后代码还是c语言的,预处理其实就是将原c语言代码优化成可直接编译的干净的c语言代码。
2.编译指令:gcc -S 后缀为.c或.cpp的原文件/后缀为.i的预处理文件 -o 后缀为.i的目标文件指令所用选项解析:-S:从现在开始进行程序的编译,当编译完成就停下来。-o:把处理的结果写到后面自定义的临时文件中。功能:将c语言翻译成为汇编语言注:1.编译指令中的原文件既可以是原本的.c或.cpp文件也可以是后缀为.i的预处理文件,如果是.c或.cpp文件则会经过预处理和编译生成对应后缀为.s的编译文件,如果是.i文件则会经过编译生成对应后缀为.s的编译文件。2.gcc编译test.i文件使用命令gcc -S test.i -o test.s生成名称为test.s的目标文件,如下图一所示。vim打开test.s文件,如下图二所示,test.s文件中生成了对应的汇编语言。
3.汇编指令:gcc -c 后缀为.c或.cpp的原文件/后缀为.i的预处理文件/后缀为.s的编译文件 -o 后缀为.o的汇编文件指令所用选项解析:-c:从现在开始进行程序的编译,当编译完成就停下来。-o:把处理的结果写到后面自定义的临时文件中。功能:将汇编语言翻译成为可重定位的二进制文件注:1.gcc编译test.s文件使用命令gcc -c test.s -o test.o生成名称为test.o的目标文件,如下图一所示。vim打开test.o文件,如下图二所示,test.o文件中生成了对应的可重定位的二进制文件。2.汇编生成的后缀为.o的二进制文件是不能运行的,如下图所示,系统提示这不是一个可执行的文件。后缀为.o的可重定位的二进制文件不能运行的原因是文件中的一些符号还没有进行关联。
创建一个test.c文件,vim打开test.c文件,输入下图一的代码,该代码中没有包含头文件,这样的代码也是c语言代码,对test.c文件进行编译如下图二所示也可以编译通过。因此头文件包含不是必须的,所有的包含头文件的操作,本质是因为想使用头文件所声明的方法。汇编生成的后缀为.o的二进制文件是不能运行的,因为这里只预处理编译汇编了test.c原文件里面的代码。我们在test.c文件中包含了头文件,那么有了头文件中各种函数的声明,而没有函数的定义,函数的定义所在文件我们没有预处理编译汇编。我们只有将函数的定义所在文件进行预处理编译汇编并且和test.c预处理编译汇编后的test.o文件进行关联(链接),代码才能够运行。
问题:头文件中声明的函数,其对应定义所在的文件在哪里呢?
答:在c标准库中,Linux下c标准库路径为/lib64/libc-2.17.so,这里c标准库名称为libc-2.17.so,如下图所示
4.链接指令:gcc 后缀为.c或.cpp的原文件/后缀为.i的预处理文件/后缀为.s的编译文件/后缀为.o的汇编文件 -o 目标文件指令所用选项解析:-o:把处理的结果写到后面自定义的临时文件中。功能:在系统特定路径下搜索需要使用的函数定义所在库,进行预处理编译汇编后与我们自己的后缀为.o的原文件关联(链接),生成可执行程序。注:1.使用命令gcc test.o -o mytest,生成可执行程序mytest,运行mytest文件,得到结果,如下图所示。如果想查看一个可执行程序所依赖的库,可以使用ldd命令,输入命令ldd mytest,如下图一所示,可以看到mytest文件所依赖的库中有libc.so.6库,使用命令ls /lib64/libc.so.6 -al如下图二所示,可以看到libc.so.6库指向的就是前面我们介绍的 libc-2.17.so c标准库。
命令快速记忆:预处理命令用的选项:-E编译命令用的选项:-S汇编命令用的选项:-c链接命令不用选项预处理所用选项、编译所用选项、汇编所用选项连在一起就是ESc,和键盘中的Esc相似。文件后缀快速记忆:预处理生成文件后缀:.i编译生成文件后缀:.s
汇编生成文件后缀:.o
链接生成文件后缀:.out
预处理生成文件后缀、编译生成文件后缀、汇编生成文件后缀连在一起就是iso,镜像文件的后缀就是ios。
头文件:给我们提供了可以使用的方法,所有的开发环境,具有语法提示,本质是通过头文件帮我们搜索的。库文件:给我们提供了可以使用的方法和实现,以供链接,形成我们自己的可执行程序。库文件分类:静态库、动态库。静态库:静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。Linux下静态库以.a结尾,windows下静态库以.lib结尾。在链接阶段,静态库对应静态链接,静态链接就是将库文件的代码直接拷贝到可执行文件中。静态库的优点:不依赖任何库,直接拷贝到自己的可执行程序中。静态库的缺点:浪费资源。动态库:动态库与静态库相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。Linux下动态库以.so结尾,windows下动态库以.dll结尾。在链接阶段,动态库对应动态链接,动态链接就是在程序执行时由运行时链接文件加载库。动态库的优点:大家共享一个库,可以节省资源。动态库的缺点:一旦库缺失,会导致几乎所有的程序失效。注:1.gcc在编译时默认使用动态库。使用命令 gcc hello.o –o hello 对hello.o完成了动态链接之后,gcc就可以生成hello可执行文件。创建一个test.c文件并写入c语言代码,使用命令gcc test.c -o mytest编译test.c文件生成mytest可执行文件。使用命令 ldd mytest观察mytest可执行文件的链接库,如下图所示,可以看到链接的库后缀为.so,因此链接的为动态库。使用命令file mytest观察mytest可执行文件的属性,如下图所示,可以看到mytest可执行文件使用的是动态链接共享库的方式。因此gcc默认情况下,生成的可执行程序是动态链接的,如果想让生成的可执行程序是静态链接的,那么要在命令的后面加上static选项,命令为gcc test.c -o mytest1 -static,如下图所示。可以看出相同的test.c原文件,可执行程序如果是静态链接的版本要比动态链接的版本大很多,因此gcc默认采用动态链接。此时使用命令 ldd mytest2观察mytest2可执行文件的链接库,如下图所示,可以看到提示不是一个动态可执行的文件,因此链接的为静态库。使用命令file mytest2观察mytest2可执行文件的属性,如下图所示,可以看到mytest2可执行文件使用的是静态链接的方式。
2.如果没有安装c语言的静态库,那么就无法使用gcc以-static静态链接方式对c语言文件进行编译,如下图所示,同理如果没有安装c++的静态库,那么就无法使用g++以-static静态链接方式对c++文件进行编译。使用命令yum install -y glibc-static即可安装c语言静态库,使用命令yum install -y libstdc++-static即可安装c++静态库。
首先创建hello.c文件,然后vim hello.c写入c语言代码,如下图所示。使用命令gdb hello,就进入了gdb调试命令行中,退出gdb调试命令行输入quit+回车即可,如下图二所示。
使用命令gdb hello进入gdb调试命令行中,可以看到有no debugging symbols found提示,这是在提示我们无法调试。因为gcc默认生成的可执行程序无法调试。
程序的发布方式有两种,debug模式和release模式。Linux下gcc/g++出来的二进制程序,默认是release模式。如果要使用gdb进行调试,必须在源代码生成二进制程序的时候,加上-g选项,如下图所示,可以看到hello.c原文件生成的debug版本可执行程序hello_g要比生成的release版本可执行程序hello大,并且debug版本的hello_g也可以运行。问题:同一个原文件生成的debug版本可执行程序要比release版本可执行程序大,那么到底大在哪里呢?
答:Linux中有一个命令是readelf,其功能是以段的方式读取可执行程序,使用命令readelf -S hello读取release版本可执行程序hello,使用命令readelf -S hello_g读取debug版本可执行程序hello_g,如下图一所示。
分别使用命令readelf -S hello | grep debug和readelf -S hello_g | grep debug读取debug版本可执行程序和release版本可执行程序,可以看出debug版本可执行程序比release版本可执行程序多了很多调试信息,如下图二所示。
使用命令gdb hello_g来调试debug版本的可执行程序,进入gdb调试命令行,如下图所示。
显示原代码:在gdb调试命令行,使用list命令或简写成l,就可以显示原代码,如下图一所示。但是这样只显示了全部代码其中的十行,如果想从第1行开始显示则使用命令l 0,如下图二所示。如果想显示main函数部分代码则使用命令l main,如下图三所示。在显示某一段代码时,如果敲击回车键,则会显示后面的代码,如下图四所示。
打断点:在gdb调试命令行,使用breakpint命令或简写成b,就可以在某处打断点,b 15就是在原代码的15行打断点,如下图所示,这里给我们提示断点编号1在文件hello.c的第16行。
查看断点:在gdb调试命令行,使用info b命令,就可以查看所有的断点,如下图所示。
删除断点:在gdb调试命令行,使用delete命令或简写成d,就可以删除对应编号的断点,如下图所示,使用命令d 1,就将编号为1的断点删除了。
运行到第一个断点处调试代码:在gdb调试命令行,使用run命令或简写成r,运行代码到第一个断点处停止,如下图所示。
从一个断点运行到下一个断点处调试代码:在gdb调试命令行,使用continue命令或简写成c,运行代码到下一个断点处停止,如下图所示。
查看变量内容:在gdb调试命令行,使用print命令或简写成p,查看变量的内容,如下图所示,p result显示此时变量result的值,p &result显示此时变量result的地址。
逐语句执行(vs中的F11):在gdb调试命令行,使用step命令或简写成s,逐语句往后执行,如下图所示,连着两次s逐语句执行,运行到了第二个断点位置,此时如果再s逐语句执行就会进入AddToTop函数中。
逐过程执行(vs中的F10):在gdb调试命令行,使用next命令或简写成n,逐过程往后执行,如下图所示,代码运行到了第二个断点的位置,此时如果想进入AddToTop函数则s逐语句执行即可,如果不想进入函数则n逐过程执行,这里原代码19行为空所以直接跳到了第20行,此时p result得到的结果就是5050。
查看堆栈:在gdb调试命令行,使用bt命令或简写成n,查看此时的堆栈信息,如下图所示,进入AddToTop函数中并使用命令bt,显示此时的堆栈信息。
持续显示某个变量的内容:在gdb调试命令行,使用display命令,持续显示某个变量的内容,如下图所示,使用命令display res、display i持续显示变量res和i的内容,这样在后面进行逐语句逐过程调试的时候,display变量的内容就会持续显示。
关闭持续显示某个变量的内容:在gdb调试命令行,使用undisplay命令,不再持续显示某个变量的内容,如下图所示,持续显示的变量前面有一个编号,想关闭某个变量的持续显示undisplay+变量的编号 即可,使用undisplay 1关闭res变量的持续显示。
直接运行到某一行调试代码:在gdb调试命令行,使用until命令,直接跳转到某一行进行调试(跳过的代码自动执行),如下图所示,使用命令until 10直接跳转到第10行,因为第10行没有内容所以跳到了第11行,这样直接跳出了for循环。
执行完成一个函数就停下来:在gdb调试命令行,使用finish命令,直接运行完一个函数,跳转到调用函数的地方,如下图所示,在AddToTop函数中时使用finish命令直接跳转到了调用AddToTop函数的地方。
一些问题:
.h文件、.c文件、.cpp文件,在这样多文件的情况下先编译哪一个?
链接需要哪些库?
库和头文件等在哪里找?
整个项目结构该如何维护?
在windows下的vs集成环境中不涉及上面的问题,因为vs自动帮我们处理了,在Linux下需要考虑上面的问题。
如下图所示,在Linux下一个项目有很多文件,那么我们需要分别将这些文件进行编译生成可执行程序,然后手动将这些文件进行链接生成最终的可执行程序,这样成本很高不方便,我们可以使用Makefile解决该问题。
1.make:
make是一个命令工具,是一个解释makefile中指令的命令工具。
2.makefile/Makefile:
makefile/Makefile是一个文件,是在当前路径下的一个普通文件,文件中包含依赖关系和依赖方法两部分。
例:对下图一所示的mytest.c文件编译生成mytest可执行程序,那么mytest为目标文件,其依赖关系和依赖方法如下图二所示。
在Makefile文件中目标文件的依赖关系和依赖方法如下图所示,依赖关系部分目标文件和依赖文件之间用冒号隔开,依赖方法必须以Tab键开头。
项目目录中有了Makefile文件中的依赖关系和依赖方法,那么我们如果想编译目录中的文件就不需要再使用gcc命令了,直接使用make命令即可,如下图所示,make之后自动就会生成对应的可执行程序。
Makefile文件中不仅应该有生成目标可执行程序的依赖关系和依赖方法,还应该有对应删除目标可执行程序的依赖关系和依赖方法,Makefile文件中删除可执行程序的依赖关系和依赖方法如下图所示,clean:的后面是删除目标可执行程序的依赖关系,这里为空是因为没有依赖列表,clean:的下一行是删除目标可执行程序的依赖方法。
下图的.PHONY:clean给clean标签前面加了一个.PHONY,.PHONY可以理解为Makefile中的一个关键字,用.PHONY修饰的标签,即.PHONY:后面跟的内容可以称为伪目标,伪目标也是目标,因此也有自己的依赖关系和依赖方法。使用.PHONY修饰的伪目标,其特点为总是被执行的。
此时如果我们想清理make出的可执行程序,使用make clean命令即可,如下图所示。
注:
1.一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。2.makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
3.make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建。
4.make指令默认只会形成第一个目标文件,执行该依赖关系的依赖方法,如下图一所示,如果将mytest目标文件的依赖关系和依赖方法与clean伪目标文件的依赖关系和依赖方法进行交换,那么我们使用make指令执行的就是clean伪目标文件依赖关系的依赖方法,使用make mytest才会执行mytest目标文件依赖关系的依赖方法,如下图二所示。
一般情况下习惯把目标可执行程序放在第一个,让make默认去形成目标可执行程序。
5.关键字.PHONY:makefile语法格式中的一个关键字,clean被.PHONY修饰时表示总是被执行的。
问题:“总是被执行的”是什么意思?
答:首先我们讲解什么是“总是不被执行的”,makefile文件中内容如下图一所示,如下图二所示,我们多次调用make命令,只有第一次会帮助我们生成全新的mytest目标可执行程序,从第二次开始系统告诉我们mytest已经是最新的目标可执行程序了,不能再生成了,这种现象就是“总是不被执行的”。如果在makefile文件中将目标文件mytest也用.PHONY修饰,如下图三所示,我们多次调用make命令,每一次都会帮助我们生成全新的mytest目标可执行程序,如下图四所示,这种现象就是“总是被执行的”。
一般我们不会把目标文件设置成伪目标,而总是把清理clean设置成伪目标。
6.makefile文件中内容如下图一所示,多次调用make指令,只有第一次会帮助我们生成全新的mytest目标可执行程序,从第二次开始系统告诉我们mytest已经是最新的目标可执行程序了,不能再生成了。此时如果我们直接多次调用gcc命令,那么每次使用gcc命令还是会重新生成目标可执行文件,如下图二所示。这就说明是makefile在起作用,如果makefile内目标可执行程序没有被.PHONY修饰,那么如果makefile识别到生成的目标可执行程序是最新的就不会再重新生成了。
7.makefile是如何识别exe/bin是新的还是旧的呢?
补充知识:Linux下的文件有ACM(Access、Modify、Change)三种时间,如下图所示,使用stat命令可以显示文件的这三种时间,其中Access表示读取或进入文件的时间,Modify表示文件内容改变的时间,Change表示文件属性改变的时间。
如下图所示,将mytest文件所有者的运行权限关闭,也就是修改了文件的属性,此时stat mytest可以看到文件的Change部分时间改变了而文件的Access、Modify部分时间没有改变。
如下图所示,打开mytest.c文件修改一些代码,也就是修改了文件的内容,此时stat mytest可以看到文件的Change、Modify部分时间改变了而文件的Access部分时间没有改变。
问题:我们没有改变文件的属性为什么文件的Change部分时间改变了?
答:这是因为文件内容的改变可能会影响到文件属性的变化,例如增加代码会让文件变大。
问题:我们使用vim命令进入了文件,为什么Access部分时间没有改变?
答:在正常情况下,修改文件的内容和属性是一个很低频的事情,但访问文件的内容是一个很高频的事情,Linux内核在旧版本只要进入访问文件,Access部分时间会跟着改变,新版本的Linux内核进行了优化,当Access部分时间累计要改变一定次数后再对Access部分时间进行改变。这么优化的原因是Access部分时间也是数据,频繁访问文件频繁修改Access部分时间会导致计算机效率变慢。
现在回答makefile是如何识别exe/bin是新的还是旧的这一问题。假设mytest.c是原文件,对mytest.c进行编译得到了mytest可执行程序,此时如果对mytest.c文件进行修改,那么mytest可执行程序就是旧版本。对mytest.c文件进行修改,mytest.c文件的Modify部分时间就会改变,系统对比如果mytest.c文件的Modify部分时间如果在mytest文件时间之后,那么就重新编译mytest.c文件,如果mytest.c文件的Modify部分时间如果在mytest文件时间之前,就不再对mytest.c文件进行编译。
如下图所示,此时mytest可执行文件的时间在test.c文件Modify部分时间之后,那么mytest就是新的,此时进行make指令系统不会对mytest.c重新进行编译。使用touch命令更新mytest.c文件的时间戳,touch命令可以创建文件也可以重新更新文件的时间戳,那么在进行make指令系统就会对mytest.c重新进行编译。
因此,系统是根据对比源文件最近修改时间和可执行程序的时间,评估要不要重新生成可执行程序。如果是“总是被执行的”,就是说忽略对比时间的操作,完全按照指令执行。
多文件的makefile:
创建一个项目目录里面有test.c、test.h、main.c三个文件,如下图一所示。三个文件分别写入下图二所示的代码。想要编译这个项目的文件得到可执行程序,简单粗暴的方法就是使用gcc命令,命令为gcc -o hello main.c test.c,如下图三所示。
这里没有写test.h头文件的原因:如果是静态链接,在预处理阶段头文件中的代码就已经被展开到了源文件中,因此如果是静态链接不需要写。如果是动态链接,只需要让编译器编译源文件时找到头文件即可,编译器找头文件有两种找法,第一种是在系统默认的路径下查找(Linux下默认是在/usr/include/路径下),第二种是在当前路径下查找,因为test.h头文件就在当前目录下,编译器能找到,因此如果是动态链接也不需要写。
使用make/makefile的方法编译该项目生成可执行文件,如下图所示,创建makefile文件,首先写入hello目标文件的依赖关系和依赖方法,hello目标文件依赖main.o和test.o文件,然而没有main.o和test.o文件,makefile中前面出现的依赖文件如果不存在,会向后查找运行,根据后面的依赖关系和依赖方法生成对应所需的文件。因此我们在后面应该写出main.o和test.o的依赖关系和依赖方法。
在clean清理的部分,这里要删除所有的.o文件,可以使用命令rm -f *.o(注意这里*.o之间没有空格),这里还要删除hello可执行程序,可以使用rm -f *.o hello命令同时清理所有.o文件和hello可执行程序。
有了makefile,此时我们要编译该项目生成对应的可执行程序,使用make命令即可,要删除所有生成的程序,使用make clean即可,如下图二所示。
注:
1.有了依赖关系那么在依赖方法中,即使不加o选项和对应的自定义文件,编译器也会自动生成对应名称的文件,例如main.o文件的依赖方法使用gcc -c main.c或gcc -c main.c -o main.o两个命令功能完全相同。
2.这里makefile文件中,hello目标文件依赖关系可以直接为main.c和test.c文件,依赖方法为gcc -o hello main.c test.c,这样就不用再写main.o和test.o的依赖关系和依赖方法了,如下图所示。上面的makefile文件那么写是想演示一下目标文件形成链接的基本操作过程。
在makefile文件形成可执行文件的依赖方法命令中,可以使用$@符号和$^符号表示目标文件和依赖文件列表,如下图所示两种写法是等价的。
注: $^符号表示依赖文件列表,如果依赖文件有多个那么$^符号可同时表示这多个依赖文件。
缓冲区理解:
如下图一所示的代码,该代码运行的结果是先打印hello word再停顿两秒,根据该代码运行结果来看,编译器是先运行代码一再运行代码二。如果将代码一部分打印/n去掉,如下图二所示的代码,该代码运行的结果是先停顿两秒再打印hello word,根据该代码运行结果来看,编译器是先运行代码二再运行代码一。我们学过c语言,知道编译器一定是先执行代码一再执行代码二的,那为什么将/n去掉会出现这样的运行结果呢?
将/n去掉的情况,其实printf代码一部分执行过已经打印了,只不过没有显示出来,等到程序运行结束才显示出来的。在sleep的两秒钟,hello word其实在缓冲区中。
缓冲区就是一段内存空间。缓冲区有很多策略,刷新策略是其中之一,刷新策略就是立马将缓冲区内存中的内容打印出来。刷新策略默认执行行刷新,行刷新就是所要输出的字符串是否是一个完整行,如果是一个完整行就立刻刷新,将缓冲区内容打印出来,如果不是完整行就不刷新,等到缓冲区满了或程序退出了或遇到/n换行符再刷新将缓冲区内容打印出来。
只要我们打印的内容包含/n,那么包含/n和前面所有内容合起来叫做一行内容,因为是一行内容,所以会立刻刷新缓冲区将缓冲区内容打印出来。这就是前面有/n的情况运行的结果是先打印hello word再停顿两秒的原因,而没有/n的情况,缓冲区不会立刻刷新,最后等到了程序退出才刷新缓冲区,所以才会先停顿两秒再打印hello word。
问题:如果我们不想使用/n换行符就想立刻刷新缓冲区将缓冲区内容打印出来,有没有什么办法呢?
补充:当一个程序启动的时候,系统会默认打开stdin、stdout、stderr输入输出和错误流(输入输出和错误流可以理解为三个输入输出文件,程序启动默认打开这三个文件)
答:fflush可以帮助我们进行刷新工作,使用fflush(stdout)代码就是将输出流刷新,printf内容其实就是将内容打到stdout中,stdout就是我们前面提到的缓冲区,我们可以理解其为输出缓冲区,fflush(stdout)将输出缓冲区刷新,内容就会立刻打印出来。如下图所示,在/n去掉的情况中加上fflush(stdout),得到的运行结果是先打印hello word再停顿两秒。
注:
1.Linux下提供了sleep函数,Linux下sleep函数按照秒让程序进行休眠,使用sleep函数要包含
头文件。
回车和换行概念:
回车:将光标回到当前行的最开始(最左侧)
换行:新起一行(光标不回到最开始)
注:
1.因此我们口头上常说的换行或者回车其实是这里的回车+换行。
2.回车对应的是\r,回车+换行对应的是\n。
倒计时小程序:
注:下面的代码是有问题的,因为printf没有\n,缓冲区没有被刷新,倒计时的内容在缓冲区中第一个字符位置不停的覆盖,并不会打印出来。
进度条小程序:
设计一个进度条外观如下图所示,进度条分为三部分。第一部分中展示进度条的进度,每过一秒增加一个#;第二部分显示进度的百分比;第三部分是一个\,当进度条正常运行时\就不停的旋转。
makefile文件:
process.c文件:
注:
1.Linux下提供了usleep函数,Linux下usleep函数按照微秒(1秒等于微妙)让程序进行休眠,使用usleep函数要包含
头文件。 2.为了让第一部分的右括号不跟着进度条移动,我们打印的时候使用printf("[%100s]\r",bar),也就是输出字符串时预留出100个字符的空位,但是这样的话运行后进度条是从右往左打的,这是因为c语言默认是右对齐的,如果要让进度条从左往右打,应该使用printf("[%-100s]\r",bar),也就是调整成左对齐的。
3.第二部分中要打印出百分号,因为%是特殊符号,因此要打印%应该使用%%,代码为printf("[%-100s][%d%%]\r",bar,cnt)。
4.第三部分如果进度条正常运行,那么是一个不停旋转的\,如果要让\旋转,可以依次打印|/-\。由于\是特殊符号,因此要打印\应该使用\\。
5.c语言其实可以输出彩色的内容,格式如下图所示,\033[字背景颜色;字体颜色m会作用在后面的字符串上,\033[0m会作用在后面的字符串上,这里\033[0m是将后面字符串格式重新设置为原本默认格式。
c语言输出彩色内容可以参考博客:c语言 printf 颜色,关于printf如何输出颜色_XY LIU的博客-CSDN博客
版本控制是指对软件开发过程中各种程序代码、配置文件及说明文档等文件变更的管理,是软件配置管理的核心思想之一。
版本控制最主要的功能就是追踪文件的变更。它将什么时候、什么人更改了文件的什么内容等信息忠实地了记录下来。每一次文件的改变,文件的版本号都将增加。除了记录版本变更外,版本控制的另一个重要功能是并行开发。软件开发往往是多人协同作业,版本控制可以有效地解决版本的同步以及不同开发者之间的开发通信问题,提高协同开发的效率。并行开发中最常见的不同版本软件的错误(Bug)修正问题也可以通过版本控制中分支与合并的方法有效地解决。
git:
git是一个开源的分布式版本控制系统,可以有效、高速地处理从很小到非常大的项目版本管理,其也是Linus Torvalds为了帮助管理Linux内核开发而开发的一个开源的版本控制软件。
gitee和github:
gitee和github是基于git工具搭建起来的网站。
在gitee上创建仓库:
首先登陆下面的gitee网站注册gitee账号,然后新建一个gitee仓库,如下图一所示。
填写仓库名称和仓库介绍,选择私有(现在仅有私有选项),点击初始化仓库,选择所用语言,添加gitignore,选择开源许可证。其中开源许可证随便选择一个即可,而gitignore是用来对上传文件进行筛选的,通常我们只需要上传.h、.c、.cpp的源文件即可,我们可以将其他临时文件的后缀添加到gitignore模板中,这样就上传的时候就会进行筛选,不会再上传与添加到gitignore模板中后缀相同的文件。
点击设置模板选择Readme文件。选择分支模板部分是有关多人开发的,我们现在不需要多人开发因此不用选择。
新建仓库选择好后,如下图二所示,点击创建即可。
gitee网站:Gitee - 基于 Git 的代码托管和研发协作平台
注:现在信息管理较为严格,在gitee上无法直接创建开源项目,只能创建私有项目,当私有项目被审核通过才可以进行开源。
将gitee仓库克隆到本地:
在gitee仓库中点击克隆/下载,选择HTTPS,复制链接,如下图一所示。
打开Xshell,使用命令git clone+链接,如下图二所示,输入gitee的账号和密码,此时我们就将gitee仓库克隆到了本地。
此时在当前目录下使用ll命令,我们就可以看到新增了一个以gitee仓库名为名称的目录,如下图一所示,进入该目录,使用ls -al命令,我们可以看到新增了一个.git隐藏文件,如下图二所示,这里的.git文件就是本地的git仓库,我们将代码同步到gitee远端其实就是将这里本地git仓库与远端进行同步。
将代码上传到远端:
在新增的以gitee仓库名为名称的目录中创建一个test.c文件,将该文件上传至远端。
使用git status命令可以查看本地仓库和远端仓库之间的关系,如下图所示,系统提示有一个没有被管理的文件test.c。
使用三板斧上传代码到远端:分别是add、commit、push
首先使用git add+上传文件名,这里使用命令git add test.c。
其次使用git commit -m+"提交日志",这里使用命令git commit -m "新增测试代码,一个hello word程序",经过这一步要上传的文件就已经被添加到了本地git仓库中。-m选项和提交日志必须要写,提交日志部分描述代码内容。
最后使用git push命令并输入gitee账号和密码,将本地git仓库中代码上传到远端仓库中。
如下图一所示,经过上面的三板斧操作,test.c代码就被上传到了远端gitee的仓库中,在gitee中刷新仓库就可以看到上传的test.c文件,如下图二所示。
注:
1.第一次commit,系统可能要确认你的身份,让你输入你的邮箱和姓名,如下图一所示,复制上面的命令行将[email protected]部分换成你的邮箱,将Your Name部分换成你的姓名即可,如下图二所示。
2.如果上传完成后的test.c文件在远端进行了编辑修改(模拟多人协作编程修改了你的远端代码),如下图一所示,此时你的本地git仓库和远端的仓库就不同步了,此时创建一个t.c文件,写入代码,再使用三板斧上传t.c文件无法上传成功,如下图二所示,出现了hint冲突,这是因为远端的test.c代码和本地git仓库的test.c代码不同步,提交后面的t.c文件时要先保证本地git仓库中的其他文件和远端仓库的文件同步才能再提交t.c文件。
使用git pull命令并输入gitee账号密码,将远端的文件拉取过来,如下图三所示,此时系统会给我们提示很多解决冲突的方法,直接切换到底行模式q退出即可,此时本地的git仓库将远端文件拉取过来,完成了同步,查看test.c文件内容,如下图四所示,与远端我们修改后的test.c内容一致,此时解决了hint冲突,再使用git push命令即可将t.c上传至远端仓库中,如下图五所示。
3.使用git log命令可以看到git日志,git日志中存储着所链接仓库的所有提交记录,如下图所示。
4.如果没有安装git,使用yum install -y git进行安装即可。
5.本地中,在以gitee仓库名为名称的目录中有一个.gitignore隐藏文件,如下图一所示,凡是与.gitignore文件中后缀相同的文件都禁止进入本地git仓库,也就禁止将其提交到远端gitee上,因此vim .gitignore打开该文件,可以看到有很多后缀内容,如下图二所示。
如果有不想提交到远端的一类文件,将该类文件的后缀添加到该.gitignore文件中即可,如下图三所示,我们添加了.X和.x后缀。此时我们创建a.x、a.X、a.y、a.Y四个文件,如下图四所示,使用三板斧命令git add .、git commit -m "测试不可提交的文件功能是否完成"、git push将这四个文件尝试提交到远端,如下图五所示,其中git add .是将所有没有提交到远端的文件全部进行提交,细心的话可以看到这里在commit的时候后缀为.x和.X的文件没有进入本地git仓库。如下图六所示,在远端的gitee仓库中,后缀.y和.Y的文件成功上传而后缀.x和.X的文件没有上传。
6.前面提到过我们在gitee创建的仓库是私有的,其没有开源,如果想让仓库开源则进入仓库界面点击管理,如下图一所示,在是否开源栏选择开源,勾选所有的承诺点击保存,等到gitee官方审核通过即可。