C++泛型编程:模版

news2024/9/23 8:26:25

引言        

泛型编程(Generic Programming)是一种编程范式,允许编写与类型无关的代码,从而使程序更加灵活和可重用。在C++中,泛型编程主要通过模板(Templates)来实现。模板使得我们可以编写通用函数和类,从而在不同类型之间复用相同的算法或逻辑。这样,程序的灵活性和可扩展性得到了极大的提升。

例如一个交换函数,我们可能由于不同的参数类型,要重载多个函数。

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;
}

而c++的模版便是告诉编译器一个模子,让编译器根据不同的类型通过该模子来生成代码,只需要下面一段代码就能实现上面的所有功能

template<typename T>
T Add(const T& x, const T& y)
{
	T tmp = x;
	x = y;
	y = tmp;
}

接下来,让我们一起来深入了解 C++ 模板的概念、用法以及一些高级特性吧。 

1.函数模版

函数模板允许我们创建一个可以处理不同类型参数的函数。

1.1基本语法

template <typename T>
T functionName(T arg1, T arg2) {
    // 函数体
}

 template <typename T>:定义一个模版,T是类型参数(类型参数可自定义)

T functionName (T arg1, T arg2):函数返回类型和参数类型均为模板参数

1.2实例

隐式化实例:

#include <iostream>
using namespace std;
template<typename T>
T Add(const T& x, const T& y)
{
	return x + y;
}

int main()
{
	int a1 = 10;
	int a2 = 20;
	double b1 = 10.1;
	double b2 = 20.1;

	Add(a1, a2);
	Add(b1, b2);
	
	//没有与参数列表匹配的函数模版实例
	//Add(a1, b1);

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

	return 0;
}

显示化实例:在函数名后的<>中指定模版参数的实际类型

int main()
{
	int a1 = 10;
	double b1 = 10.1;

	Add<int>(a1, b1);

	return 0;
}

1.3模版参数匹配原则 

完全匹配优先,非模板函数优先

当调用模板时,编译器首先尝试找到与调用模版参数类型完全匹配的模版实例,如果非模板函数参数完全匹配,则优先选择非模板函数

// 专门处理int的加法函数
int Add(int left, int right)
{
	return left + right;
}
// 通用加法函数
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{
	return left + right;
}
void Test()
{
	Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化
	Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
}

1.4函数模板默认参数

函数模板的默认参数是在函数模板定义中为模板参数指定的默认值。当使用函数模板时,如果没有为相应的模板参数提供具体的值,就会使用默认参数。
以下是一个函数模板默认参数的示例:

#include <iostream>
using namespace std;

template<typename T = int, int N = 10>
void printArray(T arr[]) {
    for (int i = 0; i < N; i++) {
        cout << arr[i] << " ";
    }
    cout << std::endl;
}

int main() {
    int intArray[] = {1, 2, 3, 4, 5};
    printArray(intArray); // 使用默认参数,T 为 int,N 为 10

    double doubleArray[] = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7};
    printArray<double, 7>(doubleArray); // 显式指定模板参数,T 为 double,N 为 7

    return 0;
}


在这个例子中,函数模板 printArray 有两个模板参数 T 和 N ,分别表示数组元素的类型和数组的大小。 T 的默认类型是 int , N 的默认值是 10 。
 
需要注意的是,函数模板默认参数的指定应该遵循一定的规则:
 
1. 默认参数只能从右向左依次提供,即如果某个模板参数有默认参数,那么它右边的所有模板参数也必须有默认参数。
2. 在函数模板调用时,如果要为某个模板参数提供具体的值,那么它左边的所有模板参数也必须显式地指定。

2.类模版

类模板允许我们创建可以处理任意数据类型的类

2.1类模板的基本语法

类模板的语法类似于函数模板。我们通过template关键字引入类型参数(或其他参数),以定义一个类模板。常见的形式是:

template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};

这里的T是一个模板参数,表示将来可以用来替代任何类型。Class类中的成员变量、构造函数和成员函数都使用模板参数T,因此该类可以处理不同类型的数据。

