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

    • 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++ 专业术语

函数声明与定义

定义与作用

声明告诉编译器"这个名字存在",定义告诉编译器"这个东西是什么":

int add(int a, int b);      // 声明:只有签名,没有函数体
int add(int a, int b) {     // 定义:签名 + 函数体
    return a + b;
}

函数是 C++ 程序的基本组织单元。理解声明与定义的分离、参数传递机制和函数指针是设计正确接口的基础。

核心原理

声明与定义的关系

参数传递方式

完整示例

示例一:参数传递六种方式的性能对比

场景说明:构造一个中大型数据结构,对比六种传递方式的性能差异和语义效果。

#include <iostream>
#include <string>
#include <vector>
#include <chrono>

class Report {
public:
    std::string title;
    std::vector<int> data;

    Report(std::string t, std::vector<int> d)
        : title(std::move(t)), data(std::move(d)) {}

    // 拷贝构造函数(展示拷贝开销)
    Report(const Report& other)
        : title(other.title), data(other.data) {
        std::cout << "  [拷贝了 Report: " << title << "]" << std::endl;
    }

    // 移动构造函数
    Report(Report&& other) noexcept
        : title(std::move(other.title)), data(std::move(other.data)) {
        std::cout << "  [移动了 Report: " << title << "]" << std::endl;
    }
};

// 方式 1:传值(发生完整拷贝)
void process_by_value(Report r) {
    std::cout << "  处理 Report: " << r.title
              << " (数据量: " << r.data.size() << ")" << std::endl;
}

// 方式 2:传引用(无拷贝,可修改)
void process_by_ref(Report& r) {
    r.title += " [已处理]";
    std::cout << "  修改 Report: " << r.title << std::endl;
}

// 方式 3:传 const 引用(无拷贝,不可修改)—— 推荐默认选择
void process_by_const_ref(const Report& r) {
    std::cout << "  读取 Report: " << r.title << std::endl;
    // r.title = "xxx";  // 编译错误
}

// 方式 4:传指针(可空)
void process_by_ptr(Report* r) {
    if (r) {
        std::cout << "  通过指针处理: " << r->title << std::endl;
    } else {
        std::cout << "  空指针,跳过" << std::endl;
    }
}

// 方式 5:传右值引用(转移所有权)
void process_by_rvalue(Report&& r) {
    Report local = std::move(r);  // 窃取资源
    std::cout << "  转移所有权: " << local.title << std::endl;
}

int main() {
    std::cout << "===== 参数传递方式对比 =====" << std::endl;

    Report rpt("季度报告", std::vector<int>(100000, 42));

    std::cout << "\n1. 传值(会发生拷贝):" << std::endl;
    process_by_value(rpt);

    std::cout << "\n2. 传引用:" << std::endl;
    process_by_ref(rpt);
    std::cout << "  函数外标题变为: " << rpt.title << std::endl;

    std::cout << "\n3. 传 const 引用:" << std::endl;
    process_by_const_ref(rpt);

    std::cout << "\n4. 传指针:" << std::endl;
    process_by_ptr(&rpt);
    process_by_ptr(nullptr);

    std::cout << "\n5. 传右值引用(移动语义):" << std::endl;
    process_by_rvalue(Report("临时报告", std::vector<int>(10, 1)));

    return 0;
}

预期输出:

===== 参数传递方式对比 =====

1. 传值(会发生拷贝):
  [拷贝了 Report: 季度报告]
  处理 Report: 季度报告 (数据量: 100000)

2. 传引用:
  修改 Report: 季度报告 [已处理]
  函数外标题变为: 季度报告 [已处理]

3. 传 const 引用:
  读取 Report: 季度报告 [已处理]

4. 传指针:
  通过指针处理: 季度报告 [已处理]
  空指针,跳过

5. 传右值引用(移动语义):
  [移动了 Report: 临时报告]
  转移所有权: 临时报告

逐段分析:

  • 传值:对 rpt(含 10 万元素的 vector)产生完整深拷贝,开销巨大。验证了 [拷贝了 Report] 消息
  • 传引用:零拷贝,但函数内部可修改原对象。rpt.title 被追加了 [已处理]
  • 传 const 引用:零拷贝 + 防修改,是只读场景的最佳选择
  • 传指针:结构体内存开销小,可表达"此参数可选"(nullptr)的语义
  • 传右值引用:绑定到临时对象,可窃取其资源。process_by_rvalue 内部用 std::move 将资源转移到局部变量
  • 默认选择:只读 → const T&;输出参数(要修改) → T&;可选参数 → T*;转移所有权 → T&&

