模板笔记007 - 声明 形参 实参

声明

  • 类模板和函数模板是c++的两种基本类型的模板
    模板的声明需要引入一个参数化子句:template <...>

  • 联合模板也是类模板的一种

  • 和普通的函数一样,模板函数也可以有缺省参数

template <typename T>
void func1(Stack<T> const&, int max =10)
{}template <typename T>
void func2(Array<T>*, T const& = T的默认构造 ) // 只是为了语法高亮,后一个参数是 T const& = T()
{}

对于基本类型,T()为0

  • 参数化除了用在两种基本类型的模板之外,还可以用于一下三种声明:

    1. 类模板成员函数的定义
    2. 类模板嵌套类成员的定义
    3. 类模板的静态数据成员的定义

    需要注意的是上面3种特殊的情况都不是模板

    template <int I>class A{void open();class B;static int num;};template <int I>void A<I>::open(){}template <int I>class A<I>::B{};template <int I>int A<I>::num = 0;

上面这3种情况,都不是使用自身的模板,而是使用外围类模板,参数也是由外围类模板决定
书上并不建议我们称她们为模板

  • 虚函数

    成员函数模板不能称为虚函数,相反,类模板的普通成员函数可以成为虚函数

  • 每个模板都有一个名字,且在所属作用域下是唯一的
    int a;class a; // oktemplate <typename T>class a; // error
  • 模板名字具有链接
    extern "C++" template <typename T>void func1() {} // 标准链接,是默认的,可以忽略不写,一般也没有写extern "C" template <typename T>void func2() {} // error, 不能有C链接extern "python" template <typename T>void func3() {} // ok, 非标链接,属于兼容性链接,目前(2002年)还没有编译器实现,

模板通常具有外部链接(唯一的例外是static函数模板),所以函数内部不能声明模板(因为函数内部都是局部对象,不能保证模板名字的唯一性)

模板的声明只能出现在namespace scope或是class scope里

  • 模板的基本类型有两种:类模板和函数模板。那什么叫基本模板?

    即普通声明(没有在模板名称后面加< >)

    template <typename T> class A;template <typename T> class B<T>;template <typename T> void func1(T*);template <typename T> void func2<T>(T*);// 13就是基本模板,24就不是.偏特化和全特化就是非基本模板,函数模板大部分是基本模板

模板参数 (形参)

  • 模板声明时需要引入参数化子句,模板参数就在参数化子句里 template <typename T, int I>

    模板参数分3种:类型参数;非类型参数;模板的模板参数

    参数名如果在接下来没有用到,可以省去不写,这点和函数一样

    在同一对< >中,后面的参数可以引用前面的参数,但前面的不能引用后面的。

    template <typename T, T* root, template<T*> class B>class A;
  • 类型参数可以通过template或是class引入

    在模板内部,类型参数的作用相当于typedef 类型定义

    非类型参数是在编译期或是链接期可以确定的值,可以是以下类型:

    1. 整形 枚举
    2. 指针类型(普通对象指针,函数指针,指向成员的指针类型)
    3. 引用类型(对象引用,函数引用)

    除此之外,其他类型都不能作为非类型参数使用

        template <typename T, typename T::B* BB>class A;

    这是一种特殊的情况,用typename来声明非类型参数, 此处typename 是用来修饰T::的

    1. 函数和数组都能用来做模板的非类型参数,只是需要先将他们转换成指针类型
    2. 非类型参数不能具有static / mutable 修饰,可以有const / volatile , 如果这两个用于修饰非类型参数整个,那么编译器将忽略修饰
    3. 非类型模板参数只能是右值,不能被取址, 不能被赋值
  • 模板的模板参数

    1. 模板的模板参数表示的是一个占位符 placehoder
    2. 只能用class 不能用struct 或是 union

      template <template<typename X> class C> // ok
      void f(C<int>* p);template <template<typename X> struct C> // error
      void f(C<int>* p);template <template<typename X> union C> // error
      void f(C<int>* p);
    3. 上面的C就是模板的模板参数,X就是模板的模板参数的参数,X可以有缺省值

      template <template<typename T, typename A = MyA> class B>
      class C
      {B<int> b;B<int, MyA> c; // b 和 c 是等同的,
      };
    4. 模板的模板参数的模板参数名只能被模板的模板参数的其他参数声明使用

    这个比较绕口,英文如下:the name of a template parameter of a template template parameter can be used only in the declaration of other parameters of that template template parameter.
    翻译一下:template template parameter 是上面的B, a template parameter of B 指B的模板参数T和A,整句话连在一起就是:B的模板参数只能被B的其他模板参数使用.

    通常模板的模板参数的参数名称在后面并不会用到, 所以名称也常常省略掉

  • 模板参数的缺省值

    缺省值的规则和函数缺省值的规则一样,需要注意一点的是:缺省值不能依赖自身的参数,但可以依赖前面的参数;再者缺省值不能重复声明:

    template <typename T = void>
    class A;template <typename T = void>
    class A; // error:重复出现缺省实参