2.2实例

// 类模版
template<typename T>
class Stack
{
public:
     Stack(size_t capacity = 4)
    {
      _array = new T[capacity];
      _capacity = capacity;
      _size = 0;
    }
void Push(const T& data);
private:
      T* _array;
      size_t _capacity;
      size_t _size;
};

2.3非类型模板参数

除了类型参数外,类模板还可以接受非类型的模板参数。非类型参数通常用于定义与类型无关的值,例如数组的大小、常量等。例子如下:

template<typename T, int Size>
class Array {
private:
    T arr[Size];
public:
    Array() {
        for (int i = 0; i < Size; ++i) {
            arr[i] = 0;
        }
    }
    T& operator[](int index) {
        return arr[index];
    }
    int getSize() const {
        return Size;
    }
};

在这里,`Array`类模板接受一个类型参数`T`和一个整数参数`Size`,用于定义数组的大小。该模板可以实例化不同类型和大小的数组。

使用方式:

int main() {
    Array<int, 5> intArray;  // 定义一个包含 5 个 int 元素的数组
    intArray[0] = 10;
    std::cout << intArray[0] << std::endl;  // 输出 10
    std::cout << "Size: " << intArray.getSize() << std::endl;  // 输出 5
}

2.4. 类模板的默认参数

类模板和函数模板一样,也可以为模板参数提供默认值。

template<typename T = int>
class MyClass {
private:
	T value;
public:
	MyClass(T v) 
		: value(v) 
	{}
	T getValue() const 
	{ 
		return value; 
	}
};

int main() {
	MyClass<> obj(10); // 使用默认的 int 类型
	std::cout << obj.getValue() << std::endl; // 输出 10
}

在这里,如果没有提供模板参数,MyClass会默认使用int类型

需要注意的是,类模板默认参数的指定也要遵循一定的规则:
 
1. 默认参数只能从右向左依次提供,即如果某个模板参数有默认参数,那么它右边的所有模板参数也必须有默认参数。
2. 在函数模板调用时,如果要为某个模板参数提供具体的值,那么它左边的所有模板参数也必须显式地指定。

3.模板的特化

模板特化允许我们为特定类型定义不同于通用模板的特殊实现。当通用的模板定义不能满足某些特定类型的需求时,可以通过模板特化来提供专门的实现。类模板的特化分为完全特化部分特化

- 例如,假设有一个通用的模板函数用于比较两个值的大小:

template<typename T>
bool compare(T a, T b) {
    return a < b;
}

这个函数可以比较任何类型的值。但是,如果对于特定类型,如指针类型,需要不同的比较方式,可以进行模板特化:

template<>
bool compare<int*>(int* a, int* b) {
    return *a < *b;
}


在这个特化版本中,专门针对 int* 类型的指针进行了比较,比较的是指针所指向的值的大小,而不是指针本身的地址大小。 

3.1模板的完全特化

函数模板的完全特化:

函数模板的特化可以为某种具体类型提供定制的实现:

#include <iostream>

template<typename T>
void func(T value) {
    std::cout << "General template: " << value << std::endl;
}

// 完全特化,针对 char* 类型
template<>
void func<char*>(char* value) {
    std::cout << "Specialized for char*: " << value << std::endl;
}

int main() {
    func(10);           // 调用通用模板,输出 "General template: 10"
    func("Hello");      // 调用特化版本,输出 "Specialized for char*: Hello"
}


在这个例子中,函数模板被特化为char*类型,并为该类型提供了不同的实现。

类模板的完全特化:

类模板的完全特化是针对某一特定类型提供特殊的实现。例如,我们为bool类型特化一个类模板:

template<typename T>
class MyClass {
public:
    void print() {
        std::cout << "General template\n";
    }
};

// 针对 bool 类型进行完全特化
template<>
class MyClass<bool> {
public:
    void print() {
        std::cout << "Specialized for bool\n";
    }
};

