在之前c语言的学习中,对于字符串我们很难像整型和其他变量那样进行比较和直接的复制或者定义去运用。但在C++中为了解决这个问题提供了一种类,String类,以类型的形式对字符串进行封装,它除了作为储存字符的容器外,可以对字序列的一系列操作。
下面我们对String类进行简单的模拟实现:
namespace wjx { class string { public: private: char* _str; };
首先,我们定义一个自己的命名空间,来实现我们的string类,定义初始数据char*类型的_str来作为string类中的数据。
string(const char* str) :_str(new char[strlen(str) + 1]) { strcpy(_str, str); }
我们先来实现String类中的构造函数,首先计算出所加字符串的长度,为原有数据_str开辟空间,此处+1是为了放置'\0'方便操作,开辟空间后将str通过strcpy函数复制给_str,完成构造。
string(const string& s) :_str(new char[strlen(s._str)+1]) { strcpy(_str, s._str); } ~string() { delete[] _str; _str = nullptr; }
上述为实现string类的拷贝构造函数(深拷贝)和析构函数,一个类中如果没有定义拷贝构造函数,就会调用默认的拷贝构造函数。而拷贝构造分为深浅拷贝,下图为深浅拷贝的底层实现:
在我们没有定义拷贝构造函数时,系统会调用默认的拷贝构造函数,string s1("hello world"),string s2(s1),如上图所示系统默认的拷贝构造函数为普通的传值拷贝,所以s1与s2指向同一块空间,但当出作用域时,调用析构函数,s1指向的空间被释放,s2则指向空,所以程序会崩溃。
但当我们使用深拷贝时,则不会出现那样的情况,因为我们在定义深拷贝函数时,会首先开辟一个与要拷贝字符串等长的空间,再将其复制过去,所以s1与s2指向的位置是不同的,析构函数释放时是互不影响的,所以这就是深浅拷贝的区别。
下面为赋值运算符重载的方法:
string& operator=(const string& s) { if (this != &s) { char* tmp = new char[strlen(s._str) + 1]; strcpy(tmp, s._str); delete[] _str; _str = tmp; } return *this; }
上述代码为赋值运算符重载,当我们调用赋值运算符重载时,需要先开辟一个空间用来复制需要拷贝的字符串,再通过释放原来的空间并用成员变量指向新的空间,从而达到赋值。注意的是if (this != &s)是需要区别s1=s1时的情况的,不能自己给自己赋值。
下面为拷贝构造函数和赋值运算符函数的现代写法:
string(const string& s)//拷贝构造函数现代写法 :_str(nullptr) { string tmp(s._str); swap(_str, tmp._str); } string& operator=(const string& s)//赋值运算符重载的现代写法 { if (this != &s) { string tmp(s); swap(_str, tmp._str); } return *this; }
现代写法对于之前的传统写法来说,对于拷贝构造函数,传统方法需要申请新空间,但现代版本不需要,直接创造一个对象,因为对象被创建后,数据的底层空间已经被开辟出来了,直接可以通过交换来实现拷贝构造;而对于赋值运算符重载来说传统版本需要申请和释放空间,而新版本利用创建对象和交换函数来直接实现。