C和C++项目构建工具:CMake用法详细指南

2021年3月8日16:30:20 发表评论 1,879 次浏览

本文概述

前言

CMake项目构建教程

前面我们讨论了Makefile的编写,编写好Makefile后我们使用make命令执行项目构建。那么CMake是什么呢?CMake是一个跨平台Makefile生成工具。我们首先在CMakeLists.txt中编写CMake构建规则,然后执行cmake命令生成Makefile,最后我们就可以执行make命令构建项目了。

那么相对于直接编写Makefile,使用CMake有什么好处呢?CMake的构建逻辑相对于直接编写Makefile更为简单,很多复杂的逻辑只需要几行代码就完成了。所以原生编写Makefile类似于C语言(底层),而使用CMake类似于Python(高级)。

CMake一般用于C/C++语言项目的构建,虽然也支持其它语言,但C/C++是主要的。如果你要开发C/C++相关的项目,那么CMake是首选的构建工具,但当然不是说make+Makefile已经没人用了,只是CMake是最流行的,也是最简单的。

CMake的构建目标是什么?

CMake是一个构建工具,那我们学它来干什么的呢?像之前讨论其他构建工具一样,使用CMake我们仍然要首先明确我们的目标是什么,否则你学完也不知道要干嘛,那就浪费时间了。

首先我们要使用CMake处理文件,包括:

  • 获取源文件进行编译。
  • 获取本地头文件辅助编译。
  • 链接第三方库,包括静态库和动态库。
  • 导入第三方库的头文件(使用任何第三方库,首先要得到库文件,然后是头文件)。

然后我们需要针对目标进行编译,构建的目标一般包括:

  • 静态库。
  • 动态库。
  • 和可执行程序。

接着,我们有安装相关文件的需求,包括:

  • 安装库或可执行程序,静态库不需要安装,链接的时候已经放到目标代码中。而动态库则需要安装,或者使用其它方式指定,以让系统找到该库。安装可执行程序让程序正式投入使用。
  • 安装头文件,这是可选的,这是为了让编译器在编译的时候在系统默认路径中找到头文件,一般是库的头文件。

最后,我们有分发项目的需求,包括:

  • 二进制分发,就是把已经编译好的程序分发出去。
  • 源码分发,把项目完整代码分发出去。

其实构建工具也就是以上这些主要需求,只是可能工具的使用方式和构建脚本不同,但它们实现的目标都是类似的。

另外一个值得注意的是,CMake并没有提供包依赖管理的功能,虽然有个CPM,但是并不流行。这似乎是C/C++项目的一个习惯,使用第三方库只能是获取库文件+头文件,有的还要自己编译。如果对相关东西不太熟悉,像我以前刚使用Linux,经常有make编译的,经常出错,非常恐怖!

第一个例子:CMake基本工作流程

首先创建一个C++项目,在项目根目录创建一个CMakeLists.txt,我们在其中编写构建逻辑,如下:

.
├── build
├── CMakeLists.txt
├── include
│   └── cal.h
├── lib
├── readme.txt
├── sources
└── src
    ├── cal.cpp
    └── main.cpp

现在我们开始编写CMakeLists.txt。

设置CMake最低版本

设置语法如下:

cmake_minium_required(VERSION <number>)

其中<number>为CMake的版本号,如果你不知道,可以在命令行输入cmake -version,输入当前版本即可。

设置项目名和版本号

设置语法如下:

project(<Name> VERSION <VersionNumber>)

其中项目名<Name>是任意的,<VersionNumber>是项目的版本,一般形式为:<主版本号>.<副版本号>.<修订版本号>。

头文件配置和导入版本号

我们在CMakeLists的某些配置可能要传递到源码中使用,例如版本号,实现传递的方式是使用一个头文件*.h.in,步骤如下:

1、在源码目录中新建一个文件,例如config.h.in,内容如下:

#cmakedefine myproject_VERSION_MAJOR @myproject_VERSION_MAJOR@
#cmakedefine myproject_VERSION_MINOR @myproject_VERSION_MINOR@

其中myproject为你上面使用project()设置的项目名称,@@表示的内容会被替换为真实值。

