21天学会C++:Day14----模板

· CSDN的uu们,大家好。这里是C++入门的第十四讲。
· 座右铭:前路坎坷,披荆斩棘,扶摇直上。
· 博客主页: @姬如祎
· 收录专栏:C++专题

目录

1. 知识引入

2. 模板的使用

2.1 函数模板

2.2 类模板

3. 模板声明和定义分离

3.1 同一文件中的声明与定义分离

3.2 分文件的声明与定义分离

4. 非类型模板参数

5. 模版的特化

5.1 模板的全特化

5.2 模板的偏特化

6. 模板总结


1. 知识引入

有一天,我们在写C语言程序的时候,想要交换两个数的值,于是我们很快就写了一个交换两个整形变量的函数:

void Swap(int x, int y)
{int tmp = x;x = y;y = tmp;
}

但是写了一会代码,你发现你又要交换两个double的值,你又要重新写一个Swap double的函数。假如后来你还需要交换其他类型的变量,那么你就需要写更多的Swap函数。是不是偷一下子就变大了。不过别慌,C++带着新的语法走来了!

2. 模板的使用

听到模板这个名词,我们就想到了显示生活中的模具,通过一个模具我们就能制作出很多产品。同理通过一个模板,我们就能实现很多功能,满足你的各种需求!C++的模板是泛型编程的基础,所谓泛型编程:编写与类型无关的通用代码,是代码复用的一种手段

我们来看看模板的语法:

template <typaname T1, typename T2, ··· , typaname TN>

函数/类

 模板通过关键字 template 来定义,template 后面紧跟一个 尖括号 , 尖括号中加上关键字 typename (typename 换成class也行) 然后跟上模板参数,其中尖括号中的写法与函数的形参列表极为相似。其中的T1,T2 ··· 叫做模板参数,名字可以随意更改。根据template下方定义的类型,模板可以分为函数模板类模板

2.1 函数模板

我们先来看看函数模板的用法吧:我们就拿上面的Swap函数来试试吧,看看有了函数模板能省事多少!

//定义函数模板
template<typename T>
void Swap(T& x, T& y)
{T tmp = x;x = y;y = tmp;
}

模板起始就是将类型参数化,在上面的代码中我们将Swap函数的参数类型用模板参数代替,当我们调用函数时,编译器会根据传入参数的类型,自动为T实例化出对应的类型!

通过调试发现,即使我们传入不同类型的参数,也能够做到交换两个变量的值。

你可能会好奇,编译时怎么做到的呢?其实是这样的:当你使用 int 类型调用 Swap 函数那么编译器就会根据函数模板生成一个参数类型为 int 的函数,当你使用 double 类型调用 Swap 函数那么编译器就会根据函数模板生成一个参数类型为 double 的函数。你可能会说,这和直接些两个函数没有什么区别啊!但事实时,我们只写了一个函数模板,多余的事儿我们都交给了编译器,这不香吗?

通过观察汇编代码,可以看到确实是生成了连个不同参数类型的函数:

在C++中,通过函数模板生成函数的过程我们叫做模板的实例化。

上面我们使用函数模板的方式叫做隐式实例化 ,即不指定模板参数的类型,编译器根据参数的类型自动推导模板参数的类型。

但是并不是所有的情况都能通过隐式实例化来完成,那个时候就必须显示实例化啦:

template<typename T>
T* alloc(size_t n)
{return new T[n];
}int main()
{int* a1 = alloc(10);//显示实例化int* a2 = alloc<int>(10);return 0;
}

隐式实例化是会报错的,因为他无法通过你传入的参数推导出模板参数T的实际类型。 

2.2 类模板

类模板和函数模板差不多,只不过定义函数的地方改成定义类。

在下面的代码中,我们定义了一个名为Stack的模板类,根据模板实例化时传入的模板参数的类型不同,我们就能实例化出来栈内元素类型不同的栈。在C语言中我们只能通过 typedef 来实现变换栈内元素的类型,但是确做不到在一个工程中同时使用 一个数据元素是 int 的栈, 一个数据元素是 double 的栈(除非你不嫌麻烦,赋值一份栈的代码)。但是有了类模板就能轻松做到。

template<typename T>
class Stack
{
public:private:T* _a;int _size;int _capacity;
};int main()
{Stack<int> st1;Stack<double> st2;
}

