C++函数三剑客:缺省参数·函数重载·引用的高效编程指南
前引:在C++编程中,缺省参数、函数重载、引用是提升代码简洁性、复用性和效率的三大核心机制。它们既能减少冗杂的代码,又能增强接口设计的灵活性。本文将通过清晰的理论解析与实战案列,带你深入理解这三者的设计思想、使用场景以及闭坑指南~
【注:由于内容平均排版,引用的结尾特安排在下一篇!】
目录
缺省参数
何为缺省参数
全缺省参数
半缺省参数
不缺省参数
注意
函数重载
何为函数重载
参数个数不同
参数类型不同
类型顺序不同
注意
为何C++支持函数重载,而C不支持
引用
什么是引用
引用的使用
一级指针的变化:
二级指针的变化:
编辑
语法注意:
一级指针函数的变化:
二级指针函数的变化:
编辑编辑
语法注意:
引用的指针的优劣
优先使用引用的场景:
必须使用指针的场景:
关于引用返回值的讲解
缺省参数
何为缺省参数
允许函数在声明时指定默认值,调用函数时可以省略部分参数。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。例如:
Func();//没有传参使用参数的默认值
Func(10);//传参了使用指定的参数
全缺省参数
顾名思义,全缺省参数就是所有参数全部都额外指定,例如:
Func();Func(1,2,3);//与参数个数无关
半缺省参数
半缺省位于全缺省 和 不缺省之间,也比较好理解,比如:
Func();
Func(1,3);(与参数个数无关)
不缺省参数
所有参数都不额外指定,例如:
Func(1,2,3);//缺不缺省与参数个数无关
Func(1,2);
Func(1);
注意
这样看来我们的参数指不指定似乎是随机的!但是,它有语法规则限制:
默认参数必须从右往左连续设置(默认参数可以是常量、全局变量、函数返回值....但不能是局部变量),例如:
参数必须从右往左连续设置的几种情况:
(1)传参顺序错误
(2)函数参数顺序错误
(3)声明与定义只能选择一个设置函数缺省,否则就会报错
(4)缺省只有.cpp文件支持,在纯c的.c文件中不支持
函数重载
何为函数重载
是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题
参数个数不同
参数个数指的是传参的个数,不是函数接收的参数
参数类型不同
函数参数接收的类型可以不同,例如:
void Func(int a=5,int b=10,int c=10)
{std::cout << a + b + c << std::endl;
}void Func(double a = 5, double b = 10, double c = 10)
{std::cout << a + b + c << std::endl;
}
同时,参数类型可以不用统一,只要保证函数名相同就行,例如:
void Func(int a=5,int b=10,int c=10)
{std::cout << a + b + c << std::endl;
}void Func(double a = 5, double b = 10, double c = 10)
{std::cout << a + b + c << std::endl;
}void Func(double a = 5, double b = 10, int c = 10)
{std::cout << a + b + c << std::endl;
}
类型顺序不同
这点其实和刚才的参数类型不同很相似,我们直接看:
void Func(double a=5,int b=10,int c=10)
{std::cout << a + b + c << std::endl;
}void Func(double a = 5, double b = 10, int c = 10)
{std::cout << a + b + c << std::endl;
}
注意
(1)需要注意的是,函数传参跟声明个数是匹配的
比如它不会去自动识别你想匹配哪个函数。两个函数传参,对应两个函数接收
(2)如果只有返回值不同是不构成重载的,因为编译器无法根据参数是判断是不是构成重载
为何C++支持函数重载,而C不支持
首先不论是C还是C++,从代码->展示效果都会经历这几个阶段:
既然这个过程是一样的,那么它们之间必定在某个阶段有所差别:
小编认为可以这么总结:
假如我现在缺钱,想找某个人借,那么C借给我 跟 C++借给我的方式是不一样的!
在C中的编译阶段,根据函数定义直接兑现承诺,根据函数定义直接去替换成对应的函数,那么当再次调用这个函数,发现跟之前的不一样了,就会报错。比如 C 直接借给你钱!
在C++中的编译阶段,根据函数声明先承诺,根据函数的声明生成不同的函数类型,因此可以存在多个同名的函数,此时同名不代表是同一个函数。C++ 会先答应借给你,并不是直接借给你钱!
引用
什么是引用
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空 间,它和它引用的变量共用同一块内存空间
引用是可以赋值给变量的,俗称为“引用变量”,例如:从可以赋值来看,我们看到 pc 并不是指针
int tmp = 10;int& pc = tmp;
int cur = pc;
形象比喻:比如李逵,江湖上叫“黑旋风”,他也叫“铁牛”,这三个称呼是同一个人,你叫其中的某一个称呼,叫的其实是同一个对象
引用的使用
学了C++中的引用之后,你会发现C++中的指针使用与C中的指针使用有很大的变化,更简洁
例如常见的几种C中指针用法:
C++中学了引用之后,我们可以这么改进,下面我们分开对比,否则不好理解!
一级指针的变化:
//设置变量int tmp = 10int& cur = tmp;//C++cur = 30;;
我们可以看到,C++引用到一级指针中去,可以不用去解引用了,直接通过变量访问
二级指针的变化:
在C中我们的二级指针是指向一级指针地址的
在C++引用中,引用的对象同样是一级指针,不是变量本身哦!(牢记),例如:
//二级指针引用int*& coun = p;//C++*coun = 100;
解释:coun是p的引用二者地址相同,操作的也就是p,*coun操作的也就是*p,*p也就是tmp本身
这里需要注意:C++二级指针引用还是需要指向一级指针,因为二级指针的定义是:二级指针指向一级指针地址 ,只是这里不需要去给 p 加上取地址
语法注意:
C指针:
(1)可以不对指针进行初始化,但是有野指针的风险
(2)可以对指针进行赋值,比如为 NULL,或者指向其它变量或指针
(3)使用时操作变量需要解引用*,在函数里面->才能访问成员
例如:
//可以不初始化
int* p;
//可以赋值
int* p = NULL;
//需要解引用访问
*p = 20;
C++引用:
(1)必须初始化,且绑定后不可以重新绑定
(2)无空引用,始终指向有效的对象
(3)直接通过引用名访问,无需解引用
例如:
int tmp = 10;//必须初始化int& pc = tmp;//无空引用,始终指向有效对象int& cur = NULL;//直接通过引用名访问,无需解引用pc = 20;
一级指针函数的变化:
首先是传参,传指向对象 或者 指针命名对象都是可以的,它们的接收参数形式是统一的
就仿佛:我给“铁牛”沏一杯茶,和我给“黑旋风”沏一杯茶,对象是同一个人
Func(pc);Func(tmp);
二级指针函数的变化:
都需要传二级指针,它们的传参对象都指向同一个 (一级指针地址 与 引用对象同址),例如下面的 p 与 cur同址
Func(cur);Func(p);
语法注意:
(1)强制初始化,且无法为空,杜绝引用错误
//杜绝为空
int& pc;//必须绑定对象
int tmp;
int& cur = tmp;
(2)严格匹配类型,无法隐式转换
int tmp = 10;
//严格匹配类型,无法隐式转换
double& pc = tmp;
(3)C++引用二级指针需要指向一级指针,这个一级指针不能是C++引用类型
//设置变量
int tmp = 10;//一级指针
int* pc = &tmp;
int& cur = tmp;//二级指针
//不能使用C++一级指针引用对象
int*& pc = cur;//错误语法
int*& pc = pc;//正确语法
引用的指针的优劣
两者在运行性能上几乎一致,但引用通过语法减少了潜在的错误,提高了代码的健壮性
(1)引用概念上定义一个变量的别名,指针存储一个变量地址
(2)引用在定义时必须初始化,指针没有要求
(3)引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何 一个同类型实体
(4)没有NULL引用,但有NULL指针
(5)在 sizeof 中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数 (32位平台下占4个字节)
(6)引用自加即引用的实体增加 1 ,指针自加即指针向后偏移一个类型的大小
优先使用引用的场景:
(1)函数参数传递
(2)操作符重载
(3)返回值
必须使用指针的场景:
(1)动态内存管理
(2)与C库交互,比如调用 printf 或C标准库函数时
(3)可选参数或者多态
关于引用返回值的讲解
首先我们看一个错误例子,这是将一个引用变量赋值给另一个变量 ret:
首先:这里的打印虽然是“1”,但是它的打印值其实是不固定的,有以下两种情况:
(1)如果Func函数结束,栈帧销毁,没有清理栈帧,那么 ret 的结果侥幸是正确的
(2)如果Func函数结束,栈帧销毁,,清理了栈帧,那么 ret 的结果就是随机的
例如下面:普通函数可以通过中间变量赋值带回,引用没有中间变量,销毁了也就无法返回值了
我们的解决方案有以下两种:
(1)使用全局变量 或 静态变量,如:int n -> static int n
原因:当使用 static 修饰局部变量时,变量的周期会持续到整个程序结束,即函数调用完也不会 被销毁
int& Func()
{static int n = 0;//修改方式1n++;return n;
}int main()
{int ret = Func();std::cout << ret << std::endl;return 0;
}
(2)改返回值,而非引用,如:int& Func()-> int Func()
原因:修改之后只是一个简单的赋值,不与引用规则发生冲突
int Func()//修改方式2
{int n = 0;n++;return n;
}int main()
{int ret = Func();std::cout << ret << std::endl;return 0;
}
所以在使用引用做返回值时,需要特别注意该函数的生命周期,引用的使用是很活跃的!
【雾非雾】期待与你下次相遇!