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

上海模板建站哪家好个人网站制作流程

上海模板建站哪家好,个人网站制作流程,做网页课件的网站,辽宁建设工程信息网怎么业绩加分文章目录 string的介绍学习的意义auto关键字和范围forstring中的常用接口构造和析构对string得容量进行操作string的访问迭代器(Iterators):运算符[ ]重载 string类的修改操作非成员函数 string的模拟实现不同平台下的实现注意事项模拟实现部分所有的模拟实现函数预…

文章目录

  • string的介绍
    • 学习的意义
    • auto关键字和范围for
    • string中的常用接口
      • 构造和析构
      • 对string得容量进行操作
      • string的访问
        • 迭代器(Iterators):
        • 运算符[ ]重载
      • string类的修改操作
      • 非成员函数
    • string的模拟实现
      • 不同平台下的实现
      • 注意事项
      • 模拟实现部分
        • 所有的模拟实现函数
        • 预先处理的函数
        • 构造函数和析构函数
        • 简单迭代器
        • 容量操作
        • 访问操作
        • 修改操作
        • string的比较关系
        • 操作函数
        • 流插入/提取运算符重载
      • 对于深拷贝的改进

string的介绍

本章节将重点介绍string的模拟实现,其内部的函数使用则需要自行查看文档使用:
string类的使用

本文将对string类中重点的函数进行模拟实现。在使用string类时,必须包含#include string这个头文件以及using namespace std;

学习的意义

C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

所以c++引入了string这个类进行管理这个字符串,并且把其对应的操作函数写成其成员函数。这样子十分方便使用。

string其实是c++库中实现的一个类,我们可以理解为其是字符串。只不过底层使用类似于之前学习过的顺序表进行实现的。在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。

auto关键字和范围for

这是c++11引入的概念,为了方便学习后续内容需要先进行了解。

auto关键字其实可以理解为类型的自动识别:

在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
1.用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
2.当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
3.auto不能作为函数的参数,可以做返回值,但是建议谨慎使用
4.auto不能直接用来声明数组
5.auto声名变量的同时必须进行初始化

这些概念我们来简单介绍一下:

即我们在声名变量时可以这么写:

#include<iostream>
using namespace std;
int main() {auto a = 10;auto b = 'a';auto c = 10.5;auto d = &a;cout << typeid(a).name() << endl;cout << typeid(b).name() << endl;cout << typeid(c).name() << endl;cout << typeid(d).name() << endl;return 0;
}

可以使用typeid中的name函数打印出变量的类型名称:
在这里插入图片描述
我们发现确实是自动识别了。

但是注意千万不能不进行初始化,因为auto关键字其实是编译器经过特殊处理的,需要通过赋值的内容进行自行推到。如果没有值赋予,就无法推导出类型。

同一行的声名如auto a = 10, b = 5必须是同一个类型的数据,如果前后有不相同的数据,会导致类型推导失败。

函数的参数部分是不能使用auto关键字的,但是返回值却可以。但是需要慎用。如果有多个函数嵌套调用且返回值均是auto,需要一直往调用本函数的上一个函数进行推导是何类型的返回值。这非常麻烦,且会导致代码逻辑混乱。

当然用的最多的地方是范围for的使用:

int array[] = { 1, 2, 3, 4, 5 };
// C++98的遍历
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i){array[i] *= 2;
}
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i){cout << array[i] << endl;
}
// C++11的遍历
for (auto& e : array)e *= 2;
for (auto e : array)cout << e << " " << endl;

也就是说,auto关键字在for中使用的时候,会自动识别访问的类型并且从头进行访问,不需要我们判断访问范围,这是十分方便的。

对于string类中也是可以使用范围for,但是是使用其内部迭代器进行操作的,具体的会在概念部分进行重点的讲解。

对于auto的使用,最大的特点就是在于对于某些类型代码长度比较长的时候,是可以进行一定程度上简化的,还有就是在范围for中的使用。其余情况下,能确定类型还是尽量写成确定类型,也是为了代码的可读性。

string中的常用接口

