目录
软件包管理器yum
背景:在Linux下安装软件,一般是下载程序的全部源代码,然后编译,运行,得到可执行程序。
但是这种方法的可移植性极低且复杂,所以就有人把常用软件编译运行好之后打包上传到服务器上,用户通过包管理器可以直接获取到软件包。
yum(Yellow dog Updater, Modified)
需要注意:关于yum的所有操作,必须保证主机网络通畅,yum才可以从远程服务器获取下载链接
rzsz
这是一个linux组件,可以理解为手机上的应用。该工具(软件)用于Windows机器和远端Linux主机通过XShell互传文件。
查看软件包
yum list | grep lrzsz
软件包名称: 主版本号.次版本号.源程序发行号-软件包的发行号.主机平台.cpu架构.
"x86_64" 后缀表示64位系统的安装包, "i686" 后缀表示32位系统安装包. 选择包时要和系统匹配.
"el7" 表示操作系统发行版的版本. "el7" 表示的是 centos7/redhat7. "el6" 表示 centos6/redhat6
最后一列, base 表示的是 "软件源" 的名称, 类似于 "小米应用商店", "华为应用商店" 这样的概念
安装软件
sudo yum install -y lrzsz
安装软件时由于需要向系统目录中写入内容, 一般需要 sudo 或者切到 root 账户下才能完成.
yum安装软件只能一个装完了再装另一个. 正在yum安装一个软件的过程中, 如果再尝试用yum安装另外一个软件, yum会报错.
卸载软件
sudo yum remove lrzsz
Vim
强大的编辑器
命令模式(command)
控制屏幕光标的移动,字符、字或行的删除,移动复制某区段及进入Insert mode下,或者到 last line mode
shift + 4 = $:光标定位到当前行最右侧
shift + 6 = ^:光标定位到当前行最左侧
shift + g :定位到文本尾
n + shift +g :定位到第n行
gg:定位到文本开始
H J K L
n yy:复制当前行,加n复制当前行在内的n行
n p:粘贴到下一行,加n粘贴到下面n行
u(undo):撤销刚才的操作
ctrl + r:撤消撤销操作
n dd:删除所在行,加n删除所在行在内的n行
shift + ` = ~:大小写切换
n r:替换光标所指,加n一次性替换多个
shift + r = R:开启替换模式
n x:删除光标所指,加n一次性删除多个
n shift + x = X :删除光标左侧,加n一次性删除多个
w b:跳词改变光标
shift + 3 = # :高亮光标所指单词,可以用n改变光标
shift + zz:ZZ:保存退出
插入模式(insert)
只有在Insert mode下,才可以做文字输入,按「ESC」键可回到命令行模式。该模式是我们后面用的最频繁的编辑模式。
底行模式(last line)
文件保存或退出,也可以进行文件替换,找字符串,列出行号等操作。 在命令模式下,shift+: 即可进入该模式。要查看你的所有模式:打开vim,底行模式直接输入:
:help vim-modes
:w保存
:wq保存并退出
:!commend不关闭编辑器强制执行指令
:vs filename对比某个文件,如果文件不存在,则创建。比较结束,如果直接退出,文件不存在,如果保存退出,文件则存在。命令模式下ctrl +w + w切换光标所在文件
gcc/g++
编译器做的工作:编译链接过程请看另一篇文章
语言和编译器的自举过程
先有c语言还是先有c语言的编译器?
先有比c语言低级的比如汇编语言写的c编译器,然后用c语言写一个c编译器,用汇编语言写的c编译器编译,得到用c语言写的c编译器。
函数库
在我们的C源代码中,并未定义类似printf的函数,且预编译过程中包含的头文件stdio.h也仅仅只有函数的声明,为什么程序可以正常运行呢?
系统会把函数的实现打包写入一个库文件,Linux下库的名字是libc.so.6,在没有特别指定的时候,gcc会在系统默认的路径下 /usr/lib/ 下去找库文件,这是一个二进制文件,和编译过程中生成的后缀为.o的可重定位目标二进制文件链接,得到可执行程序
函数库分为动态库和静态库:
静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为“.a”
动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,把类似调用printf函数的地方替换为一个地址,这样可以节省系统的开销。动态库一般后缀名为“.so”,如前面所述的 libc.so.6 就是动态库。gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件,如下所示。 gcc hello.o –o hello
gcc默认生成的二进制程序,是动态链接的,这点可以通过 file 命令验证
gcc/g++默认使用动态库
怎么安装静态库?
//root用户: yum install -y glibc-static libstdc++-static
怎么编译
用cpp源文件举例:
默认的编译方式是调用动态库:
g++ work.cpp
生成 a.out 的可执行文件
调用静态库:只需加上-static 选项即可
g++ -o b.out work.cpp -static
手动生成b.out的可执行文件
make/Makefile
像vs2019这样的工具,我们写的大量的文件都会被自动编译,链接,这个过程不需要程序员的参与,这也是集成开发环境的优势。
但是,在原生的环境下,比如我正在使用的云服务器,没有集成开发环境的情况下,写一般的正式项目都会有大量的文件,如何处理他们这个工作由程序员来解决。这个时候需要make/Makefile登场。
make是指令,Makefile是文件
一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。
makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建。
过程实例
先写一个源文件
手动创建Makefile或者makefile
然后配置Makefile
如何配置:
依赖关系和依赖方法
如result:task.c这一行叫做依赖关系,result叫做目标文件,task.c叫做依赖文件列表(或依赖多个文件)
紧跟的一行叫做依赖方法,需要用Tab键缩进
使用 make指令即可生成项目
如果再次make
因为没有修改源文件,生成的项目不会发生改变,所以make工具不会再自动构建项目。
在Makefile配置新的目标
再make前用clean,类似vs2022的清理已经生成的项目
如果这样写Makefile
makefile和make形成目标文件的时候,默认形成第一个目标文件
默认只形成一个
如你所见,使用make重复编译是被不允许的,但是我们可以自己手动使用gcc编译,那么make和Makefile是如何判断源文件是否修改过,需要编译呢?
AMC时间
广义来说,文件 = 内容 + 属性。
Access time:最近一次访问文件的时间,但是由于实际过程中经常访问文件,所以,这个时间的变化不会很确定。
Modify time:最后一次修改文件内容的时间
Change time:最后一次修改文件属性的时间,上图的例子中修改文件内容的同时,文件的大小也会发生变化,所以chang time 也会随之改变
而make和makefile是通过对比源文件和可执行程序的Modify时间来决定是否需要编译的
比如task.c和相对应的result
Makefile
.PHONY修饰伪目标
在.PHONY 定义的伪目标中,那就直接在Makefile中就执行伪目标的依赖和命令。不管Makefile同级目录下是否有该伪目标同名的文件,即使有也不会产生冲突。另一个就是提高执行makefile时的效率
自定义变量
语法推导
进度条
缓冲区和换行回车
c语言有缓冲区这一概念
1 #include <stdio.h>
2 #include <unistd.h>
3
4 int main(void)
5 {
6 printf("1111 \n");
7 sleep(2);
8 return 0;
9 }
1 #include <stdio.h>
2 #include <unistd.h>
3
4 int main(void)
5 {
6 printf("1111 ");
7 sleep(2);
8 return 0;
9 }
代码一的结果是因为\n刷新了缓冲区。在第二个代码片段中,实际上需要打印的内容被保存了起来,但是没有刷新缓冲区。
\r回车 即光标回到当前行开头,且删除行内容
\n换行,跳到下一行对应位置
倒计时
1 #include <stdio.h>
2 #include <unistd.h>
3
4 int main(void)
5 {
6 int i = 10;
7 while(i>= 0)
8 {
9 printf("%-2d\r",i);
10 fflush(stdout);
11 i--;
12 sleep(1);
13 }
14 return 0;
15 }
~
~
~
进度条
//这只是单纯写了一个进度条样式
void processbar2()
19 {
20 char buffer[NUM];
21 memset(buffer,0,sizeof(buffer));
22
23 int count = 0;
24 int n = strlen(LABEL);
25 buffer[0] = HEAD;
26 while(count <= 100)
27 {
28 printf("[%-100s][%3d%%][%c]\r",buffer,count,LABEL[count%n]);
29 fflush(stdout);
30 buffer[count++] =BODY;
31 if(count < 100) buffer[count] = HEAD;
32 usleep(100000);
33 }
34 printf("\n");
35 }
真正的进度条必然和某个东西关联,比如下载,解压。这个代码很麻烦,我这里只讲需要注意的东西,为什么这样写,要怎么写,顺便理清我的思路。
先模拟一个下载场景
文件总大小FILESIZE 一个G,用一个变量total表示剩余下载量,用循环,表示当剩余下载量为0对时候,结束下载,用一个变量one模拟每次的下载量,用随机数来模拟,这里模拟每次下载5M,每次下载,剩余下载量就会减小,因为是随机数模拟,无法保证FILESIZE可以减小到0,所以加一个判断。用一个变量表示下载量,用一个变量表示比率,即下载了百分之多
少,OK,下载场景模拟完了。
#define FILESIZE 1024*1024 void download() 8 { 9 int total = FILESIZE; 10 srand(time(NULL)^1023);//随机数种子 11 while(total) 12 { 13 int one = rand()%(1024*5); 14 total -= one; 15 if(total < 0) total = 0; 16 17 int download = FILESIZE - total; 18 double rate = (download*1.0 / FILESIZE)*100; 19 // printf("%f\n",rate); 20 21 usleep(10000); 22 } 23 }
然后将每次的下载量用进度条的形式表示出来即可,然后回车就行。
直接说,这个实现的进度条:
1.他的“下载速度”是1%,如果你增大上段代码的one,即每次下载量,比率rate会跳跃式变化,所以下面这段代码的赋值会出现问题,总而言之,这里实现的进度条就是模拟每次只能下载1%。
2.光标不和下载百分比挂钩,即你下载速度可能慢,但是光标一直在变化
OK,buffer数组定义为全局变量,你不能每次调用这个进度条,都开一个数组,用单独一个变量count来表示光标的变化。用# 来画这个进度条,打印只打印100个字符,即这个数组的下标0 - 99,fflush将打印内容刷新出来。
#define SIGN '#' void processbar_flush(double rate) 38 { 39 static int count = 0; 41 42 printf("[%-100s][%.1f%%][%c]\r",buffer,rate,LABEL[count%4]); 43 fflush(stdout); 44 45 buffer[(int)rate] = SIGN; 46 48 if(rate >= 100.00 ) printf("\n"); 49 count++; 50 count %= 4; 51 }
git
怎么用
gdb
程序最后有两种发行模式,一种是release,也叫发行版,一般用来测试,该程序不包含调试信息;一种是debug模式,包含调试信息。gcc默认编译的结果是release版,所以为了调试需要加-g选项
gcc -o mytest test.c -g
所有的调试器都是为了帮助我们发现程序的问题,所以gdb的指令可以类比vs2019的调试功能来理解。
list或者l 行号:显示源文件代码,每次显示10行
list或者l 函数名:显示函数的源代码
run或者r:运行程序
break或者b 行号/函数名/文件:行号:打断点
next或者n:类比vs2019 F10,执行一行
step或者s:类比vs2019 F11,执行一句
info break或者b:显示断点信息
delete或者d n:删除编号为n的断点
disable/enable n:使能编号为n的断点
——
print或者p 变量:打印
display 变量:常显示
undisplay 编号:取消常显示
continue或者c:从一个断点运行到下一个断点
finish:将当前函数运行结束
until:运行到第n行,前提是合法的
breaktrace或者bt:调用堆栈
set var name=value:临时修改name的值为value,执行某一个分支,不修改源代码