前言

上一篇文章《Unity3D中常用的数据结构总结与分析》简单总结了一下小匹夫工作中经常遇到的一些数据结构。不过小匹夫一直有种观点,就是光说的热闹实际啥也不做真的没啥意思。光说不练假把式,那么这篇文章不如记录一下小匹夫自己动手实现一个有类似功能的数据结构的过程吧。

模仿List<T>

寻思半天,写代码是为了啥?不是为了写以致用嘛?那么小匹夫工作中用的最多的数据结构是啥?思来想去还就是List<T>了,而且平时使用的时候的确也觉得有自己定制的空间。作为一个类,重要的无非是它的名字,构造函数,属性和各种方法,因为小匹夫喜欢吃鸡蛋,再加上一个好朋友的喵叫蛋壳,所以咱们的新数据结构就叫做EggArray<T>好了,既然是要模仿List<T>,那么定好类名之后我们自然需要去参考一下List<T>的构造函数,属性和方法(列出的都是公有的)从而进一步来确定我们自己的类成员咯。不过呢,首先我们要先明确我们自己的EggArray<T>到底需要怎么实现,以及实现哪些功能,List<T>只是我们模仿的对象,如果实现的还都是List<T>自己的那一套,我们也就没有什么必要做现在的这些事情了。

EggArray<T>是什么

上一篇文章分析过,List<T>的内部其实也是一个Array,且是强类型的,所以我们的EggArray<T>也秉承这个特点,内部通过一个Array来实现,且需要声明类型。但是同时我们也看到List<T>继承和实现了很多接口,比如能实现foreach方法的IEnumerable接口等,而且值类型和引用类型通吃。这里为了EggArray<T>实现起来轻装简行,我们不继承List<T>继承的各种接口,同时我们的EggArray只服务于引用类型。(也是从方便和使用的角度考虑,毕竟值类型不能赋值null,引用类型可以赋值null这一点,作为一个博客的内容就没有必要去考虑服务值类型了)。那么小伙伴可能想问了,不继承那些接口,像最基本的foreach这种需求是不是匹夫混蛋你就不想实现了?NO,NO,俗话说得好,"车到山前必有路,听说委托也不错"。。。咳咳扯远了,其实也很简单,小匹夫上上篇文章《Unity3D中使用委托和事件(一)》介绍过的委托代理其实就可以用来实现EggArray<T>的foreach功能,甚至还有好多小匹夫自己定制的功能,比如Map,Filter,Without之类的。下面具体实现的时候小匹夫还会再扯。

EggArray<T>的成员

那么明确了大的方向,再经过小匹夫自己的定制,对List<T>的成员进行增减之后,我们的EggArray<T>类和它的成员(变量&&属性构造函数私有方法公有方法,小匹夫定制方法(在下一篇中说))如下:

EggArray类

//EggArray类
public class EggArray<T> where T : class
{
}

属性&变量(暂定,下一篇还会根据情况扩充):

属性

说明
Capacity EggArray的容量
Count EggArray中的元素个数
items T[],一个Array,因为上一篇文章说过List<T>的内部其实还是Array,所以内部我们也使用Array
foreachHandler 一个delegate,用来实现foreach的功能

//EggArray<T>的属性&&变量
private int capacity;
private int count;
private T[] items;
public delegate void foreachHandler(T item); public int Count
{
get
{
return this.count;
}
} public int Capacity
{
get
{
return this.capacity;
}
}

构造函数:

构造函数 说明
EggArray() 初始化 EggArray<T> 类的新实例,该实例为空并且具有默认初始容量。
EggArray(int32) 初始化 EggArray<T> 类的新实例,该实例为空并且具有指定的初始容量。
//EggArray的构造函数,默认容量为8
public EggArray() : this()
{
} public EggArray(int capacity)
{
this.capacity = capacity;
this.items = new T[capacity];
}

下面就是EggArray的各种方法了,上文小匹夫已经说过了,咱们这里只是参考List<T>列出来的一些公共方法,有一些List<T>烂大街的方法比如Add,Rmove这些公共方法肯定都是要实现的,可是一些私有方法咱们平时接触不到呀,甚至在List<T>也没有查到。那么小匹夫觉得很重要,也很能体现咱们EggArray<T>长度十分灵活特点的一个私有方法,应该就非那个能灵活改变数组长度的方法莫属了吧?我们称之为Resize()好了。