示例二:函数指针与回调机制

场景说明:实现一个成绩排序框架,通过函数指针注入比较策略。

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

struct Student {
    std::string name;
    int id;
    double gpa;
};

// 比较函数类型
using Comparator = bool (*)(const Student&, const Student&);

bool compare_by_id(const Student& a, const Student& b) {
    return a.id < b.id;
}

bool compare_by_gpa(const Student& a, const Student& b) {
    return a.gpa > b.gpa;  // 降序
}

bool compare_by_name(const Student& a, const Student& b) {
    return a.name < b.name;
}

// 通用的排序函数,通过函数指针注入策略
void sort_students(std::vector<Student>& students, Comparator cmp) {
    std::sort(students.begin(), students.end(), cmp);
}

// 返回函数指针
Comparator get_comparator(const std::string& strategy) {
    if (strategy == "id")   return compare_by_id;
    if (strategy == "gpa")  return compare_by_gpa;
    if (strategy == "name") return compare_by_name;
    return nullptr;  // 无效策略
}

int main() {
    std::vector<Student> students = {
        {"张三", 1003, 3.8},
        {"李四", 1001, 3.5},
        {"王五", 1002, 4.0}
    };

    // 直接传递函数指针
    std::cout << "===== 按 ID 排序 =====" << std::endl;
    sort_students(students, compare_by_id);
    for (const auto& s : students) {
        std::cout << s.id << " | " << s.name << " | GPA " << s.gpa << std::endl;
    }

    std::cout << "\n===== 按 GPA 排序 =====" << std::endl;
    sort_students(students, compare_by_gpa);
    for (const auto& s : students) {
        std::cout << s.id << " | " << s.name << " | GPA " << s.gpa << std::endl;
    }

    // 通过函数返回函数指针
    std::cout << "\n===== 策略选择 =====" << std::endl;
    auto cmp = get_comparator("name");
    if (cmp) {
        sort_students(students, cmp);
        for (const auto& s : students) {
            std::cout << s.name << std::endl;
        }
    }

    return 0;
}

预期输出:

===== 按 ID 排序 =====
1001 | 李四 | GPA 3.5
1002 | 王五 | GPA 4
1003 | 张三 | GPA 3.8

===== 按 GPA 排序 =====
1002 | 王五 | GPA 4
1003 | 张三 | GPA 3.8
1001 | 李四 | GPA 3.5

===== 策略选择 =====
李四
王五
张三

逐段分析:

  • using Comparator = bool (*)(const Student&, const Student&); 定义函数指针类型别名
  • sort_students 通过 Comparator 参数接收策略,实现排序逻辑与比较逻辑的解耦
  • get_comparator 返回函数指针——是策略模式的函数式实现
  • 函数指针是轻量级回调,但不如 std::function 灵活(后者可以包装 lambda 和有状态的函数对象)

易错场景与面试考点

易错场景

场景错误表现正确做法
只声明不定义就被调用链接错误 undefined reference确保链接了所有目标文件
函数声明和定义的签名不一致链接错误或意外重载头文件中的声明与源文件定义逐字一致
返回局部变量的引用或指针悬挂引用,UB返回值类型
大对象按值传递不必要的深拷贝使用 const T&
函数指针为空时调用段错误调用前检查 if (fp)

常见面试问题

  1. 声明和定义的区别?为什么需要头文件?——声明引入名称和类型;定义提供完整实现。头文件用于在多翻译单元间共享声明,避免重复定义。

  2. f(T)、f(T&)、f(const T&)、f(T&&) 各自的用途是什么?——传值用于小对象;T& 用于输出参数;const T& 是只读默认选择;T&& 用于移动语义(窃取资源)。

  3. 如何声明一个函数指针?void (*fp)(int) 各部分含义?——fp 是指向"接收 int、返回 void"函数的指针。括号围绕 *fp 表示指针,否则会被解析为返回 void* 的函数声明。

  4. 函数重载决议的基本规则?——建立候选集 → 筛选可行函数 → 选择最佳匹配。精确匹配优于提升优于标准转换。

  5. std::function 和函数指针的区别?——函数指针只能指向普通函数和静态成员函数;std::function 是类型擦除的通用可调用对象包装器,可包装 lambda、bind 结果、函数对象等。

小结

  • 声明放头文件,定义放源文件——C++ 项目组织的基本模式
  • 只读参数默认用 const T&,输出参数用 T&,可选参数用 T*
  • 函数指针实现策略注入,适合编译期确定的回调;运行期变体多用 std::function
  • 永远不要返回局部对象的引用或指针
下一页
函数重载