Linux环境搭建

虚拟机配置

ifconfig指令

ifconfig 是一个经典的 网络接口配置工具,主要用于查看和管理 Linux 系统中的网络接口(如网卡)。

核心作用

  1. 查看网络信息
    • 显示所有已启用网卡的 IP 地址、子网掩码、MAC 地址、MTU(最大传输单元)等。
    • 显示网卡的收发数据包统计(RX/TX packets、errors、drops 等),帮助排查网络问题。
  2. 配置网络接口(需要 root 权限)
    • 启用 / 禁用网卡:ifconfig ens160 up / ifconfig ens160 down
    • 临时设置 IP 地址:ifconfig ens160 192.168.1.100 netmask 255.255.255.0

在现代 Linux 发行版(如 CentOS 8、Ubuntu 20.04+)中,ifconfig 属于 net-tools 包,默认可能未安装,推荐使用功能更强大的 ip 命令:

  • ip addr:查看 IP 地址(等价于 ifconfig
  • ip link:查看链路状态
  • ip route:查看路由表

设置管理员权限

在普通用户中,设置管理员权限,需要更改sudoers文件

  1. 切换到管理员用户下: su root
  1. 更改sudoers文件的权限,加上写权限:chmod u+w /etc/sudoers
  1. 修改该文件中的内容:vi /etc/sudoers

使用上下键找到以 root ALL= ... 开头的一行内容

将光标放在该行,按 yy 键 (复制),然后按 p 键(粘贴)

在复制出来的那行中,按 a 键进入编辑模式,将该行中的root改成自己的用户名 zpp

Esc键 后 —-> shift + : 后输入wq保存并退出即可

将sudoers文件的写权限去除:chmod u-w /etc/sudoers

回到普通用户权限:指令exit 或者 ctrl + D快捷键

切换Linux镜像源

默认官方的centos 8镜像源已经不可用了,需要切换到国内的镜像源(阿里云)

切换步骤:

  1. 备份原有的仓库配置:
1
2
sudo mkdir /etc/yum.repos.d/backup #创建一个用于存放备份的文件夹
sudo mv /etc/yum.repos.d/CentOS-* /etc/yum.repos.d/backup #备份文件
  1. 下载阿里云的镜像文件
1
sudo curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-vault-8.5.2111.repo
  1. 清理缓存并重建元数据
1
2
sudo dnf clean all #清除缓存
sudo dnf makecache #创建元数据

网络问题

​ 如何判断自己的虚拟机是否有外网,如果出现下面的情况说明有网

  1. 一直没有ip地址

  2. 虚拟机没有网

  3. 设置后有ip地址,但是关机重启后就没有了

解决方法

  1. 看看虚拟机是否没有连接网络
  1. 上述连接后还没有网络,查看网络配置器,切换网络模式
  1. 如果上述操作还不行,需要配置网卡配置
1
2
cat /etc/sysconfig/network-scripts/ifcfg-ens33
sudo vi /etc/sysconfig/network-scripts/ifcfg-ens33

​ 按照更改sudoers文件的内容形式改成将上面相关文件信息更改后,保存退出 —>重启虚拟机即可

  1. 检查IP地址是否存在

​ 不存在的解决方案:

​ 看一下虚拟机的网络编辑器:

image-20260302175903314 image-20260302175909148

​ 看一下虚拟机的服务是否开启:如果没有开启,需要开启一下

image-20260302175951559

​ 看一下网络适配器:

  1. 如果上面的操作依然没有解决你的问题:重置一下虚拟机的网络
  1. 查看windows是否为家庭版,如果为家庭版,建议升级成专业版

查看设备管理器中的网络连接

总结:

  1. 虚拟机无法获取IP地址:网络管理冲突(一般发生在CentOS7中)、网络模式(NAT)、VM服务不正常

  2. 主机和虚拟机网络不互通:网络适配器不正常

  3. 虚拟机无法连接外网:配置了静态IP不正确,检查三个IP段是否一致

用户相关

​ Linux是支持多用户开发的,可以有一个管理员用户和多个普通用户

  • 管理员用户:执行重要操作时无需输入密码
  • 普通用户:正常开发合作的用户

切换账户

1
su 账户名

如果是进入管理员帐号想退出可以用exit指令或su出去

建议exit回到「原发起用户」,不会占用内存,生命周期结束,若用su则是另建新角色,root还在

ls指令

1
ls -l #详细信息

第二列是权限列:r-w-x对应读-写-可执行

权限列后一列是硬链接数,即 这个文件真实内容被多少个名字 “指向”

之后是用户名+用户所在组名

Xshell

​ Xshell是一款Windows平台上的SSH(secure Shell)客户端软件,能够完成远程连接和管理linux服务,支持SSH1和SSH2协议,提供了安全的加密连接,能够防止数据被窃取和篡改。

下载地址:家庭/学校免费 – NetSarang Website

​ 创建会话连接,主机ip可以在服务器内通过ifconfig指令得到

vim编辑器使用

​ vim是linux系统下的文本编辑器,可以对不同文本进行编辑,功能是对文件内容的编辑、保存、更改等操作

​ vim有三种模式:命令行模式、插入模式、底行模式

如何打开vim编辑器

  1. touch一个文件,然后使用指令:vi/vim + 文件名即可,这种方式打开文件,无论是否编辑文件,都会在文件系统中产生一个普通文件
  2. 直接使用vi/vim + 不存在的文件名,如果对文本内容进行更改,则保存退出后,会在文件系统上产生一个普通文件,如果没有更改数据,保存后退出,则不会在文件系统上创建该文件

命令行模式

1)使用vi + 文件名,刚进入编辑器的模式就是命令行模式