2、然后我们在CMakeLists.txt中使用configure_file(F.h.in F.h)将*.h.in文件转换为*.h文件,如下:

configure_file(src/config.h.in config.h)

其中config.h文件会在构建目录下生成,构建目录使用全局变量PROJECT_BINARY_DIR表示项目要使用该文件,我们要在CMakeLists.txt中导入该头文件:

target_include_directories(myproject PUBLIC $(PROJECT_BINARY_DIR))

最后在源码中使用,如下:

std::cout << "Version: " << myproject_VERSION_MAJOR <<
        "." << myproject_VERSION_MINOR << std::endl;

设置C++标准

如果你需要使用C++某些新特性,那就要设置C++的标准,通过两个全局变量设置:

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

设置构建类型

设置方式如下:

set(CMAKE_BUILD_TYPE Release)
set(CMAKE_BUILD_TYPE Debug)

创建一个生成目标

这里我们的目标是可执行文件,创建该目标使用add_executable,创建库使用add_library,如下:

add_executable(myproject main.cpp cal.cpp)

最后,我们的完整配置如下所示:

# 设置CMake最低版本
cmake_minimum_required(VERSION 3.1.0)
# 设置项目名称和版本号
project(myproject VERSION 1.1.0)

# 设置C++标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# 设置构建类型
set(CMAKE_BUILD_TYPE Release)

# 转换配置头文件
configure_file(src/config.h.in config.h)

#创建可执行程序的目标 
add_executable(myproject src/main.cpp src/cal.cpp)
# 包含配置头文件的所在目录 
target_include_directories(myproject PUBLIC ${PROJECT_BINARY_DIR} include)

需要注意的是,add_executable是一个创建目标的命令,如果没有这个命令,那么就不存在目标,所以相关target_**的指令需要在创建目标之后才能使用,否则会出错。

生成Makefile

编写完成后就可以生成Makefile了,但是我们一般不在根目录执行cmake,而是在build目录中,cmake生成Makefile的命令如下:

cmake + CMakeLists.txt所在目录

因为我们是在build中执行,那么CMakeLists.txt的所在目录为..(点点),而当前目录使用一个点表示。cmake会在执行命令的所在目录中生成相关构建文件,这里我们执行:cd build,然后执行:cmake ..(点点)。

以上是默认构建,我们还可以执行构建类型:

cmake <DIR> -DCMAKE_BUILD_TYPE:STRING=<Debug | Release>

Make+Makefile构建项目

执行完以上命令后,就可以在build目录中生成项目的Makefile文件了,并且生成了一个config.h文件,然后我们可以直接执行make构建项目了,生成的目标就在build目录中。

除了使用make命令,还可以直接使用cmake提供的构建命令:cmake --build .,其中后面的一个点表示当前目录,其效果是类似的。

小结

以上有一个稍微复杂的就是生成配置头文件,将*.h.in文件转换成*.h文件。这是一种常用的方式,因为很多时候我们需要将构建中的某些值同步传递到源码中使用,以上是传递版本号,我们还可以传递例如数据库的配置信息等等。

下面我们开始学习CMake的构建语言,CMake也有其相对成熟的构建语言,内容不算多,但学完CMake的语言,结合CMake提供的命令参考文档(https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html),基本上我们使用CMake构建项目都没问题了。

CMake语言

严格上说,这不算一种语言,CMake的所有编写元素基本都是由命令组成的,但是它提供的命令具有编程语言一般的特性,例如变量、条件、循环语句、函数等。

CMake的命令由指令名称、小括号和参数组成,其中指令名称不区分大小写,多个参数使用空格分隔,例如:

set(sources main.cpp cal.cpp hello.cpp)

set命令用于设置变量,第一个参数是变量的名称,后面是变量的值,该变量是一个List,后面多个参数使用空格分隔。

字符串

字符串有三种形式:

  • 方括号形式:[={len}[ CONTENT ]==],len表示结尾=的个数,这里len=2(我试了下似乎不能用,只有首尾都是=才能用)。
  • 引号形式:使用””括起来的字符串表示一个完整的字符串。
  • 无引号形式:多个空格分开的字符串表示一个列表。

其中,在方括号内引用变量不会被解释,在引号和无引号形式中才会解释${}引用的变量。

