第2章:C++泛型机制的基石:数据类型表——《C++泛型:STL原理和应用》读书笔记整理

时间:2021-07-10 00:19:59

第二章:C++泛型机制的基石——数据类型表

2.1 类模板的公有数据类型成员
2.1.1 类的数据类型成员

  C++类中不仅可以定义数据成员和函数成员,而且还可以定义数据类型成员。在泛型设计中,类的数据类型成员是一个常用的感念。所谓类的数据类型成员,就是在一个类中使用typedef定义一个已知数据类型的别名。例如:

typedef long double LDBL

  在C++中,这种在类模板中定义的数据类型也称nested type(嵌入式类型)。既然nested type与字段、方法都属于类成员,那么当然可以为它们赋予响应的访问属性。于是,这些具有public属性的类型成员就成为类外部模块可以使用的公有类型成员。显然,外部模块就可以通过这些公有类型成员,实现与类模板之间的功能协作

template<typename T1, typename T2>
class MyTraits{
public:
typedef T1 my_Type1;
typedef T2 my_Type2;
}; int main(int argc, char** argv){
//类外引用类模板的公有类型成员,与引用静态成员一样。
//为了区别,需要加上typename关键字
typename MyTraits<int, double>::my_Type1 t1;
typename MyTraits<int, double>::my_Type2 t2;
}
2.1.2 再谈typedef

  typedef字面的意思是类型定义,或定义类型。但是它不能凭空定义一个类型,它只能为某个现有类型(系统固有的、用户自定义的)命名一个别名。人们常常使用别名的原因相同,那就是在某些时候使用别名会带来极大的好处。

  1. 增加程序的可读性。

    可以通过给某一数据类型命名别名的方法说明这个数据类型在应用中的作用。
typedef int Phone;
typedef int Age; //很明显wangxin_P这个变量就是用来存储电话的!
Phone Wangxin_P;
  1. 为冗长复杂的类型命名简单的别名。
/*
一个数组,数组存放的是:
函数指针:{
参数:指针函数:{
参数:空;
返回值:空;
}
返回值:空;
}
*/
void ( *b[10] )( void(*)() );
//pFunParam是一个函数指针,指向参数为空,返回值为空的函数类型。
typedef void(* pFunParam) ();
//pFunx是一个函数指针,指向参数为别名为pFunParam的类型,返回值为空。
typedef void(*pFunx) (pFunParam); void (*b[10])(void(*)());
pFunx b[10];
/*
以上两者等价:
声明一个数组b,具有十个元素,元素类型是:
函数指针:{
参数:函数指针:{
参数:空;
返回值:空;
}
返回值:空;
}
*/
  1. 编写平台无关代码(跨平台)

    最值得称道的 typedef 应用莫过于编写平台无关代码。具体做法是使用类名别名编程,例如使用 UINT32 这个别名定义程序中的32位无符号整型变量,至于这个别名的真实类型名则根据运行这个代码的平台使用 typedef 确定。也就是说,在更换运行平台时,源代码不需要进行改动!!!!
//平台A
typedef int UINT32;
//平台B
typedef unsigned int UINT32;
2.2 内嵌式数据类型表及数据类型衍生

  为方便使用,一个类模板会把用 typedef 定义的所有公有数据类型集中形成一个数据类型表,并放在模板中靠前的位置。这个数据类型表存在于类模板内部,所以叫内嵌式数据类型表

  为了充分利用从模板参数中获得数目有限的数据类型,一个模板通常会尽可能利用那些从模板参数获得的类型资源,除了把通过模板参数传递过来的类型定义成公有成员之外,也把它们的衍生类型也一并定义成公有成员。

//有人把这种数据类型表形象地称为类型榨取机,或者类型萃取机
template<typename T>
struct map{
typedef T value_type;
typedef T& reference;
typedef T* pointer;
}
2.3 数据类型表
2.3.1 数据类型表的概念

  在一个类模板中,如果其全部成员都是公有数据类型,那么相对于嵌入式数据类型表,这个类模板就叫独立数据类型表,简称数据类型表

  这种表通常被用于规范模板类型表。假如有一个项目,要求项目中的模板都必须至少使用两个参数,而且必须向外提供这两个参数的值类型及引用类型,那么为了说明这个要求,项目负责人就可以写一个如下独立数据类型表。

