C++ Primer Plus 学习笔记 - 第8章 函数探幽
本章概览
Why This Matters: 第7章我们掌握了函数的基本用法,但 C++ 的函数远不止于此。本章将深入探索函数的高级特性,这些特性是编写现代 C++ 代码的基础。引用让函数可以直接操作调用方的数据,默认参数简化了接口设计,函数重载实现了同名函数的多态,模板则让泛型编程成为可能。
前置知识: 建议先掌握第7章(函数基础)和第4章(数组、指针、结构体),否则在引用参数、模板参数推导上会吃力。
核心目标:
- 理解内联函数的工作原理与适用场景
- 掌握引用变量的声明与使用
- 学会按引用传递参数的设计模式
- 理解默认参数的作用域规则
- 掌握函数重载的匹配规则
- 理解函数模板的原理与模板具体化
第8章知识体系:函数探幽
├── 内联函数
│ ├── inline 关键字
│ ├── 编译期展开
│ └── 适用场景与限制
│
├── 引用变量
│ ├── 引用 vs 指针
│ ├── 引用声明与初始化
│ └── 常引用 const&
│
├── 按引用传递参数
│ ├── 引用参数设计
│ ├── 返回引用
│ └── 引用 vs 值传递
│
├── 默认参数
│ ├── 默认参数声明
│ ├── 作用域规则
│ └── 设计原则
│
├── 函数重载
│ ├── 重载匹配规则
│ ├── const 参数重载
│ └── 注意事项
│
└── 函数模板
├── 模板定义
├── 模板参数推导
├── 模板具体化
└── 模板分文件管理
一、基础知识讲解 (Core Concepts)
1.1 内联函数(Inline Functions)
内联函数是 C++ 提供的一种优化机制,它告诉编译器在调用点直接展开函数体,而不是进行函数调用。
1.1.1 为什么需要内联函数
普通函数调用有额外开销:
// 普通函数调用过程
int result = add(10, 20);
// 实际执行:
// 1. 保存当前执行上下文(寄存器、堆栈)
// 2. 跳转到 add 函数地址
// 3. 执行 add 函数体
// 4. 返回结果
// 5. 恢复执行上下文
对于简单函数(如只返回 a+b),这个开销可能比函数本身执行时间还长。
1.1.2 内联函数的写法
// 完整示例 - 可直接编译运行
// 文件名:inline_function.cpp
#include <iostream>
using namespace std;
// 内联函数建议:函数体简短(1-3行)、无循环、无递归
inline int add(int a, int b) {
return a + b;
}
// 更复杂的函数不适合内联,编译器会忽略 inline 建议
int multiply(int a, int b) {
return a * b;
}
int main() {
int x = 10, y = 20;
// 内联函数在编译时会被展开为:
// int result = x + y;
int result = add(x, y);
cout << "10 + 20 = " << result << endl;
cout << "5 * 6 = " << multiply(5, 6) << endl;
return 0;
}
1.1.3 内联函数的工作原理
源代码:
int result = add(10, 20);
编译器优化后(近似):
int result = 10 + 20; // 直接展开,无需调用
1.1.4 内联函数的使用准则
适合内联的函数:
- 函数体非常小(如 1-3 行代码)
- 没有循环、递归
- 频繁调用(如在循环中)
不适合内联的函数:
- 函数体较大(编译器会忽略 inline)
- 有循环、递归
- 包含复杂逻辑
// 适合内联 ✅
inline int max(int a, int b) {
return (a > b) ? a : b;
}
// 不适合内联 ❌
inline int processData(vector<int>& data) {
// 大量代码...
for (auto& item : data) { // 有循环
// 处理...
}
// 递归调用...
return calculate(data);
}
1.1.5 内联与宏的区别
| 特性 | 内联函数 | 宏(#define) |
|---|---|---|
| 类型检查 | ✅ 有 | ❌ 无 |
| 调试 | ✅ 可调试 | ❌ 难以调试 |
| 作用域 | ✅ 受控 | ❌ 全局 |
| 安全性 | ✅ 安全 | ❌ 容易出错 |
// 宏的陷阱
#define SQUARE(x) x * x
int a = 5;
int b = SQUARE(a + 1); // 展开为:a + 1 * a + 1 = 5 + 5 + 1 = 11 ❌
// 内联函数安全
inline int square(int x) {
return x * x;
}
int c = square(a + 1); // 正确计算:6 * 6 = 36 ✅
SOLID - 单一职责:内联函数应只做一件事,做好一件事。
1.2 引用变量(Reference Variables)
引用是 C++ 独有的特性,它为变量提供了另一个名字(别名)。
1.2.1 引用的基本用法
// 完整示例 - 可直接编译运行
// 文件名:reference_basic.cpp
#include <iostream>
using namespace std;
int main() {
int a = 10;
int& ref = a; // ref 是 a 的引用(别名)
cout << "a = " << a << endl; // a = 10
cout << "ref = " << ref << endl; // ref = 10
ref = 20; // 通过引用修改,等同于修改 a
cout << "a = " << a << endl; // a = 20
cout << "ref = " << ref << endl; // ref = 20
cout << "&a = " << &a << endl; // a 和 ref 地址相同
cout << "&ref = " << &ref << endl;
return 0;
}
引用必须初始化,且一旦绑定就不能改变。
int a = 10, b = 20;
int& ref = a; // ✅ ref 绑定到 a
// int& ref2; // ❌ 错误:引用必须初始化
// ref = b; // ❌ 这是在赋值,不是改变绑定
引用的本质:引用在行为上更接近 const 指针。它具有以下关键特性:
- 必须在创建时进行初始化:引用必须在定义时绑定到一个变量,无法事后赋值
- 一旦与某个变量关联,则无法改变:引用一旦绑定,就始终指向同一个变量,不能重新绑定到其他变量
int a = 10, b = 20;
int* const p = &a; // const 指针:指针本身是 const,不能指向其他变量
int& ref = a; // 引用:行为类似 const 指针,不能重新绑定
// p = &b; // ❌ 错误:const 指针不能改变指向
// ref = b; // ❌ 这只是赋值,不是改变绑定(a 的值变为 20)
虽然引用在底层通常实现为 const 指针,但它提供了更简洁的语法(无需解引用操作符 *)。
1.2.2 引用 vs 指针
| 特性 | 引用 | 指针 |
|---|---|---|
| 是否必须初始化 | ✅ 必须 | ✅ 建议初始化 |
| 是否可为空 | ❌ 不能 | ✅ 可以 |
| 是否可重新绑定 | ❌ 不能 | ✅ 可以 |
| 语法简洁性 | ✅ 更简洁 | ❌ 需要 * 和 -> |
| 可获取地址 | ✅ 可以 &ref |
✅ 可以 &ptr |
// 完整示例 - 可直接编译运行
// 文件名:reference_vs_pointer.cpp
#include <iostream>
using namespace std;
int main() {
int a = 10;
int* p = &a; // 指针需要取地址
int& ref = a; // 引用直接绑定
// 使用上的区别
*p = 20; // 指针需要解引用
ref = 30; // 引用直接使用
cout << "a = " << a << endl; // a = 30
// 指针可以为空,引用不能
int* p2 = nullptr; // ✅ 合法
// int& ref2; // ❌ 编译错误
// 指针可以重新指向,引用不能
int b = 100;
p = &b; // ✅ 指针可以重新指向
// ref = b; // ❌ 这是赋值,不是重新绑定
return 0;
}
1.2.3 常引用(const 引用)
常引用是只读引用,不能通过它修改数据。
// 完整示例 - 可直接编译运行
// 文件名:const_reference.cpp
#include <iostream>
#include <string>
using namespace std;
void printValue(const int& value) {
// value = 100; // ❌ 错误:不能修改
cout << "value = " << value << endl; // ✅ 可以读取
}
void processString(const string& str) {
cout << "str = " << str << endl;
// str += " world"; // ❌ 错误:不能修改
}
int main() {
int x = 10;
const int& ref = x; // 常引用
// ref = 20; // ❌ 错误:不能通过常引用修改
x = 30; // ✅ 可以通过原变量修改
printValue(42); // ✅ 可以传入字面量
processString("hello"); // ✅ 可以传入临时对象
return 0;
}
常引用的价值:
- 保护数据不被意外修改
- 允许绑定到临时对象(右值)
- 避免拷贝,提高性能
// 常引用允许绑定临时对象
const string& getName() {
return "temporary"; // ✅ 临时对象可以绑定到 const&
}
// 普通引用不能绑定临时对象
// string& s = "temp"; // ❌ 错误
1.3 按引用传递函数参数
1.3.1 为什么要按引用传递
按值传递会拷贝整个对象,对于大型对象(如 string、vector、自定义类)开销很大。
// 按值传递:拷贝整个对象
void printString1(string s) {
cout << s << endl;
}
// 按引用传递:只传递地址,不拷贝
void printString2(const string& s) {
cout << s << endl;
}
int main() {
string longString(1000, 'a'); // 1000个字符
printString1(longString); // 拷贝 1000 字符,开销大
printString2(longString); // 只传地址,开销小
}
1.3.2 按引用传递的四种场景
// 完整示例 - 可直接编译运行
// 文件名:reference_parameters.cpp
#include <iostream>
#include <string>
#include <vector>
using namespace std;
// 场景1:只读参数 → const 引用
void printSum(const vector<int>& numbers) {
int sum = 0;
for (int n : numbers) {
sum += n;
}
cout << "sum = " << sum << endl;
}
// 场景2:需要修改参数 → 普通引用
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
// 场景3:输出多个值 → 引用参数
void divide(int a, int b, int& quotient, int& remainder) {
if (b == 0) {
quotient = 0;
remainder = 0;
return;
}
quotient = a / b;
remainder = a % b;
}
// 场景4:避免拷贝的大对象
void processLargeObject(const string& input, string& output) {
output = "processed: " + input;
}
int main() {
// 场景1:只读
vector<int> nums = {1, 2, 3, 4, 5};
printSum(nums);
// 场景2:交换
int x = 10, y = 20;
cout << "交换前: x=" << x << ", y=" << y << endl;
swap(x, y);
cout << "交换后: x=" << x << ", y=" << y << endl;
// 场景3:多返回值
int q, r;
divide(17, 5, q, r);
cout << "17 / 5 = " << q << " ... " << r << endl;
// 场景4:大对象处理
string result;
processLargeObject("hello", result);
cout << result << endl;
return 0;
}
1.3.3 引用参数的注意事项
1. 避免返回局部引用
// ❌ 危险:返回局部变量的引用
int& badFunction() {
int x = 10;
return x; // x 在函数结束后被销毁
}
// ✅ 安全:返回传入参数的引用
int& getElement(vector<int>& arr, int index) {
return arr[index]; // arr 的生命周期由调用方管理
}
2. 区分输入参数和输出参数
// 输入参数:const 引用
void processInput(const Data& data);
// 输出参数:普通引用(或指针)
void processOutput(Data& result);
// 如果参数可能被修改,加注释说明
void modifyVector(vector<int>& data); // 会修改 data
3. 引用参数的空值问题
引用不能为空,如果需要表示"无值",用指针或 std::optional。
// 用指针表示可选
void findUser(int id, User* outUser); // outUser 可以为 nullptr
// 或用 std::optional(C++17)
#include <optional>
optional<User> findUser(int id);
1.4 默认参数(Default Arguments)
默认参数允许在调用函数时省略某些参数,使用预设值。
1.4.1 默认参数的基本用法
// 完整示例 - 可直接编译运行
// 文件名:default_arguments.cpp
#include <iostream>
#include <string>
using namespace std;
// 完整示例 - 可直接编译运行
// 文件名:default_arguments.cpp
void greet(string name = "World", string prefix = "Hello") {
cout << prefix << ", " << name << "!" << endl;
}
int main() {
greet(); // Hello, World!
greet("Alice"); // Hello, Alice!
greet("Bob", "Hi"); // Hi, Bob!
greet("Charlie", "Welcome"); // Welcome, Charlie!
return 0;
}
1.4.2 默认参数的原则
原则:默认参数必须从右向左依次排列
// ✅ 合法:默认参数靠右
void func1(int a, int b = 10, int c = 20);
void func2(int a, int b = 10, int c); // ❌ 错误:c 在 b 右边但没有默认值
// 调用
func1(1); // a=1, b=10, c=20
func1(1, 5); // a=1, b=5, c=20
func1(1, 5, 15); // a=1, b=5, c=15
1.4.3 默认参数的作用域规则
// 完整示例 - 可直接编译运行
// 文件名:default_scope.cpp
#include <iostream>
using namespace std;
int value = 100;
// 函数原型中的默认参数
void display(int x, int multiplier = 2);
int main() {
int value = 200; // 局部变量,遮蔽全局变量
// 默认参数使用全局 value = 100
display(10); // 10 * 2 = 20
// 函数内部使用局部 value = 200
void display(int x, int multiplier) {
cout << "x=" << x << ", multiplier=" << multiplier << endl;
cout << "全局value=" << ::value << endl; // 显式使用全局
}
return 0;
}
注意:默认参数在函数声明中指定,不在定义中。
// 完整示例 - 可直接编译运行
// 文件名:default_declaration.cpp
#include <iostream>
using namespace std;
// 声明中指定默认参数
void setPosition(int x = 0, int y = 0);
// 定义中不再指定默认参数
void setPosition(int x, int y) {
cout << "位置: (" << x << ", " << y << ")" << endl;
}
int main() {
setPosition(); // (0, 0)
setPosition(10); // (10, 0)
setPosition(10, 20); // (10, 20)
return 0;
}
1.4.4 默认参数的设计原则
// ✅ 好的设计:常用值作为默认值
void setDelay(int milliseconds = 1000); // 默认1秒
// ✅ 好的设计:用 -1 表示无效
int findIndex(const vector<int>& arr, int target, int startPos = 0);
// ❌ 不好的设计:默认值可能不是最常用的情况
void setPort(int port = 8080); // 实际常用的是 80 或 443
1.5 函数重载(Function Overloading)
函数重载允许同名函数共存,通过参数列表区分。
1.5.1 函数重载的基本用法
// 完整示例 - 可直接编译运行
// 文件名:function_overload.cpp
#include <iostream>
using namespace std;
// 重载:参数类型不同
int add(int a, int b) {
cout << "int 版本" << endl;
return a + b;
}
double add(double a, double b) {
cout << "double 版本" << endl;
return a + b;
}
string add(const string& a, const string& b) {
cout << "string 版本" << endl;
return a + b;
}
int main() {
cout << add(1, 2) << endl; // 调用 int 版本
cout << add(1.5, 2.5) << endl; // 调用 double 版本
cout << add(string("Hello"), string("World")) << endl;
return 0;
}
1.5.2 重载匹配规则
编译器按以下优先级选择:
- 精确匹配
- 类型提升(char → int,float → double)
- 标准转换(int → double)
- 用户定义转换
// 完整示例 - 可直接编译运行
// 文件名:overload_resolution.cpp
#include <iostream>
using namespace std;
void print(int x) {
cout << "int: " << x << endl;
}
void print(double x) {
cout << "double: " << x << endl;
}
void print(const char* x) {
cout << "const char*: " << x << endl;
}
int main() {
print(10); // 精确匹配 int
print(10.5); // 精确匹配 double
print("hello"); // 精确匹配 const char*
print('a'); // char 提升为 int,调用 int 版本
print(true); // bool 提升为 int,调用 int 版本
return 0;
}
1.5.3 const 参数的重载
// 完整示例 - 可直接编译运行
// 文件名:const_overload.cpp
#include <iostream>
#include <string>
using namespace std;
// 重载:const 参数
void display(int* p) {
cout << "非const指针: ";
if (p) cout << *p << endl;
else cout << "nullptr" << endl;
}
void display(const int* p) {
cout << "const指针: ";
if (p) cout << *p << endl;
else cout << "nullptr" << endl;
}
void process(string& s) {
cout << "非const string: " << s << endl;
}
void process(const string& s) {
cout << "const string: " << s << endl;
}
int main() {
int x = 10;
const int cx = 20;
display(&x); // 调用非 const 版本
display(&cx); // 调用 const 版本
string s1 = "hello";
const string s2 = "world";
process(s1); // 调用非 const 版本
process(s2); // 调用 const 版本
return 0;
}
1.5.4 函数重载的注意事项
1. 返回类型不能区分重载
// ❌ 错误:返回类型不同不算重载
int add(int a, int b);
double add(int a, int b); // 编译错误
2. 参数列表必须实质不同
// ✅ 有效重载:参数类型不同
void func(int x);
void func(double x);
// ✅ 有效重载:参数个数不同
void func(int x);
void func(int x, int y);
// ✅ 有效重载:参数类型顺序不同
void func(int x, double y);
void func(double x, int y);
// ❌ 无效:参数名不同不算重载
void func(int a);
void func(int b); // 编译错误
3. 避免隐式转换导致的歧义
// ❌ 可能产生歧义
void process(int x);
void process(double x);
process(10); // 精确匹配 int
process(10.0); // 精确匹配 double
process('a'); // char 提升为 int,但也可转为 double,歧义!
1.6 函数模板(Function Templates)
模板允许编写通用代码,自动生成针对具体类型的代码。
1.6.1 模板的基本用法
// 完整示例 - 可直接编译运行
// 文件名:function_template.cpp
#include <iostream>
#include <string>
using namespace std;
// 函数模板
template <typename T>
T maximum(T a, T b) {
return (a > b) ? a : b;
}
int main() {
// 编译器自动生成对应的函数
cout << maximum(10, 20) << endl; // int 版本
cout << maximum(3.14, 2.71) << endl; // double 版本
cout << maximum(string("apple"), string("banana")) << endl; // string 版本
return 0;
}
编译器生成代码的过程:
源代码:
maximum(10, 20)
编译器生成:
int maximum(int a, int b) {
return (a > b) ? a : b;
}
然后调用生成的函数
1.6.2 多个模板参数
// 完整示例 - 可直接编译运行
// 文件名:multiple_template_params.cpp
#include <iostream>
using namespace std;
// 多个模板参数
template <typename T1, typename T2>
void printPair(T1 first, T2 second) {
cout << "first: " << first << ", second: " << second << endl;
}
// 返回类型可以与参数类型不同
template <typename T1, typename T2>
T1 convert(T2 value) {
return static_cast<T1>(value);
}
int main() {
printPair(10, "hello"); // T1=int, T2=const char*
printPair(3.14, 100); // T1=double, T2=int
int x = convert<int>(5.9); // 5.9 → 5
double y = convert<double>(42); // 42 → 42.0
cout << "x = " << x << endl;
cout << "y = " << y << endl;
return 0;
}
1.6.3 模板参数推导规则
// 完整示例 - 可直接编译运行
// 文件名:template_deduction.cpp
#include <iostream>
#include <vector>
using namespace std;
template <typename T>
void process(T value) {
cout << "处理: " << value << endl;
}
// 显式指定模板参数
template <typename T>
T create() {
return T();
}
int main() {
// 自动推导
process(10); // T = int
process(3.14); // T = double
process("hello"); // T = const char*
// 显式指定
process<double>(10); // 显式指定 T = double,输出 10.0
return 00;
}
1.6.4 模板具体化(Template Specialization)
当通用模板不适用于某种类型时,可以提供具体化版本。
// 完整示例 - 可直接编译运行
// 文件名:template_specialization.cpp
#include <iostream>
#include <cstring>
using namespace std;
// 通用模板
template <typename T>
void swap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
// 字符数组的具体化
template <>
void swap<char*>(char*& a, char*& b) {
// 交换指针本身,而不是字符串内容
char* temp = a;
a = b;
b = temp;
}
int main() {
int x = 10, y = 20;
swap(x, y);
cout << "x = " << x << ", y = " << y << endl;
char s1[] = "hello";
char s2[] = "world";
swap(s1, s2); // 使用具体化版本
cout << "s1 = " << s1 << ", s2 = " << s2 << endl;
return 0;
}
1.6.5 具体化 vs 重载
// 具体化和函数重载的选择
// 方法1:函数重载
void handle(int* p);
void handle(double* p);
// 方法2:模板具体化
template <typename T>
void process(T* p);
template <>
void process<int>(int* p);
// 选择建议:
// - 如果函数逻辑完全不同 → 用函数重载
// - 如果只是某些类型需要特殊处理 → 用具体化
二、进阶应用 (Modern C++ Practice)
2.1 引用参数的实际应用模式
2.1.1 链式调用(返回引用)
// 完整示例 - 可直接编译运行
// 文件名:chain_call.cpp
#include <iostream>
#include <string>
using namespace std;
class Builder {
private:
string result;
public:
Builder& add(const string& s) {
result += s;
return *this; // 返回自身引用
}
Builder& add(int n) {
result += to_string(n);
return *this;
}
string build() {
return result;
}
};
int main() {
Builder b;
string s = b.add("Hello")
.add(" ")
.add("World")
.add(123)
.build();
cout << s << endl; // Hello World123
return 0;
}
2.1.2 移动语义(C++11)
// 完整示例 - 可直接编译运行
// 文件名:move_semantics.cpp
#include <iostream>
#include <string>
using namespace std;
// 使用移动语义避免拷贝
void processString(string&& s) { // 右值引用
cout << "处理: " << s << endl;
s += " (已处理)";
}
int main() {
string s = "hello";
// 传入左值:需要拷贝
processString(s); // ❌ 编译错误:不能传给右值引用
// 传入右值:移动
processString("temporary"); // ✅ 直接移动
// 使用 std::move 强制移动
processString(move(s));
cout << "s 移动后: " << s << endl; // s 变为空
return 0;
}
2.2 模板的编译模型
2.2.1 包含模型(推荐)
// 文件:max.h
#ifndef MAX_H
#define MAX_H
// 模板实现必须放在头文件中
template <typename T>
T maximum(T a, T b) {
return (a > b) ? a : b;
}
#endif
// 使用时直接 include
#include "max.h"
2.2.2 显式实例化
// 文件:template.cpp
template <typename T>
T add(T a, T b) {
return a + b;
}
// 显式实例化:告诉编译器生成这些版本
template int add<int>(int, int);
template double add<double>(double, double);
2.3 auto 推导与模板
// 完整示例 - 可直接编译运行
// 文件名:auto_template.cpp
#include <iostream>
#include <vector>
#include <type_traits>
using namespace std;
template <typename T>
auto getFirst(vector<T>& v) -> typename vector<T>::iterator {
return v.begin();
}
// 使用 decltype 推导返回类型
template <typename T1, typename T2>
auto add(T1 a, T2 b) -> decltype(a + b) {
return a + b;
}
int main() {
vector<int> nums = {1, 2, 3};
cout << *getFirst(nums) << endl;
auto result = add(10, 20.5); // 自动推导为 double
cout << result << endl;
return 0;
}
三、工程陷阱与避坑指南 (Engineering Pitfalls)
3.1 内联函数的误用
现象:函数体很长但加了 inline,编译器忽略,预期性能提升没有实现。 原因:编译器忽略过长函数的 inline 请求。 解决方案:只对短小函数使用 inline。
// ❌ 编译器会忽略
inline int complexCalculation(int x) {
for (int i = 0; i < 1000; i++) {
// 大量计算...
}
return x;
}
3.2 引用参数未加 const 导致误修改
现象:函数意外修改了调用方的数据。 原因:引用参数没有加 const,调用方以为不会修改。 解决方案:只读参数一律加 const。
// ❌ 危险:语义不清
void process(vector<int>& data);
// ✅ 安全:明确表示只读
void process(const vector<int>& data);
3.3 默认参数重复指定
现象:编译错误或行为异常。 原因:在函数声明和定义中同时指定默认参数。 解决方案:只在声明中指定。
// ❌ 错误:重复指定
void func(int x = 10);
void func(int x = 10) { } // 编译错误
// ✅ 正确:只在声明中指定
void func(int x = 10);
void func(int x) { }
3.4 函数重载与模板的歧义
现象:编译器不知道选哪个版本。 原因:重载和模板都可能匹配。 解决方案:使用显式类型或重新设计。
template <typename T>
void process(T x) {
cout << "模板版本" << endl;
}
void process(int x) {
cout << "非模板版本" << endl;
}
int main() {
process(10); // ❌ 歧义:两个版本都匹配
process<int>(10); // ✅ 明确调用模板版本
process(10); // ✅ 明确调用非模板版本
}
3.5 模板编译时间过长
现象:大型项目模板实例化导致编译很慢。 原因:每个模板实例都生成代码。 解决方案:
// 1. 使用显式实例化减少重复
template int max<int>(int, int);
// 2. 分离声明和实现
// header.h
template <typename T> T max(T, T);
// impl.cpp
template <typename T>
T max(T a, T b) { return a > b ? a : b; }
template int max<int>(int, int); // 显式实例化
四、面试高频考点 (Interview Focus)
Q1: 内联函数和宏的区别?
答:
- 内联函数有类型检查,宏没有
- 内联函数可以调试,宏难以调试
- 内联函数有作用域控制,宏是全局的
- 内联函数更安全,宏容易出错(如
SQUARE(a+b))
Q2: 引用和指针的区别?
答:
- 引用必须初始化,指针不需要
- 引用不能为空,指针可以
- 引用不能重新绑定,指针可以
- 引用语法更简洁(不需要
*和->)
Q3: 什么时候使用 const 引用?
答:
- 只读参数用 const 引用,避免拷贝
- 避免意外修改调用方数据
- 允许绑定临时对象
Q4: 默认参数为什么只能在一处指定?
答:
- 避免重复指定导致的二义性
- 编译器选择声明处的默认值
- 实际工程中约定在头文件声明中指定
Q5: 函数重载的匹配顺序?
答:
- 精确匹配
- 类型提升(char→int,float→double)
- 标准转换(int→double)
- 用户定义转换
Q6: 模板具体化和函数重载如何选择?
答:
- 函数逻辑完全不同 → 函数重载
- 某些类型需要特殊处理 → 模板具体化
五、总结与回顾 (Summary & Review)
核心记忆点
- 内联函数:编译期展开,适合短小函数,避免函数调用开销
- 引用变量:变量的别名,必须初始化,不能重新绑定
- 按引用传递:避免拷贝,只读用 const,可修改用普通引用
- 默认参数:从右向左排列,只在声明处指定
- 函数重载:同名函数靠参数列表区分,返回类型不能作为区分依据
- 函数模板:通用代码,编译器自动生成具体类型版本
- 模板具体化:为特定类型提供特殊实现
函数设计检查清单
- 是否需要返回多个值?(用引用参数)
- 参数是否大对象?(用 const 引用避免拷贝)
- 是否有常用默认值?(使用默认参数)
- 是否有多种参数类型需要处理?(使用重载或模板)
- 是否需要通用算法?(使用模板)
下一章预告:第 9 章将进入类和对象的学习,我们会探讨面向对象编程的核心概念:类的定义、构造函数、析构函数、封装与抽象。