C++ 入门三:函数与模板

一、内联函数:编译期嵌入的 "高效函数"

1. 什么是内联函数?

  • 核心特性:在编译阶段,内联函数的函数体会直接嵌入到调用它的代码中,避免了普通函数的调用开销(如压栈、跳转、返回)。

  • 语法 :在函数声明或定义前加 inline 关键字。

    inline void greet() {
    cout << "Hello, Inline Function!" << endl;
    }

2. 内联函数 vs 宏函数:深度对比

特性 内联函数 宏函数(#define)
处理阶段 编译阶段(编译器处理) 预处理阶段(预处理器替换)
类型安全 有(遵循 C++ 类型检查) 无(纯文本替换,可能引发类型错误)
作用域 受作用域规则限制 全局有效
副作用 低(按函数规则处理表达式) 高(参数可能被多次计算,如 MAX(a,b) 中的 ab
调试支持 可调试(有函数符号) 不可调试(替换后无原始函数信息)

示例对比

宏函数实现加法可能出错:

复制代码
#define ADD(a, b) a + b  // 若调用 ADD(5, 3)*2,实际为 5 + 3*2 = 11,而非预期的 16

内联函数安全可靠:

复制代码
inline int add(int a, int b) { return a + b; }  // 严格按表达式优先级计算

二、默认参数函数:灵活调用的 "懒人写法"

1. 语法与规则

  • 定义:在函数参数列表中为形参指定默认值,调用时可省略对应实参。

  • 格式返回值类型 函数名(参数类型 形参 = 默认值, ...)

    // 声明时指定默认参数(通常在头文件中声明)
    int sum(int a, int b = 10, int c = 20);

    // 定义时不再重复默认值(避免重复定义)
    int sum(int a, int b, int c) {
    return a + b + c;
    }

2. 调用示例

复制代码
cout << sum(5) << endl;       // 使用默认值 b=10, c=20,结果 35
cout << sum(5, 3) << endl;    // 使用默认值 c=20,结果 28
cout << sum(5, 3, 2) << endl; // 全用实参,结果 10

3. 注意事项

  • 默认参数位置 :默认参数需从右往左依次设置,即右侧参数有默认值时,左侧参数必须全有默认值。

    复制代码
    // 错误:int func(int a=10, int b); 左侧有默认值,右侧无
    // 正确:int func(int a, int b=10);
  • 作用域匹配:默认参数的值在声明时确定,与定义位置无关。

三、函数重载:同名不同参的 "多面手"

1. 重载的本质与规则

  • 核心概念:同一作用域内,允许存在多个同名函数,只要参数列表(个数、类型或顺序)不同。
  • 规则
    • 重载函数的参数列表必须不同,与返回值无关。
    • 功能相近的函数建议重载,避免用同一函数名实现无关操作。

2. 示例:多类型输出函数

复制代码
#include <iostream>
using namespace std;

// 重载1:输出整数
void print(int num) {
    cout << "Integer: " << num << endl;
}

// 重载2:输出浮点数
void print(double num) {
    cout << "Double: " << num << endl;
}

// 重载3:输出字符串
void print(const char* str) {
    cout << "String: " << str << endl;
}

int main() {
    print(100);      // 调用print(int)
    print(3.14);     // 调用print(double)
    print("Hello");  // 调用print(const char*)
    return 0;
}

3. 避坑指南

  • 返回值不能作为重载依据

    cpp

    复制代码
    int func(int a);
    double func(int a); // 编译错误!参数相同,返回值不同不构成重载
  • 慎用默认参数重载:避免因参数默认值导致调用歧义。

四、函数模板:一次定义,通用所有类型

在 C++ 中,函数模板是实现泛型编程的重要工具。它允许我们定义一个与数据类型无关的函数,通过类型参数让同一个函数逻辑适用于多种数据类型。本节将从基础语法、使用场景、高级特性到常见问题,逐步解析函数模板的核心知识。

1. 函数模板的核心思想:类型参数化

1.1 为什么需要函数模板?

假设我们需要实现一个交换两个变量值的函数,传统方法需要为每种数据类型编写独立的函数(如swap_intswap_double),导致代码冗余。函数模板通过类型参数化,让一套代码适配所有类型:

复制代码
// 传统方法:针对int类型的交换函数
void swap_int(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

// 函数模板:一次定义,适配所有类型
template<typename T>
void swap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}
1.2 基础语法:template关键字与类型参数
  • 模板声明 :使用 template<typename T>template<class T> 声明类型参数 T(可自定义名称,如TypeData)。

  • 函数定义 :在函数参数和返回值中使用类型参数 T,替代具体数据类型。

    // 声明模板并定义函数
    template<typename T> // T为类型参数
    T add(T a, T b) {
    return a + b; // 支持所有支持+运算符的类型
    }

2. 函数模板的使用:自动类型推导与显式指定

2.1 自动类型推导:编译器的 "智能匹配"

调用函数模板时,编译器会根据传入的实参自动推导类型参数 T,无需手动指定:

复制代码
int x = 10, y = 20;
swap(x, y); // 推导为 swap<int>(x, y)

double a = 3.14, b = 2.71;
swap(a, b); // 推导为 swap<double>(a, b)

char c1 = 'A', c2 = 'B';
swap(c1, c2); // 推导为 swap<char>(c1, c2)
2.2 显式指定类型:明确告诉编译器你的需求

当自动推导失败(如参数类型不一致)时,可显式指定类型参数:

复制代码
// 显式指定类型:将int和double转换为double类型交换
int m = 5;
double n = 3.2;
swap<double>(m, n); // 等价于 swap(m, n),但显式指定T为double

3. 模板特化:为特殊类型定制逻辑

3.1 为什么需要模板特化?

通用模板在处理某些特殊类型(如char*字符串)时可能不符合预期(默认会交换指针地址而非字符串内容)。此时需通过模板特化提供定制实现:

复制代码
// 通用模板:交换任意类型(对char*无效,会交换指针)
template<typename T>
void swap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp; // 交换char*指针,而非字符串内容
}

// 特化版本:针对char*类型,交换字符串内容(假设T为char*)
template<>
void swap<char*>(char*& a, char*& b) {
    char* temp = new char[strlen(a) + 1]; // 分配临时空间
    strcpy(temp, a);
    strcpy(a, b);
    strcpy(b, temp);
    delete[] temp; // 释放内存
}
3.2 特化语法:template<>声明特化版本
  • 完全特化 :针对具体类型(如char*double)。

  • 部分特化(较少用):针对类型的部分特性(如指针、引用)。

    // 完全特化:处理bool类型(示例:无实际意义,仅演示语法)
    template<>
    void swap<bool>(bool& a, bool& b) {
    // 定制逻辑
    }

4. 函数模板 vs 函数重载:相似但不同

4.1 核心区别
特性 函数模板 函数重载
目的 泛型编程,一套代码适配多种类型 同名函数处理不同参数列表的场景
定义方式 使用template声明类型参数 相同函数名,不同参数列表(个数 / 类型 / 顺序)
类型检查 编译期推导类型,支持所有符合操作的类型 每个重载版本为独立函数,类型固定
4.2 协同使用:模板与重载共存
复制代码
// 函数模板:通用加法
template<typename T>
T add(T a, T b) {
    return a + b;
}

// 重载版本:处理int和double混合加法
double add(int a, double b) {
    return a + b;
}

int main() {
    add(10, 20);       // 调用模板,T推导为int
    add(3.14, 2.71);   // 调用模板,T推导为double
    add(5, 3.2);       // 调用重载版本(int+double)
    return 0;
}

5. 常见易错点与最佳实践

5.1 模板定义必须放在头文件中
  • 原因 :模板实例化发生在编译期,若定义在.cpp文件中,其他文件调用时无法找到具体实现,导致链接错误。
  • 正确做法 :将模板声明和定义统一放在头文件(如.h.hpp)中。
5.2 类型推导失败:操作符不支持

当类型参数T不支持函数中的操作(如T没有+运算符),编译时会报错:

复制代码
template<typename T>
T add(T a, T b) {
    return a + b; // 若T为自定义类且未重载+,编译错误
}

class MyClass { /* 未重载+运算符 */ };
MyClass obj1, obj2;
add(obj1, obj2); // 编译错误:没有匹配的operator+
5.3 避免过度使用模板:简单场景用重载更清晰

若仅需处理少数几种类型(如intdouble),直接使用函数重载比模板更易维护:

复制代码
// 推荐:少量类型用重载
int add(int a, int b);
double add(double a, double b);

// 不推荐:模板可能增加理解成本
template<typename T>
T add(T a, T b);

6. 实战案例:通用最大值函数

6.1 通用模板实现
复制代码
#include <iostream>
using namespace std;

template<typename T>
T max(T a, T b) {
    return a > b ? a : b;
}

int main() {
    // 整数
    cout << max(10, 20) << endl;       // 20
    // 浮点数
    cout << max(3.14, 2.71) << endl;   // 3.14
    // 字符
    cout << max('A', 'B') << endl;      // 'B'(ASCII值比较)
    return 0;
}
6.2 特化处理字符串(按长度比较)
复制代码
// 通用模板:按字典序比较(默认行为)
template<>
const char* max<const char*>(const char* a, const char* b) {
    return strlen(a) > strlen(b) ? a : b; // 按字符串长度比较
}

int main() {
    cout << max("hello", "world") << endl; // 输出"hello"(长度5>5?不,实际按字典序,需修正逻辑,此处仅演示特化语法)
    return 0;
}

7. 总结:函数模板的核心优势

  • 代码复用:一套逻辑适配所有类型,避免重复编写相似函数。
  • 类型安全:编译器确保类型一致性,减少运行时错误。
  • 泛型编程基础 :为后续学习类模板、STL 容器(如vectormap)奠定基础。

通过函数模板,C++ 实现了 "一次定义,通用所有类型" 的强大能力,让代码更简洁、灵活。掌握模板的使用与特化技巧,能显著提升开发效率,尤其在处理数值、容器等通用逻辑时优势明显。下一节我们将探索类模板,将泛型思想应用于自定义类型,进一步解锁 C++ 的强大潜力。

五、类模板:数据类型参数化的 "容器"

在 C++ 中,类模板是实现泛型编程的核心工具之一。它允许将类中的数据类型作为参数传递,从而创建出适用于多种数据类型的通用类。这种 "数据类型参数化" 的能力,让我们可以像 "模具" 一样定义类,根据不同的类型参数生成具体的类,显著提升代码的复用性和灵活性。

1. 类模板的核心思想:类型参数化

1.1 为什么需要类模板?

假设我们需要一个 "坐标类",既能表示整数坐标 (int, int),也能表示浮点坐标 (double, double),甚至字符串坐标 (char*, char*)。传统方法需要为每种类型编写独立的类,导致代码冗余。而类模板通过 类型参数化,只需定义一次类,即可适配所有类型:

复制代码
// 传统方法:针对int的坐标类
class IntPoint {
private:
    int x, y;
public:
    IntPoint(int a, int b) : x(a), y(b) {}
};

// 类模板:一次定义,适配所有类型
template<typename T> // T为类型参数
class Point {
private:
    T x, y; // 成员变量使用类型参数T
public:
    Point(T a, T b) : x(a), y(b) {} // 构造函数使用T
    T getX() { return x; }
    T getY() { return y; }
};
1.2 基础语法:template声明与类型参数
  • 模板声明 :使用 template<typename T>template<class T> 声明类型参数(T 为自定义名称,可理解为 "任意类型")。

  • 类定义 :在类的成员(变量、函数、构造函数)中直接使用类型参数 T

    template<typename T> // 声明类模板,T为类型参数
    class Container {
    private:
    T data; // 数据成员使用T
    public:
    Container(T val) : data(val) {} // 构造函数参数为T类型
    T getData() { return data; } // 成员函数返回T类型
    };

2. 类模板的定义与实例化:从 "模板" 到具体类

2.1 定义类模板:类内与类外实现
  • 类内定义:成员函数直接在类体中实现(适合简单逻辑)。

    复制代码
    template<typename T>
    class Pair {
    private:
        T first, second;
    public:
        Pair(T a, T b) : first(a), second(b) {} // 构造函数类内定义
        void print() { // 成员函数类内定义
            cout << first << ", " << second << endl;
        }
    };
  • 类外定义 :需添加模板头 template<typename T>,并通过 类名<T>:: 指明作用域(适合复杂逻辑)。

    复制代码
    template<typename T> // 模板头
    class Stack {
    private:
        T* data;
        int size;
    public:
        Stack(int s); // 构造函数声明
        void push(T val); // 成员函数声明
    };
    
    // 类外定义构造函数
    template<typename T> // 再次声明模板头
    Stack<T>::Stack(int s) : size(s) {
        data = new T[s]; // 动态分配T类型数组
    }
    
    // 类外定义成员函数
    template<typename T>
    void Stack<T>::push(T val) {
        // 实现逻辑
    }
2.2 实例化对象:必须显式指定类型

类模板无法自动推导类型,创建对象时必须 显式指定类型参数 ,格式为 类名<类型>

复制代码
// 实例化:指定T为int
Point<int> p1(10, 20); // 整数坐标,x=10, y=20

// 实例化:指定T为double
Point<double> p2(3.14, 2.71); // 浮点坐标,x=3.14, y=2.71

// 错误示例:未指定类型(编译错误)
// Point p3(1, 2); // 错误,需明确类型,如Point<int> p3(1, 2);

3. 多类型参数:处理不同类型的成员

类模板支持 多个类型参数,用逗号分隔,适用于成员类型不同的场景(如键值对、坐标分量类型不同):

复制代码
template<typename T1, typename T2> // 两个类型参数T1和T2
class KeyValue {
private:
    T1 key; // 键为T1类型
    T2 value; // 值为T2类型
public:
    KeyValue(T1 k, T2 v) : key(k), value(v) {}
    void print() {
        cout << "Key: " << key << ", Value: " << value << endl;
    }
};

// 实例化:键为int,值为double
KeyValue<int, double> kv(100, 3.14); 
kv.print(); // 输出:Key: 100, Value: 3.14

4. 类模板的成员函数特性

4.1 成员函数的实例化时机

类模板的成员函数 仅在被调用时才会实例化,未调用的函数不会生成具体代码,节省编译时间。例如:

复制代码
template<typename T>
class MyClass {
public:
    void func1() { /* 复杂逻辑,未被调用则不实例化 */ }
    void func2() { /* 简单逻辑,被调用时才生成代码 */ }
};

MyClass<int> obj;
obj.func2(); // 实例化func2<int>
// obj.func1(); // 未调用,不实例化func1<int>

4.2 类内定义 vs 类外定义的区别

  • 类内定义:成员函数默认为内联函数(可隐式优化,适合简短代码)。
  • 类外定义 :必须严格遵循模板语法,否则编译报错(如遗漏 template<typename T> 或作用域 ::)。

5. 模板特化:为特殊类型定制行为

5.1 为什么需要特化?

通用模板对某些特殊类型(如 char* 字符串)的处理可能不符合预期(例如默认存储指针而非字符串内容)。通过 模板特化,可为特定类型提供定制实现:

复制代码
// 通用模板:存储任意类型并打印(对char*会输出指针地址,错误)
template<typename T>
class Printer {
public:
    void print(T data) {
        cout << data << endl; // 对char*输出地址,如0x7ffd...
    }
};

// 特化版本:针对char*类型,正确输出字符串内容
template<> // 空模板参数列表表示完全特化
class Printer<char*> { // 指定特化类型为char*
public:
    void print(char* data) {
        cout << "String: " << data << endl; // 输出字符串内容
    }
};

// 使用特化版本
Printer<char*> p;
p.print("Hello C++"); // 输出:String: Hello C++
5.2 完全特化 vs 部分特化
  • 完全特化 :指定所有类型参数(如 template<> class Printer<char*>),适配具体类型。

  • 部分特化 (较少用):针对类型的部分特性(如指针、引用),例如:

    复制代码
    template<typename T>
    class Printer<T*> { // 部分特化:针对T类型的指针
        void print(T* data) { /* 处理指针的逻辑 */ }
    };

    6. 实战案例:通用动态数组类模板

    6.1 定义类模板:实现动态数组功能

    cpp 复制代码
    template<typename T>
    class DynamicArray {
    private:
        T* arr; // 存储T类型数据的指针
        int length; // 数组长度
    public:
        // 构造函数:初始化数组长度并分配内存
        DynamicArray(int len) : length(len) {
            arr = new T[len]; // 动态分配T类型的数组空间
        }
    
        // 析构函数:释放内存
        ~DynamicArray() {
            delete[] arr; // 释放T类型数组
        }
    
        // 重载下标运算符:访问元素
        T& operator[](int index) {
            return arr[index]; // 返回T类型引用
        }
    
        // 获取数组长度
        int getLength() {
            return length;
        }
    };
6.2 实例化与使用
复制代码
int main() {
    // 实例化为int数组,长度5
    DynamicArray<int> intArr(5);
    for (int i = 0; i < intArr.getLength(); i++) {
        intArr[i] = i + 1; // 赋值:1, 2, 3, 4, 5
    }
    cout << "Int Array: " << intArr[2] << endl; // 输出:3

    // 实例化为double数组,长度3
    DynamicArray<double> doubleArr(3);
    doubleArr[0] = 3.14;
    doubleArr[1] = 2.71;
    cout << "Double Array: " << doubleArr[0] + doubleArr[1] << endl; // 输出:5.85

    return 0;
}

7. 常见易错点与避坑指南

7.1 实例化时未显式指定类型
  • 错误示例

    复制代码
    template<typename T> class Stack;
    Stack stack; // 错误!未指定类型参数,需改为Stack<int> stack;
  • 正确做法 :创建对象时必须显式指定类型,如 Stack<double> stack(10);

7.2 类外定义成员函数时遗漏模板头
  • 错误示例

    复制代码
    template<typename T>
    class MyClass {
        void func();
    };
    
    // 错误:缺少模板头
    void MyClass<T>::func() { /* ... */ }
    
    // 正确写法:
    template<typename T>
    void MyClass<T>::func() { /* ... */ }
7.3 头文件包含问题
  • 问题 :类模板的定义必须与声明放在同一个头文件(.h.hpp)中,否则其他文件调用时会因无法实例化而报错。
  • 原因:模板实例化发生在编译期,需保证定义可见。

8. 总结:类模板的核心价值

  • 数据类型参数化 :通过 template 让类的成员(变量、函数)支持任意类型,如容器、数据结构的基础。
  • 代码高度复用 :一套类定义适配多种数据类型,避免重复编写相似代码(如 vector<int>vector<double> 共用同一模板)。
  • 类型安全:编译器确保类型一致性,减少运行时错误(如避免手动类型转换)。

类模板是 C++ 泛型编程的基石,STL 中的 vectorlistmap 等容器均基于类模板实现。掌握类模板的定义、实例化和特化技巧,能够让开发者编写出更通用、灵活的代码,尤其在处理需要适配多种数据类型的场景时优势显著。后续结合继承与多态,类模板还能实现更复杂的泛型设计,进一步释放 C++ 的强大能力。

六、实战:用模板实现通用最大值函数

需求:输入 5 个同类型数据,返回最大值

函数模板实现
复制代码
#include <iostream>
using namespace std;

template <typename T>
T max_of_five(T a, T b, T c, T d, T e) {
    T max_val = a;
    if (b > max_val) max_val = b;
    if (c > max_val) max_val = c;
    if (d > max_val) max_val = d;
    if (e > max_val) max_val = e;
    return max_val;
}

int main() {
    // 验证 int 类型
    int i1 = 1, i2 = 3, i3 = 5, i4 = 7, i5 = 9;
    cout << "Int Max: " << max_of_five(i1, i2, i3, i4, i5) << endl;  // 输出 9

    // 验证 float 类型
    float f1 = 2.5, f2 = 4.8, f3 = 1.2, f4 = 3.9, f5 = 5.1;
    cout << "Float Max: " << max_of_five(f1, f2, f3, f4, f5) << endl;  // 输出 5.1

    // 验证 char 类型(ASCII 码比较)
    char c1 = 'a', c2 = 'z', c3 = 'A', c4 = 'Z', c5 = '0';
    cout << "Char Max: " << max_of_five(c1, c2, c3, c4, c5) << endl;  // 输出 z(ASCII 最大)
    return 0;
}

七、模板的优势与适用场景

技术 核心优势 典型场景
内联函数 减少函数调用开销,提高执行效率 高频调用的简短函数(如存取器)
默认参数函数 简化函数调用,减少重载数量 有常用默认值的参数(如阈值、配置项)
函数重载 统一相似功能的函数名 处理不同类型的相似操作(如加减运算)
函数模板 类型无关的函数逻辑,代码高度复用 通用算法(排序、查找、交换)
类模板 数据类型参数化,构建通用数据结构 容器类(数组、链表、栈、队列)

八、实际应用案例:从理论到实战

案例 1:游戏开发中的内联函数 ------ 高频调用的性能优化

场景 :在游戏循环中,需要频繁计算物体的坐标变换(如平移、旋转),这类函数短小且调用次数极高。
方案:使用内联函数减少函数调用开销,提升帧率。

复制代码
// 3D坐标类
class Vector3 {
public:
    float x, y, z;
    // 内联函数:向量加法(每天调用数百万次)
    inline Vector3 operator+(const Vector3& other) const {
        return {x + other.x, y + other.y, z + other.z};
    }
    // 内联函数:快速获取模长(避免函数调用延迟)
    inline float length() const {
        return sqrt(x*x + y*y + z*z);
    }
};

// 游戏主循环(每秒调用60次以上)
void gameLoop() {
    Vector3 position(10.5f, 20.3f, 5.0f);
    Vector3 velocity(2.0f, 0.5f, 1.5f);
    // 高频调用内联函数
    position = position + velocity; 
    float speed = position.length();
    // ... 其他渲染逻辑
}

优势:将数学运算直接嵌入调用处,消除压栈 / 弹栈开销,对性能敏感的实时系统(如游戏、嵌入式控制)至关重要。

案例 2:日志系统中的默认参数 ------ 灵活配置日志级别

场景 :开发框架或大型项目时,需要记录不同级别的日志(调试、信息、警告、错误),默认输出信息级日志。
方案:用默认参数简化调用,同时支持自定义级别。

复制代码
// 日志级别枚举
enum LogLevel { DEBUG, INFO, WARNING, ERROR };

// 默认参数:level=INFO,prefix="INFO"
void log(const string& message, LogLevel level = INFO, 
         const string& prefix = "INFO") {
    time_t now = time(nullptr);
    cout << "[" << ctime(&now) << "] " << prefix << ": " << message << endl;
}

// 调用示例(90%场景用默认参数)
int main() {
    log("系统启动完成");  // 等价于 log(message, INFO, "INFO")
    log("连接数据库失败", ERROR, "ERROR");  // 显式指定错误级别
    log("调试数据:", DEBUG, "DEBUG");  // 调试模式专用
    return 0;
}

优势:避免为每个日志级别写重载函数,调用更简洁,适配大多数 "信息日志" 场景,同时保留灵活性。

案例 3:数学库中的函数重载 ------ 支持多种数据类型的向量运算

场景 :实现一个向量运算库,需支持 intfloatdouble 等类型,以及 2D/3D 向量。
方案:用函数重载统一接口,根据参数类型和个数自动匹配。

复制代码
// 2D向量加法(整数)
Vector2i operator+(const Vector2i& a, const Vector2i& b) {
    return {a.x + b.x, a.y + b.y};
}

// 2D向量加法(浮点数)
Vector2f operator+(const Vector2f& a, const Vector2f& b) {
    return {a.x + b.x, a.y + b.y};
}

// 3D向量加法(支持混合类型:int + float)
Vector3f operator+(const Vector3i& a, const Vector3f& b) {
    return {a.x + b.x, a.y + b.y, a.z + b.z};
}

// 使用时统一调用+号,编译器自动匹配
Vector2i a(2, 3), b(5, 4);
Vector2i c = a + b;  // 调用整数版加法

Vector3i d(1, 2, 3);
Vector3f e(0.5f, 1.5f, 2.5f);
Vector3f f = d + e;  // 调用混合类型加法

优势 :用户无需记忆不同函数名(如 addVec2iaddVec2f),通过重载实现 "见名知意",提升库的易用性。

案例 4:STL 中的函数模板 ------ 通用算法的基石

场景 :C++ 标准库(STL)中的 std::swapstd::maxstd::sort 等函数,需支持所有内置类型和自定义类型。
方案:用函数模板实现类型无关逻辑,用户无需为每种类型写专属函数。

复制代码
// 自定义学生类(支持比较运算符)
class Student {
public:
    string name;
    int score;
    // 重载<运算符,用于排序和比较
    bool operator<(const Student& other) const {
        return score < other.score;
    }
};

int main() {
    // 交换两个int变量(STL的swap模板)
    int x = 10, y = 20;
    swap(x, y);  // 等价于模板实例化 swap<int>(x, y)

    // 求两个字符串的最大值(按字典序)
    string s1 = "apple", s2 = "banana";
    cout << max(s1, s2) << endl;  // 输出 "banana"

    // 对学生数组排序(自定义类型,依赖operator<)
    Student students[] = {{"Alice", 85}, {"Bob", 70}, {"Charlie", 90}};
    sort(students, students + 3);  // 按分数升序排列
    return 0;
}

优势:模板让 STL 算法 "一次编写,全类型适用",用户只需为自定义类型实现必要接口(如比较运算符),即可复用强大的标准库功能。

案例 5:类模板实现动态数组 ------ 替代 C 语言的泛型数据结构

场景 :实现一个类似 std::vector 的动态数组,支持任意数据类型,自动管理内存。
方案:用类模板封装数据类型,实现扩容、元素访问、拷贝构造等功能。

复制代码
template <typename T>
class DynamicArray {
private:
    T* data;
    int size;
    int capacity;

public:
    // 构造函数:默认容量10
    DynamicArray(int cap = 10) : size(0), capacity(cap) {
        data = new T[capacity];
    }

    // 尾插元素(自动扩容)
    void push_back(const T& value) {
        if (size == capacity) {
            capacity *= 2;
            T* newData = new T[capacity];
            copy(data, data + size, newData);  // 复用STL算法
            delete[] data;
            data = newData;
        }
        data[size++] = value;
    }

    // 下标访问
    T& operator[](int index) { return data[index]; }

    // 析构函数
    ~DynamicArray() { delete[] data; }
};

// 使用:存储int、string、自定义类
int main() {
    DynamicArray<int> arr;
    arr.push_back(10);
    arr.push_back(20);
    cout << arr[0] << endl;  // 输出10

    DynamicArray<string> strArr;
    strArr.push_back("Hello");
    strArr.push_back("World");
    cout << strArr[1] << endl;  // 输出World
    return 0;
}

优势 :类模板让数据结构与类型解耦,避免为 intstring 等类型重复实现数组逻辑,代码量减少 90% 以上,且类型安全(避免 C 语言的void*强制转换)。

案例 6:模板特化 ------ 处理特殊类型的定制逻辑

场景 :当模板对某个特定类型(如 boolchar*)需要特殊处理时,使用模板特化。
方案 :为 bool 类型的交换函数增加日志记录,区分普通类型。

复制代码
// 通用模板:交换任意类型
template <typename T>
void swap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

// 特化版本:针对bool类型,增加调试日志
template <>
void swap<bool>(bool& a, bool& b) {
    cout << "Swapping bool values: " << a << " <-> " << b << endl;
    bool temp = a;
    a = b;
    b = temp;
}

int main() {
    bool flag1 = true, flag2 = false;
    swap(flag1, flag2);  // 调用特化版本,输出日志
    return 0;
}

优势:在保持模板通用性的同时,为特殊类型提供定制逻辑,兼顾灵活性与特殊性。

九、总结:案例中的核心思想

  1. 性能优先:内联函数用于高频小函数,模板减少代码冗余从而间接提升编译优化空间。
  2. 接口统一:默认参数和重载让函数调用更自然,模板让算法与数据结构 "一次编写,全类型适用"。
  3. 类型安全 :模板在编译期检查类型匹配,比 C 语言的void*更安全,减少运行时错误。
  4. 代码复用:STL 和自定义模板类(如动态数组)避免重复造轮子,聚焦业务逻辑。

通过这些案例可以看出,C++ 的函数与模板特性并非 "炫技",而是解决实际开发问题的高效工具。新手在学习时,可尝试从 "如果我要实现一个 XX 功能,应该用什么技术" 的角度出发,反向巩固语法知识,加速从 "学语法" 到 "用语法" 的过渡。

相关推荐
梦の13 分钟前
C++Cherno 学习笔记day21 [86]-[90] 持续集成、静态分析、参数计算顺序、移动语义、stdmove与移动赋值操作符
c++·笔记·学习
夜月yeyue23 分钟前
STM32启动流程详解
linux·c++·stm32·单片机·嵌入式硬件·c#
ん贤24 分钟前
图论基础理论
c语言·数据结构·c++·算法·图论
修复bug1 小时前
利用pnpm patch命令实现依赖包热更新:精准打补丁指南
开发语言·javascript·vue.js
Vdeilae1 小时前
QT QCHeckBox 互斥设置方法
c++
꧁坚持很酷꧂1 小时前
Qt实现文件传输客户端(图文详解+代码详细注释)
开发语言·qt
白露与泡影1 小时前
阿里一面:Nacos配置中心交互模型是 push 还是 pull ?(原理+源码分析)
开发语言·php·交互
techdashen1 小时前
性能比拼: Node.js vs Go
开发语言·golang·node.js
十碗阳春面1 小时前
MATLAB 中文注释乱码的问题
开发语言·matlab
aiden:)2 小时前
设计模式之工厂模式(factory pattern):在商品对象创建系统中的应用
java·开发语言·设计模式·软件工程·软件构建