【C++】第四篇:模板初识

C++模板及其原理


在这里插入图片描述

泛型编程的概念

在谈模板之前先来了解一下“泛型编程”的概念。什么叫泛型,就是可以广泛的编程,所以可以初步的认为这种编程方法可以是的我们的编程复杂度降低,一定程度上提高编程效率。

在我写过的一篇博客中 手把手带你入门C++面向对象提到过C++中的函数重载及其原理,比如说交换函数:

void swap(int a, int b) 
{
	int tmp = a;
    a = b;
    b = tmp;
}

void swap(double a, double b) 
{
    double tmp = a;
    a = b;
    b = tmp;
}

通过函数的重载可以使得在同名函数的存在,但是如果我还想交换char``float``long等等其他类型的数据的时候,还要写很多的函数,这样还是会使得程序看起来很冗余,有没有一种更通用的方法,写一个通用的交换函数呢?因而C++再泛型编程上引进了模板的概念。就是给一个函数的模样,其中函数的内部实现与参数的类型无关,参数的类型只是一个模具,可以通过类型推导或者外界指定参数类型从而达到“一劳永逸”的效果。

模板分成两种:

1)函数模板

2)类模板

函数模板

函数模板的概念

函数模板是一个函数家族,该函数模板与参数类型无关,在使用函数模板的时候,根据实参的类型,实例化出对应类型版本的函数。

函数模板的格式

template <typename T1, typename T2, ... >
// 下面是函数的编写

注意:typename是用定义模板的关键字,也可以用class替代,T只是一个名字,也可以去其他好理解的名字。

函数模板的演示:

例如:交换函数

template<typename T>
void Swap(T& a, T& b) 
{
    T tmp = a;
    a = b;
    b = tmp;
}

函数模板的原理

其实函数模板就是一个模具,它本身并不是函数,是编译器通过对实参的推导然后在编译阶段实例化产生出了一份特定的类型的函数。如果函数模板的工作室在“照葫芦画瓢”,那么模板本身就是“葫芦”,而实例化产生的特定类型的函数就是“瓢”。

在这里插入图片描述

函数模板的实例化

函数模板在转换成某个特定类型的参数的过程叫做函数模板的实例化,实例化也分为两种:1)隐式实例化 2)显式实例化

隐式实例化

隐式实例化就是不明确的说明参数的类型,让函数模板自己类型推导

template<typename T>
void Swap(T& a, T& b) 
{
    T tmp = a;
    a = b;
    b = tmp;
}

int main()
{
    int n1 = 1, n2 = 2;
    double d1 = 1.1, d2 = 2.2;
    // 通过自己的类型推导实例化函数
    Swap(n1, n2);
    Swap(d1, d2);
    
    Swap(n1, d1);// 这种写法是错误的
    return 0;
}

当想上面最后一个栗子一样,传入两个不同类型的参数的时候就会报错。

有三种解决方法:

1)强制类型转换

swap(n1, (int)d1);// 这样出入的参数就不是double而是int了

2)模板参数改变

template<typename T1, typename T2>
void Swap(T1& a, T2& b) 
{
    T1 tmp = a;
    a = b;
    b = tmp;
}

typename有两个可以推导出两个参数,然后在a = b;的时候T2强转成T1.

3)显式实例化函数

下面要介绍的。

显式实例化

在函数名后加上一个<>来制定转换的类型

Swap<int>(n1, d1);

像这样编译器就一定会生成Tint的函数。然后d1在传参的时候就会强转成int.

函数参数的匹配原则

如果在调用函数的时候,有两个函数都可以调用,匹配原则一般是找更为合适的函数去匹配。

栗子1:

template<typename T>
void Swap(T& a, T& b)// 版本1
{
	T tmp = a;
	a = b;
	b = tmp;
}

void Swap(int& a, int& b)// 版本2
{
	int tmp = a;
	a = b;
	b = tmp;
}

int main()
{
	int n1 = 1, n2 = 2;
	Swap(n1, n2);     // 调用版本1
	Swap<int>(n1, n2);// 调用版本2

	return 0;
}

第一个调用因为存在int特化版本所以就不会调用函数模板的示例化版本。第二个函数因为已经显式实例化函数模板了,所以调用版本2.

栗子2:

template<typename T1, typename T2>// 版本1
void Swap(T1& a, T2& b)
{
	T1 tmp = a;
	a = b;
	b = tmp;
}

void Swap(int& a, int& b)// 版本2
{
	int tmp = a;
	a = b;
	b = tmp;
}