template<typename T, typename U>
class TypeTb1{
public:
typedef T value_type1;
typedef T& reference1; typedef U value_type2;
typedef U& reference2;
};

  然后要求其他人所设计的类模板必须继承自这个独立类型表。于是凡是继承了 TypeTb1 的类就都有一张与 TypeTb1 完全相同的类型表。简单地说,这种独立类型表就是一种接口,是一种类型接口

template<typename T, typename U>
class A:public TypeTb1<T, U>
{
//类A的设计内容
}; //例如STL使用binary类模板描述了它对标准二元函数(有两个形参的函数)的要求
template<class _A1, class _A2, class _R>
struct binary
{
typedef _A1 Arg1;
typedef _A2 Arg2;
typedef _R Rtn;
}
2.3.2 数据类型表的应用
template<typename T>
class Num:public TypeTb1<T>
{
//继承了TypeTb1
}; //普通测试函数,可以发布Num<int>的数据类型表
//缺点,重用性差,每次都需要修改模板参数
//事实证明编译器已经只能到可以省略显式调用数据类型表时的typename关键字了
void Tfunction(Num<int>::value_type x,
Num<int>::reference y,
Num<int>::pointer z){
cout<<"x="<<x<<endl;
cout<<"y=&x="<<y<<endl;
cout<<"*z="<<z<<endl;
}
//测试函数模板,注意:
//1.Num不是类型,Num<int>才是类型。
//2.需要使用关键字typename。隐式调用需要关键字。
template<typename T>
void Tfunc(typename T::value_type x,
typename T::reference y,
typename T::pointer z){
cout<<"x="<<x<<endl;
cout<<"y=&x="<<y<<endl;
cout<<"*z="<<z<<endl;
} int main(int argc, char** argv){
//显式调用时,typename关键字可以省略
typename Num<int>::value_type a = 100;
//调用普通测试函数
Tfunction(a, a, &a);
//调用测试函数模板,类型为Num<int>,而不是Num,Num类模板。
Tfunc<Num<int>>(a, a, &a); return 0;
}
2.4 特化数据类型表

  众所周知,数据类型表就是一种类模板,而由特化类模板形成的数据类型表就是特化类型表。特化类型表的作用之一就是为了实现同一业务逻辑不同接口的统一

class Test1{
public:
char compute(int x, double y){
return x;
}
};
class Test2{
public:
int compute(double x, double y){
return x;
}
};
//Test1和Test2两个类的逻辑相同,只是接口不同
//出于代码重用的方式,我们可以写成一个类模板
template<typename Arg1, typename Arg2, typename Ret>
class Test{
public:
Ret compute(Arg1 x, Arg2 y){
return x;
}
};
//这种方法的缺点就是我们需要在声明的时候给出三个模板参数
Test<int, double, char> t1; //但是如果只是区分两个类,不考虑其他参数类型情况,我们不必如此麻烦
//我们可以使用一套统一的数据类型表,作为类的接口
template<typename T>
class TypeTb1{
typedef int ret_type; //返回值数据类型
typedef int par1_type; //参数1的数据类型
typedef int par2_type; //参数2的数据类型
};
//声明两个空类,作为两个标识符,注意Test1和Test2都是类型
class Test1;
class Test2;
//特例化数据类型表,当模板参数为Test1类时
template<>
class TypeTb1<Test1>{
typedef char ret_type; //返回值数据类型
typedef int par1_type; //参数1的数据类型
typedef double par2_type; //参数2的数据类型
};
template<>
class TypeTb1<Test2>{
typedef int ret_type; //返回值数据类型
typedef double par1_type; //参数1的数据类型
typedef double par2_type; //参数2的数据类型
};
//使用统一数据类型表作为接口,声明的类模板
template<typename T>
class Test{
public:
//当模板参数为Test1或者Test2时,生成特例化类型表
//所以compute函数的参数与返回值类型跟着改变
typename TypeTb1<T>::ret_type compute(
typename TypeTb1<T>::par1_type x,
typename TypeTb1<T>::par2_type y){
return x;
}
}
//成功通过一个模板参数区分了两个类
Test<Test1> t1;
Test<Test2> t2;
2.5 STL 中的Traits表

  Traits 实际上是特化数据类型表在STL中的一个具体应用,也是数据类型表。但是因为他构思巧妙,备受推崇,人称Traits技巧。

  1. 应用背景

      在STL中有一种具有指示数据位置功能的对象,叫做迭代器( iterator ),它们大多是类模板,并在模板中包含内嵌数据类型表,从而能向外提供模板参数传递过来的数据类型及其衍生类型的别名给用户。这些 Iterator 类模板代码大体如下
