目录
返回有效字符串(将字符串以c语言的形式char*返回):c_str
从字符串pos位置开始往后找字符c或者字符串,返回该字符在字符串中的位置:find
输入getline(直到'\n’(delim)才结束输入,一般用于读取有空格的输入)
🚀0.前言
言C++之言,聊C++之识,以C++会友,共向远方。各位博友,你们好啊,这里是持续分享C++知识的小赵同学,今天要分享的C++知识是string ,在这一章,小赵将会向大家深度剖析string的代码实现,并尝试去实现string。
🚝1.实现string构造函数
1.1内部元素
string的底层是实际上用的边长数组,我们可以用char*指针去代替去实现,在放上两个记录目前使用情况的变量。
#pragma once
class mystring
{
public:
private:
char* _str;
size_t _size;//记录目前有多少元素
size_t _capacity;//记录一共可以放多少个元素
};
1.2实现构造函数,析构函数和拷贝构造函数
构造函数
我们在上一章 揭开C++ STL的神秘面纱之string:提升编程效率的秘密武器-CSDN博客 推荐了三个目前比较好用的构造函数
那么现在我们可以尝试去实现一下它。
string()
mystring(){
_str = nullptr;
_size = 0;
_capacity = 0;
}
string(const char*s)
mystring(const char* s)
:_capacity(strlen(s))//可用的元素
,_size(strlen(s))
{
_str = new char[_capacity + 1];//开空间
strcpy(_str, s);//将s复制拷贝一份给_str
}
这里开空间为什么会是_capacity+1,因为在我们的C语言规定字符串的结尾要有一个'\0',而strlen统计的则是'\0'之前的元素的个数。
template <class InputIterator>
mystring(InputIterator first, InputIterator last)
我们最后来到的了我们的迭代器构造这一块,我们要在我们的类里面补上我们的迭代器。(我们还是延续我们之前的想法把迭代器当指针用,其实大家如果去扒底层实现,会发现迭代器的底层就是用指针去实现的)
typedef char* Iterator;
typedef const char* Const_Iterator;
然后我们就可以很轻易的实现我们的构造函数了。
template <class InputIterator>
mystring(InputIterator start, InputIterator end)
:_size(0)
, _capacity(end - start)
{
_str = new char[_capacity + 1];
while (start != end) {
_str[_size++] = *start++;
}
_str[_size] = '\0';
}
这里一定一定要注意模板的函数定义和声明不能同时有!!!
这里一定一定要注意模板的函数定义和声明不能同时有!!!
这里一定一定要注意模板的函数定义和声明不能同时有!!!
重要的事情说三遍
这里的主要原因在于:模板它只会实例化一次,如果我们的mian.c文件包含了声明模板函数的.h文件,那么.h文件会将声明实例化,这个时候函数并没有实例化,则无法找到其的实现,若在一个文件里写了声明和定义也只会实例化靠前的这个时候也会有冲突。
拷贝构造函数
拷贝构造这里我们也是实现上一章的这个。
在这里我们一共有两种做法,小赵都写一下。
第一种,老老实实规规矩矩写
mystring::mystring(const mystring& s)
//:_str(s._str)//浅拷贝问题,这里会让_str和s._str共同指向一块空间
:_size(s._size)
,_capacity(s._capacity)
{
_str = new char[s._size + 1];
strcpy(_str, s._str);
}
//这个写法和我们的构造函数很像
第二种,利用前面的构造函数去写
mystring::mystring(const mystring& s){
mystring a(s._str);//构造一个一样的a
Swap(a);//对里面的元素进行交换
}
void mystring::Swap(mystring& a)
{
swap(_str, a._str);
swap(_size, a._size);
swap(_capacity, a._capacity);
}
这里的a就是一个临时的变量,所以我们可以直接让我们的mystring用它的。
第二种方法体现了我们的嵌套思想,大家可以尝试在后续的编程中去使用它。
析构函数
mystring::~mystring(){
delete [] _str;//释放空间
_size = 0;
_capacity = 0;
_str = nullptr;//指向空指针
}
补:赋值重载:operator=
mystring& mystring::operator=(mystring s)//利用拷贝构造,构造临时变量
{
Swap(s);//交换
return *this;
}
这里我们就用上面说的第二种方法去做,就不去实现第一种了(大家有兴趣可以自己实现一下)。然后我们主要谈一下赋值重载这里的一个问题。
为什么还是引用返回值?(这里实际上是两个问题,但是解决这一个问题就可以都解决)
这里如果大家简单的理解这里的引用返回可能会觉得这里是为了节省空间吧,毕竟如果传值返回要拷贝构造浪费空间,实际上这是一部分原因,但还有一个另一个的原因,大家看这里的用法。
在我们的C语言中是存在连续赋值的需求的,如果我们自己的类只是返回一个拷贝构造的值的话,那么无法支持这种连续赋值的需求,这也就解释了为什么要返回,为什么要引用返回值得问题。
🚝2.功能函数
2.1返回string的信息
返回有效字符和总字符的个数:size capacity
size_t mystring::size()//返回目前有多少元素
return _size;
size_t mystring::capacity()//返回string的能用的所有空间
return _capacity;
检查是否没有元素:empty
bool mystring::empty()
return _size == 0;
返回有效字符串(将字符串以c语言的形式char*返回):c_str
const char* mystring::c_str()const
return _str;
2.2对string内部空间操作
开空间:reserve
void mystring::reserve(int n)
{
if (n > _capacity)//如果比原来的空间大
{
char*str= new char[n + 1];//开一个新空间
strcpy(str, _str);//拷贝旧的空间到新空间
delete _str;//释放旧空间
_str = str;//指向新空间
_capacity = n;
}
}
将有效字符的个数改成n个:resize
void mystring::resize(int n)
{
reserve(n);
while (_size < n)
{
_size++;
}
_str[n] = '\0';如果比现在的大,那么就截断
}
void mystring::resize(int n,char a)
{
reserve(n);
while (_size < n)
{
_str[_size++] = a;//多的位置用a填
}
_str[n] = '\0';
}
清空有效字符:clear
void mystring::clear()
{
_size = 0;
}
🚝 3.迭代器
iterator::begin,end
typedef char* Iterator;
typedef const char* Const_Iterator;
mystring::Iterator mystring::begin()
return _str;
mystring::Iterator mystring::end()
return _str + _size;
const_iterator
mystring::const_Iterator mystring::begin()const
{
return _str ;
}
mystring::const_Iterator mystring::end()const
{
return _str + _size;
}
这里的反向迭代器和这个实现方法差不多,大家可以自己尝试实现以下.
🚝4.operator[]操作符的实现
operator[]
char& mystring::operator[](int n)
{
assert(n < _size);//如果>=_size则报错
return _str[n];
}
🚝5.增删查改
5.1增
在字符串后尾插字符c:push_back
void mystring::push_back(char a)
{
if (_size == _capacity) {
reserve(_size * 2);
}
_str[_size++] = a;
_str[_size] = '\0';
}
在字符串后追加一个字符串:append
void mystring::append(const mystring& s)
{
if (_size + s.size() >_capacity){
reserve((_size + s.size())*2);//空间不够扩容
}
for (int i = 0; i < s.size(); i++)
{
_str[_size + i] = s[i];
}
_size += s.size();//更新_size
_str[_size ] = '\0';
}
真香:operator+=
mystring& mystring::operator+=(const mystring& s)
{
this->append(s);
return *this;
}
mystring& mystring::operator+=(char c)
{
this->push_back(c);
return *this;
}
前插:insert
void mystring::insert(size_t pos, const mystring& s)//前插
{
assert(pos <=_size);
if (s.size() + _size > _capacity){
reserve((s.size() + _size) * 2);
}
size_t len = s.size();
size_t end = _size;
_size += s.size();
while ((int)end>=(int)pos){//这里一定要转成int,因为size_t只有正数,所以0-1之后会变成最大的正数
_str[len + end] = _str[end];
end--;
}
strncpy(_str+pos,s.c_str(),len);
_str[_size] = '\0';
}
char*的版本和这个差不多,大家可以自己实现以下
5.2删
在字符串后尾删除字符:pop_back
void mystring::pop_back()
{
_str[--_size] = '\0';
}
在某处向后删除:erase
void mystring::erase(size_t pos, int len=npos)
{
assert(pos > _size);
if (len == -1||pos+len>=_size)
{
_str[pos] = '\0';
}
else
{
int begin =pos+len;
while (begin <=_size){
_str[begin-len] = _str[begin];//把后面所有的都移到前面
begin++;
}
_size -= len;
}
}
5.3查
从字符串pos位置开始往后找字符c或者字符串,返回该字符在字符串中的位置:find
int mystring::find(const string& s, size_t pos=0)
{
assert(pos < _size);
char* str = _str + pos;
char* m = strstr(str, s.c_str());//返回_str第一次出现str的地址
if (m)
{
return m - _str;
}
return -1;
}
int mystring::find(char c, size_t pos=0 )
{
assert(pos < _size);
for (int i = pos; i < _size; i++)
{
if (_str[i] == c)
{
return i;
}
}
return -1;
}
5.4改
这里的改,我们就直接用operator[]就可以。
补:返回字串substr
mystring substr(size_t pos = 0, int len = npos)const
{
if (len == -1 || pos + len>=_size)//如果超出string长度
{
return mystring(_str + pos);
}
else
{
mystring ans;
for (int i = pos; i < pos + len; i++)
{
ans += _str[i];
}
return ans;
}
return nullptr;
}
🚝6.输入输出
输入cin
istream& operator>>(istream& in, String& str)
{
str.clear();//先清除原字符串
char ch = in.get();//使用get函数读取单个字符
while (ch != ' ' && ch != '\n')//读取到空格或换行就停止
{
str += ch;
ch = in.get();
}
return in;
}
这里的返回in主要是为了支持连续的输入cin>>a>>b;后面的输出也是如此
如果我们学习过操作系统等知识应该会知道我们的输入其实是有一个缓冲区,这里我们可以更真实的模仿下。
istream& operator>>(istream& in, string& s)
{
s.clear();//清除原本的内容;
int i = 0;
char buff[129];//定义一个缓冲区
char ch =in.get();
while (ch != '\n'&&ch !=' ')//如果需要换行或者' '
{
buff[i++] = ch;
ch=in.get();
if (i == 128)//缓冲区满了,刷新缓冲区
{
buff[i] = '\0';
s += buff;
i = 0;
}
}
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
输出cout
ostream& operator<<(ostream& out, string& s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
(其实输出也有缓冲区,这里就不演示了)
这里一定要注意如果要实现这个的时候,就不要定义和声明分离,否则编译器会报错;
不要在.h文件里面写,在一个.cpp文件中写即可,问题的关键在于如果在.h文件中写,所有#include了该.h文件的cpp都会编译一个operator<<函数,造成多次定义,所以保证operator<<函数编译时只出现一次就行,所以选择一个.cpp文件中实现operator<<函数就可以
输入getline(直到'\n’(delim)才结束输入,一般用于读取有空格的输入)
istream& getline(istream& in, mystring& str, char delim='\n')
{
str.clear();
char ch = in.get();
while (ch != delim)//直到等于delim跳出
{
str += ch;
ch = in.get();
}
return in;
}
🚝7.完整代码
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<assert.h>
using namespace std;
namespace zcg
{
/*ostream& operator<<(ostream& out, const mystring& s);
istream& operator>>(istream& in, mystring& s);*/
class mystring {
public:
typedef char* Iterator;
typedef const char* const_Iterator;
void Swap(mystring& a);
//构造函数
mystring();
mystring(const char* s);
//template <class InputIterator>
//mystring(InputIterator start, InputIterator end);
template <class InputIterator>
mystring(InputIterator start, InputIterator end)
:_size(0)
, _capacity(end - start)
{
_str = new char[_capacity + 1];
while (start != end) {
_str[_size++] = *start++;
}
_str[_size] = '\0';
}
//拷贝构造函数
mystring(const mystring& s);
//赋值重载
mystring& operator=(mystring s);
//析构函数
~mystring();
//获取一部分值
size_t size()const;
size_t capacity()const;
bool empty();
const char* c_str() const;
//对空间操作
void reserve(int n);
void resize(int n);
void resize(int n, char a);
void clear();
Iterator begin();
Iterator end();
const_Iterator begin()const;
const_Iterator end()const;
char& operator[](int n);
char operator[](int n)const;
//增
void push_back(char a);
void append(const mystring& s);
mystring& operator+=(const mystring& s);
mystring& operator+=(char c);
void insert(size_t pos, const mystring& s);
//删
void pop_back();
void erase(size_t pos, int len = npos);
//查
int find(const mystring& s, size_t pos = 0);
int find(char c, size_t pos = 0);
//返回字串
const mystring substr(size_t pos = 0, int len = npos)const;
private:
const static int npos = -1;
char* _str;
size_t _size;//记录目前有多少元素
size_t _capacity;//记录一共可以放多少个元素
};
//输入输出
void mystring::Swap(mystring& a)
{
swap(_str, a._str);
swap(_size, a._size);
swap(_capacity, a._capacity);
}
mystring::mystring() {
_str = nullptr;
_size = 0;
_capacity = 0;
}
mystring::mystring(const char* s)
:_capacity(strlen(s))
, _size(strlen(s))
{
_str = new char[_capacity + 1];
strcpy(_str, s);
}
mystring::Iterator mystring::begin()
{
return _str + _size;
}
mystring::Iterator mystring::end()
{
return _str + _size;
}
mystring::const_Iterator mystring::begin()const
{
return _str;
}
mystring::const_Iterator mystring::end()const
{
return _str + _size;
}
char& mystring::operator[](int n)
{
assert(n < _size);
return _str[n];
}
char mystring::operator[](int n)const
{
assert(n < _size);
return _str[n];
}
//
传统写法
//mystring::mystring(const mystring& s)
:_str(s._str)//浅拷贝问题
// :_size(s._size)
// ,_capacity(s._capacity)
//{
// _str = new char[s._size + 1];
// strcpy(_str, s._str);
//}
mystring::mystring(const mystring& s) {
mystring a(s._str);
Swap(a);
}
mystring& mystring::operator=(mystring s)
{
Swap(s);
return *this;
}
size_t mystring::size()const
{
return _size;
}
size_t mystring::capacity()const
{
return _capacity;
}
bool mystring::empty()
{
return _size == 0;
}
const char* mystring::c_str() const
{
return _str;
}
void mystring::reserve(int n)
{
if (n > _capacity)//如果比原来的空间大
{
char* str = new char[n + 1];//开一个新空间
strcpy(str, _str);//拷贝旧的空间到新空间
delete _str;//释放旧空间
_str = str;//指向新空间
_capacity = n;
}
}
void mystring::resize(int n)
{
reserve(n);
while (_size < n)
{
_str[_size++] = '0';
}
_str[n] = '\0';
}
void mystring::resize(int n, char a)
{
reserve(n);
while (_size < n)
{
_str[_size++] = a;
}
_str[n] = '\0';
}
void mystring::clear()
{
_size = 0;
}
void mystring::push_back(char a)
{
if (_size == _capacity) {
reserve(_size * 2);
}
_str[_size++] = a;
_str[_size] = '\0';
}
void mystring::append(const mystring& s)
{
if (_size + s.size() > _capacity) {
reserve((_size + s.size()) * 2);//空间不够扩容
}
for (int i = 0; i < s.size(); i++)
{
_str[_size + i] = s[i];
}
_size += s.size();//更新_size
_str[_size] = '\0';
}
mystring& mystring::operator+=(const mystring& s)
{
this->append(s);
return *this;
}
mystring& mystring::operator+=(char c)
{
this->push_back(c);
return *this;
}
void mystring::insert(size_t pos, const mystring& s)//前插
{
assert(pos <= _size);
if (s.size() + _size > _capacity) {
reserve((s.size() + _size) * 2);
}
size_t len = s.size();
size_t end = _size;
_size += s.size();
while ((int)end >= (int)pos) {
_str[len + end] = _str[end];
end--;
}
strncpy(_str + pos, s.c_str(), len);
_str[_size] = '\0';
}
void mystring::pop_back()
{
_str[--_size] = '\0';
}
void mystring::erase(size_t pos, int len)
{
assert(pos <= _size);
if (len == -1 || pos + len >= _size)
{
_str[pos] = '\0';
}
else
{
int begin = pos + len;
while (begin <= _size) {
_str[begin - len] = _str[begin];//把后面所有的都移到前面
begin++;
}
_size -= len;
}
}
int mystring::find(const mystring& s, size_t pos)
{
assert(pos < _size);
char* str = _str + pos;
char* m = strstr(str, s.c_str());//返回_str第一次出现str的地址
if (m)
{
return m - _str;
}
return -1;
}
int mystring::find(char c, size_t pos)
{
assert(pos < _size);
for (int i = pos; i < _size; i++)
{
if (_str[i] == c)
{
return i;
}
}
return -1;
}
const mystring mystring::substr(size_t pos, int len)const
{
if (len == -1 || pos + len >= _size)
{
return mystring(_str + pos);
}
else
{
mystring ans;
for (int i = pos; i < pos + len; i++)
{
ans += _str[i];
}
return ans;
}
return nullptr;
}
ostream& operator<<(ostream& out, const mystring& s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
istream& operator>>(istream& in, mystring& s)
{
s.clear();//清除原本的内容;
int i = 0;
char buff[129];//定义一个缓冲区
char ch = in.get();
while (ch != '\n' && ch != ' ')
{
buff[i++] = ch;
ch = in.get();
if (i == 128)
{
buff[i] = '\0';
s += buff;
i = 0;
}
}
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
istream& getline(istream& in, mystring& str, char delim='\n')
{
str.clear();
char ch = in.get();
while (ch != delim)
{
str += ch;
ch = in.get();
}
return in;
}
mystring::~mystring() {
delete[]_str;
_size = 0;
_capacity = 0;
_str = nullptr;
}
};
💎8.结束语
好了小赵今天的分享就到这里了,如果大家有什么不明白的地方可以在小赵的下方留言哦,同时如果小赵的博客中有什么地方不对也希望得到大家的指点,谢谢各位家人们的支持。你们的支持是小赵创作的动力,加油。
如果觉得文章对你有帮助的话,还请点赞,关注,收藏支持小赵,如有不足还请指点,方便小赵及时改正,感谢大家支持!!!