石岩附近做网站公司,长春网站优化团队,网站推广工具网络,公司网站建设工作重点文章目录 前言一、string类各函数接口总览二、默认构造函数string(const char* str );string(const string str);传统拷贝写法现代拷贝写法 string operator(const string str);传统赋值构造现代赋值构造 ~string(); 三、迭代器相关函数begin );string(const string str);传统拷贝写法现代拷贝写法 string operator(const string str);传统赋值构造现代赋值构造 ~string(); 三、迭代器相关函数begin end 四、容量和大小相关函数size capacityreserveresizeempty 五、修改字符串相关函数c_strpush_backappendoperatorinserteraseclearswapsubstr 六、访问字符串相关函数operator[ ]find 七、关系运算符重载函数八、 流插入与流提取流插入流提取getline 总结 前言 string类的模拟实现源代码 我好像把string类的模拟实现给遗漏了 没关系我们现在来补 一、string类各函数接口总览 同样我们先来简单看下我们要实现的接口另外为了避免跟库里面的string发生冲突我们要用自己的命名空间包起来
namespace HQ
{class string{public:typedef char* iterator;typedef const char* const_iterator;iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;// string(); // 无参和带参往往可以合成同一个string(const char* str );string(const string str);string operator(const string str);~string();const char* c_str() const;size_t size() const;char operator[](size_t pos);const char operator[](size_t pos) const;void reserve(size_t n 0);void push_back(char ch);void append(const char* str);string operator(char ch);string operator(const char* str);void insert(size_t pos, char ch);void insert(size_t pos, const char* str);void erase(size_t pos 0, size_t len npos);size_t find(char ch, size_t pos 0);size_t find(const char* str, size_t pos 0);void swap(string str); // string自己实现一个std里的代价极大string substr(size_t pos 0, size_t len npos);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 _buff[16];char* _str nullptr;size_t _size 0;size_t _capacity 0;const static size_t npos;};istream operator (istream is, string str);ostream operator (ostream os, const string str);
}不要害怕跟着我一步一步来看
二、默认构造函数
string(const char* str “”);
我们设置缺省函数可是我们试想一下缺省值给nullptr合理吗 显然不合理因为成员变量应该无论如何要先赋值个\0即默认构造为空字符串而没有空格就自带一个\0 string::string(const char* str) // 缺省值声明和定义分离:_size(strlen(str)) // 不算\0且字符串大小才用初始化列表来初始化这是顺序的原因
{// 三个strlen效率低用_size来初始化_str new char[_size 1]; // 为存储字符串开辟空间多开一个用于存放\0_capacity _size; // 不算\0strcpy(_str, str); // 将C字符串拷贝到已开好的空间
}string(const string str);
拷贝构造在实现之前我们再来回顾一下深拷贝和浅拷贝的定义 浅拷贝拷贝出来的目标对象的指针和源对象的指针指向的内存空间是同一块空间。其中一个对象的改动会对另一个对象造成影响。 深拷贝深拷贝是指源对象与拷贝对象互相独立。其中任何一个对象的改动不会对另外一个对象造成影响 很明显我们并不希望拷贝出来的两个对象之间存在相互影响因此我们这里需要用到深拷贝。下面提供深拷贝的两种写法
传统拷贝写法 先开辟一块足以容纳源对象字符串的空间然后将源对象的字符串拷贝过去接着把源对象的其他成员变量也赋值过去即可。因为拷贝对象的_str与源对象的_str指向的并不是同一块空间所以拷贝出来的对象与源对象是互相独立的
string::string(const string str):_str(new char[str._capacity 1]),_size(str._size),_capacity(str._capacity)
{strcpy(_str, str._str);
}现代拷贝写法 现代写法与传统写法的思想不同先根据源字符串的C字符串调用构造函数构造一个tmp对象然后再将tmp对象与拷贝对象的数据交换即可。拷贝对象的_str与源对象的_str指向的也不是同一块空间是互相独立的
string::string(const string s)
{string tmp(s._str);swap(tmp);
}string operator(const string str);
赋值运算符重载与拷贝构造函数类似赋值运算符重载函数的模拟实现也涉及深浅拷贝问题我们同样需要采用深拷贝
传统赋值构造
传统写法与拷贝构造函数的传统写法几乎相同只是左值的_str在开辟新空间之前需要先将原来的空间释放掉并且在进行操作之前还需判断是否是自己给自己赋值若是自己给自己赋值则无需进行任何操作
string string::operator(const string str)
{// 考虑到两个字符串的长度可能不太一样// 干脆直接销毁旧的新开一个拷贝过去if (this ! str) {char* tmp new char[str._capacity 1];strcpy(tmp, str._str);delete[] _str;_str tmp;_size str._size;_capacity str._capacity;}return *this;
}现代赋值构造
通过采用“值传递”接收右值的方法让编译器自动调用拷贝构造函数然后我们再将拷贝出来的对象与左值进行交换即可但是这里为了避免自己给自己赋值我们还是选择引用传值在内部在拷贝构造一个临时字符串用来交换
string string::operator(const string str)
{if (this ! str) // 防止自己给自己赋值{string tmp(str); // 用s拷贝构造出对象tmpswap(tmp); // 交换这两个对象}return *this; // 返回左值支持连续赋值
}~string();
string类的析构函数需要我们进行编写因为每个string对象中的成员_str都指向堆区的一块空间当对象销毁时堆区对应的空间并不会自动销毁为了避免内存泄漏我们需要使用delete手动释放堆区的空间
string::~string()
{delete[] _str; // 不会产生矛盾,就算只有一个底层也是调用delete _str;_str nullptr;_size _capacity 0;
}三、迭代器相关函数 string类中的迭代器实际上就是字符指针只是给字符指针起了一个别名叫iterator而已 注不是所有的迭代器都是指针 typedef char* iterator; typedef const char* const_iterator; begin end
begin函数的作用就是返回字符串中第一个字符的地址 end函数的作用就是返回字符串中最后一个字符的后一个字符的地址即’\0’的地址
string::iterator string::begin()
{return _str;
} string::iterator string::end()
{return _str _size;
}string::const_iterator string::begin() const
{return _str;
}string::const_iterator string::end() const
{return _str _size;
}四、容量和大小相关函数
size capacity
因为string类的成员变量是私有的我们并不能直接对其进行访问所以string类设置了size和capacity这两个成员函数用于获取string对象的大小和容量 size函数用于获取字符串当前的有效长度不包括’\0’ capacity函数用于获取字符串当前的容量不包括’\0’ size_t string::size() const
{return _size;
}size_t string::capacity() const
{return _capacity;
}reserve
其规则
当n大于对象当前的capacity时将capacity扩大到n或大于n当n小于对象当前的capacity时什么也不做
代码中使用strncpy进行拷贝对象C字符串而不是strcpy是为了防止对象的C字符串中含有有效字符’\0’而无法拷贝strcpy拷贝到第一个’\0’就结束拷贝了
void string::reserve(size_t n)
{if (n _capacity){char* tmp new char[n 1];strncpy(tmp, _str, _size 1); // 将对象原本的C字符串拷贝过来包括\0delete[] _str;_str tmp;_capacity n;}
}resize
其规则
当n大于当前的size时将size扩大到n扩大的字符为ch若ch未给出则默认为’\0’当n小于当前的size时将size缩小到n
void string::resize(size_t n, char ch \0)
{if (n _size) // n小于当前size{_size n; // 将size调整为n_str[_size] \0; // 在size个字符后放上\0}else if (n _capacity){reserve(n); // 扩容for (size_t i _size; i n; i) // 将size扩大到n扩大的字符为ch{_str[i] ch;}_size n; // size更新_str[_size] \0; // 字符串后面放上\0}
}empty
empty是string的判空函数
bool string::empty() const
{return _size 0;
}五、修改字符串相关函数
c_str
按照C语言的格式返回字符串
const char* string::c_str() const
{return _str;
}push_back
push_back函数的作用就是在当前字符串的后面尾插上一个字符尾插之前首先需要判断是否需要增容若需要则调用reserve函数进行增容然后再尾插字符注意尾插完字符后需要在该字符的后方设置上’\0’否则打印字符串的时候会出现非法访问因为尾插的字符后方不一定就是’\0’
void string::push_back(char ch)
{if (_size _capacity){reserve(_capacity 0 ? 4 : _capacity * 2);}_str[_size] ch;_size;_str[_size] \0;
}append
append函数的作用是在当前字符串的后面尾插一个字符串尾插前需要判断当前字符串的空间能否容纳下尾插后的字符串若不能则需要先进行增容然后再将待尾插的字符串尾插到对象的后方因为待尾插的字符串后方自身带有’\0’所以我们无需再在后方设置’\0’
void string::append(const char* str)
{size_t len strlen(str);if (_size len _capacity){// 大于2倍需要多少开多少小于2倍按2倍扩reserve(_size len 2 * _capacity ? _size len : 2 * _capacity);}strcpy(_str _size, str);_size len;
}operator 有三个重载 string operator(const string str); string operator(const char* s); string operator(char c); string string::operator(char ch)
{push_back(ch);return *this;
}string string::operator(const char* str)
{append(str);return *this;
}string string::operator(const string str)
{append(str.c_str());return *this;
}insert
insert函数的作用是在字符串的任意位置插入字符或是字符串
// 插入字符注意end不会为-1
void string::insert(size_t pos, char ch)
{assert(pos _size);// 谨慎使用if (_size _capacity){size_t newCapacity _capacity 0 ? 4 : _capacity * 2;reserve(newCapacity);}size_t end _size;while (end pos) {_str[end 1] _str[end];if (end 0) break; // end -1 - err--end;}_str[pos] ch;_size;
}insert函数用于插入字符时首先需要判断pos的合法性若不合法则无法进行操作紧接着还需判断当前对象能否容纳插入字符后的字符串若不能则还需调用reserve函数进行扩容。插入字符的过程也是比较简单的先将pos位置及其后面的字符统一向后挪动一位给待插入的字符留出位置然后将字符插入字符串即可
void string::insert(size_t pos, const char* str)
{assert(pos _size);size_t len strlen(str);reserve(_size len);size_t end _size;while (end pos) {_str[end len] _str[end];if (end 0) break; // end -1 - err--end;}memcpy(_str pos, str, len);_size len;
}insert函数用于插入字符串时首先也是判断pos的合法性若不合法则无法进行操作再判断当前对象能否容纳插入该字符串后的字符串若不能则还需调用reserve函数进行扩容插入字符串时先将pos位置及其后面的字符统一向后挪动len位len为待插入字符串的长度给待插入的字符串留出位置然后将其插入字符串即可
erase
先来关注函数原型 默认从0位置开始一直清楚到末尾 void erase(size_t pos 0, size_t len npos); erase函数的作用是删除字符串任意位置开始的n个字符。删除字符前也需要判断pos的合法性这时候一共有两种情况
pos位置及其之后的有效字符都需要被删除
这时我们只需在pos位置放上’\0’然后将对象的size更新即可
pos位置及其之后的有效字符只需删除一部分
这时我们可以用后方需要保留的有效字符覆盖前方需要删除的有效字符此时不用在字符串后方加’\0’因为在此之前字符串末尾就有’\0’了
void string::erase(size_t pos, size_t len)
{assert(pos _size);// 当len npos时候条件判断一定成立if (len _size - pos) {// pos后(含)全删完_str[pos] \0;_size pos;}else {strcpy(_str pos, _str pos len);_size - len;}
}clear
clear函数用于将对象中存储的字符串置空实现时直接将对象的_size置空然后在字符串后面放上’\0’即可
void string::clear()
{_str[0] \0;_size 0;
}swap
swap函数用于交换两个对象的数据直接调用库里的swap模板函数将对象的各个成员变量进行交换即可
void string::swap(string str)
{std::swap(_str, str._str);std::swap(_size, str._size);std::swap(_capacity, str._capacity);
}substr
substr功能是返回从指定位置开始len长度的字符串先创建string空对象用于接收截取字符串当len npos 或len _size - pos代表了从pos位置到尾的字符串截取并且尽量书写 len _size - pos 而不是 len pos _size 这种是为了防止 len pos 超过类型最大值范围
string string::substr(size_t pos, size_t len)
{if (len _size - pos) {string sub(_str pos);return sub;}else {string sub;sub.reserve(len);for (size_t i pos; i pos len ; i) {sub _str[i];}sub._size len;return sub;}
}六、访问字符串相关函数
operator[ ]
[ ]运算符的重载是为了让string对象能像C字符串一样通过[ ] 下标的方式获取字符串对应位置的字符 这里要有两种版本一种可读可写一种只读不写
char string::operator[](size_t pos)
{assert(pos _size);return _str[pos];
}const char string::operator[](size_t pos) const
{assert(pos _size);return _str[pos];
}find
实现这两种重载 size_t find(char ch, size_t pos 0); size_t find(const char* str, size_t pos 0); // 正向查找第一个匹配的字符
size_t string::find(char ch, size_t pos)
{assert(pos _size); //检测下标的合法性for (size_t i pos; i _size; i) {if (_str[i] ch) {return i;}}return npos;
}// 正向查找第一个匹配的字符串
size_t string::find(const char* sub, size_t pos)
{assert(pos _size); //检测下标的合法性char* p strstr(_str pos, sub);return p NULL ? npos : p - _str;
}对于第一种首先判断所给pos的合法性然后通过遍历的方式从pos位置开始向后寻找目标字符若找到则返回其下标若没有找到则返回npos。npos是string类的一个静态成员变量其值为整型最大值 对于第二种首先也是先判断所给pos的合法性然后我们可以通过调用strstr函数进行查找。strstr函数若是找到了目标字符串会返回字符串的起始位置若是没有找到会返回一个C语言空指针NULL若是找到了目标字符串我们可以通过计算目标字符串的起始位置和对象C字符串的起始位置的差值进而得到目标字符串起始位置的下标
七、关系运算符重载函数 关系运算符有 、、、、、! 这六个但是对于C中任意一个类的关系运算符重载我们均只需重载其中的两个剩下的四个关系运算符可以通过复用已经重载好了的两个关系运算符来实现
bool string::operator(const string s) const
{return strcmp(_str, s._str) 0;
}bool string::operator(const string s) const
{return !(*this s);
}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 strcmp(_str, s._str) 0;
}bool string::operator!(const string s) const
{return !(*this s);
}八、 流插入与流提取
流插入
重载运算符是为了让string对象能够像内置类型一样使用运算符直接输入 输入前我们需要先将对象的C字符串置空然后从标准输入流读取字符直到读取到’ ‘或是’\n’便停止读取 另外还有很明显得重载为全局函数
istream operator(istream is, string str)
{str.clear(); // 要覆盖先前的内容先清除一下char ch is.get();//is ch; // 拿不到空格和换行while (ch ! ch ! \n) {str ch;ch is.get();}return is;
}流提取
重载运算符是为了让string对象能够像内置类型一样使用运算符直接输出打印。实现时我们可以直接使用范围for对对象进行遍历即可
ostream operator(ostream os, const string str)
{for (size_t i 0; i str.size(); i) {os str[i];}return os;
}getline
getline函数用于读取一行含有空格的字符串。实现时于运算符的重载基本相同只是当读取到’\n’的时候才停止读取字符
// 举个例子哈
int main()
{string str;cin str; // 假设输入hello worldcout str; // 只会输出helloreturn 0;
}如上我们会发现空格字符无法被插入str这时候就是getline发挥的时候了 再来道具体的题目说不定能让你有更深的认识 总结 总算是补上了可以看出string类的完整实现还是蛮复杂的