
一:C++泛型编程和模板
1:C++模板概述
定义
模板是C++支持参数化多态的工具,它允许程序员编写一段代码,使其能够处理多种不同类型的数据,而无需为每种类型都重复编写相似的代码逻辑。
分类:主要分为函数模板和类模板:
函数模板:定义了一个通用的函数形式,可以根据不同的类型参数生成具体的函数实例。例如:
template <typename T>T add(T a, T b){return a + b;}
上述代码定义了一个函数模板add,它可以对任意支持+操作的同类型T的两个参数进行相加操作。当我们在代码中使用add(5, 3)(此时T被推导为 int)或者add(3.14, 2.71)(此时T被推导为double)时,编译器会根据实际传入的参数类型自动生成对应的具体函数版本。
类模板:用于创建类的通用模板,类中的成员变量和成员函数可以基于模板参数类型来定义。示例如下:
template <typename T>class MyVector {private:T* data;size_t size;public:MyVector(size_t n) : size(n) {data = new T[n];}~MyVector() {delete[] data;}voidpush_back(T element){// 这里假设实现了向数组中添加元素等逻辑(简单示意,实际可能更复杂)}};
这里定义了一个简单的类模板MyVector,它可以根据不同的类型T来创建不同元素类型的向量类,比如MyVector<int>就是存储int类型元素的向量类,MyVector<double>则是存储double类型元素的向量类。
2:C++泛型编程认识
概念
泛型编程是一种编程范式,它关注的是编写能够在多种数据类型上通用的算法和数据结构,而C++中的模板是实现泛型编程的核心机制。通过泛型编程,可以提高代码的复用性、灵活性以及类型安全性。
优势
代码复用:不用为每种类型都重写相似的逻辑,如排序算法可以写成模板形式,既能对int数组排序,也能对double数组排序等。
类型安全:编译器会基于模板实例化时的具体类型进行类型检查,减少类型相关的错误,比如在上面add函数模板中,如果传入的两个参数类型不一致且不能隐式转换,编译器就会报错。
3:强类型语言与C++模板
强类型语言特点
C++是强类型语言,意味着在编译时,每个变量和表达式都有明确的类型,并且编译器会严格检查类型的一致性和合法性。例如,不能直接将int类型的值赋给double类型的变量(除非进行显式的类型转换)。
C++模板与强类型的结合
C++模板在强类型语言的基础上发挥作用,它利用类型参数化来在保证类型安全的前提下实现通用的代码逻辑。在模板实例化过程中,编译器会根据传入的实际类型来生成符合强类型要求的代码。例如,对于函数模板add,如果传入的类型不符合+操作要求(比如尝试对两个自定义类进行相加,而该类没有定义+运算符重载),编译器会检测到并报错,体现了强类型的约束性以及模板的灵活性之间的良好结合。
4:实例代码及运行说明
以下是完整的示例代码,展示函数模板和类模板的使用以及如何运行它们:
#include<iostream>#include<string>// 函数模板示例template <typename T>T add(T a, T b){return a + b;}// 类模板示例template <typename T>class MyVector {private:T* data;size_t size;public:MyVector(size_t n) : size(n) {data = new T[n];}~MyVector() {delete[] data;}voidpush_back(T element){// 这里假设实现了向数组中添加元素等逻辑(简单示意,实际可能更复杂)}};intmain(){// 使用函数模板int result1 = add(5, 3);std::cout << "整数相加结果: " << result1 << std::endl;double result2 = add(3.14, 2.71);std::cout << "浮点数相加结果: " << result2 << std::endl;// 使用类模板MyVector<int> vec(2);vec.push_back(5);// 这里只是简单示意类模板对象的使用,实际还可以添加更多完善的操作return 0;}
运行结果:

二:函数模板
1:函数模板的定义
函数模板是一种通用的函数定义形式,它使用模板参数来表示类型,使得函数可以针对不同的数据类型进行操作,而无需为每种类型重复编写相似的函数代码。
其基本语法格式如下:
template <typename 模板参数名1, typename 模板参数名2,...>返回类型 函数名(参数列表) {// 函数体逻辑}
例如,下面定义了一个简单的函数模板用于比较两个值的大小并返回较大值:
template <typename T>T maxValue(T a, T b) {return (a > b)? a : b;}
这里template关键字表示开始定义模板,<typename T>声明了一个名为T的类型模板参数,函数maxValue可以根据传入的实际类型(如int、double等)来实例化具体的函数版本。
2:函数模板的使用
函数模板在使用时,编译器会根据传入的实际参数类型自动推导模板参数类型来实例化相应的具体函数。例如:
#include<iostream>template <typename T>T maxValue(T a, T b){return (a > b) ? a : b;}intmain(){int num1 = 5, num2 = 3;double num3 = 3.14, num4 = 2.71;// 编译器自动推导 T 为 int 类型,实例化出对应的 int 版本函数int result1 = maxValue(num1, num2);std::cout << "整数比较结果: " << result1 << std::endl;// 编译器自动推导 T 为 double 类型,实例化出对应的 double 版本函数double result2 = maxValue(num3, num4);std::cout << "浮点数比较结果: " << result2 << std::endl;return 0;}
运行结果:

3:重载函数模板
可以对函数模板进行重载,即定义多个同名但模板参数或函数参数列表不同的函数模板。例如:
#include<iostream>#include<string>// 基础的函数模板template <typename T>T add(T a, T b){return a + b;}// 重载的函数模板,处理不同类型相加(比如一个 int 和一个 double)template <typename T1, typename T2>autoadd(T1 a, T2 b){return static_cast<decltype(a + b)>(a + b);}// 针对字符串的特殊重载函数模板(连接字符串)template <typename T>std::string add(const std::string& a, const std::string& b){return a + b;}intmain(){int num1 = 5, num2 = 3;double num3 = 5.12;std::string str1 = "Hello", str2 = " World";// 调用第一个函数模板实例(处理同类型 int)int result1 = add(num1, num2);std::cout << "整数相加结果: " << result1 << std::endl;// 调用重载的函数模板实例(处理不同类型 int 和 double)double result2 = add(num1, num3);std::cout << "不同类型相加结果: " << result2 << std::endl;// 调用针对字符串的重载函数模板实例std::string result3 = add(str1, str2);std::cout << "字符串连接结果: " << result3 << std::endl;return 0;}
运行结果:

三:类模板
1:类模板的定义与声明
类模板允许定义一个通用的类结构,其成员变量和成员函数可以基于模板参数类型来定义,语法如下:
定义形式:
template <typename 模板参数名1, typename 模板参数名2,...>class 类名 {// 类成员变量、成员函数定义等};
例如,定义一个简单的类模板表示一个可以存储不同类型元素的容器类(类似简化版的vector):
template <typename T>class MyContainer {private:T* elements;size_t size;public:MyContainer(size_t n) : size(n) {elements = new T[n];}~MyContainer() {delete[] elements;}voidpush_back(T element){// 这里假设实现简单的添加元素逻辑(实际可能更复杂)}};
类模板的声明与定义类似,只是通常在头文件中可以先声明类模板,然后在对应的.cpp文件中进行定义(实现成员函数等),如下是声明示例:
template <typename T>classMyContainer;
2:简单类模板实例
使用类模板时,需要指定具体的模板参数类型来实例化类对象,例如:
#include<iostream>template <typename T>class MyContainer {private:T* elements;size_t size;public:MyContainer(size_t n) : size(n) {elements = new T[n];std::cout << "执行:构造函数." << std::endl;}~MyContainer() {delete[] elements;std::cout << "执行:析构函数." << std::endl;}voidpush_back(T element){// 这里假设实现简单的添加元素逻辑(实际可能更复杂)}};intmain(){// 实例化 MyContainer 类模板为存储 int 类型元素的类MyContainer<int> container(5);container.push_back(10);// 后续可以进行更多针对这个实例化类对象的操作return 0;}
运行结果:

3:默认模板参数
可以为类模板(也适用于函数模板)的模板参数提供默认值,这样在使用模板时,如果没有显式指定该参数的值,就会采用默认值。例如:
#include<iostream>template <typename T = int, size_t N = 10>class Array {private:T data[N];public:voidprint(){for (size_t i = 0; i < N; ++i) {std::cout << data[i] << " "<<std::endl;}std::cout << std::endl;}};intmain(){// 使用默认模板参数,T 为 int,N 为 10Array<> arr1;arr1.print();std::cout << std::endl;// 显式指定 T 为 double,N 仍为默认的 10Array<double> arr2;arr2.print();std::cout << std::endl;// 显式指定 T 为 char,N 为 5Array<char, 5> arr3;arr3.print();std::cout<< std::endl;return 0;}
运行结果:

4:为具体类型的参数提供默认值(针对函数模板情况示例)
在函数模板中也可以为具体类型的参数提供默认值,比如:
#include<iostream>template <typename T, typename U = int>T add(T a, U b = 0) {return a + b;}intmain(){int num1 = 5;// 这里 U 采用默认值 int,b 采用默认值 0int result1 = add(num1);std::cout << "结果 1: " << result1 << std::endl;double num2 = 3.14;int num3 = 2;// 显式指定 U 为 int,传入具体的 b 值double result2 = add(num2, num3);std::cout << "结果 2: " << result2 << std::endl;return 0;}
运行结果:

5:有界数组模板
有界数组模板可以用来创建具有固定大小且类型安全的数组类模板,示例如下:
#include<iostream>#include<cassert>template <typename T, size_t N>class BoundedArray {private:T data[N];public:BoundedArray() {}T& operator[](size_t index) {assert(index < N); // 检查索引是否越界return data[index];}const T& operator[](size_t index) const {assert(index < N);return data[index];}};intmain(){BoundedArray<int, 5> arr;for (size_t i = 0; i < 5; ++i) {arr[i] = i;}for (size_t i = 0; i < 5; ++i) {std::cout << arr[i] << " ";}std::cout << std::endl;// 以下语句会触发断言失败(越界访问)// arr[5] = 10;return 0;}
运行结果:

6:类模板的定制
类模板的定制通常指的是根据具体需求对类模板进行特化或者部分特化,以满足特定类型或特定情况的处理要求。
全特化:针对具体的模板参数类型,重新完整地定义类模板的实现,例如:
#include<iostream>// 基础类模板template <typename T>class MyTemplate {public:voidprint(){std::cout << "通用模板版本" << std::endl;}};// 全特化版本,针对 int 类型template <>class MyTemplate<int> {public:voidprint(){std::cout << "针对 int 类型的特化版本" << std::endl;}};intmain(){MyTemplate<double> t1;t1.print();MyTemplate<int> t2;t2.print();return 0;}
运行结果:

部分特化:只对部分模板参数进行特化指定,例如当模板有多个参数时,对其中某些参数进行特定类型的限定来实现不同的处理逻辑,以下是简单示例(假设类模板有两个类型参数):
#include<iostream>template <typename T1, typename T2>class PairTemplate {public:voidprint(){std::cout << "通用模板版本" << std::endl;}};// 部分特化,当第一个参数为 int 时的情况template <typename T2>class PairTemplate<int, T2> {public:voidprint(){std::cout << "第一个参数为 int 的部分特化版本" << std::endl;}};intmain(){PairTemplate<double, int> p1;p1.print();PairTemplate<int, char> p2;p2.print();return 0;}
运行结果:

四:链表类模板
1:链表的基本概念
定义
链表是一种常见的数据结构,它由一系列节点组成,每个节点包含数据部分以及指向下一个节点的指针(在单链表中)。通过这些节点之间的指针链接,形成了一个链式的存储结构,与数组不同,链表在内存中的存储不是连续的。
优点
动态性:可以方便地进行节点的插入和删除操作,不需要像数组那样提前分配固定大小的内存空间,能根据实际需求动态地增加或减少元素个数。
内存利用灵活:节点可以分散存储在内存中的不同位置,能更有效地利用内存,尤其是在处理元素个数不确定的数据集合时优势明显。
缺点
访问效率相对低:不像数组可以通过下标直接访问元素,链表需要从表头开始依次遍历节点才能访问到特定位置的元素,时间复杂度相对较高。
2:设计单链表
节点结构体定义
首先需要定义链表的节点结构体,它包含数据域和指向下一节点的指针域。示例代码如下:
template <typename T>struct ListNode {T data;ListNode* next;ListNode(const T& val) : data(val), next(nullptr) {}};
单链表类的基本设计
接下来设计单链表类,包含一些基本的操作函数,如插入节点、删除节点、遍历链表等。以下是一个简单的单链表类的定义示例(部分功能实现):
template <typename T>class SinglyLinkedList {private:ListNode<T>* head;public:SinglyLinkedList() : head(nullptr) {}// 在链表头部插入节点voidinsertAtHead(const T& value){ListNode<T>* newNode = new ListNode<T>(value);newNode->next = head;head = newNode;}// 遍历链表并输出节点数据voidtraverse(){ListNode<T>* current = head;while (current!= nullptr) {std::cout << current->data << " ";current = current->next;}std::cout << std::endl;}};
3:链表类模板的使用
实例化链表类模板
使用之前定义的链表类模板,需要指定具体的数据类型来实例化链表对象,例如:
#include<iostream>template <typename T>struct ListNode {T data;ListNode* next;ListNode(const T& val) : data(val), next(nullptr) {}};template <typename T>class SinglyLinkedList {private:ListNode<T>* head;public:SinglyLinkedList() : head(nullptr) {}// 在链表头部插入节点voidinsertAtHead(const T& value){ListNode<T>* newNode = new ListNode<T>(value);newNode->next = head;head = newNode;}// 遍历链表并输出节点数据voidtraverse(){ListNode<T>* current = head;while (current != nullptr) {std::cout << current->data << " ";current = current->next;}std::cout << std::endl;}};intmain(){SinglyLinkedList<int> myList;myList.insertAtHead(5);myList.insertAtHead(3);myList.insertAtHead(1);myList.traverse();return 0;}
更多操作及应用场景
除了上述基本操作外,还可以实现如在链表指定位置插入节点、删除指定位置节点、查找节点等功能,以满足不同的实际需求。例如,以下是添加在指定位置插入节点功能的代码示例:
#include<iostream>template <typename T>struct ListNode {T data;ListNode* next;ListNode(const T& val) : data(val), next(nullptr) {}};template <typename T>class SinglyLinkedList {private:ListNode<T>* head;public:SinglyLinkedList() : head(nullptr) {}// 在链表头部插入节点voidinsertAtHead(const T& value){ListNode<T>* newNode = new ListNode<T>(value);newNode->next = head;head = newNode;}// 在链表指定位置插入节点(假设位置从 0 开始计数)voidinsertAtPosition(int position, const T& value){if (position == 0) {insertAtHead(value);return;}ListNode<T>* current = head;for (int i = 0; i < position - 1 && current != nullptr; ++i) {current = current->next;}if (current != nullptr) {ListNode<T>* newNode = new ListNode<T>(value);newNode->next = current->next;current->next = newNode;}}// 遍历链表并输出节点数据voidtraverse(){ListNode<T>* current = head;while (current != nullptr) {std::cout << current->data << " ";current = current->next;}std::cout << std::endl;}};intmain(){SinglyLinkedList<int> myList;myList.insertAtHead(5);myList.insertAtHead(3);myList.insertAtPosition(1, 4);myList.traverse();return 0;}
运行结果:

4:类模板的静态数据成员
概念
类模板中的静态数据成员是与类模板相关联的,而不是与类模板的具体实例化对象相关联。所有同一个类模板的不同实例化对象共享同一个静态数据成员的副本,它可以用于记录一些与整个类模板相关的状态信息,比如实例化对象的个数等。
定义与使用示例
以下是在链表类模板中添加静态数据成员来记录链表节点总数的示例代码:
#include<iostream>template <typename T>struct ListNode {T data;ListNode* next;ListNode(const T& val) : data(val), next(nullptr) {}};template <typename T>class SinglyLinkedList {private:ListNode<T>* head;static int nodeCount; // 静态数据成员声明public:SinglyLinkedList() : head(nullptr) {++nodeCount; // 每次创建链表对象时节点数加 1}~SinglyLinkedList() {--nodeCount; // 链表对象销毁时节点数减 1}// 在链表头部插入节点voidinsertAtHead(const T& value){ListNode<T>* newNode = new ListNode<T>(value);newNode->next = head;head = newNode;}// 遍历链表并输出节点数据voidtraverse(){ListNode<T>* current = head;while (current != nullptr) {std::cout << current->data << " ";current = current->next;}std::cout << std::endl;}staticintgetNodeCount(){return nodeCount;}};template <typename T>int SinglyLinkedList<T>::nodeCount = 0; // 静态数据成员定义并初始化为 0intmain(){SinglyLinkedList<int> myList1;myList1.insertAtHead(5);SinglyLinkedList<int> myList2;myList2.insertAtHead(3);std::cout << "节点总数: " << SinglyLinkedList<int>::getNodeCount() << std::endl;return 0;}
运行结果:

夜雨聆风