当前位置:首页 » 《休闲阅读》 » 正文

C++:模板(1)

25 人参与  2024年10月07日 18:40  分类 : 《休闲阅读》  评论

点击全文阅读


目录

实现泛型的交换函数

函数模板

1.概念

2.格式

3.原理

4.函数模板实例化

5.函数模板参数的匹配原则

类模板

1.定义格式

2.实例化

3.声明与定义问题


实现泛型的交换函数

我们实现一个对所有类型都通用的交换函数,可以用函数重载来实现。

void Swap(int& x, int& y){int temp = x;x = y;y = temp;}void Swap(double& x, double& y){double temp = x;x = y;y = temp;}void Swap(char& x, char& y){char temp = x;x = y;y = temp;}//......

使用函数重载可以实现,但有几个缺点:

1.重载函数仅仅是类型不同,代码复用率低,当有新类型出现的时候,就又需要增加对应的函数。

2.代码的可维护性低,一个出错就可能导致所有的重载出错。

那我们能不能自己实现一个交换函数的模具,我们传递不同类型的值,就根据这个类型生成对应的交换函数?

就如同下面的浇筑过程。

答案是可以的,这就是C++中的模板模板分为函数模板和类模板,模板又是泛型编程的基础。

泛型编程:编写与类型无关的通用代码,实现代码复用。

函数模板

1.概念

函数模板代表一个函数家族,函数模板与类型无关,在使用时被参数化,根据实参产生函数的特定类型版本。

2.格式

模板都需要用一个关键字template,使用格式如下:

​//template<typename T1, typename T2, ...... , typename Tn>template<class T1, class T2, ......, class Tn>返回值类型 函数名 (参数列表哦){ }​

typename是用来定义模板参数的关键字,也可以使用class,但是不能用struct代替。

用模板实现交换函数

template<class T>void Swap(T& x, T& y){T temp = x;x = y;y = temp;}

具体例子

template<class T>void Swap(T& x, T& y){T temp = x;x = y;y = temp;}int main(){int i1 = 10;int i2 = 50;cout << "交换前i1、i2为:" << i1 << " " << i2 << endl;Swap(i1, i2);cout << "交换后i1、i2为:" << i1 << " " << i2 << endl;cout << endl;double d1 = 28.8;double d2 = 67.6;cout << "交换前d1、d2为:" << d1 << " " << d2 << endl;Swap(d1, d2);cout << "交换后d1、d2为:" << d1 << " " << d2 << endl;cout << endl;char ch1 = 'c';char ch2 = 'x';cout << "交换前ch1、ch2为:" << ch1 << " " << ch2 << endl;Swap(ch1, ch2);cout << "交换后ch1、ch2为:" << ch1 << " " << ch2 << endl;cout << endl;return 0;}

 

3.原理

函数模板是一个蓝图,他本身并不是函数,是编译器产生特定具体类型函数的模具。

模板就是将本来我们做的重复的事情交给了编译器。

在编译器编译阶段,对于函数模板的使用,编译器根据传入的实参类型来生成对应类型的函数以供调用。比如:当double模板使用函数模板时,编译器通过实参的类型确定T为double,然后生成一个double类型的函数,对于其他类型也是如此。

4.函数模板实例化

不同类型的参数使用函数模板时,称为函数模板的实例化函数模板实例化分为:隐式实例化和显式实例化

实例化出来的函数就是模板函数,模板函数的生成就是将函数模板的类型形参实例化的过程。

1.隐式实例化:编译器根据传入实参推算模板参数的实际类型

template<class T>T Add(const T& x, const T& y){return x + y;}int main(){int i1 = 20, i2 = 30;cout << "i1 + i2 =" << Add(i1, i2) << endl;cout << endl;double d1 = 15.0, d2 = 20.0;cout << "d1 + d2 =" << Add(d1, d2) << endl;cout << endl;return 0;}

注意以下这种方式时不能通过编译的:

Add(i1, d1);

因为在编译阶段,编译器已经根据i1的类型推断T为int类型,而模板参数列表T只有一个,当编译器接收到d1时,发现时double类型,那编译器就会因为无法确定将T确定为int还是double而报错

在模板中,编译器一般不会进行类型转换操作,如果转换出问题,那编译器就需要背黑锅。

这种报错有两种解决方式:1.我们自己强制转化 2. 显式实例化

//用户自己强制转化Add(i1, (int)d1);//显式实例化Add<int> (i1, d1);

2.显式实例化:在函数名<>中指定模板参数的实际类型

int main(){int i1 = 10;double d1 = 10.0;//显式实例化Add<int>(i1, d1);return 0;}

如果类型不匹配,编译器会尝试进行隐式类型转换,若不成功编译器就会报错。

5.函数模板参数的匹配原则

1.一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板可以实例化为这个非模板函数。

// 专门处理int的加法函数int Add(int x, int y){return x + y;}// 通用加法函数template<class T>T Add(T x, T y){return x + y;}void Test(){Add(1, 2); // 与非模板函数匹配,编译器不需要生成对应类型的模板函数Add<int>(1, 2); // 调用编译器特化的Add版本}

2.对于非模板函数与同名的函数模板,在条件相同的情况下,在调用时会优先调用非模板函数而不会使用函数模板产生实例。如果函数模板可以实例化产生更加匹配的模板函数,那么会选择模板函数。

// 专门处理int的加法函数int Add(int x, int y){return x + y;}// 通用加法函数template<class T1, class T2>T1 Add(T1 x, T2 y){return x + y;}void Test(){Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数}

3.模板函数不允许自动类型转换,但普通函数可以。

类模板

1.定义格式

template<class T1, class T2, ..., class Tn>class 类模板名{// 类内成员定义};

例子

// 类模版template<typename T>class Stack{public:Stack(size_t capacity = 4){_array = new T[capacity];_capacity = capacity;_size = 0;}void Push(const T& data);private:T* _array;size_t _capacity;size_t _size;};// 模版不建议声明和定义分离到两个文件.h 和.cpp会出现链接错误。template<class T>void Stack<T>::Push(const T& data){// 扩容_array[_size] = data;++_size;}int main(){Stack<int> st1; // intStack<double> st2; // doublereturn 0;}

2.实例化

类模板的实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,并且将实例化的类型放中间。

类模板名字不是真正的类,实例化的结果才是真正的类。

// Stack是类名,Stack<int>才是类型Stack<int> st1; // intStack<double> st2; // double

3.声明与定义问题

为什么在c++中,模板(函数)的声明和定义不建议分离到两个文件?

 

func.h 函数声明

func.cpp 函数定义

test.cpp 调用函数

报链接错误的直接原因就是链接时,符号表没有对应函数的地址。

1.代码开始编译的时候,首先就预处理,把头文件展开、宏替换、条件编译、去掉注释,.h和对应对的.cpp文件合在一起生成.i文件

2.然后就到编译,根据语法树,检查语法,生成对应对的汇编代码,模板这时候问题就出在这,函数的.i文件,有声明有定义,没有具体类型,test.i中有函数的声明,有类型,但是没有定义,所以就不能生成具体的函数符号表也就没有对应的地址,函数.i文件普通函数有声明有定义有类型,可以生成,这时test.i还是转换成汇编 call func(?),等着链接时把地址连接上,也没有报错,由.i文件生成.s文件;

3.编译完就到了汇编,汇编代码转换成二进制机械码,生成.obj文件;

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

解决方案:声明定义不分离;显式实例化模板。


拜拜,下期再见?

摸鱼ing?✨?


点击全文阅读


本文链接:http://zhangshiyu.com/post/168752.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1