泛型编程
C++中的泛型编程是一种编程范式,它强调代码的重用性和类型独立性。通过泛型编程,你可以编写与特定数据类型无关的代码,使得相同的代码可以用于多种数据类型。
利用重载实现泛型编程
/*利用重载实现泛型编程*/
#include<iostream>
using namespace std;
void Swap(int& left,int& right){int temp=left;left=right;right=temp;}
void Swap(double& left,double& right){int temp=left;left=right;right=temp;}
void Swap(char& left,char& right){int temp=left;left=right;right=temp;}
//...
利用重载实现泛型编程的缺陷
代码冗余:
每个重载函数需要为每种类型单独编写,这会导致大量类似的代码。对于每个新类型,都需要添加一个新的重载版本,这使得代码难以维护。
可扩展性差:
如果需要支持新的类型,必须手动添加新的重载函数。这限制了代码的可扩展性,尤其是对于那些无法预先知道所有将要使用的类型的情况。
类型检查不够灵活:
重载依赖于编译时的静态类型检查。这意味着对于泛型代码,如果类型不匹配,编译器可能无法找到合适的重载,导致编译错误。
运行时效率:
重载函数通常会产生更多的运行时代码,因为每种类型的操作都是独立的函数。
泛型算法实现困难:
使用重载来实现真正的泛型算法非常困难。重载需要为每一种可能的类型组合提供一个实现。
维护和调试困难:
当有许多重载函数时,维护和调试变得更加困难。特别是当函数有多个参数,且每个参数都可能有多种类型时,重载的组合会急剧增加。
模版
在C++中,模板是实现泛型编程的一种强大工具,允许程序员编写与数据类型无关的代码。模板可以分为两种主要类型:函数模板和类模板。
函数模板
函数模板允许您编写处理不同类型的通用函数。它们在编译时根据提供的类型参数自动实例化。
/*函数模版*/
#include <iostream>
template <typename T>
T max(T x, T y) {return (x > y) ? x : y;}int main() {int a = 5, b = 10;std::cout << "Max of a and b: " << max(a, b) << std::endl;double c = 3.5, d = 4.5;std::cout << "Max of c and d: " << max(c, d) << std::endl;}
template <typename T>
T max(T x, T y) {return (x > y) ? x : y;}
template
<typename
T
>
表示用T表示任意数据类型,后面紧接一个函数,称为函数模版。
在函数中可以使用T表示数据类型,使用函数时,会自动识别数据类型,自动编写对应函数。
类模板
类模板允许您创建可以处理任何数据类型的泛型类。实例化时,您指定特定的数据类型。
/*类模版*/
#include<iostream>
#include<stack>
#include<vector>
template <typename T>
class Stack {
private:std::vector<T> elements;public:void push(T const& elem) {elements.push_back(elem);}void pop() {if (elements.empty()) {throw std::out_of_range("Stack<>::pop(): empty stack");}elements.pop_back();}T top() const {if (elements.empty()) {throw std::out_of_range("Stack<>::top(): empty stack");}return elements.back();}bool empty() const {return elements.empty();}};int main() {Stack<int> intStack;intStack.push(7);std::cout << intStack.top() << std::endl;}
template
<typename
T
>
表示用T表示任意数据类型,后面紧接一个类,称为类模版。
在类中可以使用T表示数据类型,使用类的时候需要再后面加上<T代表的数据类型>。
Typename关键字
在C++中,关键字 typename
用于泛型编程,尤其是在模板编程中。它的主要作用是指示后续的标识符是一个类型名。这在两个主要场景中非常重要:模板参数的声明和模板内部的依赖类型名。
模板参数的声明
在定义模板时,typename
可用于声明一个类型模板参数:
template <typename T>
class MyClass {T data;// ...
};
在这里,typename T
表示 T
是一个类型参数,这意味着当你实例化 MyClass
时,你可以用任何类型替换 T
。
依赖类型名
在模板内部,当你引用一个依赖于模板参数的类型时,你需要在这个类型前使用 typename
来告诉编译器它是一个类型。这通常发生在模板的成员函数中:
template <typename T>
class MyClass {
public:typename T::SubType method();
};
在这个例子中,T::SubType
可能是一个类型,但在模板定义的时候编译器并不知道 T
会被什么替换,因此它无法确定 T::SubType
是一个类型还是其他东西。在这里使用 typename
告诉编译器 T::SubType
是一个类型。
关键字 class
与 typename
的可互换性
在模板参数的声明中,typename
和 class
关键字是可以互换的,二者意味着相同的事情:
template <class T> // 同样有效
class MyClass {// ...
};
但是在依赖类型名的场景下,只能使用 typename
。
模版的原理
编写模板
当你编写一个函数模板时,你实际上是在定义一个函数的蓝图,而不是一个具体的、可以直接调用的函数。这个蓝图告诉编译器如何生成针对特定类型的函数实例。
template <typename T>
T max(T a, T b) {return a > b ? a : b;
}
在这个例子中,T
是一个占位符,代表任何类型。
编译器的实例化过程
当你调用一个模板函数时,编译器会查看你提供的参数类型,并根据这些类型,以及函数模板的定义,生成一个具体的函数实例。这个过程称为模板实例化。
int main() {auto result = max(5, 10); // 调用 max<int>(int, int)
}
在这个例子中,编译器看到你用两个整数调用了 max
函数,所以它生成了一个接受两个 int
类型参数的 max
函数的实例。
类型推导
C++11及以后的版本支持自动类型推导,这意味着在许多情况下,你不需要显式指定模板参数的类型;编译器可以从函数调用中的参数类型推导出来。
代码生成
一旦模板实例化完成,编译器就会生成与普通函数相同的机器码。这意味着使用函数模板不会比直接使用针对特定类型编写的函数有更多的运行时开销。
函数模版实例化
函数模板实例化是一个编译时过程,其中编译器根据模板函数被调用时提供的具体类型参数生成特定的函数实例。这个过程允许程序员编写一次模板代码,然后用不同的类型多次实例化,以适应不同的使用场景。
实例化过程
当编译器遇到一个模板函数调用时,它会检查提供给函数模板的实际类型参数。然后,编译器生成一个新的函数,其中模板参数被实际调用中使用的具体类型所替换。这个生成的函数就是模板的一个实例。
隐式实例化
在C++11及更高版本中,函数模板调用时往往不需要显式指定类型参数。编译器能够根据传递给函数的参数自动推导出模板参数的类型。这使得代码更简洁易读。
/*函数模版隐式实例化*/
#include <iostream>
using namespace std;
template<typename T>
T Add(const T& left, const T& right) {return left + right;}
int main() {int a1 = 10, a2 = 20;double d1 = 10.1, d2 = 20.2;cout << "Add(a1,a2):" << Add(a1, a2) << endl;cout << "Add(d1,d2):" << Add(d1, d2) << endl;}
显式实例化
虽然编译器通常能够自动实例化函数模板,但在某些情况下,可能需要或想要显式地指定模板的实例化。这可以通过提供模板参数的具体类型来完成。
/*函数模版显示实例化*/
#include <iostream>
using namespace std;
template<typename T>
T Add(const T& left, const T& right) {return left + right;}
int main() {int a1 = 10, a2 = 20;double d1 = 10.1, d2 = 20.2;cout << "Add(a1,a2):" << Add<int>(a1, a2) << endl;cout << "Add(d1,d2):" << Add<double>(d1, d2) << endl;}
函数模版参数匹配规则
1.一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
/*函数模版显示实例化*/
#include <iostream>
using namespace std;
//通用加法函数
template<typename T>
T Add(const T& left, const T& right) {return left + right;}
//专门处理int的加法函数
int Add(const int& left, const int& right) {return left + right;}
int main() {Add(1,2); //与非模版函数匹配,编译器不需要特化Add<int>(1,2); //调用编译器特化的Add版本
}
2.对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板。
/*函数模版精准匹配或生成匹配的函数*/
#include <iostream>
using namespace std;
//通用加法函数
template<typename T>
T Add(const T& left, const T& right) {return left + right;}
//专门处理int的加法函数
int Add(const int& left, const int& right) {return left + right;}
int main() {Add(1, 2); //与非模版函数匹配,编译器不需要特化Add(1, 2.1); //模版函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
}
3.模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
类模版实例化
类模板实例化是C++中一种创建具体类从泛型类模板的过程。类模板通过允许类型作为参数,提供了一种强大的方式来编写灵活且可重用的代码。实例化过程中,模板参数被具体的类型替换,从而生成一个特定类型的类定义。
template <typename T>
class Box {
public:T contents;Box(T newValue) : contents(newValue) {}void show() const {std::cout << contents << std::endl;}
};
隐式实例化
在使用类模板时,你可以让编译器通过构造函数或方法的参数类型来自动推导模板参数类型。
Box box1(123); // 隐式实例化为 Box<int>
Box box2("C++"); // 隐式实例化为 Box<const char*>
显式实例化
也可以显式地指定模板参数的类型,明确地告诉编译器你想要实例化的具体类型。
Box<int> box1(123); // 显式实例化为 Box<int>
Box<std::string> box2("C++"); // 显式实例化为 Box<std::string>
使用实例化的类
一旦类模板被实例化,就可以像使用任何其他普通类一样使用它。你可以创建对象,调用方法等。
box1.show(); // 显示:123
box2.show(); // 显示:C++
结尾
最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。
同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。
谢谢您的支持,期待与您在下一篇文章中再次相遇!