要注意的是,很多时候很有必要使用双引号把字符串括起来,否则有可能被当做列表处理(当然我还没遇过这个问题)。

下面是一个例子:

message([=[
    This is the first line in a bracket argument with bracket length 1.
    No \-escape sequences or ${variable} references are evaluated.
    This is always one argument even though it contains a ; character.
    The text does not end on a closing bracket of length 0 like ]].
    It does end in a closing bracket of length 1.
    ]=])
message("Hello CMakeLists.txt")
message(XShell)

注释

CMake中使用#作为行注释开始,多行注释使用#[[多行]],例如:

# 这是一个单行注释
#[[
这是一个多行注释
]]

变量

在CMake中定义变量使用set和option,取消变量定义使用unset,其中set设置的变量可以是任意值,而option定义的变量只能有两种值:ON和OFF,类似true和false。

set设置变量的语法:

set(<variable> <value>... [PARENT_SCOPE])

其中[PARENT_SCOPE]为指定变量的作用域,引用变量的形式为:

${<variable>}

如下,我们使用set定义并使用变量:

set(var1 "A String")
set(myname "MyName: ${var1}")
message(${myname})

我们可以将变量缓存到CMakeCache.txt中,上次的结果下次继续用,缓存变量的形式为:

set(<variable> <value>... CACHE <type> <docstring> [FORCE])

另外我们可以使用option命令设置变量,这种类型的变量一般只有两种值,形式如下:

option(<option_variable> "help string describing option" [initial value])

中间的字符串只是对该option变量的描述。

CMake的变量有其作用域,作用域有三种:

  • 函数作用域,在函数内部定义的变量只能在其内部使用。
  • 目录作用域,除了函数作用域的变量,变量的作用域在当前的目录及其子目录中。
  • 缓存,缓存到CMakeCache.txt,下次运行继续使用。

如前面你看到的,CMake提供一些全局变量,我们常常使用set命令设置全局变量的值进行配置,例如设置C++标准:

set(CMAKE_CXX_STANDARD 14)

宏定义

宏定义的语法如下:

macro(<Name> <Args>)
中间为宏定义体
定义执行逻辑
endmacro(<Name>)

宏调用形式如下:

<Name>(Arg1, Args, Arg3, ...)

下面是宏定义及其使用例子:

macro(log content code)
    if(code GREATER 2)
        message("Error: ${code}, ${content}")
    elseif(code LESS 0)
        message("Info: ${code}, ${content}")
    else()
        message("Warn: ${code}, ${content}")
    endif()
endmacro(log)

log("Cannot find Files" 4)
log("Http reuqest OK" -1)
log("Network too slow" 1)

条件语句

条件语句的完整形式如下:

if(condition)
elseif(condition)
else()
endif()

你可以看到,实际上它们都是一些命令,而不是真的语句,所以都要使用括号。

下面是一个例子:

macro(days number)
    set(flags 3)
    if(${number} EQUAL 1)
        message("Today is Monday")
    elseif(${number} EQUAL 2)
        message("Today is Tuesday")
    elseif(${number} EQUAL 3)
        message("Today is Wednesday")
    elseif(${number} LESS 1)
        message("Wrong Day")
    elseif(${number} LESS 7 AND ${number} EQUAL 7)
        message("Some Days")
    elseif(DEFINED flags AND ${number} GREATER 7)
        message("Unknown Day")
    else()
        message("Good Day")
    endif()
endmacro(days)

days(-1)
days(0)
days(1)
days(2)
days(3)
days(4)
days(6)
days(7)
days(8)
days(9)

循环命令

普通foreach循环命令的语法如下:

foreach(loop_var arg1 arg2 ...)
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
  ...
endforeach(loop_var)

其中loop_var表示列表中的单个项,后面是列表。

下面的例子定义并遍历输出一个列表:

essage("--------------foreach--------------")
set(lists cal.h cal.cpp logo.png print.h print.cpp main.h main.cpp config.xml)
foreach(source ${lists})
    set(isHeader -1)
    string(FIND ${source} ".h" isHeader)
    set(isSource -1)
    string(FIND ${source} ".cpp" isSource)
    if(isHeader LESS -1 OR isHeader GREATER -1)
        message("Header File: ${source}")
    elseif(isSource LESS -1 OR isSource GREATER -1)
        message("Source File: ${source}")
    else()
        message("Unknown File: ${source}")
    endif()
