模板和泛型编程C++
Posted atohome
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了模板和泛型编程C++相关的知识,希望对你有一定的参考价值。
第16章 模板和泛型编程
已经学习了标准容器,我们就会产生好奇,为什么它可以存储任意类型呢?向自定义的函数的形参与实参都是有着严格的类型匹配,面向对象编程和泛型编程都能处理在编写程序时不知道类型的情况,不同之处在于,OOP能处理类型在程序运行之前都未知的情况,而泛型编程中,在编译时就能获知类型了,在OOP总我们知道利用虚函数与动态绑定机制可以做到
为什么使用泛型编程
有时某种算法的代码实现是相同的,只有变量类型不同,如下面的情况
int compare(const string& s1,const string& s2)
if(s1<s2)return -1;
if(s2<s1)return 1;
return 0;
int compare(const double& d1,const double& d2)
if(s1<s2)return -1;
if(s2<s1)return 1;
return 0;
泛型编程就是为解决这种问题而生的
函数模板
函数模板就是一个公式,可以来生成针对特定类型的函数版本
编译器生成的版本通常被称为模板的实例
//example1.cpp
//模板定义以template关键词开始,后面跟模板参数列表,是一个逗号隔开一个或多个模板参数的列表
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(int argc, char **argv)
//编译器背后生成 int compare(const int& v1,const int& v2)
cout << compare(10, 13) << endl; //-1
//生成 int compare(const string& v1,const string& v2)
cout << compare(string"hello", string"asd") << endl; // 1
return 0;
typename与class
泛型参数的类型确定是编译器时检测被调用时的实参的类型确定的
template<class ...>
与template<typename ...>
两种方式都是可以的,但是现代C++更推荐typename即后者
template<class T,typename U>
T func(T*ptr,U*p)
T& tmp=*p;
//...
return tmp;
但func被调用时,编译器根据T的类型,将模板中的T类型替换为实参类型
非类型模板参数
在形参中有些值类型是我们已经确定的,但是不能确定是多少或具体内容,这是可以使用非类型模板参数
编译器会使用字面常量的大小代替N和M,实例化模板
//example2.cpp
template <unsigned N, unsigned M>
int compare(const char (&p1)[N], const char (&p2)[M])
cout << N << " " << M << endl; // 6 4
return strcmp(p1, p2);
int main(int argc, char **argv)
cout << compare("hello", "abc") << endl; // 1
//在此实际传的实参为 char[6] char[4]
return 0;
上面编译器会实例出int compare(const char (&p1)[6], const char (&p2)[4])
重点:非类型模板参数的模板实参必须是常量表达式
inline和constexpr的函数模板
inline与constexpr普通函数关键词的位置没什么区别
- inline函数模板
template <typename T>
inline int compare(const T&a,const T&b)
return 1;
- constexpr函数模板
template <typename T>
constexpr int compare(const T&a,const T&b)
return 1;
constexpr int num=compare(19,19);
cout<<num<<endl;//1
编写类型无关的代码
标准函数对象的内容在第14章 操作重载与类型转换
在模板编程中,我们力求编写类型无关的代码,尽可能减少对实参的依赖,总之模板程序应该尽量减少对实参类型的要求
在上面的代码是有两个特殊的处理
- 模板中的函数参数是const的引用(保证可处理不能拷贝的类型)
- 函数体中的判断条件仅使用
<
比较(使得类型仅支持<比较即可)
还有更优雅的写法,使用标准函数对象
//example3.cpp
template <typename T>
int compare(const T &v1, const T &v2)
if (less<T>()(v1, v2))
return -1;
if (less<T>()(v1, v2))
return 1;
return 0;
int main(int argc, char **argv)
cout << compare(string"121", string"dsc") << endl; //-1
return 0;
函数模板通常放在头文件
我们通常将类的定义与函数声明放在都文件,因为使用他们时,编译器只需掌握其形式即可即返回类型,函数形参类型等,但是函数模板不同,为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义,模板的头文件通常包括声明与定义
编译错误过程
对于函数模板的错误,通常编译器会在三个阶段报告错误
1、编译模板本身,例如定义模板本身的语法等
2、遇到模板被使用时,通常检查实参数目、检查参数类型是否匹配
3、编译用函数模板产生的函数代码,与编译实际的函数一样,依赖于编译器如何管理实例化,这类错误可能在链接时才报告
如下面的情况
Person a,b;
compare(a,b);
compare中使用了<
,但是Person类并没有<
操作,那么这样的错误在第三阶段才会报告
类模板
经过上面的学习,函数模板是用来生成函数的蓝图的。那么类模板有是怎样的呢,类模板(class template)是用来生成类的蓝图的,不像函数模板一样可以推算类型,类模板使用时在名字后使用尖括号提供额外的类型信息,正如我们使用过的list、vector等一样,它们都是类模板
定义类模板
下面是一个定义类模板的简单例子,无须解释即可学会
//example4.cpp
template <typename T>
class Data
public:
T info;
Data(const T &t) : info(t)
;
int main(int argc, char **argv)
Data<int> data1(19);
cout << data1.info << endl; // 19
Data<string> data2("hello");
cout << data2.info << endl; // hello
return 0;
实例化类模板
在使用一个类模板是,必须提供额外的信息,在example4.cpp中提供的int就是显式模板实参,它们被绑定到模板参数
每一个类模板的每个实例都形成一个独立的类,Data<int>
与其他的Data类型直接没有关联,也不会对其他Data类型的成员有特殊访问权限
模板类型做实参
类模板的类型实参可以为普通类型或者自定义类型,同时也可以为模板类型
例如用vector<>来做实参类型
//example5.cpp
template <typename T>
class Data
public:
T info;
Data(const T &t) : info(t)
;
int main(int argc, char **argv)
Data<vector<int>> data(1, 2, 3, 4);
for (const int &item : data.info)
cout << item << endl; // 1 2 3 4
return 0;
类模板的成员函数
在类外定义的类模板的成员函数必须添加template在函数定义前,在类内定义在与普通类一样其被定义为隐式的内联函数
//example6.cpp
template <typename T>
class Data
public:
T info;
Data(const T &t) : info(t)
void print()
cout << "print" << endl;
T sayHello(const T &t); //类内声明
;
//类外定义
template <typename T>
T Data<T>::sayHello(const T &t)
info = t;
cout << "hello" << endl;
return this->info;
int main(int argc, char **argv)
Data<int> data(19);
data.print(); // print
int res = data.sayHello(18); // hello
cout << res << endl; // 18
return 0;
模板参数视为已知类型
在类模板中像在函数模板中一样,将模板参数视为已知就好,以至于可以进行复杂的情况使用
在类模板中使用其他类模板时,可以使用自己的模板类型参数作为参数传给其他类模板,例如下面的vector<T>、initializer_list<T>
等。
//example7.cpp
template <typename T>
class Data
public:
shared_ptr<vector<T>> vec;
Data(const initializer_list<T> &list) : vec(make_shared<vector<T>>(list))
vector<T> &get()
return *vec;
;
int main(int argc, char **argv)
Data<int> data(1, 2, 3, 4, 5);
vector<int> &vec = data.get();
for (auto item : vec)
cout << item << endl; // 1 2 3 4 5
return 0;
默认情况下,对于一个实例化了的类模板,其成员只有在使用时才被实例化
类模板内使用自身
类模板类在类外定义的函数成员中,使用自己时的类型时可以不提供模板参数,但是如果作为方法参数或者反回值类型,则需要写尖括号,在函数体内不用写尖括号
在类内定义的成员中,则可以省略写尖括号
最佳实践:都写上尖括号就好了,也会使得看代码的人更容易理解
//example8.cpp
template <typename T>
class Data
public:
T info;
Data(const T &t) : info(t)
Data print(const T &t) //类内定义成员方法
Data data(t);
return data;
Data<T> sayHello(const T &t); //类内声明
;
//类外定义
template <typename T>
Data<T> Data<T>::sayHello(const T &t)
Data d(t); //与Data<T> t(t)等价
info = t;
return d;
int main(int argc, char **argv)
Data<int> data(19);
Data<int> data1 = data.print(20);
Data<int> data2 = data.print(18);
cout << data1.info << endl; // 20
cout << data2.sayHello(18).info << endl; // 18
return 0;
类模板和友元
当类模板有一个非模板友元,则这个类模板的所有实例类对此友元友好
//example9.cpp
template <typename T>
class Data
private:
T t;
public:
Data(const T &t) : t(t)
friend void print();
;
void print()
Data<int> data(19);
Data<string> data1("oop");
cout << data.t << " " << data1.t << endl; // 19 oop
int main(int argc, char **argv)
print();
return 0;
一对一友好关系
类模板与另一个(类或函数)模板间友好关系的常见形式为建立对应实例及其友元间的友好关系
//example10.cpp
#include <iostream>
using namespace std;
//模板类与函数模板声明
template <typename>
class A;
template <typename>
class B;
template <typename T>
void print(T t);
template <typename T>
class A
public:
void test()
B<T> b;
b.b = 888;
cout << b.b << endl;
// B<string> b1;//错误与B<string>不是友元关系
// b1.b = "oop";
;
template <typename T>
class B
public:
T b;
friend class A<T>; //将A<T>称为B<T>的友元
friend void print<T>(T t);
;
template <typename T>
void print(T t)
B<T> b;
cout << b.b << endl;
B<string> b1; //为什么是B<stirng>的友元
//因为在此使用B<string>时,B内生成了friend void print(string t);
b1.b = "oop";
cout << b1.b << endl;
int main(int argc, char **argv)
A<int> a;
a.test(); // 888
print(19); // 888 oop
return 0;
通过和特定的模板友好关系
让另一个类模板的所有实例都都称为友元
下面的代码比较长,总之最重要的就是形如一下两种友元声明
friend class B<A>;
template<typename T> friend class B;
的区别
//example11.cpp
template <typename T>
class B;
template <typename X>
class C;
class A
friend class B<A>; //声明 B<A>为A的友元
template <typename T>
friend class B; // B模板的所有实例都是A的友元
A(int a) : n(a)
private:
int n;
;
template <typename T>
class B
friend class A; //声明A为B的友元
template <typename X>
friend class C;
// 所有实例之间都是友元关系 B<int> 与 C<string>之间也是友元
// friend class B<T>;
//与上一句截然不同 此作用只是如B<int>与C<int>之间为友元
private:
T t;
public:
B(T t) : t(t)
void test()
A a(19);
cout << a.n << endl; // B<T>为A的友元
;
template <typename X>
class C
public:
void test()
B<int> b1(19); //所有B<T>实例的友元都包括C<X>
B<string> b2("oop");
cout << b1.t << " " << b2.t << endl;
;
int main(int argc, char **argv)
C<int> c;
c.test(); // 19 oop
B<int> b1(0);
B<string> b2("oop");
b1.test(); // 19
b2.test(); // 19
return 0;
令模板自己的类型参数成为友元
在新标准中,可以将模板类型参数声明为友元,当然只有其模板实参为复合自定义类型时才显得有意义
//example12.cpp
template <typename Type>
class A;
class Data;
template <typename Type>
class A
friend Type; //重点
public:
A(int n) : n(n以上是关于模板和泛型编程C++的主要内容,如果未能解决你的问题,请参考以下文章
C++ Primer 5th笔记(chap 16 模板和泛型编程)模板特例化
C++ Primer 5th笔记(chap 16 模板和泛型编程)模板实参推断
C++ Primer 5th笔记(chap 16 模板和泛型编程)函数指针和实参推断
C++ Primer 5th笔记(chap 16 模板和泛型编程)模板实参推断和引用