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>

非类型模板参数:表示一个值而非一个类型

值必须是常量表达式,模板在编译时被实例化

  1. 如果是整型:必须是常量表达式
  2. 如果是引用或指针:传入的实参必须有静态生存期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页),我们仍然能用该类型实例化类。

默认情况下,对于一个实例化了的类模板,其成员只有在使用时才被实例化

类模板和友元

  1. 一对一友好关系:两模板类的同类型实例化互为友元
//前置声明,在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>都是本对象的友元
  1. 通用和特定的模板友好关系:一个模板类将另一个模板类的类型的实例都声明为自己的友元
//前置声明,在模板类声明时需要用到
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的前置声明
};

为了让另一模板类的所有实例成为友元,友元声明中必须使用与类模板本身不同的模板参数。

  1. 令模板自己类型成为友元
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++ 函数模板与类模板的更多相关文章

  1. [Reprint] C++函数模板与类模板实例解析

    这篇文章主要介绍了C++函数模板与类模板,需要的朋友可以参考下   本文针对C++函数模板与类模板进行了较为详尽的实例解析,有助于帮助读者加深对C++函数模板与类模板的理解.具体内容如下: 泛型编程( ...

  2. C++_进阶之函数模板_类模板

     C++_进阶之函数模板_类模板 第一部分 前言 c++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来 ...

  3. C++复习:函数模板和类模板

    前言 C++提供了函数模板(function template).所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表.这个通用函数就称为函数模板.凡是函数体 ...

  4. 【校招面试 之 C/C++】第2题 函数模板、类模板、特化、偏特化

    1.C++模板 说到C++模板特化与偏特化,就不得不简要的先说说C++中的模板.我们都知道,强类型的程序设计迫使我们为逻辑结构相同而具体数据类型不同的对象编写模式一致的代码,而无法抽取其中的共性,这样 ...

  5. C++解析(26):函数模板与类模板

    0.目录 1.函数模板 1.1 函数模板与泛型编程 1.2 多参数函数模板 1.3 函数重载遇上函数模板 2.类模板 2.1 类模板 2.2 多参数类模板与特化 2.3 特化的深度分析 3.小结 1. ...

  6. C++学习之函数模板与类模板

    泛型编程(Generic Programming)是一种编程范式,通过将类型参数化来实现在同一份代码上操作多种数据类型,泛型是一般化并可重复使用的意思.泛型编程最初诞生于C++中,目的是为了实现C++ ...

  7. C++ 函数模板与类模板(使用 Qt 开发编译环境)

    注意:本文中代码均使用 Qt 开发编译环境,如有疑问和建议欢迎随时留言. 模板是 C++ 支持参数化程序设计的工具,通过它可以实现参数多态性.所谓参数多态性,就是将程序所处理的对象的类型参数化,使得一 ...

  8. C++ 模板常见特性(函数模板、类模板)

    背景 C++ 是很强大,有各种特性来提高代码的可重用性,有助于减少开发的代码量和工作量. C++ 提高代码的可重用性主要有两方面: 继承 模板 继承的特性我已在前面篇章写过了,本篇主要是说明「模板」的 ...

  9. C++进阶-1-模板基础(函数模板、类模板)

    C++进阶 模板 1.1 函数模板 1 #include<iostream> 2 using namespace std; 3 4 // 模板 5 6 // 模板的简单实例 7 // 要求 ...

  10. 【C++ 泛型编程01:模板】函数模板与类模板

    [模板] 除了OOP外,C++另一种编程思想称为 泛型编程 ,主要利用的技术就是模板 C++提供两种模板机制:函数模板和类模板 函数模板 函数模板作用 建立一个通用函数,其函数返回值类型和形参类型可以 ...

随机推荐

  1. rcc of stm32

    1. G0 2. F0 / F1 / F3 F0 F1 F3 3. F2/F4 F205 f429 f7

  2. Web端OA办公后台管理系统(使用AxureRP设计)思路与效果分享

    本期带来一套OA办公后台管理系统(办公一体化)的设计分享.本次的作品设计,使用AxureRP软件. 一套实用的后台OA系统,一定是功能强大.能覆盖常用功能的.本次分享的系统,包含组织.员工管理.考勤. ...

  3. JavaScript设计模式样例九 —— 桥接模式

    桥接模式(Bridge Pattern) 定义:是用于把抽象化与实现化解耦,使得二者可以独立变化. 目的:将抽象部分与实现部分分离,使它们都可以独立的变化. 场景:实现系统可能有多个角度分类,每一种角 ...

  4. JVM深入学习-ClassLoader篇(一)

    初识JVM --- ClassLoader深入理解 ClassLoader.SPI机制 Class对象的理解 java在诞生之初,就有一次编译到处运行的名言,今天我们来探究一下,从java代码到cla ...

  5. 编译器实现之旅——第十三章 if语句和while语句的代码生成器分派函数的实现

    在上一章的旅程中,我们已经实现了表达式类代码生成器分派函数,而在这一章的旅程中,我们将要实现if语句和while语句的代码生成器分派函数.if语句和while语句是两种典型的带有跳转指令的语句.观察C ...

  6. Windows 将透明的图片旋转,裁剪

    使用 Microsoft Office Picture Manager 本来是想找个Java代码,跑一下 忽然在 Windows 图片打开方式中有一个  Microsoft Office Pictur ...

  7. SciPy从入门到放弃

    目录 SciPy简介 拟合与优化模块 求最小值 曲线拟合 线性代数模块 统计模块 直方图和概率密度函数 统计检验 SciPy简介 SciPy是一种以NumPy为基础,用于数学.工程及许多其他的科学任务 ...

  8. vue打包项目版本号自加

    原因 项目每次打包后都需要改动项目版本号,这个改动每次都需要在package.json中修改version,比较麻烦,到底有没有一种打包后版本号自加的办法. 方案 版本号自加其实可以使用fs修改文件来 ...

  9. 单 log 实现 区间加减,查询区间 gcd

    主要是查询,要将 log 个区间拿出来依次求 gcd,当然如果是 O(1) gcd 的话可以直接求就是了.

  10. sql语句去掉前面的0(前导零,零前缀)

    sql还有个stuff的函数,很强悍. 一个列的格式是单引号后面跟着4位的数字,比如'0003,'0120,'4333,我要转换成3,120,4333这样的格式,就是去掉单引号和前导的0,用以下语句就 ...