C++入门(2)

在 C++ 中,函数和引用是构建程序的两块重要基石。本文继续之前的基础知识往下讲,先讲缺省函数与函数重载,再深入理解引用的概念、特性与使用方式。

缺省函数

缺省函数是在函数声明或定义时,给定函数参数一个缺省值。

比如说我们写一个 Add() 函数用来实现两个整型相加的功能。

cpp 复制代码
int Add(int a = 1, int b = 2)
{
	int c = a + b;
	return c;
}

1 和 2 就是我们给参数的缺省值,当我们调用该函数但不传参的时候,函数就会将缺省值赋给相应参数。

需要注意的是,缺省函数规定不能跳跃传参,只能连续传参,所以我们调用该函数有 3 种方式,也算使调用函数的方式变得多元化了。

cpp 复制代码
int main()
{
	Add();//没有传参,使用缺省值
	Add(10);//只给第一个参数传参,第二个参数使用缺省值
	Add(10, 20);//使用传过来的参数
	return 0;
}

缺省函数分为全缺省和半缺省,全缺省就是给函数所有参数都设置缺省值,我们上述所写的缺省函数就是全缺省函数。半缺省函数是只给函数的部分参数设置缺省值,规定只能从右往左设置缺省值

cpp 复制代码
//全缺省函数
int Add(int a = 1, int b = 2)
{
	int c = a + b;
	return c;
}
cpp 复制代码
//半缺省函数
int Add(int a, int b = 2)
{
	int c = a + b;
	return c;
}

对于这个半缺省函数,第一个参数就必须要传参了,因为没有设定缺省值。

关于缺省函数,最后一点要注意的是,当函数声明和定义分离时,要在声明中设定缺省值,定义中就不能写缺省值了。

cpp 复制代码
//声明里设定缺省值
int Add(int a = 1, int b = 2);

//全缺省函数
int Add(int a, int b)
{
	int c = a + b;
	return c;
}

函数重载

在 C 语言中,我们是通过函数名来区分调用的是哪个函数,所以不允许有同名函数的存在。在 C++ 中是允许有同名函数的存在的。

当存在同名函数时,我们如何区分调用的是哪个函数呢?我们是通过参数的类型或个数来区分的。

也就是多个具有不同参数个数或参数类型的同名函数存在时,我们称之为函数重载。

比如我们之前写的整型相加的函数 Add,我们可以将参数类型换为 float、char 等类型,定义这些类型的相加函数。

cpp 复制代码
//这三个函数名相同,参数类型不同,构成函数重载

//int型相加
int Add(int a, int b)
{
	int c = a + b;
	return c;
}
//float型相加
float Add(float a, float b)
{
	float c = a + b;
	return c;
}
//char型相加
char Add(char a, char b)
{
	char c = a + b;
	return c;
}

然后我们调用的时候,编译器就会根据参数类型来决定调用的是哪个函数。

cpp 复制代码
int main()
{
	int x = 10, y = 20;
	Add(x, y);//整型,调用第一个函数
	
	float c = 1.3f, d = 1.4f;
	Add(c, d);//浮点型,调用第二个函数
	
	char ch1 = 'a', ch2 = '0';
	Add(ch1, ch2);//字符型,调用第三个函数
	
	return 0;
}

我们来看一个程序:

cpp 复制代码
void Func()
{
	;
}

void Func(int a = 10, int b = 20)
{
	;
}

参数个数不同,语法上确实构成函数重载。当我们传参调用第二个函数的时候没有问题,但如果不传参呢?调用的是哪个函数?

cpp 复制代码
	Func();

这时候就产生歧义了:不传参的时候这两个函数都能匹配,那究竟调用的是哪个函数,编译器也不清楚。

所以在实际运用中不要这样写函数重载,我们一般舍弃第一种写法保留第二种,因为第二个函数包含第一个函数的功能。

引用

引用的概念和定义

引用不是定义一个对象,而是给一个已经存在的对象取别名,该对象的引用和该对象共用一块内存空间。

引用的使用方式是:类型& + 别名 = 变量

比如说我们给变量 a 取别名 b,就是这样取的:

cpp 复制代码
int a = 10;
//给a取别名b,b就是a的引用
int& b = a;

因为 b 和 a 共用一块内存空间,简单来说 b 就是变量 a 的另一个名字,所以修改 b 的内容,a 也会跟着修改。

引用的特性

关于引用有几个特点:

  • 使用时必须初始化
  • 一个变量可以有多个引用,还可以给引用取别名,但一个引用只能作为一个变量的别名
  • 引用指向的对象不能修改

因为引用是给变量起别名,所以必须要初始化引用,使引用成为某个对象的别名,否则没有实际意义,语法也是不允许的。

我们还可以给一个变量起多个别名,也就是有多个引用。

