zoukankan      html  css  js  c++  java
  • 模板与泛型编程——重载与模板,可变函数模板,模板特例化

    一、重载与模板

      函数模板可以被另一个模板或普通非模板函数重载。与往常一样,名字相同的函数必须具有不同数量或类型的参数。

      如果涉及函数模板,则函数匹配规则会在以下几方面受到影响:

    • 对于一个调用,其候选函数包括所有模板实参推断成功的函数模板实例。
    • 候选的函数模板总是可行的,因为模板实参推断会排除任何不可行的模板。
    • 与往常一样,可行函数(模板与非模板)按类型转换(如果对此调用需要的话)来排序。当然,可以用于函数模板调用的类型转换是非常有限的。
    • 与往常一样,如果恰有一个函数提供比任何其他函数都更好的匹配,则选择此函数。但是,如果有多个函数提供同样好的匹配,则:——如果同样好的函数中只有一个是非模板函数,则选择此函数。——如果同样好的函数中没有非模板函数,而有多个函数模板,且其中一个模板比其他模板更特例化,则选择此模板。——否则,调用有歧义。

    二、可变函数模板

      一个可变参数模板就是一个接受可变数目参数的模板函数或模板类。可变数目的参数被称为参数包。存在两种参数包:模板参数包,表示零个或多个模板参数;函数参数包,表示零个或多个函数参数。

      我们用一个省略号来指出一个模板参数或函数参数表示一个包。在一个模板参数列表中,class...或typename ...指出接下来的参数表示零个或多个类型的列表;一个类型名后面跟一个省略号表示零个或多个给定类型的非类型参数的列表。在函数参数列表中,如果一个参数的类型是一个模板参数包,则此参数包也是一个函数参数包。

      template<typename T, typename...args>
      std::ostream &print(std::ostream &os, const T &t, const args &...rest);

    args是一个模板参数包;rest是一个函数参数包。args表示零个或多个模板类型参数,rest表示零个或多个函数参数。

    1)sizeof...运算符

      当我们需要知道包中有多少元素时,可以使用sizeof...运算符。sizeof...返回一个常量表达式,而且不会对其实参求值。

      template<typename T, typename...args>
      std::ostream &print(std::ostream &os, const T &t, const args &...rest) {
        std::cout << sizeof...(args) << std::endl; // 类型参数的数目
        std::cout << sizeof...(rest) << std::endl; // 函数参数的数目
      }

    1、可变参数函数模板

      当我们既不知道想要处理的实参的数目也不知道它们的类型时,可变参数函数是很有用的。

      可变参数函数通常是递归的。第一步调用处理包中的第一个实参,然后用剩余实参调用自身。我们自定义的print函数也是这样的模式,每次递归调用将第二个实参打印到第一个实参表示的流中。为了终止递归,我们还需要定义一个非可变参数的print函数,它接受一个流和一个对象。

     1 #include <iostream>
     2 #include <memory>
     3 #include <string>
     4 #include <vector>
     5 
     6 template<typename T>
     7 std::ostream &print(std::ostream &os, const T &t) {
     8     return os << t;
     9 }
    10 
    11 template<typename T, typename...args>
    12 std::ostream &print(std::ostream &os, const T &t, const args &...rest) {
    13     os << t << ", ";
    14     return print(os, rest...); // 递归调用,打印其他实参
    15 }
    16 int main()
    17 {
    18     print(std::cout, 2, 3.3, "hello");
    19     return 0;
    20 }
    View Code

    2、包扩展

      对于一个参数包,除了获取其大小外,我们能对它做的唯一的事情就是扩展它。当扩展一个包时,我们还要提供用于每个扩展元素的模式。扩展一个包就是将它分解为构造的元素,对每个元素应用模式,获得扩展后的列表。我们通过在模式右边放一个省略号(...)来触发扩展操作。

    1 template<typename T, typename...args>
    2 std::ostream &print(std::ostream &os, const T &t, const args &...rest) {
    3     os << t << ", ";
    4     return print(os, rest...); // 递归调用,打印其他实参
    5 }
    View Code

      对args的扩展中,编译器将模式const args&应用到模板参数包args中的每个元素。因此,此模式的扩展结果是一个逗号分隔的零个或多个类型的列表,每个类型都形如const type&。

      第二个扩展发生在对print的递归调用中。在此情况下,模式是函数参数包的名字(即rest)。此模式扩展出一个由包中元素组成的、逗号分隔的列表。

    1)理解包扩展

      C++语言还允许更复杂的扩展模式。例如,我们可以编写第二个可变参数,对其每个实参调用debug_rep,然后调用print打印string。

     1 #include <iostream>
     2 #include <sstream>
     3 #include <memory>
     4 #include <string>
     5 #include <vector>
     6 
     7 template<typename T>
     8 std::ostream &print(std::ostream &os, const T &t) {
     9     return os << t;
    10 }
    11 
    12 template<typename T, typename...args>
    13 std::ostream &print(std::ostream &os, const T &t, const args &...rest) {
    14     os << t << ", ";
    15     return print(os, rest...); // 递归调用,打印其他实参
    16 }
    17 
    18 template<typename T>
    19 std::string debug_rep(const T &t) {
    20     std::ostringstream ret;
    21     ret << "-" << t << "-";
    22     return ret.str();
    23 }
    24 
    25 template<typename... args>
    26 std::ostream &errorMsg(std::ostream &os, const args& ...rest) {
    27     return print(os, debug_rep(rest)...);
    28 }
    29 int main()
    30 {
    31     errorMsg(std::cout, 2, 3.3, "hello");
    32     return 0;
    33 }
    View Code

    3、转发参数包

    三、模板特例化

      编写单一模板,使之对任何可能的模板实参都是最合适的,都能实例化,这并不总是能办到。在某些情况下,通用模板的定义对特定类型是不合适的:通用定义可能编译失败或做得不正确。其他时候,我们也可以利用某些特定知识来编写更高效的代码,而不是从通用模板实例化。当我们不能(或不希望)使用模板版本时,可以定义类或函数模板的一个特例化版本。

    1)定义函数模板特例化

      当我们特例化一个函数模板时,必须为原模板中的每个模板参数都提供实参。为了指出我们正在实例化一个模板,应使用关键字template后跟一个空尖括号对(<>)。空尖括号指出我们将为原模板的所有模板参数提供实参。

    1 template<>
    2 void show(const char* const &x) { // show的特殊版本,处理字符数组的指针
    3     std::cout << x << "    second" << std::endl;
    4 }
    View Code

    2)函数重载与模板实例化

      当定义函数模板的特例化版本时,我们本质上接管了编译器的工作。即,我们为原模板的一个特殊实例提供了定义。重要的是要弄清:一个特例化版本本质上是一个实例,而非函数名的一个重载版本

      我们将一个函数定义为一个特例化版本还是一个独立的非模板函数,会影响到函数匹配。

      注意:模板及其特例化版本应该声明在同一个头文件中。所有同名版本的声明应该放在前面,然后是这些模板的特例化版本。

      将函数定义为特例化版本:

     1 #include <iostream>
     2 #include <sstream>
     3 #include <memory>
     4 #include <string>
     5 #include <vector>
     6 
     7 template<typename T>
     8 void show(const T& x) {
     9     std::cout << x << "    first" << std::endl;
    10 }
    11 
    12 template<>
    13 void show(const char* const &x) { // show的特殊版本,处理字符数组的指针
    14     std::cout << x << "    second" << std::endl;
    15 }
    16 
    17 template<std::size_t N>
    18 void show(const char(&x)[N]) {
    19     std::cout << x << "    third" << std::endl;
    20 }
    21 int main()
    22 {
    23     show("hi");
    24     return 0;
    25 }
    View Code

      将函数定义为普通函数:

     1 #include <iostream>
     2 #include <sstream>
     3 #include <memory>
     4 #include <string>
     5 #include <vector>
     6 
     7 template<typename T>
     8 void show(const T& x) {
     9     std::cout << x << "    first" << std::endl;
    10 }
    11 
    12 void show(const char* const &x) { 
    13     std::cout << x << "    second" << std::endl;
    14 }
    15 
    16 template<std::size_t N>
    17 void show(const char(&x)[N]) {
    18     std::cout << x << "    third" << std::endl;
    19 }
    20 int main()
    21 {
    22     show("hi");
    23     return 0;
    24 }
    View Code

    3)类模板特例化

      我们还可以特例化类模板。

     1 #include <iostream>
     2 #include <sstream>
     3 #include <memory>
     4 #include <string>
     5 #include <vector>
     6 
     7 template<typename T>
     8 class Base {
     9 public:
    10     Base(T x):data(x){}
    11     void show() {
    12         std::cout << "T" << std::endl;
    13     }
    14 private:
    15     T data;
    16 };
    17 // Base的特例化版本
    18 template<>
    19 class Base<int> {
    20 public:
    21     Base(int x) :data(x) {}
    22     void show() {
    23         std::cout << "int" << std::endl;
    24     }
    25 private:
    26     int data;
    27 };
    28 int main()
    29 {
    30     Base<std::string> sb("hi");
    31     sb.show();
    32     Base<int> ib(233);
    33     ib.show();
    34     return 0;
    35 }
    View Code

    4)类模板部分特例化

      与函数模板不同,类模板的特例化不必为所有模板参数提供实参。我们可以指定一部分而非所有模板参数,或是参数的一部分而非全部特性。一个类模板的部分特例化本身是一个模板,使用它时用户还必须为那些在特例化版本中未指定的模板参数提供实参。

      我们只可以部分特例化类模板,而不能部分特例化函数模板。

      由于一个部分特例化版本本质是一个模板,与往常一样,我们首先定义模板参数。类似任何其他特例版本,部分特例化版本的名字与原模板的名字相同。对每个未完全确定类型的模板参数,在特例化版本的模板参数列表中都有一项与之相应。在类名之后,我们要为特例化的模板参数指定实参,这些实参列于模板名之后的尖括号中。这些实参与原始模板中的参数按位置对应。

    5)特例化成员

      我们可以只特例化成员函数而不是特例化整个模板。

     1 #include <iostream>
     2 #include <sstream>
     3 #include <memory>
     4 #include <string>
     5 #include <vector>
     6 
     7 template<typename T>
     8 class Base {
     9 public:
    10     Base(T x):data(x){}
    11     void show() {
    12         std::cout << "T" << std::endl;
    13     }
    14 private:
    15     T data;
    16 };
    17 
    18 template<>
    19 void Base<int>::show() {
    20     std::cout << "int" << std::endl;
    21 }
    22 int main()
    23 {
    24     Base<std::string> sb("hi");
    25     sb.show();
    26     Base<int> ib(233);
    27     ib.show();
    28     return 0;
    29 }
    View Code

  • 相关阅读:
    servlet和JSP页面乱码问题
    用例图——远程网络教学系统
    struts 2 实现表单传送数据到数据库
    Java web jsp页面实现日历的显示 (/WdatePicker控件)
    抽象类与继承相关练习(java)
    (一)、写一个怪物的类,类中有属性姓名(name),攻击力(attack),有打人的方法(fight)。(方法的重写)
    构造方法是练习
    类和对象 练习
    String字符串相关练习
    Java 数组中删除数据
  • 原文地址:https://www.cnblogs.com/ACGame/p/10340275.html
Copyright © 2011-2022 走看看