当前位置:首页 » 《关注互联网》 » 正文

【C++】string类(2)

0 人参与  2024年10月24日 08:41  分类 : 《关注互联网》  评论

点击全文阅读


?个人主页: 起名字真南
?个人专栏:【数据结构初阶】 【C语言】 【C++】

请添加图片描述

目录

引言1 模拟实现string类基本框架2 实现string类中的主要成员函数2.1 Push_Back 函数2.2 reserve 函数2.3 append 函数2.4 c_str 函数2.5 begin ,end 函数2.5 operator= 函数2.6 operator+= 函数2.7 insert 函数2.8 erase 函数2.9 find 函数2.10 substr 函数 3 实现string类中的非成员函数3.1 双目运算符的重载3.2 输出流3.3 输入流

引言

C++中的std::string类是标准库提供的高级字符串处理工具,支持动态内存管理和丰富的操作函数。为了加深对字符串类的理解,我们将从零开始模拟实现一个简化版的String类,涵盖字符串的创建、拷贝、连接、查找等基本功能。本篇文章的主要目的是展示如何设计和实现一个类似于std::string的类,并探讨其中涉及的内存管理和操作细节。

1 模拟实现string类基本框架

为了和库函数中的 string 做出区分,所以模拟实现的类名是 String
用一个字符串构造一个函数时,我们在初始化的时候给一个默认参数,默认值为 ""空字符串),不能是单引号。这样可以让我们即使不传入任何参数的情况下,也可以构造一个空的 String 对象

#include<iostream>#include<assert.h>using namespace std;namespace wzr{class String{public:String(const char* str = ""){size_t len = strlen(str);//实际上要存储的数据是len个因为是字符数组所以最后一位留给'\0'//char* tmp = new char[len + 1] //_arr = tmp;//上面的写法是错误写法因为他只是开辟了空间并没有将内容复制过去_arr = new char[len + 1];//使用strcpy可以将str的内容(包括'\0'都统一复制到_arr里面)strcpy(_arr,str);//都没有包含\0_size = _capacity = len;}//拷贝构造函数 要求新构造的函数与原函数一样String(const String& str){//首先获取str中的元素个数_size = str._size;//开辟空间//_arr = new char[_size + 1];  实际空间大小不是原函数的空间大小_arr = new char[str._capacity + 1];//复制字符串的内容到新构造的字符串中strcpy(_arr,str._arr)//同步capacity//不同于使用字符串构造函数 _capacity 需要与原函数保持一致//_capacity = _size;_capacity = str._capacity;}//我们现在已经初步实现了构造函数接下来实现析构函数~String(){//delete [] _arr;//delete和[]之间没有空格delete[] _arr;_arr = nullptr;_capacity = _size = 0;}private:char* _arr;size_t _size;size_t _capacity;const static size_t npos;}}

2 实现string类中的主要成员函数

2.1 Push_Back 函数

内存不够需要开辟空间,这时候就需要用到 reserve 函数来开辟空间。在进行分配内存大小的时候,我们一般按照原来大小的二倍扩容 reserve(2 * _capacity),但是这个时候出现了一个问题:如果我们的字符串是一个空字符串,空间大小是 0,那么二倍以后依旧是 0。所以我们在这里需要用到三目操作符,并且给一个初始值是 4

void String::push_back(const char ch){//在进行尾插之前我们首先要检查内存大小是否足够if(_capacity == _size){reserve(_capacity == 0 ? 4 : 2 * _capacity)}// 内存空间足够可以尾插_arr[_size] = ch;_size++;//尾插过后因为是字符数组所以需要将最后一位置为0_arr[_size] = '\0';}

2.2 reserve 函数

不管是尾插还是插入字符串,只要是涉及到增加数据都需要扩容,所以我们实现 reserve 函数。在进行扩容之前,首先要判断 n 和原内存空间的大小

如果 n 大于原内存空间,那么我们可以直接扩容如果 n 小于原内存空间,就需要判断是否小于数据空间。如果小于数据空间直接减少内存会导致数据泄露如果 n 在这之间,则进行缩小到 n
//不管是尾插还是插入字符串只要是涉及到增加数据都都需要扩容//所以我们实现reserve函数void String::reserve(size_t n){if(n > _capacity){//开辟空间大小为n//新建一个临时空间用于拷贝char* tmp = new char[n + 1];//将原函数arr的数据拷贝到tmp变量里strcpy(tmp,_arr);//释放原空间的大小delete[] _arr;_arr = tmp;_capacity = n;//_size不发生变化//进行缩小}else if(n < _capacity && n >= _size){char* tmp = new char[n + 1];//我们在进行数据拷贝的时候一定要注意当我们在进行拷贝的时候//_arr的空间大小是大于tmp的所以我们只需要拷贝我们需要的字符数strncpy(tmp, _arr, _size);//strcpy(tmp, _arr);tmp[_size] = '\0';delete[] _arr;_arr = tmp;_capacity = n;//缩小_size不会发生变化}}

2.3 append 函数

void String::append(const char* str){//断言追加的字符串不能回空assert(str);//首先进行判断内存是否足够//使用len来记录添加字符串的字符数size_t len = strlen(str);if (_size + len > _capacity){//如果原来的数据加上新的字符串的个数大于原空间内存的大小//我们需要进行扩容reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);}//扩容完成现在要考试移动数据strcpy(_arr + _size, str);_size += len;//由于strcpy会把'\0'自动拷贝过来所以我们不需要手动添加}

2.4 c_str 函数

因为我们设置的 _arr 是私有变量,所以为了方便访问以及保护私有变量的安全,我们模拟实现了 c_str 函数。实现这个函数的同时还有一个目的,就是可以在 test.cpp 函数内输出 String 类型对象数组的内容
并且像这种短小但是调用频繁的函数,我们直接写在头文件中,因为在类中定义的函数默认为内联函数,可以减少函数的调用,从而提高程序的运行效率内联函数在编译器的编译阶段就会被替换,所以函数体积不宜过大

char* c_str(){return _arr;}//我们不仅需要返回普通数据还要返回常量const char* c_str() const{return _arr;}

2.5 begin ,end 函数

//其实迭代器的底层逻辑就是通过typedef来定义的所以我们这里的数据类型都是char类型//所以在定义的时候只需要定义char* 和 const char*typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _arr;}iterator end(){return _arr + _size;}const_iterator begin() const{return _arr;}const_iterator end() const{return _arr + _size;}

2.5 operator= 函数

重载赋值操作符的时候,把它放在 String 类的里面作为成员函数有以下好处:

可以直接访问私有成员变量固定左操作数(即当前对象),因为赋值操作符的左操作数总是当前对象。同时返回 *this 可以支持链式赋值(例如 a = b = c)。

并且 C++ 也规定了赋值操作符必须是成员函数

String& operator=(const String& str){_arr = new char[_capacity + 1];strcpy(_arr, str._arr);_size = str._size;_capacity = str._capacity;return *this;}

2.6 operator+= 函数

在进行模拟实现的时候,因为 ++=Push_Backappend 函数的功能相同
所以我们可以直接调用已经实现好的两个函数

String& String::operator+=(char ch){push_back(ch);return *this;}String& String::operator+=(const char* ch){append(ch);return *this;}

2.7 insert 函数

//insert 函数在指定位置增加添加数据void String::insert(size_t pos, char ch){//判断pos指针是否有效assert(pos >= 0 && pos <= _size);//判断内存空间大小if (_size + 1 >= _capacity){//注意原内存可能空间为0的时候reserve(_capacity ==0 ? 1 : 2 * _capacity);}//将pos以及pos以后的位置向后移位//定义一个end变量用来记录最后一个位置size_t end = _size;while (end >= pos){_arr[end + 1] = _arr[end];end--;}//因为我们在移动数据的时候已经将\0向后移位所以不需要在进行赋值_arr[pos] = ch;_size += 1;//当参数为一个字符串的时候void String::insert(size_t pos, const char* ch){assert(pos >= 0 && pos <= _size);//判断内存空间大小size_t len = strlen(ch);if (_size + len >= _capacity){reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);}//将pos以及pos以后len个长度的位置向后移位size_t end = _size;while (end >= pos){_arr[end + len] = _arr[end];end--;}//插入数据for (size_t i = 0; i < len; i++){_arr[pos + i] = ch[i];}_size += len;}}

2.8 erase 函数

void String::erase(size_t pos){assert(this->_arr);assert(pos >= 0 && pos <= _size);//移除某一位置的元素只需要将该位置后面的元素直接向前移位//因为是删除数据所以不涉及到开辟空间内存的问题for (size_t i = 0; i < _size - pos; i++){//遍历从pos位置一直到最后一个字符//将pos位置后面的字符向前移动一位_arr[pos + i] = _arr[pos + 1 + i];}_size -= 1;}

2.9 find 函数

使用 npos 的前提是String 类中声明 npos 变量为静态变量,这样所有该类的对象都可以共享这个变量,并且可以通过类名直接访问。同时,静态成员变量的定义要在类的外面进行

// 在 String 类内部声明静态变量class String {public:    static const size_t npos; // 声明静态常量成员变量    // 其他成员函数和变量...};// 在类的外部定义静态变量const size_t String::npos = -1; // 赋值为 -1,表示未找到

这样,npos 可以被所有 String 对象共享,并通过 String::npos 访问。

const size_t String::npos = -1;size_t String::find(char c){assert(this->_arr);for (size_t i = 0; i < _size; i++){if (c == _arr[i]){return i;//这里是找到了返回下标}}//C++中npos代表无,所以这里没有找到返回的时无return npos;}size_t String::find(const char* ch, size_t pos){//从pos位置寻找一个字符串并返回首字符的坐标size_t len = strlen(ch);//调用strstr来寻找const char* ptr = strstr(_arr + pos, ch);if (ptr == nullptr){return npos;}else{return ptr - _arr;}}

2.10 substr 函数

String String::substr(size_t pos, size_t len){//从当前字符的n位置出提取len个字符形成一个新的String类对象//判断如果len过大可能会多开空间if (len > _size - pos){len = _size - pos;}String sub;sub.reserve(len);for (size_t i = 0; i < len; i++){sub += _arr[pos + i];}return sub;}

3 实现string类中的非成员函数

3.1 双目运算符的重载

bool operator==(const String& s1, const String& s2){return strcmp(s1.c_str(), s2.c_str()) == 0;}bool operator>(const String& s1, const String& s2){return strcmp(s1.c_str(), s2.c_str()) > 0;}bool operator>=(const String& s1, const String& s2){return (s1 > s2 || s1 == s2);}bool operator<(const String& s1, const String& s2){return !(s1 >= s2);}bool operator<=(const String& s1, const String& s2){return !(s1 > s2);}bool operator!=(const String& s1, const String& s2){return !(s1 == s2);}

3.2 输出流

ostream& operator<<(ostream& out, const String& s){out << s.c_str() << endl;return out;}

3.3 输入流

istream& operator >> (istream& in, String& s1){//首先清空s1s1.clear();//提前开辟一块空间用来存储输入的数据const size_t N = 256;char buff[N];//获取字符char ch;ch = in.get();//作为buff的指针用来存储数据int i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == N - 1){//+=会找0的位置buff[i] = '\0';s1 += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';s1 += buff;}return in;}

点击全文阅读


本文链接:http://zhangshiyu.com/post/176612.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1