C++的编译器在编译函数时,会对函数进行换名,将参数的类型信息整合到新的名字中,解决函数重载和名字冲突的矛盾。
在C++标准语法规定,在编译C++函数时候,会进行换名,将函数的参数表类型信息整合到新的名字中,因为满足多个重载函数的多个函数参数有所差异,这样在换名字之后他所得到的新的名字也是有所差异的 ,通过这样的语法规则,来解决函数重载和名字冲突的矛盾。
c++
//test.c
void func(int i,double d){}
/*现在分别使用C编
译器和C++编译器来
编译test.c源文件,
查看二者差异。*/
gcc -C test.c -o 1.o
g++ -C test.C -o 2.o
nm//可以查看一个目标文件中的一些标识符
nm 1.o
00000000 T func//gcc 编译后目标文件中的函数名字并没有改变
nm 2.0
00000000 T _Z4funcid
/*经过C++编译器g++编
译后的源文件中的函数
名字被改变了,变为
了"_Z4funcid",其中"_Z"是
编译器的内置标识,并
没有什么具体的含义,我
们可以忽略它,"4"表示的
应该是函数名字的长
度(func正好是4个字符),
"i"表示int,"d"表示double.
因为此时源文件test.c中
的函数func(int i,double d)的
参数是一个int类型和
一个double类型。换名就
是将函数的参数表的类型
信息给整合到新的名字中。
这样假设在一个文件当中
定义了重载关系的多个函
数,经过换名以后,因为
它的参数有差别,因此换
名后的名字也有所差异,
这时候再进行链接的话,不
会有重定义的错误。*/
c++
//test.c
void func(int i, double d){}
void func(int i, int j){}
/*此时test.c中的函
数构成重载关系
(相同作用域;相
同函数名;不同参
数)。此时再次使用
gcc编译器编译,会
报重定义的错误,但
是使用g++编译器编
译却可以编译通过,
通过nm命令查看
可以发现新换的
名字的函数名字
是有所差异的,因
为名字不同,所以
编译时不会出现重
定义错误。这就是
函数重载原理。*/
g++ -C test.c
nm test.o
00000000 T _Z4funcid
00000015 T _z4funcii
函数重载的原理不重要,因为实际开发中,知道什么是函数重载,知道函数重载的应用的语法就可以,但还是要知其然更要知其所以然。
在C++中,extern "C" 声明了它的一个作用,这和C编程与C++混合编程是有关联的。
exter "C"声明的作用:
(1)可以在C++函数前面加extern"C"声明,强制要求C++编译器按照C的风格去编译函数,不要进行换名,方便C程序去调用。
(2)被extern "C"声明的函数无法重载
void func(int i){...}//编译之后:_Z4funci
extern "C" void func(int i){...}//编译之后:func
假设现在有个一工程大型的项目.
mkdir 03project
在实际开发中经常会涉及到C和C++的混合编程,现在来模拟这样一个工程,用纯C语言或者纯C++语言来实现都不是很合适,想采用混编的形式,有的地方追求的是效率,用C语言编程更方便点,有的地方使用C++实现则更省事,这样在当前的工程中有C语言的文件也有C++的源文件。
touch c.c
touch cpp.cpp
c++
//vim cpp.cpp
/*C++的封装
和继承度应该更
好点,面向对象
编程,使用C++写
一些逻辑更为复
杂的程序比较合适*/
#include <iostream>
using namespace std;
void hello(void){
cout<< "hello c++!"<<endl;
}
C++
//vim c.c
/*通常是将函数
的声明写到头文
件中,再通过include包
含此头文件,include声
明的作用就是将头文件
中声明的东西原样地
拷贝到当前文件中,此
处为了示意方便就不写
头文件了,直接将函数声
明放在这里。*/
void hello(void);
int main(void){
hello();//主函数中直接调用C++源文件中的函数
return 0;
}
将两个文件链接
ls
c.c cpp.cpp
//这两个文件分开分别编译
g++ -C cpp.cpp //只编译不链接
gcc -C c.c//只编译不链接
//编译后就得到了以".o"结尾的目标文件
ls
c.c c.o cpp.cpp cpp.o
/*现在需要将两个目标文件链接起来,
链接使用g++或者gcc都可以,
但g++更方便点,不需要显式
链接标准c++库了*/
g++ cpp.o c.o
/*此时会报错,
报错信息如下,
就是找不到,不知
道定义在哪个位置,
我们知道我们已经
在cpp.cpp中定义了,
为什么说找不到呢?
这就是函数换名的原因啦
*/
c.o: 在函数'main'中:
c.c:(.test+0x12):对'hello'未定义的引用
collect2:error: ld returned 1 exit status
//查看cpp.o
nm cpp.o
00000000 T _Z5hellov
/*可以看出它换名字了,
所以在C的源文件中
调用hello()函数时候找
不到"hello"函数名了。
那该如何解决呢?*/
方法1:在C程序中调用被C++编译器更换的原C++源文件中的函数的名字
修改C语言的源文件,调用换名字后的函数,原来的名字已经不存在了,经过C++编译器编译原来的名字已经被更换了
C++
//vim c.c
//void hello(void);
void _Zhellov(void);//声明更换名字后的函数
int main(void)
{
//hello();
_Z5hellov();//调用更换名字后的函数
return 0;
}
//此时再次重新编译链接就可以成功
gcc -C c.c
g++ c.o cpp.o//可以成功
./a.out //也可以得到正常的想要的结果
hello C++!
/*这样子做这
种C语言和C++混合
编程的问题可以被解
决,但是这种解决问
题的方法不好,因为
此时C源文件中掉用的
式更换名字后的函数
,如果自己是一名
程序维护者,自己
肯定想要看看这个
被调用的函数的实
现是如何做的,但是
此时找遍整个函数
的定义和声明根本
找不到"void _Z5hellov(void)"类
似这种被换过名字
的函数的定义和声明,
这种被换过名字的函数
直接写进去的话代码可
读性十分差劲,还有就
是因为在标准的C++中没
有特别详细的关于换名字
的规定规则,标准C++只
是规定了要把函数的参数
类型表信息整合到新的名
字当中,但是它并没有
严格说明具体是怎么整
合的,比如:表达这个
void类型的参数,当前
的GUN的编译器是加
了一个"v",但是若是换
成微软的编译器可
能是加"void"全拼或者
其他的形式,这种整合
的形式有所差异,这样
的代码就不具备跨平台
的特性,所以这种做法
不好,需要另外的解决
方法。解决方法如下方法2:*/
方法2:在C++源文件中添加:extern "C"
C++
//vim c.c
void hello(void);
//void _Zhellov(void);//声明更换名字后的函数
int main(void)
{
hello();
// _Z5hellov();//调用更换名字后的函数
return 0;
}
//vim cpp.cpp
#include <iostream>
using namespace std;
extern "C" void hello(void){
cout <<"hello C++!"<<endl;
}
/*C++源文中的函数
直接编译函数会被
换名字,这样在C源
文件中调用时候会
出现未定义错误,因此
解决方法是在C++源文
件中定义函数的前面加
上:extern "C","C"是代
表C语言的意思,这时显
式的声明下,告诉C++编
译器,在编译这个函数时
候呢使用C语言的方式
进行编译,也就是告诉
编译器编译时候不要变
换函数名字了,这样做
的目的就是为了方便C
程序去调用C++程序。*/
gcc -C c.c
g++ -C cpp.cpp
g++ c.o cpp.o
./a.out
hello C++!
/*可以看出能够
正常编译了,
正常链接了,
经过g++编译
的文件cpp.cpp,因
为我们在函数前
面添加了:extern "C",此
时再看cpp.o中的表
示符内容,函数的
名字不会被更换修改.*/
nm cpp
00000000 T hello
/*
实际开发中,
涉及到C和C++混
合编程时候,
在C++的源文件
的函数定义的
函数名字前面添
加:extern "C",告
诉C++编译器在编
译时不要更换名字,
方便C程序直接调用。
*/
/*
extern "C" void hello(void){
cout <<"hello C++!"<<endl;
}
像这样仅仅
是为了方便C
函数的调用,
他是无 法重载的
,函数换名是通过
重载来实现的,通过
extern "C "声明之
后,函数无法换名,
那么它也就无法重载了。
*/