c语言和c++的相互调用_c调用c++-程序员宅基地

技术标签: c++  c  相互调用  C/C++编程  extren-C  cplusplu  c/c++语言  

本文收录于微信公众号「 LinuxOK 」,ID为:Linux_ok,关注公众号第一时间获取更多技术学习文章。

在实际项目开发中,c和c++代码的相互调用是常见的,c++能够兼容c语言的编译方式,但是c++编译器g++默认会以c++的方式编译程序,而c程序编译器gcc会默认以c的方式编译它,所以c和c++的相互调用存在一定的技巧。

####1.c方式编译和c++方式编译
一般.cpp文件是采用g++去编译,.c文件是采用gcc编译,然而这不是绝对的。
(1)gcc和g++都可以编译.c文件,也都可以编译.cpp文件。g++和gcc是通过后缀名来辨别是c程序还是c++程序的(这一点与Linux辨别文件的方式不同,Linux是通过文件信息头辨别文件的)。
(2)在gcc看来,.c文件会以c方式去编译,.cpp文件则是以c++的方式去编译,注意,gcc不会主动去链接c++用到库stdc++,所以用gcc编译cpp文件时需要手动指定链接选项-lstdc++。而对于g++,不管是.c还是.cpp文件,都是以c++方式去编译。
(3)还需要注意,并不是说__cpluscplus 是g++编译器才会定义的宏,确切的说,是只有以c++编译的方式去编译文件的宏才会定义的宏,这样说来,gcc编译.cpp文件、g++编译.c、.cpp文件,这个 __cplusplus都会被编译器定义。

####2.c++调用c程序
假设c程序是之前写好的具有价值的静态库,该库是由add.o和sub.o编译而成,而add.c和sub.c是由c语言写的:
add.h和add.c

//add.h
#ifndef __ADD_H__
#define __ADD_H__
int add(int, int);
#endif /* __ADD_H__ */

//add.c
#include "add.h"

int add(int a, int b)
{
	return a + b;
}

sub.h和sub.c

//sub.h
#ifndef __SUB_H__
#define __SUB_H__
int sub(int, int);
#endif /* __SUB_H__ */

//sub.c
#include <stdio.h>
#include "sub.h"
int sub(int a, int b)
{
    printf("%d - %d = %d\n", a, b, a - b);
    return 0;
}

将各自编译成.o文件,并打包成静态库

$ gcc -c add.c
$ gcc -c sub.c
$ ar cqs libadd.a *.o

这样就生成了libadd.a静态库。
若要编译成动态库,则

$ gcc -shared -fPIC *.o -o libadd.so

main.cpp是c++写的,用g++编译。

//main.cpp
#include "add.h"
#include "sub.h"
#include <stdio.h>

int main(void)
{
    int c = add(1, 6);
    sub(8, 2);
    printf("c = %d\n", c);
    return 0;
}

编译:
这里写图片描述
报错未定义的add和sub函数。
通过nm确实是可以看到add和sub标号的存在的:
这里写图片描述
原因在于,main.cpp是c++文件,用g++编译器编译时(gcc也是一样的结果),会优先选择cpp的编译方式,也就是会用cpp的编译方式去编译add()、sub()函数。然而,它们是.c文件,用的是c语言的方式去编译的,所以出现如上问题。注意,cpp编译器是兼容c语言的编译方式的,所以在编译cpp文件的时候,调用到.c文件的函数的地方时,需要用extern "C"指定用c语言的方式去编译它:

//使得add.h、sub.h里面的代码用c语言的方式去编译
extern "C"{
#include "add.h"
#include "sub.h"
}
#include <stdio.h>
int main(void)
{
    int c = add(1, 6);
    printf("c = %d\n", c);
    return 0;
}

编译运行
这里写图片描述
特别注意extern "C"是c++方式编译才认识的关键字。

####3. c语言调用c++程序
c语言用c方式编译,c++程序用c++方式,要使得c语言能调用c++程序无非有2种方法(c++调用c也是一样)
(1) c语言程序用c++方式编译
既然是c语言调用c++程序,肯定是要采取c++方式编译,所以觉得这个没什么意义。操作很简单,用g++方式编译c程序即可,注意,g++会对语法、类型等更为严格的检查。
(2) c++程序用c语言方式编译
a. c方式编译和c++方式编译,其差异就在于符号表标识符。同一个函数名,在c方式编译的其函数名跟编译前的函数一致,c++方式编译的则是以函数名结合参数作为编译后的函数名。要确保文件以.c方式编译,可以利用__cplusplus,这个宏在c++编译的方式才会定义的宏,结合之前的extern C用法如下:

#ifdef __cplusplus
extern "C"{
#endif /* __cplusplus */

//用c方式编译的代码

#ifdef __cplusplus
}
#endif /* __cplusplus */