int main()
{
	int n1 = 1;
	double d1 = 2.0;
	Swap(n1, d1);// 调用版本1

	return 0;
}

这个行为可以调用两个参数,一个是用实例化的函数模板,一个是用强制转参数类型的函数。但是匹配原则会选择实例化版本,因为这样更为合适对应参数的类型,而不是改变参数的原有类型。

总结匹配原则:

1)对于非模板函数和同名的模板函数来说,如果其他的条件相同,在调用是会优先调用非模板函数而不是实例化出的模板函数。

2)如果函数模板可以匹配出更好的更可以匹配函数参数的函数则调用实例化出的模板函数。

3)模板函数不允许自动类型转换。

类模板

类模板的概念

既然函数可以模具,那类中有不同的需要统一的参数,所以就有了类模板。

类模板的格式

template<class T1, class T2, ...>
class 类名
{
    // ...
};

注意:class也可以换成typename

类模板的实例化

template <class T>
class Vector
{
public:
	// 构造函数
	Vector(size_t capacity = 10) :
		_data(new T[capacity]),
		_size(0),
		_capacity(capacity) {}
	
	// 析构函数
	~Vector();

	// 其他函数...
private:
	T* _data;
	size_t _size;
	size_t _capacity;
};

// 在类外实现也需要写template<class T>
// Vector<T>才是类型,Vector不是
template<class T>
Vector<T>::~Vector()
{
	delete[] _data;
	_size = _capacity = 0;
}

类模板的示例化只能用显式实例化,不需要指明类型才可以。Vector不是类型,Vector才是类型

Vector<int> v1;
Vector<double> v2;
Vector<char> v3;

注意:

1)在类中声明的函数在类外实现的时候,每一个函数的前面都必须要加上template <class T>这样的模板才可以

