C++ 函数模板与类模板
16.1.1 函数模板
tmplate
+模板参数列表
template<typename T>
int compare(const T &v1, const T &v2){
if(v1<v2) return -1;
if(v2<v1) return 1;
return 0;
}
模板参数列表不能为空
实例化:实现模板的某个特定版本
cout<<compare(0,1)<<endl; //T为int
cout<<compare(vector1, vector2); //T为vector<int>
非类型模板参数:表示一个值而非一个类型
值必须是常量表达式,模板在编译时被实例化
- 如果是整型:必须是常量表达式
- 如果是引用或指针:传入的实参必须有静态生存期static
例如,我们可以编写一个compare版本处理字符串字面常量。这种字面常量是constchar 的数组。由于不能拷贝一个数组,所以我们将自己的参数定义为数组的引用(参见6.2.4节,第195页)。由于我们希望能比较不同长度的字符串字面常量,因此为模板定义了两个非类型的参数。第一个模板参数表示第一个数组的长度,第二个参数表示第二个数组的长度:
template<unsigned N, unsigned M>
int compatre(const char (&p1)[N], const char (&p2)[M]){
if(N<M) return -1;
if(M<N) return 1;
return 0;
}
compare("hi", "mom");
//编译器实例化出:
int compare(const char (&p1)[3], const char (&p2)[4]);
模板的编译
当编译器遇到一个模板定义时,它并不生成代码。
只有当我们实例化出模板的一个特定版本时,编译器才会生成代码。
模板则不同:为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义。因此,与非模板代码不同,模板的头文件通常既包括声明也包括定义。
最佳实践:定义一个函数模板print,遍历打印任何类型任何大小的数组
template<typename T, unsigned N>
void print(T (&array)[N]){
for(T it : array){
cout<<it<<" ";
}
cout<<endl;
}
16.1.2 类模板
与函数模板不同,类模板在实例化时要提供额外类型信息
定义类模板
template<typename T> class Blob{
public:
typedef T vale_type;
typedef typename vector<T>::size_type size_type;
//构造函数
Blob();
Blob(std::initializer_list<T> il);
//Blob中的元素数目
size_type siez() const {return data->size(); }
bool empty() const {return data->empty(); }
//添加和删除元素
void push_back(const T &t) {data->push_back(t);}
void piop_back();
//元素访问
T& back();
T& operator[](size_type i);
private:
shared_ptr<vector<T>> data;
//若data[i]无效,则抛出异常
void check(size_type i, const string &msg) const;
};
实例化模板
Blob<int> ia; //空Bolb<int>
Blob<int> ia2 = {0,1,2,3,4}; //有5个空元素的Blob<int>
当编译器从我们的Blob模板实例化出一个类时,它会重写Blob模板,将模板参数T的每个实例替换为给定的模板实参,在本例中是int。
在类外定义成员函数
在类外定义成员函数时,不仅要表明类的作用域,还要带上模板实参
template<typename T>
void Blob<T>::check(size_type i, const string &msg)const{
if( i >= data->size() )
throw std::out_of_range(msg);
}
类模板成员函数的实例化
默认情况下,一个类模板的成员函数只有当程序用到它时才进行实例化。例如,下面代码
//实例化Blob<int>和接受initializer_list<int>的构造函数
Blob<int> squares = {0, 1,2,3,4,5,6,7,8,9};
//实例化Blob<int>::size() const
for (size_t i= 0; i != squares.size(); ++i)
squares[i] = i*i;//实例化Blob<int>::operator[](size_t)
实例化了 Blob类和它的三个成员函数:operator[]、size和接受initializer_list的构造函数。
如果一个成员函数没有被使用,则它不会被实例化。成员函数只有在被用到时才进行实例化,这一特性使得即使某种类型不能完全符合模板操作的要求(参见9.2节,第294页),我们仍然能用该类型实例化类。
默认情况下,对于一个实例化了的类模板,其成员只有在使用时才被实例化
类模板和友元
- 一对一友好关系:两模板类的同类型实例化互为友元
//前置声明,在Blob中声明友元所需要的
template <typename> class BlobPtr;
template <typename> class Blob; //运算符==中的参数所需要的
template <typenaem T>
bool operator==(const Blob<T> &, const Blob<T>&);
template <typename T> class Blob{
//每个Blob实例将访问权限授予用相同类型实例化的BlobPtr和相等运算符
friend class BlobPtr<T>;
friend bool operator==<T>
(const Blob<T>&, const Blob<T>&);
//其他成员定义不变
};
友元的声明用Blob的模板形参作为它们自己的模板实参。因此,友好关系被限定在用相同类型实例化的Blob 与 BlobPtr相等运算符之间;
Blob<char> ca; //BlobPtr<char>和operator==<char>都是本对象的友元
Blob<int> ia; //BlobPtr<int>和operator==<int>都是本对象的友元
- 通用和特定的模板友好关系:一个模板类将另一个模板类的类型的实例都声明为自己的友元
//前置声明,在模板类声明时需要用到
template <typename T> class Pal;
class C{//C是一个普通的非模板类
friend class Pal<C>; //用类C实例化的Pal是C的一个友元
//Pal2的所有实例化都是C的友元,这种情况无需前置声明
template <typename T> friend class Pal2;
};
template <typename T> class C2{//C2本身是一个类模板
//C2的每个实例都将相同的实例化的Pal声明为友元
friend class Pal<T>; //Pal模板什么必须在作用域之内
//Pal2的所有实例都是C2的所有实例的友元,不需要前置声明
template <typename X> friend class Pal2;
//Pal3是一个非模板类,它是C2所有实例的友元
friend class Pal3; //不需要Pal3的前置声明
};
为了让另一模板类的所有实例成为友元,友元声明中必须使用与类模板本身不同的模板参数。
- 令模板自己类型成为友元
8template <typename Type> class Bar{
friend Type; //将访问权限授予用来实例化的Bar类型
//...
};
模板类型别名
由于模板不是一种类型,所以我们不能用typedef
而要用using
template<typename T> using twin = pair<T,T>;
twin<string> authors; //authors是一个pair<string, string>
一个模板类型是一族类的别名
twin<int> win_loss; //win_loss是一个pair<int,int>
twin<double> area; //area是一个pair<double,double>
当我们使用模板类型别名时,可以固定一个或多个参数
template <typename T> using partNo = pair<T, unsigned>;
partNo<string> books; //books是一个pair<string, unsigned>
partNo<Vehicle> cars; //cars是一个pair<Vehicle, unsigned>
partNo<Student> kisd; //kids是一个pair<Student, unsigned>
类模板参数的static成员
每种类型的实例化对象间共享,不同类型的实例化对象间不共享
16.1.3 模板参数
使用类的类型成员
回忆一下,我们用作用域运算符(::)来访问static成员和类型成员。在普通(非模板)代码中,编译器掌握类的定义。因此,它知道通过作用域运算符访问的名字是类型还是 static成员。例如,如果我们写下string::size_type
,编译器有string 的定义,从而知道size_type是一个类型。
但对于模板代码就存在困难。例如,假定T是一个模板类型参数,当编译器遇到类似T::mem
这样的代码时,它不会知道mem是一个类型成员还是一个static数据成员,直至实例化时才会知道。
但是,为了处理模板,编译器必须知道名字是否表示一个类型。例如,假定T是一个类型参数的名字,当编译器遇到如下形式的语句时:
T::size_type * p;
它需要知道我们是正在定义一个名为p的变量还是将一个名为size_type的static数据成员与名为p的变量相乘。
默认情况下,C++语言假定通过作用域运算符访问的名字不是类型。因此,如果我们希望使用一个模板类型参数的类型成员,就必须显式告诉编译器该名字是一个类型。我们通过使用关键字typename
来实现这一点:
template <typename T>
typename T::value_type top(const T& c)
{
if ( !c.empty())
return c.back();
else
return typename T::value_type ();
}
当我们希望通知编译器一个名字表示类型时,必须使用关键字typename,而不能使用class。
默认模板实参
函数模板的默认实参
就像我们能为函数参数提供默认实参一样,我们也可以提供默认模板实参(default template argument)。在新标准中,我们可以为函数和类模板提供默认实参。而更早的C++标准只允许为类模板提供默认实参。
例如,我们重写compare,默认使用标准库的less函数对象模板
/// l compare有一个默认模板实参less<T>和一个默认函数实参F()
template <typename T, typename F = less<T>>
int compare (const T &v1, const T &v2,F f = F()){
if ( f(v1, v2) ) return -1;
if (f(v2, v1) ) return 1;
return 0;
}
在这段代码中,我们为模板添加了第二个类型参数,名为F,表示可调用对象的类型;并定义了一个新的函数参数f,绑定到一个可调用对象上。
我们为此模板参数提供了默认实参,并为其对应的函数参数也提供了默认实参。默认模板实参指出compare将使用标准库的 less函数对象类,它是使用与 compare一样的类型参数实例化的。默认函数实参指出f将是类型F的一个默认初始化的对象。
当用户调用这个版本的compare时,可以提供自己的比较操作,但这并不是必需的:
bool i = compare(0,42);//使用less; i为-1
//结果依赖于item1和item2中的isbn
Sales_data item1(cin), item2(cin) ;
bool j = compare (item1,item2,compareIsbn) ;
第一个调用使用默认函数实参,即,类型less的一个默认初始化对象。在此调用中,T为int,因此可调用对象的类型为less。compare 的这个实例化版本将使用less进行比较操作。
在第二个调用中,我们传递给compare三个实参: compareIsbn(参见11.2.2节)和两个sales_data类型的对象。当传递给 compare三个实参时,第三个实参的类型必须是一个可调用对象,该可调用对象的返回类型必须能转换为bool值,且接受的实参类型必须与compare的前两个实参的类型兼容。与往常一样,模板参数的类型从它们对应的函数实参推断而来。在此调用中,T的类型被推断为sales_data,F被推断为compareIsbn的类型。
与函数默认实参一样,对于一个模板参数,只有当它右侧的所有参数都有默认实参时,它才可以有默认实参。
类模板的默认实参
无论何时使用一个类模板,我们都必须在模板名之后接上尖括号。尖括号指出类必须从一个模板实例化而来。特别是,如果一个类模板为其所有模板参数都提供了默认实参,且我们希望使用这些默认实参,就必须在模板名之后跟一个空尖括号对:
template <class T = int> class Numbers { // T默认为int
public:
Numbers(T v = 0): val(v) { }
//对数值的各种操作
private:
Tval;
};
Numbers<long double> lots_of_precision;
Numbers<> average_precision; //空<>表示我们希望使用默认类型
此例中我们实例化了两个 Numbers版本: average_precision是用int 代替T实例化得到的; lots_of _precision是用long double代替T实例化而得到的。
C++ 函数模板与类模板的更多相关文章
- [Reprint] C++函数模板与类模板实例解析
这篇文章主要介绍了C++函数模板与类模板,需要的朋友可以参考下 本文针对C++函数模板与类模板进行了较为详尽的实例解析,有助于帮助读者加深对C++函数模板与类模板的理解.具体内容如下: 泛型编程( ...
- C++_进阶之函数模板_类模板
C++_进阶之函数模板_类模板 第一部分 前言 c++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来 ...
- C++复习:函数模板和类模板
前言 C++提供了函数模板(function template).所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表.这个通用函数就称为函数模板.凡是函数体 ...
- 【校招面试 之 C/C++】第2题 函数模板、类模板、特化、偏特化
1.C++模板 说到C++模板特化与偏特化,就不得不简要的先说说C++中的模板.我们都知道,强类型的程序设计迫使我们为逻辑结构相同而具体数据类型不同的对象编写模式一致的代码,而无法抽取其中的共性,这样 ...
- C++解析(26):函数模板与类模板
0.目录 1.函数模板 1.1 函数模板与泛型编程 1.2 多参数函数模板 1.3 函数重载遇上函数模板 2.类模板 2.1 类模板 2.2 多参数类模板与特化 2.3 特化的深度分析 3.小结 1. ...
- C++学习之函数模板与类模板
泛型编程(Generic Programming)是一种编程范式,通过将类型参数化来实现在同一份代码上操作多种数据类型,泛型是一般化并可重复使用的意思.泛型编程最初诞生于C++中,目的是为了实现C++ ...
- C++ 函数模板与类模板(使用 Qt 开发编译环境)
注意:本文中代码均使用 Qt 开发编译环境,如有疑问和建议欢迎随时留言. 模板是 C++ 支持参数化程序设计的工具,通过它可以实现参数多态性.所谓参数多态性,就是将程序所处理的对象的类型参数化,使得一 ...
- C++ 模板常见特性(函数模板、类模板)
背景 C++ 是很强大,有各种特性来提高代码的可重用性,有助于减少开发的代码量和工作量. C++ 提高代码的可重用性主要有两方面: 继承 模板 继承的特性我已在前面篇章写过了,本篇主要是说明「模板」的 ...
- C++进阶-1-模板基础(函数模板、类模板)
C++进阶 模板 1.1 函数模板 1 #include<iostream> 2 using namespace std; 3 4 // 模板 5 6 // 模板的简单实例 7 // 要求 ...
- 【C++ 泛型编程01:模板】函数模板与类模板
[模板] 除了OOP外,C++另一种编程思想称为 泛型编程 ,主要利用的技术就是模板 C++提供两种模板机制:函数模板和类模板 函数模板 函数模板作用 建立一个通用函数,其函数返回值类型和形参类型可以 ...
随机推荐
- git push --recurse-submodules = on-demand 递归push
I have the following project structure: root-project | |-- A | | | |-- C | |-- B A和B是根项目的子模块. C又是项目A ...
- 需要多久才能看完linux内核源码?
代码中自由颜如玉!代码中自有黄金屋! 一.内核行数 Linux内核分为CPU调度.内存管理.网络和存储四大子系统,针对硬件的驱动成百上千.代码的数量更是大的惊人. 先说说最早的内核linux 0.11 ...
- python模块xlsxwriter使用
1.安装 pip install XlsxWriter 2.使用 # -*- coding: utf-8 -*- from io import BytesIO import qrcode # impo ...
- idea关闭窗口快捷键
File->settings->keymap->main menu->window->editor tabs->close 根据自己的使用习惯将想要关闭的标签设置快 ...
- Apache HTTP Server 使用
安装 macOS: brew install apache2 Ubuntu: sudo apt install apache2 使用 配置文件路径: macOS: /opt/homebrew/etc/ ...
- SpringMVC:域对象共享数据
SpringMVC:域对象共享数据 使用ServletAPI向request域对象共享数据 @RequestMapping("/testServletAPI") public St ...
- DRBD - Distributed Replication Block Device
Ref https://computingforgeeks.com/install-and-configure-drbd-on-centos-rhel https://www.veritas.com/ ...
- Python 潮流周刊#68:2023 年 Python 开发者调查结果(摘要)
本周刊由 Python猫 出品,精心筛选国内外的 250+ 信息源,为你挑选最值得分享的文章.教程.开源项目.软件工具.播客和视频.热门话题等内容.愿景:帮助所有读者精进 Python 技术,并增长职 ...
- Linq操作XML生成XML,实体生成XML和互转
开发接口中难免会遇到一些数据对接需要转换城xml,看到很多之前的代码都使用很传统的方法循环集合并定义xml后一一生成的,代码之封锁 特此使用了简单易用linq操作分享给大家,希望可以帮到需要的同学 今 ...
- 论文解读 -TongGu:专注于文言文的大模型
一.简要介绍 文言文是通往中国古代丰富遗产和智慧的门户,但其复杂性给大多数没有专业知识的现代人构成了巨大的理解障碍.虽然大型语言模型(LLM)在自然语言处理(NLP)方面显示出了显著的能力,但它们在文 ...