C++string类的实现详解编程语言

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

#include<string>//注意这里没有.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<string> 
 
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); 
}

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

其工作原理如图所示:
这里写图片描述

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)返回本对象引用 
    }

其运行状态如图所示:
这里写图片描述

分析:一般情况下,上面的两种写法都可以,但是相对而言,第二种更优一点。
方法一,先释放原有的空间,但是如果下面用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类的成员函数即可,除非面试官特别要求。

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/18192.html

(0)
上一篇 2021年7月19日
下一篇 2021年7月19日

相关推荐

发表回复

登录后才能评论