函数重载 (Function Overloading)
什么是函数重载?
函数重载允许在同一个作用域内创建多个同名函数,只要它们的参数列表不同即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| #include <iostream>
using namespace std;
// 整数版本
int add(int x, int y) {
return x + y;
}
// 双精度版本
double add(double x, double y) {
return x + y;
}
// 不同参数数量
int add(int x, int y, int z) {
return x + y + z;
}
int main() {
cout << add(3, 4) << endl; // 调用 int add(int, int)
cout << add(3.5, 4.2) << endl; // 调用 double add(double, double)
cout << add(1, 2, 3) << endl; // 调用 int add(int, int, int)
return 0;
}
|
函数重载的规则
- 参数列表必须不同(类型、数量或顺序)
- 返回类型不足以区分重载函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| // 有效的重载 - 参数类型不同
void display(char c) {
cout << "Character: " << c << endl;
}
void display(const char* str) {
cout << "C-string: " << str << endl;
}
void display(const string& str, ostream& os = cout) {
os << "String: " << str << endl;
}
void display(const vector<int>& vec, ostream& os = cout) {
os << "Vector: ";
for (size_t i = 0; i < vec.size(); i++) {
os << vec[i] << " ";
}
os << endl;
}
// 无效的重载 - 只有返回类型不同
// void process(int x);
// bool process(int x); // 错误:编译器无法区分
// int process(int x); // 错误:编译器无法区分
|
为什么返回类型不能用于重载?
编译器在调用函数时无法根据返回类型来区分:
1
2
3
4
5
6
7
8
| // 假设允许这样的重载
void display(int x);
bool display(int x);
int display(int x);
// 调用时会产生歧义
int value = 10;
display(value); // 编译器不知道调用哪个版本!
|
默认参数 (Default Arguments)
基本用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| #include <iostream>
#include <vector>
#include <fstream>
using namespace std;
// 支持默认输出流的显示函数
void display(const vector<int>& vec, ostream& os = cout) {
for (size_t i = 0; i < vec.size(); i++) {
os << vec[i] << " ";
}
os << endl;
}
// 调试开关函数
void vec_increment(vector<int>& vec, ostream* ofile = nullptr) {
for (size_t i = 0; i < vec.size(); i++) {
vec[i]++;
if (ofile) { // 如果提供了输出流,输出调试信息
(*ofile) << "Incremented element " << i
<< " to " << vec[i] << endl;
}
}
}
int main() {
vector<int> numbers = {1, 2, 3, 4, 5};
// 使用默认参数(输出到标准输出)
display(numbers);
// 输出到文件
ofstream outFile("output.txt");
display(numbers, outFile);
// 不调试模式
vec_increment(numbers);
// 调试模式(输出到文件)
ofstream debugFile("debug.txt");
vec_increment(numbers, &debugFile);
return 0;
}
|
默认参数规则
1. 右向规则
如果某个参数有默认值,它右边的所有参数都必须有默认值。
1
2
3
4
5
6
| // 错误示例
void funcA(int iX = 0, int iY, ostream& os = cout); // 无效
// 正确示例
void funcA(int iY, int iX = 0, ostream& os = cout); // 有效
void funcB(int iX = 0, int iY = 0, ostream& os = cout); // 有效
|
2. 单一定义规则
默认参数只能在函数声明或定义中指定一次。
推荐做法:在头文件的声明中指定
1
2
3
4
5
6
7
8
9
10
11
12
| // math_utils.h
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
#include <iostream>
using namespace std;
// 在声明中指定默认参数
void displayNumber(int num, ostream& os = cout);
double calculateArea(double radius, double pi = 3.14159);
#endif
|
1
2
3
4
5
6
7
8
9
10
11
| // math_utils.cpp
#include "math_utils.h"
// 在定义中不再重复默认参数
void displayNumber(int num, ostream& os) {
os << "Number: " << num << endl;
}
double calculateArea(double radius, double pi) {
return pi * radius * radius;
}
|
函数模板
问题背景:重复的代码
假设我们需要为不同类型的 vector 编写 display 函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| #include <iostream>
#include <vector>
#include <string>
using namespace std;
// 重复的代码 - 每种类型都需要一个单独的函数
void display(const vector<int>& vec, ostream& os = cout) {
for (size_t i = 0; i < vec.size(); i++) {
os << vec[i] << " ";
}
os << endl;
}
void display(const vector<long>& vec, ostream& os = cout) {
for (size_t i = 0; i < vec.size(); i++) {
os << vec[i] << " ";
}
os << endl;
}
void display(const vector<double>& vec, ostream& os = cout) {
for (size_t i = 0; i < vec.size(); i++) {
os << vec[i] << " ";
}
os << endl;
}
void display(const vector<string>& vec, ostream& os = cout) {
for (size_t i = 0; i < vec.size(); i++) {
os << vec[i] << " ";
}
os << endl;
}
|
解决方案:函数模板
函数模板允许我们编写一次代码,然后让编译器根据实际使用的类型生成具体的函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| #include <iostream>
#include <vector>
#include <string>
using namespace std;
// 函数模板 - 通用的 display 函数
template <typename ElemType>
void display(const vector<ElemType>& vec, ostream& os = cout) {
for (size_t i = 0; i < vec.size(); i++) {
ElemType t = vec[i]; // 类型在实例化时确定
os << t;
if (i < vec.size() - 1) os << ", ";
}
os << endl;
}
// 使用示例
int main() {
vector<int> intVec = {1, 2, 3, 4, 5};
vector<double> doubleVec = {1.1, 2.2, 3.3, 4.4};
vector<string> stringVec = {"apple", "banana", "cherry"};
// 编译器自动推断类型并生成相应的函数
cout << "Integer vector: ";
display(intVec); // ElemType 绑定为 int
cout << "Double vector: ";
display(doubleVec); // ElemType 绑定为 double
cout << "String vector: ";
display(stringVec); // ElemType 绑定为 string
return 0;
}
|
函数模板的工作原理
模板实例化过程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // 当编译器看到 display(intVec) 时,它会生成:
void display_int_version(const vector<int>& vec, ostream& os = cout) {
for (size_t i = 0; i < vec.size(); i++) {
int t = vec[i]; // 注意:类型变为 int
os << t;
if (i < vec.size() - 1) os << ", ";
}
os << endl;
}
// 当编译器看到 display(doubleVec) 时,它会生成:
void display_double_version(const vector<double>& vec, ostream& os = cout) {
for (size_t i = 0; i < vec.size(); i++) {
double t = vec[i]; // 注意:类型变为 double
os << t;
if (i < vec.size() - 1) os << ", ";
}
os << endl;
}
|
显式指定模板参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| #include <iostream>
#include <vector>
using namespace std;
template <typename T>
T getMax(T a, T b) {
return (a > b) ? a : b;
}
int main() {
// 编译器自动推断类型
cout << getMax(3, 5) << endl; // T 推断为 int
cout << getMax(3.5, 2.1) << endl; // T 推断为 double
// 显式指定类型
cout << getMax<int>(3, 5) << endl; // 显式指定 T 为 int
cout << getMax<double>(3, 5.5) << endl; // 显式指定 T 为 double
return 0;
}
|
选择正确的技术
1. 使用参数默认的情况
适用场景:函数只有一种实现,参数有合适的默认值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| #include <iostream>
#include <fstream>
using namespace std;
// 使用参数默认 - 日志函数,默认输出到控制台
void logMessage(const string& message,
ostream& output = cout,
bool includeTimestamp = false) {
if (includeTimestamp) {
// 这里可以添加时间戳逻辑
output << "[TIMESTAMP] ";
}
output << message << endl;
}
int main() {
logMessage("Simple message"); // 使用所有默认参数
logMessage("Message with timestamp", cout, true); // 指定部分参数
ofstream logFile("app.log");
logMessage("File log", logFile); // 输出到文件
return 0;
}
|
2. 使用函数重载的情况
适用场景:函数有多个不同的实现,算法依赖于输入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
| #include <iostream>
#include <vector>
#include <string>
using namespace std;
// 函数重载 - 不同的输入需要不同的算法
class Calculator {
public:
// 处理单个数字
static int process(int number) {
return number * 2;
}
// 处理字符串(完全不同的算法)
static string process(const string& text) {
string result = text;
for (char& c : result) {
c = toupper(c);
}
return result;
}
// 处理数字向量(又是不同的算法)
static vector<int> process(const vector<int>& numbers) {
vector<int> result;
for (int num : numbers) {
result.push_back(num * 3);
}
return result;
}
};
int main() {
cout << Calculator::process(5) << endl; // 10
cout << Calculator::process("hello") << endl; // HELLO
vector<int> nums = {1, 2, 3};
vector<int> processed = Calculator::process(nums);
for (int num : processed) {
cout << num << " "; // 3 6 9
}
cout << endl;
return 0;
}
|
3. 使用函数模板的情况
适用场景:函数只有一种算法,但需要处理多种数据类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
| #include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
// 函数模板 - 相同的算法,不同的类型
template <typename T>
class ArrayUtils {
public:
// 查找最大值 - 算法对所有可比较类型都相同
static T findMax(const vector<T>& array) {
if (array.empty()) {
throw invalid_argument("Array is empty");
}
T maxVal = array[0];
for (const T& element : array) {
if (element > maxVal) {
maxVal = element;
}
}
return maxVal;
}
// 反转数组 - 算法对所有类型都相同
static void reverse(vector<T>& array) {
size_t left = 0;
size_t right = array.size() - 1;
while (left < right) {
swap(array[left], array[right]);
left++;
right--;
}
}
// 统计满足条件的元素 - 算法通用
template <typename Predicate>
static int countIf(const vector<T>& array, Predicate condition) {
int count = 0;
for (const T& element : array) {
if (condition(element)) {
count++;
}
}
return count;
}
};
int main() {
// 处理整数
vector<int> intArray = {3, 1, 4, 1, 5, 9, 2, 6};
cout << "Max int: " << ArrayUtils<int>::findMax(intArray) << endl;
ArrayUtils<int>::reverse(intArray);
cout << "Reversed: ";
for (int num : intArray) cout << num << " ";
cout << endl;
// 处理双精度数
vector<double> doubleArray = {3.14, 2.71, 1.41, 1.61};
cout << "Max double: " << ArrayUtils<double>::findMax(doubleArray) << endl;
// 处理字符串
vector<string> stringArray = {"apple", "banana", "cherry", "date"};
cout << "Max string: " << ArrayUtils<string>::findMax(stringArray) << endl;
// 使用条件统计
int evenCount = ArrayUtils<int>::countIf(
vector<int>{1, 2, 3, 4, 5, 6},
[](int x) { return x % 2 == 0; }
);
cout << "Even numbers: " << evenCount << endl;
return 0;
}
|
综合比较与决策
技术选择决策树
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
| #include <iostream>
#include <vector>
#include <string>
using namespace std;
// 决策指南示例
class DecisionExamples {
public:
// 情况1:使用参数默认 - 单一算法,有合理的默认值
static void saveToFile(const string& data,
const string& filename = "output.txt",
bool append = true) {
// 文件保存逻辑
cout << "Saving '" << data << "' to " << filename
<< " (append: " << append << ")" << endl;
}
// 情况2:使用函数重载 - 不同输入需要完全不同的算法
static void encode(int number) {
// 数字编码算法
cout << "Encoding number: " << number << " -> " << (number * 2) << endl;
}
static void encode(const string& text) {
// 文本编码算法(完全不同)
string encoded;
for (char c : text) {
encoded += to_string(static_cast<int>(c)) + " ";
}
cout << "Encoding text: '" << text << "' -> " << encoded << endl;
}
// 情况3:使用函数模板 - 相同算法,不同类型
template <typename T>
static void sortAndPrint(vector<T>& data) {
sort(data.begin(), data.end());
cout << "Sorted: ";
for (const T& item : data) {
cout << item << " ";
}
cout << endl;
}
};
// 实际应用示例
void demonstrateDecisionMaking() {
cout << "=== 参数默认示例 ===" << endl;
DecisionExamples::saveToFile("Hello"); // 使用默认文件名和模式
DecisionExamples::saveToFile("World", "log.txt"); // 指定文件名,使用默认模式
DecisionExamples::saveToFile("Data", "data.txt", false); // 指定所有参数
cout << "\n=== 函数重载示例 ===" << endl;
DecisionExamples::encode(42); // 调用数字版本
DecisionExamples::encode("Hello"); // 调用字符串版本
cout << "\n=== 函数模板示例 ===" << endl;
vector<int> numbers = {5, 2, 8, 1, 9};
DecisionExamples::sortAndPrint(numbers); // 处理整数
vector<string> words = {"banana", "apple", "cherry"};
DecisionExamples::sortAndPrint(words); // 处理字符串
}
int main() {
demonstrateDecisionMaking();
return 0;
}
|
混合使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
| #include <iostream>
#include <vector>
using namespace std;
// 混合使用:模板 + 默认参数 + 重载
template <typename T>
class AdvancedUtils {
public:
// 模板 + 默认参数
template <typename Container>
static void printContainer(const Container& container,
const string& label = "Container",
ostream& os = cout) {
os << label << " [" << container.size() << "]: ";
for (const auto& element : container) {
os << element << " ";
}
os << endl;
}
// 重载 + 模板
static T process(const T& value) {
return value * 2; // 假设 T 支持乘法
}
static string process(const string& value) {
return "Processed: " + value; // 字符串的特殊处理
}
};
int main() {
vector<int> numbers = {1, 2, 3};
AdvancedUtils<int>::printContainer(numbers);
AdvancedUtils<int>::printContainer(numbers, "Numbers", cout);
cout << AdvancedUtils<int>::process(5) << endl;
cout << AdvancedUtils<string>::process("test") << endl;
return 0;
}
|
头文件包含机制
为什么需要 #include 文件?
在 C++ 中,#include 指令用于将其他文件的内容插入到当前文件中,主要有以下原因:
-
声明共享:让多个源文件能够访问相同的函数和类声明
-
代码组织:分离接口和实现
-
模块化:将相关功能组织在一起
为什么需要多个 .cpp 文件?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // 不好的做法:所有代码在一个文件中
// main_monolithic.cpp
#include <iostream>
using namespace std;
class MyClass {
public:
void foo() { cout << "foo" << endl; }
int bar;
};
class AnotherClass {
public:
void test() { cout << "test" << endl; }
};
int main() {
MyClass obj;
obj.foo();
return 0;
}
|
多文件组织的优势:
1
2
3
4
5
6
7
8
9
10
11
12
| // 好的做法:分离文件
// myclass.h - 头文件(接口)
#ifndef MYCLASS_H
#define MYCLASS_H
class MyClass {
public:
void foo();
int bar;
};
#endif
|
1
2
3
4
5
6
7
8
| // myclass.cpp - 实现文件
#include "myclass.h"
#include <iostream>
using namespace std;
void MyClass::foo() {
cout << "foo" << endl;
}
|
1
2
3
4
5
6
7
8
| // main.cpp - 主程序
#include "myclass.h"
int main() {
MyClass obj;
obj.foo(); // 正常工作,MyClass 已定义
return 0;
}
|
为什么需要多文件组织:
- 编译加速:只重新编译修改的文件
- 代码组织:逻辑分离,易于维护
- 接口分离:头文件声明,源文件实现
重复包含问题与解决方案
问题示例:
1
2
3
| // main.cpp
#include "myclass.h" // 第一次定义
#include "myclass.h" // 重复定义错误!
|
解决方案
1
2
3
4
5
6
7
8
9
| // myclass.h
#ifndef MYCLASS_H
#define MYCLASS_H
class MyClass { /* ... */ };
#endif
// 或使用 #pragma once(现代编译器)
#pragma once
class MyClass { /* ... */ };
|
1
2
3
4
5
6
7
| // a.h - 只使用前置声明
class B; // 前置声明
class A {
B* b_ptr; // 指针 - 前置声明足够
B& b_ref; // 引用 - 前置声明足够
B* getB(); // 返回指针 - 前置声明足够
};
|
1
2
3
4
| // a.cpp - 实现文件中包含
#include "a.h"
#include "b.h" // 包含完整定义
// 实现代码...
|
1
2
3
4
5
| // a.h - 必须包含
#include "b.h" // 需要完整定义
class A : public B { // 继承
B b_object; // 包含对象
};
|
总结
- 头文件中:优先使用前置声明
- 源文件中:包含所需的完整头文件
- 总是使用:头文件保护(
#ifndef 或 #pragma once)
- 减少依赖:使用指针/引用替代对象包含
- 编译优化:分离声明与实现
核心原则
- 能用前置声明就不用包含
- 在能选择的情况下,用指针/引用替代对象
- 头文件保持最小依赖