template<typename T>
class Iterator_1{
public:
typedef T value_type;
typedef value_type* pointer;
typedef value_type& reference;
//其余代码···
}
//Iterator_2, Iterator_3 ......
  1. 使用特化模板实现指针的数据类型表

      众所周知,C/C++指针也是用来指示数据位置的,因此也属于迭代器范畴,所以为了类型接口的统一,那么指针也应该能向外提供数据类型。但很遗憾,指针是C/C++的原生类型不是类或者类模板,不可能有内嵌数据类型表,所以必须想别的方法满足 STL 的要求。

      指针之所以不能向外提供数据类型,原因就在于它不是一个类,没有用于建立数据类型表的地方,所以首先要为指针配备一个可以容纳指针数据类型表的类模板。显然,为了使应用程序以 T* 类型找到这个类模板,这应该是一个如下形式的特化模板
//基础模板
template<typename T>
struct Traits{
//空表
}
//指针的特化模板
template<typename T>
struct Traits<T*>{
typedef T value_type;
typedef value_type* pointer;
typedef value_type& reference;
}
//Traits表特别体现了数据类型表的“类型压榨”能力
//即它不仅能把用户经由模板参数传递来的一个数据类型的所有衍生类型都能提取出来,
//而且还可以把衍生类型的原生数据类型压榨出来。
Traits<double*>::value_type t1 = 100;
cout<<t1<<endl;
  1. 汇总同类类模板的内嵌数据类型表形成统一接口

      使用特化数据类型表解决了原生类型的数据类型表的问题,但各个迭代器的数据类型表还是分散于各自迭代器类中的,不利于管理。因此希望有一个同一类事物(这里是迭代器)数据类型表的总表。

      于是 STL 的设计者看到了 Traits 基础模板的那个空表。如果将各个迭代器模板中的数据类型表内容稍加改变后复制到这个空表中不是挺好的吗!那 Traits 就是迭代器类型表的总表。于是 Traits 的空表就变成了下面的样子:
//这里的模板参数T的实参将来都是各个迭代器类以及T*。
template<typename T>
struct Traits{
//因为编译器不认识带有域分隔符::的类型,因此这里需要使用关键字typename
typedef typename T::value_type value_type; //隐式调用数据类型成员
typedef typename T::pointer pointer;
typedef typename T::reference reference;
}
//指针特化模板与之前一样。
//测试如下,由此可见数据类型表都变成了Traits,统一了接口
Traits<Iterator_1<int>>::value_type t1 = 100;
Traits<Iterator_2<double>>::value_type t2 = 9.23;
Traits<double*>::value_type t3 = 3.33;

