文章

程序设计范式-Week3

程序设计范式-第3周小结

程序设计范式-Week3

函数重载 (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. 返回类型不足以区分重载函数
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 指令用于将其他文件的内容插入到当前文件中,主要有以下原因:

  1. 声明共享:让多个源文件能够访问相同的函数和类声明

  2. 代码组织:分离接口和实现

  3. 模块化:将相关功能组织在一起

为什么需要多个 .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;       // 包含对象
};

总结

  1. 头文件中:优先使用前置声明
  2. 源文件中:包含所需的完整头文件
  3. 总是使用:头文件保护(#ifndef#pragma once
  4. 减少依赖:使用指针/引用替代对象包含
  5. 编译优化:分离声明与实现

核心原则

  • 能用前置声明就不用包含
  • 在能选择的情况下,用指针/引用替代对象
  • 头文件保持最小依赖
本文由作者按照 CC BY 4.0 进行授权