一、内联函数:编译期嵌入的 "高效函数"
1. 什么是内联函数?
-
核心特性:在编译阶段,内联函数的函数体会直接嵌入到调用它的代码中,避免了普通函数的调用开销(如压栈、跳转、返回)。
-
语法 :在函数声明或定义前加
inline
关键字。inline void greet() {
cout << "Hello, Inline Function!" << endl;
}
2. 内联函数 vs 宏函数:深度对比
特性 | 内联函数 | 宏函数(#define) |
---|---|---|
处理阶段 | 编译阶段(编译器处理) | 预处理阶段(预处理器替换) |
类型安全 | 有(遵循 C++ 类型检查) | 无(纯文本替换,可能引发类型错误) |
作用域 | 受作用域规则限制 | 全局有效 |
副作用 | 低(按函数规则处理表达式) | 高(参数可能被多次计算,如 MAX(a,b) 中的 a 、b ) |
调试支持 | 可调试(有函数符号) | 不可调试(替换后无原始函数信息) |
示例对比 :
宏函数实现加法可能出错:
#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_int
、swap_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
(可自定义名称,如Type
、Data
)。 -
函数定义 :在函数参数和返回值中使用类型参数
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 避免过度使用模板:简单场景用重载更清晰
若仅需处理少数几种类型(如int
和double
),直接使用函数重载比模板更易维护:
// 推荐:少量类型用重载
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 容器(如
vector
、map
)奠定基础。
通过函数模板,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 定义类模板:实现动态数组功能
cpptemplate<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 中的 vector
、list
、map
等容器均基于类模板实现。掌握类模板的定义、实例化和特化技巧,能够让开发者编写出更通用、灵活的代码,尤其在处理需要适配多种数据类型的场景时优势显著。后续结合继承与多态,类模板还能实现更复杂的泛型设计,进一步释放 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:数学库中的函数重载 ------ 支持多种数据类型的向量运算
场景 :实现一个向量运算库,需支持 int
、float
、double
等类型,以及 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; // 调用混合类型加法
优势 :用户无需记忆不同函数名(如 addVec2i
、addVec2f
),通过重载实现 "见名知意",提升库的易用性。
案例 4:STL 中的函数模板 ------ 通用算法的基石
场景 :C++ 标准库(STL)中的 std::swap
、std::max
、std::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;
}
优势 :类模板让数据结构与类型解耦,避免为 int
、string
等类型重复实现数组逻辑,代码量减少 90% 以上,且类型安全(避免 C 语言的void*
强制转换)。
案例 6:模板特化 ------ 处理特殊类型的定制逻辑
场景 :当模板对某个特定类型(如 bool
、char*
)需要特殊处理时,使用模板特化。
方案 :为 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;
}
优势:在保持模板通用性的同时,为特殊类型提供定制逻辑,兼顾灵活性与特殊性。
九、总结:案例中的核心思想
- 性能优先:内联函数用于高频小函数,模板减少代码冗余从而间接提升编译优化空间。
- 接口统一:默认参数和重载让函数调用更自然,模板让算法与数据结构 "一次编写,全类型适用"。
- 类型安全 :模板在编译期检查类型匹配,比 C 语言的
void*
更安全,减少运行时错误。 - 代码复用:STL 和自定义模板类(如动态数组)避免重复造轮子,聚焦业务逻辑。
通过这些案例可以看出,C++ 的函数与模板特性并非 "炫技",而是解决实际开发问题的高效工具。新手在学习时,可尝试从 "如果我要实现一个 XX 功能,应该用什么技术" 的角度出发,反向巩固语法知识,加速从 "学语法" 到 "用语法" 的过渡。