目录
编译器
厂商 C C++
GNU gcc g++
main.cpp
#include <cstdio>
int main() {
printf("Hello, world!\n");
return 0;
}
编译器, 是一个根据源代码生成机器码的程序
g++ main.cpp -o a.out
调用编译器程序g++, 读取main.cpp中源码, 根据C++标准生成相应的机器指令码, 输出到a.out可执行文件
./a.out
执行可执行文件
考究命令
objdump -D a.out | less
//将可执行文件a.out 转成汇编语言, less命令是的查看可以上下滚动
richard@rich:~/workspace$ ldd a.out
linux-vdso.so.1 (0x00007ffdce762000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa4d24d1000)
/lib64/ld-linux-x86-64.so.2 (0x00007fa4d2ac4000)
多文件编译与链接
hello.cpp
#include <cstdio>
void hello() {
printf("Hello, world\n");
}
main.cpp
#include <cstdio>
void hello();
int main() {
hello();
return 0;
}
g++ -c hello.cpp -o hello.o
g++ -c main.cpp -o main.o
g++ hello.o main.o -o a.out
使用 -c 选项指定生成临时的对象文件,再根据一系列对象文件进行链接,得到最终的a.out
如果单独改动hello.cpp文件的话,只需要执行
g++ -c hello.cpp -o hello.o
g++ hello.o main.o -o a.out
Makefile构建系统
make 这个程序,只需写出不同文件之间的依赖关系,和生成各文件的规则
特点
1. 当只更新了hello.cpp时只会重新编译hello.o, 不需要将main.o重新编译
2. 能够自动并行地发起对hello.cpp和main.cpp的编译, 加快编译速度(make -j)
3. 用通配符批量生成构建规则,避免针对每个.cpp和.o重复写 g++ 命令(%.o: %.cpp)
文件结构
richard@rich:~/workspace$ tree
.
├── hello.cpp
├── main.cpp
└── Makefile
Makefile内容,
a.out: hello.o main.o
g++ hello.o main.o -o a.out
hello.o: hello.cpp
g++ -c hello.cpp -o hello.o
main.o: main.cpp
g++ -c main.cpp -o main.o
执行make
richard@rich:~/workspace$ make
g++ -c hello.cpp -o hello.o
g++ -c main.cpp -o main.o
g++ hello.o main.o -o a.out
无更新执行make
richard@rich:~/workspace$ make
make: 'a.out' is up to date.
Makefile 的构建系统 CMake
跨平台的 CMake 的产生, 只需编写CMakeLists.txt,就能够在调用时生成当前系统所支持的构建系统
目录结构
richard@rich:~/workspace$ tree
.
├── CMakeLists.txt
├── hello.cpp
├── main.cpp
└── run.sh
CMakeLists.txt内容
cmake_minimum_required(VERSION 3.12)
project(hellocmake LANGUAGES CXX)
add_executable(a.out main.cpp hello.cpp)
读取当前目录的 CMakeLists.txt,并在 build 文件夹下生成 build/Makefile
cmake -B build
make -C build 和 cmake -B build一样, 但更跨平台
让 make 读取 build/Makefile,并开始构建 a.out
cmake --build build
执行可执行文件 a.out
build/a.out
合并起来放在 run.sh 中,
cmake -B build
cmake --build build --target a.out
build/a.out
build 目录下生成的文件列表
-rwxrwxr-x 1 richard richard 8368 5月 31 14:06 a.out
-rw-rw-r-- 1 richard richard 12377 5月 31 14:06 CMakeCache.txt
drwxrwxr-x 6 richard richard 4096 5月 31 14:06 CMakeFiles
-rw-rw-r-- 1 richard richard 1622 5月 31 14:06 cmake_install.cmake
-rw-rw-r-- 1 richard richard 5752 5月 31 14:06 Makefile
CMake 中的静态库与动态库
add_executable 生成可执行文件
add_library 生成库文件
# 生成静态库 libtest.a
add_library(test STATIC source1.cpp source2.cpp)
# 生成动态库 libtest.so
add_library(test SHARED source1.cpp source2.cpp)
创建库以后, 要在某个可执行文件中使用该库只需要,
# 为 myexec 链接库 libtest.a
target_link_libraries(myexec PUBLIC test)
CMakeLists.txt内容,
cmake_minimum_required(VERSION 3.12)
project(hellocmake LANGUAGES CXX)
add_library(hellolib STATIC hello.cpp)
add_executable(a.out main.cpp)
target_link_libraries(a.out PUBLIC hellolib)
生成的文件列表,
richard@rich:~/workspace/build$ ls -ll
total 48
-rwxrwxr-x 1 richard richard 8368 5月 31 14:56 a.out
-rw-rw-r-- 1 richard richard 12377 5月 31 14:56 CMakeCache.txt
drwxrwxr-x 7 richard richard 4096 5月 31 14:56 CMakeFiles
-rw-rw-r-- 1 richard richard 1622 5月 31 14:56 cmake_install.cmake
-rw-rw-r-- 1 richard richard 1690 5月 31 14:56 libhellolib.a
-rw-rw-r-- 1 richard richard 6209 5月 31 14:56 Makefile
头文件
hello.cpp
#include <cstdio>
void hello() {
printf("Hello, world\n");
}
hello.h
void hello();
main.cpp
#include <cstdio>
#include "hello.h"
int main() {
hello();
return 0;
}
other.cpp
#include <cstdio>
#include "hello.h"
void otherfunc() {
hello();
}
run.sh
cmake -B build
cmake --build build --target a.out
build/a.out
CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
project(hellocmake LANGUAGES CXX)
add_library(hellolib STATIC hello.cpp)
add_executable(a.out main.cpp other.cpp)
target_link_libraries(a.out PUBLIC hellolib)
递归引入头文件
在 C++ 中常常用到很多的类, 和函数一样, 类的声明也会被放到头文件中
有时候我们的函数声明需要使用到某些类, 需要用到声明了该类的头文件, 需要递归地 #include
MyClass.h
struct MyClass {
int m_number;
};
hello.h
#include "MyClass.h"
void hello(MyClass mc);
hello.cpp
#include <cstdio>
#include "hello.h"
void hello(MyClass mc) {
printf("Hello, my number is %d!\n", mc.m_number);
}
main.cpp
#include <cstdio>
#include "hello.h"
#include "MyClass.h"
int main() {
MyClass mc;
mc.m_number = 42;
hello(mc);
return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
project(hellocmake LANGUAGES CXX)
add_library(hellolib STATIC hello.cpp)
add_executable(a.out main.cpp)
target_link_libraries(a.out PUBLIC hellolib)
如果多个头文件都引用了 MyClass.h, 那么 MyClass 会被重复定义两遍
解决方案, MyClass.h内容如下,
#ifndef MYCLASS_H
#define MYCLASS_H
struct MyClass {
int m_number;
};
#endif
或者在头文件前面加上一行, #pragma once
#pragma once
struct MyClass {
int m_number;
};
CMake子模块
复杂的工程中, 需要划分子模块
把 hellolib 库的源代码移到 hellolib 文件夹下, 该目录下CMakeLists.txt 定义了 hellolib 的生成规则
根目录可以使用 CMake 的 add_subdirectory 添加子目录, 子目录也包含一个 CMakeLists.txt,其中定义的库在 add_subdirectory 之后就可以使用
目录结构如下,
richard@rich:~/workspace$ tree
.
├── CMakeLists.txt
├── hellolib
│ ├── CMakeLists.txt
│ ├── hello.cpp
│ └── hello.h
├── main.cpp
└── run.sh
hellolib 目录下 CMakeLists.txt,
add_library(hellolib STATIC hello.cpp)
根目录下 CMakeLists.txt,
cmake_minimum_required(VERSION 3.12)
project(hellocmake LANGUAGES CXX)
add_subdirectory(hellolib)
add_executable(a.out main.cpp)
target_link_libraries(a.out PUBLIC hellolib)
子模块头文件处理
hello.h 在 hellolib 子文件夹中, main.cpp 需要改成 #include "hellolib/hello.h"
通过 target_include_directories 指定的路径会被视为与系统路径等价
hellolib/CMakeLists.txt
add_library(hellolib STATIC hello.cpp)
target_include_directories(hellolib PUBLIC .)
main.cpp
#include <cstdio>
#include <hello.h>
int main() {
hello();
return 0;
}
CMake其他选项
target_include_directories(myapp PUBLIC /usr/include/eigen3) # 添加头文件搜索目录
target_link_libraries(myapp PUBLIC hellolib) # 添加要链接的库
target_add_definitions(myapp PUBLIC MY_MACRO=1) # 添加一个宏定义
target_add_definitions(myapp PUBLIC -DMY_MACRO=1) # 与 MY_MACRO=1 等价
target_compile_options(myapp PUBLIC -fopenmp) # 添加编译器命令行选项
target_sources(myapp PUBLIC hello.cpp other.cpp) # 添加要编译的源文件
作为纯头文件引入
目录结构
-rwxrw-rw- 1 richard richard 156 4月 13 08:23 CMakeLists.txt
drwxrwxrwx 3 richard richard 4096 5月 30 18:08 glm
-rwxrw-rw- 1 richard richard 270 4月 13 08:23 main.cpp
-rwxrw-rw- 1 richard richard 62 4月 13 08:23 run.sh
使用target_include_directories 引入
CMakeLists.txt内容,
cmake_minimum_required(VERSION 3.12)
project(hellocmake LANGUAGES CXX)
add_executable(a.out main.cpp)
target_include_directories(a.out PUBLIC glm/include)
作为子模块引入
作为 CMake 子模块引入, 即 通过 add_subdirectory
方法就是把那个项目(以fmt为例)的源码放到你工程的根目录, 这些库较友好支持作为子模块引入
-rwxrw-rw- 1 richard richard 166 4月 13 08:23 CMakeLists.txt
drwxrwxrwx 8 richard richard 4096 5月 30 18:08 fmt
-rwxrw-rw- 1 richard richard 169 4月 13 08:23 main.cpp
-rwxrw-rw- 1 richard richard 62 4月 13 08:23 run.sh
CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
project(hellocmake LANGUAGES CXX)
add_subdirectory(fmt)
add_executable(a.out main.cpp)
target_link_libraries(a.out PUBLIC fmt)
CMake 引用系统中预安装的第三方库
以 opencv 为例
find_package(OpenCV REQUIRED)
target_link_libraries(ImageShow PRIVATE ${OpenCV_LIBS})
Ubuntu 安装 opencv ,请参考 Ubuntu 搭建C++ OpenCV 4.6.0和cmake编译
一个显示图片的C++程序,构建项目, 项目结构
richard@rich:~/workspace$ tree
.
├── 1.png
├── CMakeLists.txt
├── main.cpp
└── run.sh
进入ImageShow目录,在目录下新建main.cpp文件
main.cpp
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
using namespace cv;
using namespace std;
int main(int argc, char** argv )
{
Mat image;
image = imread( argv[1], 1 );
namedWindow("Display Image", WINDOW_AUTOSIZE );
imshow("Display Image", image);
waitKey(0);
return 0;
}
CMakeLists.txt
#cmake needs this line
cmake_minimum_required(VERSION 3.1)
#Define project name
project(ImageShow)
#Find OpenCV, you may need to set OpenCV_DIR variable
#to the absolute path to the directory containing OpenCVConfig.cmake file
#via the command line or GUI
find_package(OpenCV REQUIRED)
#Enable C++11
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
#Declare the executable target built from your sources
add_executable(ImageShow main.cpp)
#Link your application with OpenCV libraries
target_link_libraries(ImageShow PRIVATE ${OpenCV_LIBS})
run.sh
cmake -B build
cmake --build build --target ImageShow
build/ImageShow 1.png