首页 > 编程语言 >C++模板--类模板

C++模板--类模板

时间:2024-12-22 16:26:46浏览次数:6  
标签:name -- C++ showme score Student 模板 template

一篇文章带你走进类模板的世界
!!!

前言

上一篇文章的链接:https://blog.csdn.net/hujiahangdewa/article/details/144630185
有了上一篇文章的铺垫,我们再来看看类模板。其实就是要看template 这段代码的后面跟的是什么,如果跟的是函数的定义,那么它就是一个函数模板,如果跟的是类,那么它就是一个类模板。
当然,类模板也有一些细节,需要我们注意。


一、如何定义类模板?

  • 要定义一个类模板,你可以使用template关键字和一个或多个模板参数来定义一个通用的类。类模板的定义如示例:
#include <iostream>
#include <string>
using namespace std;

//如何定义一个类模板
template <typename T1,typename T2>//定义了两个模板参数T1和T2
class Student{
public:
    Student(string name,T1 age,T2 score)//类的构造方法
    {
        this->name = name;
        this->age = age;
        this->score = score;
    }
    void showMe()
    {
        cout<<this->name<<" "<<this->age<<" "<<this->score<<endl;
    }
    ~Student(){}//析构函数
private:
    string name;
    T1 age;
    T2 score;
};

int main()
{
    Student<int,int> obj("张三",18,100);//类的实例化
    obj.showMe();

    return 0;
}

在以上例子中,类的实例化Student<T1,T2> obj(“张三”,10,100);T1和T2是参数类型,可以根据需求来更改。

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

  1. 类模板的类型参数可以有默认值
template <typename T1,typename T2>
template <typename T1=int,typename T2>  //会报错,因为如果给了一个参数默认值,其后的都必须给上默认值
template <typename T1,typename T2=double>//正确,参数T2后面没有其他的参数,所以是可以通过编译的

  1. 类模板没有自动推导的使用方式
    我们在实例化类的时候,必须要指出我们定义类模板时的参数列表的实际参数类型,否则将会报错。
Student<int,int> obj("张三",18,100);//正确,成功编译
    obj.showMe();
Student ock("张三",18,100);//错误,类模板没有自动推导参数类型的功能
    

三、类模板成员函数的实例化

  1. 延迟实例化
    在函数被调用时才被实例化
    我们用例子来说明,如下:
#include <iostream>
#include <string>
using namespace std;

template <typename T>
class MyClass{
public:
    void AAA()
    {
        T obj_one;
        obj_one.showme();//showme()这个函数并没有定义
    }
};

int main()
{
    MyClass<string > obj;
    obj.AAA();

    return 0;
}

在以上这个例子中,写完这一整段代码,不运行程序,它不会提示报错。但我们在实例化MyClass这个类,运行程序时会报错,因为找不到showme这个方法。
那我们新写一个类,然后在这个类模板中定义一个showme这个方法,再将这个类传给MyClass会怎么样呢?

class Me{
public:
    void showme()
    {
        cout<<"I am a template class"<<endl;
    }
};

int main()
{
    MyClass<Me> obj;//将Me类当成参数传给MyClass
    obj.AAA();//输出的结果就是类Me中的showme方法

    return 0;
}
  1. 模板类外成员函数的定义
    在定义类模板的成员函数时,如果你想要将函数的实现放在类的外部,你需要在函数定义之前加上模板参数的声明。具体来说,你需要在函数名称前面加上模板参数列表,并在函数名后面加上尖括号和模板参数名。然后,在编写函数的实现。
    以下是代码示例:
#include <iostream>
#include <string>
using namespace std;

template <typename T>
class Me{
public:
    void showme(T info);//函数的声明
};
//以下是函数的实现
template <typename T>
void Me<T>::showme(T info) {
    cout<<info<<endl;
}

  1. 类模板对象作为参数
    在C++模板编程中,类模板对象作为参数是值将一个类模板的实例(对象)传递给函数或者其他类模板的构造函数、成员函数等。这种做法可以让我们在编写通用的代码时可以灵活处理不同类型的数据。
    一般有三种解决方法:
    在这里插入图片描述
    例子如下:
