c++之模板初阶详解

文章目录

  • c++之模板初阶详解
    • 泛型编程
    • 函数模板
      • 函数模板概念
      • 函数模板格式
      • 模板的原理
      • 函数模板的实例化
      • 模板实例化的个数
      • 对于同不同类型的传参!
        • 如何处理这个问题呢?
      • 关于具体存在的函数和模板函数的优先级问题!
    • 类模板
      • 类模板的用法!
      • 类模板的实例化!
      • 模板的范围
      • 类模板的运用实例!
    • 模板的缺陷!
      • 解决方法!

泛型编程

我们以前是如何实现一个通用的函数呢?

void swap(int& x, int& y)
{int temp = x;x = y;y = temp;
}void swap(double& x, double& y)
{double temp = x;x = y;y = temp;
}
void swap(char& x, char& y)
{double temp = x;x = y;y = temp;
}

使用函数重载来实现一个通用的函数!但是函数重载也有很多的问题!

  1. .重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
  2. 代码的可维护性比较低,一个出错可能所有的重载均出错

所以有没有一个方法能够解决以上的缺点,同时又保留优点呢?

所以C++提供了模板作为手段来解决这些问题!

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

函数模板

函数模板概念

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。

函数模板格式

template <typename T1,typename T2,typename T3,typename T4…typename Tn>

或者 template <class T1,class T2,class T3, class T4…class Tn>

 template <class T>
void swap(T& left, T& right)
{T temp = left;left = right;  right = temp;
}int main()
{int a = 0, b = 1;swap(a, b);double c = 1.11, d = 1.2222;swap(c, d);char e = 'a', f = 'b';swap(e, f);}

模板的原理

那么这三个调用的swap函数是同一个函数吗

答案是错误的!这三个swap函数是三个不同的函数!

而且从函数创建的角度来看!我们调用函数都要创建栈帧!这三个函数的栈帧大小都是不一样的!所以也就不可能是同一个函数!

我们还可以看看反汇编下的代码

可以看到这三个函数的地址都是不一样!

所以调用的不是模板!模板是无法生成指令!因为类型不确定所以导致了栈帧大小无法确定!

但是函数是编译器通过模板来生成的!

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器


在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供 调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然 后产生一份专门处理double类型的代码,对于字符类型也是如此。

函数模板的实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例 化。

和对象的实例化是有区别的!类的实例化是编译器通过类的对齐规则计算类的大小有多大,类的内存分布规则是怎么样的,然后开一块空间出来!给对象!最后去调用构造函数!

但是模板的实例化是比编译器通过我们传的参数类型,使用函数模板来替换对应的T生成对应的具体函数!

模板实例化的个数

上面的代码我们看出来模板一共实例化的三个函数!

 template <class T>
void swap(T& left, T& right)
{T temp = left;left = right;  right = temp;
}
int main()
{int a = 0, b = 1;swap(a, b);int a =0double c = 1.11, d = 1.2222;swap(c, d);char e = 'a', f = 'b';swap(e, f);}

答案是生成3个!当有相同的参数类型的函数调用的时候!如果之前已经生成过,那么就会调用之前生成的那个函数!

函数没有销毁这个概念!函数只是一串命令!只是函数每一次调用的栈帧有销毁的概念!

对于同不同类型的传参!

template <class T>
void swap(T& left, T& right)
{T temp = left;left = right;right = temp;
}
int main()
{int a = 0, b = 1;double c = 1.11, d = 1.2222;char e = 'a', f = 'b';swap(a, c);//这个会报错!表面上看是因为类型不同导致的!//但是有没有想过一个问题?//我们平时在将double 赋值个int 的时候往往会出现隐式类型转换,为什么这次就出现不了了//那如果我们使用强制类型转换呢?return 0;
}

如何处理这个问题呢?

  1. 使用const类型的参数去接收强转之后的具有常性的临时变量!不过这样就意味着该变量无法修改所以接下里了我们将使用add函数来进行演示!

  2. 刚刚都是一种隐式的去让编译器自己推演生成对应的函数!我们也可以自己指定让编译器去生成我们想要的函数!直接跳过推演的阶段!

  3. 多定一个模板参数即可!

