谈一谈从 Delphi 2009 之后就支援的重要功能 – 泛型 (Generic)
前言
在C++的语言基础当中,除了物件导向、事件驱动的概念之外,模版设计(Template)也是非常重要的一环。然而,C++的开发人员能够善用模版设计的并不多。模版设计这个好物,一般还有一个名称,就是泛型 (Generic),这个好物在Delphi 2009 之后,也已经被加入到 Object Pascal里面了,只是我们实在很少用到它。
然而,江湖一点诀,说破没秘诀,大家对于泛型的少用,很多是因为不知道有这个功能,其次是知道有这个功能却不知道怎么使用。
所以,我们这一篇就来深入浅出的介绍一下『泛型』是什么,顺便用几个简单的范例来使用『泛型』吧。
泛型? 样板? 揭起它的神秘面纱
所谓的泛型、样板,其实就是在写code的时候,把需要先定义好型别的宣告用一个关键字 <T> 来取代,未来真正在使用的时候,把T改成真正的型别,就可以让这段code适用于多种不同的型别了。
这样说明,如果您就听懂了,那应该也不需要来看这篇文章,表示您的悟性颇高,属于非常有能力的Programmer。(谜之音:喵的,听的懂我跟你姓! 这不是跟我大学资料结构或物件导向程式设计老师说的一样嘛?)
用实例来说明吧,我们说地球话,才不会被赶回火星……….
以前,写资料结构作业的时候,或者用 Object Pascal 写程式的时候,如果我们要用Delphi 来实作一个堆叠,我们通常会这么想:
- 堆叠嘛,资料要先进先出,所以要宣告一个阵列来储存资料
- 然后要定义一个 Push 方法,把资料放进去
- 也要定义一个Pop方法,把资料取出来
- 因为是堆叠,所以是后进先出 (最后放进去的要最先被取出,只有一个进出口)
可以用底下这张图片来帮助思考:
以一个存放『整数』的堆叠来说,最最基本的宣告一般就会写成这样:
TMyStack = class (TObject)
Private
FElements: array[0..5] of Integer; // 用来存放元素的阵列.
Public
Function push(element: integer) : integer; // 可以传回 push进去的元素放在什么位置.
Function pop: integer; // 直接传回最后一个元素.
Constructor Create(); ovevrride; reintroduce;
Destructor Destory(); override;
End;
实作的程式码我就不写了,最近实在太多学生上网到处找作业的答案范本。
这段程式码宣告了一个名为 TMyStack 的堆叠类别 (Stack Class),里面是有很多问题的,例如 FElements 只能放 6 个整数,有元素个数的限制,因为我们前面说过这是一个存放『整数』的堆叠,所以 push 方法的参数是整数型别,pop 方法所回传的资料也是整数型别。
先来解决资料长度限制的问题
我记不清是从 Delphi 5 还是 Delphi 7开始,Object Pascal就被赋予了可变长度阵列的功能,可以透过 setLength 来调整阵列的长度,宣告的写法可以写成:
Var
varLengIntArray : array of Integer;
调整长度的作法则是:
setLength(varLengIntArray, 20);
后面的数字就是阵列调整后的长度。
这样的作法,让上面的整数堆叠阵列脱离了固定长度的限制,改写过的 Class 宣告就会变成:
TMyStack = class (TObject)
Private
FElements: array of Integer; // 用来存放元素的阵列.
FElementCount: integer;
Public
Function push(element: integer) : integer; // 可以传回 push进去的元素放在什么位置.
Function pop: integer; // 直接传回最后一个元素.
Property count: integer; read FElementCount;
Constructor Create(); ovevrride; reintroduce;
Destructor Destory(); override;
End;
这样修改以后,push跟pop方法里面也都要有相对应的程式修改,例如在 Create的时候,就要先对 FElementCount 做初始化,push 跟 pop 方法里面,也得调整长度:
Constructor TMyStack.Create();
Begin
Inherited Create();
FElementCount := 0; // 初始化,把元素个数设为 0;
setLength(FElements, 0); // 初始化,把阵列长度也设为 0;
end;
function TMyStack.push(element: integer) : integer;
begin
Inc(self.FElementCount); // 把元素个数加一
setLength(FElements, self.FElementCount); // 把阵列长度也多加一个
self.FElements[self.FElementCount -1] := element; // 把要 push 的元素放在新增的
// 阵列位置上
End;
Function TMyStack.pop: integer;
Begin
Result := self.FElements[self.FElementCount -1]; // 把最后一个元素回传.
Dec(self.FElementCount); // 把元素个数减一
setLength(FElements, self.FElementCount); // 把阵列长度也多减掉一个
end;
这样修改完以后,整数堆叠就没有长度限制了。
但是,我们只需要整数堆叠吗? 会不会明天要一个字串堆叠? 后天会不会要一个自定 record 或者 class 的堆叠?
如果每次需要堆叠,就要重写一次上面的程式码,而要修改的地方只有型别,那不是烦死人了?如果又好死不死遇到堆叠里面要加一些额外的功能(客户的想像力永远走在我们前面, 你说是吧?),那所有堆叠的程式码要一个一个去修改,光想像就很想对电脑下毒手………
那有没有什么方法,可以让我们写一个堆叠,就可以存放所有型别?
当然有,泛型,就是我们的救赎啊……..
要使用泛型,我们得在 use 区段里面引入 System.Generics.Collections,这里面有非常多的好物可以用。
我们首先把前面已经改过的类别宣告,再做一些小调整,使用TArray<T> 这段程式码来取代 array of Integer,让 FElements 可以容纳各种型别的资料:
TMyStack<T> = class (TObject)
Private
FElements: TArray<T>; // 用来存放元素的阵列.
FElementCount: integer;
Public
Function push(element: T) : integer; // 可以传回 push进去的元素放在什么位置.
Function pop: T; // 直接传回最后一个元素.
Property count: integer; read FElementCount;
Constructor Create(); ovevrride; reintroduce;
Destructor Destory(); override;
End;
实作的程式码则需要修改为:
Constructor TMyStack<T>.Create();
Begin
Inherited Create();
FElementCount := 0; // 初始化,把元素个数设为 0;
setLength(FElements, 0); // 初始化,把阵列长度也设为 0;
end;
function TMyStack<T>.push(element: T) : integer;
begin
Inc(self.FElementCount); // 把元素个数加一
setLength(FElements, self.FElementCount); // 把阵列长度也多加一个
self.FElements[self.FElementCount -1] := element; // 把要 push 的元素放在新增的
// 阵列位置上
End;
Function TMyStack<T>.pop: T;
Begin
Result := self.FElements[self.FElementCount -1]; // 把最后一个元素回传.
Dec(self.FElementCount); // 把元素个数减一
setLength(FElements, self.FElementCount); // 把阵列长度也多减掉一个
end;
我的老天鹅啊,这真是太方便了吧,程式码这样写就好了? 那使用上要怎么用?
就这样:
Var
integerStack : TMyStack<Integer>;
begin
integerStack := TMyStack<Integer>.Create;
try
integerStack.push(79);
integerStack.push(7);
integerStack.push(21);
integerStack.push(13);
finally
integerStack.Free;
end;
end;
上述这段程式码,在 finally执行以前,就会建立出以下图为范例的堆叠资料了:
我们也可以做字串堆叠:
Var
stringStack : TMyStack<String>;
begin
stringStack := TMyStack<String>.Create;
try
stringStack.push(‘这’);
stringStack.push(‘就’);
stringStack.push(‘是’);
stringStack.push(‘泛’);
stringStack.push(‘型’);
stringStack.push(‘啊’);
finally
integerStack.Free;
end;
end;
上述这段程式码,在 finally执行以前,建立出来的堆叠资料则如下图:
这样一来,程式码都没有变,我们只在使用 TMyStack<T> 这个 Class 的时候,在宣告、建立Class的时候指明要使用什么型别,就能够自由的把一份程式码用在各种不同型别上了,是不是很方便?
在 System.Generics.Collections 里面,TList<T>更是好用,以前我们得要自己做TObjectList,才能透过所有物件都是从 TObject 衍生出来的特性建立出可以储存物件的List,而且每次使用的时候还得做型别转换才能正确使用。
现在透过 TList<T>,这些额外的程式码、型别转换的工作就都省下来了,甚至连TStack<T>, TQueue<T>, 也都有提供,是不是也让您想要玩玩看了呢?
泛型说穿了,就是把原本我们需要先写明的型别,用<T>这个关键字取代掉,而改以在实际宣告、使用的时候才叙明型别,这样一来,真的省下好多好多程式码,也省下很多时间可以做其他更有意义的事情了,当然,这些事情还是要我们自己去发掘的,大家加油!
谈一谈从 Delphi 2009 之后就支援的重要功能 – 泛型 (Generic)的更多相关文章
- 从一张图开始,谈一谈.NET Core和前后端技术的演进之路
从一张图开始,谈一谈.NET Core和前后端技术的演进之路 邹溪源,李文强,来自长沙.NET技术社区 一张图 2019年3月10日,在长沙.NET 技术社区组织的技术沙龙<.NET Core和 ...
- Delphi 2009 泛型容器单元(Generics.Collections)[1]: TList<T>
Delphi 2009 新增了泛型容器单元: Generics.Collections, 同时还有一个 Generics.Defaults 单元做支持. Generics.Collections 包含 ...
- 谈一谈Java8的函数式编程(二) --Java8中的流
流与集合 众所周知,日常开发与操作中涉及到集合的操作相当频繁,而java中对于集合的操作又是相当麻烦.这里你可能就有疑问了,我感觉平常开发的时候操作集合时不麻烦呀?那下面我们从一个例子说起. 计 ...
- 谈一谈泛型(Generic)
谈一谈泛型 首先,泛型是C#2出现的.这也是C#2一个重要的新特性.泛型的好处之一就是在编译时执行更多的检查. 泛型类型和类型参数 泛型的两种形式:泛型类型( 包括类.接口.委托和结构 没有泛型枚 ...
- 谈一谈Elasticsearch的集群部署
Elasticsearch天生就支持分布式部署,通过集群部署可以提高系统的可用性.本文重点谈一谈Elasticsearch的集群节点相关问题,搞清楚这些是进行Elasticsearch集群部署和拓 ...
- 谈一谈iOS事件的产生和传递
谈一谈iOS事件的产生和传递 1.事件的产生 发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中. UIApplication会从事件队列中取出最前面的事件,并将事件 ...
- 谈一谈对MySQL InnoDB的认识及数据库事物处理的隔离级别
介绍: InnoDB引擎是MySQL数据库的一个重要的存储引擎,和其他存储引擎相比,InnoDB引擎的优点是支持兼容ACID的事务(类似于PostgreSQL),以及参数完整性(有外键)等.现在Inn ...
- 谈一谈APP版本号问题
如题:谈一谈APP版本号问题 为什么要谈这个问题,周五晚上11~12点,被微信点名,说APP有错,无效的版本号,商城无法下单.我正在准备收拾东西,周末回老家,结果看到这样问题,菊花一紧.我擦,我刚加的 ...
- 谈一谈深度学习之semantic Segmentation
上一次发博客已经是9月份的事了....这段时间公司的事实在是多,有写博客的时间都拿去看paper了..正好春节回来写点东西,也正好对这段时间做一个总结. 首先当然还是好好说点这段时间的主要工作:语义分 ...
随机推荐
- 1、js的基本对象和垃圾回收
js常用的基本类型:Undefined,null,string,number,boolen 还有一种复杂的数据类型 object.判断类型可以用 typeof. 确定值是否是有穷的,isFinite, ...
- tp5 自定义排序
- LoadRunner学习笔记(三)
一. LR如何监控Windows系统资源 一般通过LR进行压力测试,都需要实时监控服务端的系统资源,我们可以直接在远程连接服务器上面开启任务管理器 或者在控制面板中找到性能计数器来监控,但是为了在L ...
- cc.Lable组件,RichText组件,AudioSouce组件的使用
一.cc.Lable组件的使用 1.创建Label的方法 a.通过菜单直接创建Label组件:b.先创建节点,然后在节点上绑定Label组件即可. 2.Label 面板上的属性 String => ...
- 【JAVA】HashMap的原理及多线程下死循环的原因
再次翻到以前工作中遇到的一个问题,HashMap在多线程下会出现死循环的问题,以前只是知道会死循环,导致CPU100%把机器拖跨,今天来彻底看看 首先来看下,HashMap的原理:HashMap是一个 ...
- Definition of matrix norms
In my previous post, I introduced various definitions of matrix norms in \(\mathbb{R}^{n \times n}\) ...
- SharePoint修改左上角文字的命令行
$webapp = Get-SPWebApplication “http://test-spweb1” --需要修改的站点$webapp.SuiteNavBrandingText = “XXXXXX” ...
- Monkey如何使用
1.Monkey是Google提供的一个命令行工具,可以运行在模拟器或者实际设备中.它向系统发送伪随机的用户事件(如按键.手势.触摸屏等输入),对软件进行稳定性与压力测试. Monkey是什么:Mon ...
- 网络对抗技术 20165220 Exp6 信息搜集与漏洞扫描
网络对抗技术 20165220 Exp6 信息搜集与漏洞扫描 实验任务 (1)各种搜索技巧的应用 (2)DNS IP注册信息的查询 (3)基本的扫描技术:主机发现.端口扫描.OS及服务版本探测.具体服 ...
- net view ERROR 6118
如上图,使用net view命令出现错误6118(server2012),谷歌百度油管解决了. 尝试1: 把Computer Browser服务打开.https://blog.csdn.net/qq_ ...