标签:提取 tab tor 组成 c++ 语言特性 generator vector 扩展
学习 Golang,有时需要 Cgo,所以需要学习 C、C++。
语言入门:
https://item.jd.com/12580612.html
https://item.jd.com/28326539330.html
参见: https://github.com/hunterhug/cmake_example
稍微很长的历史,来源网络,可以先跳过。
C 语言之所以命名为 C,是因为 C 语言源自 Ken Thompson 发明的 B 语言,而 B 语言则源自 BCPL 语言。
ISO/IEC 9899:1990 - Programming languages -- C
。因为此标准是在1990年发布的,所以有些人把简称作C90标准。不过大多数人依然称之为C89标准,因为此标准与ANSI C89标准完全等同。ISO/IEC 9899:1990/Cor 1:1994
,有些人简称为C94标准。ISO/IEC 9899:1990/Amd 1:1995 - C Integrity
,有些人简称为C95标准。ISO/IEC 9899:1999 - Programming languages -- C
,简称C99标准。这是C语言的第二个官方标准。ISO/IEC 9899:2011 - Information technology -- Programming languages -- C
,简称C11标准,原名C1X。这是C语言的第三个官方标准,也是C语言的最新标准。标准: C89/ANSIC/C90 -- C94 -- C95 -- C99 -- C11 -C18
最初导致C++诞生的原因是在Bjarne博士等人试图去分析UNIX的内核的时候,这项工作开始于1979年4月,当时由于没有合适的工具能够有效的分析由于内核分布而造成的网络流量,以及怎样将内核模块化。
同年10月,Bjarne博士完成了一个可以运行的预处理程序,称之为Cpre,它为C加上了类似Simula的类机制。在这个过程中,Bjarne博士开始思考是不是要开发一种新的语言,当时贝尔实验室对这个想法很感兴趣,就让Bjarne博士等人组成一个开发小组,专门进行研究。
当时不是叫做C++,而是C with class,这是把它当作一种C语言的有效扩充。由于当时C语言在编程界居于老大的地位,要想发展一种新的语言,最强大的竞争对手就是C语言,所以当时有两个问题最受关注:C++要在运行时间、代码紧凑性和
数据紧凑性方面能够与C语言相媲美,但是还要尽量避免在语言应用领域的限制。在这种情况下,一个很自然的想法就是让C++从C语言继承过来,但是我们的Bjarne博士更具有先见之明,他为了避免受到C语言的局限性,参考了很多的语言,例如:从Simula继承了类的概念,从Algol68继承了运算符重载、引用以及在任何地方声明变量的能力
第一个带有类编译器的C被称为Cfront,它来自于名为CPre的C编译器。它是一个用类代码将C语言翻译成普通C语言的程序。值得注意的一点是Cfront大部分是用C语言编写的,这使得它成为了一个自托管编译器(一个可以自己编译的编译器)。Cfront后来在1993年被抛弃,因为很难将新的特性集成到其中,即c++异常。尽管如此,Cfront对未来编译器的实现和Unix操作系统产生了巨大的影响。
下面让我们来一起看一下C++历史上的主要事件:
c++ ISO/IEC 14882:1998
的第一个国际标准,也就是所谓的c++ 98。注释的c++参考手册据说对标准的开发有很大的影响。标准模板库从1979年开始概念开发,也包括在内。标准: C++98 -- C++03 -- C++11 -- C++14 -- C++17 -- C++20
STL(Standard Template Library,标准模板库)是惠普实验室开发的一系列软件的统称。它是由Alexander Stepanov、Meng Lee和David R Musser在惠普实验室工作时所开发出来的。STL的代码从广义上讲分为三类:algorithm(算法)、container(容器)和iterator(迭代器),几乎所有的代码都采用了模板类和模版函数的方式,这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。
在C++标准中,STL被组织为下面的13个头文件:<algorithm>、<deque>、<functional>、<iterator>、<vector>、<list>、<map>、<memory>、<numeric>、<queue>、<set>、<stack>和<utility>
。
Boost 库通过加入一些在实践中非常有用的函数对 C++ 标准进行了补充。 由于 Boost C++ 库是基于 C++ 标准的,所以它们是使用最先进的 C++ 来实现的。 它们是平台独立的,并由于有一个大型的开发人员社区,它可以被包括 Windows 和 Linux 在内的许多操作系统所支持。 Boost库由C++标准委员会库工作组成员发起,其中有些内容有望成为下一代C++标准库内容。在C++社区中影响甚大,是不折不扣的“准”标准库。Boost由于其对跨平台的强调,对标准C++的强调,与编写平台无关。大部分boost库功能的使用只需包括相应头文件即可,少数(如正则表达式库,文件系统库等)需要链接库。可以说,Boost库是为C++语言标准库提供扩展的一些C++程序库的总称。C++11, C++14很多特性都是从boost库来的,如果所在的公司使用的编译器可以支持C++11或以上那么可以先用标准的来就行,不够再去翻翻boost。
编译程序需要使用各种参数,所以很麻烦,出现了不同的make构建工具,这些工具遵循不同的规范和标准,所执行的Makefile格式也不同,但如果想让软件跨平台,必须要保证能够在不同平台编译,但使用这些make工具就得为每一种标准写一次Makefile,因此CMake应运而生。CMake首先允许开发者编写一种与平台无关的CMakeList.txt来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的本地化Makefile和工程文件,如Unix的Makefile或Windows的Visual Studio。
一般程序写完后需要编译成二进制机器码,然后再调用执行。
下面是一个本人工作中遇到的经典编译:
gcc read.c -o read.exe -g -Wall "common/common.c" "common/ss_help.c" "lib/base64/base64.c" "lib/cJSON/cJSON.c" -I"../include" -I"./common" -I"./lib" -I"./lib/base64" -I"./lib/cJSON" -lslm_runtime -lslm_control -lss_user_login -lm -ldl -lpthread -L"./" -L"./../lib64" -Wl,-rpath="./" -Wl,-rpath="./../lib64"
具体参数解释如下:
gcc 编译工具
read.c 主函数文件
-o read.exe 编译成read.exe二进制
-g 可以使用gdb调试
-Wall 生成所有警告信息
"common/common.c" "common/ss_help.c" "lib/base64/base64.c" "lib/cJSON/cJSON.c" 一起编译的其他C源文件
-I"../include" -I"./common" -I"./lib" -I"./lib/base64" -I"./lib/cJSON" 依赖的头文件所在目录
-lslm_runtime -lslm_control -lss_user_login -lm -ldl -lpthread 链接的动态库 libslm_runtime等,可用ldd read.exe查看
-L"./" -L"./../lib64" 放在/lib和/usr/lib和/usr/local/lib里的库直接用-l参数就能链接,否则要加上库文件所在的目录名
-Wl,-rpath=./ 编译时指定的 -L 的目录,只是在程序链接成可执行文件时使用的。程序执行时动态链接库加载不到动态链接库,所以要加上。或者加上环境变量也可以:export LD_LIBRARY_PATH
是有点麻烦,因此使用构建工具很有必要。我们先重温一下编译过程,再学习构建工具。
*.c文件 -- 预处理 -- 编译 -- 汇编 -- 链接 -- 可执行函数
预处理器->编译器->汇编器->链接器
查看:
cd Demo0
1.编辑 hello.c:
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("hello world!\n");
return 0;
}
2.预处理过程实质上是处理“#”,将#include包含的头文件直接拷贝到hell.c当中;将#define定义的宏进行替换,同时将代码中没用的注释部分删除等。
具体做的事儿如下:
(1)将所有的#define删除,并且展开所有的宏定义。说白了就是字符替换
(2)处理所有的条件编译指令,#ifdef #ifndef #endif等,就是带#的那些
(3)处理#include,将#include指向的文件插入到该行处
(4)删除所有注释
(5)添加行号和文件标示,这样的在调试和编译出错的时候才知道是是哪个文件的哪一行
(6)保留#pragma编译器指令,因为编译器需要使用它们。
预编译:
gcc -E hello.c -o a.i
cat a.i
3.编译的过程实质上是把高级语言翻译成机器语言的过程,即对a.c做:
(1)词法分析,
(2)语法分析
(3)语义分析
(4)优化后生成相应的汇编代码
从 高级语言->汇编语言->机器语言(二进制)
编译器生成汇编代码:
gcc -S a.i -o a.s
cat a.s
汇编器生成机器代码:
gcc -c hello.c -o a.o
4.链接器将翻译成的二进制与需要用到库绑定在一块。它将所有二进制形式的目标文件和系统组件组合成一个可执行文件:
gcc a.o -o a
./a
hello world!
更直接生成不同环节的文件,省缺中间环节:
# 预处理
gcc -E hello.c -o a.i
# 编译
gcc -S hello.c -o a.s
# 汇编
gcc -c hello.c -o a.o
# 链接
gcc hello.c -o a
根据链接方式的不同,链接过程可以分为:
文件夹下有三个文件:
cd link
tree
├── add.c
├── add.h
└── main.c
add.h:
#ifndef __ADD_H__
#define __ADD_H_
int add(int a, int b);
#endif
add.c:
#include "add.h"
int add(int a, int b)
{
return a+b;
}
main.c:
#include "add.h"
#include "stdio.h"
int main()
{
int sum = add(5,6);
printf("%d",sum);
return 0;
}
我们可以将 add.c 封装成静态库和动态库来使用。
首先将 add.c 编译成目标文件:
gcc -c add.c -o add.o
然后根据目标文件生成静态库:
ar -cr libadd.a add.o
ar命令可以用来创建、修改库,也可以从库中提出单个模块
如果把多个 .o 文件插入库 .a 里,只需要在后面用空格分开写出来,格式:
ar -cr 静态库libname.a name1.o name2.o
生成静态库 l ibadd.a 后,编译 main.c 连接静态库生成可执行文件main:
gcc main.c -o main -I./ -L./ -ladd
./main
动态库需要使用 -shared 选项以及 -fPIC 选项
gcc -c -fPIC add.c -o a.o
-fPIC表示代码是和地址无关的,不需要被不同模块重定位
然后根据目标文件生成动态库.so文件
gcc -shared -o libadd.so a.o
-shared 选项表示生成的是.so动态库文件
上面的步骤可以直接写成:
gcc -shared -fPIC main.c -o libadd.so
链接动态库:
gcc main.c -o main -I./ -L./ -ladd
运行:
./main
./main: error while loading shared libraries: libadd.so: cannot open shared object file: No such file or directory
我们现在有几种方式配置动态库的方法:
export LD_LIBRARY_PATH="./:$LD_LIBRARY_PATH"
./main
这种方式将动态库路径配置到LD_LIBRARY_PATH,只是暂时生效。
或者使用 ldconfig 机制(需 root 权限):
参考: Cmake,以下所有例子都是转载的网络并自己实验一遍的。
你或许听过好几种 Make 工具,例如 GNU Make ,QT 的 qmake ,微软的 MS nmake,BSD Make(pmake),Makepp,等等。这些 Make 工具遵循着不同的规范和标准,所执行的 Makefile 格式也千差万别。这样就带来了一个严峻的问题:如果软件想跨平台,必须要保证能够在不同平台编译。而如果使用上面的 Make 工具,就得为每一种标准写一次 Makefile ,这将是一件让人抓狂的工作。
CMake就是针对上面问题所设计的工具:它首先允许开发者编写一种平台无关的 CMakeList.txt 文件来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。从而做到“Write once, run everywhere”。显然,CMake 是一个比上述几种 make 更高级的编译配置工具。一些使用 CMake 作为项目架构系统的知名开源项目有 VTK、ITK、KDE、OpenCV、OSG 等。
在 linux 平台下使用 CMake 生成 Makefile 并编译的流程如下:
Debian/Ubuntu等系列apt套件安装:
sudo apt install cmake
cmake --version
可以进入查看:
cd Demo1
计算指数乘方的 main.c:
#include <stdio.h>
#include <stdlib.h>
/**
* power - Calculate the power of number.
* @param base: Base value.
* @param exponent: Exponent value.
*
* @return base raised to the power exponent.
*/
double power(double base, int exponent)
{
int result = base;
int i;
if (exponent == 0) {
return 1;
}
for(i = 1; i < exponent; ++i){
result = result * base;
}
return result;
}
int main(int argc, char *argv[])
{
if (argc < 3){
printf("Usage: %s base exponent \n", argv[0]);
return 1;
}
double base = atof(argv[1]);
int exponent = atoi(argv[2]);
double result = power(base, exponent);
printf("%g ^ %d is %g\n", base, exponent, result);
return 0;
}
CMakeLists.txt:
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo1)
# 指定生成目标
add_executable(main main.c)
上面的 CMakeLists.txt 文件,依次出现了几个命令:
执行:
cmake .
make
./main 2 3
可以进入查看:
cd Demo2
把 power
拆出来,变成两个源文件。
├── main.c
├── MathFunctions.c
└── MathFunctions.h
main.c:
#include <stdio.h>
#include <stdlib.h>
#include "MathFunctions.h"
int main(int argc, char *argv[])
{
if (argc < 3){
printf("Usage: %s base exponent \n", argv[0]);
return 1;
}
double base = atof(argv[1]);
int exponent = atoi(argv[2]);
double result = power(base, exponent);
printf("%g ^ %d is %g\n", base, exponent, result);
return 0;
}
MathFunctions.h:
#ifndef POWER_H
#define POWER_H
extern double power(double base, int exponent);
#endif
CMakeLists.txt:
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo2)
# 查找目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 指定生成目标
add_executable(main ${DIR_SRCS})
本来多个源文件可以使用add_executable(main main.c MathFunctions.c)
,但源文件一多就抓急,所以使用:
# 查找目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
执行:
cmake .
make
./main 2 3
可以进入查看:
cd Demo3
tree
├── CMakeLists.txt
├── main.c
└── math
├── CMakeLists.txt
├── MathFunctions.c
└── MathFunctions.h
需要分别在项目根目录 Demo3 和 math 目录里各编写一个 CMakeLists.txt 文件。为了方便,我们可以先将 math 目录里的文件编译成静态库再由 main 函数调用。
根目录的 CMakeLists.txt:
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo3)
# 查找目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 添加 math 子目录
add_subdirectory(math)
# 指定生成目标
add_executable(main ${DIR_SRCS})
# 添加链接库
target_link_libraries(main MathFunctions)
使用命令 add_subdirectory
指明本项目包含一个子目录 math,这样 math 目录下的 CMakeLists.txt 文件和源代码也会被处理。
使用命令 target_link_libraries
指明可执行文件 main 需要连接一个名为 MathFunctions 的链接库 。
根目录的 CMakeLists.txt:
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_LIB_SRCS 变量
aux_source_directory(. DIR_LIB_SRCS)
# 指定生成 MathFunctions 链接库
add_library (MathFunctions ${DIR_LIB_SRCS})
在该文件中使用命令 add_library
将 src 目录中的源文件编译为静态链接库:libMathFunctions.a
。
执行:
cmake .
make
./main 2 3
CMake 允许为项目增加编译选项,从而可以根据用户的环境和需求选择最合适的编译方案。
可以进入查看:
cd Demo4
tree
├── CMakeLists.txt
├── config.h.in
├── main.c
└── math
├── CMakeLists.txt
├── MathFunctions.c
└── MathFunctions.h
main.c:
#include <stdio.h>
#include <stdlib.h>
#include <config.h>
#ifdef USE_MYMATH
#include <MathFunctions.h>
#else
#include <math.h>
#endif
int main(int argc, char *argv[])
{
if (argc < 3){
printf("Usage: %s base exponent \n", argv[0]);
return 1;
}
double base = atof(argv[1]);
int exponent = atoi(argv[2]);
#ifdef USE_MYMATH
printf("Now we use our own Math library. \n");
double result = power(base, exponent);
#else
printf("Now we use the standard library. \n");
double result = pow(base, exponent);
#endif
printf("%g ^ %d is %g\n", base, exponent, result);
return 0;
}
我们定义了一个宏:USE_MYMATH
来决定使用我们自己的库还是标准库的。
这里引用了一个 config.h
文件,这个文件预定义了 USE_MYMATH 的值。但我们并不直接编写这个文件。
根目录的 CMakeLists.txt:
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo4)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
# 加入一个配置头文件,用于处理 CMake 对源码的设置
configure_file (
"${PROJECT_SOURCE_DIR}/config.h.in"
"${PROJECT_BINARY_DIR}/config.h"
)
# 是否使用自己的 MathFunctions 库
option (USE_MYMATH
"Use provided math implementation" ON)
# 是否加入 MathFunctions 库
if (USE_MYMATH)
include_directories ("${PROJECT_SOURCE_DIR}/math")
add_subdirectory (math)
set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
else ()
set (EXTRA_LIBS ${EXTRA_LIBS} m)
endif (USE_MYMATH)
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 指定生成目标
add_executable (main ${DIR_SRCS})
# 添加链接库
target_link_libraries (main ${EXTRA_LIBS})
其中:
1.set(CMAKE_INCLUDE_CURRENT_DIR ON)
。自定义变量使用SET(OBJ_NAME xxxx)
,使用时${OBJ_NAME}
,
这个CMAKE_INCLUDE_CURRENT_DIR
参数默认OFF,当ON时表示${CMAKE_CURRENT_SOURCE_DIR}
和 ${CMAKE_CURRENT_BINARY_DIR}
被加入到include path中,即头文件路径中。
cmake的常用变量:
CMAKE_BINARY_DIR,PROJECT_BINARY_DIR,_BINARY_DIR:这三个变量内容一致,如果是内部编译,就指的是工程的顶级目录,如果是外部编译,指的就是工程编译发生的目录。
CMAKE_SOURCE_DIR,PROJECT_SOURCE_DIR,_SOURCE_DIR:这三个变量内容一致,都指的是工程的顶级目录。
CMAKE_CURRENT_BINARY_DIR:外部编译时,指的是target目录,内部编译时,指的是顶级目录
CMAKE_CURRENT_SOURCE_DIR:CMakeList.txt所在的目录
CMAKE_CURRENT_LIST_DIR:CMakeList.txt的完整路径
CMAKE_CURRENT_LIST_LINE:当前所在的行
CMAKE_MODULE_PATH:如果工程复杂,可能需要编写一些cmake模块,这里通过SET指定这个变量
LIBRARY_OUTPUT_DIR,BINARY_OUTPUT_DIR:库和可执行的最终存放目录
2.configure_file
命令用于加入一个配置头文件 config.h ,这个文件由 CMake 从 config.h.in 生成,通过这样的机制,将可以通过预定义一些参数和变量来控制代码的生成。
3.option
命令添加了一个 USE_MYMATH 选项,并且默认值为 ON。
4.include_directories ("${PROJECT_SOURCE_DIR}/math")
指定头文件路径。
5.set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
表示设置${EXTRA_LIBS
}和MathFunctions
两个作为数组设置进变量,为后面链接库做准备。
config.h.in:
#cmakedefine USE_MYMATH
这样 CMake 会自动根据 CMakeLists 配置文件中的设置自动生成 config.h 文件。
为了便于交互式的选择该变量的值,可以使用 ccmake 命令来编译:
安装 ccmake 和创建编译目录:
sudo apt install cmake-curses-gui
mkdir build
编译:
cd build
ccmake ..
EMPTY CACHE
EMPTY CACHE:
Press [enter] to edit option CMake Version 3.5.1
Press [c] to configure
Press [h] for help Press [q] to quit without generating
Press [t] to toggle advanced mode (Currently On)
按键盘的方向键可以在不同的选项窗口间跳转,按下 enter 键可以修改该选项。修改完成后可以按下 c 选项完成配置,之后再按 g 键确认生成 Makefile 。ccmake 的其他操作可以参考窗口下方给出的指令提示。
make
./main 2 3
Now we use our own Math library.
2 ^ 3 is 8
我们再清除一下 build
目录然后更改选项:
mkdir build
cd build
ccmake ..
make
./main 2 3
Now we use the standard library.
2 ^ 3 is 8
CMake 也可以指定安装规则,以及添加测试。这两个功能分别可以通过在产生 Makefile 后使用 make install 和 make test 来执行。在以前的 GNU Makefile 里,你可能需要为此编写 install 和 test 两个伪目标和相应的规则,但在 CMake 里,这样的工作同样只需要简单的调用几条命令。
可以进入查看:
cd Demo5
首先先在 math/CMakeLists.txt
文件里添加下面两行:
# 指定 MathFunctions 库的安装路径
install (TARGETS MathFunctions DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)
指明 MathFunctions 库的安装路径。将目标TARGETS
静态库libMathFunctions
安装在bin
文件夹中,而将文件FILES
头文件MathFunctions.h
安装在include
文件夹中。
之后同样修改根目录的 CMakeLists 文件,在末尾添加下面几行:
# 指定安装路径
install (TARGETS main DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/config.h"
DESTINATION include)
通过上面的定制,生成的 main 文件和 MathFunctions 函数库 libMathFunctions.o 文件将会被复制到 /usr/local/bin 中,而 MathFunctions.h 和生成的 config.h 文件则会被复制到 /usr/local/include 中。
编译:
mkdir build
cd build
ccmake ..
make
sudo make install
[ 50%] Built target MathFunctions
[100%] Built target main
Install the project...
-- Install configuration: ""
-- Installing: /usr/local/bin/main
-- Installing: /usr/local/include/config.h
-- Installing: /usr/local/lib/libMathFunctions.a
-- Installing: /usr/local/include/MathFunctions.h
main 2 3
Now we use our own Math library.
2 ^ 3 is 8
测试完毕我们清除 install 的文件:
sudo rm -rf /usr/local/bin/main
sudo rm -rf /usr/local/include/config.h
sudo rm -rf /usr/local/lib/libMathFunctions.a
sudo rm -rf /usr/local/include/MathFunctions.h
添加测试同样很简单。CMake 提供了一个称为 CTest 的测试工具。我们要做的只是在项目根目录的 CMakeLists 文件中调用一系列的 add_test 命令。
仍然是 Demo5。
根目录的 CMakeLists 文件添加:
# 启用测试
enable_testing()
# 测试程序是否成功运行
add_test (test_run main 5 2)
# 测试帮助信息是否可以正常提示
add_test (test_usage main)
set_tests_properties (test_usage
PROPERTIES PASS_REGULAR_EXPRESSION "Usage: .* base exponent")
# 测试 5 的平方
add_test (test_5_2 main 5 2)
set_tests_properties (test_5_2
PROPERTIES PASS_REGULAR_EXPRESSION "is 25")
# 测试 10 的 5 次方
add_test (test_10_5 main 10 5)
set_tests_properties (test_10_5
PROPERTIES PASS_REGULAR_EXPRESSION "is 100000")
# 测试 2 的 10 次方
add_test (test_2_10 main 2 10)
set_tests_properties (test_2_10
PROPERTIES PASS_REGULAR_EXPRESSION "is 1024")
上面的代码包含了四个测试。第一个测试 test_run 用来测试程序是否成功运行并返回 0 值。
剩下的三个测试分别用来测试 5 的 平方、10 的 5 次方、2 的 10 次方是否都能得到正确的结果。其中 PASS_REGULAR_EXPRESSION
用来测试输出是否包含后面跟着的字符串。
add_test (test_5_2 main 5 2)
set_tests_properties (test_5_2
PROPERTIES PASS_REGULAR_EXPRESSION "is 25")
加了一个叫test_5_2
名字的测试测试,检查输出是否是is 25
。
执行测试:
make test
Running tests...
Test project /opt/cmake_example/Demo5/build
Start 1: test_run
1/5 Test #1: test_run ......................... Passed 0.00 sec
Start 2: test_usage
2/5 Test #2: test_usage ....................... Passed 0.00 sec
Start 3: test_5_2
3/5 Test #3: test_5_2 ......................... Passed 0.00 sec
Start 4: test_10_5
4/5 Test #4: test_10_5 ........................ Passed 0.00 sec
Start 5: test_2_10
5/5 Test #5: test_2_10 ........................ Passed 0.00 sec
如果有很多的测试要写,很麻烦,所以可以定义一个重复利用的宏:
# 定义一个宏,用来简化测试工作
macro (do_test arg1 arg2 result)
add_test (test_${arg1}_${arg2} main ${arg1} ${arg2})
set_tests_properties (test_${arg1}_${arg2}
PROPERTIES PASS_REGULAR_EXPRESSION ${result})
endmacro (do_test)
# 利用 do_test 宏,测试一系列数据
do_test (5 2 "is 25")
do_test (10 5 "is 100000")
do_test (2 10 "is 1024")
产生的效果是一样的。
让 CMake 支持 gdb 的设置也很容易,只需要指定 Debug 模式下开启 -g 选项:
set(CMAKE_BUILD_TYPE "Debug")
set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
执行并调试:
ccmake .
make
gdb main
(gdb) r 2 3
Starting program: /opt/cmake_example/Demo5/build/main 2 3
Now we use our own Math library.
2 ^ 3 is 8
(gdb) l
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <config.h>
4
5 #ifdef USE_MYMATH
6 #include <MathFunctions.h>
7 #else
8 #include <math.h>
9 #endif
10
(gdb) b 18
Breakpoint 1 at 0x4008ae: file /opt/cmake_example/Demo5/main.c, line 18.
(gdb) r 2 3
Starting program: /opt/cmake_example/Demo5/build/main 2 3
Breakpoint 1, main (argc=3, argv=0x7fffffffdc48) at /opt/cmake_example/Demo5/main.c:18
18 double base = atof(argv[1]);
(gdb) c
Continuing.
Now we use our own Math library.
2 ^ 3 is 8
[Inferior 1 (process 3119) exited normally]
gdb调试指令一条龙:
有时候可能要对系统环境做点检查,例如要使用一个平台相关的特性的时候。在这个例子中,我们检查系统是否自带 pow 函数。如果带有 pow 函数,就使用它;否则使用我们定义的 power 函数。
可以进入查看:
cd Demo6
根目录的 CMakeLists:
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo6)
set (CMAKE_INCLUDE_CURRENT_DIR ON)
# 检查系统是否支持 pow 函数
message ("doing check pow")
include (${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake)
check_function_exists (pow HAVE_POW)
check_function_exists (printf HAVE_XXXX)
# 加入一个配置头文件,用于处理 CMake 对源码的设置
configure_file (
"${PROJECT_SOURCE_DIR}/config.h.in"
"${PROJECT_BINARY_DIR}/config.h"
)
# 是否加入 MathFunctions 库
if (NOT HAVE_POW)
include_directories ("${PROJECT_SOURCE_DIR}/math")
add_subdirectory (math)
set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (NOT HAVE_POW)
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 指定生成目标
add_executable(main ${DIR_SRCS})
target_link_libraries (main ${EXTRA_LIBS})
# 指定安装路径
install (TARGETS main DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/config.h"
DESTINATION include)
# 启用测试
enable_testing()
# 测试程序是否成功运行
add_test (test_run main 5 2)
# 测试帮助信息是否可以正常提示
add_test (test_usage main)
set_tests_properties (test_usage
PROPERTIES PASS_REGULAR_EXPRESSION "Usage: .* base exponent")
# 定义一个宏,用来简化测试工作
macro (do_test arg1 arg2 result)
add_test (test_${arg1}_${arg2} main ${arg1} ${arg2})
set_tests_properties (test_${arg1}_${arg2}
PROPERTIES PASS_REGULAR_EXPRESSION ${result})
endmacro (do_test)
# 利用 do_test 宏,测试一系列数据
do_test (5 2 "is 25")
do_test (10 5 "is 100000")
do_test (2 10 "is 1024")
其中添加 CheckFunctionExists.cmake 宏,并调用 check_function_exists 命令测试链接器是否能够在链接阶段找到 pow 函数:
# 检查系统是否支持 pow 函数
message ("doing check pow")
include (${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake)
check_function_exists (pow HAVE_POW)
check_function_exists (printf HAVE_XXXX)
如果存在,宏 HAVE_POW 将会被设置。
执行:
mkdir build
cd build
cmake ..
doing check pow
-- Looking for pow
-- Looking for pow - not found
-- Looking for printf
-- Looking for printf - found
make
为什么 pow 找不到呢,而 printf 找得到,因为数学函数位于libm.so库文件中(这些库文件通常位于/lib目录下),-lm选项告诉编译器,我们程序中用到的数学函数要到这个库文件里找。printf位于libc.so库文件中,使用libc.so中的库函数在编译时不需要加-lc选项,当然加了也不算错,因为这个选项是gcc的默认选项。
可以进入查看:
cd Demo7
给项目添加和维护版本号是一个好习惯,这样有利于用户了解每个版本的维护情况,并及时了解当前所用的版本是否过时,或是否可能出现不兼容的情况。
首先修改顶层 CMakeLists 文件,在 project 命令之后加入如下两行:
set (Demo_VERSION_MAJOR 1)
set (Demo_VERSION_MINOR 0)
分别指定当前的项目的主版本号和副版本号。
之后,为了在代码中获取版本信息,我们可以修改 config.h.in 文件,添加两个预定义变量:
// the configured options and settings for Tutorial
#define Demo_VERSION_MAJOR @Demo_VERSION_MAJOR@
#define Demo_VERSION_MINOR @Demo_VERSION_MINOR@
此时 main.c:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "config.h"
#include "math/MathFunctions.h"
int main(int argc, char *argv[])
{
if (argc < 3){
// print version info
printf("%s Version %d.%d\n",
argv[0],
Demo_VERSION_MAJOR,
Demo_VERSION_MINOR);
printf("Usage: %s base exponent \n", argv[0]);
return 1;
}
double base = atof(argv[1]);
int exponent = atoi(argv[2]);
#if defined (HAVE_POW)
printf("Now we use the standard library. \n");
double result = pow(base, exponent);
#else
printf("Now we use our own Math library. \n");
double result = power(base, exponent);
#endif
printf("%g ^ %d is %g\n", base, exponent, result);
return 0;
}
编译后可以打印版本号:
mkdir build
cd build
cmake ..
make
./main
./main Version 1.0
Usage: ./main base exponent
我们要学习如何配置生成各种平台上的安装包,包括二进制安装包和源码安装包。为了完成这个任务,我们需要用到 CPack ,它同样也是由 CMake 提供的一个工具,专门用于打包。
这是最重要的部分,因为我们工程完成后需要交付或者部署。
可以进入查看:
cd Demo8
我们在顶层 CMakeLists 文件最后添加:
# 构建一个 CPack 安装包
include (InstallRequiredSystemLibraries)
set (CPACK_RESOURCE_FILE_LICENSE
"${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set (CPACK_PACKAGE_VERSION_MAJOR "${Demo_VERSION_MAJOR}")
set (CPACK_PACKAGE_VERSION_MINOR "${Demo_VERSION_MINOR}")
include (CPack)
执行:
mkdir build
cd build
cmake ..
cpack -C CPackConfig.cmake
cpack -C CPackConfig.cmake
CPack: Create package using STGZ
CPack: Install projects
CPack: - Run preinstall target for: Demo8
CPack: - Install project: Demo8
CPack: Create package
CPack: - package: /opt/cmake_example/Demo8/build/Demo8-1.0.1-Linux.sh generated.
CPack: Create package using TGZ
CPack: Install projects
CPack: - Run preinstall target for: Demo8
CPack: - Install project: Demo8
CPack: Create package
CPack: - package: /opt/cmake_example/Demo8/build/Demo8-1.0.1-Linux.tar.gz generated.
CPack: Create package using TZ
CPack: Install projects
CPack: - Run preinstall target for: Demo8
CPack: - Install project: Demo8
CPack: Create package
CPack: - package: /opt/cmake_example/Demo8/build/Demo8-1.0.1-Linux.tar.Z generated.
执行 cpack -C CPackConfig.cmake 命令后生成三个二进制包:
Demo8-1.0.1-Linux.sh Demo8-1.0.1-Linux.tar.gz Demo8-1.0.1-Linux.tar.Z
这 3 个二进制包文件所包含的内容是完全相同的。我们可以执行其中一个 Demo8-1.0.1-Linux.sh 。此时会出现一个由 CPack 自动生成的交互式安装界面:
./Demo8-1.0.1-Linux.sh
Do you accept the license? [yN]:
y
By default the Demo8 will be installed in:
"/opt/cmake_example/Demo8/build/Demo8-1.0.1-Linux"
Do you want to include the subdirectory Demo8-1.0.1-Linux?
Saying no will install in: "/opt/cmake_example/Demo8/build" [Yn]:
y
安装好了:
cd Demo8-1.0.1-Linux/bin
./main
可以参考 OpenSSL with CMake build system
顶层 CMakeLists 文件:
project( openssl )
cmake_minimum_required( VERSION 2.8.3 )
set(BUILD_OBJECT_LIBRARY_ONLY OFF BOOL)
include_directories ( BEFORE SYSTEM ${CMAKE_CURRENT_BINARY_DIR}/crypto ${CMAKE_CURRENT_BINARY_DIR}/ssl crypto . )
add_definitions( -DOPENSSL_NO_ASM )
if( WIN32 AND NOT CYGWIN )
add_definitions( -DOPENSSL_SYSNAME_WIN32 )
add_definitions( -DWIN32_LEAN_AND_MEAN )
endif ( )
if( MINGW )
set( CMAKE_SHARED_LINKER_FLAGS "-Wl,--export-all" )
endif()
add_subdirectory( crypto )
add_subdirectory( ssl )
if( BUILD_OBJECT_LIBRARY_ONLY)
file( COPY e_os2.h DESTINATION ${CMAKE_CURRENT_BINARY_DIR} )
else()
add_subdirectory( apps )
install( FILES e_os2.h DESTINATION include/openssl )
install( FILES tools/c_hash tools/c_info tools/c_issuer tools/c_name tools/c_rehash
FAQ LICENSE PROBLEMS README README.ASN1 README.ENGINE
DESTINATION share/openssl )
install( DIRECTORY doc DESTINATION ./ )
install( FILES e_os2.h DESTINATION include/openssl )
# Generate the package target
set( CPACK_GENERATOR ZIP TGZ )
set( CPACK_PACKAGE_NAME "openssl-cmake" )
set( CPACK_PACKAGE_VERSION_MAJOR 1 )
set( CPACK_PACKAGE_VERSION_MINOR 0 )
set( CPACK_PACKAGE_VERSION_PATCH 1u )
include( CPack )
endif()
更多参考: https://cmake.org/cmake-tutorial/
readelf命令,一般用于查看ELF格式的文件信息,常见的文件如在Linux上的可执行文件,动态库(.so)或者静态库(.a) 等包含ELF格式的文件。
readelf -dl main
动态库路径设置:
export LD_LIBRARY_PATH=LD_LIBRARY_PATH:/XXX
./main
标签:提取 tab tor 组成 c++ 语言特性 generator vector 扩展
原文地址:https://www.cnblogs.com/nima/p/11751382.html