15. c++基础知识回顾(4)

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*

五、函数模板重载

  1. 模板与普通函数重载:普通函数优先级更高,匹配时优先调用普通函数
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;
}
  1. 多个不同参数列表的模板互相重载
cpp 复制代码
template <typename T>
void Show(T a);
template <typename T>
void Show(T a, T b);

六、核心特性与限制

  1. 编译期实例化
    模板本身不生成机器码,只有代码中调用该模板、传入具体类型时,才会生成对应类型的函数;没被调用的模板不会编译。
  2. 类型必须支持模板内操作
    模板内部用到的运算符/函数,传入的类型必须实现:
cpp 复制代码
template <typename T>
void Func(T a) { a.show(); }
// 调用Func<int>会报错,int没有show()成员函数
  1. 模板参数区分类型参数 (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;

六、模板类实例化规则

  1. 模板本身不生成代码 ,只有你写明 类名<类型> 时,编译器才生成对应实体类(实例化);
  2. Stack<int>Stack<double>两个完全无关的独立类,内存布局、成员互不干扰;
  3. 类模板的成员函数是延迟实例化:只有调用到该函数,编译器才会生成函数代码,不调用则不编译。

七、类模板特化(特殊版本)

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

九、关键易错点

  1. 类外实现成员函数,必须带上 template<typename T>类名<T>::
  2. 类模板不能做函数返回值类型推导,必须手动指定模板参数;
  3. 不同模板实参生成的类是完全独立类型,不能互相赋值;
  4. 模板参数仅在编译期生效,运行时不存在类型占位符T。

十、一句话总结

类模板用 template 定义通用数据结构,通过 <类型> 显式实例化生成不同类型的类;标准库所有容器底层都是类模板。


💡解答

C++模板非类型参数限制详解

一、先搞懂什么是非类型模板参数

模板参数分两类:

  1. 类型参数template<typename T> 里的T,代表一种数据类型;
  2. 非类型参数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++规则里是禁止的)

三、补充额外常见限制

  1. 传入的实参必须是编译期可求值的常量表达式,不能传普通局部变量:
cpp 复制代码
int n = 20;
Array<n> arr; // 非法,n是运行期变量,不是编译常量
  1. 局部变量指针没有外部链接,不能作为非类型参数:
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>


最终总结

  1. 第一段❌:非类型参数不能是double浮点型;
  2. 第二段❌:非类型参数不能是string类类型;
  3. 第三段❌:const全局数组默认内部链接,不符合指针非类型参数的外部链接要求;
  4. 第四段✅: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和改进方向。

一、整体概览

这是一个模板化的单链表实现,核心包含两个类:

  1. ListNode<T>:链表的节点类,存储数据和指向下一个节点的指针
  2. List<T>:链表主类,提供头尾插入、打印等功能
  3. 亮点 :在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_tailNULL,访问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 

五、关键知识点总结

  1. 单链表结构:每个节点包含数据和指向下一个节点的指针,头尾指针让头尾插入O(1)
  2. 模板类template <typename T>让代码可以处理任意数据类型
  3. 迭代器本质:对指针的封装,通过重载运算符实现指针行为
  4. 运算符重载
    • ++:实现迭代器向后移动
    • *:解引用获取元素值
    • ->:访问元素的成员
    • ==/!=:比较迭代器位置
  5. STL约定begin()指向第一个元素,end()指向最后一个元素的下一个位置(左闭右开)

六、可以改进的地方

  1. 添加析构函数:防止内存泄漏,遍历链表释放所有节点
  2. 添加删除操作popFront()popBack()erase()
  3. 添加插入操作:在指定位置插入元素
  4. 添加拷贝构造函数和赋值运算符:防止浅拷贝导致的双重释放问题
  5. 添加const_iterator:支持const对象的遍历
  6. 异常安全:处理内存分配失败的情况

最终完善:添加拷贝构造与赋值运算符(彻底解决浅拷贝问题)

现在补充拷贝构造函数赋值运算符重载 ,采用工业级标准的拷贝并交换(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_headm_tail指针,导致两个链表对象指向同一块内存:

cpp 复制代码
List<int> list1;
list1.pushBack(1);
List<int> list2 = list1; // 浅拷贝:list1和list2共享同一块内存

list1list2销毁时,会两次释放同一块内存,导致程序崩溃。

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)

这是实现赋值运算符的工业级标准方法,具有三大优势:

  1. 自赋值安全:不会出现自己给自己赋值导致的内存释放问题
  2. 异常安全:如果拷贝过程中抛出异常,当前对象不会被修改
  3. 代码简洁:复用拷贝构造函数和析构函数,避免重复代码

执行流程

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级别的性能和完整性,还可以补充:

  1. 移动构造函数和移动赋值运算符:C++11特性,支持O(1)时间的对象转移
  2. const_iterator:支持const对象的只读遍历
  3. size():维护一个成员变量记录元素个数,O(1)返回
  4. front()/back():返回头尾元素的引用
  5. emplace系列函数:直接在容器内构造对象,避免拷贝

我来彻底给你讲清楚assert的一切,包括它的原理、实现、语法细节和使用技巧。

一、assert是什么?

assertC/C++标准库提供的一个调试宏 ,用于在开发阶段进行断言检查。它的核心作用是:

  • 调试模式 :如果括号内的表达式为false,程序会立即崩溃,并打印出断言失败的位置和信息
  • 发布模式:会被编译器完全优化掉,不产生任何代码,不影响程序性能

二、这句代码的详细解释

cpp 复制代码
assert(ptr != nullptr && "解引用空迭代器!");

这是C++中assert标准写法,我拆成两部分给你讲:

1. 核心逻辑:ptr != nullptr

这是我们要检查的条件:确保迭代器内部的指针ptr不是空指针

  • 如果ptr != nullptrtrue:断言通过,什么也不做
  • 如果ptr != nullptrfalse:断言失败,程序崩溃

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
关键说明:
  1. NDEBUG:控制assert是否生效

    • 调试模式(Debug):编译器不会定义NDEBUG,assert生效
    • 发布模式(Release):编译器会自动定义NDEBUG,assert被完全移除
  2. #expression:预处理运算符,把表达式转换成字符串字面量

  3. __FILE____LINE____func__:编译器内置宏,分别代表:

    • 当前文件名
    • 当前行号
    • 当前函数名
  4. __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,程序可能会出现莫名其妙的段错误,很难定位问题。

总结

  1. assert是C/C++标准库的调试宏,定义在<cassert>头文件中
  2. assert(条件 && "错误信息")是标准写法,用于添加自定义错误提示
  3. 字符串字面量可以包含中文,只要编译器支持UTF-8编码
  4. 调试模式下assert生效,发布模式下被完全优化掉
  5. 只用于检查内部错误,不要在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算法(如sortfind_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表达式让代码更简洁,不需要单独定义一个类,直接在使用的地方定义函数逻辑。

六、函数对象的本质总结

  1. 底层原理 :函数对象的调用obj(a, b)会被编译器转换为obj.operator()(a, b)
  2. 核心价值:将"函数逻辑"作为"对象"传递,实现了行为的参数化
  3. 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; };

八、总结

  1. 本质std::function是一个万能函数盒子,可以装任何可调用对象
  2. 核心价值:统一了所有可调用对象的类型,让它们可以被统一存储、传递和调用
  3. 基本用法function<返回值(参数类型)> 变量名 = 可调用对象;
  4. 三大场景:回调函数、策略模式、函数表
  5. 最佳实践 :需要存储/传递可调用对象时用std::function,临时使用用auto