首页 > 编程语言 >C++刷怪笼(6)模板初阶

C++刷怪笼(6)模板初阶

时间:2024-09-19 19:49:49浏览次数:3  
标签:right 函数 int 刷怪 C++ 编译器 初阶 模板 left

1.前言

在学习C++模板之前,我们会被同种函数的不同数据类型的繁琐写法而折磨,今天我们进入对模板的学习,来进一步的感受C++为我们今后的编程学习和工作所带来的便利。

2.模板

2.1泛型编程

我们应该如何去实现一个所有数据类型通用的函数?

void Swap(int& left, int& right)
{
    int temp = left;
    left = right;
    right = temp;
} 

void Swap(double& left, double& right)
{
    double temp = left;
    left = right;
    right = temp;
} 

void Swap(char& left, char& right)
{
    char temp = left;
    left = right;
    right = temp;
}

使用函数重载虽然可以实现,但是有一下几个不好的地方:
1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增
加对应的函数
2. 代码的可维护性比较低,一个出错可能所有的重载均出错
那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?
 

如果在C++中,也能够存在这样一个模具,通过给这个模具中填充不同材料(类型),来获得不同材料的铸件(即生成具体类型的代码),那将会节省许多时间。巧的是前人早已将树栽好,我们只需在此乘凉。


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

模板又分为:函数模板和类模板

2.2函数模板

2.2.1函数模板概念

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

2.2.2函数模板格式

template<typename T1, typename T2, ......, typename Tn>

返回值类型 函数名(参数列表){}

例如:

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

 注意:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)

2.2.3函数模板的原理

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

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

2.2.4函数模板的实例化

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

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

template<class T>
T Add(const T& left, const T& right)
{
    return left + right;
} 
int main()
{
    int a1 = 10, a2 = 20;
    double d1 = 10.0, d2 = 20.0;
    Add(a1, a2);
    Add(d1, d2);
/*
该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型
通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有
一个T,
编译器无法确定此处到底该将T确定为int 或者 double类型而报错
注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要
背黑锅
Add(a1, d1);
*/
    // 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化
    Add(a, (int)d);
    return 0;
}


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

int main(void)
{
    int a = 10;
    double b = 20.0;

    // 显式实例化
    Add<int>(a, b);

    return 0;
}

如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。

2.2.5模板参数的匹配原则

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

// 专门处理int的加法函数
int Add(int left, int right)
{
    return left + right;
} 
// 通用加法函数
template<class T>
T Add(T left, T right)
{
    return left + right;
} 
void Test()
{
    Add(1, 2); // 与非模板函数匹配,编译器不需要特化
    Add<int>(1, 2); // 调用编译器特化的Add版本
}

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

// 专门处理int的加法函数
int Add(int left, int right)
{
    return left + right;
} 
// 通用加法函数
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{
    return left + right;
} 
void Test()
{
    Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化
    Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
}

3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

2.3类模板

2.3.1类模板的定义格式

template<class T1, class T2, ..., class Tn>
class 类模板名
{
    // 类内成员定义
};
#include<iostream>
using namespace std;

// 类模版
template<typename T>
class Stack
{ 
public:
    Stack(size_t capacity = 4)
    {
        _array = new T[capacity];
        _capacity = capacity;
        _size = 0;
} 
    void Push(const T& data);
private:
    T* _array;
    size_t _capacity;
    size_t _size;
};

// 模版不建议声明和定义分离到两个文件.h 和.cpp会出现链接错误
template<class T>
void Stack<T>::Push(const T& data)
{
    // 扩容
    _array[_size] = data;
    ++_size;
} 
int main()
{
    Stack<int> st1; // int
    Stack<double> st2; // double

    return 0;
}

2.3.2类模板的实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化  类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

// Stack是类名,Stack<int>才是类型

Stack<int> st1; // int
Stack<double> st2; // double

3.小结

本文带着读者对模板有了一定的认识,后面我们还将继续对模板进行学习,大家巩固基础,稳中求进,一直进步,最后别忘了点赞加关注,我会一直伴随着你们前进的脚步的,我们下期再见。
 


 


 

标签:right,函数,int,刷怪,C++,编译器,初阶,模板,left
From: https://blog.csdn.net/cklshidad666/article/details/142367539

