C++提高编程---模板---类模板

目录

一、类模板

1.模板

2.类模板的作用

3.语法

4.声明

二、类模板和函数模板的区别

三、类模板中成员函数的创建时机

四、类模板对象做函数参数

五、类模板与继承

六、类模板成员函数类外实现

七、类模板分文件编写

八、类模板与友元

九、类模板案例


一、类模板

1.模板

模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。

模板是一种对类型进行参数化的工具;

通常有两种形式:函数模板和类模板;

函数模板针对仅参数类型不同的函数;

类模板针对仅数据成员和成员函数类型不同的类。

使用模板的目的就是能够让程序员编写与类型无关的代码。比如编写了一个交换两个整型int 类型的swap函数,这个函数就只能实现int 型,对double,字符这些类型无法实现,要实现这些类型的交换就要重新编写另一个swap函数。使用模板的目的就是要让这程序的实现与类型无关,比如一个swap模板函数,即可以实现int 型,又可以实现double型的交换。模板可以应用于函数和类。

注意:模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。

2.类模板的作用

建立一个通用类,类中的成员  数据类型可以不具体制定,用一个虚拟的类型来代表

3.语法

template <typename  T>

4.声明

template  -- 声明创建模板

typename  --  表明其后的符号是一种数据类型,可以用clss代替

T      --  通用的数据类型,名称可以替换,通常为大写字母

示例

#include<iostream>

using namespace std;

// 类模板

template<class name_type,class age_type>

class person

{

public:

    person(name_type name,age_type age)

    {

        this->name=name;

        this->age=age;

    }

    void show()

    {

        cout<<"姓名:"<<this->name<<"\t年龄:"<<this->age<<endl;
    
    }

 

    name_type name;

    age_type age;

};

void test01()

{

    // 类型参数化

    person<string,int> p1("Ton",89);

    p1.show();

}

int main()

{

    test01();

    return 0;

}

运行结果:

总结:类模板和函教模板语法相似。在声明模板template后面加类,此类称为类模板

二、类模板和函数模板的区别

区别:

  1. 类模板没有自动类型推导的使用方式
  2. 类模板在模板参数列表中可以有默认参数

示例:

#include<iostream>

using namespace std;

// 类模板

template<class name_type,class age_type  = int> // 可以指定某一个通用类型具体的类型

class person

{

public:

    person(name_type name,age_type age)

    {

        this->name=name;

        this->age=age;

    }

    void show()

    {

        cout<<"姓名:"<<this->name<<"\t年龄:"<<this->age<<endl;

    }

 

    name_type name;

    age_type age;

};

void test01()

{

    // 自动类型推导不行
    
    // person p1("TON",23);

    // 只能用指定类型

    person<string,int> p1("Ton",89);

    p1.show();

}

void test02()

{

    // 类模板在模板参数列表中可以有默认值

    person<string> p2("Jack",90);

    p2.show();

}

int main()

{

    test01();

    test02();

    return 0;

}

运行结果:

三、类模板中成员函数的创建时机

类模板中成员函数和普通类中成员函数创建时机的区别:

  1. 普通类中的成员函数一开始就可以创建
  2. 类模板中的成员函数是在调用的时候才创建的

示例:

#include<iostream>

using namespace std;

class person1

{

public:

    void showperson1()

    {

        cout<<"person1的调用"<<endl;

    }

};

class person2

{

public:

    void showperson2()

    {

        cout<<"person2的调用"<<endl;

    }

};

template<class T>

class my_class

{

public:

    T obj;

 

    // 类模板中的成员函数

    //   在运行前都不会创建这两个成员函数

    void func1()

    {

        obj.showperson1();

    }

 

    void func2()

    {

        obj.showperson2();

    }

};

 

void test01()

{

    my_class<person1> m;

    m.func1();

    //m.func2();  运行出错,说明函数调用才会去创建成员函数

    my_class<person2> m1;

    m1.func2();

}

int main()

{

    test01();

    return 0;

}

运行结果:

