目录
string的模拟实现,vs下:
string的头文件
//string.h
//声明和定义分离的写法
//vector<T>就不能声明定义分离了,因为它是类模板,不支持声明定义分离写法
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
namespace bit
{
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& s);
string& operator=(const string& s);
~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);
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& s);
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;
size_t _size;
size_t _capacity;
//
//const static size_t npos = -1;
// ֧
//const static double N = 2.2;
const static size_t npos;
};
istream& operator>> (istream& is, string& str);
ostream& operator<< (ostream& os, const string& str);
}
string.cpp
构造函数
构造函数设置为缺省参数,若不传入参数,则默认构造为“ ”(空字符串)。字符串的初始大小和容量均设置为传入的字符串的长度
string::string(const char* str)//可以不传参数,string.h的构造函数声明处有缺省值" "
:_size(strlen(str))
{
_str = new char[_size + 1];
_capacity = _size;
strcpy(_str, str);
}
拷贝构造
string类中涉及到资源的管理,编译器只是将对象中的值拷贝过来过来(浅拷贝);其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。
浅拷贝:string类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。
写法一:传统
先开辟一块空间,然后拷贝数据过去,接着把源对象的其他成员变量也赋值过去。
// s2(s1)
string::string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
写法二:现代(swap)
先根据源字符串的c字符串调用构造函数构造一个tmp对象,然后再将tmp对象与拷贝对象的数据交换即可,这里注意_str要初始化为空指针,因为交换后会析构tmp,释放空指针时没问题的,而释放随机值就会报错。
string::string(const string& s)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string tmp(s._str);
swap(tmp);
}
赋值重载
写法一:传统
先开一块新空间,然后在拷贝旧数据,再释放掉旧空间,这里尽量是先开空间再释放,避免我们开空间失败导致原始数据的丢失。
// s1 = s3
// s1 = s1
string& string::operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
写法二:现代(swap)
拷贝构造函数的现代写法是通过调用构造函数构造出一个对象,然后将该对象与拷贝对象交换。
string& string::operator=(string s)
{
swap(s);
return *this;
}
析构函数
当对象销毁时堆区对应的空间并不会自动销毁,为了避免内存泄漏,所以我们需要使用delete释放堆区的空间。
string::~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
获取C类型字符串
const char* string::c_str() const
{
return _str;
}
求长度和容量(size/capacity)
因为string类的成员变量是私有的,所以不能直接进行访问它的私有成员,所以string类设置了size( )和capacity( )这两个成员函数,用来获取string对象的大小和容量。
size函数用于获取字符串当前的有效长度
size_t string::size() const
{
return _size;
}
capacity函数用于获取字符串当前的容量。
size_t string::capacity() const
{
return _capacity;
}
访问字符串中元素:[]重载
[ ]运算符的重载是为了支持像C字符串和数组一样,通过[ ] +下标的方式访问对应位置的元素。
两个版本:
1:可读可写
char& string::operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
2:只读
const char& string::operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
reserve(扩容)
reserve函数通常用来扩容,但是注意一般只有当新的容量大于当前容量时才执行。
它不会实时去改变size,记得要自己去改变size。
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
resize
resize规则:
1、当n大于当前的size时,将size扩大到n,扩大的字符填充为ch,ch的默认值为’\0’。
2、当n小于当前的size时,将size缩小到n。
reserve和resize的共同点如下: 当 填写的n值<=当前值 时,都不会缩减容器本身的容量,即对原内存空间并无影响。 当 填写的n值>当前值 时,都会增大容器本身的容量 即capacity会变化。
push_back
ush_back函数就是在字符串的末尾插上一个字符,尾插之前需要判断是否需要增容,如果需要,调用reserve函数进行增容,然后再尾插字符,注意尾插完字符后需要在末尾增加’\0’同时_size++
方法一:传统
void string::push_back(char ch)
{
if (_size == _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
_str[_size] = ch;
_str[_size + 1] = '\0';
++_size;
}
方法二:现代
void string::push_back(char ch)
{
insert(_size, ch);
}
append
append函数就是在当前字符串末尾插一个字符串,尾插前需要判断当前字符串的空间能否够尾插后的字符串,如果不行,就需要先进行增容,然后再在进行尾插,因为尾插的字符串后方有’\0’,所以直接用strcpy就可以,注意_size要变成_size + len
两种写法:
void string::append(const char* str)
{
/*size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str+_size, str);
_size += len;*/
insert(_size, str);
}
operator+=(字符/字符串)
+=运算符的重载是为了实现字符串与字符、字符串之间能够直接使用+=运算符进行尾插。
+=运算符实现字符串与字符之间的尾插也可以直接复用push_back函数即可。
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
+=运算符实现字符串与字符串之间的尾插直接复用append函数即可。
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
insert(字符/字符串)
insert函数是用来在字符串的任意位置插入字符或字符串
首先需要判断pos在有效范围内,然后还需判断string对象能否容纳插入字符后的字符串,如果不能就需调用reserve函数进行扩容,插入一个字符时需要从最后一个位置开始往后挪动一位直到把pos位置挪动后结束。
字符:两种写法
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
/*int end = _size;
while (end >= (int)pos)
{
_str[end + 1] = _str[end];
--end;
}*/
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end-1];
--end;
}
_str[pos] = ch;
++_size;
}
字符串:两种写法
void string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
/*int end = _size;
while (end >= (int)pos)
{
_str[end + len] = _str[end];
--end;
}*/
size_t end = _size+len;
while (end > pos+len-1)
{
_str[end] = _str[end - len];
--end;
}
memcpy(_str + pos, str, len);
_size += len;
}
erase
erase函数用来删除字符串任意位置开始的n个字符。首先,需要判断pos的合法性,删除的时候分两种情况:
1:从pos位置开始后面的字符全部被删除。(不写第二个参数)
2 只需删除从pos位置开始的一部分字符。
void string::erase(size_t pos, size_t len)
{
assert(pos < _size);
// len大于前面字符个数时,有多少删多少
if (len >= _size-pos)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
find(字符/字符串)
find函数用于在字符串中查找一个字符或字符串
1、查找第一个匹配的字符。
从pos位置开始向后寻找目标字符,如果找到,就返回其下标;如果没有找到,就返回npos。
size_t string::find(char ch, size_t pos)
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
2、查找第一个匹配的字符串。
(这里可以使用strstr函数查找,strstr函数若是找到了目标字符串会返回字符串的起始位置,若是没有找到会返回一个空指针。)
size_t string::find(const char* sub, size_t pos)
{
char* p = strstr(_str + pos, sub);
return p - _str;
}
swap
swap函数用于交换两个对象的数据,这里我们只需要交换两对象的指向,同时改变_size和_capacity的大小就可以了。
// s1.swap(s3)
void string::swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
substr
substr函数用来从字符串中提取出一个子字符串
有两种写法:可以开另一位博主,链接在最下方,
string string::substr(size_t pos, size_t len)
{
// len大于后面剩余字符,有多少取多少
if (len > _size - pos)
{
string sub(_str + pos);
return sub;
}
else
{
string sub;
sub.reserve(len);
for (size_t i = 0; i < len; i++)
{
sub += _str[pos + i];
}
return sub;
}
}
关系运算符重载函数
关系运算符总共有 >、>=、<、<=、==、!= 这六个,但是对于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);
}
void string::clear()
{
_str[0] = '\0';
_size = 0;
}
>>和<<运算符重载
<<运算符的重载
直接把string对象的字符串依次输出就可以了
>>运算符的重载
在输入前需要我们清空原来string对象的字符串,然后从标准输入流读取字符,直到读取到空格或是换行便停止读取。
istream& operator>> (istream& is, string& str)
{
str.clear();
char ch = is.get();
while (ch != ' ' && ch != '\n')
{
str += ch;
ch = is.get();
}
return is;
}
ostream& operator<< (ostream& os, const string& str)
{
for (size_t i = 0; i < str.size(); i++)
{
os << str[i];
}
return os;
}
getline
getline函数用于读取一行含有空格的字符串,当读取到’\n’的时候才停止读取字符。
istream& getline(istream& in, string& s)
{
s.clear();
char ch = in.get();
while (ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}
shrink_to_fit
shrink_to_fit函数用于缩容到_size位置,用的比较少,首先,开_size大小空间,拷贝数据,释放旧空间,改变容量大小到_size。
void string::shrink_to_fit()
{
char* tmp = new char[_size + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;//这里我经常忘写,大家也要注意
_capacity = _size;
}
部分代码摘自: