C++笔记:模板

news2025/1/7 7:18:00

模板

为什么要学习模板编程

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

        为什么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.coloradmin.cn/o/1820687.html

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

相关文章

如何在Excel中快速找出含有多位小数的数字

在日常工作中&#xff0c;使用Excel处理数据是一项常见任务。然而&#xff0c;有时我们会遇到一些看似简单&#xff0c;却令人头疼的问题。例如&#xff0c;当我们在一个包含大量数据的列中发现某个数字的小数点位数过多时&#xff0c;如何快速找到这个数字&#xff1f;本文将介…

从零开始的<vue2项目脚手架>搭建:vite+vue2+eslint

前言 为了写 demo 或者研究某些问题&#xff0c;我经常需要新建空项目。每次搭建项目都要从头配置&#xff0c;很麻烦。所以我决定自己搭建一个项目初始化的脚手架&#xff08;取名为 lily-cli&#xff09;。 脚手架&#xff08;scaffolding&#xff09;&#xff1a;创建项目时…

高考志愿专业选择:计算机人才需求激增,人工智能领域成热门

随着2024年高考的落幕&#xff0c;数百万高三学生站在了人生新的十字路口&#xff0c;面临着一个重要的抉择&#xff1a;选择大学专业。这一选择不仅关乎未来四年的学习生涯&#xff0c;更可能决定一个人一生的职业方向和人生轨迹。在众多专业中&#xff0c;计算机相关专业因其…

8.12 面要素符号化综述

文章目录 前言面要素介绍总结 前言 本章介绍如何使用矢量面要素符号化说明&#xff1a;文章中的示例代码均来自开源项目qgis_cpp_api_apps 面要素介绍 地理空间的要素分为点、线和面&#xff0c;对应的符号也分三类&#xff1a;Marker Symbol、Line Symbol和Fill Symbol&…

智慧校园建造方针

在高校的现代化建造中&#xff0c;构建现代化的教育渠道、工作体系、网络信息,是数字化学校建造的方针。一起&#xff0c;数字化学校的全面性和安稳&#xff0c;是完成建造方针的要害。树立的数字化学校渠道,能够完成各资源信息的有效办理和传输。在21世纪的教育蓝图中&#xf…

【linux】给net/socket.c部分接口添加pr_info后运行情况

net/socket.c 合入文件及代码&#xff1a; https://gitee.com/r77683962/linux-6.9.0/commit/d9aca07352311a9c185cbc2d3c39894e02f10df3 开机后dmesg命令运行效果&#xff1a; 这也是一部分&#xff0c;不过从这里看出来&#xff0c;添加打印日志的地方不太好&#xff0c;另…

江协科技51单片机学习-0 购买套件

前言&#xff1a; 本文是根据哔哩哔哩网站上“江协科技51单片机”视频的学习笔记&#xff0c;在这里会记录下江协科技51单片机开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了江协科技51单片机教学视频和链接中的内容。 引用&#xff1a; 51单片机入门教程-2…

【C++】stack、queue模拟实现

&#x1f497;个人主页&#x1f497; ⭐个人专栏——C学习⭐ &#x1f4ab;点击关注&#x1f929;一起学习C语言&#x1f4af;&#x1f4ab; 目录 导读 1. stack和queue的底层 1.1 stack 1.2 queue 2. 什么是适配器 3. 常见适配器 4. stack具体实现 4.1 成员变量 4.2 …

Solr7.4.0报错org.apache.solr.common.SolrException

文章目录 org.apache.solr.common.SolrException: Exception writing document id MATERIAL-99598435990497269125316 to the index; possible analysis error: cannot change DocValues type from NUMERIC to SORTED_NUMERIC for field "opt_time"Exception writing…

震坤行坤合供应链荣获“2024 LOG低碳供应链物流-最具影响力品牌商”

震坤行坤合供应链荣获“2024 LOG低碳供应链物流-最具影响力品牌商” 近日&#xff0c;罗戈网在青岛举办了2024第三届低碳供应链&物流创新发展高峰论坛&#xff0c;此次峰会聚焦“物流碳中和&#xff0c;可持续供应链”这一核心议题&#xff0c;汇聚国内外双碳践行企业、低碳…

