1. 模板基础
1.1 模板的介绍
(1)模板能实现其他语法难以实现的功能。
(2)模板分为类模板和函数模板,函数模板又分为普通函数模板和成员函数模板。
1.2 类模板基础
/*
* 在头文件XXX.hpp中定义模板
**/
#pragma once
template<typename T>
class MyArray
{
using iterator = T*;
using const_iterator = const T*;
public:
MyArray(size_t count);
~MyArray();
iterator begin() const;
const_iterator cbegin() const;
private:
T* data; // 要使用数组,最好使用头指针,因为C++智能指针对数组的支持并不好
};
// 最好都在类外定义函数,在类外定义的函数需要加上模板头
template<typename T>
MyArray<T>::MyArray(size_t count)
{
if (count)
{
data = new T[count]();
}
else
{
data = nullptr;
}
}
template<typename T>
MyArray<T>::MyArray(size_t count)
{
if (count)
{
data = new T[count]();
}
else
{
data = nullptr;
}
}
template<typename T>
MyArray<T>::~MyArray()
{
if (data)
{
delete[] data;
}
}
template<typename T>
// 重定义的东西需要typename关键字进行指定
// 这是因为::会判断iterator是否为成员变量或成员函数,而typename告诉编译器iterator是一个新的类型
typename MyArray<T>::iterator MyArray<T>::begin() const
{
return data;
}
template<typename T>
typename MyArray<T>::const_iterator MyArray<T>::cbegin() const
{
return data;
}
/*
* 模板类的使用
**/
#include <iostream>
#include"myArray.hpp"
void testMian()
{
MyArray<int> myArrayI(1);
std::cout << *myArrayI.cbegin() << std::endl;
}
模板的实现原理:
模板需要编译两次,第一次只检查最基础的语法;当模板真正被调用时,比如把int带入T后,进行第二次编译,那时生成需要的类或函数。
不管是类模板还是函数模板的声明和定义都必须写在同一个文件中。
2. initializer_list与typename
2.1 initializer_list
initializer_list就是初始化列表,我们可以用初始化列表初始化各种容器,比如vector。
在MyArray模板中加入初始化列表的初始化。
#include<type_traits> // 类型萃取头文件
// 模板特化
template<typename T>
struct get_type
{
using type = T;
};
template<typename T>
struct get_type<T*>
{
using type = T;
};
// 模板类
template<typename T>
class MyArray
{
using iterator = T*;
using const_iterator = const T*;
public:
MyArray(size_t count);
MyArray(const std::initializer_list<T>& list);
MyArray(std::initializer_list<T>&& list);
~MyArray();
iterator begin() const;
const_iterator cbegin() const;
private:
T* data;
};
template<typename T>
MyArray<T>::MyArray(const std::initializer_list<T>& list)
{
if (list.size())
{
unsigned count = 0;
data = new T[list.size()]();
if (std::is_pointer<T>::value) // 萃取技术,判断类型是否为指针
{
for (auto elem : list)
{
data[count++] = new typename get_type<T>::type(*elem);
}
}
else // 如果不为指针,直接浅拷贝
{
for (const auto& elem : list)
{
data[count++] = elem;
}
}
}
else
{
data = nullptr;
}
}
template<typename T>
MyArray<T>::MyArray(std::initializer_list<T>&& list)
{
if (list.size())
{
unsigned count = 0;
data = new T[list.size()]();
for (const auto& elem : list)
{
data[count++] = elem;
}
}
else
{
data = nullptr;
}
}
2.2 typename
- 在定义模板时表示这个是一个特定的类型。
- 在类外表明这是一个自定义类型。
3. 函数模板
3.1 普通函数模板
普通函数模板与类模板写法类似。
namespace mystd
{
template<typename iter_type, typename func_type>
void for_each(iter_type first, iter_type last, func_type func)
{
for (auto iter = first; iter != last; ++iter)
{
func(*iter);
}
}
}
void testMian()
{
std::vector<int> ivec{ 1, 2, 3, 4, 5 };
mystd::for_each(ivec.begin(), ivec.end(), [](int& elem) {
++(elem);
});
}
3.2 类成员函数模板
template<typename T>
class MyVector
{
public:
template<typename T2>
void outPut(const T2& elem);
}
// 类成员函数的类外定义
template<typename T>
template<typename T2>
void MyVector<T>::outPut(const T2& elem)
{
std::cout << elem << std::endl;
}
4. 默认模板参数
如果一个函数模板有了默认参数,之后该参数后续的所有都要加默认参数。
template<typename T, typename allocator_type = std::allocator<T>> // 默认模板参数
class MyVector
{
public:
void MyVector<T, allocator_type>::outPut(T&& elem) // 都要加模板参数
{
std::cout << elem << std::endl;
}
};
5. 模板特化
我们可以通过指定类型,来完成某些更精细的操作。
模板特化的应用:通过特化来获取类型
#include<type_traits> // 萃取技术头文件
template<typename T>
struct get_type
{
using type = T;
};
template<typename T>
struct get_type<T*>
{
using type = T;
};
// 用来获取指针的特定类型
if (std::is_pointer<T>::value) // 萃取技术,判断类型是否为指针
{
for (auto elem : list)
{
// get_type<T>::type 通过模板特化来获取指针的特定类型
data[count++] = new typename get_type<T>::type(*elem);
}
}
注意:当模板函数需要特殊类型处理时就进行重载,当模板类需要特殊类型处理时就进行特化。
6. 万能引用
万能引用是C++中即能当左值,又能当右值的引用。
6.1 万能引用的格式:
(1)T&&(模板类型)
template<typename T> // 只有这个时候确定的T,才能是万能引用
void func(T&& parm) // 注意只有 T&& 是万能引用类型,如const T&&不是万能引用
// 当传入右值时,类型为:T&&
// 当传入左值时,类型为:T& && 引用折叠为 T&
template<typename T>
class MyVector
{
public:
void MyVector<T>::outPut(T&& elem) // 此时T属于类中,在类构建时已经确定,因此不是万能引用
{
std::cout << elem << std::endl;
}
};
(2)auto&&(auto类型)
auto&& elem = var; // auto&&为万能引用类型,同时const auto&&也不是万能引用
6.2 引用折叠
一个引用不是左值引用就是右值引用,当一个万能引用被认为是左值时,T&&类型应该是T& &&,此时类型会被折叠为T&。
7. 完美转发
完美转发的问题,是为了解决万能引用之间的转发。
template<typename T>
void func1(T&& var)
{
func2(var);
}
template<typename T>
void func2(T&& var)
{
// 此时func2接收的值,不管是左值还是右值,都是左值
}
于是C++提供了forward模板解决上述问题。
template<typename T>
void func1(T&& var)
{
func2(std::forward<T>(var)); // 完美转发为了配合万能引用,因此必须加上<T>
// std::forward<T>(var) 会根据var的类型,自动推测左值或右值
}
template<typename T>
void func2(T&& var)
{
// ......
}
8. 萃取技术
原理:通过模板特化的方式对类型进行判断,以达到类型萃取的目的。
struct TrueType
{
static bool Get()
{
return true;
}
};
struct FalseType
{
static bool Get()
{
return false;
}
};
template<class T>
struct MyTypeTraits
{
typedef FalseType IsMyType;
};
// 通过模板特化将int判断出来
template<>
struct MyTypeTraits<int>
{
typedef TrueType IsMyIntType;
};
/*
* 在函数中调用即能判断类型
**/
if (MyTypeTraits<T>::IsMyType::Get())
C++提供了相应的类型萃取头文件type_traits,以供我们进行类型判断。
#include<type_traits>
std::is_integral::value() // 判断是否为整形