【C++篇】引领C++模板初体验:泛型编程的力量与妙用

news2024/9/23 7:14:25

文章目录

  • C++模板编程
    • 前言
      • 第一章: 初始模板与函数模版
        • 1.1 什么是泛型编程?
          • 1.1.1 为什么要有泛型编程?
          • 1.1.1 泛型编程的优势
        • 1.2 函数模板的基础
          • 1.2.1 什么是函数模板?
          • 1.2.2 函数模板的定义格式
          • 1.2.3 示例:通用的交换函数
            • 输出示例:
          • 1.2.4 模板中的`typename`与`class`
        • 1.3 函数模板的原理
          • 1.3.1 函数模板的实例化
          • 1.3.2 隐式实例化与显式实例化
      • 第二章: 类模板
        • 2.1 类模板概念
          • 2.1.1 类模板的定义格式
          • 2.1.2 示例:简单的类模板
            • 输出示例:
        • 2.2 类模板的实例化
        • 2.3 类模板中的成员函数定义
        • 2.4 为什么不建议类模板的定义和声明分离?
          • 2.4.1 模板的编译时行为
          • 2.4.2 链接器无法找到定义
          • 2.4.3 无法预编译模板
          • 2.4.4 解决方案:将声明和定义放在同一个头文件中
      • 第三章: 模板的匹配原则
        • 3.1 模板的匹配原则
  • 写在最后

C++模板编程

💬 欢迎讨论:如果你在学习过程中有任何问题或想法,欢迎在评论区留言,我们一起交流学习。你的支持是我继续创作的动力!

👍 点赞、收藏与分享:觉得这篇文章对你有帮助吗?别忘了点赞、收藏并分享给更多的小伙伴哦!你们的支持是我不断进步的动力!
🚀 分享给更多人:如果你觉得这篇文章对你有帮助,欢迎分享给更多对C++感兴趣的朋友,让我们一起进步!

前言

C++作为一门强大的编程语言,以其丰富的功能和灵活的设计著称。模板编程是C++中非常重要的一个特性,通过模板可以实现泛型编程,编写与数据类型无关的代码,极大地提高了代码的复用性和可维护性。本文将从泛型编程、函数模板、类模板等几个方面详细讲解C++模板的使用,并结合实际的代码示例进行分析,帮助大家全面掌握模板编程的知识。

本篇文章将包含以下几个部分:

  1. 泛型编程的基本概念
  2. 函数模板的定义与使用
  3. 类模板的实现
  4. 模板的匹配原则

通过阅读本文,你将能够掌握C++模板编程的基础知识,理解其背后的工作原理,并学会如何在实际项目中应用这些技术。


第一章: 初始模板与函数模版

1.1 什么是泛型编程?

泛型编程(Generic Programming)是C++中的一种编程范式,旨在编写与数据类型无关的通用代码。这意味着你可以编写一次代码,并通过不同的数据类型进行复用。C++通过模板(Template)来实现泛型编程,模板是泛型编程的核心工具。

1.1.1 为什么要有泛型编程?
  • 问题提出:如何实现一个通用的交换函数呢?
void Swap(int& left, int& right)
{
    int temp = left;
    left = right;
    right = temp;
}
void Swap(double& left, double& right)
{
    double temp = left;
    left = right;
    right = temp;
}
void Swap(char& left, char& right)
{
    char temp = left;
    left = right;
    right = temp;
}
//....等等

使用函数重载虽然可以实现,但是有一下几个不好的地方:

  1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
  2. 代码的可维护性比较低,一个出错可能所有的重载均出错

那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?

在这里插入图片描述
如果在C++中,也能够存在这样一个模具,通过给这个模具中填充不同材料(类型),来获得不同
材料的铸件(即生成具体类型的代码),那将会节省许多头发。巧的是前人早已将树栽好,我们只需在此乘凉。这就是泛型编程

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础

1.1.1 泛型编程的优势

泛型编程的主要优势包括:

  • 代码复用性强:通过模板,你可以避免为每个数据类型单独编写相同功能的代码。
  • 提高代码的可维护性:代码只需编写一次,减少了冗余代码,后续如果需要修改或修复,只需在一处进行。
  • 减少编写错误:重复编写代码时容易出错,而模板可以让编译器自动生成所需代码,减少人为失误。