小匹夫还说过要自己定制一些平时会用到,但List<T>并没有现成方法的方法了。比如把EggArray<T>中的每个值映射到一个新的数组中的Map方法,遍历List中的每个值,返回包含所有通过predicate真值检测的元素值的Filter方法,或者是遍历List,以List中的元素的某个成员进行排序的indexBy方法,还有返回一个除去所有null值的Compact方法等等。下面就按照这3类不同的方法列出来我们的EggArray<T>中的方法。

私有方法

私有方法 说明
Resize 当数组元素个数大于或等于数组的容量时,调用该方法进行扩容,会创建一个新的Array存放数据,“增长因子”为2

//当数组元素个数不小于数组容量时,需要扩容,增长因子growthFactor为2
private void Resize()
{
int capacity = this.capacity * growthFactor;
if (this.count > capacity)
{
this.count = capacity;
}
T[] destinationArray = new T[capacity];
Array.Copy(this.items, destinationArray, this.count);
this.items = destinationArray;
this.capacity = capacity;
}

公共方法(List<T>也有的)

公共方法 说明
Add 将对象添加到 EggArray<T> 的结尾处。
AddRange 将指定集合的元素添加到 EggArray<T> 的末尾。
Insert 将元素插入 EggArray<T> 的指定索引处。
Contains 确定某元素是否在 EggArray<T> 中。
Clear 从 EggArray<T> 中移除所有元素。
ToArray 将 EggArray<T> 的元素复制到新数组中。
Sort 使用默认比较器对整个 EggArray<T> 中的元素进行排序。
Foreach 对 EggArray<T> 的每个元素执行指定操作。
Remove 从 EggArray<T> 中移除特定对象的第一个匹配项。
RemoveAt 移除 EggArray<T> 的指定索引处的元素。
Find 搜索与指定谓词所定义的条件相匹配的元素,并返回整个 EggArray<T> 中的第一个匹配元素。
IndexOf 搜索指定的对象,并返回整个 EggArray<T> 中第一个匹配项的从零开始的索引。

///List<T>已有的功能
/// <summary>
/// Add the specified item.
/// </summary>
/// <param name="item">Item.</param>
public void Add(T item)
{
if (this.count >= this.capacity)
{
this.Resize();
}
this.items[this.count++] = item;
}
/// <summary>
/// Adds the range.
/// </summary>
/// <param name="collection">Collection.</param>
public void AddRange(IEnumerable<T> collection)
{
if (collection != null)
{
foreach (T current in collection)
{
this.Add(current);
}
}
}
/// <summary>
/// Insert the specified index and item.
/// </summary>
/// <param name="index">Index.</param>
/// <param name="item">Item.</param>
public void Insert(int index, T item)
{
if (this.count >= this.capacity)
{
this.Resize();
}
this.count++;
for (int i = this.count - ; i > index; i--)
{
this.items[i] = this.items[i - ];
}
this.items[index] = item;
}
/// <summary>
/// Contains the specified arg.
/// </summary>
/// <param name="arg">Argument.</param>
public bool Contains(T arg)
{
for (int i = ; i < this.count; i++)
{
if (this.items[i].Equals(arg))
{
return true;
}
}
return false;
}
/// <summary>
/// Clear this instance.
/// </summary>
public void Clear()
{
if (this.count > )
{
for (int i = ; i < this.count; i++)
{
this.items[i] = null;
}
this.count = ;
}
}
/// <summary>
/// Tos the array.
/// </summary>
/// <param name="array">Array.</param>
public void ToArray(T[] array)
{
if (array != null)
{
for (int i = ; i < this.count; i++)
{
array[i] = this.items[i];
}
}
}
/// <summary>
/// Sort the specified comparer.
/// </summary>
/// <param name="comparer">Comparer.</param>
public void Sort(IComparer<T> comparer)
{
Array.Sort<T>(this.items, , this.count, comparer);
}
/// <summary>
/// Foreach the specified handler.
/// </summary>
/// <param name="handler">Handler.</param>
public void Foreach(EggArray<T>.IterationHandler handler)
{
for (int i = ; i < this.count; i++)
{
handler(this.items[i]);
}
}
/// <summary>
/// Remove the specified arg.
/// </summary>
/// <param name="arg">Argument.</param>
public bool Remove(T arg)
{
for (int i = ; i < this.count; i++)
{
if (this.items[i].Equals(arg))
{
this.items[i] = null;
this.Compact();
return true;
}
}
return false;
}
/// <summary>
/// Removes at index.
/// </summary>
/// <param name="index">Index.</param>
public void RemoveAt(int index)
{
if (index < this.count)
{
this.items[index] = null;
this.Compact();
}
} /// <summary>
/// Indexs the of.
/// </summary>
/// <returns>The of.</returns>
/// <param name="arg">Argument.</param>
public int IndexOf(T arg)
{
for (int i = ; i < this.count; i++)
{
if (this.items[i].Equals(arg))
{
return i;
}
}
return -;
}