2)命令行模式的作用:主要完成对文本内容整体的复制、粘贴、剪切、删除、光标移动等操作

3)如何进入命令行模式:

​ 刚进入编辑器的模式就是命令行模式

​ 其他模式进入命令行模式只需要按:ESC键即可

4)命令行模式主要操作

  • yy: 复制当前行的文本内容

  • nyy:复制从广播所在行及以后的n行文本内容

  • p:将剪切板中的内容进行粘贴

  • dd:删除光标所在的一行文本内容(剪切)

  • ndd:删除光标所在行及以后的n行数据(剪切)

  • u:撤销上一步操作

  • ctrl+r:反撤销

  • gg:将光标直接跳到首行位置

  • nG:表示跳到第n行

  • 0:表示光标移动到当前行的行首位置

  • $:表示光标移动到当前行的行尾位置

插入模式

1)功能:用于文件内容的编辑

2)如何进入插入模式,只能从命令行模式进入插入模式,不能直接从底行模式进入插入模式

​ 键盘上特殊的键能从命令行模式进入插入模式

  • INSERT键:从光标所在的字符前进入插入模式
  • i键:从光标所在的字符前进入插入模式
  • I键:从光标所在的行行首进入插入模式
  • a键:从光标所在的字符后面进入插入模式
  • A键:从光标所在行的行尾进入插入模式
  • o键:从光标所在行的下一行进入插入模式
  • O键:从光标所在行的上一行进入插入模式
  • s键:删除光标所在的字符后,从光标位置进入插入模式
  • S键:删除光标所在的一行文本后,从当前行进入插入模式

底行模式

1)功能:完成对文本内容的保存、退出操作或者替换、查找工作

2)如何进入底行模式:从命令行模式中键入 shift + 冒号,就是输入一个冒号

3)主要操作

  • :w 保存文本内容
  • :q退出编辑器
  • :q! 强制退出
  • :wq 保存后退出
  • :x 保存退出
  • :set number 在编辑器左侧显示行号
  • :set nonumber 不显示行号
  • :/string 查找字符串string,并将光标定位在包含该字符串的行首
  • :%s/string1/string2/g 表示将所有的string1更换成string2
  • :m,ns/string1/string2/g 表示[m,n]行闭区间内的所有string1更换成string2

总结

C++环境搭建

  1. 安装g++编译环境、gcc编译环境
1
2
3
4
5
6
7
sudo yum install gcc //安装gcc编译器,用于编译c语言程序
sudo yum install gcc-c++ //安装g++编译器,用于编译C++程序
验证是否安装成功
gcc --version //如果显示版本号,则表示安装成功
g++ --version //如果显示版本号,则表示安装成功
sudo yum remove gcc //卸载gcc编译器,用于编译c语言程序
sudo yum remove gcc-c++ //卸载g++编译器,用于编译C++程序
  1. 安装gdb调试工具
1
2
sudo yum install gdb //安装gdb调试工具
gdb --version //验证是否安装成功
  1. CMake工具
1
2
sudo yum install cmake //安装CMake脚本工具
Cmake --version //查看是否安装成功

可能出现的报错:

cmake: symbol lookup error: cmake:undefined symbol:archive write add filter zstd

核心原因:安装的 CMake 版本和系统里的 libarchive 库版本不兼容。

问题根源:

  • 你用 yum install cmake 安装的是 CentOS 8 官方源里的 CMake 3.20.2。
  • 这个版本的 CMake 依赖 libarchive 库中的 archive_write_add_filter_zstd 函数(用于 Zstd 压缩)。
  • 但 CentOS 8 自带的 libarchive 版本太旧,不包含这个函数,所以 CMake 启动时就会报 “未定义符号” 的错误。

解决方法:

1
yum install cmake libarchive #更新libarchive库

Linux下C++程序

C++程序框架的介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<iostream>
//第一条指令表示预处理指令
//#是预处理指令的标识
//include:表示文件包含指令,将指定的文件在该处进行展开
//iostream:i(input)、o(output)、stream(流),本质上也是一个类,在C++中,一般头文件名就是类名
#include<string> //例如string类所在的头文件就是string
using namespace std;
//使用标准的命名空间,头文件中定义的,系统指定的同一的名称
//using:使用命名空间的关键字。有三种用途:命名空间使用、类似于c语言的typedef用于类型重定义、子类中重新定义父类继承的成员的类型
//namespace:命名空间的关键字
//std:系统提供的标准命名空间,系统提供的C++的头文件中,定义的类名、函数名、宏名、类型名,都会同一定义到该命名空间中,每个头文件中几乎都会定义一个名为std的命名空间,所以,同一作用域下,可以定义多个同名的命名空间,但是要求命名空间中的名字不能重复
int main(int argc, const char *argv[])
//主函数:程序的入口函数,每个程序有且只能有一个名为main的函数
//这个函数,可以使用有参的,也可以是无参的,是有返回值的
//参数argc:外部传参的个数
//参数argv:是一个字符指针数组,每一个元素都是一个字符串,表示外部传参的值
{
//花括号表示函数的作用域
cout << "hello world" << endl;//输出语句,cout是ostream的一个类对象
return 0;
}

