C++ Template 基础篇(一):函数模板[通俗易懂]

C++Template基础篇(一):函数模板Template所代表的泛型编程是C++语言中的重要的组成部分,我将通过几篇blog对这半年以来的学习做一个系统的总结,本文是基础篇的第一部分。CTemplate基础篇一函数模板为什么要有泛型编程函数模板定义普通函数模板成员函数模板为什么成员函数模板不能是虚函数virtual实参推断如何使用当返回值类型也是参数时实参推断时的自动类

大家好,又见面了,我是你们的朋友全栈君。

Template 基础篇-函数模板

Template所代表的泛型编程是C++语言中的重要的组成部分,我将通过几篇blog对这半年以来的学习做一个系统的总结,本文是基础篇的第一部分。

为什么要有泛型编程

C++是一门强类型语言,所以无法做到像动态语言(python javascript)那样子,编写一段通用的逻辑,可以把任意类型的变量传进去处理。泛型编程弥补了这个缺点,通过把通用逻辑设计为模板,摆脱了类型的限制,提供了继承机制以外的另一种抽象机制,极大地提升了代码的可重用性。

注意:模板定义本身不参与编译,而是编译器根据模板的用户使用模板时提供的类型参数生成代码,再进行编译,这一过程被称为模板实例化。用户提供不同的类型参数,就会实例化出不同的代码。

函数模板定义

把处理不同类型的公共逻辑抽象成函数,就得到了函数模板。

函数模板可以声明为inline或者constexpr的,将它们放在template之后,返回值之前即可。

普通函数模板

下面定义了一个名叫compare的函数模板,支持多种类型的通用比较逻辑。

template<typename T>
int compare(const T& left, const T& right) {
    if (left < right) {
        return -1; 
    }
    if (right < left) {
        return 1; 
    }
    return 0;
}

compare<int>(1, 2); //使用模板函数

成员函数模板

不仅普通函数可以定义为模板,类的成员函数也可以定义为模板。

class Printer {
public:
    template<typename T>
    void print(const T& t) {
        cout << t <<endl;
    }
};

Printer p;
p.print<const char*>("abc"); //打印abc

为什么成员函数模板不能是虚函数(virtual)?

这是因为c++ compiler在parse一个类的时候就要确定vtable的大小,如果允许一个虚函数是模板函数,那么compiler就需要在parse这个类之前扫描所有的代码,找出这个模板成员函数的调用(实例化),然后才能确定vtable的大小,而显然这是不可行的,除非改变当前compiler的工作机制。

实参推断

为了方便使用,除了直接为函数模板指定类型参数之外,我们还可以让编译器从传递给函数的实参推断类型参数,这一功能被称为模板实参推断。

如何使用

compare(1, 2); //推断T的类型为int
compare(1.0, 2.0); //推断T的类型为double
p.print("abc"); //推断T的类型为const char*

有意思的是,还可以通过把函数模板赋值给一个指定类型的函数指针,让编译器根据这个指针的类型,对模板实参进行推断。

int (*pf) (const int&, const int&) = compare; //推断T的类型为int

当返回值类型也是参数时

当一个模板函数的返回值类型需要用另外一个模板参数表示时,你无法利用实参推断获取全部的类型参数,这时有两种解决办法:

  • 返回值类型与参数类型完全无关,那么就需要显示的指定返回值类型,其他的类型交给实参推断。

    注意:此行为与函数的默认实参相同,我们必须从左向右逐一指定。

    template<typename T1, typename T2, typename T3>
    T1 sum(T2 v2, T3 v3) {
      return static_cast<T1>(v2 + v3);
    }
    
    auto ret = sum<long>(1L, 23); //指定T1, T2和T3交由编译器来推断
    
    template<typename T1, typename T2, typename T3>
    T3 sum_alternative(T1 v1, T2 v2) {
      return static_cast<T1>(v1 + v2);
    }
    auto ret = sum_alternative<long>(1L, 23); //error,只能从左向右逐一指定
    auto ret = sum_alternative<long,int,long>(1L,23); //ok, 谁叫你把最后一个T3作为返回类型的呢?
  • 返回值类型可以从参数类型中获得,那么把函数写成尾置返回类型的形式,就可以愉快的使用实参推断了。

    template<typename It>
    auto sum(It beg, It end) -> decltype(*beg) {
      decltype(*beg) ret = *beg;
      for (It it = beg+1; it != end; it++) {
         ret  = ret + *it;
      }
      return ret;
    }
    
    vector<int> v = {
        
        1, 2, 3, 4};
    auto s = sum(v.begin(), v.end()); //s = 10

实参推断时的自动类型转换

编译器进行模板实参推断时通常不会对实参进行类型转换,只有以下几种情况例外:

  • 普通对象赋值给const引用 int a = 0; -> const T&
  • 数组名转换为头指针 int a[10] = {0}; -> T*
  • 函数名转换为函数指针 void func(int a){...} -> T*

函数模板重载

函数模板之间,函数模板与普通函数之间可以重载。编译器会根据调用时提供的函数参数,调用能够处理这一类型的最特殊的版本。在特殊性上,一般按照如下顺序考虑:

  1. 普通函数

  2. 特殊模板(限定了T的形式的,指针、引用、容器等)

  3. 普通模板(对T没有任何限制的)

对于如何判断某个模板更加特殊,原则如下:如果模板B的所有实例都可以实例化模板A,而反过来则不行,那么B就比A特殊。

template<typename T>
void func(T& t) { //通用模板函数
    cout << "In generic version template " << t << endl;
}

template<typename T>
void func(T* t) { //指针版本
    cout << "In pointer version template "<< *t << endl;
}

void func(string* s) { //普通函数
    cout << "In normal function " << *s << endl;
}

int i = 10;
func(i); //调用通用版本,其他函数或者无法实例化或者不匹配
func(&i); //调用指针版本,通用版本虽然也可以用,但是编译器选择最特殊的版本
string s = "abc";
func(&s); //调用普通函数,通用版本和特殊版本虽然也都可以用,但是编译器选择最特化的版本
func<>(&s); //调用指针版本,通过<>告诉编译器我们需要用template而不是普通函数

模板函数特化

有时通用的函数模板不能解决个别类型的问题,我们必须对此进行定制,这就是函数模板的特化。函数模板的特化必须把所有的模版参数全部指定。

template<>
void func(int i) {
    cout << "In special version for int "<< i << endl; 
}

int i = 10;
func(i); //调用特化版本
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/127506.html原文链接:https://javaforall.net

赞 (0)
全栈程序员-站长的头像全栈程序员-站长

相关推荐

  • presentation里的reference_preference的用法

    presentation里的reference_preference的用法Perference也就是我们常说的偏好设置,首选项设置,能够自己主动保存一些数据,比如我们在上一次使用的时候的一些内容,则在下一次启动后依旧生效,而不须要再进行配置。当用户改变设置时,系统就会更新SharedPreference文件里相应的值。perference使用键值对的方式来处理,在android3.0之前,我们一般去继承Preference这个基类,去给用户呈现一个…

    2022年9月7日
  • Unity HLOD System[通俗易懂]

    Unity HLOD System[通俗易懂]1.1HLODSystem简介首先,HLODSystem主要的目标是为了减少DrawCall。然后,进行更多的Batch批处理。其次,减少面数和纹理,这样我们相应地节省了内存,并提升了加载时间。HLODSystem只针对当前所在的地方进行加载,它会流式加载网格和纹理,在后台进行异步的操作。本次HLOD是基于官方AutoLOD代码的扩展和改进制作出来了的,链接:https://github.com/Unity-Technologies/AutoLOD,链接里有一篇官方的文章,…

    2022年10月22日
  • 备份从android到ios系统文件,如何将音乐从安卓设备转移到iOS设备中「建议收藏」

    备份从android到ios系统文件,如何将音乐从安卓设备转移到iOS设备中「建议收藏」这几天,小编刚从Android手机切换到iPhone,可是Android手机上还有很多音乐文件没有保存下来,这时候该怎么处理呢?好在,小编发现了一款神器——iMazing,它可以帮助我们顺利的转移各种文件,音乐文件也是不在话下,下面就和小编一起来看看具体该怎么操作吧!图1iPhone将Android手机中的音乐文件转移到电脑中如果大家使用的是Mac电脑,需要到Android官方网站下载Andor…

    2022年9月18日
  • Python获取时间戳及常用的时间格式转换[通俗易懂]

    Python获取时间戳及常用的时间格式转换[通俗易懂]常用的时间格式转换:时间戳:10位13位16位ISO格式UTC时间代码如下:#!/usr/bin/envpython#coding=utf-8″””#:author:TerryLi#:url:https://blog.csdn.net/qq_42183962#:copyright:©2020-presentTerryLi#:motto:IbelievethattheGodrewardsthediligent.”””import

    2022年10月2日
  • jvm 吞吐量_jvm oom

    jvm 吞吐量_jvm oom公式吞吐量=运行代码时间/(运行代码时间+垃圾收集时间)示例虚拟机总共运行了100分钟1)运行代码:99分钟2)垃圾收集:1分钟吞吐量=99/100=99%参数-XX:GCRatio 设置吞吐量的大小-XX:MaxGCPauseMillis 控制最大的垃圾收集停顿时间分析互联网交互式项目:需要在停顿时间范围内(100~200ms),保证最大吞吐量(96%~)传统项目:需要在保证最大吞吐量范围内,降低停顿时间吞吐量越大,代表着垃圾收集时间越短,那么用户代码可以充分

    2022年10月8日
  • css颜色渐变样式怎么用_文本效果内置样式渐变填充

    css颜色渐变样式怎么用_文本效果内置样式渐变填充使用CSS渐变CSS渐变类型的一种特殊类型表示,由两种或多种颜色之间的渐进过渡组成。您可以选择三种类型的渐变:线性(由linear-gradient函数创建),径向(由radial-gradient()函数创建)和圆锥(由conic-gradient(en-US)函数创建)。您还可以使用repeating-linear-gradient和repeating-radial-gradient函数创建重复渐变。渐变可以在任何使用的地方使用,例如在背景中。由于渐变是动态生成

    2022年10月30日

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号