C++笔记:模板

模板

为什么要学习模板编程

        在学习模板之前,一定要有算法及数据结构的基础,以及重载,封装,多态,继承的基础知识,不然会出现看不懂,或者学会了没办法使用。

        为什么C++会有模板,来看下面的代码。

        add()第一版
#include <iostream>
#include <string>
using namespace std;

int add(int a, int b) {
    return a + b;
}

double add(double a, double b) {
    return a + b;
}

string add(string a, string b) {
    return a + b;
}

int main() {
    cout << add(1, 2) << endl;
    cout << add(2.1, 3.3) << endl;
    string a = "hello", b = "world";
    cout << add(a, b) << endl;

    return 0;
}

        当我们使用add函数时,不同的类型要去重载实现不同参数的add函数,那么有多少种相同类型进行相加,那么我们就要重载实现多少种add函数,那么就对于我们程序员来说这种方法就很麻烦,那么模板编程就可以帮我们避免这种麻烦。来看下面这段代码:

        add()第二版
#include <iostream>
#include <string>
using namespace std;


template<typename T>
T add(T a, T b) {
    return a + b;
}

int main() {
    cout << add(1, 2) << endl;
    cout << add(2.1, 3.3) << endl;
    string a = "hello", b = "world";
    cout << add(a, b) << endl;

    return 0;
}

        可以发现第一版我实现了3种add函数,而第二版我只实现了一种add函数,直接少写了很多重复的逻辑代码,这就是为什么需要学习模板编程。

模板编程(泛型编程)

程序 = 算法 + 数据结构

数据结构:可以存储任意类型

算法:能够操作存储任意类型数据的数据结构

        例如vector容器它是能够存储任意类型的顺序表,sort函数可以对任意类型的顺序表进行排序并且还可以自定义排序规则,而这两个例子都是通过模板编程进行实现。

        模板对于我们程序员来说是一种工具,而这种工具我们可以在程序设计中把任意类型进行抽象出来。

模板函数

        例如文章开头的例子,我不知道add中需要传入的参数是什么,那么也不知道具体的返回值是什么,那么我们就需要利用模板编程进行抽象出来一个模板函数,进行可以对任意类型进行处理的函数。

        那么我拿第二版的add函数进行继续探索模板函数,现在我有一个需求是

cout << add(1, 1.2) << endl

        传入的参数是不同的,那么我该如何设计,如下:

#include <iostream>
#include <string>
using namespace std;


//template是用来引入模板参数的关键字
//typename先理解为定义一个任意类型的变量
template<typename T, typename U>
T add(T a, U b) {
    return a + b;
}

int main() {
    cout << add(1, 2) << endl;
    cout << add(2.1, 3.3) << endl;
    string a = "hello", b = "world";
    cout << add(a, b) << endl;
    cout << add(1, 1.2) << endl;// 结果 2
    cout << add(1.2, 1) << endl;// 结果 2.2
    return 0;
}

        那我就加两个模板任意参数就可以了,这样就可以不发生报错了,但是又有一个问题了,我,我把add(1, 1.2)中的参数进行调换了位置,他的结果会不一样,因为我的返回值是T类型,那么对应的就是第一个参数的类型,如果第一个参数是1那么返回值类型就是int,第一个参数是1.2那么返回值类型就是float。

        那么这里会引入一个新的关键字decltype

        decltype:

        这里我们用到的是第二点,也就是判断复杂表达式的结果是什么类型。

#include <iostream>
#include <string>
using namespace std;


//template是用来引入模板参数的关键字
//typename先理解为定义一个任意类型的变量
template<typename T, typename U>
//T和U是任意类型
//不管什么类型都有一个默认构造
decltype(T() + U()) add(T a, U b) {
    return a + b;
}

