常用的命令及快捷键

本文最后更新于 2024年10月17日 上午

本文主要分享了自己在使用Linux和Windows系统及各种软件过程中常用的命令及快捷键。

注意中文和英文字符-

Linux

Win = Super

快捷键

一般

快捷键 作用
Win+L 锁屏
Ctrl+Alt+T 新建终端
Ctrl+Alt+A(自定) flameshot 截图
Ctrl+Alt+F(自定) fsearch 搜索文件
Ctrl+Shift+N 新建文件夹
Win+I(自定义) 打开设置中心
Ctrl+L 全选文件夹路径
F2 文件(夹)重命名
Win 活动大纲
Win+Tab/Alt+Tab 切换应用程序
Ctrl+Alt+上/下箭头,点击桌面左上角的“Activities” 切换工作区(同一屏幕的全部内容)
Alt+Enter 查看选择文件/文件夹的属性,代替单击右键选择属性
Ctrl+1/2 改变文件夹视图查看方式,图标视图/列表视图
Ctrl+H 显示隐藏文件
双击Ctrl(自定) 打开uTools
启动位于任务栏的程序 Win + 数字键

窗口

快捷键 作用
Win+E(自定义) 新建文件夹窗口
win+H 隐藏窗口
Win+D 隐藏或显示全部应用窗口
Win+A 显示应用程序菜单
Super+箭头 移动窗口位置(左/右:贴左/右;上:最大化;下:恢复)
Ctrl+W 关闭标签页
Ctrl+Q/Alt+F4 (强制)关闭应用程序(窗口)。ALT+F4是基于任务管理器连接结束进程项,CTRL+Q只是关闭的快捷键
Alt+空格(自定) 激活窗口菜单(内含置顶选项,也适用于没有系统标题栏的应用)
Win+Shift+向上/下翻页 将窗口上/下移一个工作区
Win+Shift+上/下/左/右箭头 将窗口上/下/左/右移一个显示器
Alt+F7(自定义) 使用键盘方向键移动窗口,按住Shift键快速贴边,按Enter键确认

目录

快捷键 作用
Backspace 返回上一级目录
Alt+左箭头 回退(搭配Backspace回到下一级目录)
Alt+右箭头 前进(只能响应回退操作)

打开文件夹之后,按backspace删除键就可以返回上一级;或者Alt+←也是可以返回上一级,用Alt+←返回上一级后,按Alt+→键就可以倒退回去一级。

鼠标

  1. 直接拖拉在最大化时的窗口标题可以还原窗口大小,再拖放回去就最大化。
  2. 在音量控制处用鼠标滚轮滚动可以直接调整音量,在任务栏中滚动滚轮可以切换任务栏,工作区则切换工作区。
  3. 工作区里的小窗口可以在不同工作区内拖拉,即移动到另一个工作区的功能。
  4. 按住 Shift 拖动窗口可以吸附上下边缘的移动。
  5. 水平滚动文件,请在按住 Shift 的同时转动鼠标滚轮。
  6. 双击选中词,三击选中整句。
  7. 按住Alt,鼠标可以选中带链接的内容(而不打开链接)。
  8. 按住Shift,鼠标在开头和结尾点击,可以选中全文(不用拖动了)。
  9. 拖动选中可直接完成复制粘贴的操作(对于某些禁止复制粘贴的网站)。
  10. 在Win11,按住Shift,点击鼠标右键,呼出旧版菜单。
  11. 鼠标中间有快捷关闭的功能。

路径

  1. 对于在.zshrc等文件中使用export命令时,举例说明:

    1
    2
    3
    4
    5
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/ros/melodic/lib:/path/else
    export PATH=/home/uusama/mysql/bin:$PATH
    # 例如新增LD_LIBRARY_PATH,注意
    # 1. 使用$LD_LIBRARY_PATH来引用前面定义的LD_LIBRARY_PATH,防止后面的覆盖掉了前面的。
    # 2. 路径之间用英文:分隔,中间不需要加空格。

    系统查找命令时按照冒号分割顺序从前向后依次查找。

    配置路径的文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    sudo updatedb  # 更新数据库

    sudo gedit ~/.bashrc
    source ~/.bashrc
    sudo gedit ~/.zshrc
    source ~/.zshrc

    sudo gedit /etc/profile # 此文件涉及系统的环境,即环境变量相关。这里修改会对所有用户起作用。
    # 不能对zsh直接生效?需要在命令行里运行一遍source ~/.zshrc才能生效。
    source /etc/profile # 让环境变量生效

    sudo gedit /etc/ld.so.conf.d/opencv.conf
    sudo ldconfig # 运行配置

    sudo gedit /etc/ld.so.conf
    sudo ldconfig # 运行配置

    sudo gedit /etc/bash.bashrc
    source /etc/bash.bashrc
  2. Ubuntu快捷方式图标的存放位置:

    • /usr/share/applications
    • /usr/share/icons/Mojave-CT-Night/mimes/scalable
  3. 打开回收站:

    1
    cd ~/.local/share/Trash/files
  4. wine软件安装路径:/home/lll25655/.wine/drive_c/'Program Files (x86)'

  5. 打开eDiary:

    1
    wine /home/lll25655/.wine/drive_c/'Program Files (x86)'/eDiary-4.2.2-win64/eDiary.exe
  6. 第三方库库默认安装路径

    1. /usr/include

    2. /usr/local/include

  7. 字体安装路径:/usr/share/fonts

小贴士

  1. 直接在当前文件夹窗口打字,可以搜索该目录及子目录下的文件(夹)。按ESC键退出。
  2. Application 键即位置在键盘上右 Ctrl 键左边的那个键,作用相当于单击鼠标右键。
  3. 误按Ctrl+Alt+F7(进入文字界面)黑屏后按Ctrl+Alt+F1回到图形界面。
  4. 直接将"文件管理器"中的文件拖到"终端"中就可以在终端中得到完整的路径名。

终端Terminal

Linux命令

软件、库的安装、卸载和升级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 安装
sudo apt update # 先更新软件包列表,检查可用的软件包更新
# 查看可选的安装版本
apt-cache search exact_package_name # 你可以使用一个正则表达式来搜索本地 APT 缓存中的包。
apt-cache policy exact_package_name # 如果你指定了软件包的名称,它将显示该软件包是否已经安装,在哪个版本的仓库中可用,以及它的优先级。
sudo apt install exact_package_name[=xxx] # 安装软件,例如:sudo apt install libc6-dev=2.27-3ubuntu1.6

# 卸载(不知名的软件)
apt list --installed | grep -i possible_package_name # 模糊搜索
sudo apt remove exact_package_name # 卸载软件
sudo apt-get purge exact_package_name # 清除配置

# 升级
# apt 可以看作 apt-get 和 apt-cache 命令的子集, 可以为包管理提供必要的命令选项。apt-get 虽然没被弃用,但作为普通用户,还是应该首先使用 apt。
sudo apt update # 更新软件包列表,检查可用的软件包更新
apt list --upgradable # 查看可用安装
sudo apt upgrade # 安装可用的软件包更新
sudo apt install exact_package_name # 直接升级特定的包

优先级:

  • < 0:永远不会安装,
  • 1..99:仅当尚未安装其他版本的软件包时才会安装,
  • 100..499:仅当其他发行版中没有安装或提供其他更新版本时才会安装,
  • 500....989:仅当目标发行版中没有安装或没有更新的版本时才会安装,
  • 990..1000:将安装,除非安装的版本较新,
  • > 1000:将始终安装,即使它强制 APT 降级到旧版本。

默认情况下,每个已安装的软件包版本的优先级为 100,未安装的软件包的优先级为 500。同一软件包可能有多个不同优先级的版本。APT 会安装优先级较高的版本,除非安装的版本较新。

查找查看

辅助搭配

Linux grep (global regular expression) 命令用于查找文件里符合条件的字符串或正则表达式。

1
ls -l /usr/local | grep cuda  # 查询`/usr/local`路径下的软链接,且要求包括`cuda`字符串。

常用选项:

  • -i:忽略大小写进行匹配。
  • -v:反向查找,只打印不匹配的行。
  • -n:显示匹配行的行号。
  • -r:递归查找子目录中的文件。
  • -l:只打印匹配的文件名。
  • -c:只打印匹配的行数。

当前终端

1
2
3
4
5
6
7
8
9
10
cat /etc/shells  # 查看系统安装了的shell
# /usr/bin/zsh为软链接,指向/bin/zsh。可能是为了系统兼容性。
echo $SHELL # 当前的shell。实际没显示成功,但是确实改成功了?
# 切换终端Shell
chsh -s /bin/bash
chsh -s /bin/zsh
chsh -s $(which zsh)
# 或
exec bash # 将命令行从zsh切换为bash
exec zsh # 重新运行zsh命令行工具

环境变量

1
2
echo $PATH
echo $CUDA_HOME

内存

1
2
free -g  # 以 GB 为单位,显示内存使用情况。
# -s 根据指定的间隔时间,持续显示内存使用情况。

文件系统和挂载点

1
2
3
4
# 推荐,可查看到交换空间
cat /etc/fstab
# 或,信息更全,有硬盘使用率等
df -hT

文件(夹)大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# du 命令需要在root权限下操作,或者在命令行前加sudo命令也可以。但排序是乱的,不好查看。
# 使用 sort 命令 的 -k 参数设置对第二列的值进行重排,也就是按照文件(夹)名进行排序
sudo du -ah --max-depth=1 --exclude=./proc --exclude=./tmp --exclude=./lost+found --exclude=./media --exclude=./mnt --exclude=./run /path/to/your/folder/ | sort -k 2

df -B G /path/to/your/folder/ # 以GB为单位显示指定磁盘文件的使用情况。如果没有指定文件,则显示所有挂载的文件系统的磁盘使用情况。
df -B M /path/to/your/folder/ # MB
# 加路径为当前路径所在的磁盘空间,不加路径为所有。

# 上述两个命令的常用参数:
-a或 -all 显示目录中个别文件的大小。
-h或 --human-readable 以K,M,G为单位,提高信息的可读性。
--max-depth=<目录层数> 超过指定层数的目录后,予以忽略。
-exclude=<目录或文件> 略过指定的目录或文件。

ll # 文件大小以字节为单位显示,并且在输出中的第 5 列。

文件(夹)修改时间

1
ll  # ls -l

版本信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# Linux内核版本
uname -a # 系统架构x64(x86_64、amd64)
# 系统架构:x64(x86_64、amd64)、arm64(aarch64)、mips64(mips64el)
# cmake
cmake --version
# conda
conda --version
# CPU核心数
nproc
# CPU占用
top
# CUDA
ls -l /usr/local | grep cuda # 检查是否安装了CUDA
nvcc -V # 查看自己安装的CUDA版本
lspci | grep -i nvidia # 检查系统是否有支持 CUDA 编程的 GPU
# cuDNN
apt show libcudnn8 # 通过查询已安装的libcudnn8包来验证cuDNN的版本
# 查看版本(路径可能不一致,有一个命令有输出就行)
cat /usr/include/cudnn_version.h
cat /usr/include/cudnn.h
cat /usr/local/cuda/include/cudnn_version.h | grep CUDNN_MAJOR -A 2
cat /usr/local/cuda/include/cudnn.h | grep CUDNN_MAJOR -A 2
# Eigen
/usr/include/eigen3 # Eigen安装路径
/usr/include/eigen330 # Eigen3.3.0安装路径
/usr/include/eigen340 # Eigen3.4.0安装路径
gedit /usr/include/eigen3/Eigen/src/Core/util/Macros.h
# GLIBC_
ldd --version
strings /lib/x86_64-linux-gnu/libm.so.6 |grep GLIBC_ # strings /lib64/libc.so.6 |grep GLIBC_
ll /lib/x86_64-linux-gnu/libm.so.6
# gcc
gcc -v
# java
java -version
# 内存
free
# NVIDIA
lspci | grep -i nvidia # 查看是否安装NVIDIA显卡
nvidia-smi # 查看显卡信息
watch -n 0.2 nvidia-smi # 每 0.2 秒刷新一次
# 显卡型号
lspci | grep -i nvidia
# Opencv
/usr/local # opencv3.1.0安装路径
/usr/local/opencv/opencv320 # opencv3.2.0安装路径
/usr/local/opencv/opencv345 # opencv3.4.5安装路径
pkg-config --modversion opencv # 查询版本
pkg-config --cflags opencv # 得到opencv的安装路径
# -> -I/usr/local/include/opencv -I/usr/local/include
pkg-config --cflags opencv4 # 附加:得到opencv4的安装路径
# openssl
openssl version -a
# Pangolin
find / -name pangolin
# protoc
protoc --version
# python
which python
ll /usr/bin/python
python -V # python --version
sudo update-alternatives --config python
sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 1
sudo update-alternatives --install /usr/bin/python python /usr/bin/python2 2
# Qt5.9.9
qmake -v
# ROS
echo $ROS_PACKAGE_PATH

用户手册

Linux man 命令是 "manual" 单词的缩写,用于查看各种命令、函数和配置文件的手册页面。

1
man [选项] [节号] 命令/主题

查找文件

1
2
3
4
5
6
7
8
9
10
11
sudo updatedb  # 更新数据库

# find命令
find / -name httpd.conf # 在根目录下查找文件httpd.conf,表示在整个硬盘查找
find -name your-file-name # 在当前目录中搜索
# 可以使用通配符*来实现粗略匹配
# 可以逻辑运算符not(!)、and(-a)、or(-o) 一起使用以组成的复合条件进行文件查找
find / -name hello_world* 2>/dev/null # 忽略错误信息输出

# locate命令
locate filename

目录列表

1
2
3
4
5
6
7
8
pwd  # 显示当前目录位置 
ls
-a 显示所有文件及目录 ( 以. 开头的隐藏文件也会列出)
-l 以长格式显示文件和目录信息,包括权限、所有者、大小、创建时间等
-h:文件大小以K,M,G为单位,提高信息的可读性
ls /usr/bin/gcc* # 在/usr/bin目录下所有含有gcc前缀的文件
#此处使用 ll 命令更好(可以看到软链接):
ll /usr/bin/gcc*

文件内容

参考链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 查看整个文件
cat [filename] # cat /home/user/test.txt

# 查看开头几行
head -n [rows] [filename] # head -n 20 /home/user/test.txt

# 查看末尾几行
tail -n [rows] [filename] # tail -n 10 /home/user/test.txt

# 查看中间几行
# 方法一:
cat [filename]| head -n [endRow] | tail -n +[beginRow]
# 查看/home/user/test.txt的10~20行
# head -n 20 表示前20行
# tail -n 10 表示后10行
# tail -n +10 表示第10行之后的
cat /home/user/test.txt| head -n 20 | tail -n +10
# 方法二:
sed -n '[begin],[end]p' [filename]
sed -n '10,20p' /home/user/test.txt

查找过去编辑的文件

1
find . -type f -mmin -10  # 查找过去 10 分钟内编辑的所有文件

历史命令

1
2
3
4
history 5  # 这将显示您最近执行的五个命令,以便于快速调用和重新运行它们。
# 您可以使用感叹号 ( ! ) 后跟命令编号来快速重新执行历史记录中的任何命令。
# 例如,如果您想重新运行上面的mkdir newdir命令(命令号 125),您只需键入:
!125

文件新建、复制、移动、重命名和删除

基本

