C#学习笔记13:大练习作业参考解析

-呈阶梯状分布- 28 次阅读 2167 字 发布于 25 天前 最后更新于 25 天前 C#学习笔记


题目

先来看上一篇中留下的作业题目:
写下一个名为MyList的类,仿造List类,并实现以下功能:

  1. 在构造方法中传入初始元素(传入元素的数量不定)
  2. 实现索引器索引功能,且在索引超出范围时抛出异常,如throw new ArgumentOutOfRangeException("索引参数超出范围");
  3. 拥有Count和Capacity两个属性,分别代表元素个数和数组长度,其中Count只读,Capacity只能增大,变小则抛出异常,如第2条,作用域都为public
  4. 实现Add,Insert,Sort,RemoveAt,IndexOf,LastIndexOf,Remove,作用域都为public
  5. 在Add,Insert中实现扩容机制
  6. 重载==运算符,要求有两个MyList<T>类型的参数,实现当两个MyList元素数量和内容相同时返回true,否则返回false,同时重载!=运算符
  7. 拥有两个字段,一个是data,用于作为数组存元素;一个是count,作为元素个数使用,作用域都为private

注意事项如下:

  1. 扩容时注意Count和Capacity元素修改
  2. 在Add,Insert,RemoveAt,Remove中注意Count元素修改
  3. 对于无法使用==时,可以使用a.Equals(b)判断a和b是否相等
  4. 对于!=重载,取==的反义即可
  5. 建议在类内大多情况下使用属性,而非私有字段

接下来我们一步步写下代码:

代码框架

首先,我们创建一个类MyList。

using System;

namespace Csharp
{
    class MyList
    {
        
    }
    class Program
    {
        static void Main(string[] args)
        {

        }
    }
}

然后加入字段data(存储数组)和count(元素个数),以及属性Count(元素个数)和Capacity(数组长度)。
并为MyList添加一个泛型T,用于标识data数组的元素类型。
Count只需get无需set,这能使得元素个数变为只读属性。
记得给两个私有字段设置初始值!

using System;

