C++ Templates读书笔记1__函数模板


本文是《C++ Templates》一书的学习笔记,如果发现有错误或者不明确的地方,欢迎指正,如果对本文有更好的建议,同样也欢迎提出,谢谢。

模板是泛型编程的基础,所谓泛型编程,就是以独立与任何特定类型的方式编写代码。
函数模板示例:
template <typename T>
inline const T &max(const T &a, const T &b)
{
	T tmp = (a > b) ? a : b;
	return tmp;
}
模板定义以关键字template开始,后接模板形参表, 模板形参表用<>括住一个或者多个模板形参的列表,以逗号分隔。
类型形参跟在关键字class或者typename之后定义。类型参数是T。
以下操作使用了函数模板
int main()
{
	printf("ret = %f\n", ::max(11, 22));
	return 0;
}
程序根据不同的参数类型,实例化了相应类型的模板实例, 如上面的例子,参数类型为int,则程序就有调用一下代码的语义。
const int &max(const int &a, const int &b)
{
    T tmp = (a > b) ? a: b;
    return tmp;
}
那么如果函数调用的实参使用了两种不同的类型,则会发生错误,如下的调用
    ::max(1, 1.1); // error 第一个参数类型为int,第二个参数类型为double
解决这个错误的办法有以下三种:
1. 对实参进行强制类型转换,使他们可以相互匹配
    ::max(static_cast<double>(1), 1.1);
2. 显示指定T的类型
    ::max(static_cast<double>(1), 1.1);
3. 指定两个参数可以有不同类型
    如以下模板的定义:
template<typename T1, typename T2>
inline const T2 &add(const T1 &a, const T2 &b)
{
    T2 tmp = a + b;
    return tmp;
}
    如下调用
int main()
{
    printf("ret = %.2f\n", ::add(1, 1.1));
    return 0;
}
如果模板参数定义如下:
template<typename T1, typename T2, typename T3>
inline const T3 &add(const T1 &a, const T2 &b)
{
    T3 tmp = a + b;
    return tmp;
}
如下调用
int main()
{
    printf("ret = %.2f\n", ::add(1, 1.1));
    return 0;
}
则编译器会发生错误。编译器没有办法隐式的为T3赋值,这种情况下必须显示的指定T3的类型, 如下调用
    int main()
    {
        printf("ret = %.2f\n", ::add<int, double, double>(1, 1.1));
	return 0;
    }
显然这样的调用有些过于复杂,对于T3,可以显示的为其指定类型,而T1, T2则可以根据实参类型进行演绎。
也就是说,改变模板参数的声明顺序,只显示指定第一个实参。
你必须指定“最后一个不能被隐式演绎的模板实参之前的”所有实参类型, 如下的模板定义:
template<typename RT, typename T1, typename T2>
inline const  RT &add(const T1 &a, const T2 &b)
{
    RT  tmp = a + b;
    return tmp;
}
如下调用
int main()
{
    printf("ret = %.2f\n", ::add<double>(1, 1.1));
    return 0;
}
在这个例子中,调用add<double>时显式的将RT指定为double, 但是其他两个参数T1和T2可以根据调用实参分别演绎为int和double.

和普通函数一样,函数模板也可以被重载
一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非函数模板

如下程序:
namespace 
{
	inline int const &max(int const &a, int const &b)
	{
		printf("max(int const &a, int const &b)\n");
		return a < b ? a : b;
	}

	template<typename T>
	inline T const &max(T const &a, T const &b)
	{
		printf("template<typename T> inline T const &max(T const &a, T const &b)\n");	
		return a < b ? a : b;
	}

	template<typename T>
	inline T const &max(T const &a, T const &b, T const &c)
	{
		printf("template<typename T> inline T const &max(T const &a, T const &b, T const &c)\n");
		return ::max(::max(a, b), c);
	}

}
有如下调用:
int main()
{
	std::cout << "::max(7, 42, 68) = " << ::max(7, 42, 68) << std::endl;
	std::cout << "::max(7.0, 42.0) = " << ::max(7.0, 42.0) << std::endl;
	std::cout << "::max('a', 'b') = " << ::max('a', 'b') << std::endl;
	std::cout << "::max(7, 42) = " << ::max(7, 42) << std::endl;
	std::cout << "::max<>(7, 42) = " << ::max<>(7, 42) << std::endl;
	std::cout << "::max<double>(7, 42) = " << ::max<double>(7, 42) << std::endl;
	std::cout << "::max('a', 42.7) = " << ::max('a', 42.7) << std::endl;
	return 0;
}
则输出结果为

::max(7, 42, 68)
调用了3个参数的模板函数max(),根据这个max()的实现,3个参数都是int类型的,所以调用了非模板函数max()。
::max(7.0, 42.0)
由于两个参数是double的,所以调用了二个参数的模板函数max()。
::max(7.0, 42.0)
同理调用了模板函数。
::max(7, 42)  
对于非模板函数和同名的函数模板,如果其他条件都是相同的话,那么在调用的时候,重载解析过程通常会调用非模板函数,而不会从该模板产生出一个实例。
此时调用了非模板函数。
::max(7, 42)  
显式的调用了函数模板。
::max<double>(7, 42) 
显示的调用了函数模板。
::max('a', 42.7)
由于模板是不允许自动类型转换的,但是普通函数可以,所以调用了非模板函数。将'a'和42.7转换为int
下面这个例子将会为指针和普通的C字符串重载这个球最大值的模板
namespace
{
	
	template<typename T>
	inline T const &max(T const &a, T const &b)
	{
		printf("template<typename T> inline T const &max(T const &a, T const &b)\n");
		return a > b ? a : b;
	}

	template<typename T>
	inline T const &max(T const &a, T const &b, T const &c)
	{
		return ::max(::max(a, b), c);
	}

	template<typename T>
	inline T* const &max(T *const &a, T *const &b)
	{
		printf("template<typename T> inline T* const &max(T *const &a, T *const &b)\n");
		return *a > *b ? a : b;
	}

	inline char const *const &max(char const * const &a, char const *const &b)
	{
		printf("inline char const *const &max(char const * const &a, char const *const &b)\n");
		return std::strcmp(a, b) > 0 ? a : b;
	}

}
有如下调用:
int main(int argc, char **argv)
{
        int a = 7;
    	int b = 42;
	std::cout << ::max(a, b) << std::endl; // 求两个int的最大值

	std::string s = "hey";
	std::string t = "you";
	std::cout << ::max(s, t) << std::endl; // 求两个std::string类型的最大值

	int *p1 = &b;
	int *p2 = &a;
	std::cout << ::max(p1, p2) << std::endl; // 求两个指针所指向值的最大值

	char const *s1 = "David";
	char const *s2 = "Nico";
	std::cout << ::max(s1, s2) << std::endl; // 求两个c字符串的最大值
	return 0;
}
输出结果如下:


当调用重载函数的时候, 调用结果就有可能与该重载函数在此时可见与否这个实例有关,但也有可能没有关系。
事实上,定义一个具有3个参数的max()版本,而且直到该定义处还没有看到一个具有两个int参数的重载max()版本的声明,
那么这个具有3个int实例的max()调用将会使用具有2个参数的模板,而不会使用基于int的重载版本max()。

总结:
1. 模板函数为不同的模板实参定义了一个函数家族。
2. 当你传递模板实参的时候,可以根据实参的类型来对函数模板进行实例化。
3. 你可以显示指定模板参数。
4. 你可以重载函数模板。
5. 当重载函数模板的时候, 把你的改变限制在:显示的指定模板参数。
6. 一定要让函数模板的所有重载版本的声明都位于他们被调用的位置之前。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章