乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

函数重载

定义与作用

函数重载(Function Overloading)允许在同一个作用域内定义多个同名但参数列表不同的函数。编译器根据调用时的实参类型自动选择最匹配的版本,这个过程称为重载决议(Overload Resolution)。

重载的核心价值:

  • 接口统一:相同语义的操作使用同一个名字
  • 类型安全:编译器在编译期选择正确版本
  • 泛型基础:模板函数与重载配合形成强大的泛型机制

核心原理

重载决议三步骤

const 重载

成员函数可以通过 const 修饰实现重载,提供读写和只读两种接口:

class Container {
public:
    int& operator[](size_t i);       // 非 const 版本:可读写
    const int& operator[](size_t i) const;  // const 版本:只读
};

ADL(Argument-Dependent Lookup)

ADL 使得 std::cout << "hello" 中的 operator<< 能在 std 命名空间中被找到。

完整示例

示例一:日志系统的重载设计

场景说明:设计一个日志系统,通过重载支持多种数据类型的输出。

#include <iostream>
#include <string>
#include <vector>
#include <iomanip>

// 日志级别枚举
enum class LogLevel { Info, Warning, Error };

// 基础输出函数 — 不同数据类型的重载
void log(LogLevel level, const std::string& msg) {
    const char* prefix;
    switch (level) {
        case LogLevel::Info:    prefix = "[INFO] "; break;
        case LogLevel::Warning: prefix = "[WARN] "; break;
        case LogLevel::Error:   prefix = "[ERROR]"; break;
        default:                prefix = "[????] ";
    }
    std::cout << prefix << " " << msg << std::endl;
}

// 重载:接收整数
void log(LogLevel level, int value) {
    log(level, std::to_string(value));
}

// 重载:接收浮点数
void log(LogLevel level, double value) {
    std::ostringstream oss;
    oss << std::fixed << std::setprecision(2) << value;
    log(level, oss.str());
}

// 重载:接收布尔值
void log(LogLevel level, bool value) {
    log(level, std::string(value ? "true" : "false"));
}

// 重载:简洁版本(默认 Info 级别)
void log(const std::string& msg) {
    log(LogLevel::Info, msg);
}

int main() {
    log("系统启动");                             // 调用简洁版
    log(LogLevel::Info, "数据库连接成功");        // 完整版 string
    log(LogLevel::Warning, 404);                 // 整数版
    log(LogLevel::Error, 3.14159);               // 浮点版
    log(LogLevel::Info, true);                   // 布尔版

    return 0;
}

预期输出:

[INFO]  系统启动
[INFO]  数据库连接成功
[WARN]  404
[ERROR] 3.14
[INFO]  true

逐段分析:

  • log(LogLevel, string) 是主实现,其他重载版本转换为 string 后委托给它——DRY 原则
  • 编译器根据实参类型自动选择:404 匹配 int、3.14159 匹配 double、true 匹配 bool
  • 简洁版 log(string) 默认使用 Info 级别,通过参数数量不同实现重载
  • 如果添加 log(LogLevel, long),log(LogLevel::Info, 404) 会产生歧义(int → long 和 int → double 都是标准转换)

示例二:数学向量类的 const 重载与 ADL

场景说明:实现二维向量类,通过 const 重载提供读写/只读访问,并利用 ADL 实现运算符。

#include <iostream>
#include <cmath>

namespace math {

class Vec2 {
public:
    Vec2(double x, double y) : x_(x), y_(y) {}

    // const 重载:只读访问
    double x() const { return x_; }
    double y() const { return y_; }

    // 非 const 重载:可修改访问
    double& x() { return x_; }
    double& y() { return y_; }

    double length() const {
        return std::sqrt(x_ * x_ + y_ * y_);
    }

private:
    double x_, y_;
};

// 全局运算符:通过 ADL 找到
Vec2 operator+(const Vec2& a, const Vec2& b) {
    return Vec2(a.x() + b.x(), a.y() + b.y());
}

Vec2 operator-(const Vec2& a, const Vec2& b) {
    return Vec2(a.x() - b.x(), a.y() - b.y());
}

// operator<< 也在 math 命名空间,ADL 可找到
std::ostream& operator<<(std::ostream& os, const Vec2& v) {
    return os << "(" << v.x() << ", " << v.y() << ")";
}

}  // namespace math

