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

news2024/12/26 17:19:48

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的语法也更加简洁

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

代码随想录|Day26|贪心01|455.分发饼干、376.摆动序列、53.最大子数组和

455.分发饼干 大尺寸的饼干既可以满足胃口大的孩子也可以满足胃口小的孩子。 局部最优&#xff1a;尽量确保每块饼干被充分利用 全局最优&#xff1a;手上的饼干可以满足尽可能多的孩子 思路&#xff1a;大饼干 尽量分给 大胃口孩子 将小孩和饼干数组排序&#xff0c;我们从大到…

40+重量级DFLab合成模型含各种神丹底丹万能模型合集分享

之前玩DFL软件积累下来的资源&#xff0c;部分模型非常稀缺&#xff0c;之前买的都很贵&#xff0c;现在不玩了&#xff0c;分享给有缘人&#xff0c;懂货的自然懂。必须懂得怎么用再下载&#xff0c;否则对你没有任何价值。点击下载 所见即所得。其中包含几个重量级稀缺资源&…

新款理想L7一边增配一边减配,难怪大家都去买华为问界

文 | AUTO芯球 作者 | 雷歌 我真是要被理想汽车笑死了&#xff0c;真不愧是“定语榜单之王”。 几年前理想汽车搞了一个“中国市场新势力品牌销量周榜” 两个定语&#xff0c;将比亚迪&#xff0c;特斯拉排除在外&#xff0c;自己在自己打造的榜单里做了一年多的冠军宝座。…

试题E(求阶乘)

解题思路&#xff1a; 写不出来&#xff0c;看的题解。要想凑个10&#xff0c;就必须要有一个2和5&#xff0c;但是明显在一个阶乘里&#xff0c;因子为2的数量一定多余5的数量&#xff0c;所以计算5的数量。 解题代码&#xff1a; import java.util.Scanner; ​ public clas…

RabbitMQ 01

01.定义 02.功能

何为布控球?布控球的分类对比

主要的分类有&#xff1a; 根据内部的主控板卡的系统分类&#xff0c;典型的是基于海思芯片的嵌入式LINUX系统的&#xff0c;一般出国标GB28181&#xff0c;另外一种是剑走偏锋的安卓系统的&#xff0c;需要把球机的输出YUV转换为UVC接入安卓主板&#xff0c;作为外接USB摄像头…

Matplotlib数据可视化实战-2绘制折线图(1)

函数plot是matplotlib.pyplot模块中的一个重要函数&#xff0c;用于在图形中绘制折线图。其基本用法是plot(x, y, [fmt], **kwargs)&#xff0c;其中x和y分别代表X轴和Y轴上的数据点&#xff0c;通常是以列表、数组或一维序列的形式给出。通常用的参数有&#xff1a; 基本参数…

大学教材《C语言程序设计》(浙大版)课后习题解析 | 第一、二章

概述 本文主要提供《C语言程序设计》(浙大版) 第一、二章课后习题解析&#xff0c;以方便同学们完成题目后作为参考对照。后续将写出三、四章节课后习题解析&#xff0c;如想了解更多&#xff0c;请持续关注该专栏。 专栏直达链接&#xff1a;《C语言程序设计》(浙大版)_孟俊宇…

《深入浅出LLM 》(二):大模型基石知识

&#x1f389;AI学习星球推荐&#xff1a; GoAI的学习社区 知识星球是一个致力于提供《机器学习 | 深度学习 | CV | NLP | 大模型 | 多模态 | AIGC 》各个最新AI方向综述、论文等成体系的学习资料&#xff0c;配有全面而有深度的专栏内容&#xff0c;包括不限于 前沿论文解读、…

抖音IP属地怎么更改

抖音是一个非常受欢迎的短视频平台&#xff0c;吸引了无数用户在上面分享自己的生活和才艺。然而&#xff0c;随着快手的火爆&#xff0c;一些用户开始担心自己的IP地址会被他人获取&#xff0c;引起个人隐私风险。那么&#xff0c;抖音用户又该如何更改到别的地方呢&#xff1…

ArcGIS添加天地图底图服务

目录 一、注册天地图官网、申请Key 二、ArcGis配置和使用 1、配置 2、使用 三、其他方法 一、注册天地图官网、申请Key 进入官网&#xff0c;并注册账号。 地址&#xff1a;国家地理信息公共服务平台 天地图 (tianditu.gov.cn) 点击地图API&#xff0c;申请Key。 注意&am…

python闭包详解(实例)

“闭包”这个词语相信大多数学过编程的同学并不陌生&#xff0c;但是有时候理解起来还是有一定难度。 先看定义&#xff1a;闭包是由函数和与其相关的引用环境组合而成的实体。比如参考资源中就有这样的的定义&#xff1a;在实现深约束时&#xff0c;需要创建一个能显式表示引…

线段树(算法思想+模板+例题)

基础思想 : 介绍 线段树可以用来维护区间信息(区间和 &#xff0c; 区间最值 &#xff0c; 区间GCD等) &#xff0c;可以在log n的时间内执行区间修改 和 区间查询 ; 1 . 叶子结点的特点是左右结点相等 &#xff0c; 存储元素本身; 2 . 非叶子结点存储的是区间内的统计值 &…

力扣|两数相加|链表

给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外&#xff0c;这两个数都不会以 0 …

用户多部门切换部门,MySQL根据多个部门id递归获取所有上级(祖级)、获取部门的全路径(全结构名称)

背景 之前做过的项目&#xff0c;都是一个用户就一个部门的&#xff0c;现在碰到个一个用户在多个部门的需求&#xff0c;而且需要可以切换不同部门查看不同数据。 就比如说一个大公司下面有多个子公司&#xff0c;每个子公司有好多部门、子部门等等&#xff0c;然后有部分用…

JDK下载配置

一、JDK的作用 Java开发环境&#xff1a;JDK提供了完整的Java开发环境&#xff0c;包含编译器&#xff08;javac&#xff09;、解释器&#xff08;java&#xff09;、打包工具&#xff08;jar&#xff09;、文档生成工具&#xff08;javadoc&#xff09;等一系列工具&#xff0…

人工智能(Educoder)-- 搜索技术 -- 盲目式搜索

第1关&#xff1a;盲目搜索之宽度优先搜索算法 任务描述 本关任务&#xff1a;给定迷宫地图以及在迷宫中的起始位置&#xff0c;利用宽度优先搜索算法求解走出迷宫的最短路径长度&#xff0c;走出迷宫意味着达到迷宫地图的边界&#xff08;所有位置下标0开始&#xff09;。 …

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

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

鸿蒙一次开发,多端部署(十二)资源使用

在页面开发过程中&#xff0c;经常需要用到颜色、字体、间距、图片等资源&#xff0c;在不同的设备或配置中&#xff0c;这些资源的值可能不同。有两种方式处理&#xff1a; 应用资源&#xff1a;借助资源文件能力&#xff0c;开发者在应用中自定义资源&#xff0c;自行管理这些…

I/O多路复用:select/poll/epoll

最基本的 Socket 模型 要想客户端和服务器能在网络中通信&#xff0c;那必须得使用 Socket 编程&#xff0c;它是进程间通信里比较特别的方式&#xff0c;特别之处在于它是可以跨主机间通信。 Socket 的中文名叫作插口&#xff0c;咋一看还挺迷惑的。事实上&#xff0c;双方要…