片头
哈喽~小伙伴们,在上一篇中,我们讲解了C++的string类的相关函数,这一章中,我们将继续深入学习string类函数,准备好了吗?咱们开始咯~
五、对内容进行修改
⑤insert函数
在指定位置插入字符或者字符串
函数声明
//insert函数void insert(size_t pos, char ch);//插入字符
函数定义
//insert函数,插入字符void string::insert(size_t pos, char ch) {//严格控制pos的取值范围,避免越界assert(pos <= _size);//如果_capacity和_size相等,需要扩容if (_capacity == _size) {size_t newCapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newCapacity);}//end从'\0'的位置开始size_t end = _size;while (end >= pos) {_str[end + 1] = _str[end];//最后一次: pos+1 = pos;--end;//每执行完一次,end向前挪动一位}_str[pos] = ch;//将pos位置放入ch字符++_size; //_size更新}
这里的end初始指向末尾_size,也就是'\0'的位置,将数据不断往后挪动,直到腾出pos位置。
测试一下:
但是如果这时,我们往下标为0的位置头插'D',发现编译器崩溃,为啥呢?
因为pos此时为0, 如果pos大于等于0,则进入循环,若pos小于0,就结束。end是size_t类型,就不会小于0。如果end减到-1,再转成无符号类型size_t,无符号整型-1是一个很大很大的数,必然会越界。所以,我们将end的类型修改为int。
int end = _size;
但是此时的pos仍然是size_t类型,在一个操作符两边的操作数,如果它们类型不一样,它们就会发生隐式类型转换,当有符号类型遇到无符号类型,有符号类型就会隐式类型转换成无符号类型。
解决方法1:将pos强转成int类型
int end = _size; //end从'\0'的位置开始while (end >= (int)pos) //将pos强转成int,避免类型不同{ _str[end + 1] = _str[end]; //最后一次:_str[pos+1] = _str[pos]--end;}_str[pos] = ch;++_size; }
解决方法2:end仍然是size_t类型,但是起始位置是_size+1
size_t end = _size + 1; //end的起始位置是_size的下一个位置while (end > pos) //end>pos位置继续,end==pos就结束{_str[end] = _str[end - 1]; //最后一次: 下标为0的元素挪到下标为1的位置end--;}_str[pos] = ch;++_size;
我们 初始指向'\0'的下一个位置,最终当end和pos值相等时跳出循环,完成插入与对成员变量的修改。
接下来,我们用insert函数实现插入字符串
函数声明
void insert(size_t pos, const char* str);//插入字符串
函数定义
//方法1: void string::insert(size_t pos, const char* str) {//严格控制pos的取值范围,避免越界assert(pos <= _size);int len = strlen(str); //新字符串的长度(不包括'\0')if (_size + len > _capacity) //检查是否需要扩容{reserve(_size + len);}int end = _size; //end的起始位置_sizewhile (end >= (int)pos) //判断end是否满足循环条件{_str[end + len] = _str[end];end--;}memcpy(_str + pos, str, len); //使用memcpy函数,拷贝len个字符,不包括'\0'_size += len; //_size更新}
诶,为啥这里不能使用strcpy函数了呢?
因为strcpy函数会把插入字符串的'\0'一起拷贝过去,改变了原字符串的内容,这样就不行的。
中间的逻辑也可以这样进行修改,保证结果正确:
int end = _size + len; //将end设定为_size+lenwhile (end > pos + len - 1) //判断end是否满足循环条件{_str[end] = _str[end - len];end--;}memcpy(_str + pos, str, len); //使用memcpy函数,拷贝len个字符,不包括'\0'_size += len; //_size更新
测试一下:
实现了insert函数后,我们就可以在push_back函数和append函数复用它们
//尾插一个字符void string::push_back(char ch) {insert(_size, ch); //在_size位置,插入一个字符ch}//尾插一个字符串void string::append(const char* str) {insert(_size, str);//在_size位置,插入一个字符串}
⑥erase函数
函数声明
//erase函数void erase(size_t pos = 0 , size_t len = npos);
从pos位置开始,删除len个字符(pos默认为0,len默认为npos)
这里的pos不能等于_size,因为我们不能删去'\0',npos为整数-1,这里我们需要自己在类中定义。
public:static const int npos;
在外部进行初始化
const int string::npos = -1;
函数定义
//erase函数void string::erase(size_t pos, size_t len = npos) {assert(pos < _size); //严格控制pos的有效区间//len大于后面字符个数时,有多少删多少if (len == npos || len >= _size - pos) {_str[pos] = '\0'; //将pos的位置改为'\0'_size = pos; //有效元素的个数为pos个}else{//len小于后面的字符个数strcpy(_str + pos, _str + pos + len);//将后面的字符拷贝到pos位置_size -= len; //有效字符个数更新}}
运行一下:
⑦find查找字符或字符串
函数声明
//find函数size_t find(char ch, size_t pos = 0); //查找字符size_t find(const char* sub, size_t pos = 0); //查找字符串
函数定义
//从pos位置开始,查找字符size_t string::find(char ch, size_t pos) {for (int i = pos; i < _size; i++) {if (_str[i] == ch) {return i;}}return npos;}//从pos位置开始,查找字符串size_t string::find(const char* sub, size_t pos) {char* p = strstr(_str + pos, sub);return p - _str;//返回的就是下标}
(1)查找字符:我们给了缺省值npos,如果不赋值,默认从起始位置开始找,遍历数组;若找到目标字符,则返回下标;若找不到,则返回npos
(2)查找目标字符串:我们调用字符串函数strstr,如果找到返回目标字符串起始位置的指针;若没找到,则返回空指针。我们再加一个判断条件,如果不为空,指针相减即为目标字符串起始位置的下标(第一个元素的下标);若为空,则找不到,返回npos。
因此,我们还可以把查找字符串函数再改进一下:
//从pos位置开始,查找字符串size_t string::find(const char* sub, size_t pos) {//p指针表示找到目标字符串的地址char* p = strstr(_str + pos, sub);//如果p指针存在,直接返回目标字符串的下标if (p) {return p - _str;}else {//如果p指针不存在,返回nposreturn npos;}}
运行一下
⑧substr函数
substr函数用于从字符串中提取一个子字符串
函数声明
//substr函数string substr(size_t pos = 0, size_t len = npos);
函数定义
//substr函数//定义方法1:string string::substr(size_t pos, size_t len) {string sub;if (len == npos || len >= _size - pos) {for (size_t i = pos; i < _size; i++) {sub += _str[i];}}else {for (size_t i = pos; i < pos + len; i++) {sub += _str[i];}}return sub;}
我们直接在新的sub字符串尾插字符
还有另外一种定义方法:
//substr函数//定义方法2:string string::substr(size_t pos, size_t len) {if (len == npos || len >= _size - pos) {string sub(_str + pos);return sub;}else {string sub;sub.reserve(len);//至少开len个字符的空间for (int i = 0; i < len; i++) //执行拷贝的次数{sub += _str[pos + i];//从pos位置开始,拷贝len个字符}return sub;}}
⑨swap函数
函数声明
//swap函数void swap(string& s);
函数定义
//swap函数void string::swap(string& s) {//使用std库里面的swap函数std::swap(_str, s._str); std::swap(_capacity, s._capacity);std::swap(_size, s._size);}
运行一下
六、重载函数
①operator=赋值函数
函数声明
//operator=赋值函数string& operator=(const string& s);
函数定义
//operator=赋值函数string& string::operator=(const string& s) {char* temp = new char[s._capacity + 1]; //多开一个空间,存放'\0'strcpy(temp, s._str); //数据拷贝delete[] _str; //释放原来的空间_str = temp; //将指针指向新地址_size = s._size; //_size更新_capacity = s._capacity; //_capacity更新return *this; //返回*this}
运行一下
但是如果是自己赋值给自己,那么就不需要释放原来的旧空间。因此,我们需要将代码改进
//operator=赋值函数string& string::operator=(const string& s) {if (this != &s) //如果不是自身赋值{char* temp = new char[s._capacity + 1]; //多开一个空间,存放'\0'strcpy(temp, s._str); //数据拷贝delete[] _str; //释放原来的空间_str = temp; //将指针指向新地址_size = s._size; //_size更新_capacity = s._capacity; //_capacity更新}return *this; //返回*this}
}
开辟一块新空间,将原内容拷贝到新空间中并释放原来的空间,然后更改指针指针指向和成员变量,最后返回*this
②operator==等几个比较函数
函数声明
//比较函数bool operator<(const string& s)const;bool operator<=(const string& s)const;bool operator>(const string& s)const;bool operator>=(const string& s)const;bool operator==(const string& s)const;bool operator!=(const string& s)const;
函数定义
//比较2个字符串元素的ASCII码值bool string::operator<(const string& s)const {return strcmp(_str, s._str) < 0;}bool string::operator==(const string& s)const {return strcmp(_str, s._str) == 0;}
我们可以先写出2个简单的函数,剩下的情况可以复用上面的函数
其余的函数如下:
//比较函数bool string::operator<=(const string& s)const {return *this < s || *this == s;}bool string::operator>(const string& s)const {return !(*this <= s);}bool string::operator>=(const string& s)const {return !(*this < s);}bool string::operator!=(const string& s)const {return !(*this == s);}
③流插入和流提取
注意:流插入和流提取不能写在string类里面。比如:cout<<d1,这样只能传递2个参数,如果放到了成员函数,就会多一个this指针,这个参数就不方便传递。因此,我们把它写在类外面。
函数声明
//流插入和流提取istream& operator>>(istream& is, string& str);ostream& operator<<(ostream& os, const string& str);
函数定义
//流提取ostream& operator<<(ostream& os, const string& str) {for (size_t i = 0; i < str.size(); i++) {os << str[i];}return os;}
我们不需要将上述函数设置为友元,因为我们没有访问成员变量,是通过迭代器来实现的
//流插入//字符串清理void string::clear() {_str[0] = '\0';_size = 0;}istream& operator>>(istream& is, string& str) {str.clear();char ch;ch = is.get();char buff[128]; //缓冲数组size_t i = 0;while (ch != ' ' && ch != '\n') //读字符读不到空格{buff[i++] = ch;if (i == 127) {buff[127] = '\0';str += buff;i = 0;}//s += ch;ch = is.get();}if (i > 0) {buff[i] = '\0';str += buff;}return is;}
输入是对内容的覆盖,所以我们首先实现一个clear函数来清空字符串,原理很简单:
//字符串清理void string::clear() {_str[0] = '\0';_size = 0;}
只需要修改_size并设置'\0'即可
注意,我们cin输入是读不到空格和换行符的。
其中,ch = is.get();从输入流中读取单个字符
我们这里创造了一个缓冲数组char buff[128];,来简单进行讲解一下:
这个数组功能就是存储字符的内容,如果数组满了,则直接进行+=将整个数组内容尾插到字符串中,并更新索引,其主要特点就是,避免了我们自己为字符串s不断扩容降低了效率,并且可能会造成空间的浪费
与cin不同的是,getline可以读取空格,所以我们只需要修改循环条件即可
istream& getline(istream& in, string& s) {s.clear();char ch;ch = in.get();char buff[128];size_t i = 0;while (ch != '\0') {buff[i++] = ch;if (i == 127) {buff[127] = '\0';s += buff;i = 0;}//s += ch;ch = in.get();}if (i > 0) {buff[i] = '\0';s += buff;}return in;}
七、完整代码
string.h文件
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
#include<string.h>
#include<assert.h>namespace bit
{class string{public:typedef char* iterator;//将char*重命名为iteratortypedef const char* const_iterator;//非const版本的iteratoriterator begin(); //提供iterator begin()函数iterator end(); //提供iterator end()函数//const版本的iteratorconst_iterator begin() const; const_iterator end() const;//string();//无参构造string(const char* str = ""); //全缺省的构造函数~string(); //析构函数string(const string& s); //拷贝构造函数const char* c_str() const; //c_str函数size_t size() const; //size()函数size_t capacity() const; //capacity()函数//非const版本char& operator[](size_t pos); //operator[]函数//const版本const char& operator[](size_t pos)const;//预留空间void reserve(size_t n);//尾插一个字符void push_back(char ch);//尾插一个字符串void append(const char* str);//operator+=函数可以构成重载,函数名相同,参数不同string& operator+=(char ch);string& operator +=(const char* str);//insert函数void insert(size_t pos, char ch);//插入字符void insert(size_t pos, const char* str);//插入字符串//erase函数void erase(size_t pos = 0, size_t len = npos);//find函数size_t find(char ch, size_t pos = 0); //查找字符size_t find(const char* sub, size_t pos = 0); //查找字符串//substr函数string substr(size_t pos = 0, size_t len = npos);//operator=赋值函数string& operator=(const string& s);//swap函数void swap(string& s1);//比较函数bool operator<(const string& s)const;bool operator<=(const string& s)const;bool operator>(const string& s)const;bool operator>=(const string& s)const;bool operator==(const string& s)const;bool operator!=(const string& s)const;//字符串清理void clear();private:char* _str;size_t _size;size_t _capacity;//静态成员变量npos在类里面声明const static size_t npos;};//流插入和流提取istream& operator>>(istream& is, string& str);ostream& operator<<(ostream& os, const string& str);//getline函数模拟流输入istream& getline(istream& in, string& s);
}
string.cpp文件
#include"string.h"
namespace bit {//静态成员变量npos在类外定义const size_t string::npos = -1;//指定类域,在类里面声明,在类外面定义//begin()函数返回的是第一个元素string::iterator string::begin() {return _str;}//end()函数返回的是最后一个元素的下一个位置string::iterator string::end() {return _str + _size;}string::const_iterator string::begin() const {return _str;}string::const_iterator string::end() const {return _str + _size;}//无参构造
//string::string() {
// //_str = new char('\0');
// _str = new char[1]{'\0'};
// _size = 0;
// _capacity = 0;
//} //构造函数string::string(const char* str)//将_size放在初始化列表里面:_size(strlen(str)){//_str和_capacity放到函数体里面_str = (new char[_size + 1]);_capacity = _size;strcpy(_str, str);}//析构函数string::~string() {delete[] _str;_str = nullptr;_size = _capacity = 0;}//拷贝构造函数string::string(const string& s) {_str = new char[s._capacity + 1]; //多开1个空间,存放'\0'strcpy(_str, s._str); //拷贝数据_capacity = s._capacity; //设置容量_size = s._size; //设置有效数据的个数}//c_str函数const char* string::c_str() const {return _str;}//size()函数size_t string::size() const {return _size;}//capacity()函数size_t string::capacity() const {return _capacity;}//operator[]函数char& string::operator[](size_t pos) {assert(pos < _size);//严格控制pos的有效区间return _str[pos];}//const版本const char& string::operator[](size_t pos)const {assert(pos < _size);return _str[pos];}void string::reserve(size_t n) {//如果n大于当前的_capacity,需要扩容if (n > _capacity) {//开n+1个空间,多开1个空间预留给'\0'//'\0'是不包括在_capacity里面的char* temp = new char[n + 1];strcpy(temp, _str); //拷贝数据delete[] _str; //释放旧空间_str = temp; //将新的地址赋给_str_capacity = n; //_capacity为n,代表n个有效数据}}//尾插一个字符void string::push_back(char ch) {如果_capacity == _size,说明空间为0或者空间满了,需要扩容//if (_capacity == _size) {// size_t newCapacity = _capacity == 0 ? 4 : 2 * _capacity;// reserve(newCapacity);//}//_str[_size] = ch; //新的字符ch插入到原来存放'\0'的位置//_str[_size + 1] = '\0'; //'\0'就存放到下一个位置//_size++; //_size更新insert(_size, ch); //在_size位置,插入一个字符ch}//尾插一个字符串void string::append(const char* str) {获取str新字符串的长度//size_t len = strlen(str);如果_size+len大于原有的capacity,扩容//if (_size + len > _capacity) {// reserve(_size + len);//}strcat(_str, str);//在原来的基础上,尾插str新字符串//strcpy(_str+_size, str);//自定义起始位置,从'\0'的位置开始//_size += len; //_size更新insert(_size, str);//在_size位置,插入一个字符串}string& string::operator+=(char ch) {push_back(ch);//调用push_back函数return *this;}string& string::operator +=(const char* str) {append(str);//调用append函数return *this;}//insert函数void string::insert(size_t pos, char ch) {//严格控制pos的取值范围,避免越界assert(pos <= _size);//如果_capacity和_size相等,需要扩容if (_capacity == _size) {size_t newCapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(newCapacity);}end从'\0'的位置开始//int end = _size;将pos强转成int,避免类型不同//while (end >= (int)pos) {// _str[end + 1] = _str[end];// //最后一次: pos+1 = pos;// --end;// //每执行完一次,end向前挪动一位//}size_t end = _size + 1; //end的起始位置是_size的下一个位置while (end > pos) //end>pos位置继续,end==pos就结束{_str[end] = _str[end - 1]; //最后一次: 下标为0的元素挪到下标为1的位置end--;}_str[pos] = ch;//将pos位置放入ch字符++_size; //_size更新}void string::insert(size_t pos, const char* str) {//严格控制pos的取值范围,避免越界assert(pos <= _size);int len = strlen(str); //新字符串的长度(不包括'\0')if (_size + len > _capacity) //检查是否需要扩容{reserve(_size + len);}//int end = _size; //end的起始位置_size//while (end >= (int)pos) //判断end是否满足循环条件//{// _str[end + len] = _str[end];// end--;//}int end = _size + len; //将end设定为_size+lenwhile (end > pos + len - 1) //判断end是否满足循环条件{_str[end] = _str[end - len];end--;}memcpy(_str + pos, str, len); //使用memcpy函数,拷贝len个字符,不包括'\0'_size += len; //_size更新}//erase函数void string::erase(size_t pos, size_t len) {assert(pos < _size); //严格控制pos的有效区间//len大于后面字符个数时,有多少删多少if (len == npos || len >= _size - pos) {_str[pos] = '\0'; //将pos的位置改为'\0'_size = pos; //有效元素的个数为pos个}else{//len小于后面的字符个数strcpy(_str + pos, _str + pos + len);//将后面的字符拷贝到pos位置_size -= len; //有效字符个数更新}}//find函数//从pos位置开始,查找字符size_t string::find(char ch, size_t pos) {for (int i = pos; i < _size; i++) {if (_str[i] == ch) {return i;}}return npos;}//从pos位置开始,查找字符串size_t string::find(const char* sub, size_t pos) {//char* p = strstr(_str + pos, sub);//return p - _str;//返回的就是下标//p指针表示找到目标字符串的地址char* p = strstr(_str + pos, sub);//如果p指针存在,直接返回目标字符串的下标if (p) {return p - _str;}else {//如果p指针不存在,返回nposreturn npos;}}//substr函数string string::substr(size_t pos, size_t len) {string sub;if (len == npos || len >= _size - pos) {for (size_t i = pos; i < _size; i++) {sub += _str[i];}}else {for (size_t i = pos; i < pos + len; i++) {sub += _str[i];}}return sub;}//substr函数//string string::substr(size_t pos, size_t len) {// if (len == npos || len >= _size - pos) {// string sub(_str + pos);// return sub;// }// else {// string sub;// sub.reserve(len);//至少开len个字符的空间// for (int i = 0; i < len; i++) //执行拷贝的次数// {// sub += _str[pos + i];//从pos位置开始,拷贝len个字符// }// return sub;// }//}//operator=赋值函数string& string::operator=(const string& s) {if (this != &s) //如果不是自身赋值{char* temp = new char[s._capacity + 1]; //多开一个空间,存放'\0'strcpy(temp, s._str); //数据拷贝delete[] _str; //释放原来的空间_str = temp; //将指针指向新地址_size = s._size; //_size更新_capacity = s._capacity; //_capacity更新}return *this; //返回*this}//swap函数void string::swap(string& s) {//使用std库里面的swap函数std::swap(_str, s._str); std::swap(_capacity, s._capacity);std::swap(_size, s._size);}//比较函数bool string::operator<(const string& s)const {return strcmp(_str, s._str) < 0;}bool string::operator<=(const string& s)const {return *this < s || *this == s;}bool string::operator>(const string& s)const {return !(*this <= s);}bool string::operator>=(const string& s)const {return !(*this < s);}bool string::operator==(const string& s)const {return strcmp(_str, s._str) == 0;}bool string::operator!=(const string& s)const {return !(*this == s);}//字符串清理void string::clear() {_str[0] = '\0';_size = 0;}//流插入istream& operator>>(istream& is, string& str) {str.clear();char ch;ch = is.get();char buff[128]; //缓冲数组size_t i = 0;while (ch != ' ' && ch != '\n') //读字符读不到空格{buff[i++] = ch;if (i == 127) {buff[127] = '\0';str += buff;i = 0;}//s += ch;ch = is.get();}if (i > 0) {buff[i] = '\0';str += buff;}return is;}//流提取ostream& operator<<(ostream& os, const string& str) {for (size_t i = 0; i < str.size(); i++) {os << str[i];}return os;}istream& getline(istream& in, string& s) {s.clear();char ch;ch = in.get();char buff[128];size_t i = 0;while (ch != '\0') {buff[i++] = ch;if (i == 127) {buff[127] = '\0';s += buff;i = 0;}//s += ch;ch = in.get();}if (i > 0) {buff[i] = '\0';s += buff;}return in;}}
片尾
今天,在上一篇的基础上,我们深入学习了string类函数的模拟实现,希望看完这篇文章能对友友们有所帮助!!!
求点赞收藏加关注!!!
谢谢大家!!!