模板与泛型

本文详细介绍了C++中的模板概念,包括函数模板、类模板、typename的使用、成员函数模板、模板显式实例化、using关键字、模板特化(全特化与偏特化)、函数模板特化及可变参数模板。通过示例解释了模板的工作原理和如何避免重复实例化问题。

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

目录

模板简要

template<typename T>
template<typename T = int>  //默认值
  • 类型T必须支持模板中的各种操作
  • 模板在程序编译期间被实例化,T的值必须是编译期常量表达式
  • 模板不会产生代码,只有在调用模板时才会产生特定类型的代码
  • 模板参数可以有默认值

函数模板

template<typename T>
const T& max(const T& a, const T& b)
{
    return a > b ? a : b;
}

int main()
{
    max(12,34);             
    //编译器根据函数实参推断出T是int类型,int类型支持 > 运算符

    max<double>(12.34,56);  
    //必须显式指定模板参数,编译器推断不出来

    max<A>(A(),A());        
    //类A必须支持 > 运算

    return 0;
}
  • 类型T必须支持函数模板里的各种操作,比如上面这个max函数模板,类型T必须支持>比较运算符。
  • 函数模板本身不会产生代码,只有调用函数模板时,才会在编译期间生成一个特定的函数。
  • 如果不调用函数模板,就不会产生任何代码。
//普通类型作为模板类型参数
template<typename T,int Q>
const T& max(const T& a, const T& b)
{
    return a > b ? a : b;
}

int main()
{
    max<int,5>(12,34);      
    //此时的5也就是模板类型int Q没什么用,仅做测试

    int a = 5;
    max<double,a>(12.34,56);  
    //编译失败,模板参数的值必须在编译期间就能确定,只能使用常量,const 或者 constexpr
    
    return 0;
}
  • 普通类型也可以作为模板参数,例如intconst int
  • 类类型和浮点类型不能作为模板参数

类模板

  • 必须显式指定模板类型参数,编译器推断不出来
  • 类模板的函数声明和实现必须放在同一个头文件中
  • 类模板的成员函数(包括普通函数)只有为程序所用才进行实例化。如果某函数从未使用,则不会实例化该成员函数
  • 类模板参数也可以是普通类型
template<typename T>
class Myclass
{
public:
    Myclass();
    ~Myclass()
    {
        //析构函数,类内定义
    }
    Myclass<T>& operator=(const Myclass& myclass);
    Myclass& operator=(const Myclass& myclass);
    //在类内,上面两种方式均可,<T>可以不加,但在类外定义一定要加<T>
    //因为Myclass<T>才是完整类,Myclass只是一个模板不是类
};

template<typename T>
Myclass<T>::Myclass()
{
    //构造函数,类外定义,注意::之前是带<T>的完整类名
}

template <typename T>
Myclass<T>& Myclass<T>::operator=(const Myclass& myclass)
{
    //类外定义
}

typename

  • typename用于模板参数列表关键字,此时可以与class关键字互换
  • typename用于声明其后是一个类型,此时不可以与class关键字互换
template<class T>       //此时typename可与class互换
class Myclass
{
public:
    using point = T*;   //定义一个类内类型
    point myfunc();     //返回类型是类内类型
};

template<typename T>
typename Myclass<T>::point Myclass<T>::myfunc()
{
    //类外实现时,必须要加typename声明其后的Myclass<T>::point是一个类型
}

成员函数模板

  • 成员函数模板不能是虚函数
  • 成员函数模板本身是函数模板,能够推断模板类型
  • 不调用成员函数模板就不会实例化函数
//普通类的成员函数模板
class Myclass
{
public:
    template<typename T>
    T func(T temp);
};

template<typename T>
T Myclass::func(T temp)
{
    //...
}
//模板类的成员函数模板
template<typename T>
class Myclass
{
public:
    template<typename F>
    F func(F temp);
};

template<typename T>    //类的模板参数列表
template<typename F>    //函数的模板参数列表
F Myclass<T>::func(F temp)
{
    //...
}

模板显式实例化

有这样一个问题

如果一个头文件中定义了一个模板,在两个或多个源文件中都使用了这个模板
因为每个文件都是独立编译,最后再链接为可执行程序
那么在每个源文件中都会生成一个模板的实例
如果模板类型还相同,就会生成相同的实例,也就是重复代码。

解决方法

在一个源文件中显式实例化

template Myclass<int>;          //显式实例化类模板
template int& add(int&, int&);  //显式实例化函数模板

在其他使用到相同类型的源文件中显式声明

extern template Myclass<int>;           //显式声明类模板
extern template int& add(int&, int&);   //显式声明函数模板

缺点

  • 有的编译器不会实现这种效果,还是会在每个源文件中都实例化
  • 这种显式实例化会实例化类模板里的所有函数,而不是调用的时候再实例化

using

//函数指针
template<typename T>
using func = T(*)(T, T);

int sum(int a, int b)
{
    return a + b;
}

int main()
{
    func<int> f = sum;
    std::cout << f(12, 34);
    return 0;
}