四、类模板对象做函数参数

类模板实例化出的对象,向函数传参的方式

三种传入方式:

  1. 指定传入的类型 --- 直接显示对象的数据类型(**常用)**
  2. 参数模板化         --- 将对象中的参数变为模板进行传递
  3. 整个类模板化     --- 将这个对象类型   模板化进行传递

示例:

#include<iostream>

#include<typeinfo>

using namespace std;

/*

三种传入方式:

 

1. 指定传入的类型 --- 直接显示对象的数据类型

2. 参数模板化         --- 将对象中的参数变为模板进行传递

3. 整个类模板化     --- 将这个对象类型   模板化进行传递

*/

template<class T1,class T2>

class person

{

public:

    person(T1 name, T2 age)

    {

        this->name = name;

        this->age = age;

    }

    void show()

    {

        cout<<"姓名:"<<this->name<<"\t年龄:"<<this->age<<endl;

    }

    T1 name;

    T2 age;

};

 

// 1.指定传入类型

void print1(person<string ,int> &p)

{

    p.show();

}

void test01()

{

    // 1. 指定传入的类型 --- 直接显示对象的数据类型

    person<string ,int> p ("TOM",99);

    print1(p);

}

 

// 2.参数模板化

template<class T1,class T2>

void print2( person<T1,T2> &p1)

{

    p1.show();

    cout<<"看编译器推断的模板是什么类型"<<endl;

    cout<<"T1的类型为:"<<typeid(T1).name()<<endl;

    cout<<"T2的类型为:"<<typeid(T2).name()<<endl;

}

void test02()

{
    
    // 2. 参数模板化         --- 将对象中的参数变为模板进行传递

    person <string ,int> p1 ("JACK",78);

    print2(p1);

}

 

// 3.将整个类模板化

template<class T>

void print3(T &p2)

{

    p2.show();

    cout<<"T的数据类型"<<endl;

    cout<<"T的类型为:"<<typeid(T).name()<<endl;

}

void test03()

{

    person<string,int> p2("LILY",45);

    print3(p2);

}

int main()

{

    test01();

    test02();

    test03();

    return 0;

}

运行结果:

总结:

  • 通过类模板创建的对象。可以有三种方式向函数中进行传参
  • 使用比较广泛是第一种:指定传入的类型

五、类模板与继承

注意:

  1. 当子类继承父类是一个类模板时,子类在声明的时候,要指定出父类T的类型
  2. 如果不能确定,编译器无法给予子类分配内存
  3. 如果想灵活指定父类中T的类型,子类也需要变为类模板

示例1:

#include<iostream>

#include<typeinfo>

using namespace std;

// 类模板与继承

template<class T>

class Base

{

    T m;

};

// class Son:public Base  // 必须要知道父类中T的类型,才能继承给子类

class Son:public Base<int>

{

 

};

int main()

{

    Son s1;

    return 0;

}

生成成功:

示例2:

#include<iostream>

#include<typeinfo>

using namespace std;

template<class T>

class Base

{

    T m;

};

// 如果想要灵活指定父类中T的类型,子类需要变类模板

template<class T1,class T2>

class Son2:public Base<T2>

{

public:

    Son2()

    {

          cout<<"T1的类型为:"<<typeid(T1).name()<<endl;
          cout<<"T2的类型为:"<<typeid(T2).name()<<endl;

    }

    T1 ojb;

};

void test02()

{

    Son2<int ,char>s2;// char 传给父类,int 传给子类

}

int main()

{

    test02();

    return 0;

}

运行结果:

六、类模板成员函数类外实现

示例:

#include<iostream>

using namespace std;

 

// 类模板成员函数类外实现

template<class T1,class T2>

class person

{

public:

    person(T1 name,T2 age);

    /*{

        this->name=name;

        this->age=age;

    }*/
    
    void show();

    /*{

        cout<<"姓名:"<<this->name<<"\t年龄:"<<this->age<<endl;

    }*/

    T1 name;

    T2 age;

};

