C++:模板(1)

HZzzzzLu 2024-10-06 08:05:01 阅读 96

目录

实现泛型的交换函数

函数模板

1.概念

2.格式

3.原理

4.函数模板实例化

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

类模板

1.定义格式

2.实例化

3.声明与定义问题


实现泛型的交换函数

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

<code>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,使用格式如下:

<code>​

//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.隐式实例化:编译器根据传入实参推算模板参数的实际类型

<code>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; // int

Stack<double> st2; // double

return 0;

}

2.实例化

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

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

// Stack是类名,Stack<int>才是类型

Stack<int> st1; // int

Stack<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😴✨🎞



声明

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