在这里插入图片描述


1.2 函数模板的基础
1.2.1 什么是函数模板?

函数模板(Function Template)是一个与类型无关的函数“蓝图”。通过模板参数,编译器在编译期间会根据实际的数据类型生成相应的函数版本。

1.2.2 函数模板的定义格式

我们可以通过以下格式来定义一个函数模板:

template<typename T> 
返回类型 函数名(参数列表) {
    // 函数体
}
  • template:告诉编译器接下来的内容是模板。
  • typename T:定义一个模板参数T,可以用来表示任何类型。
  • 返回类型参数列表可以使用T作为数据类型。

注意:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替
class),下文会讲到

1.2.3 示例:通用的交换函数

那我们就可以使用模板来编写交换函数,它可以交换任意类型的数据:

/**
 * @brief 通用的交换函数
 * @tparam T 通用的类型参数,由编译器根据实参推断
 * @param left 左侧变量
 * @param right 右侧变量
 */
template<typename T>
void Swap(T& left, T& right) {
    T temp = left;
    left = right;
    right = temp;
}

int main() {
    int a = 10, b = 20;
    double x = 1.1, y = 2.2;
    char c1 = 'A', c2 = 'B';

    // 使用模板函数进行交换
    Swap(a, b);    
    Swap(x, y);    
    Swap(c1, c2);  

    std::cout << "交换后的整数: " << a << " " << b << std::endl;
    std::cout << "交换后的浮点数: " << x << " " << y << std::endl;
    std::cout << "交换后的字符: " << c1 << " " << c2 << std::endl;

    return 0;
}
输出示例:
交换后的整数: 20 10
交换后的浮点数: 2.2 1.1
交换后的字符: B A
1.2.4 模板中的typenameclass

在定义模板时,typenameclass是可以互换的。你可以选择以下两种方式:

template<typename T>  // 使用 typename
template<class T>     // 使用 class

虽然两者功能相同,但推荐使用typename,因为它能够更好地表达该参数是一个类型参数,避免与类的定义产生混淆。


1.3 函数模板的原理

函数模板的核心在于它不是一个真正的函数,而是一个编译器用来生成特定类型函数的蓝图。编译器根据模板的使用情况推导出具体的类型,并生成相应的代码。这一过程称为模板的实例化

1.3.1 函数模板的实例化

当我们调用模板函数时,编译器会根据实际的参数类型生成对应的函数版本。比如:

template<class T>
T Add(const T& left, const T& right) {
    return left + right;
}

int main() {
    int a = 10, b = 20;
    double x = 1.1, y = 2.2;

    Add(a, b);  // 生成 Add<int> 版本
    Add(x, y);  // 生成 Add<double> 版本

    return 0;
}

在这里插入图片描述

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应
类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,
将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此。

1.3.2 隐式实例化与显式实例化
template<class T>
    T Add(const T& left, const T& right)
{
    return left + right;
}
int main()
{
    int a1 = 10, a2 = 20;
    double d1 = 10.0, d2 = 20.0;
    Add(a1, a2);
    Add(d1, d2);

    Add(a1, d1);
    /*
该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型
通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有
一个T,
编译器无法确定此处到底该将T确定为int 或者 double类型而报错
注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要
背黑锅

*/
    // 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化
    Add(a1, (int)d1);//自己强制转换
    return 0;
}

模板实例化分为两种:

  1. 隐式实例化:编译器根据实参推导出模板参数,并自动生成函数。例如上面的Add函数就是隐式实例化。
  2. 显式实例化:如果想要强制指定模板参数,可以使用显式实例化:

就是第二种处理方式

int main(void)
{
    int a = 10;
    double b = 20.0;
    // 显式实例化
    Add<int>(a, b);
    return 0;
};

第二章: 类模板

2.1 类模板概念

类模板(Class Template)是用于定义与类型无关的类,它允许我们在类的定义中使用模板参数,编译时再根据实际类型进行类的实例化。类模板与函数模板类似,只不过它是用来生成类的。

2.1.1 类模板的定义格式

