这里写目录标题

  • 一、泛型编程与模板
  • 二、函数模板
    • 1.概念
    • 2.原理
    • 3.函数模板的实例化
    • 3. 模板实现复数类的加法
    • 4.模板参数的匹配原则
  • 三、类模板
    • 1.定义格式
      • 用类模板实现顺序表
    • 2.非类型模板参数
  • 四、模板的特化
    • 1.函数模板的特化
    • 2.类模板的特化
      • 全特化
      • 偏特化
  • 五、分离编译
    • 1.模板不支持分离编译
    • 2.解决方案:.hpp文件
    • 3.模板总结

一、泛型编程与模板

泛型编程:编写与类型无关的代码,是代码复用的一种手段。模板是泛型编程的基础

二、函数模板

1.概念

  • 函数模板是一个通用的函数,该模板与类型无关,其函数类型和形参类型不确定,在使用时被参数化,根据实参类型产生函数的特定类型版本。
  • 函数模板的格式:
    template<typename T1, typename T2,…,typename Tn>
    返回值类型 函数名(参数列表){}

利用函数模板写一个通用的交换函数:

template<typename T>
void Swap(T& left, T& right)
{T temp = left;left = right;right = temp;
}

函数模板适用于函数体相同、函数的参数个数相同类型不同的情况。

2.原理

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。

3.函数模板的实例化

  • 隐式实例化
    编译器根据实参推演模板参数的实际类型

  • 显式实例化:在函数名后的<>中指定模板参数的实际类型

    显式实例化时编译器不进行类型推演,如果类型不匹配,编译器会进行隐式类型转换,无法转换成功则报错。

3. 模板实现复数类的加法

上面的加法函数模板也可以完成自定义类型对象的加法,如定义一个复数类Complex,只需要在类内部对’+'进行重载即可


调用结果:

完整代码:

#include<iostream>
using namespace std;template<class T>
T Add(const T& left, const T& right)
{cout << typeid(T).name() << endl;return left + right;
}class Complex
{friend ostream& operator<<(ostream& _cout, const Complex & x);
public:Complex(double real = 0.0, double image = 0.0): _real(real), _image(image){}Complex operator+(const Complex& c)const{return Complex(_real + c._real, _image + c._image);}private:double _real;double _image;
};ostream& operator<<(ostream& _cout, const Complex & x)
{_cout << x._real << "." << x._image;return _cout;
}int main()
{Complex c1(1.0, 2.0);Complex c2(3.0, 4.0);Complex c3;c3 = Add(c1, c2);cout << c3 << endl;system("pause");return 0;
}

4.模板参数的匹配原则

  • 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。

  • 对于非模板函数和同名函数模板,如果其他条件都相同,在调用时会优先调用非模板函数而不会从改模板产生出一个实例。如果模板可以产生一个更好匹配的函数,那么选择模板。

  • 模板不允许自动类型转化

三、类模板

1.定义格式

template<tpyemame T1, typename T2, ..., typename Tn>
class Name
{//
};

用类模板实现顺序表

#include<iostream>
#include<assert.h>
#include<string.h>
using namespace std;template <typename T>
class Seqlist
{public:Seqlist(size_t InitCapicity = 3):_array(new T[InitCapicity]), _size(0), _capacity(InitCapicity){}~Seqlist(){if (_array){delete[]_array;_array = nullptr;_capacity = 0;_size = 0;}}//尾插void PushBack(const T& data){CheckCapacity();_array[_size] = data;_size++;}//尾删void PopBack(){if (Empty()){return;}_size--;}//头插void PushFront(const T& data){CheckCapacity();for (int i = (int)_size; i > 0; i--){_array[i] = _array[i - 1];}_array[0] = data;_size++;}size_t Capacity(){return _capacity;}size_t Size(){return _size;}//重载[]T operator[](int position){//assert()内的条件满足则不会触发断言assert(position < _size);return _array[position];}T& Back();T& Front();private:void CheckCapacity(){if (_size == _capacity){//2倍扩容_capacity *= 2;T* temp = new T[_capacity];//将原来顺序表的内容拷贝到新的空间for (int i = 0; i < _size; i++){temp[i] = _array[i];}//清空旧空间内容delete[] _array;_array = temp;}}//判空bool Empty(){return 0 == _size;}T* _array;size_t _size;size_t _capacity;
};template<typename T>
T&  Seqlist<T>:: Back()
{return _array[_size - 1];
}template<typename T>
T& Seqlist<T>::Front()
{return _array[0];
}class Date
{//类内声明为友元函数friend ostream& operator<<(ostream& _cout, const Date& d);
public:Date(int year=2022, int month=1, int day=1):_year(year), _month(month), _day(day){}private:int _year;int _month;int _day;
};//类外定义
ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}int main()
{Seqlist<int> s1;s1.PushBack(1);s1.PushBack(2);s1.PushBack(3);s1.PushBack(4);s1.PushBack(5);s1.PushFront(0);cout << s1.Back() << endl;cout << s1.Capacity() << endl;cout << s1.Front() << endl;cout << s1.Size() << endl;cout << s1[3] << endl;Seqlist<double> s2;s2.PushBack(1.8);s2.PushBack(2.2);s2.PushBack(3.5);s2.PushBack(4.9);s2.PushBack(5.2);s2.PushFront(6.6);cout << s2.Back() << endl;cout << s2.Capacity() << endl;cout << s2.Front() << endl;cout << s2.Size() << endl;cout << s2[3] << endl;//将Date类对象保存在顺序表中Seqlist<Date> s3;Date d1(2022, 8, 4);Date d2(2022, 8, 5);Date d3(2022, 8, 6);s3.PushBack(d1);s3.PushBack(d2);s3.PushBack(d3);cout << s3.Back() << endl;cout << s3.Capacity() << endl;cout << s3.Front() << endl;cout << s3.Size() << endl;cout << s3[2] << endl;system("pause");return 0;
}

