1. 缺省参数
缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。
这里有个例子:车子有个备胎,当车轮坏掉时才会启用备胎,如果有车轮就用车轮。
翻译一下:函数有缺省参数,当函数没有指定实参就用缺省值,如果有实参就用实参。(额,如果还不太好理解就请联想一下另一种“备胎”😢);
1.1 缺省参数的分类
#include<iostream>
using namespace std;
//全缺省(全都缺省)
void testA(int a = 1, int b = 2, int c = 3)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
//半缺省(缺省一部分)
//必须从右往左给缺省值,并且是连续的。否则会产生歧义,不知道你传的参是给谁传的
void testB(int a, int b = 2, int c = 3)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
int main()
{
//全缺省
testA();//1 2 3
testA(10);//10 2 3
testA(10, 20);//10 20 3
testA(10, 20, 30);//10 20 30
//如果我只想传第二个位置的实参,这样写可以吗?不行哦,C++暂时不支持这样的语法
//testA(, 20);//错误
//半缺省
//至少得传一个参
testB(10);//10 2 3
testB(10, 20);//10 20 3
testB(10, 20, 30);//10 20 30
return 0;
}
1.2 缺省参数的应用
栈的初始化
struct Stack
{
int* a;
int size;
int capacity;
};
//半缺省
//在初始化时默认n为4,如果有其他需求也可以改变n的值
void StackInit(struct Stack* ps, int n = 4)
{
assert(ps);
ps->a = (int*)malloc(sizeof(int) * n);
ps->size = 0;
ps->capacity = n;
}
int main()
{
struct Stack st;
StackInit(&st, 100);
return 0;
}
1.3 缺省参数的注意点
1️⃣ 缺省值必须是常量或者全局变量
2️⃣ C语言不支持(编译器不支持 )
3️⃣⚠️注意!:缺省参数不能在函数声明和定义中同时出现
//a.h
void TestFunc(int a = 10);
// a.c
void TestFunc(int a = 20)
{}
// 注意:如果声明与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用哪个缺省值。
只用在声明给缺省值,定义里不用给。
在这稍微解释一下:因为编译阶段我们包含的头文件已经展开了,我们拥有了声明(但还没拿到定义),这时如果我们没有传参,编译器就要去找缺省值,就成功在声明中找到了。如果只把缺省值放在定义里,编译阶段还拿不到定义,就找不到缺省值,会报错!(汇编阶段生成符号表才能找到定义)
2.函数重载
2.1 函数重载概念
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的
形参列表(参数个数 或 类型 或 顺序)必须有不同,常用来处理实现功能类似数据类型不同的问题
//参数个数不同
double Add(int left, double mid, double right)
{
return left + mid + right;
}
//类型的顺序不同
double Add(int left, double right)
{
return left + right;
}
double Add(double left, int right)
{
return left + right;
}
int main()
{
cout << Add(1, 2.2, 3.3) << endl;
cout << Add(1, 2.2) << endl;
cout << Add(1.1, 2) << endl;
return 0;
}
下面两个函数属于函数重载吗?
short Add(short left, short right)
{
return left+right;
}
int Add(short left, short right)
{
return left+right;
}
上面两个函数只有返回值不同,不构成函数重载。因为调用的时候没法判断要调用哪个。
2.2 C++支持函数重载的底层逻辑
为什么C++支持函数重载,而C语言不支持函数重载呢?
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
1️⃣实际我们的项目通常是由多个头文件和多个源文件构成,而通过我们C语言阶段学习的编译链接,我们可以知道,【当前a.cpp中调用了b.cpp中定义的Add函数时】,编译后链接前,a.o的目标文件中没有Add的函数地址,因为Add是在b.cpp中定义的,所以Add的地址在b.o中。那么怎么办呢?
2️⃣所以链接阶段就是专门处理这种问题,链接器看到a.o调用Add,但是没有Add的地址,就会到b.o的符号表中找Add的地址,然后链接到一起。如果啥都不记得了请看👉c语言自学教程——程序环境和预处理
3️⃣那么链接时,面对Add函数,连接器会使用哪个名字去找呢?这里每个编译器都有自己的函数名修饰规则。
由于Windows下vs的修饰规则过于复杂,而Linux下gcc的修饰规则简单易懂,下面我们使用了gcc演示了这个修饰后的名字。
5️⃣通过下面我们可以看出gcc的函数修饰后名字不变。而g++的函数修饰后变成【_Z+函数长度+函数名+类型首字母】。
6️⃣通过这里就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。
7️⃣另外我们也理解了,为什么函数重载要求参数不同!而跟返回值没关系
结论:
-
在linux下,采用gcc编译完成后,函数名字的修饰没有发生改变
-
在linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中
2.3 extern“C”
有时候在C++工程中可能需要将某些函数按照C的风格来编译**,**在函数前加extern “C”,意思是告诉编译器,将该函数按照C语言规则来编译。实际上C和C++都可以相互调用(C->C、C->C++、C++ ->C++、C++ ->C)
比如:tcmalloc是google用C++实现的一个项目,他提供tcmallc()和tcfree两个接口来使用,但如果是C项目就没办法使用,那么就使用extern “C”来解决。
鉴于大家都是初学者,我们就来手把手教学如何调用静态库
假设我们想调用栈,首先将栈封装成静态库:
完成后重新生成解决方案,生成Stack_C.lib文件
在Stack_C\Debug文件下能找到静态库
之后我们创建一个cpp文件,通过静态库使用栈
#include"../../Stack_C/Stack_C/Stack.h"//包含头文件
找到你的静态库生成的地址,我的是Stack_C\Debug,找到之后点确认
两个静态库之间记得要加;
但是你这样运行还是会有链接错误
现在把栈的.c
文件改成.cpp
文件,在运行就可以通过啦!(就相当于cpp调用cpp)
之前链接不上的原因是:test文件是cpp文件,stack是c文件,cpp通过符号表找不到c的函数(上面解释过:两者的函数名修饰规则不同)
那么我们能否让cpp文件调用c呢?加上个extern"C"
,告诉编译器按照C语言风格找函数名。
extern"C"
{
#include"../../Stack_C/Stack_C/Stack.h"//包含头文件
}
开始尝试c调用c再搞一个test.c文件设置好C的附加库目录就能成功执行了
重点是如何c调用cpp
跟上面的操作一样,再创建一个文件叫Stack_CPP,封装成CPP的静态库,让test.c文件设置好CPP的附加库目录、调用.h文件
执行之后仍然会报错(因为命名规则不同,找不到)
这时,我们在cpp文件里用上条件编译,当在cpp文件中EXTERN_C 被识别成extern “C”,在c文件里隐藏EXTERN_C(因为.c文件不认识extern “C”)这样让cpp的名字修饰规则跟c一样,就能链接起来了
#ifdef __cplusplus
#define EXTERN_C extern "C"
#else
#define 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"
可以加在函数声明的前面,也可以括起来多个函数声明)
#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++相互调用编译失败的原因,为了让它们能够相互调用,我们提出了extern "C"的概念
本文涉及到了编译和预处理等底层逻辑,建议大家先弄懂👉c语言自学教程——程序环境和预处理。磨刀不误砍柴工,祝大家一步步脚踏实地,不断进步。
目前在不断更新<C++语言>的知识总结,已经更新完了<C语言><数据结构初阶>,未来我会系统地更新<Linux系统编程><Linux网络编程><数据结构进阶><MySQL数据库>等内容。想要系统学习编程的小伙伴可以关注我!