定义类模板的语法格式如下:

template<class T>
class 类名 {
    // 类的成员变量和方法
};
  • template<class T>:告诉编译器这是一个模板类,T是一个类型参数。
  • 类的成员和方法可以使用T作为数据类型,编译时由用户提供的类型来替代T
2.1.2 示例:简单的类模板

下面是一个简单的栈(Stack)类模板,用于存储任意类型的数据:

#include<iostream>

using namespace std;

/**
 * @brief 栈的类模板
 * @tparam T 通用的类型参数
 */
template<class T>
class Stack {
public:
    Stack(size_t capacity = 4) {
        _array = new T[capacity];
        _capacity = capacity;
        _size = 0;
    }

    ~Stack() {
        delete[] _array;
    }

    /// @brief 将元素压入栈中
    void Push(const T& data) {
        if (_size == _capacity) {
            Expand();  // 扩容
        }
        _array[_size++] = data;
    }

    /// @brief 弹出栈顶元素
    void Pop() {
        if (_size > 0) {
            --_size;
        }
    }

    /// @brief 返回栈顶元素
    T& Top() {
        if (_size > 0) {
            return _array[_size - 1];
        }
        throw out_of_range("栈为空!");
    }

    /// @brief 检查栈是否为空
    bool IsEmpty() const {
        return _size == 0;
    }

    /// @brief 获取栈中元素的数量
    size_t Size() const {
        return _size;
    }

private:
    T* _array;
    size_t _capacity;
    size_t _size;

    /// @brief 扩展栈的容量
    void Expand() {
        size_t newCapacity = _capacity * 2;
        T* newArray = new T[newCapacity];
        for (size_t i = 0; i < _size; ++i) {
            newArray[i] = _array[i];
        }
        delete[] _array;
        _array = newArray;
        _capacity = newCapacity;
    }
};

int main() {
    Stack<int> st;  // 创建存储整数的栈
    st.Push(10);
    st.Push(20);
    st.Push(30);

    cout << "栈顶元素: " << st.Top() << endl;
    st.Pop();
    cout << "弹出后栈顶元素: " << st.Top() << endl;

    return 0;
}
输出示例:
栈顶元素: 30
弹出后栈顶元素: 20

在这个类模板中,T是一个通用类型参数。Stack<int>Stack类模板的一个实例化,表示它是一个存储int类型数据的栈。编译器会根据实际使用的类型自动生成相应的类。

2.2 类模板的实例化

与函数模板不同,类模板在使用时必须显示地提供类型参数。实例化类模板时,必须在类名后面的尖括号<>中指定实际的类型参数。例如:

Stack<int> st1;    // 实例化为处理 int 类型的栈
Stack<double> st2; // 实例化为处理 double 类型的栈

这里的Stack<int>Stack<double>分别表示不同的类型,即不同的模板实例。编译器会根据模板参数生成相应的类代码。

2.3 类模板中的成员函数定义

对于类模板,成员函数可以在类定义内或定义外实现。类模板的成员函数定义外置时,需要在函数名之前加上模板声明和模板参数。例如:

template<class T>
void Stack<T>::Push(const T& data) {
    if (_size == _capacity) {
        Expand();  // 扩容
    }
    _array[_size++] = data;
}
2.4 为什么不建议类模板的定义和声明分离?

在C++中,类模板的实现与普通类有一个显著的区别:模板是在编译时根据实际类型实例化的,而不是像普通的类那样在编译期和链接期处理。这导致了一个很重要的问题:如果将类模板的声明和定义分离到不同的文件中,可能会导致链接错误。以下是详细原因:

2.4.1 模板的编译时行为

类模板的本质是一个“蓝图”,它并不是一个完整的类,而是一个在需要时根据实际类型生成代码的模式。因此,模板只有在实际使用(实例化)时,编译器才会生成对应的类型的代码。编译器无法预先知道你会使用哪些类型来实例化模板,因此它不会为模板生成实际的代码。

2.4.2 链接器无法找到定义

当你将类模板的声明放在头文件中,而把定义放在.cpp文件中时,模板实例化的过程可能发生在不同的编译单元中。因为模板只有在编译期被实例化,链接器在链接时无法看到模板的定义,除非在编译时所有模板的实例化代码都可见。如果定义在.cpp文件中,其他使用模板的编译单元无法找到这个定义,导致链接器报错

