zoukankan      html  css  js  c++  java
  • 第4课 模板的细节改进(1)_模板别名和函数模板

    1. 右尖括号的问题

    (1)实例化模板,会出现两个连续右尖括号(>>),如:

      ①vector<list<int> >; //注意两个右尖括号之间有空格。C++98/03/11均支持

      ②vector<list<int>>;  //c++11开始支持这种写法!在C++98/03将连写的>>当成右移操作符,该行会编译出错!

    (2)强制类型转换出现的两个连续右尖括号

      const vector<int> v = static_cast<vector<int>>(v); //C++98/03不支持,C++11支持。

    (3)特殊应用出现的连续右尖括号,如

      Template<int i> class Test{};

      Test<100 >> 2> t; //C++98/03“>>”被看作是右移操作符。C++11不会这样认为,它会将Test后的<第一个>进行匹配,所以该行会编译失败,应改为Test<(100 >> 2)> t;即加个括号。

    【编程实验】右尖括号

    //main.cpp

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    //编译选项:g++ test.cpp
    //          g++ -std=c++11 test.cpp
    
    //关于Foo和Bar词源的一类说法:
    //1.foobar又为foo-bar,其中的bar是beyond all recognition的缩写,
    //  意为无法识别,一塌糊涂的意思。
    //2. foo是fu的变体,是fuck-up的缩写,同样是一团糟的意思。
    
    template <typename T>
    struct Foo
    {
        typedef T type;
    };
    
    template <int N>
    struct Bar
    {    
    };
    
    int main()
    {    
        Foo<vector<int>>::type f1;  //C++98/03中">>"被看作右移操作符!编译出错
                                    //C++11中是合法的!
        Foo<decltype(100 >> 2)> f2; //相当于:Foo<int> f2;
        
        //Bar<100 >> 2>   b1;       //100/4=25,C++98/03会将>>看作右移操符符,合法!
                                    //C++11不认为>>是右移操作符,编译错误!改进如下:
        Bar<(100 >> 2)> b2;         //加一个小括号,改变优先级
            
        return 0;
    }

    2. 模板的别名

    (1)typedef和using关键字

      ①类模板(class template)和模板类(template class)的不同:类模板是用来产生类的模板,是一种模板。而“模板类”是用该模板产生出来的类,是一种类型

      ②typedef可以定义类型的别名,但不能用来重定义模板的别名

      ③C++11引入using关键字,它覆盖了typedef的全部功能。既可以用来定义类型的别名,也可以定义模板的别名。而且采用类似于赋值的方式,从语法比typedef更加清晰。

    //****************using覆盖了typedef的全部功能*******************
    //重定义unsigned int,两者等价
    typedef unsigned int uint_t;
    using uint_t = unsigned int;
    
    //重定义std::map,两者等价
    typedef std::map<std::string, int> map_int_t;  //OK,为模板类重定义别名。注意,这里是模板类
                                                   //而不是类模板。模板类本质是一种类型,而不是模板!
    using map_int_t = std::map<std::string, int>;  //使用using关键字
    
    //重定义函数指针,两者等价
    typedef void(*func_t)(int, int);
    using func_t = void(*)(int, int);

    (2)两种定义模板别名方式的比较

      ①C++98/03中为模板重定义别名时,需要外加一个包装类才能为模板定义别名,使用烦琐。

      ②using可以轻松地为模板重定义别名。只需要在普通类型别名的语法基础前加上template的参数列表,如template<typename T1, int N>。

    【编程实验】using关键字及模板的别名

    //*********两种为模板定义别名的比较(以重定义函数模板为例)*******
    //1. 使用包装类 + typedef(C++98/03)
    template <typename T>
    struct func_t
    {
        typedef void(*type)(T, T);
    };
    
    func_t<int>::type func1;//使用时
    
    //2. 使用using关键字(C++11)
    template <typename T>
    using func_t = void(*) (T, T);
    
    func_t<int> func2;

    3. 函数模板的默认模板参数

    (1)类模板和函数模板的默认模板参数

      ①在C++98/03中,类模板可以有默认参数,但不支持函数模板的默认参数。而C++11中同时支持类和函数模板的默认模板参数

      ②当所有模板参数都有默认参数时,函数模板的调用与普通函数一致。但对于类模板,在使用时也须在模板名称后跟“<>”来实例化。

      ③在为多默认模板参数类模板指定默认值时(声明时),必须遵照“从右往左”的规则进行指定。而函数模板没有这个限制,但当调用多默认模板参数的函数模板时如果显式指定模板的参数,则参数填充的顺序是从左往右的。

    (2)函数模板的参数推导规则(按优先级从高到低):template<typename T = int> void func(T val = 0){};

      ①显式指定类型。如func<float>(0);

      ②自动推导类型:如func<3.14>,编译器将推导出T为double。

      ③模板参数的默认值,如func(),默认T为int。(注意,模板函数的默认形参不是模板参数推导的依据。函数模板参数的选择,总是由函数的实参推导而来的

     【编程实验】函数模板的默认模板参数

    #include <iostream>
    #include <typeinfo>
    using namespace std;
    
    //编译选项:g++ test.cpp
    //          g++ -std=c++11 test.cpp
    
    //********测试不同版本C++对类模板和函数模板默认模板参数的支持*************
    template<typename T = int>
    class A{};           //C++98/03编译通过,C++11编译通过
    
    template<typename T = int>
    void func1() {};     //C++98/03编译失败,C++11编译通过
    
    
    
    //**************在声明时为多个默认模板参数指定默认值**********************
    //为类模板指定默认模板参数
    template<typename T1, typename T2 = int> class C1; //从右往左指定,OK
    //template<typename T1 = int, typename T2> class C2; //error。
    
    //为函数模板指定默认模板参数
    template<typename T1, typename T2 = int> //OK
    void Func2(T1 a, T2 b);  
                
    template<typename T1, typename T2 = int> //OK,指定模板默认参数的顺序可任意!
    void Func3(T1 a, T2 b); 
    
    //***************调用函数模板时模板参数的填充*******************************
    template <typename R = int, typename U>
    R func4(U val)
    {
        cout <<  "R's type is: "<< typeid(R).name() << ", U's type is: " << typeid(U).name() << endl;
    }
    
    //******************当默认模板参数遇到模板参数自动推导时*******************
    template <typename T>
    struct Identity     //包装类,为定义模板的别名而增加的!
    {
        typedef T type;
    };
    
    template <typename T = float>
    void func5(typename Identity<T>::type val, T t = 0)
    {
        cout << "val = " << val << typeid(val).name();
        cout <<  ", t = " << t << typeid(T).name()<< endl;
    }
    
    int main()
    {    
        func4(123);       //R为int,U为int
        func4<long>(123); //调用时函数模板时,参数的填充从左往右的。即R为long, U为int 
    
        func5(3.14, 0);   //从第2个实参中推导出T的类型为int类型。(因为3.14只能用来推导Identity模板中的T,而不是func5模板中的T。注意,此T非彼T!)
        func5(3.14);      //第2个参数使用默认模板参数类型,即T为默认的float类型
        func5(123, 3.14); //第2个实参为double,推导出T为double类型
        
        return 0;
    }
    /*测试结果:
    e:StudyC++114>g++ test3.cpp -std=c++11
    e:StudyC++114>a.exe
    R's type is: i, U's type is: i
    R's type is: l, U's type is: i
    val = 3i, t = 0i
    val = 3.14f, t = 0f
    val = 123d, t = 3.14d
    */
  • 相关阅读:
    简单工厂模式&工厂方法模式&抽象工厂模式的区别及优缺点及使用场景
    JDK1.8的新特性
    在Button样式中添加EventSetter,理解路由事件
    关于C#低版本升级高版本时,项目中引用Microsoft.Office.Interop.Word,程序提示不存在类型或命名空间名office.
    无法安装或运行此应用程序。该应用程序要求首先在"全局程序集缓存(GAC)"中安装程序集
    C#winform跨窗体传值和调用事件的办法
    C#线程处理:七、线程实列
    C#线程处理:六、线程同步(三)
    C#线程处理:五、线程同步(二)
    C#线程处理:四、线程同步
  • 原文地址:https://www.cnblogs.com/5iedu/p/7623014.html
Copyright © 2011-2022 走看看