谈一谈从 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了..正好春节回来写点东西,也正好对这段时间做一个总结. 首先当然还是好好说点这段时间的主要工作:语义分 ...
随机推荐
- GitLab CI/CD 进行持续集成
简介 从 GitLab 8.0 开始,GitLab CI 就已经集成在 GitLab 中,我们只要在项目中添加一个 .gitlab-ci.yml 文件,然后添加一个 Runner,即可进行持续集成. ...
- STM32F0使用LL库实现SHT70通讯
在本次项目中,限于空间要求我们选用了STM32F030F4作为控制芯片.这款MCU不但封装紧凑,而且自带的Flash空间也非常有限,所以我们选择了LL库实现.本篇我们将基于LL库采用模拟I2C接口的方 ...
- Mac 小功能
Safari 安装扩展 https://safari-extensions.apple.com/?category=translation 关闭第三方验证 有时候打开自己下载的安装包会提示 ...
- eclipse下properties文件中文乱码的解决方案
今天在工程下编辑.properties文件时输入了中文然后就保存出错,弄了好久才搞定!大家瞄瞄 在中文操作系统下,Eclipse中的Java类型文件的编码的默认设置是GBK,但是对Properties ...
- C语言博客作业2--循环结构
1.1 思维导图 1.2 本章学习体会及代码量学习体会 1.2.1 学习体会 对本章学习感觉相对前面的难度有较大提升,而且刚开始对嵌套循环比较陌生,像龟兔赛跑和输出菱形都是用了较长时间才完成,所以我认 ...
- 洛谷 P3366 【模板】最小生成树
题目链接 https://www.luogu.org/problemnew/show/P3366 题目描述 如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出orz 输入输出格式 输入格式: ...
- syslog-ng源码安装问题
title: 2019-4-22 tags: 新建 author:yangxiaoyi --- 问题:在源码安装syslog-ng软件时执行./configure遇到如下报错, require eve ...
- [原创]基于Zybo SDIO WiFi模块调试
采用的是RTL8189 SDIO 模块,介绍如下 The Realtek RTL8189ES-VB-CG is a highly integrated single-chip 802.11n Wire ...
- Spring 源码学习系列
前言 Spring框架之于 JavaEE 程序员来说,犹如锄头之于农民.Java 程序员每天都要使用Spring框架,Spring框架也确实是个可手的工具. 最初使用Spring的时候,我们需要配置m ...
- C# Common Log function
public int Log(string info) { info = "-----------------------------" + DateTime.Now.ToStri ...