2.4.3 无法预编译模板

与普通类不同,类模板无法被预编译或只在一个编译单元中定义然后供其他单元使用。普通的类在编译过程中,编译器会生成目标代码并储存在.obj文件中,链接时其他编译单元可以引用这些已生成的代码。而类模板无法这样做,因为它需要知道使用时的类型才能生成实际的代码。

2.4.4 解决方案:将声明和定义放在同一个头文件中

为了避免上述问题,C++的惯用方法是将类模板的声明和定义都放在同一个头文件中。这使得每个使用模板的编译单元在实例化模板时,编译器能够访问到模板的定义,并根据需要生成实际的代码。这种方式确保了编译器能够在编译期处理模板的实例化,而不会在链接时出现找不到定义的问题。

错误用法:

// Stack.h
template<typename T>
class Stack {
public:
    void Push(const T& value);
    // 声明在头文件中
};

// Stack.cpp
template<typename T>
void Stack<T>::Push(const T& value) {
    // 定义在.cpp文件中
}

在这种情况下,如果不同编译单元使用了Stack<int>Stack<double>,链接器可能会报错,因为它无法找到模板的定义。

正确用法:

// Stack.h
template<typename T>
class Stack {
public:
    void Push(const T& value);
};

template<typename T>
void Stack<T>::Push(const T& value) {
    // 声明和定义都在头文件中
}

这种方法确保每个编译单元都能访问到模板的完整定义,避免链接时的错误。

总结:

类模板的代码只有在实例化时才生成,因此类模板的定义必须在每个使用它的编译单元中可见。将模板的声明和定义放在同一个头文件中,可以确保模板实例化时能够访问到其定义,避免链接错误。这也是为什么大多数C++开发者在编写模板时会将模板的实现放在头文件中的原因。

注意:

函数模板与类模板不同,当代大多数编译器支持函数模板的声明和定义分离,这是因为函数模板的实例化往往只涉及函数的具体调用,不像类模板这么复杂,具体之后的博客会更详细的讲解此处的内容,敬请期待哦💕


第三章: 模板的匹配原则

3.1 模板的匹配原则

C++编译器在调用模板时,会根据实参类型和函数参数类型进行匹配。模板的匹配规则如下:

  1. 优先调用非模板函数:如果存在一个与实参完全匹配的非模板函数,编译器将优先调用非模板函数,而不是通过模板生成一个实例。

    例如:

    int Add(int a, int b) {
        return a + b;
    }
    
    template<typename T>
    T Add(T a, T b) {
        return a + b;
    }
    
    int main() {
        int a = 10, b = 20;
        cout << Add(a, b) << endl;  // 调用非模板版本
    }
    
  2. 如果非模板函数没有匹配,则调用模板实例:如果模板函数比非模板函数更能匹配参数类型,编译器将生成模板实例。

    例如:

    double Add(double a, double b) {
        return a + b;
    }
    
    template<typename T>
    T Add(T a, T b) {
        return a + b;
    }
    
    int main() {
        int a = 10, b = 20;
        double x = 1.1, y = 2.2;
    
        cout << Add(a, b) << endl;  // 调用模板实例 Add<int>
        cout << Add(x, y) << endl;  // 调用非模板版本 Add(double, double)
    }
    

写在最后

本文基础的讲解了C++模板编程的基础知识,涵盖了泛型编程、函数模板、类模板、模板匹配原则等概念。通过这些模板功能,C++开发者可以编写更加灵活和可复用的代码,大幅提高编程效率。

模板编程虽然强大,但使用时也需要谨慎,尤其是在处理模板特化和匹配规则时。如果能够合理地使用模板技术,相信你的代码质量将会有显著提升。


以上就是关于【C++篇】引领C++模板初体验:泛型编程的力量与妙用的内容啦,在之后会有另一篇博客来讲解有关模板的更多进阶内容,敬请期待哦,然后各位大佬有什么问题欢迎在评论区指正,或者私信我也是可以的啦,您的支持是我创作的最大动力!❤️

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2156951.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【网络】高级IO——Reactor版TCP服务器