endforeach()

cmake还支持一种forrange循环,语法如下:

foreach(loop_var RANGE start stop [step]) 

使用例子如下:

set(numbers)
foreach(n RANGE 100 1000 90)
    list(APPEND numbers "${n}  ")
endforeach()
message(${numbers})

接着是常见的while循环,语法如下:

while(condition)
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
  ...
endwhile(condition)

下面使用while循环计算1加到100的和:

set(count 0)
set(i 1)
while(i LESS 101)
    math(EXPR count "${count}+${i}")
    math(EXPR i "${i}+1")
endwhile()
message("1 + 2 + ... + 100 = ${count}")

要注意的是,math命令中的数学表达式不能使用空格,否则会出现莫名的错误。

CMake函数命令

函数定义语法如下:

function(<name> [arg1 [arg2 [arg3 ...]]])
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
  ...
endfunction(<name>)

下面的例子中,我们计算a和b的和,将其计算结果赋值给外部变量val。该例子在函数内部正常使用${variable}访问变量。

# 函数 
set(val 0)
function(sum a b)
    set(v 0)
    math(EXPR v "${a}+${b}")
    set(val ${v} PARENT_SCOPE)
endfunction()

sum(9 8)
message(${val})

还有另一种表示函数参数的变量形式:

  • ARGC参数个数
  • ARGV参数列表
  • ARGV0参数0
  • ARGV1参数1
  • ARGV2参数2
  • ARGN超出最后一个预期参数的参数列表

List、数组或列表

定义一个列表和定义一个普通变量类似,语法如下:

set(<name> <item1> <item2> <item3> ...)

CMake提供相关的操作用于操作列表,其中遍历列表可以使用上面说到的foreach和while循环,更多的列表操作命令可以参考:https://cmake.org/cmake/help/latest/command/list.html。

下面的例子是列表的一些简单操作:

# 列表
set(names java c++ c python objective-c swift kotlin shell)
set(len)
list(LENGTH names len)
message("names length: ${len}")
set(name)
list(GET names 4 name)
message("names[4] = ${name}")
set(isExist)
list(FIND names shell isExist)
message("names contains shell(index): ${isExist}")
list(INSERT names 0 spring)
list(INSERT names 0 ubuntu)
list(INSERT names 0 Music)
message("${names}")

CMake文件处理

CMake的参考文档有文件处理的不同命令,这里只介绍几个重要的命令。

首先是include_directories(DIR),该命令用于包含指定目录的头文件到项目的所有目标(目标由add_executable、add_library和add_custom_target创建的目标),它并不只是将头文件添加到当前目标,还包括它的子目录中的所有目标。

所以如果你的项目是一个多模块构建的项目,不建议使用该命令,应该使用target_include_directories(Target DIR)命令明确地将头文件包含到特定的目标中。

另外还有一个命令:aux_source_directory(<dir> <variable>),该命令用于将<dir>中指定的目录中的所有源文件收集起来,并复制给变量<variable>。

最后是file命令,该命令包含了所有可能用到的文件操作,就不一一介绍了,具体可参考:https://cmake.org/cmake/help/latest/command/file.html。

CMake构建目标

环境检查

有时我们可能需要检查系统中是否包含某些函数,这时可以结合config.h.in和config.h文件添加一个宏,如HAVE_POW,使用configure_file转换配置头文件。

为了检查某个函数是否存在,我们需要加载相应的模块:

include (${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake)

接着使用以下命令检查函数是否存在:

check_function_exists (pow HAVE_POW)

构建选项

如果要使用额外的编译选项可以使用add_compile_options添加,例如:

if (MSVC)
    # warning level 4 and all warnings as errors
    add_compile_options(/W4 /WX)
else()
    # lots of warnings and all warnings as errors
    add_compile_options(-Wall -Wextra -pedantic -Werror)
endif()

如果要使用额外的链接选项,可以使用以下命令添加:

add_link_options(<option> ...)

构建头文件config.h.in和config.h

数据的第一来源是CMakeLists中的变量,包括全局变量。Config.h.in一般放在当前源码目录中,如果在config.h.in中遇到@valuename@,并且CMakeLists中也有变量valuename,那么CMake会使用CMakeLists中valuename去替换该表达式(@valuename@)。

例如我的CMakelists里面有两个变量myname和names,然后我在config.h.in中假如以下宏:

#cmakedefine myname @myname@
#cmakedefine names @names@

运行cmake ..生成Makefile后,CMake会在构建生成目录生成一个config.h头文件,该头文件的内容如下:

#define myname MyName: A String
#define names Music;ubuntu;spring;java;c++;c;python;objective-c;swift;kotlin;shell

Config.h头文件可以添加到编译路径中,在项目源码中使用。

如果不使用CMakeLists中的变量值覆盖,那么可以不使用@value@表达式,而是自己在config.h.in中直接指定值,其过程和以上类似,只不过不需要@@了。

使用第三方库

像平时一样,使用第三方库需要库文件和头文件:

  • 首先是使用target_include_directories(Target DIR)导入库的头文件到指定目标,另外还有一个target_link_directories(这个更像是链接库文件的目录,如果你的库文件和其头文件不在同一个目录,出错的话可以尝试使用这个,当然我还没试过),据官方文档介绍,应该尽量使用target_include_directories。
  • 然后是使用target_link_libraries(Target LIBS)链接库到指定目标。

自定义编译库

如果我们的项目有多个模块,或者想将当前项目作为库使用,可以自定义编译库,构建一个库使用add_library指令:

add_library(<name> [STATIC | SHARED | MODULE]
            [EXCLUDE_FROM_ALL]
            [<source>...])

其中你可以指定构建的是动态库还是静态库,除了MacOS平台,SHARD和MODULE类似。

多模块构建

多模块构建如下:

  • 首先是目录结构,一般在项目根目录使用一个CMakeLists.txt,用于统领整个项目。子模块的根目录拥有自己的CMakeLists.txt,模块之间是父子的关系。
  • 子模块中的构建目标为库,使用add_library,一般是动态库。
  • 父模块中使用add_subdirectory(DIR)添加子模块,DIR为子模块的目录,CMake则会自动去使用子模块的CMakeLists.txt进行构建。
  • 链接子模块的库文件:target_link_librares(Target LibName)。
  • 使用子模块的头文件:target_include_directories。

安装、测试和分发

安装

通常,我们需要安装三种文件:

  • 安装可执行程序:系统的应用一般也是安装到bin目录中,这样你在shell下就可以直接运行该应用了,使用该命令进行安装:install (TARGETS <TargetName> DESTINATION bin)。
  • 安装库:当前程序需要加载的库或当前项目依赖一般都可以安装到本地,原因是程序运行或构建都会从本地默认目录中加载库,使用该命令安装库:install(TARGETS libName DESTINATION </usr/lib DIR>)。
  • 安装文件:和安装库类似,如果你将头文件安装到本地就不用显式指定头文件目录了,安装的目录也是默认被加载的,使用该命令安装头文件install (FILES <FileName> DESTINATION include)。

以上命令只是在CMakeLists.txt中的命令,当我们使用cmake ..生成CMakefile后,需要使用make install进行安装,或者使用camke --install <BuidDIR>进行安装,使用使用cmake安装命令可以指定根前缀,例如cmake --install . --prefix "/home/myuser/installdir"。

默认安装根目录为:usr/local/,其中变量CMAKE_INSTALL_PREFIX定义了默认安装根目录。

测试

本人觉得CMake提供的测试功能真的还好,似乎C/C++的单元测试没有Java那么方便。

CMake的测试一般写在CMakeLists.txt底部,使用步骤如下:

  • 在文件结尾添加测试
  • 启动测试:enable_testing()
  • 添加测试add_test(<TestName> <App> <Args>),<TestName>自定义测试名称,<App>被测试的可执行程序,<Args>程序执行参数
  • 断言:set_tests_properties (<TestName> PROPERTIES PASS_REGULAR_EXPRESSION <expr>),针对测试<TestName>的执行,PASS_REGULAR_EXPRESSION检查输出是否包含后面的表达式,使用正则表达式<expr>检查运行结果
  • 使用make test执行全部测试

GDB调试

使用以下配置GDB调试:

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")

生成安装包

首先在CMakeLists.txt中添加一下代码:

include (InstallRequiredSystemLibraries)
set (CPACK_RESOURCE_FILE_LICENSE
    "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set (CPACK_PACKAGE_VERSION_MAJOR "${<Target>_VERSION_MAJOR}")
set (CPACK_PACKAGE_VERSION_MINOR "${<Target>_VERSION_MINOR}")
include (CPack)
  • 然后在构建目录中运行cpack,或者使用参数,-G指定生成器,例如-G ZIP,-C或--config指定配置,如-C Debug。
  • 生成二进制安装包如:cpack -C CPackConfig.cmake,或直接运行cpack。
  • 生成源码安装包:cpack -C CPackSourceConfig.cmake。
  • 或使用make打包:make package。

CMake多模块构建完整配置

下面我们综合前面学过的来尝试构建一个多模块的项目,并且尝试使用第三方JSON库,这里我将开发环境转到windows下的CLion,不过并不算完整,准确来说应该要包含单元测试的,这个还是下次搞C++再弄吧。

项目整体如下:

  • 每个子模块使用标准的目录,子模块目录采取父子的形式(发现平行目录是不行的),目录包括lib、build、include、src、sources。
  • 根目录和每个子模块都有自己的CMakeLists.tx。
  • 每个子模块编译成静态库或动态库。
  • 子模块有4个:A/B/C以及App,App作为应用程序入口,B和C模块独立,A模块依赖于B和C模块,App同时依赖于这三个模块。

其中B和C模块配置类似,如下:

aux_source_directory(src BSOURCES)
add_library(C STATIC ${BSOURCES})
target_include_directories(C PUBLIC src)

A模块配置如下:

aux_source_directory(src ASOURCES)

add_subdirectory(B)
add_subdirectory(C)

add_library(A STATIC ${ASOURCES})
target_include_directories(A PUBLIC src B/src C/src)
target_link_libraries(A PUBLIC B C)

App配置如下:

cmake_minimum_required(VERSION 3.20)
project(project)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_subdirectory(A)
aux_source_directory(src SOURCES)
add_executable(project ${SOURCES})
target_include_directories(project PUBLIC include A/src)
target_link_directories(project PUBLIC lib)
target_link_libraries(project PUBLIC A jsonxx)

这里我使用了一个第三方库jsonxx,先从github下载下来,然后将其编译成静态库,动态库也可以,我这里使用静态库是为了方便运行。导入静态库的头文件使用target_include_directories(用于编译,-I),链接库文件目录使用target_link_directories(用于链接,-L),链接库文件使用target_link_libraries(具体库名称,-l)。

到此基本完成了CMake项目的构建,当然不是非常完整。其中CMake主要用于项目构建,对于一些更迫切的需求则很难胜任,比如我就觉得写单元测试没有Java那么方面,也没有Java那么人性化的依赖管理。

总结

由以上内容有没有发现CMake比Make简单很多?编写CMakeLists.txt只需要类似编程那样调用几个函数就可以完成很多复杂的构建功能了,而编写Makefile有时还需要兼顾编译器。

另外,其实CMake的脚本命令不算太友好,还算过得去。关于一些构建命令的相关参数我还是要认真看看才行,例如PRIVATE、PUBLIC、INTERFACE的区别等等。

关于C/C++的编程环境,在类Unix系统下一般可以使用vim+cmake,而在macOS或Windows下则可以选择一些IDE,毕竟IDE还是灵活过命令行,有些IDE是不使用CMake的,例如VS。

而对于使用C/C++来开发什么,不少人都是很迷惑的,包括我。开发Web后端?多数情况使用Java、PHP、Python似乎更好呢!在我看来,C/C++适合用于处理高密集型的需求,例如高并发、图形处理、对算法有更高的要求、数学相关、游戏相关。但是一个正常的后端程序员怎么会搞这些东西呢?所以还是要搞高端的东西,不然用其他语言会更好。

木子山

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: