wordpress搜索引擎,温州seo品牌优化软件,凡客诚品官方在哪个网店,建设工程合同甲方模拟实现string类 一.命名空间与类成员变量二.构造函数1.无参#xff08;默认#xff09;构造2.有参构造3.兼容无参和有参构造4.拷贝构造1.传统写法2.现代写法 三.析构函数四.string类对象的容量操作1.size2.capacity3.clear4.empty5.reserve6.resize 五.string类对象的访问及… 模拟实现string类 一.命名空间与类成员变量二.构造函数1.无参默认构造2.有参构造3.兼容无参和有参构造4.拷贝构造1.传统写法2.现代写法 三.析构函数四.string类对象的容量操作1.size2.capacity3.clear4.empty5.reserve6.resize 五.string类对象的访问及遍历操作1.operator[]2.实现迭代器beginend 六.string类对象的增删查改操作1.operator1.传统写法2.现代写法 2.push_back3.append4.operator5.insert6.erase7.find8.substr9.c_str10.swap 七.非成员函数1.string比较函数2.流插入与流提取3.getline 一.命名空间与类成员变量 根据string的结构显然可知string实质就是字符数组但有一点区别就是string可以扩容再类比动态顺序表就不难得出string的成员变量。在模拟实现string时为了与C标准库中的string作区分可以给定命名空间。
成员变量
char* str指向string第一个字符的指针。size_t sizestring中有效数据的个数。size_t capacitystring可以存放有效数据的容量。static const size_t npos静态成员。
大体结构如下
namespace xzy
{class string{private:char* _str nullptr;size_t _size 0;size_t _capacity 0;static const size_t npos; //静态成员类内声明};const size_t string::npos -1; //类外初始化
}二.构造函数
class string
{
public:string():_str(nullptr),_size(0),_capacity(0){}string(const char* str):_size(strlen(str)), _capacity(_size),_str(new char[_capacity 1]){}const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity;
};第一种由于将_str初始化为nullptr通过C语言中的返回_str直到遇到’\0’停止打印字符串的方法而_str为nullptr打印nullptr导致程序崩溃。第二种看似程序正常但真的是正确的吗其实初始化列表出现的顺序并不是初始化的顺序而是按照成员变量声明的顺序初始化成员变量先初始化_str而_capacity是随机值导致开辟的空间不确定导致出现错误。
正确的方法如下
1.无参默认构造 由于string默认含有’\0’可以提前开辟一个’\0’而’\0’不是有效的数据也不算入容量之中。
string():_str(new char[1]{\0}), _size(0), _capacity(0)
{}2.有参构造 注意容量中不包含’\0’而string中有包含’\0’所以在开辟空间时要加上一个’\0’的空间。
string(const char* str)
{_size strlen(str);_capacity _size;_str new char[_capacity 1];strcpy(_str, str);
}3.兼容无参和有参构造
string(const char* str )
{_size strlen(str);_capacity _size;_str new char[_capacity 1];strcpy(_str, str);
}不传参时用缺省值str为空的常量字符串strlen(str)为0且sizeof(str)为1含有一个隐藏的’\0’刚好满足无参构造。传参时就用实参满足有参构造。
4.拷贝构造
string(const string str)
{_str str._str;_size str._size;_capacity str._capacity;
}
int main()
{xzy::string s1;xzy::string s2(s1);return 0;
}分析当我们未提供拷贝构造时编译器会提供拷贝构造进行简单的值拷贝浅拷贝正如以上代码。但是存在很大的漏洞s1的_str与s2的_str指向堆区同一块空间程序结束时分别调用各自的析构函数从而对同一块空间释放两次这是未定义行为导致程序崩溃。
1.传统写法
思路先开空间再利用strcpy拷贝最后修改有效数据大小与容量。
string(const string str)
{_str new char[str._capacity 1];strcpy(_str, str._str);_size str._size;_capacity str._capacity;
}2.现代写法
构造一个临时对象进行交换。
void swap(string str)
{std::swap(_str, str._str);std::swap(_size, str._size);std::swap(_capacity, str._capacity);
}string(const string str)
{string tmp(str._str);swap(tmp);
}注意由于没有初始化列表不确定s2_str被初始化为nullptr取决于编译器可以在类成员变量声明时加上缺省值确保s2.str为nullptr而避免s2._str为随机值交换给tmp变成野指针函数结束时tmp调用析构函数释放不合法的空间导致程序崩溃。
class string
{
private:char* _str nullptr;size_t _size 0;size_t _capacity 0;
};三.析构函数 _str是在堆区开辟的空间要用delete[]释放空间否则造成内存泄漏。
~string()
{delete[] _str;_str nullptr;_size _capacity 0;
}四.string类对象的容量操作
1.size
size_t size() const
{return _size;
}2.capacity
size_t capacity() const
{return _capacity;
}3.clear
void clear()
{_str[0] \0;_size 0;
}4.empty
5.reserve
扩容时先开辟新空间千万记得多开一个空间保存’\0’再将旧空间拷贝到空间释放旧空间修改_str指向新空间最后修改容量。学了Cnew就取代realloc了。
void reserve(size_t n)
{if (n _capacity){char* tmp new char[n 1];strcpy(tmp, _str);delete[] _str;_str tmp;_capacity n;}
}6.resize
修改有效数据的个数时先比较修改后的有效数据与原有数据的大小若小于则修改_size若大于再比较容量与修改后的有效数据的大小判断是否扩容利用memset函数初始化。
void string::resize(size_t n, char c)
{if (n _size){// 如果newSize大于底层空间大小则需要重新开辟空间if (n _capacity){reserve(n);}memset(_str _size, c, n - _size);}_size n;_str[n] \0;
}五.string类对象的访问及遍历操作
1.operator[]
char operator[](int pos)
{assert(pos 0 pos _size);return _str[pos];
}const char operator[](int pos) const
{assert(pos 0 pos _size);return _str[pos];
}提供两个版本的operator[]普通重载[]与const修饰的重载[]。若初始化一个常量字符串时const string s(“123”); 由于存在权放大问题就无法调用普通重载[]而const修饰的重载[]就可以使用。 重载operator[]本质就是函数重载而函数的返回值是不支持函数重载条件的为了让两个operator[]满足函数重载的条件可以const随便修饰一个成员函数。隐藏了this指针实际const修饰的是this所指的对象。 第一个函数的参数列表的第一个位置隐藏了string* const this第二个函数的参数列表的第一个位置隐藏了const string* const this函数的参数不同就满足了函数重载的条件可以共存。
2.实现迭代器beginend
typedef char* iterator;
typedef const char* const_iterator;iterator begin()
{return _str;
}
iterator end()
{return _str _size;
}const_iterator begin() const
{return _str;
}
const_iterator end() const
{return _str _size;
}为了与标准库里的类似重定义char* 为iterator。同理提供两个版本的迭代器iterator与const_iterator。
六.string类对象的增删查改操作
1.operator
注意operator只能写成成员函数不能写成成员函数。
1.传统写法
与传统写法的拷贝构造类似。
string operator(const string str)
{if (this ! str){delete[] _str;_str new char[str._capacity 1];strcpy(_str, str._str);_size str._size;_capacity str._capacity;}return *this;
}注意如果没写 if (this ! str) 自己给自己赋值时delete[] _str 后_str为野指针自己给自己拷贝程序崩溃。
2.现代写法
与现代写法的拷贝构造类似。
string operator(const string str)
{if (this ! str){//string tmp(str.c_str()); //调用构造string tmp(str); //调用拷贝构造swap(tmp); //刚好函数结束时tmp将赋值前的空间释放相当的完美}return *this;
}//更完美的方法一行搞定
string operator(string tmp)
{swap(tmp);return *this;
}2.push_back
尾插时先检查容量再进行尾插。注意最后要补上\0。
void push_back(char ch)
{if (_size _capacity){reserve(_capacity 0 ? 4 : 2 * _capacity);}_str[_size] ch;_size;_str[_size] \0;
}3.append
追加时先要判断容量是否大于有效数据所追加的字符串大小。若小于则无需扩容若大于两倍则需要多少就扩容多少小于两倍就按照两倍扩容。最后拷贝字符串即可。
void append(const char* str)
{size_t len strlen(str);if (_size len _capacity){reserve(_size len 2 * _capacity ? _size len : 2 * _capacity);}strcpy(_str _size, str);_size len;
}4.operator
一个字符直接调用push_back即可。
string operator(char ch)
{push_back(ch);return *this;
}一个字符串直接调用append即可。
string operator(const char* str)
{append(str);return *this;
}5.insert
插入一个字符先检查容量再整体往后挪动一位最后插入即可。
但是存在一些坑如下
当在pos0位置插入字符时end0时进入循环- -end由于end类型为无符号整形size_t则end不是-1而是一个非常大的值进入死循环。就算将end修改为int 循环条件endpos时两边类型不同会进行算数转换int转换成size_tend转换成size_t类型依旧进入死循环。
正确写法
void insert(size_t pos, char ch)
{assert(pos 0 pos _size);if (_size _capacity){reserve(_capacity 0 ? 4 : 2 * _capacity);}//第一种强转size_t为int//int end _size;//while (end (int)pos)//{// _str[end 1] _str[end];// --end;//}//_str[pos] ch;//_size;//推荐这种end始终大于0size_t end _size 1;while (end pos){_str[end] _str[end - 1];--end;}_str[pos] ch;_size;
}插入一个字符串先检查容量再整体往后挪动为插入的字符串预留空间最后插入字符串即可。
void insert(size_t pos, const char* str)
{assert(pos 0 pos _size);size_t len strlen(str);if (_size len _capacity){reserve(_size len 2 * _capacity ? _size len : 2 * _capacity);}//整体后移//memmove(_str len, _str, sizeof(char) * len);size_t end _size len;while (end pos len - 1){_str[end] _str[end - len];--end;}//插入字符串for (size_t i 0; i len; i){_str[pos i] str[i];}_size len;
}6.erase
删除时比较要删除的子串长度与pos及其以后字符串的的大小判断是否pos及其以后得字符全删除。
void erase(size_t pos, size_t len npos
)
{assert(pos 0 pos _size);if (len _size - pos){_str[pos] \0;_size pos;}else{//memmove(_str pos, _str pos len, sizeof(char) * (_size - pos - len 1));for (size_t i pos; i _size - len; i){_str[i] _str[i len];}_size - len;}
}7.find
查找字符找到返回下标未找到返回npos。
size_t find(char ch, size_t pos 0)
{assert(pos 0 pos _size);for (size_t i 0; i _size; i){if (_str[i] ch){return i;}}return npos;
}查找字符串利用C语言接口strstr查找子串函数找到返回下标未找到返回npos。
size_t find(const char* str, size_t pos 0)
{assert(pos 0 pos _size);const char* ptr strstr(_str pos, str);if (ptr nullptr){return npos;}else{return ptr - _str;}
}8.substr
返回子串比较要返回子串长度与pos及其以后字符串的的大小判断是否pos及其以后得字符全返回。
注意深浅拷贝问题由于是返回局部string而局部string出函数被销毁。此时会拷贝构造一个临时string作为返回而默认的拷贝构造是浅拷贝简单的值拷贝局部string销毁时临时变量string中的_str变成野指针外面又拷贝构造接收该临时string本身就是无效的string程序结束前调用析构函数释放空间重复的delete导致程序崩溃。解决方法自己写一个深拷贝构造。
string substr(size_t pos 0, size_t len)
{assert(pos 0 pos _size);if (len _size - pos){len _size - pos;}string sub;sub.reserve(len);for (size_t i 0; i len; i){sub _str[pos i];}return sub;
}9.c_str
返回字符串首字符的地址用于调用C语言接口例如strcpymemmove等。
const char* c_str() const
{return _str;
}10.swap
调用std::swap进行对象值交换。
void swap(string str)
{std::swap(_str, str._str);std::swap(_size, str._size);std::swap(_capacity, str._capacity);
}七.非成员函数
1.string比较函数
只需要利用strcmp函数比较实现两个函数就可以调用实现多个函数。
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);
}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);
}2.流插入与流提取 在C中屏幕和键盘分别通过标准输出流std::cout和标准输入流std::cin来实现数据的流插入输出和流提取输入。以下是针对屏幕输出和键盘输入的流插入与流提取的详细介绍
屏幕输出与流插入operator流插入operator用于将数据发送到输出流中在C中标准输出流std::cout是与屏幕通常是控制台或命令行界面相关联的。当你使用操作符将数据发送到std::cout时数据会被格式化如果需要的话并显示在屏幕上。键盘输入与流提取operator流提取operator用于从输入流中读取数据在C中标准输入流std::cin是与键盘或任何标准输入设备相关联的。当你使用操作符从std::cin中读取数据时它会从键盘获取输入并根据需要将其存储在提供的变量中。
注意
流插入与流提取不推荐写成成员函数例如ostream operator(ostream out); 因为左边是类对象调用时要写成sout非常别扭。不需要写成友元函数可以做到不用访问类内的私有成员完成流插入与流提取。
ostream operator(ostream out, const string str)
{/*string::const_iterator it str.begin();while (it ! str.end()){cout *it;it;}*/for (auto ch : str){out ch;}return out;
}istream operator(istream in, string str)
{str.clear();char ch;//in ch; //错误ch不会提取空白字符陷入死循环ch in.get();while (ch ! ch ! \n){str ch;//in ch;ch in.get();}return in;
}注意流提取cin默认跳过空白字符不会读取空白字符例如空格、换行可以用cin.get()函数从键盘获得空白字符类似C语言中的getc()函数。
优化方法减少扩容临时存放到字符数组中等到满了时再到其中。
istream operator(istream in, string str)
{str.clear();const int N 256;char buff[N];int i 0;char ch;ch in.get();while (ch ! ch ! \n){buff[i] ch;if (i N - 1){buff[i] \0;str buff;i 0;}ch in.get();}if (i 0){buff[i] \0;str buff;}return in;
}3.getline
getline函数可以读取含有空格的字符串将’\n’作为分隔符。
istream getline(istream in, string str)
{str.clear();char ch;ch in.get();while (ch ! \n){str ch;ch in.get();}return in;
}