C程序编译、链接与项目构建

C程序编译、链接与项目构建

    • 摘要
    • C编译环境
    • 静、动态库
      • 介绍
      • gcc与g++和程序编译、链接
      • Visual Studio创建和链接库
      • 动态库的显示调用
    • Make
      • 介绍
      • 安装
      • 使用
    • CMake
      • 介绍
      • 安装
      • 使用
        • 构建方式
        • 内部构建
        • 外部构建
        • 构建使用静/动态库
        • 常用[系统]变量
        • 常用指令
        • CMake模块
    • Make与CMake的联系与区别

摘要

本篇博客对C/C++程序的编译环境、库创建链接、Make和CMake工具的使用进行介绍,以便加深理解和记忆

C编译环境

  • C编译环境

    • GNU(GNU’s Not Unix):GNU是一个自由软件运动的项目,旨在开发一个类Unix操作系统。GNU项目创建了一系列工具和库,为开发者提供了自由的软件开发环境。其中包括GCC编译器、MinGW和其他开发工具。GNU以开源和自由软件的理念而闻名,为用户提供了更大的自由度和可定制性。

    • GCC(GNU Compiler Collection):GCC是GNU项目的核心组件之一,也是一个开源的编译器集合。它支持多种编程语言,包括C、C++、Objective-C、Fortran等。GCC是一个跨平台的编译器,提供了许多优化选项和功能,以生成高质量的可执行文件。(最早名为GNU Compiler C,针对C)

    • MinGW(Minimalist GNU for Windows):MinGW是一个开源的软件开发工具集,旨在为Windows提供GNU开发环境。它包含了一组用于Windows的头文件和库文件,以及GCC(GNU Compiler Collection)编译器。MinGW使得开发者能够在Windows上开发和编译使用GNU工具链的应用程序,它提供了一种轻量级的方式在Windows环境下进行开发。

    • MSVC(Microsoft Visual C++):MSVC是微软公司开发的一款C++编译器和集成开发环境(IDE)。它是Windows平台上最常用的C++开发工具之一。MSVC提供了丰富的开发工具、调试功能和图形化界面,使得Windows开发变得更加便捷。

  • 区别与联系:MinGW和GCC都属于GNU项目的一部分,它们提供了在Windows环境下进行GNU开发的工具和编译器。GCC是跨平台的编译器集合,MinGW专注于在Windows上提供GNU工具链的支持

静、动态库

介绍

  • 什么是库

