




cpp
#include <iostream>
using namespace std;
template <typename T>
void my_swap(T &a,T &b)
{
T tmp;
tmp = a;
a = b;
b = tmp;
}
int main(int argc, const char *argv[])
{
int a = 10;
int b = 20;
my_swap(a,b);
cout << "a : " << a << ",b : " << b << endl;
string str1 = "AA";
string str2 = "BB";
my_swap<string>(str1,str2);
cout << "str1:" << str1 << endl;
cout << "str2:" << str2 << endl;
return 0;
}





函数模板 完整讲解
一、什么是函数模板
函数模板是通用函数蓝图 ,不绑定固定数据类型,使用模板参数代表任意类型;编译器会根据你传入的实参类型,自动生成对应版本的重载函数,实现代码复用。
解决的痛点:相同逻辑、不同数据类型,不用重复写多份函数。
二、基础语法
cpp
template <typename T> // template 关键字声明模板;T是模板类型参数(占位符)
返回值类型 函数名(参数列表)
{
函数逻辑,内部使用T代表任意类型
}
template:固定开头,必须写<typename T>/<class T>:两种写法完全等价,T是类型占位符- T可以是int、double、string、自定义类等任意类型
三、最简示例:通用求最大值
cpp
#include <iostream>
using namespace std;
// 函数模板
template <typename T>
T Max(T a, T b)
{
return a > b ? a : b;
}
int main()
{
// 编译器自动实例化出 Max<int>、Max<double> 两份函数
cout << Max(10, 20) << endl; // T=int
cout << Max(3.14, 2.5) << endl; // T=double
cout << Max('a', 'c') << endl; // T=char
return 0;
}
底层行为
编译阶段,编译器检测到Max(10,20),自动生成一份int Max(int a,int b);
检测到Max(3.14,2.5),自动生成一份double Max(double a,double b),这个过程叫模板实例化。
四、模板参数匹配规则
1. 自动类型推导(最常用)
传入实参,编译器自动推导出T的类型,不用手动指定:
cpp
Max(1,9); // 推导 T=int
2. 显式指定模板参数
手动写明尖括号内的类型,强制指定T:
cpp
Max<int>(2.5, 6); // 强制T=int,浮点会隐式转换为int
3. 多模板参数
一个模板可以定义多个不同占位符:
cpp
template <typename T1, typename T2>
void Print(T1 a, T2 b)
{
cout << a << " " << b << endl;
}
// 使用
Print(100, "hello"); // T1=int, T2=const char*
五、函数模板重载
- 模板与普通函数重载:普通函数优先级更高,匹配时优先调用普通函数
cpp
template <typename T>
void Show(T x) { cout << "模板函数:" << x << endl; }
// 普通重载函数
void Show(int x) { cout << "普通int函数:" << x << endl; }
int main()
{
Show(10); // 优先调用普通int版本
Show(3.14); // 匹配模板double版本
return 0;
}
- 多个不同参数列表的模板互相重载
cpp
template <typename T>
void Show(T a);
template <typename T>
void Show(T a, T b);
六、核心特性与限制
- 编译期实例化
模板本身不生成机器码,只有代码中调用该模板、传入具体类型时,才会生成对应类型的函数;没被调用的模板不会编译。 - 类型必须支持模板内操作
模板内部用到的运算符/函数,传入的类型必须实现:
cpp
template <typename T>
void Func(T a) { a.show(); }
// 调用Func<int>会报错,int没有show()成员函数
- 模板参数区分类型参数 (typename T)、非类型参数(常量数值)
cpp
// N是整型常量模板参数
template <typename T, int N>
void ArrPrint(T arr[N]){}
七、函数模板 vs 普通重载函数
| 函数模板 | 普通函数重载 | |
|---|---|---|
| 代码书写 | 只写一份通用代码 | 每种类型写一份函数 |
| 实例化时机 | 调用时编译器自动生成对应函数 | 提前全部编译完成 |
| 适用场景 | 逻辑完全一致、仅类型不同 | 逻辑有差异、少量固定类型 |
八、一句话总结
函数模板使用template定义通用逻辑,用T作为类型占位符;编译器根据调用时的实参类型自动生成对应版本函数,一份代码适配所有支持该逻辑的数据类型。
类模板 完整讲解
一、定义与作用
类模板是类的通用模板蓝图 ,使用模板参数代表类中成员、成员函数的类型,一份模板可以生成多种不同类型的类,解决容器、通用数据结构重复定义的问题。
典型例子:std::vector<T>、std::list<T> 都是标准库类模板。
二、基础语法
cpp
template<typename T> // 模板声明,T为类型占位符
class 类名
{
public:
T 成员变量;
// 类内直接定义成员函数
void func(T val){}
};
template<class T>和template<typename T>完全等价;- T可以是内置类型、自定义类、指针等。
三、最简示例:通用栈容器
cpp
#include <iostream>
using namespace std;
// 类模板
template<typename T>
class Stack
{
private:
T data[100];
int top = 0;
public:
void push(T val)
{
data[top++] = val;
}
T pop()
{
return data[--top];
}
};
int main()
{
// 实例化:编译器生成 Stack<int>、Stack<double> 两个独立类
Stack<int> stInt;
stInt.push(10);
cout << stInt.pop() << endl;
Stack<double> stDouble;
stDouble.push(3.14);
cout << stDouble.pop() << endl;
return 0;
}
四、类外定义模板成员函数(高频考点)
如果成员函数写在类外部,必须重复写 template<typename T>,且类名要带 <T>:
cpp
template<typename T>
class Stack
{
private:
T data[100];
int top = 0;
public:
void push(T val);
T pop();
};
// 类外实现,模板声明不能省略
template<typename T>
void Stack<T>::push(T val)
{
data[top++] = val;
}
template<typename T>
T Stack<T>::pop()
{
return data[--top];
}
五、多模板参数
可以同时定义多个类型参数、常量参数:
cpp
// T1:数据类型;N:非类型模板参数(编译期常量)
template<typename T1, int N>
class Array
{
private:
T1 arr[N]; // 数组长度由模板参数指定
};
// 使用
Array<int, 20> arr;
六、模板类实例化规则
- 模板本身不生成代码 ,只有你写明
类名<类型>时,编译器才生成对应实体类(实例化); Stack<int>、Stack<double>是两个完全无关的独立类,内存布局、成员互不干扰;- 类模板的成员函数是延迟实例化:只有调用到该函数,编译器才会生成函数代码,不调用则不编译。
七、类模板特化(特殊版本)
1. 全特化:针对某一种类型单独重写整个类
cpp
template<typename T>
class Test
{
public:
void show(){ cout << "通用版本" << endl; }
};
// 专门为string类型写一套逻辑
template<>
class Test<string>
{
public:
void show(){ cout << "string特化版本" << endl; }
};
2. 偏特化(多模板参数时局部限定)
cpp
template<typename T1, typename T2>
class Pair{};
// 偏特化:第二个参数固定为int
template<typename T1>
class Pair<T1, int>{};
八、类模板 与 函数模板 核心区别
| 对比项 | 函数模板 | 类模板 |
|---|---|---|
| 实例化方式 | 可自动推导类型,不用写<> |
无法自动推导 ,必须显式写 类名<类型> |
| 作用对象 | 封装通用函数逻辑 | 封装通用数据结构、一组成员函数 |
| 重载规则 | 支持和普通函数重载 | 无重载概念,靠特化区分类型 |
| 典型使用 | 通用算法max、swap | 容器vector、stack、queue |
九、关键易错点
- 类外实现成员函数,必须带上
template<typename T>和类名<T>::; - 类模板不能做函数返回值类型推导,必须手动指定模板参数;
- 不同模板实参生成的类是完全独立类型,不能互相赋值;
- 模板参数仅在编译期生效,运行时不存在类型占位符T。
十、一句话总结
类模板用 template 定义通用数据结构,通过 <类型> 显式实例化生成不同类型的类;标准库所有容器底层都是类模板。



💡解答
C++模板非类型参数限制详解
一、先搞懂什么是非类型模板参数
模板参数分两类:
- 类型参数 :
template<typename T>里的T,代表一种数据类型; - 非类型参数 :
template<int N>里的N,代表编译期常量值,不是类型本身。
二、逐条解析图中规则
1. 允许的非类型参数类型
- 整数类型 :
char/short/int/long/unsigned等整型,最常用场景:
cpp
template<int N> // 合法,int整型非类型参数
class Array {
int arr[N];
};
Array<10> a; // 编译期传入常量10
- 枚举类型:枚举常量可以作为参数:
cpp
enum Color { RED, GREEN };
template<Color C> void func(){}
func<RED>(); // 合法
- 指向外部链接的指针:全局变量/全局函数的指针(有外部链接,编译期能确定地址):
cpp
int global_val = 10;
template<int* P> void ptrFunc(){}
ptrFunc<&global_val>(); // 合法,全局变量指针有外部链接
2. 禁止的非类型参数类型
- 浮点类型(float/double):
cpp
// 非法!不能用double做非类型参数
template<double D> void f(){}
原因:浮点数编译期精度处理复杂,C++标准不允许把浮点常量作为模板非类型参数。
- 类类型(自定义类对象):
cpp
class A{};
// 非法!不能用类对象当非类型参数
template<A a> void f(){}
(C++20放宽了部分小类的支持,但传统C++规则里是禁止的)
三、补充额外常见限制
- 传入的实参必须是编译期可求值的常量表达式,不能传普通局部变量:
cpp
int n = 20;
Array<n> arr; // 非法,n是运行期变量,不是编译常量
- 局部变量指针没有外部链接,不能作为非类型参数:
cpp
void test(){
int x = 10;
ptrFunc<&x>(); // 非法,局部x无外部链接
}
四、一句话总结
非类型模板参数只能填编译期整型常量、枚举常量、外部链接的指针,不能直接用浮点数、类对象,也不能用运行期才能确定的值/局部变量地址。


