C++ template笔记

文章介绍了C++中的模板特例化,强调它并非重载,而是一个实例。接着讲解了可变参数模板(VariadicTemplates),展示了如何使用参数包进行函数的递归调用。此外,还提到了参数包的完美转发和类模板的成员函数定义及实例化,包括利用initializer_list进行初始化。最后讨论了类模板分文件编写的解决办法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

模板的特例化

        特例化一个模板时,模板参数列表为空,标明将显式提供所有模板实参。需要注意的是一个特例化的函数模板并不是模板重载,本质上是一个实例。

template<>
/*形参a,b分别为一个指向const char对象的const指针的引用*/
const char* const& getMax(const char* const &a, const char* const &a) {
    return strcmp(a, b) > 0 ? a:b;


const char *a = "hi", *b = "hello";
cout << getMax(a, b) << endl;    //输出hi

可变参函数模板(Variadic Templates)

        C++11允许使用数目可变的模板参数,可变数目的参数称为参数包,用...表示,可包含从0到任意个模板参数。

1)包展开示例一:

#include <iostream>
using namespace std;

void fun()
{
}

template <typename T>
void fun(const T& lastarg) {
    cout << "deal whih the last: " << lastarg << endl;
}

/*
1) C++11允许使用数目可变的模板参数,可变数目的参数称为参数包,用省略号表示,可包含从0到任意个模板参数。
*/
template <typename T, typename...Types>
/*形参args为模板参数包类型,接受可变数目的实参*/
void fun(const T&firstarg, const Types&...args) {
    /*使用sizeof...运算符打印参数包args中参数的个数*/
    cout << "first arg: "<< firstarg << "   sizeof...(args):" << sizeof...(args) << endl;
    /*可以通过递归的方式展开函数模板参数包:第一次处理参数包中的第一个参数,然后用剩余
参数调用自身。为了处理边界条件(包参数rest变为0个,然后若再次回调的话就调用不到本身了),
还需要定义一个同名的处理函数*/
    fun(args...);
}


int main() {
    fun(1, 1.5, "C++");
    return 0;
}

2)包展开示例二: 

/*可以通过递归的方式展开函数模板参数包:第一次处理参数包中的第一个参数,然后用剩余
参数调用自身*/
template <typename T, Typename...Args>
ostream& print(ostream &os, const T &t, const Args&...rest) {
    os << t << " ";    //打印第一个参数
    return print(os ,rest...);    //递归调用
}
/*为了处理边界条件(包参数rest变为0个,然后若再次回调的话就调用不到本身了),还需要定义
一个处理函数,这里写一个带有非可变参的终止函数模板如下所示*/
template <typename T>
ostream& print(ostream &os, const T &t) {
    return os << t;    //打印最后一个参数
}

         上面print模板函数带有3个参数,但是函数体中递归调用语句只有二个参数,执行过程为:参数包rest中的第一个参数与形参t绑定,剩余的参数组成一个新的参数包,当参数包里面只生剩下一个参数时,非可变参模板与可变参模板都匹配,但非可变参模板更特例化,编译器将首选非可变参数模板,例如:

print(cout, 1, 2,5, "C++") << endl;    //输出1 2.5 C++

3)参数包的完美转发   

        可以组合使用可变参模板和forward函数,实现参数包完美转发:

template <typename...Args>
void fun(Args&&...args) {    //foo函数负责将所有的实参不变地传给foo函数
    foo(std::forward<Args>(args)...)
}

/*上面的参数扩展模式std::forward<Args>(args)... 相当于std::forward<Ti>(ti)。
其中Ti为参数包中第i个参数ti的类型。例如有如下foo函数和函数调用*/
void foo(const string &s, int &&i) {
    cout << s << i << endl;
}
/*该调用将扩展为std::forward<const char*>("abc"), std::forward<int>(42)*/
foo("abc", 22); 

类模板

1)成员函数定义

        类模板成员函数具有与类模板相同的模板参数,因此如果在类模板之外定义成员函数,则它们必须以关键字template开始,后接与类模板相同的模板参数列表。注意,紧随类名后面的参数列表代表一个实例化的实参列表,每个参数不需要typename/class说明符,例如:

template <typename T, size_t N>
class Array {
    T m_ele[N];
public:
    Array() { }
    /*initializer_list是C++11标准库提供的新类型,支持具有相同类型但数量未知的列表类型。
    因此,利用initializer_list类型形参可以实现以列表初始化的方式来构造对象。*/
    Array(const std::initializer_list<T>&);
    T& operator[] (size_t i);
    constexpr size_t size() {return N;}
}
/*下面的构造函数初始化列表中,利用T类型的默认初始化方式(T())初始化m_ele中的每一个元素。*/
template <typename T, size_t N>
Array <T, N>::Array(const std::initializer_list<T> &1):m_ele{T()} {
    size_t m = 1.size() < N ? 1.size() : N;
    for(size_t i = 0; i < m; ++i) {
        m_ele[i] = *(1.begin() + i);
    }
}
/*下标运算符函数返回数组m_ele中第i个元素的引用*/
template <typename T, size_t N>
T& Array<T, N>::operator[](size_t i) {
    return m_ele[i];    
}

2)实例化

/*下面代码在编译时创建另一个实例类Array<int, 5>, 创建对象b时,将指向上面的带形参的
构造函数,其中形参1接受初始化列表{1, 2, 3},其余元素具有默认值0*/
Array<int, 5>b = {1, 2, 3};    //创建一个Array<int, 5>类型对象b

for(int i = 0; i < b.size(); ++i)
    cout << b[i] << " ";    //输出结构为1 2 3 0 0

        另外需要注意:类模板实例化时类模板中的成员函数并没有实例化,成员函数只有在被调用时才会被实例化(即产生真正的成员函数) ,成员虚函数除外。

3)分文件编写的2种解决办法

        模板的实例化是由编译器完成的,而不是由链接器完成的,分文件编写的话可能会导致在链接期间找不到对应的实例。因此类模板的分文件编写有二种解决办法。第一种解决方式是直接包含源文件(比如:#include "person.cpp");第二种解决方式是将.h和.cpp中的内容写到一起并将后缀改写为.hpp,这种方法比较常用,但是.hpp后缀只是约定俗称,看到.hpp就知道是类模板,并不是必须的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张帅峰_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值