深入理解c++中的函数模板

非类型模板参数

模板参数分类类型形参非类型形参
类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。

namespace bite
{template<class T, size_t N>class array{public:void push_back(constT& data){//N=10;_array[_size++] = data;}T& operator[](size_t){assert(index < _size)return _array[index];}bool empty()const{return 0 == _size;}size_t size()const{return _size;}private:T _array[N];size_t _size;};
}

注意事项

  1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
  2. 非类型的模板参数必须在编译期就能确认结果。

模板的特化

模板特化的概念

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,比如

class Date
{
public:Date(int year, int month, int day):_year(year), _month(month), _day(day){}bool operator>(const Date&d)const{return _day > d._day;}friend ostream& operator<<(ostream& _cout, const Date& d){_cout << d._year << "/" << d._month << "/" << d._day << endl;return _cout;}
private:int _year;int _month;int _day;
};
template<class T>
T& Max(T& left, T& right)
{return left > right ? left : right;
}char *p1 = "world";char *p2 = "hello";cout << Max(p1, p2) << endl;cout << Max(p2, p1) << endl;

这个代码他就无法比较字符串类型的变量的大小
此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式

函数模板特化

如果不需要通过形参改变外部实参加上const
例如

template<class T>
const T& Max(const T& left, const T& right)
{return left > right ? left : right;
}
//函数模板的特化
template<>
char *& Max<char*>(char*& left, char*& right)
{//>0大于 =0等于 <0小于if (strcmp(left, right) > 0){return left;}return right;
}

注意事项

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

一般函数模板没必要特化,直接把相应类型的函数给出就行

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

类模板的特化

全特化

//全特化-----对所有类型参数进行特化
template<class T1, class T2>
class Data
{
public:Data() { cout << "Data<T1, T2>" << endl; }
private:T1 _d1;T2 _d2;
};template<>
class Data<int, int>
{
public:Data() { cout << "Data<int, int>" << endl; }
private:int _d1;int _d2;
};int main()
{Data<int, double>d1;Data<int, int>d2;return 0;
}

偏特化

部分特化
//偏特化,将模板参数列表中的参数部分参数类型化
template<class T1>
class Data<T1,int>
{
public:Data() { cout << "Data<T1, int>" << endl; }
private:T1 _d1;int _d2;
};int main()
{Data<int, double>d1;Data<int, int>d2;Data<double, int>d3;system("pause");return 0;
}
参数更进一步的限制
//偏特化:让模板参数列表中的类型限制更加的严格
template<class T1,class T2>
class Data<T1*, T2*>
{
public:Data() { cout << "Data<T1*, T2*>" << endl; }
private:T1* _d1;T2* _d2;
};int main()
{Data<int*, int>d1;Data<int, int*>d2;Data<int*, int*>d3;//特化Data<int*, double*>d4;//特化system("pause");return 0;
}

模板特化的作用之类型萃取

编写一个通用类型的拷贝函数

template<class T>
void Copy(T* dst, T* src, size_t size)
{memcpy(dst, src, sizeof(T)*size);
}

上述代码虽然对于任意类型的空间都可以进行拷贝,但是如果拷贝自定义类型对象就可能会出错,因为自定义类型对象有可能会涉及到深拷贝(比如string),而memcpy属于浅拷贝。如果对象中涉及到资源管理,就只能用赋值。

class String
{
public:String(const char* str = ""){if (str == nullptr)str = "";this->_str = new char[strlen(str) + 1];strcpy(this->_str, str);}String(const String& s):_str ( new char[strlen(s._str)+1]){strcpy(this->_str, s._str);}String& operator=(const String &s){if (this != &s){char *str = new char[strlen(s._str) + 1];strcpy(str, s._str);delete[]_str;		_str = s._str;}}~String(){delete[]_str;}private:char* _str;
};//写一个通用的拷贝函数,要求:效率尽量高
template<class T>
void Copy(T* dst, T* src, size_t size)
{memcpy(dst, src, sizeof(T)*size);
}void TestCopy()
{int array1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };int array2[10];Copy(array2, array1, 10);String s1[3] = { "111", "222", "333" };String s2[3];Copy(s2, s1, 3);
}

如果是自定义类型用memcpy,那么会存在