相关文章

  • 【c++基础知识——&引用的深度理解】
    C++引用深度理解对于一个函数来说,传值和传引用,在函数功能上没有区别,但在性能和副作用方面有显著差异。传值当按值传递参数时,函数会创建参数的一个副本。这样做的好处是函数内部对参数的修改不会影响原始变量,但缺点是对于大对象来说,拷贝操作会带来性能开销。传引用......
  • 深度长文:揭开C/C++三目运算符的全部秘密,助你写出更优雅的代码(上)
    在编程中,简洁和高效是程序员永恒追求的目标。当我们面对条件判断时,通常第一反应是使用if-else语句——这是最为常见的选择。然而,C和C++中还有一种非常简洁优雅的条件判断方式——三目运算符(TernaryOperator)。也许你曾经在一些代码中见到它,简短的?:语法,但却不知道它的工作原理......
  • C++Builder11的静态连接问题的解决
    1、问题用C++Builder11写了一个小程序,想将所有的运行包放在一个exe文件中,方便分发。但就是找不到原来版本中的Static-LinkC++RuntimeLibrary选项。2、经历(1)选择菜单project-options-C++linker去掉LinkwithDynamicRTL右边的√去掉>LinkwiththeDelphiRuntimeLibra......
  • 南沙C++信奥老师解一本通题 1357:车厢调度(train)
    ​ 【题目描述】有一个火车站,铁路如图所示,每辆火车从A驶入,再从B方向驶出,同时它的车厢可以重新组合。假设从A方向驶来的火车有n节(n≤1000),分别按照顺序编号为1,2,3,…,n。假定在进入车站前,每节车厢之间都不是连着的,并且它们可以自行移动到B处的铁轨上。另外假定车站C可以停放任......
  • C++如何在main函数之前执行自定义操作
    目录一.前言二.利用全局变量的初始化机制1.利用构造函数2.用函数结果对全局变量赋值三.gcc可以利用__attribute__四.其他一.前言我们知道C++程序在main函数运行之前会先执行一些动作,比如一系列初始化动作,那么我们怎么让C++程序在main函数运行前执行一些自定义函数呢......
  • C++ 面试模拟02
    第一部分:基础知识什么是拷贝构造函数和赋值运算符?它们之间有什么区别?在C++中,const关键字的作用是什么?有哪些常见用法?C++中的内存管理机制是怎样的?如何避免内存泄漏?虚函数(virtualfunction)的作用是什么?虚函数表(vtable)是如何工作的?第二部分:面向对象编程什么是多态性?C++中......
  • C++ 逆向之 main 函数的查找
    在整个程序的逆向分析过程中,寻找main函数是逆向分析过程的第一步,程序的主要逻辑从这里展开。这里面涉及到两个概念:用户入口(UserEntryPoint)和应用程序入口(ApplicationEntryPoint)。用户入口用户入口是开发者编写的用于程序开始的函数。对于大多数C/C++程序而言,这个入......
  • c++1095: 时间间隔(多实例测试) (字符串和字符以及数字的转换)
    问题描述:题目描述从键盘输入两个时间点(24小时制),输出两个时间点之间的时间间隔,时间间隔用“小时:分钟:秒”表示。要求程序定义如下两个函数,并在main()中调用这两个函数实现相应的功能/*三个形参分别为为用于表示一个时间点的时、分、秒,函数返回对应的秒。*/int HmsToS(int......
  • C++-练习-41
    题目:编写一个程序,它打开一个文本文件,逐个字符地读取该文件,知道到达文件末尾,然后指出该文件中包含多少个字符。(包含空格)源代码:#include<iostream>#include<fstream>intmain(){ usingnamespacestd; charch; intch_num=0; ifstreamfin; fin.open("people.......
  • C++-练习-42
    题目:编写一个程序,记录捐献给"维护合法权利团队"的资金。该程序要求用户输入捐献者数目,然后要求用户输入每一个捐献者的姓名和款项。这些信息被存在一个动态分配的结构数组中。每个结构有两个成员:用来存储姓名的字符数组和用力啊存储款项的double成员。读取所有的数据后,程序将......