环境搭建
ubuntu系统
# 编译器、调试器安装 #
sudo apt update
sudo apt install build-essential gdb
# 检查是否安装成功
gcc --version
g++ --version
gdb --version
# 安装cmake #
sudo apt install cmake
# 检查
cmake --version
centos系统
# 安装编译器 #
yum install -y gcc g++ gcc-c++ make automake texinfo wget openssl openssl-devel
# 安装cmake #
wget https://cmake.org/files/v3.16/cmake-3.16.8.tar.gz
# 解压并构建
tar -xf cmake-3.16.8.tar.gz
cd cmake-3.16.8
./configure
# 编译,安装
make && make install
# 构建软连接
ln -s /usr/local/bin/cmake /usr/bin/cmake
# 安装gdb #
# 先安装termcap
wget https://ftp.gnu.org/gnu/termcap/termcap-1.3.1.tar.gz
tar -xf termcap-1.3.1.tar.gz
cd termcap-1.3.1
./configure
make
make install
# 安装gdb
wget http://mirrors.ustc.edu.cn/gnu/gdb/gdb-7.9.tar.xz
tar -xf gdb-7.9.tar.xz
cd gdb-7.9
./configure
make
make install
# 构建软连接
ln -s /usr/local/bin/gdb /usr/bin/gdb
windows
先安装MinGW
安装包网址: www.mingw-w64.org/downloads/
安装到指定指定目录后,如我是安装在D:\cppCompile\下:
其中的bin文件夹里面就存放了g++.exe和gcc.exe以及gdb.exe三个可执行文件,我们将待编译(调试)的文件以及相关参数配置输入给这些可执行文件即可以进行编译。 然后我们需要配置环境变量:
我的电脑->右键属性->高级系统设置->环境变量->系统变量->找到Path变量->编辑->新建一个路径->粘贴上MinGW的bin目录地址。
打开cmd验证安装是否成功:
gcc.exe --version # 或gcc --version
g++.exe --version # 或g++ --version
gdb.exe --version # 或gdb --version
安装Cmake
这个网站下载cmake比较快
进入LatestRelease目录,找到windows的x86_64版本下载安装包(.msi后缀),下载到指定目录,然后双击文件安装时记得勾选自动添加环境变量。
打开cmd验证安装是否成功:
cmake --version
c++编译过程
1.预处理
指定-E选项表示仅仅对源码文件进行预处理,生成.i文件
以例1的文件示范,仅仅对main.cpp执行预处理
g++ -E main.cpp -o main.i # -E命令不支持操作多个文件, win和linux都是这个命令
生成的main.i文件的部分内容如下,仍然是可供人直接阅读的:
2.编译
指定-S选项表示产生汇编语言后停止编译,生成.s文件
g++ -S main.i -o main.s # 由main.i生成main.s
g++ -S main.cpp -o main.s # 由main.cpp生成main.s
main.s是汇编文件,部分内容如下:
3.汇编
指定-c选项表示产生把源代码文件编译为机器语言
g++ -c main.s -o main.o # 由main.s生成main.o
g++ -c main.cpp -o main.o # 由main.cpp生成main.o
4.链接
指定-o选项产生可执行bin文件
g++ main.cpp -o main.exe # 由main.cpp生成main.exe
C++编译的重要参数
-g参数
指定该参数可生成带调试信息的的可执行文件
g++ -g main.cpp -o main.exe
-O[n]
指定该参参数可优化源代码
g++ -O2 -g main.cpp -o main.exe
# -O选项:对源代码做基本的优化,效果等价与-O1
# -O0选项:不做优化
# -O1选项:等价-O
# -O2选项:除了完成-O1的优化后还会指定一些额外的优化工作
# -O3选项:优化循环展开以及更多的优化工作
-l和-L
-l指定库文件,-L指定库文件路径
# -l参数(小写的L)就是用来指定程序要链接的库,-l参数紧接着就是库名
# 在/lib和/usr/lib和/usr/local/lib里的库直接用-l参数就能链接
# 链接上述三个默认库路径下的glog库
g++ -lglog test.cpp
# 如果要链接的库文件没在上面三个目录里,需要使用-L参数(大写)指定库文件所在目录
# -L参数跟着的是库文件所在的目录名
# 链接/home/bing/myfolder目录下的mytest库,库文件名称为libmytest.so在
# 在写库文件名称的时候省略lib
g++ -L/home/bing/myfolder -lmytest main.cpp
-I
-I指定头文件搜索目录
# /usr/include目录一般是不用指定的,gcc知道去那里找
# 但是如果头文件不在/usr/icnclude里我们就要用-I参数指定了,-I参数可以用相对路径,比如头文件在当前目录,可以用-I.来指定
g++ main.cpp -Iinclude -o main.exe
-Wall
输出警告信息
g++ -Wall main.cpp -o main.exe
-w
关闭输出警告信息
g++ -w main.cpp -o main.exe
-std=c++11
设置编译标准
g++ -std=c++11 main.cpp -o main.exe
-o
指定输出文件名
g++ -std=c++11 main.cpp -o main.exe win中有exe后缀
g++ -std=c++11 main.cpp -o main. linux中无exe后缀
-D
定义宏
c++命令行编译
例1:简单程序
我们先来看一个简单的小例子,在win下将一个简单的HelloWorld程序编译为可执行文件,工程HELLO文件夹下有一个main.cpp文件(先忽略.vscode目录,原生的命令行编译用不到这些文件)
main.cpp内容如下:
#include<iostream>
using namespace std;
int main(void)
{
std::cout << "Hello World!" << std::endl;
return 0;
}
编译过程如下:
# win下, 进入cmd, 切换到main.cpp所在的目录 #
g++ main.cpp -o main.exe # 编译
main.exe # 执行
# linux下, 切换到main.cpp所在的目录 #
g++ main.cpp -o main
./main # 执行
可以发现编译结束后,当前目录出现一个可执行文件(win下带有后缀exe,linux下无后缀)
例2:稍微复杂点的多文件工程
linux下,工程目录文件名为Prj1,其目录树结构为:
main.cpp为主函数,include目录下存放swap.h的头文件,src目录下存放swap.cpp源码文件
- 主函数main.cpp:
#include "swap.h"
int main(int argc, char **argv){
swap mySwap(109, 90);
mySwap.printInfo();
mySwap.run();
mySwap.printInfo();
return 0;
}
- 头文件swap.h:
#include <iostream>
class swap
{
public:
swap(int a, int b)
{
this->_a = a;
this->_b = b;
}
void run();
void printInfo();
private:
int _a;
int _b;
};
- 源码文件swap.cpp:
#pragma once
#include "swap.h"
void swap::run()
{
int temp;
temp = this->_a;
this->_a = this->_b;
this->_b = temp;
}
void swap::printInfo()
{
std::cout << "_a:" << this->_a << std::endl;
std::cout << "_b:" << this->_b << std::endl;
}
编译可执行文件
从上述文件中可以看出,在主函数中我们使用了swap这个类,所以我们需要申明这个类的头文件#include "swap.h",这个头文件存放在include目录下,申明了类的结构和一些方法名称(头文件同时实现了构造方法),而其他的成员函数均定义在src下的swap.cpp源码文件里面,所以我们需要将main.cpp和swap.cpp一起编译为可执行文件,同时告诉编译器头文件去哪个目录下找(-I命令后直接跟上头文件的搜索目录),编译命令如下:
# win下, 进入cmd, 切换到main.cpp所在的目录 #
g++ main.cpp src/swap.cpp -Iinclude -o main.exe # 编译
main.exe # 执行
# linux下, 切换到main.cpp所在的目录 #
g++ main.cpp src/swap.cpp -Iinclude -o main # 编译
./main # 执行
编译静态库与动态库
库文件是别人写好的,我们可以直接使用,但是要遵循相关的规范,这样可以提高我们的代码复用率,类似python中的包,python中直接安装相关包后import导入即可使用,c++的库需要在链接阶段将指定库链接到目标代码中才可以使用。
linux下的静态库/动态库命名方式是:libXXX.a和libXXX.so
windows下的静态库/动态库命名方式是:XXX.lib和XXX.dll
生成静态库并且链接
在例2的工程中,假设我们想将swap.cpp这个交换两数的功能编译成静态库去供别人使用:
# 切换到src目录
cd src
# 首先汇编
g++ swap.cpp -c -I../include -o swap.o
# 然后生成静态库libswap.a
ar rs libswap.a swap.o
静态库在src下生成,目录结构如下:
然后现在其他开发者需要使用你的这个libswap.a的静态库,只需要写一个main.cpp的源文件,源码中标明你这个库的头文件,将该静态库链接到main.cpp这个目标代码中即可
# 切换到上级目录
cd ..
# 进行链接,-I指定库的头文件目录,-L指定库的目录,-l指定库名称,即可生成staticMain
g++ main.cpp -Iinclude -Lsrc -lswap -o staticMain
# 执行
./staticMain
# [root@localtk CmakeTest]# ./staticMain
# _a:109
# _b:90
# _a:90
# _b:109
生成动态库并且链接
在例2的工程中,假设我们想将swap.cpp这个交换两数的功能编译成动态库去供别人使用:
# 切换到src目录
cd src
# 生产动态链接库
g++ swap.cpp -I../include -fPIC -shared -o libswap.so
动态库libswap.so在src下生成,目录结构如下:
# 链接动态库 #
# 切换到src的上级目录
cd ..
# 这个链接的命令其实和链接静态库的命令一样
# 那我们如何知道是链接了动态库libswap.so还是链接了静态库libswap.a呢
# 当有名称相同的静态库和动态库存在时,应该是会优先链接动态库
g++ main.cpp -Iinclude -Lsrc -lswap -o dynamicMain
# 执行
./dynamicMain
# [root@localtk CmakeTest]# ./dynamicMain
# ./dynamicMain: error while loading shared libraries: libswap.so: cannot open shared object file: No such file or directory
# 应该这样执行
LD_LIBRARY_PATH=./src ./dynamicMain
# 出错了,因为是动态链接,libswap.so并没有嵌入到dynamicMain可执行文件中,而是在执行dynamicMain时去系统默认的指定路径下搜索该库,但是该库并不在默认路径下,而是在src下,所以我们需要指定库的搜索目录
# [root@localtk CmakeTest]# LD_LIBRARY_PATH=./src ./dynamicMain
# _a:109
# _b:90
# _a:90
# _b:109
静态库与动态库的区别
- 静态库
静态库的链接时间:在编译过程中就被载入到目标程序中
静态库的链接方式:库中的所有函数都被整合到了目标代码
静态库的优点:编译后不依赖外部环境的函数库
静态库的缺点:若使用的静态库发生了更新,必须要重新编译目标程序才能使用更新后的版本,静态库在运行时内存占用大,因为会载入库中的所有函数。
- 动态库
动态库的链接时间:在编译过程中没有被载入到目标程序中
动态库的链接方式:在目标代码执行时才会去指定位置搜索库文件
动态库的优点:动态库变化不影响目标代码,无须修改目标代码即可使用更新后的库函数,动态库在运行时内存占用小,因为只会载入库中用到的函数模块。
动态库的缺点:依赖外部