int main() {
    using math::Vec2;  // 只引入 Vec2,其他通过 ADL

    Vec2 pos(3.0, 4.0);
    Vec2 offset(1.0, 2.0);

    // 无需 using math::operator+,ADL 自动查找
    auto result = pos + offset;
    auto diff = pos - offset;

    // 无需 using math::operator<<
    std::cout << pos << " + " << offset << " = " << result << std::endl;
    std::cout << pos << " - " << offset << " = " << diff << std::endl;

    // const 重载的使用
    const Vec2 constant_vec(10.0, 0.0);
    std::cout << "常量向量的长度: " << constant_vec.length() << std::endl;
    // constant_vec.x() = 5.0;  // 编译错误:const 版本返回 const double&

    // 非 const 对象可以使用读写版本
    Vec2 mutable_vec(1.0, 1.0);
    mutable_vec.x() = 100.0;  // 调用非 const 重载
    std::cout << "修改后: " << mutable_vec << std::endl;

    return 0;
}

预期输出:

(3, 4) + (1, 2) = (4, 6)
(3, 4) - (1, 2) = (2, 2)
常量向量的长度: 10
修改后: (100, 1)

逐段分析:

  • x() 有两个重载:非 const 版返回 double&(可修改),const 版返回 double(只读)
  • const Vec2 对象调用 x() 时,自动选择 const 版本——编译器根据对象的 const 限定符匹配
  • operator+ 和 operator<< 定义在 math 命名空间中,Vec2 也在 math 中——ADL 使得这些函数在不写 using 的情况下也能被找到
  • ADL 避免了写 math::operator+(pos, offset) 的冗长语法,使得 pos + offset 可以自然书写

示例三:重载歧义与解决方案

#include <iostream>

void f(int)    { std::cout << "f(int)" << std::endl; }
void f(double) { std::cout << "f(double)" << std::endl; }
void f(long)   { std::cout << "f(long)" << std::endl; }

int main() {
    f(42);          // 精确匹配 f(int)
    f(3.14);        // 精确匹配 f(double)
    f('A');         // char → int 提升,匹配 f(int)
    f(42L);         // 精确匹配 f(long)

    // f(3.14f);    // 歧义!float → int? float → double? float → long?
                     // 三者都是标准转换,无以分出最佳

    // 解决方案:显式转换
    f(static_cast<double>(3.14f));  // 明确调 f(double)

    return 0;
}

易错场景与面试考点

易错场景

场景错误表现正确做法
只有返回类型不同的重载编译错误重载必须参数列表不同
派生类同名函数隐藏基类所有重载基类重载不可见使用 using Base::f; 引入
默认参数与重载引发歧义意外匹配减少默认参数,使用重载替代
窄化转换导致意外匹配调用了意料外的重载使用显式转换或调整重载设计
不同命名空间的函数通过 ADL 被意外找到行为不符合预期注意命名空间设计

常见面试问题

  1. 重载决议的三步骤是什么?——建立候选函数集 → 筛选可行函数 → 选择最佳可行函数。最佳匹配通过隐式转换序列的等级排名决定。

  2. 重载(overload)、覆盖(override)、隐藏(hide)的区别?——重载是同作用域同名不同参;覆盖是派生类虚函数替代基类虚函数;隐藏是派生类同名非虚函数屏蔽基类所有同名函数。

  3. ADL 是什么?为什么需要它?——Argument-Dependent Lookup,在函数实参的关联命名空间中查找函数。它让 cout << "hello" 能自动找到 std::operator<<。

  4. 为什么 void f(int) 和 void f(const int) 不构成重载?——顶层 const 被忽略。void f(int) 和 void f(int&) 可以重载。

  5. void f(int*) 和 void f(const int*) 能重载吗?——能。底层 const 不被忽略,这两个函数的参数类型不同。

小结

  • 重载决议三步骤:候选 → 可行 → 最佳(精确匹配 > 提升 > 标准转换)
  • const 重载提供读写/只读双接口,编译器根据对象 const 性自动选择
  • ADL 让运算符和自定义类型的函数调用更自然
  • 派生类同名函数会隐藏基类所有重载,用 using Base::f; 引入
  • 避免仅返回类型不同的重载、避免重载与默认参数混用以防歧义
上一页
函数声明与定义
下一页
默认参数与内联函数