酒店网络推广方案,优化网络培训,室内设计基础知识点,电子商务学网页制作吗引言#xff1a;
北京时间#xff1a;2023/2/19/8:48#xff0c;昨天更新了有关进程状态的博客#xff0c;然后在休息的时候#xff0c;打开了腾讯视屏#xff0c;然后看到了了一个电视剧#xff0c;导致上头#xff0c;从晚上6点看到了10点#xff0c;把我宝贵的博客…引言
北京时间2023/2/19/8:48昨天更新了有关进程状态的博客然后在休息的时候打开了腾讯视屏然后看到了了一个电视剧导致上头从晚上6点看到了10点把我宝贵的博客时间给搞没了伤心现在就让我们开始将功补过把昨天就应该开始写的博客给补一补加油不怂就是干今天博客的内容非常简单就是使用我们以前学过的知识无论是类和对象、内存管理还是string类中的函数使用运用它们写一个较为完整的string类出来也就是自己实现string类写库里面的东西想想还是挺激动的就让我们带着这份激动go go go
string类的模拟实现
string类基本框架
首先开始学习之前明白string类的底层就是一个字符数组而已本质上我们在实现string类的过程中也就是在补全之前学习中的一些不足和一些重难点当然最关键的可以复习之前的各种知识点并且在复习的过程中更好的理解这些知识点的使用把一小块一小块的知识给合并成一大块的知识所以模拟实现string类是非常重要的。
步入正题实现string类基本框架如下代码从代码入手再做细节方面处理 以上就是一个string类的基本框架大部分函数功能还没有实现只是实现了基本的构造函数和析构函数以及一些细节方面的处理。
string类框架之后的地基
搞定了上述有关string类中的构造函数和析构函数当然最重要的是类和对象的使用我们可以说是有了一个string类的基本框架也可以说是我们拥有了一个基本的类框架所以现在就让我们走进string类复习并使用更多的知识来实现这个STL中的经典字符类。
拷贝构造
在搞定了构造函数和析构函数之后实现一个类最重要的莫过于是拷贝构造函数了虽然拷贝构造函数和构造函数、析构函数一样都是一个默认成员函数但是懂的都懂默认成员函数并不是万能的准确的来说是编译器并不是万能的这些默认成员函数大部分都只是对内置类型起作用而我们自己实现的自定义类型就像是后娘养的人家是爱答不理并不能很好的把自定义类型进行相应的初始化所以对这些后娘养的自定义类型则需要我们自己来实现初始化当然经典的初始化场景有在初始化列表初始化、给缺省值但本质还是在初始化列表初始化、调用拷贝构造函数初始化等所以调用拷贝构造函数初始化是自定义类型初始化的一个好地方我们现在就来复习巩固一下拷贝构造函数。当然重点就是想要讲深浅拷贝问题 如下图 当然上述的前提是通过成员变量中有一个char* _str的指针涉及指针就涉及指针指向的空间就涉及深拷贝问题涉及深拷贝就涉及析构问题这些看似无关却紧密相连这就是自我实现string类的好处搞清各个知识点之间的关系和熟练掌握运用所以以后写拷贝构造函数第一点就是考虑深浅拷贝问题。
const成员函数使用场景和运算符重载
从上图中我们可以发现我们许多函数在实现的时候都可以去调用那些已经自己实现好了的函数或者库函数来实现一些新的功能并且可以发现只要使用const来修饰成员函数只要我们对该函数不做改变数据的操作这种方法是很好的可以有效的避免权利放大问题解决不必要的麻烦可以让我们使用const对象调用函数的时候变得更加的放心程序变得更加的稳定。 并且注意 我们在进行运算符重载时进行字符串的比较使用的是strcmp函数表明此时我们比较的是该字符串的ASCII码值而不是该字符串的大小和容量。
string类中迭代器的实现 从上图可以看出范围for的本质就是迭代器从迭代器可以实现语法糖来分析足以看出迭代器身为STL六大天王之一不是浪得虚名的。并且此时注意 const修饰的迭代器该迭代器对象是可以被改变的只是该对象中指向的内容不可以被修改而已
扩容函数和字符、字符串插入函数 总了来说字符和字符串插入删除函数大致上都是差不多的细节方面处理到位就行跟数据结构中的顺序表本质上是一样的这里就不过多介绍。并且此时我们把字符插入这些函数实现之后string类中的函数也就完成了地基部分此时我们完成了地基就可以开始盖房子了由于时间关系我们把盖房子部分留到下一篇博客。
具体代码如下 包括测试部分注释很全
#define _CRT_SECURE_NO_WARNINGS
#includeiostream
#includestring
#includeassert.h
using namespace std;namespace wwx
{class String{public:typedef char* iterator;//普通类型迭代器typedef const char* const_iterator;//const类型迭代器(注意此时自己是可以修改的只是解引用后的值是不可以修改的)iterator begin(){return _str;//此时因为String本质上就是一个字符数组所以_str就是首元素地址就是第一个字符}iterator end(){return _str _size;//这个就是最后一个字符前提是要知道第一个字符的位置}const_iterator begin()const{return _str;}const_iterator end()const{return _str _size;}String(const char* str )//或者写成\0反正这个位置只要可以让strlen算出一个0来就行了全缺省构造函数: _size(strlen(str)){_capacity _size 0 ? 3 : _size;//_capacity为0第一种解决方法_str new char[_capacity 1];strcpy(_str, str);}String(const String s)//注意拷贝构造也是有初始化列表的并且要回想起以前有关this指针的知识此时*this就是s3str就是s2:_size(s._size),_capacity(s._capacity){//深拷贝因为使用了指针或者说因为有自己实现析构函数_str new char[s._capacity 1];strcpy(_str, s._str);}//并且此时要记住此时的拷贝构造除了利用*this指针以外还有一个是使用赋值运算符String operator(const String s)//区分赋值和拷贝构造赋值是两个已经存在的对象而拷贝构造是一个已经初始化的对象去初始化另一个要创建的对象{if (this ! s)//防止自己给自己赋值{//_size s._size;//_capacity s._capacity;//delete[]_str;//这种赋值方法可以很好的避免被赋值空间太大或太小的问题只是伴随着开空间的消耗而已//_str new char[s._capacity 1];//strcpy(_str, s._str);//为了防止空间开辟失败把原来的空间中的数据给破坏下面的写法就更好char* tmp new char[s._capacity 1];//解决原理先开空间再销毁再给给strcpy(tmp, s._str);delete[]_str;_str tmp;//此时因为指针指向的空间本质是内置类型所以会自己去调用拷贝构造函数不需要调用我们自己实现的拷贝构造函数_size s._size;_capacity s._capacity;}return *this;}~String(){delete[]_str;_str nullptr;_size _capacity 0;}const char* c_str(){return _str;}char operator[](size_t pos)//普通类型使用下面的是给特殊的const类型函数使用{assert(pos _size);return _str[pos];}const char operator[](size_t pos)const//后面位置上给了const前面就一定也要给一个const因为此时加了const导致返回值的类型也变成了const类型{assert(pos _size);return _str[pos];}size_t size()const{return _size;}//运算符重载// 所以得出结论只要是函数内部不进行数据修改的我们就把const给加上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;//return *this s || s *this;//此时就是简单的把赋值顺序调换一下该代码就是有问题的因为此时的s是const类型的妥妥的权利放大} 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 resize(size_t n, char ch \0){int len strlen(_str);if (_capacity n){reserve(2 * n);}if (n _size){_size n;_str[_size] \0;return;}_size n;for (int i len; i _size; i){_str[i] ch;}_str[_size] \0;}void reserve(size_t n)//此时的这个n参数表示的就是n需要扩n个空间{char* tmp new char[n 1];//此时为了像上述一样防止开辟失败所以先开辟再赋值注意capacity和字符个数的区别capacity少1strcpy(tmp, _str);//此时就是注意只要是在类里面的函数都是自带一个this指针的类对象delete[]_str;_str tmp;_capacity n;//开空间跟_size是没有关系的只有插入数据的时候才跟_size有关系}void push_back(char ch)//注意有一个this指针此时就是为了在这个this指针后面插入字符{if (_size _capacity)//这种判断的是需要多少扩多少所以可以二倍二倍的扩{//注意此时要配套使用不可以使用realloc扩容reserve(_capacity 0 ? 4 : _capacity * 2);}_str[_size] ch;//string的本质就是一个字符数组_size;_str[_size] \0;//此时就是因为插入字符之后把原来的\0给搞没了所以要重新给一个\0不然就会导致无法计算strlen之类的问题}void append(const char* str)//注意此时这个函数是用来插入字符串的不是上述用来插入字符的{size_t len strlen(str);if (_size len _capacity)//此时这个位置表示的就是插入len个字符插入len个字符刚好等于capacity可以但是不可以超过{//此时这种直接插入多个就不可以2倍2倍的扩需要一次性扩大一点reserve(_size len);}strcpy(_str _size, str);//该拷贝是因为可以直接把原来字符串中的\0给覆盖掉//strcat(_str, str);//但是最好不要使用strcat追加目的是因为防止原字符串过长\0不好找因为strcat只有找到了\0才会进行追加_size len;//字符串是不需要处理\0的因为strcpy会拷贝\0}String operator(char ch){push_back(ch);return *this;}String operator(const char* str){append(str);return *this;}//某个位置插入、某个位置删除void insert(size_t pos, char ch)//某个位置插入字符{assert(pos _size);//防止传参的时候越界if (_size 1 _capacity)//还是因为等于的时候是刚刚好满了所以不怕只有大于的时候才需要扩{reserve(2 * _capacity);}size_t end _size;while (end pos){_str[end 1] _str[end];--end;}_str[pos] ch;_size;}void insert(size_t pos, const char* str)//某个位置插入字符串{assert(pos _size);size_t len strlen(str);if (len 0){return;}if (_size len _capacity){reserve(_capacity len);}//扩容完之后就是插入数据size_t end _size len;for (int i end; i pos len; --i){_str[i] _str[i - len - 1];}char c _str[pos len];strcpy(_str pos, str);_str[pos _size] c;_size len;_str[_size] \0;}void erase(size_t pos, size_t len npos){assert(pos0 pos _size);size_t end _size;while (end pos){_str[end - 1] _str[end];--end;}--_size;}private:char* _str;size_t _size;size_t _capacity;static size_t npos;//npos此时给一个静态成员变量供给大家使用//有一个特例可以不需要在全局定义static但是只针对于整形,就是加一个const//static const size_t npos -1;//static const size_t N 10;//估计是为了可以这样使用int _arr[N];};size_t String::npos -1;void Print(const String s){for (size_t i 0; i s.size(); i){cout s[i] ;//此时因为这个函数就是一个const修饰的函数所以无论在函数内存调用运算符重载还是别的函数此时这些函数都需要有const属性所以导致我们需要实现两个[]运算符重载一个给普通类型使用一个给const类型使用}cout endl;for (auto ch : s)//证明const属性的迭代器所以需要把迭代器也给弄成两份一份普通类型一份const类型{cout ch ;}cout endl;}void test_string1(){String s1;String s2(hello world);cout s1.c_str() endl;cout s2.c_str() endl;for (int i 0; i 10; i){s2[i];}String s3(s2);//拷贝构造经典的指针指向同一块空间问题涉及深拷贝cout s3.c_str() endl;cout s2.c_str() endl;s3 s2;//赋值要注意有自己给自己赋值的时候 s2 s2;cout s3.c_str() endl;cout s2.c_str() endl;}void test_string2()//验证const修饰的函数需要使用具有const属性的函数{wwx::String s1(hello world);for (size_t i 0; i s1.size(); i)//注意此时访问的不是string类中的成员变量访问的是计算size大小的公有函数{cout s1[i] ;}cout endl;Print(s1);}void test_string3()//验证正向迭代器反向迭代器先不学{String s1(gdkkn vnqkc);String::iterator it s1.begin();//普通it类型String::const_iterator it2 s1.begin();//const类型的itwhile (it ! s1.end()){cout (*it) ;//指针不仅可以读而且可以写it;//虽然被const修饰但是自己是可以修改的例it是可以的(*it)就是不可以的总自己可以修改只是指向的内容不可以修改而已it2;//cout (*it2) ;//const迭代器指向的内容不允许被修改}cout endl;for (auto ch : s1)//很好的证明了范围for就是使用迭代器实现的傻白甜{cout ch ;}cout endl;}void test_string4()//验证运算符重载{string s1(hello world);string s2(hello world);string s3(xxxxxxxxxxx);//比大小此时比的是ASCII码值cout (s1 s3) endl;//涉及运算符的优先级,所以要加上()cout (s1 s3) endl;cout (s1 s2) endl;cout (s1 s3) endl;cout (s1 s3) endl;cout (s1 ! s2) endl;}void test_string5()//验证字符和字符串追加{string s1(hello world);s1.push_back( );//反正就是注意使用this指针就行因为this指针代表的就是s1对象cout s1.c_str() endl;s1.append(xxxxxxxxxxxxxxxx);cout s1.c_str() endl;s1 aaaaaaaaaaaaaaaaaa;cout s1.c_str() endl;}void test_string6(){String s1(hello world);s1.insert(6, m);s1.insert(7, y);s1.insert(8, );//搞定了中间插入此时要防止是在最头上插入等问题cout s1.c_str() endl;s1.insert(5, bit);cout s1.c_str() endl;s1.erase(5, 3);cout s1.c_str() endl;}
}以上就是string类地基部分代码注释很全注意测试代码需要放到test.cpp文件中测试 总结还是那句话自己实现string类可以把以前学的知识得到很好的巩固。