现在我们来讲一下string中的一些重要概念。其实严格意义上来说,string出现的比STL库要早,并不是STL库中的一员,但是由于其性质和使用方式和STL中其他的类都被封装成类似的方式和对应的接口进行调用,所以是可以把string当作STL中的一员进行学习的。

只不过由于string出现的更早,内部成员函数会更多也更冗杂(也是为了向前兼容),所以只需要重点掌握几个重点函数即可,其余的使用的时候查一下文档即可。重点函数即需要模拟实现的部分。

string可以理解为字符串,字符串常见的操作即:访问、修改、查找、反转、交换等。其内部各种函数又被重载成针对不同中参数的时候对应得操作。下面我们来看看重点的接口:

构造和析构

既然是一个类,又因为其类似于顺序表得实现,所以是需要自行编写构造函数和析构函数的:

(constructor)函数名称功能说明
string() (重点)构造空的string类对象,即空字符串
string(const char* s) (重点)用C-string来构造string类对象
string(size_t n, char c)string类对象中包含n个字符c
string(const string&s) (重点)拷贝构造函数
~string()(重点)析构函数

析构函数操作系统会自行调用,但是在创建字符串的时候,就需要使用其构造函数。

对string得容量进行操作

函数名称功能说明
size(重点)返回字符串有效字符长度
length返回字符串有效字符长度
capacity返回空间总大小
empty (重点)检测字符串释放为空串,是返回true,否则返回false
clear (重点)清空有效字符
reserve (重点)为字符串预留空间
resize (重点)将有效字符的个数该成n个,多出的空间用字符c填充

这里需要说明一点:为了兼容c的用法,string的结尾处也是会加一个‘\0’的。而capacity计算的是不包括‘\0’这个位置以外其他空间的数量,这点需要十分注意。也就是说,假设满容状态下有50个字符,但是为了存储‘\0’会多开一个空间,即总共51个空间,但是真正计算容量的时候其实是50个,需要始终多开一个存储‘\0’

还有就是字符串有效长度的问题,也是因为string出现的较早,为了兼容c的用法加入了length这个函数,其实在STL库中更多的使用还是size这个函数,计算的就是不包括’0’的字符个数。

clear函数只会把string中的字符数变为0,即清空字符串,但是不会改变其容量。
reserve函数在不同平台下的实现是不一样的,如果传入一个比当前空间数还要小的数给reserve函数,c++并没有明确规定此种行为应该如何操作。在g++编译器下会选择缩容,而vs2022下是不做任何改变的。

resize函数则会改变容量,是可以缩容的,扩容需要填充字符,默认填充的是‘\0’

string的访问

迭代器(Iterators):

在这里插入图片描述
begin和end返回的其实是string的开始位置和结束位置(正向迭代器iterator),这是类似于指针的东西:
在这里插入图片描述
注意end()的位置就是‘\0’的位置。

而rbegin和rend是反向迭代器(reverse_iterator):即rbegin指向的是反向开头位置,rend是反向结尾数据:
在这里插入图片描述
即对反向迭代器++就是往前走。rend其实指向的是串中第一个元素的前一个位置。

剩下的四个带字母c的迭代器其实是上述四个迭代器对应的常量迭代器,也就是说,使用常量迭代器时,不能对string中的数据进行修改。当然在string类中迭代器用的不算多,因为有更方便的方式,但是在STL其他的容器中(如链表)就会用的比较多。

运算符[ ]重载

毕竟是字符串,在c语言中字符串可以当作数数组使用,也源于[]这个运算符的特性。所以我们希望能够像数组那样访问字符串。

在这里插入图片描述
为了方便修改,该运算符重载函数返回该位置的引用。

这里有两种形式,一种是可以修改的,另外一种返回的是不可修改的。如果传入的pos越界会触发断言报错。

string类的修改操作

函数功能说明
push_back在字符串后尾插字符c
append在字符串后追加一个字符串
operator+= (重点)在串后追加串str或追加字符c
c_str(重点)返回C格式字符串
find + npos(重点)从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
rfind + npos从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
substr + npos在str中从pos位置开始,截取n个字符,然后将其返回