int main() {
    MyClass<int> obj1;
    obj1.print();  // 输出 General template

    MyClass<bool> obj2;
    obj2.print();  // 输出 Specialized for bool
}

在这个例子中,当模板参数为bool时,调用特化版本,而其他类型调用通用模板。

3.2模板的部分特化

部分特化是指特化模板的某些参数,而不是全部参数。类模板可以进行部分特化,但函数模板不能进行部分特化

(1)类模板的部分特化

#include <iostream>

// 通用模板
template<typename T, typename U>
class MyClass {
public:
    void display() {
        std::cout << "General template\n";
    }
};

// 部分特化,当第二个参数是 int 时
template<typename T>
class MyClass<T, int> {
public:
    void display() {
        std::cout << "Partially specialized template for second parameter int\n";
    }
};

int main() {
    MyClass<double, double> obj1;
    obj1.display();  // 输出 "General template"

    MyClass<double, int> obj2;
    obj2.display();  // 输出 "Partially specialized template for second parameter int"
}

在这个例子中,当第二个模板参数为int时,会使用特化的模板。其他类型组合仍然使用通用模板。

(2)指针和引用的部分特化

部分特化还可以用于特定的类型模式,例如指针或引用类型。如下例:

#include <iostream>

// 通用模板
template<typename T>
class MyClass {
public:
    void display() {
        std::cout << "General template\n";
    }
};

// 部分特化,针对指针类型
template<typename T>
class MyClass<T*> {
public:
    void display() {
        std::cout << "Specialized template for pointer types\n";
    }
};

int main() {
    MyClass<int> obj1;
    obj1.display();  // 输出 "General template"

    MyClass<int*> obj2;
    obj2.display();  // 输出 "Specialized template for pointer types"
}

在这个例子中,MyClass<int*>会匹配到特化的指针类型版本,而MyClass<int>仍然使用通用模板。

3.3. 模板特化与继承

- 在 C++中,可以结合模板和继承来创建更加灵活和可扩展的类层次结构。通过模板参数,可以在基类中定义一些通用的功能,而派生类可以根据具体的需求进行特化或扩展。
- 例如,考虑一个通用的容器类模板:

template<typename T>
class Container {
public:
    void add(T item) {
        // 添加元素到容器的通用实现
    }
    // 其他通用的容器操作
};

然后,可以创建一个派生类来特化这个容器类,例如一个只存储整数的容器:

class IntContainer : public Container<int> {
public:
    // 可以添加针对整数容器的特殊操作
};

在这个例子中, IntContainer 继承自 Container<int> ,继承了通用容器类的功能,并可以根据整数的特点添加特定的操作。

#include <iostream>
using namespace std;
template<typename T>
class Base {
public:
    void print() {
        cout << "Base template\n";
    }
};

// 特化 Base 类,针对 int 类型
template<>
class Base<int> {
public:
    void print() {
        cout << "Specialized Base template for int\n";
    }
};

template<typename T>
class Derived : public Base<T> {
public:
    void show() {
        cout << "Derived template\n";
        this->print();
    }
};

int main() {
    Derived<double> obj1;
    obj1.show();  // 调用通用模板,输出 "Base template"

    Derived<int> obj2;
    obj2.show();  // 调用特化模板,输出 "Specialized Base template for int"
}

在这个例子中,基类Base<int>进行了完全特化,当派生类继承自Base<int>时,它将使用特化版本的print()函数,而其他类型使用通用版本。

4. 模板特化的应用场景

模板特化通常用于以下场景:

- 处理某些类型的特殊需求:例如,对bool类型、指针类型、数组类型等进行特殊处理。
- 针对容器或算法进行优化:在某些类型上进行优化以提高性能,例如对于std::vector<bool>的特殊优化。
- 处理特定类型的不同行为:例如,针对浮点数和整数提供不同的处理逻辑。
  

模板特化是C++泛型编程中非常强大且灵活的特性。通过模板特化,程序员可以为某些类型提供特定的处理方式,而不影响其他类型的通用逻辑。理解和合理使用模板特化可以让代码更加高效、灵活。

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

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