  1. 浅拷贝----导致代码崩溃
  2. 内存泄露—S2数组中每个String类型对象原来的空间丢失了

增加一个拷贝函数处理浅拷贝

template<class T>
void Copy2(T* dst, T*src, size_t size)
{for (size_t i = 0; i < size; ++i){dst[i] = src[i];}
}
  1. 优点:一定不会出错
  2. 缺陷:效率比较低,让用户做选择,还需要判断调用哪个函数

让函数自动去识别所拷贝类型是内置类型或者自定义类型

bool IsPODType(const char* strType)
{//此处将所有的内置类型枚举出来const char * strTypes[] = { "char", "short", "int", "long", "long long", "float", "double" };for (auto e : strTypes){if (strcmp(strType, e) == 0)return true;}return false;
}template<class T>
void Copy(T* dst, T*src, size_t size)
{//通过typeid可以将T的实际类型按照字符串的方式返回if (IsPODType(typeid(T).name()){//T的类型:内置类型memcpy(dst, src, sizeof(T)*size);}else{//T的类型:自定义类型----原因:自定义类型种可能会存在浅拷贝for (size_t i = 0; i < size; ++i){dst[i] = src[i];}}
}

在编译期间就确定类型—类型萃取

如果把一个成员函数的声明和定义放在类里面,编译器可能会把这个方法当成内联函数来处理

class String
{
public:String(const char* str = ""){if (str == nullptr)str = "";this->_str = new char[strlen(str) + 1];strcpy(this->_str, str);}String(const String& s):_str(new char[strlen(s._str) + 1]){strcpy(this->_str, s._str);}String& operator=(const String& s){if (this != &s){char* str = new char[strlen(s._str) + 1];strcpy(str, s._str);delete[] _str;_str = str;}return *this;}~String(){delete[]_str;}private:char* _str;
};//确认T到底是否是内置类型
//是
//不是
//对应内置类型
struct TrueType
{static bool Get(){return true;}
};//对应自定义类型
struct FalseType
{static bool Get(){return true;}
};template<class T>
struct TypeTraits
{typedef FalseType PODTYPE;};template<>
struct TypeTraits<char>
{typedef TrueType PODTYPE;
};template<>
struct TypeTraits<short>
{typedef TrueType PODTYPE;
};
template<>
struct TypeTraits<int>
{typedef TrueType PODTYPE;
};//........还有很多内置类型template<class T>
void Copy(T* dst, T* src, size_t size)
{// 通过typeid可以将T的实际类型按照字符串的方式返回if (TypeTraits<T>::PODTYPE::Get()){// T的类型:内置类型memcpy(dst, src, sizeof(T)*size);}else{// T的类型:自定义类型---原因:自定义类型中可能会存在浅拷贝for (size_t i = 0; i < size; ++i)dst[i] = src[i];}
}

STL中的类型萃取

// 代表内置类型
struct __true_type {};
// 代表自定义类型
struct __false_type {};template <class type>
struct __type_traits
{typedef __false_type is_POD_type;
};// 对所有内置类型进行特化
template<>
struct __type_traits<char>
{typedef __true_type is_POD_type;
};
template<>
struct __type_traits<signed char>
{typedef __true_type is_POD_type;
};
template<>
struct __type_traits<unsigned char>
{typedef __true_type is_POD_type;
};
template<>
struct __type_traits<int>
{typedef __true_type is_POD_type;
};
template<>
struct __type_traits<float>
{typedef __true_type is_POD_type;
};
template<>
struct __type_traits<double>
{typedef __true_type is_POD_type;
};
// 注意:在重载内置类型时,所有的内置类型都必须重载出来,包括有符号和无符号,比如:对于int类型,必
须特化三个,int -- signed int -- unsigned int
// 在需要区分内置类型与自定义类型的位置,标准库通常都是通过__true_type与__false_type给出一对重载// 函数,然后用一个通用函数对其进行封装
// 注意:第三个参数可以不提供名字,该参数最主要的作用就是让两个_copy函数形成重载
template<class T>
void _copy(T* dst, T* src, size_t n, __true_type)
{memcpy(dst, src, n*sizeof(T));
}
template<class T>
void _copy(T* dst, T* src, size_t n, __false_type)
{for (size_t i = 0; i < n; ++i)dst[i] = src[i];
}
template<class T>
void Copy(T* dst, T* src, size_t n)
{_copy(dst, src, n, __type_traits<T>::is_POD_type());
}

分离编译

预处理----->编译---->汇编----->链接
在这里插入图片描述

// a.h
template<class T>
T Add(const T& left, const T& right);
// a.cpp
template<class T>
T Add(const T& left, const T& right)
{return left + right;
}
// main.cpp
#include"a.h"
int main()
{Add(1, 2);Add(1.0, 2.0);return 0;
}

没有模板的函数,没有问题
有模板的函数,编译可以过,但是链接会出错

函数模板编译:

  1. 实例化之前:编译器只做一些简答的语法检测,不会生成处理具体类型的代码。并不会确认函数的入口地址
  2. 实例化期间:编译器会推演形参类型来确保模板参数列表中T的实际类型,在生成具体类型的代码
  3. 不支持分离编译

解决分离编译

  1. 将声明和定义放到一个文件 “xxx.hpp” 里面或者xxx.h其实是可以的。
  2. 模板定义的位置显式实例化。

模板总结

优点

  1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
  2. 增强了代码的灵活性

缺点

  1. 模板会导致代码膨胀问题,也会导致编译时间变长
  2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误

更加深入学习参考这个链接

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383133.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c++中的IO流

c语言中的IO操作 标准类型的输入输出: 输入------>数据来源是通过键盘进行输入输出------>程序中的数据输出到控制台 c语言中: scanf:输入 printf:输出 两个函数的相同点 1 —格式串 2 —不定参数 两个函数的缺陷 1 —用户要提供数据的格式—用户要记忆大量的格式串—…

201301 JAVA2~3级---走格子

请编写一个函数&#xff08;允许增加子函数&#xff09;&#xff0c;计算n x m的棋盘格子&#xff08;n为横向的格子数&#xff0c;m为竖向的格子数&#xff09;沿着各自边缘线从左上角走到右下角&#xff0c;总共有多少种走法&#xff0c;要求不能走回头路&#xff0c;即&…

复习Linux基本操作----常见指令

Linux基本操作 ls命令 ls(list):相当于windows上的文件资源管理器 语法&#xff1a; ls [选项][目录或文件] 功能&#xff1a;对于目录&#xff0c;该命令列出该目录下的所有子目录与文件。对于文件&#xff0c;将列出文件名以及其他信息。 常用选项&#xff1a; -a 列出目…

复习Linux基础操作---权限操作

shell命令以及运行原理 Linux严格意义上说的是一个操作系统&#xff0c;我们称之为“核心&#xff08;kernel&#xff09;“ &#xff0c;但我们一般用户&#xff0c;不能直接使用kernel。而是通过kernel的“外壳”程序&#xff0c;也就是所谓的shell&#xff0c;来与kernel沟…

【剑指offer】_01 (二维数组中的查找)

题目描述 在一个二维数组中&#xff08;每个一维数组的长度相同&#xff09;&#xff0c;每一行都按照从左到右递增的顺序排序&#xff0c;每一列都按照从上到下递增的顺序排序。请完成一个函数&#xff0c;输入这样的一个二维数组和一个整数&#xff0c;判断数组中是否含有该…

再谈c++中的多态

何为多态 多态的概念&#xff1a;通俗来说&#xff0c;就是多种形态&#xff0c;具体点就是去完成某个行为&#xff0c;当不同的对象去完成时会产生出不同的状态。 多态的实现 在继承的体系下 基类中必须有虚函数(被virtual关键字修饰的成员函数)&#xff0c;在派生类中必须…

再谈c++中的继承

继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保持原有类特性的基础上进行扩展&#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称派生类。继承呈现了面向对象程序设计的层次结构&#xff0c;体现了…

红黑树概念及其相关操作的实现

红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但它并不像AVL树一样&#xff0c;每个结点绑定一个平衡因子。但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。 通过 对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c…

模拟实现STL中map和set容器

红黑树的迭代器 //红黑树的迭代器 template<class T> struct RBTreeIterator {typedef RBTreeNode<T>Node;typedef RBTreeIterator<T> Self; public:RBTreeIterator(Node* pNode nullptr):_pNode(pNode){}//具有指针操作T& operator*(){return _pNode-…

排序上---(排序概念,常见排序算法,直接插入,希尔排序,直接选择排序,堆排序)

排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。稳定性&#xff1a;假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键字的记录&#xff0c;若经过排序&…

排序下---(冒泡排序,快速排序,快速排序优化,快速排序非递归,归并排序,计数排序)

排序上 排序上 交换类排序 基本思想&#xff1a;所谓交换&#xff0c;就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置&#xff0c;交换排序的特点是&#xff1a;将键值较大的记录向序列的尾部移动&#xff0c;键值较小的记录向序列的前部移动。 冒泡…

哈希的概念及其操作

哈希概念 顺序结构以及平衡树中&#xff0c;元素关键码与其存储位置之间没有对应的关系&#xff0c;因此在查找一个元素时&#xff0c;必须要经过关键码的多次比较。顺序查找时间复杂度为O(N)&#xff0c;平衡树中为树的高度&#xff0c;即O( Log2N)&#xff0c;搜索的效率取决…

软件工程---1.概述

软件的特征 抽象&#xff1a; 不可触摸&#xff0c;逻辑实体&#xff0c;可记录&#xff0c;但看不到复制成本低&#xff1a;不受物质材料的限制&#xff0c;不受物理定律或加工过程的制约&#xff0c;与开发成本相比&#xff0c;复制成本很低无折旧、受硬件制约、未完全摆脱手…

软件工程---2.软件过程

三个模型 瀑布模型增量模型集成和配置模型 没有适用于所有不同类型软件开发的过程模型。 瀑布模型 需求定义系统和软件的设计实现与单元测试集成与系统测试运行与维护 瀑布模型的特征 从上一项活动中接受该项活动的工作成果&#xff08;工作产品&#xff09;&#xff0c;作…

软件工程---4.需求工程

需求工程定义 找出、分析、文档化并且检查需求的过程被称为需求工程 需求的两个描述层次 用户需求&#xff0c;指高层的抽象需求。使用自然语言、图形描述需求。系统需求&#xff0c;指底层的详细需求。使用系统需求文档&#xff08;有时被称为功能规格说明&#xff09;应该…

软件工程---5.系统建模

从不同视角对系统建模 外部视角&#xff0c;上下文模型&#xff0c;对系统上下文或环境建模交互视角&#xff0c;交互模型&#xff08;功能模型&#xff09;&#xff0c;对系统与参与者或系统内构件之间的交互建模结构视角&#xff0c;结构模型&#xff08;静态模型&#xff0…

软件工程---6.体系结构设计

体系结构模型是什么&#xff1f; 体系结构模型&#xff0c;该模型描述系统如何被组织为一组相互通信的构件 体系结构分类 小体系结构关注单个程序的体系结构。在这个层次上&#xff0c;我们关注单个的程序是如何补分解为构件的。大体系结构关注包括其他系统、程序和程序构件…

【剑指offer】_07 矩形覆盖

题目描述 我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2*n的大矩形&#xff0c;总共有多少种方法&#xff1f; 解题思路 依旧是斐波那契数列 2n的大矩形&#xff0c;和n个21的小矩形 其中target*2为大矩阵的大小 有以下几种情形…

软件工程---07.设计与实现

软件设计和软件实现 软件设计是一个创造性的活动&#xff0c;在此活动中需要基于客户需求识别软件构件及其关系。软件实现是将设计实现为一个程序的过程 为开发一个系统设计&#xff0c;你需要 理解并定义上下文模型以及系统的外部交互设计系统体系结构识别系统中的主要对象…

软件工程---15.软件复用

复用的图(牢记) 软件复用的好处 开发加速有效的专家利用提高可依赖性降低开发成本降低过程风险符合标准 软件复用的缺点 创建&#xff0c;维护以及使用一个构件库查找&#xff0c;理解以及适配可复用构件维护成本增加缺少工具支持“不是在这里发明的”综合症 应用框架 现在…