// 构造函数的类外实现

template<class T1,class T2>

person<T1,T2>::person(T1 name,T2 age)

{

    this->name=name;

    this->age=age;

}

 

// 成员函数的类外实现

template<class T1,class T2>

void person<T1,T2>::show()

{

    cout<<"姓名:"<<this->name<<"\t年龄:"<<this->age<<endl;

}

void test01()

{

    person<string,int>p("TOM",28);

    p.show();

}

int main()

{

    test01();

    return 0;

}

运行结果:

七、类模板分文件编写

问题:

类模板中成员函数创建的时机是在调用阶段,导致分文件编写时连接不到

解决

  1. 直接包含 .cpp 源文件
  2. 将声明和实现写到同一个文件中,并更改后缀名为 .hpp , hpp 是约定的名称,并不是强制

示例:

person.hpp文件

#include<iostream>

using namespace std;



// 类模板成员函数类外实现

template<class T1, class T2>

class person

{

public:

    person(T1 name, T2 age);

    T1 name;

    T2 age;

};

// 构造函数的类外实现

template<class T1, class T2>

person<T1, T2>::person(T1 name, T2 age)

{

    this->name = name;

    this->age = age;

}



// 成员函数的类外实现

template<class T1, class T2>

void person<T1, T2>::show()

{

    cout << "姓名:" << this->name << "\t年龄:" << this->age << endl;

}

.cpp文件

#include<iostream>

#include"person.hpp"

using namespace std;

void test01()

{

    person<string, int>p("TOM", 28);

    p.show();

}

int main()

{

    test01();

    return 0;

}

八、类模板与友元

类模板配合友元函数的类内实现和类外实现

  1. 全局函数类内实现 -- 直接在类内声明友元即可
  2. 全局函数类外实现 -- 需要提前让编译器知道全局函数的存在

示例:

#include<iostream>

using namespace std;

 

template<class T1,class T2>

class person;

 

// 类外实现

template<class T1,class T2>

void print2(person<T1,T2> p)

{

    cout<<"类外实现---姓名:"<<p.name<<"\t年龄:"<<p.age<<endl;

}

 

// 类模板与友元

template<class T1,class T2>

class person

{

 

    // 通过全局函数打印输出

    // 全局函数类内实现

    friend void print1(person<T1,T2> p)

    {

        cout<<"姓名:"<<p.name<<"\t年龄:"<<p.age<<endl;

    }
    
    // 全局函数类外实现

    // 需要加空模板的参数列表

    // 如果全局函数是类外实现,需要让编译器提前知道这个函数的存在

    friend void print2<>(person<T1,T2> p);

    public:

    person(T1 name,T2 age)

    {

        this->name=name;

        this->age=age;

    }

 

private:

    T1 name;

    T2 age;

};

 

 

void test01()

{

    person<string,int>p("TOM",28);

    print1(p);

    print2(p);

}

int main()

{

    test01();

    return 0;

}

运行结果:

九、类模板案例

目的:

  1. 可以对内置数据类型以及自定义数据类型的数据进行存储
  2. 将数组中的数据存储到堆区
  3. 构造函数中可以传入数组的容量
  4. 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
  5. 提供尾插法和尾删除法对数组中的数据进行增加和删除
  6. 可以通过下标的方式访问数组中的元素
  7. 可以获取数组中当前元素的个数和数组的容量

my_array.hpp

#pragma once

#include<iostream>

using namespace std;

// 自己的通用的数组类

 

template<class T>

class my_array

{

public:

    // 有参构造, 传入容量

    my_array(int copacity)

    {

        cout << "my_array的有参构造" << endl;

        this->m_Capacity = copacity;

        this->m_Size = 0;

        this->p_Address = new T[this->m_Capacity];

    }

 

    //拷贝构造

    my_array(const my_array& arr)