2)Vector不是类型这只是一个模板,Vector<具体类型>这样的才是一个特定类。

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
C++ Primer中文版(第5版)[203M]分3个压缩包 本书是久负盛名的C++经典教程,其内容是C++大师Stanley B. Lippman丰富的实践经验和C++标准委员会原负责人Josée Lajoie对C++标准深入理解的完美结合,已经帮助全球无数程序员学会了C++。 对C++基本概念和技术全面而且权威的阐述,对现代C++编程风格的强调,使本书成为C++初学者的最佳指南;对于中高级程序员,本书也是不可或缺的参考书。 目录 第1章 开始 1   1.1 编写一个简单的C++程序 2   1.1.1 编译、运行程序 3   1.2 初识输入输出 5   1.3 注释简介 8   1.4 控制流 10   1.4.1 while语句 10   1.4.2 for语句 11   1.4.3 读取数量不定的输入数据 13   1.4.4 if语句 15   1.5 简介 17   1.5.1 Sales_item 17   1.5.2 初识成员函数 20   1.6 书店程序 21   小结 23   术语表 23   第Ⅰ部分 C++基础 27   第2章 变量和基本型 29   2.1 基本内置型 30   2.1.1 算术型 30   2.1.2 型转换 32   2.1.3 字面值常量 35   2.2 变量 38   2.2.1 变量定义 38   2.2.2 变量声明和定义的关系 41   2.2.3 标识符 42   2.2.4 名字的作用域 43   2.3 复合型 45   2.3.1 引用 45   2.3.2 指针 47   2.3.3 理解复合型的声明 51   2.4 const限定符 53   2.4.1 const的引用 54   2.4.2 指针和const 56   2.4.3 顶层const 57   2.4.4 constexpr和常量表达式 58   2.5 处理型 60   2.5.1 型别名 60   2.5.2 auto型说明符 61   2.5.3 decltype型指示符 62   2.6 自定义数据结构 64   2.6.1 定义Sales_data型 64   2.6.2 使用Sales_data 66   2.6.3 编写自己的头文件 67   小结 69   术语表 69   第3章 字符串、向量和数组 73   3.1 命名空间的using声明 74   3.2 标准库型string 75   3.2.1 定义和初始化string对象 76   3.2.2 string对象上的操作 77   3.2.3 处理string对象中的字符 81   3.3 标准库型vector 86   3.3.1 定义和初始化vector对象 87   3.3.2 向vector对象中添加元素 90   3.3.3 其他vector操作 91   3.4 迭代器介绍 95   3.4.1 使用迭代器 95   3.4.2 迭代器运算 99   3.5 数组 101   3.5.1 定义和初始化内置数组 101   3.5.2 访问数组元素 103   3.5.3 指针和数组 105   3.5.4 C风格字符串 109   3.5.5 与旧代码的接口 111   3.6 多维数组 112   小结 117   术语表 117   第4章 表达式 119   4.1 基础 120   4.1.1 基本概念 120   4.1.2 优先级与结合律 121   4.1.3 求值顺序 123   4.2 算术运算符 124   4.3 逻辑和关系运算符 126   4.4 赋值运算符 129   4.5 递增和递减运算符 131   4.6 成员访问运算符 133   4.7 条件运算符 134   4.8 位运算符 135   4.9 sizeof运算符 139   4.10 逗号运算符 140   4.11 型转换 141   4.11.1 算术转换 142   4.11.2 其他隐式型转换 143   4.11.3 显式转换 144   4.12 运算符优先级表 147   小结 149   术语表 149   第5章 语句 153   5.1 简单语句 154   5.2 语句作用域 155   5.3 条件语句 156   5.3.1 if语句 156   5.3.2 switch语句 159   5.4 迭代语句 165   5.4.1 while语句 165   5.4.2 传统的for语句 166   5.4.3 范围for语句 168   5.4.4 do while语句 169   5.5 跳转语句 170   5.5.1 break
C++著名的基础书籍潘嘉杰著,很适合初学者~~口碑很好的哦~~ 适合基础看的,大牛飞过~~~ 第一篇 过程化的程序设计   第1章 良好的学习开端 1   1.1 软件与程序 1   1.2 程序设计要做什么 1   1.3 选好一种语言 2   1.4 C++能够做些什么 2   1.5 C语言、C++和VisualC++的关系 2   1.6 学习程序设计的方法和必要准备 3   1.7 总结 3   第2章 Hello,World 4   第3章 各种各样的“箱子”——变量 12   第4章 要走哪条路——条件语句 20   第5章 有个圈儿的程序——循环语句 36   第6章 好用的“工具”——函数 51   第7章 好大的“仓库”——数组 69   第8章 内存里的快捷方式——指针 84   第9章 自己设计的箱子——枚举和结构 98   第二篇 实战程序设计   第10章 高效阅读程序代码 119   第11章 调试程序代码技巧 127   第12章 编写程序技巧 150   第三篇 面向对象的程序设计   第13章 初识对象 163   第14章 再识对象 169   第15章 造物者与毁灭者——对象生灭 178   第16章 共有财产·好朋友·操作符 206   第17章父与子——继承 228   第18章 再谈输入与输出 273   第19章 万用的模板 285   第20章 异常的处理 297   附录A 常用保留字列表 305   附录B 常见编译错误和解决方法 307   附录C 参考答案 310   附录D 参考文献 356
易学 C++ PDF 及源代码,原书配套,很全。 PDF高清有目录 第一篇 过程化的程序设计   第1章 良好的学习开端 1   1.1 软件与程序 1   1.2 程序设计要做什么 1   1.3 选好一种语言 2   1.4 C++能够做些什么 2   1.5 C语言、C++和VisualC++的关系 2   1.6 学习程序设计的方法和必要准备 3   1.7 总结 3   第2章 Hello,World 4   第3章 各种各样的“箱子”——变量 12   第4章 要走哪条路——条件语句 20   第5章 有个圈儿的程序——循环语句 36   第6章 好用的“工具”——函数 51   第7章 好大的“仓库”——数组 69   第8章 内存里的快捷方式——指针 84   第9章 自己设计的箱子——枚举和结构 98   第二篇 实战程序设计   第10章 高效阅读程序代码 119   第11章 调试程序代码技巧 127   第12章 编写程序技巧 150   第三篇 面向对象的程序设计   第13章 初识对象 163   第14章 再识对象 169   第15章 造物者与毁灭者——对象生灭 178   第16章 共有财产•好朋友•操作符 206   第17章父与子——继承 228   第18章 再谈输入与输出 273   第19章 万用的模板 285   第20章 异常的处理 297   附录A 常用保留字列表 305   附录B 常见编译错误和解决方法 307   附录C 参考答案 310   附录D 参考文献 356   ……
目录 第1篇初级篇 第1章 初识C++ 1.1 c++简介 1.2 C++与C的区别 1.3 学习c++之前需要先学C吗 1.4 c++与其他语言的区别 1.5 c++的版本以及安装问题 第2章 做一个最简短的C4-+程序 2.1 简单的屏幕输出小程序 2.2 输出语句的使用 2.3 std::介绍 2.4 iostream与iostream.h的区别 2.5 重名问题 2.6 注释 2.7 总结 第3章 初步了解函数 3.1 一个简单的函数 3.2 函数的传参 3.3 函数的返回值、参数与变量 3.4.函数的声明与定义 3.5 局部变量 3.6 全局变量 3.7 总结 第4章 C4-+数据型 4.1 变量的定义 4.2 将变量及数据存储在内存中 4.3 布尔型变量 4.4 字符型变量 4.5 wchart双字符型变量 4.6 整型概述 4.7 整型变量的定义 4.8 浮点型变量 4.9 常量 4.10枚举型常量 第5章 if语句与运算符 5.1 语句的定义 5.2 块的定义 5.3 表达式的定义 5.4 运算符的定义 5.4.1 赋值运算符的定义 5.4.2 数学运算符的定义 5.4.3 赋值运算符与数学运算符的联合 5.5 自加与自减 5.5.1 前置 5.5.2 后置 5.6 表达式的优先级 5.7 关系运算符 5.8 if语句 5.8.1 else语句 5.8.2 elseif语句 5.8.3 if语句的嵌套 5.9 逻辑运算符及其使用 5.9.1 逻辑“与” 5.9.2 逻辑“或” 5.9.3 逻辑“非” 5.9.4 逻辑运算符的优先级 5.9.5 运算式的真假关系 5.1 0三目运算符 5.1 0.1 三目运算符的优先问题 5.1 0.2 三目运算符的使用问题 5.1 0.3 三目运算符的型别问题 5.1 0.4 三目运算符在字符型变量中的应用 5.1 1复杂嵌套的if语句 5.1 2总结 第6章 面向对象 6.1 面向对象程序语言的主要特征 6.2 、对象和成员 6.3 、对象和成员的使用方法及区别 6.3.1 声明一个 6.3.2 命名习惯 6.3.3 定义一个对象 6.3.4 与对象的区别 6.3.5 对象与成员的关系 6.3.6 不要给赋值 6.3.7 对象只能调用中存在的方法 6.4 公有 6.5 私有 6.6 成员函数的声明和定义 6.7 内联函数 6.7.1 普通内联函数 6.7.2 内联成员函数 6.8 头文件与源文件 6.9 const成员函数 6.10构造函数 6.11默认构造函数 6.12析构函数 6.13析构对象数组 6.14总结 第7章 循环语句 7.1 循环语句的前身——goto语句 7.2 慎用goto语句 7.3 while语句 7.3.1 带运算符的while语句 7.3.2 以字符为条件的while语句 7.3.3 限定while循环的次数 7.3.4 continue语句 7.3.5 break语句 7.3.6 永不休止的while循环 7.4. do……while循环 7.5 for循环 7.5.1 灵活的for循环 7.5.2 条件为空的for循环 7.5.3 执行为空的for循环 7.5.4 嵌套的for循环 7.6 switch语句 7.6.1 switch语句常见错误 7.6.2 switch的菜单功能 7.7 总结 第8章 指针 8.1 什么是地址 8.2 用指针来保存地址 8.2.1 空指针 8.2.2 指针与变量型 8.2.3 用指针来访问值 8.2.4 指针地址、指针保存的地址和 该地址的值 8.2.5 指针对数值的操作 8.2.6 更换指针保存的地址 8.3 为什么使用指针 8.3.1 栈和堆 8.3.2 用指针创建堆中空间 8.3.3 用指针删除堆中空间 8.4 动态内存 8.4.1 内存泄漏 8.4.2 在堆中创建对象 8.4.3 在堆中删除对象 8.4.4 访问堆中的数据成员 8.4..5 在构造函数中开辟内存空间 8.4.6 对象在栈与堆中的不同 8.5 this指针 8.6 指针的常见错误 8.7 指针运算 8.7.1 指针的加减运算 8.7.2 指针的赋值运算 8.7 _3指针的相减运算 8.7.4 指针的比较运算 8.8 指针 8.8.1 常量指针 8.8.2 指向常量的指针 8.8.3 指向常量的常指针 8.9 总结 第9章 引用 9.1 什么是引用 9.1.1 引用的地址 9.1.2 引用就是别名常量 9.1.3 引用对象 9.1 4空引用 9.2 函数的参数传递 9.2.1 按值传递 9.2.2 按址传递 9.2.3 按别名传递 9.2.4 让函数返回多个值 9.3 传递对象 9.3.1 按值来传递对象 9.3.2 利用指针来传递对象 9.3.3 利用cost指针来传递对象 9.3.4 利用引用来传递对象 9.3.5 到底是使用引用还是指针 9.3.6 引用和指针可以一块用 9.4 引用应注意的问题 9.4.1 引用容易犯的错误 9.4.2 引用一个按值返回的堆中对象 9.4 -3引用一个按别名返回的堆中对象 9.4.4 在哪里创建,就在哪里释放 9.5 总结 第10章 深入函数 10.1 函数重载 10.1.1 普通函数的重载 10.1.2 成员函数的重载 10.2 函数的默认参数 10.3 重载构造函数 10.3.1 成员变量的赋值与初始化 10.3.2 成员变量的初始化与构造函数 10.3.3 复制构造函数 10.3.4 构造函数和new运算符 10.3.5 再谈默认构造函数 10.4.析构函数和delete运算符 10.4..1 默认析构函数 10.4.2 调用构造函数进行型转换 10.5 浅层复制构造函数 10.6 深层复制构造函数 第11章 运算符重载 11.1 运算符重载 11.2 在成员函数中实现自加 11.3 重载前置自加运算符 11.4 创建临时对象 11.5 创建无名临时对象 11.6 取消创建临时对象 11.7 重载后置自加运算符 11.8 重载加法运算符函数operator+ 11.9 重载赋值运算符函数operator 11.10转换型运算符 11.10.1 温习调用构造函数实现的型转换 11.10.2 通过构造函数将变量转换为一个对象的成员变量 11.10.3 通过operator关键字进行转换 11.11什么可以重载,什么不可以重载 第12章 继承 12.1 什么是继承和派生 12.1.1 复杂的继承和派生 12.1.2 继承和派生如何在C++中实现 12.1.3 继承的种及语法 12.1.4 单一继承 12.2 公有型、保护型和私有型 12.3 访问权限 12.4 多重继承 12.5 继承的构造与析构 12.6 合理利用基构造函数 12.7 继承和重载的两义性问题 12.7.1 多重继承容易产生两义性 12.7.2 继承中的重载 12.7.3 两义性的归属问题 12.7.4 减少两义性产生的混淆问题 12.7.5 虚基不会产生两义性 12.8 总结 第13章 虚函数 13.1 指向子对象的父指针 13.2 虚函数 13.3 拳击游戏 13.4 继承是否可以实现多态性 13.5 在编译时的静态联编 13.6 在运行时的静态联编 13.7 在运行时的动态联编 13.8 在编译时的动态联编 13.9 调用虚函数 13.9.1 在虚函数中调用成员函数 13.9.2 3种调用虚函数的方式比较 13.10被继承的虚函数仍然是虚函数 13.11系统是如何调用虚函数的 13.12在虚函数中使用成员名限定 13.13虚析构函数 13.14总结 第14章 数组 14.1 数组的基本用法 14.1.1 什么是数组 14.1.2数组元素 14.1.3数组下标越界 14.1.4 倒序输出 14.1.5 将数组的下标定义为常量 14.1.6 手动操作数组元素 14.1.7 数组的初始化 14.2 数组的用途 14.2.1 求平均考试成绩 14.2.2 兔子繁殖问题 14.2.3 数字排序问题 14.3 数组在内存中的分布 14.4.输出数组名 14.5 数组名与函数 14.6 传递与接收 14.7 数组与函数 14.7.1 函数传参实例一——求数组所有元素的和 14.7.2 函数传参实例二——用递增法查找数据 14.7.3 函数传参实例三——用二分算法查找数据 14.7.4 函数传参实例四——判断数组是否按照顺序排列 14.7.5 函数传参实例五——判断数组排列方式后执行不同的函数 14.8 数组在对象中的传参 14.9 对象数组 14.10 在对象数组中初始化成员变量 14.11 指针数组 14.12 枚举常量与数组 14.13 多维数组 14.14 多维数组的初始化 14.15 字符数组 14.16 重载数组下标操作符 14.17 总结 第15章 链表 15.1 声明链表结构 15.2 简单的图书链表 15.2.1 图书链表 15.2.2 的链表 15.3 动态链表 15.3.1 动态链表的建立 15.3.2 解决输入字符造成死循环的问题 15.3.3 动态链表的显示 15.3.4 动态链表的删除 第16章 多态性 第17章 的特殊成员 第2篇 高级篇 第19章 代码重用 第20篇 高级篇 第20章 友元与嵌套 第21章 流 第22章 命名空间 第23章 模板 第24章 异常和错误处理 第25章 补充知识 附录A ASCII码对照表 附录B C++的关键字 附录C C++常用头文件列表 附录D 运算符的优先级 后记

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hyzhang_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值