模板特化

  • 特化与泛化相反,是对特殊的类型进行特殊的处理
  • 模板特化依赖于泛化,没有泛化的模板,也就没有特化的模板

全特化

//泛化的类模板
template<typename T,typename F>
class Myclass
{
public:
    Myclass() 
    {
        cout << "泛化版本" << endl;
    }
    void func()
    {
        cout << "泛化版本func()函数" << endl;
    }
};

//<int,int>全特化的类模板
template<>
class Myclass<int, int>
{
public:
    Myclass()
    {
        cout << "<int,int>特化版本" << endl;
    }
    //这样特化之后,<int,int>实例出的类中就不存在func()函数
};

//特化<int,double>的成员函数
template<>
void Myclass<int, double>::func()
{
    cout << "特化<int,double>::func()函数" << endl;
}

偏特化

模板参数数量上偏特化

template<typename T,typename F,typename U>
class Myclass
{
public:
    Myclass() 
    {
        cout << "泛化版本" << endl;
    }
};

template<typename T>
class Myclass<int, T, int>
{
public:
    Myclass()
    {
        cout << "<int,T,int>偏特化版本" << endl;
    }
};

模板参数范围上偏特化

template<typename T>
class Myclass
{
public:
    Myclass() 
    {
        cout << "泛化版本" << endl;
    }
};

template<typename T>
class Myclass<const T>
{
public:
    Myclass()
    {
        cout << "<const T>偏特化版本" << endl;
    }
};

template<typename T>
class Myclass<T*>
{
public:
    Myclass()
    {
        cout << "<T*>偏特化版本" << endl;
    }
};

template<typename T>
class Myclass<T&>
{
public:
    Myclass()
    {
        cout << "<T&>偏特化版本" << endl;
    }
};

template<typename T>
class Myclass<T&&>
{
public:
    Myclass()
    {
        cout << "<T&&>偏特化版本" << endl;
    }
};

函数模板特化

函数模板只能全特化,不能偏特化

template<typename T, typename U>
void add(T a, U b)
{
    cout << "泛化" << endl;
}

template<>
void add<int, int>(int a,int b)
{
    cout << "特化" << endl;
}

可变参数模板

  • C++ 2.0新特性,模板可以接受任意个数的参数。
  • 三个点号...有了特殊意义。
  • 参数可以是任意个数,并且任意类型,类型可以不相同。

可变参数函数模板

//终止函数
void print()
{
}

//参数包展开
template <typename T, typename... Types>
void print(const T& firstArg,const Types&... args)
{
    cout << firstArg << endl;
    cout << sizeof...(args) << endl;
    print(args...);
}

template <typename... Types>
void print(const Types&... args)
{
    cout << sizeof...(args) << endl;
}

int main(int argc, char* argv[])
{
    //可以省略print<>里的内容,函数模板自动推断
    print(7.5, "hello", bitset<16>(377), 42);
    return 0;
}
  • main中的print调用第二个print(第二个print比第三个print更特化),此print接受一个参数和一个包(不定参数)。因此至少需要一个参数(包可以为空)。
  • 第一个print用来处理第二个print的最后一次调用(最后没有参数了)

可变参数类模板

  • 递归继承方式递归组合方式 展开参数包
    template<typename... Types>
    class Myclass
    {
    public:
        Myclass()
        {
            cout << "主模板 && 终止模板" << endl;
        }
    };
    递归继承方式展开参数包
    template<typename T,typename... Types>
    class Myclass<T, Types...> :public Myclass<Types...>
    {
    public:
        Myclass(T t, Types... types) :Myclass<Types...>(types...)
        {
            cout << typeid(T).name() << ":" << t << endl;
        }
    };
    递归组合(复合)方式展开参数包
    template<typename T,typename... Types>
    class Myclass<T, Types...>
    {
    public:
        Myclass(T t, Types... types) :m_class(types...)
        {
            cout << typeid(T).name() << ":" << t << endl;
        }
        Myclass<Types...> m_class;
    };
    main函数调用
    int main(int argc, char* argv[])
    {
        //必须显式指定<>中的模板参数
        Myclass<int, double, char> myc(1, 12.3, 'q');
        return 0;
    }
    程序运行结果:

    主模板 && 终止模板
    char:q
    double:12.3
    int:1

  • tuple和递归调用 展开参数包
    template<int count,int maxCount,typename... T>
    class Myclass
    {
    public:
        static void func(const tuple<T...>& t)
        {
            cout << get<count>(t) << endl;
            Myclass<count + 1, maxCount, T...>::func(t);
        }
    };
    
    //特化版本,用于结束递归调用
    template<int maxCount, typename... T>
    class Myclass<maxCount, maxCount, T...>
    {
    public:
        static void func(const tuple<T...>& t)
        {
            //空
        }
    };
    
    template<typename... T>
    void func(const tuple<T...>& t)
    {
        Myclass<0, sizeof...(T), T...>::func(t);
    }
    
    
    int main(int argc, char* argv[])
    {
        tuple<float, int, char> t(12.3f, 100, 'y');
        func(t);
        return 0;
    }
    程序运行结果:

    12.3
    100
    y

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值