    {

        this->m_Capacity = arr.m_Capacity;

        this->m_Size = arr.m_Size;

        // 浅拷贝 this->p_Address = arr. p_Address;

        // 深拷贝

        this->p_Address = new T[arr.m_Capacity];

 

        //  将arr中的数据拷贝进来

        for (int i = 0; i < this->m_Size; i++)

        {

            this->p_Address[i] = arr.p_Address[i];

        }

    }

 

    // operator = 防止浅拷贝的问题

    my_array& operator=(const my_array& arr)

    {

        // 先判断原来堆区是否有数据,先释放堆区数据

        if (this->p_Address != NULL)

        {

            delete[]this->p_Address;

            this->p_Address = NULL;

            this->m_Capacity = 0;

            this->m_Size = 0;

        }

        this->m_Capacity = arr.m_Capacity;
    
        this->m_Size = arr.m_Size;

        this->p_Address = new T[arr.m_Capacity];

        for (int i = 0; i < this->m_Size; i++)

        {

            this->p_Address[i] = arr.p_Address[i];

        }

        return *this;

    }

 

    // 尾插法

    void Push_Back(const T& Val )

    {

        // 判断容量是否最大了

        if (this->m_Capacity == this->m_Size)

        {

            return;

        }

        this->p_Address[this->m_Size] = Val;  // 在数组的末尾插入数据

        this->m_Size++;  // 更新数组的大小

    }

 

    // 尾删法

    void Pop_Back()

    {

        // 让用户访问不到最后一个元素,就是删除

        if (this->m_Size == 0)

        {

            return;

        }

        this->m_Size--;

    }

 

    // 可以通过下标的方式访问数组

    T& operator[](int index)

    {

        return this->p_Address[index];

    }

 

    // 返回数组的容量

    int get_Caoacity()

    {

        return this->m_Capacity;

    }

 

    // 返回数组大小

    int get_Size()

    {

        return this->m_Size;

    }

 

    // 析构函数

    ~my_array()

    {

        if (this->p_Address != NULL)

        {

            delete[] this->p_Address;

            this->p_Address = NULL;

        }

    }

private:

    T* p_Address;   // 指针指向堆区开辟的真实数据

 

    int m_Capacity;   // 数组容量

 

    int m_Size;  // 数组大小

};

模板array.cpp

#include"my_array.hpp"

#include<iostream>

using namespace std;

void print_arr(my_array<int>& arr)

{

    for (int i = 0; i < arr.get_Size(); i++)

    {

        cout << arr[i] << endl;

    }

}

void test01()

{

    my_array<int> arr1(5);

    for (int i = 0; i < 5; i++)

    {

        // 利用尾插法向数组中插入数据

        arr1.Push_Back(i);

    }

    cout << "arr1的打印输出" << endl;

    print_arr(arr1);

    cout << "arr1的容量为:" <<arr1.get_Caoacity()<< endl;

    cout << "arr1的大小为:" << arr1.get_Size() << endl;

 

    cout << "arr2的打印输出" << endl;

    my_array<int> arr2(arr1);
    
    print_arr(arr2);

    // 尾删

    arr2.Pop_Back();

    cout << "arr2的容量为:" << arr2.get_Caoacity() << endl;

    cout << "arr2的大小为:" << arr2.get_Size() << endl;

}

 

// 测试自定义的数据类型

class person

{

public:

    person() {};

    person(string name,int age)

    {

        this->name = name;

        this->age = age;

    }

    string name;

    int age;

};

 

void print_person_arr(my_array<person>& arr)

{

    for (int i = 0; i < arr.get_Size(); i++)

    {

        cout << "姓名:" << arr[i].name << "\t年龄:" << arr[i].age << endl;

    }

}

 

void test02()

{

    my_array<person> arr(10);

    person p1("TON", 78);

    person p2("JEEYU", 678);

    person p3("hello", 567);

    person p4("bgood", 567);
    
    person p5("daj", 78);

 

    // 将数据插入到数组中

    arr.Push_Back(p1);

    arr.Push_Back(p2);

    arr.Push_Back(p3);

    arr.Push_Back(p4);

    arr.Push_Back(p5);

    // 打印数组

    print_person_arr(arr);

    // 输出容量

    cout << "arr的容量:" << arr.get_Caoacity() << endl;

    // 输出大小
    
    cout << "arr的大小:" << arr.get_Size() << endl;
        
}

