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>():1234return 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;
} 

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

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

什么是神经网络的非线性

大家好啊&#xff0c;我是董董灿。 最近在写《计算机视觉入门与调优》&#xff08;右键&#xff0c;在新窗口中打开链接&#xff09;的小册&#xff0c;其中一部分说到激活函数的时候&#xff0c;谈到了神经网络的非线性问题。 今天就一起来看看&#xff0c;为什么神经网络需…

Vue router深入学习

Vue router深入学习 一、单页应用程序介绍 1.概念 单页应用程序&#xff1a;SPA【Single Page Application】是指所有的功能都在一个html页面上实现 2.具体示例 单页应用网站&#xff1a; 网易云音乐 https://music.163.com/ 多页应用网站&#xff1a;京东 https://jd.co…

【MYSQL】单表查询

查询语法&#xff1a; select 字段&#xff08;*表示全字段&#xff09; from 数据表 【where 条件表达式】 【group by 分组字段【having 分组条件表达式】】 【order by 排序字段【asc | desc】】 例子&#xff1a; 教职工表Teacher(Tno, TName, age, sal, mgr, DNo)&#…

通过异步序列化提高图表性能 Diagramming for WPF

通过异步序列化提高图表性能 2023 年 12 月 6 日 MindFusion.Diagramming for WPF 4.0.0 添加了异步加载和保存文件的功能&#xff0c;从而提高了响应能力。 MindFusion.Diagramming for WPF 提供了一个全面的工具集&#xff0c;用于创建各种图表&#xff0c;包括组织结构图、图…

【概率方法】MCMC 之 Gibbs 采样

上一篇文章讲到&#xff0c;MCMC 中的 HM 算法&#xff0c;它可以解决拒绝采样效率低的问题&#xff0c;但是实际上&#xff0c;当维度高的时候 HM 算法还是在同时处理多个维度&#xff0c;以两个变量 x [ x , y ] \mathbf{x} [x,y] x[x,y] 来说&#xff0c;也就是同时从联合…

机器学习 | Python贝叶斯超参数优化模型答疑

机器学习 | Python贝叶斯超参数优化模型答疑 目录 机器学习 | Python贝叶斯超参数优化模型答疑问题汇总问题1答疑问题2答疑问题3答疑问题汇总 问题1:想问一下贝叶斯优化是什么? 问题2:为什么使用贝叶斯优化? 问题3:如何实现? 问题1答疑 超参数优化在大多数机器学习流水线…

[GPT]Andrej Karpathy微软Build大会GPT演讲(上)--GPT如何训练

前言 OpenAI的创始人之一,大神Andrej Karpthy刚在微软Build 2023开发者大会上做了专题演讲:State of GPT(GPT的现状)。 他详细介绍了如何从GPT基础模型一直训练出ChatGPT这样的助手模型(assistant model)。作者不曾在其他公开视频里看过类似的内容,这或许是OpenAI官方…

Project Euler 865 Triplicate Numbers(线性dp)

题目 能通过每次消除3个一样的数字&#xff0c;最终把数字消成空的数字是合法的&#xff0c; 求串长度不超过n的&#xff0c;没有前导0的数字中&#xff0c;合法的数字的个数 n10000&#xff0c;答案对998244353取模&#xff0c;只需要输出数字 思路来源 乱搞AC 题解 暴力…

MacBook电脑内存容量小根本不够用?如何一键解决?

得益于M1系列芯片的强势表现&#xff0c;很多朋友都换用了MacBook&#xff0c;首次接触到了macOS系统。但出乎意料的是&#xff0c;很多人就开始受罪了……明明这么出色的硬件&#xff0c;为何到处都不顺手呢&#xff1f;尤其是容量&#xff0c;MacBook相比同价位的Windows笔记…

在 Qt Creator 中编写 Doxygen 风格的注释

2023年12月10日&#xff0c;周日上午 如何生成Doxygen 风格的注释 在需要Doxygen 风格注释的函数上方输入 /**&#xff0c;然后按下 Enter 键。Qt Creator 将自动为你生成一个注释模板。 输入&#xff0c;Qt Creator会自动帮你补全Doxygen标签 不得不说&#xff0c;写了Doxyge…

江科大 STM32入门教程 P14 定时中断和定时器外部时钟

1 通用定时器中断的初始化&#xff08;Time2&#xff09; 1.1 开启RCC的TimxCLK时钟, 由于Time2是由APB1总线的外设控制的 RccAPB1PeriphClockCmd(RCC_APB1PeriPh_TIM2,ENABLE);//使能APB1总线1.2 选择时基单元时钟 选择时基单元内部时钟 TIM_InteralClockConfig(IIM2);//内…

openGauss学习笔记-150 openGauss 数据库运维-备份与恢复-物理备份与恢复之gs_backup

文章目录 openGauss学习笔记-150 openGauss 数据库运维-备份与恢复-物理备份与恢复之gs_backup150.1 背景信息150.2 前提条件150.3 语法150.4 参数说明150.5 示例 openGauss学习笔记-150 openGauss 数据库运维-备份与恢复-物理备份与恢复之gs_backup 150.1 背景信息 openGaus…