int main() {
    //int也有可以当作类来使用
    //所以任意类型都有构造函数
    int num = int(1);
    cout << add(1, 2) << endl;
    cout << add(2.1, 3.3) << endl;
    string a = "hello", b = "world";
    cout << add(a, b) << endl;
    cout << add(1, 1.2) << endl;
    cout << add(1.2, 1) << endl;
    return 0;
}

        问题又来了,如果我传入T类型,而这个T类型默认构造被删除了,那这个代码是会发生报错的,那么如何去解决呢,那么这里又引出了一个新的概念返回值后置:

        这里会用到一个新的关键字auto

        

#include <iostream>
#include <string>
using namespace std;


//template是用来引入模板参数的关键字
//typename先理解为定义一个任意类型的变量
template<typename T, typename U>
//在原来返回值的位置写一个auto关键字
//auto是用来推到后置返回值的类型
//因为-> 在参数列表后面,根据代码的执行顺序,那么a和b就可以进行使用
//所以可以用decltype进行判断a + b的返回类型
//然后传给auto进行推断,最后确定返回值类型
auto add(T a, U b) -> decltype(a + b){
    return a + b;
}

int main() {
    //int也有可以当作类来使用
    //所以任意类型都有构造函数
    int num = int(1);
    cout << add(1, 2) << endl;
    cout << add(2.1, 3.3) << endl;
    string a = "hello", b = "world";
    cout << add(a, b) << endl;
    cout << add(1, 1.2) << endl;
    cout << add(1.2, 1) << endl;
    return 0;
}
        模板类:

        在下面这份代码中,我利用模板实现了一个模板类,而这个模板类是一个简单对于数组的实现:

#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;

//利用模板创建一个模板类
template<typename T>
class A {
public : 
    A(int n = 10) : n(n) {
        this->arr = new T[n];
    }
    T &operator[](int ind) {
        if (ind < 0 || ind > n) return __end;
        return arr[ind];
    }
    void rand_arr() {
        for (int i = 0; i < n; i++) {
            int x = rand() % 100;
            arr[i] = x - (T)x / 10;
        }
        return ;
    }
    ~A() {
        delete arr;
    }
    //在声明友元函数时,也要加上模板的关键字引入和模板参数
    template<typename U>
    friend ostream &operator<<(ostream &out, const A<U> &obj);
private :
    T *arr;
    int n;
    T __end;
};
//重载输出时也需要利用到模板编程
template<typename T>
ostream &operator<<(ostream &out, const A<T> &obj) {
    for (int i = 0; i < obj.n; i++) {
        cout << obj.arr[i] << " ";
    }
    return out;
}


int main() {
    srand(time(0));
    //通过模板类创建对象时
    //需要确定模板类型中的模板参数类型
    A<int> a;
    a.rand_arr();
    A<double> b;
    b.rand_arr();
    cout << a << endl;
    cout << b << endl;
    return 0;
}

        认识了大概的模板类进行如何使用我们继续往下探索:

        模板特化与偏特化

        模板特化

        现在假如我对于add函数进行使用时,我需要对int类型特殊处理,也就是返回值结果需要加2,那么就需要用到模板函数的特化:

#include <iostream>
#include <string>
using namespace std;


//template是用来引入模板参数的关键字
//typename先理解为定义一个任意类型的变量
template<typename T, typename U>
//在原来返回值的位置写一个auto关键字
//auto是用来推到后置返回值的类型
//因为-> 在参数列表后面,根据代码的执行顺序,那么a和b就可以进行使用
//所以可以用decltype进行判断a + b的返回类型
//然后传给auto进行推断,最后确定返回值类型
auto add(T a, U b) -> decltype(a + b){
    return a + b;
}

//由于是特化,那么我们就确定了参数
//就不需要传入模板参数了,但是也得需要template关键字引入
template<> 
int add(int a, int b) {
    return a + b + 2;  
}

int main() {
    //int也有可以当作类来使用
    //所以任意类型都有构造函数
    int num = int(1);
    cout << add(1, 2) << endl; //那么这里的结果应该是 1 + 2 + 2 = 5
    cout << add(2.1, 3.3) << endl;
    string a = "hello", b = "world";
    cout << add(a, b) << endl;
    cout << add(1, 1.2) << endl;
    cout << add(1.2, 1) << endl;
    return 0;
}
        模板偏特化

        如下我传入的参数是指针类型时,我该如何进行处理