将顺序表写成类模板后,可以自定义要存放在顺序表中的数据类型,只需要定义对应数据类型的对象即可,这里分别定义了int、double和自定义类型Date类的数据。
要注意的是,这里Seqlist不是具体的类,而是类模板名,是编译器根据被实例化的类型生成具体类的模具。Seqlist<int>,Seqlist<double>,Seqlist<Date>等才是一个类名。

2.非类型模板参数

  • 这里N在函数模板中是一个常量,不可修改。可以给定默认值,与缺省参数作用类似,作默认值。
  • 这里N只能传整型家族的数据,如:int、char等
  • N的值必须在编译阶段就确定

四、模板的特化

1.函数模板的特化

特化:特殊化处理

template<typename T>
const T Max(const T& left, const T& right)
{if (left > right){return left;}return right;
}

对于上面的函数模板,int 类型和double类型都可以比较大小,但是不能能处理字符串

函数模板的特化步骤:

  1. 必须要先有一个基础的函数模板
  2. 关键字template后面接―对空的尖括号<>
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  4. 函数形参表:必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪错误

因为函数模板特化较为复杂且不实用,函数模板一般不需要进行特化 ,如果模板哪种类型出错,直接将该类型对应的普通函数写出来。对于上面字符串类型不能直接比较的情况,直接写出字符串类型比较的普通函数即可。
比较char*类型的普通函数:

char* Max(char* left, char* right)
{if (strcmp(left, right) > 0)return left;return right;
}

2.类模板的特化

全特化

所有的参数都具体化,类型化的类模板
模板类

template<typename T1,typename T2>
class Date
{public:Date(){cout << "Date(T1,T2)" << endl;}
private:T1 _d1;T2 _d2;
};

全特化的模板类,专门用于处理参数为int,char类型

template<>
class Date<int,char>
{public:Date(){cout << "Date<int,char>" << endl;}
private:int _d1;int _d2;
};

定义Date类对象时,

偏特化

  • 第一种:部分特化
//类模板的第二个参数做了特殊化处理
template<typename T1>
class Date<T1,int>
{public:Date(){cout << "Date(T1,int)" << endl;}
private:T1 _d1;int _d2;
};

对于这个偏特化的类模板,定义对象时,只要第二个参数是int类型,就会调用特化处理的模板

  • 第二种:参数更严格的限制。对模板参数特性进行特化,包括将模板参数特化为指针、引用或是另外一个模板类。
//两个参数偏特化为指针类型
class Date<T1*, T2*>
{public:Date(){cout << "Date(T1*, T2*)" << endl;}private:T1* _d1;T2* _d2;
};

模板特化的应用:类型萃取
参考文章:
C++之类型萃取
类型萃取

五、分离编译

概念:
每个项目由多个源文件共同实现,而每个源文件单独编译成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译。
注意:头文件不参与编译,在预处理阶段,头文件已经展开,即:将头文件中的内容拷贝一份放到源文件。

1.模板不支持分离编译


如上图所示,在template.cpp源文件中定义了Sub函数,并在template.h头文件中进行了声明。
但是在编译过程中,编译器是对各个源文件进行单独编译的,template.cpp源文件进行编译的过程中,没有检测到Sub函数模板的实例化,所以不会生成对应的代码,在main.cpp源文件中进行调用,链接阶段便会出错。如图:

理解两个概念:

  • 导出符号表:编译完成后该源文件中地址(函数定义的位置)已经确定的函数
  • 未解决符号表:源文件中地址还没有确定的函数

这里main.cpp源文件编译完成后,没有找到Sub函数的定义,但是由于头文件中进行了声明,在预处理阶段头文件中的声明会拷贝到源文件中,所以并不会立即报错,而是将Sub函数放在未解决符号表中,链接阶段,在template.cpp文件的导出符号表中找Sub函数的入口地址,而如果Sub函数没有生成则会报错。

2.解决方案:.hpp文件

将模板定义在 .hpp 文件中

调用时只需要包含模板定义所在的.hpp文件即可

