C++学习笔记:函数模版
2012-04-02 11:09
399 查看
在编写程序中经常遇到的情况是为了实现大致相同的功能而不得不编写多个函数,而这些函数只是返回类型和形参类型不同,要怎么解决这种问题呢?那就是使用泛型编程。
有时我们想分别为整形和浮点型编写重载函数,而两者的实现本质又相同,这时我们就可以利用函数模版来实现,这里以绝对值函数为例:
template<class T>
T absval(T x)
{
if (x < 0)
return -x;
else
return x;
}
关键就在第一行。关键字template意为以下是一个模版,这里它是一个函数模版定义。尖括号包含一个由逗号分隔的模版形参列表。函数模版就是一个能根据形参类型生成函数的模式。在函数模版定义中,T表示一个类型,它将来可能为任意类型。absval函数的调用者会确定用哪个来替换T。
编译器遇到函数模版定义时会记下该模版,但不会生成任何代码,而直到使用该函数模版时才生成一个真正的函数。流程可以理解为:编译器先读取模版源代码,并用int等模版实参替换模版形参T,然后编译器再编译生成的代码。
这里要注意的是模版实参与模版形参的概念,他们有函数实参与函数形参的概念不同。
在上例中,模版形参是T,而模版实参必须为某个类型。不能把类型作为函数实参传递,但模版不同,实际上程序没有“传递”任何东西。模版的神奇之处在于编译时刻:编译器读取模版定义,当它随后发现对函数模版的函数调用时,它检查函数实参类型,然后根据该函数的实参类型判定模版的实参。随后编译器使用该模版实参替换T,从而生成一个新的函数实例。
模版形参:
有时形参的名字可能比T更加具体,如果模版有多个形参,则各个参数的名字不能相同,因此代码中会出现T之外的名字。如,copy算法是有两个形参的函数模版:一个是输入迭代器,一个是输出迭代器,代码如下:
template<class InputIterator, class OutputIterator>
OutputIterator copy(InputIterator start, InputIterator end, OutputIterator result)
{
for ( ; start != end; ++start, ++result)
*result = *start;
return result;
}
模版实参:
模版最让人舒服的地方在于编译器能够自动从函数实参推导出模版实参,不过编译器可不是任何时候都有这个能力,因此有时还要显示的告知编译器。如下面的算法:
template<class T>
T min(T a, T b)
{
if (a < b)
return a;
else
return b;
}
如果两个实参类型相同编译器能够正确推导出需要的类型并正确工作。
int x(10), y(20), z(std::min(x, y));
但如果形参类型不同,则编译器就不知道应该用哪个类型作为实参了。
int x(10);
long y(20);
std::cout << std::min(x, y); //报错
这是因为编译器在处理模版时不会进行自动类型转换,这与我们手动编写函数的情况不同(会自动转换)。
所以我们要指明模版形参类型来知道编译器。将第三行改为
std::cout << std::min<long>(x, y);
还有一种情况:先看下例:
#include <iostream>
#include <istream>
#include <ostream>
template<class T, class U>
U input_sum(std::istream& in)
{
T x;
U sum(0);
while (in >> x)
sum += x;
return sum;
}
int main()
{
long sum(input_sum<int, long>(std::cin));
std::cout << sum << '\n';
}
此例中函数实参并未使用数据和累加操作符类型,所以编译器不能推导出模版形参,这也需要显示声明(加粗处)。
关于声明与定义:
我们知道在编写函数的时候,我们一般将函数的声明放到一个.hpp文件中,而将函数的定义放到一个同名的.cpp文件中,但在使用一个模版函数之前,编译器不仅需要函数模版声明,通常还需要完整的函数模版定义。也就是说,如果在一个头文件中定义了一个模版,那么该文件必须包含该函数模版的内容。当然也有其他技术可以实现将模版的声明与定义放到不同的文件中,但要想正确使用还有点困难。
有时我们想分别为整形和浮点型编写重载函数,而两者的实现本质又相同,这时我们就可以利用函数模版来实现,这里以绝对值函数为例:
template<class T>
T absval(T x)
{
if (x < 0)
return -x;
else
return x;
}
关键就在第一行。关键字template意为以下是一个模版,这里它是一个函数模版定义。尖括号包含一个由逗号分隔的模版形参列表。函数模版就是一个能根据形参类型生成函数的模式。在函数模版定义中,T表示一个类型,它将来可能为任意类型。absval函数的调用者会确定用哪个来替换T。
编译器遇到函数模版定义时会记下该模版,但不会生成任何代码,而直到使用该函数模版时才生成一个真正的函数。流程可以理解为:编译器先读取模版源代码,并用int等模版实参替换模版形参T,然后编译器再编译生成的代码。
这里要注意的是模版实参与模版形参的概念,他们有函数实参与函数形参的概念不同。
在上例中,模版形参是T,而模版实参必须为某个类型。不能把类型作为函数实参传递,但模版不同,实际上程序没有“传递”任何东西。模版的神奇之处在于编译时刻:编译器读取模版定义,当它随后发现对函数模版的函数调用时,它检查函数实参类型,然后根据该函数的实参类型判定模版的实参。随后编译器使用该模版实参替换T,从而生成一个新的函数实例。
模版形参:
有时形参的名字可能比T更加具体,如果模版有多个形参,则各个参数的名字不能相同,因此代码中会出现T之外的名字。如,copy算法是有两个形参的函数模版:一个是输入迭代器,一个是输出迭代器,代码如下:
template<class InputIterator, class OutputIterator>
OutputIterator copy(InputIterator start, InputIterator end, OutputIterator result)
{
for ( ; start != end; ++start, ++result)
*result = *start;
return result;
}
模版实参:
模版最让人舒服的地方在于编译器能够自动从函数实参推导出模版实参,不过编译器可不是任何时候都有这个能力,因此有时还要显示的告知编译器。如下面的算法:
template<class T>
T min(T a, T b)
{
if (a < b)
return a;
else
return b;
}
如果两个实参类型相同编译器能够正确推导出需要的类型并正确工作。
int x(10), y(20), z(std::min(x, y));
但如果形参类型不同,则编译器就不知道应该用哪个类型作为实参了。
int x(10);
long y(20);
std::cout << std::min(x, y); //报错
这是因为编译器在处理模版时不会进行自动类型转换,这与我们手动编写函数的情况不同(会自动转换)。
所以我们要指明模版形参类型来知道编译器。将第三行改为
std::cout << std::min<long>(x, y);
还有一种情况:先看下例:
#include <iostream>
#include <istream>
#include <ostream>
template<class T, class U>
U input_sum(std::istream& in)
{
T x;
U sum(0);
while (in >> x)
sum += x;
return sum;
}
int main()
{
long sum(input_sum<int, long>(std::cin));
std::cout << sum << '\n';
}
此例中函数实参并未使用数据和累加操作符类型,所以编译器不能推导出模版形参,这也需要显示声明(加粗处)。
关于声明与定义:
我们知道在编写函数的时候,我们一般将函数的声明放到一个.hpp文件中,而将函数的定义放到一个同名的.cpp文件中,但在使用一个模版函数之前,编译器不仅需要函数模版声明,通常还需要完整的函数模版定义。也就是说,如果在一个头文件中定义了一个模版,那么该文件必须包含该函数模版的内容。当然也有其他技术可以实现将模版的声明与定义放到不同的文件中,但要想正确使用还有点困难。
相关文章推荐
- 初探C++函数模版学习笔记
- C++学习笔记之模板(1)——从函数重载到函数模板
- C++学习笔记_6:函数的重载
- 【C++】学习笔记二十九——函数
- 【菜鸟C++学习笔记】25.函数重载
- C++学习笔记->函数重载
- C++学习笔记二——常量指针、指针常量和函数指针
- C++学习笔记12——函数的参数传递
- C++学习笔记-----operator=函数处理自赋值
- C++学习笔记8 - 函数探幽
- C++学习笔记17——函数重载
- C_PlusPlus学习笔记 - 2_函数 (C++语言程序设计【第三版】 郑莉等,清华大学出版社)
- Lua学习笔记(6) : 在Lua中调用C++之函数
- Effective C++学习笔记 条款06:如不想使用编译器自动生成的函数,就该明确拒绝
- C++学习笔记--继承中的同名变量和同名函数
- C++字符串处理函数学习笔记
- C++学习笔记 函数重载
- 【C++学习笔记】函数基础和参数传递
- C++基础学习笔记----第四课(函数的重载、C和C++的相互调用)
- C++学习笔记--函数对象