C++ 环境准备

sudo apt-get install g++ 安装编译器。
sudo apt-get install gdb 安装调试器。
在 vscode 中安装拓展

编译指令

简单:”g++ 选项 源文件名”,例如 g++ -o test main.cpp
高级:”g++ 选项 源文件名 输出选项 链接库”,例如 g++ -std=c++17 -Wall -O2 -I./include main.cpp -o my_app -L./lib -lpthread

  1. 编译选项
    -std=c++17 指定 C++ 标准。
    -I./include 指定头文件搜索路径。
    -g 调试, Wall 警告。
    -On 编译、链接时优化,提高可执行程序运行速度,减慢编译速度,默认 -O0, 推荐 -O2
    -c 只编译不链接,常用于把源文件编译成静态库或动态库。

  2. 输出选项
    -o 文件名 指定输出文件名。

  3. 链接库
    -L./libs 指定库文件搜索路径。
    -l库文件名 链接库,若库文件名为 libpthread.so 则命令为 -lpthread

静态库和动态库

若动态库和静态库同时存在,编译器将优先使用动态库。

概念与特点

静态库:程序在编译时把库文件的二进制代码链接到目标程序中,这种方式称为静态链接。
如果多个程序中用到了同一静态库中的函数或类,就会存在多份拷贝

  • 静态库的链接在编译时期完成,执行时代码加载速度快。
  • 目标程序的可执行文件较大,浪费空间。
  • 程序的更新和发布不方便,一个静态库更新后,所有使用它的程序都需要重新编译。

动态库:程序在运行时把库文件的二进制代码载入。
如果多个进程中用到了同一动态库中的函数或类,内存中只会存在一份,避免了空间浪费问题

  • 可实现进程间的代码共享,因此动态库也称为共享库。
  • 程序升级较简单,无需重新编译程序,只需更新动态库即可。
  • 需要提前设置 LD_LIBRARY_PATH 环境变量。

制作

  1. 制作静态库
    1.”g++ -c 源代码文件清单”,如 g++ -c source1.cpp source2.cpp source3.cpp
    2.”ar rcs lib库名.a 目标文件清单”,如 ar rcs libmylib.a source1.o source2.o source3.o

  2. 制作动态库
    “g++ -fPIC -shared 源代码文件清单 -o lib库名.so”,如 g++ -fPIC -shared math.cpp -o libmymath.so

Makefile 与 CMake

Makefile

  1. 在目录下创建文件 “Makefile” ,编写命令内容。开头需要使用 Tab 缩进,而非空格
  2. 运行命令 make 即可编译链接出可执行程序,使用 “./程序名” 运行。
    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
    # # v1
    # test: main.cpp printhello.cpp factorial.cpp
    # g++ -o test main.cpp printhello.cpp factorial.cpp

    # # v2
    # CXX = g++
    # TARGET = test
    # OBJ = main.o printhello.o factorial.o
    # # make 时执行 g++, 先找 TARGET , TARGET 不存在找 OBJ, OBJ 不存在,编译三个 .cpp 文件生成 .o 文件
    # # 然后再链接 OBJ 文件,生成可执行文件 hello
    # $(TARGET): $(OBJ)
    # $(CXX) -o $(TARGET) $(OBJ)
    # # 编译 OBJ
    # main.o: main.cpp
    # $(CXX) -c main.cpp
    # printhello.o: printhello.cpp
    # $(CXX) -c printhello.cpp
    # factorial.o: factorial.cpp
    # $(CXX) -c factorial.cpp

    # v3
    CXX = g++
    TARGET = hello
    OBJ = main.o printhello.o factorial.o
    # 编译选项,显示所有的warning
    CXXFLAGS = -c -Wall
    # $@表示的就是冒号前面的TARGET,$^表示的是冒号后OBJ的全部.o依赖文件
    $(TARGET): $(OBJ)
    $(CXX) -o $@ $^
    # $<表示指向%.cpp依赖的第一个,但是这里依赖只有一个
    # $@表示指向%.o
    %.o: %.cpp
    $(CXX) $(CXXFLAGS) $< -o $@
    # 为了防止文件夹中存在一个文件叫clean
    .PHONY: clean
    # -f表示强制删除,此处表示删除所有的.o文件和TARGET文件
    clean:
    rm -f *.o $(TARGET)

CMake

  1. 在项目根目录下创建 .txt文件 CMakeLists.txt ,编写内容。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    cmake_minimum_required(VERSION 3.10)

    project(test)

    set(CMAKE_CXX_STANDARD 20) # 指定 C++ 标准

    add_executable(test main.cpp factorial.cpp printhello.cpp)

    ## 自动查找当前目录下所有的源文件,并将列表赋值给变量 SRC_LIST
    #aux_source_directory(. SRC_LIST)

    ## 使用变量 SRC_LIST 编译程序,不用手写 main.cpp factorial.cpp ...
    #add_executable(test ${SRC_LIST})
  2. 创建 “build” 文件夹 mkdir build ,进入该目录 cd build
  3. 运行命令 cmake .. ,执行根目录的内容,在当前目录生成 “Makefile”。
  4. 运行命令 make 即可编译链接出可执行程序,使用 “./程序名” 运行。