int a = 10, b = 20;
cout << add(&a, &b) << endl;

        这里我们就会用到偏特化:

#include <iostream>
#include <string>
using namespace std;


//template是用来引入模板参数的关键字
//typename先理解为定义一个任意类型的变量
template<typename T, typename U>
//在原来返回值的位置写一个auto关键字
//auto是用来推到后置返回值的类型
//因为-> 在参数列表后面,根据代码的执行顺序,那么a和b就可以进行使用
//所以可以用decltype进行判断a + b的返回类型
//然后传给auto进行推断,最后确定返回值类型
auto add(T a, U b) -> decltype(a + b){
    return a + b;
}

//由于是特化,那么我们就确定了参数
//就不需要传入模板参数了,但是也得需要template关键字引入
template<> 
int add(int a, int b) {
    return a + b + 2;  
}

//模板偏特化版本
//当传入的参数是指针类型时的版本
template<typename T, typename U>
auto add(T *a, U *b) -> decltype(*a + *b) {
    cout << "this is piantehua" << endl;
    return *a + *b;
}

int main() {
    //int也有可以当作类来使用
    //所以任意类型都有构造函数
    int num = int(1);
    cout << add(1, 2) << endl; //那么这里的结果应该是 1 + 2 + 2 = 5
    cout << add(2.1, 3.3) << endl;
    string a = "hello", b = "world";
    cout << add(a, b) << endl;
    cout << add(1, 1.2) << endl;
    cout << add(1.2, 1) << endl;

    int c = 10, d = 20;
    cout << add(&c, &d) << endl;
    return 0;
}
可变参数模板
typename

现在来说一下typename的作用:

typename的作用就是,声明后面的表达式是一个类型。

        可变参模板函数        

        现在我要实现一个函数叫做print,他可以打印所有的参数,并且参数的个数是任意的:
        

#include<iostream>
using namespace std;
//递归出口,打印最后一个参数
//也就是偏特化版本,只有一个参数时的print
template<typename T>
void print(T a) {
    cout << a << endl;
    return ;
}

//一个模板参数代表当前层数的参数的类型, ARGS代表后续跟着的参数的类型
template<typename T, typename ...ARGS>
void print(T a, ARGS ...args) {
    //打印当前层的的第一个参数
    cout << a << " ";
    //递归到下一层,去打印下一个参数
    print(args...);
    return ;
}


int main() {
    int a = 10;
    print(a, 12.3, "hello", '0', "gg");
    return 0;
}

        那么最终只有一个参数时,会调用的print的偏特化版本只有一个参数时,进行递归结束。

        可变参模板类

        下面实现一个类,这个类模板的参数个数是不定的,并且演示了如何获取每一层中分别对应的变参类型:

#include<iostream>
using namespace std;

//template引入当前类型中的第一个参数T
//然后引入变参列表ARGS
template<typename T, typename ...ARGS>
class ARG {
public :
    //将T类型重命名为getT
    typedef T getT;
    //将下个一个类重命名为next_T
    typedef ARG<ARGS...> next_T;
};
//类的递归出口,只有一个参数时的模板类
template<typename T> 
class ARG<T> {
public :
    typedef T getT;
};


int main() {
    //取到第一层中的int
    ARG<int, double, long long, float>::getT a;
    //取到第二层中的double
    ARG<int, double, long long, float>::next_T::getT b;
    //取到第三层中的long long
    ARG<int, double, long long, float>::next_T::next_T::getT c;
    //取到第四层中的float
    ARG<int, double, long long, float>::next_T::next_T::next_T::getT e;
    cout << sizeof(a) << endl;
    cout << sizeof(b) << endl;
    cout << sizeof(c) << endl;
    cout << sizeof(e) << endl;
    return 0;
}

        下面通过上面的代码,在提出一个需求:

        之前我取最后一层的类型需要这样去取,那如果我有10个,100个参数,就需要去写,