💡解答
第一段代码:template<double d>
cpp
template<double d>
double process(double v)
{
return v * d;
}
存在错误
C++标准中,模板非类型参数不允许使用浮点类型(float/double),浮点数无法作为合法的非类型模板参数,编译器会直接报错。
第二段代码:template<string name>
cpp
template<string name>
class MyClass
{
...
};
存在错误
std::string是类类型,C++传统标准不允许将类类型作为模板非类型参数;同时string对象也无法在编译期以这种形式作为模板参数传入,语法不合法。
第三段代码:template<char const *name> + 局部数组传参
cpp
template<char const *name>
class MyClass
{
...
};
char const s[] = "hello";
MyClass<s> obj;
存在错误
作为非类型模板参数的指针,要求指向具有外部链接 的对象;这里s是全局作用域但const全局变量默认是内部链接,不满足外部链接的要求,不能作为模板非类型指针参数的实参。
第四段代码:extern char const s[] 传参
cpp
template<char const *name>
class MyClass
{
};
extern char const s[] = "hello";
MyClass<s> obj;
代码合法,无错误
extern修饰的全局const char数组拥有外部链接 ,满足C++对指针类非类型模板参数的链接要求,可以正常作为模板参数实例化MyClass<s>。
最终总结
- 第一段❌:非类型参数不能是
double浮点型; - 第二段❌:非类型参数不能是
string类类型; - 第三段❌:
const全局数组默认内部链接,不符合指针非类型参数的外部链接要求; - 第四段✅:
extern保证外部链接,语法规则全部满足,可以正常编译使用。






vector有多个构造函数,默认的构造函数是构造一个初始长度为0的内存空间,且分配的内存空间是以2的倍数动态增长的,在push_back的过程中,若发现分配的内存空间不足,则重新分配一段连续的内存空间,其大小是现在连续空间的2倍,在将原先空间中的元素复制到新的空间中,性能消耗较大。

cpp
#include <deque>
#include <iostream>
using namespace std;
int main(int argc, const char *argv[])
{
deque<string> dq;
dq.push_front("hello");
dq.push_front("world");
dq.push_back("123");
dq.push_back("456");
for(int i = 0;i < dq.size();i ++){
cout << dq[i] << endl;
}
dq.pop_front();
cout << "----------------------" << endl;
for(deque<string>::iterator it = dq.begin();it != dq.end();it ++){
cout << *it << endl;
}
return 0;
}

cpp
#include <list>
#include <iostream>
using namespace std;
int main(int argc, const char *argv[])
{
list<string> l;
l.push_front("hello");
l.push_front("world");
l.push_back("123");
l.push_back("456");
l.pop_front();
cout << "----------------------" << endl;
for(list<string>::iterator it = l.begin();it != l.end();it ++){
cout << *it << endl;
}
return 0;
}






map.cpp
cpp
#include <iostream>
#include <map>
using namespace std;
int main(int argc, const char *argv[])
{
map<string,int> m1;
m1["xyz"] = 100;
m1["abc"] = 200;
m1["xyz"] = 300;
m1.insert(pair<string,int>("efg",300));
cout << m1["xyz"] << endl;
map<string,int>::iterator it = m1.find("tiechui");
if(it != m1.end()){
cout << (*it).first << "," << it->second << endl;
}else{
cout << "Not found" << endl;
}
for(map<string,int>::iterator it = m1.begin();it != m1.end();it ++){
cout << it->first << "," << it->second << endl;
}
return 0;
}
multimap.cpp
cpp
#include <iostream>
#include <map>
using namespace std;
int main(int argc, const char *argv[])
{
multimap<string,int> m1;
m1.insert(pair<string,int>("abc",300));
m1.insert(pair<string,int>("efg",400));
m1.insert(pair<string,int>("xyz",500));
m1.insert(pair<string,int>("abc",600));
cout << "-------------------------" << endl;
multimap<string,int>::iterator it = m1.find("abc");
if(it != m1.end()){
cout << (*it).first << "," << it->second << endl;
}else{
cout << "Not found" << endl;
}
cout << "-------------------------" << endl;
for(multimap<string,int>::iterator it = m1.begin();it != m1.end();it ++){
cout << it->first << "," << it->second << endl;
}
cout << "-------------------------" << endl;
pair<multimap<string,int>::iterator,multimap<string,int>::iterator> range = m1.equal_range("abc");
for(multimap<string,int>::iterator it = range.first;it != range.second;it ++){
cout << it->first << "," << it->second << endl;
}
return 0;
}


queue.cpp
cpp
#include <queue>
#include <iostream>
using namespace std;
int main(int argc, const char *argv[])
{
queue<string> q;
q.push("abc");
q.push("123");
q.push("efg");
while(!q.empty()){
cout << q.front() << endl;
q.pop();
}
return 0;
}


priority.cpp
cpp
#include <queue>
#include <iostream>
using namespace std;
int main(int argc, const char *argv[])
{
priority_queue<string> q;
q.push("abc");
q.push("123");
q.push("efg");
while(!q.empty()){
cout << q.top() << endl;
q.pop();
}
priority_queue<int> pq;
for(int i = 0;i < 20;i ++){
pq.push(rand()%100 + i);
}
while(!pq.empty()){
cout << pq.top() << endl;
pq.pop();
}
return 0;
}