相关文章

nodejs基于vue电子产品商城销售网站的设计与实现 _bugfu

目录 技术栈具体实现截图系统设计思路技术可行性nodejs类核心代码部分展示可行性论证研究方法解决的思路Express框架介绍源码获取/联系我 技术栈 该系统将采用B/S结构模式&#xff0c;开发软件有很多种可以用&#xff0c;本次开发用到的软件是vscode&#xff0c;用到的数据库是…

告别枯燥:我开发了一个在电脑桌面上使用弹幕来背单词的软件

前言 在这个快节奏的时代&#xff0c;我们每天都在忙碌中度过&#xff0c;手机虽然方便&#xff0c;但往往难以找到一整块时间来专心背单词。然而&#xff0c;你是否意识到&#xff0c;每天坐在电脑前的时间远比使用手机的时间要长&#xff1f;现在我们来介绍一个新型的学习软…

阿里云kafka消息写入topic失败

1. 问题现象描述 20240918,14:22&#xff0c;测试反馈说kafka有问题&#xff0c;生产者写入消息的时候报错&#xff0c;并发了一张日志截图&#xff0c;主要报错如下&#xff1a; to topic xxxx: org.apache.kafka.common.errors.TimeoutException: Expiring 1 record(s) for x…

Linux中的环境变量及main函数参数详解

目录 Linux中的环境变量 常见环境变量 PATH : 和环境变量相关的命令 通过系统调用获取或设置环境变量 getenv putenv 新增环境变量 进程切换&#xff1a; main函数参数 命令行参数 Linux中的环境变量 环境变量(environment variables)一般是指在操作系统中用来指定操…

Leetcode 每日一题:Diameter of Binary Tree

写在前面&#xff1a; 最近被学校的 campus involvement 社团活动的招新宣传和选拔&#xff0c;以及找工作频繁的参加招聘会和网上申请忙的焦头烂额&#xff0c;马上又要到来的期中考试让我再次意识到了大学生活的险恶。虽然大家都说学生时代是最幸福的时代&#xff0c;但这个…

Python中的数据可视化:从基础图表到高级可视化

数据可视化是数据分析和科学计算中不可或缺的一部分。它通过图形化的方式呈现数据&#xff0c;使复杂的统计信息变得直观易懂。Python提供了多种强大的库来支持数据可视化&#xff0c;如Matplotlib、Seaborn、Plotly等。本文将从基础图表入手&#xff0c;逐步介绍如何使用这些库…

vue3 选择字体的颜色,使用vue3-colorpicker来选择颜色

1、有的时候我们会用到颜色的选择器&#xff0c;像element-plus提供了&#xff0c;但是ant-design-vue并没有&#xff1a; 这个暂时没有看到&#xff1a; 但是Ant Design 5的版本有&#xff0c;应该不是vue的。 2、使用第三方提供的vue3-colorpicker&#xff1a;storybook/cli…

18_Python文件操作

计算机中的文件 文件是存储在计算机上的数据集合&#xff0c;它可以是文本、图片、音频、视频或其他任何类型的数据。 在计算机系统中&#xff0c;文件通常用来长期保存信息。 文本文件&#xff1a;一种以字符编码&#xff08;如ASCII、UTF-8、UTF-16等&#xff09;的形式存储…

C++离线查询

前言 C算法与数据结构 打开打包代码的方法兼述单元测试 概念及原理 离线算法( offline algorithms)&#xff0c;离线计算就是在计算开始前已知所有输入数据&#xff0c;输入数据不会产生变化&#xff0c;且在解决一个问题后就要立即得出结果的前提下进行的计算。 通俗的说&a…

深入浅出:Eclipse 中配置 Maven 与 Spark 应用开发全指南

Spark 安装配置 1.在 Eclipse 中配置 Maven Eclipse 中默认自带 Maven 插件&#xff0c;但是自带的 Maven 插件不能修改本地仓库&#xff0c;所 以通常我们不使用自带的 Maven &#xff0c;而是使用自己安装的&#xff0c;在 Eclipse 中配置 Maven 的 步骤如下&#xff1a;…

