微信公众号:「小林coding」

用简洁的方式,分享编程小知识。

背景

C++ 是很强大,有各种特性来提高代码的可重用性,有助于减少开发的代码量和工作量。

C++ 提高代码的可重用性主要有两方面:

  • 继承
  • 模板

继承的特性我已在前面篇章写过了,本篇主要是说明「模板」的特性。

使用「模板」的特性设计,实际上也就是「泛型」程序设计。

函数模板

01 变量交换函数模板

假设我们设计一个交换两个整型变量的值的函数,代码如下:

// 交换两个整型变量的值的Swap函数:
void Swap(int & x,int & y)
{
int tmp = x;
x = y;
y = tmp;
}

如果是浮点类型的变量的值交换,则替换 int 类型为 double 即可,代码如下:

// 交换两个double型变量的值的Swap函数:
void Swap(double & x,double & y)
{
double tmp = x;
x = y;
y = tmp;
}

那如果是其他变量类型的值交换,那不是每次都要重新写一次 Swap 函数?是不是很繁琐?且代码后面会越来越冗余。

能否只写一个 Swap 函数,就能交换各种类型的变量?

答案是肯定有的,就是用「函数模板」来解决,「函数模板」的形式:

template <class 类型参数1,class 类型参数2,...>
返回值类型 模板名 (形参表)
{
函数体
};

具体 Swap 「函数模板」代码如下:

template 就是模板定义的关键词,T 代表的是任意变量的类型。

template <class T>
void Swap(T & x,T & y)
{
T tmp = x;
x = y;
y = tmp;
}

那么定义好「函数模板」后,在编译的时候,编译器会根据传入 Swap 函数的参数变量类型,自动生成对应参数变量类型的 Swap 函数:

int main()
{
int n = 1,m = 2;
Swap(n,m); //编译器自动生成 void Swap(int & ,int & )函数 double f = 1.2,g = 2.3;
Swap(f,g); //编译器自动生成 void Swap(double & ,double & )函数 return 0;
}

上面的实例化函数模板的例子,是让编译器自己来判断传入的变量类型,那么我们也可以自己指定函数模板的变量类型,具体代码如下:

int main()
{
int n = 1,m = 2;
Swap<int>(n,m); // 指定模板函数的变量类型为int double f = 1.2,g = 2.3;
Swap<double>(f,g); // 指定模板函数的变量类型为double return 0;
}

02 查询数组最大值函数模板

在举一个例子,下面的 MaxElement 函数定义成了函数模板,这样不管是 int、double、char 等类型的数组,都可以使用该函数来查数组最大的值,代码如下:

// 求数组最大元素的MaxElement函数模板
template <class T>
T MaxElement(T a[], int size) // size是数组元素个数
{
T tmpMax = a[0];
for(int i = 1;i < size;++i)
{
if(tmpMax < a[i])
{
tmpMax = a[i];
}
}
return tmpMax;
}

03 多个类型参数模板函数

函数模板中,可以不止一个类型的参数:

template <class T1, class T2>
T2 MyFun(T1 arg1, T2 arg2)
{
cout<< arg1 << " "<< arg2<<endl;
return arg2;
}

T1 是传入的第一种任意变量类型,T2 是传入的第二种任意变量类型。


04 函数模板的重载

函数模板可以重载,只要它们的形参表或类型参数表不同即可。

// 模板函数 1
template<class T1, class T2>
void print(T1 arg1, T2 arg2)
{
cout<< arg1 << " "<< arg2<<endl;
} // 模板函数 2
template<class T>
void print(T arg1, T arg2)
{
cout<< arg1 << " "<< arg2<<endl;
} // 模板函数 3
template<class T,class T2>
void print(T arg1, T arg2)
{
cout<< arg1 << " "<< arg2<<endl;
}

上面都是 print(参数1, 参数2) 模板函数的重载,因为「形参表」或「类型参数表」名字不同。

05 函数模板和函数的次序