int main()

{

    test01();

    test02();

    return 0;

}

运行结果:

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

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

相关文章

HTML CSS 发光字头特效

效果展示&#xff1a; 代码&#xff1a; <html><head> </head><style>*{margin: 0;padding: 0;}body {text-align: center;}h1{/* border: 3px solid rgb(201, 201, 201); */margin-bottom: 20px;}.hcqFont {position: relative;letter-spacing: 0.07…

数据结构【DS】Ch8 排序

文章目录 插入排序选择排序归并&基数外部排序 插入排序 交换排序 选择排序 归并&基数 外部排序

电脑可以连接wifi,甚至可以qq聊天,但就是不能用浏览器上网,一直显示未检测出入户网线的解决方案

今天回到家&#xff0c;准备办公却发现电脑可以连接wifi&#xff0c;甚至可以qq聊天&#xff0c;但就是不能用浏览器上网&#xff0c;一直显示未检测出入户网线的解决方案&#xff0c;小白也可以看懂 以下有几种解决方案&#xff0c;不妨都试试&#xff0c;估计可以解决95%的相…

【C语言基础篇】结构控制(下)转向语句break、continue、goto、return

文章目录 一、break语句 1. break在 while 循环中 2. break在 for 循环中 3. break在 do…while 循环中 4. break在 switch 语句中 5. break 总结 二、continue语句 1. continue在 while 循环中 2. continue在 for 循环中 3. continue在 do...while 循环中 4. con…

PY调包侠——Collections高效库

一、【写在前面】 PY是一个调包侠语言&#xff0c;多学一个库可以提高计算速度。Collections提供了各种数据类型和集合工具&#xff0c;可以很方便的处理各种数据结构。如果您有刷力扣的习惯&#xff0c;可以经常看到Collections和itertools的身影&#xff0c;经常用这两个可以…

【数据结构】二叉树相关oj题(一)

目录 1、二叉树的构建及遍历 1.1、题目介绍 1.2、解题思路 1.3、代码描述 1.4、完整代码 2、二叉树的层次遍历 2.1、题目介绍 2.2、解题思路 2.3、代码描述 2.4、完整代码 1、二叉树的构建及遍历 1.1、题目介绍 原题链接&#xff1a;KY11 二叉树构建及遍历_牛客题霸…

ElasticSearch 7.x现网运行问题汇集1

问题描述&#xff1a; 现网ElasticSearch health状态变为red&#xff0c;有分片无法assign。如下摘录explain的结果部分&#xff1a; "note": "No shard was specified in the explain API request, so this response explains a randomly chosen unassigned s…

docker 部署 sentinel

docker 部署 sentinel 环境安装 拉取镜像 目前稳定的版本是1.8.0 docker pull bladex/sentinel-dashboard:1.8.0启动服务 docker run --name sentinel -p 8858:8858 -td bladex/sentinel-dashboard:1.8.0登录 登录的时候账号和密码都是sentinel

openGauss学习笔记-203 openGauss 数据库运维-常见故障定位案例-修改索引时只调用索引名提示索引不存在

文章目录 openGauss学习笔记-203 openGauss 数据库运维-常见故障定位案例-修改索引时只调用索引名提示索引不存在203.1 修改索引时只调用索引名提示索引不存在203.1.1 问题现象203.1.2 原因分析203.1.3 处理办法 openGauss学习笔记-203 openGauss 数据库运维-常见故障定位案例-…

modelscope下载模型

# 私有模型下载&#xff0c;前提是您有响应模型权限 方法1 git lfs install git clone http://oauth2:your_git_tokenwww.modelscope.cn/<namespace>/<model-name>.git 如何获取git token 用您的账号登录https://www.modelscope.cn &#xff0c;在个人中心->访…

