C++泛型编程之模板的使用

文章目录

      • 1.模板的概念
        • 1.函数模板
        • 2.类模板
        • 3.模板特化
        • 4.模板元编程
      • 2.模板的使用
        • 1.函数模板的使用
        • 2.类模板的使用
        • 3.模板特化
        • 4.模板参数推导
        • 5.普通函数和函数模板的调用规则
          • 1. 非模板函数优先
          • 2. 最佳匹配原则
          • 3. 显式模板参数指定
          • 4. 函数模板特化
          • 5. 重载决议
          • 示例
        • 7.注意事项
        • 8.类模板成员函数类外实现
          • 1.类模板声明及成员函数声明
          • 2.类模板成员函数的类外实现
          • 3.使用类模板
          • 4.注意事项
        • 9.类模板的分文件编写
        • 10.模板案例
      • 3.类模板与继承和友元
        • 1.类模板与继承
        • 2.类模板与友元
          • 1.类模板与非模板友元函数
          • 2.类模板与模板友元类

1.模板的概念

C++中的模板是一种实现泛型编程的机制,它允许程序员编写与类型无关的代码,从而提高了代码的重用性、灵活性和抽象级别。模板可以用来创建泛型类(类模板)和泛型函数(函数模板),使得这些类和函数能够应用于多种数据类型。

1.函数模板

函数模板是创建通用函数的方式,它允许函数操作不同类型的参数,而无需为每种类型重复编写相同的函数代码。函数模板通过类型参数(通常用typenameclass关键字声明)来指定函数可以接受的任何数据类型。编译器会在实际调用函数时根据传入的参数类型自动实例化具体的函数版本。

示例:

template <typename T>
T max(T a, T b) {return (a > b) ? a : b;
}

在这个例子中,T是一个类型参数,编译器会根据实际调用时的参数类型(如intdouble等)生成相应类型的max函数。

2.类模板

类模板与函数模板类似,但它是应用于类的,允许类的定义参数化。类模板允许创建可以处理各种数据类型的类,从而增加了类的通用性和适应性。

示例:

template <typename T>
class Vector {
private:T* elements;int size;
public:Vector(int s);~Vector();void push_back(T element);// ... 其他成员函数
};

这里,Vector类是一个模板类,它可以用任何数据类型T实例化,创建特定类型的向量,如Vector<int>Vector<double>

3.模板特化

模板特化允许为模板提供特定类型的特殊实现。这可以是全特化(为所有类型参数提供具体类型)或偏特化(只为部分类型参数提供具体类型)。

4.模板元编程

模板元编程是利用模板在编译时期进行计算的技术,它可以生成高效的静态(编译时计算)代码。这是通过模板实例化和递归模板实例化实现的,常用于计算编译时常量表达式、类型检查等。

总的来说,C++模板机制极大地增强了语言的能力,使得编写高效、灵活且可重用的代码成为可能。

2.模板的使用

C++模板的使用广泛涵盖了函数模板和类模板两大类别,下面分别简要介绍它们的使用方法:

1.函数模板的使用

函数模板允许你编写一个通用的函数,它能接受多种数据类型的参数。使用函数模板的基本步骤包括定义和调用:

定义:

template <typename T>
T add(T a, T b) {return a + b;
}

在这个例子中,T是一个类型参数,表示函数参数和返回类型可以是任何类型。编译器会根据实际调用时提供的参数类型自动实例化相应的函数版本。

调用:

int resultInt = add<int>(5, 3);    // 显式指定类型
double resultDouble = add(2.5, 3.7); // 编译器自动推导类型//
// Created by 86189 on 2024/6/24.
//
#include <iostream>
using namespace std;template<typename T>
T add(T a, T b) {return a + b;
}int main() {int a = 10, b = 20;cout << add<int>(a, b) << endl;  //显式指定类型cout << add(a, b) << endl; //模板推导类型char c = 'a';//cout << add(a, c) << endl;  模板函数无法自动强制转换类型cout << add<int>(a, c) << endl;  //显示指定类型return 0;
}
2.类模板的使用

类模板允许你创建可以处理任意数据类型的类。定义和使用类模板的步骤如下:

定义:

template <typename T>
class Box {
private:T value;
public:Box(T val) : value(val) {}T getValue() const { return value; }void setValue(T val) { value = val; }
};

在这个例子中,Box类是一个模板类,它有一个私有成员value,其类型由类型参数T决定。

实例化与使用:

Box<int> intBox(10);    // 创建一个Box实例,T被实例化为int
std::cout << intBox.getValue() << std::endl;Box<std::string> stringBox("Hello"); // T被实例化为std::string
std::cout << stringBox.getValue() << std::endl;
3.模板特化

有时你可能需要为特定类型提供一个不同的实现,这就是模板特化。例如,为整数类型优化add函数:

template <>
int add<int>(int a, int b) {return a + b; // 假设这里有一个特别的优化实现
}
4.模板参数推导

在大多数情况下,编译器能够自动推导出模板参数的类型,因此你不需要显式指定类型。但在某些复杂情况或需要明确指定类型以避免歧义时,可以使用尖括号语法来显式指定模板参数。

5.普通函数和函数模板的调用规则

在C++中,普通函数和函数模板的调用遵循一定的规则,这些规则决定了编译器如何解析和选择合适的函数版本来执行。以下是几个关键点:

1. 非模板函数优先

当一个普通函数和一个函数模板都可以作为候选函数时,非模板函数具有更高的优先级。如果一个非模板函数和一个函数模板都能匹配给定的调用,编译器会优先选择非模板函数,这一规则称为“非模板函数优先”。

//
// Created by 86189 on 2024/6/24.
//
#include <iostream>using namespace std;void printSay(){cout << "普通函数调用" << endl;
}template<typename T>
void printSay(){cout << "函数模板调用" << endl;
}int main()
{printSay();  //模板函数优先printSay<int>();  //通过空参函数列表调用模板函数return 0;
}
2. 最佳匹配原则

对于函数模板,编译器会尝试根据传入的实际参数类型来实例化模板,寻找最佳匹配。这意味着编译器会选择那个使得转换成本最小,最特化的模板实例。如果存在多个同样特化的模板实例,且转换成本相同,则会导致编译错误(重载模糊不清)。

#include <iostream>// 通用模板函数
template <typename T>
void display(T value) {std::cout << "Generic template with type: " << typeid(T).name() << ", value: " << value << std::endl;
}// 对int类型的特化模板
template <>
void display<int>(int value) {std::cout << "Specialized template for int, value: " << value << std::endl;
}// 非模板函数,接受double类型,但限制为非负数,以与int特化有所区别
void display(double value) {if (value >= 0) {std::cout << "Non-template function for non-negative double, value: " << value << std::endl;} else {std::cout << "Invalid call for negative double value" << std::endl;}
}int main() {display(5);      // 调用特化模板 display<int>display(3.14);   // 调用非模板函数 display(double),因为传入的是正浮点数display(-3.14);  // 也会调用非模板函数 display(double),但输出提示无效调用display('A');    // 调用通用模板 display<char>// 显示指定模板参数来调用通用模板,尽管对于整数这不是必需的,但展示了显式指定的方法display<int>(3);  // 明确指定为int类型,调用特化模板 display<int>return 0;
}
3. 显式模板参数指定

用户可以显式地指定模板参数,强制编译器使用特定的类型实例化模板函数。这种方式可以解决重载模糊的问题,或是在自动类型推导不足以表达意图时使用。

template <typename T>
void foo(T x);void foo(int x);foo(5);    // 调用非模板函数 foo(int)
foo<>(5.0); // 显式指定模板参数,调用模板函数 foo<double>
4. 函数模板特化

如果存在针对特定类型的模板特化,当该特化版本和通用模板都匹配时,编译器会优先选择特化版本。

5. 重载决议

在涉及函数模板和普通函数的重载时,编译器将根据重载决议规则(包括匹配度、转换序列的长度和类型转换的类型等)来决定调用哪个函数。如果模板实例化后的函数和非模板函数都适合作为候选者,非模板函数优先;如果都是模板函数,选择最特化或转换成本最低的那个。

示例

考虑以下代码:

void print(int x) { std::cout << "int: " << x << std::endl; }template <typename T>
void print(T x) { std::cout << "template: " << x << std::endl; }int main() {print(5);    // 调用非模板函数 print(int)print(3.14); // 调用模板函数 print<double>
}

在这个例子中,当调用print(5)时,非模板函数print(int)由于优先级更高而被调用;而print(3.14)则匹配到模板函数,实例化为print<double>

7.注意事项
  • 类型兼容性:模板函数或类中的操作需要确保对所有可能的类型参数都是合法的。
  • 模板实例化:模板只有在被使用时才会被实例化,这意味着如果模板中有错误,可能只在实例化时被发现。
  • 编译时间增加:大量或复杂的模板使用可能会增加编译时间。

模板是C++的强大特性,正确使用它们可以显著提升代码的效率和可维护性。

