柔性数组结构体设计:
Struct Node 用于模拟String
struct StrNode //均可以在构造对象时使用
{
int ref; //引用计数
int len; //字符串长度
int size; //空间大小
char data[]; //柔性数组
};
类的设计
StrNode作为类的私有成员(注,StrNode属于类型)
使用StrNode类型的指针pstr,作为访问结构体中的成员接口。
class String
{
private:
struct StrNode //均可以在构造对象时使用
{
int ref; //引用计数
int len; //字符串长度
int size; //空间大小
char data[]; //柔性数组
};
StrNode* pstr; //通过str来访问StrNode中的成员
构造函数设计
len
:是要传入data[]的字符串长度
sizeof(StrNode)
:计算的是结构体类型总字节的偏移量。
public:
//构造函数
String(const char* p = NULL) :pstr(NULL)
{
if (p != NULL)
{
int len = strlen(p);
//pstr申请堆空间存放ref,len,size,data[]
pstr = (StrNode*)malloc(sizeof(StrNode) + len * 2 + 1);
pstr->ref = 1; //引用计数,表示此时有一个对象
pstr->len = len; //传给data 的字符串长度
pstr->size = len * 2;//data中的容量大小
strcpy_s(pstr->data, len+1, p);
}
cout << "create construct " << this << endl;
}
析构函数设计
1.先判断pstr是否为空。ptr中有如下四个成员
2.
注意:
(如果ref不为1,说明有一个以上的对象指向pstr,不能对pstr指向的堆空间进行释放,只需要置为空,断开当前对象和pstr的联系,只能等只剩下一个对象,在对空间进行释放。见可以防止内存泄漏)
对对象个数减一,确定没有对象指向该字符串。则把该对象内的ptr中申请的堆空间释放,并置为空。
~String()
{ //首先判断pstr是否为空,不为空,就对ref-1
if (pstr != NULL && --pstr->ref == 0)
{
free(pstr);
}
//否则pstr的指向均为空,或没有对象
cout << "destory " << this << endl;
pstr = NULL;
}
};
int main()
{
String s1{ "baiU" };
//String s2(s1);
}
拷贝构造函数设计
- 先将s1中的ptr置为空
- 将s1中ptr指向的地址空间传给s2
- 然后s2对应的ref+1
如下图:
//拷贝构造
String(const String& s) :pstr(NULL)
{ //先将pstr置为空
if (s.pstr != NULL)
{
pstr = s.pstr;
pstr->ref += 1;
}
cout << "copy consruct" << this << endl;
}
所以s1和s2中pstr指向同一空间。
赋值运算符重载
1.首先判断对象s1等于对象s2
2.不相等,则在判断s1中ptr是否为空,不为空则将对象减一,然后将ptr指向的堆空间释放。
3.让s1中的ptr指向s2中的ptr。
4.如果s1ptr不为空了,在对引用计数加一
5.返回this指针。
String& operator=(const String& s)
{ //先判断this指向的pstr中的data是否和s的字符串中的内容相等
if (this != &s)
{
//再次判断pst是否为空和对象个数
if (pstr != NULL && --pstr->ref == 0)
{
free(pstr);
}
pstr = s.pstr;
if (pstr != NULL)
{
pstr->ref += 1;
}
}
cout << "operator =" << this << endl;
return *this;
}
int main()
{
String s1{ "baiU" };
//String s2(s1);
String s3("shiqianyu");
s3 = s1;
}
String s1{ "baiU" };
String s3("shiqianyu");
s3 = s1;
括号运算符重载
使用原因: 希望可以通过[]运算符来改变对象字符串的某个元素
int main()
{
String s1{ "baiU" };
String s2(s1);
s1[1] = 's';
}
如果有多个对象指向pstr,也就是指向同一个字符串,那么改变s1,就会改变s2的值,这个时候,我们就需要进行写时拷贝。
写时拷贝
1.先判断pstr是否为空,如果为空,说明没有申请空间,直接退出。
2.判断输入的下标,是否在下标范围内。
3.判断引用计数是否大于一,如果不大于直接返回下标元素,大于,说明还有其他对象,指向该字符串,需要进行拷贝。
4.重新申请堆空间,将内容拷贝到新的堆区
5.将新空间的引用计数加一,原来空间的引用计数减一,最后将新空间赋给该对象的指针pstr
char& operator[](const int index)
{
if (pstr == NULL) exit(1);
assert(index >= 0 && index <= pstr->len - 1);
if (pstr->ref > 1)
{
int total = sizeof(StrNode) + pstr->size + 1;
StrNode* newNode = (StrNode*)malloc(total);
memcpy(newNode, pstr, total);
newNode->ref = 1;
pstr->ref = 1;
pstr = newNode;
}
return pstr->data[index];
}
只有一个对象指向pstr
return pstr->data[index];
int main()
{
String s1{ "baiU" };
s1[1] = 's';
}
重载[]运算符2
如果我们不希望这个运算符是双向的,既可以利用它来取值,又可以利用它来赋值,那么就不需要返回一个引用,也不用写实拷贝。
char operator [](const int index)const
{
assert(index >= 0 && index <= pstr->size - 1);
return pstr->data[index];
}
修改某个下标的元素
int main()
{
String s1{ "baiU" };
//String s2(s1);
//String s3("shiqianyu");
//s3 = s1;
//s1[1] = 's';
s1.modify(0, 'a');
}
当我们只需要数据,而不需要获取,我们直接写一个修改函数就可。
原理和写实拷贝基本相同。
bool modify(const int index,const char ch)
{
if (pstr == NULL) exit(1);
assert(index >= 0 && index <= pstr->len - 1);
if (pstr->ref > 1)
{ //说明有多个对象指向同一个结点。
int total = sizeof(StrNode) + pstr->size + 1;
StrNode* newNode = (StrNode*)malloc(total);
memcpy(newNode, pstr, total);
newNode->ref = 1;
pstr->ref = 1;
pstr = newNode;
}
pstr->data[index] = ch;
return true;
}
移动构造函数,移动赋值
- 移动构造,就是将自己的资源进行转移,转移到要构造的对象里。
- 移动赋值,将自己的资源赋值给目标对象,自身置为NULL。
//移动构造函数
String(String&& s) :pstr(NULL) //不需要创建临时对象。
{ //先将pstr置为空
pstr = s.pstr;
s.pstr =NULL;
cout << "move consruct" << this << endl;
}
//移动赋值
String& operator=(String&& s) //对对象本身进行操作
{ //先判断this指向的pstr中的data是否和s的字符串中的内容相等
if (this == &s) return *this;
//再次判断pst是否为空和对象个数
if (pstr != NULL && --pstr->ref == 0)
{
free(pstr);
}
pstr = s.pstr; //让s1的ptr指向s2指向的ptr
s.pstr = NULL; //让s2断开
cout << "operator =" << this << endl;
return *this;
}
String fun()
{
String s2("shiqinayu");
return s2;
}
int main()
{
String s1{ "baiU" };
s1 = fun();
}
1.对s1初始化
2. 调用构造函数对s2初始化
3. return s2,优先调用移动构造函数,构造将亡值对象,让将亡值对象的ptr指向s2的ptr指向的空间,然后将s2中的ptr置为空。
4. 析构s2
- 接下来返回到调用点出,调用移动赋值,直接将s2的堆区空间释放,然后让s1的ptr指向,将亡值对象ptr指向的空间。然后将不具名对象ptr置为空即可完成赋值。
加法运算符重载
- 如果两个对象都空,则直接返回空String()/
- 如果s1为空,s2不为空,则返回s2.
- 如果s1不为空,s2为空,则返回s1
- 如果s1和s2都不为空,则需要申请一个新的指针,开辟一段新的堆空间,将两个字符串放进去。
5.最后返回时,需要调用构造函数,创建一个新对象。(因为是将对象赋值给对象),为了防止调用上面的构造函数,所以需要创建一个特殊的的构造函数。
private: //这样才能访问strNode
String(StrNode* p):pstr(p){}
//加法运算符重载
String operator+(const String& s)const
{
if (pstr == NULL && s.pstr == NULL)return String();
else if (pstr != NULL && s.pstr == NULL)return *this;
else if (pstr == NULL && s.pstr != NULL)return s;
else
{
int total = (pstr->len + s.pstr->len) * 2;
StrNode* newNode = (StrNode*)malloc(total + 1);
strcpy_s(newNode->data, pstr->len+1, pstr->data);
strcat_s(newNode->data, total, s.pstr->data);
newNode->ref = 1;
newNode->len = pstr->len + s.pstr->len;
newNode->size = total;
return String(newNode);
}
}
int main()
{
String s1{ "baiU" };
String s3("shiqianyu");
s1 = s3 + s1;
}
s3+s1
调用了operator+完成
newNode =
return String(StrNode *p):pstr§
调用特殊的构造函数,创建一个将亡值对象,让该将亡值对象中的ptr将newNode指向的空间
最后:s1 = s3 + s1 ==》 s1->ptr = ptr;
调用移动赋值让s1对象中的ptr指向将亡值对象中的ptr;
+=运算符重载
String& operator +=(const String& s)
{
if (pstr != NULL && s.pstr != NULL)
{
if (pstr->ref > 1)
{
int total = pstr->len + s.pstr->len;
pstr->ref -= 1;
char* tmp = pstr->data; //
pstr = (StrNode*)malloc(sizeof(StrNode) + total * 2 + 1);
strcpy(pstr->data, tmp);
strcat(pstr->data, s.pstr->data);
pstr->ref = 1;
pstr->len = total;
pstr->size = total * 2;
}
else
{
int total = pstr->len + s.pstr->len;
if (pstr->size < total)
{
pstr = (StrNode*)realloc(pstr, sizeof(StrNode) + total * 2 + 1);
pstr->size = total * 2;
}
strcat(pstr->data, s.pstr->data);
pstr->len = total;
}
}
else if (this->pstr == NULL && s.pstr != NULL)
{
pstr = s.pstr;
pstr->ref += 1;
}
return *this;
}
int main()
{
String s1{ "baiU" };
String s3("shiqianyu");
s1 += s3;
}
- 判断thsi指针所指对象的引用计数大于1,则开辟空间。
- 如果引用计数等于1,那么如果size小于两个字符串长度之和,就对其扩容,否则直接拼接
- 如果this为空,另外一个对象不为空,则直接赋值给this所指的对象,引数加一
- 不满足上述条件什么都不做,直接返回this。