3.模板总结

  • 【优点】
    1.模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
    2.增强了代码的灵活性
  • 【缺陷】
    1.模板会导致代码膨胀问题,也会导致编译时间变长,因为类型推演和生成代码都需要耗费时间
    2.出现模板编译错误时,错误信息非常凌乱,不易定位错误

C++模板——模板特化、分离编译相关推荐

  1. c++模板函数声明定义分离编译错误详解

    今天看到accelerated c++上有个简单的vector容器的实现Vec,就再vs2008上编译了下: /  Vec.h #ifndef GUARD_VEC_H #define GUARD_VE ...

  2. C++ 泛型编程(二):非类型模板参数,模板特化,模板的分离编译

    目录 非类型模板参数 函数模板的特化 类模板的特化 全特化 偏特化 部分参数特化 参数修饰特化 模板分离编译 问题分析 解决方法 非类型模板参数 模板的参数分为两种,一种是非类型参数,一种是类型参数. ...

  3. 如何解决类模板的分离编译问题?

    一模板: 模板不是数据类型,只能算是一种行为集合的表示.编译器在使用模板时,通过更换模板参数来创建数据类型.这个过程就是模板实例化(Instantiation), 从模板类创建得到的类型称之为特例(s ...

  4. C++ 函数模板与分离编译模式

    代码编译运行环境:VS2017+Debug+Win32 1.分离编译模式 一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件连接起来形成单一的可执行文件的过程 ...

  5. C++模板(函数模板,类模板)的基本使用与非类型模板参数与模板的特化

    C++模板 模板初阶 泛型编程 函数模板 函数模板概念 函数模板格式 函数模板的原理 函数模板的实例化 隐式实例化 显式实例化:在函数名后的<>中指定模板参数的实际类型 模板参数的匹配原则 ...

  6. C++模板之特化与偏特化详解

    2019独角兽企业重金招聘Python工程师标准>>> C++函数模板与类模板实例解析_C 语言_脚本之家 http://www.jb51.net/article/53746.htm ...

  7. C++ 模板偏特化-来自STL的思考

    之前学习STL时接触过一段时间的模板,模板是C++泛型编程编程的基础 STL从头到尾都是模板泛型编程,我觉得用的最巧妙的就是在traits萃取技巧时用到的模板偏特化 先简要回顾一下模板吧,模板主要分为 ...

  8. 第十天2017/04/21(2、泛型编程:模板 / 全特化、偏特化)

    1.什么是模板? template<class T1,class T2,.....> 类属----类型参数化,又称参数模板使得程序可以从逻辑功能上抽象,把被处理的对象(数据)的类型作为参数 ...

  9. 用汇编的眼光看C++(之缺省模板、特化模板)

    [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 缺省函数是C++的一个基本特色.缺省函数定义比较简单,也就是说,对于函数的某一个输入参数或者几 ...

最新文章

  1. Eclipse 调试器(引用IT168)
  2. 手把手教你走进Hyperledger Fabric
  3. 19_Android中图片处理原理篇,关于人脸识别网站,图片加载到内存,图片缩放,图片翻转倒置,网上撕衣服游戏案例编写
  4. oracle重命名日志成员出错,Oracle日志文件
  5. android夜间模式揭露动画,Android Material Design系列之夜间模式
  6. 循环序列模型 —— 1.11 双向神经网络
  7. Think in Java第四版 读书笔记6第12章 异常处理
  8. Transformers Assemble(PART II)
  9. python基础教程pdf-Python基础教程(第3版) PDF高清完整版免费下载|百度云盘
  10. LINUX下用C判断一个进程是否活着
  11. 阿里云播放器的官方文档
  12. java final修饰的数组_Java基于final修饰数据过程解析
  13. HP 816 817墨盒计数器清零方法
  14. Windows10必装的宝藏便签软件不用起来太可惜了
  15. pythonobject转int_python – Pandas:将dtype’object’转换为int
  16. win10系统显示打印机未连接到服务器,win10系统连接打印机提示“打印处理器不存在”如何解决...
  17. 通过 ANE(Adobe Native Extension) 启动Andriod服务 推送消息(五)
  18. php 获取qq头像,免费的API接口推荐(获取QQ昵称、头像、QQ秀等等)
  19. 03:成绩排序 个人博客:doubleq.win
  20. 记录一次最坑的微信会员卡 跳转型开发时的bug errcode72011

热门文章

  1. 微信小程序学习之路(一)
  2. 今晚8点:手把手教你使用 ART-Pi 入门 TOUCHGFX
  3. 华为又收天才少女,进华为的标准是什么?
  4. Centos8 NFS服务器搭建
  5. 香农的“创意思维在编程的应用
  6. 关于拉格朗日坐标系求解要转为初始构型的讨论(1),为何欧拉坐标系不行。
  7. 巧用flashback database实现灵活的数据切换
  8. 架构训练营作业4-千万级学生管理系统的考试试卷存储方案
  9. Locust系列-Locust入门
  10. 面向对象设计原则之三--依赖倒置(转)原则