在有多个函数和函数模板名字相同的情况下,编译器如下规则处理一条函数调用语句:

  1. 先找参数完全匹配的普通函数(非由模板实例化而得的函数);
  2. 再找参数完全匹配的模板函数;
  3. 再找实参数经过自动类型转换后能够匹配的普通函数;
  4. 上面的都找不到,则报错。

代码例子如下:

// 模板函数 - 1个参数类型
template <class T>
T Max(T a, T b)
{
cout << "TemplateMax" <<endl; return 0;
} // 模板函数 - 2个参数类型
template <class T, class T2>
T Max(T a, T2 b)
{
cout << "TemplateMax2" <<endl; return 0;
} // 普通函数
double Max(double a, double b)
{
cout << "MyMax" << endl;
return 0;
} int main()
{
int i=4, j=5; // 输出MyMax - 匹配普通函数
Max( 1.2, 3.4 ); //输出TemplateMax - 匹配参数一样的模板函
Max( i, j ); //输出TemplateMax2 - 匹配参数类型不同的模板函数
Max( 1.2, 3 ); return 0;
}

匹配模板函数时,当模板函数只有一个参数类型时,传入了不同的参数类型,是不进行类型自动转换,具体例子如下:

// 模板函数 - 1个参数类型
template<class T>
T myFunction( T arg1, T arg2)
{
cout<<arg1<<" "<<arg2<<"\n";
return arg1;
} ... // OK :替换 T 为 int 类型
myFunction( 5, 7); // OK :替换 T 为 double 类型
myFunction(5.8, 8.4); // error :没有匹配到myFunction(int, double)函数
myFunction(5, 8.4);

类模板

01 类模板的定义

为了多快好省地定义出一批相似的类,可以定义「类模板」,然后由类模板生成不同的类

类模板的定义形式如下:

template <class 类型参数1,class 类型参数2,...> //类型参数表
class 类模板名
{
成员函数和成员变量
};

用类模板定义对象的写法:

类模板名<真实类型参数表> 对象名(构造函数实参表);

02 Pair类模板例子

接下来,用 Pair 类用类模板的方式的实现,Pair 是一对的意思,也就是实现一个键值对(key-value)的关系的类。

// 类模板
template <class T1, class T2>
class Pair
{
public:
Pair(T1 k, T2 v):m_key(k),m_value(v) {};
bool operator < (const Pair<T1,T2> & p) const;
private:
T1 m_key;
T2 m_value;
}; // 类模板里成员函数的写法
template <class T1, class T2>
bool Pair<T1,T2>::operator < (const Pair<T1,T2> &p) const
{
return m_value < p.m_value;
} int main()
{
Pair<string,int> Astudent("Jay",20);
Pair<string,int> Bstudent("Tom",21); cout << (Astudent < Bstudent) << endl; return 0;
}

输出结果:

1

需要注意的是,同一个类模板的两个模板类是不兼容的:

Pair<string,int> *p;
Pair<string,double> a;
p = & a; //错误!!

03 函数模板作为类模板成员

当函数模板作为类模板的成员函数时,是可以单独写成函数模板的形式,成员函数模板在使用的时候,编译器才会把函数模板根据传入的函数参数进行实例化,例子如下:

// 类模板
template <class T>
class A
{
public:
template<class T2>
void Func(T2 t) { cout << t; } // 成员函数模板
}; int main()
{
A<int> a;
a.Func('K'); //成员函数模板 Func被实例化
a.Func("hello"); //成员函数模板 Func再次被实例化 return 0;
}

04 类模板与非类型参数

类模板的“<类型参数表>”中可以出现非类型参数:

template <class T, int size>
class CArray
{
public:
void Print( )
{
for( int i = 0;i < size; ++i)
cout << array[i] << endl;
}
private:
T array[size];
}; CArray<double,40> a2;
CArray<int,50> a3; //a2和a3属于不同的类

类模板与派生

01 类模板从类模板派生

上图的代码例子如下:

// 基类 - 类模板
template <class T1,class T2>
class A
{
T1 v1; T2 v2;
}; // 派生类 - 类模板
template <class T1,class T2>
class B:public A<T2,T1>
{
T1 v3; T2 v4;
}; // 派生类 - 类模板
template <class T>
class C:public B<T,T>
{
T v5;
}; int main()
{
B<int,double> obj1;
C<int> obj2;
return 0;
}

02 类模板从模板类派生

上图的代码例子如下:

template <class T1,class T2>
class A
{
T1 v1; T2 v2;
}; template <class T>
class B:public A<int,double> // A<int,double> 模板类
{
T v;
}; int main()
{
//自动生成两个模板类 :A<int,double> 和 B<char>
B<char> obj1;
return 0;
}

03 类模板从普通类派生

上图的代码例子如下:

// 基类 - 普通类
class A
{
int v1;
}; // 派生类 - 类模板
template <class T>
class B:public A // 所有从B实例化得到的类 ,都以A为基类
{
T v;
}; int main()
{
B<char> obj1;
return 0;
}

04 普通类从模板类派生

上图的代码例子如下:

template <class T>
class A
{
T v1;
}; class B:public A<int>
{
double v;
}; int main()
{
B obj1;
return 0;
}

类模板与友元

01 函数、类、类的成员函数作为类模板的友元

代码例子如下:

// 普通函数
void Func1() { } // 普通类
class A { }; // 普通类
class B
{
public:
void Func() { } // 成员函数
}; // 类模板
template <class T>
class Tmp
{
friend void Func1(); // 友元函数
friend class A; // 友元类
friend void B::Func(); // 友元类的成员函数
}; // 任何从 Tmp 实例化来的类 ,都有以上三个友元

02 函数模板作为类模板的友元

// 类模板
template <class T1,class T2>
class Pair
{
private:
T1 key; //关键字
T2 value; //值
public:
Pair(T1 k,T2 v):key(k),value(v) { }; // 友元函数模板
template <class T3,class T4>
friend ostream & operator<< (ostream & o, const Pair<T3,T4> & p);
}; // 函数模板
template <class T3,class T4>
ostream & operator<< (ostream & o, const Pair<T3,T4> & p)
{
o << "(" << p.key << "," << p.value << ")" ;
return o;
} int main()
{
Pair<string,int> student("Tom",29);
Pair<int,double> obj(12,3.14); cout << student << " " << obj;
return 0;
}

输出结果:

(Tom,29) (12,3.14)

03 函数模板作为类的友元

// 普通类
class A
{
private:
int v;
public:
A(int n):v(n) { } template <class T>
friend void Print(const T & p); // 函数模板
}; // 函数模板
template <class T>
void Print(const T & p)
{
cout << p.v;
} int main()
{
A a(4);
Print(a);
return 0;
}

输出结果:

4

04 类模板作为类模板的友元

// 类模板
template <class T>
class B
{
private:
T v;
public:
B(T n):v(n) { } template <class T2>
friend class A; // 友元类模板
}; // 类模板
template <class T>
class A
{
public:
void Func( )
{
B<int> o(10); // 实例化B模板类
cout << o.v << endl;
}
}; int main()
{
A<double> a;
a.Func ();
return 0;
}

输出结果:

10

类模板与静态成员变量

类模板中可以定义静态成员,那么从该类模板实例化得到的所有类,都包含同样的静态成员。

template <class T>
class A
{
private:
static int count; // 静态成员
public:
A() { count ++; }
~A() { count -- ; };
A( A & ) { count ++ ; } static void PrintCount() { cout << count << endl; } // 静态函数
}; template<> int A<int>::count = 0; // 初始化
template<> int A<double>::count = 0; // 初始化 int main()
{
A<int> ia;
A<double> da; // da和ia不是相同模板类
ia.PrintCount();
da.PrintCount();
return 0;
}

输出:

1
1