处理(特别是包含众多小文件的)大文件夹推荐使用[rsync](# rsync)命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
gedit ~/.bashrc	 # 使用gedit打开文件
touch file1.txt # 新建文件

mkdir [-p] dirName # 创建目录。-p 确保目录名称存在,不存在的就建一个。
mkdir -p runoob2/test # 在工作目录下的 runoob2 目录中,建立一个名为 test 的子目录。若 runoob2 目录原本不存在,则建立一个。(注:本例若不加 -p 参数,且原本 runoob2 目录不存在,则产生错误。)
mkdir -p {dev,test,prod}/{backend,frontend} # 使用大括号{}一次性创建多个目录

# 在指定的目录下,创建一个名为 LINK_NAME 的硬链接或符号链接,指向名为 TARGET 的文件或目录。
ln [OPTION]... [-T] TARGET LINK_NAME

# 复制文件或目录cp命令
# 示例
cp file.txt /path/to/destination/
-a:此选项通常在复制目录时使用,它保留链接、文件属性,并复制目录下的所有内容。其作用等于 dpR 参数组合。
-r 或 --recursive:用于复制目录及其所有的子目录和文件,如果要复制目录,需要使用该选项。
-i 或 --interactive:在复制前提示确认,如果目标文件已存在,则会询问是否覆盖,回答 y 时目标文件将被覆盖。
-p 或 --preserve:保留源文件的权限、所有者和时间戳信息。

# Linux `mv`(英文全拼:move file)命令用来为文件或目录改名、或将文件或目录移入其它位置
# 注意:需要先执行`Ctrl+H`显示隐藏文件命令,否则,隐藏文件以及隐藏文件夹不会被移动到新目录
# 将源文件名 source_file 改为目标文件名 dest_file
mv source_file(文件) dest_file(文件)
# 将文件 source_file 移动到目标目录 dest_directory 中
mv source_file(文件) dest_directory(目录)
# 目录名 dest_directory 已存在,将 source_directory 移动到目录名 dest_directory 中;
# 目录名 dest_directory 不存在则 source_directory 改名为目录名 dest_directory
mv source_directory(目录) dest_directory(目录)
# 出错
mv source_directory(目录) dest_file(文件)

# 删除文件和文件夹rm命令
rm -rf test/ # 删除 test 文件夹,删除文件夹要加 -r 参数
-i 删除前逐一询问确认。
-f 即使原档案属性设为唯读,亦直接删除,无需逐一确认。
-r 将目录及以下之档案亦逐一删除。
# 删除当前目录及其子目录中所有.json文件的bash命令:
find . -name "*.json" -type f -delete
# find:这是主命令,用于查找文件。
# .:这告诉find命令从当前目录开始搜索。
# -name "*.json":这告诉find命令查找所有以.json结尾的文件。
# -type f:这告诉find命令只查找文件,不包括目录。
# -delete:这告诉find命令删除找到的所有文件。

rsync

基本语法
1
sudo apt-get install rsync
  • -a, --archive: 归档模式,表示以递归的方式传输文件,并保持所有文件属性不变
  • --delete: 删除那些target中有而source没有的文件
  • --exclude: 指定排除不进行同步的文件,比如--exclude="*.iso"
  • -h: 以人类可读的格式输出。
  • --progress: 显示进度
  • -v: 详细输出模式
用法

rsync命令不仅可以传输文件夹,还可以传输单个文件。

复制/移动大文件夹

本机使用 rsync 命令时,可以作为cpmv命令的替代方法,将源目录同步到目标目录。

1
2
3
rsync -ahv --progress source/ destination/  # 把source文件夹的内容复制进destination文件夹
rsync -ahv --progress source destination/ # 把source文件夹复制进destination文件夹
rsync -ahv --progress source1/ source2/ destination/ # source1、source2都会被同步到destination目录。
删除大文件夹

在删除包含许多小文件的大文件夹时,可以考虑使用rsync命令,以提高删除速度。以下是具体步骤:

  1. 使用rsync将文件夹同步为空目录,达到快速删除文件的效果。
  2. 删除空目录。

具体命令如下:

1
2
3
4
mkdir empty_dir/  # 创建一个空目录
rsync -ahv --progress --delete empty_dir/ /path/to/your/folder/ # 将目标文件夹同步为空目录,快速删除文件内容
rmdir /path/to/your/folder/ # 删除同步后的空目录
rmdir empty_dir/ # 删除创建的空目录

请替换/path/to/your/folder/为你要删除的文件夹的实际路径。

增量备份

参考链接

rsync 的最大特点就是它可以完成增量备份,也就是默认只复制有变动的文件。

除了源目录与目标目录直接比较,rsync 还支持使用基准目录,即将源目录与基准目录之间变动的部分,同步到目标目录。

具体做法是,第一次同步是全量备份,所有文件在基准目录里面同步一份。以后每一次同步都是增量备份,只同步源目录与基准目录之间有变动的部分,将这部分保存在一个新的目标目录。这个新的目标目录之中,也是包含所有文件,但实际上,只有那些变动过的文件是存在于该目录,其他没有变动的文件都是指向基准目录文件的硬链接。

--link-dest参数用来指定同步时的基准目录。

1
rsync -a --delete --link-dest /compare/path /source/path /target/path

上面命令中,--link-dest参数指定基准目录/compare/path,然后源目录/source/path跟基准目录进行比较,找出变动的文件,将它们拷贝到目标目录/target/path。那些没变动的文件则会生成硬链接。这个命令的第一次备份时是全量备份,后面就都是增量备份了。

下面是一个脚本示例,备份用户的主目录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash

# A script to perform incremental backups using rsync

set -o errexit
set -o nounset
set -o pipefail

readonly SOURCE_DIR="${HOME}"
readonly BACKUP_DIR="/mnt/data/backups"
readonly DATETIME="$(date '+%Y-%m-%d_%H:%M:%S')"
readonly BACKUP_PATH="${BACKUP_DIR}/${DATETIME}"
readonly LATEST_LINK="${BACKUP_DIR}/latest"

mkdir -p "${BACKUP_DIR}"

rsync -av --delete \
"${SOURCE_DIR}/" \
--link-dest "${LATEST_LINK}" \
--exclude=".cache" \
"${BACKUP_PATH}"

rm -rf "${LATEST_LINK}"
ln -s "${BACKUP_PATH}" "${LATEST_LINK}"

上面脚本中,每一次同步都会生成一个新目录${BACKUP_DIR}/${DATETIME},并将软链接${BACKUP_DIR}/latest指向这个目录。下一次备份时,就将${BACKUP_DIR}/latest作为基准目录,生成新的备份目录。最后,再将软链接${BACKUP_DIR}/latest指向新的备份目录。

mkcd

mkdircd命令合并为一个mkcd命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mkcd() {
mkdir -p "$1" && cd "$1"
}

# or
function mkcd {
if [ ! -n "$1" ]; then
echo "Enter mkcd followed by a directory name"
elif [ -d $1 ]; then
echo "\`$1' already exists"
else
mkdir $1 && cd $1
fi
}

这个函数接受一个参数($1),首先使用mkdir -p命令创建一个目录,然后使用cd命令进入这个目录。-p选项可以确保如果目录已经存在,mkdir命令不会报错。

你可以将这个函数添加到你的bash配置文件(如~/.bashrc~/.bash_profile)中,这样每次打开一个新的终端时,这个函数都会被定义。

使用这个函数的方式如下:

1
mkcd new_directory

这将创建一个名为new_directory的新目录,并立即进入这个目录。

下载

wget 与 curl 命令详解

测试网络连接:

1
ping www.baidu.com

wget

1
2
3
4
5
6
7
8
9
sudo apt install wget

# 下载单个文件
wget [options] <URL>
wget -O --show-progress myfile.zip http://www.example.com/testfile.zip # 如果不指定"-O" 选项,wget默认会以 url 路径最后一个 "/" 的后面全部字符为下载的文件名

# 断点续传
wget -c http://www.example.com/testfile.zip # 当下载的文件特别大或者网络原因,文件没有下载完连接就已经被断开,使用 -c 选项可以在网络连接恢复时接着上次的下载任务继续下载,而不需要重头开始下载文件
wget --tries=40 http://www.example.com/testfile.zip # wget默认重试20次连接下载文件,如果网络一直有问题下载可能失败。如果需要的话,你可以使用--tries增加重试次数。

下载超时,尝试添加参数--no-cookie --no-check-certificate

curl

1
2
3
4
5
6
7
8
9
10
11
# 单个文件下载
curl [ -o 自定义文件名| -O] --progress-bar http://www.example.com/index.html
# -o 自定义文件名:把服务器响应输出到指定文件
# -O:与-o选项作用一样,区别在于以 url 路径最后一个"/"之后的部分作为文件名
# 如果这两个选项都不写,curl 默认会把服务器响应内容输出到终端

# 断点续传
curl -O -C 偏移量 http://www.example.com/testfile.zip
# -C 偏移量:从指定的偏移量处继续下载,偏移量以字节为单位
# 如果让curl自动推断出正确的续传位置可以使用 "-" 代替偏移量,例如:
curl -O -C - http://www.example.com/testfile.zip

目录(跳转)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
mkdir 新建目录

./(点斜杠)表示当前工作目录的相对路径
.表示当前目录
..表示(当前目录的)父目录

pwd # 打印当前所在目录

cd dir # 切换到指定目录
cd / # 进入根目录
cd ~ # root用户, 相当于 cd /root;普通用户,相当于cd /home/当前用户名,即主目录
cd - # 切换最开始的目录
cd . # 目前所在目录
cd .. # 返回上一层
cd ../.. # 返回上两层
cd ../*** # 进入当前目录父目录的**目录
cd ~=cd /root # 进入root的根目录

# 标记目录
# 记录当前目录路径,待会返回需要用到
cur=`pwd`
# 返回项目
cd $cur
  1. 把目录结构信息保存到文本中,树状图:

    1
    tree > /home/luke/tree.txt
  2. 在终端打开文件管理器:nautilusxdg-open[空格]目标路径。

    1
    2
    3
    4
    5
    nautilus /path/to/directory  # or
    xdg-open /path/to/directory
    # 打开当前终端所在目录
    nautilus . # or
    xdg-open .

压缩解压缩

zip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 一般处理
zip -r FileName.zip DirName # 压缩
unzip test.zip # 解压
unzip -O GBK 6.zip # 解决解压出来中文乱码

# 高级处理
# 压缩、设置密码且分卷
zip -r -P 123456 temp.zip data/
zip -s 10m temp.zip --out data.zip
# 解压缩分卷
# 好用
sudo apt-get install p7zip
sudo apt-get install p7zip-full
sudo apt-get install p7zip-rar
7z x data.zip # 分卷文件在同一文件夹下,解压首文件即可,不需要合并分卷。密码会在解压终端提示输入。
# 不好用
cat data.* > tounzip.zip
unzip -P 123456 tounzip.zip

tar

.tar.gz = .tgz

1
2
tar -zcvf FileName.tar.gz DirName  # 压缩
tar -zxvf cmake-3.23.0-rc1.tar.gz # 解压
  • -c: 创建新的归档文件
  • -z: 使用 gzip 压缩归档文件
  • -v: 显示详细输出,列出被添加到归档中的文件
  • -f: 指定归档文件的名称

tar.xz

1
2
3
4
5
6
7
8
# 压缩
tar -cvf Image.tar Image/
xz -z Image.tar # 如果要保留被压缩的文件加上参数 -k ,如果要设置压缩率加入参数 -0 到 -9 调节压缩率。如果不设置,默认压缩等级是6。

# 解压
# 先将Image.tar.xz 解压成 Image.tar
xz -d Image.tar.xz # 使用 -k 参数来保留被解压缩的文件。
tar -xvf Image.tar # 再用tar xvf Image.tar来解包

xz的参数:

  • -z, --compress: 强制压缩
  • -d, --decompress: 强制解压
  • -k, --keep: 保留(不删除)输入文件

pigz

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 压缩文件(夹)
tar --use-compress-program=pigz -cvpf /目录名/文件名.tgz (空格) /boot(压缩的文件或者目录)
--use-compress-program=pigz 是指定 ( pigz )来进行压缩
-cvpf
-c 创建压缩文件
-v 显示压缩或解压的过程
-p 保留原始的权限与属性
-f 目标文件名
--exclude=/xx 排除这个目录不压缩

# 解压文件(夹)
tar --use-compress-program=pigz -xvpf 文件.tgz -C / (这是指定解压到了根目录)(不加默认解压到当前文件夹)
--use-compress-program=pigz 是指定 ( pigz )来进行解压
-xvpf
-x 解开压缩文件
-v 显示压缩或解压的过程
-p 保留原始的权限与属性
-f 目标文件名
-C 指定解压到的目录

设置文件权限

chmod命令的语法

指令名称:chmod

使用权限:所有使用者

使用方式:chmod [-cfvR] [--help] [--version] mode file...

参数格式:

  • u:表示该档案的拥有者。
  • g:表示与该档案的拥有者属于同一个群体(group)者。
  • o:表示其他以外的人。
  • a:表示这三者皆是。
  • +:表示增加权限。
  • -:表示取消权限。
  • =:表示唯一设定权限。
  • r:表示可读取。
  • w:表示可写入。
  • x:表示可执行。
  • X:表示只有当该档案是个子目录或者该档案已经被设定过为可执行。
  • R:对目前目录下的所有文件与子目录进行相同的权限变更(即以递归的方式逐个变更)
1
2
3
4
5
6
7
# 将档案 file1.txt 设为所有人皆可读取
chmod ugo+r file1.txt # 或
chmod a+r file1.txt
# 将档案 file1.txt 与 file2.txt 设为该档案拥有者,与其所属同一个群体者可写入,但其他以外的人则不可写入
chmod ug+w,o-w file1.txt file2.txt
# 将目前目录下的所有档案与子目录皆设为任何人可读取
chmod -R a+r *

此外chmod也可以用数字来表示权限,语法为:chmod abc file,其中a,b,c各为一个数字,分别表示User、Group、及Other的权限。

一般是三个数字:

  • 第一个数字表示文件所有者的权限。
  • 第二个数字表示与文件所有者同属一个用户组的其他用户的权限。
  • 第三个数字表示其它用户组的权限。

权限分为三种:读(r=4),写(w=2),执行(x=1)。综合起来还有可读可执行(rx=5=4+1)、可读可写(rw=6=4+2)、可读可写可执行(rwx=7=4+2+1)。

所以,chmod 755 设置用户的权限为:

  1. 文件所有者可读可写可执行
  2. 与文件所有者同属一个用户组的其他用户可读可执行
  3. 其它用户组可读可执行

777就是rwxrwxrwx,意思是该登录用户(可以用命令id查看)、他所在的组和其他人都有最高权限。

chmod 4755chmod 755 的区别在于开头多了一位,这个4表示其他用户执行文件时,具有与所有者相当的权限。具有root的权限。

chmod a=rwx filechmod 777 file效果相同。

chmod ug=rwx,o=x filechmod 771 file效果相同。

1
2
# 查看文件权限
ll

root用户把某个文件权限给到普通用户

1
2
3
4
5
6
7
# 先切换到root环境
su root
# 赋权
chown -R username filepath
# username 指的是 你的普通用户名称
# filepath 指的是 你需要赋权给普通用户的文件夹路径
# 这样普通用户也就可以操作这个路径下的文件了

硬盘

挂载硬盘

1
2
3
4
5
# 挂载 U 盘根目录的内容会直接出现在 /mnt/ 文件夹内,
# 而不是 /mnt/ 文件夹内先有一个U盘的名称,名称文件夹内再有内容。
# 因此,如有需要,需要提前在 /mnt/ 下使用 sudo 新建文件夹。
sudo mount /dev/sdX1 /mnt/ # mount part
sudo umount /mnt/ # 卸载

其中,/dev/sdX1是U盘的设备名,你可以使用lsblkfdisk -l命令来查找U盘的设备名。

例如,插入硬盘前后,使用lsblk发现新增:

1
2
sdc      8:32   0   7.3T  0 disk 
└─sdc1 8:33 0 7.3T 0 part

硬盘类型

在Linux系统上,可以使用以下命令来查看硬盘是机械硬盘(HDD)还是固态硬盘(SSD):

1
cat /sys/block/sdX/queue/rotational

sdX替换为硬盘的设备名,比如sda。如果输出是1,则表示是机械硬盘;如果输出是0,则表示是固态硬盘。

除了查看/sys/block目录下的信息外,你还可以使用lsblk命令结合-o选项来获取更多详细信息,包括硬盘类型:

1
lsblk -d -o name,rota
  • lsblk 列出所有块设备。
  • -d 选项只显示磁盘本身,不显示其分区。
  • -o 选项指定要显示的列,其中name表示设备名,rota表示是否是旋转设备(0表示固态硬盘,1表示机械硬盘)。

通过这种方式,你可以在一个命令中看到所有硬盘的类型。

小贴士

Linux顺序执行多行命令

  • 分号;
    • 没有任何逻辑关系的连接符。当多个命令用分号连接时,各命令之间的执行成功与否彼此没有任何影响,都会一条一条执行下去。
  • 逻辑或||
    • 当用此连接符连接多个命令时,前面的命令执行成功,则后面的命令不会执行。前面的命令执行失败,后面的命令才会执行。
  • 逻辑与&&
    • 当用此连接符连接多个命令时,前面的命令执行成功,才会执行后面的命令,前面的命令执行失败,后面的命令不会执行,与 || 正好相反。
  • 管道符|
    • 当用此连接符连接多个命令时,前面命令执行的正确输出,会交给后面的命令继续处理。若前面的命令执行失败,则会报错,若后面的命令无法处理前面命令的输出,也会报错。

在Linux中使用AppImage

  1. 使其可执行。右键-属性-权限-允许将文件作为程序执行;或,命令行执行:

    1
    2
    3
    4
    chmod u+x <AppImage File>
    # u user 文件所有者
    # + 为指定的用户类型增加权限
    # x 执行权限 设置为可执行权限
  2. 运行 AppImage 文件。使 AppImage 文件可执行后,只需双击它即可运行它。它将看到该软件正在运行,就像您在系统上安装它一样;或,命令行执行:

    1
    ./app.appimage  # sudo
  3. 卸载 AppImage 软件。由于从未安装过该软件,因此无需“卸载”它。只需删除关联的 AppImage 文件,您的软件就会从系统中删除。

其它

  1. Linux diff 命令用于比较文件的差异。diff 以逐行的方式,比较文本文件的异同处。如果指定要比较目录,则 diff 会比较目录中相同文件名的文件,但不会比较其中子目录。

  2. 指令需要root权限,要么命令前加sudo进行提权,要么以root身份执行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # Ubuntu 首次进入 root 用户模式
    # 开启一个新的终端

    # 设置 root 用户密码(Linux下输入的密码默认不会显示出来)
    sudo passwd root
    # 输入一个新的密码: yourpassword
    # 请在确认一遍密码: yourpassword

    # 切换到root用户
    su root
    # 输入刚刚设置的密码

    # 退出root用户
    exit
  3. $表示普通用户,#表示root用户。

  4. 定义环境变量:

    1
    2
    sudo gedit ~/.bashrc
    export OPENAI_API_KEY={Your OpenAI API Key here}
  5. timeout是用来控制程序运行的时间,运行指定的命令。如果在指定时间后仍在运行,则杀死该进程。

    1
    2
    3
    4
    5
    timeout 5s ping www.baidu.com  # 5分钟之后终止ping操作
    s : 秒 (默认)
    m : 分钟
    h : 小时
    d : 天

命令行解释器Shell

终端负责是从用户这里接收输入(键盘、鼠标等输入设备),扔给 Shell,然后把 Shell 返回的结果展示给用户(比如通过显示器)。而 Shell 负责从终端那里拿到用户输入的命令,解析后交给操作系统内核去执行,并把执行结果返回给终端。

Bash

快捷键 作用
Tab zsh自动补全
Ctrl+C 终止进程/命令
Ctrl+V 插入特殊字符。当你按下Ctrl+V后,终端会等待你输入下一个字符,然后将其ASCII值插入到当前位置。例如,如果你按下Ctrl+V然后按Enter,终端将插入一个表示换行的特殊字符。
Shift+Ctrl+C 复制
Shift+Ctrl+V 粘贴
Shift+Ctrl+F 查找
Shift+Ctrl+T 新建标签页
Shift+Ctrl+N 新建同路径窗口
Shift+Ctrl+W 关闭标签页
Shift+Ctrl+Q 关闭窗口
Ctrl+A 光标移动到开始位置
Ctrl+E 光标移动到最末尾
Ctrl+K 删除此处至末尾的所有内容
Ctrl+U 删除此处至开始的所有内容
Ctrl+W 当在控制台或一个xterm窗口敲入文本时, CTRL+W 会删除从在光标处往后(回)的第一个空白符之间的内容。在某些设置里, CTRL+W 删除光标往后(回)到第一个非文字和数字之间的字符。
Ctrl+L 相当于clear,即清屏
Ctrl+Y 将之前已经清除的文本粘贴回来(主要针对CTRL+U或CTRL+W)。
Ctrl+S 冻结终端
Ctrl+Q 解冻终端
Ctrl+T 将光标位置的字符和前一个字符进行位置交换

Terminator

更方便的终端,区别于默认GNOME终端。

快捷键

快捷键 作用
Ctrl+Shift+E 垂直分割窗口
Ctrl+Shift+O 水平分割窗口
F11 全屏
Ctrl+Shift+C 复制
Ctrl+Shift+V 粘贴
Shift+Ctrl+W 关闭(当前)终端
Shift+Ctrl+Q 关闭窗口
Ctrl+Shift+N/Ctrl+Tab 在分割的各窗口之间切换
Alt+Up/Down/Left/Right 移动到上/下/左/右边的终端
Ctrl+Shift+Up/Down/Left/Right 在水平/垂直分割的终端中将分割条向上/下/左/右移动
Ctrl+Shift+X 将分割的某一个窗口放大至全屏使用
Ctrl+Shift+Z 从放大至全屏的某一窗口回到多窗格界面

选中即复制,鼠标中键粘贴。对于单词,双击即可选中,三击选中一行。

配置

修改打开的窗口的默认大小:

1
gedit ~/.config/terminator/config

修改 layouts - default - window0 - size(没有就新增)(对应的是像素值)

1
2
3
4
5
6
7
8
9
10
[layouts]
[[default]]
[[[child1]]]
parent = window0
profile = default
type = Terminal
[[[window0]]]
parent = ""
size = 1500, 900
type = Window

保存,关闭文件。关闭所有终端后,再重新打开就应用更改了。

zsh

特性

  1. 色彩高亮:不同的颜色表明当前命令的类型,并且路径有无下划线表示路径是否存在。
  2. 智能补全:在使用cd切换路径时,按下tab会列出当前目录下的目录和文件,如果是bash,它会提示你手动输入,但是zsh中你可以继续按一下tab进入选择模式,继续使用tab选择,或者使用方向键选择目标目录而不需手动输入。
  3. 按键盘右方向键->直接确定整个补全提示。Ctrl+->按整词顺序依次确定。
  4. d命令“回车”后,会列出我们最近进入的目录历史,并且会给这些目录加上序号,只需要输入对应目录的序号,即可重新进入该目录。

插件

路径:~/.oh-my-zsh/plugins

1
2
3
4
5
6
7
8
9
10
plugins=(
zsh-syntax-highlighting
zsh-autosuggestions
zsh-completions
history-substring-search
git
autojump
extract
sudo
)
  1. zsh-autosuggestions————命令提示/补全。提示和补全有不同实现机制,好用之处在于补全,输入命令会根据输入的历史自动补全,并且随着输入不断修正,如果补全是你期望的结果,按下右方向键接受,再回车即可。

    如果你用缓冲区末尾的光标按下→ key (forward-char widget)或 End (End-of-line widget) ,它就会接受这个建议,用这个建议替换命令行 buffer 的内容。

    配置:

    1. 当您键入命令时,您将看到在光标之后提供的一个柔和的灰色的完成。可以通过设置ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE变量来更改此颜色。

      默认值是 fg = 8,它将从256色调色板中将前景色设置为8色。如果您的终端只支持8种颜色,您将需要使用一个介于0和7之间的数字。

      还可以设置背景颜色,建议可以设置粗体、下划线或突出。例如,这将在青色背景上显示带有粗体、下划线、粉红色文字的建议:

      1
      ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE="fg=#ff00ff,bg=cyan,bold,underline"
    2. 等等。

  2. autojump:这个插件会自动统计我们经常cd的目录,不同目录会有不同的权重。在我们想要进入某个目录时,使用j <dir-name>即可以帮助我们快速跳转到目标目录。

  3. extract:将各种解压命令集合成使用 x <archived file>来提取压缩文件。

  4. sudo:双击两次ESC自动在句首添加sudo

报错

  1. zsh: corrupt history file /home/XXX/.zsh_history

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 解决:
    cp .zsh_history zsh_history
    rm -f .zsh_history
    strings zsh_history .zsh_history
    # 或
    cd ~
    mv .zsh_history .zsh_history_bad
    strings .zsh_history_bad > .zsh_history
    fc -R .zsh_history
  2. 等等。

Powerlevel10k

下面都是以Oh My Zsh框架为例,其它方式详见Powerlevel10k官方文档

安装

  1. 克隆仓库:

    1
    git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k  # for Oh My Zsh
  2. Set ZSH_THEME="powerlevel10k/powerlevel10k" in ~/.zshrc.

其它安装方式

(重新)配置:

1
p10k configure

配置向导根据您的偏好创建 ~/.p10k.zsh 。可以通过编辑此文件来完成其他提示自定义。它有大量注释可以帮助您浏览配置选项。

例如:

  1. 配置终端路径显示层数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # If set to "first" ("last"), remove everything before the first (last) subdirectory that contains
    # files matching $POWERLEVEL9K_SHORTEN_FOLDER_MARKER. For example, when the current directory is
    # /foo/bar/git_repo/nested_git_repo/baz, prompt will display git_repo/nested_git_repo/baz (first)
    # or nested_git_repo/baz (last). This assumes that git_repo and nested_git_repo contain markers
    # and other directories don't.
    #
    # Optionally, "first" and "last" can be followed by ":<offset>" where <offset> is an integer.
    # This moves the truncation point to the right (positive offset) or to the left (negative offset)
    # relative to the marker. Plain "first" and "last" are equivalent to "first:0" and "last:0"
    # respectively.
    typeset -g POWERLEVEL9K_DIR_TRUNCATE_BEFORE_MARKER=first:-1 # default false

    默认显示全路径,现在配置成了显示倒数两个文件夹。

  2. 调整终端窗口大小时出现可怕的混乱

更新

  1. 更新仓库:

    1
    git -C ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k pull  # for Oh My Zsh
  2. 更新 Powerlevel10k 后重新启动 Zsh。不要使用 source ~/.zshrc

其它更新方式

卸载

  1. ~/.zshrc 中删除所有对“p10k”的引用。您可能会在顶部看到此片段:

    1
    2
    3
    if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then
    source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh"
    fi

    底部是这样的:

    1
    [[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh

    这些是由配置向导添加的。删除它们。

  2. ~/.zshrc~/.zpreztorc~/.zimrc 中删除所有对“powerlevel10k”的引用(其中一些文件可能会丢失 - 这是正常的)。这些引用是您在安装 Powerlevel10k 时手动添加的。如果需要提醒,请参阅安装说明

  3. 验证所有对“p10k”和“powerlevel10k”的引用均已从 ~/.zshrc~/.zpreztorc~/.zimrc 中消失:

    1
    grep -E 'p10k|powerlevel10k' ~/.zshrc ~/.zpreztorc ~/.zimrc 2>/dev/null

    如果此命令产生输出,则仍然引用“p10k”或“powerlevel10k”。你需要删除它们。

  4. 删除Powerlevel10k配置文件。该文件由配置向导创建,可能包含您自己的手动编辑。

    1
    rm -f ~/.p10k.zsh
  5. 删除Powerlevel10k源文件。这些文件已在您安装 Powerlevel10k 时下载。删除它们的命令取决于您选择的安装方法。如果需要提醒,请参阅安装说明

    1
    rm -rf -- ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k  # for Oh My Zsh
  6. 重新启动 Zsh。不要使用 source ~/.zshrc

  7. 删除 Powerlevel10k 缓存文件:

    1
    rm -rf -- "${XDG_CACHE_HOME:-$HOME/.cache}"/p10k-*(N) "${XDG_CACHE_HOME:-$HOME/.cache}"/gitstatus
  8. 完成。

其它卸载方式

导出配置文件

bash

1
gedit ~/.bashrc

zsh

1
gedit ~/.zshrc

gnome-terminal

1
2
3
4
# 导出
dconf dump /org/gnome/terminal/legacy/profiles:/ > ~/Documents/gnome-terminal-profiles.dconf
# 导入
dconf load /org/gnome/terminal/legacy/profiles:/ < ~/Documents/gnome-terminal-profiles.dconf

报错:

  • 错误:密钥文件不以组开头

  • 原因: .dconf 文件的引导行是问题所在:default='...'

  • 解决:用这个替换引导线似乎使它可以加载:[/]

    因此,如果它有帮助,您尝试加载的文件应具有如下所示的格式。请注意标题中的列表项与下面的条目一一对应。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    [/]
    list=['22cb430e-3fde-42cb-a167-cc35a6b788cc', '4b1f055e-9bc4-4b58-bb12-eb5657db78fc']

    [:22cb430e-3fde-42cb-a167-cc35a6b788cc]
    audible-bell=false
    background-color='rgb(255,255,221)'
    cursor-shape='ibeam'
    foreground-color='rgb(0,0,0)'
    palette=['rgb(46,52,54)', 'rgb(204,0,0)', 'rgb(58,43,143)', 'rgb(196,160,0)', 'rgb(52,101,164)', 'rgb(117,80,123)', 'rgb(6,152,154)', 'rgb(211,215,207)', 'rgb(85,87,83)', 'rgb(239,41,41)', 'rgb(150,52,226)', 'rgb(252,233,79)', 'rgb(114,159,207)', 'rgb(173,127,168)', 'rgb(52,226,226)', 'rgb(238,238,236)']
    use-theme-colors=false
    visible-name='light'

    [:4b1f055e-9bc4-4b58-bb12-eb5657db78fc]
    audible-bell=false
    background-color='rgb(0,0,0)'
    background-transparency-percent=17
    cursor-shape='ibeam'
    font='Monospace 12'
    foreground-color='rgb(172,228,255)'
    palette=['rgb(46,52,54)', 'rgb(175,95,255)', 'rgb(0,102,255)', 'rgb(196,160,0)', 'rgb(255,7,168)', 'rgb(117,80,123)', 'rgb(6,152,154)', 'rgb(211,215,207)', 'rgb(85,87,83)', 'rgb(235,148,255)', 'rgb(0,229,255)', 'rgb(252,233,79)', 'rgb(114,159,207)', 'rgb(173,127,168)', 'rgb(52,226,226)', 'rgb(238,238,236)']
    use-system-font=true
    use-theme-colors=false
    use-theme-transparency=false
    use-transparent-background=true
    visible-name='hi'

Terminator

1
gedit ~/.config/terminator/config

Powerlevel10k

1
gedit ~/.p10k.zsh

小技巧

Shell脚本一次性启动多个程序(对应多个终端)

经常开发ros环境的应该知道,当节点太多难以控制的时候,不如写个脚本直接启动多个节点(对应多个终端)。

先介绍一下gnome-terminal命令的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
gnome-terminal  # 打开一个新的终端
# 参数
# 基本用法
--maximize # 打开后自动最大化
--full-screen # 打开后全屏
--window # 打开多个终端,多个标签页
gnome-terminal --window --window #打开两个
gnome-terminal --window --tab --window --tab --tab #打开两个,第一个两个tab,第二个3个tab
-t # 设置终端的标题。注意,有些版本不支持。
# 设置打开的位置和大小(宽度x高度+左侧偏移量+上方偏移量)
gnome-terminal --geometry=80x25+10+10

# 启动后自动执行命令
-e # 可以出现多次。如果在所有--window前面,表示对所有window和tab起作用,如果在--window或者--tab后面,表示只针对这个tab执行,要注意-e后面只能有一个参数,也就是说如果有空格,需要用引号。
-x # 只能出现一次,在-x后面的所有内容,均认为是要执行的命令,所以可以出现空格,这些命令是针对所有tab都执行的。
gnome-terminal -x bash -c "ls"
gnome-terminal -e 'bash -c "ls"'
# 注,运行时会警告:
# 参数“-x”弃用并可能在 gnome-terminal 的后续版本中移除。
# 参数“-e”弃用并可能在 gnome-terminal 的后续版本中移除
# 使用“-- ”以结束选项并将要执行的命令行追加至其后。
# 所以,现在可以直接用“--”来替换掉“-x”和“-e”了。

# 执行完成后保持终端不自动关闭。
# 1. 最后加上exec bash的命令,并用分号跟前面的命令隔开。
gnome-terminal -x bash -c "ls; exec bash"
gnome-terminal -e 'bash -c "ls; exec bash"'
# 2. 修改terminal的配置,在terminal点右键,选择Profiles->Profile Preferences,然后找到Title and Command,里面有一项When command exits,后面选择为Hold the terminal open,然后就可以了。
# 3. 把结果重定向给less,这样less执行完之前,是不会退出的。
gnome-terminal -x ls|less

了解上面后我们可以创建属于自己的脚本了:

  1. 在ROS工作空间(例如,catkin_ws文件夹)下创建一个脚本文件,例如runROS.sh

  2. 写入内容,例如:

    1
    2
    3
    4
    5
    6
    #!/bin/bash
    gnome-terminal --window -- bash -c "source ./devel/setup.bash && roslaunch PACKAGE_NAME *.launch; exec bash"
    sleep 3s
    gnome-terminal --window -- bash -c "source ./devel/setup.bash && rosrun PACKAGE_NAME NODE_NAME *.yaml; exec bash"
    sleep 3s
    gnome-terminal --window -- bash -c "rosbag play /yourfolder/*.bag; exec bash"

    需要什么节点按照这个模块添加即可。

    对于使用Terminator终端和zsh shell,修改文件内容为:

    1
    2
    3
    4
    5
    6
    #!/bin/zsh
    terminator -e "zsh -c 'source ./devel/setup.zsh && roslaunch PACKAGE_NAME *.launch; exec zsh'"
    sleep 3s
    terminator -e "zsh -c 'source ./devel/setup.zsh && rosrun PACKAGE_NAME NODE_NAME *.yaml; exec zsh'"
    sleep 3s
    terminator -e "zsh -c 'source ./devel/setup.zsh && rosbag play /yourfolder/*.bag; exec zsh'"

    这里播放rosbag前面需要加上source ./devel/setup.zsh,否则会报错zsh:1: command not found: rosbag

    如果exec zsh命令未能在命令运行完成后保持终端窗口打开,可以尝试修改terminal的配置,在terminal点右键,选择Profiles->Profile Preferences,然后找到Title and Command,里面有一项When command exits,后面选择为Hold the terminal open。

    因为roscoreroslanuch打开ROS节点后不会自行关闭,需要使用快捷键Ctrl+C手动关闭,所以不会运行到exec zsh这一步。

    而且,使用快捷键Ctrl+C手动关闭ROS节点后,终端会直接退出,也就是关闭窗口(这个BUG?反而能自动关闭终端窗口,减少了自己的操作?)。

    如果Ctrl+C后不想关闭终端,可以在terminal点右键,选择Profiles->Profile Preferences,然后找到Title and Command,里面有一项When command exits,后面选择为Hold the terminal open。不过即使这样,当前终端也不再能输入命令,只能使用Terminator的功能水平分割/数值分割另起一个终端。

  3. 给脚本文件授予权限:

    1
    sudo chmod 755 runROS.sh

    chmod 755 设置用户的权限为:

    1. 文件所有者可读可写可执行。
    2. 与文件所有者同属一个用户组的其他用户可读可执行。
    3. 其它用户组可读可执行。
  4. 运行脚本文件:

    1
    2
    cd catkin_ws
    ./runROS.sh
  5. 完成。

进阶使用

Shell脚本在语句报错后终止运行

shell脚本在语句报错后默认仍会继续向下执行,解决方法为:

1
2
3
4
5
6
7
8
#!/bin/bash
# 增加语句
set -e
# 或,二选一即可
set -o errexit
# 或,使用逻辑与
python3 main.py &&
python3 do.py

默认情况下,脚本执行后,屏幕只显示运行结果,没有其他内容。如果多个命令连续执行,它们的运行结果就会连续输出。有时会分不清,某一段内容是什么命令产生的。

set -x用来在运行结果之前,先输出执行的那一行命令。执行echo bar之前,该命令会先打印出来,行首以+表示。这对于调试复杂的脚本是很有用的。

Shell脚本自动输入密码

1
echo "password" | sudo -S command  # 可见加上-S参数sudo才会从标准输入中读取密码,不加-S参数以上命令将起不到作用

Shell脚本按下Ctrl+C后仍可以执行功能

How to handle ctrl+c in bash scripts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/bash

cleanup(){
echo ""
echo "CTRL+C pressed, clean up things before exiting..."
rm -rf test.tmp 2>/dev/null
exit 1
}

# Trap the SIGINT signal (Ctrl+C)
trap cleanup SIGINT

while true; do
touch test.tmp
sleep 1
echo "Running..."
rm -f test.tmp
done

trap命令允许捕获信号(Ctrl+C是SIGINT信号)并在捕获后执行命令,trap语法如下:

1
trap function_command_to_execute SIGNAL
  • 为什么你使用 echo 两次?
    • 按 ctrl+c 而不输入换行符会弄乱输出。
  • 为什么将 stderr 重定向到/dev/null
    • /dev/null是一个特殊的设备名称,充当黑洞!你扔在那里的所有东西都会消失!这样,如果我们尝试删除的文件已经被删除,则屏幕上不会显示错误,这可能会无缘无故地让用户感到困惑!
  • 为什么用 exit 1 退出脚本?
    • 退出代码非常有用!退出代码 0 表示程序/脚本按预期完成,其他任何值都可能表示因异常方式或错误退出。
    • 这使得故障排除更加容易,而且还允许控制流,我们可以使用 $? 读取脚本或程序的退出状态。这允许 bash 脚本决定如果另一个脚本或程序异常退出时该怎么做。

为Shell脚本中的文本着色

Linux: How to colorize text in bash scripts

代码/编译

编译与卸载

ROS编译与运行

编写CmakeLists.txt

CmakeLists.txt里指定第三方库所在的路径

CmakeLists.txt里指定第三方库所在的路径,即指定其编译安装后.cmake文件所在的路径,例如:

1
2
3
4
5
6
7
8
# 指定OpenCVConfig.cmake文件的目录
# 注意opencv安装目录下的/share/OpenCV
set(OpenCV_DIR /usr/local/share/OpenCV) # 默认安装在/usr/local/
set(OpenCV_DIR /usr/local/opencv/opencv345/share/OpenCV) # 自定义安装在/usr/local/opencv/opencv345的openv3.4.5
set(OpenCV_DIR /usr/local/opencv/opencv452/lib/cmake/opencv4)
# set(OpenCV_DIR /opt/ros/kinetic/share/OpenCV-3.3.1-dev)
# set(OpenCV_DIR /path/to/opencv/build)
find_package(OpenCV REQUIRED)
1
find_library(flowfilter_gpu_LIBS NAMES flowfilter_gpu PATHS /usr/local/include)

首先,set(OpenCV_DIR /usr/local/opencv/opencv345/share/OpenCV)这行代码设置了一个变量OpenCV_DIR,它指向OpenCV库的安装位置。在这个例子中,OpenCV库被安装在/usr/local/opencv/opencv345/share/OpenCV这个路径下。OpenCV_DIR这个变量通常用于指定OpenCV的cmake配置文件的位置,这个文件包含了OpenCV库的版本信息、编译选项等信息。

然后,find_package(OpenCV REQUIRED)这行代码告诉CMake去查找OpenCV库。REQUIRED关键字表示如果CMake不能找到OpenCV库,那么CMake应该停止配置过程并显示错误信息。如果CMake成功找到了OpenCV库,那么它将设置一些变量,例如OpenCV_INCLUDE_DIRSOpenCV_LIBS,这些变量分别包含了OpenCV的头文件路径和库文件路径,可以在后续的target_include_directoriestarget_link_libraries命令中使用。

如果没有设置OpenCV_DIRfind_package命令会在默认的路径下查找OpenCV库。这些默认的路径包括:

  • CMake的模块路径(CMAKE_MODULE_PATH
  • CMake的安装前缀(CMAKE_PREFIX_PATH
  • 系统的环境变量路径

具体来说,find_package会查找名为OpenCVConfig.cmakeopencv-config.cmake的文件,这个文件通常位于OpenCV库的安装目录中。

如果你的OpenCV库安装在非标准的位置,或者你有多个版本的OpenCV库并且想要选择一个特定的版本,那么你可以通过设置OpenCV_DIR来指定OpenCV库的路径。如果没有设置OpenCV_DIR,CMake可能会找到错误的版本或者找不到OpenCV库。

OpenCV_DIROpenCV_INCLUDE_DIR是两个不同的变量,它们在CMake中的作用也不同。

  • OpenCV_DIR是用于指定OpenCV的cmake配置文件位置的变量。find_package命令会使用OpenCV_DIR变量的值作为查找OpenCV配置文件的起始路径。如果OpenCV_DIR被设置,find_package就会直接在这个路径下查找配置文件,而不会在其他路径下查找。

  • OpenCV_INCLUDE_DIR通常是find_package命令找到OpenCV库后设置的一个变量,它包含了OpenCV的头文件路径。这个变量通常用于target_include_directories命令,以便你的项目可以找到OpenCV的头文件。

如果你设置了OpenCV_INCLUDE_DIR,而不是OpenCV_DIR,然后调用find_package(OpenCV REQUIRED),那么find_package命令可能无法找到正确的OpenCV库,因为它不知道在哪里查找OpenCV的配置文件。在这种情况下,find_package命令可能会找到错误的OpenCV版本,或者找不到OpenCV库。find_package命令找到的结果会覆盖你设置的OpenCV_INCLUDE_DIR变量。

总的来说,如果你想要指定OpenCV库的位置,你应该设置OpenCV_DIR,而不是OpenCV_INCLUDE_DIR

(在ROS中)编译第三方开源软件需要下载的问题

注意CmakeList.txt里有没有指定具体版本。在package.xml里也可以看到指定的版本。

其实就类似于在系统中cmakemakemake install的步骤,只不过这里的第三方库是安装在了ROS工作区里被相互调用,catkin clean后也就删除掉了,而没有安装在系统环境里。也方便使用指定版本的第三方库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# URL https://github.com/gflags/gflags/archive/v2.2.1.zip
# GIT_REPOSITORY https://github.com/jbeder/yaml-cpp
# GIT_TAG ${YAML_CPP_TAG}
# DOWNLOAD_COMMAND rm -f SuiteSparse-${VERSION}.tar.gz && wget --retry-connrefused --waitretry=1 --timeout=40 --tries 3 https://github.com/ethz-asl/thirdparty_library_binaries/raw/master/SuiteSparse-${VERSION}.tar.gz
DOWNLOAD_COMMAND ""
URL "/yourfolder/gflags-2.2.1.zip" # 使用存档时,它将自动解压缩,除非设置了 DOWNLOAD_NO_EXTRACT 选项来阻止它。
# 默认解压路径为catkin_ws/build/xxx/xxx_src-prefix/src/xxx_src
URL_MD5 4628df9eeae10ae5f0c486f1ac982fce # (可选,仅用作文件验证)与URL搭配。随便输入一个MD5码,编译时会报错当前压缩包正确的MD5码。
UPDATE_COMMAND "" # 与URL搭配。使用自定义命令覆盖下载方法的更新步骤。
DOWNLOAD_NO_EXTRACT TRUE # 允许通过为此选项传递一个布尔真值来禁用下载步骤的提取部分。
# 如果希望控制下载的归档文件的位置及其名称,可以使用以下选项
set(CMAKE_CURRENT_BINARY_DIR /yourfolder) # 放在主程序中,而不包含在ExternalProject_Add()
DOWNLOAD_DIR ${CMAKE_CURRENT_BINARY_DIR}
DOWNLOAD_NAME jsoncpp_1.8.4.tar.gz

或者,将src(这个文件是原本解压下载的第三方源码source的地方,具体名称要看CMakeLists.txt中SOURCE_DIR的设置)中的各个第三方源码都解压好,放到src对应的文件夹中。例如catkin_ws/build/xxx/xxx_src-prefix/src/xxx.tar.gz

cmake

一般流程

1
2
3
4
cd package_name
mkdir build
cd build
cmake ..

我个人推荐把第三方库安装在/usr/local文件夹下进行管理。例如,在/usr/local文件夹下新建文件夹eigen3,后在eigen3文件夹下新建文件夹eigen330eigen340

定义编译参数

可在cmake命令后加参数:

1
2
3
4
# 指定安装目录。使用该参数可以指定安装目录,使得在执行make install命令时,生成的可执行文件、库文件、头文件等可以被安装到指定的目录下。
cmake -DCMAKE_INSTALL_PREFIX=/usr/local/package_name ..
# 指定使用的gcc和g++版本
-DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10

也可以在CMakeLists.txt文件中定义,例如,启用参数EFFT_USE_FFTW3

1
cmake DEFFT_USE_FFTW3 ..

或,在CMakeLists.txt文件中:

1
target_compile_definitions(efft-unit-tests PRIVATE EFFT_USE_FFTW3)

保存cmake输出

ROS: cmake build的输出在catkin_ws/logs/your_package_name/build.cmake.log、build.make.log里。

cmake命令的输出信息通常在终端中显示,而不是保存在文件中。这些信息包括配置过程中的警告、错误以及其他重要信息。

然而,你可以将cmake命令的输出重定向到一个文件中。例如,你可以使用以下命令将输出保存到一个名为output.txt的文件中:

1
cmake .. > output.txt

在这个命令中,>操作符将cmake命令的输出重定向到output.txt文件中。如果output.txt文件已经存在,这个命令将覆盖它的内容。如果你想要追加输出到文件中,而不是覆盖它,你可以使用>>操作符,如下所示:

1
cmake .. >> output.txt

请注意,这些命令只会捕获标准输出,而不会捕获错误输出。如果你也想要捕获错误输出,你可以使用2>&1,如下所示:

1
cmake .. > output.txt 2>&1

在这个命令中,2>&1将错误输出重定向到标准输出,然后>操作符将标准输出重定向到output.txt文件中。这样,output.txt文件将包含所有的输出,包括错误信息。

make

1
2
3
4
5
# 建议在make之前先查看CPU的核心数
nproc # 你可以根据你的核心数来调整make指令的参数,比如笔者的CPU核心数为12,则可以执行:
make -j12
make # 根据Makefile编译源代码,连接,生成目标文件,可执行文件。
make install # 将编译成功的可执行文件安装到系统目录中,一般为/usr/local/bin目录。

cmake --install .make install的区别:

cmake --install .make install都是用来安装编译好的程序的命令,但它们在使用的构建系统和工作方式上有所不同。

  • make install是GNU Make的命令,它依赖于Makefile中的install目标。这个install目标通常会将编译好的二进制文件、库文件、头文件等复制到系统的指定位置,如/usr/local/bin/usr/local/lib等。这个命令通常在使用GNU Autotools或者手写Makefile的项目中使用。

  • cmake --install .是CMake的命令,它会执行CMakeLists.txt文件中定义的安装规则。这个命令在CMake 3.15及以后的版本中可用,它是cmake -P cmake_install.cmake的一个更简洁的替代。这个命令的好处是它不依赖于特定的构建系统,可以在任何CMake支持的构建系统中使用。

总的来说,这两个命令的功能是类似的,但cmake --install .更加通用,不依赖于特定的构建系统。

卸载

如果因为反复./configure xx然后make会导致安装路径混乱,sudo make install失败。所以如果路径设错了又已经make完,需要make clean来清除一下。如果想把自己刚刚make install安装的卸载掉,可以在那个目录里直接用sudo make uninstall

1
2
3
4
5
6
7
8
9
10
11
12
13
cd package_name/build

# 卸载使用make install命令安装到系统路径的文件
# `install_manifest.txt`文件里包含了所有安装的文件的路径
cat install_manifest.txt | sudo xargs rm # 或
sudo xargs rm < install_manifest.txt
# 上述命令只会删除掉文件夹里的文件,会留下空文件夹。所以可以继续对照`install_manifest.txt`文件,手动删掉多余的空文件夹。

sudo make uninstall
sudo make clean #清除上一次make命令生成的文件
sudo make distclean #清除上一次make以及configure命令生成的文件
cd ..
sudo rm -r build

make uninstall 是一个常见的 makefile 目标,它的主要作用是删除由 make install 命令安装的文件。

当你运行 make install 命令时,通常会将一些文件(例如可执行文件、库文件、头文件等)复制到系统的某些目录下(例如 /usr/local/bin/usr/local/lib 等)。make uninstall 命令就是用来删除这些文件的。

然而,需要注意的是,并不是所有的 makefile 都提供 make uninstall 目标。如果 makefile 没有提供这个目标,运行 make uninstall 命令将会导致错误。

在运行 make uninstall 命令之前,你应该查看 makefile 或者相关的文档,以确认这个命令是否可用,以及它会删除哪些文件。

cat install_manifest.txt | sudo xargs rmsudo make uninstall 都是用来删除由 make install 命令安装的文件的。然而,它们的工作方式有所不同。

  • cat install_manifest.txt | sudo xargs rm:这个命令会读取 install_manifest.txt 文件,这个文件通常由 make install 命令生成,包含了所有被安装的文件的列表。然后,它会使用 xargs rm 命令删除这些文件。这个命令不依赖于 makefile,只要 install_manifest.txt 文件存在,就可以使用。
  • sudo make uninstall:这个命令会执行 makefile 中的 uninstall 目标。这个目标通常会删除所有被 make install 命令安装的文件。然而,需要注意的是,并不是所有的 makefile 都提供 uninstall 目标。如果 makefile 没有提供这个目标,运行 sudo make uninstall 命令将会导致错误。

总的来说,这两个命令的功能是相似的,但是 cat install_manifest.txt | sudo xargs rm 命令更为直接,不依赖于 makefile。而 sudo make uninstall 命令则需要 makefile 提供 uninstall 目标。

make cleanmake distclean 是两个常见的 makefile 目标,它们的功能取决于 makefile 的编写者如何定义它们。但是,通常它们的功能如下:

  • make clean:这个命令通常用于删除所有由 makefile 生成的文件。这通常包括编译产生的对象文件(.o 或 .obj 文件)和编译器生成的中间文件。但是,它通常不会删除配置文件或者 makefile 文件。
  • make distclean:这个命令通常用于将目录恢复到初始状态。除了删除 make clean 会删除的文件,它还会删除配置文件和 makefile 文件。这个命令通常在你想要重新配置和编译一个项目时使用。

需要注意的是,这两个命令的具体行为取决于 makefile 的编写者。在使用这些命令之前,你应该查看 makefile 或者相关的文档,以了解这些命令的具体行为。

编程命名规范

  1. 匈牙利命名法(将变量类型写进变量名的命名方法)。

    其基本原则是,变量名=属性+类型+对象描述。通过在变量名前面加上相应的小写字母的符号标识作为前缀,标识出变量的作用域,类型等。

    这些符号可以多个同时使用,顺序是先m_(成员变量),再指针,再简单数据类型,再其他。例如:m_lpsStr,表示指向一个字符串的长指针成员变量。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    # 前缀类型
    a 数组(Array)
    b 布尔值(Boolean)
    by 字节(Byte)
    c 有符号字符(Char)
    cb 无符号字符(Char Byte,并没有神马人用的)
    cr 颜色参考值(Color Ref)
    cx,cy 坐标差(长度 Short Int)
    dw 双字(Double Word)
    fn 函数(Function)
    h Handle(句柄)
    i 整形(Int)
    l 长整型(Long Int)
    lp 长指针(Long Pointer)
    m_ 类成员(Class Member)
    n 短整型(Short Int)
    np 近程指针(Near Pointer)
    p 指针(Pointer)
    s 字符串(String)
    sz 以 Null 做结尾的字符串型(String with Zero End)
    w 字(Word)
  2. 驼峰式命名法,又叫小驼峰式命名法。常用于变量名,函数名。

    要求第一个单词首字母小写,后面其他单词首字母大写。

    1
    2
    3
    int myAge;
    char myName[10];
    float manHeight;
  3. 帕斯卡命名法,又叫大驼峰式命名法。常用于类名,属性,命名空间等。

    与小驼峰式命名法的最大区别在于,每个单词的第一个字母都要大写。

    1
    2
    3
    int MyAge;
    char MyName[10];
    float ManHeight;
  4. 下划线命名法。

    下划线命名法并不如大小驼峰式命名法那么备受推崇,但是也是浓墨重彩的一笔。尤其在宏定义和常量中使用比较多,通过下划线来分割全部都是大写的单词。还有变量名太长的变量

    该命名规范,也是很简单,要求单词与单词之间通过下划线连接即可。

    1
    2
    3
    int my_age;
    char my_name[10];
    float man_height;
  5. 在C++编程中,变量名后加_的命名方式通常用于表示类的私有数据成员。这是一种命名约定,用于区分类的数据成员和局部变量,以提高代码的可读性。例如,fftwInput_fftwOutput_plan_都是类的私有数据成员,它们的名字都以_结尾。

    一般来说,类的数据成员应该被定义为私有(private),这是面向对象编程中的封装原则。通过将数据成员设为私有,可以防止外部代码直接访问或修改这些数据,从而保护类的内部状态的完整性。

    然而,有时候,你可能会选择将某些数据成员设为公有(public)。这通常在数据成员是类的公开接口的一部分,或者类本身就是一个简单的数据结构时发生。

    至于是否在公有数据成员的名称后加_,这完全取决于你的命名约定。在某些命名约定中,可能会在所有数据成员的名称后加_,无论它们是公有的还是私有的。在其他命名约定中,可能只在私有数据成员的名称后加_

    总的来说,关键是选择一种命名约定,并在整个代码库中一致地遵循它,以提高代码的可读性和一致性。

    变量名称前加下划线:这通常用于表示私有成员变量或者类的内部变量。然而,根据C++标准,名称以一个下划线开头的变量可能被保留给编译器的实现,所以一般不推荐这种做法。

  6. 在编程中,ijk通常用作循环变量,特别是在嵌套循环中。idxjdx是对这些传统变量名的扩展,其中idx可能表示"index",jdx可能表示第二个索引。

    然而,更具描述性的变量名可能会使代码更易于理解。例如,如果idx遍历的是当前帧的目标检测框,那么你可能会选择名为curBoxIdx的变量名。同样,如果jdx遍历的是前一帧的目标检测框,那么你可能会选择名为prevBoxIdx的变量名。

  7. 在我们的系统中,所有环境变量都使用大写字母命名。所以当我们声明局部变量时,应该使用小写字母来声明,以避免环境和局部变量名发生冲突。

  8. 等等。

命名空间

在C++中, using namespace std; 这条语句的作用是将 std 命名空间中的所有名称导入到当前作用域中,使得我们可以直接使用 std 命名空间中的类型和函数,而无需每次都完整地书写它们的命名空间。

然而,这种做法也有其缺点。首先,它会导致命名空间污染,即同一个作用域中可能存在多个同名的类型或函数,导致编译器无法区分它们。其次,这种做法可能会导致程序效率降低,因为导入的命名空间中的类型和函数可能会增加编译时间和运行时间。

因此,有些开发者会选择显式地书写类型和函数的完整名称,例如 std::cout ,而不是使用 using namespace std; 。这种做法可以避免命名空间污染,并且可以确保编译器能够准确地解析类型和函数的名称。同时,这种做法也可以提高程序的可读性,使得代码更加清晰易懂。

总的来说,是否使用 using namespace std; ,以及是否显式地书写类型和函数的完整名称,取决于开发者的个人喜好和编程习惯。但是,在编写大型项目时,为了避免命名空间污染和保证程序的效率,建议尽量少使用 using namespace std; ,而是显式地书写类型和函数的完整名称。

代码注释

一般注释

C++

1
2
3
4
5
6
7
// 一般注释

/**
* @brief 多行注释
* @param
* @return
*/

python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 单行注释

'''
使用 3 个单引号分别作为注释的开头和结尾
可以一次性注释多行内容
这里面的内容全部是注释内容
'''

"""
使用 3 个双引号分别作为注释的开头和结尾
可以一次性注释多行内容
这里面的内容全部是注释内容
"""

特殊注释

这些是注释中的标签(tag),有时也被称作“代码标签(codetag)”或“标记(token)”。

标识:

  • TODO:标记代码中需要实现的功能或任务。
  • FIXME:标记代码中需要修复的问题或缺陷。
  • XXX:如果代码中有该标识,说明标识处代码虽然实现了功能,但是实现的方法有待商榷,代码有问题或具误导性,需引起警惕。希望将来能改进,要改进的地方会在说明中简略说明。
  • HACK/BODGE/KLUDGE:标记临时性修复或不优雅的解决方案。英语翻译为砍。如果代码中有该标识,说明标识处代码我们需要根据自己的需求去调整程序代码。
  • BUG/DEBUG:标记已知的Bug或错误。
  • UNDONE:对之前代码改动的撤销。
  • NOTE:提供额外的注释或提示信息,帮助理解代码意图或设计决策。

格式:

1
2
3
4
5
6
7
/*
* 1. 使用大写字母
* 2. 只用双正斜杠//,而不是三个正斜杠 ///
* 3. 在标签后使用半角冒号 :
*/
//TODO: Need implementation.
//FIXME: We need to avoid the problem of duplicating windows when clicking multiple times on this menu item.

C++数据类型

C++形参和实参

参考资料

形参和实参是函数中的两个重要概念。

形参(形式参数)是在函数定义中出现的参数,它是一个虚拟参数,只有在函数调用时才会接收到传递进来的实际参数。形参可以被看作是一个占位符,在函数定义时并没有实际的数值,只有在函数调用时才会得到实参的数值。形参的主要作用是表示函数需要从外部传递进来的数据。

实参(实际参数)是在函数中实际出现的参数,它的值可以是常量、变量、表达式、类等。实参的值是确定的,必须在函数调用时提供。实参的主要作用是向函数传递数据,将数据的值传递给形参,在函数体内被使用。

要注意的是,形参和实参之间的传递方式有两种:值传递和地址传递。值传递是指将实参的值复制给形参,形参在函数内部使用时不会改变实参的值。而地址传递是指将实参的地址传递给形参,形参在函数内部使用时可以通过地址修改实参的值。

总结起来,形参是函数定义中的参数,是一个虚拟的占位符,用于接收函数调用时传递进来的实参。实参是函数调用时提供的具体数值,用于向函数传递数据。形参和实参之间的传递方式可以是值传递或地址传递。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include<iostream>
void exchange1(int x,int y);
void exchange2(int &x,int &y);
void exchange3(int *x,int *y);
using namespace std;
int main()
{
int a = 7777777,b = 666666;
exchange1(a,b);
cout << a << endl << b << endl;
exchange2(a,b);
cout << a << endl << b << endl;
exchange3(&a,&b);
cout << a << endl << b << endl;
}

// exchange1中a和b没有交换的原因是:x和y是函数定义中的形参,代码中吧实参a,b分别赋值给了x,y这两个形参变量。代码中只是替换掉了x和y的值,实参a和b不受影响,交换失败。
void exchange1(int x,int y)
{
int temp;
temp = y;
y = x;
x = temp;
}

// exchange2中,x和y是a和b的引用,操作x和y交换就等于a和b交换,交换成功。
void exchange2(int &x,int &y)
{
int temp;
temp = y;
y = x;
x = temp;
}

// exchange3中,x和y两个形参是a和b的指针,也就是存放实参的地址。然后函数里面交换了x和y指向的数据地址,也就是实参a和b,交换成功。
void exchange3(int *x,int *y)
{
int temp;
temp = *y;
*y = *x;
*x = temp;
}

C++修饰符类型

参考链接

C++ 允许在 char、int 和 double 数据类型前放置修饰符。

修饰符是用于改变变量类型的行为的关键字,它更能满足各种情境的需求。

下面列出了数据类型修饰符:

  • signed:表示变量可以存储负数。对于整型变量来说,signed 可以省略,因为整型变量默认为有符号类型。
  • unsigned:表示变量不能存储负数。对于整型变量来说,unsigned 可以将变量范围扩大一倍。
  • short:表示变量的范围比 int 更小。short int 可以缩写为 short。
  • long:表示变量的范围比 int 更大。long int 可以缩写为 long。
  • long long:表示变量的范围比 long 更大。C++11 中新增的数据类型修饰符。
  • float:表示单精度浮点数。
  • double:表示双精度浮点数。
  • bool:表示布尔类型,只有 true 和 false 两个值。
  • char:表示字符类型。
  • wchar_t:表示宽字符类型,可以存储 Unicode 字符。

修饰符 signed、unsigned、long 和 short 可应用于整型,signedunsigned 可应用于字符型,long 可应用于双精度型。

这些修饰符也可以组合使用,修饰符 signedunsigned 也可以作为 longshort 修饰符的前缀。例如:unsigned long int

C++ 允许使用速记符号来声明无符号短整数无符号长整数。您可以不写 int,只写单词 unsigned、shortlongint 是隐含的。

C++中的类型限定符

参考链接

类型限定符提供了变量的额外信息,用于在定义变量或函数时改变它们的默认行为的关键字。

限定符 含义
const const 定义常量,表示该变量的值不能被修改。
volatile 修饰符 volatile 告诉该变量的值可能会被程序以外的因素改变,如硬件或其他线程。。
restrict restrict 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict。
mutable 表示类中的成员变量可以在 const 成员函数中被修改。
static 用于定义静态变量,表示该变量的作用域仅限于当前文件或当前函数内,不会被其他文件或函数访问。
register 用于定义寄存器变量,表示该变量被频繁使用,可以存储在CPU的寄存器中,以提高程序的运行效率。

逻辑运算符

在C++中,"或"逻辑可以使用逻辑运算符||来表示,|是按位或运算符。

"与"逻辑可以使用逻辑运算符&&来表示,&是按位与运算符。

在C++中,if ( IMU_VERSION == 0)if ( IMU_VERSION = 0)有着本质的区别。

if ( IMU_VERSION == 0)是一个比较操作,它检查IMU_VERSION是否等于0。如果IMU_VERSION的值为0,那么条件为真,if语句中的代码块将被执行。如果IMU_VERSION的值不为0,那么条件为假,if语句中的代码块将不会被执行。

if ( IMU_VERSION = 0)是一个赋值操作。它将IMU_VERSION的值设置为0,然后检查赋值后的IMU_VERSION是否为非零。在这种情况下,由于赋值为0,条件始终为假,因此if语句中的代码块将不会被执行。同时,这也改变了IMU_VERSION的原始值,这可能不是你想要的结果。因此,你应该谨慎使用赋值操作符=if语句中,除非你确实想在检查条件的同时赋值。

C++函数

普通函数、类的普通成员函数和类的静态成员函数之间的区别

在C++中,普通函数、类的普通成员函数和类的静态成员函数之间有以下几点主要区别:

  1. 普通函数:属于全局函数,不受具体类和对象的限制,可以直接调用。
    • 普通函数不属于任何类,它只能访问全局变量和其参数。它不能访问类的成员变量和成员函数(除非有一个类的对象或指针作为参数传入)。
  2. 类的普通成员函数:类的普通成员函数属于类的实例(对象),它可以访问类的所有成员(包括私有成员、保护成员和公有成员)。每个对象都有自己的成员函数副本。普通成员函数必须通过对象来调用。
    • 本质上是一个包含指向具体对象this指针的普通函数,即C++类的普通成员函数都隐式包含一个指向当前对象的this指针。
  3. 类的静态成员函数:类的静态成员函数属于类本身,而不属于类的任何对象。它只能访问类的静态成员变量和静态成员函数,不能访问类的非静态成员变量和非静态成员函数。静态成员函数可以通过类名直接调用,也可以通过对象调用。
    • 静态成员函数在某种程度上类似于全局函数,因为它们不依赖于类的任何特定对象,而是属于类本身。这意味着你可以在不创建类的对象的情况下调用静态成员函数。
    • 然而,静态成员函数并不完全等同于全局函数。静态成员函数仍然是类的一部分,它可以访问类的静态成员(包括私有静态成员),而全局函数则不能。
    • 静态成员函数没有this指针。this指针是一个指向调用成员函数的特定对象的指针。因为静态成员函数不依赖于任何特定对象,所以它没有this指针。这也意味着静态成员函数不能访问类的非静态成员变量或非静态成员函数。

如果成员函数想作为回调函数来使用,如创建线程等,一般只能将它定义为静态成员函数才行。

在C++中,回调函数通常需要是全局函数或静态成员函数,因为它们具有固定的函数签名,可以被用作函数指针。非静态成员函数不能直接用作回调函数,因为它们有一个隐含的this参数,这会改变它们的函数签名。

然而,有一些方法可以让你使用非静态成员函数作为回调函数。例如,你可以使用std::bindlambda表达式来捕获this指针,然后调用非静态成员函数。这种方法在C++11及以后的版本中可用。

以下是一个简单的例子来说明这三种函数的区别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MyClass {
public:
int x; // 普通成员变量
static int y; // 静态成员变量。只被初始化一次,下次执行初始化语句会直接跳过。

void func() { // 普通成员函数
x = 1; // 可以访问普通成员变量
y = 2; // 可以访问静态成员变量
}

static void staticFunc() { // 静态成员函数
// x = 3; // 错误:不能访问普通成员变量
y = 4; // 可以访问静态成员变量
}
};

int MyClass::y = 0; // 初始化静态成员变量

void globalFunc(MyClass& obj) { // 普通函数
// obj.x = 5; // 可以通过对象访问其成员变量
// MyClass::y = 6; // 可以访问静态成员变量
}

类的构造函数和析构函数的调用时机

在C++中,类的构造函数和析构函数的调用时机如下:

  1. 构造函数:当创建类的对象时,会自动调用类的构造函数。构造函数的主要任务是初始化对象的数据成员。构造函数的名称与类名相同,没有返回类型。构造函数可以有参数,可以被重载。

    • 当创建一个类的对象时,例如MyClass obj;,会调用默认构造函数(无参数的构造函数)。
    • 当以参数方式创建一个类的对象时,例如MyClass obj(1, 2);,会调用相应的参数构造函数。
    • 当一个对象作为另一个对象的初始化参数时,例如MyClass obj1 = obj2;MyClass obj1(obj2);,会调用拷贝构造函数。
  2. 析构函数:在C++中,当类的对象需要在其生命周期结束时执行某些操作(如释放资源、关闭文件、断开网络连接等)时,就需要定义析构函数。当一个对象的生命周期结束时(例如,对象离开其作用域,或者使用delete删除动态分配的对象),会自动调用其析构函数。析构函数的主要任务是执行清理工作,例如释放对象可能拥有的资源。析构函数的名称是类名前加上波浪号~,没有返回类型,也不能有任何参数,因此不能被重载。

    • 一个局部变量,那么当它的定义域(例如一个函数或一个代码块)结束时,它的析构函数会被调用。
    • 一个全局变量或者静态变量,那么当程序结束时,它的析构函数会被调用。

    而有的对象不会被析构函数自动释放,因此就不能使用空的析构函数,而是在析构函数内定义释放规则。

    以下是一个例子,该例子中的类FileWrapper封装了一个文件指针,需要在析构函数中关闭文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #include <cstdio>

    class FileWrapper {
    private:
    FILE* file_;

    public:
    FileWrapper(const char* filename, const char* mode) {
    file_ = std::fopen(filename, mode);
    if (file_ == nullptr) {
    throw std::runtime_error("Failed to open file.");
    }
    }

    ~FileWrapper() {
    if (file_ != nullptr) {
    std::fclose(file_); // 关闭文件
    }
    }

    // 其他成员函数...
    };

    在这个例子中,FileWrapper的构造函数打开一个文件,并将文件指针存储在file_中。然后,FileWrapper的析构函数在file_不为nullptr时关闭文件。这样,无论FileWrapper对象何时被销毁(例如,离开作用域或被delete删除),都会自动关闭文件,防止资源泄漏。

    如果FileWrapper使用空的析构函数,那么文件将不会被关闭,可能会导致资源泄漏和其他问题。

以下是一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MyClass {
public:
MyClass() {
// 构造函数
std::cout << "Constructor called!" << std::endl;
}

~MyClass() {
// 析构函数
std::cout << "Destructor called!" << std::endl;
}
};

int main() {
MyClass obj; // 创建对象,调用构造函数
// 当obj离开作用域时,调用析构函数
return 0;
}

在这个例子中,当MyClass obj;执行时,会调用MyClass的构造函数。当obj离开其作用域(即main函数结束时),会调用MyClass的析构函数。

输出函数的选择

  1. C++ 中 printfcout 有什么区别?

    在 C++ 中,printfcout 都用于输出,但它们来自不同的库,有不同的用法和特性。下面是一些主要的不同之处:

    1. 来源
      • printf 是 C 语言中的标准输入输出函数,来源于 C 语言的标准库(stdio.h)。在 C++ 中依然可用,但需要包含头文件 <cstdio> 或者 C 风格的 <stdio.h>
      • cout 是 C++ 专有的,属于 C++ 标准库中的一个对象,用于输出。cout 是定义在 <iostream> 头文件中的标准输出流对象。
    2. 使用语法
      • printf 使用格式化字符串。例如:printf("%d %s", 100, "C++");
      • cout 使用流插入运算符(<<)。例如:cout << 100 << " C++";
    3. 类型安全
      • printf 在处理类型时不够安全,因为它依赖于格式化字符串正确地匹配变量的类型;如果不匹配,可能会造成运行时错误或未定义行为。
      • cout 是类型安全的,编译器会检查插入到cout中的数据类型,确保与预期匹配,减少了错误的可能性。
    4. 性能
      • printf 的性能通常情况下比 cout 快,尤其是在格式化字符串操作时,因为 cout 的类型安全和重载运算符使得它的运行效率稍低。
      • 但是,通过启用 C++ 的 std::ios_base::sync_with_stdio(false); 以及避开使用 std::endlstd::endl 会刷新输出缓冲区),可以提升 cout 的性能,使其与 printf 相近。
    5. 灵活性和功能
      • 使用 cout 更灵活,因为它支持对输出格式的更细致控制,例如设置宽度、精度和填充字符等。此外,cout 可以直接打印用户定义的类型,只要为这些类型重载了输出运算符(<<)。
      • printf 在打印自定义类型时没有直接的方式,因为它需要预定义的格式化串。
    6. 可移植性
      • printf 基于 C 语言,因此在许多不同的平台和编译器中使用广泛,具有很高的可移植性。
      • cout 虽然是 C++ 的一部分,理论上在所有支持 C++ 的平台上都是可用的,但在跨平台编程时,对输出格式的控制可能会遇到不一致性。

    综上所述,printfcout 都有各自的用途和优势,选择使用哪个取决于具体的编程需求和个人喜好。在实际编程中,了解它们的差异有助于更好地决策和优化代码。

  2. 在C++程序开发中,推荐使用printf还是cout

    在 C++ 程序开发中,通常推荐使用 cout 而不是 printf。这一推荐基于以下几个理由:

    1. 类型安全cout 是类型安全的,这意味着编译器会对插入到 cout 的数据类型进行检查,减少了类型不匹配的错误。而 printf 需要开发者手动指定类型,在类型不匹配的情况下可能导致未定义行为或运行时错误。
    2. 易于使用:使用 cout 需要较少的手工干预来处理不同类型的数据,因为它可以自动调整输出格式,而 printf 请求用户显式地指定格式化字符串。特别是在处理字符串和数字的混合输出时,cout 的语法更为直观和简洁。
    3. 可扩展性:对于自定义类型,cout 允许开发者通过重载输出运算符 (<<) 来定义自己的输出格式,这提供了良好的可扩展性。而 printf 不能直接支持自定义类型的打印,除非通过某种方式将其转换为内建类型。
    4. 标准一致性cout 是 C++ 标准库的一部分,充分利用 cout 能够使程序更加面向对象,更符合 C++ 的编程风格。printf 来自 C 语言标准库,虽然在 C++ 中仍然可用,但并不是 C++ 风格的首选。
    5. 灵活性cout 提供了更好的控制输出格式的功能,如设置宽度、填充、精度等,而且这些设置易于读写和维护。

    然而,在某些特定情况下,printf 可能更为有用: - 当您需要更详细地控制输出格式,尤其是在涉及复杂格式时,printf 的格式化字符串可能更易于使用和理解。 - 在性能极致敏感的场景中,经过适当的优化后,printf 在某些情况下可能比 cout 有更好的性能。

    总之,虽然 cout 通常是在 C++ 中处理输出的首选,但确切选择取决于具体的应用场景、性能需求和个人偏好。在决定使用哪一个之前,考虑项目的需求和上下文非常重要。

  3. 在使用C++语言的ROS平台开发中,推荐使用printfcout还是ROS_INFO

    在使用 C++ 语言开发 ROS (Robot Operating System) 平台的应用时,通常推荐使用 ROS_INFO 以及其相关的宏(如 ROS_WARN, ROS_ERROR, ROS_DEBUG 等)来进行消息的输出。这些宏专为 ROS 开发设计,提供了一些优势和特性,特别是在进行机器人软件开发时:

    1. 统一的日志管理ROS_INFO 和其他相关宏能够将输出消息统一管理,使得日志记录、查看和分析变得更加容易。ROS 提供的 rqt_console 和 roslaunch 日志文件都可以捕捉这些输出,方便开发者调试和追踪问题。
    2. 级别管理:不同于 printfcout 只能单纯输出文本,ROS_INFO 等宏允许你按照不同的严重性级别输出消息,如信息、警告、错误、调试等。这有助于开发者在运行时筛选和控制想要查看的输出类型。
    3. 节点识别:使用 ROS 日志函数,每条日志消息都会自动附带节点名信息。这对于在复杂系统中跟踪消息来源特别有用,尤其是当多个节点同时运行时。
    4. 格式化和类型安全ROS_INFO 等宏支持类似 printf 的格式化功能,使得在输出文本时可以包含变量。同时,由于 ROS_INFO 是在 C++ 环境下设计的,因此也可以在一定程度上享受到 C++ 类型安全的好处(尽管其内部实现可能是利用 printf 风格的格式化字符串)。
    5. 条件编译ROS_DEBUG 等调试相关的宏,在发布模式下可以通过编译选项完全禁用,从而不会对发布版本的性能造成影响。

    综上所述,虽然在 ROS 平台下开发时 printfcout 依然可用,但 ROS_INFO 和相关的宏提供了更多符合 ROS 开发环境和需求的优势。因此,在大多数情况下,推荐使用 ROS_INFO 及其相关宏进行消息输出和日志记录。

  4. 在使用C++语言的ROS平台开发中,推荐使用printfcoutROS_INFO还是ROS_INFO_STREAM

    在使用C++语言的ROS(Robot Operating System)平台开发时,选择printfcoutROS_INFO还是ROS_INFO_STREAM取决于具体的应用场景和你的偏好。下面是它们各自的特点和适用情况:

    printf

    • 来源于C语言,C++也支持。
    • 用于格式化输出,语法稍显复杂,对类型安全的支持不如C++的IO流。
    • 适用于快速的调试或者当开发环境更接近C语言时使用。

    std::cout

    • C++标准输入输出流的一部分,使用起来比printf更为方便和安全。
    • 支持类型安全的输出,可以通过重载<<操作符来扩展对自定义类型的支持。
    • 适用于C++程序的通用输出,特别是需要输出自定义类型数据时。

    ROS_INFOROS_INFO_STREAM

    • 这是ROS中专门用于日志输出的宏和流。
    • ROS_INFO 类似于printf,而ROS_INFO_STREAM则类似于std::cout,使用方法根据个人偏好和输出内容的复杂度来选择。
    • 它们的优势在于:
    • 集成了ROS的命名空间和节点名,可以更清楚地知道是哪个节点产生的日志。
    • 可以通过ROS的配置文件来调整日志的级别,方便调试和运行时的日志管理。
    • 支持网络日志(rosout),使得可以在ROS的不同部分或不同机器上收集和查看日志。

    推荐使用

    • 对于简单的调试信息,如果你更习惯C++风格,推荐使用ROS_INFO_STREAM;如果你倾向于使用类似C语言的格式化输出,可以选择ROS_INFO
    • 对于非ROS系统级的调试或者涉及大量复杂数据类型输出时,std::cout可能更为直接和方便。
    • 一般建议尽量避免使用printf,除非你有特别的理由,因为它不提供类型安全且在C中使用std::coutROS_INFO_STREAM可以更好地利用C的特性。

    综上所述,选择哪一种取决于你的具体需求和开发习惯。在ROS开发中,ROS_INFOROS_INFO_STREAM因为其与ROS系统的集成,通常会是首选。

    ROS消息打印

