++的语法真是太多了

首先,栈可以使用指针压栈吗?

当然可以,这里写一个简单的栈

提到指针,可能会想到字符串,我们可以有以下几种传入

string str; 最简单的传入,得益于C++提供的强大的string类,我们可以轻松的完成这个操作

char str[size]; C风格的字符串,但对某些模板类方法可能不可用

char * str = new char[size];

对于我们实现的这个类来说,至少对输入数据不做修改是不可行的

template<class Type>
bool Stack<Type>::push(const Type &t)
{if(now < MAX){items[now++] = t;return true;}else{return false;}
}

我们可以看见,这样压栈元素的是一个指针,在没有人为的干预下,这个指针是指向的内存是不会改变的,我们重复对str输入是无意义的;

妥当的做法还是让程序提供一个指针数组(一般是二维数组),其中每个指针都指向不同的字符串。栈的任务是管理指针,而不是创建指针。

//
// Created by JAN on 2022/1/26.
//#ifndef C___STACK_H
#define C___STACK_H
template <class T>
class Stack
{
private:enum { MAX = 10};T items[MAX];int now;
public:Stack() : now(0) {}bool push(T & t);bool pop();bool empyt() const;const T& top() const;
};template <class T>
bool Stack<T>::push(T &t)
{if(now < MAX){items[now++] = t;return true;}elsereturn false;
}template<class T>
bool Stack<T>::empyt() const
{if(now == 0)return true;elsereturn false;
}template<class T>
const T &Stack<T>::top() const
{//int temp = now-1;return items[now-1];
}template<class T>
bool Stack<T>::pop()
{if(now > 0){now--;return true;}elsereturn false;
}
#endif //C___STACK_H
//
// Created by JAN on 2022/1/25.
//#ifndef C___STACK_H
#define C___STACK_Htemplate<class Type>
class Stack
{
private:enum { MAX = 5 };Type items[MAX+1];//也可以写成这样的形式,但是别忘了在构造函数中new一块空间出来,和一个析构函数释放空间//Type *items;int now;
public:Stack();bool empty();bool push(const Type & t);bool pop();Type top();
};
template<class Type>
Stack<Type>::Stack()
{now = 0;
}
template<class Type>
bool Stack<Type>::empty()
{return now == 0;
}template<class Type>
bool Stack<Type>::push(const Type &t)
{if(now < MAX){items[++now] = t;return true;}else{return false;}
}template<class Type>
bool Stack<Type>::pop()
{if(now > 0){now--;return true;}elsereturn false;
}template<class Type>
Type Stack<Type>::top()
{if(now > 0){return items[now];}
}#endif //C___STACK_H

运行结果

可以看见重复输出了eee


模板的递归使用

我们知道有array这个模板类,所谓的递归也就是模板套模板罢了

array< array<int, 3>, 5> arr;

外层的array中包含 五个 包含 三个int 的array 模板

描述成语言可能不好理解,不过看一下源码就懂了

#include <iostream>
#include <valarray>
#include <array>
using namespace std;int main()
{array< array<int, 3>, 5> arr;for(int i=0;i<5;i++){for(int j=0;j<3;j++){cin >> arr[i][j];}}for(int i=0;i<5;i++){for(int j=0;j<3;j++){cout << arr[i][j] << " ";}cout << endl;}return 0;
}

可像二维数组那样进行操作


接受多个类型的模板

假设我们要用一个二元组存放数据,这两个元类型可能是相同的,也可能是不同的,那么我们如何编写模板类

简单模拟一下二元组pair,非常简单,可能动动手就写出来了

pair.h

#ifndef PAIR_H
#define PAIR_H
//二元组
//template_name <class type_name, class type_name ... some argument>
//我们可以将上面的称为模板类原型
template <class Type1, class Type2>
class Pair
{
private:Type1 t1;Type2 t2;
public:Pair() = default;Pair(const Type1 & _t1, const Type2 & _t2) : t1(_t1), t2(_t2) {}const Type1 & first() const;const Type2 & second() const;
};template <class Type1, class Type2>
const Type1 & Pair<Type1, Type2>/*这里的作用域就是模板原型,这样写,这里容易忘记*/::first() const
{return t1;
}template <class Type1, class Type2>
const Type2 & Pair<Type1, Type2>::second() const
{return t2;
}#endif

main.cpp

#include <iostream>
#include "pair.h"
using namespace std;int main()
{Pair<int, double> p(3,4.12);cout << p.first() << endl;cout << p.second() << endl;return 0;
}

默认类型模板

就像默认函数参数那样,如果没声明可以自动就变为默认值,模板类如果没有声明类型,就会自动变为默认类型

template <class Type1=int, class Type2=int>...Pair <> p; //Pair<int, int> p;

模板类的具体化

1.隐式实例化

就像模板函数一样,模板类在没有指出实例化对象时编译器会帮助我们自动实例化我们传入的类型。在编译器需要对象之前,不会生成隐式的实例化。

array <int,3> * parr;

这只是声明一个模板类指针,并没有实例化。

2.显示实例化

使用关键字template并指出所需的类型来声明类时,编译器将会生成类声明的显式实例化。声明必须位于模板定于的名称空间中。

下面显式声明一个<string,int>的类

template class class_name <string,int>

在这种情况下,虽然没有创建或者提及对象,编译器也将生成类声明(包括方法定义)。和隐式实例化一样,也将根据通用模板来生成具体化。

3.显式具体化

显式具体化是特定类型的定义。有些类型我们需要有特定的模板类方法处理它。这时候就要用到显式具体化。显式具体化是模板类通用性的进一步补充。

定义格式如下

template class class_name <sprcialized-type-name>
{do sometings
};

4.部分具体化

即限制模板的通用性,假如 我们写了只可以处理整数的模板类,不想让浮点类型使用这个模板,我们可以这样做

template <class Type, int>
class class_name
{some things;
}

这样模板类的第二个类型只能是整形

如果有多个模板可供选择,编译器将会选择具体化程度最高的

特别注意为指针提供的模板具体化

template <class T>
class A
{...
};template <class T*>
class A
{...
};
------------------------------------------------------
A<char *> a;

若没有第二个模板声明,编译器会将T变为char *

而第二个不是将T变为char **而是变为char


成员模板

模板可用作结构体,类或者模板的成员。要完全实现STL的设计,必须使用这项特性。下面写一个简单的模板类的嵌套

#include <iostream>using std::endl;
using std::cout;
//定义模板类template <class T>
class beat
{
private:template<class V>class hold{private:V val;    //创建一个type variablepublic://构造函数hold(V v=0) : val(v) {}//class method`void show() const{cout << val << endl;}V value() const{return val;}};//定义内层模板之后我们便可以创建两个模板类内的模板类//beat类把这两个模板类当作成员hold<T> q; //和最外层类实例化一个数据类型hold<int> n; //显式实例化int类型的public://class method//构造函数,实际上是调用hold内的构造函数beat(T t, int i) : q(t), n(i) {}template<class U>//这里是模板函数//U u是这个模板函数独有的, T t和大模板类的T实例化一种类型U blab (U u, T t){return (n.value()+q.value()) * u / t;}void show() const{q.show();n.show();}
};
int main()
{//beat(T t, int i) : q(t), n(i)beat<double> guy(3.5, 3);cout << "T was set to double\n";guy.show();cout << "V was set to T, which is double, then V was set to int\n";//由于是T is double 所以 下面的2.3 若将其变为2也是double 类型cout << guy.blab(10,2.3) << endl;cout << "U was set to int\n";cout << guy.blab(10.0,2.3) << endl;cout << "U was set to double\n";return 0;
}

将模板类作为参数

模板类可以包含类型参数(type)也可以包含非类型参数(int a),同样的,模板类也可以包含本身就是模板的参数,是C++11的特性,用于实现STL

其实,模板类的递归就是将模板类作为了参数

语法形式很好理解

注意中间没有逗号

template < /**/template <typename type_name> class/**/ type_name>
class class_name
{do something;
};

其中 /**/template <typename type_name> class/**/是类型,后面的type_name是参数

假设我们要对二元组进行栈处理,那我们之前的栈就会不起作用,因为二元组也是一个模板类,之前的栈不支持模板类作为类型参数,可以对栈进行改造,使其能接收二元组模板类,在这基础上,可以写一个新的模板类,把栈和二元组集成进去就可以,这个类接受两个模板类为对像,还可以写一个另外的模板类,接受一个栈模板类,在这个模板类中实例化两个模板栈类对象,代表二元组。

第二种办法看似麻烦,但是他的通用性可能会更好一点,第三种方法既简单又方便实现

这里我们用最后一种形式实现

#include <iostream>
#include "stack.h"
using namespace std;template <template<class T> class S, class T1, class T2>
class IDE
{
private:S<T1> stack1;S<T2> stack2;
public:IDE() = default;//class methodbool push(T1 & t1, T2 & t2);bool pop();void top(T1 & t1, T2 & t2) const;
};template <template<class T> class S, class T1, class T2>
bool IDE<S, T1, T2>::push(T1 &t1, T2 &t2)
{return stack1.push(t1) && stack2.push(t2);
}template <template<class T> class S, class T1, class T2>
bool IDE<S, T1, T2>::pop()
{return stack1.pop() &&stack2.pop();
}template <template<class T> class S, class T1, class T2>
void IDE<S, T1, T2>::top(T1 &t1, T2 &t2) const
{t1 = stack1.top();t2 = stack2.top();
}
int main()
{IDE<Stack/*no Stack <argument>*/,int ,double> stk;int a;double b;cin >> a >> b;stk.push(a,b);cin >> a >> b;stk.push(a,b);int c;double d;stk.top(c,d);cout << c << " " << d << endl;stk.pop();stk.top(c,d);cout << c << " " << d;stk.pop();return 0;
}

很明显,我们可以发现,我们无法实现top这个函数,我们无法返回两个元素,若返回只能返回一个结构体,或者改变top的做法。但用其他方法可直接返回一个pair,因为pair模板类是直接压入栈中的。


模板类和友元

如同类可以声明友元一样,模板类也可以声明友元函数。模板的友元分为三类。

1.非模板友元

2.约束模板友元

即友元的类型取决于类被实例化时的类型

3.非约束模板友元

即友元的所有具体化都是类的每一个具体化的友元

读不懂不重要,理解了才重要。


1.非模板友元

在模板中声明一个常规的函数做友元

正因为他不是模板函数,所以模板函数的每一种实例化都要单独写一个友元函数出来

我的友元是我的友元,你的友元是你的友元,虽然我们像兄弟一样都是从模板类具体化出来的,虽然我们很像,但我的朋友不是我兄弟的朋友,我的友元不是我兄弟的友元——模板类如是说道。

template <class T>
class A
{friend void func();...
};

可以这样吗

template <class T>
class A
{friend void func(A &);...
};

当然不行,因为不存在A这样的对象,只有特定的具体化才能这样做

我们可以这样

template <class T>
class A
{friend void func(A<T>);...
};

因为func并不是类方法,而是友元函数,他不属某个类,也不是模板函数,所以在使用时要显式具体化,在编译时期,我们假设用户传入int类型,那么编译器就会把T换为int,对于func来说,这就是个显式具体化。

重载版本

friend void func(A<int>);
friend void func(A<double>);

一个小示例

#include <iostream>
using namespace std;template<class T>
class A
{
private:T data;static int total;
public:A(T a) : data(a) { total++; }friend void report();friend void show(const A<T> &);
};template<class T>
int A<T>::total = 0;
//这里必须显式具体化
void show(const A<int> &t)
{cout << t.data << endl;
}//这里也是
void show(const A<double> &t)
{cout << t.data << endl;
}
void report()
{cout << " <int> is " << A<int>::total << endl;cout << " <double> is " << A<double>::total << endl;
}
int main()
{report();A<int> a(1);report();A<double> b(3.14);report();A<int> c(10);report();show(a);show(b);show(c);return 0;
}

别忘了初始化静态变量哦!

运行结果

由此也可见A<int>,A<double> ,是两个类!!!并且他们是出自一个模子的。


2.约束模板友元

因为<int>, <double>是不同的类,所以应该让类的每一种具体化都获得一个与友元匹配的具体化

说白了就是模板函数做友元,正好对应上面的如是说道。

可以修改前一个示例,使友元函数本身称为模板

包含以下三补

首先在类前面定义每个友元函数的模板

这里的T是模板函数的T,不是模板类的T,T实例化后是某个模板类如<int>,<string>
template <class T> void show(T &);
template <class T> void report();

然后,在模板类中再次将函数声明为友元

template <T>
class A
{friend void show <> (A<T> &);friend void report <T> ();...
};

声明中的<>指出模板具体化。对于show,可以为空,因为可以从函数参数推断模板类型参数

相当于

void show <A<T> (A<T> &);

最后再像定义模板函数那样定义友元函数即可。

这就正好是模板函数的具体化。

最好将友元的Type 和模板类的Type区分开

#include <iostream>using std::cout;
using std::endl;
template <class T> void counts();
template <class T> void report(T &);template<class TT>
class HasFriend
{
private:TT item;static int ct;
public:HasFriend(const TT& i) : item(i) { ct++; }~HasFriend() { ct--; }friend void counts <TT>();//这里为什么是HasFriend<TT>& 而不是T&friend void report <>(HasFriend<TT>&);
};template <class T>
void counts()
{cout << "template size: " << sizeof(HasFriend<T>) << ";";cout << "template counts(): " << HasFriend<T>::ct << endl;
}template<class T>
void report(T & t)
{cout << t.item << endl;
}//注意初始化也要用模板,代表他是这个模板类下的静态成员
template <class TT>
int HasFriend <TT>::ct= 0;
int main()
{counts<int>();HasFriend <int> hfi1(10);HasFriend <int> hfi2(20);HasFriend <double> hfi3(10.5);report(hfi1);report(hfi2);report(hfi3);cout << "counts<int>() output:\n";counts<int>();cout << "counts<double>() output:\n";counts<double>();return 0;
}

正如程序所见,两个counts报告的大小不同,证明了每个类型都有他自己的友元函数。

正对应每个counts的具体化。


3.非约束模板友元

很简单

class A
{
public:template<class C, class D> friend void show(C & c, D & d);
};

类外只需要将声明的分号去掉,friend去掉,接花括号定义即可。


模板别名

为了简化代码,可能会用typedef为模板类起一个别名,在C++11中也有类似的功能

template <class T>using arrtype = std::array<T,12>;

arrtype就是std::array<T,12>的别名了。功能比typedef更加强大一些。

//using arrtype = std::array<T,12>;
arrtype<int>; //等价于std::array<int,12>arrtype<string>; //等价于std::array<string,12>

using 还可以用于非模板类。当用于非模板时,这种语法与typedef等价

最后,关于具体化和实例化的一些说明

类定义(实例化)在声明类对象并指定特定类型时生成。例如,下main的声明将导致编译器声成类声明,用声明中的实际类型sotr替换模板类中的T

class IC <sotr> sic;

这里,类名为IC <sotr>,而不是IC,IC<sotr>被称为模板具体化

在这种情况下,编译器将使用通用模板生成一个int具体化——CI<int>,虽然尚未请求这个类的对象

可提供显式具体化——覆盖模板类定义的具体类声明,假如模板类不能很好的处理一个字符数组时,我们可以显式具体化一个

方法时template <>打头(就是语法),,然后是模板类名称,在加上尖括号(其中包含要具体化的类型)。

template <> class IC(char *)
{// T variable_name;char * str;
public:IC(const char * s) : str(s) {}...
}

这样,就可以获得专门处理字符数组的模板,而不是再用通用模板类方法,和模板函数的具体化也很像。

延庆川北小区45孙老师 东屯 收卖废品破烂垃圾炒股 废品孙

whaosoft aiot http://143ai.com

文章有点长 谅解哈

C++深入理解模板类相关推荐

  1. c++模板类(一)理解编译器的编译模板过程

    如何组织编写模板程序 前言 常遇到询问使用模板到底是否容易的问题,我的回答是:"模板的使用是容易的,但组织编写却不容易".看看我们几乎每天都能遇到的模板类吧,如STL, ATL, ...

  2. 问模板函数、函数模板,模板类、类模板的区别的问题?

    问模板函数.函数模板,模板类.类模板的区别的问题? - 赵保龙 - 博客园 问模板函数.函数模板,模板类.类模板的区别的问题? 在C++中有好几个这样的术语,但是我们很多时候用的并不正确,几乎是互相替 ...

  3. C++模板类的虚函数成员

    C++模板类只有在被使用的时候才会被特化,同样其成员函数也是在被使用的时候才被实例化.但是虚函数成员例外,原因应该是在定义一个模板类类型的变量时(使用模板类),为了确定虚函数表的大小,就已经实例化了虚 ...

  4. 模板类的全特化、偏特化

    我们先来定义一个普通的模板类 1 template<class T> 2 struct Test 3 { 4 Test(){ cout << "模板类" & ...

  5. [C++基础]034_C++模板编程里的主版本模板类、全特化、偏特化(C++ Type Traits)

    http://www.cnblogs.com/alephsoul-alephsoul/archive/2012/10/18/2728753.html 1.  主版本模板类 首先我们来看一段初学者都能看 ...

  6. 这篇文章绝对让你深刻理解java类的加载以及ClassLoader源码分析

    前言 package com.jvm.classloader;class Father2{public static String strFather="HelloJVM_Father&qu ...

  7. [转贴]从零开始学C++之STL(二):实现一个简单容器模板类Vec(模仿VC6.0 中 vector 的实现、vector 的容量capacity 增长问题)...

    首先,vector 在VC 2008 中的实现比较复杂,虽然vector 的声明跟VC6.0 是一致的,如下: C++ Code  1 2   template < class _Ty, cla ...

  8. [置顶] 从零开始学C++之STL(二):实现简单容器模板类Vec(vector capacity 增长问题、allocator 内存分配器)...

    首先,vector 在VC 2008 中的实现比较复杂,虽然vector 的声明跟VC6.0 是一致的,如下: C++ Code  1 2   template <  class _Ty,  c ...

  9. C++基础——用C++实例理解UML类图

    类展示 基本概念及术语 可见性visibility 参数的方向parameter direction 类成员变量或者函数的类型 类关系 关系的多重性multiplicity 类关系 assocatio ...

最新文章

  1. Centos7.2 基于LAMP框架部署Discuz论坛
  2. 2017/05/07 java 基础 随笔
  3. 多数据中心的高可用结构【环状星型数据库架构】
  4. java运行环境(JRE)
  5. 解决ArcGIS 9.3卸载时出现invalid install.log file的方法
  6. 如何在屏幕实时显示自己键盘的输入字符?
  7. 是什么还让你停留在 iOS 平台?是这些理由吗
  8. Linux学习笔记---移植官方linux步骤(二)
  9. SharePoint 关于拓扑错误的解决方案
  10. 4059. 统计某指定字符出现个数
  11. 名企笔试:京东 2016 算法工程师笔试题(登楼梯)
  12. 在线的图片、js、css压缩优化工具介绍(配合小强视频 前端性能分析精要)
  13. Excel如何批量根据身份证号码查询出地址
  14. 安卓kali安装mysql_安卓手机安装kali教程(root篇)
  15. 工程量计算专用工具-支持灌注桩、搅拌桩、格构柱
  16. c#实现短信发送程序
  17. ubuntu mysql mysqldb_告诉你在Ubuntu上安装MySQLdb的方法及命令
  18. 一招判断云主机IP是不是原生IP
  19. centos mysql 大小写_linux 、centos 安装MySQL及踩坑大小写敏感
  20. 祝福丨TF中文社区成立一周年

热门文章

  1. Redis 主从同步实现过程
  2. 苹果转向英特尔的冒险为何取得了巨大成功
  3. 产品经理必修课(5):用户研究
  4. Java酒瓶瓶盖_酒瓶与瓶盖配合常见问题分析
  5. SC-FDM和OFDM的区别
  6. 阿里巴巴fonticon字体图标的使用
  7. 贪心法求解磁盘驱动调度问题
  8. quartus仿真5:74194构建模8扭环形计数器
  9. MATLAB-偏最小二乘回归分析
  10. 《工业互联网行动计划》新旧对比,揭秘工业互联未来3年新格局!