引言:我们知道在C++中字符串是用string类型来表示的,那么string类是如何实现的呢?
分析string类的构造函数
在C语言中,字符串是用字符数组来表示的,所以在C++的string类中,就可以来一个 char * 类型的指针作为string类的属性,来存储字符串。
class string{
private:
char *s;
};
那么string类的构造函数就是通过strcpy将给定的字符串拷贝给char * 类型的指针,并通过new这个操作符来开辟内存空间来存储,需要注意的是要为'\0'多开辟一个字节的内存空间
// 构造函数
string(const char *str=nullptr){
// 传递的字符串为空,只为'\0'开辟一个字节的存储空间
if(str == nullptr)
{
s = new char[1];
strcpy(s,"");
}
else{
s = new char[strlen(str)+1];
strcpy(s,str);
}
}
除了上述方式,我们还可以用初始化列表的方式来构造
// 构造函数
String(const char *str=nullptr):s(strcpy(new char[str?strlen(str)+1:1],str?str:"")){
// 因为strcpy是返回char * 类型的,所以可以这样使用
}
分析string类的析构函数
析构函数要注意的一点就是在释放内存空间时,要先判断一下字符指针是否为空,为空就不需要再释放了
// 析构函数
~string(void){
if(s!=nullptr)
{
delete[] s;
s = nullptr;
}
}
分析string类的拷贝构造函数
我们知道拷贝构造函数是通过一个已经存在的同类型的对象来构造一个新的副本对象,那么我们就只用将传递进来的string类的对象拷贝一份给副本对象就可以了
// 拷贝构造
string(const string &str){
s = new char[strlen(str.s)+1];
strcpy(s,str.s);
}
同样的,我们也可以通过初始化列表的方式来实现
// 拷贝构造
string(const string &str):s(strcpy(new char[strlen(str.s)+1],str.s)){
}
分析string类的拷贝赋值函数
我们知道拷贝赋值函数是两个已经存在的同类型之间的对象之间的相互赋值,那么我们可以通过释放被赋值对象的内存空间,将要拷贝对象的值拷贝一份给被拷贝对象
// 拷贝赋值 返回值加&表示返回的值能连续赋值
string& operator=(const string &str){
if(this != &str) // 避免自己给自己赋值时直接delete自己
{
delete[] s;
str = new char(strlen(str.s)+1);
strcpy(s,str.s);
}
return *this;
}
但这样做会有一个问题,就是如果赋值失败的话,自己本来的值被delete掉了,新的值又没有获取到,就很尴尬了,所以建议先保存一份,再进行赋值
// 拷贝赋值 返回值加&表示返回的值能连续赋值
string& operator=(const string &str){
if(this != &str) // 避免自己给自己赋值时直接delete自己
{
char *ps = new char[strlen(str.s)+1];
strcpy(ps,str.s);
delete[] s;
s = ps;
}
return *this;
}
还有一种很妙的方法,就是通过拷贝构造函数来拷贝一个局部对象,然后交换这个局部对象和被赋值对象的值,交换完之后局部对象会调用析构函数自动销毁,就省去了写delete的步骤
// 拷贝赋值 返回值加&表示返回的值能连续赋值
string& operator=(const string &str){
if(this != &str) // 避免自己给自己赋值时直接delete自己
{
// tmp是局部对象,交换完之后会调用析构函数自动销毁
String tmp(str); // 拷贝构造
std::swap(tmp.s,s); //指名std的swap,避免名字冲突
}
return *this;
}
分析string类的移动构造函数
移动构造函数是C++11中的,是用一个存在的对象来构造一个同类型的新的对象,并且旧的对象是要马上消亡的,这时就可以把旧对象的资源赠予新的对象,并保证旧对象能够正常析构,那么我们只需要将旧对象的值赠予新对象,然后保证旧对象能够析构就可以了
// 移动构造
string(string&& str)
{
s = str.s;
str.s = nullptr;
}
分析string类的移动赋值函数
移动赋值函数也是C++11中的,是用一个存在的对象給另外一个存在的对象赋值,并且旧对象马上就要消亡,这时就可以直接把资源交给被赋值的对象。那么我们可以通过释放被赋值对象的内存空间,将要旧对象的值直接交给被赋值的对象
// 移动赋值
string& operator=(string&& str)
{
if(this != &str)
{
delete[] s;
s = str.s;
str.s = nullptr;
}
return *this;
}
代码实现
#include <iostream>
#include <cstring>
using namespace std;
class String{
private:
char *s;
public:
// 构造函数
String(const char *str=nullptr):s(strcpy(new char[str?strlen(str)+1:1],str?str:"")){
/*
if(str == nullptr)
{
s = new char[1];
strcpy(s,"");
}
else{
s = new char[strlen(str)+1];
strcpy(s,str);
}
*/
}
// 析构函数
~String(void){
if(s!=nullptr)
{
delete[] s;
s = nullptr;
}
}
// 拷贝构造
String(const String &str):s(strcpy(new char[strlen(str.s)+1],str.s)){
/*
s = new char[strlen(str.s)+1];
strcpy(s,str.s);
*/
}
// 移动构造
String(String&& str)
{
s = str.s;
str.s = nullptr;
}
// 拷贝赋值 返回值加&表示返回的值能连续赋值
String& operator=(const String &str){
if(this != &str) // 避免自己给自己赋值时直接delete自己
{
// tmp是局部对象,交换完之后会调用析构函数自动销毁
String tmp(str); // 拷贝构造
std::swap(tmp.s,s); //指名std的swap swap(tmp,s) 这样会递归调用自己,死循环
/*
char *ps = new char[strlen(str.s)+1];
strcpy(ps,str.s);
delete[] s;
s = ps;
*/
/*
delete[] s;
str = new char(strlen(str.s)+1);
strcpy(s,str.s);
*/
}
return *this;
}
// 移动赋值
String& operator=(String&& str)
{
if(this != &str)
{
delete[] s;
s = str.s;
str.s = nullptr;
}
return *this;
}
size_t length(void)const{
return strlen(s);
}
const char *c_str(void)const{
return s;
}
// 重载[]运算符
char &operator[](size_t index){
return s[index];
}
const char &operator[](size_t index)const{
return s[index];
}
// 重载输入输出运算符
friend ostream& operator<<(ostream &os,const String &s);
friend istream& operator>>(istream &is,String &s);
};
ostream& operator<<(ostream &os,const String &s){
os << s.s ;
return os;
}
istream& operator>>(istream &is,String &s){
is >> s.s ;
return is;
}
int main()
{
String s1("hello World!");
cout << s1 << endl;
String s;
cout << "请输入一串字符:";
cin >> s;
cout << s << endl;
String s2(s1);
cout << s2 << endl;
String s3("sdasad");
cout << s3 << endl;
s1 = s3;
cout << s1 << endl;
}