C++ Primer 第五版 ——《第十六章 》“ 模板与泛型编程 ” 学习笔记

本文详细介绍了C++ Primer第五版第十五章的内容,涵盖函数模板、模板类型参数、非类型模板参数、类模板、模板实例化、模板编译错误处理、类模板的成员函数、成员模板、模板实参推断等多个关键知识点,深入探讨了模板在C++编程中的应用和原理。
摘要由CSDN通过智能技术生成

目录

函数模板

模板类型参数 (579P)

非类型模板参数 (580P)

给函数模板 定义 inline 和 constexpr  关键字 (581P)

 less 关键字 (581P)

模板编译 (582P)

模板的编译错误主要是在实例化过程中报告的 ( 582P)

类模板 (583P)

在类模板作用域内引用其它模板类型 (585P)

对于一个实例化了的类模板, 类模板的成员函数只有在程序使用该成员函数时才会实例化。(587P)

使用一个类模板类型时,在类模板自身的作用域中,可以直接使用模板名而不提供实参 (587P)

类模板与友元 (588P)

使用 using 声明为模板声明别名 (590P)

类模板的 static 成员 (591P)

模板类型参数的作用域

使用类的类型成员

函数模板默认实参

类模板默认实参

成员模板

普通 ( 非模板 )类的成员模板

类模板的成员模板

控制实例化

模板实参推断

类型转换和模板类型参数

用一个模板类型参数作用于一个函数中的多个参数类型(602Page)

在函数模板中使用非模板类型参数的类型 (602Page)

在调用函数模板时指定显式模板实参 (603 Page)

正常类型转换应用于显式指定的函数实参

尾置返回类型用于确定函数模板的返回类型(605Page)

能够进行类型转换的标准库模板类(605Page)

用一个函数模板初始化一个函数指针或者对一个函数指针赋值 (607Page)

从左值引用函数参数推断类型( 608Page)

从右值引用函数参数推断类型( 608Page)


 


函数模板


  • 注意: 在模板的定义中(不管是类模板还是函数模板),模板参数列表都不能为空。

模板参数列表中表示的是在某类或函数定义中需要用到的类型或值,当使用模板时,我们需要隐式地或显式地指定模板实参。当我们调用一个函数模板时,  编译器(通常)用函数实参类型来为我们推断模板的实参类型。示例程序:

template <typename T>
int compare(const T &v1, const T &v2)
{
	if (v1 < v2) return -1;
	if (v2 < v1) return 1;
	return 0;
}
int main()
{
	cout << compare<int>(11, 0) << endl; // T is int, 显式指定模板实参类型, 将其绑定在 模板参数上
	cout << compare(151.5, 12.36) << endl; // T is double ,隐式指定模板实参类型, 将其绑定在 模板参数上
	system("pause");
	return 0;
}

编译器用模板实参的类型为我们实例化一个特定类型 版本的函数。当编译器实例化一个模板时, 它使用实际的模板实参类型代替对应的模板类型参数来创建出该模板的一个新“实例”。

上述的程序用两个不同的类型实例化了两个不同版本的 compare ,这些编译器生成的特定类型版本的函数通常被称为模板的实例( 有些书上也叫模板函数 )。


模板类型参数 (579P)


 模板类型参数 ( 就像上述程序中 “ < T > ”)有什么用处呢?:

  • 可以用来指定返回类型或函数的参数类型, 以及在函数体内用于变量声明或强制类型转换。

注意的是,声明每一个模板类型参数前都必须使用关键字 class 或 typename( 它们含有相同,可以同时互换使用)。

通常我们应该使用 typename 关键字来声明模板类型参数, 因为它比class 更加的直观,也能更加清楚地指出随后的名字是一个类型名称。


非类型模板参数 (580P)


可以通过一个特定的类型名而非关键字class 或 typename 来给模板定义一个非类型参数, 非类型参数必须是一个常量表达式。

当一个模板被实例化时, 非类型参数被一个用户提供的或编译器推断出的值所代替, 从而允许编译器在编译时实例化模板。

有哪些类型可以作为非类型参数呢?

  • bool、char、wchar_t、char16_t、char32_t、short、int、long、long long、
  • 指向对象类型的指针
  • 指向函数类型的指针
  • 左值引用

绑定到非类型的整型参数的实参必须是一个常量表达式。 绑定到指针或引用的非类型参数的实参必须具有静态的生存期。

有哪些类型不可以作为非类型参数呢?

  • double 、float、long double、类类型(比如:string)
  • 我们不能用一个普通 (非 static) 局部变量或动态对象作为指针或引用非类型模板参数的实参。
template<unsigned N, unsigned M>
int compare(const char(&p1)[N], const char(&p2)[M])
{
	return strcmp(p1, p2);
}
int main()
{
	cout << compare("hi", "mom") << endl; 
	
	system("pause");
	return 0;
}

在模板定义内, 模板非类型参数必须是一个常量值。在需要常量表达式的地方, 都可以使用非类型模板参数, 例如:指定数组大小。


给函数模板 定义 inline 和 constexpr  关键字 (581P)


inline或 constexpr 说明符放在模板参数列表之后, 返回类型之前:

// ok: inline specifier follows the template parameter list
template <typename T> inline T min(const T&, const T&);

// error: incorrect placement of the inline specifier
inline template <typename T> T min(const T&, const T&);

记住编写泛型代码有两个重要原则:

  • 模板中的函数形参是对const的引用
  • 函数体中的条件判断仅使用<比较运算。

那为什么函数参数是对const的引用?

  • 因为将函数的参数设定为 const 引用可以保证该函数可以用于不能拷贝的类型( 比如: unique_ptr 和 IO 类型)。
  • 当该函数处理大的对象时,那么这种设计也会使函数运行得更快。

那为什么仅使用<比较运算?

  • 如果编写代码时只使用<运算符, 我们就降低了该函数对要处理的类型的要求。这些类型必须支持<, 但支不支持 > 并不重要。

 less 关键字 (581P)


 


模板编译 (582P)


当编译器遇到一个模板定义时, 它并不生成代码。只有当我们实例化出模板的一个特定版本时, 编译器才会生成代码。

当我们使用 (而不是定义,是实例化)模板时, 编译器才生成代码, 这一特性影响了我们如何组织代码以及何时检测到错误。

 为了生成一个实例化版本, 编译器需要有函数模板或类模板成员函数的定义代码。因此,与非模板代码不同, 模板的头文件通常既包括声明也包括定义。所以说 ,函数模板和类模板成员函数的定义通常放在头文件中。


模板的编译错误主要是在实例化过程中报告的 ( 582P)


练习16.1:

当调用一个函数模板时, 编译器会利用给定的函数实参来推断模板实参,它使用实际的模板实参来代替对应的模板参数来创建出模板的一个 “ 新实例 ”,也就是一个真正可以调用的函数, 这个过程称为实例化。

练习题16.3:

因为 Compare 函数中 使用的是 < 运算符进行操作的,需要类型 T 事先就定义该运算符, 但是我们的实际类型 Sales_data 它没有实现 < 运算符, 所以会发生错误

练习题16.4:


template <typename T,typename V>
T find_V( T v1,  T v2, const V &v3)
{
	while (v1 != v2 && *v1 != v3)
	{
		++v1;
	}
	return v1;
}
int main()
{
	vector<int> v1{ 0,5,6,7,8 };
	if (!v1.empty())
	{
		auto temp = find_V(v1.begin(), v1.end(),6);
		if (temp == v1.end())
		{
			cout << "没有找到6" << endl;
		}
		else
			cout << "找到6 " << endl;
	}
	list<string> v2 = { "huang","chengt","tao" };
	if (!v2.empty())
	{
		auto temp = find_V(v2.begin(), v2.end(), "sda");
		if (temp == v2.end())
		{
			cout << "没有找到sda" << endl;
		}
		else
			cout << "找到sda " << endl;
	}
	system("pause");
	return 0;
}

练习题16.5:


template <typename T>
 constexpr void print( T &v1)
{
	for (auto tt : v1)
	{
		cout << tt << " ";
	}
	cout << endl;
}
int main()
{
	int v1[] = { 5,6,7,8,9,10 };
	print(v1);
	vector<string> v2{ "huang","chengt","tao" };
	print(v2);
	system("pause");
	return 0;
}

练习题16.6:

begin 函数是指向数组首元素的指针, 而 end 指向数组尾元素之后的指针。 当使用该函数时,可以很容易写出一个循环并且处理数组中的元素。


template <typename T>
 constexpr void print( T v1,T v2)
{
	 while (v1 != v2)
	 {
		 cout << *v1 << " ";
		 ++v1;
	}
	 cout << endl;
}
int main()
{
	int v1[]{ 5,6,7,8,9,5 };
	int *p = v1;
	auto tt = end(v1) - begin(v1); // 计算数组中的数量
	int *v = &v1[tt]; // 指向数组尾元素下一位置的指针

	print(p, v);
	system("pause");
	return 0;
}

练习题16.7:

template <typename T>
 constexpr int find_V( T v1,  T v2)
{
	int temp = 0;
	while (v1!=v2)
	{
		++v1;
		++temp;
	}
	return temp;
}
int main()
{
	int v1[] = { 5,6,7,8,9,10 };
	cout << "数组的大小为:" << find_V(begin(v1), end(v1)) << endl;
	system("pause");
	return 0;
}

练习题16.8:

因为大多数标准库容器的迭代器都定义了 == 和 !=, 但是它们大多数都没有定义< 运算符, 因此只要使用 迭代器 和 !=的习惯, 就不在意用的是哪种容器。


类模板 (583P)


类模板与函数模板的不同之处是:

  • 编译器不能为类模板推断模板参数类型。我们创建类模板的实例化时,必须在模板名后的尖括号中提供 额外的信息, 它用来替换模板参数的模板实参列表。

当我们使用一个类模板时,我们必须提供显式的模板实参列表, 它们被绑定到模板参数使用实际的模板实参来代替对应的模板参数来创建出模板的一个 “ 新实例 ”,也就是一个特定类型的模板类。

template <typename T> class Blob 
{
public:
	typedef T value_type;
	typedef typename std::vector<T>::size_type size_type;

	// constructors
	Blob();
	Blob(std::initializer_list<T> il);


	// number of elements in the Blob
	size_type size() const { return data->size(); }
	bool empty() const { return data->empty(); }


	// add and remove elements
	void p
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值