第2章:C++泛型机制的基石:数据类型表——《C++泛型:STL原理和应用》读书笔记整理的更多相关文章

  1. STL源码分析读书笔记--第二章--空间配置器(allocator)

    声明:侯捷先生的STL源码剖析第二章个人感觉讲得蛮乱的,而且跟第三章有关,建议看完第三章再看第二章,网上有人上传了一篇读书笔记,觉得这个读书笔记的内容和编排还不错,我的这篇总结基本就延续了该读书笔记的 ...

  2. 第1章:C&plus;&plus;泛型技术基础:模板——《C&plus;&plus;泛型:STL原理和应用》读书笔记整理

    第1章:C++泛型技术基础:模板 1.2 关于模板参数 1.2.1 模板参数类型 类型参数   typename声明的参数都属于类型参数,它的实参必须为系统内置或者用户自定义的数据类型,包括类模板实体 ...

  3. STL源码分析读书笔记--第5章--关联式容器

    1.关联式容器的概念 上一篇文章讲序列式容器,序列式容器的概念与关联式容器相对,不提供按序索引.它分为set和map两大类,这两大类各自有各自的衍生体multiset和multimap,的底层机制都是 ...

  4. STL源码剖析读书笔记--第6章&amp&semi;第7章--算法与仿函数

    老实说,这两章内容还蛮多的,但是其实在应用中一点点了解比较好.所以我决定这两张在以后使用过程中零零散散地总结,这个时候就说些基本概念好了.实际上,这两个STL组件都及其重要,我不详述一方面是自己偷懒, ...

  5. STL源码分析读书笔记--第三章--迭代器(iterator&rpar;概念与traits编程技法

    1.准备知识 typename用法 用法1:等效于模板编程中的class 用法2:用于显式地告诉编译器接下来的名称是类型名,对于这个区分,下面的参考链接中说得好,如果编译器不知道 T::bar 是类型 ...

  6. STL源码剖析读书笔记--第四章--序列式容器

    1.什么是序列式容器?什么是关联式容器? 书上给出的解释是,序列式容器中的元素是可序的(可理解为可以按序索引,不管这个索引是像数组一样的随机索引,还是像链表一样的顺序索引),但是元素值在索引顺序的方向 ...

  7. Android开发艺术探索——第二章:IPC机制(中)

    Android开发艺术探索--第二章:IPC机制(中) 好的,我们继续来了解IPC机制,在上篇我们可能就是把理论的知识写完了,然后现在基本上是可以实战了. 一.Android中的IPC方式 本节我们开 ...

  8. Java核心技术卷一基础知识-第12章-泛型程序设计-读书笔记

    第12章 泛型程序设计 本章内容: * 为什么要使用泛型程序设计 * 定义简单泛型类 * 泛型方法 * 类型变量的限定 * 泛型代码和虚拟机 * 约束与局限性 * 泛型类型的继承规则 * 通配符类型 ...

  9. Android群英传》读书笔记 &lpar;3&rpar; 第六章 Android绘图机制与处理技巧 &plus; 第七章 Android动画机制与使用技巧

    第六章 Android绘图机制与处理技巧 1.屏幕尺寸信息屏幕大小:屏幕对角线长度,单位“寸”:分辨率:手机屏幕像素点个数,例如720x1280分辨率:PPI(Pixels Per Inch):即DP ...

随机推荐

  1. DDL&lpar;Oracle&rpar;

    DDL       数据定义       建表       建视图 建其他 drop create table t (a varchar2 (10));可变字符串最大为10 transaction - ...

  2. 利用脚本修改SQL SERVER排序规则

    利用脚本修改SQL SERVER排序规则 编写人:CC阿爸 2014-3-1 l  今年的一项重要工作是对公司所用系统进行繁简的转换,程序转成简体基本很容易解决,但数据库转换成简体,就没那么容易了.经 ...

  3. erlang常用命令

    1 erlang启动时就运行odbc erl -s odbc 2 ping 节点 net_adm:ping('rabbit@COMPUTERNAME'). 3 运行cmd命令 os:cmd(&quot ...

  4. jQuery动画效果实现

    <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...

  5. Kafka Offset Storage

    1.概述 目前,Kafka 官网最新版[0.10.1.1],已默认将消费的 offset 迁入到了 Kafka 一个名为 __consumer_offsets 的Topic中.其实,早在 0.8.2. ...

  6. iOS&lowbar;38&lowbar;手势

    Pan平移手势 终于效果图: Swipe轻扫手势 LongPress长按手势 Pinch和Rotation手势 捏合(缩放)和旋转 终于效果图: 涂鸦 终于效果图: 事件分3大类:触摸.加速计.远程遥 ...

  7. Make Things Move -- Javascript html5版(一)文件目录结构和工具方法准备

    从这一篇开始,就来开始我们的make things move之旅吧 在此之前,要知道ActionScript(AS)的语法和JS是不一样的,AS是相对于JS而言更好的支持了面向对象的特性,所以我们可以 ...

  8. SVN使用小记

    SVN(Subversion)是优秀的版本控制工具,之前在eclipse里面项目管理的时候,File-->Import-->SVN-->从SVN检出项目-->创建新的资源库位置 ...

  9. 《设计模式之禅》--备忘录扩展:clone方式的备忘录

    接上篇<设计模式之禅>--策略扩展:策略枚举 需求:使用clone方式实现备忘录模式 发起人角色 public class Originator implements Cloneable ...

  10. Java经典设计模式之十一种行为型模式(附实例和详解)

    Java经典设计模式共有21中,分为三大类:创建型模式(5种).结构型模式(7种)和行为型模式(11种). 本文主要讲行为型模式,创建型模式和结构型模式可以看博主的另外两篇文章:Java经典设计模式之 ...