我们先来解释一下何为npos,对string类执行修改操作时,会不可避免地涉及到长度的事情。但是长度究竟要多长需要用户输入。但是由于c++中缺省参数的存在,是可以设定默认值的。所以c++在string类中加入静态常量成员变量static const size_t npos,且初始化为-1。由于是size_t类型变量,是无符号整形,所以-1赋值给它其实是赋值的补码,即全1。即size_t对应的是无符号int整形的最大值。

函数讲解:
对于push_back还是很好理解的,就和顺序表的尾插一个字符一样。但是对于尾插其实用的更多的是operator+=的运算符重载,因为更简介也好理解。operator+=可以尾插字符,也可以尾插字符串。

而c_str返回的是string中存储串的c格式的字符串,因为要兼容c的用法。

find和refind其实很好理解,就是从pos位置(不能越界)找传入符号的第一次出现位置。(最常用),只不过一个是向后找,一个向前找。

而substr则是返回string中从pos位置开始长度为len的串,构造一个string并且返回。当然这个len的默认长度是npos。如果后续长度够,则返回len个。如果后续长度不够,则后续的字符全返回。

非成员函数

函数功能说明
operator+尽量少用,因为传值返回,导致深拷贝效率低
operator>> (重点)输入运算符重载
operator<< (重点)输出运算符重载
getline (重点)获取一行字符串
relational operators (重点)大小比较

对于+的运算符重载,其实可以理解为拼接两个字符串,将+后的string拼接在前面一个位置上后传值返回。

而流插入流提取符号则是方便输入,使得我们直接输入字符串就能构造一个string类。

但是这样为什么还要getline函数呢?因为对于标准输入流cin,如果在缓冲区中识别空格会忽略掉。而getline不会:
在这里插入图片描述

在这里插入图片描述
还有就是一些比较关系的运算符重载,这些我们会在模拟实现部分讲解。

string的模拟实现

不同平台下的实现

在这里我们得先知道一个事情就是:在不同平台下,对于string类的实现是不一样的。

对于vs2022来讲,其string类在实现的时候多加入了一个大小为16的buffer数组。也就是当构造的字符串长度小于等于16时,就存储在buffer中,反之才会存储在指针指向的开辟空间上。而对于gcc编译器,则是直接存储在指针指向的空间上。

gcc编译器在扩容的时候严格村寻二倍扩容原则。而vs2022则复杂一些,扩容的时候可能会考虑容量对齐的方式。

对于模拟实现,其实是为了更好的理解这个类的使用,理解其背后的思想。而不是要写出一些更好的实现,所以在后续的模拟实现部分将写最简单的形式,即采用顺序表进行实现,只不过要考虑字符串的一些特殊问题。

注意事项

由于是我们自己写的string类,但是标准库中也有string类。很容易起到命名冲突的问题。在学习命名空间的时候我们就讲到,为了尽可能防止命名冲突,写项目的时候最好将自己部分的代码写入自己的命名空间。所以我设定了命名空间Mystring。

模拟实现部分