cpp 复制代码
//一个变量可以有多个引用
int& b = a;
int& e = b;
int& f = a;
//实际上都和a共用一块内存空间

但是不能改变引用的指向,如果我们要把 b 的指向改成 c:

我们知道 &b 就是取 b 的地址的意思,&b = c 的意思就变成了把 c 的值赋给 b 的地址,这是不允许的,或者写成 b = c,意思是把 c 的值赋给 b,所以在 C++ 语法层面上也是没有更改引用指向的办法的。

引用的使用

引用作为函数参数

引用通常可以作为函数参数,比如说实现交换两个值的函数 Swap,我们之前写的是这样的:

c 复制代码
void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

我们把一级指针作为函数参数,通过解引用指针的方法来交换实参中的值。

我们现在将参数改为引用类型,就会方便很多。

cpp 复制代码
//引用作为函数参数
void Swap(int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;
}

我们将引用作为函数参数,操作它们就相当于操作实参,直接交换两个实参的值,也同样达到了影响形参可以改变实参的目的,达到了和传指针一样的效果。

引用和指针在功能上有重叠,但各有各的特点。

比如说我们自己定义了一个结构体类型,里面的东西特别多。

c 复制代码
typedef struct st
{
	//n串代码
}st;

然后我们将 st 作为函数参数类型。

c 复制代码
void Func(struct st a)
{
	;
}

形参是实参的一份临时拷贝,所以我们在传参的时候就会产生拷贝,内容较大的时候耗时也长。

但如果我们函数参数是引用类型的,就不会产生拷贝,因为该参数相当于传过来的实参的别名,就是对该实参进行操作。

引用作为函数参数可以减少拷贝,提高效率。

引用作为返回值

当我们普通地传值传参的时候,我们返回来的值实际上是编译器创建的临时对象中存储的值。

比如说我们的 Add() 函数,返回值为 int 类型。

cpp 复制代码
int Add(int a, int b)
{
	int c = a + b;
	return c;
}

这里是传值返回。如果我们想对返回值进行修改呢?

cpp 复制代码
int a = ++Add(10, 20); // 修改返回值再赋给 a

编译器就会报这样一个错误:

为什么呢?这是因为函数在返回值的时候返回的是临时对象,而该临时对象具有常性,是不能被修改的,所以编译器会报这个错误。

函数在返回值的时候,编译器会创建一个临时对象,该临时对象把 c 的值拷贝进来,然后返回该临时对象储存的值。

如果我们返回的是引用呢?

cpp 复制代码
int& Add(int a, int b)
{
	int c = a + b;
	return c;
}

该返回值就能被正常修改。

函数返回的是引用的时候,比如说编译器起的别名是 int& tmp = c,返回来的是 tmp,就是 c 的引用,在这过程中编译器不会创建临时变量,相当于把 c 的别名返回来,不会产生拷贝,我们还可以对返回进行操作。

所以引用作为函数返回的时候,可以减少拷贝,提高效率,还可以对返回的引用进行操作。

既然返回引用有这么多好处,我们可以把所有函数返回都改成引用类型吗?

答案当然是不行的,比如我们上面返回引用的例子:

cpp 复制代码
int& Add(int a, int b)
{
	int c = a + b;
	return c;
}

c 是局部对象,在函数栈帧销毁时 c 的空间就被回收了,所以我们拿到 c 的引用,实际上该块内存空间已经不属于我们了,本质上就是野引用,类似于野指针,这是不安全的。

我们上面 ++Add(10, 20) 得到的恰好是 31,这只是个巧合,我们在返回一块不属于我们的内存空间的操作已经是错的了,再去修改这块内存空间的内容更是错上加错。

所以当返回对象是局部变量,在函数栈帧销毁时生命周期就结束的情况下,我们不能返回引用。

相关推荐
To0R1 小时前
Agent 上线前必须过的五道门(附 Spring Boot 实现)
java
vipxieliang1 小时前
固定电话验证详解:区号、号码、分机号的完整验证
java·spring boot
步行cgn1 小时前
Spring Boot 主入口类上的 @Enable 和 @Scan 注解详解
java·spring boot·后端
lhldsg1 小时前
全民健身解决方案软件开发实战:从架构设计到部署指南
java·前端·数据库·小程序
两点王爷1 小时前
Java 与前端加载 MVT 数据:从服务端切片到浏览器渲染
java·前端·状态模式
en.en..1 小时前
C语言核心解析:#define与typedef本质区别
开发语言·c++·算法
码匠许师傅2 小时前
【设计模式精讲】26.策略模式(Strategy)
c++·设计模式·策略模式·uml
TinyMemory2 小时前
Java 面向对象核心入门(九):方法重写 Override|彻底分清重载与重写
java·面向对象·override·方法重写·重载与重写
LaughingZhu2 小时前
Product Hunt 每日热榜 | 2026-09-08
java·ide·深度学习·百度·intellij-idea