以上便是我们仿照List<T>的公共方法所要实现的我们自己的公共方法,但是看说明我们很快就能发现一个问题。啥嘞?对嘞,就是很多方法都有方向性。比如Add方法,是将新的对象添加到EggArray<T>的末尾,可是我想要加到最开始怎么办。又或者Find方法,返回第一个满足条件的元素,但是要是我想要找最后一个匹配的呢?类似的问题还存在于IndexOf,Remove等等。所以这就是我们定制我们自己方法的定制思路之一:为了拓展已有方法的适用范围。(关于两端操作,大家想到了什么吗?没错,就是LinkedList,但是LinkedList本质上是链表,而我们的内部实现其实是Array,所以只是借鉴一下LinkedList的功能而非实现方法。其实这里对insert方法的实现就能看出和EggArray内部同为Array的List<T>在处理中间插入新的元素是多蛋疼的一件事情)

但是我们回到List<T>的MSDN页面,看看罗列出来的公有方法,总觉得少了点什么。哎?最直观的,貌似没有Slice呀。或者是我想做一些有限的过滤功能以得到符合我们简单需求的新数组,哎?貌似也没有Filter之类的功能?其实我们还有好多需求。。。那么我们第二条定制思路就有了:为了实现List<T>没有实现而我们日常需要用到的功能。在继续下面的内容之前,还是要简单说明一下几个需要注意的点。

  1. Insert方法,上面已经说过了,处理元素插入时,数组是不如链表的。
  2. Contains、IndexOf等方法,这里需要说明一下,在这些方法中我使用了.equles来判断作为参数传入的元素是否与数组内的元素值相同。作为一个处理引用类型的数据结构,我还是要说明一下equles和==的区别,即equals是比较他们的值,而==相当于比较它们在堆中的位置!即==判断的是是否是同一个对象。为了严谨,下面还将引入用==进行比较确定元素身份的方法。
  3. Foreach的实现手段,如上文所述,我们并没有继承和实现那么多接口,所以List<T>实现Foreach的手段我们就无法使用了。但是想想Foreach的目的无法就是遍历的过程中进行一些自己需要的操作,所以这里我使用了delegate来实现这一点。同样,Find这样的功能也可以通过delegate来实现,关于Find的实现放在下面的代码中了。

好啦,上面就是这篇文章的内容了,因为断断续续写了一周所以内容有点多,如果都盛放在一篇里面,可能连小匹夫都要有点密集恐惧症了。那么在下一篇文章《自己动手,实现一种类似List<T>的数据结构(二)》中,小匹夫将详细介绍下小匹夫觉得有用且有趣的方法。

装模作样的声明一下:本博文章若非特殊注明皆为原创,若需转载请保留原文链接及作者信息慕容小匹夫