template <class T>
T add(T& left, T& right)
{return left + right;
}
template <class T>
T add2(const T& left, const T& right)
{return left + right;
}
template <class T,class T2>
T add3(const T& left,const T2& right)
{return left + right;
}
int main()
{int a = 0, b = 1;double c = 1.11, d = 1.2222;char e = 'a', f = 'b';add(a, (int)c);//还是会报错!因为这样子函数是成功的生成了!//但是强制类型转换必然会是生成一个具有常性的临时变量变量!//将T& 接收 const T是不可以的!//发生了权限的放大!int k = add2(a, (int)c);//这样子就可以成功的使用该函数了!//上面的我们都要是要让编译器进行推演然后得到对应的类型函数!//但是我们也可以直接跳过这个阶段!我们可以显示的去指定让编译器去生成对应的类型函数!   int k1 = add<int>(a, c);//会报错!函数虽然已经生成了!但是理由同同上,因为发生了权限的放大!int k2 = add2<int>(a,c);int k3 = add<double>(a,c);//这样子就可以使用了!//使用两个模板参数!int k4 = add3(a,c);return 0;
}

关于具体存在的函数和模板函数的优先级问题!

当模板函数和具体的类型函数同时存在的时候会先调用那个呢?

template <class T>
T add(T& left,T& right)
{return left + right;
}
int add(int left, int right)
{return left + right;
}int main()
{int a = 0, b = 1;double c = 1.11, d = 1.2222;char e = 'a', f = 'b';int k = add(a,b);int k1 = add<int>(a,b);return 0;
}

答案是若是隐性的去生成对应的类型类型,那么编译器回去优先调用已经存在的对应类型的函数!

只有显性的去要求生成的时候,编译器才会去生成!

从这个我们也可以看处理模板名的函数名修饰规则和普通的函数名修饰规则是不一样的!

类模板

以前我们想让一个类可以在多个类型复用我们可能会使用!typedef

