教你编写STL的string类-01(理解C/C++内存管理)

来源:互联网 发布:网络编程视频教程 编辑:程序博客网 时间:2024/06/05 23:06

STL里的string类相信许多人都用过,它是C++提供的对于字符串操作的类,它与C的字符数组相比,最大的优点就是操作方便,使用它进行存储字符串,或者打印字符串时,不用担心开辟的存储空间是否够用,因为string可以动态增加容纳字符串的空间大小,也不用担心会造成内存泄露,因为析构函数会被自动执行。

现在就来介绍string的实现原理:string的内部实现是依靠一个char*的字符指针,然后根据存储的字符串的大小,动态开辟存储空间。虽然看起简单,如果你不清楚C/C++的内存模型,要实现string是不容易的。以下的例子,因为会用到string.h这个头文件里的函数,所以为了不引起二义性,以_string作为我们编写的仿string类。现在先介绍一个错误的实现:相信很多人第一次实现string时会这么写:

//_string.h

#include <iostream.h>

#include <string>

class _string

{

public:

    _string(const char* str=NULL);

    _string(const _string& other);

    _string operator=(const _string& other);

    ~_string();

private:

    char* m_data;

};

//_string.cpp

#include "_string.h"

_string::_string(const char* str)

{

    if(str==NULL)

    {

       m_data=new char[1];

       *m_data=0;

    }

    else

    {

       m_data=new char[strlen(str)+1];

       strcpy(m_data,str);

    }

}

_string::_string(const _string& other)

{

    m_data=other.m_data;

}

_string _string::operator=(const _string& other)

{

    m_data=other.m_data;

    return *this;

}

_string::~_string()

{

    delete[] m_data;

}

现在我们在主函数里去使用我们创建的这个类:

//main.cpp

#include "_string.h"

void main()

{

    _string s1("Ricky");

    _string s2(s1);

   _string s3;

   s3=s1;

}

此时当我们运行程序时,会出现运行时报错,程序崩溃了。这是为什么呢?问题就出在拷贝构造函数和赋值函数上,为了能理解这个错误,首先必须知道C/C++的内存管理。以上面的为例,当我们创建了s1后,它的m_data指向了一块内存区,该内存区存储的值为Ricky,当我们执行_string s2(s1);时,此时执行了拷贝构造函数,它只是简单的执行了地址的赋值,即s2的m_data也指向了s1的m_data所在的地址空间,s3=s1;也是同样的道理,执行了赋值函数后,只是把s3的m_data指向了s1的m_data所在的地址。这会造成什么结果呢,结果就是当s1,s2,s3依次被析构时,m_data这块地址会被删除3次,同一块地址是不能被删除3次的,所以就造成了运行时崩溃。

那么我们应该怎么做呢,方法有两种:深复制和引用计数。STL的string类就是使用引用计数实现的。不过我们先介绍深复制。

提到深复制,那么就得先说下浅复制,浅复制就是像上面的拷贝构造函数和赋值函数那样,仅仅是进行简单的赋值,所以操作者和被操作者指向的是同一块内存;深复制是单独开辟一块内存,把需要的数据复制到这块内存里面。这样,当我们析构时,删除的就不是同一块内存了,而是自己独有的。下面看看深复制的实现:

为了能显示结果,这里重载了输出操作符<<:

//_string.h

#include <iostream.h>

#include <string.h>

class _string

{

public:

    _string(const char* str=NULL);

    _string(const _string& other);

    _string operator=(const _string& other);

    friend ostream& operator<<(ostream& os,const _string& other);

    ~_string();

private:

    char* m_data;

};

//_string.cpp

#include "_string.h"

_string::_string(const char* str)

{

    if(str==NULL)

    {

       m_data=new char[1];

       *m_data=0;

    }

    else

    {

       m_data=new char[strlen(str)+1];

       strcpy(m_data,str);

    }

}

_string::_string(const _string& other)

{

    m_data=new char[strlen(other.m_data)+1];

    strcpy(m_data,other.m_data);

}

_string _string::operator=(const _string& other)

{

    if(this==&other)//防止自赋值

       return *this;

    delete[] m_data;//删除m_data开辟的原有空间

    //给m_data开辟新空间并赋值

    m_data=new char[strlen(other.m_data)+1];

    strcpy(m_data,other.m_data);

    return *this;

}

ostream& operator<<(ostream& os,const _string& other)

{

    os<<other.m_data;

    return os;

}

_string::~_string()

{

    delete[] m_data;

}

//main.cpp

#include "_string.h"

void main()

{

    _string s1("Ricky");

    _string s2(s1);

    _string s3;

    s3=s1;

    cout<<s1<<endl;

    cout<<s2<<endl;

    cout<<s3<<endl;

}

现在运行程序,不会崩溃,并能正确显示出结果:

 

下一篇为大家介绍使用引用计数实现string类。