堆分配存储串

本文通过一个具体的C++字符串类实现案例,探讨了在类设计过程中如何正确使用引用传递、const关键字来确保参数不被修改,以及如何避免内存泄漏等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

string类型是天天用的玩意,但是自己实现时,用顺序存储还是链式存储更合适呢?

《数据结构》中介绍了三种方式:

1,定长顺序存储

    长度有限,貌似没大用处

3,块链存储

数组和链表的一种混用形式,原本直觉上认为这种方式较合理,但仔细思考,发现其便利性也仅体现在连接了。综合来说不如第2种。

2,堆分配存储

    这是今天要实现的方式。

    方法的选择上,理论上好歹也应该重载下加号运算符什么的,但运算符重载作为C++的专题留到以后再说。现在仅关心数据结构,实现部分教材中列出的方法。

串的长度直接记录下来,结尾没有补0作结束符。

效率上当然没法和STL提供的相比,仅是意思下练练手罢。


一开始还有种想法,string这玩意太平常,了解下干脆别浪费时间自己实现了,结果粗大事了。事实证明不动手还是不行啊。


以下是一份错误的代码:

#include <iostream>
using std::cout;
using std::endl;

class MyString
{
public:
    char *ch;
    int length;

    MyString();
    ~MyString();

    //给串赋字符串常量值
    void StrAssign(char *chars);

    //按序比较,>T返回值>0,= =,< <
    int StrCompare(MyString T);

    //S = S + S2;
    void Concat(MyString S2);

    //返回从pos起len长度的子串
    MyString SubString(int pos, int len);

    void Show();
};

//初始化为空串
MyString::MyString()
{
    ch = NULL;
    length = 0;
}

MyString::~MyString()
{
    delete ch;
    ch = NULL;
}

void MyString::StrAssign(char *chars)
{
    //不管三七二十一,先释放掉原有数据
    if (ch)
    {
        delete ch;
        ch = NULL;
    }
    
    //计算chars长度
    int i=0;
    while(chars[i] != NULL)
    {
        i++;
    }

    if (0 == i)     //注意若所赋串为空,length需归0
    {
        ch = NULL;
        length = 0;
    }
    else
    {
        ch = new char[i]();
        for(int j=0; j<i; ++j)
        {
            ch[j] = chars[j];
        }
        length = i;
    }
}

int MyString::StrCompare(MyString T)
{
    for(int i=0; (i<length) && (i<T.length); ++i)
    {
        if (ch[i] > (T.ch)[i])
        {
            return 1;
        }
        else if (ch[i] < (T.ch)[i])
        {
            return -1;
        } 
    }

    return length - T.length;
}

void MyString::Concat(MyString S2)
{
    //为空直接赋值
    if (ch == NULL)
    {
        ch = S2.ch;
        length = S2.length;
    }
    else
    {
        //备份原串
        char *tmp = new char[length]();
        for(int i=0; i<length; ++i)
        {
            tmp[i] = ch[i];
        }

        length = length + S2.length;

        delete ch;
        ch = new char[length]();

        int i = 0;
        //复制原串
        for(; i<(length - S2.length); ++i)
        {
            ch[i] = tmp[i];
        }
        delete tmp;
        tmp = NULL;

        //复制S2
        for(; i<length; ++i)
        {
            ch[i] = (S2.ch)[i - length + S2.length];
        }
    }
}

MyString MyString::SubString(int pos, int len)
{
    MyString result;
    if ((pos<=0)||(pos>length)||(len<0)||(len>length-pos+1))
    {
        cout<<"invalid param or empty String"<<endl;
        result.ch = NULL;
        result.length = 0;
        return result;
    }

    if (0 == len)
    {
        result.ch = NULL;
        result.length = 0;
    }
    else
    {
        result.ch = new char[len]();
        for(int i=0; i<len; ++i)
        {
            result.ch[i] = ch[i+pos-1]; //注意此处要-1
        }

        result.length = len;
    }

    return result;
}

void MyString::Show()
{
    if (NULL == ch)
    {
        cout<<"the string is empty"<<endl;
    }
    else
    {
         for(int i=0; i<length; ++i)
        {
            cout<<ch[i];
        }
        cout<<endl;
    }
}

