我的编程空间,编程开发者的网络收藏夹
学习永远不晚

C++和C的混合编译的项目实践

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

C++和C的混合编译的项目实践

简介

C++ 语言的创建初衷是 “a better C”,但是这并不意味着 C++ 中类似 C 语言的全局变量和函数所采用的编译和连接方式与 C 语言完全相同。作为一种欲与 C 兼容的语言, C++ 保留了一部分过程式语言的特点(被世人称为"不彻底地面向对象"),因而它可以定义不属于任何类的全局变量和函数。但是, C++ 毕竟是一种面向对象的程序设计语言,为了支持函数的重载, C++ 对全局函数的处理方式与 C 有明显的不同。

本文将介绍如何通过 extern “C” 关键字在 C++ 中支持 C 语言 和 在C语言中如何支持 C++

某企业曾经给出如下的一道面试题

为什么标准头文件都有类似以下的结构?

//head.h
#ifndef HEAD_H
#define HEAD_H

#ifdef __cplusplus
extern "C" {
#endif

    

#ifdef __cplusplus
}
#endif

#endif 

问题分析

  • 这个头文件head.h可能在项目中被多个源文件包含(#include “head.h”),而对于一个大型项目来说,这些冗余可能导致错误,因为一个头文件包含类定义或inline函数,在一个源文件中head.h可能会被#include两次(如,a.h头文件包含了head.h,而在b.c文件中#include a.h和head.h)——这就会出错(在同一个源文件中一个结构体、类等被定义了两次)。
  • 从逻辑观点和减少编译时间上,都要求去除这些冗余。然而让程序员去分析和去掉这些冗余,不仅枯燥且不太实际,最重要的是有时候又需要这种冗余来保证各个模块的独立

为了解决这个问题,上面代码中的

#ifndef HEAD_H
#define  HEAD_H

#endif 

就起作用了。如果定义了HEAD_H,#ifndef/#endif之间的内容就被忽略掉。因此,编译时第一次看到head.h头文件,它的内容会被读取且给定HEAD_H一个值。之后再次看到head.h头文件时,HEAD_H就已经定义了,head.h的内容就不会再次被读取了。

那么下面这段代码的作用又是什么呢?

#ifdef __cplusplus
extern "C" {
#endif

#ifdef __cplusplus
}
#endif

我们将在后面对此进行详细说明。

关于 extern “C”

前面的题目中的 __cplusplus 宏,这是C++中已经定义的宏,是用来识别编译器的,也就是说,将当前代码编译的时候,是否将代码作为 C++ 进行编译。

首先从字面上分析extern “C”,它由两部分组成:extern关键字、“C”。下面我就从这两个方面来解读extern "C"的含义。

首先,被它修饰的目标是 extern 的;其次,被它修饰的目标是 C 的。

extern关键字

被 extern “C” 限定的函数或变量是 extern 类型的。

extern是C/C++语言中表明函数全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数。

被extern修饰的函数,需要在编译阶段去链接该目标文件,并且与extern对应的关键字是 static,被static修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块使用时,其一般是不可能被extern “C”修饰的。

**注意:**例如语句 extern int a; 仅仅是对变量的声明,其并不是在定义变量 a ,声明变量并未为 a 分配内存空间。定义语句形式为 int a; 变量 a 在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。

被 extern “C” 修饰的变量和函数是按照 C 语言方式编译和连接的。

由于C++和C两种语言的亲密性,并且早期大量的库都是由C语言实现的,所以不可避免的会出现在C++程序中调用C的代码、C的程序中调用C++的代码,但是它们各自的编译和链接的规则是不同的。

函数名修饰

  1. 由于Windows下vs的修饰规则过于复杂,而Linux下gcc的修饰规则简单易懂,下面我们使用了gcc演示了这个修饰后的名字。
  2. 通过下面我们可以看出gcc的函数修饰后名字不变。而g++的函数修饰后变成【_Z+函数长度+函数名+类型首字母】。

分别使用C的编译器和C++的编译器去编译并获得一个可执行文件

使用C语言(gcc)编译器编译后结果

使用objdump -S 命令查看gcc生成的可执行文件:

image-20220518225738183

使用C++编译器(g++)编译后结果

使用objdump -S 命令查看g++生成的可执行文件:

image-20220518225745296

**linux:**修饰后的函数名= _Z + 函数名长度 + 形参类型首字母,Windows下也是相似的,细节上会有所不同,本质上都是通过函数参数信息去修饰函数名。

C++的编译和链接方式

采用g++编译完成后,函数的名字将会被修饰,编译器将函数的参数类型信息添加到修改后的名字中,因此当相同函数名的函数拥有不用类型的参数时,在g++编译器看来是不同的函数,而我们另一个模块中想要调用这些函数也就必须使用C++的规则去链接函数(找修饰后的函数名)才能找到函数的地址。

C的编译和链接方式

对于C程序,由于不支持重载,编译时函数是未加任何修饰的,而且链接时也是去寻找未经修饰的函数名。

C和C++直接混合编译时的链接错误

在C++程序,函数名是会被参数类型信息修饰的,这就造成了它们之间无法直接相互调用。

例如:

print(int)函数,使用g++编译时函数名会被修饰为 _Z5printi,而使用gcc编译时函数名则仍然是print,如果直接在C++中调用使用C编译规则的函数,会链接错误,因为它会去寻找 _Z5printi而不是 print。

【C和C++的编译和链接方式的不同】参考:

C++的函数重载

extern“C”的使用

extern "C"指令非常有用,因为C和C++的近亲关系。注意:extern "C"指令中的C,表示的一种编译和连接规约,而不是一种语言。

并且extern "C"指令仅指定编译和连接规约,并不影响语义,编译时仍是一个C++的程序,遵循C++的类型检查等规则。

对于下面的代码它们之间是有区别的

extern "C" void Add(int a, int b);
//指定Add函数应该根据C的编译和连接规约来链接
extern void Add(int a, int b);
//声明在Add是外部函数,链接的时候去调用Add函数

如果有很多内容要被加上extern “C”,你可以将它们放入extern “C”{ }中。

通过上面的分析,我们知道extern "C"的真实目的是实现类C和C++的混合编程,在C++源文件中的语句前面加上extern “C”,表明它按照类C的编译和连接规约来编译和连接,而不是C++的编译的连接规约。这样在类C的代码中就可以调用C++的函数or变量等。

那么混合编译首先要处理的问题就是要让我们所写的C++程序和C程序函数的编译时的修饰规则链接时的修饰规则保持一致。

总共就有下面四种情况,也就是说一个C的库,应该能同时被C和C++调用,而一个C++的库也应能够同时兼容C和C++。

image-20220518225753095

为了展示如上四种情况,我们分别建立一个C静态库和C++静态库

C程序调用C的库,C++程序调用C++的库,这是理所应当的,因此我们关注的问题是如何交叉调用

用法举例

静态库是什么

库是写好的现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常

之所以称为【静态库】,是因为在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接。

试想一下,静态库与汇编生成的目标文件一起链接为可执行文件,那么静态库必定跟.o文件格式相似。其实一个静态库可以简单看成是一组目标文件(.o/.obj文件)的集合,即很多目标文件经过压缩打包后形成的一个文件。静态库特点总结:

  • 静态库对函数库的链接是放在编译时期完成的。
  • 程序在运行时与函数库再无瓜葛,移植方便。
  • 浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。

静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库,因此体积较大

创建C静态库

我们以一个栈的静态库为例:

首先新建项目Stack_C

image-20220518225759900

新建源文件和头文件

image-20220518225807539

写好栈的代码

注意一定是C程序,即源文件后缀为c

image-20220518225814328

更改输出文件类型

右键项目名称—>属性

image-20220518225821196

更改为配置类型为静态库

image-20220518225828078

生成静态库

image-20220518225835992

查看是否生成成功

VS一般在项目路径下的x64\Debug路径下:

image-20220518225845182

至此,静态库已经可以成功建立了。

再新建一个项目,写一个去调用该静态库实现的栈的程序(以括号匹配问题为例)

不过对于VS我们的静态库是默认不去使用的,因此我们需要将静态库的路径和库的名称分别添加到库目录和依赖项,才能让程序能去调用该静态库。

image-20220518225851175

更改链接器配置

右键项目名—>点击属性

“属性面板“—>”配置属性”—> “链接器”—>”常规”,附加依赖库目录中输入,静态库所在目录;

增加库目录(路径为我们刚刚生成的静态库所在的Debug文件夹)

image-20220518225858234

增加附加依赖项

名称为Stack_C项目生成的静态库名,一般是项目名 + .lib

“属性面板”—>”配置属性”—> “链接器”—>”输入”,附加依赖库中输入静态库名StaticLibrary.lib。

image-20220518225905839

我们先尝试使用C程序来调用该静态库

新建项目

将源文件后缀改为c;包含上Stack_C项目(静态库项目)的头文件;点击生成解决方案;

image-20220518225913686

成功生成,说明成功调用。

尝试使用C++程序调用C静态库

  • 将源文件后缀改为cpp;
  • 头文件保持不变;
  • 点击生成解决方法

结果报错了:

image-20220518225924430

这说明在链接的过程中出现了问题,也就是在我们的程序找不到静态库中函数的地址,原因是我们的静态库是C语言的,没有对函数进行修饰,但在我们的调用方是C++程序,在链接过程中找的是修饰过的函数名,因此无法找到函数的地址。

既然C语言的静态库只能按照C的规则去编译这些函数(即不修饰函数名),那么我们只要让C++程序按照C语言的链接规则(即找未经修饰的函数名)去找到函数名不就解决了?

两种思路:

  • 改变C库的编译和链接方式为C++规则;
  • 改变C++程序调用库函数的编译和链接方式为C的规则;

方法1是不行的,因为C语言中可没有extern “C++”这种东西,那么考虑方法2;

这时我们可以借助extern“C”改变C++程序的链接规则,让C++去按照C的规则去找函数名,即未经过任何修饰的函数名,那就一定能找到函数的地址,来去正确调用静态库。

在源文件test.cpp使用extern “C”,去改变包含的头文件中的函数的链接规则

//调用库的的模块的头文件包含
extern "C"
{
	#include"..\..\Stack_C\Stack_C\stack.h"
}
//程序的代码
//...

那么在test.cpp去链接函数时,就会直接去找原函数名。

这样就解决了。

还有一个一步到位的解决方法,利用条件编译,根据当前程序的类型,选择是否去执行extern “C”指令。

  • 调用方是C程序,不做处理;
  • 调用方是C++程序,需要使用extern“C”将程序改为C的链接规则;
//调用库的的模块的头文件包含
#ifdef __cplusplus//如果是c++程序,就执行extern “C”,使用C的链接方式,去找未经修饰的函数名
extern "C"{
#endif
#include"..\..\Stack_C\Stack_C\stack.h"
#ifdef __cplusplus
}
#endif
//程序的代码
//...

但是这样的处理不太好,我们作为调用方自然是想可以直接通过头文件包含的方式去使用库里的函数,因此采用下列方法,更改库的头文件函数声明为:

#ifdef __cplusplus//如果定义了宏__cplusplus就执行#ifdef 到 #endif之间的语句
extern "C"
{
#endif
void StackInit(struct Stack* s);
void StackPush(struct Stack* s, DataType x);
void StackPop(struct Stack* s);
DataType StackTop(struct Stack* s);
int StackSize(struct Stack* s);
void StackDestory(struct Stack* s);
bool StackEmpty(struct Stack* s);
#ifdef __cplusplus
}
#endif

这样的一段代码,无论是C++程序还是C程序都可以直接#include就能去调用该静态库了。

创建C++静态库

步骤和创建C的静态库相同,只不过要将项目中的源文件后缀改为cpp,就会生成一个C++的静态库,因此不再阐述。

创建完成后,我们仍使用刚刚的项目,并且添加C++静态库路径到库目录,添加C++静态库名称到附加依赖项,仍然以括号匹配问题为例去调用该库。(记得删除C静态库的库目录和附加依赖项,否则我们的程序有可能还会去调用C的静态库,这样我们就无法探究如何去调用C++静态库的问题了)

尝试使用C程序调用C++静态库

我们不着急调用,经过先前的经验,这里可以判断,C++的程序去调用C++的库一定是没问题的,但是C程序就不好说了,因此我们要搞定C程序调用C++库的情况,先搞清楚它们的差异:

这里的C++程序去调用函数是去寻找修饰后的函数名,C程序是去找未修饰的函数名,要想让它们保持一致有两个思路:

改变C程序的编译和链方式为C++的规则;改变C++静态库的编译方式为C的规则;

但是方法1是不行的,之前也说过,C语言中没有extern “C++”这种东西,那么考虑方法2;

对库的头文件中的函数做如下处理:

//用C的规则去搞库的编译和链接方式
extern "C"
{
	void StackInit(struct Stack* s);
	void StackPush(struct Stack* s, DataType x);
	void StackPop(struct Stack* s);
	DataType StackTop(struct Stack* s);
	int StackSize(struct Stack* s);
	void StackDestory(struct Stack* s);
	bool StackEmpty(struct Stack* s);
}

那么现在C++的静态库的函数名都是没有经过修饰的。(C的规则)

但是我们去编译仍然报错:

error C2059: 语法错误:“字符串”

"StackInit”未定义;假设外部返回int

“StackPush”未定义;假设外部返回int

“StackEmpty”未定义;假设外部返回int

“StackTop”未定义;假设外部返回int

“StackPop”未定义;假设外部返回int

这是因为我们使用C程序时也包含了此头文件,但是C语言中无法识别extern“C”,因此报错。

我们尝试使用条件编译来决定是否使用extern“C”,根据调用方的不同改变函数链接规则:

  • 调用方是C++程序,那么需要使用extern“C”将C++程序的函数链接规则变为C的;
  • 调用方是C程序,不使用extern“C”语句;

因此我们做如下处理,将库的头文件中的函数声明加上:

#ifdef __cplusplus//如果定义了宏__cplusplus就执行#ifdef 到 #endif之间的语句
extern "C"
{
#endif
void StackInit(struct Stack* s);
void StackPush(struct Stack* s, DataType x);
void StackPop(struct Stack* s);
DataType StackTop(struct Stack* s);
int StackSize(struct Stack* s);
void StackDestory(struct Stack* s);
bool StackEmpty(struct Stack* s);
#ifdef __cplusplus
}
#endif

总结:C++和C之间的混合编译,为了消除函数名修饰规则不同的的差别,我们需要使用extern ”C“来改变C++的编译和连接方式。

但这样问题也随之而来:

C++的库就失去了函数重载的特性,如果库中有同名函数,那么就无法正确编译,因为按照C的方式去编译,函数名会冲突。

如何解决这个问题呢?

实际上这个问题无法解决,一旦选择了将某个函数指定了按照C的方式去编译链接,那么这个函数就已经失去了重载的特性了,不过Cpp的库中未被指定按照C的规则去编译和链接的那些函数,仍然可以被重载,并且具有C++的一切特性。

因此这个问题无解,只有通过避免“一刀切”的方法来保护那些我们想重载的函数,也就是说一部分库里的函数那就是实现给C程序调用的,我们就通过extern“C”改变它的编译和链接方式,而对于那些实现给C++程序调用的函数接口,我们不做任何处理,并且不暴露给C程序。

想要实现上述过程,我们需要在静态库项目中创建两个头文件libc.hlibcpp.hlibc.h声明那些需要暴露给C程序的函数接口,并且使用上面介绍的条件编译和extern“C”,libcpp.h声明那些暴露给给Cpp程序的函数接口,这样两个头文件的函数的链接规范互不相同,也互不干扰。只需要将lic.h在C程序调用的地方使用#include 包含,libcpp.h在C++程序调用的地方使用#include包含即可使用。

因此C++库中哪个接口需要暴露给C,我们就用extern“C”修饰哪个接口。

image-20220518225938546

总之,C的库可以给C程序和C++程序调用,而C++库也可以被C程序和C++程序调用

如果要满足这个库中所有的函数都能同时被C++和C调用,那么无论是C的库还是C++的库,最终这个库的编译和链接方式都只能是C的规范,因为C++可以使用C的链接规范但是C不能使用C++的链接规范,也就导致了如果库的链接规范是C++的,那么无论如何,C程序都无法调用。

值得一提的是C++程序中的函数可以使用两种链接规范,因此我们可以针对函数的使用场景来选择该函数的编译和链接规范,使得一部分函数保留C++的特性,但一部分函数就只能为了兼容C而牺牲C++的特性,想要既兼容C又保留C++的特性,这是做不到的。

到此这篇关于C++和C的混合编译的项目实践的文章就介绍到这了,更多相关C++和C混合编译内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

C++和C的混合编译的项目实践

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

python+C、C++混合编程的应用

TIOBE每个月都会新鲜出炉一份流行编程语言排行榜,这里会列出最流行的20种语言。排序说明不了语言的好坏,反应的不过是某个软件开发领域的热门程度。语言的发展不是越来越common,而是越来越专注领域。有的语言专注于简单高效,比如python
2023-01-31

Python和C++的混合编程(使用Bo

想要享受更轻松愉悦的编程,脚本语言是首选。想要更敏捷高效,c++则高山仰止。所以我一直试图在各种通用或者专用的脚本语言中将c++的优势融入其中。原来贡献过一篇《c++和js的混合编程》也是同样的目的。  得益于机器学习领域的发展,Pytho
2023-01-30

C++项目开发经验分享:C++开发项目的实践经验

C++是一种编程语言,特别适合用于开发高性能和复杂的应用程序。作为一名C++开发者,我有幸参与了多个C++项目的开发,并积累了一些宝贵的实践经验。在本文中,我想分享一些关于C++项目开发的经验和技巧。首先,一个成功的C++项目需要良好的架构
C++项目开发经验分享:C++开发项目的实践经验
2023-11-22

C/C++在Java、Android和Objective-C三大平台下实现混合编程

Android和iOS开发都支持C++开发,可以一套代码多平台使用。同时C++难以反编译的特性也可以为Android开发带来代码的保密,另一native特性也可以提高代码的运行效率。 一、为什么使用C/C++便于移植,用C/C++写得库可以
2022-06-06

SpringBoot整合chatGPT的项目实践

本文主要介绍了SpringBoot整合chatGPT的项目实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
2023-03-24

C++ 泛型编程最佳实践和注意事项?

在 c++++ 中泛型编程时,遵循最佳实践至关重要,包括选择合适的容器类型、优先使用标准库算法、避免嵌套泛型以及注意类型擦除。这些实践有助于编写高效、可维护且无错误的代码,例如下面所示的计算列表元素和的函数:template t sum_
C++ 泛型编程最佳实践和注意事项?
2024-04-24

如何使用包含 C 代码的包正确编译 Golang 项目

问题内容我开始用 Golang 编写一个项目,但立即遇到了问题。我需要从我的代码连接到硬件,我有一个驱动程序和一个供应商提供的 Golang 包装器。包装器连接的描述说需要将代码放在项目的 src 目录中(我不使用这样的目录,但我将文件按
如何使用包含 C 代码的包正确编译 Golang 项目
2024-02-06

如何更好的实现C++编译

如何更好的实现C++编译,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。C++只是一个ISO标准,标准中规定了C++的基本语法以及这些语法对应的语义(可以简单理解为语法的含义
2023-06-17

混合语言编程—C#使用原生的Directx和OpenGL绘图的方法

本文要说的是混合C#和C/C++语言编程,在C#的Winform和WPF下使用原生的Direct和OpenGL进行绘图
2022-11-15

C#中如何实现事件和委托的编译

这篇文章给大家分享的是有关C#中如何实现事件和委托的编译的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。C#事件和委托的编译需求操作C#事件时,有时会得到编译错误:事件“Delegate.GreetingManag
2023-06-18

VS里使用C#制作窗口应用的项目实践

本教程详细介绍了在VisualStudio中使用C#创建窗口应用程序的步骤,包括:创建项目、设计用户界面、添加代码、处理用户输入、与数据交互、错误处理、调试、测试、部署、最佳实践等。遵循此指南,您可以逐步构建功能强大的窗口应用程序。
VS里使用C#制作窗口应用的项目实践
2024-04-02

怎么分析和提高C/C++程序的编译速度

这篇文章主要讲解了“怎么分析和提高C/C++程序的编译速度”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么分析和提高C/C++程序的编译速度”吧!硬件、编译器造成的使用好点的电脑无疑是一个
2023-06-16

C++模板编程的精髓与实践

c++++模板编程通过使用泛型类型在编译时生成可适用于不同类型数据的代码,从而提升代码的灵活性和可重用性。它广泛应用于容器类、算法函数和元编程,并能动态生成代码或优化编译时性能。实战案例中,模板函数计算不同类型数据的最大值,以演示其灵活性。
C++模板编程的精髓与实践
2024-05-21

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录