目录
6、函数重载:
6.1、函数重载概念:
6.2、名字修饰(name Mangling):
6.3、extern "C" :
6.3.1、C++调用C的静态库或者动态库:
6.3.2、C调用C++的静态库或者动态库:
函数重载的意义:函数的名字仅仅是让编译器知道它调用的是哪个函数,而函数重载可以在一定程度上减轻程序员起名字,记名字的负担、
#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;
void Swap(int*p1, int*p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void Swap(double*p1, double*p2)
{
double tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
//相比C语言而言,做到了可以在同一个作用域中(全局域和命名空间域)使用同名函数,但是要写成两个函数,在后续会有一个模板对这一块进行优化,直接合成一个Swap函数就可以解决所有的交换问题,并且适用于所有类型
//即,适用于int与int,char与char等等类型的交换、
int main()
{
int a = 0, b = 1;
double c = 1.1, d = 2.2;
Swap(&a, &b);
Swap(&c, &d);
//此处看起来像是在使用同一个函数,但实际上使用的是不同的两个函数,只不过两个函数的函数名相同、
printf("%d %d\n", a, b); //1 0
printf("%.2lf %.2lf\n", c, d); //2.20 1.10
}
注意:main函数不能进行函数重载!
注意:C语言不支持在同一作用域中(全局域,命名空间域)存在同名函数,只要名字相同就属于重定义、
int Add(int left, int right)//函数1
{
return left + right;
}
double Add(double left, double right)//函数2
{
return left + right;
}
int Add(int a,int b,int c)//函数3
{
return a + b + c;
}
void Print(int a,double c)//函数4
{
cout << a << endl;
cout << c << endl;
}
void Print(double c,int a)//函数5
{
cout << a << endl;
cout << c << endl;
}
int Sub(int right, int left)//函数6
{
return left + right;
}
int Sub(int left, int right)//函数7
{
return left - right;
}
void print(int A, double B)//函数8
{
cout<<"void print(int A, double B)"<
(1)函数1和函数2属于参数类型不同、
(2)函数1和函数3属于参数个数不同、
(3)函数4和函数5属于参数顺序不同、
(4)函数6和函数7不属于参数顺序不同、
(5)函数8和函数9属于参数顺序不同、
问:为什么函数的返回值和函数的返回类型都无法作为函数重载的条件?
答:我们在调用函数时使用的仅仅是函数名和实参,并不涉及到函数返回值和函数返回类型,所以函数返回值不同和函数返回类型不同均无法作为函数重载的条件,只有函数名和形参列表可以作为函数重载的条件、
short Add(short left, short right) { return left+right; } int Add(short left, short right) { return left+right; }
上述两个函数不属于函数重载、
拓展:
void TestFunc(int a = 10) { cout << "void TestFunc(int)" << endl; } void TestFunc(int a) { cout << "void TestFunc(int)" << endl; }
上述两个函数不属于函数重载,因为,此时两个调用函数的函数名相同,但是,形参的类型相同,形参个数相同,顺序也相同,所以,不满足形参列表必须不同的条件,即,不满足函数重载的条件,
C++可以调用C程序,C可以调用C++程序,两者可以互相进行调用、
拓展:
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接、
对比Linux会发现,windows下C++编译器对函数名字修饰非常诡异,但道理都是一样的、
扩展学习:C/C++函数调用约定和名字修饰规则:
C++的函数重载 - 吴秦 - 博客园
C/C++ 函数调用约定_低调的狮子的博客-CSDN博客
8、通过这里就理解了C语言没办法支持重载,因为同名函数没办法区分,而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载,另外我们也理解了,为什么函数重载要求参数不同!而跟返回值和返回类型没关系、
在链接过程中,除了可以找调用函数的定义(地址),链接对应上,进行合并,还可以使用其他库里面的东西,比如别人写好的一个库,第三方库,开源的东西,包括数据库中的IKI接口,这都是写好的库,在C和C++中称之为静态库或者动态库,则在链接过程中,C可以调用C的静态库或者动态库,C++可以调用C++的静态库或者动态库,除此之外,C可以调用C++的静态库或动态库,C++也可以调用C的静态库或者动态库,在后者这两种交叉调用中,会存在一个鸿沟,截止到目前,可以简单的把静态库和动态库认为是合并后得到的符号表,虽然在本质上两者存在差异,但是原理大致是相同的,所以可以进行简单的理解,则上述四种调用的过程中,等编辑和编译均完成并不存在问题后,可以根据命名规则得到的结果去合并后的符号表中去查找调用函数的地址进行链接,也就是在这里的,根据命名规则得到的结果去静态库或者动态库中查找调用函数的地址进行链接,C可以直接调用C的静态库或者动态库,C++可以直接调用C++的静态库或者动态库,但是如果C直接调用C++的静态库或动态库,C++直接调用C的静态库或者动态库,是不可以的,找不到调用函数的地址,因为不同的编译器中的命名规则是不一样的,则根据命名规则得到的结果也是不一样的,所以找不到,应该怎么办呢?,则可以使用: extern "C" 来解决上述问题,它可以改变编译方式(链接方式),从而来完成: C调用C++的静态库或动态库,C++调用C的静态库或者动态库,此处以windows环境下创建静态库为例,其他的在Linux中会进行具体阐述、
在同一个工程项目中,不能同时存在.c和.cpp的源文件,但是由于C++兼容极大部分C语言的语法和库(库里面的东西,比如库函数等等),故在同一个工程项目(C++工程项目)中,可以同时存在C和C++语法,其实本质上这些C语言的语法也是C++的语法、
1、先把一个工程封装为静态库,此处是把栈封装为一个静态库(C的静态库):
此时若直接进行执行的话,会报如下错误:
当执行时,默认当做是可执行的程序(应用程序),则必须定义入口点,也就是所谓的必须要有main函数,此时要把栈封装成一个静态库,它不是一个可执行程序(应用程序),所以会报错,具体封装过程为:
当执行时,由于默认生成可执行的程序(应用程序),所以会在Debug目录下生成.exe文件,此时要把栈封装成一个静态库,它不是一个可执行程序(应用程序),所以在Debug目录下生成.lib文件、
2、现在有一个C++的工程,想要使用由栈封装成的静态库(C的静态库),应该怎么使用呢?
2.1、使用相对路径包含头文件:
//由于在该工程中,没有Stack.h这个自定义头文件,所以不可以直接包该头文件,则使用相对路径,在包头文件时,默认的是绝对路径,即默认的是当前所在的目录、
#include"../../Stack_C/Stack_C/Stack.h" // ..表示上层目录、
也可以直接将上述所找到的Stack.h头文件复制到该C++项目的当前目录下,然后将其添加到解决方案资源管理器中的头文件选项中,然后在我们当前项目源文件中用#include"Stack.h"指令直接进行头文件包含,当上述过程完毕后,即使用相对路径包含头文件后,若直接进行编译的话,会出现错误如下所示:
此时,编译过程是没有问题的,但是链接过程出现问题,所以还要进行下面的操作:
2.2、 配置:
即,把封装静态库生成的.lib文件所在的目录添加到附加库目录上,点击确定,再把封装静态库生成的.lib文件的文件名复制一份放在附加依赖项最前面,使用 ;进行分割、
点击应用,再点确定,此时链接器在进行链接时,会直接去链接该静态库,具体步骤为,去附加库目录中找, 找存在于附加依赖项中的静态库/动态库,而这些静态库/动态库中又有合并后的符号表,最终去合并后的符号表中查找调用函数的地址,现在,已经包了头文件,则有了栈的各个接口函数的声明,我们之前所写的代码大都是在一个工程中,所以当找调用函数的地址时,会去合并后的符号表中查找,但是现在使用的是静态库,所以会去该静态库或动态库中查找调用函数的地址,按理说,现在已经配置成功了,但是,当运行时发现仍无法正常运行,会出现链接错误,如下图所示:
此时,若把静态库工程中的Stack.c源文件改成Stack.cpp的话,再重新生成解决方法之后,再次运行这个C++的工程,就会发现运行成功,那么为什么会发生这种情况呢?
上面错误是链接错误,因为已经包了头文件,有个栈中各个接口函数的声明,所以编译过程不会出错,只是链接不上各个接口函数而已,即找不到各个接口函数的地址,这是因为,静态库工程中原本是Stack.c,所以生成的则是C的静态库/动态库,此时则属于C++直接调用C的静态库/动态库,这是不可以的,因为命名规则是不同的,故没有办法在该静态库/动态库中找到调用函数的地址,如果把静态库工程中的Stack.c源文件改成Stack.cpp的话,这就属于C++调用C++的静态库/动态库,这是可以的,因为其命名规则是相同的,就可以在该静态库/动态库中找到调用函数的地址,那如果不改变静态库工程中的Stack.c源文件的话,即,C++调用C的静态库/动态库,应该怎么做呢?在此默认不改变静态库的属性,即,在链接过程中的交叉调用时,不通过改变静态库的属性来进行链接,即上面例子中,不可以把C的静态库/动态库变为C++的静态库/动态库从而进行链接、
解决方法:
首先,C++认识C语言的命名规则,因为C++兼容极大部分C语言的语法和库(库里面的东西,比如C++可以使用C语言库函数printf等等,此时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不认识C++的命名规则,所以不可以直接调用,但是由于C++兼容极大部分C语言的语法和库,可以对C++静态库/动态库进行一些操作,使之按照C语言的规则命名和链接调用,最后可以看做是转换成了C调用C的静态库/动态库、
extern"C"
{
#include"../../Stack_C/Stack_C/Stack.h" // ..表示上层目录、
}
此时,再次运行该C++工程,便可以运行成功,因为,该头文件会被展开,所以该头文件中的内容都展在了extern"C"的这个{ }内,而 extern"C" 的作用就是告诉C++编译器,其{ }中包含的所有内容,包括所有函数的声明,均要按照C语言的方式去链接调用,即按照C语言的规则去找,不要按照C++的方式去链接调用,不要按照C++的规则去找,这是可以的,因为C++认识C的命名规则,是C库,命名规则也要按照C语言的方式去命名,所以C++调用C的静态库/动态库可以认为是转化成了C调用C的静态库/动态库,只有extern"C",而没有extern"C++",C语言中不存在extern"C"的概念,不可以在C语言中使用,extern"C"是C++中的语法、
//C++工程:
#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;
//由于在该工程中,没有Stack.h这个自定义头文件,所以不可以直接包该头文件,则使用相对路径,在包头文件时,默认的是绝对路径,即默认的是当前所在的目录、
extern"C"
{
#include"../../Stack_C/Stack_C/Stack.h" // ..表示上层目录、
}
bool isValid(char * s)
{
ST st;
StackInit(&st);
while (*s)
{
if (*s == '(' || *s == '[' || *s == '{')
{
StackPush(&st, *s);
s++;
}
else
{
if (StackEmpty(&st))
{
StackDestory(&st);
return false;
}
char top = StackTop(&st);
StackPop(&st);
if ((*s == ')' && top != '(') || (*s == ']' && top != '[') || (*s == '}' && top != '{'))
{
StackDestory(&st);
return false;
}
else
{
s++;
}
}
}
bool ret = StackEmpty(&st);
StackDestory(&st);
return ret;
}
int main()
{
printf("%d\n", isValid("[]{}()"));
printf("%d\n", isValid("[{]}"));
return 0;
}
1、先把一个工程封装为静态库,此处是把栈封装为一个静态库(C++的静态库),在第1步中其他的步骤均是一样的、
2、现在有一个C的工程,想要使用由栈封装成的静态库(C++的静态库),应该怎么使用呢?
2.1、使用相对路径包含头文件:
//由于在该工程中,没有Stack.h这个自定义头文件,所以不可以直接包该头文件,则使用相对路径,在包头文件时,默认的是绝对路径,即默认的是当前所在的目录、
#include"../../Stack_CPP/Stack_CPP/Stack.h" // ..表示上层目录、
也可以直接将上述所找到的Stack.h头文件复制到该C++项目的当前目录下,然后将其添加到解决方案资源管理器中的头文件选项中,然后在我们当前项目源文件中用#include"Stack.h"指令直接进行头文件包含,当上述过程完毕后,即使用相对路径包含头文件后,若直接进行编译的话,也会出现同样的链接错误,此时,编译过程是没有问题的,但是链接过程出现问题,再进行配置,后发现仍编译不通过,具体原因和上述类似,就不进行讲解,其解决方法如下所示:
方法一:
直接在所有函数的声明前面加上 extern"C",除了所有的函数的声明之外都无需改变、
#pragma once //防止头文件被重复包含、
#include
#include
#include
#include
//实现数组(顺序表)栈、
//静态、
//下面是定长的静态栈的结构,实际中一般不实用,所以我们主要实现下面的支持动态增长的栈、
//#define N 100
//typedef int STDataType;
//typedef struct Stack
//{
// STDataType a[N];
// int top;//记录栈顶的位置、
//}Stack;
//动态、
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top; //记录栈顶的位置、
int capacity; //容量的大小、
}ST;
//初始化、
extern"C" void StackInit(ST* ps);
//销毁栈、
extern"C" void StackDestory(ST* ps);
//入栈、
extern"C" void StackPush(ST* ps, STDataType x);
//出栈、
extern"C" void StackPop(ST* ps);
//判断是否栈为空、
extern"C" bool StackEmpty(ST* ps);
//访问栈顶的数据、
extern"C" STDataType StackTop(ST* ps);
//记录栈内数据的个数、
extern"C" int StackSize(ST* ps);
重新生成解决方案,发现C++的静态库能够成功运行,原因是,在所有的调用函数的声明前面都加上extren"C"的话,就是告诉C++编译器,要按照C语言的规则去命名并且链接调用,这是可以的,因为C++认识C语言的命名规则,但再运行C程序,发现,仍会报错,这是因为,这个C程序的头文件展开时,出现了错误,C语言中不存在extern"C"这个概念,所以C语言编译器不认识extern"C",故当头文件展开后,会进行报错,再做如下操作:
#pragma once //防止头文件被重复包含、
#include
#include
#include
#include
//实现数组(顺序表)栈、
//静态、
//下面是定长的静态栈的结构,实际中一般不实用,所以我们主要实现下面的支持动态增长的栈、
//#define N 100
//typedef int STDataType;
//typedef struct Stack
//{
// STDataType a[N];
// int top;//记录栈顶的位置、
//}Stack;
//动态、
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top; //记录栈顶的位置、
int capacity; //容量的大小、
}ST;
//方法一: 条件编译+宏、
#ifdef __cplusplus //_cplusplus宏标识是C++编译器特有的,是C++工程项目中天生独有的,C语言编译器中不存在、
#define EXTERN_C extern"C"
#else
#define EXTERN_C //把空用EXTERN_C代替、
#endif
//初始化、
EXTERN_C void StackInit(ST* ps);
//销毁栈、
EXTERN_C void StackDestory(ST* ps);
//入栈、
EXTERN_C void StackPush(ST* ps, STDataType x);
//出栈、
EXTERN_C void StackPop(ST* ps);
//判断是否栈为空、
EXTERN_C bool StackEmpty(ST* ps);
//访问栈顶的数据、
EXTERN_C STDataType StackTop(ST* ps);
//记录栈内数据的个数、
EXTERN_C int StackSize(ST* ps);
方法二:
使用 extern"C" 把所有的函数的声明括起来,就是在告诉C++编译器,其{ }中的所有的内容均要按照C语言的方式去命名并且链接调用,当前形式和在每一个调用函数的声明前面都加上extern"C"的功能是一样的,因为前者把所有的调用函数的声明都括起来了,当调用函数的声明很多的话,前者便可体现出其优势,再加上条件编译,若不加条件编译的话也会报错,其原因和方法一中不加条件编译是一样的,不再进行阐述、
//方法二: 条件编译、
//若调用函数的声明很多的话,使用方法一是不太方便的、
#ifdef __cplusplus
extern"C"
{
#endif
//初始化、
void StackInit(ST* ps);
//销毁栈、
void StackDestory(ST* ps);
//入栈、
void StackPush(ST* ps, STDataType x);
//出栈、
void StackPop(ST* ps);
//判断是否栈为空、
bool StackEmpty(ST* ps);
//访问栈顶的数据、
STDataType StackTop(ST* ps);
//记录栈内数据的个数、
int StackSize(ST* ps);
#ifdef __cplusplus
}
#endif
对于这两种交叉调用而言,若想成功调用,其解决方法均是在C++这一方进行的操作,即,C++调用C静态库/动态库,则对C++这个工程中的代码进行操作,若C调用C++静态库/动态库,则对C++静态库/动态库进行操作、
注意:当使用C调用C++的静态库/动态库时,此时要想成功运行,必须要对C++静态库/动态库进行一些操作,才可以成功运行, 在这个操作中,不管使用上述的方法一还是二,都避免不了使用在头文件中使用extern"C",此时,在使用了extern"C"的前提下,Stack.h头文件中的所有调用函数的声明中,不可以存在函数重载,因为extern"C"的作用就是告诉C++编译器要按照C的规则去命名和链接调用,若在使用了extern"C"的前提下,Stack.h头文件中的所有调用函数的声明中,出现了函数重载,这时候C++静态库重新生成解决方案都无法完成,因为在部分符号表中就会出现歧义,除此之外,由于Stack.cpp源文件中也包了Stack.h头文件,所以当.cpp源文件中的头文件进行展开的时候,展开后其中会存在extern"C",由于是.cpp源文件,所以支持extern"C"语法,但是要记住,此时,在.cpp源文件中,存在一些函数的定义,这些函数是被enxtern"C"修饰过声明的函数,这些函数的定义均要按照C语言的规则进行命名和调用,除此之外,在.cpp源文件中,还会存在一些函数的定义,这些函数并没有在头文件中进行声明,所以更不会存在这些函数的声明被extern"C"修饰的情况,所以这些函数的定义均要按照C++的规则进行命名和链接调用,因为这些函数定义在.cpp源文件中,则此时.cpp源文件中能够出现函数重载,只不过和真正意义上的函数重载还有一点差别,具体差别存在于两个同名函数的命名规则上面,但可以都称之为函数重载,因为调用函数的函数名是相同的、
上图是可行的,因为C++支持任何类型的函数重载、
上图是不行的,因为C语言不支持任何类型的函数重载、
另一方面是,如果Stack.h头文件中没有使用extern"C"的话,那么该头文件中可以出现函数重载,因为是按照C++的规则命名和链接调用的,同理,即使Stack.cpp中包了Stack.h头文件,那么当该头文件进行展开时,展开后不会出现extern"C",所以Stack.cpp中默认按照的C++的规则命名和链接调用的,所以在Stack.cpp源文件中是支持函数重载的,若在方法一中,把重载的函数的声明前面不加extren"C",或者在方法二中,把重载的函数都拿到extern"C"外面去,这样重载函数就会按照C++的规则去命名和链接调用,由于C++是支持函数重载的,所以在C++静态库/动态库重新生成解决方案时,不会报错,可以正常运行,但是在运行C程序时,会报错,因为,在C程序中要进行头文件的展开,所以会出现问题,因为C语言不支持函数重载、
上述C调用C++的静态库/动态库的两种解决方法中,具体的链接过程是怎么样的呢?
答:如下图所示:
由于我们是C调用C++的静态库/动态库,要想调用成功,则需要对C++静态库/动态库进行一些操作,即上图所示,由于.cpp源文件中的后两个函数在头文件中的声明都被extern "C"修饰,所以,在.cpp源文件中的后两个函数也要按照C语言的规则命名和链接调用,这些函数均按照C语言的命名规则存放在合并后的符号表中,当在C工程中调用这些函数时,就能找到这些函数的地址,但是在.cpp源文件中的第一个函数并没有在头文件中进行声明,所以更不会使得extern "C"修饰这个函数的声明,故这个函数在.cpp源文件中的调用和定义部分均要按照C++的规则来进行,因为在.cpp源文件中,要注意,此时在C工程中是无法调用该函数的,因为他都未在Stack.h头文件中进行声明,所以在编译过程就会出现错误,调用不成功,只能在.cpp源文件中内部进行按照C++的规则来调用该函数,这样就可以完成了整个链接过程、
C++调用C的静态库/动态库的具体的链接过程比较简单,不再进行阐述、
C++调用C语言的静态库/动态库可以理解,比较常见,比如:以前有些库是通过C来写的,比如C++项目中要使用一些网络库,还有包括C自己的一些库,都是使用C语言来写的,此时会涉及到C++调用C语言的静态库/动态库,C语言调用C++的静态库/动态库应用场景也有,但不是很常见,比如:谷歌的项目tcmalloc-master,该项目是用来替换C语言中的malloc函数,谷歌认为该项目在多线程下的效率比较高,该项目是使用C++来实现的,如果一个C的项目想要使用该tcmalloc-master项目的话,就涉及到了C语言调用C++的静态库/动态库的问题,那么就一定能说该项目中不存在重载吗,也不是,只是暴露出来的五个函数,即在头文件中进行了声明的五个函数中不能存在重载,但是它的源文件里面可能实现了成百上千个函数,在源文件中可能会存在函数重载的变形,也称之为存在函数重载,此时tcmalloc-master项目只暴露了五个函数出来,即,在头文件中只对五个函数进行了声明,他源文件里面可能实现了成百上千个函数,在源文件中可能会存在函数重载的变形,但是暴露出来的只有五个函数,这五个函数不能存在函数重载,把暴露出来的五个函数使用extern"C"修饰即可,所以当C调用C++的静态库/动态库时,不可以直接说C++静态库/动态库中一定不存在函数重载,只能说,在头文件中的extern "C"修饰的所以内容中,也就是头文件中,不能存在函数重载,但是在其.cpp源文件中可能会存在函数重载的变形,也可以称之为函数重载、