目录
模板参数
分类:被class 或 typename修饰的参数 和 常量
注意事项:常量不能是浮点数、类类型的对象、字符串
被class 或 typename修饰的参数
基本概念:该参数是一个不确定类型,模板在实例化时才会将其替换为具体的类型,即运行时替换
#include <iostream>
template<typename T>
class MyClass
{
public:
T value;
MyClass(T v) : value(v) {}
void display()
{
std::cout << "Value: " << value << std::endl;
}
};
int main()
{
MyClass<int> obj(10); // T 被替换为 int
obj.display(); // 输出: Value: 10
}
非类类型模板参数
基本概念:该参数是值或常量,模板在编译时该参数就会进行替换,即编译时替换
template<typename T, int N>
class Array
{
public:
T arr[N]; // 使用非类类型模板参数 N 来定义数组的大小
};
int main()
{
Array<int, 5> intArray; // N 被替换为 5,T 被替换为 int
}
C++11的数组容器-array
基本概念:C++11 引入的 std::array
是标准库中的一种容器,它对初始数组的进行了封装,提供了更多的可用接口
包含头文件:<array>
格式:std::array<T, N> arr
T
:数组元素的类型N
:数组的大小(编译时确定)
特点:
- 固定大小:
std::array
的大小在编译时确定,不能在运行时更改 - 内存连续性:
std::array
的元素存储在连续的内存块中,可以通过指针操作访问数组元素 - 支持 STL 接口:
std::array
提供了标准容器的常用接口,例如begin()
、end()
、size()
等,并且支持与标准库算法的配合使用(比如std::sort
)
#include <iostream>
#include <array>
#include <algorithm>
int main() {
std::array<int, 5> arr = {5, 3, 1, 4, 2};
// 对数组进行排序
std::sort(arr.begin(), arr.end());
// 输出排序后的结果
for (const auto& elem : arr) {
std::cout << elem << " ";
}
std::cout << std::endl;
}
- 安全性增强:
std::array
提供了边界检查的at()
函数,在数组访问越界时抛异常
#include <iostream>
#include <array>
#include <stdexcept>
int main()
{
std::array<int, 5> arr = {1, 2, 3, 4, 5};
try
{
// 正常访问
std::cout << "Element at index 2 (using at()): " << arr.at(2) << std::endl;
// 越界访问,at() 会抛出异常
std::cout << "Element at index 10 (using at()): " << arr.at(10) << std::endl;
}
catch (const std::out_of_range& e)
{
std::cerr << "Out of range exception: " << e.what() << std::endl;
}
// 使用 operator[] 访问,不进行边界检查
std::cout << "Element at index 2 (using []): " << arr[2] << std::endl;
// 如果越界,行为未定义
std::cout << "Element at index 10 (using []): " << arr[10] << std::endl; // 可能会导致未定义行为
}
- 与 C 风格数组的兼容性:
std::array
提供了data()
函数,可以返回一个指向底层 C 风格数组的指针,便于与传统 C 风格代码的互操作
#include <iostream>
#include <array>
int main() {
std::array<int, 5> arr = {1, 2, 3, 4, 5};
// 获取底层的C风格数组指针
int* p = arr.data();
// 使用指针访问数组元素
for (int i = 0; i < arr.size(); ++i) {
std::cout << p[i] << " ";
}
std::cout << std::endl;
}
按需实例化
基本概念:编译器在编译阶段不会直接生成模板的代码,而是等到模板被实例化时才会生成对应的机器码,这里所谓的实例化就是指模板的参数被某个具体的类型进行替换时(编译器在第一次看到 MyClass<T>
的定义时,只记录模板的定义,不会为 MyClass<T>
生成具体的代码;当 MyClass<int>
被使用时,编译器实例化了 MyClass<int>
,此时它生成了具体的机器码)在未被使用时,编译器知道模板的结构和逻辑,但它不会真正地去生成机器码
错误检查:运行下面的程序并查看运行效果
#include <iostream>
#include <array>
#include <vector>
#include <assert.h>
using namespace std;
namespace bit
{
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
{
assert(index < N);
return _array[index];
}
size_t size()const
{
return _size;
}
bool empty()const
{
return 0 == _size;
}
private:
T _array[N];
size_t _size;
};
}
int main()
{
bit::array<int> a1;
cout << a1.empty() << endl;//不报错
//a1[1];//增加一个调用a1[1]时报错
return 0;
}
-
main()
中调用a1[1]
时,编译器才会实例化operator[]
,此时才会暴size(1)
的语法错误
注意事项:因为编译器优化的原因所以还未运行时 wrongFunction();编译器就会提示有错误,同时对于编译器虽然不对模板的细小逻辑进行检查,但是仍然会对其中少或多了{而产生报错(主打一个不要在意这些细节)
模板的特化
基本概念:在原模板类的基础上,针对特殊类型进行特殊化处理
匹配机制:使用最符合自身参数列表的类模板
分类:全特化和偏特化
- 全特化:模板参数列表中的全部参数确定化
- 偏特化:模板参数列表中的部分参数确定化
注意事项:
1、偏特化可以将参数类型进一步的进行规定,比如指针类型、引用类型等
2、所有的模板特化在使用前都需要提供一个通用模板,特化后的模板可以根据需要新增新的成员变量,当然也可以不用带成员变量
#include <iostream> using namespace std; // 通用模板类 template<class T1, class T2> class Data { public: Data() { cout << "Data<T1,T2>" << endl; } private: T1 _d1; T2 _d2; }; // 类模板的偏特化:当第二个模板参数是 char 时,对 T1 保持通用 template<class T1> class Data<T1, char> { public: Data() { cout << _d3 << endl; cout << "Data<T1, char>" << endl; } private: T1 _d3 = 'c';//定义新的成员变量 }; int main() { Data<char, char> d1; // 使用偏特化(第二个参数是 char) return 0; }
偏特化中有一个新增的成员变量:
函数模板特化
基本概念:函数模板只支持全特化,不支持偏特化,因为函数模板和函数重载的规则会产生冲突,如果允许偏特化,编译器在匹配时可能会难以选择使用哪个函数
格式:
// 通用函数模板
template <typename T>
返回类型 函数名(参数列表)
{
// 通用模板的实现
}
// 函数模板特化(针对特定类型)
template <>
返回类型 函数名<特定类型>(参数列表)
{
// 特化版本的实现
}
#include <iostream>
using namespace std;
// 通用函数模板
template<typename T1, typename T2>
void func(T1 a, T2* s)
{
cout << "Generic template version: " << a << ", " << *s << endl;
}
// 针对 <int, int*> 类型组合进行特化
template<>
void func<int, int>(int a, int* s)
{
cout << "Specialized template for <int, int*>: " << a << ", " << *s << endl;
}
int main()
{
int x = 10;
int y = 20;
func(x, &y); // 调用特化版本
double z = 30.5;
func(100, &z); // 调用通用版本
return 0;
}
关于func<int,int>而不是func<int,int*>的解释:在模板特化时我们只关心模板参数的基础类型,<>中只要写基础类型即可,()中可以写指针等类型
类模板的全特化
格式:
// 通用类模板
template <typename T>
class MyClass
{
// 通用模板的实现
};
// 全特化:针对特定类型进行完整特化
template <>
class MyClass<int>
{
// 针对 int 类型的特化实现
};
优先调用全特化模板:
#include <iostream>
using namespace std;
// 通用模板类
template<typename T>
class MyClass
{
public:
void show()
{
cout << "Generic template" << endl;
}
};
// 全特化:针对 int 类型
template<>
class MyClass<int>
{
public:
void show()
{
cout << "Specialized template for int" << endl;
}
};
int main()
{
MyClass<double> obj1; // 使用通用模板
obj1.show(); // 输出: Generic template
MyClass<int> obj2; // 使用全特化版本
obj2.show(); // 输出: Specialized template for int
return 0;
}
类模板的偏特化
格式:
#include <iostream>
using namespace std;
// 通用模板类
template<class T1, class T2>
class Data {
public:
Data() {
cout << "Data<T1,T2>" << endl;
}
private:
T1 _d1;
T2 _d2;
};
// 偏特化:当第二个模板参数是 char 时,对 T1 保持通用
template<class T1>
class Data<T1, char>
{
public:
Data()
{
cout << "Data<T1, char>" << endl;
}
};
// 偏特化:当两个模板参数都是指针类型时
template<class T1, class T2>
class Data<T1*, T2*>
{
public:
Data()
{
cout << "Data<T1*, T2*>" << endl;
}
};
// 偏特化:当第一个参数是引用类型,第二个参数是指针类型时
template<class T1, class T2>
class Data<T1&, T2*>
{
public:
Data()
{
cout << "Data<T1&, T2*>" << endl;
}
};
int main()
{
Data<char, char> d3; // 使用偏特化(第二个参数是 char)
Data<char*, char*> d4; // 使用偏特化(两个参数是指针)
Data<int*, char*> d5; // 使用偏特化(两个参数是指针)
Data<int*, string*> d6; // 使用偏特化(两个参数是指针)
Data<int&, string*> d7; // 使用偏特化(第一个参数是引用,第二个参数是指针)
return 0;
}
选择优先级
优先级:实例化类对象时,选择使用的类模板顺序是 全特化 > 偏特化 > 通用模板
#include <iostream>
using namespace std;
// 通用模板类
template<typename T>
class MyClass
{
public:
void show()
{
cout << "Generic template" << endl;
}
};
// 偏特化:针对指针类型进行偏特化
template<typename T>
class MyClass<T*>
{
public:
void show()
{
cout << "Partial specialization for pointer types" << endl;
}
};
// 全特化:针对 int 类型进行全特化
template<>
class MyClass<int>
{
public:
void show()
{
cout << "Full specialization for int" << endl;
}
};
int main()
{
MyClass<double> obj1; // 通用模板
obj1.show(); // 输出: Generic template
MyClass<int> obj2; // 全特化
obj2.show(); // 输出: Full specialization for int
int x = 42;
MyClass<int*> obj3; // 偏特化,针对指针类型
obj3.show(); // 输出: Partial specialization for pointer types
return 0;
}
模板的分离编译
基本概念:一个程序/项目由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式,但是对于类模板而言,不能使用分离编译模式
解释:模板的代码生成是在编译时进行的,如果模板的定义在源文件a.cpp,而模板的声明在头文件b.hpp,当另一个源文件c.cpp包含b.hpp并尝试使用模板时,由于模板的定义在a.cpp中,所以即使c.cpp看不到模板的实现,无法生成实例化代码,导致出错,故一般将类模板的定义在头文件
22、2:33处
总结
模板的优点:
- 复用了代码,节省资源,更快的迭代开发,C++的标准模板库也因此而生
- 增强代码灵活性
模板的缺点:
- 模板会导致代码膨胀问题,也会导致编译时间变长
- 出现模板编译错误时,错误信息十分混乱,不易定位
~over~