
◆ 博主名称Severus-Yuan◆ 来自泥潭充满野心强大冷静从不后悔◆ 欢迎光临我的思考天地期待我们在这刹那的同频共振Severus-Yuan的专栏链接C语言C语言初阶数据结构与算法初阶数据结构与算法数据结构算法题数据结构算法题CC文章目录一编码(了解即可)二深浅拷贝的传统现代写法1传统写法2现代写法1.拷贝构造继续优化2.赋值继续优化3.测试3全局swap和类成员swap三引用计数的写时拷贝了解即可vs和g下string结构的说明一编码(了解即可)内存和磁盘中只有0/1如何用0/1去表示世界上的文字呢在C语言中我们了解了ASCII码ASCII码就是一种编码编码是值和符号的一种映射关系。ASCII值在计算机中存的实际就是八个比特位表示的值。这八个比特位先换成整型再通过整型去ASCII表找对应的字符。但很显然只有ASCII码是不够的全世界有这么多文字又该怎么办呢这时候就出现了unicode编码国际所有文字。这也能说明为啥string是模板了因为字符的编码方案有多种那么对应的类的实例化就有多种。还有一种编码是GBK编码中日韩文字。二深浅拷贝的传统现代写法1传统写法在博客【C初阶】string类的模拟实现近三万字呕心沥血详细分析中深拷贝的拷贝函数、赋值运算符重载函数都是自己手动开辟的内存空间自己完成拷贝或赋值操作。//string.h//拷贝构造函数string(conststrings){_ptrnewchar[strlen(s._ptr)1];strcpy(_ptr,s._ptr);//会拷贝\0_capacitys._capacity;_sizes._size;}//赋值stringoperator(conststrings){if(s!*this){delete[]_ptr;_ptrnewchar[strlen(s._ptr1)];strcpy(_ptr,s._ptr);_sizes._size;_capacitys._capacity;}return*this;}2现代写法现代写法下的深拷贝倾向于说把活外包出去让别人帮我们干活。1.拷贝构造//string.h//*this(s)string(conststrings){stringtmp(s._ptr);//交换指针、_size和_capacityswap(_ptr,tmp._ptr);swap(_size,tmp._size);swap(_capacity,tmp._capacity);}分析一下为什么要这么写此处拷贝函数的目的是将s的内容拷贝给this指向的内容。首先我们用s的_ptr指针构造一个tmp的string类对象(有单参数构造函数此处为隐式类型转换)tmp和s的_ptr指针都指向相同的内容但_ptr不同_size相同但是_capacity不一定相同。然后再交换this-_ptr和tmp._ptr地址交换_ptr指向的内容没变、this-_size和tmp._size、this-_capacity和tmp._capacity这样s的内容就通过tmp间接拷贝给了this。注意拷贝构造本质上就是构造这里新对象的构造调用的只有拷贝构造进入现代写法的拷贝构造函数之后并未对*this对象new空间所以_ptr/_size/_capacity都是随机值。在交换之后tmp得到的就是这三个随机值。_size和_capacity倒是问题不大但tmp出函数了是要析构的而且还是调用手动实现的析构函数手动实现的析构函数才会对_ptr指向的堆内存进行释放。delete[]不能是随机值但可以是nullptr因为对nullptr的情况做了特殊处理。所以我们需要在声明处给缺省值。//string.hnamespaceyuan{classstring{private:char*_ptrnullptr;size_t _capacity0;size_t _size0;conststaticsize_t npos;}};图示分析继续优化实现现代写法的拷贝构造函数时连着使用了三个swap函数不如我们直接实现一个专门用来交换string类对象的_ptr/_size/_capacity的swap函数。//string.hvoidswap(strings){std::swap(_ptr,s._ptr);std::swap(_size,s._size);std::swap(_capacity,s._capacity);}//this(s)string(conststrings){stringtmp(s._ptr);//交换指针、_size和_capacityswap(tmp);}2.赋值//string.hvoidswap(strings){std::swap(_ptr,s._ptr);std::swap(_size,s._size);std::swap(_capacity,s._capacity);}//*this sstringoperator(conststrings){if(s!this){//构造stringtmp(s._ptr);//拷贝构造//string tmp(s);swap(tmp);}return*this;}现代写法下的赋值运算符重载函数和现代写法下的拷贝构造的逻辑简直一模一样只是说拷贝构造tmp出函数之后析构的是空但赋值tmp出函数之后是把s的_ptr原来指向的空间真的释放了。继续优化//string.hstringoperator(string tmp){//不用再考虑自己给自己赋值了等进入函数内部早拷贝完了swap(tmp);return*this;}3.测试//test.cppnamespaceyuan{voidtest15(){strings15(hello);strings16(s15);couts16endl;strings17(bank);strings18(yuan);couts18 ;s18s17;couts18endl;}}intmain(){yuan::test15();return0;}3全局swap和类成员swap全局string类对象的全局swap是由库里的swap模板实例化而来的模板如下类成员类成员的swap函数就和我们在这手动实现的swap函数逻辑一样交换两个string类对象的_ptr、_size和_capacity。但全局swap函数代价就太大了根据模板可知会进行三次深拷贝。不过其实调用哪个没差根据string类的全局swap可知其实最后也会调用string类的成员函数swap可以理解成在模板和现成的成员函数之间那肯定还是调现成的除非模板能生成更匹配的函数。三引用计数的写时拷贝了解即可浅拷贝会带来改值、覆盖、析构两次或内存泄漏等多个问题所以我们选择深拷贝但是深拷贝的代价是要申请一块新空间我们不禁想所有情况下都必须要进行深拷贝吗有没有即使有_ptr但也可以浅拷贝的情况呢其实是有的。对于析构两次或内存泄漏的问题使用浅拷贝完全没问题。但需要额外加一个引用计数用于记录有多少个对象指向了同一块内存空间。析构两次譬如此时有n个对象指向了同一块内存空间那么引用计数就是n每当一个对象不指向这块空间时就n–只有当n减到0时的那个对象才会调用析构函数完成对内存空间的回收释放内存泄漏进行赋值前的赋值空间失去了唯一的指向对象那n–得0会调用析构函数完成对内存空间的回收释放这样就不会造成内存泄漏的问题了。对于改值、覆盖的问题如果引用计数为1那就可以放心修改但是如果引用计数1就不能原空间更改了还是要老老实实地创建空间进行深拷贝。这也就是为啥叫做引用计数的写时拷贝——等要写值修改的时候才进行深拷贝。既然有时还是得深拷贝那为啥还费这么大的劲搞一个引用计数的写时拷贝出来可以理解成博弈思想吧但凡出现不用改值的情况可以用浅拷贝的情况那就是赚了。vs和g下string结构的说明初学C如有错误欢迎支持。——end——