n - 1个next_T吗,所以我需要进行迭代更新一下:

ARG<int, double, float, char>::next_T::next_T::next_T::getT e;

        改完之后:

ARG<3,int, double, float, char>::getT e;

        数字3就代表我要取的对应的类型,那么如何实现看下面代码:

#include<iostream>
using namespace std;

//基础模板声明
//因为进行偏特化处理时或者特化处理时需要基础模板
template<int n, typename ...ARGS>
class ARG_imag;

//偏特化模板递归
template<int n, typename T, typename ...Rest>
class ARG_imag<n, T, Rest...>{
public :
    //进行递归,直到找到需要的层数
    typedef typename ARG_imag<n - 1, Rest...>::thisT thisT;
};

//偏特化模板递归出口
//当n等于0时,说明到达需求层数,进行递归结束
template<typename T, typename ...Rest>  
class ARG_imag<0, T, Rest...> {
public :
    typedef T thisT;
};

//进行封装
//用户调用的是ARG
template<int n, typename ...ARGS>
class ARG {
public :
    typedef typename ARG_imag<n, ARGS...>::thisT getT;
};


int main() {
    //取到第一层中的int
    ARG<0, int, double, float, char>::getT a = 123;
    //取到第二层中的double
    ARG<1, int, double, float, char>::getT b = 12.3;
    //取到第三层中的float
    ARG<2, int, double, float, char>::getT c = 123.3;
    //取到第四层中的char
    ARG<3, int, double, float, char>::getT e = 'c';
    cout << "sizeof(a) = " << sizeof(a) << " a = " << a << endl;
    cout << "sizeof(b) = " << sizeof(b) << " b = " << b << endl;
    cout << "sizeof(c) = " << sizeof(c) << " c = " << c << endl;
    cout << "sizeof(e) = " << sizeof(e) << " e = " << e << endl;
    return 0;
}
          模板中的引用重叠

        

        C++11 标准引入了 "引用折叠" 规则,这个规则定义了在模板实例化过程中,不同类型的引用组合如何被折叠成最终的引用类型。引用折叠规则如下:

  • T & & -> T &
  • T & && -> T &
  • T && & -> T &
  • T && && -> T &&

        也就是说传入的类型是右值引用,并且参数中也是右值引用,T类型才是右值引用,否则是左值引用。

        下面带入代码演示:
 

#include<iostream>
using namespace std;

#define TEST(func, n) {\
    printf("%s(%s) ", #func, #n);\
    func(n);\
}

template<typename T>
void func(T &&a) {
    //假如T为int & 那么a的类型就为int & &&然后通过折叠得到为int &
    if (is_same<T &, decltype(a)>::value) {
        cout << " is left" << endl;
    //假如T为int && 那么a的类型就为int&& &&然后通过折叠得到为int &&
    } else if (is_same<T &&, decltype(a)>::value) {
        cout << " is right" << endl;
    } else {
        cout << " is a type" << endl;
    }
    return ;
}




int main() {
    int n = 123; 
    int& l = n;
    int&& r = 123;
    TEST(func, n); //n为左值, T类型就为int &
    TEST(func, l); //l为左值, T类型就为int &
    TEST(func, r); //r为左值, T类型就为int &
    TEST(func, 123); //123为右值, T类型就为int &&
    TEST(func, move(n)); //move(n)为右值, T类型就为int &&
    return 0;
}

        那么又有新问题出现了,传入的类型为引用的类型,那么该如何取获取他的类型呢,如下:

        

std::remove_reference 是一个类型特征工具,它能从一个类型中去除引用,并返回无引用的类型。例如:

  • 对于 int&std::remove_reference<int&>::typeint

  • 对于 int&&std::remove_reference<int&&>::type 也是 int

#include<iostream>
using namespace std;


