C++ : 模板初阶

水墨不写bug 2024-06-27 13:35:04 阅读 100

标题:C++ : 模板初阶

@水墨不写bug


35c30ce09f2e4cfcb18d1a24af689db1.png


正文开始:

C语言的问题 :

写不完的swap函数

        在学习C语言时,我们有一个经常使用的函数swap函数,它可以将两个对象的值交换。

我们通常这样实现它:

void swap(int* t1,int* t2)

{

int tem = *t1;

*t1 = *t2;

*t2 = tem;

}

         这是很简单的函数。如果我们想交换其他类型的对象,就需要用到函数重载

void swap(int* t1,int* t2){int tem = *t1;*t1 = *t2;*t2 = tem;}

void swap(float* t1, float* t2){ float tem = *t1;*t1 = *t2;*t2 = tem;}

void swap(double* t1, double* t2){ double tem = *t1;*t1 = *t2;*t2 = tem;}

void swap(char* t1, char* t2){ char tem = *t1;*t1 = *t2;*t2 = tem;}

void swap(long* t1, long* t2){ long tem = *t1;*t1 = *t2;*t2 = tem;}

//...

         但是,内置类型的指针理论上可以达到n级指针,n可以趋于无限大;除此之外,还有自定义类型。这样一来,你就会发现,类型是无法枚举的!一个swap函数需要实现一种类型的交换,那么swap函数是写不完的!


        人工解决重复写同一个逻辑,仅仅是类型不同的代码是很低效的!为了解决这个问题,C++引入模板的概念:

        顾名思义,模板就是模板,作用就是印出不同的东西,这个东西就是swap函数!


(一)模板简介

         模板是C++相对于C的一个新的语法,他需要用到关键字template(模板),如果我们用模板来实现swap函数,就可这样写:

template <typename T>

void swap(T& t1, T& t2)

{

T tem = t1;

t1 = t2;

t2 = tem;

}

        template<typename或class 模板参数> + 模板主体 就是模板的基本形式。

        要成功的调用模板函数,也需要特定的条件:

template <class T>

void swap(T& t1, T& t2)

{

T tem = t1;

t1 = t2;

t2 = tem;

}

/*

void swap(int t1,int t2){int tem = t1;t1 = t2;t2 = tem;}

void swap(float t1, float t2){ float tem = t1;t1 = t2;t2 = tem;}

void swap(double t1, double t2){ double tem = t1;t1 = t2;t2 = tem;}

void swap(char t1, char t2){ char tem = t1;t1 = t2;t2 = tem;}

void swap(long t1, long t2){ long tem = t1;t1 = t2;t2 = tem;}

*/

int main()

{

int a = 1, b = 5;

::swap(a, b);

cout << a << " " << b << endl;

return 0;

}

         我们可以指定调用全局的swap模板函数,完成对象的值交换。

(1)为什么模板可行?模板的原理?

        在编译时,编译器会 匹配 模板参数的类型,如上例,a,b的类型都是整形,所以编译器会根据根据函数的模板生成一份函数,这份函数仅仅将 T模板参数 改成 匹配出的类型):

void swap(int t1,int t2)

{

int tem = t1;

t1 = t2;

t2 = tem;

}

        这样我们人工编写的工作量就减少了非常多,因为一个理论上模板可以生成n个函数(n可趋于无穷大)。

(2)匹配冲突

         由于交换的是两个参数的值,当两个参数的类型不同时,理论上不能交换,此时编译器也会因为匹配类型冲突而报错:

5385c38ab2074763a4c3c8361d4bbddc.png

        如何解决这个问题:

i,再增加一个模板参数(推荐)

        由于只有一个模板参数, 当推断类型既是int又是double时,一个参数就应付不过来了,所以需要再增加一个参数。两个模板参数,就可以进行两次参数类型匹配:

template <class T1,class T2>

void swap(T1& t1, T2& t2)