#include <iostream>
#include <string>
using namespace std;
//定义一个模板类,模板参数T1,T2
template <typename T1,typename T2>
class student{
public:
    student(T1 name,T2 score)
    {
        this->name = name;
        this->score = score;
    }
    void showme()
    {
        cout<<"我是:"<<name<<"分数是:"<<score<<endl;
    }
private:
    T1 name;
    T2 score;
};
//定义一个函数
//如果我的不是类模板,而是一个普通的类,这里不会报错
void func1(student &p)//会报错,因为没有给出参数列表
{
    p.showme();    
}

int main()
{
    student<string,int> obj("张三",99);
    func1(obj);


    return 0;
}

对于以上的报错,我们有三种解决的方法。

//第一种方法,给出student的参数列表,确定具体的类型传入
void func1(student<string,int> &p)
{
    p.showme();
}

//方法2:参数模板化
template <typename T1,typename T2>
void func2(student<T1,T2> &p)
{
    p.showme();
}

//方法3:将整个类模板化
template <typename T>
void fun3(T &p)//理论上来说可以忽略参数类型,但是不这么写会报错
{
    p.showme();//确保实例化p的类中必须要包含showme()这个函数,否则将会报错
}

四、类模板与继承

当谈及类模板和继承时,可以有几种不同的情况和用法

  1. 基类是类模板
    如果基类是类模板,而派生类不是,派生类可以通过特定类型的继承来实现。派生类可以使用基类模板的特定实例化。
//父类是模板
template <typename T>
class Base{
public:
    T var;
};

//子类继承父类,父类是模板类,子类是普通类
class Myclass:public Base{//出现了报错,如果父类是一个普通的类,这样写是不会报错的
};
//解决方法就是确定父类的泛型类型
//1.确定基类的类型
class Myclass:public Base<int>{//正确,编译通过
};
//当父类是模板类,子类也是模板类时
template <typename T,typename U>//T是给基类的类型,U是给子类的类型
class Myclass1:public Base<T>{
    U age;
};

五、友元函数和模板类

分两种情况:在类内部实现的全局友元函数和在类外部实现的全局友元函数

  1. 在类内部实现的全局友元函数
#include <iostream>
#include <string>
using namespace std;

template <typename T1,typename T2>
class student{
    //在这里定义一个友元函数,也就是全局函数,友元函数的内部实现
    //如果去掉了friend,将会报错
    //类内部实现
    friend void prints(student<T1,T2>&p)
    {
        cout<<p.name<<" "<<p.score<<endl;//可以直接访问私有变量
        p.showme(); //可以访问共有函数
    }
public:
    student(T1 name,T2 score)
    {
        this->name=name;
        this->score=score;
    }
    void showme()
    {
        cout<<"我是"<<this->name<<"分数是:"<<this->score<<endl;
    }

private:
    T1 name;
    T2 score;
};


int main()
{
    student<string,int> s("张三",100);//实例化对象
    prints(s);//调用全局函数,prints不属于类里面的方法,所以可以直接调用

    return 0;
}
  1. 在类外实现的全局友元函数
#include <iostream>
#include <string>
using namespace std;

//提前让编译器知道类的存在
template <typename T1,typename T2>
class Student;

//提前让编译器知道这个全局函数的存在
template <typename T1,typename T2>
void Prints(Student<T1,T2> &p);

//定义一个模板类,含有两个自定义参数类型
template <typename T1,typename T2>
class Student{
    //类内声明,类外实现
    //必须加一个空模板参数,作用是告诉编译器这是一个模板方法
    friend void Prints<>(Student<T1,T2> &p);
public:
    Student(T1 name,T2 score)
    {
        this->name = name;
        this->score = score;
    }
    void showme()
    {
        cout<<"我是"<<this->name<<",分数是"<<this->score<<endl;
    }
private:
    T1 name;
    T2 score;
};
//类外的定义
template <typename T1,typename T2>
void Prints(Student<T1,T2> &p){
    cout<<"我是"<<p.name<<",分数是"<<p.score<<endl;//直接访问私有变量
    p.showme();//直接访问共有函数
}