【学习iOS高质量开发】——对象、消息、运行期

文章目录 一、理解“属性”这一概念1.如何定义实例变量2.什么是不兼容现象&#xff0c;如何解决3.理解property关键字4.理解dynamic关键字5.属性特质1.原子性&#xff1a;2.读/写权限&#xff1a;3.内存管理语义 7.要点 二、在对象内部尽量直接访问实例变量1.直接访问和属性访问…

[网络编程]UDP协议,基于UDP协议的回显服务器

目录 1.UDP协议介绍 2.UDP协议在Java中的类 2.1DatagramSocket类 2.2DatagramPacket 3.回显服务器 3.1Sever端 3.2Client端 1.UDP协议介绍 UDP协议是一种网络协议&#xff0c;它是无连接的&#xff0c;全双工&#xff0c;并且是面向数据报&#xff0c;不可靠的一种协议…

交友盲盒小程序版本 全开源版本-YISHEN源码网

这个小程序是云开发的不需要服务器域名 支持流量主wx支付。超级能吸引年轻人的一款小程序 版本新增&#xff1a; 1.Ui美化 2.星座匹配&#xff08;通过星座进行盲盒&#xff09; 3.后台管理&#xff08;可以实时看到用户数量&#xff09; 4.支付S I P 9功能&#xff08;后…

vue3-模版引用ref

1. 介绍 概念&#xff1a;通过 ref标识 获取真实的 dom对象或者组件实例对象 2. 基本使用 实现步骤&#xff1a; 调用ref函数生成一个ref对象 通过ref标识绑定ref对象到标签 代码如下&#xff1a; 父组件&#xff1a; <script setup> import { onMounted, ref } …

React、vue、h5端项目避免缓存

前言&#xff1a; h5项目和pc端项目上线时&#xff0c;有时只有细微的改变&#xff0c;但是部署完后&#xff0c;再次访问却是没变化。必须清除缓存才行。 pc端项目手动清除一下还行&#xff0c;但是h5项目却不行。尤其 嵌套在app里&#xff0c;只能 清除 app的缓存&#xff0…

数据库(表的基本操作)

目录 1.1 表的基本操作 1.1.1 创建表 1.1.2 表物理存储结构 1.1.3 数据类型 文本类型&#xff1a; 数字类型&#xff1a; 时间/日期类型&#xff1a; 常用的数据类型&#xff1a; 1.1.4 查看表 SHOW 命令 查看表结构&#xff1a; 1.1.5 删除表 查看表结构&#xf…

TCP服务器的演变过程:C++使用libevent库开发服务器程序

C使用libevent库开发服务器程序 一、引言二、libevent简介三、Libevent库的封装层级3.1、reactor对象封装struct event_base3.2、事件对象struct event3.3、struct bufferevent对象3.4、evconnlistener对象3.5、事件循环3.6、事件处理 四、完整示例代码小结 一、引言 手把手教…

论文精读--ResNet

ResNet论文 撑起计算机视觉半边天的ResNet【论文精读】_哔哩哔哩_bilibili Abstract Deeper neural networks are more difficult to train. We present a residual learning framework to ease the training of networks that are substantially deeper than those used pre…

E/E架构升级是汽车智能化发展关键

E/E架构升级是汽车智能化发展的关键。传统汽车采用的分布式E/E架构因计算能力不足、通讯带宽不足、不便于软件升级等瓶颈&#xff0c;无法满足现阶段汽车发展的需求&#xff0c;E/E架构升级将助力智能汽车实现跨越式革新。汽车E/E架构升级主要体现在硬件架构升级、软件架构升级…

docker:Web迁移

系列文章目录 docker&#xff1a;环境安装 docker:Web迁移 文章目录 系列文章目录前言一、Mariadb1.拉取镜像2.创建容器3.数据同步4.数据分离 二、PHP项目1.拉取镜像2.创建容器3.容器互通 三、Flask项目1.拉取镜像2.创建镜像3.自定义镜像1.安装apache2.安装python33.意外退出 …