第4课 模板的细节改进

Posted 浅墨浓香

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第4课 模板的细节改进相关的知识,希望对你有一定的参考价值。

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<(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类型。(因为包装类模板禁止了val的自动推导)
    func5(3.14);      //第2个参数使用默认模板参数类型,即T为默认的float类型
    func5(123, 3.14); //第2个实参为double,推导出T为double类型
    
    return 0;
}
/*测试结果:
e:\Study\C++11\4>g++ test3.cpp -std=c++11
e:\Study\C++11\4>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
*/

以上是关于第4课 模板的细节改进的主要内容,如果未能解决你的问题,请参考以下文章

第57课 深入理解函数模板

OpenMMLab 实战营打卡 - 第 六 课 语义分割

第56课 函数模板的概念和意义

OpenMMLab 实战营打卡 - 第 4 课

第56课 函数模板的概念和意义

2020新书Bootstrap 4导论第二版,366页pdf,使用Bootstrap 4.5创建强大的Web应用程序