typedef int STDateType;
class stack
{public:stack(STDateType newcapcacity){STDateType* temp = (STDateType*)malloc(sizeof(STDateType) * newcapcacity);if (temp == nullptr){perror("malloc fail");exit(-1);}_a = temp;_top = 0;_capacity = newcapcacity;}~stack(){free(_a);_a = nullptr;_top = 0;_capacity = 0;}stack& operator=(stack& st){if (this != &st){_a = (T*)malloc(sizeof(T) * st._capacity);if (_a == nullptr){perror("malloc fail");exit(-1);}memcpy(_a, st._a, st._top * sizeof(T));_capacity = st._capacity;_top = st._top;}return *this;}void Push(STDateType x){//...}
private:STDateType* _a;int _top;int _capacity;
};

但是这是有缺点的那就是万一我要同时使用的多个类型的类呢?那不就只能重新复制粘贴一份,而且因为了类名不能相同我们还得重新取名而且即使是单个类型的重复,我们也要反复的修改typedef!

typedef真正解决的是可维护性!方便在我们修改的时候只要修改一次!不是真正的泛型!

int main()
{//整形!stack st1;st1.Push(1);//浮点型stack st2;st2.Push(1.1);return 0;
}

类模板的用法!

template<class T>
class stack
{public:stack(T newcapcacity = 4){T* temp = (T*)malloc(sizeof(T) * newcapcacity);if (temp == nullptr){perror("malloc fail");exit(-1);}_a = temp;_top = 0;_capacity = newcapcacity;}~stack(){free(_a);_a = nullptr;_top = 0;_capacity = 0;}stack& operator=(stack& st){if (this != &st){_a = (T*)malloc(sizeof(T) * st._capacity);if (_a == nullptr){perror("malloc fail");exit(-1);}memcpy(_a, st._a, st._top * sizeof(T));_capacity = st._capacity;_top = st._top;}return *this;}void Push(const T& x){//...}//使用T以后 push推荐使用引用!因为以前使用内置类型,类型大小不大!不怎么占用空间!//以后万一遇到类似于日期类,时间类或者其他比较大,更复杂的类的时候,那么使用传值传参就不怎么好了!
private:T* _a;int _top;int _capacity;
};

类模板的实例化!

类模板和函数模板不一样!函数模板可以通过实参推演形参来产生特定的类型函数!

但是类模板不一样!类模板没有时机去推演类型!所以这就导致了,类模板只能显示的去调用!

所以类模板统一显示实例化!

int main()
{stack<int> st1;st1.Push(1);stack<double> st2;st2.Push(1.2222);return 0;
}

如果不显示实例化

类模板和函数模板一样,只是一个模板,不能当做真正的类去使用!

stack<int> st1;、
stack<double> st2;

这两个类是不同的类型!因为这两个的类的大小都是可能不一样的!成员变量的大小也可能不一样!

它们是同一个类模板实例化出来的,但是它们不是同一个类型的类!

可以认为是同一个妈生的双胞胎!但是双胞胎肯定不是同一个人!

st1 = st2;
//这个会报错!
//赋值重载只限定在同一个类!
//st1和st2压根不是同一个类!

模板的范围

模板只能给模板一个函数或在类使用,不可以同时给两个!

在那个类或者函数里面,模板可以在任意范围生效!

template<class T>
class A
{A(){_a = 0;}
private:T _a
};
class B
{B(){_b = 0;}
private:T _b
};
//要一个模板对应一个类!
template<class T>
class A
{A(){_a = 0;}
private:T _a
};
template<class T>
class B
{B(){_b = 0;}
private:T _b
};
//函数模板也是同理!

类模板的运用实例!

c++中很少再去使用数组,取而代之的是array和vector!因为数组不安全!

当我们对数组进行访问的时候,因为对于数组的检查是抽查!编译器是不一定报错的!

对于原声的数组越界写可能会被检查到,但是越界读几乎检查不到!

但是在array中这个检查就是绝对的!

#define N 10
template<class T>
class array
{public:T& operator[] (size_t i){assert(i<N)return _a[i];}
private:T _a[N];
};int main()
{array<int> a;for (int i = 0; i < N; i++){a[i] = i;//a[i]相当于 a.operator[] (i);}for (int i = 0; i < N; i++){cout << " " << a[i];}cout << endl;for (int i = 0; i < N; i++){a[i]++;}for (int i = 0; i < N; i++){cout << " " << a[i];}return 0;
}

虽然使用array会因为调用建立栈帧导致性能损失!但是因为类里面定义的都均为内联,所其实性能损失并没有多少!

模板的缺陷!

模板也是存在缺陷的!——那就是模板不支持分离编译!

就是说将声明放在.h文件中,将定义放在.cpp文件中!

//template.h
#include<iostream>
using std::cout;
using std::endl;
template<class T>
class stack
{public:stack(int newcapcacity);~stack();void Push(const T& x);
private:T* _a;int _top;int _capacity;
};//template.cpp
#include "template.h"
template<class T>
stack<T>::stack(T newcapcacity)
{T* temp = (T*)malloc(sizeof(T) * newcapcacity);if (temp == nullptr){perror("malloc fail");exit(-1);}_a = temp;_top = 0;_capacity = newcapcacity;
}template<class T>
stack<T>::~stack()
{free(_a);_a = nullptr;_top = 0;_capacity = 0;
}//这个虽然没有用T但是也要加上声明!说明这是属于类模板的!template<class T>
void Push(const T& x)
{//...
}
//test.h
#include"template.h"int main()
{stack<int> st(1);st.Push(1);st.Push(2);return 0;
}

**然后我们发现了出现了这个这个不是编译错误这个是链接链接错误!出现链接错误就说明声明没有找到定义!**这是为什么!?

首先我们要先悉知一下编译链接的流程!

解决方法!

  1. 在定义的地方进行显示实例化!

    这样的话就可以在定义的地方生成函数了!

    #include "template.h"
    template<class T>
    stack<T>::stack(T newcapcacity)
    {T* temp = (T*)malloc(sizeof(T) * newcapcacity);if (temp == nullptr){perror("malloc fail");exit(-1);}_a = temp;_top = 0;_capacity = newcapcacity;
    }
    template
    class stack<int>;
    class stack<double>;
    //......
    

    但是这个方法失去了模板的优势!我们每使用一种就要在定义的地方显式实例化一次!这样其实很麻烦!

  2. 方法二将定义和声明都放在同一个源文件下面

//template.h
#include<iostream>
using std::cout;
using std::endl;
template<class T>
class stack
{public:stack(int newcapcacity);~stack();void Push(const T& x);
private:T* _a;int _top;int _capacity;
};template<class T>
stack<T>::stack(T newcapcacity)
{T* temp = (T*)malloc(sizeof(T) * newcapcacity);if (temp == nullptr){perror("malloc fail");exit(-1);}_a = temp;_top = 0;_capacity = newcapcacity;
}
template<class T>
stack<T>::~stack()
{free(_a);_a = nullptr;_top = 0;_capacity = 0;
}//这个虽然没有用T但是也要加上声明!说明这是属于类模板的!

这样能解决的原因是因为,声明和定义都是在同一个文件下面所以自然就不需要进行链接了!

因为声明和定义都是在同一个文件里面,所以在编译阶段call的地址就自然就可以找到了!

读者可能会有疑惑,那为什么不直接写在类里面?还要多此一举!答案是为了有更好的可读性!在工程中,有的类的成员函数可能多达上千行!这样会导致可读性很差!不能方便快速的浏览类的成员函数和成员变量!

c++之模板初阶详解!相关推荐

  1. 【明解C语言】之指针初阶详解

    目录 一.指针是什么 二.指针和指针类型 1. 指针+-整数 2. 指针的解引用 3.练习 三.野指针 1.野指针成因 2.规避野指针的有效方法 四.指针运算 1.指针+-整数 2.指针-指针 3.指 ...

  2. 学习笔记:C++初阶【C++入门、类和对象、C/C++内存管理、模板初阶、STL简介、string、vector、list、stack、queueu、模板进阶、C++的IO流】

    文章目录 前言 一.C++入门 1. C++关键字 2.命名空间 2.1 C语言缺点之一,没办法很好地解决命名冲突问题 2.2 C++提出了一个新语法--命名空间 2.2.1 命名空间概念 2.2.2 ...

  3. C++ - 类模板(class template) 详解 及 代码

    类模板(class template) 详解 及 代码 本文地址: http://blog.csdn.net/caroline_wendy/article/details/16906827 类模板(c ...

  4. easycode 表配置_idea的easyCode的 MybatisPlus模板的配置详解

    EasyCode 插件 EasyCode 插件 是一款根据表结构生成代码的很方便的Idea插件, 强烈推荐. 并且可以自定义模板来控制生成的类 我在使用的过程中发现一些问题,现在把解决办法记录下来, ...

  5. ecshop模板smarty foreach详解

    Smarty目录: /libs Smarty的功能类目录 /tempalates 模板文件目录 /templates_c 模板缓存文件目录 /configs 配置文件目录 /cache 缓存文件目录 ...

  6. C/C++内存管理模板初阶

    内存管理和模板初阶 1 内存管理 1.1 C/C++ 的内存分布 1.2 C 中动态内存管理方式 1.3 C++ 中动态内存管理方式 1.3.1 new/delete操作内置类型 1.3.2 new/ ...

  7. C++函数模板的使用详解

    更多python.PHP.JAVA.C.C++教程请到友情连接: 菜鸟教程https://www.piaodoo.com 茂名一技http://www.enechn.com ppt制作教程步骤 htt ...

  8. 平衡树初阶——AVL平衡二叉查找树+三大平衡树(Treap + Splay + SBT)模板【超详解】...

    平衡树初阶--AVL平衡二叉查找树 一.什么是二叉树 1. 什么是树. 计算机科学里面的树本质是一个树状图.树首先是一个有向无环图,由根节点指向子结点.但是不严格的说,我们也研究无向树.所谓无向树就是 ...

  9. vue-cli生成的模板各个文件详解(转)

    vue-cli脚手架中webpack配置基础文件详解 一.前言 原文:https://segmentfault.com/a/1190000014804826 vue-cli是构建vue单页应用的脚手架 ...

最新文章

  1. 为什么匿名内部类参数必须为final类型
  2. [转帖]最值得了解的10大开源技术
  3. 什么是分布式锁?redis、zookeeper、etcd实现分布式锁有什么不同之处?
  4. django 1.8 官方文档翻译:8-5 加密签名
  5. 一文弄懂java中的Queue家族
  6. php提交表单处理,PHP表单处理
  7. python怎么写出来的_如何写出优雅又地道的Python代码?【转载】
  8. java.servlet不存在_eclipse提示servlet不存在 的解决办法
  9. ubuntu ssh服务器与客户端的文件互传
  10. 如何使用Java逐行读取大文本文件?
  11. 计算机一级学科博士点的双非大学,2021年这些工学各学科,实力强劲的双非大学,强力推荐学生报考...
  12. 申请与认证IB课程全流程
  13. linux下测硬盘读写速率,linux下测试硬盘读写速度 互联网技术圈 互联网技术圈
  14. P3376 【模板】网络最大流
  15. 今天是我的生日,十年如一日
  16. 苹果开发者账户续费,支付授权失败,真正的解决办法
  17. 软件工程北大慕课答案
  18. 【无标题】java核心资料
  19. 第七讲:1.物联网敲击桌面打开小台灯
  20. tensor转换为list

热门文章

  1. e路通电子传真(实现企业传真无纸化办公)beta版震撼发布
  2. 安卓手机如何玩转「动作手势检测」?有TensorFlow就够了 | 实用教程
  3. Ext.js5属性表格(更新数据)(handler和listener的区别)(蓝色的时候是蓝色的combo)(source)(19)
  4. Java基础 第五节 第九课
  5. python之数据分析可视化(b站排行播放量,简单详细)
  6. 基于elementui源码实现自定义穿梭框transfer组件
  7. 【开箱视频】仪器验收需要注意哪些细节?
  8. adobe acrobat 9 pro中文版
  9. 有关SQL*PLUS命令使用大全
  10. 一个超级好的学习网站!!!