上面的代码需要注意的点:

  • 类模板里的静态成员初始化的时候,最前面要加template<>
  • ia 和 da 对象是不同的模板类,因为类型参数是不一致,所以也就是不同的模板类。

C++ 模板常见特性(函数模板、类模板)的更多相关文章

  1. 3.2 STL中的函数对象类模板

    *: STL中有一些函数对象类模板,如下所示: 1)例如要求两个double类型的x 和y 的积,可以: multiplies<double>()(x,y); 该表达式的值就是x*y的值. ...

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

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

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

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

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

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

  5. 类模板语法知识体系梳理(包含大量常犯错误demo,尤其滥用友元函数的错误)

    demo 1 #include <iostream> #include <cstdio> using namespace std; //template <typenam ...

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

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

  7. C++函数模板&类模板

    函数模板 模板概念及语法 主要目的,简化代码,减少重复代码.基本语法格式:  template<class T> 或者 template<typename T> //末尾不加分 ...

  8. C++ 函数模板&类模板详解

    在 C++ 中,模板分为函数模板和类模板两种.函数模板是用于生成函数的,类模板则是用于生成类的. 函数模板&模板函数     类模板&模板类  必须区分概念 函数模板是模板,模板函数时 ...

  9. 对C++ templates类模板的几点补充(Traits类模板特化)

    前一篇文章<浅谈C++ templates 函数模板.类模板以及非类型模板参数>简单的介绍了什么是函数模板(这个最简单),类模板以及非类型模板参数.本文对类模板再做几点补充. 文章目录1. ...

随机推荐

  1. Project Euler Problem 16-Power digit sum

    直接python搞过.没啥好办法.看了下别人做的,多数也是大数乘法搞过. 如果用大数做的话,c++写的话,fft优化大数乘法,然后快速幂一下就好了.

  2. HDU1358 Period 题解 KMP算法

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1358 题目大意:给你一个长度为 \(n\) 的字符串 \(s\) ,那么它有 \(n\) 个前缀. 对 ...

  3. pytorch入坑一 | Tensor及其基本操作

    由于之前的草稿都没了,现在只有重写…. 我好痛苦 本章只是对pytorch的常规操作进行一个总结,大家看过有脑子里有印象就好,知道有这么个东西,需要的时候可以再去详细的看,另外也还是需要在实战中多运用 ...

  4. HDU 2674

    0 <= N<=10^9 看到这个数据范围知道常规方法肯定做不出来. 不过一想想既然是mod2009,是不是只要其中含有一个2009,那么其结果一定是0了呢 说了这里思路,就是看什么时候出 ...

  5. [转]1.2 java web的发展历史

    前言 了解java web的发展历史和相关技术的演进历程,非常有助于加深对java web技术的理解和认识. 阅读目录 1.Servlet的出现 2.Jsp的出现 3.倡导了MVC思想的Servlet ...

  6. vue-learning:26 - component - 组件三大API之一:prop

    组件三大API之一: prop prop的大小写 prop接收类型 字符串数组形式 对象形式: type / required / default / validator prop传递类型: 静态传递 ...

  7. navicat for mysql连接本地数据库

    navicat for mysql连接本地数据库 打算使用navicat连接本地数据库,连接的时候,一直连接不上.然后猜想是不是本地数据库没有设置好.输入mysql,出错内容:access denie ...

  8. Delta Lake基础操作和原理

    目录 Delta Lake 特性 maven依赖 使用aws s3文件系统快速启动 基础表操作 merge操作 delta lake更改现有数据的具体过程 delta表schema 事务日志 delt ...

  9. gif 格式

    现在使用gif的场景有很多,很多老师喜欢在课件添加 gif 图片 在开始讲gif之前,先告诉大家 gif 的格式. 请看图片,gif 图分为图片文件头(File Header),gif信息(GIF D ...

  10. Iptables-linux服务器做路由转发

    https://blog.csdn.net/liang_operations/article/details/80747510 实现内部服务器C可以经过服务器B进行上网. 3.1服务器双网卡,一块配置 ...