多模态大模型应用开发技术学习

前篇提到多模态模型应用是未来的应用方向&#xff0c;本篇就聊聊技术学习方面的内容。 应用场景 多模态大模型技术的应用场景非常广泛&#xff0c;涵盖了从日常生活到专业领域的各个方面。以下是一些主要的应用场景&#xff1a; 办公自动化&#xff1a;多模态大模型可以用于…

K8s 之微服务的定义及详细资源调用案例

什么是微服务 用控制器来完成集群的工作负载&#xff0c;那么应用如何暴漏出去&#xff1f; 需要通过微服务暴漏出去后才能被访问 Service是一组提供相同服务的Pod对外开放的接口。借助Service&#xff0c;应用可以实现服务发现和负载均衡。service默认只支持4层负载均衡能力&…

指针 (七)

一 . 回调函数 什么是回调函数呢&#xff1f;就是说我们将函数的指针&#xff08;地址&#xff09;作为参数传递给另一个函数&#xff0c;当这个指针被用来调用其所指向的函数时&#xff0c;这个被调用的函数就是回调函数。回调函数并不是由该函数的实现方直接调用&#xff0c…

MySQL函数介绍--日期与时间函数(二)

我相信大家在学习各种语言的时候或多或少听过我们函数或者方法这一类的名词&#xff0c;函数在计算机语言的使用中可以说是贯穿始终&#xff0c;那么大家有没有思考过到底函数是什么&#xff1f;函数的作用又是什么呢&#xff1f;我们为什么要使用函数&#xff1f;其实&#xf…

移动技术开发:RecyclerView瀑布流水果列表

1 实验名称 RecyclerView瀑布流水果列表 2 实验目的 掌握RecyclerView控件的实现方法和基本应用 3 实验源代码 布局文件代码&#xff1a; activity_main&#xff1a; <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android&q…

【学习笔记】手写 Tomcat 五

目录 一、优化 Servlet 创建一个抽象类 继承抽象类 二、三层架构 业务逻辑层 数据访问层 1. 在 Dao 层操作数据库 2. 调用 Dao 层&#xff0c;实现业务逻辑功能 3. 调用 Service 层&#xff0c;响应数据 测试 三、数据库连接池 1. 手写数据库连接池 2. 创建数据库…

C语言题目之单身狗2

文章目录 一、题目二、思路三、代码实现 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、题目 二、思路 第一步 在c语言题目之打印单身狗我们已经讲解了在一组数据中出现一个单身狗的情况&#xff0c;而本道题是出现两个单身狗的情况。根据一个数…

当Navicat报错 Can not connect to MySQL server的解决方法!

今天运行数据库时突然弹出一个error&#xff1a; 原因&#xff1a;MySQL的服务没有打开&#xff0c;需要检查MySQL的开启状态即可。 具体做法&#xff1a; 1.右键“开始”&#xff0c;点击“计算机管理” 2. 选择“服务和应用程序”&#xff0c;并点击“服务” 3.在服务中找…

ESP32-WROOM-32 [创建AP站点-TCP服务端-数据收发]

简介 ESP32 创建TCP Server AP站点&#xff0c; PC作为客户端连接站点并收发数据 指令介绍 注意,下面指令需要在最后加上CRLF, 也就是\r\n(回车换行) ATRESTORE // 恢复出厂设置 ATCWMODE2 // 设置 Wi-Fi 模式为 softAP ATCIPMODE0 // 需要数据传输模式改为0&#xff0c; 普通…

Cesium 绘制可编辑点

Cesium Point点 实现可编辑的pointEntity 实体 文章目录 Cesium Point点前言一、使用步骤二、使用方法二、具体实现1. 开始绘制2.绘制事件监听 三、 完整代码 前言 支持 鼠标按下 拖动修改点&#xff0c;释放修改完成。 一、使用步骤 1、点击 按钮 开始 绘制&#xff0c;单…