编译步骤

  • way1:
1
2
3
#一步到位的编译,***是文件名称
1、g++ ***.cpp #此时会默认生成一个 a.out 的可执行程序
2、g++ ***.cpp -o 可执行程序名称 #此时会生成可执行程序
  • way2:
1
2
3
4
5
6
7
8
9
10
11
12
13
#分步编译:(重要) ESc ---> iso
1、预处理(Pre_processing)
功能:将源程序头文件展开、删除注释、宏替换
语法格式:g++ -E ***.cpp -o ***.i
2、编译(Compiling)
功能:将程序编译生成汇编语言
语法格式:g++ -S ***.i -o ***.s
3、汇编(ASSembling)
功能:将汇编语言编译生成二进制文件
语法格式:g++ -c ***.s -o ***.o
4、链接(Linking)
功能:链接相关库文件,生成可执行程序
语法格式:g++ ***.o -o 可执行程序

执行可执行程序

语法格式:

1
./program_name

sys库使用

man手册使用方法

man = manual(手册)

​ linux系统自带、最权威的命令帮助文档几乎所有命令、函数、配置文件都有 man 页,可以在该手册也中查看函数、指令的功能,也是相关操作的使用说明书,一共有七章内容,主要使用前三章,第一章是shell指令相关说明,第二章是系统调用函数相关说明(重点),第三章是库函数(重要)

使用方法

  • 只需要键入 man + 相关函数/指令即可
1
2
3
4
5
#例如
man ls
man cd
man gcc
man yum
  • 退出手册:键入 q

操作快捷键

  • 上下箭头:滚动
  • /关键词:搜索
  • n:下一个搜索结果
  • q:退出

里面一般有什么

  • NAME:命令简介
  • SYNOPSIS:怎么用(语法格式)
  • DESCRIPTION:详细说明
  • OPTIONS:所有参数解释
  • EXAMPLES:例子

如果想要查看C语言库函数相关功能,可能需要安装相关库

1
sudo yum install man-pages man-pages-devel

c/c++库函数printf查询案例

常用的内核提供的函数库 sys

1、文件的操作open()read()write()close()lseek()等等

2、进程控制函数fork()exit()wait()execl()等等

3、信号操作kill()signal()等等

4、网络通信socket()bind()listen()等等

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<iostream>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#include<stdio.h>
#include<error.h>
#include<unistd.h>
using namespace std;
int main()
{
//1、定义文件描述符,并打开文件
int fd = -1;
if((fd = open("./test.txt", O_WRONLY|O_CREAT|O_TRUNC, 0664)) ==-1)
{
perror("open error");
return -1;
}
//2、读写文件
write(fd, "hello", strlen("hello"));
//3、关闭文件
close(fd);
return 0;
}

使用GDB调试程序

​ 在linux系统下的警告和错误,当程序出现bug时,linux终端会给大家两种不同的信息

  • 警告(warning):有时的警告是不影响可执行程序的产生
  • 错误(error): 错误如果不改正,是不能生产可执行程序的

总结:相比于gcc编译器,g++编译器要求更加严格

​ 警告可以被忽略,继续产生可执行程序,但是错误必须更改后才能产生可执行程序

什么是GDB?

​ GDB, GNU项目调试器,允许您查看一个程序执行时“内部”发生了什么,或者一个程序崩溃时正在做什么。

官网:https://www.sourceware.org/gdb/

主要功能

  1. 启动程序,指定可能影响其行为的任何内容。
  2. 使程序在指定条件下停止。
  3. 检查程序停止时发生了什么。
  4. 更改程序中的内容,这样您就可以尝试纠正一个错误的影响,并继续了解另一个错误。

gdb可以调试指定的当前程序,向程序中传递参数

gdb还可以调试出错的文件,查看错误原因

gdb还可以调试正在运行的进程

gdb使用

  1. 准备c++程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<iostream>
#include<stdio.h> //属于C语言的头文件,包含了输入输出函数
using namespace std;
//自定义函数
void print()
{
cout<<"hello world"<<endl;
cout<<"这是我想要说给大家听的!!!"<<endl;
}
int main(int argc, const char *argv[])
{
int arr[5] = {1,2,3,4,5}; //定义一个整形数组
int i = 0; //循环变量
for(i=0; i<5; i++)
{
cout << arr[i] << " ";
}
cout << endl;
//调用自定义函数
print();
return 0;
}
  1. 编译程序,编译选项中需要加上 -g
1
g++ -g ***.cpp -o ***
  1. 启动gdb调试
