扬中网站建设流程,专业网站建设方案,做网站怎样套用模板,苏州网络营销推广软件运营一、前言
想要模拟实现一个库中的类#xff0c;那就要首先要熟悉如何使用这个类。建议通过下面博客#xff0c;完成对Cstring类的学习。
C的string类-CSDN博客
二、模拟实现
我们将从string的成员函数即成员变量入手#xff0c;模拟实现string类。
成员变量
string类的…一、前言
想要模拟实现一个库中的类那就要首先要熟悉如何使用这个类。建议通过下面博客完成对Cstring类的学习。
C的string类-CSDN博客
二、模拟实现
我们将从string的成员函数即成员变量入手模拟实现string类。
成员变量
string类的实现并未给出对应的标准因此实现比较多样。为了防止出现命名冲突我们将自己实现的string独自封装到单独的命名空间。下面是string的成员变量的实现。
namespace MyString
{class string{private:char* _a;size_t _size;size_t _capacity;};}
string我们可以看成是由数组、数组大小、数组空间构成的一个类。
成员函数
由于string的成员函数写的十分冗余被业内也大量吐槽因此我们实现几个主要的成员方法也是最常用的几个成员方法。
构造函数
构造函数用来实现函数调用时的初始化需要注意的是在初始化时初始化列表阶段走的顺序不是写入初始化列表的顺序而是成员变量的声明顺序。
需要注意两个个坑点
坑点一const char* 不能去初始化char* 当我们开始实现时发现程序报错了这是为什么呢
这是因为const char*类型的值不能用于初始化char*类型的值。
在C中const char*和char*之间的区别与C语言中的相同const char*是指向常量字符的指针意味着通过这个指针你不能修改所指向的字符。这种类型通常用于指向字符串字面量因为这些字面量在程序中是只读的存储在程序的常量区。
那就意味着我们无法用_a指向str。因此我们需要给_a额外开辟一块空间将str的值拷贝到这块空间。 坑点二初始化列表走的顺序是变量声明的顺序
string::string(const char* str):_a(new char[_capacity 1]),_size(strlen(str)), _capacity(_size 1)
{}
有了上面的经验我们想把_a开辟一块新空间把内容拷贝进去。但是上述的代码也是不正确的
我们出初始化先走的是_a给_a开辟空间的时候_capacity并未初始化因此_capacity 1的大小是未定义的。 正确方法
为了合理的初始化顺序重点观众声明顺序我们采用初始化列表和函数体共同使用的方法去初始化构造函数。
string::string(const char* str):_size(strlen(str)), _capacity(_size)
{_a new char[_capacity 1]; //多开一个存储\0strcpy(_a, str); //strcpy会给dest字符串自动添加\0
}
我们先控制好大小之后再去开辟空间完成对_a的初始化。 优化
先说明一个知识。“” 这个字符串代表的是常量字符串长度是0。
int main()
{cout strlen() endl;return 0;
} 上述给出的构造函数并不是默认构造因为我们没有给出缺省值。下面通过给出一个默认的常量字符串完成初始化。
string::string(const char* str ):_size(strlen(str)) //默认常量字符串长度 0, _capacity(_size)
{_a new char[_capacity 1]; //多开一个存储\0strcpy(_a, str); //strcpy会给dest字符串自动添加\0
}
当我们不给出参数时会使用缺省的长脸字符串_size 0 ; _capacity 0; _a的大小是1给\0预留空间。
通过初始化列表与函数体的结合使用这个函数便实现了默认构造与构造函数的结合。 析构函数
析构函数的任务是完成数据的销毁与资源的释放。在string中需要完成如下。
string::~string()
{_size _capacity 0;delete[] _a;_a nullptr; //需要让指针置空}
c_str
c_str可以返回数组并且返回的类型兼容C语言数组的属性。
const char* string::c_str() const { return _a; } []重载
由于[]存在读和写两种两种需求所以需要写出重载函数来完成读和写的功能。在返回时应尽量采用引用返回。同时应该注意assert去检查给出的下标是否合法。
char string::operator[] (size_t pos)
{assert(pos _size - 1); //pos是size_t所以 0 恒成立return _a[pos];
}const char string::operator[] (size_t pos) const
{assert(pos _size - 1);return _a[pos];
}capacity、size
size_t string::capacity() const
{return _capacity;
}应该用const修饰函数保证const成员与非const成员都可以访问。
size_t string::size() const
{return _size;
}reserve
reserve函数用来完成空间的修正。对_capacity进行修正。一般来说空间大小只增不减因此只有当作新空间大小大于原来空间大小的时候才能进行开辟空间操作。
对于C而言开辟空间我们一般用new。因此reserve函数的实现必然包含以下步骤1.开辟新空间 2.内容拷贝 3.释放旧空间防止泄露
void string::reserve(size_t n) //开辟空间,对_capacity作出修改
{//1.重新开空间 2.深拷贝 3.释放旧空间if (n _capacity){char* tmp new char[n 1];memcpy(tmp, _a, _size 1);delete[] _a;_a tmp;_capacity n; //将空间修正为n}}
push_back
功能是尾插。需要额外注意空间是否还有剩余。在尾插完成之后需要人为添加\0使得C能够兼容C。
void string::push_back(char ch)
{if (_size _capacity) //利用reserve扩容{reserve(_capacity 0 ? 4 : 2 * _capacity);}_a[_size] ch;_size; //_size类比length_a[_size] 0; //补充\0符合C语言的规范
}append
字符串的追加。字符串的追加也需要扩容但是不是简单的二倍扩容这么简单扩容时需要保证扩容后的大小必须可以容纳新的字符串。
在追加时可以用strcpy函数strcpy的特性在代码中有所体现。
void string::append(const char* str) //追加字符串
{size_t len strlen(str);if (len _size _capacity)reserve(len _size);strcpy(_a _size, str); //从_size处完成内容的拷贝/*strcpy 自动添加\0可从任意位置开始拷贝*/_size len;
}operator
完成字符与字符串的追加。返回的*this就是对象
string string::operator(char ch)
{push_back(ch);return *this;
}string string::operator(const char* str)
{append(str);return *this;
} insert
完成任意位置的插入。需要额外注意 1.头插和尾插能不能完成。 2.\0需要完成移动
因此我们直接借助下标end _size1即可完成数据的移动 void string::insert(size_t pos, char ch)
{assert(pos _size); // 是尾插if (_size _capacity) //利用reserve扩容{reserve(_capacity 0 ? 4 : 2 * _capacity);}size_t end _size 1; //保证可以完成头插while (end pos) //后移包含\0{_a[end] _a[end - 1];--end;}_a[pos] ch;_size;//_a[_size] 0; //可有可无\0也发生了后移
}void string::insert(size_t pos, const char* str)
{assert(pos _size); //不需要检查是否0size_t len strlen(str);if (len _size _capacity)reserve(len _size 1); //对this指针对象进行扩容int end _size; //防止出现size_t的死循环while (end (int)pos){_a[end len] _a[end];--end;}strncpy(_a pos, str, len); _size len;}
注意当需要完成字符串的插入时最好用int作为end的类型防止出现死循环同时强转pos类型防止出现提升。 /* strncpy 函数在C语言中用于拷贝字符串。它的原型是
char *strncpy(char *dest, const char *src, size_t n); 这个函数从源字符串 src 拷贝至目标字符串 dest最多拷贝 n 个字符。 如果源字符串的长度小于 nstrncpy 会在目标字符串后面添加额外的空字符 (\0)直到总共拷贝了 n 个字符。 如果源字符串的长度大于或等于 n则不会在目标字符串后面添加空字符。 */ erase
用来完成删除操作。值得一提的是缺省参数在声明时可以给出在定义时不可以给出。 void string::erase(size_t pos, size_t len) //声明给出定义不给出缺省
{assert(pos _size); // _size是\0的位置不能删除if (len npos || pos len _size) //len过长时{_a[pos] 0;_size pos;}else //len的长度合适时{int cur pos len;while (cur (int)_size){_a[pos] _a[cur];}_size - len;}
} clear
Erases the contents of the string, which becomes an empty string (with a length of 0 characters).
清楚内容长度清零。
void string::clear()
{_size 0;_a[0] 0;
}函数重载
只要完成 就可以服用全部重载。 bool string::operator(const string s) const
{return strcmp(_a, s._a) 0; //内部可使用private成员
}bool string::operator(const string s) const
{return strcmp(_a, s._a) 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 !(*this s);
}
迭代器函数
对于迭代器的实现有很多种方式可以采用下标可以采用指针。实现方式没有明确规定我们这里采用指针来实现。
public: typedef char* iterator; //typedef受到访问权限的限制 typedef const char* const_iterator;
在类的内部将迭代器iterator有指针类型实现。typedef收到访问权限的限制。 string::iterator string::begin()
{return _a;
}string::iterator string::end()
{return _a _size; //end返回的是最后一个元素的下一个位置。
}string::const_iterator string::begin() const
{return _a;
}string::const_iterator string::end() const
{return _a _size;
}
需要注意的是end是最后一个元素的下一个位置。
/* 在C中迭代器的end()函数代表的是最后一个元素的下一位置。 它是用来标记容器的超出末端的位置所以在进行遍历等操作时通常会使用这个迭代器来检查是否到达了容器的末尾。 例如如果你有一个 vector 容器并且想要遍历它你可以这样做
std::vectorint vec {1, 2, 3, 4, 5}; for (auto it vec.begin(); it ! vec.end(); it) { std::cout *it std::endl; } 在这个循环中it 会在每次迭代后递增直到它等于 vec.end()这时循环结束。 在循环体中*it 是有效的并且指向当前的元素当 it 达到 vec.end() 时它不再指向任何元素因此不应该被解引用。
*/
赋值重载与拷贝构造
string内部提供了swap函数我们可以借助swap函数进行标题函数的实现。
void string::swap(string s)
{std::swap(_a, s._a);std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}
在swap内部交换三个成员变量。
拷贝构造的实现1.完成初始化 2.swap 一下tmp与*this
//s1(s2)
string::string(const string s) //在进入函数体之前先对this进行初始化:_a(nullptr), _size(0), _capacity(_size){string tmp(s._a);swap(tmp);
}
/* 在C中未初始化的对象是不应该调用析构函数的。 析构函数是用来释放对象所拥有的资源的如果一个对象没有被正确地初始化 它可能没有分配资源或者分配的资源处于未知的状态。 在这种情况下调用析构函数可能会导致未定义行为UB包括程序崩溃或者数据损坏。 */
赋值重载的实现同样借助swap函数。假设S2 S3那我们利用S3生成一个形参swap S2 形参即可。交换完成之后形参出作用域也可以自动销毁。同时为了保证的连续性返回类型应该是string对象类型。
// s2 s3
string string::operator(string tmp) //直接形参接收
{swap(tmp);return *this; //保证返回类型也是string可以连等
}resize
resize用来修改size的大小。可分为两个情况讨论。void string::resize(size_t n, char ch) //更新size
当n _size 和 n _size。分别对应扩大size和缩小size
void string::resize(size_t n, char ch) //更新size
{if (n _size){_size n;_a[_size] 0;}else{reserve(n); //函数内部开辟n 1个空间while (_size n){_a[_size] ch;}_a[_size] 0;}
}find
用来查找内容可以查找字符或者字符串。
查找字符
size_t string::find(char ch, size_t pos) //函数的定义不能有缺省值
{for (size_t i pos; i _size; i){if (_a[i] ch)return i;}return npos;
}查找字符串有两种方法方法一使用strstr函数 size_t string::find(const char* sub, size_t pos)
{const char* ptr strstr(_a pos, sub);if (ptr nullptr)return npos;return ptr - _a;
}方法二自己实现功能的查找
需要遍历str1去找到第一个匹配的字符匹配成功之后后移。
/*
思路遍历原数组匹配往后走。
*/size_t string::find(const char* sub, size_t pos 0)
{assert(pos _size);size_t i pos;for (; i _size; i) //遍历原数组{if (_a[i] sub[0]){size_t len strlen(sub);size_t m i;for (size_t j 0; j len; j) //循环判断{if (m _size || sub[j] ! _a[m]) {break;}m; //后移}if (m - i len) //如果完全一致长度替代flagreturn i;}}return npos;}
m - i len则表示两个字符串完全一致。不能直接在判断语句写出m。由于m在每次比较后都会递增所以m - i的计算可能不会反映实际的匹配长度因为m可能会超出实际不匹配的位置。即位置匹配成功之后才可以m。 substr
用来生成子串。其核心逻辑是1.设定好大小开空间 2.拷贝内容
开大小确定合适的长度 拷贝内容确定合适的终止位置end //1.设定好大小开空间 2.拷贝内容
string string::substr(size_t pos, size_t len)
{assert(pos _size);string tmp;size_t end pos len;//设定大小if (pos len _size || len npos) //len太长{len _size - pos;end _size;}tmp.reserve(len); //开空间for (size_t i pos; i end; i) //内容拷贝{tmp _a[i];}return tmp; //临时对象不用引用返回。
}全局函数 ostream operator(ostream out, const string s);
string函数支持直接流提取与流插入的操作。
流提取
ostream MyString::operator(ostream out, const string s)
{for (auto ch : s) //写了迭代器之后就可以用范围forout ch;return out;
}
流插入
我们借助一个插入缓冲区来防止过多的浪费空间。步骤1.输入内容 2.流入缓冲区 3.从缓冲区提取内容 4.继续从将内容读取到缓冲区中 最后需要清除缓冲区 istream MyString::operator(istream in, string s)
{s.clear(); //清空对象char ch in.get();size_t i 0;char* buff new char[129]; //建立缓冲区while (ch ! ch ! 10){buff[i] ch; //流入缓冲区if (i 128){ buff[i] 0;s buff; //从缓冲区读取i 0;}ch in.get(); //继续读取缓冲区}if (i ! 0){buff[i] 0;s buff;}return in;}