C++模版进阶

前言

        学习到这里,C++容器的部分暂时告一段落。不过接下来进行的是和模版有关的内容,不少人就要问了,之前不是写过模版的博客吗?当然写过,只不过写的是初级的内容,模版还可以更加深化的去讲。这设计到一些底层的实现,不过大致的逻辑是相通的,同时也是给继承和多态做一个铺垫。

        本期内容比较简单内容较少,熟系的小伙伴直接跳过。

一、模版特化

        C++中的模版特化,是基于已有模版的基础上,对模版假定内容进行固定。就像我们使用模版的时候能够进行显示实例化一样,模版除了计算机自己写,也可以经由程序员自己编写。

        自己编写的情况出现为,在特定的情况下走特定的程序,与普通模版做出区分。例如我们之前所做的日期类比较,不是单独比较年或者月和日。而是优先的比较年,随后是月,最后是日。这种情况也发生在传入内容是指针的时候,由于地址是不确定的,如果直接比较那么大小就会出错,我们需要比较指针指向的内容。这样我们就可以用模版的特化,让程序走指针特化的模版,从而达到解引用后比较目的。

        说到这里,我们来看看模版特化时,计算机执行的特点吧。

二、模版选择优先级

1、普通函数、模版函数和模版特化函数

        对于计算机来说,由模版到具体的函数是需要消耗资源的。就像做饭一样,模版就好比是菜场新鲜的菜,需要买回家烹饪才能吃。而对于模版特化来说,就像是闲饭,热热就可以吃。普通函数就是新鲜美味的做好的饭,可以直接吃。

        计算机也是聪明的很,如果对得上号,就表示能够在这些函数中做出选择。这个时候有新鲜的就会优先吃新鲜的,没有新鲜的就吃旧的,没有旧的就只好去菜场买自己做了吃。编译器在选择同名函数的时候优先级是:“普通函数”>“模版特化函数”>“模版函数。

        根据以下代码:

#include <iostream>
using namespace std;

template<typename Type>
Type Max(const Type &a, const Type &b)
{
	cout << "This is Max<Type>" << endl;

	return a > b ? a : b;
}

template<>
int Max<int>(const int &a, const int &b)
{
	cout << "This is Max<int>" << endl;

	return a > b ? a : b;
}

template<>
char Max<char>(const char &a, const char &b)
{
	cout << "This is Max<char>" << endl;

	return a > b ? a : b;
}

int Max(const int &a, const int &b)
{
	cout<< "This is Max" <<endl;

	return a > b ? a : b;
} 

int main()
{
	Max(10, 20);
	Max(12.34, 23.45);
	Max('A', 'B');
	Max<int>(20, 30);

	return 0;
}

        结果为:

        也正好佐证了上述顺序:

// Max(10, 20);->int Max(const int &a, const int &b);
// 能够调用合适的普通函数就直接调用
// Max(12.34, 23.45);->Type Max(const Type &a, const Type &b);
// 没有浮点数的特化,就只能自己做。调用原始模版
// Max('A', 'B');->char Max<char>(const char &a, const char &b);
// 有特殊模版能够支持char类型,识别后调用它
// Max<int>(20, 30);->int Max<int>(const int &a, const int &b);
// 声明调用模版int,并且优int特化的模版,调用模版int特化

2、偏特化/半特化

        偏特化或者叫半特化,这样的函数存在于函数模版超过两个的函数中,将一部分函数模版的类型进行固定。除了向int、char或者自定义类型之外。也可以对模版进行这个模版是指针或者是引用的声明。例如对于如下类模板Date来说:

template<class T1, class T2>
class Data
{
public:
    Data() { cout << "Data<T1, T2>" << endl; }

private:
    T1 _d1;
    T2 _d2;
};

        半特化包括,将其中部分模版参数类型固定:

// 将T2固定为int
template <class T1>
class Data<T1, int>
{
public:
    Data() { cout << "Data<T1, int>" << endl; }

private:
    T1 _d1;
    int _d2;
};

        将模板的内容声明为指针:

// 模板的内容声明为指针
template <typename T1, typename T2>
class Data <T1*,T2*>
{
public:
    ta() { cout << "Data<T1*, T2*>" << endl; }

private:
    T1 _d1;
    T2 _d2;
};

        将模版的类型声明为引用:

// 模板的内容声明为引用
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:
    Data(const T1& d1, const T2& d2)
        : _d1(d1)
        , _d2(d2)
    {
        cout << "Data<T1&, T2&>" << endl;
    }

