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

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


🔥 星恒随风: 个人主页 ❄️ 个人专栏: 《指针合集》 《C语言基础》 《数据结构》 《机器学习导论》 《前端基础》 《python基础》 ✨ 数据即知识,压缩即智能


目录


一、函数重载是什么?

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

这里 bc 都是 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;

这里 bcd 本质上都是 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:

  • 适合短小、频繁调用的函数;
  • 只是对编译器的建议,不保证一定展开;
  • 比宏函数更安全;
  • 通常定义在头文件中。

相关推荐
zavoryn1 小时前
Python 面试高频:装饰器、迭代器、生成器和上下文管理器一次讲清
开发语言·python·面试
basketball6161 小时前
C++ 高级编程:1. 多线程基本操作
开发语言·c++
十五年专注C++开发1 小时前
std::vector<T>到QVector<T>的数据复制方案
c++·vector·iterator模式·qvector
rqtz2 小时前
【机器人】ROS结合Qt开发上位机软件工作空间配置
开发语言·qt·ros
lengxuemo10 小时前
ICC2学习笔记之Placement and Optimization
笔记·学习
温柔只给梦中人10 小时前
NLP学习:注意力机制
人工智能·学习·自然语言处理
sheeta199811 小时前
LeetCode 每日一题笔记 日期:2026.06.02 题目:3635. 最早完成陆地和水上游乐设施的时间 II
笔记·算法·leetcode
逐影者39711 小时前
ch592f学习
学习
小满Autumn11 小时前
MVVM Light 架构笔记:定位器、命令、消息与 IoC 实践
笔记·学习·架构·c#·上位机·mvvm