GDB 调试

待写

Linux 的系统错误

strerror() 函数在 <cstring> 中,用于获取错误代码对应的详细信息。
错误原因保存在全局变量 errno 中,值只有在库函数调用发生错误时才会被设置,当库函数调用成功时,errno 的值不会被修改,不会主动的置为0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream> 
#include <cstring> // strerror()函数需要的头文件。
#include <cerrno> // errno 全局变量的头文件。
#include <sys/stat.h> // mkdir()函数需要的头文件。
using namespace std;
int main() {
int iret=mkdir("/tmp/aaa/bb/cc/dd",0755);
if (iret!=0) {
cout << "iret=" << iret << endl;
cout << errno << ":" << strerror(errno) << endl;
perror("调用 mkdir(/tmp/aaa/bb/cc/dd)失败");
}
iret=mkdir("/tmp/dd",0755);
if (ireet!=0) {
cout << "iret=" << iret << endl;
cout << errno << ":" << strerror(errno) << endl;
perror("调用 mkdir(/tmp/dd)失败");
}
}

Linux 的信号

Linux 的文件 IO

<unistd.h>
open() 首个文件返回值为3,012已被文件描述符占用, close() 关闭,成功返回0失败-1。
int fd = open("a.txt", ...); 等于把文件描述符分配给 fd

  1. O_RDONLY : 只读的方式打开
  2. O_WRONLY :只写的方式打开
  3. O_RDWR : 读写的方式打开
  4. O_CREATE : 如果文件不存在,就创建文件
  5. O_APPEND : 以追加的方式打开文件
    read() 读,成功返回实际读到的字节数,读到结束返回0,失败返回-1
    write() 写,成功返回写入字节数,失败返回-1
    lseek() 控制指针位置。

其他

grep 逐行扫描,并匹配的行打印出来
| 管道,ps -ef | grep nginx 查找并列出系统当前运行的 nginx 进程

Linux 的时间操作

Linux 的目录操作

access() 函数用于判断当前用户对目录或文件的存取权限。

stat 结构体

struct stat 结构体用于存放目录或文件的详细信息,成员变量比较多,重点关注 st_mode, st_sizest_mtime 成员。
注意:
st_mtime 是一个整数表示的时间,需要程序员自己写代码转换格式。
st_mode 成员的取值很多,用以下两个宏来判断:
S_ISREG(st_mode) 是否为普通文件,如果是,返回真。
S_ISDIR(st_mode) 是否为目录,如果是,返回真。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct stat 
{
dev_t st_dev; // 文件的设备编号。
ino_t st_ino; // 文件的i-node。
mode_t st_mode; // 文件的类型和存取的权限。
nlink_t st_nlink; // 连到该文件的硬连接数目,刚建立的文件值为1。
uid_t st_uid; // 文件所有者的用户识别码。
gid_t st_gid; // 文件所有者的组识别码。
dev_t st_rdev; // 若此文件为设备文件,则为其设备编号。
off_t st_size; // 文件的大小,以字节计算。
size_t st_blksize; // I/O 文件系统的I/O 缓冲区大小。
size_t st_blocks; // 占用文件区块的个数。
time_t st_atime; // 文件最近一次被存取或被执行的时间,
// 在用mknod、 utime、read、write 与tructate 时改变。
time_t st_mtime; // 文件最后一次被修改的时间,
// 在用mknod、 utime 和write 时才会改变。
time_t st_ctime; // 最近一次被更改的时间,在文件所有者、组、 权限被更改时更新。
};

stat() 库函数

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
// stat() 函数获取 path 参数指定目录或文件的详细信息,保存到 buf 结构体中。
int stat(const char *path, struct stat *buf); // 返回值:0-成功,-1-失败,errno 被设置。

#include <stdio.h>
#include <iostream>
#include <cstdio>
#include <sys/stat.h>
#include <unistd.h>
using namespace std;
int main(int argc,char *argv[])
{
if (argc != 2) { cout << "Using:./demo 文件或目录名\n"; return -1; }
struct stat st; // 存放目录或文件详细信息的结构体。
// 获取目录或文件的详细信息
if (stat(argv[1],&st) != 0)
{
cout << "stat(" << argv[1] << "):" << strerror(errno) << endl; return -1;
}
if (S_ISREG(st.st_mode))
cout << argv[1] << "是一个文件(" << "mtime=" << st.st_mtime << ",size=" << st.st_size
<< ")\n";
if (S_ISDIR(st.st_mode))
cout << argv[1] << "是一个目录(" << "mtime=" << st.st_mtime << ",size=" << st.st_size
<< ")\n";
}

rename() 函数用于重命名目录或文件,相当于操作系统的 mv 命令。
remove() 函数用于删除目录或文件,相当于操作系统的 rm 命令。