8.类模板成员函数类外实现

在C++中,类模板的成员函数可以在类定义内声明,也可以在类外实现。如果选择在类外实现成员函数,需要注意保持模板参数列表的一致性。

1.类模板声明及成员函数声明

首先,定义一个类模板及其成员函数的声明:

template<typename T>
class MyClass {
public:MyClass(T initVal);void display();
private:T value;
};
2.类模板成员函数的类外实现

接下来,在类定义之外实现这些成员函数。注意,在实现成员函数时,需要包含完整的模板参数列表:

// 构造函数的实现
template<typename T>
MyClass<T>::MyClass(T initVal) : value(initVal) {// 初始化逻辑
}// 成员函数display的实现
template<typename T>
void MyClass<T>::display() {cout << "Value: " << value << endl;
}
3.使用类模板

最后,可以在main函数或其他地方实例化并使用这个模板类:

int main() {MyClass<int> objInt(10);objInt.display(); // 输出 Value: 10MyClass<double> objDouble(3.14);objDouble.display(); // 输出 Value: 3.14return 0;
}
4.注意事项
  • 模板参数一致性:在类外实现模板成员函数时,确保函数前的模板参数列表与类模板的参数列表完全一致。
  • 实现文件分离:虽然上述示例将模板的声明和实现放在了同一文件中,但实践中,为了组织清晰,有时会将声明放在头文件(.h.hpp),而将实现放在源文件(.cpp)。不过,由于模板的特例化是在使用时完成的,编译器需要看到模板的完整定义才能生成特定类型的代码,因此通常推荐将模板的声明和实现都放在同一个头文件中。
  • 避免链接错误:遵循上述做法可以防止因模板定义不完整导致的链接错误。
9.类模板的分文件编写

将模板的声明和实现都放在同一个头文件中,命名后缀为.hpp

10.模板案例

实现数组排序:

//
// Created by 86189 on 2024/6/24.
//
#include <iostream>using namespace std;template<typename T>
void mySort(T arr[], int len) {for (int i = 0; i < len - 1; ++i) {for (int j = 0; j < len - 1 - i; ++j) {if (arr[j] > arr[j + 1]) {T temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}}
}int main() {int arr[] = {1,2,3,4,6,5};mySort<int>(arr,sizeof(arr)/sizeof(arr[0]));for (int i : arr) {cout << i << " ";}cout << endl;char str[] = "andbhsc"; // 确保数组只包含你想要排序的内容int len = sizeof(str)/sizeof(char) - 1; // 减1是为了排除末尾的空字符'\0'mySort<char>(str, len);for(char j : str){if(j != '\0') { // 排序后的数组可能把'\0'排到了前面,这里过滤掉cout << j << " ";}}cout << endl;return 0;
}

3.类模板与继承和友元

1.类模板与继承

当类模板与继承结合使用时,你可以创建一个泛型的基类,然后从这个基类派生出具体的类。这意味着派生类也可以是泛型的,同时继承了基类的泛型特性。这在实现一些设计模式(如策略模式、工厂模式等)或者需要处理多种数据类型的类层次结构时非常有用。

例如:

template<typename T>
class Base {
public:T value;void setValue(T val) { value = val; }
};// 从Base<T>派生出Derived,继承其泛型特性
template<typename T>
class Derived : public Base<T> {
public:void printValue() { cout << "Value: " << this->value << endl; }
};

在这个例子中,Derived类继承了Base类,并且保留了其泛型性。这意味着你可以为Derived指定具体类型,比如Derived<int>Derived<string>,同时利用和扩展基类的泛型功能。

2.类模板与友元

类模板与友元功能结合使用时,可以让一个函数或另一个类成为模板类的友元,从而能够访问该模板类的私有或保护成员。这对于需要外部函数或类来协助模板类完成某些操作,而又不希望公开所有内部细节的情况非常有用。下面是一个简单的示例来说明这一点:

1.类模板与非模板友元函数
template<typename T>
class MyClass {
private:T data;public:MyClass(T d) : data(d) {}// 声明一个非模板的友元函数friend void displayMyClass(const MyClass<T>& obj);
};// 定义友元函数
template<typename T>
void displayMyClass(const MyClass<T>& obj) {cout << "Data: " << obj.data << endl;
}int main() {MyClass<int> obj(10);displayMyClass(obj); // 输出 Data: 10return 0;
}

在这个例子中,displayMyClass是一个非模板的友元函数,但它能够访问模板类MyClass<T>的私有成员data

2.类模板与模板友元类

如果需要一个模板类成为另一个模板类的友元,那么友元声明也需要模板化:

template<typename T>
class MyClass {
private:T data;public:MyClass(T d) : data(d) {}// 声明一个模板友元类template<typename U>friend class FriendClass;
};// 友元类定义
template<typename U>
class FriendClass {
public:void displayData(const MyClass<U>& obj) {cout << "FriendClass accessing Data: " << obj.data << endl;}
};int main() {MyClass<int> obj(20);FriendClass<int> friendObj;friendObj.displayData(obj); // 输出 FriendClass accessing Data: 20return 0;
}

这里,FriendClass是一个模板类,并且被声明为MyClass<T>的模板友元。因此,它可以访问所有MyClass<T>实例的私有成员。

总结来说,通过将函数或类声明为模板类的友元,可以在保持封装性的同时,允许外部代码以一种受控的方式访问模板类的内部数据。这种机制在设计复杂系统时特别有用,特别是当模板类需要与其他组件紧密协作时。

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

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

相关文章

v-for中为什么要使用key

在Vue中&#xff0c;v-for指令用于循环遍历数组或对象&#xff0c;并为每个元素或属性生成相应的DOM元素或组件实例。当使用v-for循环渲染时&#xff0c;Vue会尽量复用已有的元素&#xff0c;而不是重新创建。为了实现这个复用机制&#xff0c;Vue会根据每个元素的key来跟踪它们…

基于STM32的温湿度检测TFT屏幕proteus恒温控制仿真系统

一、引言 本文介绍了一个基于STM32的恒温控制箱检测系统&#xff0c;该系统通过DHT11温湿度传感器采集环境中的温湿度数据&#xff0c;并利用TFT LCD屏幕进行实时显示。通过按键切换页面显示&#xff0c;通过按键切换实现恒温控制箱的恒温控制。为了验证系统的可靠性和稳定性&…

MongoDB的核心点是什么,选择是否使用!

MongoDB概述 定义: MongoDB是一个文档数据库&#xff0c;设计目的在于简化应用程序的开发和扩展。起源: 由DoubleClick创始人Dwight Merriman和Kevin O’Connor于2007年启动&#xff0c;以应对大规模流量需求。 MongoDB发展历程 开发背景: 由于关系型数据库无法满足DoubleCl…

在 TS 中使用 Manifold 建模

一 Manifold 是什么 1.1 简介 Manifold 是一个几何处理库&#xff0c;专注于高效、可靠的布尔运算和几何操作。它主要用于3D建模和计算几何领域&#xff0c;提供了高性能的几何算法&#xff0c;适用于需要精确几何计算的应用场景。 1.2 主要特点 高效的布尔运算&#xff1a…

Lombok的hashCode方法

Lombok对于重写hashCode的算法真的是很经典&#xff0c;但是目前而言有一个令人难以注意到的细节。在继承关系中&#xff0c;父类的hashCode针对父类的所有属性进行运算&#xff0c;而子类的hashCode却只是针对子类才有的属性进行运算&#xff0c;立此贴提醒自己。 目前重写ha…

png格式快速压缩该怎么做?在电脑压缩png图片的方法

png格式的图片如何快速压缩变小呢&#xff1f;现在网络的不断发展&#xff0c;图片是日常用来分享展示内容的一种常用手段&#xff0c;其中使用最多的一种图片格式就是png&#xff0c;png格式具有无损压缩支持透明底的特性&#xff0c;在很多的场景下都会使用。 现在图片的清晰…

本周AI动态:生成型AI的命运掌握在法院手中

本周AI领域发生了音乐公司指控两家开发AI歌曲生成器的初创公司Udio和Suno侵犯版权的事件。 美国音乐唱片行业协会&#xff08;RIAA&#xff09;周一宣布&#xff0c;由索尼音乐娱乐公司、环球音乐集团、华纳唱片公司等发起的诉讼已经提起。诉讼声称&#xff0c;Udio和Suno在未…

乒乓征途:开球网 跨越积分鸿沟的热爱与挑战

乒乓征途&#xff1a;跨越积分鸿沟的热爱与挑战 在乒乓球这项集速度、技巧与策略于一体的运动中&#xff0c;我以一名业余爱好者的身份&#xff0c;勇敢地踏上了开球网这一竞技的广阔舞台。这里&#xff0c;积分不仅是衡量实力的标尺&#xff0c;更是通往更高层次比赛的通行证…

贷款承诺状态映射参数表,用于加工的提示信息

在设计贷款承诺状态映射参数表时&#xff0c;目的是将贷款的不同状态映射为相应的提示信息&#xff0c;以便于系统能够自动生成和发送通知给相关的借款人或银行员工。以下是一个简化的参数表示例&#xff0c;用于指导贷款状态的加工和提示信息生成&#xff1a; | 状态代码 |…

Arduino - 线性执行器(支撑或滑杆)

Arduino - 线性执行器(支撑或滑杆) Arduino - 执行器 In this tutorial, we are going to learn: 在本教程中&#xff0c;我们将学习&#xff1a; How linear actuator works 线性执行器的工作原理How to make linear actuator extend or retract. 如何使线性执行器伸展或缩…

speakTTS文字转语音播放功能

场景&#xff1a; speak-tts 文字转换语音的使用播放、暂停、恢复 安装 npm install speak-tts 引入 import Speech from ‘speak-tts’ 需求&#xff1a; 1.多条播报内容需要一条一条的播报 一进入页面就开始播报&#xff08;数组的形式 后台返回&#xff09; 2.暂停之后 在点…

Java AWT基础—创建一个简单的应用程序

目录 背景&#xff1a; 代码展示: 代码详解: AWT和Swing的区别: 总结: 背景&#xff1a; 在Java中&#xff0c;AWT(Abstract Windows Toolkit)是最早的图形月用户界面(GUI1)工具包&#xff0c;虽然随着Swing的出现&#xff0c;AWT的使用有所减少&#xff0c;但了解AWT基…

百元蓝牙耳机哪款性价比高?盘点性价比高的百元蓝牙耳机品牌

在如今快节奏的生活中&#xff0c;蓝牙耳机已经成为人们日常生活中不可或缺的配件。然而&#xff0c;市面上百元左右性价比高的蓝牙耳机琳琅满目&#xff0c;消费者往往难以选择到一款质量好、耐用的产品。我们希望可以为广大消费者提供一些参考和建议&#xff0c;接下来&#…

基于51单片机电子称—串口显示

基于51单片机电子称设计 &#xff08;仿真&#xff0b;程序&#xff09; 功能介绍 具体功能&#xff1a; 1.矩阵键盘组成按键&#xff0c;输入价格结算、打印&#xff1b; 2.用滑动变阻器和ADC0832模拟称重&#xff1b; 3.LCD1602可以显示重量、单价和总价&#xff1b; 4.…

spring03-aop

spring aop: 只能增强方法&#xff0c;&#xff0c;spring aop 底层是动态代理&#xff0c;&#xff0c;&#xff0c;动态代理的本质是生成一个子类&#xff0c;&#xff0c;重写这个方法&#xff0c;进行增强&#xff0c;&#xff0c;所以final修饰的类和方法&#xff0c;&…

0121__线程 thread_once

线程 thread_once_thraed once-CSDN博客

QCOM 平台增加分区流程 及 注意事项

本文以qcom msm8909 为例增加carrier分区&#xff0c;留文以备后忘&#xff1a; 1、修改分区表partition.xml diff --git a/non_hlos/common/config/emmc/partition.xml b/non_hlos/common/config/emmc/partition.xml index 74ca036..11f38bc 100755 --- a/non_hlos/common/con…

家用洗地机十大品牌什么牌子好用?2024十大爆款洗地机分享

在快节奏的生活中&#xff0c;清洁家居成为了一项繁琐却必不可少的任务。而洗地机的出现&#xff0c;无疑给忙碌的都市人带来了福音。选择一款优质的洗地机可以大大提升我们清洁的效率&#xff0c;改善我们的生活品质。那么&#xff0c;哪家洗地机清洁力最强&#xff0c;更适合…

ONLYOFFICE 桌面编辑器 8.1 版发布:全面提升文档处理效率的新体验

文章目录 什么是ONLYOFFICE &#xff1f;ONLYOFFICE 桌面编辑器 8.1 发布&#xff1a;新功能和改进功能强大的 PDF 编辑器幻灯片版式功能从右至左语言支持多媒体功能增强无缝切换工作模式其他改进和优化总结 什么是ONLYOFFICE &#xff1f; https://www.onlyoffice.com/zh/off…

JavaScript的学习之图片的切换

目录 一、寻找素材 二、编写简单的静态html页面 代码示例 效果展示 三、JS功能的实现 JS代码 完整代码 效果展示 一、寻找素材 随便去网上找几张图片素材 二、编写简单的静态html页面 代码示例 <!doctype html> <html><head><meta charset"…