这样,对于一份.c文件,采用gcc编译时候没有定义__cplusplus,宏判断不起作用,且自是用c语言的方式编译,采用g++编译定义了_cplusplus,经过上面宏判断,所以还是会以c语言的方式编译。注意,extern “C”是g++才具有的关键字,gcc并没有,所以如果用gcc编译而不加以宏判断直接使用extern “C”那么就会出现语法错误。
用c方式去编译c++文件,还要注意重载函数。c方式编译的c++文件决定不能出现重载函数。尝试extern “C”中出现重载函数:

#ifdef __cplusplus
extern "C"{
#endif /* __cplusplus */
int func(int a, int b){
    return a + b;
}

void func(const char* str){
    printf("str = %s\n", str);
}
#ifdef __cplusplus
}
#endif /* __cplusplus */

int main(void)
{
    func("hello");
    return 0;
}

这里写图片描述
提示找不到func函数。在c语言中肯定不能出现同名函数,不然编译器怎么知道它要调用的是哪一个。去除extern “C”关键字,也就是使其采用c++方式编译,编译结果:
这里写图片描述
编译通过。通过nm命令可以查看可执行程序的符号表:
这里写图片描述
可见c++编译方式会将重载函数名结合参数形成唯一的函数名。但是c方式编译的可执行文件却并非如此,函数名经过编译后的符号还是之前的函数名,所以出现找不到调用函数的现象。
b. c程序要调用c++写的程序,涉及到的可能有:c程序调用c++的普通函数,c程序调用c++的重载函数,c程序调用c++的成员函数(包括虚函数)。c程序自然是采用c的方式去编译的,即是采用gcc编译器,然而c++是采用c++方式编译,所以要强制将c++代码以c的方式去编译。
(1) c程序调用c++的普通函数

add.h和add.cpp
//add.h
#ifndef _ADD_H_
#define _ADD_H_
int add(int, int);
#endif /* _ADD_H_ */
//add.cpp
#include <iostream>
extern "C" {    //以c的方式去编译
int add(int a, int b)
{
    std::cout<<"a + b = "<< a + b << std::endl;
    return 0;
}
}

注意不能在声明add函数的add.h中指定以c的方式去编译,因为add.h是要被main.c文件包含的,c方式编译时并不能认得extern “C”关键字。
main.c

//main.c
#include <stdio.h>
#include "add.h"
int main(void)
{
    add(4, 9);
    return 0;
}

编译运行:
这里写图片描述
因为add.cpp用到标准c++库,所以要手动链接该库。
(2)c调用c++的重载函数
前面已经知道,在extern “C”中是不允许出现重载函数的,因为c方式下的程序并不支持重载函数,所以需要对重载函数进行接口封装。使用extern “C”是要告诉编译器按照c的方式来编译封装接口,接口里面的函数还是按照c++的语法和c++方式编译。
add.h和add.cpp

//add.h
#ifndef _ADD_H_
#define _ADD_H_
int add_ii(int, int);
int add_c(char);
#endif /* _ADD_H_ */

上面两个函数是供c程序调用的,自然需要用c方式编译。而这两个函数的实现体可以去调用c++的重载函数,这就完美契合了。

//add.cpp
#include <iostream>
int add(int a, int b)
{
    std::cout<< "a + b = " << a + b << std::endl;
    return 0;
}

int add(char c)
{
    std::cout << "c = " << c << std::endl;
}

extern "C" int add_ii(int a, int b)  //供c程序调用,用c方式编译
{
    add(a, b);
}

extern "C" int add_c(char c)
{
    add(c);
}

main.cpp

//main.cpp
#include <stdio.h>
#include "add.h"

int main(void)
{
    add_ii(4, 9);
    add_c('d');
    return 0;
}

编译运行:
这里写图片描述
(3)c程序调用c++的成员函数
这个比较复杂,先不说。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_29344757/article/details/73332501

智能推荐

shell、cmd、dos和脚本语言区别和联系_shell和dos的关系-程序员宅基地

文章浏览阅读526次。问题一:DOS与windows中cmd区别在windows系统中,“开始-运行-cmd”可以打开“cmd.exe”,进行命令行操作。操作系统可以分成核心(kernel)和Shell(外壳)两部分,其中,Shell是操作系统与外部的主要接口,位于操作系统的外层,为用户提供与操作系统核心沟通的途径。在windows系统中见到的桌面即explorer.exe(资源管理器)是图形shell,而cmd就是命令行shell。这算是cmd与dos的最大区别,一个只是接口、一个是操作系统。只是cmd中的某些命令和dos_shell和dos的关系

fping-2_fping2-程序员宅基地

