NOI Linux 指北
从终端开始
什么是终端?
对于一个计算机来说,输入输出设备显然不是必需品(脱离使用的范畴,没有输入输出设备它也能通电成为一个吉祥物),而最初人们为了与计算机互动,发明了终端,即人类用户与计算机交互的设备。
对于一个计算机操作系统,其运行过程中本身并没有显示任何东西,所谓桌面环境之类在显示器上显示的东西都是后话,最简单的输入输出方式即“我输入文字,计算机输出文字”,于是引入今天我们所要遇到的第一个概念,我称其为“文字终端”,即你输入文字,计算机输出文字的地方。
对于现在来说,以往奢侈的图形显示现在已经满地都是,但是为了还能用上简单快捷的文字终端的功能,出现了“终端模拟器”,实际上就是开一个小黑窗口,里面能够输入输出(类比 CMD)。
打开终端
在桌面或者文件管理器中右键,点击“在终端中打开”
于是出现了一个如上的窗口,这就是一个终端模拟器,里面运行一个被称为“Shell”的进程,这里不多作介绍,理解它能够输入输出文字就行。
什么是 SHELL?
呃,点开这里就默认你已经知道编写程序、输入输出什么的基本概念了。
SHELL,顾名思义,是一层“壳”。前面说到,我们需要一个“终端”来进行交互,而SHELL实现的东西就是“输入”,处理,然后“输出”。相当于给机器套了一层“可交互”的“外壳”。
和终端模拟器的区别?你显然输入要有来源,输出要有目标。终端模拟器就相当于一个屏幕,他能把你键盘里输入的东西扔到SHELL 里面,然后把 SHELL 输出的东西扔到你的眼前。
NOI Linux,或者说 Ubuntu,默认使用的 Shell 是 Bash。
在这里你可以输入命令,做到创建文件,修改文件,删除文件等基本上你在图形界面下能做的所有事情。
先...管理文件?
在 Shell 中,.
表示当前目录,..
表示当前目录的父目录,/
表示根目录
Linux 下只有一个根目录,不会像 Windows 一样不同的盘有不同的根目录
也就是如下
Windows:
1 | C:- |
Linux:
1 | / - |
绝对路径:能唯一确定的路径,也就是说从根目录表示的确定的位置,比如 /usr/local/bin
相对路径:相对于某一目录的位置,比如当前目录是 /usr/local/
, 则上面的路径可以表示为 bin
或 ./bin
先提供相应的命令:
1 | 这是一条注释,SHELL 中使用井号做单行注释 |
NOI Linux 2 的比赛环境下默认给你挂载 noip 文件夹并在 Linux 桌面上创建快捷方式,因此如果你在桌面上进入终端,则可以通过下面的命令进入 noip 文件夹
1 | cd noip |
或者
1 | cd ./noip |
开始写代码吧!
想编辑下文件?
如果你在 Windows 下写好了代码想要粘贴到 Linux 的文件里,则可以
1 | 新建 a.cpp 文件 |
nano 是 GNU 的一款简易终端文本编辑器,相对于 Vi/Vim 来说更适合新手使用
复制了 Windows 的代码之后,确保 VMware 的共享剪贴板已经打开,然后按下 Ctrl
+ Shift
+ V
粘贴,然后按下 Ctrl
+ S
保存,Ctrl
+ X
退出。
当然,有图形化编辑器供你使用
1 | 使用闭源 Visual Studio Code 编辑 a.cpp |
在此之后你可以通过 cat
命令1验证文件内容
1 | cat a.cpp |
编译 C++ 源码
GNU 编译套件
GNU Compile Collection 是 GNU 开发的一套编译器,在这里我们只使用 GNU C/C++ 编译器部分。
另:GNU计划的目标是开发出一款完全自由的操作系统和软件生态,自由软件运动致力于推进全部软件的隐私保护以及自由化,自由软件属于全人类,自由软件开发者们留下的作品与学习资料是不朽的!如果你对自由软件运动有疑问或有兴趣,可前往GNU 官方网站 了解更多相关内容。
开始编译
1 | g++ grainrainisgod.cpp |
这个命令会在当前目录下编译 grainrainisgod.cpp 文件,然后默认输出一个 a.out 可执行文件2。
可是我懒得重命名文件,所以如何执行输出可执行文件的位置和名称呢?只需要加 -o /path/to/direction
即可
1 | g++ grainrainisgod.cpp -o grainrainisgod |
当然,你也可以加入其他比赛要求的编译选项,比如说吸个氧(加 -O2
参数)
1 | g++ grainrainisgod.cpp -o grainrainisgod -O2 |
开启编译警告
编译警告在编译的编译阶段和警告阶段发出,能帮你发现一些低级问题和简单 UB,比如著名 UB(但是萌妹 OPTIM 还是犯了这个问题)
1 | a = a++ + ++a; |
编译器会发出警告
1 | warning: multiple unsequenced modifications to 'a' [-Wunsequenced] |
这样的警告对于比赛选手来说肯定是越多越好越全越好,所以需要开启全部警告,即添加 -Wall
参数
1 | g++ grainrainisgod.cpp -o grainrainisgod -O2 -Wall |
检查内存错误以及未定义行为
Sanitizer 是谷歌开发的一个检测程序问题的开源套件,一般集成在 gcc 以及 clang 编译器中。相应的,MinGW-W64 并没有为 Sanitizer 做 Windows 兼容,因而无法在比赛环境 Windows 使用(LLVM/Clang 编译器包含的 Sanitizer 支持 Windows,MSVC 的也支持但是只有 AddressSanitizer)(但是显然比赛环境没有这两个编译器),所以显得 Linux 环境非常重要。
Address Sanitizer 用于检测程序的内存错误,比如爆栈等问题。使用方法是添加 -fsanitize=address
选项
Undefined Behaviour Sanitizer 用于检测程序的未定义行为,比如访问空指针,数组越界访问等问题,使用方法是添加 -fsanitize=undefined
选项。
值得注意的是,这两个 Sanitizer 会显著拖慢程序运行速度,所以添加这两个编译选项编译出来的程序在运行时间和内存占用上参考意义不大。同时如果动态分配内存过多可能 Address Sanitizer 会抽风,UBSanitizer 倒是问题不大。所以酌情使用。
总结:
1 | g++ grainrainisgod.cpp -o grainrainisgod -O2 -Wall -fsanitize=address -fsanitize=undefined |
如上是我赛时经常使用的编译命令。
执行可执行文件
1 | ./grainrainisgod |
.
(当前目录)+ /
(目录分隔符)+ grainrainisgod
可执行文件名称
注意,这里不能直接输入文件名,必须加上表示路径的符号(相对和绝对都行,此举是为了保证你知道你要执行的程序是“当前目录下”的“这个程序”,别执行成“李鬼”了)。
然后就可以快乐地输入了。
注意:在这个终端中复制使用 Ctrl
+Shift
+ C
,粘贴使用 Ctrl
+Shift
+V
Ctrl
+ C
用于终止当前正在运行的程序,也可以在输入命令打错字时 Ctrl
+ C
重开一行。
进阶:一些 bash 指令
刚才已经学过一些命令了,接下来将会教给大家更多的 bash 相关的东西。
关系运算符
command1 && command2
:当command1
执行成功后才会执行command2
。command1 || command2
:当command1
执行失败时才会执行command2
。
然后给几个练习题:
命令echo
:后面接一个字符串,作用是输出这个字符串。
示例:echo tibrellalaji
,回车,终端出现了tibrellalaji
一串字符。
猜测以下的命令输出结果是什么?(一行一题)
1 | echo 1 && echo 23 |
答案
1 | 忽略报错信息 |
变量
赋值
1 | a=1 |
bash 变量无需声明,可以直接赋值之后使用。
变量名要求和 C++ 差不多:
- 字母、数字、下划线
- 数字不能开头
但是赋值和大部分语言是不同的:等号两边不能有空格。
你不必在乎 bash 的变量类型。
使用
变量名前加 $
,两边可以加大括号也可以不加。
示例:
1 | mainpage=www.tibrella.space |
容易发现大括号的作用:区分变量名的边界。如果你写的是 echo https://$mainpageisgood
的话后面就是空的了,因为 bash 把 mainpageisgood
识别成了一个变量名,而这个变量不存在(也就是为空)。
输入输出重定向与管道符
这里是重点,在 OI 中写对拍脚本啥的非常能用得上这个东西。
假设有一个文件叫做 testfile
,一个程序叫 test
,则:
还记得吗?执行文件时必须加 ./
,否则不能执行。
testfile < ./test
:将test
的输入(stdin
)重定向到testfile
,相当于在你的程序中加入了freopen("testfile","r",stdin)
./test > testfile
:将test
的输出(stdin
)重定向到testfile
,相当于在你的程序中加入了freopen("testfile","w",stdin)
。换句话说,test
程序输出的东西将会写入到testfile
中。- 上面两个可以同时使用,比如
joker.in < ./joker > joker.out
,相当于程序中加了两行freopen
。
然后是管道符,它代表把前一个程序的输出重定向到后一个程序的输入里面。
cat testfile | ./test
:输出filename
文件的内容,并把其重定向到test
程序的输入里面。等价于testfile < ./test
为啥他叫管道符呢?现在我们写一个输入一个字符串再输出它的程序:
1 |
|
编译一下:g++ test.cxx -o test
然后执行这个命令:
1 | echo pipe | ./test | ./test | ./test |
我们发现最后的输出是 pipe
。
显然,这玩意就像管道,能把不同的机器(程序)的输入输出连接起来。
编写一个 bash 脚本
1 | touch test.sh # 创建一个 bash 脚本 |
文件内容:
1 |
|
#!/bin/bash
表明你这个 test.sh
文件需要使用 /bin/bash
程序运行。(写 #!/bin/sh
也行,这里讲的语法和 sh/zsh 是兼容的,但是你亲爱的 NOI Linux 只有 bash
和 sh
)
按下 Ctrl
+ S
保存,Ctrl
+ X
退出。
然后你需要让这个文件可以被执行,即给他“被执行”的权限:
1 | chmod +x test.sh |
执行可以直接当可执行文件执行。
1 | ./test.sh |
输出:
1 | Hello World |