C++string类的实现

C++中提供了一种新的数据类型——字符串类型(string)。实际上string并不是C++的基本类型,它是在C++标准库中声明的一个字符串类,用这种数据类型可以定义对象,每一个字符串变量都是string类的一个对象。标准库类型string表示可变长的字符序列,使用string类型必须首先包含它的头文件。
作为标准库的一部分,string定义在命名空间std中。
【例】

#include//注意这里没有.h 
using namespace std;

string类的意义有两个:第一个是为了处理char类型的数组,并封装了标准C中的一些字符串处理的函数。而当string类进入了C++标准后,它的第二个意义就是一个容器。

string类有106个成员接口函数。C++ 的一个常见面试题是让你实现一个 String 类,限于时间,不可能要求具备 std::string 的功能,但至少要求能正确管理资源。具体来说:
1)能像 int 类型那样定义变量,并且支持赋值、复制。
2)能用作函数的参数类型及返回类型。
3)能用作标准库容器的元素类型,即 vector/list/deque 的 value_type。(用作 std::map 的 key_type 是更进一步的要求,本文从略)。

下面是模拟实现string类的几个重要函数:
1、构造函数
【例】

#define _CRT_SECURE_NO_WARNINGS
#include

using namespace std;

class String
{
public:
    String(char *str = "")
    {
        if (str == NULL)
        {
            _str = new char[1];//为了与delete[]配合使用
            *_str = '\0';
            size = 0;
        }
        else
        {
            int length = strlen(str);
            _str = new char[length + 1];
            strcpy(_str, str);
            size = length;
        }
    }
    ~String()
    {
        if (NULL != _str)
        {
            delete[] _str;//delete[]释放的空间必须是动态分配的
        }
    }
private:
    char *_str;//指向字符串的指针
    size_t size;//保存当前字符串长度
};

void Test2()
{
    String s1("hello");
    String s2(new char[3]);
}

int main()
{
    Test2();
    system("pause");
    return 0;
}

释:在构造函数中,_str被初始化为空字符串(只有‘\0’)而不是NULL。因为C++中的任何字符串的长度至少为1(即至少包含一个结束符‘\0’)。孔字符串也是有效的字符串,它的长度为1,因此他代表一块合法的内存单元而不是NULL。

2、拷贝构造函数
1)浅拷贝
【例】

String::String(const String& s)//浅拷贝
    :_str(s._str)
{}

void Test2()
{
    String s1("hello");
    String s2(s1);
}

以上代码会使系统崩溃。因为s1只是将_str的地址传给s2中的_str,即浅拷贝(位拷贝),如下图所示:
这里写图片描述
由图可知:s1和s2指向同一块内存,析构时先对s2释放“hello”这块空间,当要再析构s1时,系统将崩溃。

释:浅拷贝又称为位拷贝,编译器只是将指针的内容拷贝过来,导致多个对象共用一块内存空间,当其中任意对象将这块空间释放之后,当再次访问时将出错。

解决方法:(深拷贝)给要拷贝构造的对象重新分配空间。
【例】

String::String(const String& s)//深拷贝
    :_str(new char[strlen(s._str) + 1])
{
    strcpy(_str, s._str);
    size = strlen(s._str);
}

void Test2()
{
    String s1("hello");
    String s2(s1);
}

其运行的状态如图所示:
C++string类的实现_第1张图片
由图可知:拷贝的对象s2中_str的值(字符串的地址)和s1对象中的_str的值不同,“hello”保存在地址不同的两个空间里,说明了系统为对象重新开辟了空间——深拷贝。

其工作原理如图所示:
C++string类的实现_第2张图片

3、拷贝赋值函数
【例】

    //方法一:
    String& String::operator=(const String& s)
    {
        if (this != &s)//检查自赋值
        {
            delete[]_str;
            _str = new char[strlen(s._str) + 1];
            strcpy(_str, s._str);
          size = strlen(s._str);

        }
        return *this;//为了支持链式访问
    }

    //方法二:
    String& String:: operator=(const String& s)
    {
        if (this != &s)//1)检查自赋值
        {//2)创建临时变量获得分配的内存空间,并复制原来的内容
            char *tmp = new char[strlen(s._str) + 1];
            strcpy(tmp, s._str);
            delete[]_str;//3)释放原有的内存
            _str = s._str;
            size = strlen(s._str);
        }
        return *this;//4)返回本对象引用
    }

其运行状态如图所示:
C++string类的实现_第3张图片

分析:一般情况下,上面的两种写法都可以,但是相对而言,第二种更优一点。
方法一,先释放原有的空间,但是如果下面用new开辟新空间时失败了,而这时将s2赋值给s3,不仅不能成功赋值(空间开辟失败),还破坏了原有的s3对象。
方法二,先开辟新空间,将新空间的地址赋给一个临时变量,就算这时空间开辟失败,也不会影响原本s3对象。
综上所述:第二种方法更优一点。

注意:最后的返回值是为了支持链式访问。
例如:s3 = s2 = s1;

4、拷贝构造函数的现代写法:
【例】

String::String(const String& s)
    :_str(NULL)//一定要对_str初始化
{
    String tmp(s._str);
    std::swap(tmp._str, _str);
}

释:如果没有初始化,_str的值很可能是一个随机值,其指向的内存空间是不合法的。当tmp._str和_str交换后析构tmp就会出错。

5、赋值运算符重载函数的两种现代写法
【例】

//第一种:
String& String::operator=(String s)
{
    std::swap(_str, s._str);
    return *this;
}

//第二种:
String& String::operator=(const String& s)
{
    if (this->_str != s._str)
    {
        String tmp(s);
        std::swap(tmp._str, _str);
    }
    return *this;
}

在面试时,一般写出上面四个string类的成员函数即可,除非面试官特别要求。

你可能感兴趣的:(C++)