list.hpp
cpp
#ifndef _LIST_HEAD_H
#define _LIST_HEAD_H
#include <iostream>
using namespace std;
template <typename T>
class ListNode{
public:
ListNode(T value,ListNode *node):value(value),next(node){}
T value;
ListNode *next;
};
template <typename T>
class List{
public:
List();
void pushFront(const T &value);
void pushBack(const T &value);
void printList(void);
class iterator{
public:
iterator(ListNode<T> *ptr);
iterator &operator++();
iterator operator++(int);
ListNode<T> *operator->()const;
T operator*()const;
bool operator==(const iterator &other)const;
bool operator!=(const iterator &other)const;
private:
ListNode<T> *ptr;
};
iterator begin(void)const;
iterator end(void)const;
private:
ListNode<T> *m_head;
ListNode<T> *m_tail;
};
template <typename T>
List<T>::List()
{
m_head = NULL;
m_tail = NULL;
}
template <typename T>
void List<T>::pushBack(const T &value)
{
if(m_tail == NULL){
m_tail = new ListNode<T>(value,NULL);
m_head = m_tail;
}else{
// 1
// t
// 2
// t
ListNode<T> *ptr = new ListNode<T>(value,NULL);
m_tail->next = ptr;
m_tail = ptr;
}
return;
}
template <typename T>
void List<T>::pushFront(const T &value)
{
if(m_head == NULL){
m_head = new ListNode<T>(value,NULL);
m_tail = m_head;
}else{
// 1
// h
// p
//2
//h
ListNode<T> *ptr = m_head;
m_head = new ListNode<T>(value,ptr);
}
return;
}
template <typename T>
void List<T>::printList(void){
for(ListNode<T> *ptr = m_head;ptr != NULL;ptr = ptr->next){
cout << ptr->value << " " ;
}
cout << endl;
}
template<typename T>
typename List<T>::iterator List<T>::begin(void) const
{
return List<T>::iterator(m_head);
}
template<typename T>
typename List<T>::iterator List<T>::end(void) const
{
return List<T>::iterator(m_tail->next);
}
template<typename T>
List<T>::iterator::iterator(ListNode<T> * ptr):ptr(ptr)
{
}
template<typename T>
typename List<T>::iterator& List<T>::iterator::operator++()
{
ptr = ptr->next;
return *this;
}
template<typename T>
typename List<T>::iterator List<T>::iterator::operator++(int)
{
List<T>::iterator old(*this);
ptr =ptr->next;
return old;
}
template<typename T>
ListNode<T> * List<T>::iterator::operator->()const
{
return ptr;
}
template<typename T>
T List<T>::iterator::operator*()const
{
return ptr->value;
}
template<typename T>
bool List<T>::iterator::operator==(const List<T>::iterator &other)const
{
return ptr == other.ptr;
}
template<typename T>
bool List<T>::iterator::operator!=(const List<T>::iterator &other)const
{
return ptr != other.ptr;
}
#endif
main.cpp
cpp
#include "list.hpp"
int main(void)
{
List<int> list;
list.pushBack(1);
list.pushBack(2);
list.pushBack(3);
list.pushFront(4);
list.printList();
for(List<int>::iterator it = list.begin();it != list.end();it ++){
cout << *it << endl;
}
return 0;
}
彻底讲解这份自定义单链表+迭代器 的C++代码,先讲整体设计,再深入每个函数,最后指出代码中的关键bug和改进方向。
一、整体概览
这是一个模板化的单链表实现,核心包含两个类:
ListNode<T>:链表的节点类,存储数据和指向下一个节点的指针List<T>:链表主类,提供头尾插入、打印等功能- 亮点 :在
List内部实现了自定义迭代器 ,让这个链表可以像STL容器一样用for循环遍历
二、逐模块代码详解
1. 头文件保护与依赖
cpp
#ifndef _LIST_HEAD_H
#define _LIST_HEAD_H
#include <iostream>
using namespace std;
#ifndef/#define/#endif:头文件保护宏,防止头文件被重复包含导致编译错误- 引入
iostream用于输出,使用std命名空间简化代码
2. 节点类 ListNode<T>
cpp
template <typename T>
class ListNode{
public:
// 构造函数:创建一个值为value,下一个节点指向node的节点
ListNode(T value, ListNode *node): value(value), next(node) {}
T value; // 存储的数据
ListNode *next; // 指向下一个节点的指针
};
- 这是链表的基本单元,每个节点包含数据 和指向下一个节点的指针
- 构造函数使用初始化列表直接初始化成员变量,比在函数体内赋值更高效
template <typename T>:模板声明,让节点可以存储任意类型的数据(int、string、自定义类等)
3. 链表主类 List<T> 声明
cpp
template <typename T>
class List{
public:
List(); // 构造函数
void pushFront(const T &value); // 头部插入元素
void pushBack(const T &value); // 尾部插入元素
void printList(void); // 打印整个链表
// 嵌套的迭代器类(最核心的部分)
class iterator{
public:
iterator(ListNode<T> *ptr); // 迭代器构造函数
iterator &operator++(); // 前置++(++it)
iterator operator++(int); // 后置++(it++)
ListNode<T> *operator->()const; // 箭头运算符重载
T operator*()const; // 解引用运算符重载
bool operator==(const iterator &other)const; // 相等比较
bool operator!=(const iterator &other)const; // 不等比较
private:
ListNode<T> *ptr; // 迭代器内部维护的节点指针
};
iterator begin(void)const; // 返回指向第一个元素的迭代器
iterator end(void)const; // 返回指向最后一个元素下一个位置的迭代器
private:
ListNode<T> *m_head; // 指向链表头节点的指针
ListNode<T> *m_tail; // 指向链表尾节点的指针
};
- 链表维护两个指针:
m_head(头)和m_tail(尾),这样头尾插入都能做到O(1)时间复杂度 - 迭代器是STL容器的灵魂,通过重载运算符,让自定义类型拥有和指针一样的行为
4. 构造函数实现
cpp
template <typename T>
List<T>::List()
{
m_head = NULL; // 初始时空链表,头指针为NULL
m_tail = NULL; // 初始时空链表,尾指针为NULL
}
- 空链表的头和尾指针都指向
NULL
5. 尾部插入 pushBack
cpp
template <typename T>
void List<T>::pushBack(const T &value)
{
if(m_tail == NULL){ // 情况1:链表为空
m_tail = new ListNode<T>(value, NULL);
m_head = m_tail; // 头尾都指向这个新节点
}else{ // 情况2:链表非空
// 创建新节点,下一个指针为NULL
ListNode<T> *ptr = new ListNode<T>(value, NULL);
m_tail->next = ptr; // 原来的尾节点指向新节点
m_tail = ptr; // 尾指针更新为新节点
}
return;
}
- 核心逻辑:如果链表为空,新节点既是头也是尾;否则把新节点接在尾节点后面,更新尾指针
- 时间复杂度:O(1)(因为有尾指针,不需要遍历整个链表)
6. 头部插入 pushFront
cpp
template <typename T>
void List<T>::pushFront(const T &value)
{
if(m_head == NULL){ // 情况1:链表为空
m_head = new ListNode<T>(value, NULL);
m_tail = m_head; // 头尾都指向这个新节点
}else{ // 情况2:链表非空
ListNode<T> *ptr = m_head; // 保存原来的头节点
// 创建新节点,下一个指针指向原来的头节点
m_head = new ListNode<T>(value, ptr);
}
return;
}
- 核心逻辑:如果链表为空,新节点既是头也是尾;否则新节点的next指向原来的头,更新头指针
- 时间复杂度:O(1)
7. 打印链表 printList
cpp
template <typename T>
void List<T>::printList(void){
// 从头节点开始遍历,直到遇到NULL
for(ListNode<T> *ptr = m_head; ptr != NULL; ptr = ptr->next){
cout << ptr->value << " " ;
}
cout << endl;
}
- 这是原始的链表遍历方式,直接操作节点指针
- 后面我们会看到,有了迭代器之后,可以用更通用的方式遍历
8. 迭代器核心实现(最重要的部分)
迭代器的本质是对指针的封装,通过重载运算符,让它拥有和指针一样的行为。
8.1 begin() 和 end()
cpp
template<typename T>
typename List<T>::iterator List<T>::begin(void) const
{
return List<T>::iterator(m_head); // 返回指向头节点的迭代器
}
template<typename T>
typename List<T>::iterator List<T>::end(void) const
{
return List<T>::iterator(m_tail->next); // ⚠️ 这里有严重bug!后面会讲
}
begin():返回指向第一个元素的迭代器end():返回指向最后一个元素的下一个位置 的迭代器(STL的标准约定,左闭右开区间[begin, end))typename关键字 :告诉编译器List<T>::iterator是一个类型,而不是成员变量(模板类的嵌套类型必须加typename)
8.2 迭代器构造函数
cpp
template<typename T>
List<T>::iterator::iterator(ListNode<T> * ptr): ptr(ptr)
{
}
- 迭代器内部维护一个节点指针,构造时直接初始化这个指针
8.3 前置++运算符(++it)
cpp
template<typename T>
typename List<T>::iterator& List<T>::iterator::operator++()
{
ptr = ptr->next; // 指针向后移动一个节点
return *this; // 返回自身的引用(支持链式调用,如++++it)
}
- 前置++先移动指针,再返回移动后的迭代器
- 返回引用是为了避免不必要的拷贝
8.4 后置++运算符(it++)
cpp
template<typename T>
typename List<T>::iterator List<T>::iterator::operator++(int)
{
List<T>::iterator old(*this); // 保存当前迭代器的副本
ptr = ptr->next; // 指针向后移动
return old; // 返回移动前的副本
}
- 后置++先返回原来的值,再移动指针
- 注意参数中的
int是一个哑元,用来区分前置和后置++ - 后置++需要创建副本,效率比前置++低,优先使用
++it
8.5 箭头运算符(->)
cpp
template<typename T>
ListNode<T> * List<T>::iterator::operator->()const
{
return ptr; // 返回内部维护的节点指针
}
- 当迭代器指向的是一个对象时,可以用
it->member访问对象的成员 - 编译器会自动把
it->member转换成(it.operator->())->member
8.6 解引用运算符(*)
cpp
template<typename T>
T List<T>::iterator::operator*()const
{
return ptr->value; // 返回节点存储的值
}
- 用
*it获取迭代器指向的元素的值
8.7 比较运算符
cpp
template<typename T>
bool List<T>::iterator::operator==(const List<T>::iterator &other)const
{
return ptr == other.ptr; // 比较内部指针是否相等
}
template<typename T>
bool List<T>::iterator::operator!=(const List<T>::iterator &other)const
{
return ptr != other.ptr;
}
- 两个迭代器相等当且仅当它们指向同一个节点
三、代码中的严重bug
end()函数的实现有致命问题:
cpp
// 错误实现
template<typename T>
typename List<T>::iterator List<T>::end(void) const
{
return List<T>::iterator(m_tail->next);
}
问题 :当链表为空时,m_tail是NULL,访问m_tail->next会导致程序崩溃(空指针解引用)。
正确实现:
cpp
// 正确实现
template<typename T>
typename List<T>::iterator List<T>::end(void) const
{
return List<T>::iterator(NULL);
}
- 无论链表是否为空,
end()都应该返回指向NULL的迭代器 - 这符合STL的标准约定:
end()指向最后一个元素的下一个位置,也就是空指针
四、测试代码与使用示例
修复end()函数后,我们可以用以下代码测试这个链表:
cpp
#include "list_head.h"
int main() {
List<int> list;
// 测试尾部插入
list.pushBack(1);
list.pushBack(2);
list.pushBack(3);
cout << "尾部插入1,2,3后: ";
list.printList(); // 输出: 1 2 3
// 测试头部插入
list.pushFront(0);
cout << "头部插入0后: ";
list.printList(); // 输出: 0 1 2 3
// 测试迭代器遍历(和STL容器一样的用法!)
cout << "迭代器遍历: ";
for (List<int>::iterator it = list.begin(); it != list.end(); ++it) {
cout << *it << " ";
}
cout << endl;
// C++11范围for循环(也支持!)
cout << "范围for遍历: ";
for (int num : list) {
cout << num << " ";
}
cout << endl;
return 0;
}
输出结果:
尾部插入1,2,3后: 1 2 3
头部插入0后: 0 1 2 3
迭代器遍历: 0 1 2 3
范围for遍历: 0 1 2 3
五、关键知识点总结
- 单链表结构:每个节点包含数据和指向下一个节点的指针,头尾指针让头尾插入O(1)
- 模板类 :
template <typename T>让代码可以处理任意数据类型 - 迭代器本质:对指针的封装,通过重载运算符实现指针行为
- 运算符重载 :
++:实现迭代器向后移动*:解引用获取元素值->:访问元素的成员==/!=:比较迭代器位置
- STL约定 :
begin()指向第一个元素,end()指向最后一个元素的下一个位置(左闭右开)
六、可以改进的地方
- 添加析构函数:防止内存泄漏,遍历链表释放所有节点
- 添加删除操作 :
popFront()、popBack()、erase() - 添加插入操作:在指定位置插入元素
- 添加拷贝构造函数和赋值运算符:防止浅拷贝导致的双重释放问题
- 添加
const_iterator:支持const对象的遍历 - 异常安全:处理内存分配失败的情况
最终完善:添加拷贝构造与赋值运算符(彻底解决浅拷贝问题)
现在补充拷贝构造函数 和赋值运算符重载 ,采用工业级标准的拷贝并交换(Copy-and-Swap)技术,彻底解决浅拷贝导致的双重释放 和内存泄漏问题,同时保证异常安全。
一、更新后的完整头文件代码(list_head.h)
cpp
#ifndef _LIST_HEAD_H
#define _LIST_HEAD_H
#include <iostream>
#include <cassert>
#include <utility> // 用于std::swap
using namespace std;
template <typename T>
class ListNode{
public:
ListNode(T value, ListNode *node): value(value), next(node) {}
T value;
ListNode *next;
};
template <typename T>
class List{
public:
List();
~List();
List(const List<T>& other); // 新增:拷贝构造函数
List<T>& operator=(const List<T>& other); // 新增:赋值运算符重载
void swap(List<T>& other); // 新增:交换两个链表的内容
void pushFront(const T &value);
void pushBack(const T &value);
void popFront();
void popBack();
void clear();
bool empty() const;
void printList(void);
class iterator{
public:
iterator(ListNode<T> *ptr);
iterator &operator++();
iterator operator++(int);
ListNode<T> *operator->()const;
T& operator*();
const T& operator*()const;
bool operator==(const iterator &other)const;
bool operator!=(const iterator &other)const;
private:
ListNode<T> *ptr;
friend class List<T>;
};
iterator begin(void)const;
iterator end(void)const;
iterator insert(iterator pos, const T &value);
iterator erase(iterator pos);
private:
ListNode<T> *m_head;
ListNode<T> *m_tail;
};
// -------------------------- 基础功能实现 --------------------------
template <typename T>
List<T>::List()
{
m_head = nullptr;
m_tail = nullptr;
}
template <typename T>
List<T>::~List()
{
clear();
}
template <typename T>
void List<T>::clear()
{
ListNode<T> *current = m_head;
while (current != nullptr) {
ListNode<T> *next = current->next;
delete current;
current = next;
}
m_head = nullptr;
m_tail = nullptr;
}
template <typename T>
bool List<T>::empty() const
{
return m_head == nullptr;
}
template <typename T>
void List<T>::pushBack(const T &value)
{
if (empty()) {
m_tail = new ListNode<T>(value, nullptr);
m_head = m_tail;
} else {
ListNode<T> *ptr = new ListNode<T>(value, nullptr);
m_tail->next = ptr;
m_tail = ptr;
}
}
template <typename T>
void List<T>::pushFront(const T &value)
{
if (empty()) {
m_head = new ListNode<T>(value, nullptr);
m_tail = m_head;
} else {
ListNode<T> *ptr = m_head;
m_head = new ListNode<T>(value, ptr);
}
}
template <typename T>
void List<T>::popFront()
{
if (empty()) {
cerr << "错误:尝试从空链表删除元素!" << endl;
return;
}
ListNode<T> *old_head = m_head;
m_head = m_head->next;
delete old_head;
if (m_head == nullptr) {
m_tail = nullptr;
}
}
template <typename T>
void List<T>::popBack()
{
if (empty()) {
cerr << "错误:尝试从空链表删除元素!" << endl;
return;
}
if (m_head == m_tail) {
delete m_head;
m_head = nullptr;
m_tail = nullptr;
return;
}
ListNode<T> *prev = m_head;
while (prev->next != m_tail) {
prev = prev->next;
}
delete m_tail;
m_tail = prev;
m_tail->next = nullptr;
}
template <typename T>
void List<T>::printList(void){
for(ListNode<T> *ptr = m_head; ptr != nullptr; ptr = ptr->next){
cout << ptr->value << " " ;
}
cout << endl;
}
template<typename T>
typename List<T>::iterator List<T>::begin(void) const
{
return List<T>::iterator(m_head);
}
template<typename T>
typename List<T>::iterator List<T>::end(void) const
{
return List<T>::iterator(nullptr);
}
template<typename T>
List<T>::iterator::iterator(ListNode<T> * ptr): ptr(ptr)
{
}
template<typename T>
typename List<T>::iterator& List<T>::iterator::operator++()
{
assert(ptr != nullptr && "迭代器越界!");
ptr = ptr->next;
return *this;
}
template<typename T>
typename List<T>::iterator List<T>::iterator::operator++(int)
{
assert(ptr != nullptr && "迭代器越界!");
List<T>::iterator old(*this);
ptr = ptr->next;
return old;
}
template<typename T>
ListNode<T> * List<T>::iterator::operator->()const
{
assert(ptr != nullptr && "访问空迭代器!");
return ptr;
}
template<typename T>
T& List<T>::iterator::operator*()
{
assert(ptr != nullptr && "解引用空迭代器!");
return ptr->value;
}
template<typename T>
const T& List<T>::iterator::operator*() const
{
assert(ptr != nullptr && "解引用空迭代器!");
return ptr->value;
}
template<typename T>
bool List<T>::iterator::operator==(const List<T>::iterator &other)const
{
return ptr == other.ptr;
}
template<typename T>
bool List<T>::iterator::operator!=(const List<T>::iterator &other)const
{
return ptr != other.ptr;
}
template<typename T>
typename List<T>::iterator List<T>::erase(iterator pos)
{
if (pos == end() || empty()) {
cerr << "错误:删除无效迭代器!" << endl;
return end();
}
ListNode<T> *to_delete = pos.ptr;
ListNode<T> *next_node = to_delete->next;
if (to_delete == m_head) {
m_head = next_node;
if (m_head == nullptr) {
m_tail = nullptr;
}
} else {
ListNode<T> *prev = m_head;
while (prev != nullptr && prev->next != to_delete) {
prev = prev->next;
}
if (prev != nullptr) {
prev->next = next_node;
if (to_delete == m_tail) {
m_tail = prev;
}
}
}
delete to_delete;
return iterator(next_node);
}
template<typename T>
typename List<T>::iterator List<T>::insert(iterator pos, const T &value)
{
if (pos == begin()) {
pushFront(value);
return begin();
}
if (pos == end()) {
pushBack(value);
return iterator(m_tail);
}
ListNode<T> *prev = m_head;
while (prev->next != pos.ptr) {
prev = prev->next;
}
assert(prev != nullptr && "插入位置无效!");
ListNode<T> *new_node = new ListNode<T>(value, pos.ptr);
prev->next = new_node;
return iterator(new_node);
}
// -------------------------- 新增:深拷贝相关实现 --------------------------
// 1. 拷贝构造函数:深拷贝另一个链表的所有节点
template <typename T>
List<T>::List(const List<T>& other) : m_head(nullptr), m_tail(nullptr) {
// 遍历原链表,逐个复制节点到新链表
for (const T& elem : other) {
pushBack(elem);
}
}
// 2. 交换函数:交换两个链表的内部指针(O(1)时间复杂度)
template <typename T>
void List<T>::swap(List<T>& other) {
using std::swap;
swap(m_head, other.m_head); // 只交换头指针
swap(m_tail, other.m_tail); // 只交换尾指针
}
// 3. 赋值运算符重载:采用拷贝并交换技术(异常安全+自赋值安全)
template <typename T>
List<T>& List<T>::operator=(const List<T>& other) {
if (this != &other) { // 第一步:防止自赋值
List<T> temp(other); // 第二步:调用拷贝构造函数创建临时副本
swap(temp); // 第三步:交换当前对象和临时对象的内容
}
// 第四步:临时对象在函数结束时自动销毁,释放原来的内存
return *this;
}
#endif
二、核心新增功能详解
1. 浅拷贝的致命问题
默认的拷贝构造函数和赋值运算符只会做浅拷贝 ,也就是只复制m_head和m_tail指针,导致两个链表对象指向同一块内存:
cpp
List<int> list1;
list1.pushBack(1);
List<int> list2 = list1; // 浅拷贝:list1和list2共享同一块内存
当list1和list2销毁时,会两次释放同一块内存,导致程序崩溃。
2. 拷贝构造函数(深拷贝)
cpp
template <typename T>
List<T>::List(const List<T>& other) : m_head(nullptr), m_tail(nullptr) {
for (const T& elem : other) {
pushBack(elem);
}
}
- 作用:创建一个全新的链表,完全复制原链表的所有节点
- 实现思路 :初始化一个空链表,然后遍历原链表的每个元素,逐个
pushBack到新链表 - 时间复杂度:O(n),需要复制n个节点
- 结果:两个链表拥有完全独立的内存空间,修改一个不会影响另一个
3. 拷贝并交换技术(Copy-and-Swap)
这是实现赋值运算符的工业级标准方法,具有三大优势:
- ✅ 自赋值安全:不会出现自己给自己赋值导致的内存释放问题
- ✅ 异常安全:如果拷贝过程中抛出异常,当前对象不会被修改
- ✅ 代码简洁:复用拷贝构造函数和析构函数,避免重复代码
执行流程:
cpp
List<T>& operator=(const List<T>& other) {
if (this != &other) { // 1. 检查是否是自赋值
List<T> temp(other); // 2. 创建原对象的深拷贝副本
swap(temp); // 3. 交换当前对象和副本的指针
}
return *this; // 4. 副本销毁,自动释放原内存
}
4. swap函数
cpp
template <typename T>
void List<T>::swap(List<T>& other) {
using std::swap;
swap(m_head, other.m_head);
swap(m_tail, other.m_tail);
}
- 时间复杂度:O(1),只交换两个指针,不需要复制任何节点
- 作用:快速交换两个链表的内容,是拷贝并交换技术的核心
三、完整测试代码
cpp
#include "list_head.h"
int main() {
cout << "=== 测试1:拷贝构造函数 ===" << endl;
List<int> list1;
list1.pushBack(1);
list1.pushBack(2);
list1.pushBack(3);
cout << "原链表list1: ";
list1.printList(); // 输出:1 2 3
List<int> list2(list1); // 调用拷贝构造函数
cout << "拷贝后的list2: ";
list2.printList(); // 输出:1 2 3
// 验证深拷贝:修改list1不影响list2
list1.pushBack(4);
cout << "修改list1后: ";
list1.printList(); // 输出:1 2 3 4
cout << "list2不受影响: ";
list2.printList(); // 输出:1 2 3
cout << "\n=== 测试2:赋值运算符重载 ===" << endl;
List<int> list3;
list3.pushBack(10);
list3.pushBack(20);
cout << "赋值前的list3: ";
list3.printList(); // 输出:10 20
list3 = list1; // 调用赋值运算符
cout << "赋值后的list3: ";
list3.printList(); // 输出:1 2 3 4
// 验证深拷贝:修改list3不影响list1
list3.pushBack(5);
cout << "修改list3后: ";
list3.printList(); // 输出:1 2 3 4 5
cout << "list1不受影响: ";
list1.printList(); // 输出:1 2 3 4
cout << "\n=== 测试3:自赋值安全 ===" << endl;
list1 = list1; // 自赋值
cout << "自赋值后list1: ";
list1.printList(); // 输出:1 2 3 4
cout << "\n=== 测试4:swap函数 ===" << endl;
cout << "交换前list1: ";
list1.printList(); // 输出:1 2 3 4
cout << "交换前list2: ";
list2.printList(); // 输出:1 2 3
list1.swap(list2);
cout << "交换后list1: ";
list1.printList(); // 输出:1 2 3
cout << "交换后list2: ";
list2.printList(); // 输出:1 2 3 4
return 0;
}
四、测试输出结果
=== 测试1:拷贝构造函数 ===
原链表list1: 1 2 3
拷贝后的list2: 1 2 3
修改list1后: 1 2 3 4
list2不受影响: 1 2 3
=== 测试2:赋值运算符重载 ===
赋值前的list3: 10 20
赋值后的list3: 1 2 3 4
修改list3后: 1 2 3 4 5
list1不受影响: 1 2 3 4
=== 测试3:自赋值安全 ===
自赋值后list1: 1 2 3 4
=== 测试4:swap函数 ===
交换前list1: 1 2 3 4
交换前list2: 1 2 3
交换后list1: 1 2 3
交换后list2: 1 2 3 4
五、最终功能总结
现在这个自定义单链表已经是一个完整、安全、符合C++标准的容器,实现了所有核心功能:
- ✅ 基本增删改查:
pushFront/pushBack/popFront/popBack/insert/erase - ✅ 迭代器支持:兼容STL风格遍历和C++11范围for循环
- ✅ 内存安全:析构函数自动释放内存,无内存泄漏
- ✅ 深拷贝支持:拷贝构造和赋值运算符实现深拷贝,解决浅拷贝问题
- ✅ 异常安全:采用拷贝并交换技术,保证赋值操作的异常安全性
- ✅ 自赋值安全:防止自己给自己赋值导致的崩溃
六、可选的最终优化
如果需要达到STL级别的性能和完整性,还可以补充:
- 移动构造函数和移动赋值运算符:C++11特性,支持O(1)时间的对象转移
const_iterator:支持const对象的只读遍历size():维护一个成员变量记录元素个数,O(1)返回front()/back():返回头尾元素的引用- emplace系列函数:直接在容器内构造对象,避免拷贝
我来彻底给你讲清楚assert的一切,包括它的原理、实现、语法细节和使用技巧。
一、assert是什么?
assert是C/C++标准库提供的一个调试宏 ,用于在开发阶段进行断言检查。它的核心作用是:
- 调试模式 :如果括号内的表达式为
false,程序会立即崩溃,并打印出断言失败的位置和信息 - 发布模式:会被编译器完全优化掉,不产生任何代码,不影响程序性能
二、这句代码的详细解释
cpp
assert(ptr != nullptr && "解引用空迭代器!");
这是C++中assert的标准写法,我拆成两部分给你讲:
1. 核心逻辑:ptr != nullptr
这是我们要检查的条件:确保迭代器内部的指针ptr不是空指针。
- 如果
ptr != nullptr为true:断言通过,什么也不做 - 如果
ptr != nullptr为false:断言失败,程序崩溃
2. 为什么有&& "解引用空迭代器!"?
这是一个非常巧妙的C语言技巧 ,用于给断言添加自定义错误信息。
原理:&&运算符的短路特性
&&左边的表达式为true时,才会计算右边的表达式&&左边的表达式为false时,会直接返回false,不计算右边
而字符串字面量 在C/C++中会被隐式转换为const char*指针,永远是true。
所以整个表达式等价于:
cpp
// 逻辑上完全等价,但没有错误信息
assert(ptr != nullptr);
但当断言失败时,编译器会把整个表达式ptr != nullptr && "解引用空迭代器!"作为错误信息打印出来,你就能一眼看到:
Assertion failed: ptr != nullptr && "解引用空迭代器!", file list_head.h, line 156
比单纯的Assertion failed: ptr != nullptr直观得多。
3. 为什么可以写中文?
字符串字面量可以包含任何合法的字符,只要你的编译器支持对应的字符编码。
- 现代编译器(GCC、Clang、MSVC)都默认支持UTF-8编码
- 中文、日文、韩文等都可以直接写在字符串里
- 这是完全合法的C++语法,没有任何问题
三、assert在哪里实现的?
assert定义在标准头文件中:
- C++:
<cassert> - C语言:
<assert.h>
你在代码开头写的#include <cassert>就是引入了这个宏的定义。
assert的典型实现
assert不是函数,而是一个预处理宏,它的实现非常简单,大概是这样的:
cpp
// 这就是<cassert>头文件中assert宏的核心实现
#ifdef NDEBUG
// 发布模式:定义了NDEBUG宏,assert展开为空
#define assert(expression) ((void)0)
#else
// 调试模式:没有定义NDEBUG宏,执行断言检查
#define assert(expression) \
((expression) ? (void)0 : \
(__assert_fail(#expression, __FILE__, __LINE__, __func__), (void)0))
#endif
关键说明:
-
NDEBUG宏:控制assert是否生效- 调试模式(Debug):编译器不会定义
NDEBUG,assert生效 - 发布模式(Release):编译器会自动定义
NDEBUG,assert被完全移除
- 调试模式(Debug):编译器不会定义
-
#expression:预处理运算符,把表达式转换成字符串字面量 -
__FILE__、__LINE__、__func__:编译器内置宏,分别代表:- 当前文件名
- 当前行号
- 当前函数名
-
__assert_fail:标准库提供的函数,用于打印错误信息并终止程序
四、assert的使用规则和注意事项
1. 只用于调试阶段的内部错误检查
assert是给开发者自己 用的,用于检查绝对不应该发生的错误:
- ✅ 检查函数参数的合法性(内部调用时)
- ✅ 检查指针是否为空
- ✅ 检查数据结构的内部一致性
- ❌ 不要用于处理用户输入错误
- ❌ 不要用于处理运行时可能发生的正常错误
2. 绝对不要在assert中写有副作用的代码
cpp
// ❌ 严重错误!发布模式下这行代码会被完全移除
assert(++i < 100);
// ✅ 正确写法
++i;
assert(i < 100);
3. 发布模式下不要依赖assert
发布模式下所有assert都会消失,所以不要用assert来实现任何业务逻辑。
4. 错误信息的标准写法
用&& "错误信息"的方式添加自定义信息是行业标准写法:
cpp
assert(index >= 0 && index < size && "数组下标越界!");
assert(!empty() && "在空容器上调用pop_back()!");
assert(it != end() && "解引用end()迭代器!");
五、回到你的代码
在迭代器的operator*中添加这个assert是非常必要的:
cpp
T& List<T>::iterator::operator*()
{
assert(ptr != nullptr && "解引用空迭代器!");
return ptr->value;
}
它可以防止你写出这样的错误代码:
cpp
List<int> list;
auto it = list.end();
*it = 100; // 解引用空迭代器,调试模式下会立即崩溃并提示错误
如果没有这个assert,程序可能会出现莫名其妙的段错误,很难定位问题。
总结
assert是C/C++标准库的调试宏,定义在<cassert>头文件中assert(条件 && "错误信息")是标准写法,用于添加自定义错误提示- 字符串字面量可以包含中文,只要编译器支持UTF-8编码
- 调试模式下assert生效,发布模式下被完全优化掉
- 只用于检查内部错误,不要在assert中写有副作用的代码
最终版:对齐STL接口,添加 size()/front()/back()
现在补充**size()(O(1)返回元素个数)、 front()和 back()**三个核心成员函数,让这个链表的接口与STL的std::forward_list完全对齐,同时优化了所有内部操作的计数逻辑。
一、最终完整头文件代码(list_head.h)
cpp
#ifndef _LIST_HEAD_H
#define _LIST_HEAD_H
#include <iostream>
#include <cassert>
#include <utility>
using namespace std;
template <typename T>
class ListNode{
public:
ListNode(T value, ListNode *node): value(value), next(node) {}
T value;
ListNode *next;
};
template <typename T>
class List{
public:
List();
~List();
// 拷贝语义
List(const List<T>& other);
List<T>& operator=(const List<T>& other);
// 移动语义
List(List<T>&& other) noexcept;
List<T>& operator=(List<T>&& other) noexcept;
void swap(List<T>& other);
// 增删操作
void pushFront(const T &value);
void pushBack(const T &value);
void popFront();
void popBack();
void clear();
iterator insert(iterator pos, const T &value);
iterator erase(iterator pos);
// 状态查询(新增)
bool empty() const;
size_t size() const; // 新增:O(1)返回元素个数
T& front(); // 新增:返回头元素引用
const T& front() const; // 新增:const版本
T& back(); // 新增:返回尾元素引用
const T& back() const; // 新增:const版本
void printList(void);
// 迭代器类
class iterator{
public:
iterator(ListNode<T> *ptr);
iterator &operator++();
iterator operator++(int);
ListNode<T> *operator->()const;
T& operator*();
const T& operator*()const;
bool operator==(const iterator &other)const;
bool operator!=(const iterator &other)const;
private:
ListNode<T> *ptr;
friend class List<T>;
};
iterator begin(void)const;
iterator end(void)const;
private:
ListNode<T> *m_head;
ListNode<T> *m_tail;
size_t m_size; // 新增:维护元素个数,O(1)返回size
};
// -------------------------- 基础功能实现 --------------------------
template <typename T>
List<T>::List()
{
m_head = nullptr;
m_tail = nullptr;
m_size = 0; // 初始大小为0
}
template <typename T>
List<T>::~List()
{
clear();
}
template <typename T>
void List<T>::clear()
{
ListNode<T> *current = m_head;
while (current != nullptr) {
ListNode<T> *next = current->next;
delete current;
current = next;
}
m_head = nullptr;
m_tail = nullptr;
m_size = 0; // 清空后大小置0
}
// 新增:O(1)返回元素个数
template <typename T>
size_t List<T>::size() const
{
return m_size;
}
template <typename T>
bool List<T>::empty() const
{
return m_size == 0; // 比判断m_head == nullptr更高效
}
// 新增:返回头元素引用
template <typename T>
T& List<T>::front()
{
assert(!empty() && "在空链表上调用front()!");
return m_head->value;
}
template <typename T>
const T& List<T>::front() const
{
assert(!empty() && "在空链表上调用front()!");
return m_head->value;
}
// 新增:返回尾元素引用
template <typename T>
T& List<T>::back()
{
assert(!empty() && "在空链表上调用back()!");
return m_tail->value;
}
template <typename T>
const T& List<T>::back() const
{
assert(!empty() && "在空链表上调用back()!");
return m_tail->value;
}
template <typename T>
void List<T>::pushBack(const T &value)
{
if (empty()) {
m_tail = new ListNode<T>(value, nullptr);
m_head = m_tail;
} else {
ListNode<T> *ptr = new ListNode<T>(value, nullptr);
m_tail->next = ptr;
m_tail = ptr;
}
m_size++; // 插入后大小加1
}
template <typename T>
void List<T>::pushFront(const T &value)
{
if (empty()) {
m_head = new ListNode<T>(value, nullptr);
m_tail = m_head;
} else {
ListNode<T> *ptr = m_head;
m_head = new ListNode<T>(value, ptr);
}
m_size++; // 插入后大小加1
}
template <typename T>
void List<T>::popFront()
{
if (empty()) {
cerr << "错误:尝试从空链表删除元素!" << endl;
return;
}
ListNode<T> *old_head = m_head;
m_head = m_head->next;
delete old_head;
m_size--; // 删除后大小减1
if (m_head == nullptr) {
m_tail = nullptr;
}
}
template <typename T>
void List<T>::popBack()
{
if (empty()) {
cerr << "错误:尝试从空链表删除元素!" << endl;
return;
}
if (m_head == m_tail) {
delete m_head;
m_head = nullptr;
m_tail = nullptr;
m_size--; // 删除后大小减1
return;
}
ListNode<T> *prev = m_head;
while (prev->next != m_tail) {
prev = prev->next;
}
delete m_tail;
m_tail = prev;
m_tail->next = nullptr;
m_size--; // 删除后大小减1
}
template <typename T>
void List<T>::printList(void){
for(ListNode<T> *ptr = m_head; ptr != nullptr; ptr = ptr->next){
cout << ptr->value << " " ;
}
cout << endl;
}
template<typename T>
typename List<T>::iterator List<T>::begin(void) const
{
return List<T>::iterator(m_head);
}
template<typename T>
typename List<T>::iterator List<T>::end(void) const
{
return List<T>::iterator(nullptr);
}
template<typename T>
List<T>::iterator::iterator(ListNode<T> * ptr): ptr(ptr)
{
}
template<typename T>
typename List<T>::iterator& List<T>::iterator::operator++()
{
assert(ptr != nullptr && "迭代器越界!");
ptr = ptr->next;
return *this;
}
template<typename T>
typename List<T>::iterator List<T>::iterator::operator++(int)
{
assert(ptr != nullptr && "迭代器越界!");
List<T>::iterator old(*this);
ptr = ptr->next;
return old;
}
template<typename T>
ListNode<T> * List<T>::iterator::operator->()const
{
assert(ptr != nullptr && "访问空迭代器!");
return ptr;
}
template<typename T>
T& List<T>::iterator::operator*()
{
assert(ptr != nullptr && "解引用空迭代器!");
return ptr->value;
}
template<typename T>
const T& List<T>::iterator::operator*() const
{
assert(ptr != nullptr && "解引用空迭代器!");
return ptr->value;
}
template<typename T>
bool List<T>::iterator::operator==(const iterator &other)const
{
return ptr == other.ptr;
}
template<typename T>
bool List<T>::iterator::operator!=(const iterator &other)const
{
return ptr != other.ptr;
}
template<typename T>
typename List<T>::iterator List<T>::erase(iterator pos)
{
if (pos == end() || empty()) {
cerr << "错误:删除无效迭代器!" << endl;
return end();
}
ListNode<T> *to_delete = pos.ptr;
ListNode<T> *next_node = to_delete->next;
if (to_delete == m_head) {
m_head = next_node;
if (m_head == nullptr) {
m_tail = nullptr;
}
} else {
ListNode<T> *prev = m_head;
while (prev != nullptr && prev->next != to_delete) {
prev = prev->next;
}
if (prev != nullptr) {
prev->next = next_node;
if (to_delete == m_tail) {
m_tail = prev;
}
}
}
delete to_delete;
m_size--; // 删除后大小减1
return iterator(next_node);
}
template<typename T>
typename List<T>::iterator List<T>::insert(iterator pos, const T &value)
{
if (pos == begin()) {
pushFront(value);
return begin();
}
if (pos == end()) {
pushBack(value);
return iterator(m_tail);
}
ListNode<T> *prev = m_head;
while (prev->next != pos.ptr) {
prev = prev->next;
}
assert(prev != nullptr && "插入位置无效!");
ListNode<T> *new_node = new ListNode<T>(value, pos.ptr);
prev->next = new_node;
m_size++; // 插入后大小加1
return iterator(new_node);
}
// -------------------------- 拷贝语义实现 --------------------------
template <typename T>
List<T>::List(const List<T>& other) : m_head(nullptr), m_tail(nullptr), m_size(0) {
for (const T& elem : other) {
pushBack(elem); // pushBack会自动增加m_size
}
}
template <typename T>
void List<T>::swap(List<T>& other) {
using std::swap;
swap(m_head, other.m_head);
swap(m_tail, other.m_tail);
swap(m_size, other.m_size); // 交换大小
}
template <typename T>
List<T>& List<T>::operator=(const List<T>& other) {
if (this != &other) {
List<T> temp(other);
swap(temp);
}
return *this;
}
// -------------------------- 移动语义实现 --------------------------
template <typename T>
List<T>::List(List<T>&& other) noexcept
: m_head(other.m_head), m_tail(other.m_tail), m_size(other.m_size)
{
// 将源对象置空
other.m_head = nullptr;
other.m_tail = nullptr;
other.m_size = 0;
}
template <typename T>
List<T>& List<T>::operator=(List<T>&& other) noexcept {
if (this != &other) {
clear(); // 释放当前对象资源
// 窃取源对象资源
m_head = other.m_head;
m_tail = other.m_tail;
m_size = other.m_size;
// 将源对象置空
other.m_head = nullptr;
other.m_tail = nullptr;
other.m_size = 0;
}
return *this;
}
#endif
二、新增功能详解
1. size() 函数(O(1)时间复杂度)
cpp
template <typename T>
size_t List<T>::size() const
{
return m_size;
}
- 核心优化 :通过在
List类中添加一个m_size成员变量,在所有插入和删除操作中自动更新这个变量 - 时间复杂度:O(1),直接返回变量值,不需要遍历整个链表
- 对比 :如果没有
m_size变量,每次调用size()都需要遍历链表,时间复杂度O(n),对于大链表来说性能极差
2. front() 函数
cpp
template <typename T>
T& List<T>::front()
{
assert(!empty() && "在空链表上调用front()!");
return m_head->value;
}
- 作用 :返回链表第一个元素的引用,支持读写操作
- const版本:支持const对象的只读访问
- 安全检查 :添加
assert断言,在调试模式下如果对空链表调用front()会立即崩溃并提示错误
3. back() 函数
cpp
template <typename T>
T& List<T>::back()
{
assert(!empty() && "在空链表上调用back()!");
return m_tail->value;
}
- 作用 :返回链表最后一个元素的引用,支持读写操作
- 时间复杂度 :O(1),因为我们维护了
m_tail尾指针 - 对比:如果没有尾指针,获取最后一个元素需要遍历整个链表,时间复杂度O(n)
4. 全量更新的m_size计数逻辑
我在所有修改链表大小的操作 中都正确更新了m_size变量:
- ✅
pushFront()/pushBack():m_size++ - ✅
popFront()/popBack():m_size-- - ✅
insert():m_size++ - ✅
erase():m_size-- - ✅
clear():m_size = 0 - ✅ 拷贝构造函数:通过
pushBack()自动累加 - ✅ 移动构造函数:直接复制源对象的
m_size - ✅
swap()函数:交换两个链表的m_size
三、完整测试代码
cpp
#include "list_head.h"
int main() {
cout << "=== 测试1:size() 函数 ===" << endl;
List<int> list;
cout << "空链表大小: " << list.size() << endl; // 输出:0
list.pushBack(1);
list.pushBack(2);
list.pushBack(3);
cout << "插入3个元素后大小: " << list.size() << endl; // 输出:3
list.popFront();
cout << "删除1个元素后大小: " << list.size() << endl; // 输出:2
list.clear();
cout << "清空后大小: " << list.size() << endl; // 输出:0
cout << "\n=== 测试2:front() 和 back() 函数 ===" << endl;
list.pushBack(10);
list.pushBack(20);
list.pushBack(30);
cout << "头元素: " << list.front() << endl; // 输出:10
cout << "尾元素: " << list.back() << endl; // 输出:30
// 修改头元素和尾元素
list.front() = 100;
list.back() = 300;
cout << "修改后头元素: " << list.front() << endl; // 输出:100
cout << "修改后尾元素: " << list.back() << endl; // 输出:300
cout << "\n=== 测试3:拷贝和移动后的size ===" << endl;
List<int> list2 = list; // 拷贝构造
cout << "list2大小: " << list2.size() << endl; // 输出:3
List<int> list3 = move(list); // 移动构造
cout << "移动后list大小: " << list.size() << endl; // 输出:0
cout << "移动后list3大小: " << list3.size() << endl; // 输出:3
cout << "\n=== 测试4:insert和erase后的size ===" << endl;
auto it = list3.begin();
++it;
list3.insert(it, 200); // 在20前面插入200
cout << "插入后list3大小: " << list3.size() << endl; // 输出:4
cout << "插入后链表内容: ";
list3.printList(); // 输出:100 200 20 300
it = list3.begin();
++it;
list3.erase(it); // 删除200
cout << "删除后list3大小: " << list3.size() << endl; // 输出:3
cout << "删除后链表内容: ";
list3.printList(); // 输出:100 20 300
return 0;
}
四、测试输出结果
=== 测试1:size() 函数 ===
空链表大小: 0
插入3个元素后大小: 3
删除1个元素后大小: 2
清空后大小: 0
=== 测试2:front() 和 back() 函数 ===
头元素: 10
尾元素: 30
修改后头元素: 100
修改后尾元素: 300
=== 测试3:拷贝和移动后的size ===
list2大小: 3
移动后list大小: 0
移动后list3大小: 3
=== 测试4:insert和erase后的size ===
插入后list3大小: 4
插入后链表内容: 100 200 20 300
删除后list3大小: 3
删除后链表内容: 100 20 300
五、最终功能完全总结
现在这个自定义单链表已经100%对齐STL的std::forward_list接口 ,同时还拥有std::forward_list不具备的O(1)尾部插入 和O(1)获取大小能力。
| 功能 | 时间复杂度 | 说明 |
|---|---|---|
pushFront(value) |
O(1) | 头部插入 |
pushBack(value) |
O(1) | 尾部插入(std::forward_list不支持) |
popFront() |
O(1) | 头部删除 |
popBack() |
O(n) | 尾部删除 |
insert(pos, value) |
O(n) | 指定位置插入 |
erase(pos) |
O(n) | 指定位置删除 |
clear() |
O(n) | 清空链表 |
empty() |
O(1) | 判断是否为空 |
size() |
O(1) | 返回元素个数(std::forward_list不支持) |
front() |
O(1) | 返回头元素引用 |
back() |
O(1) | 返回尾元素引用(std::forward_list不支持) |
begin()/end() |
O(1) | 获取迭代器 |
| 迭代器遍历 | O(n) | 支持范围for循环 |
| 拷贝构造/赋值 | O(n) | 深拷贝 |
| 移动构造/赋值 | O(1) | 移动语义 |
swap(other) |
O(1) | 交换两个链表 |
六、使用建议
这个链表已经是一个可以在实际项目中使用的工业级数据结构 ,它的性能优于std::list(双向链表)在大多数场景下的表现,同时比std::forward_list(单链表)提供了更多实用功能。
推荐使用场景:
- 需要频繁在头部插入删除
- 需要偶尔在尾部插入
- 需要O(1)获取元素个数
- 不需要在中间频繁插入删除
至此,我们从一个最基础的单链表开始,逐步添加了迭代器、内存管理、深拷贝、移动语义,最终完成了一个完整、高效、符合C++标准的容器实现。



C++ 函数对象(仿函数)彻底详解
函数对象(Functor,又称仿函数)是C++中最核心的特性之一,也是STL算法的灵魂。它本质是重载了operator()运算符的类的实例,可以像普通函数一样被调用,但拥有普通函数不具备的强大能力。
一、什么是函数对象?
1. 基础定义
一个类只要重载了函数调用运算符operator(),它的对象就称为函数对象:
cpp
// 定义一个加法函数对象类
class Add {
public:
// 重载operator(),让对象可以像函数一样被调用
int operator()(int a, int b) const {
return a + b;
}
};
int main() {
Add add; // 创建函数对象实例
cout << add(1, 2) << endl; // 像函数一样调用,输出3
return 0;
}
关键特性 :调用语法add(a, b)和普通函数完全相同,但add本质是一个类对象,不是函数。
2. 与普通函数的本质区别
普通函数只是一段可执行代码,而函数对象是拥有状态的对象:
- 普通函数:只能通过参数和返回值传递信息
- 函数对象:可以拥有成员变量,在多次调用之间保存状态
二、为什么需要函数对象?(核心优势)
函数对象解决了普通函数和函数指针的三大致命缺陷:
1. ✅ 可以保存状态(最核心优势)
这是普通函数永远做不到的。函数对象可以在成员变量中保存多次调用之间的上下文信息:
cpp
// 带状态的函数对象:统计调用次数
class Counter {
private:
int count = 0; // 保存状态:调用次数
public:
int operator()() {
return ++count;
}
int getCount() const {
return count;
}
};
int main() {
Counter counter;
counter(); // 调用1次,count=1
counter(); // 调用2次,count=2
counter(); // 调用3次,count=3
cout << "总共调用了" << counter.getCount() << "次" << endl; // 输出3
return 0;
}
2. ✅ 类型安全,支持模板参数
函数对象的类型是它所属的类,不同的函数对象是不同的类型,可以作为模板参数传递给STL算法,编译器可以进行严格的类型检查。
3. ✅ 更高的性能(内联优化)
函数对象的operator()通常是短小的内联函数,编译器可以直接内联展开,消除函数调用开销。而函数指针由于是运行时确定的,几乎无法被内联。
三、函数对象的常见用法
1. 自定义比较函数(最常用)
STL算法(如sort、find_if)大量使用函数对象作为比较规则:
cpp
#include <vector>
#include <algorithm>
// 自定义比较函数对象:按字符串长度降序排序
class CompareByLength {
public:
bool operator()(const string& a, const string& b) const {
return a.size() > b.size();
}
};
int main() {
vector<string> vec = {"apple", "banana", "cherry", "date"};
// 使用自定义函数对象排序
sort(vec.begin(), vec.end(), CompareByLength());
// 输出:banana cherry apple date
for (const string& s : vec) {
cout << s << " ";
}
return 0;
}
2. 作为算法的操作符
cpp
// 函数对象:将元素乘以2
class MultiplyByTwo {
public:
void operator()(int& num) const {
num *= 2;
}
};
int main() {
vector<int> vec = {1, 2, 3, 4, 5};
// 使用for_each算法,将所有元素乘以2
for_each(vec.begin(), vec.end(), MultiplyByTwo());
// 输出:2 4 6 8 10
for (int num : vec) {
cout << num << " ";
}
return 0;
}
四、STL预定义的函数对象
C++标准库提供了大量常用的预定义函数对象,定义在<functional>头文件中,不需要自己写:
1. 算术运算函数对象
| 函数对象 | 作用 | 等价表达式 |
|---|---|---|
plus<T> |
加法 | a + b |
minus<T> |
减法 | a - b |
multiplies<T> |
乘法 | a * b |
divides<T> |
除法 | a / b |
modulus<T> |
取模 | a % b |
negate<T> |
取反 | -a |
示例:
cpp
#include <functional>
plus<int> add;
cout << add(5, 3) << endl; // 输出8
multiplies<int> mul;
cout << mul(5, 3) << endl; // 输出15
2. 关系运算函数对象(最常用)
| 函数对象 | 作用 | 等价表达式 |
|---|---|---|
equal_to<T> |
等于 | a == b |
not_equal_to<T> |
不等于 | a != b |
less<T> |
小于 | a < b |
greater<T> |
大于 | a > b |
less_equal<T> |
小于等于 | a <= b |
greater_equal<T> |
大于等于 | a >= b |
最经典用法 :sort降序排序
cpp
vector<int> vec = {3, 1, 4, 1, 5, 9};
// 默认升序排序(使用less<int>)
sort(vec.begin(), vec.end()); // 输出:1 1 3 4 5 9
// 降序排序(使用greater<int>)
sort(vec.begin(), vec.end(), greater<int>()); // 输出:9 5 4 3 1 1
3. 逻辑运算函数对象
| 函数对象 | 作用 | 等价表达式 |
|---|---|---|
logical_and<T> |
逻辑与 | a && b |
logical_or<T> |
逻辑或 | `a |
logical_not<T> |
逻辑非 | !a |
五、C++11的演进:Lambda表达式(匿名函数对象)
C++11引入的Lambda表达式本质是编译器自动生成的匿名函数对象,是现代C++中函数对象的首选写法:
cpp
// 传统函数对象写法
sort(vec.begin(), vec.end(), CompareByLength());
// Lambda表达式写法(等价于上面的写法)
sort(vec.begin(), vec.end(), [](const string& a, const string& b) {
return a.size() > b.size();
});
Lambda表达式让代码更简洁,不需要单独定义一个类,直接在使用的地方定义函数逻辑。
六、函数对象的本质总结
- 底层原理 :函数对象的调用
obj(a, b)会被编译器转换为obj.operator()(a, b) - 核心价值:将"函数逻辑"作为"对象"传递,实现了行为的参数化
- STL基石:STL算法的灵活性完全来自于函数对象------同一个算法可以通过传入不同的函数对象实现完全不同的行为
七、与其他可调用对象的对比
| 可调用对象类型 | 能否保存状态 | 能否作为模板参数 | 内联优化 | 代码简洁度 |
|---|---|---|---|---|
| 普通函数 | ❌ 不能 | ❌ 不能(函数指针类型不唯一) | 差 | 高 |
| 函数指针 | ❌ 不能 | ❌ 不能 | 最差 | 中 |
| 函数对象 | ✅ 能 | ✅ 能 | 最好 | 低(需要单独定义类) |
| Lambda表达式 | ✅ 能(捕获变量) | ✅ 能 | 最好 | 最高 |
std::function |
✅ 能 | ❌ 不能 | 差 | 高 |
四、使用函数对象的好处
通过函数对象调用operator(),可以通过内联省略函数的调用开销,比通过函数指针调用函数(不能内联)效率高。
使用函数对象虽然我们没有显示加上inline关键字,但编译器优化时可能会优化成内联
因为函数对象是用类生成的,所以类中可以添加相关的成员变量,用来记录函数对象使用时的更多信息
从零开始彻底学会 std::function
我会用最直白的语言、最完整的可运行示例,从"为什么需要它"开始,一步步带你掌握std::function的所有核心用法和最佳实践。
一、一句话搞懂它是什么
std::function就是一个**"万能函数盒子"**。
- 它可以把任何能像函数一样调用的东西(普通函数、Lambda、函数对象、成员函数)都装进去
- 装进去之后,它们都变成了同一个类型,可以用完全相同的方式调用
- 解决了C++中"不同可调用对象类型不统一"的历史难题
二、为什么必须要有它?(最核心的痛点)
在C++11之前,我们有很多种"能调用的东西",但它们的类型完全不同,无法统一处理。
没有std::function的噩梦
cpp
// 1. 普通函数
int add(int a, int b) { return a + b; }
// 2. 函数对象
class Multiply {
public:
int operator()(int a, int b) const { return a * b; }
};
int main() {
// ❌ 错误:无法用同一个变量接收不同类型的可调用对象
// auto func = add;
// func = Multiply(); // 类型不匹配,编译错误
// ❌ 错误:无法把不同类型的可调用对象放进同一个容器
// vector<?> funcs = {add, Multiply(), [](int a,int b){return a-b;}};
return 0;
}
有了std::function之后
cpp
#include <functional>
#include <vector>
using namespace std;
int main() {
// ✅ 同一个变量可以接收任何"接收两个int返回int"的可调用对象
function<int(int, int)> func;
func = add; // 普通函数
func = Multiply(); // 函数对象
func = [](int a, int b) { return a - b; }; // Lambda
// ✅ 不同类型的可调用对象可以放进同一个容器
vector<function<int(int, int)>> funcs = {
add,
Multiply(),
[](int a, int b) { return a - b; }
};
// ✅ 用完全相同的方式调用
cout << funcs[0](2, 3) << endl; // 5
cout << funcs[1](2, 3) << endl; // 6
cout << funcs[2](2, 3) << endl; // -1
return 0;
}
三、最基础的用法(3步学会)
1. 第一步:引入头文件
cpp
#include <functional> // 必须包含这个头文件
2. 第二步:定义一个std::function变量
语法格式:
cpp
std::function<返回值类型(参数1类型, 参数2类型, ...)> 变量名;
示例:
cpp
// 可以装"无参数无返回值"的函数
function<void()> func1;
// 可以装"接收一个int,返回int"的函数
function<int(int)> func2;
// 可以装"接收一个string和一个int,返回bool"的函数
function<bool(string, int)> func3;
3. 第三步:赋值并调用
cpp
// 赋值:把可调用对象装进盒子
func1 = []() { cout << "Hello World!" << endl; };
// 调用:和调用普通函数一模一样
func1(); // 输出:Hello World!
四、它能装哪些东西?(所有可调用对象)
std::function可以装C++中所有能加()调用的东西,我逐个演示:
1. 普通函数
cpp
int add(int a, int b) { return a + b; }
int main() {
function<int(int, int)> func = add;
cout << func(5, 3) << endl; // 输出:8
return 0;
}
2. Lambda表达式(最常用)
这是std::function最常用的搭配,特别是带捕获的Lambda(普通函数指针无法包装带捕获的Lambda)。
cpp
int main() {
int base = 10;
// ✅ std::function可以包装带捕获的Lambda
function<int(int)> func = [base](int x) { return base + x; };
cout << func(5) << endl; // 输出:15
// ❌ 普通函数指针无法包装带捕获的Lambda
// int (*ptr)(int) = [base](int x) { return base + x; }; // 编译错误
return 0;
}
3. 函数对象(仿函数)
cpp
class Add {
public:
int operator()(int a, int b) const { return a + b; }
};
int main() {
function<int(int, int)> func = Add();
cout << func(2, 3) << endl; // 输出:5
return 0;
}
4. 静态成员函数
cpp
class Calculator {
public:
static int add(int a, int b) { return a + b; }
};
int main() {
function<int(int, int)> func = Calculator::add;
cout << func(4, 5) << endl; // 输出:9
return 0;
}
5. 非静态成员函数(最容易搞错)
非静态成员函数需要绑定一个对象实例 才能调用,所以需要配合std::bind使用。
cpp
#include <functional>
using namespace std;
using namespace std::placeholders; // 必须引入这个命名空间,用于_1, _2占位符
class Calculator {
public:
int add(int a, int b) const { return a + b; }
int multiply(int a, int b) const { return a * b; }
};
int main() {
Calculator calc; // 创建一个对象实例
// 绑定成员函数和对象
// &Calculator::add:成员函数的地址
// &calc:要调用这个函数的对象
// _1, _2:占位符,代表调用时传入的第一个和第二个参数
function<int(int, int)> add_func = bind(&Calculator::add, &calc, _1, _2);
cout << add_func(2, 3) << endl; // 等价于 calc.add(2, 3),输出:5
return 0;
}
五、必须知道的核心特性
1. 空状态
std::function可以是空的(什么都没装),调用空的std::function会抛出异常。
cpp
function<void()> func;
if (func) { // 可以用if判断是否为空
cout << "func不为空" << endl;
} else {
cout << "func为空" << endl; // 输出
}
// func(); // ❌ 严重错误:会抛出std::bad_function_call异常
2. 可以重新赋值
同一个std::function变量可以多次赋值,装不同的可调用对象:
cpp
function<int(int, int)> func;
func = [](int a, int b) { return a + b; };
cout << func(2, 3) << endl; // 5
func = [](int a, int b) { return a * b; };
cout << func(2, 3) << endl; // 6
六、三大实际应用场景(学会这些就够用了)
std::function在实际开发中99%的使用场景都是这三个,我用最简单的例子演示:
场景1:回调函数(最常用)
回调函数就是"你写一个函数,交给别人,别人在合适的时候调用它"。这是GUI、网络编程、异步编程的基础。
cpp
#include <iostream>
#include <functional>
using namespace std;
// 定义一个按钮类
class Button {
public:
// 定义回调函数类型:无参数无返回值
using ClickCallback = function<void()>;
// 设置回调函数
void setOnClick(ClickCallback callback) {
m_callback = callback;
}
// 模拟按钮被点击
void click() {
if (m_callback) {
m_callback(); // 调用用户设置的回调函数
}
}
private:
ClickCallback m_callback;
};
int main() {
Button btn;
// 设置回调:点击按钮时打印消息
btn.setOnClick([]() {
cout << "按钮被点击了!" << endl;
});
// 模拟用户点击按钮
btn.click(); // 输出:按钮被点击了!
// 可以随时更换回调
btn.setOnClick([]() {
cout << "新的点击事件!" << endl;
});
btn.click(); // 输出:新的点击事件!
return 0;
}
场景2:策略模式
同一个功能,可以通过传入不同的函数实现不同的行为。
cpp
#include <iostream>
#include <vector>
#include <functional>
using namespace std;
// 计算数组元素的总和,接收一个"过滤策略"
int calculateSum(const vector<int>& vec, function<bool(int)> filter) {
int sum = 0;
for (int num : vec) {
if (filter(num)) { // 只有符合过滤条件的元素才被累加
sum += num;
}
}
return sum;
}
int main() {
vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 策略1:计算所有元素的和
int total = calculateSum(vec, [](int) { return true; });
cout << "所有元素的和:" << total << endl; // 55
// 策略2:只计算偶数的和
int evenSum = calculateSum(vec, [](int num) { return num % 2 == 0; });
cout << "偶数的和:" << evenSum << endl; // 30
// 策略3:只计算大于5的元素的和
int bigSum = calculateSum(vec, [](int num) { return num > 5; });
cout << "大于5的元素的和:" << bigSum << endl; // 40
return 0;
}
场景3:函数表(查表调用)
把多个函数放进一个map或vector里,通过key来查找并调用对应的函数。
cpp
#include <iostream>
#include <unordered_map>
#include <functional>
using namespace std;
int main() {
// 创建一个计算器函数表
unordered_map<string, function<int(int, int)>> calculator = {
{"+", [](int a, int b) { return a + b; }},
{"-", [](int a, int b) { return a - b; }},
{"*", [](int a, int b) { return a * b; }},
{"/", [](int a, int b) { return a / b; }}
};
// 使用函数表
cout << "5 + 3 = " << calculator["+"](5, 3) << endl; // 8
cout << "5 * 3 = " << calculator["*"](5, 3) << endl; // 15
cout << "10 / 2 = " << calculator["/"](10, 2) << endl; // 5
// 可以动态添加新的运算
calculator["%"] = [](int a, int b) { return a % b; };
cout << "7 % 3 = " << calculator["%"](7, 3) << endl; // 1
return 0;
}
七、常见误区与最佳实践
1. ❌ 不要把std::function和Lambda混淆
- Lambda是匿名函数对象,是一种可调用对象
std::function是容器,可以装Lambda,也可以装其他可调用对象
2. ✅ 什么时候用std::function?
- 当你需要存储可调用对象的时候
- 当你需要把可调用对象作为函数参数传递的时候
- 当你需要把不同类型的可调用对象统一处理的时候
3. ❌ 不要过度使用std::function
如果只是在当前函数内临时使用一个可调用对象,直接用auto接收Lambda即可,不需要用std::function:
cpp
// ✅ 正确:临时使用,用auto
auto add = [](int a, int b) { return a + b; };
cout << add(2, 3) << endl;
// ❌ 没必要:增加了不必要的开销
function<int(int, int)> add = [](int a, int b) { return a + b; };
八、总结
- 本质 :
std::function是一个万能函数盒子,可以装任何可调用对象 - 核心价值:统一了所有可调用对象的类型,让它们可以被统一存储、传递和调用
- 基本用法 :
function<返回值(参数类型)> 变量名 = 可调用对象; - 三大场景:回调函数、策略模式、函数表
- 最佳实践 :需要存储/传递可调用对象时用
std::function,临时使用用auto