您的位置: 首页 - 站长

phpcms 视频网站模板网站设计 视频

当前位置: 首页 > news >正文

phpcms 视频网站模板,网站设计 视频,阿里云网站注册,视频播放类网站建设费用第一章#xff1a;为什么要学习string类 1.1 C语言中的字符串 C语言中#xff0c;字符串是以\0结尾的一些字符的集合#xff0c;为了操作方便#xff0c;C标准库中提供了一些str系列的库函数#xff0c;但是这些库函数与字符串是分离开的#xff0c;不太符合OOP的思想为什么要学习string类 1.1 C语言中的字符串 C语言中字符串是以\0结尾的一些字符的集合为了操作方便C标准库中提供了一些str系列的库函数但是这些库函数与字符串是分离开的不太符合OOP的思想而且底层空间需要用户自己管理稍不留神可能还会越界访问。 1.2 面试题 在OJ中有关字符串的题目基本以string类的形式出现而且在常规工作中为了简单、方便、快捷基本都使用string类很少有人去使用C库中的字符串操作函数。 第二章标准库中的string类 2.1 string类 字符串是表示字符序列的类标准的字符串类提供了对此类对象的支持其接口类似于标准字符容器的接口但添加了专门用于操作单字节字符字符串的设计特性。string类是使用char(即作为它的字符类型使用它的默认char_traits和分配器类型(关于模板的更多信息请参阅basic_string)。string类是basic_string模板类的一个实例它使用char来实例化basic_string模板类并用char_traits和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。注意这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列这个类的所有成员(如长度或大小)以及它的迭代器将仍然按照字节(而不是实际编码的字符)来操作。 总结 string是表示字符串的字符串类该类的接口与常规容器的接口基本相同再添加了一些专门用来操作string的常规操作。string在底层实际是basic_string模板类的别名typedef basic_stringchar, char_traits, allocator string;不能操作多字节或者变长字符的序列。 在使用string类时必须包含#include头文件以及using namespace std; 2.2 string类的常用接口说明

  1. string类对象的常见构造 函数名称功能说明string() 重点构造空的string类对象即空字符串string(const char* s) 重点用C-string来构造string类对象string(size_t n, char c)string类对象中包含n个字符cstring(const strings) 重点拷贝构造函数 2. string类对象的容量操作 函数名称 功能说明size重点返回字符串有效字符长度length返回字符串有效字符长度capacity返回空间总大小empty 重点检测字符串释放为空串是返回true否则返回falseclear 重点 清空有效字符reserve 重点为字符串预留空间resize 重点将有效字符的个数该成n个多出的空间用字符c填充 注意 size()与length()方法底层实现原理完全相同引入size()的原因是为了与其他容器的接口保持一致一般情况下基本都是用size()。clear()只是将string中有效字符清空不改变底层空间大小。resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个不同的是当字符个数增多时resize(n)用0来填充多出的元素空间resize(size_t n, char c)用字符c来填充多出的元素空间。注意resize在改变元素个数时如果是将元素个数增多可能会改变底层容量的大小如果是将元素个数减少底层空间总大小不变。reserve(size_t res_arg0)为string预留空间不改变有效元素个数当reserve的参数小于string的底层空间总大小时reserver不会改变容量大小。 3. string类对象的访问及遍历操作 函数名称功能说明operator[] 重点返回pos位置的字符const string类对象调用begin endbegin获取一个字符的迭代器 end获取最后一个字符下一个位置的迭代器rbegin rendbegin获取一个字符的迭代器 end获取最后一个字符下一个位置的迭代器范围forC11支持更简洁的范围for的新遍历方式 4. string类对象的修改操作 函数名称功能说明push_back在字符串后尾插字符cappend在字符串后追加一个字符串operator (重点)在字符串后追加字符串strc_str(重点)返回C格式字符串find npos(重点)从字符串pos位置开始往后找字符c返回该字符在字符串中的位置rfind从字符串pos位置开始往前找字符c返回该字符在字符串中的位置substr在str中从pos位置开始截取n个字符然后将其返回 注意 在string尾部追加字符时s.push_back© / s.append(1, c) / s c三种的实现方式差不多一般情况下string类的操作用的比较多操作不仅可以连接单个字符还可以连接字符串。对string操作时如果能够大概预估到放多少字符可以先通过reserve把空间预留好。 5. string类非成员函数 函数功能说明operator尽量少用因为传值返回导致深拷贝效率低operator 重点输入运算符重载operator 重点输出运算符重载getline 重点获取一行字符串relational operators 重点大小比较 string类内部就是char*为什么不直接用char*而是要封装成string类。 为了适应不同的编码UTF-8、UTF-16、UTF-32等从而表示不同国家的文字 接口示例 #include iostream #include string #include list using namespace std;void test_string1() {string s1;string s2(hello);//输入 输出cin s1;//C可以按需提取C语言只能使用固定大小的数组cout s1 endl;cout s2 endl;//拼接字符串//相比strcat可读性更强strcat空间不够不能自动扩容//strcat要先找\0如果字符串较长消耗时间string ret1 s1 s2;//s1s1也可以两个s1的内容拷贝到ret1对象对象cout ret1 endl;string ret2 s1 我来了;//对象字符串cout ret2 endl; }void test_string2() {string s1(hello world);//直接调用构造函数string s2 hello world;//构造拷贝构造优化本质是隐式类型转换单参数构造函数支持隐式类型转换//遍历stringfor (size_t i 0; i s1.size(); i)cout s1[i] ;//读cout endl;for (size_t i 0; i s1.size(); i)s1[i];//写cout s1 endl;//ifmmp!xpsme//遍历的第二种方式迭代器string::iterator it s1.begin();//iterator是在string这个类里面定义的所以需要加string类域while (it ! s1.end()) { //end指向最后一个有效字符的后一个位置即\0cout *it ;//读it;}cout endl;it s1.begin();//while (it s1.end()) //这里可以改为但不建议//因为迭代器是通用的string空间是连续的但其他数据结构不一定比如listwhile (it ! s1.end()) {*it a;//写it;}cout s1 endl;//aaaaaaaaaaa//链表示例listint lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);listint::iterator lit lt.begin();while (lit ! lt.end()) { //链表只能用!不能用cout *lit ;lit;}cout endl; }void test_string3() {string s1(hello world);//正向遍历string::iterator it s1.begin();//iterator是在string这个类里面定义的所以需要加string类域while (it ! s1.end()) { //end指向最后一个有效字符的后一个位置即\0cout *it ;//读it;}cout endl;//反向遍历//string::reverse_iterator rit s1.rbegin();auto rit s1.rbegin();//rbegin的返回值就是反向迭代器所以可以使用autowhile (rit ! s1.rend()) {cout *rit ;//d l r o w o l l e hrit;//这里是从后向前倒着走}cout endl;//原理编译时编译器替换成迭代器//读for (auto ch : s1) //范围for不支倒序遍历。如果想修改需要引用cout ch ;cout endl;//写for (auto ch : s1)ch;cout s1 endl; }//void func(string s) //不推荐传值传参会调用拷贝构造 void func(const string s) {//string::iterator it s.begin();//迭代器支持读写但参数有const修饰所以报错//string::const_iterator it s.begin();auto it s.begin();while (it ! s.end()) {//const迭代器不支持写//*it a;cout *it ;it;}cout endl;//string::const_reverse_iterator rit s.rbegin();auto rit s.rbegin();while (rit ! s.rend()) {cout rit ;rit;}cout endl; }void test_string4() {string s1(hello worldxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx);func(s1);string s2(s1);//拷贝构造string s3(s1, 6, 5);//从下标6位置开始取5个字符 cout s3 endl;//world//npos 是一个静态成员常量值无符号整形-1即最大值表示“直到字符串的末尾”。string s4(s1, 6);//从从下标6位置开始取到结束。最后一个是缺省参数nposcout s4 endl;//不使用缺省参数也可以计算string s5(s1, 6, s1.size() - 6);//size是字符串长度cout s5 endl;string s6(10, a);//用10个a初始化cout s6 endl;string s7(s6.begin(), –s6.end());//用迭代器区间初始化cout s7 endl;//aaaaaaaa//三种赋值方法string s8;s8 s7;s8 xxx;s8 y; }void test_string5() {string s1(hello world);cout s1.size() endl;//11 size具有通用性cout s1.length() endl;//11cout s1.capacity() endl;//15s1.clear();//仅清除数据不释放空间cout s1.size() endl;//0cout s1.capacity() endl;//15//为什么结尾是\0//需要兼容C语言而且有些场景只能调C的接口//文件示例string filename;cin filename;FILE fout fopen(filename.c_str(), r);//fopen函数第一个参数是字符串//const char* c_str() const; 返回一个指向数组的指针该数组包含以 null 结尾的字符序列即 C 字符串表示字符串对象的当前值。 }void test_string6() {string s;size_t old s.capacity();cout 初始 s.capacity() endl;for (size_t i 0; i 100; i) {s.push_back(x);if (s.capacity() ! old) {cout 扩容: s.capacity() endl;old s.capacity();}}//初始15//扩容:31//扩容:47//扩容:70//扩容:105string s1;//reserve价值确定大概要多少空间提前开辟减少扩容提高效率s1.reserve(100);//申请100字符空间但vs编译器会多开size_t old1 s1.capacity();cout 初始 s1.capacity() endl;for (size_t i 0; i 100; i) {s1.push_back(x);if (s1.capacity() ! old1) {cout 扩容: s1.capacity() endl;old1 s1.capacity();}}//初始111s1.reserve(10);//一般不会减小但STL没规定。文档说明是一个不具有约束力的缩小请求cout s1.size() endl; }void test_string7() {string s1(hello world);cout s1.size() endl;//11cout s1.capacity() endl;//15//原size resize 原capacity (111315)//s1.resize(13);//仅修改sizes1.resize(13, x);cout s1 endl;//hello worldxxcout s1.size() endl;//13cout s1.capacity() endl;//15//原capacity resize (1520)s1.resize(20, x);cout s1 endl;//hello worldxxxxxxxxxcout s1.size() endl;//20cout s1.capacity() endl;//31//resize 原size 520)s1.resize(5);cout s1 endl;//hellocout s1.size() endl;//5cout s1.capacity() endl;//31//resize在string里用的并不多这里主要为了保持接口一致性//实际中可能遇到的示例string s2;s2.resize(10, #);//开辟10字符并用#初始化cout s2 endl;//##########cout s2.size() endl;//10cout s2.capacity() endl;//15s2[0];//越界终止程序但更常用s2.at(0);//越界可以捕获异常程序继续执行 }void test_string8() {string s;s.push_back(#);//只能插入一个字符cout s endl;s.append(hello);//append用于插入字符串cout s endl;string ss(world);s.append(ss);cout s endl;//但更常用既可以插入字符也可以插入字符串s #;s world;s ss;//尽量使用因为是传值返回string ret1 ss #;string ret2 ss hello; }void test_string9() {string str;string base The quick brown fox jumps over a lazy dog.;str.assign(base);//将base的数据赋值给str如果str有数据直接覆盖cout str endl;str.assign(base, 5, 10);//从下标5开始取10个字符 赋值cout str endl;}void test_string10() {// insert/erase/replace尽量不用因为涉及挪动数据效率不高//接口设计复杂需要时查看文档string str(hello world);str.insert(0, 1, x);//在下标0插入1个字符 xstr.insert(str.begin(), x);//头插//string erase(size_t pos 0, size_t len npos);//第一个参数是下标第二个参数是删除个数。默认参数npos表示结束str.erase(5);//从下标5开始删到结束string s1(hello world);s1.replace(5, 1, 20%);//将下标5开始后1个字符替换为20%cout s1 endl;//hello20%world//空格替换为20%//思路创建一个新string对象s3遍历原string对象s2//如果s2的字符不是空就添加到s3中是空就添加20%string s2(The quick brown fox jumps over a lazy dog.);string s3;for (auto ch : s2) {if (ch ! )s3 ch;elses3 20%;}cout s3 endl;//如果要求输出的是s2s2 s3;s2.assign(s3);//swap效率最差3次深拷贝2次赋值//但标准库里专门为string准备了一个全局的swap永远不会调用到需要拷贝和赋值的那个swap所以和下方s2.swap(s3)一样swap(s2, s3);printf(s2:%p\n, s2.c_str());//s2:01155288printf(s3:%p\n, s3.c_str());//s3:01153F80s2.swap(s3);//最好的方式。该方式为交换s2和s3的指向printf(s2:%p\n, s2.c_str());//s2:01153F80printf(s3:%p\n, s3.c_str());//s3:01155288 }void test_string11() {string s1(test.cpp);//读取文件后缀//size_t find(char c, size_t pos 0) const;//找到第一个指定字符并返回位置第二个缺省参数0表示从头开始//如果未找到匹配项该函数将返回 stringnpos。size_t i s1.find(.);//从pos位置取len个字符//string substr (size_t pos 0, size_t len npos) const;//npos表示取到结束string s2 s1.substr(i);//从i位置取剩余字符cout s2 endl;//.cppstring s3(test.cpp.tar.zip);//rfind找到倒数第一个指定字符size_t j s3.rfind(.);string s4 s3.substr(j);cout s4 endl;//.zipstring s5(https://legacy.cplusplus.com/reference/string/string/rfind/);string sub1, sub2, sub3;size_t i1 s5.find(:);if (i1 ! string::npos)//npos 是一个静态成员常量值无符号整形-1即最大值sub1 s5.substr(0, i1);//协议elsecout i1没有找到 endl;size_t i2 s5.find(/, i1 3);if (i2 ! string::npos)sub2 s5.substr(i1 3, i2 - (i1 3));//域名 左闭右开右-左就是个数elsecout i2没有找到 endl;sub3 s5.substr(i2 1);//资源名cout sub1 endl;//httpscout sub2 endl;//legacy.cplusplus.comcout sub3 endl;//reference/string/string/rfind/ }void test_string12() {//find_first_of 在字符串中搜索与其参数中指定的任何字符匹配的第一个字符。string str(Please, replace the vowels in this sentence by asterisks.);size_t found str.find_first_of(aeiou);while (found ! string::npos) {str[found] *;found str.find_first_of(aeiou, found 1);}cout str endl;//Pls*, r*plc th* v*w*ls *n th*s s*ntnc by *st*rsks.//find_first_not_of 在字符串中搜索与参数中指定的任何字符都不匹配的第一个字符。//find_last_of 在字符串中搜索与参数中指定的任何字符匹配的最后一个字符。} 课堂练习 1. 917. 仅仅反转字母 - 力扣LeetCode class Solution { public:bool isLetter(char ch) {if (ch a ch z)return true;if (ch A ch Z)return true;return false;}string reverseOnlyLetters(string s) {int begin 0, end s.size() - 1;while (begin end) {//下方while循环如果没有begin end条件//且遇到没有字母的情况就会一直while (begin end !isLetter(s[begin]))//begin从左往右找字母begin;while (begin end !isLetter(s[end]))//end从右往左找字母–end;swap(s[begin], s[end]);begin;–end;}return s;} }; 2. 387. 字符串中的第一个唯一字符 - 力扣LeetCode class Solution { public:int firstUniqChar(string s) {// 使用计数排序思路//额外创建一个映射数组int countA[26] {0};//只有26个小写字母用0-25映射a-z//统计次数for (auto ch : s)//遍历数组countA[ch - a];//相对映射//字符存储都是用ASCII码值用当前字符-字符a得到差值恰好映射数组下标//再遍历一遍查看s数组中哪些字符在计数数组中只出现1次for (int i 0; i s.size(); i)//遇到计数数组存储1的第一个下标处即第一个不重复的字符//s[i] - a是s数组的字符在计数数组中的位置if (countA[s[i] - a] 1)return i;return -1;} }; 3. 字符串最后一个单词的长度_牛客题霸_牛客网 #include cstddef #include iostream #include string using namespace std;int main() {string str;getline(cin, str);//该方法遇到换行才结束// cin str;//该方法的问题只能读取到第一个空格 size_t i str.rfind( );//从后往前找到第一个空格后面的部分就是最后一个单词if (i ! string::npos)cout str.size() - (i 1) endl;//i指向空格size指向\0;左开又开左-(右1)elsecout str.size() endl; }4. 415. 字符串相加 - 力扣LeetCode class Solution { public:string addStrings(string num1, string num2) {int end1 num1.size() - 1, end2 num2.size() - 1;//size是个数end是下标所以size-1string str; // 新结果字符串int next 0; // 进位变量while (end1 0 || end2 0) {// 不能这么取因为上方循环条件可能出现某个end0的情况// int x1 num1[end1] - 0;int x1 end1 0 ? num1[end1] - 0 : 0; // 大于等于0才取最后字符否则用0代替因为加0不影响结果int x2 end2 0 ? num2[end2] - 0 : 0;// 新结果字符串每次循环的最后一位原来两个老字符串最后一位进位int ret x1 x2 next;// 每次要调整进位next ret / 10; // 如果新结果10要进位ret ret % 10; // 新结果要保留的值是小于10//头插法//每次得到新的结果都要插入到新字符串最前面// str.insert(0, 1, 0 ret);//头插要挪动数据是N^2的算法//用尾插加逆置提高效率str (0 ret);–end1;–end2;}// //头插法// if (next 1)// str.insert(0, 1, 1);//尾插逆置if (next 1)str 1;reverse(str.begin(), str.end());return str;} }; 第三章string类的模拟实现 3.1 string类的模拟实现 string.h #pragma once #include assert.h//该.h文件没有包含任何头文件也可以编译成功 //因为.h文件是在.cpp文件中展开而该.h文件在.cpp文件中靠后位置并且.cpp文件中已经包含头文件 namespace bit {class string {public://模拟实现迭代器typedef char iterator;typedef const char* const_iterator;iterator begin() {return _str;}iterator end() { //end指向\0_size是字符串长度return _str _size;}const_iterator begin() const {return _str;}const_iterator end() const {return _str _size;}无参构造函数//string()// :_str(nullptr)//这种方式会导致对空指针解引用// ,_size(0)// ,_capacity(0) //{}//下方两种初始化方式都可以//string()// :_str(new char[1])// ,_size(0)// ,_capacity(0) //{// _str[0] \0;//}//string()// :_str(new char[1]{\0})// , _size(0)// , _capacity(0) {}//带参构造函数//string(const char* str)// //初始化列表顺序是按照声明顺序所以下方会先初始化_str但此时_capacity还没有初始化是随机值// :_size(strlen(str))// ,_capacity(_size)// ,_str(new char[_capacity1])//{// strcpy(_str, str);//}//正确版本//string(const char* str)// :_size(strlen(str))// ,_capacity(_size)//{// _str new char[_capacity 1];// strcpy(_str, str);//}//构造函数最理想方式是全缺省参数//string(const char* str \0)//这种方式类型不匹配\0是char类型双引号的形式才是字符串类型但这种方式不规范因为C语言规定常量字符串结尾默认有\0这样就有2个\0//string(const char* str \0)string(const char* str )//不要加空格加空格_str就会有空格//如果使用缺省参数strlen是0申请空间时开辟了1个字节strcpy会把\0拷贝过去:_size(strlen(str)), _capacity(_size) {_str new char[_capacity 1];strcpy(_str, str);}传统写法s2(s1)//string(const string s) {// _str new char[s._capacity 1];// strcpy(_str, s._str);// _size s._size;// _capacity s._capacity;//}s2 s3//string operator(const string s) {// //不考虑s2空间大小相当于让s2指向新空间// if (this ! s) {// char* tmp new char[s._capacity 1];// strcpy(tmp, s._str);// delete[] _str;//释放s2// _str tmp;//s2指向新空间// _size s._size;// _capacity s._capacity;// }// return *this;//}//现代写法void swap(string s) {std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}//s2(s1)string(const string s)//有些编译器不会初始化内置类型this指向的内置类型变量就有可能是随机值// 交换给tmp后出了作用域tmp要销毁也就是调用析构函数这个时候就会出问题//所以需要初始化:_str(nullptr), _size(0), _capacity(0) {string tmp(s._str);swap(tmp);}现代写法s2 s3//string operator(const string s) {// //不考虑s2空间大小相当于让s2指向新空间// if (this ! s) {// string tmp(s);// swap(tmp);//this-swap(tmp)// //s2换给个了tmptmp出了作用域要销毁即调用析构函数// }// return *this;//}//进一步优化//s2 s3string operator(string tmp) {//上方是传引用传参所以还要手动调用一次拷贝构造//这里直接传值传参解决该问题即传参时就拷贝构造tmpswap(tmp);return this;}~string() {delete[] _str;_str nullptr;_size _capacity 0;}char operator {assert(pos _size);return _str[pos];}const char operator const { //只读版本assert(pos _size);return _str[pos];}size_t capacity() const {return _capacity;}size_t size() const {return _size;}const char c_str() const {return _str;}void reserve(size_t n) { //插入数据需要扩容并且还可以给外部提供开辟空间的接口if (n _capacity) { //这里需要检查因为外部也可能调用该函数char* tmp new char[n 1];//需要多开1个空间存\0strcpy(tmp, _str);//将原数据拷贝到新空间strcpy会拷贝\0delete[] _str;//释放原数据的空间_str tmp;//_str指向新空间_capacity n;//更新新容量}}void resize(size_t n, char ch \0) {//分三种情况//1. n _size 删除//2. _size n _capacity 插入//3. n _capacity//1.n小于等于原字符串长度if (n _size) {_str[n] \0;_size n;}else { //2.n大于原字符串长度reserve(n);//用reserve调整_capacitywhile (_size n) { //_size是字符串长度当_size作为下标时最后一个元素的下标是_size-1_size指向\0_str[_size] ch;_size;}_str[_size] \0;}}size_t find(char ch, size_t pos 0) { //查找字符for (size_t i pos; i _size; i)if (_str[i] ch)return i;return npos;}size_t find(const char* str, size_t pos 0) { //查找字符串const char* p strstr(_str pos, str);if (p) //如果p不为空说明找到了return p - _str;//用找到子串的指针子串起始位置减去整个字符串的起始位置就是下标elsereturn npos;}string substr(size_t pos, size_t len npos) {string s;size_t end pos len;//取到结束//如果取的len太大超过原字符串结尾需要对len处理if (len npos || pos len _size) {len _size - pos;//这里是因为len长度太大end _size;}s.reserve(len);for (size_t i pos; i end; i)s _str[i];return s;}void push_back(char ch) {//检查扩容if (_size _capacity)//reserve(_capacity * 2);//如果_capacity是0乘2还是0reserve(_capacity 0 ? 4 : _capacity * 2);//尾插字符_str[_size] ch;_size;_str[_size] \0;}void append(const char* str) {//先计算追加字符串的长度size_t len strlen(str);if (_size len _capacity)//等于不需要扩容说明空间恰好满足reserve(_size len);//尾插字符串strcpy(_str _size, str);_size len;}//追加字符和字符串更常用的还是string operator(char ch) {push_back(ch);return this;}string operator(const char str) {append(str);return *this;}该版本头插有bug//void insert(size_t pos, char ch) { // //插入数据前要判断容量// assert(pos _size);// if (_size _capacity)// reserve(_capacity 0 ? 4 : _capacity * 2);// size_t end _size;// //插入数据需要挪动数据最先从最后位置即\0// //两个变量都是无符号整型当end减到-1时其实是最大整型值依然满足条件从而越界// //将end修改为int也不行因为循环条件end pos这里会发生类型提升// while (end pos) { // _str[end 1] _str[end];// –end;// }// _str[pos] ch;// _size;//更新_size//}//正确版本-插入字符void insert(size_t pos, char ch) {//插入数据前要判断容量assert(pos _size);if (_size _capacity)reserve(_capacity 0 ? 4 : _capacity * 2);size_t end _size 1;//让end指向\0的后一位置即插入字符后新字符串最后字符的位置 while (end pos) { //如果是头插end最终指向第二个元素_str[end] _str[end - 1];//end前一位置的值赋值给end位置–end;}_str[pos] ch;_size;//更新_size}该版本头插有bug参考上方插入字符函数//void insert(size_t pos, const char* str) {// assert(pos _size);// size_t len strlen(str);// if (_size len _capacity) //先判断是否需要扩容// reserve(_size len);// //挪动数据// size_t end _size;// while (end pos) { //endpos时还会再–等于-1时其实是最大整型值依然满足条件从而越界// _str[end len] _str[end];// –end;// }// strncpy(_str pos, str, len);//被插入字符串拷贝进原字符串。不能用strcpy会拷贝\0// _size len;//}//正确版本-插入字符串void insert(size_t pos, const char* str) {assert(pos _size);size_t len strlen(str);if (_size len _capacity) //先判断是否需要扩容reserve(_size len);//挪动数据size_t end _size len;//end指向插入字符串后新字符串最后字符的位置while (end pos) {_str[end] _str[end - len];//end-len就是原字符串最后字符位置–end;}strncpy(_str pos, str, len);//被插入字符串拷贝进原字符串。不能用strcpy会拷贝\0_size len;}void erase(size_t pos, size_t len npos) {assert(pos _size);//1.如果指定位置开始删除的长度等于字符串结尾 or 长度等于默认值nposif (len npos || pos len _size) {_str[pos] \0;_size pos;}else {size_t begin pos len;//begin指向删除部分的后一字符while (begin _size) { //从begin位置开始把之后的字符向前挪动包括_size指向的\0_str[begin - len] _str[begin];//begin-len就是pos位置begin;}_size - len;}}//运算符重载bool operator(const string s) const {return strcmp(_str, s._str) 0;}bool operator(const string s) const {return strcmp(_str, s._str) 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);}void clear() {_str[0] \0;_size 0;}private:char _str;size_t _size;size_t _capacity;public:const static size_t npos;//const static size_t npos -1;//const静态整型变量可以在这里初始化其他类型都不可以};const size_t string::npos -1;//流插入、流提取不是一定要写成友元。写成友元的目的是访问私有ostream operator(ostream out, const string s) {这里还不能使用范围for因为string对象被const修饰所以需要有const修饰的迭代器//for (size_t i 0; i s.size(); i)// out s[i];//return out;//添加有const修饰的迭代器后可以使用范围forfor (auto ch : s)out ch;return out;}该版本需要多次扩容//istream operator(istream in, string s) {// s.clear();//接收数据前要清理原对象中的数据这样才和库里面实现的一致// char ch;// //in ch;//cin和scanf一样获取不到空格和换行所以导致下方就没有空格和换行导致死循环// ch in.get();//istream提供的get类似于C语言的getchar// while (ch ! ch ! \n) {// s ch;// //in ch;// ch in.get();// }// return in;//}//该版本输入多少就开辟多少istream operator(istream in, string s) {s.clear();char buff[129];size_t i 0;char ch;ch in.get();while (ch ! ch ! \n) {buff[i] ch;if (i 128) { //129个元素下标是0~128buff[i] \0;s buff;i 0;}ch in.get();}if (i ! 0) {buff[i] \0;s buff;}return in;}void test_string1() {string s1(hello world);cout s1.c_str() endl;string s2;cout s2.c_str() endl;//三种遍历方式//1.[]运算符重载for (size_t i 0; i s1.size(); i)cout s1[i] ;cout endl;//2.迭代器string::iterator it s1.begin();while (it ! s1.end()) {cout *it ;it;}cout endl;//3.范围for 范围for的底层就是迭代器for (auto ch : s1)//ch的改变不影响s1如果要ch改变能影响到s1那么ch就要引用cout ch ;cout endl;}void test_string2() {string s1(hello world);cout s1.c_str() endl;//hello worlds1.push_back( );s1.append(hello bit);cout s1.c_str() endl;//hello world hello bits1 #;s1 *******;cout s1.c_str() endl;//hello world hello bit#*******}void test_string3() {string s1(hello world);cout s1.c_str() endl;//hello worlds1.insert(5, %);cout s1.c_str() endl;//hello% worlds1.insert(0, %);cout s1.c_str() endl;//%hello% world}void test_string4() {string s1(hello world);string s2(hello world);cout (s1 s2) endl;//1s1[0] z;cout (s1 s2) endl;//1cout s1 endl;cin s1;cout s1 endl;}void test_string5() {string s1(hello world);s1.insert(5, abc);cout s1 endl;//helloabc worlds1.insert(0, xxx);cout s1 endl;//xxxhelloabc worlds1.erase(0, 3);cout s1 endl;//helloabc worlds1.erase(5, 100);cout s1 endl;//hellos1.erase(2);cout s1 endl;//he}void test_string6() {string s1(hello world);s1.resize(5);cout s1 endl;s1.resize(15, x);cout s1 endl;}void test_string7() {string s1(test.cpp);string s5(https://legacy.cplusplus.com/reference/string/string/rfind/);string sub1, sub2, sub3;size_t i1 s5.find(:);if (i1 ! string::npos)//npos 是一个静态成员常量值无符号整形-1即最大值sub1 s5.substr(0, i1);//协议elsecout i1没有找到 endl;size_t i2 s5.find(/, i1 3);if (i2 ! string::npos)sub2 s5.substr(i1 3, i2 - (i1 3));//域名 左闭右开右-左就是个数elsecout i2没有找到 endl;sub3 s5.substr(i2 1);//资源名cout sub1 endl;//httpscout sub2 endl;//legacy.cplusplus.comcout sub3 endl;//reference/string/string/rfind/}void test_string8() {string s1(hello world);string s2 s1;//这里是拷贝构造cout s1 endl;cout s2 endl;string s3(xxxxxxxxx);s2 s3;cout s2 endl;cout s3 endl;}void test_string9() {string s1;cin s1;cout s1 endl;cout size s1.size() endl;cout capacity s1.capacity() endl;} } string.cpp #include iostream #include vector #include list #include stringusing namespace std;#include string.h int main() {//bit::test_string1();//bit::test_string2();//bit::test_string3();//bit::test_string4();//bit::test_string5();//bit::test_string6();//bit::test_string7();//bit::test_string8();//bit::test_string9();string s1;cout s1.capacity() endl;//15string s2(hello world);cout s2.capacity() endl;//15cout sizeof(s1) endl;//28cout sizeof(s2) endl;//28//标准库设计//小于16字符串存到buff数组里面//大于等于16存在_str指向的空间//这种设计避免碎片空间//_buff[16]//_str//_size//_capacityreturn 0; } 3.2 浅拷贝 浅拷贝也称位拷贝编译器只是将对象中的值拷贝过来。如果对象中管理资源最后就会导致多个对象共享同一份资源当一个对象销毁时就会将该资源释放掉而此时另一些对象不知道该资源已经被释放以为还有效所以当继续对资源进项操作时就会发生发生了访问违规。 3.3 深拷贝 如果一个类中涉及到资源的管理其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。 int main() {//浅拷贝问题//1.析构两次//解决方案使用引用计数记录有几个对象指向这块空间//拷贝的时候要引用计数//析构的时候要–引用计数引用计数减到0时说明是最后一个对象再释放。//2.一个修改数据会影响另一个//引用计数写时拷贝延迟拷贝。写的时候才拷贝且需要修改引用计数return 0; }3.4 写时拷贝  写时拷贝就是一种拖延症是在浅拷贝的基础之上增加了引用计数的方式来实现的。 引用计数用来记录资源使用者的个数。在构造时将资源的计数给成1每增加一个对象使用该资源就给计数增加1当某个对象被销毁时先给该计数减1然后再检查是否需要释放资源如果计数为1说明该对象时资源的最后一个使用者将该资源释放否则就不能释放因为还有其他对象在使用该资源。