C++ 入门三:函数与模板
一、内联函数:编译期嵌入的 “高效函数”
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推导为intadd(3.14, 2.71); // 调用模板,T推导为doubleadd(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) {} // 构造函数使用TT 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数组,长度5DynamicArray<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数组,长度3DynamicArray<double> doubleArr(3);doubleArr[0] = 3.14;doubleArr[1] = 2.71;cout << "Double Array: " << doubleArr[0] + doubleArr[1] << endl; // 输出:5.85return 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:// 构造函数:默认容量10DynamicArray(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; // 输出10DynamicArray<string> strArr;strArr.push_back("Hello");strArr.push_back("World");cout << strArr[1] << endl; // 输出Worldreturn 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 功能,应该用什么技术” 的角度出发,反向巩固语法知识,加速从 “学语法” 到 “用语法” 的过渡。
相关文章:
C++ 入门三:函数与模板
一、内联函数:编译期嵌入的 “高效函数” 1. 什么是内联函数? 核心特性:在编译阶段,内联函数的函数体会直接嵌入到调用它的代码中,避免了普通函数的调用开销(如压栈、跳转、返回)。语法&#…...
解析券商qmt的优缺点
现在已经对于大QMT进行了一步步的深入了解与学习,也已经开始积木式搭建策略,进行交易了,但是,随时不断的深入,发现的问题也越来越多。下面开始逐一解析: 首页 | 迅投知识库 这是详细的说明。 目前券商给大…...
CSE lesson2 chrony服务器
CSE lesson2 chrony服务器 timedatectl命令 NTP(network time protocal)网络时间协议,时钟服务器同步时间的时候会使用到该协议进行时间同步。 #关闭/开启时间同步服务 [rootlocalhost ~]# timedatectl set-ntp 0/1#设置时间(必须关闭时间同步服务才能…...
时光交响曲:杭州的科技与传统交响
故事背景 故事发生在中国浙江杭州,以现代科技与文化传统的交融为背景,展现了人与自然、历史的深刻联系。在晨曦中的茶园、宁静的运河书屋、科技堤坝等地方,每个场景都充满了生机与活力,展示了科技如何赋予传统文化新的生命&#x…...
【大模型智能体】Agent2Agent协议加上MCP协议也许会成为未来Agent智能体系统的标配
之前在文章《基于Claude MCP协议的智能体落地示例》、《MCP(Model Context Protocol) 大模型智能体第一个开源标准协议》我们已经对MCP协议做了介绍,MCP提供了将大模型连接到不同数据源和工具的标准方式,包括内容仓库、商业工具和开发环境。 以上解决的是…...
opencv(C++)处理图像颜色
文章目录 介绍使用策略设计模式比较颜色实现方案计算两个颜色向量之间的距离1. 简单方法:曼哈顿距离计算(Manhattan Distance)2.使用 OpenCV 的 cv::norm 函数3.使用 OpenCV 的 cv::absdiff 函数错误示例 使用 OpenCV 函数实现颜色检测实现方…...
2025年焊接与热切割作业证考试真题分享
焊接与热切割作业属于特种作业操作证考试,理论知识点专业性强、安全规范要求高,如何高效备考成为关键!【100分题库】焊接与热切割作业理论备考题库紧扣最新考试大纲,帮你系统掌握考点,一次通过考试! 1、下…...
AI 代码生成工具如何突破 Java 单元测试效能天花板?
一、传统单元测试的四大痛点 时间黑洞:根据 JetBrains 调研,Java 开发者平均花费 35% 时间编写测试代码覆盖盲区:手工测试覆盖率普遍低于 60%(Jacoco 全球统计数据)维护困境:业务代码变更导致 38% 的测试用…...
【C++游戏引擎开发】第13篇:光照模型与Phong基础实现
一、Phong模型数学原理 1.1 光照叠加公式 L = k a I a + k d I d max ( 0 , n ⋅ l ) + k s I s max ( 0 , r ⋅ v ) α L = k_a I_a + k_d I_d \max(0, \mathbf{n} \cdot \mathbf{l}) + k_s I_s \max(0, \mathbf{r} \cdot \mathbf{v})^\alpha L=kaIa+kdIdmax(0…...
如何在Android系统上单编ko?
文章目录 一、先了解编译驱动需要什么?二、配置makefile1、在Android系统编译LOG上找到编译器信息(一般都会打印出来)2、基于源MK构造 可独立运行的makefile3)进入docker,在此makefile目录下敲make4)最后根…...
虚拟dom工作原理以及渲染过程
浏览器渲染引擎工作流程都差不多,大致分为5步,创建DOM树——创建StyleRules——创建Render树——布局Layout——绘制Painting 第一步,用HTML分析器,分析HTML元素,构建一颗DOM树(标记化和树构建)。 第二步,用…...
无人机视觉定位,常用相机,及相机提供的数据信息
常用相机类型 单目相机:仅使用一个摄像头进行图像采集,结构简单、成本低。它可以获取无人机前方或下方的二维图像信息,包括物体的形状、颜色、纹理等。双目相机:由两个摄像头组成,模拟人类双眼视觉原理,通…...
A2L文件解析
目录 1 摘要2 A2L文件介绍2.1 A2L文件作用2.2 A2L文件格式详解2.2.1 A2L文件基本结构2.2.2 关键元素与声明2.2.3 完整A2L文件示例 3 总结 1 摘要 A2L文件(也称为ASAP2文件)是ECU开发的核心接口文件,用于标定、测量和诊断的关键配置文件&…...
Ansible:role企业级实战
文章目录 实现 nginx 角色创建task文件创建handler文件准备模板文件创建变量文件在playbook中调用角色 实现 memcached 角色创建相关目录创建相关task任务准备模板文件查看目录结构在playbook中调用角色 实现多角色的选择 实现 nginx 角色 卸载httpd,创建相关目录 a…...
vue2使用vue-echarts
1.先安装echarts npm i echarts 2.安装vue-echarts 安装的时候注意下对应的版本 "echarts": "5.5.0", "vue-echarts": "6.7.3",这是我安装的版本 注意事项: 如果安装之后报错:"export watchEffect …...
多光谱相机:海洋管道漏油(溢油)监测
每年海上溢油和化工管道漏油造成的污染事故和经济损失频发,在生态方面,漏油会带来导致水质恶化、生态系统破坏、食物链受损。在经济方面,会造成渔业损失、旅游业损失、航运业损失。在健康方面,会造成食品安全问题,直接…...
Kaggle-Digit Recognizer-(多分类+卷积神经网络CNN)
Digit Recognizer 题意: 给你每个图片的dataframe类型的数据,让你预测出每个图片可能是多少。 思考: 数据处理 1.首先把数据从dadaframe转换成numpy,数据类型改为float32,并且并且展开为1维的28281的形状…...
jQuery多库共存
在现代Web开发中,项目往往需要集成多种JavaScript库或框架来满足不同的功能需求。然而,当多个库同时使用时,可能会出现命名冲突、功能覆盖等问题。幸运的是,jQuery提供了一些机制来确保其可以与其他库和谐共存。本文将探讨如何实现…...
MCU的USB接口作为 USB CDC串口输出
前言: 如下内容是和Chatgpt的问答对话。询问了Chatgpt 关于 MCU微控制器内部的USB端口作为串口输出是怎么工作的,是否需要在上位机上安装串口驱动程序等,Chatgpt解答的很好。 正文: STM32 使用USB作为串行设备端口,需…...
VCode 的 .S 汇编文件里面的注释不显示绿色
1. 确认文件语言模式 打开 .S 文件后,查看 VS Code 右下角的状态栏,确认当前文件的识别模式(如 Assembly、Plain Text 等)。如果显示为 Plain Text 或其他非汇编模式: 点击状态栏中的语言模式(如 Plain Te…...
【数学建模】(智能优化算法)萤火虫算法(Firefly Algorithm)详解与实现
萤火虫算法(Firefly Algorithm)详解与实现 文章目录 萤火虫算法(Firefly Algorithm)详解与实现前言1. 算法原理2. 算法流程3. Python实现4. 算法特点4.1 优点4.2 缺点 5. 应用领域6. 算法变种7. 总结与展望参考文献 前言 大家好,今天给大家介绍一种有趣且高效的群体…...
链路追踪组件学习
目录 1. 为啥需要链路追踪2. 常见的链路追踪组件3. 使用过的链路追踪组件3.1. Spring Cloud Sleuth3.2. Zipkin3.3. Apache SkyWalking 4. 集成Spring Cloud Sleuth框架4.1. 流程步骤4.2 sleuth工作流程 5. 集成zipKin5.1 添加 Zipkin 相关依赖5.2 安装zipkin服务5.3 配置 Zipk…...
# 基于OpenCV与Dlib的人脸融合技术实现
从仿射变换到人脸融合:基于OpenCV和Dlib的图像处理实践 在图像处理领域,仿射变换和人脸融合是两个非常有趣且实用的技术。仿射变换可以用于图像的几何变换,而人脸融合则可以创造出令人惊叹的视觉效果。本文将通过两个具体的代码示例…...
多光谱相机:水环境监测(水体富营养化、黑臭水体、藻类水华)
随着全球水体污染问题日益严峻,水体富营养化、黑臭水体和藻类水华等生态危机对人类健康和水生系统构成重大威胁。传统监测手段(如人工采样、单点传感器)因效率低、覆盖不足、实时性差等局限,难以满足复杂水环境的动态监管需求。多…...
记录一次nginx访问前端首页,一直显示nginx首页问题(实际是nginx访问页面权限问题)
同一台服务器,nginx配置是server { listen 8081; server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; location /New_mh_other { alias /home/hqu/data/new_mh_other; try…...
windows下命名管道双端通信
实现功能 1、命名管道双端通信(异步) 2、客户端断线重连 PS:多线程版本 PipeWrapper.h #include <windows.h> #include <string> #include <vector> #include "Utils/ThreadObject.h" #include "Utils/T…...
Linux自行实现的一个Shell(15)
文章目录 前言一、头文件和全局变量头文件全局变量 二、辅助函数获取用户名获取主机名获取当前工作目录获取最后一级目录名生成命令行提示符打印命令行提示符 三、命令处理获取用户输入解析命令行执行外部命令 四、内建命令添加环境变量检查和执行内建命令 五、初始化初始化环境…...
powerDesign 逆向 mysql 生成 物理模型,并用VBS脚本整理comment
学习自:https://www.cnblogs.com/xmyjcs/p/8536233.html 文章目录 Reverse Engineer格式化模型执行 VBS 脚本 Reverse Engineer 下面 DBMS 可以通过 ODBC(Open Database Connectivity,开放数据库连接)连接, 需要自己先…...
跨境全域中台:前端独立站群+后端共享云仓的协同作战体系
在全球化浪潮与互联网技术飞速发展的当下,跨境电商已然成为国际贸易领域中最为活跃的力量。据相关数据显示,过去几年跨境电商的年增长率持续保持高位,越来越多的企业投身于这片充满机遇与挑战的蓝海市场。在竞争日益激烈的跨境电商赛道上&…...
国产芯片解析:乐得瑞LDR6500C 超小封装全能芯片,赋能智能设备未来
LDR6500C是乐得瑞科技针对USB-C标准中的Bridge设备而开发的双USB-C DRP接口PD通信芯片,具备切换Data Role功能,支持最高USB PD 100W 充电,并且针对各大品牌设备的 USB-C 兼容性进行了特别优化,非常适合于 USB Type-C 设备快充转接…...
代码随想录-06-二叉树-05.10 二叉树的最小深度
二叉树的最小深度 #模板题 题目描述 给定一个二叉树,找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 说明叶子节点是指没有子节点的节点 具体思路(暴力) 层序遍历;找到cur.left null && cur.ri…...
系统与网络安全------网络通信原理(6)
资料整理于网络资料、书本资料、AI,仅供个人学习参考。 应用层解析 DNS Domain Name System,域名系统 用来完成域名与IP地址之间的映射,便于用户对网站的记忆和访问 端口号为TCP或UDP的53 DNS工作原理 FTP File Transfer Protocol 文件…...
【Vue #2】脚手架 指令
一、脚手架 脚手架:一个保证各项工作顺利开展的平台,方便我们 拿来就用,零配置 1. Vue 代码开发方式 相比直接 script 引入 vue 源码,有没有更好的方式编写vue代码呢? ① 传统开发模式: 基于html文件开发Vue&…...
UE5 后坐力枪口上抬和恢复
文章目录 计算后坐力并让视角上抬后坐力回落 计算后坐力并让视角上抬 在玩家蓝图里,声明一个方法OnShootOnce,在武器每次射击时调用 1检测新的后坐力是否超过了最大后坐力,并选择一个小的 2 如何将角色模型设置为相机的子物体 3 最后记录一下当前的…...
Mysql B+树高度如何计算?
MySQL 的 InnoDB 存储引擎使用 B+树 作为索引结构,其高度增加会直接影响查询性能(每次高度增加意味着多一次磁盘 I/O)。以下是 B+树高度增加的 关键场景 和 优化建议: 1. B+树高度增加的触发条件 (1) 数据量持续增长 根本原因:B+树的层级由数据量(记录数)和每个节点的容…...
UE5 使用贴花创建弹孔
文章目录 使用射线检测击中点在击中点处创建贴花 使用射线检测击中点 和untiy一样,发射一条射线,在命中点处创建弹孔 在武器里定义射击检测方法 以下是对上边使用的方法的展开 GetShootStartPosition:获取射击起点 computeShootEndPosition:…...
程序持续内存泄漏问题定位参考
0 概括 本文用于记录 x86-Linux 应用程序发生持续性内存泄漏问题时的定位方法。主要介绍valgrind工具的应用。 1 原理 对于内存泄漏问题的定位,一种朴素的想法就是对内存申请点进行监控。对于一个内存申请调用点(例如c/c中的malloc函数)&a…...
Lumion 与 Enscape 怎么选?附川翔云电脑适配指南
建筑可视化领域,Lumion 和 Enscape 是两款主流实时渲染器,核心差异体现在操作逻辑、渲染特性及适用场景。结合川翔云电脑平台的硬件支持,可进一步优化使用体验。 一、核心差异:效率、操作与场景适配 1. 操作门槛与实时性 Lumio…...
WebShell详解:原理、分类、攻击与防御
目录 一、WebShell的定义与核心概念 二、WebShell的分类 三、WebShell的攻击原理与常见手法 1. 攻击原理 2. 常见攻击路径 四、WebShell的危害 五、防御与检测策略 六、总结 一、WebShell的定义与核心概念 WebShell是一种以ASP、PHP、JSP等网页脚本形式存在的恶…...
Ubuntu 24.04 中文输入法安装
搜狗输入法,在Ubuntu 24.04上使用失败,安装教程如下 https://shurufa.sogou.com/linux/guide 出现问题的情况,是这个帖子里描述的: https://forum.ubuntu.org.cn/viewtopic.php?t493893 后面通过google拼音输入法解决了&#x…...
数码视讯TR100系列/TR100-G1/TR100-G4/数码视讯F7-国科GK6323V100C芯片-刷机固件包
数码视讯TR100系列/数码视讯TR100-G1/数码视讯TR100-G4/数码视讯F7-国科GK6323V100C芯片-刷机固件包 刷机教程: 里面共有两种方法,一是TTL线刷烧录方法;二是卡刷固件包; 下面以数码视讯TR100-…...
Cloudflare教程:免费优化CDN加速配置,提升网站访问速度 | 域名访问缓存压缩视频图片媒体文件优化配置
1、启用 Tiered Cache 缓存开关:通过选择缓存拓扑,可以控制源服务器与 Cloudflare 数据中心的连接方式,以确保缓存命中率更高、源服务器连接数更少,并且 Internet 延迟更短。 2、增加浏览器缓存时间TTL:在此期间&#…...
24体育NBA足球直播M24模板自适应板源码
源码名称:体育直播赛事扁平自适应M24直播模板源码 开发环境:帝国cms7.5 空间支持:phpmysql 带软件采集,可以挂着自动采集发布,无需人工操作! 演示地址:https://www.52muban.com/shop/184022.h…...
dify+wan2.1搭建文生视频生成工具流
本文介绍在dify中使用阿里开源的Wan2.1 1.3B模型搭建文生视频工作流的方法。 使用的工具如下: 1、dify(官方:https://docs.dify.ai/zh-hans) 2、comfyui 一、comfyui安装 为了简单起见,本文介绍使用autodl完成comfyui的部署。在autodl创建实例,使用的镜像如下图: 大…...
C# js 判断table中tr否存在相同的值
html 中如: 实现:table数据表格中,点击删除按钮时,验证相同子订单号条数是否大于1,大于允许删除。保证数据表格中只有唯一的一条子订单号数据。 <table style"width: 100%; background-color: #fff;" ce…...
HTML5+CSS3小实例:纯CSS绘制七巧板
实例:纯CSS绘制七巧板 技术栈:HTML+CSS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale…...
什么是虚拟线程?与普通线程的区别
引言:线程的演进与挑战 在传统的并发编程中,线程是一种非常重要的概念。我们使用线程来实现任务的并发执行,从而提高程序的执行效率。普通线程(如 Thread 类)是一种重量级的线程,每个线程都对应着操作系统…...
Docker MySQL的主从同步 数据备份 数据同步 配置文件
创建主库 docker run \--namemysql_1 \-e MYSQL_ROOT_PASSWORD123456 \-p 3306:3306 \-v mysql_main_data:/var/lib/mysql \--restart unless-stopped \-d \mysql:8.0进入容器内部 docker exec -it mysql_1 bash查找配置文件 find / -name my.cnf复制出主机 docker cp mysql…...
PowerBI中的DATEDIFF函数
一、语法 DATEDIFF(开始日期,结束日期,日期时间类型),返回两个日期之间的时间差 二、示例 开始日期结束日期度量值计算结果2025/4/1 15:33:202025/4/1 15:33:30计算 DATEDIFF([开始日期],[结束日期],SECOND)102025/4/1 15:332025/4/1 15:3…...
C/C++共有的类型转换与c++特有的四种强制类型转换
前言 C 语言和 C 共有的类型转换: 自动类型转换(隐式类型转换): 编译器在某些情况下会自动进行的类型转换。强制类型转换(显示类型转换): 使用 (type)expression 或 type(expression) 语法进行…...