string类简单模拟实现
目录
我们实现string将其封装到一个namespace(命名空间中)
1. string成员变量
我们在使用string的时候发现它存储字符串,并且可以动态增加容量,于是我们用_str 来存储字符串,用_size来记录它存储的字符个数,用_capacity来记录容量
此外我们还实现了一个静态变量 npos
#pragma once
#include<iostream>
#include<assert.h>
#include<cstring>
using std::cout;
using std::cin;
using std::endl;
using std::istream;
using std::ostream;
namespace Pc
{
class string
{
public:
//static const size_t npos = -1;
static const size_t npos ;
........
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
静态变量是类内声明,类外定义。但这个在类内也可以直接赋值如上注释代码。此为个例,我在此处仍然在另一个文件(同名namespace)定义
2. string的成员函数
(1). 构造函数和析构函数
实现之前先在类里实现三个函数来获取取的成员变量的值
const char* c_str() const
{
return _str;
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
我们实现的三个构造函数和一个析构函数
#pragma once
#include<iostream>
#include<assert.h>
#include<cstring>
using std::cout;
using std::cin;
using std::endl;
using std::istream;
using std::ostream;
namespace Pc
{
class string
{
public:
string()
:_str(new char[5])//_caoacity不包含\0
,_size(0)
,_capacity(4)
{
_str[0] = '\0';
}
string(const char* str)
:_str(new char [strlen(str) + 1])
//_caoacity不包含\0
, _size(strlen(str))
, _capacity(strlen(str))
{
strcpy(_str, str);
}
string(const string& s1)
:_str(new char [s1.capacity()+1])
, _size(s1.size())
, _capacity(s1.capacity())
{
strcpy(_str, s1.c_str());
}
~string()
{
delete[] _str;//str是nullptr也没问题
_str = nullptr;
_size = _capacity = 0;
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
上面的默认构造函数我们开5个空间容量就为4,因为_capacity中不包括'\0' 所以实际开的要多一个。
通过字符串构造的string只需要用strlen将字符串的长记录下来赋值就可以了。
拷贝构造函数可以用过我们上面实现的成员函数来访问s1.的各个成员变量的值
析构函数直接将new的空间delete掉,将指针指向空即可。
(2). string的访问
① 范围for访问
范围for底层实际是通过迭代器实现的,所以将迭代器实现出来就可以使用范围for了
这里实现一个简易版的范围for
typedef char* iterator;
typedef 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;
}
由于string中iterator就是一个指针所以我们这里将char* typedef一下用来模拟实现
②下标访问
即重载 [] 使其接收一个下标,返回对应位置的字符,引用返回减少消耗,与可读写,只读两种接口
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
(3). string的修改
我们先实现一个扩容的函数reserve,用来减少代码量
这些都从类外实现所以需要域作用限定符
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[ n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
① 函数重载+=
由于会+=字符或+=字符串所以进行函数重载,实现时注意越界问题
string& string::operator+=(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
return *this;
}
string& string::operator+=(const char* str)
{
int len = strlen(str);
if (_size + len >= _capacity)
{
reserve(_capacity * 2 > _size + len ? _capacity * 2 : _size + len);
}
for (int i = 0; i <= len; i++)//假设字符串为"11\0"
{
_str[_size + i] = str[i];
}
_size = _size + len;
return *this;
}
② push_back与append的实现
由于实现了两个+=我们直接复用即可
void string::push_back(char ch)
{
*this += ch;
}
void string::append(const char* str)
{
*this += str;
}
③ insert的实现
我们需要注意size_t类型是无符号整形,所以没有负值,我们可以通过下标加一,操作时整体减一,或将size_t都进行强制类型转换再操作等等
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
size_t n = _size+1;
//while (n >= pos)//隐式类型转换
//{
// _str[n + 1] = _str[n];
// n--;
//}
while (n > pos)
{
_str[n ] = _str[n-1];
n--;
}
_str[pos] = ch;
_size++;
}
void string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
int len = strlen(str);
if (_size + len >= _capacity)
{
reserve(_capacity * 2 > _size + len ? _capacity * 2 : _size + len);
}
//for (int i = _size; i >= pos; i--)//隐式类型转换
//{
// _str[i + len] = _str[i];
//}
for (int i = _size+1; i >pos; i--)
{
_str[i + len-1] = _str[i-1];
}
for (int i = 0; i < len; i++)
_str[pos + i] = str[i];
_size += len;
}
④ erase与clear的实现
erase实现如下
void string::erase(size_t pos, size_t len )
{
assert(pos < _size);
if (len > _size - pos)
{
_str[pos] = '\0';
_size = pos;
}
else
{
size_t n = pos;
for (size_t i = pos + len; i <= _size; i++)
{
_str[n] = _str[i];
n++;
}
_size -= len;
}
}
clear由于代码量小直接在类内实现,直接在下标为0处给'\0' 即可
void clear()
{
_str[0] = '\0';
_size = 0;
}
⑤ 交换函数
我们来实现string里的swap
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
各种信息直接交换很高效,C++98中全局的swap实现交换需要完成三次深拷贝,string里的swap优与全局的,但C++11中,全局swap检测到string会直接调用string里的swap因此效率差不多
拷贝构造函数也可以写为
string(const string& s1)
{
string tmp(s1._str);
swap(tmp);
}
(4). string的查找
查找字符十分简单,查找字符串我们可已通过strstr来实现,他如果找到会返回要查找字符串的第一个字符地址若没有找到返回null指针
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 -1;
}
size_t string::find(const char* str, size_t pos)
{
assert(pos < _size);
const char* ps = strstr(_str + pos, str);
if (ps == nullptr)
{
return npos;
}
else
{
return ps - _str;
}
}
(5). string的流插入与流提取的重载
我们在实现流提取时需要注意cin遇见空格与换行是不会读进去的话这时候我们就要用到一个函数get()了
ostream& operator<<(ostream& out, string& s)
{
out << s.c_str();
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
//错误示范
//char ch;
//in >> ch;
//while (ch != ' ' && ch != '\n')
//{
// s += ch;
// in >> ch;//遇到空格或换行符就不会读进去
//}
//正确
//char ch;
//ch = in.get();
//while (ch != ' ' && ch != '\n')
//{
// s += ch;
// ch = in.get();//不管什么类型一个字符一个字符的读
//}
//优化,减少存储字符串很长时扩容次数
const int N = 256;//字符串特别长,N给大一点点还会减少扩容
char charval[N];
int i = 0;
charval[i] = in.get();
while (charval[i] != ' ' && charval[i] != '\n')
{
charval[++i] = in.get();
if (i == N-1)
{
charval[i] = '\0';
s += charval;
i = 0;
}
}
if(i>0)
{
charval[++i] = '\0';
s += charval;
}
return in;
}
那还会有一个问题,输入的字符串太长,需要频繁扩容,那么消耗会很大,我们可以建立一个数组等这个数组存满了再一次性给string对象,如果没满就结束了,那就退出循环将存储的值给对象(不要忘记给数组'\0',而且数组要留一个存储'\0')。这样就要有效的减少扩容次数
我们顺便实现getline(代码几乎一样)
istream& getline(istream& in, string& str, char delim)
{
str.clear();
//优化,减少存储字符串很长时扩容次数
const int N = 256;//字符串特别长,N给大一点点还会减少扩容
char charval[N];
int i = 0;
charval[i] = in.get();
while (charval[i] != delim)
{
charval[++i] = in.get();
if (i == N - 1)
{
charval[i] = '\0';
str += charval;
i = 0;
}
}
if (i > 0)
{
charval[++i] = '\0';
str += charval;
}
return in;
}
istream& getline(istream& in, string& str)
{
str.clear();
const int N = 256;//字符串特别长,N给大一点点还会减少扩容
char charval[N];
int i = 0;
charval[i] = in.get();
while (charval[i] != '\n')
{
charval[++i] = in.get();
if (i == N - 1)
{
charval[i] = '\0';
str += charval;
i = 0;
}
}
if (i > 0)
{
charval[++i] = '\0';
str += charval;
}
return in;
}
(6). 其他一些操作
① 比较
字符串之间的比较是比较ASCII码值,我们通过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 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 !(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);
}
② substr
string string::substr(size_t pos , size_t len ) const
{
assert(pos < _size);
if (len > _size - pos)
len = _size - pos;
string s1;
s1.reserve(len);
for (int i = 0; i < len; i++)
{
s1 += _str[pos + i];
}
return s1;
}
③ 赋值运算符重载
我们判断是不是给自己赋值,是就跳过,不是就创建一个新变量拷贝构造后进行交换
出了if就将tmp给销毁了,而this指针指向的就是tmp初始化的地址,销毁的为this之前的地址
string& operator=(const string& s)
{
if (this != &s)//是自己就没必要赋值了
{
string tmp(s);
swap(tmp);
}
return *this;
}
而我们可以将其简化为,不是引用传参,那他就进行了一次拷贝构造了交换即可
string& operator=(string tmp)
{
//不需要判断是否是给自己赋值,因为tmp变量已经创建出来了
swap(tmp);
return *this;
}
3. 源代码
(1). 头文件
#pragma once
#include<iostream>
#include<assert.h>
#include<cstring>
using std::cout;
using std::cin;
using std::endl;
using std::istream;
using std::ostream;
namespace Pc
{
class string
{
public:
typedef char* iterator;
typedef char* const_iterator;
//static const size_t npos = -1;
static const size_t npos ;
//实现迭代器
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
//短小,频繁调用的都在类里
string()
:_str(new char[5])
,_size(0)
,_capacity(4)
{
_str[0] = '\0';
}
string(const char* str)
:_str(new char [strlen(str) + 1])
//_caoacity不包含\0
, _size(strlen(str))
, _capacity(strlen(str))
{
strcpy(_str, str);
}
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string(const string& s1)
//:_str(new char [s1.capacity()+1])
//, _size(s1.size())
//, _capacity(s1.capacity())
{
//strcpy(_str, s1.c_str());
string tmp(s1._str);
swap(tmp);
}
//string& operator=(const string& s)
//{
// if (this != &s)//是自己就没必要赋值了
// {
// string tmp(s);
// swap(tmp);
// }
// return *this;
//}
string& operator=(string tmp)
{
//不需要判断是否是给自己赋值,因为tmp变量已经创建出来了
swap(tmp);
return *this;
}
~string()
{
delete[] _str;//str是nullptr也没问题
_str = nullptr;
_size = _capacity = 0;
}
const char* c_str() const
{
return _str;
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
void reserve(size_t n);
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, size_t len = npos);
string substr(size_t pos = 0, size_t len = npos) const;
size_t find(char ch, size_t pos = 0);
size_t find(const char* str, size_t pos = 0);
void clear()
{
_str[0] = '\0';
_size = 0;
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
bool operator>(const string& s1,const string& s2);
bool operator<(const string& s1, const string& s2);
bool operator>=(const string& s1, const string& s2);
bool operator<=(const string& s1, const string& s2);
bool operator==(const string& s1, const string& s2);
bool operator!=(const string& s1, const string& s2);
istream& operator>>(istream& in, string& s);
ostream& operator<<(ostream& out, string& s);
istream& getline(istream& is, string& str, char delim);
istream& getline(istream& is, string& str);
}
(2). 实现功能的
#include"string.h"
namespace Pc
{
const size_t string::npos = -1;
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[ n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void string::push_back(char ch)
{
*this += ch;
}
void string::append(const char* str)
{
*this += str;
}
string& string::operator+=(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
return *this;
}
string& string::operator+=(const char* str)
{
int len = strlen(str);
if (_size + len >= _capacity)
{
reserve(_capacity * 2 > _size + len ? _capacity * 2 : _size + len);
}
for (int i = 0; i <= len; i++)//假设字符串为"11\0"
{
_str[_size + i] = str[i];
}
_size = _size + len;
return *this;
}
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
size_t n = _size+1;
//while (n >= pos)//隐式类型转换
//{
// _str[n + 1] = _str[n];
// n--;
//}
while (n > pos)
{
_str[n ] = _str[n-1];
n--;
}
_str[pos] = ch;
_size++;
}
void string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
int len = strlen(str);
if (_size + len >= _capacity)
{
reserve(_capacity * 2 > _size + len ? _capacity * 2 : _size + len);
}
//for (int i = _size; i >= pos; i--)//隐式类型转换
//{
// _str[i + len] = _str[i];
//}
for (int i = _size+1; i >pos; i--)
{
_str[i + len-1] = _str[i-1];
}
for (int i = 0; i < len; i++)
_str[pos + i] = str[i];
_size += len;
}
void string::erase(size_t pos, size_t len )
{
assert(pos < _size);
if (len > _size - pos)
{
_str[pos] = '\0';
_size = pos;
}
else
{
size_t n = pos;
for (size_t i = pos + len; i <= _size; i++)
{
_str[n] = _str[i];
n++;
}
_size -= len;
}
}
string string::substr(size_t pos , size_t len ) const
{
assert(pos < _size);
if (len > _size - pos)
len = _size - pos;
string s1;
s1.reserve(len);
for (int i = 0; i < len; i++)
{
s1 += _str[pos + i];
}
return s1;
}
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 -1;
}
size_t string::find(const char* str, size_t pos)
{
assert(pos < _size);
const char* ps = strstr(_str + pos, str);
if (ps == nullptr)
{
return npos;
}
else
{
return ps - _str;
}
}
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 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 !(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);
}
ostream& operator<<(ostream& out, string& s)
{
out << s.c_str();
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
//错误示范
//char ch;
//in >> ch;
//while (ch != ' ' && ch != '\n')
//{
// s += ch;
// in >> ch;//遇到空格或换行符就不会读进去
//}
//正确
//char ch;
//ch = in.get();
//while (ch != ' ' && ch != '\n')
//{
// s += ch;
// ch = in.get();//不管什么类型一个字符一个字符的读
//}
//优化,减少存储字符串很长时扩容次数
const int N = 256;//字符串特别长,N给大一点点还会减少扩容
char charval[N];
int i = 0;
charval[i] = in.get();
while (charval[i] != ' ' && charval[i] != '\n')
{
charval[++i] = in.get();
if (i == N-1)
{
charval[i] = '\0';
s += charval;
i = 0;
}
}
if(i>0)
{
charval[++i] = '\0';
s += charval;
}
return in;
}
istream& getline(istream& in, string& str, char delim)
{
str.clear();
//优化,减少存储字符串很长时扩容次数
const int N = 256;//字符串特别长,N给大一点点还会减少扩容
char charval[N];
int i = 0;
charval[i] = in.get();
while (charval[i] != delim)
{
charval[++i] = in.get();
if (i == N - 1)
{
charval[i] = '\0';
str += charval;
i = 0;
}
}
if (i > 0)
{
charval[++i] = '\0';
str += charval;
}
return in;
}
istream& getline(istream& in, string& str)
{
str.clear();
const int N = 256;//字符串特别长,N给大一点点还会减少扩容
char charval[N];
int i = 0;
charval[i] = in.get();
while (charval[i] != '\n')
{
charval[++i] = in.get();
if (i == N - 1)
{
charval[i] = '\0';
str += charval;
i = 0;
}
}
if (i > 0)
{
charval[++i] = '\0';
str += charval;
}
return in;
}
}
这篇文章就到这里啦~
希望能帮到你
(づ ̄3 ̄)づ╭❤~