快来参与讨论?,点赞?、收藏⭐、分享?,共创活力社区。
目录
?前言
?泛型编程
?函数模板
1.函数模板概念
2.函数模板格式
3.函数模板的原理
4.函数模板的实例化
5.模板参数的匹配原则
?类模板
1.类模板的定义格式
2.类模板的实例化
?总结
?前言
?在编程的世界中,你是否曾经为了处理不同类型的数据而不得不重复编写大量相似的代码?
?你是否渴望有一种方法能够让代码更加通用、灵活,提高代码的复用性呢?
?如果你的答案是肯定的,那么 C++ 的模板编程或许就是你一直在寻找的解决方案。
模板编程包含了?泛型编程、?函数模板和?类模板三个重要部分,犹如三把利器,助力我们在编程的征程上披荆斩棘。
?泛型编程
?“在编程过程中,当需要实现一个通用的交换函数时,如果不采用特殊的手段,会面临哪些问题呢❓”
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.函数模板概念
函数模板代表了一个函数家族,它与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
比如下面这个函数模板:
template<typename T>void Swap( T& left, T& right){ // 临时变量存储 left 的值 T temp = left; left = right; right = temp;}
?这个模板可以根据不同的类型生成对应的交换函数。
2.函数模板格式
template<typename T1, typename T2,......,typename Tn> 返回值类型 函数名 (参数列表){}
❗这里的 “typename” 是用来定义模板参数的关键字,也可以用 “class”,但不能用 “struct” 代替 “class”。
3.函数模板的原理
函数模板就像是一个蓝图,它本身不是一个具体的函数,而是编译器用来生成具体类型函数的模具。在编译器编译阶段,当我们使用函数模板时,编译器会根据传入的实参类型来推演生成对应类型的函数。
?例如,当我们用 double 类型的参数调用这个函数模板时,编译器会将模板参数 “T” 确定为 double 类型,然后生成一份专门处理 double 类型的代码
4.函数模板的实例化
隐式实例化:让编译器根据实参推演模板参数的实际类型。显式实例化:在函数名后的 “<>” 中指定模板参数的实际类型。?比如下面这个函数模板的使用:
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); // 隐式实例化,编译器推演出 T 为 int 类型 ?Add(d1, d2); // 隐式实例化,编译器推演出 T 为 double 类型 ?Add<int>(a1, b1); // 显式实例化,指定 T 为 int 类型}
5.模板参数的匹配原则
一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。???#include <iostream>// 非模板函数void printValue(int value) { std::cout << "非模板函数:值为 " << value << std::endl;}// 函数模板template <typename T>void printValue(T value) { std::cout << "函数模板:值为 " << value << std::endl;}int main() { int num = 10; printValue(num); // 调用非模板函数,因为有精确匹配 double dnum = 3.14; printValue(dnum); // 调用函数模板实例化后的版本,参数类型为 double return 0;}
对于非模板函数和同名函数模板,如果其他条件都相同,在调用时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板。??? #include <iostream>// 非模板函数void printValue(int value) { std::cout << "非模板函数:值为 " << value << std::endl;}// 函数模板template <typename T>void printValue(T value) { std::cout << "函数模板:值为 " << value << std::endl;}int main() { int num = 10; printValue(num); // 优先调用非模板函数,因为参数精确匹配非模板函数 double dnum = 3.14; printValue(dnum); // 没有精确匹配的非模板函数,调用函数模板实例化后的版本,参数类型为 double // 现在传入一个 long long 类型,没有对应的非模板函数,模板能产生更好匹配 long long lnum = 1234567890123; printValue(lnum); // 调用函数模板实例化后的版本,参数类型为 long long return 0;}
模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。??? #include <iostream>// 普通函数,可以进行自动类型转换void printValue(int value) { std::cout << "普通函数:值为 " << value << std::endl;}// 函数模板template <typename T>void printValue(T value) { std::cout << "函数模板:值为 " << value << std::endl;}int main() { // 普通函数可以自动将 double 类型转换为 int 类型进行调用 double d = 3.14; printValue(static_cast<int>(d)); // 函数模板不能自动将 double 类型转换为 int 类型进行调用 // printValue(d); // 这会导致编译错误 return 0;}
?类模板
?“如果想要定义一个可以适应不同类型数据的通用类,应该怎么做呢?”
1.类模板的定义格式
template<class T1, class T2,..., class Tn> class 类模板名 { // 类内成员定义}
例如,定义一个动态顺序表类模板:
template<class T>class Vector{public : Vector(size_t capacity = 10) : _pData(new T[capacity]) , _size(0) , _capacity(capacity) {} // 析构函数,释放动态分配的内存 ~Vector(); void PushBack(const T& data); void PopBack(); size_t Size() {return _size;} T& operator[](size_t pos) { assert(pos < _size); return _pData[pos]; }private: T* _pData; size_t _size; size_t _capacity;};
2.类模板的实例化
类模板实例化与函数模板实例化不同,需要在类模板名字后跟 “<>”,然后将实例化的类型放在 “<>” 中,这样才能得到真正的类。
template <class T>Vector<T>::~Vector(){ if(_pData) delete[] _pData; _size = _capacity = 0;}int main(){ // Vector 不是具体的类,Vector<int>才是真正的类 Vector<int> s1; Vector<double> s2;}
?总结
?C++ 的模板编程为我们提供了强大的工具,让我们能够编写更加通用、灵活和高效的代码。
?无论是泛型编程、函数模板还是类模板,都能在不同的场景下发挥巨大的作用。?
以后我将深入研究继承、多态、模板等特性,并将默认成员函数与这些特性结合,以解决更复杂编程问题!欢迎关注我?【A Charmer】