{

auto tem = t1;

t1 = t2;

t2 = tem;

}


        ##如果你是看了后面两种处理方式后又回来的,那么你很敏锐,发现了本例的问题,其实本例也发生了类型转换

        由于t1,t2类型不一致,所以在下面赋值时,必然发生了类型转换,可能导致精度丢失。但是这并不影响我们解决匹配冲突的方法:因为在实际应用中,我们一般不会让模板参数之间进行运算。(这仅仅是本例的场景不太好,使用多个模板来解决匹配冲突仍然是推荐的) ##


ii,强制转换到一致

        只能解决一些特例,就拿下面这个函数模板来说:

template<class T>

T Add(T t1,T t2)

{

return t1 + t2;

}

void test2()

{

int a = 1;

double b = 5.6;

double ret = ::Add((double)a, b);

cout << ret;

}

         将a强制类型转换到double可以使编译器对a和b类型匹配都是double,这样是可以通过编译的,但是这样操作的结果是不稳定的。强制类型转换必然意味着精度或者符号的丢失,不到迫不得已,不要使用强制类型转换!

iii,显示实例化(不再推演参数)

         只能解决一些特例,还是拿上面这个函数模板来说:

template<class T>

T Add(T t1,T t2)

{

return t1 + t2;

}

void test2()

{

int a = 1;

double b = 5.6;

double ret = ::Add<double>(a, b);

cout << ret;

}

        这也是可以通过编译的,在模板名称后面加上<参数类型>,意味着指定了生成的模板的匹配类型就是 这个指定的参数类型。由于指定了参数类型,其本质也是发生了类型转换,而类型转换是不推荐的。

总结:

        解决匹配冲突的方法:

                 i,再增加一个模板参数(推荐)

                ii,强制转换到一致

                iii,显示实例化(不再推演参数)


(3)模板实例化的条件

        当全局已经有一个匹配的函数时,模板就不会再生成新函数。

        以这个例子来说: 

void swap(double t1, double t2){ double tem = t1;t1 = t2;t2 = tem;}

template <class T>

void swap(T& t1, T& t2)

{

T tem = t1;

t1 = t2;

t2 = tem;

}

void test1()

{

double a = 1;

double b = 5;

::swap(a, b);

cout << a << " " << b << endl;

}

        模板匹配的类型是double,由于全局已经有了类型是double的函数,所以编译器不会再通过函数模板再生成一份swap函数。

        编译器会优先匹配最合适的模板:

template <class T1,class T2>

void swap(T1& t1, T2& t2)

{

T1 tem = t1;

t1 = t2;

t2 = tem;

}

template <class T>

void swap(T& t1, T& t2)

{

T tem = t1;

t1 = t2;

t2 = tem;

}

void test1()

{

int a = 1;

double b = 5;

::swap(a, b);

cout << a << " " << b << endl;

}

        本例中,由于a,b两个变量的类型不同,所以编译器会优先选择两个模板参数的swap函数,而不是一个模板参数的swap。


        在特殊情况也是可以编译通过的:

void swap(int t1, int t2)

{

int tem = t1;

t1 = t2;

t2 = tem;

}

void test1()

{

int a = 1;

double b = 5;

::swap(a, b);

cout << a << " " << b << endl;

}

总结:

        1.全局有完全匹配的函数,编译器不再由模板生成函数;直接使用现成的函数。

        2.如果有多个模板,编译器会选择最合适的模板来生成函数;

        3.类型不匹配,没有模板,也可以编译通过,但是会发生类型转换,导致精度丢失。

        其实模板远不止这些,本文仅仅是以函数模板为引子来讲解模板的语法,而模板还包括类模板,后者是实际中应用较多的。

        类模板 就放 在将来为大家分享吧! 


目录

(一)模板简介

(1)为什么模板可行?模板的原理?

(2)匹配冲突

i,再增加一个模板参数(推荐)

ii,强制转换到一致

iii,显示实例化(不再推演参数)

总结:

(3)模板的生成条件

总结:



~完

未经作者同意禁止转载



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。