各位好友, 本期 正式开启 string 深浅拷贝探究 !下面, 让我们开始 ---->战斗环节 !
先来了解一下, 浅拷贝 --------->程序运行 ------->如下 :>
首先, 什么是 浅拷贝 ?
---------->浅拷贝, 也称 位拷贝, 编译器只是将对象中的值拷贝过来。
--------->如果对象中有资源管理, 就会导致多个对象共同享用一份资源。(关键点)
当一个对象被销毁 ---->会将该资源释放掉 ! 其他对象不知道该资源已被释放, 认为还有效 !
所以 ----->会继续对该资源进行访问使用 ! ----->这个时候, 就会发生上述现象的违规访问 !
显然, 上述程序崩溃的原因 :>
string 类没有显示定义其拷贝构造函数 与赋值运算符重载, 此时编译器会合成默认的 。
当用 T1 构造 T2 时, 编译器会去调用默认的拷贝构造。最终导致的问题, T1 与 T2 共同使用一块内存空间 !
------------------------->从而, 在多次释放同一块内存空间的时候,引发 程序崩溃。 这种方式, 称为 浅拷贝 !
为了方便好友们, 有更好地理解, 现附上 图示解析, 如下 :>
显然要解决上述问题, 即 不要让同一个资源 去共享即可 !而这就是 深拷贝的核心思想 !代码如下 :>
//···
·······//
//构造函数
string(const char* str = " ")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
memcpy(_str, s._str, _size + 1);
//strcpy(_str, str);----->特殊情况下 追加‘\0’则不行
}
//深拷贝 ~~ 模块代码
string(const string& s)
{
_str = new char[s._capacity + 1];
memcpy(_str, s._str, s._size + 1);
//strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
//析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
//深拷贝 ---->测试环节
int main()
{
UC :: string T1("I like sport !");
UC :: string T2(T1);
cout << T2.c_str() <<endl;
}
为了方便好友们, 有更好地观感体验, 现附上 有彩色的代码图样 !如下 :>
深拷贝 ~~ 测试 ----->运行结果 :>
为了方便好友们, 更好地理解 ---->深拷贝 现 如下 :>
各位好友,解析 深拷贝 已完成 !现 深入探讨 深拷贝 会遇到的一些特殊情况 !请看下列图示 :>
string 对象在进行 拷贝构造 新的 String 对象时候, 发生了 程序崩溃 !
由上图可知, 增加了 一个特殊字符 ‘\0’ 发生了 程序 错误 !可见 深拷贝的函数设计存在 不足性 !
------------------>原因:>strcpy()函数的运用, 其本身存在 局限性 ---->会 遇到 ‘\0’ 终止拷贝 !
因此, 在 C 语言上, 就设计了 一个 memcpy 函数。 该函数, 不会因 ‘\0’ 存在就终止 其拷贝过程 !
再强化 :>
strcpy()函数 遇到 ‘\0’ 发生终止, 而 memcpy()则不会
memecpy()---->会拷贝完 n个字节。如果第二个字符串中没有 n个字节怎么办? 依然能拷贝成功。
-------->上述实现, 用到了重载操作符 “<<” (流输出 )
而流输出 “<<” 会将 所得到的全部字符 统统打印出来, 这同 底层 ~~ 实现原理有关 !
----------------------------------------->而有了 流输出 “<<” 自然不能忘记 流提取 “>>”
代码如下 :>
//清除函数 clear()
void clear()
{
_str[0] = ' \0 ';
_size = 0;
}
//流输出 " << "
ostream& operator<<(ostream& out, const string& s)
{
for(auto e : s)
{
cout << e << endl;
}
}
//流提取 “ >> ”
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch = in.get();
//处理缓存区内的 空格 与换行
while(ch == ' ' || ch == ' \n ')
{
ch = in.get();
}
char buff[128];
int i = 0;
while(ch == ' ' && ch == ' \n ')
{
buff[i++] = ch;
if(i == 127)
{
buff[i] = ' \0 ';
s += buff;
i = 0;
}
ch = in.get();
}
if(i != 0)
{
buff[i] = ' \0 ';
s += buff;
}
return in;
}
为了方便好友们, 有更好地观感体验, 现附上 有彩色的代码图样 !如下 :>
经调试 可得知 :>
现对 ----->流输出 ~~ 流提取 进行解析 :>
流输出 “ostream <<” 底层逻辑 走的是 迭代器部分 !
流提取 “ostream >>” 底层实现原理 是将得到的每一个字符 储存在一个临时的空间数组(栈区)上
--------------------------->之后 ----->用到 “追加字符串” 实现函数 !经 *this 返回, 指向 流提取 形参 in
各位好友, 下面继续推进 -------->string 类拷贝构造 ------->新的玩法:>赋值重载 “operator=”
----->传统写法 :>
//传统写法
// 赋值重载运算符 “operator=” 运用
string& operator=(const string& s)
{
if(this != &s) //注意此处 “!=” 并没有,自定义 !!
{
char* tmp = new char[s._capacity + 1];
delete[] _str;
_str = nullptr;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
//可单独对 交换函数 swap() 进行定义封装
void swap(string& s)
{
std :: swap(_str, s._str);
std :: swap(_size, s._size);
std :: swap(_capacity, s._capacity);
}
//现代写法 --->第一种写法
string& operator=(const strng& s)
{
if(this != &s)
{
string tamp(s);
//库里面有一个 交换函数, 别忘了 展开 “std :: swap”
std :: swap(_str. tamp._str);
std :: swap(_size, tamp._size);
std :: swap(_capacity, tamp._capacity);
}
return *this;
}
//现代写法 --->第二种写法
string& operator=(const strng& s)
{
if(this != &s)
{
string tamp(s);
//新手玩法:this ->swap(tamp);
swap(tamp);
}
return *this;
}
//优化版 ---->现代写法
string& operator=(string tamp)
{
swap(tamp);
return *this;
}
//重载运算符 “ == ”
bool operartor==(const string& s) const
{
return _size == s._size
&& memcmp(_str, s._str, _size) == 0;
}
//重载运算符 “ != ”
bool operator!=(const string& s) const
{
return !(*this == &s);
}
为了方便好友们, 有更好地观感体验, 现附上 有彩色的代码图样:>
现 对上述代码, 进行解析回顾 :>
难点一 :>不再交换 内置类型成员 ----->更换成如下形式 :>
上述改写, 会造成程序 执行, 引发 栈区溢出 ----->赋值操作会一直进行下去 !
难点二 :>现代写法 理解 ,代码如下 :>
//string 重载赋值 “ = ” 升级版现代写法
string& operator=(string tamp)
{
swap(tamp);
return *this;
} //注意 :>swap()交换函数, 可以调用 库里面的;也可以自己实现 !
------>
为了方便好友们, 有更好地观感体验, 现附上 有彩色的代码图样 :>
各位好友,传统写法 需要手动申请空间 ----->手动拷贝复制 ----->再手动释放相关空间 !
-------------------------------------------->现代写法,全是自动实现 ~~ 自动调用相关接口 !
经调试可知 :>变量 tamp 会先去调用 深拷贝 ------->拷贝 T1 对象的字符串 “sport”
------>再进行交换, 将 tamp 内的值 交换给 T2 ------>最后一步, 就是析构函数的调用, 释放内存 !
为了加深理解, 请看下面的 流程分析图 :>
下面继续进行 一个 字符串大小的比较 ---->代码如下 :>
------>头文件 “string.h”
//字符串大小比较
bool operator==(const string& s) const
{
return _size == s._size && memcmp(_str, s._str, _size) == 0;
}
//小于 “ < ” 第一种写法
bool operator<(const string& s) const
{
size_t i1 = 0;
size_t i2 = 0;
while(i1 < _size && i2 < s._size)
{
if(_str[i1] < s._str[i2])
{
return true;
}
else if(_str[i1] > s._str[i2])
{
return false;
}
else
{
i1++;
i2++;
}
}
}
//小于 “ < ” 第二种写法
bool operator<(const string& s) const
{
int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
return ret == 0 ? _size < s._size : ret < 0;
}
bool operator<=(const string& s) const
{
return *this < s || *this == s;
}
bool operator>(const string& s) const
{
return !(*this <= s);
}
bool operator>=(const string& s) const
{
return !(*this < s);
}
bool operator!=(const string& s) const
{
return !(*this == s);
}
------>测试部分 “Test.cpp”
//string 字符串大小比较测试
int main()
{
string T1("sport");
string T2("sport");
cout << (T1 == T2) << endl;
cout << (T1 < T2) << endl;
cout << (T1 > T2) << endl;
string T3("sport");
string T4("sportxxx");
cout << (T3 == T4) << endl;
cout << (T3 < T4) << endl;
cout << (T3 > T4) << endl;
string T5("soprtxxx");
string T6("sport");
cout << (T5 == T6) << endl;
cout << (T5 < T6) << endl;
cout << (T5 > T6) >> endl;
}
为了方便好友们, 有更好地观感体验, 现附上 有彩色的代码图样 :>
----->头文件部分 “string.h”
----->测试环节:>
各位好友, 本章节 String 类 深浅拷贝 ---->已讲解完成 !😊
------------------->下一期 开战 Vector(容器)环节 ! ----------->敬请期待 "😊😊