C++入门(二):函数重载、引用、const引用和 inline 内联函数

🔥 星恒随风: 个人主页 ❄️ 个人专栏: 《指针合集》 | 《C语言基础》 | 《数据结构》 | 《机器学习导论》 | 《前端基础》 | 《python基础》 ✨ 数据即知识,压缩即智能
目录
- [C++入门(二):函数重载、引用、const引用和 inline 内联函数](#C++入门(二):函数重载、引用、const引用和 inline 内联函数)
-
- 一、函数重载是什么?
- 二、函数重载的三种常见形式
-
- [1. 参数类型不同](#1. 参数类型不同)
- [2. 参数个数不同](#2. 参数个数不同)
- [3. 参数类型顺序不同](#3. 参数类型顺序不同)
- 三、返回值不同不能构成重载
- 四、缺省参数和函数重载可能产生歧义
- 五、函数重载的本质理解
- 六、引用是什么?
- [七、引用和取地址符都用 `&`,怎么区分?](#七、引用和取地址符都用
&,怎么区分?) - 八、引用的三个基本特性
-
- [1. 引用定义时必须初始化](#1. 引用定义时必须初始化)
- [2. 一个变量可以有多个引用](#2. 一个变量可以有多个引用)
- [3. 引用一旦绑定,不能再引用其他对象](#3. 引用一旦绑定,不能再引用其他对象)
- 九、引用传参:比指针传参更直观
- 十、引用传参还能减少拷贝
- 十一、引用返回:返回的不是值,而是对象本身
- 十二、引用返回的坑:不能返回局部变量引用
- [十三、const 引用是什么?](#十三、const 引用是什么?)
- [十四、const 引用可以绑定临时对象](#十四、const 引用可以绑定临时对象)
- 十五、指针和引用的关系
- [十六、inline 内联函数是什么?](#十六、inline 内联函数是什么?)
- [十七、inline 只是建议,不是命令](#十七、inline 只是建议,不是命令)
- [十八、inline 和宏函数有什么关系?](#十八、inline 和宏函数有什么关系?)
- [十九、inline 函数一般放在哪里?](#十九、inline 函数一般放在哪里?)
- 二十、总结
一、函数重载是什么?
C 语言中,同一个作用域中不能定义两个同名函数。
但是 C++ 支持函数重载。
函数重载指的是:
在同一作用域中,可以存在多个同名函数,但它们的参数列表必须不同。
参数列表不同,主要包括:
- 参数类型不同
- 参数个数不同
- 参数类型顺序不同
示例:
cpp
#include <iostream>
using namespace std;
// 参数类型不同
int Add(int left, int right)
{
cout << "int Add(int, int)" << '\n';
return left + right;
}
double Add(double left, double right)
{
cout << "double Add(double, double)" << '\n';
return left + right;
}
int main()
{
cout << Add(10, 20) << '\n';
cout << Add(1.1, 2.2) << '\n';
return 0;
}
输出:
txt
int Add(int, int)
30
double Add(double, double)
3.3
虽然两个函数都叫 Add,但参数类型不同,所以构成函数重载。
二、函数重载的三种常见形式
1. 参数类型不同
cpp
int Add(int x, int y)
{
return x + y;
}
double Add(double x, double y)
{
return x + y;
}
调用时:
cpp
Add(1, 2); // 调用 int 版本
Add(1.1, 2.2); // 调用 double 版本
编译器会根据实参类型选择最匹配的函数。
2. 参数个数不同
cpp
void Print()
{
cout << "Print()" << '\n';
}
void Print(int x)
{
cout << "Print(int)" << '\n';
}
调用:
cpp
Print(); // 调用无参版本
Print(10); // 调用一个 int 参数版本
3. 参数类型顺序不同
cpp
void Func(int a, char b)
{
cout << "Func(int, char)" << '\n';
}
void Func(char b, int a)
{
cout << "Func(char, int)" << '\n';
}
调用:
cpp
Func(10, 'a'); // 调用 Func(int, char)
Func('a', 10); // 调用 Func(char, int)
虽然参数个数一样,类型也一样,但是类型顺序不同,也可以构成重载。
三、返回值不同不能构成重载
下面这种写法是不允许的:
cpp
void Func()
{
}
int Func()
{
return 0;
}
原因是:
函数调用时,编译器无法仅根据返回值判断你到底想调用哪个函数。
例如:
cpp
Func();
这句代码没有接收返回值,编译器不知道应该调用 void Func() 还是 int Func()。
所以函数重载只看函数名和参数列表,不靠返回值区分。
四、缺省参数和函数重载可能产生歧义
看下面代码:
cpp
void f()
{
cout << "f()" << '\n';
}
void f(int a = 10)
{
cout << "f(int)" << '\n';
}
int main()
{
f(); // 这里会产生歧义
return 0;
}
为什么?
因为 f() 既可以匹配无参版本:
cpp
void f()
也可以匹配带缺省参数的版本:
cpp
void f(int a = 10)
编译器无法判断你想调用哪个,于是报错。
所以函数重载和缺省参数一起用时要格外小心。
建议:
如果缺省参数已经能解决问题,就不要再写容易冲突的重载版本。
五、函数重载的本质理解
函数重载不是运行时才决定的。
它是在编译阶段由编译器根据参数类型、参数个数等信息决定调用哪个函数。
所以它属于:
编译时多态。
你可以简单理解为:
同一个函数名,可以根据参数不同表现出不同形态。
这让函数接口更自然。
比如:
cpp
Add(1, 2);
Add(1.1, 2.2);
我们都想表达"加法",没必要写成:
cpp
AddInt(1, 2);
AddDouble(1.1, 2.2);
C++ 用函数重载让代码更符合人的表达习惯。
六、引用是什么?
引用是 C++ 非常重要的语法。
引用不是新定义一个变量,而是给已经存在的变量取一个别名。
语法:
cpp
类型& 引用名 = 被引用对象;
示例:
cpp
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int& b = a;
int& c = a;
cout << a << '\n';
cout << b << '\n';
cout << c << '\n';
b = 20;
cout << a << '\n';
cout << b << '\n';
cout << c << '\n';
return 0;
}
输出:
txt
10
10
10
20
20
20
这里 b 和 c 都是 a 的别名。
修改 b,其实就是修改 a。
可以把它理解成:
同一个人有多个名字。
比如一个人本名叫"李逵",外号叫"黑旋风",别人也叫他"铁牛"。
名字不同,但指向的是同一个人。

七、引用和取地址符都用 &,怎么区分?
C++ 中引用使用 &:
cpp
int& b = a;
C 语言中取地址也使用 &:
cpp
&a
这两个 & 不是同一个语义。
区分方式看上下文:
cpp
int& b = a; // 这里的 & 是引用声明
cout << &a; // 这里的 & 是取地址
在类型后面出现:
cpp
int& b
表示引用类型。
在表达式前面出现:
cpp
&a
表示取地址。
八、引用的三个基本特性
引用有三个重要特性:
1. 引用定义时必须初始化
错误写法:
cpp
int& ra; // 错误,引用必须初始化
正确写法:
cpp
int a = 10;
int& ra = a;
引用不是"空别名"。
既然是别名,就必须一开始说明它是谁的别名。
2. 一个变量可以有多个引用
cpp
int a = 10;
int& b = a;
int& c = a;
int& d = b;
这里 b、c、d 本质上都是 a 的别名。
3. 引用一旦绑定,不能再引用其他对象
看下面代码:
cpp
int a = 10;
int b = 20;
int& ra = a;
ra = b;
很多我们容易误以为:
ra 改成引用 b 了。
其实不是。
这句:
cpp
ra = b;
表示把 b 的值赋给 ra,也就是赋给 a。
最终结果是:
cpp
a == 20
b == 20
但 ra 仍然是 a 的引用,没有改绑到 b。
九、引用传参:比指针传参更直观
C 语言中,如果想在函数中交换两个变量的值,通常使用指针:
cpp
void Swap(int* px, int* py)
{
int tmp = *px;
*px = *py;
*py = tmp;
}
调用时:
cpp
Swap(&x, &y);
C++ 可以使用引用传参:
cpp
void Swap(int& rx, int& ry)
{
int tmp = rx;
rx = ry;
ry = tmp;
}
调用时:
cpp
Swap(x, y);
完整示例:
cpp
#include <iostream>
using namespace std;
void Swap(int& rx, int& ry)
{
int tmp = rx;
rx = ry;
ry = tmp;
}
int main()
{
int x = 0;
int y = 1;
cout << x << " " << y << '\n';
Swap(x, y);
cout << x << " " << y << '\n';
return 0;
}
输出:
txt
0 1
1 0
引用传参的效果和指针传参类似,都能在函数内部修改外部变量。
但引用写起来更自然。

十、引用传参还能减少拷贝
如果函数参数是一个大对象,按值传参会产生拷贝。
例如:
cpp
void Print(vector<int> v)
{
// 会拷贝整个 vector
}
更好的写法是:
cpp
void Print(const vector<int>& v)
{
// 不拷贝,同时不允许修改 v
}
这个写法在 C++ 中非常常见。
它的优点是:
- 使用引用,避免拷贝;
- 使用
const,防止函数内部误修改; - 接口语义更清楚。
虽然现在还没正式学习 vector不过目前你可以把这个当成一个数组,但可以先记住这个常见写法:
cpp
const 类型& 参数名
它通常表示:
只读引用传参。
十一、引用返回:返回的不是值,而是对象本身
引用不仅可以做参数,也可以做返回值。
比如栈顶函数:
cpp
int& Top(int* a, int top)
{
return a[top - 1];
}
调用:
cpp
int arr[5] = {1, 2, 3, 4, 5};
int top = 5;
Top(arr, top) += 10;
cout << arr[4] << '\n';
输出:
txt
15
为什么可以这样?
因为 Top 返回的是 arr[top - 1] 的引用,也就是这个元素本身的别名。
所以:
cpp
Top(arr, top) += 10;
等价于:
cpp
arr[4] += 10;
十二、引用返回的坑:不能返回局部变量引用
错误示例:
cpp
int& Bad()
{
int x = 10;
return x;
}
为什么错误?
因为 x 是函数内部的局部变量。
函数结束后,x 的生命周期就结束了。
你返回它的引用,相当于返回了一个已经失效对象的别名。
这会造成悬空引用。
正确原则:
只有当返回对象在函数结束后仍然存在时,才可以返回引用。
可以返回:
- 全局变量的引用;
- 静态变量的引用;
- 参数对象中的成员引用;
- 容器内部仍然有效元素的引用。
不要返回:
- 局部变量的引用;
- 临时对象的普通引用。
十三、const 引用是什么?
有时候我们不希望通过引用修改对象。
这时可以使用 const 引用。
cpp
const int& ra = a;
示例:
cpp
int a = 10;
const int& ra = a;
// ra = 20; // 错误,不能通过 const 引用修改对象
如果被引用对象本身是 const,必须用 const 引用:
cpp
const int a = 10;
// int& ra = a; // 错误,权限放大
const int& ra = a; // 正确
为什么普通引用不行?
因为:
cpp
int& ra = a;
意味着可以通过 ra 修改 a。
但 a 是 const 对象,本来就不允许修改。
所以这是权限放大,编译器不允许。

十四、const 引用可以绑定临时对象
看下面代码:
cpp
int a = 10;
// int& rb = a * 3; // 错误
const int& rb = a * 3; // 正确
a * 3 的结果不是一个有名字的变量,而是一个临时对象。
普通引用不能绑定这种临时对象。
但是 const 引用可以绑定临时对象。
类似:
cpp
const int& r = 30;
这也是合法的。
可以简单理解为:
临时对象不能被普通引用修改,但可以被 const 引用只读访问。
十五、指针和引用的关系
指针和引用很像,但不是一回事。

示例:
cpp
int a = 10;
int b = 20;
int* p = &a;
p = &b; // 指针可以改变指向
int& r = a;
r = b; // 不是改变引用对象,而是把 b 的值赋给 a
十六、inline 内联函数是什么?
inline 用来修饰函数。
被 inline 修饰的函数叫内联函数。
示例:
cpp
inline int Add(int x, int y)
{
return x + y;
}
它的思想是:
编译器可以在函数调用处直接展开函数体,从而减少函数调用开销。
普通函数调用大致要经历:
- 建立栈帧
- 参数传递
- 跳转到函数地址
- 执行函数
- 返回调用位置
对于非常短小、频繁调用的函数,这些调用开销可能比函数本身还大。
所以 C++ 提供了 inline。
十七、inline 只是建议,不是命令
inline 对编译器来说更像一个建议,你可以把这个当成一种更安全的宏定义。

编译器可以选择展开,也可以选择不展开。
一般适合 inline 的函数:
- 函数体很短;
- 调用频率高;
- 逻辑简单。
不适合 inline 的函数:
- 函数体很长;
- 有复杂循环;
- 递归函数;
- 代码膨胀风险大。
例如:
cpp
inline int Max(int x, int y)
{
return x > y ? x : y;
}
这种比较适合写成inlin函数。
十八、inline 和宏函数有什么关系?
C 语言中,为了减少函数调用开销,常用宏函数:
cpp
#define SQUARE(x) ((x) * (x))
但是宏有很多问题:
- 不做类型检查;
- 容易因为括号写错导致优先级问题;
- 参数可能被多次求值;
- 不方便调试。
例如:
cpp
#define SQUARE(x) ((x) * (x))
int a = 5;
cout << SQUARE(a++) << '\n';
宏展开后,a++ 可能被执行两次,结果不一定符合预期。
C++ 的 inline 更安全:
cpp
inline int Square(int x)
{
return x * x;
}

所以可以粗略理解为:
inline 是 C++ 用来替代一部分宏函数的更安全方案。
十九、inline 函数一般放在哪里?
如果一个函数要作为 inline 函数使用,通常把定义放在头文件中。
例如:
MathUtil.h
cpp
#pragma once
inline int Add(int x, int y)
{
return x + y;
}
为什么?
因为内联展开要求编译器在调用点看到函数定义。
如果只在头文件中放声明:
cpp
inline int Add(int x, int y);
然后把定义放到 .cpp 中,其他源文件包含头文件时可能看不到函数体,导致无法正确内联,甚至产生链接问题。

二十、总结
这一篇主要讲了 C++ 入门中非常重要的几个增强语法。
函数重载:
- 同一作用域中允许同名函数;
- 要求参数列表不同;
- 返回值不同不能构成重载;
- 重载解析发生在编译阶段。
引用:
- 引用是已有变量的别名;
- 引用定义时必须初始化;
- 引用一旦绑定,不能再绑定其他对象;
- 引用传参可以修改外部变量,也能减少拷贝;
- 引用返回可以返回对象本身,但不能返回局部变量引用。
const 引用:
- 可以引用 const 对象;
- 可以引用普通对象,但不能通过引用修改;
- 可以绑定临时对象;
- 常用于只读传参,避免拷贝。
指针和引用:
- 指针保存地址,可以改变指向;
- 引用是别名,不能改变绑定对象;
- 指针更灵活,引用更安全直观。
inline:
- 适合短小、频繁调用的函数;
- 只是对编译器的建议,不保证一定展开;
- 比宏函数更安全;
- 通常定义在头文件中。