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. 一定要讓函數模板的所有重載版本的聲明都位於他們被調用的位置之前。

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