int main()
{
    MyString s1;             s1.Show();
    s1.StrAssign("hello");   s1.Show();
    MyString s2;
    s2.StrAssign("abc");     s2.Show();
    
    if (s1.StrCompare(s2)>0)
    {
        cout<<"s1>s2"<<endl;
    }
    else if(s1.StrCompare(s2)<0)
    {
        cout<<"s1<s2"<<endl;
    }

    s1.Concat(s2);          s1.Show();

    s2.SubString(4,2);
    s2.SubString(2,2).Show();
}


倘若执行,会弹出一个我们熟悉的错误提示框——Debug Assertion Failed!

然后运行结果如下:

the string is empty

hello

abc

s1>s2


我现在刻意的练习C朝C++风格的转换,抛弃struct用class,暴露出了一些漏洞。很好。

为什么出错呢?原因是这样的:

由于我潜意识里希望作为参数传递进各方法的MyString对象不要被修改,而又避免使用对我来说较生疏的const等关键字,所以我采用了直接传递对象的方法,甚至连指针都懒得用。结果就反而弄出问题了!

注意,假如此时将StrCompare()的测试语句注释掉,会发现后面的Concat()是正常的。那明明Compare只是读取了字符作了一些比较,根本没作修改动作,为何还会影响到后面的语句呢?

秘密在于,我将s2作为参数传递进Compare()时,s2是被复制了一份的,当然ch及length都被默认的复制构造函数一一复制进去。当然这些都在预料之中没什么问题。

但是要注意!复制的这份形参,退出函数时,是要被处理的!比如这里的MyString,退出Compare()时是要被析构的!而我之前为了避免内存泄露,专门为MyString的析构函数编写了释放指针的语句。所以导致,形参被析构时,把原本被复制的s2也连累了,s2的指针被delete,资源就不见了。


所以假如把析构函数的语句注释掉,什么都不做,输出的结果就会变正常,不过这种解决方法不好,留下内存泄露的问题。


所以该用const的地方还是得用,而即便仅仅采用引用传递的方式也许反而不会产生问题。


然后我将代码的所有值传递修改为引用传递,运行后,前面部分均正常,SubString()仍报错。

原因也很简单,我很2B的在SubString()中定义了局部变量result,然后返回这个result,但是result是要被析构的啊,于是返回值就没法用了。


修改代码如下后,一切正常了。

/*
2014.4.18
堆分配存储串
*/

#include <iostream>
using std::cout;
using std::endl;

class MyString
{
public:
    char *ch;
    int length;

    MyString();
    ~MyString();

    //给串赋字符串常量值
    void StrAssign(char *chars);

    //按序比较,>T返回值>0,= =,< <
    int StrCompare(MyString *T);

    //S = S + S2;
    void Concat(MyString *S2);

    //返回从pos起len长度的子串
    void SubString(MyString *result, int pos, int len);

    void Show();
};

//初始化为空串
MyString::MyString()
{
    ch = NULL;
    length = 0;
}

MyString::~MyString()
{
    delete ch;
    ch = NULL;
}

void MyString::StrAssign(char *chars)
{
    //不管三七二十一,先释放掉原有数据
    if (ch)
    {
        delete ch;
        ch = NULL;
    }
    
    //计算chars长度
    int i=0;
    while(chars[i] != NULL)
    {
        i++;
    }

    if (0 == i)     //注意若所赋串为空,length需归0
    {
        ch = NULL;
        length = 0;
    }
    else
    {
        ch = new char[i]();
        for(int j=0; j<i; ++j)
        {
            ch[j] = chars[j];
        }
        length = i;
    }
}

int MyString::StrCompare(MyString *T)
{
    for(int i=0; (i<length) && (i<T->length); ++i)
    {
        if (ch[i] > (T->ch)[i])
        {
            return 1;
        }
        else if (ch[i] < (T->ch)[i])
        {
            return -1;
        } 
    }

    return length - T->length;
}

