zoukankan      html  css  js  c++  java
  • 深入理解C++的型别推导

    所谓型别推导,指的是我们在为变量赋予类型时不必再显式声明,编译器可以根据代码来自动推导类型。C++11中有两种型别推导的场景:模板和auto。下面我们来一一解析。

    模板的型别推导

    模板在C++中的应用可以参考我的这篇文章:https://www.cnblogs.com/wickedpriest/p/6123113.html。
    在这里我们为了简化问题,我们定义进行模板型别推导的形式为:

    template<typename **T**>
    void f(**ParamType** parm);
    

    调用的时候的格式为:

    f(**expr**);
    

    这是一个标准的函数模板,类模板以及其他形式都可以参考这个。
    在编译期,编译器会根据expr来推导T和ParamType的型别。需要注意的是,T和ParamType的型别有可能是不一样的:例如T是int,而ParamType是const int&。T型别的推导结果,不仅依赖于expr,也依赖于ParamType,具体来说分为三种情况:

    一、ParamType是个指针或引用,但不是个万能引用

    这种情况是最简单的,型别推导会这么运作:
    1.若expr具有引用型别,先将引用部分忽略
    2.对expr的型别和ParamType的型别执行模式匹配,来决定T的型别
    举个例子,我们的模式如下:

    template<typename T>
    void f(T& param);
    

    声明变量如下:

    int x = 27;         // x的型别为int
    const int cx = x;   // cx的型别为 const int
    const int &rx = x;  // rx的型别为const int &
    

    那么以下调用的型别推导为:

    f(x);     //T的型别是int,param的型别是int &
    f(cx);    //T的型别是cosnt int,param的型别是const int&
    f(rx);    //T的型别是const int,param的型别是const int&
    

    请注意对rx的推导,如前面所说,expr的型别为const int&,ParamType又是引用,那么进行型别推导时首先会忽略掉expr的引用部分,然后再根据T和ParamType进行推导。
    上面显示的都是左值引用形参,如果形参是右值引用也是一样的。

    二、ParamType是个万能引用

    关于万能应用的解析会在之后的文章中讲解,在这里我们先给出万能引用的形式:

    template<typename T>
    void f(T&& param);
    

    在这种形式下,型别推导规则如下:
    1.如果expr是个左值,T和ParamType都会被推导为左值引用(没错你没看错,虽然ParamType是个右值引用,但最终确实是被推导为了左值引用)。
    2.如果expr是个右值,则应用上面情况一中的规则。
    再次举个例子:

    int x = 27;
    const int cx = x;
    cosnt int rx& =x;
    f(x);     //x是个左值,因此T和ParamType的型别为int&
    f(cx);    //x是个左值,因此T和ParamType的型别为const int&
    f(rx);    //rx是个左值,因此T和ParamType的型别为const int&
    f(27);    //27是个右值,因此T的型别为int,ParamType的型别为int&&
    

    关于为什么会这样,我会在之后介绍万能引用的文章中予以说明。

    三、ParamType既不是指针也不是引用

    这种情况也非常简单,那就是完全地按值传递了,const和引用都会被省略掉

    template<typename T>
    void f(T param);
    
    f(x);   //T和ParamType的型别都为int
    f(cx);  //T和ParamType的型别都为int
    f(rx);  //T和ParamType的型别都为int
    

    当传递的类型是一个指针时,有一种特殊情况,如下所示:

    const char* const x = "123456";
    f(x); // ParamType的类型是const char*
    

    x的类型为const char* const,其中第一个const指的是该指针指向的内容不可修改,第二个const值得是该指针本身的值不可修改。进行型别推导时,会取消掉第二个const,第一个const则不能取消(一旦取消,则x指向的内容就可以更改了)。

    一种特殊情况:数组实参

    接下来还有一种特殊情况,那就是传递了一个数组。一般来说,讲一个数组按值传递给函数模板,形参将会被推导为指针型别:

    template<typename T>
    void f(T param);
    
    const char name[] = "6666";
    f(name);    //T的型别为const char*
    

    但神奇的是,尽管函数无法声明真正的数组型别的形参,它们却能够将形参声明成数组的引用!举个例子,如果我们这样修改f:

    template<typename T>
    void f(T& param);
    
    f(name);
    

    此时T会被推导为数组型别:const char[5]!而f的形参(param)则会被推导为数组类型的引用:const char(&)[5]。
    我们可以利用这种特性来推断数组的大小:

    template<typename T, std::size_t N>
    constexpr std::size_t arraySize(T(&)[N])noexcept
    {
      return N;
    }
    
    int nums[] = {1,2,3,4,5};
    int vals[arraySize(nums)];
    

    惊不惊喜,意不意外?

    auto的型别推导

    auto实际上在大部分情况下可以看做是模板的型别推导,除了一种特殊情况。我们先看看auto如何与模板的型别推导对应的:

    auto x = 27;
    const auto cx = x;
    const auto& rx = x;
    

    上面三种情况完全可以用模板推导表达:

    template<typename T>
    void func_for_x(T param);
    func_for_x(27);
    
    template<typename T>
    void func_for_cx(const T param);
    func_for_x(x);
    
    template<typename T>
    void func_for_rx(const T& param);
    func_for_x(x);
    

    那么对应模板推导的三种情况如下:

    auto x = 27;         //情况3(x既不是指针也不是引用)
    const auto cx = x;   //情况3(x既不是指针也不是引用)
    const auto& rx = x;  //情况1(rx是个引用,但不是万能引用)
    

    情况2同理:

    auto && uref1 = x;  //x的型别是int,且是左值,所以uref1的型别是int&
    auto && uref2 = cx; //同上,uref2的型别是const int&
    auto && uref3 = 27; //27是右值,因此uref3的型别是int&&
    

    数组也一样:

    const char name[] ="6666";
    auto arr1 = name;  //arr1的型别是const char*
    auto& arr2 = name; //arr2的型别是const char(&)[5]
    

    你看,一模一样。
    那么特殊情况是什么呢?就是当我们用初始化表达式来初始化一个类型的时候:

    int x{2};
    auto x1 = {2};
    

    上面的代码采用大括号来初始化对象,x的型别为int,而auto则不会将x1推导为int,而是std::initializer_list!这一点一定要注意。
    如果采用模板型别推导,传入一个初始化表达式,则程序会直接报错:

    template<typename T>
    void f(T param);
    f({1,2,3});   //程序无法推导T的类型,直接报错!
    

    除非你手动指定param的类型为std::initializer_list

    template<typename T>
    void f(std::initializer_list<T> param);
    

    除了这一条以外,auto的推导规则是和模板一模一样的。

  • 相关阅读:
    关于Python安装PIL库失败的原因
    SSH免密登录及配置完成后仍需要输入密码的解决办法
    Github+Hexo博客搭建教程(三)
    Github+Hexo博客搭建教程(二)
    Github+Hexo博客搭建教程(一)
    winform中控件的简单数据绑定
    分享一个跨线程访问控件的很实用的方法
    自定义两个控件,一个是显示图标和文字的矩形,一个是带边框的label(但是不是label)
    写一个给字符串根据长度添加换行符的处理方法
    Winform DataGridView控件在业务逻辑上的简单使用
  • 原文地址:https://www.cnblogs.com/wickedpriest/p/14906561.html
Copyright © 2011-2022 走看看