文章浏览阅读534次。ping命令以前是一个很好用并且常用的网络测试工具,它是基于ICMP协议,但是出于网络安全等因素,大部分网络环境以及云环境可能都会禁止ICMP协议,所以在工作中,我们必须掌握一些其他比较流行的网络测试工具,下面分别介绍tcpping、tcping、psping、hping、paping等几款网络测试工具。关于ICMP概念:ICMP是(Internet Control Message Pro..._fping2

黑马程序员--反射--实现一个简单的集合操作框架_集合框架的反射-程序员宅基地

文章浏览阅读466次。* 问题: * 已知一个Point类,如何从配置文件中获取具体的集合类,将一系列Point对象添加到集合,然后返回该集合? * 解决方案: * 由于具体集合类未知,所以不能直接编写操作方法,需要依据具体的集合称,生成字节码对象, * 再由字节码对象构造一个实例; * 这样就可以编写一个简单的框架,在未知具体类的情况下,将Point对象添加到集合中; * 关键词: 反射 Cl_集合框架的反射

C#使用SunnyUI(界面更美观)示例代码-程序员宅基地

文章浏览阅读4.4k次。C#使用SunnyUI(界面更美观)示例代码_sunnyui

2022年4月最新面经答案总结(Java基础、数据库、JVM、计网、计操、集合、多线程、Spring)持续更新_2022年4月自考java-程序员宅基地

文章浏览阅读113次。先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。经过残酷的春招实习面试,自己从中也从牛客和各个大神那些收集了很多面经,我再次深感谢谢(尤其是JavaGuide),秉持开源的想法。..._2022年4月自考java

vostro3070装win7_戴尔Vostro 成就 3070台式机装win7系统及bios设置-程序员宅基地

文章浏览阅读340次。[文章导读]戴尔Vostro 成就 3070是一款商用台式机,该机型采用Intel 酷睿i5 8100 第八代处理器。默认预装了win10系统,最近有很多网友改了win7后发现进不了系统,由于默认戴尔Vostro 成就 3070安装win7是无法使用的,除了BIOS中要关闭安全启动外,还要采用win7新机型,要不然安装后USB和集显不能使用,要采用本站的win7新机型才可以,那么戴尔Vostro ..._戴尔3070台式机怎么装win7?戴尔3070装win7系统及bios设置教程

随便推点

自己动手写AdobeReader书签插件——PDF也支持书签-程序员宅基地

文章浏览阅读202次。最近经常看一些PDF的电子文档,痛苦的是Adobe Reader竟然没有提供书签的功能,每次看完之后再回到上次看的地方都很麻,到网上一找,还真有人就做了PDF的书签,下载一试效果不错,于是参看了一下Adobe Reader的SDK,修改了一点地方,以更方便的使用,下面是程序的使用方法1、打开编辑的首选项项2、确保下面的选项都被选中3、把下面的代码保存为一个bookmark_page.js..._adoba pdf自定义书签插件

【error】postgresql relation does not exist-程序员宅基地

文章浏览阅读5.5w次,点赞10次,收藏12次。最近刚刚使用postgresql遇到很多问题。postgresql relation does not exist使用postgresql 查询 AAA 数据表时,提示 postgresql relation does not exist ,可是 SELECT tablename FROM pg_tables;AAA 表是存在的,好奇怪。搜索之后发现,是因为引号的问题。PostgreS_relation does not exist

python爬虫登陆当当网(图片旋转验证么)_当当网爬虫需登录-程序员宅基地

文章浏览阅读2.4k次。python爬虫登陆当当网,给出了旋转验证码的解决方法,也给出了使用第三方登陆绕过旋转图片验证码。_当当网爬虫需登录

c++ ->vscode && unity->vscode-程序员宅基地

文章浏览阅读304次,点赞12次,收藏9次。一·配置:MIVC—》vscode下载vs:vs下载项 命令行——编译——运行: MIVC(vs下载)—》vscode-》cl 编译命令 ;MinGW (MINGW下载)-》GNU编译器 -》g++编译命令 ; MIVC环境变量配置分为3部分 编译:cl /EHsc demo.cpp。如果生成 .exe则成功 编译错误:可以将这些添加到cpp根目录中 运行:demo.exe vscode——编译——运行 使用dcp输入code(配置了path)打开vscode 配置插

MySQL5直接到8_mysql5.5换成mysql8.0-程序员宅基地

文章浏览阅读1k次。由于在建表钟发现有些语句就是录不进去,研究发现是因为5.5版本过低导致,就想换到5.7版本,结果一看8.0都出了,据官方说明8.0要比5系列快2倍网上,遂直接换成8.0了,不过这个过程真的心累。1、卸载首先把MYSQL文件目录下的mysql5.5中的data文件放到其他地方保存,然后通过360等软件卸载就行了(我之前用的是安装版的),压缩版的写在请点击这里2、下载 解压缩上面的为压缩版,下面的为安..._eclisp中怎么把mysql5替换mysql8

大叔手记全集-程序员宅基地

文章浏览阅读92次。大叔手记:旨在记录日常工作中的各种小技巧与资料(包括但不限于技术),如对你有用,请推荐一把,给大叔写作的动力 大叔手记(1):使用Visual Studio的查找与替换替代默认的系统搜索大叔手记(2):为每个应用程序池单独设置aspnet.config配置文件大叔手记(3):Windows Silverlight/Phone7/Mango开发学习系列教程大叔手记(4):jQue...

推荐文章

热门文章

相关标签