void MyString::Concat(MyString *S2)
{
    //为空直接赋值
    if (ch == NULL)
    {
        ch = S2->ch;
        length = S2->length;
    }
    else
    {
        //备份原串
        char *tmp = new char[length]();
        for(int i=0; i<length; ++i)
        {
            tmp[i] = ch[i];
        }

        length = length + S2->length;

        delete ch;
        ch = new char[length]();

        int i = 0;
        //复制原串
        for(; i<(length - S2->length); ++i)
        {
            ch[i] = tmp[i];
        }
        delete tmp;
        tmp = NULL;

        //复制S2
        for(; i<length; ++i)
        {
            ch[i] = (S2->ch)[i - length + S2->length];
        }
    }
}

void MyString::SubString(MyString *result, int pos, int len)
{
    if ((pos<=0)||(pos>length)||(len<0)||(len>length-pos+1))
    {
        cout<<"invalid param or empty String"<<endl;
        result->ch = NULL;
        result->length = 0;
    }

    if (0 == len)
    {
        result->ch = NULL;
        result->length = 0;
    }
    else
    {
        result->ch = new char[len]();
        for(int i=0; i<len; ++i)
        {
            result->ch[i] = ch[i+pos-1]; //注意此处要-1
        }

        result->length = len;
    }
}

void MyString::Show()
{
    if (NULL == ch)
    {
        cout<<"the string is empty"<<endl;
    }
    else
    {
         for(int i=0; i<length; ++i)
        {
            cout<<ch[i];
        }
        cout<<endl;
    }
}

int main()
{
    MyString s1;             s1.Show();
    s1.StrAssign("hello");   s1.Show();
    MyString s2;
    s2.StrAssign("abc");     s2.Show();
    
    if (s1.StrCompare(&s2)>0)
    {
        cout<<"s1>s2"<<endl;
    }
    else if(s1.StrCompare(&s2)<0)
    {
        cout<<"s1<s2"<<endl;
    }

    s1.Concat(&s2);          s1.Show();

    MyString result;
    s2.SubString(&result, 4, 2);
    s2.SubString(&result, 2, 2);
    result.Show();
}

/*
运行结果:
the string is empty
hello
abc
s1>s2
helloabc
invalid param or empty String
bc

注意点:
1,字符串比较过程的逻辑关系一定要想清楚
*/

当然,要避免参数值被改动,仅靠个人思维推断是不规范的,最好还是加上const修饰符。

另外把属性改回了private,传指针改成了传引用,最终的代码如下:

/*
2014.4.18
堆分配存储串
*/

#include <iostream>
using std::cout;
using std::endl;

class MyString
{
private:
    char *ch;
    int length;

public:
    MyString();
    ~MyString();

    //给串赋字符串常量值
    void StrAssign(const char *chars);

    //按序比较,>T返回值>0,= =,< <
    int StrCompare(const MyString &T);

    //S = S + S2;
    void Concat(const MyString &S2);

    //返回从pos起len长度的子串
    void SubString(MyString *result, int pos, int len) const;

    void Show();
};

//初始化为空串
MyString::MyString()
{
    ch = NULL;
    length = 0;
}

MyString::~MyString()
{
    delete ch;
    ch = NULL;
}

void MyString::StrAssign(const char *chars)
{
    //不管三七二十一,先释放掉原有数据
    if (ch)
    {
        delete ch;
        ch = NULL;
    }
    
    //计算chars长度
    int i=0;
    while(chars[i] != NULL)
    {
        i++;
    }

    if (0 == i)     //注意若所赋串为空,length需归0
    {
        ch = NULL;
        length = 0;
    }
    else
    {
        ch = new char[i]();
        for(int j=0; j<i; ++j)
        {
            ch[j] = chars[j];
        }
        length = i;
    }
}

int MyString::StrCompare(const MyString &T)
{
    for(int i=0; (i<length) && (i<T.length); ++i)
    {
        if (ch[i] > (T.ch)[i])
        {
            return 1;
        }
        else if (ch[i] < (T.ch)[i])
        {
            return -1;
        } 
    }

    return length - T.length;
}