目录 1.什么是Reactor 1.1.餐厅里的Reactor模式 2.Reactor的由来 2.1.单 Reactor 单进程 / 线程 2.2.单 Reactor 多线程 / 多进程 2.3.多 Reactor 多进程 / 线程 3.实现单 Reactor 单进程版本的TCP服务器 3.1.Connection类 3.2.TcpServer类 3.3.Connection的真正用处 …

深蓝学院-- 量产自动驾驶中的规划控制算法 小鹏

文章目录 0. 前言1.发展现状2.行车功能中难点问题及解决思路问题1&#xff1a;车道居中辅助&#xff0c;画龙&#xff0c;蛇行问题。问题2&#xff1a;外界环境扰动以及传感器信息缺失下的横向控制难点问题3&#xff1a;大坡度平稳停车 3. 泊车功能中难点问题及解决思路问题1&a…

Spring AOP - 配置文件方式实现

目录 AOP基础概念 示例1&#xff1a;模拟在com.text包及子包项下所有类名称以ServiceImpl结尾的类的所有方法执行前、执行后、执行正常后返回值、执行过程中出异常的情况 示例2&#xff1a;统计com.text包及子包项下所有类名称以DaoImpl结尾的类的所有方法执行时长情况 AOP基…

汽车总线之---- CAN FD总线

CAN FD 最高可支持8M/s的通信速率&#xff0c;从传统CAN到CAN FD的转换是很容易实施和推广的。 CAN FD报文的帧&#xff1a;标准帧&#xff0c;扩展帧 CAN FD 标准帧结构 CAN FD 报文的标准帧与CAN 报文的标准帧的区别 CAN FD 报文的标准帧与CAN FD报文的扩展帧的区别&…

lsof可以查看当前系统中正在被使用的文件,包括动态库

lsof的英文是 list open files lsof打印结果的最后一列是Name&#xff0c;表示正在被使用或打开的文件名或动态库名 lsof直接回车&#xff0c;会显示很多&#xff0c;可以配合more命令查看 一个文件或动态库可能被多个进程打开&#xff0c;lsof会显示多行 lsof | more -1…

uniapp小程序持续获取用户位置信息,后台位置获取

做一个小程序持续获取用户位置信息的功能&#xff0c;即使小程序切换到后台也能继续获取&#xff0c;getLocation这个api只有小程序在前台才能获取位置&#xff0c;所以不用这个 先申请一个腾讯地图key 在uniapp项目配置源码视图里加上这个代码 先获取权限&#xff0c;再开启…

[项目:微服务即时通讯系统客户端(基于C++QT)]三,左侧界面搭建

三&#xff0c;左侧界面搭建 一&#xff0c;导入 先把MainWidget类做成“单例类” 采用的是单例模式&#xff0c;让某一个类&#xff0c;在指定进程中只有唯一的实例 先看一下MainWidget的框架 QWidget//这部分是头文件保护宏&#xff0c;确保该头文件只被包含一次&#x…

240922-chromadb的基本使用

A. 背景介绍 ChromaDB 是一个较新的开源向量数据库&#xff0c;专为高效的嵌入存储和检索而设计。与其他向量数据库相比&#xff0c;ChromaDB 更加专注于轻量化、简单性和与机器学习模型的无缝集成。它的核心目标是帮助开发者轻松管理和使用高维嵌入向量&#xff0c;特别是与生…

【软件工程】数据流图和数据字典

一、数据流图 3.符号 分析结果 二、数据字典 例题 选择题

使用build_chain.sh离线搭建匹配的区块链,并通过命令配置各群组节点的MySQL数据库

【任务】 登陆Linux服务器&#xff0c;以MySQL分布式存储方式安装并部署如图所示的三群组、四机构、 七节点的星形组网拓扑区块链系统。其中&#xff0c;三群组名称分别为group1、group2和group3&#xff0c; 四个机构名称为agencyA、agencyB、agencyC、agencyD。p2p_port、cha…

Python | Leetcode Python题解之第429题N叉树的层序遍历