模板实参

  • 什么是模板实参?

    模板实例化时,用来替换模板参数的这个值就是实参,上面提到过的缺省值,也是实参(这点和函数类似)

  • 有哪些方式来指定实参
    1. 显式模板实参:模板名称后面跟< int,double>, 组合起来称为template-id
    2. 注入式类名称,注入式表示在模板类的作用域里面使用了模板名,这时模板名表示的是template-id 等同于X< P1,P2… >
    3. 缺省模板实参,上面已经介绍过
    4. 实参推导,也叫实参演绎:对于非显示指定的函数模板实参,根据上下文会自动推导。如果所有的模板实参都可以通过推导获得,那么在函数模板名称后面就不需要指定< >
  • 函数模板的实参
    max<int>(1, 3); //  显式指定模板实参max(1.0, 2.0); // 自动推导

对于得不到自动推导机会的参数,我们把她们放在模板参数列表的开始处,用于显式指定

  • SFINAE

substitution failure is not an error 替换失败并不是错误,她的原则是允许创建无效的类型,但不允许试图计算无效的表达式

    template <int I> void f(int (&)[24/(4-I)]); // error:不能应用sfinae

对应不同类别的模板参数,介绍一下相应的实参

上面也说到了模板参数有3类:类型参数;非类型参数;模板的模板参数

  • 类型实参
    平常大部分类型都可以用作类型实参,除了下面两种特殊情况

    1. 局部类和局部枚举不能作为模板的类型实参,换句话:函数定义内部声明的类型不行
    2. 未命名的类或者未命名的枚举,有个例外:使用typedef 声明的未命名类/枚举可以作为类型参数
    template <typename T> class List{};typedef struct{double x, y, z;} Point;typedef enum {red, green, bule} *ColorPtr;int main(){struct B{int a;int b;};List<B*> error1;       // 局部类不行List<ColorPtr> error2; // *ColorPtr 表示的是类型, ColorPtr不是List<Point> ok;        // return 0;}

需要注意的是:替换之后,需要确保替换之后的构造是有效的

  • 非类型实参

    非类型实参可以是以下值:

    1. 另一个类型正确的非类型实参
    2. 编译期的整形常值,或是枚举。隐式转换成功即可
    3. 前面有&的外部变量或是函数名,对于数组和函数,前面的&可以省略。这类实参是用来匹配指针类型
    4. 对于引用类型的非类型形参,前面没有&的外部变量和外部函数也是ok的
    5. 一个指向成员的指针常量。eg:类似&C::m,其中C是类名,m是非静态成员。这类实参是用来匹配成员指针的

    需要注意的是:当实参匹配的是指针类型或是引用类型时,有些类型转换是无效的

    1. 用户定义的类型转换。单参构造函数;重载类型转换运算符;派生类到基类的转换;
    2. 隐式转换。隐式转换唯一生效的情况是对实参使用const/volatile 修饰时
      template <typename T, T nontype_param>
      class A;A<int, 123>* a1; // 整形int i;
      A<int*, &i>* a2; // 外部变量的地址void f();
      void f(int);
      A<void(*)(int), f>* a3; // 重载解析选择了f(int); f 前面的&会被省略class B
      {
      public:int a;static bool b;
      };
      A<bool&, B::b>* a4; // 引用类型,&可以省略,类的静态成员都可以这样用
      A<int B::*, &B::a>* a5; // 指向成员的指针,非静态成员template <typename T>
      void func();
      A<void(), &func<double>>* a6; // 函数模板实例就是函数

    有一些值不能作为非类型实参:空指针/浮点类型/字符串。针对字符串,我们可以用一个extern数组变量来存储,这是一种折中的办法。

    下面是一些错误例子:

    template <typename T, T nontype_params>
    class A;class base
    {
    public:int i;
    };class derived : public base
    {}obj;A<base*, &obj>* error1; // 这里的类型转换是无效的
    A<int&, base.i>* error2; // 域运算符. 后面的变量不会被当成变量int a[10];
    A<int*, &a[0]>* error3; // 单一数组元素的地址并不可取
  • 模板的模板实参

    模板的模板实参必须是一个类模板(class struct union),下面这句话要注意:

    在匹配过程中,模板的模板实参的缺省值不会被考虑,如果模板的模板参数具有缺省值,那么会匹配时会考虑
    翻译:

    
    #include <list>// list 的声明
    namespace std
    {template <typename T, typename Allocator = allocator<T>>class list;
    }template <typename T1,typename T2,template <typename> class Container>
    class AB
    {Container<T1> a;Container<T2> b;
    };int main()
    {AB<int, double, std::list> ab; // 会报错:std::list是具有多参数的模板...
    }// 这里模板的实参用的是std::list ,她是具有缺省参数的,但这儿不考虑
    // 需要考虑的是类模板AB的模板参数,如果加上缺省值就ok了, 将第三个参数改成如下就行
    template <typename = std::allocator<T>> class Container 
  • 实参的等价
    1. 当每个对应的实参都相等时,称两个模板是等价的:
      A< int, 3+2>p1; A< int, 1*5> p2; 是等价的
    2. 函数模板实例化之后,和普通函数不等价。
    3. 类的成员函数模板不能修饰为虚函数,因为成员函数模板和普通函数不等价
    4. 类的构造函数模板实例化的构造函数一定不是缺省的拷贝构造函数

友元

声明友元是指:授予”某个类或函数访问友元声明所在的类”的权力

  • 如果把类模板的实例声明为其他类或者类模板的友元,该类模板一定要可见,对于普通类就没有这个要求,说白一点:如果类模板实力声明是友元,要么这个类模板要么有前置声明,要么可见
  • 友元函数
    template <typename T1, typename T2>void func(T1, T2);class A{// 友元函数模板实例就是友元函数, 写法就是在函数名func后面加<>// 模板实例化的参数有两种方式确定:<>里的特化,()里的自动推导// 这两种确定参数的方式可以混合用,只是两者需要一致friend void func<>(int, int); // 自动推导friend void func<int, int>(int, int); // 全特化和自动推导一致friend void func<char>(char, int);  // 偏特化和自动推导一致friend void func<char>(char&, char);  // 不一致friend void func<>(int, int){}  // 错误:友元声明不允许出现定义};

如上例,友元函数模板实例的写法如下:
1. friend 关键字
2. 函数返回类型
3. 函数名
4. <模板实参>
5. (参数列表)

4和5部分都只有一个作用,确定实例化的参数。其中4是特化,5是自动推导,也叫自动演绎

如果友元函数名后面没有< >, 那有可能是下面两种情况之一
1. 如果函数名的形式不是::func, 那就说明这个友元函数是一个普通的友元函数,而不是一个友元函数模板实例,如果是首次声明,可以是定义
2. 如果函数名前面有::func, 那么在友元之前,一定要先声明函数或函数模板,匹配的过程中优先匹配普通函数,受限的友元不能是定义

    // 术语// 受限:函数名的形式如 ::func// 非受限:函数名前面没有双冒号(域运算符)void func(void*);template <typename T>void func(T);class A{friend void func(int){} // 非受限,表示普通函数// 未匹配,表示首次声明,可以定义friend void ::func(void*); // 受限,但会优先匹配到普通函数friend void ::func(int); // 受限,匹配到了函数模板实例friend void ::func<double*>(double*); // 受限,但带了<>就表示是函数模板实例,只有一个前提条件:函数模板要可见friend void ::func_other(){}; // 受限的友元不能是一个定义};
  • 类模板中的友元函数 友元模板 以后遇到了再分析

模板笔记007 - 模板的声明、形参、实参相关推荐

  1. 笔记②:牛客校招冲刺集训营---C++工程师(面向对象(友元、运算符重载、继承、多态) -- 内存管理 -- 名称空间、模板(类模板/函数模板) -- STL)

    0618 C++工程师 第5章 高频考点与真题精讲 5.1 指针 & 5.2 函数 5.3 面向对象(和5.4.5.5共三次直播课) 5.3.1 - 5.3.11 5.3.12-14 友元 友 ...

  2. 【C++学习笔记】模板(一)

    模板的概念 模板是实现代码复用的一种手段,是C++重要的特征之一此前在定义函数与类时,必须明确指明其中用到的变量的数据类型.如果需要对不同类型的数据进行相同的处理,就需要重新定义函数或者类. 我们知道 ...

  3. C++笔记(自定义模板)

    //盛夏白瓷梅子汤,碎冰破壁叮咚响 2020.6.7 苏 预备tips: ①形如int arr[10],double b[2]即可创建数组 ②//用new申请的动态内存可以用delete释放 int ...

  4. C++学习笔记:模板参数

    本章节主要学习一下模板形参的基本知识. 模板参数有三种类型:类型模板参数.模板的模板参数(以模板作为模板的参数).非类型模板参数. 类型模板参数 类型模板参数是我们使用模板的主要目的.也就是普通的类型 ...

  5. cpp学习笔记:模板

    一.模板的作用 建立一个通用函数,其函数返回值类型和形参类型可以不具体确定,用一个虚拟的类型来代表. template<typename T> 二.示例 1.不使用模板写两个交换变量数值 ...

  6. C++笔记7:C++提高编程1:模板—[函数模板和类模板]

    0820 C++提高编程: 1.模板-[函数模板和类模板] 2.初识STL 3.STL-常用容器 4.STL-函数对象 5.STL-常用算法 C++提高编程引言: C++除了面向对象编程思想,还有泛型 ...

  7. c++笔记 函数模板

    一.模板概论 c++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函 数,其函数类型和形参类型不具体制定,用一个虚拟的类型来代表.这个通用函数 就成为函数模板 ...

  8. C++ Primer 学习笔记_75_模板与泛型编程 --模板定义

    模板与泛型编程 --模板定义 引言: 所谓泛型程序就是以独立于不论什么特定类型的方式编写代码.使用泛型程序时,我们须要提供详细程序实例所操作的类型或值. 模板是泛型编程的基础.使用模板时能够无须了解模 ...

  9. Python-OpenCV 笔记9 -- 模板匹配

    Python-OpenCV 笔记9 – 模板匹配 1.模板匹配:matchTemplate() 函数原型: matchTemplate(image, templ, method[, result[, ...

最新文章

  1. 超年轻!93年小伙已是985大学教授、博导!
  2. win7安装MongoDB学习笔记
  3. 正则化极限学习机_手写逻辑回归(带l1正则)
  4. GO语言基础之reflect反射
  5. ubuntu~vim操作
  6. SSM框架整合(一)
  7. 【Python】map()函数
  8. 清空数据库错误:因为该表正由 FOREIGN KEY 约束引用 解决办法
  9. jvm调优:使用jconsole监控Jboss
  10. C4D立体素材|旅游度假主题海报,设计点睛之笔
  11. ibm+i+to+mysql_IBM X3650 M3下配置nginx+tomcat+mysql
  12. 【回顾】推荐系统工程师技能树
  13. 不重启程序使用最新版package
  14. mysql xmlhttp_php_xmlhttp 乱码问题解决方法
  15. android黑域系统文件,优雅地使用安卓手机,黑域免root使用教程
  16. 问题:检测到远端X服务正在运行中(CVE-1999-0526)
  17. 本科毕设不通过是什么原因,哪个少年不曾为如何能够顺利优秀毕业而愁眉苦战
  18. Python 简易实现 base64 编码
  19. 复数值神经网络matlab,学界 | Yoshua Bengio等提出深度复数网络:用复数构建深度神经网络(已开源)...
  20. iOS10开发哪些坑

热门文章

  1. 02 四旋翼无人机的组成与拼装(上)
  2. 在统计学中参数的含义是指_第一篇 理解统计学中的基本概念
  3. 江苏2022农民丰收节 国稻种芯:主场活动在苏中地区泰兴开幕
  4. android注解Dagger2,Dagger 2 在 Android 上的使用(二)
  5. Kconfig语法详解
  6. RT-Thread 内存泄漏分析利器 memtrace+ramdump
  7. vs2019无法打开包括文件
  8. Python量化交易02——双均线策略(移动平均线)
  9. ubuntu上的疑难杂症(不定期更新……)
  10. 计算机网络管理员述职报告,网络管理员述职报告