1
gdb ./***

gdb常用指令

quit/q:表示退出gdb调试

run/r:表示执行可执行程序,如果没有设置断点,则将整个程序从头到尾执行一遍

list/l:展示可执行程序的相关行信息,默认展示10行

  • list m,n :表示展示从m行到n行的信息

  • list func:表示展示func函数旁边的相关程序

break/b:表示设置断点,当调试器将程序运行到断点所在位置后,会暂停于此

  • break 行号:表示在某行设置断点

  • break func:表示在指定的函数处设置断点

  • info break:查看所有断点的信息

  • delete breakpoint 断点编号:表示删除指定的断点

next/n:表示执行下一条语句

continue/c:表示从断点处继续向后执行,直到遇到下一个断点或者程序结束

step/s:能够跳入到指定函数中,查看相关函数内部代码

print/p 变量名/地址 :表示打印指定变量或地址信息

set variable 变量名=值:表示给某个变量设置相关的值

gdb使用技巧

shell:后面可以跟终端指令,表示执行终端相关操作

set logging on:设置开启日志功能,会在当前目录中生成一个gdb.txt文件记录接下来的调试内容

watchpoint :观察点,如果设置的观察点的值发生改变,则会将该值的旧值和新值全部展示出来

gdb调试出错的文件

​ 当一个可执行程序出现错误时,会产生一个core文件,用于查看相关错误信息

​ linux系统默认是不产生core文件,需要进行相关设置后才能产生

​ 通过ulimit -a 查看所有linux的限制内容

​ 通过 ulimit -c unlimited来设置core文件的个数

​ 查看错误原因

gdb调试其他正在运行的进程

./可执行程序 &:表示将可执行程序后台运行,不占用当前终端

gdb -p 进程号:调试指定的运行的进程

pidof a.out:查看进程

kill -p 进程号:杀死进程

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
[zpp@localhost day2]$ g++ -g 05test.cpp //编译程序
[zpp@localhost day2]$ ./a.out & //后台运行程序
[1] 26837 //显示当前后台运行程序的作业号和进程号
[zpp@localhost day2]$ gdb -p 26837 //调试指定的运行的进程
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-120.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Attaching to process 26837
Reading symbols from /home/zpp/day2/a.out...done.
Reading symbols from /lib64/libstdc++.so.6...(no debugging symbols
found)...done.
Loaded symbols for /lib64/libstdc++.so.6
Reading symbols from /lib64/libm.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib64/libm.so.6
Reading symbols from /lib64/libgcc_s.so.1...(no debugging symbols found)...done.
Loaded symbols for /lib64/libgcc_s.so.1
Reading symbols from /lib64/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib64/libc.so.6
Reading symbols from /lib64/ld-linux-x86-64.so.2...(no debugging symbols
found)...done.
Loaded symbols for /lib64/ld-linux-x86-64.so.2
main (argc=1, argv=0x7ffd31897908) at 05test.cpp:17
17 int main(int argc, const char *argv[]) //进程运行在某处
Missing separate debuginfos, use: debuginfo-install glibc-2.17-326.el7_9.x86_64
libgcc-4.8.5-44.el7.x86_64 libstdc++-4.8.5-44.el7.x86_64
(gdb) n
22 test();
(gdb) n
23 test1();
(gdb) step
test1 () at 05test.c

库的制作(静态库与动态库)

为什么引入库

​ 在上述案例中,主程序要是有的源程序代码,在add.cpp中,如果项目结束后,到了交付阶段,由于主程序的生成需要其他程序联合编译,那么就要将源程序打包一起发给老板,这样该程序的开发者自身的价值就不大了,该项目的知识产权就很容易被窃取。为了保护我们的知识产权,我们引入了库的概念

什么是库

​ 库在linux中是一个二进制文件,它是由 .cpp文件(不包含main函数)编译而来,其他程序如果想要使用该源文件中的函数时,只需在编译生成可执行程序时,链接上该源文件生成的库文件即可。库中存储的是二进制文件,不容易被窃取知识产权,做到了保护作用。

​ 库在linux系统中分为两类,分别是静态库动态库

windows:

  • ***.lib:静态库

  • ***.dll: 动态库

linux:

  • ***.a:静态库
  • ***.so: 动态库

静态库及其制作

​ 概念:将一个***.cpp的文件编译生成一个 lib***.a的二进制文件,当你需要使用该源文件中的函数时,只需要链接该库即可,后期可以直接调用

​ 静态体现在:在使用g++编译程序时,会将你的文件和库最终生成一个可执行程序(把静态库也放入到可执行程序中),每个可执行程序单独拥有一个静态库,体积较大,但是,执行效率较高

案例:

  1. 准备程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//add.h
#ifndef _ADD_H_
#define _ADD_H_
int add(int m, int n); //函数声明
#endif

//add.cpp
int add(int m, int n)
{
return m+n;
}

//main.cpp
#include<iostream>
#include<stdio.h>
#include "add.h"
using namespace std;
int main(int argc, const char *argv[])
{
cout << add(3,8) << endl; //调用外部文件中的相关函数
return 0;
}
  1. 编译静态库
1
2
3
4
5
6
7
8
gcc -c ***.cpp -o ***.o //只编译不链接生成二进制文件
ar -crs lib***.a ***.o //编译生成静态库

# 如果有多个.o文件共同编译生成静态库 :ar -crs lib***.a ***.o xxx.o ...
ar:用于生成静态库的指令
c:用于创建静态库 create
r:将文件插入或者替换静态库中同名文件 replace
s:重置金泰克索引 symbol table
  1. 使用静态库
1
gcc main.cpp -L 库的路径 -l库名 -I 头文件所在路径

指令拆解

部分 含义
g++ C++ 编译器(处理 .cpp 文件,链接 C++ 标准库)
main.cpp 要编译的主源文件(程序入口 main 函数在这里)
-L . 指定库搜索路径-L = Library path,. 表示「当前目录」告诉编译器:去当前目录找静态库(.a)/ 动态库(.so)
-ladd 指定要链接的库-l = link,add 对应 libadd.a(静态库)规则:-lxxx → 找 libxxx.a/libxxx.so
-I . 指定头文件搜索路径-I = Include path,. 表示「当前目录」告诉编译器:去当前目录找 #include "add.h" 这类头文件

文件归类后可以如下操作

动态库及其制作

概念:将一个***.cpp的文件编译生成一个 lib***.so二进制文件,当你需要使用该源文件中的函数时,只需要链接该库即可,后期可以直接调用

**动态体现在:**在使用g++编译程序时,会将你的文件和库中的相关函数的索引表一起生成一个可执行程序,每个可执行程序只拥有函数的索引表,当程序执行到对应函数时,会根据索引表,动态寻找相关库所在位置进行调用,体积较小,执行效率较低,但是可以多个程序共享同一个动态库,所以,动态库也叫共享库

案例:

  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
//add.h
#ifndef _ADD_H_
#define _ADD_H_

int add(int m,int n); //函数声明

#endif

//add.cpp
int add(int m, int n)
{
return m+n;
}

//main.cpp
#include<iostream>
#include<stdio.h>
#include "add.h"
using namespace std;
int main(int argc, const char *argv[])
{
cout << add(3,8) << endl; //调用外部文件中的相关函数
return 0;
}
  1. 编译生成动态库
1
2
3
4
5
g++ -fPIC -c ***.cpp -o ***.o //编译生成二进制文件
g++ -shared ***.o -o lib***.so //依赖于二进制文件生成一个动态库

#上述两个指令可以合成一个指令
g++ -fPIC -shared ***.cpp -o lib***.so
  1. 使用动态库
1
gcc main.cpp -L 库的路径 -l库名 -I 头文件的名字
  1. 报错解决方案

    方法1:更改路径宏

    1
    export LD_LIBRARY_PATH=库的路径

​ 当你执行 export LD_LIBRARY_PATH=. 时,它会将当前目录 . 设置为这个环境变量的唯一值。

系统查找库的顺序变为:

  1. 先在当前目录 . 查找。
  2. 如果找不到,再去系统默认的库路径(如 /lib, /usr/lib, /usr/lib64 等)中查找。

两个重要的局限性:

  1. 临时生效:这个环境变量只在当前终端会话中有效。如果你关闭终端或重新登录,设置就会失效,再次运行程序时又会报错。
  2. 优先级问题:如果当前目录下有一个与系统库同名的文件(例如 libc.so),程序会优先加载当前目录下的这个文件,这可能导致程序行为异常甚至崩溃。不过,这种情况在日常开发中很少见。

方法2:将自己的动态库放入到系统的库函数目录中 (/lib64 /usr/lib64)

1
sudo mv libadd.so /lib64
  1. 动态库和静态库编译生成可执行程序的文件大小比较

第三方库使用

1、C/C++语言默认是支持标准输入输出库的,但是其他的相关函数库需要第三方引入,并且,编译程序时,需要链接上对应的库

2、使用数学库:#include<math.h>

3、线程支持类库:#include<pthread.h>

如果不能支持线程库:sudo yum install manpages-posix manpages-posix-dev

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
#include<iostream>
#include<math.h> //数学相关函数所在的头文件
#include<pthread.h> //线程支持库的相关头文件
#include<unistd.h>
using namespace std;
//定义分支线程的线程体函数
void *task(void *arg)
{
while(1)
{
cout << "hello world"<<endl;
sleep(1); //休眠函数
}
}
int main(int argc, const char *argv[])
{
pthread_t tid; //定义一个线程号变量,用于存储创建出来的线程的线程号
//创建线程
if(pthread_create(&tid, NULL, task, NULL) != 0)
{
cout << "pthread_create error" << endl;
}
//主线程
while(1);

return 0;
}

Makefile文件

什么是Makefile

​ 用于工程项目管理的一个文本文件,文件名为Makefile的文本文件

Makefile可以大写,也可以小写,一般Makefile首字母使用大写

​ 如果Makefilemakefile两个文件都存在,系统会默认使用小写

什么是make

make是一个执行Makefile的工具,是一个解释器

​ 用来对Makefile中的命令进行解析并执行一个shell指令

make这个指令在 /usr/bin 中,默认linux系统中都已经安装

​ 如果没有安装 make,安装指令如下

1
sudo yum install make

​ 查看是否安装成功:make --version

用处

​ 描述了整个工程的编译、链接规则

软件项目的自动化编译,相当于给软件编译写一份脚本文件

学习Makefile必要性

​ Linux/Uinx环境下开发的必备技能

​ 系统架构师、项目经理的核心技能

​ 研究开源项目、Linux内核原码的必需品

​ 加深对底层软件构造系统及过程的理解

理论基础

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
软件的构造过程、程序的编译和链接:预处理 --> 编译 --> 汇编-->链接
#详见1.5.2编译步骤
面向依赖的思想:
依赖一个.cpp文件的程序
可执行程序 <--依赖于-- .o文件 <--依赖于-- .s文件 <--依赖于-- .i文件 <--依赖于-- .cpp文件

依赖多个.cpp文件的程序
可执行程序 <--依赖于-- .o文件 <--依赖于-- .s文件 <--依赖于-- .i文件 <--依赖于--
.cpp文件
| -- .o文件 <--依赖于-- .s文件 <--依赖于-- .i文件 <--依赖于--
.cpp文件
| -- .o文件 <--依赖于-- .s文件 <--依赖于-- .i文件 <--依赖于--
.cpp文件
| -- .o文件 <--依赖于-- .s文件 <--依赖于-- .i文件 <--依赖于--
.cpp文件

Makefile工作过程

​ Makefile本身是面向依赖进行编写的

​ 源文件(.cpp) —> 编译(.s) —-> 目标文件(.o) —> 链接 —>可执行文件

hello.cpp—->hello.o —>hell 分两步进行撰写

​ 本质上一步就可以生成可执行程序,但是,由于在生成可执行程序时,可能会有多个文件进行参与,后期其他文件可能要进行更改,更改后,会影响到可执行程序的执行,其他没有更改的文件也要参与编译,浪费时间

案例

  1. 编写程序
1
2
3
4
5
6
7
8
9
//hello.cpp
#include<iostream>
#include<stdio.h>
using namespace std;
int main(int argc, const char *argv[])
{
cout << "hello world" << endl;
return 0;
}
  1. 终端编译程序
  1. 使用Makefile管理工程

编写Makefile文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Makefile中的注释是以#开头
# 语法格式:
# 目标:依赖
# 通过依赖生成目标的指令
# 注意:指令前面必须使用同一个tab键隔开,不能使用多个空格顶过来

hello:hello.o
g++ hello.o -o hello

hello.o:hello.s
g++ -c hello.s -o hello.o

hello.s:hello.i
g++ -S hello.i -o hello.s

hello.i:hello.cpp
g++ -E hello.cpp -o hello.i

#清理目标,使用时make clean
clean:
rm hello.i hello.s hello.o

简化的Makefile文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Makefile中的注释是以#开头
# 语法格式:
# 目标:依赖
# 通过依赖生成目标的指令
# 注意:指令前面必须使用同一个tab键隔开,不能使用多个空格顶过来

hello:hello.o
g++ hello.o -o hello

hello.o:hello.cpp
g++ -c hello.cpp -o hello.o

#清理目标,使用时make clean
clean:
rm hello.o hello
  1. 执行Makefile文件
1
2
make --->默认找到Makefile中的第一个目标开始进行执行
make 目标 ---> 找到对应的目标进行执行

Makefile语法规则

1、规则

​ 构成Makefile的基本单元,构成依赖关系的核心部件

​ 其他内容可以看做为规则的服务

2、变量

​ 类似于C++中的宏, 使用变量:$(变量名) 或者 ${变量名}

​ 作用:使得Makefile更加灵活

3、条件执行

​ 根据某一变量的值来控制make执行或者忽略Makefile的某一部分

4、函数

​ 文本处理函数:字符串的替换、查找、过滤等等

​ 文件名的处理:读取文件/目录名、前后缀等等

5、注释

Makefile中的注释,是以#号开头

规则

1、规则的构成:目标、目标依赖、命令

2、语法格式

1
2
3
4
5
6
7
8
目标:目标依赖
命令

注意事项:
命令必须使用tab键开头,一般是shell指令
一个规则中,可以无目标依赖,仅仅是实现某种操作
一个规则中可以没有命令,仅仅描述依赖关系
一个规则中,必须要有一个目标

3、目标详解

​ 1)默认目标

1
2
一个Makefile里面可以有多个目标,一般会选择第一个当做默认目标
也就是make默认执行的目标

​ 2)多目标

​ 一个规则中可以有多个目标,多个目标具有相同的生成命令和依赖文件

1
2
clean distclean:
rm hello.[^cpp] hello

​ 3)多规则目标

​ 多个规则可以是同一目标

1
2
3
4
5
6
all:test1
all:test2
test1:
echo "hello" #可以加个@进行无命令输出,即@echo "hello"
test2:
echo "world

​ 4)伪目标

1
2
3
4
5
6
7
#并不是一个真正的文件名,可以看做是一个标签
#无依赖,相比一般文件,不会重新生成、执行
#伪目标:可以无条件执行,相当于对应的指令
eg:
.PHONY:clean #设置伪目标
clean:
rm hello.[^cpp] hello

4、目标依赖

​ 1)文件时间戳

1
2
3
4
根据时间戳来判断目标依赖是否要进行更新
所有文件都更改过,则对所有文件进行编译,生成可执行程序
在上次make之后修改过的cpp文件,会被重新编译
在上次make之后修改过的头文件,依赖该头文件的目标依赖也会重新编译

​ 2)模式匹配

1
2
3
4
5
6
% ---->通配符匹配
$@ ---->目标
$^ ---->所有依赖
$< ---->第一个依赖
* ---->普通通配符
注意:%是Makefile中的规则通配符,*是普通通配符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//头文件
#ifndef _OPERATOR_H
#define _OPERATOE_H
int add(int m, int n);
#endif

//源文件
int add(int m, int n)
{
return m+n;
}

//主程序:
#include<iostream>
#include"operator.h"
using namespace std;
int main(int argc, const char *argv[])
{
cout << add(3,5) << endl;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#第一个依赖关系
all:operator
operator:main.o operator.o
# g++ main.o operator.o -o operator
g++ $^ -o $@

#main.o:main.cpp
# g++ -c $< -o $@
#operator.o:operator.cpp
# g++ -c $^ -o $@
#将上述两个规则统一成一个规则
#此时的%表示所有的Makefile内容,不能换成*
%.o:%.cpp
g++ -c $< -o $@

clean:
rm -f *.o operator #此处的*是普通通配符

5、命令

1
2
3
4
5
6
7
8
9
10
1)命令的组成
由shell命令组成,以tab键开头
2) 命令的执行
每条命令,make会开一个进程
每条命令执行完,make会检测这个命令的返回码
如果返回成功,则继续执行后面的命令
如果返回失败,则make会终止当前执行的规则并退出
3)并发执行命令
make -j4 ----->表示开辟4个线程执行
time make ----->执行make时,显示当前时间

变量

1、变量基础

1
2
3
4
5
1)变量定义:变量名 = 变量值1 变量值2 ...
2)变量的赋值
追加赋值:+= --->在原有的基础上追加相关内容
条件赋值:?= --->如果之前没有值,则为变量赋值,如果之前有值,则不进行赋值
3)变量的使用: $(变量名)或者${变量名}

2、变量的分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1)立即展开变量
使用:=操作符进行赋值
在解析阶段直接赋值常量字符串,后面再怎么改依赖的变量,它都不会变。
值是 “快照式” 的,只取解析那一刻的状态。
2)延迟展开变量
使用=操作符进行赋值
变量的值在解析时不计算,而是保留整个表达式,等到这个变量真正被使用(比如在命令里)时,才会去查找当时的最新值进行展开。
值是 “动态引用” 的,永远取最后一次赋值的结果。
3)注意事项
一般在目标、目标依赖中使用立即展开赋值
在命令中一般使用延迟展开赋值变量
4)?= 和 += 的展开方式
?=:条件赋值,它的展开方式和 = 一样,是延迟展开。
+=:追加赋值。如果变量之前是用 := 定义的(立即展开),+= 也会立即展开;如果之前是用 = 定义的(延迟展开),+= 也会延迟展开。

3、变量的外部传递

可以通过命令行给变量进行赋值操作

1
make ARCH=g++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var1 = main.o #定义一个变量并赋值
var1 += operator.o #给变量追加值
CC:=g++
#第一个依赖关系
all:operator
operator:$(var1)
# g++ main.o operator.o -o operator
$(CC) $^ -o $@
#main.o:main.cpp
# g++ -c $< -o $@
#operator.o:operator.cpp
# g++ -c $^ -o $@
#将上述两个规则统一成一个规则
#此时的%表示所有的Makefile内容,不能换成*
%.o:%.cpp
$(CC) -c $< -o $@
clean:
rm -f *.o operator #此处的*是普通通配符

条件执行

1、关键字

1
2
ifeqelseendif
ifneqelseendif

2、使用

1
2
3
4
5
6
7
8
9
ifeq (要判断的量, 判断的值)
Makefile语句
else
Makefile语句
endif

注意:
条件语句从ifeq开始执行,括号与关键字自减使用空格隔开
括号里面挨着括号处,不允许加空格

上图案例需要终端输入条件指令如:make COMPILE=g++

总结

1、执行过程

1
2
3
进入编译目录、执行make命令
依赖关系解析阶段
命令执行阶段

2、依赖解析阶段

1
2
3
解析Makefile,建立依赖关系树
控制解析过程,引入Makefile,变量展开、条件执行
生成依赖关系树

3、命令执行阶段

1
2
3
4
5
把解析生成的依赖关系树加载到内存
按依赖关系,按顺序生成这些文件
再次编译make会检测文件的时间戳,判断是否过期
如果无过期,在不在编译
如果文件有更新,则依赖该文件的所有依赖关系上的目标都会重新编译

4、make执行结果

1
2
3
make的退出码
0:表示成功执行
1:表示允许错误,退出

vscode配置

vscode官网:Download Visual Studio Code - Mac, Linux, Windows

mingw官网:mingw-w64

对 .vscode 文件夹的更改,linux系统里也一样

多文件编译

vsc调试工具使用

CMake

​ CMake是一个跨平台的安装编译工具,可以使用简单的语句来描述所有平台的安装(编译过程)

​ CMake可以说是已经成为大部分的C++开发项目的标配

​ 可以使用几行或者几十行的代码来完成非常冗长的Makefile代码

为什么要用CMake

​ 在不使用CMake时,编译工程如下

​ 在上面的机制中,工程文件中添加一个源程序 bar.cpp

使用CMake来管理工程的状态

​ 使用CMkake管理工程中添加一个新文件 bar.cpp

语法特性介绍

1> 基本语法:指令(参数1 参数2 ...)

​ 参数使用括号括起来

​ 参数之间使用空格或分号隔开

2> 注意:指令是大小写无关的,但是参数和变量是大小写相关

1
2
3
set(HELLO hello.cpp) # 定义一个变量名叫HELLO 变量的值为hello.cpp
add_executable(hello main.cpp hello.cpp) # 通过main.cpp 和hello.cpp 编译生成hello可执行程序
ADD_EXECUTABLE(hello main.cpp ${HELLO}) # 作用同上

3> 变量使用${}进行取值,但是在if控制语句中,是直接使用变量名的

if(HELLO) 是正确的

if(${HELLO}) 是不正确的

4> 语句不以分号结束

CMake重要的指令

1> cmake_minimum_required:指定CMake的最小版本支持,一般作为第一条cmake指令

1
2
# CMake设置最小支持版本为 2.8
cmake_minimum_required(VERSION 2.8)

2> project:定义工程的名称,并可以指定工程支持的语言

1
2
# 指定工程的名称为HELLOWORLD
project(HELLOWORLD CXX) # 表示工程名为HELLOWORLD 使用的语言为C++

3> set:显式定义变量

1
2
# 定义变量 SRC 其值为 sayhello.cpp hello.cpp
set(SRC sayhello.cpp hello.cpp)

4> add_executable:通过依赖生成可执行程序

1
2
# 编译main.cpp 生成main的可执行程序
add_executable(main main.cpp)

5>include_directories:向工程添加多个特定的头文件搜索路径吗,类似于g++编译指令中的 -I

1
2
# 将/usr/lib/mylibfolder 和 ./include添加到工程路径中
include_directories(/usr/lib/mylibfolder ./include)

6> link_directories:向工程中国添加多个特定的库文件搜索路径,类似于g++编译指令的 -L选项

1
2
# 将将/usr/lib/mylibfolder 和 ./lib添加到库文件搜索路径中
link_directories(/usr/lib/mylibfolder ./lib)

7> add_library:生成库文件(包括动态库和静态库)

1
2
3
# 通过SRC 变量中的文件,生成动态库
add_library(hello SHARED ${SRC}) # 该语句生成的是动态库
add_library(hello STATIC ${SRC}) # 该语句生成的是静态库

8> add_compile_options:添加编译参数

1
2
# 添加编译参数: -Wall -std=c++11
add_compile_options(-Wall -std=c++11)

9> target_link_libraries:为target添加需要链接的共享库,类似于g++编译中的 -l 指令

1
2
# 将hello 动态库文件链接到可执行程序main中
target_link_libraries(main hello)

CMake常用变量

1> CMAKE_C_FLAGS:gcc编译选项的值

2> CMAKE_CXX_FLAGS:g++编译选项的值

1
2
# 在CMAKE_CXX_FLAGS编译选项后追加 -std=c++11
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")

3> CMAKE_BUILD_TYPE:编译类型(Debug 、Release)

1
2
3
4
# 设定编译类型为Debug,调试时需要选择该模式
set(CMAKE_BUILD_TYPE Debug)
# 设定编译类型为Release,发布需要选择该模式
set(CMAKE_BUILD_TYPE Release)

CMake编译工程

​ CMake目录结构:项目主目录中会放一个CMakeLists.txt的文本文档,后期使用cmake指令时,依赖的就是该文档

两种情况:

​ 1> 包含源文件的子文件夹中包含CMakeLists.txt文件时,主目录的CMakeLists.txt要通过add_subdirector添加子目录

​ 2> 包含源文件的子文件夹中不包含CMakeLists.txt文件时,,子目录编译规则,体现在主目录中的CMakeLists.txt

内部构建

​ 内部构建:不推荐使用

​ 内部构建会在主目录下,产生一大堆中间文件,这些中间文件并不是我们最终所需要的,和工程源文件放在一起时,会显得比较杂乱无章

1
2
3
4
5
## 内部构建
# 在当前目录下,编译主目录中的CMakeLists.txt 文件生成Makefile文件
cmake . # . 表示当前路径
# 执行make命令,生成目标文件
make

外部构建

​ 将编译输出的文件与源文件放到不同的目录下,进行编译,此时,编译生成的中间文件,不会跟工程源文件进行混淆

1
2
3
4
5
6
7
8
9
## 外部构建步骤
# 1、在当前目录下,创建一个 build 文件,用于存储生成中间文件
mkdir build
# 2、进入build文件夹内
cd build
# 3、编译上一级目录中的CMakeLists.txt,生成Makefile文件以及其他文件
cmake .. # ..表示上一级目录
# 4、执行make命令,生成可执行程序
make

CMake代码实战

同目录下文件编译

1
2
3
4
5
6
#include <myhead.h>
int main(int argc, const char *argv[])
{
std::cout << "Hello, World!" << std::endl; //输出一个hello world语句
return 0;
}

CMakeLists.txt文件

1
2
3
4
5
6
# 设置最小版本支持
cmake_minimum_required(VERSION 2.8)
# 项目名称
project(HELLO)
# 生成可执行程序,依赖于hello.cpp生成hello可执行程序
add_executable(hello hello.cpp)

内部编译

外部编译

分文件编译

相关代码文件准备

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
// include/swap.h
#ifndef SWAP_H
#define SWAP_H
#include<myhead.h>
//声明一个自定义交换类
class My_swap
{
private:
int a;
int b; //两个成员变量
public:
//定义构造函数
My_swap(int a, int b)
{
this->a = a;
this->b = b;
}
//声明交换函数
void run();
//声明打印函数
void printInfo();
};
#endif

// src/swap.cpp
#include"swap.h"
//对交换函数的定义
void My_swap::run()
{
int temp = a;
a = b;
b = temp;
}
//对打印函数的定义
void My_swap::printInfo()
{
cout << "a = " << a<< endl;
cout << "b = " << b<< endl;
}

// main.cpp
#include "swap.h"
int main(int argc, const char *argv[])
{
//声明一个交换类对象,调用有参构造完成
My_swap my_swap(520, 1314);
//输出交换前的结果
my_swap.printInfo(); //520 1314
cout<<"**************************"<<endl;
//调用交换函数
my_swap.run();
//输出交换后的结果
my_swap.printInfo();
return 0;
}

分文件编译使用g++编译器生成可执行程序

创建工程管理文件 CMakeLists.txt

1
2
3
4
5
6
7
8
# 指定最小编译版本
cmake_minimum_required(VERSION 2.8)
# 执行工程名称
project(SWAP)
# 指定头文件路径 就是 g++ 编译器的 -I 选项
include_directories(include)
# 生成可执行程序
add_executable(swap_cmake main.cpp src/swap.cpp)