所有的模拟实现函数
#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include<iostream>
#include<assert.h>
#include<string.h>
using namespace std;namespace Mystring {class string {public:friend ostream& operator<<(ostream& _cout, const Mystring::string& s);friend istream& operator>>(istream& _cin, Mystring::string& s);typedef char* iterator;//constructorstring(const char* s = "")string(const string& s);string& operator=(const string& s);//Destructor~string();//iteratoriterator begin();iterator end();// modifyvoid push_back(char c);//尾插一个字符string& operator+=(char c);//也是尾插一个字符 只不过是使用运算符重载 还会返回插入后的字符串的引用string& operator+=(const char* str);//尾插字符串void clear();//只进行清空数据,不清空容量void swap(string& s);//交换操作void append(const char* str);//追加字符串// capacitysize_t size()  const;//返回字符串长度size_t capacity() const;//返回当前字符串空间数bool empty() const;//判断是否长度为0(空)void resize(size_t n, char c = '\0');//调整字符串长度 加长就补字符cvoid reserve(size_t n);//扩容(不修改内容) 不缩容// access//预算符重载 为了像数组一样获取第index坐标的内容char& operator[](size_t index);const char& operator[](size_t index)const;//relational operators//关系比较运算符bool operator<(const string& s);bool operator==(const string& s);bool operator!=(const string& s);bool operator<=(const string& s);bool operator>(const string& s);bool operator>=(const string& s);//string operationsconst char* c_str()const;//返回字符串的首地址 兼容c使用size_t find(char c, size_t pos = 0) const;//返回c在string中第一次出现的位置 没有就返回npossize_t find(const char* s, size_t pos = 0) const;//返回字串第一次出现的位置 没有也是返回nposstring& erase(size_t pos, size_t len);//删除pos位置开始的,往后数共len个元素 不够就全删string& insert(size_t pos, char c);//在pos位置上插入字符cstring& insert(size_t pos, const char* str);//在pos位置插入字符串//get_npossize_t get_npos();//获取成员变量nposprivate:char* _str;size_t _size; size_t _capacity;  static const size_t npos; };//流插入提取运算符重载ostream& operator<<(ostream& _cout, const Mystring::string& s);istream& operator>>(istream& _cin, Mystring::string& s);void TestString_Constructor_Destructor();//测试构造和销毁void TestIterator();//测试迭代器(指针版本)void TestCapacity();//测试容量的成员函数void TestAccess();//测试获取元素操作void TestModify();//测试修改逻辑void TestRelationalOperators();//测试比较逻辑void TestStringOperations();//测试串操作函数void TestMyStringIOstream();//测试IO输入}

注意的是,我们习惯的将经常调用且代码量短小的函数放在类中定义,因为类中函数默认为inline,这样能提高效率。而其余的函数最好声名与定义分离,放在另外一个文见的同一个命名空间中进行定义,同时定义时需要指定类域。这些都是命名空间以及类域的知识,需要熟悉。

预先处理的函数

有一些函数可能会被频繁的被复用,所以可以预先进行实现。这一点后续会体验到。

1.reserve函数
reserve函数是预留容量的函数,因为使用的是vs2022平台进行编译,所以我就学习这个编译器的实现,只扩不缩。还需要注意的是,为了兼容c的用法,末尾应该是要有‘\0’这个字符作为终止字符的。否则一些c库中的函数可能用不了。传入reserve函数的参数n代表为n个有效字符,所以需要多开一个空间,存储‘\0’

void string::reserve(size_t n) {if (n <= _size) return;char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp; _capacity = n;
}

由于只是处理容量,所以只需要改动_capacity的值。又由于c++中没有realloc这样类似功能的函数,所以需要自行调整空间然后赋值。这里复制直接使用c库函数即可,可读性高、方便。

2.get_npos函数
当然可以把这个值变为公有的成员变量,就不需要通过函数的方式获取。

size_t get_npos() {return npos; 
}

这样是更加规范的(个人认为)。

3.c_str函数
为了方便查看某些功能的结果,往往是需要打印出来的。很多人有疑问,使用string类的时候不是可以直接使用标准输入输出流吗?为什么不用那个呢?

我们得直到,那个是string库中实现的,里面包含了对流插入和提取运算符的重载。而我们当前实现的string是我们自己写的,和std中的是不一样的。但是又由于对流插入和运算符重载的知识点比较陌生,通常都是放在后面来讲。而对于字符串类型的数据,c++早已经实现重载,可以直接使用。所以我们可以先使用string返回的字符串类型进行打印查看

const char* string::c_str() const {return _str;
}
构造函数和析构函数

我们打开文档查看会发现,构造函数有很多种形式,但其实很冗余。真正使用的就那么一两个,所以我们只对常用的进行实现。

又由于构造函数和析构函数会被频繁的调用,所以不妨就把构造函数和析构函数写在类中。

1.输入字符串进行构造,不输入默认为空串
我们可以把这个认为不传参时这个就是默认构造函数,传参了就是调用这个构造函数。

string(const char* s = "") {_size = strlen(s);_str = new char[_size + 1];strcpy(_str, s);_capacity = _size;
}

对于此处的缺省参数,其实就是一个空串。c字符串只有一个‘\0’的时候就是空串,长度为0。
使用strcpy函数就可以直接复制了,因为strcpy会把‘\0’一起复制过来。

2.拷贝构造函数
当然拷贝构造也是很常见的,所以也得写一下:

string(const string& s) {_size = s._size;_str = new char[_size + 1];strcpy(_str, s._str);_capacity = _size;
}

3.赋值运算符重载函数

string& operator=(const string& s) {//防止有自己给自己赋值出问题if (*this != s) {delete[] _str;_size = s._size;_str = new char[_size + 1];strcpy(_str, s._str);_capacity = _size;}return *this; 
}

赋值重载就是针对于两个已存在的对象进行赋值操作,那么 被赋值的那个可能有很多种情况。所以得先清空资源,再来开空间进行深拷贝。

注意这里的所有赋值和构造都是深拷贝,因为值拷贝会导致析构两次,程序会崩溃。且不符合要求。这点在类和对象的只是讲解就已经提到了。

4.析构函数
因为这是有开辟资源的类,所以默认的析构函数肯定是会导致内存泄露的,所以必须自行写析构函数。

~string() {delete[] _str;_str = nullptr;_capacity = _size = 0;
}

然后就可以自行编写void TestString_Constructor_Destructor();这个函数的实现。这个因人而异,但总体就是检查是否构造成功,是否完成深拷贝,容量大小等是否正确即可。

简单迭代器

虽然前面说了迭代器是类似指针的东西,但实际上并不是。特别是对于STL中其他容器来讲,如果是用指针实现,那指针向后走一步都不是下一个节点的位置。只不过基于string这个类毕竟是用顺序表实现的,所以用指针来实现迭代器也未尝不可:

typedef char* iterator;
//iterator
iterator begin() { return _str;
}
iterator end() {return _str + _size;
}

在自己模拟实现的时候直接可以认为就是char*指针即可。

然后就是编写void TestIteraor();这个函数,测试一下范围for等功能即可。

容量操作

我们可能需要直到当前串的长度,容量为多少,又或是是否为空:

size_t string::size() const {return _size;
}size_t string::capacity() const {return _capacity;
}bool string::empty() const {return (_size == 0);
}

这些逻辑都十分简单。就不多说了。

重点来看一下resize函数:

void string::resize(size_t n, char c) {if (n < _size) {_size = n;}else if (n > _size) {reserve(n);for (size_t i = _size; i < n; i++) { _str[i] = c;}_size = n;}_str[_size] = '\0';
}

如果传入的n小于当前长度,那么直接缩小长度即可,并且记得处理‘\0’的问题。又或是当n大于当前长度,那就需要扩容,所以reserve函数就有用了,直接扩容即可。然后将后续扩容的所有位置都赋值为字符c。默认为‘\0’,修改长度。也是要处理‘\0’问题。鉴于两种情况都要处理,所以就合并写。

而reserve函数已经实现过了,就不再多说。

然后编写void TestCapacity();函数,测试一下reservereszie是否正确操作,是否能正确返回对应长度容量等。

访问操作

访问操作有很多种,但是真正用的多的还是对[]的运算符重载:

//access
char& string::operator[](size_t index) {assert(index < _size);//不能越界return _str[index];
}const char& string::operator[](size_t index) const {assert(index < _size);//不能越界 return _str[index]; 
}

只不过一个是能修改,一个不能修改。

然后编写void TestAccess();函数,测试一下是否能正常访问和修改。如果是const的变量是否做到了只能访问。

修改操作

修改操作即为清空、尾插、头插、任意位置插入等。

清空比较简单,直接把字符串变为空串,长度为0即可。在此我们不进行缩容。

void string::clear() {_size = 0;_str[_size] = '\0';
}

然后就是尾插函数push_back,尾插一个字符char c:
当然是需要判断是否需要扩容的:

void string::push_back(char c) {//判断是否需要扩容if (_size == _capacity) {reserve(_size * 2);}_str[_size++] = c;_str[_size] = '\0';
}

我们总体还是执行二倍扩容的原则,所以满容的情况下直接将当前容量翻倍即可。这件事情直接交给reserve函数完成就好了。内部会完成的复制,容量修改操作。pushback函数就直接修改大小即可,然后需要处理‘\0’的问题。

当然尾插更常用的还是运算符+=的重载,有两种版本,一种是尾插一个字符,一种是尾插字符串:

//尾插一个字符
string& string::operator+=(char c) {push_back(c);return *this;
}

返回的是string的引用,减少拷贝。

string& string::operator+=(const char* str) {size_t len = _size + strlen(str);if(len >= _capacity) reserve((len > 2 * _capacity) ? len : 2 * _capacity);strcpy(_str + _size, str);_size = len;return *this;
}

对于尾插字符串,就得判断一下尾插字符串后的有效长度是否超出当前容量。如果超出就需要进行扩容。但是为了防止过度扩容,可以考虑一下容量对齐,如果长度超出了当前容量的2倍就扩至需要长度容量。反之扩大到当前容量的2倍。

使用strcpy进行复制即可,‘\0’会自动处理。然后再修改容量处理返回值即可。

还有一个追加操作append,就是在字符串末尾追加一个字符串:

void string::append(const char* str) {*this += str;
}

直接复用前面写的operator+=函数即可

最后一个是交换两个字符串操作:
对于交换,很多人认为要向以往那样找个中间量,也就是开辟一个string类对象,作为中转接收。但是这样要一直调用构造函数和拷贝构造,会非常影响效率。

而我们又知道,两个string指向的串实际上是在堆上的,只不过它们在栈区上有一个指针指向这个空间。那么让他们两个指向的空间的地址交换不就好了吗?然后再让大小容量交换不就完成交换了。

而且对于交换这个函数,标准库中是有模板的。直接调用即可,只不过要指定是std标准命名空间中的那个交换函数。

void string::swap(string& s) {std::swap(_str, s._str);std::swap(_capacity, s._capacity);std::swap(_size, s._size);
}

还是一样的,需要自行编写void TestModify();函数进行测试相关功能是否正确。

string的比较关系

其实就是字符串的比较大小(如strcmp的功能)。这个部分很简单,在日期类模拟实现的时候早有类似情况,只需要写出判断是否相等和大于或者小于,就可以复用逻辑:

//relational operators
bool string::operator<(const string& s) {return (strcmp(_str, s._str) < 0);
}bool string::operator==(const string& s) {return (strcmp(_str, s._str) == 0);
}bool string::operator!=(const string& s) {return !(*this == s);
}bool string::operator<=(const string& s) {return (*this < s) || (*this == s);
}bool string::operator>(const string& s) {return !(*this <= s);
}bool string::operator>=(const string& s) {return !(*this < s);
}

只需要调用c库中的strcmp函数就可以了。剩下的就是逻辑的复用。
自行编写void TestRelationalOperations();函数进行测试相关功能是否正确。

操作函数

1.返回c串形式的指针
c_str函数,这个已经实现过了,就不再多说。

2.查找操作find
查找操作find是从pos位置开始向后查找第一个需要查找的字符或者字符串的位置。倒着找就是rfind。但是逻辑基本相同,只不过是查找方向的问题。所以只实现一下正向查找就可。

查找字符:

size_t string::find(char c, size_t pos) const { assert(pos < _size);size_t i = pos;while (i < _size) {if (_str[i] == c) return i;++i;}return npos; 
}

逻辑很简单,一个一个找,找到就返回即可。

查找字符串:

size_t string::find(const char* s, size_t pos) const {assert(pos < _size);int sublen = strlen(s);if (sublen > _size) return npos; for (size_t begin = pos; begin <= _size - sublen; begin++) { //匹配过程int i = 0;while (s[i] == _str[begin + i] && s[i] != '\0' && _str[i] != '\0') {++i;}if (i == sublen) return begin;}return npos;
}

可以使用c库中的函数strstr寻找字串。但是其原理也不是很难就自行实现也可以。

当字串长度大于被查找串长度,这肯定找不到的,所以直接返回npos即可。反之需要查找,一直到剩余长度小于字串长度就停止查找即可。每匹配成功一个字符,i就自增,直到i的值与查找的子串长度相同的时候就返回当前位置。但是需要注意的是,匹配过程不能包括字符‘\0’,否则匹配效果会出错。

2.删除操作erase
erase操作主要实现的功能就是从pos位置开始,删除长度为len的字符。(pos不能越界)

当前默认的长度len = npos,即从pos位置开始全删(因为字符串一般达不到那么长)。
所以分两种情况,一种是删除长度len > _size - pos(从pos位置开始的有效元素个数),则将后续的全删。
反之则需要挪动数据:

string& string::erase(size_t pos, size_t len) {assert(pos < _size);if (len >= _size - pos) {_size = pos;_str[_size] = '\0';}else {int poslen = pos + len;while (poslen < _size) {_str[pos] = _str[poslen];++pos;++poslen;}_str[pos] = '\0';_size = pos;}return *this;
}

具体的操作流程可以通过画图来感受。

2.插入操作insert
和erase相对,在pos位置插入字符或者字符串。

一旦涉及到插入操作,就需要进行判断是否需要扩容,所以reserve函数就又派上用场了。

插入一个字符:

string& string::insert(size_t pos, char c) {assert(pos <= _size);if (_size == _capacity) {reserve(_capacity * 2);}if (pos == _size)  *this += c;//挪动数据else {for (size_t i = _size + 1; i > pos; i--) {_str[i] = _str[i - 1];}_str[pos] = c;++_size;}return *this;
}

注意i开始的位置,从插入字符后‘\0’放在的位置开始往前走,将前一个位置的值赋值到当前i的位置。这样子到pos位置就能停下。

如果从_size位置开始,将当前的值赋值到后面去,那就要走过pos这个位置才能停下。这会出问题。因为假设pos的位置是0,那么i - 1 的值不是1,而是无符号整形最大值。因为i是size_t类型,不可能小于0。所以从_szie + 1位置开始向后移动数据。

对于pos的位置如果是当前‘\0’的位置,则使用尾插。

插入一个串:

string& string::insert(size_t pos, const char* str) {assert(pos <= _size);if (pos == _size) *this += str;else {size_t sublen = strlen(str);size_t len = sublen + _size; if (len >= _capacity) {reserve((len >= _capacity * 2) ? len : _capacity * 2);}for (size_t i = _size + sublen; i > pos + sublen - 1; i--) {_str[i] = _str[i - sublen];}memcpy(_str + pos, str, sublen);_size = len;}return *this;
}

也是从新串的‘\0’放在的位置开始,不断地将数据向后移动。我们发现其实和上面的过程是相似的,只不过移动数据的位置之间差了一个插入串的长度sublen,当sublen == 1的时候其实就是插入一个字符。然后使用memcpy函数将strsublen个字节赋值给要插入的位置即可。

对于pos的位置如果是当前‘\0’的位置,则使用尾插。

然后就是自行编写void TestStringOperations();函数进行测试相关功能是否正确。

流插入/提取运算符重载

因为流插入和流提取运算符c++中只对内置类型进行了重载,对于自定义类型是没有的。虽然标准库中确实进行了重载,但那是标准库的。

而我们在自己的命名空间内写的string还没有进行重载,没有办法做到直接将string对象插入到标准输出流,也没有办法直接从标准输入流中提取内容构造string对象。所以我们需要自行进行重载。

在之前模拟实现日期类的时候就说到了,流插入和提取运算符应该重载为全局函数,并且在类中声明为友元函数,因为重载成成员函数第一个参数必须是类对象,这样子会与平常的使用相反。

对于流插入是很简单的,因为只需要打印字符串。那直接将指向串空间的那个指针插入流中就好了,这个标准库中是已经完成重载的了:

ostream& operator<<(ostream& _cout, const Mystring::string& s) {_cout << s._str; return _cout;
}

返回的是流的引用,为了连续赋值。

而流的提取就需要注意的是:
在上面我们已经讲到了,标准输入流是会自动忽略空格的,从而导致不进行输入空格到串中。所以一旦输入空格就会导致串读取错误,不是想要的串。所以需要使用另外一个函数。

istream中的一个函数get,这个除了‘\n’都能提取到流中。
然后就可以通过这个函数一直从缓冲区内读取,直到‘\n’,不断地尾插到string中即可。

但是如果字符串比较长的情况下,一个一个尾插效率还是非常低地,所以可以考虑自行设置一个缓冲区buffer,大小为256个元素,设置内部所有元素全部为‘\0’

然后将读取到地内容放在buffer中,直到放够255个后,就一次性尾插到串中。重新开始放在第一个位置。

然后出循环后buffer中可能还剩下一些元素没有插入,个数正好是i个,所以可以直接使用memcpy函数复制i个字节到指定位置即可。然后处理’\0’。

istream& operator>>(istream& _cin, Mystring::string& s) { s.clear();//先清空 要不然读取会错乱int i = 0;char buffer[256] = { '\0' };char ch;ch = _cin.get();while (ch != '\n') {buffer[i++] = ch;if (i == 255) {s += buffer;i = 0;}ch = _cin.get();}s.reserve(s._size + i);memcpy(s._str + s._size, buffer, i);s._size += i;s._str[s._size] = '\0';return _cin;
}

最后还是一样,写一个void TestMyStringIOstream();函数测试一下即可。重点是测试当输入字符串较长的情况下是否能正常输出一样的结果。

对于深拷贝的改进

深拷贝主要就是拷贝构造部分和赋值运算符重载部分,每次都要自己开空间还是很麻烦的,也灭有办法能够让其他地方开空间呢?

答案是有的,需要用到string的交换操作。

对于拷贝构造:
在这里插入图片描述

我们调用构造函数,将tmp构造为一个和s有着一样大空间、容量、内容的对象。然后这是个动态成员函数,有this指针,直接将this指向的地址,容量,大小进行交换即可。

但是需要注意的是,由于拷贝构造是构造,当前this指针的三个内容其实是为定义的。所以最好给定缺省值。防止交换后,tmp要调用析构函数释放的是随机值。

在这里插入图片描述
即在类定义处给定缺省值。

代码实现:

string(const string& s) {string tmp(s._str);swap(tmp);
}

代码逻辑就简化了很多,不用自己开空间。

而对于赋值重载,也是可以使用这个逻辑的,我们把参数改为string的对象,而不是引用。这样子参数可以直接构造出一个对象,那么直接让this和其交换即可:

string& operator=(string tmp) {swap(tmp);return *this;
}

这样子写虽然效率上没有太多提升,但是写起来会简洁很多。

http://www.dtcms.com/wzjs/84230.html

相关文章:

  • 员工管理系统源码seo网站优化推广教程
  • 网站建设与部署阿里云大学百度服务平台
  • 北京网站建设有哪些公司好拼多多关键词优化步骤
  • 太原这边有做网站的吗互联网公司排名
  • 临沂网站建设费用沈阳百度seo关键词排名优化软件
  • 企业网站ui设计欣赏站长工具seo查询5g5g
  • 道滘镇网站仿做营销广告网站
  • 公安网站建设的目标北京seo外包
  • 成都疫情最新规定公告新塘网站seo优化
  • 个人网站做镜像免费网络推广工具
  • 想做个网站找谁做崇左seo
  • 网站导航条专门做页面跳转北京搜索关键词优化
  • 游戏网站外链建设德州seo优化
  • 属于b2c网上交易平台app优化建议
  • 网站怎么做第三方支付接口无锡百度推广平台
  • 广州网站定制开发设计建一个外贸独立站大约多少钱
  • 肇庆制作企业网站百度网站推广教程
  • html论坛源码seo点击器
  • 买布自己做网站衣服的百度贴吧网页版入口
  • 一条龙建设网站百度搜索风云排行榜
  • 法治建设网站模块广州seo公司
  • 专业的集团网站设计公司西安seo服务公司
  • wordpress短代码开发seo网络推广是干嘛的
  • 如何做淘宝店网站军事新闻今日最新消息
  • 厦门优化网站排名利尔化学股票股吧
  • 网站建设费用写创意seo网站优化方案
  • 人才网站cms网站设计与网页制作
  • 马鞍山住房建设委员会网站抖音seo公司
  • 郑州可以做网站的公司关键词首页排名代发
  • 长春网站建设服务网络平台有哪些?