5月26日每日学习文章目录string的实现0.类的private部分0.1c_strc_str是啥能干嘛‼️注意点实现0.2npos实现1.头文件定义函数导致的多文件重复定义问题是啥解决方法静态成员函数唯一区别没有this指针MyString.hMyString.cpptest.cpp2.构造函数2.1带参构造函数的坑点为什么不建议在初始化列表中定义正确的方式2.2默认构造函数正确写法2.3合一 ——上面两个构造函数3.析构函数4.size5.operator[ ]6.实现范围for实现迭代器实现const迭代器5月27日每日学习7.尾插push_back、append和7.0 .h文件中7.1 reserve7.2 push_back❌push_back 的错误写法正确写法7.3单个字符7.4 append7.5 字符串8.inserta.插入一个字符‼️特殊情况b.插入字符串9.erase10.find11.拷贝构造函数12.substr13.赋值运算符重载14. 比较string的实现0.类的private部分namespacelcj{classstring{public:private:size_t _capacity;char*_str;size_t _size;};}0.1c_strc_str是啥能干嘛是string中的一个函数能把一个string转化为c风格的字符串charconst char* cstr s.c_str();可以直接用于 C 字符串函数——能使用strlenstring不行‼️注意点不是new出来的不用delete指针指向string内部管理的内存不应delete修改原string后之前获取的指针可能失效——想再次使用应该重新获取实现在.h文件中在namespace lcj的class string中constchar*c_str()const{return_str;}0.2npos原始含义“until the end of the string” --到字符串的末尾原始定义static const size_t npos -1;实现声明在.h文件的lcj域中的string类中的private下static const size_t npos;定义在.cpp文件的lcj域中const size_t string::npos -1;奇怪的是cpp后面支持了static const类型的整形可以在.h中直接定义1.头文件定义函数导致的多文件重复定义问题是啥函数定义只能出现一次。那么如果我把用来测试的函数定义 放在 .h文件中—— 两个.c文件都有这个头文件非法原因编译链接的过程 预处理把#include的头文件内容原封不动地复制到每个.cpp文件里。编译分别把a.cpp和b.cpp编译成目标文件a.obj/a.o和b.obj/b.o。此时a.obj里记录了“我定义了sayHello”b.obj里也记录了“我定义了sayHello”。链接把多个目标文件合并成一个可执行文件。链接器看到a.obj和b.obj都定义了一个叫sayHello的函数按照 C 的单一定义规则ODR, One Definition Rule这是非法的于是报错解决方法使用inline内联链接器允许程序中有多个相同的inline函数定义只要它们完全相同。链接器会随便选一个或者全部丢弃不会报错。静态static成员函数静态成员函数唯一区别没有this指针限制不能访问普通成员变量和成员函数因为没有this指针只能访问静态static变量和函数使用上普通成员函数唯一使用方法a1.GetCount(); 有this指针静态成员函数成员变量也可以直接用A::GetCount();✅ 推荐更方便成员变量也可以先创建类对象再用A a1;a1.GetCount();没有this指针但能找到类域⬇️静态成员变量所有同类对对象共享——用来计数最标准的方法声明和定义分离MyString.h#pragmaonce#includeiostreamusingnamespacestd;namespacelcj{classstring{public:……private:……};voidtest_string1();}MyString.cpp#includeMyString.hnamespacelcj{voidtest_string1(){string s1;strings2(hello world);}}test.cpp#includeMyString.hintmain(){lcj::test_string1();return0;}2.构造函数2.1带参构造函数的坑点❌看似正确string(constchar*s):_size(strlen(s)),_str(newchar[_size1]){}private:size_t _capacity;char*_str;size_t _size;‼️错误点初始化列表的 初始化顺序 是 按声明顺序不是初始化列表顺序‼️——_str(new char[_size 1])中的_size是随机数为什么不建议在初始化列表中定义上面说的_str(new char[_size 1])中的_size可能因为声明顺序的原因是随机数即使改变了声明顺序但如果有人改了代码就完了正确的方式——建议在函数体内直接定义string(constchar*s){_sizestrlen(s);_capacity_size;_strnewchar[_capacity1];strcpy(_str,s);}private:size_t _capacity;char*_str;size_t _size;2.2默认构造函数❌看似正确但是没法打印——打印时候对空指针解引用string():_str(nullptr),_size(0),_capacity(0){}正确写法string():_str(newchar[1]{\0})//得写成这样才能像数组一样操作,_size(0),_capacity(0){}2.3合一 ——上面两个构造函数string(constchar*s)//默认有一个‘\0’{_sizestrlen(s);_capacity_size;_strnewchar[_capacity1];strcpy(_str,s);//strcpy也会加上‘\0’}3.析构函数~string(){delete[]_str;_strnullptr;_size_capacity0;}4.sizesize_tsize(){return_size;}5.operator[ ]➡️const修饰类的成员函数的知识在这里⬅️➡️运算符重载的知识在这里⬅️作用能直接通过下标访问string中的字符返回值对应字符的引用两个函数char operator[] (size_t pos);const char operator[] (size_t pos) const;const char operator[](size_t pos) const为啥末尾有const❓允许 const 对象调用如果一个string对象被声明为const那么它只能调用那些被const修饰的成员函数。没有末尾const的operator[]版本无法被const对象使用。charoperator[](size_t pos){assert(pos_size);return_str[pos];}constcharoperator[](size_t pos)const{assert(pos_size);return_str[pos];}6.实现范围for范围for的底层通过迭代器begin和end遍历数组而迭代器iterator是一种类型typedef出的结果那我自己typedef一个指针作为迭代器不就行了实现迭代器迭代器不一定是指针但在此我们可以通过指针来替代typedefchar*iterator;iteratorbegin(){return_str;}iteratorend(){return_str_size;}这样就可以使用范围forfor(autoe:s2){coute ;}实现const迭代器和迭代器是函数重载参数不同一个是普通this指针一个是const this指针typedefconstchar*const_iterator;const_iteratorbegin()const{return_str;}const_iteratorend()const{return_str_size;}5月27日每日学习7.尾插push_back、append和➡️运算符重载的知识⬅️7.0 .h文件中voidreserve(size_t n);///开空间voidpush_back(charch);voidappend(constchar*str);stringoperator(charch);stringoperator(constchar*str);7.1 reserve‼️注意开的空间 比容量大1voidstring::reserve(size_t n)///开空间{if(n_capacity){///开新空间移动数据删旧空间改变_str和_capacitychar*tmpnewchar[n1];strcpy(tmp,_str);delete[]_str;_strtmp;_capacityn;}}7.2 push_back❌push_back 的错误写法‼️没补充最后的\0voidstring::push_back(charch){///检查容量if(_size_capacity){reserve(_capacity0?4:2*_capacity);}_str[_size]ch;_size;}正确写法voidstring::push_back(charch){///检查容量if(_size_capacity){reserve(_capacity0?4:2*_capacity);}_str[_size]ch;_size;_str[_size]\0;}7.3单个字符stringstring::operator(charch){push_back(ch);return*this;}7.4 append判断增加后的容量是否超过求长度扩容通过C语言中的strcat函数进行后插改变_size为什么不修改_str和_capacity❓——因为reserve函数中已经修改过了voidstring::append(constchar*str){size_t lenstrlen(str);if(len_size_capacity){reserve(len_size_capacity?_capacitylen:2*_capacity);}strcat(_str,str);_sizelen;}7.5 字符串stringstring::operator(constchar*str){append(str);return*this;}8.inserta.插入一个字符断言像前面的push_back一样判断要不要扩容不断向后挪动数据‼️特殊情况头插如果写endpos此时pos是0 挪动数据就无法成功不论end是int还是size_t因为pos是size_t类型的标准库中就是这样的二者比较的时候会类型转换范围大转范围小end会变成size_t类型导致永远都0解决方式方法一end标成int类型且将size_t 类型的pos进行强制类型转换为int方法二使用指针不用下标方法三end和pos都是size_t类型前面说的都是从原本字符串的最后一个位置开始向后挪方法三是从最后一个位置的后一个位置赋值为前一个位置上的字符这样本来10个字符的string下标为0~9想要头插先让s[10]s[9]然后是s[9]s[8]……最后是s[1]s[0]。就不会遇到需要endpos的情况了——end从_size1开始\0也会被挪动直到pos1所以这时候条件是end posMyString.hvoidinsert(size_t pos,charch);MyString.cppvoidstring::insert(size_t pos,charch){assert(pos_size);///检查容量if(_size_capacity){reserve(_capacity0?4:2*_capacity);}1.end从 _size 开始\0不 动//size_t end _size;//while (end pos)//{// _str[end] _str[end - 1];// --end;//}//_str[end] ch;//_size;//_str[_size] \0;1.end从 _size1开始\0会被挪动 size_t end_size1;while(endpos){_str[end]_str[end-1];--end;}_str[end]ch;_size;}b.插入字符串MyString.hvoidinsert(size_t pos,constchar*str);MyString.cppvoidstring::insert(size_t pos,constchar*str){assert(pos_size);size_t lenstrlen(str);if(len_size_capacity){size_t newcapacitylen_size_capacity?_capacitylen:2*_capacity;reserve(newcapacity);}///如果不按照下面这种逐个后移和上面插入单个字逻辑相同也是可以的memmove(_strposlen,_strpos,_size1-pos);memmove(_strpos,str,len);_sizelen;}9.erasea.后面的全删b.部分删除MyString.hvoiderase(size_t pos,size_t lennpos);MyString.cppvoidstring::erase(size_t pos,size_t len){assert(pos_size);if(len_size-pos){_str[pos]\0;_sizepos;}else{memmove(_strpos,_strposlen,_size-pos-len1);_size_size-len;}}10.find‼️注意缺省参数在声明.h和定义.c中不能同时出现。建议在声明.h中写MyString.hsize_tfind(charch,size_t pos0);size_tfind(constchar*str,size_t pos0);MyString.cppsize_t string::find(charch,size_t pos){for(inti0;i_size;i){if(ch_str[i])returni;}returnnpos;}size_t string::find(constchar*str,size_t pos){char*fstrstr(_str,str);if(fnullptr)returnnpos;elsereturnf-_str;/////////注意不要写成“str”了}11.拷贝构造函数如果没有这个函数来实现深拷贝那函数中的string作为返回值就会出错下面的substr函数也实现不了string(conststrings){_strnewchar[s._capacity1];strcpy(_str,s._str);_capacitys._capacity;_sizes._size;}12.substr原型string substr (size_t pos 0, size_t len npos) const;两种情况如果len大于pos后面的字符个数那就直接把后面的字符串全返回如果len小于pos后面的字符个数那就部分返回MyString.hstringsubstr(size_t pos0,size_t lennpos);MyString.cppstring string::substr(size_t pos,size_t len){string ret;if(len_size-pos){for(intipos;i_size;i)ret_str[i];}else{for(intipos;iposlen;i)ret_str[i];}returnret;}13.赋值运算符重载‼️注意点因为在赋值之前要delete原内存所以这时候要防止自己给自己赋值stringoperator(conststrings){if(s!this){delete[]_str;_strnewchar[_capacity1];strcpy(_str,s._str);_capacitys._capacity;_sizes._size;}return*this;}14. 比较MyString.hbooloperator(conststrings);booloperator(conststrings);booloperator(conststrings);booloperator(conststrings);booloperator(conststrings);booloperator!(conststrings);MyString.cppboolstring::operator(conststrings){returnstrcmp(_str,s._str)0;}boolstring::operator(conststrings){returnstrcmp(_str,s._str)0;}boolstring::operator(conststrings){return(!((*this)s))(!((*this)s));}boolstring::operator(conststrings){return!operator(s);}boolstring::operator(conststrings){return!operator(s);}boolstring::operator!(conststrings){return!operator(s);}
爆肝整理!C++ string手写实现从0到1:构造/析构/迭代器/增删查改/比较重载,面试必考干货!
发布时间:2026/5/28 17:55:25
5月26日每日学习文章目录string的实现0.类的private部分0.1c_strc_str是啥能干嘛‼️注意点实现0.2npos实现1.头文件定义函数导致的多文件重复定义问题是啥解决方法静态成员函数唯一区别没有this指针MyString.hMyString.cpptest.cpp2.构造函数2.1带参构造函数的坑点为什么不建议在初始化列表中定义正确的方式2.2默认构造函数正确写法2.3合一 ——上面两个构造函数3.析构函数4.size5.operator[ ]6.实现范围for实现迭代器实现const迭代器5月27日每日学习7.尾插push_back、append和7.0 .h文件中7.1 reserve7.2 push_back❌push_back 的错误写法正确写法7.3单个字符7.4 append7.5 字符串8.inserta.插入一个字符‼️特殊情况b.插入字符串9.erase10.find11.拷贝构造函数12.substr13.赋值运算符重载14. 比较string的实现0.类的private部分namespacelcj{classstring{public:private:size_t _capacity;char*_str;size_t _size;};}0.1c_strc_str是啥能干嘛是string中的一个函数能把一个string转化为c风格的字符串charconst char* cstr s.c_str();可以直接用于 C 字符串函数——能使用strlenstring不行‼️注意点不是new出来的不用delete指针指向string内部管理的内存不应delete修改原string后之前获取的指针可能失效——想再次使用应该重新获取实现在.h文件中在namespace lcj的class string中constchar*c_str()const{return_str;}0.2npos原始含义“until the end of the string” --到字符串的末尾原始定义static const size_t npos -1;实现声明在.h文件的lcj域中的string类中的private下static const size_t npos;定义在.cpp文件的lcj域中const size_t string::npos -1;奇怪的是cpp后面支持了static const类型的整形可以在.h中直接定义1.头文件定义函数导致的多文件重复定义问题是啥函数定义只能出现一次。那么如果我把用来测试的函数定义 放在 .h文件中—— 两个.c文件都有这个头文件非法原因编译链接的过程 预处理把#include的头文件内容原封不动地复制到每个.cpp文件里。编译分别把a.cpp和b.cpp编译成目标文件a.obj/a.o和b.obj/b.o。此时a.obj里记录了“我定义了sayHello”b.obj里也记录了“我定义了sayHello”。链接把多个目标文件合并成一个可执行文件。链接器看到a.obj和b.obj都定义了一个叫sayHello的函数按照 C 的单一定义规则ODR, One Definition Rule这是非法的于是报错解决方法使用inline内联链接器允许程序中有多个相同的inline函数定义只要它们完全相同。链接器会随便选一个或者全部丢弃不会报错。静态static成员函数静态成员函数唯一区别没有this指针限制不能访问普通成员变量和成员函数因为没有this指针只能访问静态static变量和函数使用上普通成员函数唯一使用方法a1.GetCount(); 有this指针静态成员函数成员变量也可以直接用A::GetCount();✅ 推荐更方便成员变量也可以先创建类对象再用A a1;a1.GetCount();没有this指针但能找到类域⬇️静态成员变量所有同类对对象共享——用来计数最标准的方法声明和定义分离MyString.h#pragmaonce#includeiostreamusingnamespacestd;namespacelcj{classstring{public:……private:……};voidtest_string1();}MyString.cpp#includeMyString.hnamespacelcj{voidtest_string1(){string s1;strings2(hello world);}}test.cpp#includeMyString.hintmain(){lcj::test_string1();return0;}2.构造函数2.1带参构造函数的坑点❌看似正确string(constchar*s):_size(strlen(s)),_str(newchar[_size1]){}private:size_t _capacity;char*_str;size_t _size;‼️错误点初始化列表的 初始化顺序 是 按声明顺序不是初始化列表顺序‼️——_str(new char[_size 1])中的_size是随机数为什么不建议在初始化列表中定义上面说的_str(new char[_size 1])中的_size可能因为声明顺序的原因是随机数即使改变了声明顺序但如果有人改了代码就完了正确的方式——建议在函数体内直接定义string(constchar*s){_sizestrlen(s);_capacity_size;_strnewchar[_capacity1];strcpy(_str,s);}private:size_t _capacity;char*_str;size_t _size;2.2默认构造函数❌看似正确但是没法打印——打印时候对空指针解引用string():_str(nullptr),_size(0),_capacity(0){}正确写法string():_str(newchar[1]{\0})//得写成这样才能像数组一样操作,_size(0),_capacity(0){}2.3合一 ——上面两个构造函数string(constchar*s)//默认有一个‘\0’{_sizestrlen(s);_capacity_size;_strnewchar[_capacity1];strcpy(_str,s);//strcpy也会加上‘\0’}3.析构函数~string(){delete[]_str;_strnullptr;_size_capacity0;}4.sizesize_tsize(){return_size;}5.operator[ ]➡️const修饰类的成员函数的知识在这里⬅️➡️运算符重载的知识在这里⬅️作用能直接通过下标访问string中的字符返回值对应字符的引用两个函数char operator[] (size_t pos);const char operator[] (size_t pos) const;const char operator[](size_t pos) const为啥末尾有const❓允许 const 对象调用如果一个string对象被声明为const那么它只能调用那些被const修饰的成员函数。没有末尾const的operator[]版本无法被const对象使用。charoperator[](size_t pos){assert(pos_size);return_str[pos];}constcharoperator[](size_t pos)const{assert(pos_size);return_str[pos];}6.实现范围for范围for的底层通过迭代器begin和end遍历数组而迭代器iterator是一种类型typedef出的结果那我自己typedef一个指针作为迭代器不就行了实现迭代器迭代器不一定是指针但在此我们可以通过指针来替代typedefchar*iterator;iteratorbegin(){return_str;}iteratorend(){return_str_size;}这样就可以使用范围forfor(autoe:s2){coute ;}实现const迭代器和迭代器是函数重载参数不同一个是普通this指针一个是const this指针typedefconstchar*const_iterator;const_iteratorbegin()const{return_str;}const_iteratorend()const{return_str_size;}5月27日每日学习7.尾插push_back、append和➡️运算符重载的知识⬅️7.0 .h文件中voidreserve(size_t n);///开空间voidpush_back(charch);voidappend(constchar*str);stringoperator(charch);stringoperator(constchar*str);7.1 reserve‼️注意开的空间 比容量大1voidstring::reserve(size_t n)///开空间{if(n_capacity){///开新空间移动数据删旧空间改变_str和_capacitychar*tmpnewchar[n1];strcpy(tmp,_str);delete[]_str;_strtmp;_capacityn;}}7.2 push_back❌push_back 的错误写法‼️没补充最后的\0voidstring::push_back(charch){///检查容量if(_size_capacity){reserve(_capacity0?4:2*_capacity);}_str[_size]ch;_size;}正确写法voidstring::push_back(charch){///检查容量if(_size_capacity){reserve(_capacity0?4:2*_capacity);}_str[_size]ch;_size;_str[_size]\0;}7.3单个字符stringstring::operator(charch){push_back(ch);return*this;}7.4 append判断增加后的容量是否超过求长度扩容通过C语言中的strcat函数进行后插改变_size为什么不修改_str和_capacity❓——因为reserve函数中已经修改过了voidstring::append(constchar*str){size_t lenstrlen(str);if(len_size_capacity){reserve(len_size_capacity?_capacitylen:2*_capacity);}strcat(_str,str);_sizelen;}7.5 字符串stringstring::operator(constchar*str){append(str);return*this;}8.inserta.插入一个字符断言像前面的push_back一样判断要不要扩容不断向后挪动数据‼️特殊情况头插如果写endpos此时pos是0 挪动数据就无法成功不论end是int还是size_t因为pos是size_t类型的标准库中就是这样的二者比较的时候会类型转换范围大转范围小end会变成size_t类型导致永远都0解决方式方法一end标成int类型且将size_t 类型的pos进行强制类型转换为int方法二使用指针不用下标方法三end和pos都是size_t类型前面说的都是从原本字符串的最后一个位置开始向后挪方法三是从最后一个位置的后一个位置赋值为前一个位置上的字符这样本来10个字符的string下标为0~9想要头插先让s[10]s[9]然后是s[9]s[8]……最后是s[1]s[0]。就不会遇到需要endpos的情况了——end从_size1开始\0也会被挪动直到pos1所以这时候条件是end posMyString.hvoidinsert(size_t pos,charch);MyString.cppvoidstring::insert(size_t pos,charch){assert(pos_size);///检查容量if(_size_capacity){reserve(_capacity0?4:2*_capacity);}1.end从 _size 开始\0不 动//size_t end _size;//while (end pos)//{// _str[end] _str[end - 1];// --end;//}//_str[end] ch;//_size;//_str[_size] \0;1.end从 _size1开始\0会被挪动 size_t end_size1;while(endpos){_str[end]_str[end-1];--end;}_str[end]ch;_size;}b.插入字符串MyString.hvoidinsert(size_t pos,constchar*str);MyString.cppvoidstring::insert(size_t pos,constchar*str){assert(pos_size);size_t lenstrlen(str);if(len_size_capacity){size_t newcapacitylen_size_capacity?_capacitylen:2*_capacity;reserve(newcapacity);}///如果不按照下面这种逐个后移和上面插入单个字逻辑相同也是可以的memmove(_strposlen,_strpos,_size1-pos);memmove(_strpos,str,len);_sizelen;}9.erasea.后面的全删b.部分删除MyString.hvoiderase(size_t pos,size_t lennpos);MyString.cppvoidstring::erase(size_t pos,size_t len){assert(pos_size);if(len_size-pos){_str[pos]\0;_sizepos;}else{memmove(_strpos,_strposlen,_size-pos-len1);_size_size-len;}}10.find‼️注意缺省参数在声明.h和定义.c中不能同时出现。建议在声明.h中写MyString.hsize_tfind(charch,size_t pos0);size_tfind(constchar*str,size_t pos0);MyString.cppsize_t string::find(charch,size_t pos){for(inti0;i_size;i){if(ch_str[i])returni;}returnnpos;}size_t string::find(constchar*str,size_t pos){char*fstrstr(_str,str);if(fnullptr)returnnpos;elsereturnf-_str;/////////注意不要写成“str”了}11.拷贝构造函数如果没有这个函数来实现深拷贝那函数中的string作为返回值就会出错下面的substr函数也实现不了string(conststrings){_strnewchar[s._capacity1];strcpy(_str,s._str);_capacitys._capacity;_sizes._size;}12.substr原型string substr (size_t pos 0, size_t len npos) const;两种情况如果len大于pos后面的字符个数那就直接把后面的字符串全返回如果len小于pos后面的字符个数那就部分返回MyString.hstringsubstr(size_t pos0,size_t lennpos);MyString.cppstring string::substr(size_t pos,size_t len){string ret;if(len_size-pos){for(intipos;i_size;i)ret_str[i];}else{for(intipos;iposlen;i)ret_str[i];}returnret;}13.赋值运算符重载‼️注意点因为在赋值之前要delete原内存所以这时候要防止自己给自己赋值stringoperator(conststrings){if(s!this){delete[]_str;_strnewchar[_capacity1];strcpy(_str,s._str);_capacitys._capacity;_sizes._size;}return*this;}14. 比较MyString.hbooloperator(conststrings);booloperator(conststrings);booloperator(conststrings);booloperator(conststrings);booloperator(conststrings);booloperator!(conststrings);MyString.cppboolstring::operator(conststrings){returnstrcmp(_str,s._str)0;}boolstring::operator(conststrings){returnstrcmp(_str,s._str)0;}boolstring::operator(conststrings){return(!((*this)s))(!((*this)s));}boolstring::operator(conststrings){return!operator(s);}boolstring::operator(conststrings){return!operator(s);}boolstring::operator!(conststrings){return!operator(s);}