自己动手,实现一种类似List<T>的数据结构(一)的更多相关文章

  1. 自己动手,实现一种类似List<T>的数据结构(二)

    前言: 首先,小匹夫要祝各位看官圣诞快乐,新年愉快-.上一篇文章<自己动手,实现一种类似List<T>的数据结构(一)> 介绍了一下不依靠List<T>实现的各种接 ...

  2. jquery另外一种类似tab切换效果

    简要:最近做项目一些效果不能用淘宝kissy框架 所以代码得自己写啊 网上当然有很多组件 但是用他们的代码很多(有的是我不需要的代码) 且还要看API 还不如自己动手写个简单一个,是这么一种简单的效果 ...

  3. SQL中一种类似GUID值的函数实现

        开发中会需要用到多列值组合成一个ID值的情况.比如做数据清洗的时候,一张表A有五列,分别是医院.科室.医生.职称.电话.面有许多重复的数据需要和另一个表B(和A列相同)做对比.清洗需要做两件事 ...

  4. 一种类似Retrofit声明接口即可实现调用的WebApi客户端框架

    为.Net出力 java有okhttp,还在okhttp这上搞了一个retrofit,.net有HttpClient,但目前我没有发现有类似的retrofit框架.最近在搞mqtt的webApi封装, ...

  5. React中ref的三种用法 可以用来获取表单中的值 这一种类似document.getXXId的方式

    import React, { Component } from "react" export default class MyInput extends Component { ...

  6. Apache是目前应用最广的Web服务器,PHP3是一种类似ASP的脚本语言

    一.如何获得软件? 获得这3个软件包的方法很多,目前大多数Linux分发都捆绑了这3个软件包,如RedHat.本文介绍的安装方法是基于从这些软件的官方站点上下载获得的软件包进行的,针对RedHat L ...

  7. 使用java语言实现一个队列(两种实现比较)(数据结构)

    一.什么是队列,换句话说,队列主要特征是什么? 四个字:先进先出 六个字:屁股进,脑袋出 脑补个场景:日常排队买饭,新来的排在后面,前面打完饭的走人,这就是队列: OK,思考一个问题,我为什么写了两种 ...

  8. 快速排序的一种实现(Mark Allen 数据结构与算法 c语言版)

    之前关于快速排序一直比较模糊,网上有几种常见写法: 方法一: void quickSort(int s[], int l, int r) { if (l< r) { int i = l, j = ...

  9. Go-利用Map实现类似Python的Set数据结构

    该笔记参考<Go并发编程实战> 首先实现一个自定义的HashSet 利用interface{}作为键,布尔型作为值. package main import ( "bytes&q ...

随机推荐

  1. mac osx 安装redis扩展

    1 php -v查看php版本 2 brew search php|grep redis 搜索对应的redis   ps:如果没有brew 就根据http://brew.sh安装 3 brew ins ...

  2. 在ubuntu16.10 PHP测试连接MySQL中出现Call to undefined function: mysql_connect()

    1.问题: 测试php7.0 链接mysql数据库的时候发生错误: Fatal error: Uncaught Error: Call to undefined function mysqli_con ...

  3. 参考bootstrap中的popover.js的css画消息弹框

    前段时间小颖的大学同学给小颖发了一张截图,图片类似下面这张图: 小颖当时大概的给她说了下,其实小颖也不知道上面那个三角形怎么画嘻嘻,给她说了DOM结构,具体的css让她自己百度,今天小颖自己参考boo ...

  4. CSS知识总结(八)

    CSS常用样式 8.变形样式 改变元素的大小,透明,旋转角度,扭曲度等. transform : none | <transform-function> <transform-fun ...

  5. 【SAP业务模式】之ICS(二):基础数据

    讲完业务,计划在前台做一下ICS的基本操作,不过在操作之前,得先建立好基本的基础数据. 1.首先创建接单公司LEON,对应工厂是ADA: 2.创建生产公司MXPL,对应工厂是PL01: 3.创建接单公 ...

  6. [转载]敏捷开发之Scrum扫盲篇

    现在敏捷开发是越来越火了,人人都在谈敏捷,人人都在学习Scrum和XP...      为了不落后他人,于是我也开始学习Scrum,今天主要是对我最近阅读的相关资料,根据自己的理解,用自己的话来讲述S ...

  7. MySQL全文索引 FULLTEXT索引和like的区别

    1.概要 InnoDB引擎对FULLTEXT索引的支持是MySQL5.6新引入的特性,之前只有MyISAM引擎支持FULLTEXT索引.对于FULLTEXT索引的内容可以使用MATCH()-AGAIN ...

  8. 第六代智能英特尔® 酷睿™ 处理器图形 API 开发人员指南

    欢迎查看第六代智能英特尔® 酷睿™ 处理器图形 API 开发人员指南,该处理器可为开发人员和最终用户提供领先的 CPU 和图形性能增强.各种新特性和功能以及显著提高的性能. 本指南旨在帮助软件开发人员 ...

  9. Jexus 服务器部署导航

    说明:本索引只是方便本人查找,不涉及版权问题,所有博客,还是到元博客地址访问. <script async src="//pagead2.googlesyndication.com/p ...

  10. 我对BFC的理解

    最初这篇文章打算回答寒冬大神的第一问,谈谈CSS布局.本来呢我以为布局主要涉及float跟display相关属性,以及他们的包含框.静态位置等等.后来看了大神的一片面试文章,嗯?这里怎么还有个BFC, ...