private:
    const T1 & _d1;
    const T2 & _d2;
};

        对于指针特化和引用特化,T1/T2如果载入int*不表示int*,而是表示int。这是因为系统希望T1能够表示更多的类型。如果T1表示int*,那么就没法表示int,但是是int能够表示int*。

        根据全特化和半特化来说,全特化的优先级更高。半特化就相当于是家里洗好的菜,还要料理之后才能吃,但是全特化作为凉饭是能够直接吃的。

#include <iostream>
using namespace std;

template<class T1, class T2>
class Data
{
public:
    Data() { cout << "Data<T1, T2>" << endl; }

private:
    T1 _d1;
    T2 _d2;
};

// 将T2固定为int
template <class T1>
class Data<T1, int>
{
public:
    Data() { cout << "Data<T1, int>" << endl; }

private:
    T1 _d1;
    int _d2;
};

// 模板的内容声明为指针
template <typename T1, typename T2>
class Data <T1*,T2*>
{
public:
    ta() { cout << "Data<T1*, T2*>" << endl; }

private:
    T1 _d1;
    T2 _d2;
};

// 模板的内容声明为引用
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:
    Data(const T1& d1, const T2& d2)
        : _d1(d1)
        , _d2(d2)
    {
        cout << "Data<T1&, T2&>" << endl;
    }

private:
    const T1 & _d1;
    const T2 & _d2;
};

int main()
{
    Data<double, int> d1; 
    Data<int, double> d2; 
    Data<int *, int*> d3; 
    Data<int&, int&> d4(1, 2);

    return 0;
}

        结果也正好和上述条件相同。

3、有关指针特化的注意事项

        指针的特化有一个非常奇怪的现象。当使用指针特化,如果对指针指向的内容增加const的时候,计算机反而不会走这个const指针特化。

template<typename T>
T Add(T x, T y)
{
    cout << "T Add(T x, T y)" << endl;
    return x + y;
}

template<typename T>
T Add(T* x, T* y)
{
    cout << "T Add(T* x, T* y)" << endl;
    return *x + *y;
}

template<typename T>
T Add(const T*& x, const T*& y)
{
    cout << "T Add(const T*& x, const T*& y)" << endl;
    return *x + *y;
}

int main()
{
    int a = 10, b = 20;
    int* x = &a;
    int* y = &b;
    Add(x, y);
    return 0;
}

        对于以上代码,我们一般会认为,调用指针内容的时候回去调用:

T Add(const T*& x, const T*& y)

        但是实际上计算机认为这不是一个意思,虽然这个是个指针,但是const修饰的不是指针本身而是指针指向的内容。从结果上来说,编译器不会走它,如果没有第二个模版会调用前面第一个模版。这样就会出错,因为两个指针不能相加。增加了第二个模版才不会报错,计算机也会走第二个模版:

        所以,调用特化函数的时候,const必须修饰传入的参数类型。比如这里修饰的是指针,const就应该修饰指针,而不是指针指向的内容。例如下面的第四个模版:

// 1
template<typename T>
T Add(T x, T y)
{
    cout << "T Add(T x, T y)" << endl;
    return x + y;
}

// 2
// template<typename T>
// T Add(T* x, T* y)
// {
//     cout << "T Add(T* x, T* y)" << endl;
//     return *x + *y;
// }

// 3
template<typename T>
T Add(const T*& x, const T*& y)
{
    cout << "T Add(const T*& x, const T*& y)" << endl;
    return *x + *y;
}

// 4
template<typename T>
T Add(T* const & x, T* const & y)
{
    cout << "T Add(T* const & x, T* const & y)" << endl;
    return *x + *y;
}

int main()
{
    int a = 10, b = 20;
    int* x = &a;
    int* y = &b;
    Add(x, y);
    return 0;
}

三、编译器编译顺序与模版的关系

        模版最好不要声明和定义分离。这是由于编译器的4个过程决定的。

        编译器编译程序分为4个过程:

        (1)预处理.i

        头文件展开、预处理、条件编译、去注释等

        (2)编译.s

        检查语法、生成汇编代码

        (3)汇编.o

        汇编码转化为二进制

        (4)链接.exe

        目标文件合并在一起生成可执行程序,并把需要的函数地址等连接上。

        而模版如果分离编译在不同文件、那么在编译的阶段他不会去向声明的部分去要有哪些需要生成函数,每个文件单独编译,所以cpp文件没有实例化出来。因为编译阶段有声明所以能够通过该阶段,但是在链接阶段就会出错,因为找不到对应的实例化函数。如果不分开,那么编译阶段才知道。

        C++也支持hpp文件,该文件支持声明和定义放在一起。也就是说,为了让模版的声明和定义不分离,出了这么一种文件,解决问题。

结语

        这篇文章比起之前来说算是简单了不少,也没特别多需要举例的部分。之后的多态和继承就没这么简单了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值