void MyString::Concat(const MyString &S2)
{
    //为空直接赋值
    if (ch == NULL)
    {
        ch = S2.ch;
        length = S2.length;
    }
    else
    {
        //备份原串
        char *tmp = new char[length]();
        for(int i=0; i<length; ++i)
        {
            tmp[i] = ch[i];
        }

        length = length + S2.length;

        delete ch;
        ch = new char[length]();

        int i = 0;
        //复制原串
        for(; i<(length - S2.length); ++i)
        {
            ch[i] = tmp[i];
        }
        delete tmp;
        tmp = NULL;

        //复制S2
        for(; i<length; ++i)
        {
            ch[i] = (S2.ch)[i - length + S2.length];
        }
    }
}

void MyString::SubString(MyString *result, int pos, int len) const
{
    if ((pos<=0)||(pos>length)||(len<0)||(len>length-pos+1))
    {
        cout<<"invalid param or empty String"<<endl;
        result->ch = NULL;
        result->length = 0;
    }

    if (0 == len)
    {
        result->ch = NULL;
        result->length = 0;
    }
    else
    {
        result->ch = new char[len]();
        for(int i=0; i<len; ++i)
        {
            result->ch[i] = ch[i+pos-1]; //注意此处要-1
        }

        result->length = len;
    }
}

void MyString::Show()
{
    if (NULL == ch)
    {
        cout<<"the string is empty"<<endl;
    }
    else
    {
         for(int i=0; i<length; ++i)
        {
            cout<<ch[i];
        }
        cout<<endl;
    }
}

int main()
{
    MyString s1;             s1.Show();
    s1.StrAssign("hello");   s1.Show();
    MyString s2;
    s2.StrAssign("abc");     s2.Show();
    
    if (s1.StrCompare(s2)>0)
    {
        cout<<"s1>s2"<<endl;
    }
    else if(s1.StrCompare(s2)<0)
    {
        cout<<"s1<s2"<<endl;
    }

    s1.Concat(s2);          s1.Show();

    MyString result;  
    s2.SubString(&result, 4, 2);
    s2.SubString(&result, 2, 2);
    result.Show();
}
在堆(如C语言中的标准库函数`malloc`)上分配内存存储字符,并实现插入操作以及查找子的功能,通常涉及到动态内存管理和字符处理。下面是一个简单的C语言示例,它使用了字符连接函数`strcat`和`strcmp`来实现这个功能: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> // 动态分配和初始化字符 char* create_string(char* str) { int len = strlen(str) + 1; // 加一是因为需要空间来存储结束符'\0' char* result = (char*) malloc(len * sizeof(char)); strcpy(result, str); return result; } // 插入子 void insert_substring(char** target, const char* substring, int index) { if (*target == NULL) { *target = create_string(substring); // 如果目标字符不存在,先创建 } else { size_t substr_len = strlen(substring) + 1; char* new_str = (char*) realloc(*target, (strlen(*target) + substr_len) * sizeof(char)); // 扩展空间 if (new_str != NULL) { strcat(new_str, substring); new_str[index - 1] = '\0'; // 更新插入位置后的结束符 free(*target); // 释放原内存 *target = new_str; } else { printf("Memory allocation failed.\n"); } } } // 查找子 int find_substring(const char* string, const char* sub_string) { size_t string_len = strlen(string); size_t sub_len = strlen(sub_string); for (size_t i = 0; i <= string_len - sub_len; i++) { if (strcmp(&string[i], sub_string) == 0) { return i; // 返回子起始位置 } } return -1; // 如果未找到,返回-1 } int main() { char* target_str = "Hello, world!"; insert_substring(&target_str, "C", 4); printf("Modified string: %s\n", target_str); int sub_index = find_substring(target_str, "world"); if (sub_index != -1) printf("Substring 'world' found at position %d\n", sub_index); else printf("Substring not found.\n"); free(target_str); // 一定要记得释放不再使用的动态内存 return 0; } ``` 这个程序首先创建了一个字符并插入指定子,然后寻找并打印出子的位置。请注意,这里我们假设输入的子不会导致内存溢出。在实际应用中,需要考虑内存管理的细节和错误处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值