前言:上篇文章和大家分享了string容器的使用,但是如果我们只会使用,还不能足以支持我们在以后的工作和学习。所以我们必须要深挖string容器背后的底层原理,通过自己模拟实现一个简单的my_string从而加深对string的底层原理和string的使用的理解。
1.my_string实现的前提条件:
我们知道STL中已经帮我们实现好了string容器的各个接口,如果我们直接实现my_string就会和STL里面的string冲突,所以我们需要一个命名空间把my_string包围起来,防止和库里面的string冲突。
我们实现一个简单的stirng容器(包含构造函数,拷贝构造函数,析构函数,运算符重载,输出函数,求字符串的长度函数)
首先我们需要清楚string的成员变量里面需要什么,string是一个存放字符串的字符数组,所以我们可以在my_string的成员变量中定义一个指针,可以让这个指针进行访问字符串。
namespace ljy
{
class string
{
private:
char* _str;
};
}
然后我们就可以实现string的构造函数了,string创建对象时可能是带参也可能是不带参,所以我们需要实现两个构造函数,但是在前面我们学过一个缺省参数,所以我们可以把这两个构造函数结合起来实现。
string构造对象时如果传的无参,我们就需要一个\0才能判断传参结束,所以这个缺省参数我们可以什么都不传,然后在开辟一个字节的空间存放\0就可以了
string构造对象时如果传的有参,我们知道带参的对象是放在常量区的,而常量区对象是不能被修改的,所以我们不能把对象放在常量区,以前学c语言时我们动态开辟空间都是在堆上,所以现在我们也可以在堆上开辟空间来存放字符串。
string(const char* str = "")//这里的缺省参数不需要任何东西
:_str(new char[strlen(str) + 1])//开辟+1的空间存放\0
{
//把数据拷贝到新的空间(strcpy会自动把\0拷贝过去)
strcpy(_str, str);
}
接下来我们就实现求字符串长度的函数,这个函数非常的简单,我们可以直接使用strlen帮我们求字符串的长度就可以了。
size_t size() const//求字符串的长度不需要修改字符串可以加const
{
return strlen(_str);
}
再接着我们实现operator[ ]任意访问字符,这个函数也非常简单,我们只需返回_str[i]的结果就行了,我们返回的时this指针指向的字符串,所以出了该函数的作用域它还会存在,所以我们可以用引用返回,减少拷贝,提高代码效率。
char& operator[](size_t i)
{
return _str[i];
}
实现一个析构函数,析构函数需要释放我们开辟的空间,并且让这个指针指向空
~string()
{
delete[] _str;
_str = nullptr;
}
实现输出函数(实现类似于c_str接口的输出函数)
实现c_str的输出函数,我们只需要返回字符串的首元素地址就行了。
const char* c_str()
{
return _str;
}
我们来测试一下
void test_string1()
{
string s("hello");
cout << s.c_str() << " ";
}
实现拷贝构造函数(s2(s1))
我们先不自己实现拷贝构造函数,看使用编译器默认的拷贝构造函数,看看会不会出现什么问题,如果不出现什么问题,那么我们不是可以直接用编译器默认生成的拷贝构造函数吗?
代码直接就崩掉了,为什么呢?这里是一个深浅拷贝的问题,编译器默认生成的拷贝构造函数是值拷贝,它会按字节把s2数据一个一个拷贝到s1中。
我给大家画个图大家就能理解了。
那么我们该如何解决这个问题呢?我们需要自己实现一个深拷贝构造函数,我们可以开辟一个和s1一样大的空间,然后把s2里面的字符串拷贝到新的空间。``
//s2(s1)
string(const string& s)
//开辟一个和s1一样大的空间
:_str(new char[strlen(s._str) + 1])
{
//把s1的内容拷贝到s2中
strcpy(_str, s._str);
}
void test_string2()
{
string s1("hello");
string s2(s1);
cout << s2.c_str() << " ";
}
实现operator= (s2=s1)
如果不实现operator也会出现浅拷贝问题,现在我们需要把s1赋值给s2,所以需要开辟一个和s1大小一样的空间,然后把s1里面的值拷贝到新的空间,然后把s2的旧空间释放掉,再把新空间给s2,最后再返回s2对象
//s2=s1(防止自己给自己赋值)
string& operator=(const string& s)
{
//开辟一个和s1大小一样的新的空间,并把s1的内容拷贝到新的空间
char* tmp = new char[strlen(s._str) + 1];
strcpy(tmp, s._str);
//释放s2指向的旧空间
delete[] _str;
//再把新的空间给s2
_str = tmp;
return *this;
}
测试一下
void test_string3()
{
string s1("hello world");
string s2;
s2 = s1;
cout << s2.c_str() << " ";
}
实现简单的string容器的全部代码
namespace ljy
{
class string
{
public:
string(const char* str = "")//这里的缺省参数不需要任何东西
:_str(new char[strlen(str) + 1])//开辟+1的空间存放\0
{
//把数据拷贝到新的空间(strcpy会自动把\0拷贝过去)
strcpy(_str, str);
}
size_t size() const//求字符串的长度不需要修改字符串可以加const
{
return strlen(_str);
}
char& operator[](size_t i)
{
return _str[i];
}
~string()
{
delete[] _str;
_str = nullptr;
}
const char* c_str()
{
return _str;
}
//s2(s1)
string(const string& s)
//开辟一个和s1一样大的空间
:_str(new char[strlen(s._str) + 1])
{
//把s1的内容拷贝到s2中
strcpy(_str, s._str);
}
//s2=s1
string& operator=(const string& s)
{
if (this != &s)
{
//开辟一个和s1大小一样的新的空间,并把s1的内容拷贝到新的空间
char* tmp = new char[strlen(s._str) + 1];
strcpy(tmp, s._str);
//释放s2指向的旧空间
delete[] _str;
//再把新的空间给s2
_str = tmp;
return *this;
}
}
private:
char* _str;
};
void test_string1()
{
string s("hello");
cout << s.c_str() << " ";
}
void test_string2()
{
string s1("hello");
string s2(s1);
cout << s2.c_str() << " ";
}
void test_string3()
{
string s1("hello world");
string s2;
s2 = s1;
cout << s2.c_str() << " ";
}
}
实现简单的string容器(现代写法)
现代写法:利用第三者为我去做我想做的事,然后等他做完我再把它的成果拿给我自己使用。
namespace ljy_copy
{
class string
{
public:
//构造函数
string(const char* str)
:_str(new char[strlen(str) + 1])
{
strcpy(_str, str);
}
//利用构造函数实现拷贝构造函数
//s2(s)
string(const string& s)
:_str(nullptr)//先让_str指向空,防止由于时随机值,导致析构函数释放空间时出现问题
{
//利用tmp去构造一个和s1一样的对象
string tmp(s._str);
//再把tmp和s2交换
swap(_str, tmp._str);
}
//利用拷贝构造函数实现赋值
//s2=s
string& operator[](string s)//string s会自动调用拷贝构造函数
{
swap(_str, s._str);
return *this;
}
private:
char* _str;
};
}