文章目录
模板编程的深度解析:从基础到高级特性
模板的概念
模板是C++中的一种高级特性,它允许程序员编写通用的代码,这些代码可以在编译时根据不同的类型或值进行实例化。模板的概念是基于泛型编程的思想,它提供了一种机制,使得代码可以独立于特定的数据类型。
示例
为了更好的理解模板这个抽象的概念,我们举点例子来理解下
假设我们想要编写一个函数,它可以比较两个值并返回较大的一个。我们可以使用模板来创建一个通用的比较函数:
template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
在这个例子中,max
函数是一个模板函数,它可以接受任何类型 T
的参数,并返回较大的一个。当我们调用 max
函数时,编译器会根据传递给函数的实际类型自动实例化一个特定版本的函数。
就例如这里我在main
函数中进行调用
int main()
{
cout << max(1, 2) << endl;
cout << max(2.3, 5.5) << endl;
cout << max('a', 'b') << endl;
return 0;
}
这样就方便多了,因为有了模板函数我们就能省去因为类型不同而浪费的时间了,把编译器当成帕鲁你不想干的给编译器去干
这只是个最简单的例子用于我们理解模板,那么在我们对模板有了个雏形的印象了之后就来深入理解吧
模板参数的分类
模板参数可以分为两种主要类型:
-
类型模板参数:这些参数代表类型,用于定义模板类或模板函数的类型。
-
非类型模板参数:这些参数代表常量值,用于定义模板类或模板函数的特定值。
非类型模板参数
非类型模板参数允许我们传递常量值作为模板参数,这些值在编译时就已经确定。
我们可以拿函数参数和模板参数进行下对比来更好的理解
函数参数与非类型模板参数的对比
- 函数参数:在函数调用时传递,可以是任何值,包括变量、表达式等。
- 非类型模板参数:在模板实例化时传递,必须是编译时常量表达式。
下面是一些非类型模板参数的例子:
整型常量作为非类型模板参数
#include <iostream>
// 非类型模板参数的例子,使用整型常量
template <int N>
void printValue() {
std::cout << "The value is: " << N << std::endl;
}
int main() {
printValue<10>(); // 调用模板函数,传递整型常量10
return 0;
}
在这个例子中,N
是一个非类型模板参数,它是一个整型常量。在模板实例化时,N
被替换为具体的值10。
指向函数的指针作为非类型模板参数
#include <iostream>
// 非类型模板参数的例子,使用指向函数的指针
void print(int value) {
std::cout << "The value is: " << value << std::endl;
}
template <void (*Func)(int)>
void callFunction() {
Func(10); // 调用传递的函数
}
int main() {
callFunction<&print>(); // 调用模板函数,传递指向函数的指针
return 0;
}
在这个例子中,Func
是一个非类型模板参数,它是一个指向函数的指针。在模板实例化时,Func
被替换为指向 print
函数的指针。
指向成员的指针作为非类型模板参数
#include <iostream>
class MyClass {
public:
void printValue(int value) {
std::cout << "The value is: " << value << std::endl;
}
};
template <void (MyClass::*Func)(int)>
void callMemberFunction(MyClass& obj) {
(obj.*Func)(10); // 调用对象的成员函数
}
int main() {
MyClass obj;
callMemberFunction<&MyClass::printValue>(obj); // 调用模板函数,传递指向成员函数的指针
return 0;
}
在这个例子中,Func
是一个非类型模板参数,它是一个指向成员函数的指针。在模板实例化时,Func
被替换为指向 MyClass
类的 printValue
成员函数的指针。
注意事项
- 非类型模板参数必须在编译时就已知,不能使用变量或运行时计算的值。
- 非类型模板参数可以是整型、枚举、指针、引用或
std::nullptr_t
类型。 - 非类型模板参数可以用于模板类或模板函数。
- 在模板实例化时,非类型模板参数的值必须是常量表达式。
缺省参数
缺省参数允许我们在函数声明中为参数提供默认值。在模板编程中,我们可以使用缺省参数来简化模板的使用。
全缺省函数
全缺省函数是指所有参数都有默认值的函数。例如:
#include <iostream>
void printMessage(const std::string& message = "Hello, World!") {
std::cout << message << std::endl;
}
int main() {
printMessage(); // 调用时使用默认值
printMessage("Hello, C++!"); // 调用时传递自定义值
return 0;
}
在这个例子中,printMessage
函数有一个全缺省参数,当调用时不传递任何参数时,它将使用默认的 “Hello, World!” 消息。
半缺省函数
半缺省函数是指只有部分参数有默认值的函数。例如:
#include <iostream>
void printMessage(const std::string& message, bool isImportant = false) {
if (isImportant) {
std::cout << "Important: " << message << std::endl;
} else {
std::cout << message << std::endl;
}
}
int main() {
printMessage("Hello, C++!"); // 调用时使用默认的isImportant值
printMessage("Hello, C++!", true); // 调用时传递自定义的isImportant值
return 0;
}
在这个例子中,printMessage
函数有两个参数,其中 isImportant
参数有一个默认值 false
。当调用时不传递 isImportant
参数时,它将使用默认的 false
值。
注意事项
- 缺省参数必须从右往左依次给出,不能间隔着设置缺省参数。
- 缺省参数不能在函数声明和定义中同时出现。
- 缺省值必须是常量或者全局变量
说到这里想到了个“有用”的容器std::arry
和静态的数组。std::arry
作用是检查数组的越界问题而静态数组对越界的问题是抽查,但是std::arry
的缺点就是如果你开的空间太大会使栈溢出
就比方这样
#include <array>
int main()
{
array<int, 5> myarray = { 1, 2, 3, 4, 5 };
myarray[6];
return 0;
}
就会报错,那么回到咱们的缺省参数上为什么array
就能精准的判断越界呢?其实myarray[6];
实际上是个函数调用加断言
按需实例化
按需实例化是指模板代码只在实际使用时才被编译器实例化。这意味着模板代码的编译是延迟的,只有在需要时才会生成具体的实例。
例如,如果一个模板函数从未被调用,那么它的代码就不会被编译器实例化。
using namespace std;
#include <iostream>
namespace my
{
template <class T, size_t N = 10>
class array{
public:
T& operator[](size_t index){
assert(index < N);
size(1);//这里程序有问题
return _array[index];
}
const T& operator[](size_t index)const{ return _array[index]; }
size_t size()const{ return _size; }
private:
T _array[N];
size_t _size;
};
}
int main()
{
my::array<int,10>;
return 0;
}
这个代码里面size(1);
这里有着语法问题但是编译的时候并没有报错而是程序顺利运行,这就是按需实例化
类模板的特化允许我们为特定的类型或值提供特定的实现。特化分为两种:
- 全特化:为模板参数提供所有具体的值。
- 偏特化:为模板参数提供部分具体的值。
例如,考虑一个类模板的全特化:
//类模板
template<class T>
bool Less(T left, T right)
{
return left < right;
}
//全特化
template<>
bool Less<int*>(int* left,int* right)
{
return left < right;
}
//偏/半特化
template<class T>
bool Less<int*>(int* left,T right)
{
return left < right;
}
这就是个特化,如果满足特化的条件就走特化如果不满足就走类模板,而偏/半特化就是部分参数特化
模板分离编译
模板分离编译是指将模板定义和实现分离到不同的文件中。这样做的好处是可以减少编译时间,因为编译器不需要重新编译整个模板定义,只需要编译实现部分。
例如,我们可以将模板定义放在头文件中,将实现放在源文件中。
点赞,收藏,加关注嘿嘿