MyBatis 动态 SQL 的详细内容讲解

1. MyBatis 动态 SQL 的详细内容讲解 文章目录 1. MyBatis 动态 SQL 的详细内容讲解2. 准备工作3. if 标签4. where 标签5. trim 标签6. set 标签7. choose when otherwise 标签8. foreach 标签8.1 批量删除8.2 批量添加 9. SQL 标签与 include 标签10. 总结&#xff1a;11. 最…

文案提取小帮手轻松将视频为转文字!而且不限时长

作为一个自媒体的资深用户总在一个一个的敲字真的太慢了&#xff0c;而且很多创作者都知道追热点是和时间赛跑。如果你嫌弃自己手抄效率太低&#xff0c;看视频又嫌时间太长。 今天叫教你一个可以将视频转文字的工具&#xff0c; 这个工具就叫文案提取小帮手&#xff0c;而且…

企业如何平滑替换微软AD,构筑信创身份基座?

据统计&#xff0c;全球有超过91%的具规模企业将Microsoft Active Directory&#xff08;微软AD&#xff09;作为数字化身份的基础底座&#xff0c;其不仅为Windows系统、Exchange等应用提供统一认证与管理&#xff0c;还兼容了云桌面、EPR、OA等应用&#xff0c;应用范围广泛。…

python -- 异步、asyncio

文章目录 协程实现协成的方法greenlet实现协程yield 关键字asyncio async & await&#xff08;**重点**&#xff09; 协程的意义异步编程事件循环快速上手awaitTask对象asyncio.Future对象concurrent.futures.Future 对象 协程 协成不是操作系统提供的&#xff0c;是程序员…

超市陈列艺术:不仅仅是货品摆放,更是营销策略的体现

品类管理在门店落地的最直观表现就是单品的空间陈列管理&#xff0c;通过陈列细节的差异体现出门店的商品定位与策略。此文分析入木三分&#xff0c;值得学习。 在商品陈列的空间管理领域&#xff0c;不仅要考虑整体的空间陈列&#xff0c;也要对每个商品的空间陈列位置&#…

工作神器大合集

在当代的工作环境里&#xff0c;软件工具扮演了不可或缺的角色&#xff0c;它们的设计初衷就是为了提高工作的效率与质量。下面将推荐五款值得使用的工作效率软件&#xff1a; 1、亿可达 作为一款自动化工具&#xff0c;亿可达被誉为国内版的免费Zaiper。它允许用户无需编程知…

DIYGW可视化开发工具:微信小程序与多端应用开发的利器

一、引言 随着移动互联网的飞速发展&#xff0c;微信小程序以其轻便、易用和跨平台的特点受到了广泛关注。然而&#xff0c;微信小程序的开发相较于传统的H5网页开发&#xff0c;在UI搭建和交互设计上存在一定的挑战。为了应对这些挑战&#xff0c;开发者们一直在寻找更加高效…

Ubuntu20.04.6操作系统安装教程

一、VMware Workstation16安装 选择安装VMware Workstation&#xff0c;登录其官网下载安装包&#xff0c;链接如下&#xff1a; 下载 VMware Workstation Pro 下载后运行安装向导&#xff0c;一直Next即可。 二、Ubuntu镜像下载 ubuntu20.04 选择需要下载的镜像类型下载即…

CobaltStrike权限传递MSF

一、测试环境 操作系统&#xff1a; 1.VMware17 2.kali 6.1.0-kali5-amd64 3.Win10x64 软件&#xff1a; 1.cs4.0 2.metasploit v6.3.4-dev 二、测试思路 1.cs是一款渗透测试工具&#xff0c;但没有漏洞利用的模块&#xff0c;我们可以在拿到目标主机的权限后&#xff0c;将…

代码解读 | Hybrid Transformers for Music Source Separation[05]

一、背景 0、Hybrid Transformer 论文解读 1、代码复现|Demucs Music Source Separation_demucs架构原理-CSDN博客 2、Hybrid Transformer 各个模块对应的代码具体在工程的哪个地方 3、Hybrid Transformer 各个模块的底层到底是个啥&#xff08;初步感受&#xff09;&#xff1…