17、类模板

17、类模板

  • 类模板
    • 类模板的声明
    • 类模板的使用
    • 类模板的静态成员
    • 类模板的递归实例化
  • 类模板扩展
    • 数值型的模板参数
    • 模板型成员变量
    • 模板型成员函数
    • 模板型成员类型
    • 模板型模板参数
  • 典型模板错误
    • 嵌套依赖
    • 依赖模板参数访问成员函数模板
    • 子类模板访问基类模板
    • 类模板中的成员虚函数

在这里插入图片描述

类模板

类模板的声明

形式

template<tyepname 类型形参1,....>
class 类模板名 {...} ;
示例
template<typename A, typename B>class CMath (
public:
    A m_a;
    B func() {...}
}

在类模板外实现成员函数
语法

template<typename 类型形参1,...>
返回值类型 类模板名<类型形参1,...>::函数名(调用形参1,....){
     函数体实现; 
}

示例

template< typename A, typename B> B CMath<A,B>::func(){
    ...
}
// 类模板
#include <iostream>
using namespace std;

template<typename T>
class CMath{
public:
    CMath(const T& t1, const T& t2):m_t1(t1),m_t2(t2){}
/*    T add(){
        return m_t1 + m_t2;
    }*/
    T add();
private:
    T m_t1;
    T m_t2;
};

template<typename T>
T CMath<T>::add(){ 
    return m_t1 + m_t2;
}
// class CMath<int>{ ... }  // 编译器实例化出的类
// class CMath<double>{ ... }
// class CMath<string>{ ... }
int main( void ) {
    int nx = 10, ny = 20;
    CMath<int>  m1(nx,ny);  // 类模板的实例化  并使用类模板的实例定义对象
    cout << m1.add() << endl;

    double dx = 1.23, dy = 4.56;
    CMath<double> m2(dx,dy);
    cout << m2.add() << endl;

    string sx = "Hello," , sy = "world!";
    CMath<string> m3(sx,sy);
    cout << m3.add() << endl;
    return 0;
} 

类模板的使用

使用类模板必须对类模板进行实例化 (产生真正的类)
类模板本身并不代表一个确定的类型( 即不能用于定义对象) ,只有通过类型实参实例化成真正的类后才具备类的语义(即可以定义对象)。
示例
CMath<int,double> math;

  • 类模板被实例化时类模板中的成员函数并没有实例化, 成员函数只有在被调用时才会被实例化 (即产生真正成员函数)
    • 注意: 成员虚函数除外
  • 某些类型虽然并没有提供类模板所需要的全部功能但照样可以实例化类模板,只要不调用那些未提供功能的成员函数即可

类模板的静态成员

  • 类模板中的静态成员即不是每个对象拥有一份,也不是类模板拥有一份
  • 而应该是由类模板实例化出的每一个真正的类各有一份
  • 且为该实例化类定义的所有对象共享

类模板的递归实例化

由类模板实例化产生的类也可以用来实例化类模板自身,这种做法称之为类模板的递归实例化

通过这种方法可以构建空间上具有递归特性的数据结构 例如:多维数组

Array<Array<int> >
// 类模板的递归实例化
#include <iostream>
#include <iomanip>
using namespace std;

template<typename T>class Array{
public:
    T& operator[](size_t i){
        return m_arr[i];
    }
private:
    T m_arr[10];
};
int main( void ) {
    Array< Array<int> > m; // 递归实例化
    for(int i=0;i<10;i++)
        for(int j=0;j<10;j++)
            m[i][j] = 10*i+j;

    for(int i=0;i<10;i++){
        for(int j=0;j<10;j++)
            cout << setfill('0') << setw(2) << m[i][j] << ' ';
        cout << endl;
    }
    /*
    Array<int> a;
    for(int i=0;i<10;i++)
        a[i] = 10+i;
    for(int i=0;i<10;i++)
        cout << a[i] << ' ';
    cout << endl;
    */
    return 0;
} 

类模板扩展

数值型的模板参数

类模板的模板形参并不限于类型参数,普通数值也可以作为模板的参数。

  • 非类型模板参数只能是数值类型,且只能是整数
  • 非类型模板参数也可以有缺省值
// 数值型模板参数
#include <iostream>
#include <iomanip>
using namespace std;

template<typename T,size_t S=15>class Array{ // 数值型模板参数:类型只能是整型
public:
    T& operator[](size_t i){
        return m_arr[i];
    }
    size_t size(){
        return S;
    }
private:
    T m_arr[S];
};
int main( void ) {
    Array<int/*,20*/> a;
    for(int i=0;i<a.size();i++)
        a[i] = 10+i;
    for(int i=0;i<a.size();i++)
        cout << a[i] << ' ';
    cout << endl;
    return 0;
} 

模板型成员变量

成员变量,但其类型是由一个类模板实例化的未知类,称之为模板型成员变量。

// 模板型成员变量
#include <iostream>
#include <iomanip>
using namespace std;

template<typename T>class Array{
public:
    T& operator[](size_t i){
        return m_arr[i];
    }
private:
    T m_arr[10];
};

template<typename D>class Sum{
public:
    Sum(Array<D>& a):m_a(a){}
    D add(){
        D s = 0;
        for(int i=0;i<10;i++)
            s+=m_a[i];
        return s;
    }
private:
    Array<D> m_a; // 模板型成员变量
};
int main( void ) {
    Array<int> a;
    for(int i=0;i<10;i++)
        a[i] = 10+i;

    Sum<int> s(a);
    cout << s.add() << endl;

    return 0;
} 

模板型成员函数

类模板的成员函数模板。
在类外实现

// 模板型成员函数
#include <iostream>
using namespace std;

template<typename T>class CMath{
public:
    CMath(const T& t1, const T& t2):m_t1(t1),m_t2(t2){};
/*  template<typename D>void func(){ // 模板型成员函数
        cout << "CMath<T>::func<D>()" << endl;
    }*/
    template<typename D> void func(D d); // 声明
    T add();
private:
    T m_t1;
    T m_t2;
};
// 定义

template<typename T>
T CMath<T>::add(){
     cout << "add:"<<m_t1 + m_t2 << endl;
}

template<typename T> template<typename D>
void CMath<T>::func(D d){
    cout << "CMath<T>::func<D>()"<< d << endl;
}

int main( void ) {
    CMath<int> m(1,2);
    m.add();
    m.func<double>(1.23);
    return 0;
}

模板型成员类型

类模板中嵌套的类模板

// 模板型成员类型
#include <iostream>
using namespace std;

template<typename X>class A{
public:
    template<typename Y>class B{
        public:
            template<typename Z>class C; // 声明
    };
};
// 定义class C
template<typename X>
template<typename Y>
template<typename Z>class A<X>::B<Y>::C{
public:
    template<typename T>void foo(X x,Y y ,Z z,T t){
        cout << "A<X>::B<Y>::C<Z>::foo<D>():"<< x<< y<< z<< t << endl;
    }
};

int main( void ) {
    A<int>::B<double>::C<int> c;
    c.foo<short>(1,2,3,4);//A<X>::B<Y>::C<Z>::foo<D>():1234
    return 0;
}

模板型模板参数

类模板的模板形参也可以是类模板,可以有缺省值.

template<typename T> class Array{....};

template< template<typename D> typename C=Array >
class Sum{
。。。
}
// 模板型模板参数
#include <iostream>
#include <iomanip>
using namespace std;

template<typename T>class Array{
public:
    T& operator[](size_t i){
        return m_arr[i];
    }
private:
    T m_arr[10];
};

template<typename D,template<typename M>typename C=Array>class Sum{
public:
    Sum(C<D>& a):m_a(a){}
    D add(){
        D s = 0;
        for(int i=0;i<10;i++)
            s+=m_a[i];
        return s;
    }
private:
    C<D> m_a; 
};
int main( void ) {
    Array<int> a;
    for(int i=0;i<10;i++)
        a[i] = 10+i;

    Sum<int/*,Array*/> s(a);
    cout << s.add() << endl;

    return 0;
} 

典型模板错误

嵌套依赖

由于模板要经过两次编译,在第一次编译模板的代码时,类型形参的具体类型尚不明确,编译器将把类型形参的嵌套类型理解为某个未知类型的静态成员变量,因此编译器看到使用这样的标识符声明变量时会报告错误这就叫嵌套依赖。
解决办法:

  • 在类型形参的前面增加一个 typename 标识符,意在告诉编译器其后是一个类模板的嵌套使用。
// 嵌套依赖
#include <iostream>
#include <iomanip>
using namespace std;

class A{
public:
    class B{
    public:
        void foo(){
            cout << "A::B::foo()" << endl;
        }
    };
};

template<typename T>void Func(){
//  T::B b; // error:嵌套依赖
    /*class|*/typename T::B b;
    b.foo();
}

int main( void ) {
    Func<A>();   
    return 0;
} 

依赖模板参数访问成员函数模板

利用未知类定义的对象来访问成员函数模板时,编译器在第一次编译时无法解析成员函数模板的类型参数列表的<>而报告编译错误。
解决办法

  • 在成员函数模板之前增加template关键字,意在告诉编译器其后是一个函数模板实例,编译器就可以正确理解<>了
// 依赖模板参数访问成员函数模板
#include <iostream>
#include <iomanip>
using namespace std;

class A{
public:
    template<typename T>void foo(){
        cout << "A::foo<T>()" << endl;
    }
};

template<typename T>void Func(){
    T t;
//  t.foo<int>(); // 依赖模板参数访问成员函数模板
    t.template foo<int>();
}

int main( void ) {
    return 0;
} 

子类模板访问基类模板

在子类模板中访问基类模板的成员,编译器第一次编译时只在子类模板和全局域中搜索使用的标识符号,不会到基类模板中搜索
解决办法

  • 在子类模板中可以通过使用作用域限定符或显式使用this指针
// 子类模板访问基类模板
#include <iostream>
#include <iomanip>
using namespace std;

// 基类模板
template<typename T>class Base{
public:
    int m_i;
    void foo(){
        cout << "Base<T>::foo()" << endl;
    }
};
// 2. 全局域
//int m_i;
//void foo(){}
// 子类模板
template<typename T,typename D>class Derived:public Base<T>{
public:
    void bar(){
        // 使用类名限定 --> 未知类型的调用
        Base<T>::m_i = 100;
        Base<T>::foo();

        // 显式使用this调用
        this->m_i = 200;
        this->foo();
    }
    // 1. 子类模板内部
//  int m_i;
//  void foo(){}
};

int main( void ) {
    Derived<int,double>d;
    d.bar();
    return 0;
} 

类模板中的成员虚函数

类模板中的普通成员函数可以是虚函数

  • 即可以为类定义成员虚函数,和普通类的成员虚函数一样,类模板的成员虚函数也可以表现出多态性
    类模板中的成员函数模板不可以是虚函数
  • 根据成员虚函数的多态机制,需要一个虚函数表 (表中保存成员虚函数的入口地址),而这个表是编译器在实例化类模板时就产生,类的成员函数模板的实例化(即产生真正的函数实体)需要编译器处理完调用后才会完成,这时才出现成员虚函数的地址。
// 类模板中的成员虚函数
#include <iostream>
#include <iomanip>
using namespace std;

// 基类模板
template<typename T>class Base{
public:
    virtual void foo(){ // 普通成员函数可以是虚函数
        cout << "Base<T>::foo()" << endl;
    }
//  virtual template<typename D>void Func(){    } // error:成员函数模板不可以定义为虚函数
};
// 子类模板
template<typename T,typename D>class Derived:public Base<T>{
public:
    void foo(){
        cout << "Derived<T,D>::foo()" << endl;
    }
};

int main( void ) {
    Derived<int,double> d;
    Base<int>* pBase = &d;
    pBase->foo(); // 并且可以表现出多态
    return 0;
} 

总结:成员函数模板的延迟编译,阻碍了虚函数表的静态构建。

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

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

相关文章

Jquery easyui异步提交表单的两种方式

这篇文章分享一下easyui常用的两种表单异步提交的方式。 目录 开始前的准备工作 方式一&#xff1a;利用jquery ajax提交 $.post() $.ajax() 方式二&#xff1a;使用easyui提供的表单提交方式 开始前的准备工作 1、使用HBuilderX创建一个简单的html项目&#xff0c;删除i…

ppt编辑密码如何设置?

大家在PPT中设置了限制编辑&#xff0c;发现后面任然可以编辑文件。那么如何将PPT文件设置成禁止修改模式呢&#xff1f;今天分享几个方法给大家。 方法一 将PPT文件直接保存或者另存为一份文件&#xff0c;在保存时&#xff0c;将文件格式选择为PowerPoint图片演示文稿 方法…

精通TypeScript:打造一个炫酷的天气预报插件

前言 ​ 随着数字化和信息化的发展&#xff0c;数据大屏使用越来越广泛&#xff0c;我们不仅需要展示数据&#xff0c;更需要以一种更加美观的方式展示数据。这就必然需要使用到各种图表组件&#xff0c;比如柱状图、饼图、折线图等等。但是有一些效果不太适合通过这种常规图表…

Python中的类与对象

✨前言✨ &#x1f4d8; 博客主页&#xff1a;to Keep博客主页 &#x1f646;欢迎关注&#xff0c;&#x1f44d;点赞&#xff0c;&#x1f4dd;留言评论 ⏳首发时间&#xff1a;2023年12月11日 &#x1f4e8; 博主码云地址&#xff1a;博主码云地址 &#x1f4d5;参考书籍&…

mapstruct个人学习记录

mapstruct核心技术学习 简介入门案例maven依赖 IDEA插件单一对象转换测试结果 mapping属性Spring注入的方式测试 集合的映射set类型的映射测试map类型的映射测试 MapMappingkeyDateFormatvalueDateFormat 枚举映射基础入门 简介 在工作中&#xff0c;我们经常要进行各种对象之…

Qt简介、C++工程文件分离、创建Qt工程、Qt的帮助文档

QT 简介 core&#xff1a;核心模块&#xff0c;非图形的接口类&#xff0c;为其它模块提供支持 gui&#xff1a;图形用户接口&#xff0c;qt5之前 widgets&#xff1a;图形界面相关的类模块 qt5之后的 database&#xff1a;数据库模块 network&#xff1a;网络模块 QT 特性 开…

Android : Xui- RecyclerView+BannerLayout 轮播图简单应用

实例图&#xff1a; 1.引用XUI http://t.csdnimg.cn/Wb4KR 2.创建显示图片布局 banner_item.xml <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:app"…

【线程池】的原理分析及源码(C语言版)

线程池的原理分析及源码&#xff08;C语言版&#xff09; centos8 连接失败 线程已满_张三和你一聊聊线程池 线程池 线程池是一种用于管理和复用线程的机制&#xff0c;通过线程池可以减少线程的创建和销毁次数&#xff0c;提高程序的性能和效率。线程池通常包含一个线程…

【第三届】:“玄铁杯”RISC-V应用创新大赛(基于yolov5和OpenCv算法 — 智能警戒哨兵)

文章目录 前言 一、智能警戒哨兵是什么&#xff1f; 二、方案流程图 三、硬件方案 四、软件方案 五、演示视频链接 总结 前言 最近参加了第三届“玄铁杯”RISC-V应用创新大赛&#xff0c;我的创意题目是基于 yolov5和OpenCv算法 — 智能警戒哨兵 先介绍一下比赛&#xf…

Spring日志完结篇,MyBatis操作数据库(入门)

目录 Spring可以对日志进行分目录打印 日志持久化&#xff08;让日志进行长期的保存&#xff09; MyBatis操作数据库(优秀的持久层框架) MyBatis的写法 开发规范&#xff1a; 单元测试的写法 传递参数 Spring可以对日志进行分目录打印 他的意思是说spring相关只打印INFO…

Android画布Canvas绘图scale,Kotlin

Android画布Canvas绘图scale&#xff0c;Kotlin <?xml version"1.0" encoding"utf-8"?> <androidx.appcompat.widget.LinearLayoutCompat xmlns:android"http://schemas.android.com/apk/res/android"xmlns:app"http://schemas.…

vuepress-----22、其他评论方案

vuepress 支持评论 本文讲述 vuepress 站点如何集成评论系统&#xff0c;选型是 valineleancloud, 支持匿名评论&#xff0c;缺点是数据没有存储在自己手里。市面上也有其他的方案, 如 gitalk,vssue 等, 但需要用户登录 github 才能发表评论, 但 github 经常无法连接,导致体验…

java实现网络聊天

网络聊天实现步骤&#xff08;从功能谈论方法&#xff09;&#xff1a; 客户端&#xff1a; 1.登录面板&#xff1a;注册提醒用户注册格式&#xff0c;登录账号密码不为空&#xff0c;点击登录的时候需要连接服务器端&#xff0c;启动聊天面板。&#xff08;监听用户点击登录…

logback的使用

1 logback概述 SLF4J的日志实现组件关系图如下所示。 SLF4J&#xff0c;即Java中的简单日志门面&#xff08;Simple Logging Facade for Java&#xff09;&#xff0c;它为各种日志框架提供简单的抽象接口。 SLF4J最常用的日志实现框架是&#xff1a;log4j、logback。一般有s…

LVS 三种负载均衡模式

昨天看视频了解LVS 三种负载均衡模式 &#xff0c;分别是Network Address Translation、Direct Routing、Tunneling 注&#xff1a;boardmix boardmix博思白板&#xff0c;多人实时协作的流程图&#xff0c;思维导图工具 https://boardmix.cn/ 画流程图还是很方便的

SSL 数字证书的一些细节

参考&#xff1a;TLS/SSL 协议详解(6) SSL 数字证书的一些细节1 证书验证 地址&#xff1a;https://wonderful.blog.csdn.net/article/details/77867063 参考&#xff1a;TLS/SSL协议详解 (7) SSL 数字证书的一些细节2 地址&#xff1a;https://wonderful.blog.csdn.net/articl…

Mybatis之核心配置文件详解、默认类型别名、Mybatis获取参数值的两种方式

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…

聚类算法的性能度量

聚类算法的性能度量 聚类算法就是根据数据中样本与样本之间的距离或相似度&#xff0c;将样本划分为若干组&#xff0f;类&#xff0f;簇&#xff0c;其划分的原则&#xff1a;簇内样本相似、簇间样本不相似&#xff0c;聚类的结果是产生一个簇的集合。 其划分方式主要分为两…

[GWCTF 2019]我有一个数据库1

提示 信息收集phpmyadmin的版本漏洞 这里看起来不像是加密应该是编码错误 这里访问robots.txt 直接把phpinfo.php放出来了 这里能看到它所有的信息 这里并没有能找到可控点 用dirsearch扫了一遍 ####注意扫描buuctf的题需要控制扫描速度&#xff0c;每一秒只能扫10个多一个都…

基于深度学习的超分辨率图像技术一览

超分辨率(Super-Resolution)即通过硬件或软件的方法提高原有图像的分辨率&#xff0c;图像超分辨率是计算机视觉和图像处理领域一个非常重要的研究问题&#xff0c;在医疗图像分析、生物特征识别、视频监控与安全等实际场景中有着广泛的应用。 SR取得了显著进步。一般可以将现有…