我们可以看到:类模板在实例化的时候就只能显示实例化了!没法隐式实例化呢!即使你能够通过类中的构造函数推导出模板参数的类型 ,也不能隐式实例化呢!这是为什么呢?第 3 点会给你答案。

3. 模板声明和定义分离

3.1 同一文件中的声明与定义分离

 我们来看全局的函数模板的声明与定义分离该怎么书写:

//函数模板的声明
template<typename T>
void Swap(T& x, T& y);//函数模板的定义
template<typename T>
void Swap(T& x, T& y)
{T tmp = x;x = y;y = tmp;
}

在声明的时候需要有 template<typename T> 在定义的时候也需要有 template<typename T> 因为模板参数只能在它下面的第一个函数或者类中使用。

相比全局函数实现 声明与定义分离,我们更喜欢用的是类的成员函数的声明与定义分离:

我们在类中声明了一个push函数,虽然我们在外面实现了push函数,但是编译器依然报错,说明我们实现的方式有问题,你可能会说加一个类域?很棒,但是还是不完全正确!在解决这个问题之前还需要铺垫一个知识:普通类的类名即是一个类型!但是模板类的类名还是一个类型吗?我都这样说了,你肯定知道不是啦!的确不是,模板类的类型需要显示指定模板参数才是该类的类型。

像这样:Stack<int> 这就是一个Stack类型。

ok,我们现在大概能猜出类成员函数的声明与定义分离应该怎么写了吧:使得还需要在函数名的前面加上这个类的类型才行 。

template<typename T>
class Stack
{
public:int size(){return _size;}void push(const T& val);private:T* _a;int _size;int _capacity;
};template<typename T>
void Stack<T>::push(const T & val)
{_a[size++] = val;
}

像上面这样我们就实现了类成员函数的声明与定义分离了呢!在实际的编程中,我们习惯将那些短小的函数直接在类内定义(默认就是内联函数了),那些比较长的函数实现声明与定义分离。 

现在我们就知道为什么模板类不可能隐式实例化了嘛,因为模板类的类名不是类型,必须指定模板参数后才是类型,只有用类型才能定义变量!

3.2 分文件的声明与定义分离

我们在写C语言的时候就喜欢将函数的定义与声明分文件编写嘛!现在我们来看看模板类的成员函数如果声明与定义分文件编写会发生什么:

//test.h /
//
#pragma once
template<typename T>
class Stack
{
public:Stack(int capacity = 4){_a = new T[capacity];_size = 0;_capacity = capacity;}int size(){return _size;}void push(const T& val);void pop();private:T* _a;int _size;int _capacity;
};///
///  test.cpp /
///#include"test.h"template<typename T>
void Stack<T>::push(const T & val)
{_a[_size++] = val;
}template<typename T>
void Stack<T>::pop()
{_size--;
}///
///  main.cpp /
///
#include<iostream>
#include"test.h"using namespace std;int main()
{Stack<int> st;st.size(); // 不会出问题st.pop();st.push(1);}

还有一个奇怪的事儿就是当你注释掉 push 和 pop 函数的调用就不会报错了!这是因为 模板 函数会按需实例化 当你没有调用这个函数时 编译器是不会实例化出来对应的函数的! 

我们发现调用 push 和 pop 函数会报链接错误。这是为啥呢?链接时错误一般都是在函数有声明,没有定义的时候出现的,但是我在 test.cpp 确实是定义了 push 和 pop 函数的啊!

我们慢慢来分析,size函数没有报错是因为,size函数在声明的时候直接就定义了,编译的时候就能直接确定函数的地址。但是对于 push 和 pop 函数,因为他们的定义在另一个文件,只有在链接的时候才能确定函数的地址,当链接的时候去找 push 和 pop 函数的地址没找到,因此报了链接错误。

为什么就没找到呢?

是因为我们的 push 与 pop 的实现在另一个文件,在模板函数所在的cpp文件,不知道模板参数的具体类型,编译器不知道该实例化什么模板参数是什么类型的函数,从而无法为这两个函数确定函数地址。链接的时候自然就找不到这两个函数的地址了!

该怎么解决这个问题呢?

我们可以在 push 与 pop 所在的文件中,显示实例化模板参数,告诉编译器模板参数的类型:

告诉编译器帮我实例化模板参数为int 的函数,但是如果我们用到了 Stack<double> 那么我们还需要在这个文件中显示实例化模板参数为 double的函数!

因此 在实际的编程中我们更喜欢将模板类的类成员函数的声明与定义放在同一个文件里面!有的人为这样的文件取名为 .hpp 文件用来标识这是一个模板类!

为什么在同一个文件里面实现定义与分离就不会报错呢?我们在cpp文件中使用这个类,都需要包含这个类的头文件,包含这个头文件之后,我们就在一个cpp文件里面同时有了函数的声明与定义。当我们使用这个模板类,肯定会传入模板参数,从而确定了模板参数的类型,编译过程中,那些定义的函数就知道了模板参数的类型,只需要根据模板参数的类型实例化函数即可!

4. 非类型模板参数

模板的参数不仅仅可以通过< typename T> 将类型参数化。还允许使用整形值充当模板参数!(这里的整形值指的是整形家族,例如 int,char,unsigned int 等)

这有什么作用呢?

现在需要你实现一个静态栈,并且要求多个静态栈的大小要不相同!你会怎么做呢?使用#define 栈的大小能解决问题嘛?显然#define 和上面的typename 陷入了一样的困境,当实例化多个时都无法实现我们的需求,那看看非类型模板参数是怎么做的吧:

template<typename T, size_t N>
class Stack
{
public:private:T _a[N];int _size;int _capacity;
};int main()
{Stack<int, 100> st1; // 空间大小为 100 的静态栈Stack<int, 10> st2; // 空间大小为 10 的静态栈}

其中那个 N 就是非类型的模板参数, 观察到 N 可以直接用来当作数组的大小。因此这个 N 是一个常量哦!不允许被修改。

下面补充一下 typename 的另一层用途:

我定义了一个类:List,然后 List<T> 中将 ListNode<T> typedef 一下。在类 B 中,我们尝试去取List<T> 中的 Node 来定义一个变量,发现报出了编译错误,这是为什么呢?

原因就在于:向模板类里面取东西,编译器无法确定你取的东西是一个类型还是一个对象(例如:静态成员变量),假设你取的是一个类型,那么 List<T>::Node _node;就不会报错;单如果你取的是一个对象,这条语句就是有问题的!所以为了明确你取的东西,需要加上typename告诉编译器,你取的是一个类型! 这个语法在我后面实现STL容器时会用到!

5. 模版的特化

来看下面的代码:我们实现了一个打印的函数模板,传入什么值就打印什么值,于是我们写出了这样的代码:

template<typename T>
void Print(const T& val)
{cout << val << endl;
}

打印都没有问题,但是我有这样一个需求,就是当你传入指针的时候,我希望打印的是指针指向的内容而不是打印指针本身,这个时候应该怎么做呢?这就要使用我们的模板特化了!

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结 果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板

5.1 模板的全特化

顾名思义全特化就是将模板参数全部都特化成具体的类型。

函数模板的特化步骤:

1. 必须要先有一个基础的函数模板

2. 关键字template后面接一对空的尖括号<>

3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型

4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。

比如上面的例子:我们将模板参数T特化,当传入指针类型的时候,我们打印指针指向的内容。

template<typename T>
void Print(const T& val)
{cout << val << endl;
}template<typename T>
void Print(T* val)
{cout << (*val) << endl;
}int main()
{Print(5);int a = 10;Print(&a);double b = 20.5;Print(&b);
}

注意:一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。 该种实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化 

我们来看看模板类的全特化:

template<class T1, class T2>
class Show
{
public:Show(){cout << "Show(T1, T2)" << endl;}
};template<>
class Show<int, int>
{
public:Show(){cout << "Show(int, int)" << endl;}
};template<>
class Show<int, double>
{
public:Show(){cout << "Show(int, double)" << endl;}
};int main()
{Show<double, double> s1;Show<int, int> s2;Show<int, double> s3;
}

我们看到我们写了特化之后就能针对指定的类型进行特殊处理了:

5.2 模板的偏特化

模板的偏特化,就是值针对一部分模板参数进行特化:

template<class T1, class T2>
class Show
{
public:Show(){cout << "Show(T1, T2)" << endl;}
};template<class T1>
class Show<T1, int>
{
public:Show(){cout << "Show(T1, int)" << endl;}
};template<class T1>
class Show<T1, double>
{
public:Show(){cout << "Show(T1, double)" << endl;}
};int main()
{Show<double, double> s1;Show<int, int> s2;Show<int, double> s3;
}

 总之,模板的特化能够使得我们更加方便的处理特殊化的情况。这一点在我们实现STL中的优先级队列会提到!

6. 模板总结

【优点】

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

2. 增强了代码的灵活性。

【缺陷】

1. 模板会导致代码膨胀问题,也会导致编译时间变长。其实这个问题不可避免,如果没有模板,那么就需要你手写这么多的代码了!

2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误。(这个是真的令人头大!)

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

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

相关文章

kubernetes(k8s)PVC

概念 PVC 的全称是&#xff1a;PersistentVolumeClaim&#xff08;持久化卷声明&#xff09;&#xff0c;PVC 是用户存储的一种声明&#xff0c;PVC 和 Pod 比较类似&#xff0c;Pod 消耗的是节点&#xff0c;PVC 消耗的是 PV 资源&#xff0c;Pod 可以请求 CPU 和内存&#x…

基于Kubernetes的Serverless PaaS稳定性建设万字总结

作者&#xff1a;许成铭&#xff08;竞霄&#xff09; 数字经济的今天&#xff0c;云计算俨然已经作为基础设施融入到人们的日常生活中&#xff0c;稳定性作为云产品的基本要求&#xff0c;研发人员的技术底线&#xff0c;其不仅仅是文档里承诺的几个九的 SLA 数字&#xff0c…

AI助手引领游戏创作革命

近期&#xff0c;Roblox 在其开发者大会&#xff08;RDC&#xff09;上宣布了一款新的对话式 AI 助手&#xff1a;RobloxAssistant。这款助手的本质是简化游戏制作难度&#xff0c;用自然语言代替编程。通过输入文字提示&#xff0c;创作者可以生成游戏场景、3D 模型等操作。该…

自动驾驶中的决策规划

参考: 【干货篇】轻舟智航&#xff1a;自动驾驶中的决策规划技术&#xff08;附视频回放 PPT 下载&#xff09; - AIQ 如图所示, 各模块介绍 定位模块主要负责解答的问题是“车现在在哪里”&#xff0c;是在道路上还是在路口&#xff0c;是在高架桥上还是在停车场里。 感知…

python随手小练

题目&#xff1a; 使用python做一个简单的英雄联盟商城登录界面 具体操作&#xff1a; print("英雄联盟商城登录界面") print("~ * "*15 "~") #找其规律 a "1、用户登录" b "2、新用户注册" c "3、退出系统&quo…

jq弹窗拖动改变宽高

预览效果 <div classtishiMask><div class"tishiEm"><div id"coor"></div><div class"topNew ismove"><span class"ismove">提示</span><p onclick"closeTishi()"></p&…

计算机组成原理——基础入门总结(二)

上一期的路径&#xff1a;基础入门总结&#xff08;一&#xff09; 目录 一.输入输出系统和IO控制方式 二.存储系统的基本概念 三.cache的基本概念和原理 四.CPU的功能和基本结构 五.总线概述 一.输入输出系统和IO控制方式 IO设备又可以被统一称为外部设备~ IO接口&…

Python 根据身高体重计算体质(BMI)指数

""" 根据身高体重计算体质(BMI)指数知识点&#xff1a;1、计算公式&#xff1a;体质指数(BMI) 体重(KG) / (身高(M) * 身高(M))2、变量类型转换3、运算符幂**&#xff0c;例如&#xff1a;3 ** 2 9 <> 3 * 34、更多的运用请参考&#xff1a;https://blo…

【2023全网最全最火】Selenium WebDriver教程(建议收藏)

在本教程中&#xff0c;我将向您介绍 Selenium Webdriver&#xff0c;它是当今市场上使用最广泛的自动化测试框架。它是开源的&#xff0c;可与所有著名的编程语言&#xff08;如Java、Python、C&#xff03;、Ruby、Perl等&#xff09;一起使用&#xff0c;以实现浏览器活动的…

【Hierarchical Coverage Path Planning in Complex 3D Environments】

Hierarchical Coverage Path Planning in Complex 3D Environments 复杂三维环境下的分层覆盖路径规划 视点采样全局TSP 算法分两层&#xff0c;一层高级一层低级&#xff1a; 高层算法将环境分离多个子空间&#xff0c;如果给定体积中有大量的结构&#xff0c;则空间会进一步细…

为什么要选择Spring cloud Sentinel

为什么要选择Spring cloud Sentinel &#x1f34e;对比Hystrix&#x1f342;雪崩问题及解决方案&#x1f342;雪崩问题&#x1f342;.超时处理&#x1f342;仓壁模式&#x1f342;断路器&#x1f342;限流&#x1f342;总结 &#x1f34e;对比Hystrix 在SpringCloud当中支持多…

美创科技参编《数字政府建设与发展研究报告(2023)》 正式发布

9月14日&#xff0c;中国信息通信研究院云计算与大数据研究所牵头编制的《数字政府建设与发展研究报告&#xff08;2023&#xff09;》正式发布。 美创科技结合在政务数据安全领域的丰富实践经验&#xff0c;参与报告编写。 《数字政府建设与发展研究报告》 以“技术、业务、数…

ARM 汇编指令作业(求公约数、for循环实现1-100之间和、从SVC模式切换到user模式简单写法)

1、求两个数最大公约数 .text .globl _start_start:mov r0, #9mov r1, #15 Loop: 循环cmp r0,r1 比较r0和r1的大小beq stop 当r0和r1相等时&#xff0c;跳到stop标签subhi r0,r0,r1 r0-r1>0 时&#xff0c;证明r0>r1,将r0-r1的值赋给r0&…

近年来国内室内定位领域硕士论文选题的现状与趋势

目录 一、前言 二、选题的目的和意义 三、选题现状分析 四、选题趋势分析 一、前言 本博文采用了图表统计法分析了近5年来100余篇高被引室内定位领域硕士论文选题的现状&#xff0c;并从选题现状中得出了该领域选题的大致趋势。本文还通过分析该领域硕士毕业论文选题的现…

数字孪生技术如何提升工厂生产效率?

数字孪生技术是一项引领工业界数字化转型的创新力量。随着工业4.0时代的到来&#xff0c;制造业正经历着巨大的变革&#xff0c;数字孪生技术在这个变革中发挥了关键作用。它不仅仅是一种技术&#xff0c;更是一种理念&#xff0c;将现实世界与数字世界相结合&#xff0c;为工厂…

C++真的是 C加加

&#x1f4dd;个人主页&#xff1a;夏目浅石. &#x1f4cc;博客专栏&#xff1a;C的故事 &#x1f3e0;学习社区&#xff1a;夏目友人帐. 文章目录 前言Ⅰ. 函数重载0x00 重载规则0x01 函数重载的原理名字修饰 Ⅱ. 引用0x00 引用的概念0x01 引用和指针区分0x03 引用的本质0x04…

U盘有病毒插上电脑会感染吗?了解下U盘的病毒传播机制

U盘作为一种常见的移动存储设备&#xff0c;我们会经常使用它来传输和存储重要的文件。然而&#xff0c;有时可能会遇到文件被当作病毒误删除的情况&#xff0c;这给我们带来了不便和焦虑。好在&#xff0c;这里将向您介绍一些简单而有效的方法&#xff0c;帮助您恢复被误删除的…

vite自定义打包路径

修改vite.config.js 增加: build: { outDir:‘…/out’ }, base: ‘./’, 例子: // https://vitejs.dev/config/ export default defineConfig({plugins: [vue(),WindiCSS()],build: {outDir:../out},base: ./,server: {host:0.0.0.0,// https:{// cert: fs.readFi…

python装13的一些写法

一些当你离职后&#xff0c;让老板狂拍大腿的代码 1. any(** in ** for ** in **) 判断某个集合元素&#xff0c;是否包含某个/某些元素 代码&#xff1a; if __name__ __main__:# 判断 list1 中是否包含某个/某些元素list1 [1,2,3,4]a any(x in [5,4] for x in list1) 输…

buuctf-[网鼎杯 2020 朱雀组]phpweb

1.打开网站&#xff0c;吓我一跳 2.查看源代码&#xff0c;主要看到timezone&#xff0c;然后这个页面是五秒就会刷新一次 一开始去搜了这个&#xff0c;但是没什么用 3.使用bp抓包 会发现有两个参数&#xff0c;应该是用func来执行p 4.修改func和p file_get_contents&#…