C++小技巧

模板参数必须在编译时是已知的常量

在C++中,模板参数必须在编译时是已知的常量。这意味着你不能使用运行时从配置文件中读取的值作为模板参数。

一种可能的解决方案是使用预处理器宏来定义N_dim。你可以在编译时通过编译器的命令行参数来设置这个宏。例如:

1
2
3
#define N 512  // 默认值。
template <unsigned int N>
eFFT<N> efft; // 模板类

预处理器宏(如#define N 512)在它被定义的文件以及#include该文件的所有文件中都是可见的。如果你在一个文件中定义了N,然后在另一个文件中想要使用它,你需要确保第二个文件#include了定义N的文件。

然后在编译时,你可以使用-D选项来设置N_dim的值:

1
g++ -DN_dim=1024 -o my_program my_program.cpp

另一种解决方案是使用一个固定大小的eFFT对象,然后在运行时根据需要使用其中的一部分。这可能需要你修改eFFT类的实现,使其能够处理不同大小的输入。

重复定义问题

你的问题是在多个源文件中都包含了parameters.h,并且在这个头文件中定义了eFFT_SIZE。这导致了eFFT_SIZE的多重定义。

解决这个问题的一种方法是在parameters.h中只声明eFFT_SIZE,然后在一个源文件(例如parameters.cpp)中定义它。这样,eFFT_SIZE就只在一个地方定义了。

首先,你需要在parameters.h中将eFFT_SIZE的定义改为声明:

1
extern const unsigned int eFFT_SIZE;

然后,在parameters.cpp中定义eFFT_SIZE

1
const unsigned int eFFT_SIZE = 128;

这样,eFFT_SIZE就只在parameters.cpp中定义了一次,而在其他源文件中,它只是一个外部链接的声明。这应该可以解决你的问题。d

函数多个返回值

在C++中,有几种方法可以实现函数的多个返回值:

  1. 使用引用参数(Reference Parameters)/指针:你可以通过引用参数来修改函数外部的变量,从而实现多个返回值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void getValues(int& x, int& y) {
    x = 5;
    y = 10;
    }

    int main() {
    int a, b;
    getValues(a, b);
    // Now a == 5 and b == 10
    }
  2. 使用std::pair或std::tuple:如果你的函数需要返回两个或更多的值,你可以使用std::pairstd::tuple

    1
    2
    3
    4
    5
    6
    7
    8
    std::pair<int, int> getValues() {
    return std::make_pair(5, 10);
    }

    int main() {
    std::pair<int, int> values = getValues();
    // Now values.first == 5 and values.second == 10
    }
  3. 使用结构体(Structs)或类(Classes):如果你的函数需要返回多个相关的值,你可以定义一个结构体或类来存储这些值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    struct Values {
    int x;
    int y;
    };

    Values getValues() {
    Values values;
    values.x = 5;
    values.y = 10;
    return values;
    }

    int main() {
    Values values = getValues();
    // Now values.x == 5 and values.y == 10
    }
  4. 使用数组。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    #include <stdio.h> 

    // 将较大的元素存储在arr[0]中
    void findGreaterSmaller(int a, int b, int arr[])
    {
    // Store the greater element at
    // 0th index of the array
    if (a > b) {
    arr[0] = a;
    arr[1] = b;
    }
    else {
    arr[0] = b;
    arr[1] = a;
    }
    }

    // Driver code
    int main()
    {
    int x, y;
    int arr[2];
    printf("输入两个数字: \n");
    scanf("%d%d", &x, &y);
    findGreaterSmaller(x, y, arr);
    printf("\n最大值为:%d,最小值为:%d",
    arr[0], arr[1]);
    return 0;
    }
  5. 选择哪种方法取决于你的具体需求和编程风格。

多个源文件中共享一个变量/全局变量

在C++中,如果你想在多个源文件中共享一个变量,你可以使用extern关键字。你可以在一个源文件中定义一个变量,并在其他源文件中使用extern关键字声明同一个变量。

首先,在ESVIO/pose_graph/src/pose_graph_node.cpp文件中,你需要将SYSTEM_MODE变量的定义移动到一个头文件中,例如global.h。然后在pose_graph_node.cppkeyframe.cpp中都包含这个头文件。

global.h文件内容如下:

1
2
3
4
5
6
#ifndef GLOBAL_H
#define GLOBAL_H

extern int SYSTEM_MODE;

#endif // GLOBAL_H

然后在ESVIO/pose_graph/src/pose_graph_node.cpp文件中,你需要包含global.h并定义SYSTEM_MODE

1
2
3
#include "global.h"

int SYSTEM_MODE = 0; // 初始化SYSTEM_MODE

最后,在ESVIO/pose_graph/src/keyframe.cpp文件中,你也需要包含global.h。这样你就可以在keyframe.cpp中使用SYSTEM_MODE变量了:

1
2
3
#include "global.h"

// 现在你可以在这个文件中使用SYSTEM_MODE变量

注意,extern关键字告诉编译器变量的定义在别的地方,你不能在声明的时候初始化它。变量的定义(也就是初始化)应该在一个源文件中进行,而不是头文件。如果你在多个地方定义了同一个extern变量,那么会导致链接错误。

类与对象

为什么要定义类与对象,什么时候定义类与对象?

在面向对象的编程语言中,类(Class)是一种定义新类型的方式,它封装了数据和操作数据的方法。对象(Object)则是类的实例,它包含了类定义的数据和方法。

定义类和对象的目的主要有以下几点:

  1. 封装:类可以将数据和操作数据的方法封装在一起,隐藏内部实现细节,提高代码的安全性和可维护性。
  2. 继承:类可以继承其他类的属性和方法,实现代码的复用。
  3. 多态:通过接口或者虚函数,不同的类对象可以执行相同的操作,但表现出不同的行为,提高代码的灵活性。

何时定义类和对象主要取决于你的需求。以下是一些可能需要定义类和对象的情况:

  1. 当你需要创建一个复杂的数据结构,这个数据结构包含多个相关的数据和操作这些数据的方法时,你可以定义一个类来封装这些数据和方法。
  2. 当你需要创建多个具有相同属性和行为的对象时,你可以定义一个类,然后创建这个类的多个对象。
  3. 当你需要实现面向对象的设计模式(如工厂模式、单例模式等)时,你需要定义类和对象。

怎么用类与对象

尽量实现,提供类所需要的输入数据,所有操作在类内部封装和实现,在外部直接输出结果。

这个想法是面向对象编程(OOP)的核心原则之一,称为封装(Encapsulation)。封装是将数据(变量)和操作数据的函数(方法)捆绑在一起形成一个对象(即类的实例),并规定了外部对这些数据和函数的访问方式。

封装有以下几个优点:

  1. 提高安全性:通过隐藏对象的内部状态,只允许通过对象的方法来访问和修改,可以防止外部代码随意修改对象的内部状态,提高了代码的安全性。
  2. 简化接口:对象的用户只需要知道对象提供了哪些方法,而不需要知道这些方法是如何实现的。这使得代码更易于理解和使用。
  3. 提高可维护性:由于对象的内部实现被封装起来,所以在不影响对象的用户的情况下,可以更容易地改变对象的内部实现。

面向对象编程(OOP)的核心原则主要有以下四个:

  1. 封装(Encapsulation):封装是将对象的状态(属性)和行为(方法)捆绑在一起,同时隐藏对象的内部实现细节,只提供有限的接口供外部访问。这样可以保护对象的内部状态,提高代码的安全性和可维护性。
  2. 继承(Inheritance):继承是子类可以继承父类的属性和方法,实现代码的复用。子类可以扩展和修改父类的行为,提供更具体的功能。
  3. 多态(Polymorphism):多态是指不同的对象可以对同一消息做出不同的响应。在运行时,根据对象的实际类型来调用相应的方法,提高了代码的灵活性。
  4. 抽象(Abstraction):抽象是将复杂的系统简化为更简单的模型。通过定义抽象的类和接口,隐藏具体的实现细节,让程序员只关注有用的信息。

这四个原则是面向对象编程的基础,它们使得代码更易于理解、维护和扩展。

模板类

模板类是C++中一种特殊的类,它可以用于创建处理不同数据类型的类的蓝图。模板类的定义以关键字template开始,后面跟一个或多个模板参数。

例如,你可以定义一个模板类Array,它可以用于创建处理不同类型元素的数组:

1
2
3
4
5
template <typename T, int N>
class Array {
T elements[N];
// ...
};

在这个例子中,T是一个类型模板参数,N是一个非类型模板参数。你可以用任何类型替换T,用任何整数替换N,来创建不同的Array类:

1
2
Array<int, 10> intArray;
Array<double, 20> doubleArray;

模板类与一般的类的主要区别在于,模板类可以处理多种类型的数据,而一般的类只能处理特定类型的数据。模板类提供了一种机制,使得你可以在类定义时不指定具体的类型,而是在使用类时指定类型。这使得你的代码更加灵活,可以处理多种类型的数据。

成员初始化列表

成员初始化列表(Member Initializer List)是C++中一个重要的特性,它允许在构造函数中初始化类的成员变量。这个特性不仅可以提高代码的清晰度和执行效率,还是某些情况下初始化成员变量的唯一方法。

在C++中,构造函数可以包含一个初始化列表,用于在进入构造函数体之前初始化成员变量。初始化列表以冒号(:)开头,后面跟着用逗号(,)分隔的初始化表达式。例如:

1
2
3
4
5
6
7
8
class Example {
public:
// 含有两个参数的构造函数
Example(int x, int y) : x_(x), y_(y) {} // 用接收到的 int 类型的变量 x 的值来初始化 x_ 。
private:
int x_;
int y_;
};

在这个例子中,*x_y_*是通过成员初始化列表进行初始化的,而不是在构造函数体内赋值。

有几种情况下必须使用成员初始化列表:

  1. const成员变量:const成员变量必须在声明时初始化,不能在构造函数体内赋值。
  2. 引用成员变量:引用成员同样需要在声明时初始化。
  3. 没有默认构造函数的类类型成员:如果类成员本身是一个对象,并且这个对象没有无参的构造函数,那么必须通过成员初始化列表来调用其有参构造函数。
  4. 继承中的基类成员:如果需要在子类中初始化基类的私有成员,也需要在成员初始化列表中显式调用基类的构造函数。

使用成员初始化列表初始化类成员比在构造函数体内赋值有几个优势:

  • 性能:对于类类型的成员变量,使用成员初始化列表可以避免调用默认构造函数后再赋值,减少了一次不必要的构造和析构过程,提高了效率。
  • 顺序:成员变量的初始化顺序与它们在类定义中的声明顺序一致,与初始化列表中的顺序无关。这有助于避免一些因初始化顺序不当导致的错误。
1
2
3
4
5
6
7
8
9
10
11
12
13
class A {
private:
const int a; // const成员
public:
A() : a(10) {} // 使用成员初始化列表进行初始化
};

class B {
private:
int &b; // 引用成员
public:
B(int &b_ref) : b(b_ref) {} // 使用成员初始化列表进行初始化
};

在这个示例中,类A的成员a是一个const成员,类B的成员b是一个引用成员。它们都通过成员初始化列表进行了初始化。

其它

  1. 在C++中,如果一个类没有明确指定访问修饰符(publicprotectedprivate),那么默认的访问级别是private。这意味着,如果你没有在类定义中看到任何访问修饰符,那么该类的所有成员都是私有的。

  2. 初始赋值:

    1
    2
    3
    4
    5
    class FeatureTracker
    {
    private:
    int trackIDCounter_ = 1;
    }

    trackIDCounter_ = 1;是对成员变量trackIDCounter_的初始赋值。这意味着,当创建这个类的对象时,trackIDCounter_的初始值将被设置为1。

    在类的成员函数中,你可以更改这个成员变量的值。例如,你可以在一个成员函数中增加trackIDCounter_的值:

    1
    2
    3
    void FeatureTracker::incrementTrackID() {
    trackIDCounter_++;
    }

    在这个例子中,incrementTrackID函数将trackIDCounter_的值增加1。你可以在类的任何成员函数中更改trackIDCounter_的值,只要这个函数有权限访问这个成员变量(即,这个函数不是const的)。

  3. 等等。

结构体

概述

定义一个结构体类型就类似于定义了一个变量类型,结构体的用法类似于变量,只不过一个结构体里可以包含多个变量类型。

结构体(struct)在C++中是一种复合数据类型,它可以包含多个不同类型的数据成员。你可以将结构体看作是一个“自定义的数据类型”,这个数据类型可以包含多个其他的数据类型。

例如,你可以定义一个BoundingBox结构体来表示一个目标检测框,这个结构体包含了xywh四个整型数据成员。然后,你就可以像使用其他数据类型一样使用这个BoundingBox结构体,例如创建BoundingBox类型的变量,将BoundingBox类型的变量作为函数的参数,等等。

将一组相关的变量定义为结构体(struct)有以下几个好处:

  1. 组织性:结构体可以将一组相关的数据组织在一起,使代码更加清晰和易于理解。

  2. 代码复用:你可以多次使用同一个结构体,这有助于减少代码重复。

    如果你需要在其他地方使用相同的一组变量,你可能需要再次声明和初始化这些变量,这就是代码重复。但是,如果你将这些变量定义为一个结构体,你就可以在需要的地方创建这个结构体的实例,而不需要重复声明和初始化这些变量。这就是代码复用。以下是一个例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    struct BoundingBox {
    int x;
    int y;
    int w;
    int h;
    };

    void processBoundingBox(BoundingBox box) {
    // 在这个函数中处理目标检测框
    }

    int main() {
    BoundingBox box1 = {1, 2, 3, 4};
    processBoundingBox(box1);

    BoundingBox box2 = {5, 6, 7, 8};
    processBoundingBox(box2);

    return 0;
    }

    在这个例子中,我们定义了一个BoundingBox结构体,并在processBoundingBox函数中使用它。在main函数中,我们创建了两个BoundingBox的实例box1box2,并将它们传递给processBoundingBox函数进行处理。这样,我们就复用了BoundingBox结构体的定义,而不需要在每个需要使用目标检测框的地方都声明和初始化xywh这四个变量。

  3. 易于维护:如果你需要修改这组数据,只需要在一个地方(即结构体定义)进行修改,而不是在代码的多个地方。

    当你需要修改或更新代码时,结构体可以使这个过程更加简单和直接。举个例子,假设你有一个关于目标检测框的结构体:

    1
    2
    3
    4
    5
    6
    struct BoundingBox {
    int x;
    int y;
    int w;
    int h;
    };

    现在,你想要添加一个新的属性,比如目标检测框的颜色。如果你没有使用结构体,你可能需要在代码的多个地方添加新的变量,并且需要确保这些变量在所有的函数和方法中都被正确地更新和使用。

    但是,如果你使用了结构体,你只需要在结构体的定义中添加新的属性:

    1
    2
    3
    4
    5
    6
    7
    struct BoundingBox {
    int x;
    int y;
    int w;
    int h;
    std::string color;
    };

    这样,所有使用BoundingBox的地方都会自动获得新的color属性,你只需要在适当的地方更新和使用这个新的属性即可。这就是结构体使代码"易于维护"的一个例子。

  4. 封装:结构体可以封装数据和操作,使得数据和操作紧密相关,提高代码的可读性和可维护性。

使用

定义

在C++中,struct可以包含各种类型的成员,包括基本类型(如intdouble等)、类对象、数组、vector等。以下是一个例子:

1
2
3
4
5
6
#include <vector>

struct MyStruct {
int id;
std::vector<int> values;
};

在这个例子中,MyStruct包含一个int类型的成员id和一个vector<int>类型的成员values

你可以定义一个结构体来表示单个目标检测框,然后使用vector来存储多个这样的目标检测框。这样做的好处是,你可以很容易地添加、删除和遍历目标检测框,而且代码的可读性和可维护性也会提高。

1
2
3
4
5
6
7
8
9
10
11
struct BoundingBox {
int class_id;
float class_confidence;
int x;
int y;
int w;
int h;
};

vector<BoundingBox> boxes_left;
vector<BoundingBox> boxes_right;

在这个例子中,BoundingBox是一个结构体,它表示一个目标检测框。boxes_leftboxes_right是两个vector,它们分别存储左目和右目的目标检测框。

当你需要添加一个新的目标检测框时,你可以创建一个BoundingBox的实例,设置它的属性,然后将它添加到boxes_leftboxes_right中。当你需要遍历所有的目标检测框时,你可以遍历boxes_leftboxes_right,并对每个BoundingBox实例进行操作。

赋值与引用

在C++中,你可以通过.操作符来引用struct中的成员。如果你的struct中有一个vector成员,你可以像下面这样引用它:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <vector>

struct MyStruct {
int id;
std::vector<int> values;
};

int main() {
MyStruct s;
s.values.push_back(1); // 向vector中添加一个元素
int firstValue = s.values[0]; // 访问vector中的第一个元素
return 0;
}

在这个例子中,MyStruct是一个结构体,它有一个vector<int>类型的成员values。在main函数中,我们创建了一个MyStruct类型的变量s,然后通过.操作符来访问和操作它的values成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
struct BoundingBox {
int class_id;
float class_confidence;
int x;
int y;
int w;
int h;
};

int main() {
vector<BoundingBox> boxes_left;

// 赋值
for (int i = 0; i < detection_left.detections.size(); i++) {
BoundingBox box;
box.x = detection_left.detections[i].x * scale;
box.y = detection_left.detections[i].y * scale;
box.w = detection_left.detections[i].w * scale;
box.h = detection_left.detections[i].h * scale;
box.class_id = detection_left.detections[i].class_id;
box.class_confidence = detection_left.detections[i].class_confidence;

boxes_left.push_back(box);
}

// 索引
for (int i = 0; i < boxes_left.size(); i++) {
int x = boxes_left[i].x;
int y = boxes_left[i].y;
int w = boxes_left[i].w;
int h = boxes_left[i].h;
int class_id = boxes_left[i].class_id;
float class_confidence = boxes_left[i].class_confidence;

// 在这里处理x, y, w, h, class_id, class_confidence
}
}

在这个例子中,我们首先创建了一个BoundingBox的实例box,然后设置了box的属性,最后将box添加到boxes_left中。这个过程在循环中重复,直到处理完所有的目标检测框。

我们使用了boxes_left[i]来访问boxes_left中的第i个元素,然后使用.操作符来访问这个元素的数据成员。这个过程在循环中重复,直到处理完boxes_left中的所有元素。

在C++中,你可以使用vectorclear方法来清空vector中的所有元素。以下是一个例子:

1
boxes_left.clear();

在这个例子中,boxes_left.clear()将清空boxes_left中的所有元素。这个操作将使boxes_left的大小变为0,但不会改变它的容量。如果你希望同时清空vector的元素和容量,你可以使用swap方法:

1
vector<BoundingBox>().swap(boxes_left);

在这个例子中,vector<BoundingBox>().swap(boxes_left)将创建一个新的空vector,然后与boxes_left交换。这个操作将使boxes_left的大小和容量都变为0。

清空/初始化

在C++中,你可以使用构造函数或者赋值运算符来初始化或清空一个结构体的值。以下是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
struct BoundingBox {
int x;
int y;
int w;
int h;

// 构造函数,用于初始化结构体的值
BoundingBox() : x(0), y(0), w(0), h(0) {}

// 一个方法,用于清空结构体的值
void clear() {
x = 0;
y = 0;
w = 0;
h = 0;
}
};

int main() {
BoundingBox box;

// 使用构造函数初始化结构体的值
box = BoundingBox();

// 使用方法清空结构体的值
box.clear();

return 0;
}

在这个例子中,BoundingBox结构体有一个构造函数,它将所有的数据成员初始化为0。clear方法将所有的数据成员设置为0。在main函数中,我们创建了一个BoundingBox类型的变量box,然后使用构造函数和clear方法来初始化和清空box的值。

作为函数的参数

在C++中,你可以将结构体作为函数的输入参数或输出参数。以下是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>

// 定义一个结构体
struct Point {
int x;
int y;
};

// 将结构体作为输入参数的函数
void printPoint(Point p) {
std::cout << "Point: (" << p.x << ", " << p.y << ")" << std::endl;
}

// 将结构体作为输出参数的函数
Point getPoint() {
Point p;
p.x = 10;
p.y = 20;
return p;
}

int main() {
Point p1 = {1, 2};
printPoint(p1); // 输出: Point: (1, 2)

Point p2 = getPoint();
printPoint(p2); // 输出: Point: (10, 20)

return 0;
}

在这个例子中,printPoint函数接收一个Point类型的参数,getPoint函数返回一个Point类型的值。在main函数中,我们创建了两个Point类型的变量p1p2,并使用printPoint函数打印它们的值。

vector

添加元素

如果vector变量的大小没有预先定义,你需要先调用resize函数来设置它们的大小,然后再使用索引来赋值。或者,你可以使用push_backemplace_back来添加元素。

以下是使用resize和索引赋值的示例:

1
2
3
4
5
6
7
8
9
10
nextPts.resize(prevPts.size());
status.resize(prevPts.size());

for (size_t i = 0; i < prevPts.size(); ++i) {
// ...
nextPts[i] = prevPts[i] + flow_at_point;
// ...
status[i] = 0;
// ...
}

以下是使用push_back添加元素的示例:

1
2
3
4
5
6
7
for (size_t i = 0; i < prevPts.size(); ++i) {
// ...
nextPts.push_back(prevPts[i] + flow_at_point);
// ...
status.push_back(0);
// ...
}

如果你知道vector的最终大小,那么使用resize函数来预先设置大小,然后使用索引来赋值,通常会更快。这是因为resize函数会一次性分配足够的内存,而不需要在每次添加元素时重新分配和复制内存。此外,使用索引来赋值可以直接访问和修改vector中的元素,而不需要调用push_backemplace_back函数。

如果你不知道vector的最终大小,或者vector的大小可能会动态变化,那么使用push_backemplace_back来添加元素可能会更方便。这是因为push_backemplace_back函数可以在不知道vector最终大小的情况下动态添加元素。但是,你需要注意的是,如果vector的大小增长很快,push_backemplace_back函数可能需要多次重新分配和复制内存,这可能会影响性能。

总的来说,如果你知道vector的最终大小,推荐使用resize和索引赋值。如果你不知道vector的最终大小,或者vector的大小可能会动态变化,推荐使用push_backemplace_back

clear与容量

clear

在C++中,你可以使用vectorclear方法来清空vector中的所有元素。以下是一个例子:

1
curBoxesLeft_.clear();

在这个例子中,curBoxesLeft_.clear()将清空curBoxesLeft_中的所有元素。这个操作将使curBoxesLeft_的大小变为0,但不会改变它的容量。如果你希望同时清空vector的元素和容量,你可以使用swap方法:

1
vector<BoundingBox>().swap(curBoxesLeft_);

在这个例子中,vector<BoundingBox>().swap(curBoxesLeft_)将创建一个新的空vector,然后与curBoxesLeft_交换。这个操作将使curBoxesLeft_的大小和容量都变为0。

然后,你可以使用push_backemplace_back方法向curBoxesLeft_添加新的目标检测框。例如:

1
2
3
BoundingBox newBox;
// ... 设置newBox的属性...
curBoxesLeft_.push_back(newBox);

在这个例子中,我们首先创建了一个新的BoundingBox对象newBox,然后将其添加到curBoxesLeft_中。

在C++中,vectorclear方法会移除vector中的所有元素,但不会释放内存。如果vector是空的(即,没有赋过值),那么clear操作实际上什么也不会做,但这并不会导致问题。

以下是一个例子:

1
2
std::vector<int> v;
v.clear(); // 这是安全的,即使v是空的

在这个例子中,我们创建了一个空的vector,然后对其执行了clear操作。这不会导致错误或异常。

容量

在C++中,vector的容量(capacity)是指vector在不重新分配内存的情况下可以存储的元素的最大数量。这个值通常大于或等于vector的大小(size),vector的大小是指它当前实际包含的元素的数量。

当你向vector添加元素时,如果vector的大小超过了它的容量,那么vector会重新分配内存,以便能够存储更多的元素。这个过程可能会消耗一些时间,因此,如果你知道vector将需要存储大量的元素,你可以使用reserve方法来预先分配足够的内存,这样可以提高代码的性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
vector<int> v;

cout << "Size: " << v.size() << endl; // 输出:Size: 0
cout << "Capacity: " << v.capacity() << endl; // 输出:Capacity: 0

v.push_back(1);

cout << "Size: " << v.size() << endl; // 输出:Size: 1
cout << "Capacity: " << v.capacity() << endl; // 输出:Capacity: 1 或更大的数

v.reserve(100);

cout << "Size: " << v.size() << endl; // 输出:Size: 1
cout << "Capacity: " << v.capacity() << endl; // 输出:Capacity: 100

在这个例子中,我们首先创建了一个空的vector,然后添加了一个元素,然后预先分配了足够的内存来存储100个元素。你可以看到,vector的大小和容量在这个过程中是如何变化的。

其它

  1. std::vector的赋值操作符(=)执行的是内容的复制,而不是地址的复制。

mutex

互斥量(mutex),它是一种同步原语,用于保护共享数据免受多个线程同时访问。在多线程环境中,如果多个线程试图同时访问和修改同一块数据,可能会导致数据不一致和未定义的行为。为了防止这种情况,我们可以使用互斥量来确保在任何时候只有一个线程能够访问该数据。

比如在回调函数中被持续赋值,在其它函数中被修改。

即使变量在其他函数中只被读取,也需要保护它,因为在多线程环境中,一个线程可能在另一个线程正在写入变量的同时读取该变量,这可能会导致读取到的数据是不一致或者无效的。这种情况被称为“读-写冲突”。

一个或多个共享资源(例如,一个全局变量或一个在多个线程之间共享的数据结构),当一个线程想要访问这个共享资源时,它需要首先锁定(lock)互斥量。如果互斥量已经被另一个线程锁定,那么这个线程将会被阻塞,直到互斥量被解锁(unlock)。当线程完成对共享资源的访问后,它需要解锁互斥量,以便其他线程可以锁定互斥量并访问共享资源。

lock

1
2
3
4
std::mutex m_buf_event;
m_buf_event.lock();
// ... do some work ...
m_buf_event.unlock();

你需要手动调用lock()unlock()来锁定和解锁互斥量。这种方式的问题是,如果在lock()unlock()之间的代码抛出了异常,那么unlock()可能永远不会被调用,从而导致死锁。

示例:

1
2
3
4
5
6
7
8
9
std::mutex m_buf_event;

void eventLeft_callback(const dvs_msgs::EventArray &event_msg){
m_buf_event.lock();
if (!events_left_buf.empty())
events_left_buf.pop();
events_left_buf.push(event_msg);
m_buf_event.unlock();
}

lock_guard

在C++中,std::lock_guard对象的作用域是由其所在的代码块(即最近的大括号{}内的区域)决定的。当std::lock_guard对象在代码块内创建时,它会自动锁定传递给它的互斥量。当std::lock_guard对象超出其作用域(即离开其所在的代码块)时,它的析构函数会被调用,从而自动解锁互斥量。

1
2
3
4
5
std::mutex m_buf_event;
{
std::lock_guard<mutex> lock(m_buf_event);
// ... do some work ...
} // mutex is automatically unlocked here

使用了std::lock_guard,这是一个RAII(Resource Acquisition Is Initialization)机制的互斥包装器,它在构造时提供一个已锁定的互斥,并在析构时解锁互斥。这意味着当std::lock_guard对象超出其作用域并被销毁时,互斥量会自动被解锁,即使在lock_guard的作用域内的代码抛出了异常。这样可以避免死锁,并使代码更安全、更易于理解。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
mutex evt_buf_mutex;
mutex events_storage_mutex;

void callback_events(const dvs_msgs::EventArray::ConstPtr &msg) {
// We first lock the event buffers, to avoid any collision
{
lock_guard<mutex> lock(evt_buf_mutex);

// We then append the received events at the back of the current buffer
evt_buffers[buffer_used].insert(evt_buffers[buffer_used].end(),
begin(msg->events), end(msg->events));
}

// Add new events to events_storage
{
lock_guard<mutex> storage_lock(events_storage_mutex);
for (const dvs_msgs::Event &event : msg->events) {
events_storage.push_back(event);
// If the size of events_storage exceeds accumulation_number, we remove
// the oldest event
if (events_storage.size() > accumulation_number) {
events_storage.pop_front();
}
}
}
}

Eigen

Matrix类与Array类

相对于Matrix类提供的线性代数(矩阵)运算,Array类提供了更为一般的数组功能。Array类为元素级的操作提供了有效途径,比如点加(每个元素加值)或两个数据相应元素的点乘。

1
2
Eigen::Matrix<float, N, N> FFT;
(FFT.array().arg()).matrix(); // 将FFT矩阵转换为数组,然后调用arg()函数计算每个元素的相位(即复数的角度),最后再将结果转换回矩阵。

OpenCV

在 OpenCV 中,cv::Mat::at函数的参数顺序是(row, column),对应于(y, x),而不是通常的(x, y)。这是因为在图像处理中,我们通常将图像视为一个二维数组,其中第一个维度是行(对应于y坐标),第二个维度是列(对应于x坐标)。

OpenCV的数据类型

参考链接

S = 有符号整型 U = 无符号整型 F = 浮点型

CV_8U - 8位无符号整数(0…255)

CV_8S - 8位有符号整数(-128…127)

CV_16U - 16位无符号整数(0…65535)

CV_16S - 16位有符号整数(-32768…32767)

CV_32S - 32位有符号整数(-2147483648…2147483647)

CV_32F - 32位浮点数(-FLT_MAX…FLT_MAX,INF,NAN)

CV_64F - 64位浮点数(-DBL_MAX…DBL_MAX,INF,NAN)

而后面的C1、C2、C3是什么意思呢?这里的1、2、3代表的是通道数,比如RGB就是3通道,颜色表示最大为255,所以可以用CV_8UC3这个数据类型来表示;灰度图就是C1,只有一个通道;而带alph通道的PNG图像就是C4,是4通道图片。

负值处理

cv::Mat矩阵可以存储负值并进行运算,但这取决于你选择的数据类型。OpenCV中的cv::Mat可以存储多种类型的数据,包括有符号整数和浮点数。

例如,如果你选择CV_32F(32位浮点数)或CV_64F(64位浮点数)作为你的数据类型,那么你的cv::Mat矩阵就可以存储负值,并且可以进行各种运算,如加法、减法、乘法等。

以下是一个简单的例子:

1
2
3
cv::Mat mat(3, 3, CV_32F, cv::Scalar(-1)); // Creates a 3x3 matrix filled with -1
mat.at<float>(0, 0) = -5; // Sets the value at the first row and first column to -5
std::cout << mat << std::endl; // Prints the matrix

在这个例子中,我们创建了一个3x3的矩阵,所有元素的初始值都是-1,然后我们将第一行第一列的元素值设置为-5。

当你使用OpenCV的imshow函数来可视化一个cv::Mat矩阵时,负值的处理方式取决于矩阵的数据类型。

如果你的cv::Mat矩阵的数据类型是无符号整数(如CV_8U),那么它不能存储负值,任何负值都会被视为零。

如果你的cv::Mat矩阵的数据类型是有符号整数(如CV_8SCV_16S)或浮点数(如CV_32FCV_64F),那么它可以存储负值。在可视化这样的矩阵时,你需要先将矩阵的值规范化到0-255的范围内。你可以使用OpenCV的normalize函数来实现这一点。

以下是一个例子:

1
2
3
4
cv::Mat mat(3, 3, CV_32F, cv::Scalar(-1)); // Creates a 3x3 matrix filled with -1
cv::normalize(mat, mat, 0, 255, cv::NORM_MINMAX, CV_8U); // Normalize the values to 0-255
cv::imshow("Normalized Image", mat); // Display the normalized image
cv::waitKey(0); // Wait for a key press

在这个例子中,我们首先创建了一个3x3的矩阵,所有元素的初始值都是-1。然后我们使用cv::normalize函数将矩阵的值规范化到0-255的范围内,并将数据类型转换为CV_8U。最后,我们使用cv::imshow函数显示规范化后的图像。

访问cv::Mat对象中特定位置的像素值

在OpenCV中,有几种方法可以访问cv::Mat对象中特定位置的像素值。以下是一些常见的方法:

  1. 使用at函数:这是最直接的方法,你可以使用模板参数来指定像素的数据类型。例如,如果你的图像是一个8位单通道图像,你可以这样访问像素值:

    1
    unsigned char pixel_value = cur_event_mat_left_fft.at<unsigned char>(e_left.y, e_left.x);

    在你的代码中,你使用了cur_event_mat_left_fft.at<char>(i, j)来访问像素值。这里的char是一个有符号的8位整数,范围是-128到127。如果一个像素的真实值是255(在unsigned char中表示为255),在char中它会被表示为-1。

    为了解决这个问题,你应该使用unsigned char来访问cv::Mat的数据。

  2. 使用ptr函数:这个函数返回一个指向图像某一行的指针,然后你可以像操作普通数组一样操作这个指针。这个方法通常比at函数快,但是也更容易出错,因为你需要自己管理指针。例如:

    1
    2
    unsigned char* row_ptr = cur_event_mat_left_fft.ptr<unsigned char>(e_left.y);
    unsigned char pixel_value = row_ptr[e_left.x];
  3. 使用迭代器:你也可以使用C++的迭代器来访问cv::Mat中的像素。这个方法比较安全,但是通常比atptr函数慢。例如:

    1
    2
    cv::Mat_<unsigned char>::iterator it = cur_event_mat_left_fft.begin<unsigned char>() + e_left.y * cur_event_mat_left_fft.cols + e_left.x;
    unsigned char pixel_value = *it;

以上三种方法都可以用来访问和修改像素值。你可以根据你的需要选择最适合你的方法。

赋值

在OpenCV中,cv::Mat的赋值操作符(=)实际上是创建了一个新的头部,但是数据是共享的。这意味着,如果你有两个cv::Mat对象AB,并且你执行了A = B;,那么AB将共享相同的数据。如果你修改了A中的数据,B中的数据也会被修改。

这是因为cv::Mat使用了引用计数机制来管理数据。当你创建一个新的cv::Mat对象并赋值给另一个cv::Mat对象时,它们都会指向同一个数据,而且这个数据的引用计数会增加。当一个cv::Mat对象被销毁时,它会减少数据的引用计数。只有当引用计数变为0时,数据才会被释放。

如果你希望创建一个cv::Mat的真正副本,你可以使用clonecopyTo方法。例如,cv::Mat B = A.clone();将创建一个新的cv::Mat对象B,它包含了A的一个副本。在这种情况下,AB不会共享数据。

clone和copyTo

在OpenCV中,clonecopyTo函数都可以用来复制cv::Mat对象,但它们的使用方式和行为有一些不同。

clone函数创建一个新的cv::Mat对象,并复制源对象的所有数据。它不需要一个已经存在的目标对象,因为它会创建一个新的对象。例如:

1
2
3
cv::Mat src;
// ... 填充src...
cv::Mat dst = src.clone();

copyTo函数将源对象的数据复制到目标对象。如果目标对象已经存在,它的大小和类型必须与源对象匹配,否则它会被重新分配。copyTo还有一个可选的参数,允许你指定一个掩码,只有掩码中非零的元素才会被复制。例如:

1
2
3
cv::Mat src, dst;
// ... 填充src...
src.copyTo(dst);

总的来说,如果你只需要复制一个cv::Mat对象,并且不需要使用掩码,那么clone可能是更简单的选择。如果你需要使用掩码,或者你已经有一个目标对象并希望复制数据到这个对象,那么copyTo可能是更好的选择。

组织管理一组变量或函数

在C++中,除了结构体(struct),还有以下几种方式可以组织和管理一组变量或函数:

  1. 类(Class):类是C++中的一个核心概念,它可以包含变量(称为成员变量)和函数(称为成员函数)。类提供了封装、继承和多态等面向对象编程的特性。
  2. 命名空间(Namespace):命名空间可以用来组织一组相关的变量和函数,以避免命名冲突。
  3. 数组(Array)和向量(Vector):如果你有一组相同类型的变量,你可以使用数组或向量来存储它们。
  4. 函数(Function):如果你有一组相关的操作,你可以将它们封装在一个函数中。
  5. 枚举(Enum):如果你有一组相关的常量,你可以使用枚举来定义它们。
  6. 联合(Union):联合是一种特殊的数据类型,它可以存储不同类型的数据,但一次只能存储其中一种类型的数据。

选择哪种方式取决于你的具体需求和使用场景。

Python

if name == "main":

在Python中,if __name__ == "__main__": 是一个常见的模式。这行代码的作用是检查当前的模块是被直接运行还是被导入为一个模块。

当Python解释器读取一个源文件时,它会首先定义一些特殊的变量。其中一个就是 __name__。如果该文件被直接运行,那么 __name__ 的值会被设置为 "__main__"。如果该文件被其他Python文件导入,那么 __name__ 的值则会被设置为该文件的名字。

因此,if __name__ == "__main__": 这行代码的意思是,"如果这个文件被直接运行,那么执行以下的代码"。这个模式常常被用来在一个Python文件中编写一些测试代码,这些测试代码只有在文件被直接运行时才会执行,而在文件被导入时不会执行。

读取文件

1
2
3
import rosbag
bag_data = rosbag.Bag(rosbag_file, "r")
bag_data.close()

在Python中,使用with语句打开文件时,当with语句的代码块执行完毕后,文件会自动关闭。所以,你不需要显式地调用file.close

这是因为with语句创建了一个上下文,当离开这个上下文时,Python会自动清理相关的资源。在这个例子中,相关的资源就是打开的文件。

1
2
3
4
5
6
7
8
9
import yaml
import numpy as np

# 打开并读取YAML文件
with open('myFolder/cam_to_cam.yaml', 'r') as file:
data = yaml.safe_load(file)

# 从YAML数据中获取矩阵
T_10 = np.array(data['extrinsics']['T_10'])

data = yaml.safe_load(file)这行代码执行完毕后,file会自动关闭,无需手动关闭。

内存泄漏

参考链接

什么是内存泄漏?

内存泄漏是指程序中已分配的内存未能(在离开其作用域、程序运行结束后)成功释放,导致可用内存逐渐减少的现象。在程序运行过程中,如果反复发生内存泄漏,最终可能会导致系统可用内存耗尽,从而影响程序的性能或导致程序崩溃。内存泄漏在长时间运行的程序中尤其危险,例如服务器或持续运行的后台任务。

内存泄漏的原因

内存泄漏通常发生在以下几种情况:

  • 未释放动态分配的内存:当使用如 malloc, calloc, reallocnew 等函数分配内存后,未使用对应的 free 或 delete 来释放内存。

  • 资源占用:除了内存外,程序可能申请其他系统资源(如文件句柄、数据库连接等),未正确释放这些资源也会导致类似内存泄漏的问题。

  • 数据结构错误:例如,链表、树等数据结构若未正确处理其元素的删除操作,可能导致部分节点成为不可达的,从而造成内存泄漏。

如何判断内存泄漏?

判断和诊断内存泄漏通常需要以下几个步骤或工具:

  1. 代码审查:通过审查代码来寻找可能未释放内存的地方。特别关注那些有动态内存分配的函数或模块。
  2. 运行时工具:
    • Valgrind:这是一个编程工具,用于内存调试、内存泄漏检测等。在 Linux 环境下,使用 Valgrind 运行程序可以帮助检测内存泄漏。
    • Visual Studio:在 Windows 环境下,Visual Studio IDE 提供了内置的内存泄漏检测工具。
    • Sanitizers:如 AddressSanitizer,这是一种快速的内存错误检测工具,可以集成到 GCC 或 Clang 编译器中,用于检测内存泄漏和其他内存相关错误。
  3. 性能监控工具:使用系统或第三方性能监控工具来观察程序的内存使用情况,查看内存使用是否随时间持续增加。
  4. 日志和追踪:在代码中添加日志输出,特别是在分配和释放资源的地方,可以帮助追踪内存的使用和释放。

如何防止内存泄漏?

  • 使用智能指针:在 C++中使用 std::unique_ptr, std::shared_ptr 等智能指针可以自动管理内存,大大减少内存泄漏的风险。
  • 资源获取即初始化(RAII, Resource Acquisition Is Initialization):这是一种编程范式。资源的获取即是初始化,资源的释放即是销毁。确保在对象的生命周期内资源被正确管理。通过在对象的构造函数中分配资源,并在析构函数中释放资源,可以保证资源总是被正确管理。
  • 定期代码审查:定期进行代码审查可以帮助识别潜在的内存泄漏问题。
  • 自动化测试:编写测试用例,尤其是针对资源管理的单元测试,可以在开发过程中早期发现和解决内存泄漏问题。

其它

  1. code单词做代码释义时是不可数名词。

    1
    2
    不可数名词
    Computer code is a system or language for expressing information and instructions in a form which can be understood by a computer.
  2. Python 并没有强制要求你用 Tab 缩进或者用空格缩进,但在PEP8中,建议使用4个空格来缩进。对于任何一个编辑器或者IDE,一般都有配置选项,可以设置把 TAB 键展开为4个空格,以保证代码的兼容性。

  3. 命令行使用\实现换行:

    1
    2
    3
    sudo apt-get install \
    ros-$1-sophus \
    ros-$1-pcl-ros
  4. 报错:SyntaxError: Non-ASCII character '\xe5'

    原因:Python默认是以ASCII作为编码方式的,如果在自己的Python源码中包含了中文(或者其他非英语系的语言),此时即使你把自己编写的Python源文件以UTF-8格式保存了,但实际上,这依然是不行的。

    解决:在源代码的第一行加入:

    1
    # -*- coding: UTF-8 -*-
  5. python引入本地字体:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import matplotlib.font_manager as fm
    fm.fontManager.addfont('/usr/share/fonts/truetype/Times-New-Roman/times.ttf')
    fm.fontManager.addfont('/usr/share/fonts/truetype/simsun/simsun.ttc')

    plt.title('层次聚类树形图', fontname="simsun", fontsize=30)
    plt.xticks(fontname="Times New Roman", fontsize=26)

    colorbar = plt.colorbar()
    colorbar.ax.tick_params(labelsize=24) # 设置坐标轴标签的字体大小
    for l in colorbar.ax.yaxis.get_ticklabels():
    l.set_family('simsun')
  6. idea错误提示File was loaded in the wrong encoding: ‘UTF-8‘解决方法:

    1. 打开乱码文件,在软件右下角将当前页面的编码格式改为GB2312,弹出的提示消息中选择Reload;
    2. 在软件右下角将当前页面的编码格式改为utf-8,弹出的提示消息中选择Convert;
    3. 参考链接
  7. 如果你不想运行predict.py文件的main函数第136行以下的代码,你可以使用Python的return语句来提前结束函数的执行。你需要找到第136行的代码,并在其前面添加return语句。这将导致函数在执行到这一行时立即返回,不再执行后续的代码。

    在循环语句里,可以使用continue

  8. syntax error near unexpected token '$'{\r''

    字面意思上看是换行符出现问题,怀疑是Win下编辑过。

    1
    2
    3
    # 用vim -b 查看,发现每一行多了~M
    # 解决方法:
    sed -i 's/\r//g' xxx.sh
  9. 变量的定义和使用只在最近的{}内((主)函数、iffor等)适用。如果想要拓展变量的使用范围,可以在更外处的{}内定义变量(然后在别处赋值);或者,声明为全局变量。

  10. 在C++中,函数的默认参数值通常在函数声明中给出,也就是在.h头文件中(而不用在函数定义的*.cpp文件中再给出默认值)。这样,任何包含这个头文件的代码都可以看到这些默认值,并且可以选择是否提供自己的参数值。

  11. 一般来说,如果有 3 个或更多if-else分支,则应该考虑使用switch。如果有10个或更多条件分支,您应该考虑使用config变量或文件,并为该config编写特定的函数来进行映射。如果映射逻辑复杂但使用频繁,可以考虑创建专用的规则引擎或DSL来处理。

    if语句和switch语句的选择:多个等值判断选择switch,其它情况(区间范围等)选择if

  12. 在C++中,i++++i都会增加i的值,但它们的主要区别在于它们的返回值和执行顺序。

    i++是后置递增运算符。它首先返回i的当前值,然后再将i的值增加1。例如:

    1
    2
    int i = 5;
    int j = i++; // j现在是5,i现在是6

    ++i是前置递增运算符。它首先将i的值增加1,然后返回新的i值。例如:

    1
    2
    int i = 5;
    int j = ++i; // j现在是6,i现在也是6
  13. 在C++中,#include <filename>#include "filename"的主要区别在于编译器搜索头文件的方式。

    • #include <filename>:编译器在标准库路径中搜索头文件。这通常用于包含标准库的头文件,如<iostream><vector><mutex>等。
    • #include "filename":编译器首先在当前文件的目录中搜索头文件,如果在当前目录中找不到,编译器会在标准库路径中搜索。这通常用于包含用户自定义的头文件。
  14. variable的生命周期取决于它在哪里声明和定义。如果它在while循环外部定义,那么它将在包含该循环的函数或作用域结束时被销毁。如果它在while循环内部定义,那么它将在每次循环结束时被销毁,并在下一次循环开始时重新创建。

  15. 把函数的输入参数行想成是对输入参数的定义,把函数的调用行想成是对输入参数的赋值,函数内部就是变量赋值后的进一步操作。

  16. 变量l(包括函数的输入参数)被定义为const,这意味着它的值在初始化后不能被修改。这是一种良好的编程实践,可以防止在后续的代码中意外修改l的值。

    然而,如果你确定在后续的代码中不会修改l的值,那么将l定义为const并不是必须的。在这种情况下,将l定义为const主要是为了提高代码的可读性和可维护性,它可以让其他阅读你代码的人知道l的值在初始化后不应该被修改。

    总的来说,是否将l定义为const取决于你的编程风格和项目的编码规范。如果你的项目鼓励使用const来增强代码的可读性和可维护性,那么将l定义为const是一个好的选择。

  17. 命令行参数:

    1
    2
    3
    4
    import sys

    if __name__ == "__main__":
    npy_file = sys.argv[1] if len(sys.argv) > 1 else __file__

    sys.argv 是一个包含命令行参数的列表,其中 sys.argv[0] 是脚本的名称,sys.argv[1] 是第一个参数,sys.argv[2] 是第二个参数,以此类推。

  18. 初始化 ROS 工作空间:

    1
    alias initROS="mkdir -p catkin_ws/src && cd catkin_ws && catkin config --init --mkdirs --extend /opt/ros/melodic --merge-devel --cmake-args -DCMAKE_BUILD_TYPE=Release && cd src && catkin_init_workspace && cd .. && catkin build"
  19. 等等。

Windows

快捷键

快捷键 作用
虚拟桌面
Win+Ctrl+D 创建新的虚拟桌面,并切换到它
Win+Ctrl+<-/-> 切换桌面
Win+Tab 打开任务视图
窗口
Ctrl+N 新建当前窗口
Ctrl+Shift+N 新建文件夹
Win+ D 将所有打开的窗口最小化,并转到桌面,再次按下即刻恢复所有内容
Win+<-/-> 可以自动将应用窗口完全贴靠到屏幕两侧
Win+ ↑ 可以将应用窗口最大化
Win+ ↓ 可以将应用窗口最小化。若是当前窗口处于最大化状态,则按住 Win 键不放,连续按两下 ↓ 键即可将窗口最小化。

其它一些可参考Linux快捷键

其它:

  1. 将窗口移动到另一个虚拟桌面(不幸的是,Windows不包括直接用于在虚拟桌面之间移动窗口的键盘快捷键):Win+Tab打开任务视图后,使用鼠标拖动应用窗口到另一个虚拟桌面。

Powershell

  1. Powershell配置alias:

    1. 打开Powershell,执行echo $PROFILE命令,确定新建文件的名称和位置。

    2. 例如:在C:\Users\username\Documents\WindowsPowerShell文件夹下新建Microsoft.PowerShell_profile.ps1文件。

    3. 按格式输入命令:

      1
      2
      function 别名 { 需要替代的命令 }
      Set-alias 's' 'Select-Object'
    4. 以管理员身份打开Powershell 执行命令:

      1
      2
      Set-ExecutionPolicy RemoteSigned
      # `Set-ExecutionPolicy RemoteSigned` 是一个 Windows PowerShell 命令,用于设置 PowerShell 执行策略。执行策略用于控制是否允许在系统上运行脚本,以及允许哪些来源的脚本运行。
    5. 重启Powershell.

  2. Powershell 执行多条命令:

    1
    2
    3
    # 在powershell7上已经支持&&操作,如低于该版本请继续往下看
    (mkdir test) -and (cd test)
    mkdir test;cd test
  3. PowerShell命令自动补全:按Tab键。

    https://sspai.com/post/73019

    1. 在开始配置之前,请以管理身份运行 Install-Module -Name PSReadLine 将其更新到最新版本。

      安装路径:C:\Program Files\WindowsPowerShell\Modules\PSReadLine

    2. 利用 Set-PSReadlineKeyHandler 命令可以设置 PowerShell 中所有按键及按键组合的功能。默认情况下,Tab 键的补全是行内补全,我们可以将其调整为带菜单的补全:

      1
      Set-PSReadlineKeyHandler -Chord Tab -Function MenuComplete

      Tab 键此时会弹出所有的可能选项,上下左右按键可切换候选,Esc 键取消补全。

    3. 根据历史输入提供建议。重启PowerShell(以管理员身份),使用命令 Set-PSReadLineOption -PredictionSource History 打开,默认为行内补全,按键盘右箭头接受补全建议。(可选,不推荐)如果需要用列表形式补全的话,添加 -PredictionViewStyle ListView 参数即可。

  4. 将cmd/powershell中的打印信息输出保存为txt文本文件:

    1
    ping www.baidu.com >D:/log.txt

    保存界面上已经执行过的输出内容:点击powershell左上角的下三角形,或直接Ctrl+Shift+P打开命令面板,找到“导出文本”选项,确认保存位置。

  5. 打开网址:

    1
    2
    3
    4
    # starts with default browser and adds to open browser
    Start-Process -FilePath www.google.com
    # starts with a specific browser
    Start-Process -FilePath iexplore -ArgumentList www.google.com

小贴士

文件(夹)命名

命名规范

为了确保日常使用的方便与系统兼容性,个人电脑上的文件夹和文件命名应遵循一定的规范。以下是一些通用的指导原则和最佳实践:

  1. 简洁明了:命名应简单且直接表达文件内容或文件夹用途,避免过长或含糊不清的名称。
  2. 避免使用特殊字符:一般来说,文件名中不应包含如下字符:\/:*?"<>|。这些特殊字符在多数操作系统中有特定用途,可能导致错误或不被识别。
  3. 使用合适的文件扩展名:文件扩展名反映了文件的类型,如 .txt 表示文本文件,.jpg 表示JPEG图像文件。正确的扩展名有助于操作系统正确识别和打开文件。
  4. 利用日期和版本控制:在文件名中包含日期(如 2023-03-15)可以帮助管理文件版本。对于频繁更新的文档,可能还需要添加版本号(如 v1.0v2.0)。
  5. 避免空格:虽然大多数现代操作系统可以很好地处理文件名中的空格,但在某些情况下(如命令行操作或网络传输)可能会引起问题。可以使用下划线(_)或破折号(-)来代替空格。
  6. 区分大小写:虽然Windows系统的文件名不区分大小写,但Linux和macOS区分大小写。为了兼容性,最好在命名时注意大小写的一致性。
  7. 组织性和易读性:对于包含多个文件的文件夹,可以采用有组织的命名体系,如按功能、日期或项目分类。
  8. 国际化和本地化:如果你的文件可能会被不同语言的用户访问,避免使用特定语言的字符,尽量使用英文和国际通用的符号。

遵循这些基本原则可以帮助提升文件管理的效率和降低因命名不当引起的问题。如果有特定的组织或项目命名规则,也应优先遵守这些专业指导标准。

命名方法

整词用驼峰或下划线_命名,整词间用-分隔。

在个人电脑上命名文件夹和文件时,虽然没有像编程语言那样严格的命名规范,但仍可以借鉴一些编程中的命名方法来组织文件,使其更加有序和易于管理。以下是一些常见的命名方法,以及如何将它们应用于文件和文件夹的命名:

  1. 小驼峰式命名法 (lowerCamelCase):
    • 应用: 适用于文件名,尤其是在需要保持名称简洁且连贯的情况下。
    • 示例: personalBudget.xlsx, summerVacationPhotos.jpg
  2. 大驼峰式命名法 (UpperCamelCase):
    • 应用: 适合用于较重要的文件和项目文件夹,特别是在编程项目中。
    • 示例: ProjectReport2023.docx, TaxDocuments2023
  3. 下划线命名法 (snake_case):
    • 应用: 适用于需要强调易读性的情况,也有助于避免在使用某些操作系统和环境时出现问题。
    • 示例: final_report_2023.pdf, holiday_photos_2022
  4. 破折号命名法 (kebab-case):
    • 应用: 更常见于网页文件和资源,但也可以用于普通文件和文件夹。
    • 示例: end-of-year-summary-2023.pdf, project-budget-2023.xlsx
    • 注意: 文件系统如Windows和记录系统通常对破折号支持良好,但在某些命令行环境中可能会遇到解释上的问题。
  5. 日期前缀命名:
    • 应用: 对于需要按日期管理的文档或文件,如财务文件或定期报告。
    • 示例: 2023-01-30_MeetingNotes.docx, 2023-03-15_Invoice.pdf

选择哪种命名方法取决于个人偏好、所处的工作环境以及是否需要跨平台交互。例如,如果在一个环境中主要用Python编程,那么使用snake_case可能更自然。而如果是在处理法律或业务文档,使用UpperCamelCase或日期前缀可能更为合适。

无论选择哪种命名方法,重要的是保持一致性,这样可以减少寻找和管理文件的时间,也能使文件系统更加有序。

中文文本中夹用英文时,是否要留有空格

推荐留空,方便整词选择。如果不留空,选择时可能会选择夹杂中英文的整行而不是单个英文单词。

中文文本中夹用英文时,应根据所选用的中英文字体、字符间距以及排版的视觉效果决定英文词句与中文文字之间是否留有空格间距。如留空格,应保证体例的统一。——中华人民共和国新闻出版行业标准CY/T 154—2017号《中文出版物夹用英文的编辑规范》第8.1节

  • 但同时我更鼓励各类排版引擎自动在用户没有显式输入空格字符的情况下,也在中英文词汇间产生适当的间距。现在有一些已经做到了,比如 iOS 系统、微信、Microsoft 办公软件等。

浏览器搜索操作符

可以直接用此网址,不需要记命令,它自动组装为命令:

语法:

  • 整词搜索:双引号。示例:"关键词"
  • 逻辑与:空格。示例:关键词1 关键词2
  • 逻辑或:空格OR空格。示例:关键词1 OR 关键词2
  • 逻辑非:空格-(减号前面必须是空格,减号后面没有空格,要紧跟着需要排除的词)。示例:关键词 -排除词
  • 模糊搜索:*(通配符,匹配任意词或短语)。示例:国家* 匹夫*
  • 指定网站:关键词 site:网站地址。注意:“site:”后面跟的站点域名,不要带“http://”。site:和站点名之间,不要带空格。示例: 编程教程 site:blog.csdn.net
  • 排除指定网站:关键词 -site:网站地址。示例:编程教程 -site:blog.csdn.net

设置/重映射快捷键

  1. PowerToysKeyboard Manager工具。

  2. 添加快捷方式,右键快捷方式-属性-快捷键。参考链接

    解决快捷键反应延迟问题:

    • Win10:win+s搜索后台应用-打开后台应用,将“设置“一栏关闭即可。参考链接
    • Win11:按下Win键打开开始菜单-所有应用-找到“设置”-右键-更多-应用设置-后台组件权限:从不(默认为电源已优化(推荐))。参考链接

下载

  • Windows系统下也可以运行Linux系统下的*.sh脚本/命令。网上有解决办法,例如;或者把shell脚本语言转为Powershell脚本语言。

    • 在Powershell下使用Invoke-WebRequest命令下载文件很慢。网上有解决方法,例如
    • Powershell下可以使用wgetcurl等命令。
  • 使用IDM、迅雷等下载器。

    • IDM

      • 优点:(分块)下载速度快(比浏览器默认下载快);定时下载;(使用通配符)添加批量任务;运行站点抓取;资源嗅探

      • 缺点:收费;(大文件)分块下载后合并下载块慢(解决:设置单线程下载;下载的临时文件夹和“保存至”文件夹不要在同一块硬盘上;高配电脑(CPU和SSD))

        • 改成单线程的话,该文件下载速度会变慢。当然,如果你同时下载许多个文件,那么可以这么做,不影响。
        • 同盘剪切一定快于跨盘剪切,但同盘复制一般慢于跨盘复制。
    • 迅雷

      • 优点:定时下载;下载速度快
      • 缺点:国产软件……想要速度快需要开会员

其它

  1. Anaconda Prompt与Anaconda PowerShell Prompt与系统cmd与powershell的区别:

    1、anaconda prompt

    优点:Python 行、

    缺点:ls 不行、

    2、anaconda powershell prompt【完美】

    优点:Python 行、ls 行、

    缺点:无

    3、cmd

    优点:无

    缺点:Python不行;ls 不行、

    4、powershell

    优点:ls行

    缺点:Python不行

  2. 剪切板历史:Win+V

  3. 导入环境变量:搜索“环境变量”——新建。

软件

CLion

快捷键

自定义快捷键:设置-按键映射

快捷键 作用
搜索
Shift + Shift 全局搜索;搜索得更全,不光包括文件文本内容,还有文件夹名称,操作等
CTRL+SHIFT+F 搜索整个工程或指定目录 注:连续按两次Esc,搜索框就会消失;专注搜索文件文本内容
CTRL+SHIFT+R 全局搜索+替换
Ctrl+Shift+A 查找(想要进行的)操作
ALT+F1 导航,查找文件所在目录位置
CTRL+F 在当前窗口查找文本
CTRL+R 在当前窗口替换文本
Ctrl+E 最近的文件
Ctrl+Shift+E 最近的位置,支持键盘直接输入模糊搜索。
Alt + 1 打开/关闭项目树
Alt + Shift + 1 在项目树定位打开的文件的位置
Ctrl + Tab 跳转标签页
查看
Ctrl+鼠标左键 前往声明或用法
Ctrl+鼠标右键 高亮当前区域(Rainbow Brackets插件)
Ctrl+P 将文本光标置于方法调用的圆括号之间,按Ctrl+P可以显示有效形参的列表。
Alt+Q 显示上下文信息(在函数内部快速查看函数定义)
文本编辑
Shift+Home/End 选中行中鼠标前面(后面)的全部内容
Alt+M String Manipulation(插件,字符串编辑)
Ctrl+C/X 复制/剪切整行
Ctrl+Y 删除整行
Ctrl+Alt+Enter 在上一行插入空行
Ctrl+D 重复行或选区
Ctrl+Shift+上/下箭头 向上/下移动语句
Ctrl+Shift+U 切换大小写
Shift+Alt+U CamelCase(插件,一键转化变量为驼峰命名或下划线命名)
Ctrl+Shift+J 合并行
代码自动提示和补全 上下方向键选择,Enter插入
光标前/后移动一个整词 Ctrl+<-/->
连续选中 Shift+<-/->
整词选中 Ctrl+Shift+<-/->
移动定位
Ctrl+Shift+N 转到文件
Ctrl+E 最近的文件
CTRL+上/下箭头 代码向上/下滚动(类似鼠标滚动)
ATL+上/下箭头 上一个/下一个变量/函数(方法)
ALT+Shift+←/→; 鼠标侧面的两个前进和后退键 返回/前进上次光标所在的位置
CTRL+HOME/END 光标跳转到第一行或最后一行下
Alt+←/→ 上/下一个标签页
F4 跳转到源文件
Ctrl+G 跳转到行/列
结构 工具栏-导航
Ctrl+F12 文件大纲
Alt+Shift+H/右边栏-层次结构 查看引入/包含文件的文件有哪些
Ctrl+Alt+Shift+2 在资源管理器里打开文件所在的特定上级文件夹
Ctrl+NumPad-/NumPad+ 折叠/展开代码/文件夹
撤回
CTRL+Z 倒退
CTRL+SHIFT+Z 向前
缩进与注释
CTRL+ALT+I 自动缩进
CTRL+/ 注释// 添加或删除注释
CTRL+SHIFT+/ 注释/*...*/
在函数名称上面一行输入/*!(或者////***) 然后回车,即可自动生成注释。
Translation翻译
Ctrl+Shift+Y 光标在单词上或选中单词
Ctrl+Shift+O 打开翻译界面
其他
Ctrl+F4/ESC 关闭当前文件
按住 Alt 点击关闭标签页 关闭其他标签页
Ctrl+D 比较文件差异。按F4跳转到源文件。直接点击<<是在同行直接接受,Ctrl+点击是换行插入。
F7 转到下一个差异
Shift+F6 (批量)重命名

小技巧

查找用法

查找函数被调用的位置:

函数名-右键-查找用法(Alt+Shift+7)。分为读取值(变量被赋值/数组被push/pop的地方)和声明或预声明(变量被用到的地方)。

函数名-右键-转到-声明或用例(Ctrl+B)/Ctrl+鼠标左键点击。

“重命名”重构

您可以轻松重命名类、函数、变量和文件,同时在所有引用中实现自动纠正。为此,将文本光标放在所需符号上,然后按Shift+F6(重构|重命名)。开始输入新名称并在准备好后按 Enter。

用语言结构包围代码块

CLion提供了标准模板,用于将代码片段用各种基于源代码语言的结构包围起来。这包括了if...else条件语句、do...whilefor循环、折叠区域和其他结构。

  1. 选择所需的代码片段。
  2. 从主菜单中选择Code | Surround With,或按Ctrl+Alt+T(与打开终端的全局快捷键冲突,可以改为Ctrl+Alt+W)。
  3. 从列表中选择必要的包围语句。

创建带有补全功能的代码结构

您可以使用语句补全创建代码结构。开始输入方法声明、方法调用或语句,例如 iffor do-whiletry-catchswitch-casereturn。按 Ctrl+Shift+Enter可以将语句补全为语法正确的结构。

代码-生成

代码生成选项。使用此菜单,可以快速生成构造函数/析构函数、 getter/setter、各种运算符和实现/重写函数。

快捷键:Alt+Insert

格式化

参考链接

您可以重新格式化部分代码、整个文件、文件组、目录和模块。您还可以从重新格式化中排除部分代码或某些文件。

重新格式化代码片段:

  1. 在编辑器中,选择要重新格式化的代码片段。如果您不选择代码片段,CLion 将重新格式化整个文件。
  2. (可选)预览:按 Alt+Enter 并单击调整代码样式设置。
  3. 格式化:在主菜单中,转到代码 |重新格式化代码 或 按 Ctrl+Alt+L 。

重新格式化行缩进:

在某些情况下,“设置”对话框中“缩进检测”部分中的“检测并使用现有文件缩进进行编辑”选项 ( Ctrl+Alt+S ) |编辑|代码样式可以覆盖您的设置。在这种情况下,CLion 将显示一条通知。

  1. 在编辑器中,选择必要的代码片段并按 Ctrl+Alt+I 。
  2. 如果需要调整缩进设置,请在“设置”对话框 ( Ctrl+Alt+S ) 中,转到编辑器 | 缩进设置-代码风格,选择您要更改缩进的语言。
  3. 在“制表符和缩进”选项卡上,指定适当的缩进选项,然后单击“确定”。

重新格式化文件:

  1. 在编辑器中打开文件并按 Ctrl+Alt+Shift+L 或在项目工具窗口中右键单击该文件并选择重新格式化代码。
  2. 在打开的“重新格式化文件”对话框中,如果需要,请选择以下重新格式化选项:
    • 优化导入:如果您想要删除未使用的导入、添加缺失的导入或组织导入语句,请选择此选项。
    • 代码清理:选择此选项可运行代码清理检查。
    • 不保留换行符:根据代码样式设置重新格式化换行符。此选项会覆盖重新格式化时保留 |换行符设置。
  3. 单击运行。如果您想查看重新格式化期间对代码所做的确切更改,请使用本地历史记录功能。

在保存时自动格式化代码:设置-工具-保存时的操作:重新格式化代码

书签(快捷跳转)

  • 添加无名行书签:在编辑器中,将光标定位在一行代码上,然后按下 F11 键。或者,右键单击要添加书签的代码行旁边的装订线,然后选择“添加书签”。
  • 添加助记符行书签:在编辑器中,将光标定位在一行代码上,然后按下 Ctrl+F11 键。或者,右键单击要添加书签的代码行旁边的装订线,然后选择“添加助记符书签”。在打开的弹出窗口中,选择一个数字或字母作为此书签的标识符。或。直接按下Ctrl+Shift+Num直接添加。
  • 书签文件和文件夹:在项目工具窗口(Alt+1)中,右键单击要添加书签的项目,然后选择“书签” | “添加书签” (F11) 或 “添加助记符书签” (Ctrl+F11)。要为多个项目添加书签,请在工具窗口中选择它们,右键单击其中一个,然后选择“书签” | “添加书签” (F11)。对于助记符书签,选择一个数字或字母作为此书签的标识符。按 Enter 键或再次单击所选的字母或数字以保存书签。
  • 删除书签。再次按下F11
  • 重命名书签:在装订线上单击书签图标,然后提供新的描述。
  • 书签跳转。按住 Ctrl 键,然后按键盘上的数字助记符。或,按下 Shift+F11 键,或者从主菜单中选择“编辑” | “书签” | “显示行书签”。或,转到下一个或上一个书签:“编辑” | “书签” | “上/下一行书签”。
  • 所有的书签都分组在“书签”工具窗口中的列表中,您可以通过从主菜单选择“视图 | 工具窗口 | 书签”或按下 Alt+2 来打开它。您可以快速添加所有打开的文件的书签,并将这些书签添加到新列表中。

扩展代码选区(鼠标选择的区域)

要展开选区,请按Ctrl+W。每次按 Ctrl+W时,选区将扩展到代码的其他区域。例如,选区从一个方法名称扩展到调用此方法的表达式,然后扩展到整个语句,继而扩展到包含的块,等等。缩减选区:Ctrl+Shift+W。

导入缺失的头文件/函数声明

  • 配置自动导入。您可以在“设置 | 编辑器 | 通用 | 自动导入”中为C/C++和其他语言配置自动导入行为。
  • 对于提示找不到/未定义的变量/函数,选中,按下Alt+Enter
  • 在cpp文件中编写函数定义后,在函数名上按Alt+Enter组合键,然后选择“创建新函数/split function into declaration and definition”。这将在头文件中添加Class声明。
    1. 在CLion中打开源代码文件。
    2. 在代码中编写类的定义,并保存该文件。
    3. 右键单击源代码文件,选择“Refactor(重构)”,然后选择“Extract”。
    4. 在弹出的对话框中选择“Declaration”,然后选择“Extract”。
    5. 在新的头文件中,你可以看到类的声明已经被自动生成了。

从索引中排除

CLion为项目文件建立索引,以启用诸如搜索,导航,代码完成,代码生成和重构之类的功能。但是,您的项目可能包含不需要这些功能的文件,例如日志,二进制文件或导入的库。在这种情况下,为了减少索引时间,可以将文件标记为纯文本 ,将目录标记为排除或库。

临时文件

有时候您可能需要在项目上下文之外创建临时的笔记或者起草一些代码。而不必切换到不同的应用程序,您可以使用临时文件和临时缓冲区。

临时文件和缓冲区与特定项目无关。它们对于任何在特定IDE实例中打开的项目都是可用的。

创建临时文件:

  1. 从主菜单中选择 File | New | Scratch File,或者按下 CtrlAltShiftInsert。另外,可以在项目工具窗口中,右键点击任何区域,然后导航到 New | Scratch File。
  2. 选择临时文件的语言。相同类型的临时文件会自动编号,并添加到项目视图的 Scratches and Consoles 目录中。
  3. 另外,您可以根据当前编辑器中的选中内容创建一个新的临时文件。选择一些文本或代码,按下 Alt+Enter,然后选择 Create new scratch file from selection。CLion将尝试检测选中片段的语言,并使用适当的类型和扩展名。如果它无法检测出选中片段的语言,CLion将使用与原始文件相同的类型和扩展名创建文件。

临时文件和缓冲区的位置:默认情况下,CLion会将临时文件和缓冲区存储在 IDE配置目录 下的 scratches 目录中。它们可以在任何使用此配置目录的IDE和项目中使用。

1
2
3
4
5
6
# Windows:
%APPDATA%\JetBrains\<product><version> # C:\Users\JohnS\AppData\Roaming\JetBrains\CLion2023.2
# macOS
~/Library/Application Support/JetBrains/<product><version> # ~/Library/Application Support/JetBrains/CLion2023.2
# Linux
~/.config/JetBrains/<product><version> # ~/.config/JetBrains/CLion2023.2

将临时文件包含到您的项目中:如果一个临时文件变得足够大,以至于您希望在项目中使用它,可以将它移动到项目结构的所需目录中。

  1. 在编辑器中打开一个临时文件,或者在项目视图中的 Scratches and Consoles | Scratches 目录中选择它,按下 F6,然后选择项目中的目标目录。
  2. 在项目视图的 Scratches and Consoles | Scratches 目录中,将临时文件从源目录拖到项目中的目标目录。
  3. 在项目视图的 Scratches and Consoles | Scratches 目录中选择一个临时文件,按下 Ctrl+X,然后选择项目中的目标目录,按下 Ctrl+V。

其它

  1. 右键标签页-向下拆分/向右拆分:方便看大型内容。

Conda

在没有自行安装其他的conda环境的时候,默认会有一个base环境。

这里的*表示,这个是当前处于激活状态的环境。每次进入conda的时候,默认就是进入了base环境。

库都要安装在小环境中,不要安装在 base 里, 首先要用 conda 激活环境。

查询

1
2
3
4
5
6
7
8
9
conda --help  # 查询 conda 的命令
conda --version # 查看当前 conda 的版本
# 环境管理
# 查看所有的conda环境
conda env list # 或
conda info --envs
# 查看当前环境
conda info -e # 查看当前环境是哪个
conda info # 查看当前环境的完整信息

mamba

除了激活和退出环境仍需要 conda 命令,安转和搜索包都可以使用 mamba 替换 conda,以提高速度。

1
2
# 安装 mamba
conda install mamba -n base -c conda-forge

环境管理

1
2
3
4
5
6
conda create -n cpp_test_env  # 创建环境
# conda create -n cpp_test_env python=3.6
conda activate cpp_test_env # 激活环境,激活成功会出现一个小括号(环境名字,cpp_test_env)
conda deactivate # 退出环境,后面不需要加环境的名字
conda clean (参数) # 清空缓存
conda remove -n env_name --all # 删除环境,有一个参数

Windows:

1
2
3
4
5
6
# conda默认使用了hardlink,如果同时使用conda和pip的情况下,我个人建议加上 --copy 选项
conda create --copy -n envname python=3.9
# 打开新建环境下的lib文件夹的site.py文件(C:\Users\username\.conda\envs\conda_env\Lib\site.py), 找到USER_SITE、USER_BASE那两行, 默认是None, 修改后如下:
USER_SITE = r"C:\Users\username\.conda\envs\conda_env\Lib\site-packages"
USER_BASE = r"C:\Users\username\.conda\envs\conda_env\Scripts"
# 记得加上r抑制转义, 否则有可能因为非法转义报错, 导致进入不了虚拟环境。

可以通过配置auto_activate_base关闭自动进入conda基础环境:

1
conda config --set auto_activate_base false

如要开启,将其设为true就可以了:

1
conda config --set auto_activate_base true

库/包管理

清华源:https://pypi.tuna.tsinghua.edu.cn/simple

阿里源:https://mirrors.aliyun.com/pypi/simple/

腾讯源:http://mirrors.cloud.tencent.com/pypi/simple

豆瓣源:http://pypi.douban.com/simple/

  1. 安装包

    1. 在线安装

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      conda search qt  # 搜索可用版本
      # 你可以指定所安装软件包的版本号,如果不指定的话,就默认安装最新版本。
      conda install -y pkgs[==version] # 安装库,安装成功一般会出现三个 done 。-y 参数的作用是自动确认,忽略询问。
      # 通过调用软件的帮助文档来经常是否下载成功。如果失败, 重新下载即可。

      # 或
      # 在anaconda下用pip装包的原因:尽管在anaconda下我们可以很方便的使用conda install来安装我们需要的依赖,但是anaconda本身只提供部分包,远没有pip提供的包多,有时conda无法安装我们需要的包,我们需要用pip将其装到conda环境里。
      conda install pip # 进入环境后
      # 首先用下面命令查看我们此时用的pip为哪个环境
      # 如base环境的pip可能在/root/anaconda3/bin/pip,而其他conda环境的pip,可能在/root/anaconda3/envs/my_env/bin/pip
      which -a pip
      pip -V # pip --version
      # 配置
      pip -v config list
      pip install --upgrade pip setuptools wheel # 升级pip工具包
      pip install --upgrade pip # 更新 pip 至最新版
      pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple # 永久换源(清华源)
      # Writing to /home/xxx/.config/pip/pip.conf
      # global.index-url='https://pypi.tuna.tsinghua.edu.cn/simple'
      pip config unset global.index-url # 换回默认源
      # 搜索可用版本
      pip index versions [your python module name] # 或
      pip install package_name==
      pip install pkgs[==version] # pip安装的包,conda list结果中的build项目的Channel为pypi
      pip install -r environment.txt # 根据 txt 文件创建使用 pip 安装的环境
      pip3 --proxy 127.0.0.1:7890 install package_name
      pip install xxxx -i https://pypi.tuna.tsinghua.edu.cn/simple # 临时换源(清华源)
      pip install geometry_msgs --extra-index-url https://rospypi.github.io/simple # 临时添加额外的源
      pip install -e path/ # 执行path/目录下的setup.py文件
    2. 本地安装

      1
      2
      3
      # https://anaconda.org/这个是Anaconda包下载的地方,利用搜索框进行搜索,然后下载。
      conda install --use-local package.tar.bz2 # 安装本地软件包。
      pip install path/package_name.whl
  2. 查找包

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 查找软件常用的链接(我们认知的软件名和conda给的名字不一样)
    # https://bioconda.github.io/
    # https://anaconda.org/search
    conda search 软件名 # 查找库

    # 查看环境中所装的包
    conda list # 默认当前环境
    conda list –n cpp_test_env # 查看指定环境
    conda list fast* # 比如很早就安装某个软件,如果只想起四个字母,用通配符的去查找
    pip show packagename
  3. 更新与卸载包

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 更新
    conda update name # 更新库
    pip install --upgrade 包名称==版本号
    pip install package_name== # 会自动卸载旧版本

    # 卸载
    # --force 确保只卸载该库,而不同时卸载该库的其他库
    conda uninstall pytorch --force # 卸载使用 conda 安装的库
    conda remove name # 与上命令等同
    pip uninstall torch # 卸载使用 pip 安装的库

环境移植

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 方法一:克隆环境
conda create -n 新环境名 --clone 旧环境名 # 克隆环境

# 方法二:导出环境
conda env export > environment.yml # 导出使用 conda 安装的环境,在当前目录下生成一个名为 enviroment.yml 的文件
conda env create -f environment.yml # 根据 yml 文件创建使用 conda 安装的环境
# 在用 conda 的时候发现有些module还是未安装,上网找了下原因,原来以上只会导出conda命令直接安装的包,而我的包大多是用pip安装在Anaconda的lib和site-package里了。因此还要用导出pip的方法:
pip freeze > environment.txt # 导出使用 pip 安装的环境,在当前目录下生成一个名为 environment.txt 的文件
pip install -r environment.txt # 根据 txt 文件创建使用 pip 安装的环境

# 方法三:利用conda pack进行环境拷贝
# conda-pack包安装
# 利用conda进行conda-pack包安装
conda install -c conda-forge conda-pack
# 利用pip进行conda-pack包安装
pip install conda-pack

# 环境打包
# 打包环境,生成environment.tar.gz
conda pack -n enviroment
# 打包环境,生成defined_name.tar.gz
conda pack -n my_env -o defined_name.tar.gz
# 打包环境,使生成的environment.tar.gz置于[path]下
conda pack -p [path]

# 环境移植
# 首先要在conda安装目录下的envs文件夹下新建一个文件夹,这个文件夹的名称就是环境的名称,即在[path of conda]/envs/下
# 定位到conda下的envs文件夹下
cd envs
# 新建用于环境的
mkdir enviroment
# 将打包的环境重新恢复
tar -xzf enviromen.tar.gz -C enviroment

CTEX

  1. 创建通栏:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    \onecolumn
    % 通栏图片/表格
    \begin{multicols}{2}
    % 继续双栏内容
    \end{multicols}
    \onecolumn
    % 通栏图片/表格
    \begin{multicols}{2}
    % 继续双栏内容
    \end{multicols}

Git

Windows下的Git Bash终端快捷键:

快捷键 作用
Ctrl+insert 复制
Shift+insert 粘贴

GitHub

  1. star的原则:

    如果项目完善已停止更新或目前没有使用,就没有star的必要,来“污染”star库。star主要用于及时获取更新消息和收藏常用项目。

    1. 项目仍在持续更新。
    2. 感兴趣(但还没有深入研究)的项目(更推荐收藏在浏览器收藏夹中,还能做备注)。
  2. 默认watch:Participating and @mentions

  3. 根据Tag找存储库的历史版本:

    1
    2
    3
    GIT_REPOSITORY  https://github.com/jbeder/yaml-cpp
    GIT_TAG 11607eb5bf1258641d80f7051e7cf09e317b4746
    https://github.com/jbeder/yaml-cpp/tree/11607eb5bf1258641d80f7051e7cf09e317b4746
  4. 在GitHub上快速找到自己评论过或者发布过的问题:

    1
    2
    3
    4
    5
    6
    # 搜索框
    commenter:username
    is:issue commenter:username
    is:issue involves:username
    is:issue is:open # is:issue is:closed
    # 要查看最近的活动,请从Sort下拉列表中选择Recently updated
  5. GitHub首页只会推荐有最新releasestar项目?如果想跟进项目的commit,可以在Your stars界面筛选Sort by: Recently active

  6. 每日/周/月排行:点击GitHub首页左上角三道杠-Explore-Trending

  7. 搜索:

    1
    2
    java stars:>1000  # 目标语言是java且star数大于1k
    dvs_msgs language:Python # 指定编程语言

Google Colab

  1. Google上传和解压:

    选中谷歌云端硬盘里的文件,右键-共享-共享-设置访问权限:知道链接的任何人。共享-复制链接:

    1
    2
    3
    4
    5
    6
    # https://drive.google.com/file/d/xxx/view?usp=drive_link
    !gdown --id 'xxx' --output file.zip
    !unzip file.zip -d /content/file
    %cd /content/file
    !pwd
    !ls
  2. 查看硬件信息:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    print("============查看GPU信息================")
    # 查看GPU信息。注意提前把代码执行程序改为GPU。
    !/opt/bin/nvidia-smi
    print("==============查看pytorch版本==============")
    # 查看pytorch版本
    import torch
    print(torch.__version__)
    print("============查看虚拟机硬盘容量================")
    # 查看虚拟机硬盘容量
    !df -h
    !df -lh
    print("============查看cpu配置================")
    # 查看cpu配置
    !cat /proc/cpuinfo
    print("=============查看内存容量===============")
    # 查看内存容量
    !cat /proc/meminfo

Mermaid

GitHub仓库

官网

中文网

Typora支持图表绘制

通用语法

流程图的方向定义:

  • TB - 从上到下
  • TD - 自上而下/与自上而下相同
  • BT - 从下到上
  • RL - 右到左
  • LR - 左到右

流程图

简单示例:

flowchart TD
    A[Enter Chart Definition] --> |Text| B(Preview)
    B --- C{decide}
    C -.-> D([Keep])
    C ==> E[Edit Definition]
    E  <--> B
    D --> F[Save Image and Code]
    F --> B
    
    markdown["`This **is** _Markdown_`"]
    newLines["Line1
    Line 2
    Line 3"]
    markdown --> newLines

Microsoft Edge

Microsoft Edge 中的键盘快捷方式

快捷键 作用
窗口
Ctrl+N 新建窗口
Ctrl+Shift+N 新建无痕窗口
标签页
Ctrl+W 关闭当前标签页
Ctrl+Shift+W 关闭所有标签页
Ctrl+Shift+A 查看最近关闭的标签页
Ctrl + Shift + K 复制当前标签页
Ctrl + Tab 切换到下一个标签页
Ctrl + Shift + Tab 切换到上一个标签页
Ctrl + 1, 2, ... 8 切换到特定标签页
Ctrl + 9 切换到最后一个标签页
搜索
Alt + D/Ctrl + L/F4 选择地址栏中的 URL 以进行编辑
Ctrl + Shift + L 粘贴并搜索或粘贴并访问(不一定非得是URL)
Ctrl + E/Ctrl + K 在地址栏中打开搜索查询
Ctrl+Shift+E 在边栏中搜索
其它
F5 重新加载当前标签页
Ctrl+F/F3 查找当前标签页

Okular

快捷键 作用
F6 注释
Ctrl+4 文本选择工具
鼠标双击 打开注释/持续选择工具

TeXstudio

快捷键 作用
F5 编译并预览
F6 编译
Ctrl + F 可对源码或者PDF查看器的内容进行搜索定位
Ctrl + 鼠标单击 定位Latex源码和PDF的对应位置
Ctrl + T 注释选定代码
Ctrl + U 取消选定代码注释

其他配置:

  1. 选项-设置-构建:若写中文论文,则需修改默认编译器为XelaTeX;若为英文,则用PdfLaTex。

Typora

Markdown语法

https://support.typoraio.cn/zh/Markdown-Reference/

语法高亮

  • Shell: console, shell
  • Bash: bash, sh, zsh
  • Powershell: powershell, ps
  • Dos: dos, bat, cmd
  • language: js、javascript、java、python、c、c#、c++
  • html、htmlbars、yaml、xml、json
  • typescript、sql、nginx
1
2
3
4
sudo apt upgrade  # 安装可用的软件包更新
`高亮`
```高亮```
#! 执行

脚注

1
2
3
您可以像这样创建脚注[^footnote].

[^footnote]: Here is the *text* of the **footnote**.

快捷键

快捷键 作用
Ctrl+T 插入表格
Ctrl+Enter 表格插入行
Ctrl+Shift+Backspace 删除行
Ctrl+Shift+L 显示/隐藏边栏
Ctrl+Shift+1 显示/隐藏大纲
Ctrl+/ 源代码模式
Ctrl+K 复制链接,在Typora中选中要建立超链接的文字,按下Ctrl+K,Typora会自动读取剪切板来创建超链接。
Ctrl+Shift+` 生成代码格式,也就是使用````进行包围
Ctrl+Y 重做(与Ctrl+Z撤销相反)
Ctrl+Shift+F 当前目录下的文件全局搜索
Ctrl+L 全选行

小贴士

  1. 有序列表回车后按再按回车:取消缩进,回到行首,取消继续编号;按 TAB,新建下级列表;按Backspace ,取消继续编号,可在保持缩进的情况下输入内容。
  2. 在列表中第一次回车,取消当前列表,第二次回车,创建上级列表(没有上级列表就是正文的回车)。

vim

参考链接

vim键盘图

vi/vim的使用

1
vim runoob.txt  # 开始使用vim编辑文件

基本上 vi/vim 共分为三种模式,命令模式(Command Mode)输入模式(Insert Mode)底线命令行模式(Command-Line Mode)

命令模式

用户刚刚启动 vi/vim,便进入了命令模式。

此状态下敲击键盘动作会被 Vim 识别为命令,而非输入字符,比如我们此时按下 i,并不会输入一个字符,i 被当作了一个命令。

以下是普通模式常用的几个命令:

  • i:切换到输入模式,在光标当前位置开始输入文本。
  • ::切换到底线命令行模式,以在最底一行输入命令。
  • x:删除当前光标所在处的字符。
  • a:进入插入模式,在光标下一个位置开始输入文本。
  • o:在当前行的下方插入一个新行,并进入插入模式。
  • O:在当前行的上方插入一个新行,并进入插入模式。
  • dd:剪切当前行。
  • yy:复制当前行。
  • p(小写):粘贴剪贴板内容到光标下方。
  • P(大写):粘贴剪贴板内容到光标上方。
  • u:撤销上一次操作。
  • Ctrl + r:重做上一次撤销的操作。

若想要编辑文本,只需要启动 Vim,进入了命令模式,按下 i 切换到输入模式即可。

在其它模式下,可以随时按ESC键回到命令模式

命令模式只有一些最基本的命令,因此仍要依靠底线命令行模式输入更多命令。

输入模式

在命令模式下按下 i 就进入了输入模式,使用 Esc 键可以返回到普通模式。

在输入模式中,可以使用以下按键:

  • 字符按键以及Shift组合:输入字符
  • ENTER:回车键,换行
  • BACK SPACE:退格键,删除光标前一个字符
  • DEL:删除键,删除光标后一个字符
  • 方向键 ↑↓←→:在文本中移动光标
  • HOME/END:移动光标到行首/行尾
  • Page Up/Page Down:上/下翻页
  • Insert:切换光标为输入/替换模式,光标将变成竖线/下划线
  • ESC:退出输入模式,切换回命令模式

底线命令行模式

在命令模式下按下 :(英文冒号)就进入了底线命令模式。

底线命令模式可以输入单个或多个字符的命令,可用的命令非常多。

在底线命令模式中,基本的命令有:

  • :w:保存文件但不退出。
  • :q:退出 Vim 编辑器。
  • :wq:保存文件并退出 Vim 编辑器。
  • :q!:强制退出Vim编辑器,不保存修改。
  • :w!:若文件属性为【只读】,强制写入该档案
  • :wq!:强制保存后退出。
  • :e!:将档案还原到最原始状态!

ESC 键可随时退出底线命令行模式,切换回命令模式

vi/vim按键说明

vi/vim按键说明

VSCode

快捷键

官方文档

(当快捷键冲突时,)您可以通过导航到文件 -> 首选项 -> 键盘快捷键(Ctrl+K, Ctrl+S)并在搜索栏中搜索“导航”来更改首选键绑定。

快捷键 作用 自定义
一般
打开设置 Ctrl+, Ctrl+Alt+S
控制台终端显示与隐藏 ctrl + ~
显示所有符号 Ctrl + T
全局搜索 Ctrl+Shift+F
查找 Ctrl + F
替换 Ctrl + H
最近打开的项目(文件夹) Ctrl+P
通过文件名查找文件(快速打开最近的文件) Ctrl+E
打开命令面板(通过搜索界面访问 VS Code 中存在的所有可用命令、快捷方式和功能) Ctrl + Shift + P
跳转标签页 Ctrl + Tab
关闭文件 Ctrl+W Ctrl+F4
关闭所有文件 Ctrl + K, W
显示/隐藏侧边栏 Ctrl+B Alt+1
显示/隐藏终端 Ctrl+`
打开文件所在文件夹 Ctrl + Alt + R Alt + F1,when editorFocus
移动
向上/向下滚动行 Ctrl+↑ / ↓
快速滚动 滚动时按住 Alt 键可以以 5 倍速度滚动;此外,您还可以使用编辑器:快速滚动灵敏度 ( editor.fastScrollSensitivity ) 设置更改滚动倍数。
快速回到顶部 ctrl + home
快速回到底部 ctrl + end
前进(编辑位置) Ctrl+Shift+- Alt + Shift + ->
后退(编辑位置) Ctrl+Alt+- Alt + Shift + <-
转到行/列 Ctrl+G
注释与缩进
单行注释 [ctrl+k,ctrl+c] 或 ctrl+/
取消单行注释 [ctrl+k,ctrl+u] (按下ctrl不放,再按k + u)
多行注释 [alt+shift+A]
行增加缩进 ctrl + [
行减少缩进 ctrl + ]
多行注释 /**
文本编辑
向上/下复制行 Ctrl+Shift+Alt+↑ / ↓ Ctrl+D
向上/下移动行 Alt + ↑ / ↓ Ctrl+Shift+↑ / ↓
选择当前行 Ctrl+L
删除整行 Ctrl + Shift + K Ctrl+Y
缩小/扩大选区 Shift+Alt+<-/-> Ctrl+(Shift)+W
代码格式化 当前选择的源代码: Ctrl+K Ctrl+F;整个文档格式:Ctrl+Shift+I 当前选择的源代码: Ctrl+Alt+L
重命名符号 选择一个符号,然后键入 F2
光标前/后移动一个整词 Ctrl+<-/->
连续选中 Shift+<-/->
整词选中 Ctrl+Shift+<-/->
用语言结构包围代码块 (插件:Surround)Ctrl+Shift+T Ctrl+Alt+W
重命名符号/文件(夹) F2
选择特定区域 第一个位置鼠标单击,第二个位置按住Shift再单击
查看
查看和跳转符号 Ctrl+Shift+O
跳转到函数开头 Ctrl+Shift+O,后直接按Enter
跳转到函数结尾 Ctrl+Shift+O,后按↓,后按Enter
查看用法 Ctrl+Shift+F10
转到定义 选择一个符号,然后键入 F12;Ctrl+单击;Shift+F12,小窗浏览定义
查找所有引用 Alt+Shift+F12 Alt+Shift+7
差异跳转 F7 和 Shift+F7
将文件与剪贴板进行比较 Ctrl+K C
转到括号(在所属最近的成对括号间来回跳转) Ctrl+Shift+\
全屏 F11 Shift+F11
编辑器
打开上一个编辑器 Ctrl+PageUp
打开下一个编辑器 Ctrl+PageDown
打开组中最后一个编辑器 Ctrl+9
书签
书签:插入/删除 Ctrl+Alt+K F11
书签:跳至上一个 Ctrl+Alt+J
书签:跳至下一个 Ctrl+Alt+L Ctrl+Alt+K
书签:将选择展开到上一个 Shift+Alt+J
书签:将选择展开到下一个 Shift+Alt+L
书签:收缩选择 Shift+Alt+K
其它
CMake: 运行但不调试 Ctrl+F5
更改颜色主题 Ctrl+K Ctrl+T
聊天,在侧边栏打开聊天 Alt+3

小技巧

新建文件(夹)

  • 鼠标双击左侧“文件夹”空白处,后输入:

    1
    2
    3
    test.py  # 新建文件
    test/ # 新建文件夹
    test/test.py # 新建文件夹内的文件
  • 双击标签栏

  • Ctrl + N 键盘快捷键

  • 命令面板(Ctrl+Shift+P) - Create: New File

  • 单击文件资源管理器窗格中的New File...图标按钮

  • 文件 > 新建文件

用户代码片段/实时模板

左下角设置-用户代码片段

VS Code的代码片段填充功能默认使用的是Tab键,而不是空格键或回车键。并且,这个行为是不能被修改的。当你在一个代码片段中,你可以使用Tab键在不同的位置(被${1}, ${2}等标记的位置)之间跳转。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
{
// Place your 全局 snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
// Placeholders with the same ids are connected.
// Example:
// "Print to console": {
// "scope": "javascript,typescript",
// "prefix": "log",
// "body": [
// "console.log('$1');",
// "$2"
// ],
// "description": "Log output to console"
// }
"To //": {
"prefix": "、、",
"body": [
"// "
],
"description": "中文注释符号转英文"
},
"TODO-LLL25655-//": {
"prefix": "// todo",
"body": [
"// TODO-LLL25655: "
],
"description": "TODO-LLL25655"
},
"NOTE-LLL25655-//": {
"prefix": "// note",
"body": [
"// NOTE-LLL25655: "
],
"description": "NOTE-LLL25655"
},
"BUG-LLL25655-//": {
"prefix": "// bug",
"body": [
"// BUG-LLL25655: "
],
"description": "BUG-LLL25655"
},
"DEBUG-LLL25655-//": {
"prefix": "// debug",
"body": [
"// DEBUG-LLL25655: "
],
"description": "DEBUG-LLL25655"
},
"XXX-LLL25655-//": {
"prefix": "// xxx",
"body": [
"// XXX-LLL25655: "
],
"description": "XXX-LLL25655"
},
"UNDO-LLL25655-//": {
"prefix": "// undo",
"body": [
"// UNDO-LLL25655: "
],
"description": "UNDO-LLL25655"
},
"FIXME-LLL25655-//": {
"prefix": "// fixme",
"body": [
"// FIXME-LLL25655: "
],
"description": "FIXME-LLL25655"
},
"HACK-LLL25655-//": {
"prefix": "// hack",
"body": [
"// HACK-LLL25655: "
],
"description": "HACK-LLL25655"
},
"TODO-LLL25655-#": {
"prefix": "# todo",
"body": [
"# TODO-LLL25655: "
],
"description": "TODO-LLL25655"
},
"NOTE-LLL25655-#": {
"prefix": "# note",
"body": [
"# NOTE-LLL25655: "
],
"description": "NOTE-LLL25655"
},
"BUG-LLL25655-#": {
"prefix": "# bug",
"body": [
"# BUG-LLL25655: "
],
"description": "BUG-LLL25655"
},
"DEBUG-LLL25655-#": {
"prefix": "# debug",
"body": [# FIXME-LLL25655:
"# DEBUG-LLL25655: "
],
"description": "DEBUG-LLL25655"
},
"XXX-LLL25655-#": {
"prefix": "# xxx",
"body": [
"# XXX-LLL25655: "
],
"description": "XXX-LLL25655"
},
"UNDO-LLL25655-#": {
"prefix": "# undo",
"body": [
"# UNDO-LLL25655: "
],
"description": "UNDO-LLL25655"
},
"FIXME-LLL25655-#": {
"prefix": "# fixme",
"body": [
"# FIXME-LLL25655: "
],
"description": "FIXME-LLL25655"
},
"HACK-LLL25655-#": {
"prefix": "# hack",
"body": [
"# HACK-LLL25655: "
],
"description": "HACK-LLL25655"
}
}

生成函数注释

  • 函数注释模板插件:

    • C++: Doxygen Documentation Generator

      • 设置:C_Cpp › Doxygen: Generated Style
    • Python: autoDocstring - Python Docstring Generator

  • GitHub Copilot: 选中函数,Ctrl+I打开内联聊天,输入/doc in Chinese

创建(函数)声明/定义

鼠标选中,右键“创建声明/定义”

改变大小写

change-case:快速更改当前选择或当前单词的大小写(camelCase、CONSTANT_CASE、snake_case 等)

选中单词,按下Ctrl+Shift+P搜索Change Case Commands...

当然,可以按Ctrl+K S搜索Change Case为特定命令设置快捷键。

多代码库编码的工作空间

10 amazing VS Code tips and tricks for rapid coding

想象一下必须在 3 个打开的 VS Code 窗口中来回切换的痛苦;到处打开终端,在错误的代码库中搜索错误的文件,将 Alt + Tab 序列与其他打开的应用程序混合在一起,以及每次切换应用程序时都会造成的精神混乱和延迟。

每个文件夹都是 VS Code 的一个工作区,因此您可以使用文件 > 将文件夹添加到工作区...轻松添加更多文件夹。

一切完成后,您将拥有所需的所有文件夹,并且可以在“文件资源管理器”窗格中轻松访问其中的文件。

当您使用 Ctrl + P 或 Ctrl + Shift + F 搜索文件时,它将应用于所有文件夹中的每个文件。

您还可以使用任何文件夹作为工作目录快速创建新终端。

Zotero

快捷键 作用
zotero-reference
单击蓝色区域 复制参考文献信息,连同标识符一起复制,如DOI。
双击顶部XX条参考文献文字 复制当前所有参考文献到剪贴板
长按蓝色区域 编辑参考文献信息。建议中文参考文献使用编辑功能以精简条目,提高导入成功率。
Ctrl+单击蓝色区域 用系统浏览器打开文献URL,偶尔会查询文献地址消耗一定的时间。
一般
ctrl+鼠标滚轮 放大/缩小
ctrl+鼠标左键在原文/译文之间自由切换 翻译

主阅读界面若有跳转链接如Fig 4,点击后会在分割界面(横向/竖向)跳转,主阅读界面无跳转,避免点击前进后退。可满足看图,公式,表格的需求。但只针对有跳转链接的PDF。

其它

3-2-1备份规则

3-2-1 备份策略

3-2-1 备份规则是一种简单、有效的策略,可确保数据安全。它建议您将数据的三份副本保存在两个不同的介质上,其中一份副本保存在异地。让我们来分解一下:

  • 数据的三份副本:您的三份副本包括原始数据或生产数据以及另外两份副本。
  • 两种不同的介质上:您应该将数据存储在两种不同形式的介质上。这在今天意味着与 2000 年代末有所不同。我稍后会详细讨论这一点。
  • 一份异地副本:您应该在异地远程位置保存一份数据副本,最好距离其他两份副本几英里远。

如果您想保护您的个人信息、照片、工作文件或其他重要数据,3-2-1 备份策略是您的最佳选择。它可以帮助您避免出现容易受到人为错误、硬盘驱动器崩溃、盗窃、自然灾害或勒索软件影响的单点故障。``


常用的命令及快捷键
http://zeyulong.com/posts/a8aab215/
作者
龙泽雨
发布于
2024年3月5日
许可协议