int main()
{
    Student<string,int> stu("张三",100);//类的实例化对象
    Prints<>(stu);//调用全局函数,(在类内部实现)

    return 0;
}

大多数情况下,我是推荐使用第一种写法,第二种写法会有点繁琐,有时还可能会忘记提前写出声明,导致发生错误。

六、类模板分文件编写

我们可以直接引用.cpp文件,也可以创建.hpp
.hpp文件时模板类定义的过程
残生hpp的根本原因:cpp的单独编译产生obj,链接阶段会出现错误。
所以才使用.hpp文件
我们用例子来解释:
当我们将所有的东西放在一个文件中时,是不会报错的,如下

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

//模板类的定义
template <typename T1,typename T2>
class Student{
public:
    Student(T1 name,T2 score);
    void showme();
private:
    T1 name;
    T2 score;
};
//类成员的外部实现
template <typename T1,typename T2>
Student<T1,T2>::Student(T1 name, T2 score) {
    this->name = name;
    this->score = score;
}
template <typename T1,typename T2>
void Student<T1,T2>::showme() {
    cout << "我是:" << this->name << " 分数是:" << this->score << endl;
}

int main()
{
    Student<string,int> stu("小明",100);
    stu.showme();

    return 0;
}

接下来,我们使用普通的.h文件来声明这个类的定义

student.h
#pragma once
//模板类的定义
template <typename T1,typename T2>
class Student{
public:
    Student(T1 name,T2 score);
    void showme();
private:
    T1 name;
    T2 score;
};

student.cpp
#include "student.h"
//类成员的实现
template <typename T1,typename T2>
Student<T1,T2>::Student(T1 name, T2 score) {
    this->name = name;
    this->score = score;
}
template <typename T1,typename T2>
void Student<T1,T2>::showme() {
    cout << "我是:" << this->name << " 分数是:" << this->score << endl;
}

main.cpp
#include <iostream>
#include <string>
#include "student.h"
using namespace std;

int main()
{
    Student<string,int> stu("小明",100);
    stu.showme();

    return 0;
}

当我们运行main.cpp时,编译器会报错,因为链接时出现了问题。主要原因是因为我们的类模板有一个特点就是延迟实例化,只有被调用时才会被实例化,所以,当编译器在编译文件是,他没有检测到对函数的实例化,所以就没没有生成对应的.obj文件,当然最后的.exe文件中也不会出现链接这些文件。所以才出现了报错。(这是我的理解,有错误的地方,请在评论区指出)

解决方法就是将原来的student.h变更为student.hpp,然后在student.hpp中写入类的定义,以及方法的实现,再将main.cpp中的头文件重新编写即可解决这个错误。
如下:

student.hpp
#pragma once
#include "student.hpp"
#include <iostream>
//模板类的定义
template <typename T1,typename T2>
class Student{
public:
    Student(T1 name,T2 score);
    void showme();
private:
    T1 name;
    T2 score;
};

//类成员的实现
template <typename T1,typename T2>
Student<T1,T2>::Student(T1 name, T2 score) {
    this->name = name;
    this->score = score;
}
template <typename T1,typename T2>
void Student<T1,T2>::showme() {
    std::cout << "我是:" << this->name << " 分数是:" << this->score << std::endl;
}

总结

文章全面地介绍了 C++ 类模板相关知识,涵盖类模板定义、与函数模板的区别、成员函数实例化(包括延迟实例化、类外定义、对象作为参数)、与继承的关系、友元函数的结合以及分文件编写等方面,通过丰富的代码示例详细解释了各类特性和应用场景,为 C++ 开发者深入理解和运用类模板提供了系统的指导,有助于提升代码的复用性和灵活性,在实际编程中能更好地利用类模板解决各类问题。

标签:name,--,C++,showme,score,Student,模板,template
From: https://blog.csdn.net/hujiahangdewa/article/details/144645761

相关文章

  • springboot毕设 智能热度分析和自媒体推送平台 程序+论文
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着互联网技术的飞速发展和社交媒体的广泛普及,自媒体已成为信息传播的重要渠道。每天,海量的文章、图片和视频等内容在各大自媒体平台上发布,用户面对......
  • jquery支持移动手机的响应式轮播图插件
    这是一款支持移动手机的响应式jquery轮播图插件。它具有跨平台,响应式,支持移动设备等特点,并且在使用CSS3制作过渡动画,效果非常炫酷。 在线预览  下载  使用方法在页面中引入jquery,hammer.min.js和slider.js文件以及图标样式文件entypo.css和CSS3动画样式文件style.css......
  • 实验6
    实验任务四源代码:1#include<stdio.h>2#defineN1034typedefstruct{5charisbn[20];6charname[80];7charauthor[80];8doublesales_price;9intsales_count;10}Book;1112voidoutput(Bookx[],intn);1......
  • 查看/导入/清除外部配置
    查看/导入/清除外部配置命令功能指导用户查看、导入或者清除RAID控制卡的外部配置。命令格式storcli64 /ccontroller_id/fallshowallstorcli64 /ccontroller_id/fallimportpreviewstorcli64 /ccontroller_id/falldelete参数说明参数参数说明取值......
  • MySQL——DQL查询(最重要,最常用) 多表设计
    数据库开发-MySQL在上次学习的内容中,我们讲解了:使用DDL语句来操作数据库以及表结构(数据库设计)使用DML语句来完成数据库中数据的增、删、改操作(数据库操作)我们今天还是继续学习数据库操作方面的内容:查询(DQL语句)。查询操作我们分为两部分学习:DQL语句-单表操作DQL语句-多表......
  • 我用cursor, 半就开发了一个手机壁纸小程序,真的太强了
    前言我用chatGPT帮我写后端爬虫,分析知乎html代码,爬取知乎壁纸。然后用cursorAI工具,完全使我一个不懂前端uniapp框架的人,开发了一个小程序手机壁纸页面。原来一周的工作量,半天搞定。体验可以微信搜索《程序员博博》同名。配合chatGPT爬知乎首先我们打开知乎首页,以《有哪些你不......
  • pyqt5之网格布局
    解决fromPyQt5importQtCorefromPyQt5.QtWidgetsimport*classDemo(QWidget):def__init__(self,parent=None):#这行代码调用了QWidget类的构造函数,以确保Demo类正确地继承了QWidget的属性和方法#初始化父类属性super(Demo,sel......
  • 铺地毯(二维差分/枚举区间)
    题目:链接:https://ac.nowcoder.com/acm/problem/16593https://www.luogu.com.cn/problem/P1003思路:二维差分:差分矩阵初始值全为0,在[i,j]~[a,b]区间+v,就在mat[i,j]+v,mat[a+1,j]-v,mat[i,b+1]-v,mat[a+1,b+1]+v然后进行二维前缀和:sum[i,j]=左+上-左上+自己;枚举区间:开一个......
  • 云原生技术全科普
    云原生技术全科普目录引言什么是云原生云原生的四大支柱微服务架构容器化持续集成/持续部署(CI/CD)声明式API云原生的关键技术Kubernetes服务网格(ServiceMesh)无服务器计算(Serverless)云原生应用开发示例云原生的优势云原生面临的挑战结论1.引言云计算的发......
  • MySQL 数据库优化:分区、分表与索引创建
    MySQL数据库优化:分区、分表与索引创建目录概述MySQL分区(Partitioning)2.1什么是分区?2.2使用场景2.3分区类型2.4分区维护2.5示例:创建分区表MySQL分表(Sharding)3.1什么是分表?3.2使用场景3.3分片键选择3.4示例:手动分表3.5分表的挑战MySQL索引创建4.1什么是......