template<typename T>
void func(T &&t) {
    //通过remove_reference,去掉T的引用获取到他的类型
    typedef typename remove_reference<T>::type a;
    if (is_same<a, int>::value) cout << "a type is int" << endl;
    if (is_same<a, char>::value) cout << "a type is char" << endl;
    if (is_same<a, double>::value) cout << "a type is double" << endl;
    if (is_same<a, float>::value) cout << "a type is float" << endl;
    if (is_same<a, string>::value) cout << "a type is string" << endl;
}
    
int main() {
    int a;
    string str = "hello";
    func(a);
    func(str);
    func('a');
    func(3.14);
    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

听说前端都是切图仔,所以学了PS

PS 从零开始-基础篇 什么话都不想说了&#xff0c;前端以死后端已死&#xff0c;毁灭即是新生&#xff0c;我要开始追梦了&#xff0c; 从小就希望&#xff0c;制作一款自己的游戏&#x1f3ae;去学了编程&#xff0c;了解了&#xff1a;Java、C#、前端... 不小心入了web领域…

idea 配置文件中文乱码

再进行springboot项目开发时发现新建的配置文件中文注释乱码&#xff0c;如下: 处理办法: 1、打开idea&#xff0c;在 File 中找到 Settings,如下图 2、搜索 encodings 找到 File Encodings&#xff0c;如下图 3、将上图中圈上的地方全部改为 UTF-8 编码最后点击 Apply 应用即…

10 SpringBoot 静态资源访问

我们在开发Web项目的时候&#xff0c;往往会有很多静态资源&#xff0c;如html、图片、css等。那如何向前端返回静态资源呢&#xff1f; 以前做过web开发的同学应该知道&#xff0c;我们以前创建的web工程下面会有一个webapp的目录&#xff0c;我们只要把静态资源放在该目录下…

后端跨域问题的处理

问题描述 在做前后端分离的项目时&#xff0c;很有可能会遇到这样一种情况&#xff1a; 就是在游览器中请求后端的接口&#xff0c;出现了 CORS error 错误 报错信息如下&#xff1a; Access to XMLHttpRequest at http://localhost:8860/user/auth/login from origin http:…

MySQL 用户权限管理:授权、撤销、密码更新和用户删除(图文解析)

目录 前言1. 授予权限2. 撤销权限3. 查询权限4. Demo 前言 公司内部的数据库权限一般针对不同人员有不同的权限分配&#xff0c;而不都统一给一个root权限 1. 授予权限 授予用户权限的基本命令是GRANT 可以授予的权限种类很多&#xff0c;涵盖从数据库和表级别到列和存储过…

郑州申请大气污染防治乙级资质,这些材料必不可少

在郑州申请大气污染防治乙级资质时&#xff0c;以下材料是必不可少的&#xff1a; 一、企业基础资料&#xff1a; 企业法人营业执照副本&#xff1a;需清晰&#xff0c;且在有效期内[1][2]。企业章程&#xff1a;提交企业章程的扫描件或复印件&#xff0c;以展示企业的组织结构…

183.二叉树:二叉搜索树中的众数(力扣)

代码解决 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* Tre…

农资投入品系统架构:数字化农业的技术支撑与创新

在当今数字化时代&#xff0c;农业领域也在迅速迈向数字化和智能化的新阶段。农资投入品系统作为农业生产的重要支撑&#xff0c;其系统架构的设计与创新对于提高农业生产效率、保障粮食安全具有重要意义。本文将探讨农资投入品系统架构的设计原则、核心模块以及未来发展趋势。…

实例化游戏物体的实例(生成游戏物体)

一、实例1&#xff1a;实例化 1、准备工作&#xff1a;制备预制体&#xff0c;命名。如Circle 2、Create Empty&#xff0c;名字自取。如&#xff1a;CirclePrefab 3、给CirclePrefab添加Test.cs public GameObject CirclePrefab; // 预制体变量&#xff0c;用于存储Circle预…

湖南安全技术职业学院校长邓德艾一行到我中心考察交流

6月4日上午&#xff0c;湖南安全技术职业学院校长邓德艾一行莅临方班网安人才教育服务中心交流研讨&#xff0c;一方面是访企拓岗&#xff0c;另一方面是针对产教融合、政校企合作人才培养展开深入研讨。为将本次研讨进行得更深入全面&#xff0c;方班网安人才教育服务中心邀请…

C++17并行算法与HIPSTDPAR

C17 parallel algorithms and HIPSTDPAR — ROCm Blogs (amd.com) C17标准在原有的C标准库中引入了并行算法的概念。像std::transform这样的并行版本算法保持了与常规串行版本相同的签名&#xff0c;只是增加了一个额外的参数来指定使用的执行策略。这种灵活性使得已经使用C标准…

AI数字人的开源解决方案

目前&#xff0c;国内外已经涌现出一些优秀的数字人开源解决方案&#xff0c;这些解决方案为开发者提供了构建数字人应用的工具和基础设施。以下是一些比较知名的数字人开源解决方案。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1…

Sklearn中逻辑回归建模

分类模型的评估 回归模型的评估方法&#xff0c;主要有均方误差MSE&#xff0c;R方得分等指标&#xff0c;在分类模型中&#xff0c;我们主要应用的是准确率这个评估指标&#xff0c;除此之外&#xff0c;常用的二分类模型的模型评估指标还有召回率&#xff08;Recall&#xff…

振弦采集仪在隧道工程中的安全监测与控制研究

振弦采集仪在隧道工程中的安全监测与控制研究 隧道工程的安全监测与控制是保障隧道施工和运营安全的重要工作。隧道工程常面临的问题包括地层变形、地下水位变化、地震影响等&#xff0c;这些问题对隧道结构的安全性和使用寿命有着重要影响。因此&#xff0c;隧道工程中的安全…

JVM性能优化案例:减少对象频繁创建

JVM性能优化案例&#xff1a;减少对象频繁创建 案例背景 某金融应用系统在处理大量并发交易时&#xff0c;响应时间过长&#xff0c;并且有时出现内存溢出&#xff08;OutOfMemoryError&#xff09;的问题。经过分析&#xff0c;发现问题主要出在频繁的对象创建和较差的内存管…

OpenCV查找图像中的轮廓并且展示

1、查找轮廓随机用不同的颜色画出 import cv2 import numpy as npdef get_contour_colors(num_contours):# 定义颜色表 (BGR 格式)colors [(255, 0, 0),(255, 50, 0),(255, 100, 0),(255, 150, 0),(255, 200, 0),(255, 255, 0),(200, 255, 0),(150, 255, 0),(100, 255, 0),(5…

Linux常⽤服务器构建-ssh和scp

目录 1.ssh <1>ssh介绍 <2>安装ssh A.安装ssh服务器 B.远程登陆 <3>使⽤ssh连接服务器 2.scp 本地⽂件复制到远程&#xff1a; 本地⽬录复制到远程&#xff1a; 远程⽂件复制到本地&#xff1a; 远程⽬录复制到本地&#xff1a; 1.ssh <1>…

【LLM之RAG】Self-RAG论文阅读笔记

研究背景 尽管大型语言模型&#xff08;LLM&#xff09;展示出了显著的能力&#xff0c;但它们在生成回答时经常包含事实错误&#xff0c;因为它们仅依赖于封装在模型中的参数知识。增强型检索生成&#xff08;Retrieval-Augmented Generation, RAG&#xff09;是一种方法&…

leetcode695 岛屿的最大面积

题目 给你一个大小为 m x n 的二进制矩阵 grid 。 岛屿 是由一些相邻的 1 (代表土地) 构成的组合&#xff0c;这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0&#xff08;代表水&#xff09;包围着。 岛屿的面积是岛上值…

ubuntu18.04离线源制作

给客户部署有时需要纯内网环境&#xff0c;那这样就连不了网络。 一些包就下载不下来&#xff0c;而大家都知道用deb离线安装是非常麻烦的&#xff0c;各种依赖让你装不出来。 这里教大家打包源。 我准备2台机器&#xff0c;42和41 42可以联网&#xff0c;41不能联网。我想在…