库是写好的,成熟的,可复用的代码。本质上来说,库是一种可执行代码的二进制形式,可以被OS载入内存执行。库有两种:静态库(.a、.lib)和动态库(.so、.dll)

  • 静态库

    • 概念:链接阶段,会将汇编生成的目标文件.o/.obj与依赖的静态库(.lib)一起打包到可执行文件中。一个静态库可以简单地看成一组目标文件(.o/.obj)的集合(即很多目标文件经过压缩打包形成的一个文件)
    • 特点
      • 程序对静态库的链接是在编译时完成的
      • 可执行程序在运行时与静态库再无瓜葛,移植方便(因为静态库在编译阶段就已被目标程序链接到一起生成可执行文件)
      • 通过静态链接的可执行程序体积通常较大,因为链接到一起时会占用较多空间
    • 静态库包含的内容
      • .a/.lib二进制文件:它是静态库实际的内容
      • .h头文件,它是静态库中函数、变量、宏定义的声明,以供引用并使用
  • 动态库

    • 为何需要动态库:动态库的出现是为了解决静态库的一些不足:

      • 空间浪费
      • 静态库更新带来的程序全量更新问题:如果静态库更新了,依赖于它的应用程序都需要重新编译、发布给用户(对于用户来说,可能是一个很小的改动,却导致整个程序重新下载,全量更新
        动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新
    • 特点

      • 动态库把库函数的链接载入推迟到程序运行的时期
      • 可以实现进程之间的资源共享(因此动态库也称为共享库)
      • 将程序更新变得简单
      • 可以真正做到链接载入完全由程序员在程序代码中控制(显示调用
    • 动态库包含的内容

      • .dll/.so文件:动态库的实际内容
      • .lib:动态库的入口文件
      • .h:动态库的声明头文件,以供程序引入和使用

gcc与g++和程序编译、链接

  • gcc与g++的联系与区别

    • gcc与g++都是GNU(组织)的一个编译器

    • gcc与g++都可以编译c代码与c++代码。但是:后缀为.c的,gcc把它当做C程序,而g++当做是C++程序;后缀为.cpp的,两者都会认为是C++程序

    • 编译可以用gcc/g++,而链接可以用g++或者gcc -lstdc++。因为gcc命令不能自动和C++程序使用的库联接(当然可以选择手动链接,使用命令如下),所以通常使用g++来完成联接。但在编译阶段,g++会自动调用gcc,二者等价

  • gcc编译过程

    • 预处理:处理#开头的语句,进行文本替换
    gcc -E hello.c -o hello.i
    
    • 编译:检查代码规范性和语法错误,将程序编译为汇编代码(语法检查、代码优化)
    gcc -S hello.i -o hello.s
    
    • 汇编:将汇编代码转换为二进制目标文件(机器可识别)
    gcc -c hello.s -o hello.o
    
    • 链接:将目标文件链接成最终可执行程序(处理目标文件间的依赖关系)
    gcc hello.o -o hello
    
  • 静态链接库

    • 创建静态链接库
# 将所有指定的源文件,都编译成相应的目标文件
g++ -c greeting.cpp  name.cpp
# greeting.cpp name.cpp greeting.o name.o greeting.h name.h
# 将生成的目标文件打包成静态链接库(可以将多个目标文件打成一个链接库)
# 静态链接库的不能随意起名,需遵循如下的命名规则:libxxx.a/libxxx.lib
ar rcs libmyfunction.a name.o greeting.o 
  • 链接静态链接库
# 将主文件编译为目标文件
g++ -c main.cpp
# 链接
g++ -static main.o libmyfunction.a
# -L注定路径,-l指定库名称,中间一般不加空格
# g++ -static main.o -L /home/wohu/cpp/src -lmyfunction
  • 动态链接库

    • 创建动态链接库
    # 1.直接使用源文件创建动态链接库
    # -shared 选项用于生成动态链接库
    # -fpic(还可写成 -fPIC)选项的功能是,令 GCC 编译器生成动态链接库(多个目标文件的压缩包)时,表示各目标文件中函数、类等功能模块的地址使用相对地址,而非绝对地址。这样,无论将来链接库被加载到内存的什么位置,都可以正常使用
    gcc -fpic -shared 源文件名... -o 动态链接库名
    
    # 2.先使用 gcc -c 指令将指定源文件编译为目标文件,再由目标文件生成动态链接库
    g++ -c -fPIC name.cpp greeting.cpp
    # 生成动态链接库
    g++ -shared greeting.o name.o -o libmyfunction.so
    
    • 链接动态链接库
    g++ main.cpp  libmyfunction.so -o main
    

Visual Studio创建和链接库

  • 静态库

    • 创建静态库

    使用VS新建项目 → 选择Win32控制台程序 → 选择应用程序设置 → 勾选静态库 → 编写静态库 → Build项目

    • 链接静态库

      • 方法一:引用的静态库是同一解决方案下的子工程

        • 项目 → 属性 → 项目依赖项:在欲引用静态库的项目中点选静态库依赖项

        • 右键欲引用静态库的项目 → 属性 → 配置属性 → C/C++ → 常规 → 在附加包含目录中键入静态库{name}.h头文件所在文件夹的路径

      • 方法二:右键欲引用静态库的项目 → 属性 → 配置属性 → 链接器 → 命令行:在其他选项中键入完整的静态库.lib路径

      • 方法三

        • 右键欲引用静态库的项目 → 属性 → 配置属性 → 链接器 → 常规 → 附加库目录中键入静态库所在的目录
        • 右键欲引用静态库的项目 → 属性 → 配置属性 → 链接器 → 输入 → 附加依赖项中输入静态库的名称{name}.lib

  • 动态库

    • 创建动态库

      • 创建项目

      • 项目结构

      pch是预编译头文件,通常将一些不怎么变动的头文件预先编译,加快工程编译速度

      dllmain.cpp是dll程序的主文件,其中DllMain函数是dll的入口点,每次这个dll被加载都会执行DllMain,然后根据运行时状态执行不同的命令

      // dllmain.cpp : 定义 DLL 应用程序的入口点。
      #include "pch.h"
      
      BOOL APIENTRY DllMain( HMODULE hModule,
                             DWORD  ul_reason_for_call,
                             LPVOID lpReserved
                           )
      {
          switch (ul_reason_for_call)
          {
          case DLL_PROCESS_ATTACH:   //被程序加载时执行
          case DLL_THREAD_ATTACH:    //被线程加载时执行
          case DLL_THREAD_DETACH:    //被线程卸载时执行
          case DLL_PROCESS_DETACH:   //被程序卸载时执行
              break;
          }
          return TRUE;
      }
      
      • 编写自定义的DLL函数
      ```c
      // dllmain.cpp : 定义 DLL 应用程序的入口点。
      #include "pch.h"
      
      BOOL APIENTRY DllMain( HMODULE hModule,
                           DWORD  ul_reason_for_call,
                           LPVOID lpReserved
                         )
      {
        switch (ul_reason_for_call)
        {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
        }
        return TRUE;
      }
      
      //编写函数 pch.h里面记得要添加include<iostream>
      void test1()
      {
        std::cout << "test1 is worked\n";
      }
      
      void test2()
      {
        std::cout << "test2 is worked\n";
      }
      
      • 将函数或对象暴露给外界:编写完函数或对象,外界还是无法执行的,这就像js里的模块化编程,需要将想要给外界使用的功能暴露(export)出来

        • 方法一:在函数名(对象)前加入暴露给外界的关键字

          • C
          __declspec(dllexport) void test1()
          {
              std::cout << "test1 is worked\n";
          }
          
          • C++:C++支持函数名重载,方法是将原有函数名粉碎,向函数名中添加关于参数的信息,就是说原来的函数名就是test1,但C++会粉碎成?test1@@YAXXZ这样的名字,这也就导致了我们暴露出去的函数名其实根本不是test1。为了解决这个问题:只需要在编写dll的时候在函数前告诉编译器,用C风格来暴露test函数,就不会被粉碎函数名了
          extern "C" __declspec(dllexport) void test1()
          {
              std::cout << "test1 is worked\n";
          }
          

          实际在C++中,我们更倾向于通过对象的方式将方法暴露给外界:

          class __declspec(dllexport) test
          {
              test() {};
          };
          
        • 方法二 :使用模块定义文件(.def):右键项目 → 添加项 → 新建项 → 选择模块定义文件创建

        自定义名称,并在文件中写入(.def文件中以";"作为注释符):

        LIBRARY {dll_project_name}   ;LIBRAY后面跟dll的项目名称
        EXPORTS    ;EXPORTS代表后面的都是要export出去的函数
        test2  ;一行一个函数名
        

      生成dll:在vs的顶部工具栏,依次点击生成 → 生成dll测试 → dll文件在项目文件夹的debug目录

    • 链接动态库

      • 方法一:同一解决方案

        • 右键项目 → 属性 → 通用属性 → 引用 → 添加新引用
        • 右键项目 → 属性 → 配置属性 → C/C++ → 常规 → 附加包含目录 键入动态库.h头文件的路径
      • 方法二:不要求同一解决方案

        • 右键项目 → 属性 → 配置属性 → 链接器 → 常规 → 附加依赖库目录 键入动态库所在目录

        • 右键项目 → 属性 → 配置属性 → 连接器 → 输入 → 附加目录 键入动态库对应的.lib文件(dll的入口)

动态库的显示调用

  • Linux:#include <dlfcn.h>

    • void * dlopen( const char * pathname, int mode ):函数以指定模式打开指定的动态连接库文件,并返回一个句柄给调用进程

    • void* dlsym(void* handle,const char* symbol):dlsym根据动态链接库操作句柄(pHandle)与符号(symbol),返回符号对应的地址。使用这个函数不但可以获取函数地址,也可以获取变量地址

    • int dlclose (void *handle):dlclose用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载

    • const char *dlerror(void):当动态链接库操作函数执行失败时,dlerror可以返回出错信息,返回值为NULL时表示操作函数执行成功

    • Windows

//main.cpp : 测试动态链接库
#include <iostream>

// 函数指针:定义了一个类型名为fun的,指向空的函数指针
// https://blog.csdn.net/qq_35621436/article/details/106085752
typedef void(*func)(); 
int main(void)
{
    /**引入要加载的动态链接库
    HMODULE点进去看的话其实是HINSTANCE的一个别名,就是一个句柄
    如果获取到了,会返回这个动态库的句柄,否则返回NULL**/
    HMODULE dlltest = LoadLibraryW(L"{name}.dll");
    if (dlltest)
    {
        /**获取函数名所在的地址,即函数指针
        获取到的地址默认是void类型,因此要自己定义一个函数指针,进行强制类型转换**/
        func test = (func)GetProcAddress(dlltest, "test");
        if (test) {
            test();
        }
        else {
            MessageBoxW(NULL, L"找不到test方法", L"ERROR", NULL);
        }
    }
    else {
        MessageBoxW(NULL,L"找不到dll",L"ERROR",NULL);
    }
}

Make

Make - GNU Project - Free Software Foundation 跟我一起写Makefile — 跟我一起写Makefile 1.0 文档 (seisman.github.io)

介绍

Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作,提高开发效率。

  • Make 使用 Makefile 文件描述项目的构建过程,其中包含了源文件、目标文件以及编译和链接的命令等信息。Makefile 按照一定的规则解析,将源码和构建过程相互关联起来,执行具体的构建操作,生成目标文件或可执行文件

  • Make 工具的优势在于它可以识别哪些文件被修改了,只编译修改过的部分,以提高构建速度。此外,Make 工具还支持基于条件的编译,也就是预处理器(preprocessor)功能,可以生成不同的输出文件用于不同的平台或不同的运行环境

  • Make 工具具有很好的跨平台性,可以在 Unix/Linux、Windows、Mac 等多种操作系统上使用,并且可以与多种编程语言搭配使用,如 C、C++、Java 等

安装

  • Linux

  • Windows

    • Make for Windows (sourceforge.net):实际基于MinGW,推荐后者

    • MinGW - Minimalist GNU for Windows download | SourceForge.net

      • 下载安装,将Bin目录加入到系统环境变量中

      • 运行MinGW Installer,勾选所需的包,点击左上角的Installation,点击Apply Change

    在这里插入图片描述

    • mingw32-make.exe更名为make.exe方便使用

使用

CMake

CMake官网 CMake官方中文文档 CMake-Practice-zh-CN: CMake 实践 (github.com) 《CMake Best Practices》的非专业个人翻译 (github.com)

介绍

CMake是一种管理源代码构建的工具,被广泛用于C/C++项目

使用的工具链:cmake + make。通过cmake语法编写CMakeLists.txt文件,描述项目的构建属性和配置,并进行自动化的项目构建(可执行二进制文件、静/动态库)

  • 开源、高效率

  • 跨平台:在Linux/Unix平台,生成makefile;在Mac平台生成xcode;在Windows平台可以生成MSVC工程文件

  • 可扩展:可为cmake编写特定功能的模块,扩展cmake功能

安装

各大Linux发行版都集成了cmake,无需手动安装。如需安装可从官网上下载安装

使用

构建方式
  • 内部构建

  • 外部构建:推荐的方式,在源CMakeLists.txt所在的文件夹下新建build目录(或其他任意位置),在该位置下键入cmake ..cmake 源工程路径,此时区分出了PROJECT_SOURCE_DIR和PROJCT_BINARY_DIR

内部构建

源码和构建中间、目标在同一目录下,将hello.c和CMakeLists.txt放入同一目录

# 注意:命令和括号之间没有空格
CMAKE_MINIMUM_REQUIRED(VERSION 3.29)

#[[
 指定项目信息:该声明隐式地定义了两个变量:项目名_BINARY_DIR和项目名_BINARY_DIR,
它们指向了当前CMakeLists.txt文件的路径,更好的替代方式是PROJECT_SOURCE_DIR和
PROJCT_BINARY_DIR,它们可以避免由项目名称更改造成的问题 
PROJECT (项目名
    VERSION 版本号
    DESCRIPTION "项目介绍"
    LANGUAGE C)
]]
PROJECT(hello)

#[[
 打印信息:MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display")
    SEND_ERROR,产生错误,生成过程被跳过
    SATUS,输出前缀为—的信息
    FATAL_ERROR,立即终止所有 cmake 过程
]]
# 注意除IF语句中,变量的使用方式为:${}
MESSAGE(STATUS "This is Projct BINARY dir" ${项目名_BINARY_DIR})
MESSAGE(STATUS "This is Projct SOURCE dir" ${项目名_SOURCE_DIR})

# 显示定义变量:SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
# 变量值可以用双引号标识,以应对存在空格的情况:SET(SRC_LIST “fu nc.c”)
SET(SRC_LIST hello.c)

# 添加要构建可执行文件的源文件和输出文件,此时表示可执行文件为hello,源文件列表为SRC_LIST变量
# 多个参数可以使用空格或分号分隔:ADD_EXECUTABLE(hello main.c;func.c)
ADD_EXECUTABLE(hello ${SRC_LIST})
cmake .
外部构建

注意:CMake需要在任何子目录中建立一个CMakeLists.txt,如下将源文件放入src文件夹中,src文件夹中也需要创建一个

- root
    # 源文件
    - src
        # 源码
        hello.c
        CMakeLists.txt
    # 文档
    - doc
        # 版权说明、自述文件
        COPYRIGHT.txt
        README.txt
    # bat脚本,用于调用二进制可执行文件
    run.bat
    # 构建后的目标文件夹
    - bin
    # 构建目录
    - build
    CMakeLists.txt
# /src/CMakeLists.txt
ADD_EXECUTABLE(hello hello.c)

# 指定最终的目标二进制的位置(指最终生成的 hello 或者最终的共享库,不包含编译生成的中间文件)
# 内部编译下,PROJECT_BINARY_DIR是当前目录,外部编译下,PROJECT_BINARY_DIR是构建目录
# 与下面的ADD_SUBDIRECTORY用途相同(在哪里 ADD_EXECUTABLE 或 ADD_LIBRARY,就在哪改变路径)
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
# /CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.12...3.29)

Project(hello)

# 向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置
# ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
# EXCLUDE_FROM_ALL 参数的含义是将这个目录从编译过程中排除,比如,工程的 example,可能就需要工程构建完成后,再进入 example 目录单独进行构建
# 将 src 子目录加入工程,并指定编译输出(包含编译中间结果)路径为bin 目录。如果不进行 bin 目录的指定,那么编译结果(包括中间结果)都将存放在build/src 目录(这个目录跟原有的 src 目录对应)
ADD_SUBDIRECTORY(src bin)


# 指定安装目录的变量:默认值/usr/local,注意Windows下转义字符
SET(CMAKE_INSTALL_PREFIX C:\\Users\\liyifan31\\Desktop\\des)

# 安装命令:注意FILES、PROGRAMS、DIRECTORY是标识符
#[[
 # 目标文件安装:TARGETS
 # TARGET参数即是ADD_EXECUTABLE、ADD_LIBRARY定义的目标文件
 INSTALL(TARGETS targets...
     # 静态库、动态库、可执行二进制文件    
     [[ARCHIVE|LIBRARY|RUNTIME]
         # 定义安装路径,若以/开头则是绝对路径,CMAKE_INSTALL_PREFIX失效    
         [DESTINATION <dir>]       
         [PERMISSIONS permissions...]
         [CONFIGURATIONS
     [Debug|Release|...]]
         [COMPONENT <component>]
         [OPTIONAL]
 ] [...])
 # 普通文件安装
 INSTALL(FILES files... DESTINATION <dir>
     # 定义文件权限,默认为:OWNER_WRITE,OWNER_READ,GROUP_READ,WORLD_READ,即644权限
     [PERMISSIONS permissions...]
     [CONFIGURATIONS [Debug|Release|...]]
     [COMPONENT <component>]
     [RENAME <name>] [OPTIONAL])
  # 非目标文件的可执行程序安装(比如脚本之类),与FILES相比,默认为755权限
 INSTALL(PROGRAMS files... DESTINATION <dir>
     [PERMISSIONS permissions...]
     [CONFIGURATIONS [Debug|Release|...]]
     [COMPONENT <component>]
     [RENAME <name>] [OPTIONAL])
  # 文件夹
  INSTALL(DIRECTORY dirs... DESTINATION <dir>
     [FILE_PERMISSIONS permissions...]
     # 所在 Source 目录的相对路径。注意:abc 和 abc/有很大的区别
     # 如果目录名不以/结尾,那么这个目录将被安装为目标路径下的 abc,
     # 如果目录名以/结尾,代表将这个目录中的内容安装到目标路径,但不包括这个目录本身
     [DIRECTORY_PERMISSIONS permissions...]
     [USE_SOURCE_PERMISSIONS]
     [CONFIGURATIONS [Debug|Release|...]]
     [COMPONENT <component>]
     # 使用正则表达式进行过滤
     [[PATTERN <pattern> | REGEX <regex>]
     [EXCLUDE] [PERMISSIONS permissions...]] [...])
   # e.g.
   # 将 icons 目录安装到 <prefix>/share/myproj,将 scripts/中的内容安装到<prefix>/share/myproj
   INSTALL(DIRECTORY icons scripts/ DESTINATION share/myproj
       # 不包含目录名为 CVS 的目录
       PATTERN "CVS" EXCLUDE
       # 对于 scripts/*文件指定权限为 OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ
       PATTERN "scripts/*"
       PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ
       GROUP_EXECUTE GROUP_READ)
]]

INSTALL(DIRECTORY doc DESTINATION doc)
INSTALL(DIRECTORY data DESTINATION data)
INSTALL(TARGETS ${PROJECT_BINARY_DIR}\\bin\\Debug\\ DESTINATION bin)
cd build
cmake ..
cmake -DCMAKE_INSTALL_PREFIX=/usr
make
make install
构建使用静/动态库

目录结构

CMakeLists.txt
- lib
    CMakeLists.txt
    hello.c
    hello.h
- build
# /lib/CMakeLists.txt

SET(LIBHELLO_SRC hello.c)

#[[
    ADD_LIBRARY(libname 
        # SHARED,动态库 STATIC,静态库 MODULE,在使用 dyld 的系统有效,
        # 如果不支持 dyld,则被当作 SHARED 对待
        [SHARED|STATIC|MODULE]
        # 该库不会被默认构建,除非有其他的组件依赖或者手工构建
        [EXCLUDE_FROM_ALL]
        source1 source2 ... sourceN
)
]]
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})

#[[
    # 设置输出属性
    SET_TARGET_PROPERTIES(target1 target2 ...
         PROPERTIES prop1 value1
         prop2 value2 ...)
]]
# 解决静态库和动态库重名/名称不一致的问题
# OUTPUT_NAME 设置输出名称。解决名称不一致问题
# CLEAN_DIRECT_OUTPUT 解决输出时,同名文件清除问题
# VERSION 库版本号
# SOVERSION api版本号
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1 VERSION 1.2 SOVERSION 1)
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello" CLEAN_DIRECT_OUTPUT 1 VERSION 1.2 SOVERSION 1)

# 获取输出属性
GET_TARGET_PROPERTY(OUTPUT_VALUE hello_static OUTPUT_NAME)
MESSAGE(STATUS “This is the hello_static OUTPUT_NAME:” ${OUTPUT_VALUE})
# /CMakeLists.txt
Project(hello_lib)

ADD_SUBDIRECTORY(lib lib)

INSTALL(TARGETS hello hello_static
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION lib
)
INSTALL(FILES hello.h DESTINATION /include)
  • 使用静态库和动态库

新建项目,在根路径下创建src文件夹和CMakeLists.txt,在src文件夹下创建sort_test.c和CMakeLists.txt,sort.c中引用刚才的库头文件

# /src/CMakeLists.txt

#[[ 
    # 向工程添加多个特定的头文件搜索路径,路径之间用空格分割
    # 通过 AFTER 或者 BEFORE 参数,控制追加到当前的头文件搜索路径的后面还是前面
    INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
]]
INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/extra/sort/include") 
# 通过 SET 这个 cmake 变量为 on,可以将添加的头文件搜索路径放在已有路径的前面
# SET(CMAKE_INCLUDE_DIRECTORIES_BEFORE ON)

# 为target添加共享库,路径之间用空格分割
LINK_DIRECTORIES("${CMAKE_SOURCE_DIR}/extra/sort/lib/WIN32")
#[[链接
TARGET_LINK_LIBRARIES(target library1
     <debug | optimized> library2 ...)
]]
# 动态
TARGET_LINK_LIBRARIES(sort_test sort.dll)
# 静态
TARGET_LINK_LIBRARIES(sort_test sort.lib)


#[[
   特殊的系统环境变量:注意不是CMake变量CMAKE_INCLUDE_PATH、CMAKE_LIBRARY_PATH
   # 修改这两个环境变量到系统共存的第三方库环境变量
]]
# 在环境变量中找sort.h的路径添加到myHeader变量中
FIND_PATH(myHeader sort.h)
# 指定路径
# FIND_PATH(myHeader NAMES sort.h PATHS /usr/include /usr/include/sort)
# 判空
IF(myHeader)
# 添加到include目录
INCLUDE_DIRECTORIES(${myHeader})
ENDIF(myHeader)

# 注意搜索路径应该添加到可执行文件前
ADD_EXECUTABLE(sort_test sort_test.c)
/CmakeLists.txt
Project(sort_lib)
ADD_SUBDIRECTORY(src bin)
常用[系统]变量
  • 常用变量
常用变量含义说明
CMAKE_BINARY_DIR
PROJECT_BINARY_DIR
< < <projectname>_BINARY_DIR
工程编译发生的目录
CMAKE_SOURCE_DIR
PROJECT_SOURCE_DIR
_SOURCE_DIR
工程顶层目录
CMAKE_CURRENT_SOURCE_DIR当前处理的 CMakeLists.txt 所在的路径
CMAKE_CURRRENT_BINARY_DIRtarget 编译目录ADD_SUBDIRECTORY(src bin)可以更改这个变量的值
CMAKE_CURRENT_LIST_FILE调用这个变量的 CMakeLists.txt 的完整路径
CMAKE_CURRENT_LIST_LINE输出这个变量所在的行
CMAKE_MODULE_PATH定义自己的 cmake 模块所在的路径如果工程比较复杂,有可能会自己编写一些 cmake 模块,这些 cmake 模块是随你的工程发布的,为了让 cmake 在处理 CMakeLists.txt 时找到这些模块,你需要通过 SET 指令,将自己的 cmake 模块路径设置一下
EXECUTABLE_OUTPUT_PATH
LIBRARY_OUTPUT_PATH
重新定义最终结果的存放目录
PROJECT_NAME返回通过 PROJECT 指令定义的项目名称
  • 系统变量
# 调用系统的环境变量
$ENV{NAME}
# 设置环境变量
SET(ENV{变量名} 值)
环境变量含义说明
CMAKE_INCLUDE_CURRENT_DIR自动添加 CMAKE_CURRENT_BINARY_DIR CMAKE_CURRENT_SOURCE_DIR 到当前处理的 CMakeLists.txt相当于在每个 CMakeLists.txt 加入:
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}``${CMAKE_CURRENT_SOURCE_DIR})
CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE将工程提供的头文件目录始终至于系统头文件目录的前面当定义的头文件确实跟系统发生冲突时可以提供一些帮助
  • 系统信息
系统信息含义
CMAKE_MAJOR_VERSIONCMAKE 主版本号,比如 2.4.6 中的 2
CMAKE_MINOR_VERSIONCMAKE 次版本号,比如 2.4.6 中的 4
CMAKE_PATCH_VERSIONCMAKE 补丁等级,比如 2.4.6 中的 6
CMAKE_SYSTEM系统名称,比如 Linux-2.6.22
CMAKE_SYSTEM_NAME不包含版本的系统名,比如 Linux
CMAKE_SYSTEM_VERSION系统版本,比如 2.6.22
CMAKE_SYSTEM_PROCESSOR处理器名称,比如 i686
UNIX在所有的类 UNIX 平台为 TRUE,包括 OS X 和 cygwin
WIN32在所有的 win32 平台为 TRUE,包括 cygwin
  • 开关选项
开关选项含义
MAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS用来控制 IF ELSE 语句的书写方式
BUILD_SHARED_LIBS控制默认的库编译方式,默认为静态库
CMAKE_C_FLAGS设置C编译选项,也可以通过指令ADD_DEFINITIONS()添加
CMAKE_CXX_FLAGS设置C++编译选项,也可以通过指令ADD_DEFINITIONS()添加
常用指令
# 1.向C,C++编译器添加-D定义
# 如果你的代码中定义了#ifdef ENABLE_DEBUG #endif,这个代码块就会生效。
# 如果要添加其他的编译器开关,可以通过 CMAKE_C_FLAGS 变量和 CMAKE_CXX_FLAGS 变量设置
ADD_DEFINITIONS(-DENABLE_DEBUG -DABC)


# 2.定义 target 依赖的其他 target,确保在编译本 target 之前,其他的 target 已经被构建
ADD_DEPENDENCIES(target-name depend-target1 depend-target2 ...)

# 3.ADD_TEST(testname Exename arg1 arg2 ...)
# testname 是自定义的 test 名称,Exename 可以是构建的目标文件也可以是外部脚本等等,后面连接传递给可执行文件的参数。
# 如果没有在同一个 CMakeLists.txt 中打开ENABLE_TESTING()指令,任何 ADD_TEST 都是无效的
ADD_TEST(mytest ${PROJECT_BINARY_DIR}/bin/main)
# 4.控制 Makefile 是否构建 test 目标,涉及工程所有目录,一般情况这个指令放在工程的主CMakeLists.txt 中
ENABLE_ESTING() 

# 5.发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来自动构建源文件列表
AUX_SOURCE_DIRECTORY(dir VARIABLE)

# 6.在指定的目录运行某个程序,通过 ARGS 添加参数,如果要获取输出和返回值
# 可通过 OUTPUT_VARIABLE 和 RETURN_VALUE 分别定义两个变量
# 该指令可以在 CMakeLists.txt 处理过程中支持任何命令,如根据系统情况去修改代码文件
# 在 CMakeLists.txt 处理过程中执行命令,并不会在生成的 Makefile 中执行
EXEC_PROGRAM(Executable [directory in which to run]
                 [ARGS <arguments to executable>]
                 [OUTPUT_VARIABLE <var>]
                 [RETURN_VALUE <var>])
# 在 src 目录执行 ls 命令,并把结果和返回值存下来
EXEC_PROGRAM(ls ARGS "*.c" OUTPUT_VARIABLE LS_OUTPUT RETURN_VALUELS_RVALUE)
IF(not LS_RVALUE)
MESSAGE(STATUS "ls result: " ${LS_OUTPUT})
ENDIF(not LS_RVALUE)

# 7.文件操作指令
FILE(WRITE filename "message to write"... )
FILE(APPEND filename "message to write"... )
FILE(READ filename variable)
FILE(GLOB  variable [RELATIVE path] [globbing expressions]...)
FILE(GLOB_RECURSE variable [RELATIVE path][globbing expressions]...)
FILE(REMOVE [directory]...)
FILE(REMOVE_RECURSE [directory]...)
FILE(MAKE_DIRECTORY [directory]...)
FILE(RELATIVE_PATH variable directory file)
FILE(TO_CMAKE_PATH path result)
FILE(TO_NATIVE_PATH path result)

# 8.载入 CMakeLists.txt 文件,也用于载入预定义的 cmake 模块
# OPTIONAL 参数的作用是文件不存在也不会产生错误
# 可以指定载入一个文件,如果定义的是一个模块,那么将在CMAKE_MODULE_PATH中
# 搜索这个模块并入。载入的内容将在处理到 INCLUDE 语句是直接执行。
INCLUDE(file1 [OPTIONAL])
INCLUDE(module [OPTIONAL])

# 9.Find
# VAR 变量代表找到的文件全路径,包含文件名
FIND_FILE(<VAR> name1 path1 path2 ...) 
# VAR 变量表示找到的库全路径,包含库文件名
FIND_LIBRARY(<VAR> name1 path1 path2 ...)
# e.g.
FIND_LIBRARY(libX X11 /usr/lib)
IF(NOT libX)
MESSAGE(FATAL_ERROR “libX not found”)
ENDIF(NOT libX)
# VAR 变量代表包含这个文件的路径
FIND_PATH(<VAR> name1 path1 path2 ...) 
# VAR 变量代表包含这个程序的全路径
FIND_PROGRAM(<VAR> name1 path1 path2 ...) 
# 调用预定义在 CMAKE_MODULE_PATH 下的 Find<name>.cmake 模块,
# 也可以自己 定义Find<name>模块,通过SET(CMAKE_MODULE_PATH dir)将其放入工程的某个目录中供工程使用
FIND_PACKAGE(<name> [major.minor] [QUIET] [NO_MODULE]
                 [[REQUIRED|COMPONENTS] [componets...]])

# 10.IF指令
IF(expression)
          # THEN section.
          COMMAND1(ARGS ...)
          COMMAND2(ARGS ...)
          ...
        ELSE(expression)
          # ELSE section.
          COMMAND1(ARGS ...)
          COMMAND2(ARGS ...)
          ...
ENDIF(expression)
# 凡是出现 IF 的地方一定要有对应的 ENDIF。出现 ELSEIF 的地方,ENDIF 是可选的
# IF(var),如果变量不是空,0,N, NO, OFF, FALSE, NOTFOUND 或<var>_NOTFOUND 时,表达式为真。
# IF(NOT var ),与上述条件相反
# IF(var1 AND var2),当两个变量都为真是为真。
# IF(var1 OR var2),当两个变量其中一个为真时为真。
# IF(COMMAND cmd),当给定的cmd确实是命令并可以调用是为真。
# IF(EXISTS dir)或者IF(EXISTS file),当目录名或者文件名存在时为真。
# IF(file1 IS_NEWER_THAN file2),当 file1 比 file2 新,或者 file1/file2 其 中有一个不存在时为真,文件名请使用完整路径。
# IF(IS_DIRECTORY dirname),当dirname是目录时,为真
# 正则匹配
IF(variable MATCHES regex)
IF(string MATCHES regex)
# IF("hello" MATCHES "ell")
MESSAGE("true")
ENDIF("hello" MATCHES "ell")

# 数字比较表达式
IF(variable LESS number) 
IF(string LESS number) 
IF(variable GREATER number) 
IF(string GREATER number) 
IF(variable EQUAL number) 
IF(string EQUAL number))

# 按照字母序的排列进行比较
IF(variable STRLESS string)
IF(string STRLESS string)
IF(variable STRGREATER string) 
IF(string STRGREATER string) 
IF(variable STREQUAL string)
IF(string STREQUAL string)

# 判断变量定义:IF(DEFINED variable)
IF(WIN32)
    MESSAGE(STATUS “This is windows.”) 
    # 作一些 Windows 相关的操作
ELSE(WIN32)
    MESSAGE(STATUS “This is not windows”) 
    # 作一些非 Windows 相关的操作
ENDIF(WIN32)

# 简化ELSE、ENDIF,使用开关语句
SET(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS ON)
# 简化为
IF(WIN32)
ELSE()
ENDIF()
# 配合ELSEIF使用
IF(WIN32)
#do something related to WIN32 
ELSEIF(UNIX)
#do something related to UNIX
ELSEIF(APPLE)
#do something related to APPLE
ENDIF(WIN32)

# 11.WHILE
WHILE(condition)
          COMMAND1(ARGS ...)
          COMMAND2(ARGS ...)
          ...
ENDWHILE(condition)

# 12.FOREACH
# 列表
AUX_SOURCE_DIRECTORY(. SRC_LIST) 
FOREACH(F ${SRC_LIST})
    MESSAGE(${F})
ENDFOREACH(F)
# 范围
FOREACH(VAR RANGE 10) 
MESSAGE(${VAR}) 
ENDFOREACH(VAR)
# 范围和步进
FOREACH(A RANGE 5 15 3) 
    MESSAGE(${A}) 
ENDFOREACH(A) 
CMake模块
  • 以往找库链接的过程:引入+链接
# /src/CMakeLists.txt
INCLUDE_DIRECTORIES(/usr/include)
TARGET_LINK_LIBRARIES(curltest curl)
  • 使用CMake模块

让工程找到 FindHELLO.cmake 模块(存放在工程中的 cmake 目录)

# /CMakeLists.txt
SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)

FIND_PACKAGE可以直接调用预定义的模块

# /src/CMakeLists.txt
# 对于系统预定义的 Find<name>.cmake 模块,都会定义以下几个变量:
# 判断模块是否被找到,如果没有找到,按照工程的需要关闭某些特性、给出提醒或者中止编译
# <name>_FOUND
# 如果 <name>_FOUND 为真,则将 <name>_INCLUDE_DIR 加入 INCLUDE_DIRECTORIES
# 将 <name>_LIBRARY 加入 TARGET_LINK_LIBRARIES 中 
# FIND_PACKAGE(<name> [major.minor] [QUIET] [NO_MODULE]
#     [[REQUIRED|COMPONENTS] [componets...]])
FIND_PACKAGE(CURL)
IF(CURL_FOUND)
   INCLUDE_DIRECTORIES(${CURL_INCLUDE_DIR})
   TARGET_LINK_LIBRARIES(curltest ${CURL_LIBRARY})
ELSE(CURL_FOUND)
     MESSAGE(FATAL_ERROR ”CURL library not found”)
ENDIF(CURL_FOUND)
  • 自定义CMake模块

编库时,项目结构

- CMakeLists.txt
    - src
        CMakeLists.txt
        curl.c
    - cmake
        FindCURL.cmake
    - build
# /cmake/FindCURL.cmake
FIND_PATH(CURL_INCLUDE_DIR curl.h /usr/include/curl /usr/local/include/curl)
FIND_LIBRARY(CURL_LIBRARY NAMES curl PATH /usr/lib /usr/local/lib)
IF(CURL_INCLUDE_DIR AND CURL_LIBRARY)
   SET(CURL_FOUND TRUE)
ENDIF(CURL_INCLUDE_DIR AND CURL_LIBRARY)
IF(CURL_FOUND)
   # 根据FIND_PACKAGE中的QUIET参数判断
   IF(NOT CURL_FIND_QUIETLY)
       MESSAGE(STATUS "Found CURL: ${CURL_LIBRARY}")
   ENDIF(NOT CURL_FIND_QUIETLY)
ELSE(CURL_FOUND)
   # 根据FIND_PACKAGE中的REQUIRED参数判断
   IF(CURL_FIND_REQUIRED)
      MESSAGE(FATAL_ERROR "Could not find CURL library")
   ENDIF(CURL_FIND_REQUIRED)
ENDIF(CURL_FOUND)

Make与CMake的联系与区别

  • Make:用来处理编译顺序和依赖关系并构建最终项目的工具

  • CMake:本身不执行Make过程,而是根据不同平台的特性,生成对应平台的Makefie,这样我们每个工程只要写一个CMake文件即可了,其余的交给不同平台的处理器来产生不同的Makefile文件即可。而且CMake的语法也更加简洁

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/482723.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

优化选址问题 | 基于鹈鹕算法求解基站选址问题含Matlab源码

目录 问题代码问题 鹈鹕算法(Pelican Optimization Algorithm, POA)是一种相对较新的启发式优化算法,模拟了鹈鹕鸟觅食的行为。这种算法通常用于解决复杂的优化问题,如函数优化、路径规划、调度问题等。基站选址问题通常是一个复杂的优化问题,需要考虑覆盖范围、干扰、成…

迷宫(一)(DFS BFS)

//新生训练 #include <bits/stdc.h> using namespace std; int n, m; bool f; char mp[15][15]; int vis[15][15]; int dir[4][2] {{-1, 0}, {0, 1}, {1, 0}, {0, -1}}; bool in(int x, int y) {return 0 < x && x < n && 0 < y && y …

kali安装docker(亲测有效)

第一步&#xff1a;添加Docker官方的GPG密钥 curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add - 第二步&#xff1a; 第二步更新源 echo deb https://download.docker.com/linux/debian stretch stable> /etc/apt/sources.list.d/docker.list…

基于python+vue超市在线销售系统的设计与实现flask-django-php-nodejs

根据此问题&#xff0c;研发一套超市在线销售系统&#xff0c;既能够大大提高信息的检索、变更与维护的工作效率&#xff0c;也能够方便信息系统的管理运用&#xff0c;从而减少信息管理成本&#xff0c;提高效率。 该超市在线销售系统采用B/S架构、并采用python语言以及django…

图床项目实战:后续开发与优化

在之前的文章中&#xff0c;我们介绍了图床项目的基本实现&#xff0c;接下来&#xff0c;我将提供扩展功能和优化性能的关键代码片段。 一、图片分类管理 首先&#xff0c;我们需要在数据库中创建分类表&#xff0c;并在图片表中添加分类字段。 class Category(db.Model): …

生物信息学文章中常见的图应该怎么看?

目录 火山图 热图 箱线图 森林图 LASSO回归可视化图&#xff08;套索图&#xff09; 交叉验证图 PCA图 ROC曲线图 这篇文章只介绍这些图应该怎么解读&#xff0c;具体怎么绘制&#xff0c;需要什么参数&#xff0c;怎么处理数据&#xff0c;会在下一篇文章里面给出 火山…

AIGC——ComfyUI SDXL多种风格预设提示词插件安装与使用

概述 SDXL Prompt Styler可以预先给SDXL模型提供了各种预设风格的提示词插件&#xff0c;相当于预先设定好了多种不同风格的词语。使用这个插件&#xff0c;只需从中选取所需的风格&#xff0c;它会自动将选定的风格词汇添加到我们的提示中。 安装 插件地址&#xff1a;http…

使用双异步后,从 191s 优化到 2s

使用双异步后&#xff0c;从 191s 优化到 2s 一般我会这样做&#xff1a; 通过POI读取需要导入的Excel&#xff1b; 以文件名为表名、列头为列名、并将数据拼接成sql&#xff1b; 通过JDBC或mybatis插入数据库&#xff1b; 操作起来&#xff0c;如果文件比较多&#xff0…

springboot精品源码

springboot精品源码 所有项目都包括&#xff1a;源码数据库文件开题LW说明文档运行视频 请看主页资料联系。 项目类型包括: 1 SpringBoot学生心理咨询评估系统 2 基于SpringBoot的网上订餐系统 3 大学生租房平台的设计与实现 4 SpringBoot房屋租赁系统 5 基于SpringBoot的课…

tcp 协议详解

什么是 TCP 协议 TCP全称为 “传输控制协议(Transmission Control Protocol”). 人如其名, 要对数据的传输进行一个详细的控制。TCP 是一个传输层的协议。 如下图&#xff1a; 我们接下来在讲解 TCP/IP 协议栈的下三层时都会先解决这两个问题&#xff1a; 报头与有效载荷如何…

大数据------javase基础------day18(完结)

类加载器 作用 负责将编译后的java文件&#xff08;即.class文件&#xff09;加载到内存中供虚拟机执行 类加载的时机------总结一句话&#xff1a;用到类就加载&#xff0c;不用就不加载 创建类的实例调用类的方法访问类或者接口的类变量&#xff0c;或者为该类变量赋值使用反…

阿里云幻兽帕鲁4核16G和8核32G服务器优惠价格

2024阿里云幻兽帕鲁专用服务器价格表&#xff1a;4核16G幻兽帕鲁专用服务器26元一个月、149元半年&#xff0c;默认10M公网带宽&#xff0c;8核32G幻兽帕鲁服务器10M带宽价格90元1个月、271元3个月。阿里云提供的Palworld服务器是ECS经济型e实例&#xff0c;CPU采用Intel Xeon …

Linux:详解https协议

文章目录 什么是https协议信息窃取常见的加密数据摘要和数据指纹https的工作过程只使用对称加密只使用非对称加密都使用非对称加密非对称加密对称加密 证书数据签名https方案 本篇要总结的内容是关于https协议的相关内容 什么是https协议 在讲述https协议之前&#xff0c;首先…

差分约束系统

差分约束系统 差分约束系统&#xff08;spfa&#xff09;1、概述2、过程模拟3、推理 差分约束系统&#xff08;spfa&#xff09; 1、概述 x j − x i ≤ w k x_j-x_i\le w_k xj​−xi​≤wk​转换为&#xff1a; x j ≤ w k x i x_j\le w_kx_i xj​≤wk​xi​ 在松弛操作中&…

dubbo 源码系列之-集群三板斧---负载均衡(-)

dubbo 源码系列之-负载均衡 概述核心接口 LoadBalanceDubbo 提供了 5 种负载均衡实现&#xff0c;分别是&#xff1a;LoadBalance 接口AbstractLoadBalance ConsistentHashLoadBalance 一致性hash1. 一致性 Hash 简析1.0 hash 算法2.0 一致性Hash算法3.0 一致性hash算法 引入槽…

K8S--SpringCloud应用整合Nacos实战

原文网址&#xff1a;K8S--SpringCloud应用整合Nacos实战-CSDN博客 简介 本文介绍K8S部署SpringCloud应用整合Nacos实战。 本文是将原来的SpringCloud项目&#xff08;闪速优选&#xff09;迁移到K8S上&#xff0c;一行代码都不需要改动。用K8S运行Nacos、Gateway、SpringCl…

PHP 读取嵌入式数据 SQLite3

SQLite3 属于轻量级开源的嵌入式关系型数据库&#xff0c;但它支持 ACID(Atomicity,Consistency,Isolation,Durability) 事务。 SQLite Download Page: https://www.sqlite.org/download.html 第一步&#xff1a;在 php.ini 中开启 extensionsqlite3 第二步&#xff1a;连接数…

Redis的String类型为什么重新设计使用了SDS数据结构呢

Redis 选择重新设计其 String 类型的底层数据结构&#xff0c;采用 SDS&#xff08;Simple Dynamic String&#xff09;而不是直接使用 C 语言标准库提供的原生字符串&#xff08;char*&#xff09;的原因主要包括以下几点&#xff1a; O(1) 时间复杂度获取长度&#xff1a; 在…

机器学习金融应用技术指南

1 范围 本文件提供了金融业开展机器学习应用涉及的体系框架、计算资源、数据资源、机器学习引擎、机 器学习服务、安全管理、内控管理等方面的建议。 本文件适用于开展机器学习金融应用的金融机构、技术服务商、第三方安全评估机构等。 2 规范性引用文件 下列文件中的内容通过…

C#,图论与图算法,用于检查给定图是否为欧拉图(Eulerian Graph)的算法与源程序

1 欧拉图 欧拉图是指通过图(无向图或有向图)中所有边且每边仅通过一次通路, 相应的回路称为欧拉回路。具有欧拉回路的图称为欧拉图(Euler Graph), 具有欧拉通路而无欧拉回路的图称为半欧拉图。 对欧拉图的一个现代扩展是蜘蛛图,它向欧拉图增加了可以连接的存在点。 这给…