目录
[2.3 重载解析(Overload Resolution)--补充内容](#2.3 重载解析(Overload Resolution)--补充内容)
前言
上节小编讲解了命名空间和函数的输入输出,本节将讲解缺省参数,函数重载等知识。
1.缺省参数
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参,则采用该形参的缺省值,否则使用指定的实参,缺省参数分为全缺省和半缺省参数。(有些地方把缺省参数也叫默认参数)
全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。C++规定半缺省参数必须从右往左
依次连续缺省,不能间隔跳跃给缺省值。
例如(错误示范)
cpp
void func(int a, double b = 3.14, const char* c = "default") { // 正确
// 函数体
}
void func(int a = 1, double b, const char* c = "default") { // 错误,不能跳过 b
// 函数体
}
cpp
#include <iostream>
using namespace std;
void func(int x=0) {
cout << x << endl;
}
int main() {
// 没有传参时,使⽤参数的默认值
// 传参时,使⽤指定的实参
func();
func(10);
return 0;
}
运行结果:
0
10
带缺省参数的函数调用,C++规定必须从左到右依次给实参,不能跳跃给实参。
cpp
#include <iostream>
using namespace std;
// 全缺省
void Func1(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
// 半缺省
void Func2(int a, int b = 10, int c = 20)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
int main()
{
Func1();
Func1(1);
Func1(1, 2);
Func1(1, 2, 3);
Func2(100);
Func2(100, 200);
Func2(100, 200, 300);
Func2(100, ,300);//错误
return 0;
}
函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省
值。
cpp
// 在头文件中的函数声明
void func(int a = 1, double b = 3.14, const char* c = "default");
// 在源文件中的函数定义
void func(int a, double b, const char* c) {
// 函数体
}
2.函数重载
在C++中,函数重载(Function Overloading)是指在同一作用域内可以存在多个同名函数,但这些函数的参数列表(参数的个数、类型或者顺序)必须不同。编译器会根据传递给函数的参数来决定调用哪一个重载版本。
2.1函数重载的基本规则
1.参数数量不同:函数可以具有不同数量的参数。
cpp
#include <iostream>
using namespace std;
int Add(int a, int b) {
return a + b;
}
int Add(int a, int b, int c) {
return a + b + c;
}
int main() {
int sum1 = Add(10, 20);
double sum2 = Add(10,20,30);
cout << "sum1: " << sum1 << endl;
cout << "sum2: " << sum2 << endl;
return 0;
}
2. 参数类型不同:函数可以具有不同类型的参数。
cpp
#include <iostream>
using namespace std;
int add(int a, int b) {
cout << "int add(int a, int b)"<<endl;
return a + b;
}
double add(double a, double b) {
cout << "double add(double a, double b)"<<endl;
return a + b;
}
int main() {
int sum1 = add(10, 20);
double sum2 = add(1.5, 2.5);
cout << "sum1: " << sum1 << endl;
cout << "sum2: " << sum2 << endl;
return 0;
}
3. 参数顺序不同:如果参数类型相同,但顺序不同,也可以构成重载。
cpp
#include <iostream>
using namespace std;
void f(int a, char b)
{
cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
cout << "f(char b, int a)" << endl;
}
int main() {
f(10, 'c');
f('c', 10);
return 0;
}
2.2注意事项
- 返回类型:函数重载与返回类型无关,即不能仅通过改变返回类型来重载函数。
-默认参数:使用默认参数的函数可能会导致重载解析的歧义,应当小心使用。
cpp
int Add(int a, int b) {
return a + b;
}
double Add(int a, int b) { // 错误:不能仅通过返回类型来重载
return static_cast<double>(a + b);
}
cpp
#include <iostream>
using namespace std;
// 下⾯两个函数构成重载
// f()但是调⽤时,会报错,存在歧义,编译器不知道调⽤谁
void f()
{
cout << "f()" << endl;
}
void f(int a = 10)
{
cout << "f(int a)" << endl;
}
int main() {
f();
return 0;
}
2.3 重载解析(Overload Resolution)--补充内容
当调用一个重载函数时,编译器会根据提供的实参和每个重载函数的形参列表进行匹配,以确定调用哪一个函数。这个过程称为重载解析。如果编译器无法明确地确定应该调用哪个函数,或者找到多个合适的匹配,就会产生编译错误。
函数重载是C++多态性的一种形式,它允许程序员使用相同的名字来执行相似的操作,这增强了代码的清晰性和可读性。
3.引用
3.1引用的概念和定义
在 C++ 中,引用是一个变量的别名。一旦一个引用被初始化为指向一个对象,它就不能被重新绑定到另一个对象。这与指针不同,指针可以在任何时候指向不同的对象。
引用的概念
- 别名:引用为另一个变量提供了一个额外的名字,它和原变量指向相同的内存位置。
- 不可变性:一旦引用被初始化,它就不能被重新赋值为另一个变量的引用。
- 无需解引用 :使用引用时,不需要像指针一样使用
*
操作符来访问引用的值。 - 效率:引用在传递参数时可以避免不必要的复制,特别是对于大型对象来说,使用引用可以提高效率。
引用的定义
引用在 C++ 中是这样定义的:
类型 &引用名 = 原变量名;
一些有趣的实例:
比如:水浒传中李逵,宋江叫"铁牛",江湖上人称"黑旋风";林冲,外号豹子头;
这里,类型
是原变量的类型,&
符号用于声明引用,引用名
是你给引用起的名字,而 原变量名
是已经存在的变量。
cpp
#include <iostream>
using namespace std;
int main() {
int a = 520;
int& b = a;
int& c = a;
int& d = b;
++d;
// 引⽤:b和c是a的别名
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
// 也可以给别名b取别名,d相当于还是a的别名
cout << &d << endl;
//地址,值都是一样的
cout << a << endl;
cout << b << endl;
cout << c << endl;
cout << d << endl;
return 0;
}
3.2引用的特性
• 引用在定义时必须初始化
• 一个变量可以有多个引用
• 引用一旦引用一个实体,再不能引用其他实体
cpp
#include<iostream>
using namespace std;
int main()
{
int a = 10;
// 编译报错:"ra": 必须初始化引⽤
//int& ra;
int& b = a;
int c = 20;
// 这⾥并⾮让b引⽤c,因为C++引⽤不能改变指向,
// 这⾥是⼀个赋值
b = c;
cout << a << endl;
cout << b << endl;
cout << c << endl;
b = 30;
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
cout << a << endl;
cout << b << endl;
cout << c << endl;
return 0;
}
3.3引用的使用
1.引用在实践中主要是于引用传参和引用做返回值中减少拷贝提高效率和改变引用对象时同时改变被引用对象。
2.引用传参跟指针传参功能是类似的,引用传参相对更方便一些。
3.引用返回值的场景相对比较复杂,小编实力有限,以后等知识丰富了再会详细讲解。
4.引用和指针在实践中相辅相成,功能有重叠性,但是各有特点,互相不可替代。C++的引用跟其他语言的引用(如Java)是有很大的区别的,除了用法,最大的点,C++引用定义后不能改变指向,java的引用可以改变指向。
5.一些主要用C代码实现版本数据结构教材中,使用C++引用替代指针传参,用的是简化程序,避开复杂的指针。
代码实例:
cpp
#include <iostream>
using namespace std;
void Swap(int& x, int& y) {
int temp = x;
x = y;
y = temp;
}
int main() {
int x = 520, y = 1314;
cout << x << " " << y << endl;
Swap(x, y);
cout << x << " " << y << endl;
return 0;
}
cpp
#include<iostream>
#include <assert.h>
#include <stdlib.h>
using namespace std;
typedef int STDataType;
typedef struct Stack {
STDataType* a;
int top;
int capacity;
} ST;
void STInit(ST& rs, int n = 4) {
rs.a = (STDataType*)malloc(n * sizeof(STDataType));
rs.top = 0; // 栈顶初始化为 0,表示栈为空
rs.capacity = n;
}
void STPush(ST& rs, STDataType x) {
// 满了,扩容
if (rs.top == rs.capacity) {
printf("扩容\n");
int newcapacity = rs.capacity == 0 ? 4 : rs.capacity * 2;
STDataType* tmp = (STDataType*)realloc(rs.a, newcapacity * sizeof(STDataType));
if (tmp == NULL) {
perror("realloc fail");
return;
}
rs.a = tmp;
rs.capacity = newcapacity;
}
rs.a[rs.top] = x;
rs.top++; // 栈顶指针上移
}
int& STTop(ST& rs) {
assert(rs.top > 0); // 确保栈不为空
return rs.a[rs.top - 1]; // 返回栈顶元素的引用
}
int main() {
ST st1;
STInit(st1);
STPush(st1, 1);
STPush(st1, 2);
cout << STTop(st1) << endl; // 输出栈顶元素 2
STTop(st1) += 10; // 通过引用修改栈顶元素
cout << STTop(st1) << endl; // 输出修改后的栈顶元素 12
return 0;
}
3.4const引用
在 C++ 中,
const
引用是一种特殊类型的引用,它被用来引用一个对象,同时保证这个引用不会修改所引用的对象。这种引用通常用于函数参数,以允许函数读取传递的对象,但不允许通过引用修改它。
以下是 const
引用的几个关键点:
声明 const
引用
const 类型 &引用名 = 引用的对象;
这里,类型
是被引用对象的类型,引用名
是你给引用起的名字,而 引用的对象
是你想要引用的实际对象。
使用 const
引用的好处
- 保护数据不被修改 :通过
const
引用,你可以确保传递给函数的参数不会被函数修改。- 可以引用临时对象和字面量 :
const
引用可以引用右值(如字面量或临时对象),这是非const
引用无法做到的。- 可以与任何类型的对象兼容 :只要类型可以转换为
const
引用的类型,就可以创建const
引用。
示例
不需要注意的是类似 int& rb = a*3; double d = 12.34; int& rd = d; 这样一些场景下a*3的和结果保存在一个临时对象中, int& rd = d 也是类似,在类型转换中会产生临时对象存储中间值,也就是时,rb和rd引用的都是临时对象,而C++规定临时对象具有常性,所以这里就触发了权限放大,必须要用常引用才可以。
cpp
int main()
{
const int a = 10;
// 编译报错:error C2440: "初始化": ⽆法从"const int"转换为"int &"
// 这⾥的引⽤是对a访问权限的放⼤
//int& ra = a;
// 这样才可以
const int& ra = a;
// 编译报错:error C3892: "ra": 不能给常量赋值
//ra++;
// 这⾥的引⽤是对b访问权限的缩⼩
int b = 20;
const int& rb = b;
// 编译报错:error C3892: "rb": 不能给常量赋值
//rb++;
return 0;
}
cpp
#include<iostream>
using namespace std;
int main()
{
int a = 10;
const int& ra = 30;
// 编译报错: "初始化": ⽆法从"int"转换为"int &"
// int& rb = a * 3;
const int& rb = a*3;
double d = 12.34;
// 编译报错:"初始化": ⽆法从"double"转换为"int &"
// int& rd = d;
const int& rd = d;
return 0;
}
cpp
#include <iostream>
using namespace std;
void print(const int& x) {
cout << x << endl;
// x = 5; // 错误:不能通过 const 引用修改对象
}
int main() {
int a = 10;
const int& ref = a; // 创建一个对 a 的 const 引用
// ref = 20; // 错误:不能通过 const 引用修改对象
print(a); // 正确:传递 int
print(20); // 正确:传递字面量,通过 const 引用允许
return 0;
}
注释前的错误:
正确运行结果:
注意事项
- 一旦
const
引用被初始化,它就不能再引用其他对象。 const
引用不能引用非const
对象的地址,如果需要引用非const
对象,必须显式地声明非const
引用。
总之,const
引用在 C++ 中是一种非常有用的特性,它不仅提供了对数据的保护,还增加了代码的灵活性和安全性。
4.指针和引用的关系
C++中指针和引用就像两个性格迥异的亲兄弟,指针是哥哥,引用是弟弟,在实践中他们相辅相成,能有重叠性,但是各有自己的特点,互相不可替代。
• 语法概念上引用是一个变量的取别名不开空间,指针是存储一个变量地址,要开空间。
• 引用在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
• 引用在初始化时引用一个对象后,就不能再引用其他对象;而指针可以在不断地改变指向对象。
• 引用可以直接访问指向对象,指针需要解引用才是访问指向对象。
• sizeof中含义不同,引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8byte)
• 指针很容易出现空指针和野指针的问题,引用很少出现,引用使用起来相对更安全一些。
结束语
本篇博客也就到此结束啦,C++ 的入门也差不多了,下个阶段我们将步入类和对象的学习!!!
最后支持小编的友友和大佬们点个赞吧,也欢迎大家在评论区多多交流,感谢大家的支持!!!