Crow的数据结构(C#实现)学习笔记#5:String实现

String类说明

String类本质上就是一个char类型的数组,为String类编写方法,就可以看作对char数组编写方法。因为string和String都是微软System命名空间下的,所以我的类名使用CString

String类实现

字段和属性

str用于存储数据,Length指示长度,索引器用于查看对应下标的元素

private char[] str;
public int Length { get; private set; }
public char this[int index]
{
    get { return str[index]; }
}

构造器

这里提供两种构造器的实现,使用字符数组构造和比较常用的string构造(C#中使用双引号就是构造了一个string类)。因为我认为使用=和双引号的方式(如CString str = "Hello")作为类似string的类的构造器是十分符合直觉的(实际上我在构建C++的库的时候就是这样做的),但是因为C#出于安全考虑不允许对=操作符重载,只能放弃

public CString(char[] str)
{
    Length = str.Length;
    this.str = new char[Length];
    for(int i = 0; i < Length; i++) 
        this.str[i] = str[i];
}
public CString(string str)
{
    Length = str.Length;
    this.str = new char[Length];
    for (int i = 0; i < Length; i++)
        this.str[i] = str[i];
}

比较相关方法

比较有实例方法CompareTo,可以将对应的实例与其他实例比较,以及静态方法Compare和Equals(原本我想要重写object类的Equals方法,但是不知道如何重写GetHashCode方法,所以只写了一个非重写的版本)

public int CompareTo(CString other)
{
    int len = Length > other.Length ? other.Length : Length;
    for(int i = 0; i < len; i++)
    {
        int res = this[i] - other[i];
        if(res != 0)
        {
            if (res < 0)
                return -1;
            else
                return 1;
        }
    }
    if (Length == other.Length)
        return 0;
    else if (Length < other.Length)
        return -1;
    return 1;
}

public  bool Equals(CString other)
{
    if (other == null) 
        return false;
    return CompareTo(other) == 0;
}

public static int Compare(CString a, CString b)
    { return a.CompareTo(b); }

这里还由CompareTo引申出StartsWith和EndsWith方法,用于判断该实例是否由指定的字符串开头或结尾(SubString见下文)

public bool StartsWith(CString other)
{
    CString substr = SubString(0, other.Length);
    return substr.CompareTo(other) == 0;
}

public bool EndsWith(CString other)
{
    CString substr = SubString(Length -  other.Length, other.Length);
    return substr.CompareTo(other) == 0;
}

划分与合并方法

SubString用于得到索引开始的指定长度的字符串,Concat用于将多个字符串合并,并且对+操作符进行重载,使得Concat方法的调用更加简便

public CString SubString(int index, int length)
{
    char[] str = new char[length];
    for(int i = 0; i < length && (index + i) < Length; i++)
        str[i] = this[index + i];
    return new CString(str);
}

public static CString ConCat(CString s1, CString s2)
{
    int len1=  s1.Length;
    int len2= s2.Length;
    char[] newStr = new char[len1 + len2];
    for(int i = 0;i < len1; i++)
        newStr[i] = s1[i];
    for (int i = 0; i < len2; i++)
        newStr[len1 + i] = s2[i];
    return new CString(newStr);
}

public static CString operator +(CString s1, CString s2)
{ return ConCat(s1, s2); }

编辑字符串方法

在C#的官方定义中,一个string实例中存储的char数组是无法被修改的,只能通过重新赋值的方式对一个实例存储的值进行修改,但我在实践中又常常一道以下这类代码(以ToLower举例)

string s1 = "Hello World";
s1 = s1.ToLower();

其实目的就是改变s1内的值,但因为官方定义中s1中的值无法直接修改,所以采用了这种重新赋值的方式,我认为这是反直觉的,所以我自己的实现中我对这类方法进行了修改,不完全实现官方方法的效果,直接对实例内部的char数组进行编辑

Insert实在指定位置插入给定的字符串,Remove是删去指定位置开始的指定长度的字符串,Replace是将原字符串内所有的source变为des,ToLower和ToUpper是改变字符串内的大小写,Trim和TrimEnd是删除字符串内指定的前缀或后缀。其中,Insert和Remove提供的Array.Copy方法为字符串的合并或拆分提供了一个便捷的方法,不需要通过迭代的方式进行相应的修改,具体的方法签名请自行上网查询

public void Insert(int index, CString value)
{
    int totalLen = Length + value.Length;
    char[] totalStr = new char[totalLen];
    Array.Copy(str, totalStr, index);
    Array.Copy(value.str, 0, totalStr, index, value.Length);
    Array.Copy(str, index, totalStr, index + value.Length, Length - index);
    str = totalStr;
    Length = totalLen;
}

public void Remove(int index,  int length)
{
    char[] totalStr;
    if(Length - index <= length)
    {
        Length = index;
        totalStr = new char[Length];
        Array.Copy(str, totalStr, Length);
    }
    else
    {
        Length -= length;
        totalStr = new char[Length];
        Array.Copy(str, totalStr, index);
        Array.Copy(str, index + length, totalStr, index, Length - index);
    }
    str = totalStr;
}

public void Replace(CString source, CString des)
{
    bool flag = true;
    int index;
    while(flag)
    {
        if((index = IndexOf(source)) != -1)
        {
            Remove(index, source.Length);
            Insert(index, des);
        }
        else
            flag = false;
    }
}

public void ToLower()
{
    for(int i = 0; i < Length; i++)
    {
        if (str[i] >= 'A' && str[i] <= 'Z')
            str[i] = (char)(str[i] - 'A' + 'a');
    }
}

public void ToUpper()
{
    for(int i = 0; i < Length;i++)
    {
        if (str[i] >= 'a' && str[i] <= 'z')
            str[i] = (char)(str[i] - 'a' + 'A');
    }
}

public void Trim(char[] chars)
{
    if(StartsWith(new CString(chars)))
        Remove(0, chars.Length);
}

public void TrimEnd(char[] chars)
{
    if(EndsWith(new CString(chars)))
        Remove(Length - chars.Length, chars.Length);
}

其他方法

IndexOf方法用于查找实例字符串中第一次出现指定字符串的下标,类似C++的strstr库函数,找不到时返回-1

public int IndexOf(CString value)
{
    char c = value[0];
    int len = value.Length;
    int des = Length - len + 1;
    for(int i = 0; i < des; i++)
    {
        if (this[i] == c)
        {
            int j;
            for(j = 1;  j < len; j++)
            {
                if (this[i + j] != value[j])
                    break;
            }
            if (j == len)
                return i;
        }
    }
    return -1;
}

总结

C#中的string的功能之完善,性能之高超出了我的想象(相比C++来说,C++中我们常会自定义一个string类来满足相关的需要),因此在C#中自定义一个string类并没有什么实际用处,本文也只是作为一篇学习笔记,通过实现相关的类来熟悉C#编程和对数据结构的认识

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值