namespace Csharp
{
    class MyList<T>
    {
        private T[] data = new T[0];
        private int count = 0;
        public int Count
        {
            get
            {
                
            }
        }
        public int Capacity
        {
            get
            {
                
            }
            set
            {
                
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {

        }
    }
}

再添加上构造方法和所有需要实现的方法:

using System;

namespace Csharp
{
    class MyList
    {
        private T[] data;
        private int count;
        public int Count
        {
            get
            {
                
            }
        }
        public int Capacity
        {
            get
            {
                
            }
            set
            {
                
            }
        }
        public MyList()
        {
            
        }
        public void Add()
        {
            
        }
        public void Insert()
        {
            
        }
        public void Sort()
        {
            
        }
        public void RemoveAt()
        {
            
        }
        public void IndexOf()
        {
            
        }
        public void LastIndexOf()
        {
            
        }
        public void Remove()
        {
            
        }
    }
    class Program
    {
        static void Main(string[] args)
        {

        }
    }
}

框架定好了,接下来就是实现的问题了。

[属性]Count

由于Count是只读的,我们只需要返回字段count即可。

public int Count
{
    get
    {
        return count;
    }
}

[属性]Capacity

Capacity作为数组长度,逻辑是get返回data.Length,set则检查value是否大于Count,是则扩容,否则检查是否等于,等于则返回(什么也不做),否则就是小于了,抛出错误。

public int Capacity
{
    get
    {
        return data.Length;
    }
    set
    {
        if (Count > value)
        {
            throw new ArgumentOutOfRangeException("容量不得设置小于元素个数");
        }
        if (Count == value)
        {
            return;
        }
        if (Count < value)
        {
            T[] newData = new T[value];
            for (int i = 0; i < value; i++)
            {
                newData[i] = data[i];
            }
            data = newData;
        }
    }
}

[方法]Add

首先,参数为要添加的元素,我们设为item,类型为T。
然后,如果添加前元素个数(Count)和数组长度(Capacity)相同,那么添加后长度就会不足,所以需要扩容,具体为新建一个长度更长的数组,并把原数组的元素一个个复制到新数组,再在新数组中添加元素,最后把新数组赋值给数组。

public void Add(T item)
{
    if (Capacity == Count)
    {
        T[] new_data = new T[Capacity*2];
        for (int i = 0; i < Capacity; i++)
        {
            new_data[i] = data[i];
        }
        data = new_data;
    }
    data[Count] = item;
    count++;
}

[构造方法]MyList

我们只需要设定一个参数数组,然后把参数数组中的元素一个个Add进来就可以了,甚至什么都不需要考虑,非常变量,这也就是为什么要先写Add。

public MyList(params T[] oris)
{
    foreach(T i in oris)
    {
        Add(i);
    }
}

[方法]Insert

Insert,意为插入,我们自然只能插入在原有的元素之间,所以要检查插入位置是否正确。
有两个参数,index代表插入位置,item代表插入元素,我们可以先把插入位置往后的元素都后移,然后直接将目标位置替换为插入元素。
注意,插入前要检查是否需要扩容!

public void Inset(int index, T item)
{
    if (index<0 || index >= Count)
    {
        throw new ArgumentException("索引参数超出范围");
    }
    if (data.Length == Count)
    {
        T[] new_data = new T[Capacity*2];
        for (int i = 0; i < Capacity; i++)
        {
            new_data[i] = data[i];
        }
        data = new_data;
    }
    for (int i = Count - 1; i > index - 1; i--)
    {
        data[i+1] = data[i];
    }
    data[index] = item;
    count++;
}

[方法]RemoveAt

很简单,先检查是否索引超出范围,如果没超出范围,则将目标索引后面的元素都往前一格,即可覆盖掉原元素,达到删除的目的。

public void RemoveAt(int index)
{
    if (index<0 || index >= Count)
    {
        throw new ArgumentException("索引参数超出范围");
    }
    for (int i = index + 1; i < Count; i++)
    {
        data[i - 1] = data[i];
    }
    count--;
}

[方法]IndexOf

一个一个遍历过去,然后检查一下,每一个元素是否与目标元素相同,相同则返回索引。
如果都不符合,则返回-1,这样用户才知道有没有符合的元素!

public int IndexOf(T item)
{
    int index = -1;
    for (int i = 0; i < Count; i++)
    {
        if (item.Equals(data[i]))
        {
            index = i;
            break;
        }
    }
    return index;
}

[方法]LastIndexOf

同上,把for循环顺序改为倒序即可。

public int LastIndexOf(T item)
{
    int index = -1;
    for (int i = Count - 1; i >= 0; i--)
    {
        if (item.Equals(data[i]))
        {
            index = i;
            break;
        }
    }
    return index;
}

[方法]Remove

我们需要做到从左到右遍历寻找,并删除对应位置,正好IndexOf和RemoveAt这样个方法可以完成全套流程,我们便可以这样写:

public void Remove(T item)
{
    RemoveAt(IndexOf(item));
}

[方法]Sort

我们不要求很难的排序,可以直接用Array.Sort,前面讲过的。

public void Sort()
{
    Array.Sort(data, 0, count);  // 排序数组 开始位置 排序个数
}

[索引器]

首先检查目标索引有没有超出范围,再对data进行操作。

public T this[int index]
{
    get
    {
        if (index < 0 || index >= count)
        {
            throw new ArgumentOutOfRangeException("索引参数超出范围");
        }
        return data[index];
    }
    set
    {
        if (index < 0 || index >= count)
        {
            throw new ArgumentOutOfRangeException("索引参数超出范围");
        }
        data[index] = value;
    }
}

[重载运算符]==

先检查元素个数是否相同,再依次检查每个元素是否对应相同。(注意,此处用了Object.Equals方法,对于值对象,比较的是值本身,对于引用对象,比较的是引用地址)

public static bool operator==(MyList<T> list1, MyList<T> list2)
{
    if (list1.Count != list2.Count)
    {
        return false;
    }
    for (int i = 0; i < list1.Count; i++)
    {
        if (!list1[i].Equals(list2[i]))
        {
            return false;
        }
    }
    return true;
}

[重载运算符]!=

在重载==的同时,我们必须重载!=,只需要对==的结果取反即可。

public static bool operator!=(MyList<T> list1, MyList<T> list2)
{
    return !(list1==list2);
}

最终代码

最后,代码如下:

class MyList<T>
{
    private T[] data;
    private int count;
    public int Count
    {
        get
        {
            return count;
        }
    }
    public int Capacity
    {
        get
        {
            return data.Length;
        }
        set
        {
            if (Count > value)
            {
                throw new ArgumentOutOfRangeException("容量不得设置小于元素个数");
            }
            if (Count == value)
            {
                return;
            }
            if (Count < value)
            {
                T[] newData = new T[value];
                for (int i = 0; i < value; i++)
                {
                    newData[i] = data[i];
                }
                data = newData;
            }
        }
    }
    public MyList(params T[] oris)
    {
        foreach(T i in oris)
        {
            Add(i);
        }
    }
    public T this[int index]
    {
        get
        {
            if (index < 0 || index >= count)
            {
                throw new ArgumentOutOfRangeException("索引参数超出范围");
            }
            return data[index];
        }
        set
        {
            if (index < 0 || index >= count)
            {
                throw new ArgumentOutOfRangeException("索引参数超出范围");
            }
            data[index] = value;
        }
    }
    public static bool operator==(MyList<T> list1, MyList<T> list2)
    {
        if (list1.Count != list2.Count)
        {
            return false;
        }
        for (int i = 0; i < list1.Count; i++)
        {
            if (!list1[i].Equals(list2[i]))
            {
                return false;
            }
        }
        return true;
    }
    public static bool operator!=(MyList<T> list1, MyList<T> list2)
    {
        return !(list1==list2);
    }
    public void Add(T item)
    {
        if (Capacity == Count)
        {
            T[] new_data = new T[Capacity*2];
            for (int i = 0; i < Capacity; i++)
            {
                new_data[i] = data[i];
            }
            data = new_data;
        }
        data[Count] = item;
        count++;
    }
    public void Inset(int index, T item)
    {
        if (index<0 || index >= Count)
        {
            throw new ArgumentException("索引参数超出范围");
        }
        if (data.Length == Count)
        {
            T[] new_data = new T[Capacity*2];
            for (int i = 0; i < Capacity; i++)
            {
                new_data[i] = data[i];
            }
            data = new_data;
        }
        for (int i = Count - 1; i > index - 1; i--)
        {
            data[i+1] = data[i];
        }
        data[index] = item;
        count++;
    }
    public void Sort()
    {
        Array.Sort(data, 0, count);  // 排序数组 开始位置 排序个数
    }
    public void RemoveAt(int index)
    {
        if (index<0 || index >= Count)
        {
            throw new ArgumentException("索引参数超出范围");
        }
        for (int i = index + 1; i < Count; i++)
        {
            data[i - 1] = data[i];
        }
        count--;
    }
    public int IndexOf(T item)
    {
        int index = -1;
        for (int i = 0; i < Count; i++)
        {
            if (item.Equals(data[i]))
            {
                index = i;
                break;
            }
        }
        return index;
    }
    public int LastIndexOf(T item)
    {
        int index = -1;
        for (int i = Count - 1; i >= 0; i--)
        {
            if (item.Equals(data[i]))
            {
                index = i;
                break;
            }
        }
        return index;
    }
    public void Remove(T item)
    {
        RemoveAt(IndexOf(item));
    }
}

最后

面向对象初阶到这篇为止就结束了,如果你前面都理解了,一定能很轻松地解决这个问题。
下一篇Action委托、Func委托、多播委托、匿名方法和Lambda表达式!