模拟实现完成了string,今天我们来深度理解一下string
首先我们先对string下一个定义:
string 是一个管理字符数组的类,并且这个字符数组的结尾要用‘\0’进行标识。
我们主要了解了以下的几个有关string的内容
之前有一个不太容易理解的点:
第一组:
size_t size() const
const char* c_str () const
在讨论这个问题前,我们需要理解的一点是,这里的const 修饰函数,修饰的具体是 *this ,也就是this
所指向的对象,同时也是调用这个函数的那个对象。
这两个函数是只读的,当我们调用它的时候,并不会对其修改,所以一般都要加上const,防止误改和恶意修改。
第二组:
string& operator += (const char* str)
string& insert(size_t pos,const char* str)
这里当对象调用这两个函数的时候,我们发现它是一定要对其进行修改的,所以是一定不能加const的,因为调用这两种函数的对象一定是非const的,否则const对象又要去修改内容,岂不是自相矛盾。
第三组:
const char& operator[] (size_t i) const
char& operator [] (size_t i)
这里两个函数是同一个功能,只是进行了重载。一般都是如此有const版本和非const版本。
至于是如何重载的呢?
const char& operator[] (const string* this ,size_t i) const
char& operator [] (string* this ,size_t i)
这样就一目了然了吧。
当然还有同理的两个
const_iterator begin() const
iterator begin()
当我们需要又读又写的时候,我们就要去调用非const版本。
当我们需要只读,那我们就调用const版本。
当我们只读对象去调用它的函数的时候,
比如 const string s2 = “world”,
我们是无法调用它的非const版本的,这里是一个权限的放大。所以,我们需要再去写一个非cosnt版本的。
这里就说完了有关const的问题。
总结:
- 只读接口函数 +const
- 只写接口函数 不加const
- 可读可写一般要写两个。
我们首先要知道,C++的标准是委员会定的,为了使我们的行业更加规范,但是这里的标准只是规定了一些接口的功能,至于如何实现,那就不关委员会什么事情了。
这里都是由各个库的工作者自己决定的。
我们举个例子: vs编译器,就是微软工程师实现的; gcc/g++GNU的 c&c++ ,是开源组织实现的 ;Clang 又是另一些人实现的。
既然实现的人都不同也没有非常严格的标准,所以库和库之间的实现方法,存储方法等等细节问题,都是不一样的。
我们举一个很简单的例子你也许就能理解:
std:: string s1 (“hello world”)
qqq:: string s2 (“hello world”)
qqq是我自己的命名空间,里面是我自己写的string。
当我们用sizeof()分别打印s1和s2的字节的时候。
就发生了这样的情况
sizeof(s1)是28字节
sizeof(s2)是12字节,我们自己写的string字节很好理解,就是一个指针,两个size_t变量,加起来刚好12.
那vs库中string是28 要怎么理解呢?
我们调出我们的监视窗口,发现它的实现大致是这样:
char _Buf [16];
char* _ Ptr;
size_t _Mysize;
size_t _ MyRes;
这里其实也很好理解
首先如果你的字符串小于16个字节,那我们就直接存在_Buf中,不去开辟新的空间了,如果大于16则直接在char* _Ptr中开辟空间。这样算下来就是28了。
以上是对于实现方案的初步理解
让我们再往深走一下:
关于string深浅拷贝的解决方案
1、深拷贝
这里是深拷贝,不用我们多说了,不存在什么问题,我们主要看看第二种方案。
2、 引用计数浅拷贝 + 写时拷贝
首先我们要知道为什么我们我要深拷贝而不可以用浅拷贝,就是因为,如果使用浅拷贝,我们将会有两个指针指向同一片空间,导致析构的时候空间二次清理,
有很大的危险性。
那么这里的引用计数浅拷贝是什么呢?
拷贝方法还是浅拷贝,指向同一块空间,因为只有在需要改变数组内容的时候,才需要区分他们的空间。如果我们只读,那么一百个对象指向同一片空间都无所谓。唯一影响的就是析构和写入了。
我们在浅拷贝的时候
不能析构多次;
我们在写入数据的时候也不能对同一片空间写;
那么这个count变量就起作用了,每当有一个指针指向这个数组空间,对应的count就会加一。这样一来,只有当count等于一的时候,我们再去调用析构就好了。每当我们需要去写入数据的时候,我们需要先拷贝构造(深拷贝)一下,在你自己的空间上修改,同时把原来的count-1。新构造的count默认为1 。
这里的这种拷贝方法就叫做写时拷贝。
其中vs下就使用的是深拷贝,而gcc环境就是用到第二种 引用计数的浅拷贝+写时拷贝。
这里看起来比深拷贝考虑的详尽且有一定的优化,但是这个方案坚决不是一个好的方案。这个方案存在严重的问题,我们之后再详谈