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

news2024/12/29 1:35:33

目录

一、类模板

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;

}

运行结果:

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

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

相关文章

SpringBoot - SpringBoot手写模拟SpringBoot启动过程

依赖 建一个工程&#xff0c;两个Module: 1. springboot模块&#xff0c;表示springboot框架的源码实现 2. user包&#xff0c;表示用户业务系统&#xff0c;用来写业务代码来测试我们所模拟出来的SpringBoot 首先&#xff0c;SpringBoot是基于的Spring&#xff0c;所以我…

3DMAX初级小白班第一课:菜单栏介绍

基本介绍 这里不可能一个一个选项全部教给大家&#xff08;毕竟之后靠实操慢慢就记住了&#xff09;&#xff0c;只说一些相对需要注意的设置。 自定义-热键编辑器-热键设置 这里有你所需要的全部快捷键 自定义-自定义UI启动布局 将UI布局还原到启动的位置 自定义-通用单…

使用Go发送HTTP GET请求

在Go语言中&#xff0c;我们可以使用net/http包来发送HTTP GET请求。以下是一个简单的示例&#xff0c;展示了如何使用Go发送HTTP GET请求并获取响应。 go复制代码 package main import ( "fmt" "io/ioutil" "net/http" …

HCIP网络的类型

一.网络类型&#xff1a; 点到点 BMA&#xff1a;广播型多路访问 -- 在一个MA网络中同时存在广播&#xff08;泛洪&#xff09;机制 NBMA&#xff1a;非广播型多路访问 -- 在一个MA网络中&#xff0c;没有泛洪机制-----不怎么使用了 MA&#xff1a;多路访问 -- 在一个…

03-常用编程概念

上一篇&#xff1a;02-编程猜谜游戏 本章介绍几乎所有编程语言中都会出现的概念&#xff0c;以及它们在 Rust 中的工作原理。许多编程语言的核心都有许多共同点。本章介绍的概念都不是 Rust 独有的&#xff0c;但我们会在 Rust 的上下文中讨论这些概念&#xff0c;并解释使用这…

课堂教学内容分为哪几类

作为一名老师&#xff0c;每天都要面对各种类型的学生&#xff0c;而学生们最关心的问题之一就是&#xff1a;老师&#xff0c;你到底要教我们什么&#xff1f;这个问题看似简单&#xff0c;实则深奥。那么&#xff0c;课堂教学内容究竟分为哪几类呢&#xff1f;今天&#xff0…

【webrtc】GCC 7: call模块创建的ReceiveSideCongestionController

webrtc 代码学习&#xff08;三十二&#xff09; video RTT 作用笔记 从call模块说起 call模块创建的时候&#xff0c;会创建 src\call\call.h 线程&#xff1a; 统计 const std::unique_ptr<CallStats> call_stats_;SendDelayStats &#xff1a; 发送延迟统计 const…

【书生·浦语大模型实战营05】《(5)LMDeploy 大模型量化部署实践》学习笔记

《(5)LMDeploy 大模型量化部署实践》 课程文档&#xff1a;《LMDeploy 的量化和部署》 1、大模型部署背景 1.1 模型部署 定义 将训练好的模型在特定软硬件环境中启动的过程&#xff0c;使模型能够接收输入并返回预测结果为了满足性能和效率的需求&#xff0c;常常需要对模型…

CLion调试Nodejs源码

【环境】 macOS node-v20.11.0源码 CLion 2023.3.2 【1】下载源码 https://nodejs.org/en/download/ 【2】编译源码 解压后的目录如下 进入解压后的目录进行编译 ./configure --debug make -C out BUILDTYPEDebug -j 4需要好久… 编译成功之后在node-v20.11.0目录下会有一个…

DNS是什么?为什么需要DNS?常用的DNS记录类型有哪些?

在互联网时代&#xff0c;基本上人人都要上网&#xff0c;网络已与人们日常生活息息相关。而DNS就在我们上网的过程中扮演着重要的角色—“将网址/域名解析成 IP 地址”。那么DNS是什么&#xff1f;为什么需要DNS&#xff1f;常用的DNS类型有哪些呢&#xff1f;别着急&#xff…

[数据结构 - C++] 红黑树RBTree

文章目录 1、前言2、红黑树的概念3、红黑树的性质4、红黑树节点的定义5、红黑树的插入Insert6、红黑树的验证7、红黑树与AVL树的比较附录&#xff1a; 1、前言 我们在学习了二叉搜索树后&#xff0c;在它的基础上又学习了AVL树&#xff0c;知道了AVL树是靠平衡因子来调节左右高…

springboot+mysql马拉松赛事志愿者管理系统-计算机毕业设计源码93439

摘 要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于站在的角度存在偏差&#xff0c;人们经常能够获得不同类型信息&#xff0c;这也是技术最为难以攻克的课题。针对马拉松赛事志愿者管理等问题&#xff0c;对…

司铭宇老师: 房地产中介销售顾问培训:房产中介经纪人提升销售业绩的法宝

房地产中介销售顾问培训&#xff1a;房产中介经纪人提升销售业绩的法宝 一、了解客户需求 成功的沟通始于深入了解客户的需求和期望。房地产经纪人应该通过提问和倾听来收集信息&#xff0c;比如客户对房产的地理位置、价格范围、户型设计等的偏好。此外&#xff0c;了解客户的…

【算法专栏学习】成贤学院,程序员的福利站到了,判断子序列,经典算法实战。

&#x1f3c6;作者简介&#xff0c;普修罗双战士&#xff0c;一直追求不断学习和成长&#xff0c;在技术的道路上持续探索和实践。 &#x1f3c6;多年互联网行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &#x1f389;欢迎 &#x1f44d;点赞✍评论…

优思学院|怎样制作优秀的标准作业流程(SOP)?

我相信你一定去过众所周知的麦当劳。它在世界各地都有许多分店。不知道你是否曾在不同地方的麦当劳尝试过他们的美食&#xff0c;但你可能会好奇&#xff0c;为什么无论在世界哪个角落&#xff0c;麦当劳的食物口味都如此一致&#xff1f;它们是如何确保全球各地分店都能提供同…

中断——外部中断EXIT

终端可以分成外部中断和内部中断吗 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 野火中断章节有这样一句话 【F103在内核水平上搭载了一个异常响应系统&#xff0c; 支持为数众多的系统异常和外部中断。 其中系统异常有8个&#xff…

「 网络安全常用术语解读 」网络攻击者的战术、技术和常识知识库ATTCK详解

引言&#xff1a;随着网络攻击手段的不断升级和多样化&#xff0c;网络安全领域面临着越来越严峻的挑战。为了帮助网络安全专业人员更好地识别和防御网络攻击&#xff0c;MITRE公司创建了ATT&CK框架&#xff0c;以提供一个统一且结构化的方法来描述网络攻击者的行为和技巧。…

Git与GitHub零基础教学

大家好&#xff0c;我是星恒&#xff0c;这个帖子给大家分享的是git和github的全套教程&#xff0c;包含github初始&#xff0c;git常用命令以及基本使用&#xff0c;git的ssh链接github&#xff0c;github使用token登录&#xff0c;github和idea的配合&#xff0c;一些平时常用…

SpringCloudConfig+SpringCloudBus+Actuator+Git实现Eureka关键配置属性热更新(全程不重启服务)

文章目录 前言1.痛点2.解决方案3.具体实现3.1搭建热配置服务3.2编写配置文件3.3搭建版本控制仓库3.4Eureka-Client引入以下依赖3.5Eureka-Client微服务编写以下配置bootstrap.yml提前加载3.6分别编写测试Controller3.7测试效果3.8下线场景压测 4.SpringCloudBus优化 前言 在上…

IDEA(十)2022版本 Services中服务窗口不显示端口号解决

目录 一、问题描述二、问题分析三、解决方案3.1 设置启动参数【生效】3.2 方法二&#xff1a;设置环境变量【不生效】3.3 方法三&#xff1a;删除缓存【不生效】 四、补充&#xff1a;如何手动控制端口显示 一、问题描述 我们在使用 IDEA 的过程中&#xff0c;会发现在 Servic…