题目&#xff1a; 题解&#xff1a; class Solution:def levelOrder(self, root: Node) -> List[List[int]]:if not root:return []ans list()q deque([root])while q:cnt len(q)level list()for _ in range(cnt):cur q.popleft()level.append(cur.val)for child in c…

爬虫过程 | 蜘蛛程序爬取数据流程(初学者适用)

蜘蛛程序&#xff08;也称网络爬虫&#xff0c;是搜索引擎的重要组成部分&#xff09; 主要功能&#xff1a;遍历互联网&#xff0c;抓取网站信息并建立索引&#xff0c;便于用户在搜索引擎中检索到最新的网页内容工作原理&#xff1a;从初始网站页面的URL开始&#xff0c;发送…

qt-C++笔记之Q_DECLARE_METATYPE和qRegisterMetaType

qt-C笔记之Q_DECLARE_METATYPE和qRegisterMetaType code review! 文章目录 qt-C笔记之Q_DECLARE_METATYPE和qRegisterMetaType一.Q_DECLARE_METATYPE使用方法应用场景 二.为什么需要注册类型&#xff1f;三.使用 Q_DECLARE_METATYPE 处理自定义类型的简短示例3.1.自定义类型定…

《独孤九剑》游戏源码(客户端+服务端+数据库+游戏全套源码)大小2.38G

《独孤九剑》游戏源码&#xff08;客户端服务端数据库游戏全套源码&#xff09;大小2.38G ​ 下载地址&#xff1a; 通过网盘分享的文件&#xff1a;【源码】《独孤九剑》游戏源码&#xff08;客户端服务端数据库游戏全套源码&#xff09;大小2.38G 链接: https://pan.baidu.co…

生信服务器 | 组蛋白甲基化修饰、DNA亲和纯化测序、优青博导团队指导设计、解读实验结果。

查看原文>>>生信服务器 | 组蛋白甲基化修饰、DNA亲和纯化测序、优青博导团队免费指导设计、解读实验结果、一台服务器解决您所有的分析困扰!

VLDB 2024 圆桌会议回顾:展望物联网与 AI 时代的时序数据库

回顾我们在 VLDB 2024 8 月 26 日至 8 月 30 日&#xff0c;数据库领域的顶级国际会议 VLDB 2024 在广州举行。IoTDB 最新研发成果的三篇论文被本次大会录用&#xff08;详见&#xff1a;IoTDB 在顶级会议 VLDB 2024&#xff1a;四篇最新论文入选&#xff0c;特邀做 TPC 报告与…

6.7泊松噪声

基础概念 在OpenCV联合C中给一张图片添加泊松噪声&#xff08;Poisson Noise&#xff09;可以通过生成随机数并在图像的每个像素上加上这些随机数来实现。泊松噪声是一种统计分布服从泊松分布的噪声&#xff0c;通常用于模拟光子计数等场景。 使用泊松噪声的场景 泊松噪声通…

【记录】Excel|不允许的操作:合并或隐藏单元格出现的问题列表及解决方案

人话说在前&#xff1a;这篇的内容是2022年5月写的&#xff0c;当时碰到了要批量处理数据的情况&#xff0c;但是又不知道数据为啥一直报错报错报错&#xff0c;说不允许我操作&#xff0c;最终发现是因为存在隐藏的列或行&#xff0c;于是就很无语地写了博客&#xff0c;但内容…

Codeforces Round 972 (Div. 2) E2. Subtangle Game (Hard Version)(博弈+双指针 sg函数思想)

题目 思路来源 稲葉廻代码 题解 这个题比easy version的数据范围大了比较多&#xff0c; 不能再直接dp[i][j][k]表示数组a的第i个做开始局面时&#xff0c;位置(j,k)为起点时的获胜情况了 当然你把第一维压到bitset里&#xff0c;然后前缀和优化一下&#xff0c;还是可以通…

中序遍历二叉树全过程图解

文章目录 中序遍历图解总结拓展&#xff1a;回归与回溯 中序遍历图解 首先看下中序遍历的代码&#xff0c;其接受一个根结点root作为参数&#xff0c;判断根节点是否为nil&#xff0c;不为nil则先递归遍历左子树。 func traversal(root *TreeNode,res *[]int) {if root nil …