String类

相比于complex类的数据成员,string类因为不能一开始确定字符串长度,所以string类更合适的设计方式是定义指针,根据创建的字符串的大小动态分配内存。很多关于类设计的细节已经在complex类中提过,再次就不在赘述了。本文重点放在内存分配与释放,拷贝构造函数和拷贝赋值函数上。

#ifndef __STRING__

#define __STRING__

class String

{

    public:

       String(const char* cstr = 0);    //构造函数

       String(const String& str);       //拷贝构造函数

       String& operator = (const String& str);     //拷贝赋值函数

       ~String();   //析构函数

       char* get_c_str() const { return m_data; }

    private:

       char* m_data;

};

inline String::String(const char* cstr =  0)

{

    if (cstr)

    {

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

       strcpy(m_data, cstr);

    }

    else      //未指定初值

    {

       m_data = new char[1];

       *m_data = ‘\0’;

    }

}

inline String::String(const String& str)

{

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

    strcpy(m_data, str.m_data);

}

inline String& String::operator = (const String&)

{

    if (this == &str)     //默认this指针参数,检测自我赋值

    {

       return *this;     //a = a,则delete[]之后无法复制

    }

    delete[] m_data;

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

    strcpy(m_data, str.m_data);

    return *this;

}

inline String::~String()

{

    delete[] m_data;    //释放动态分配的内存

}

#endif

首先依旧是构造函数问题,在第一个构造函数中,如果未传入参数,则指针默认为空。如果传入的指针非空,则要根据初始字符串大小进行动态分配内存,在C++中通过new来分配内存空间,这点与Cmalloc并不完全相同。因为字符串的特性,字符串最后要加一个字节的‘\0’表示结束,分配时要尤为注意。当通过m_data指针分配好内存后再由strcpy函数完成两个字符串逐字符拷贝。

接下来要说本文最核心的知识就是浅拷贝和深拷贝问题。这里所说的拷贝简单来说就是通过某个类去初始化另一个类,比如String str1 = ‘hello\0’; String str2(str1)这种语句,我们用已经定义好的成员str1去完成对str2的初始化。此时str1指针指向字符串hello,如果此时我们在类中不单独定义拷贝构造函数,则系统会自动调用默认的方式,即把str1指针的值赋给str2,实际上只是在内存中定义了一遍hello字符串,只不过此时有两个独立的指针str1str2分别指向它。这就是我们说的浅拷贝,浅拷贝会带来诸多问题,比如当str1销毁了原本指向的字符串,而str2又需要去使用此字符串时就会出现问题,因为str1str2两个指针均有修改所指向字符串内容的功能。

为了解决浅拷贝问题,我们需要在定义构造函数时自己单独定义合适的拷贝构造函数,要做的步骤其实很简单,只需要先分配好内存空间,然后将传入的指针所指向的字符串逐字符地复制到新分配的内存空间。这样就会有两个独立的指针并且指向两块独立的内存空间,这两块内存空进的数据相同。

拷贝赋值函数相比于拷贝构造函数的最大区别在于,拷贝赋值函数在使用时,被操作的对象已经完成了初始化,所以当我们需要将某一指针执行的字符串内容复制给另一字符串时,首先要做的事是先释放掉被赋值串已经分配好的存储空间。再单独分配空间,逐字符拷贝,这个过程和拷贝构造函数深拷贝的过程是一样的。需要单独一提的是,函数的第一步最好先检查传入的指针是否和左侧的指针是完全相同的,若是则不用执行刚刚剩余的步骤,此时在主函数中对应的语句是String str1; str1 = str1

因为此处使用到了指针动态分配内存,则当String类定义的变量离开作用域时,要先通过析构函数释放掉分配的内存空间。与new相对的操作时delete,又因为字符串不是分配的单个变量,而是多个字符的集合,使用delete时要在delete之后加上[]表示释放掉所有分配的空间。

Advertisements