【c++】string类详解
目录
- string类的作用
- string类提供的接口
- string类的构造
- string类的容量操作
- size
- resize
- capacity
- reserve
- clear
- empty
- shrink_to_fit
- string类的对象访问
- []
- at
- back
- front
- 范围for
- string类的修改
- push_back
- append
- +=
- assign
- =
- insert
- erase
- swap
- pop_back
- repalce
- string类的迭代器
- begin
- end
- rbegin
- rend
- cbegin
- cend
- crbegin
- crend
- string类的查找与比较
- find
- rfind
- find_first_of
- find_last_of
- find_first_not_of
- find_last_not_of
- compare
- string类的非成员函数的操作符重载
- string类的模拟实现
- 代码整体展示
- 实现思路
string类的作用
string类是c++标准库中提供的类,可以方便快捷的处理字符串,得益于c++提供了面向对象的类,相较于C语言只依靠面向过程的函数而言,功能性、安全性、书写舒适度都更上一层楼。string类拥有自己的头文件,要使用时务必包含。
string类提供的接口
string类的构造
string();
string类的默认构造,默认会构造一个空字符串
string (const string& str);
拷贝构造
string (const string& str, size_t pos, size_t len = npos);
从str上拷贝子字符串,从pos位置开始,长度为len,len给了缺省值npos,是类中定义的一个size_t(无符号整型)类型的成员变量,将-1强转为size_t类型也就意味着npos是无符号整型中的最大值,一般情况根本达不到,这时拷贝会在str的结尾处停止,也就是说不给的话就直接拷贝到末尾。
string (const char* s);
由字符串创建string,最为常用的方式。
string (const char* s, size_t n);
由s指向的字符数组的前n个创建。
string (size_t n, char c);
创建一个字符串为n个c的string类对象。
template <class InputIterator>
string (InputIterator first, InputIterator last);
由两个迭代器之间的数据创建string类对象。
string类的容量操作
size
size_t size() const;
返回字符串的长度,不包含\0。
resize
void resize (size_t n);
void resize (size_t n, char c);
调整字符串的长度,如果n小于当前size,会截断字符串使size达到n大小,如果n大于当前size,会填充多出来的空间,如果有指定c的内容的话,就全部填充成c,没有指定就填\0。
capacity
size_t capacity() const;
返回为字符串分配的内存空间大小,string的本质和顺序表一样,capacity的值不一定和size一样,每次扩容会留出很多空间,这样就不用每次都扩容了。
reserve
void reserve (size_t n = 0);
调整为字符串分配的空间的大小,虽然我们可以使用这个函数调整capacity的大小,但这个函数对编译器来说更象一条建议,编译器会视情况选择执不执行,一般来说,对于减小capacity的操作,编译器不会执行,因为reserve会优先保护字符串,reverse是绝对无法修改影响字符串和size的。对于增大capacity的操作,编译器会执行,使capacity等于或者大于n 。
clear
void clear();
清空字符串,字符串会变成空字符串,size归0。
empty
bool empty() const;
字符串验空。空返回true,不空返回false。
shrink_to_fit
void shrink_to_fit();
要求字符串减少capacity以跟size匹配,即使减小编译器也会留出一部分空间优化,对于capacity和size的情况,编译器会无视这条指令,这个函数一般在对一个很长的函数删除了很长一部分之后才有可能用得到,如果在直接创建的情况下用很大可能会因为size和capacity相近而被编译器无视。
string类的对象访问
[]
char& operator[] (size_t pos);
const char& operator[] (size_t pos) const;
操作符[]的重载,可以直接将类名当成字符串数组名使用,非常好用的函数。
at
char& at (size_t pos);
const char& at (size_t pos) const;
返回对pos位置在字符串的引用。会自动检查pos是不是在有效位置。
back
char& back();
const char& back() const;
返回对字符串最后一个字符的引用。不能在空字符串上用,会报错。
front
char& front();
const char& front() const;
返回对字符串第一个字符的引用。不能在空字符串上用,会报错。
范围for
std::string str("abcdefg");
for (auto e : str)
{
std::cout << e;//打印abcdefg
}
std::cout << std::endl;
for (auto& e : str)
{
e = 'a';
}
std::cout << str;//aaaaaaa
c++11后引入了范围for这个语法糖,支持迭代器的类都支持,string也不例外。
string类的修改
push_back
void push_back (char c);
尾插一个字符。
append
string& append (const string& str);
string& append (const string& str, size_t subpos, size_t sublen);
string& append (const char* s);
string& append (const char* s, size_t n);
string& append (size_t n, char c);
template <class InputIterator>
string& append (InputIterator first, InputIterator last);
append作用与push_back类似,只不过它尾插的是字符串,append提供了很多函数重载,但参数和构造函数的类似,意思也一样,这里不过多赘述。
+=
string (1)
string& operator+= (const string& str);
c-string (2)
string& operator+= (const char* s);
character (3)
string& operator+= (char c);
操作符+=的重载,用于尾插字符和字符串,大部分情况可以替代push_back和append,意思还更为简洁直观,非常好用。
assign
string& assign (const string& str);
string& assign (const string& str, size_t subpos, size_t sublen);
string& assign (const char* s);
string& assign (const char* s, size_t n);
string& assign (size_t n, char c);
template <class InputIterator>
string& assign (InputIterator first, InputIterator last);
assign可以为字符串重新赋值,覆盖掉原来的,提供的函数重载与构造函数类似。
=
string (1)
string& operator= (const string& str);
c-string (2)
string& operator= (const char* s);
character (3)
string& operator= (char c);
=运算符重载可以为字符串重新赋值,与assign类似,一般情况下会用这个,更为直观,但assign有更多种重载,特殊情况会用到。
insert
string& insert (size_t pos, const string& str);
string& insert (size_t pos, const string& str, size_t subpos, size_t sublen);
string& insert (size_t pos, const char* s);
string& insert (size_t pos, const char* s, size_t n);
string& insert (size_t pos, size_t n, char c);
void insert (iterator p, size_t n, char c);
iterator insert (iterator p, char c);
template <class InputIterator>
void insert (iterator p, InputIterator first, InputIterator last);
insert可以在字符串指定位置(pos)之前插入字符或者字符串,这里提供了很多函数重载,但常用的就第一个,其他的感兴趣可以查资料。
erase
string& erase (size_t pos = 0, size_t len = npos);
iterator erase (iterator p);
iterator erase (iterator first, iterator last);
erase可以擦除一部分,从pos位置开始,长度为len。也支持迭代器删除一个字符或用迭代器划定范围删除。
swap
void swap (string& str);//string类的成员函数
void swap (string& x, string& y);//对标准库swap的重载
交换两个字符串。
pop_back
void pop_back();
尾删。
repalce
string& replace (size_t pos, size_t len, const string& str);
string& replace (iterator i1, iterator i2, const string& str);
string& replace (size_t pos, size_t len, const string& str,
size_t subpos, size_t sublen);
string& replace (size_t pos, size_t len, const char* s);
string& replace (iterator i1, iterator i2, const char* s);
string& replace (size_t pos, size_t len, const char* s, size_t n);
string& replace (iterator i1, iterator i2, const char* s, size_t n);
string& replace (size_t pos, size_t len, size_t n, char c);
string& replace (iterator i1, iterator i2, size_t n, char c);
template <class InputIterator>
string& replace (iterator i1, iterator i2,
InputIterator first, InputIterator last);
将字符串的一部分替换为其他内容。
string类的迭代器
由于string类管理的还是一片连续的空间,所以迭代器与指针很类似,string提供了很方便的指针接口。在使用上其实指针更为方便一些。而且,但这里还是简单介绍一下,迭代器在一些oj中还是有用的。
begin
iterator begin();
const_iterator begin() const;
返回一个指向第一个字符的迭代器。
end
iterator end();
const_iterator end() const;
返回一个指向最后一个字符的下一位的迭代器。
rbegin
reverse_iterator rbegin();
const_reverse_iterator rbegin() const;
返回一个指向最后一个字符的反向迭代器。
rend
reverse_iterator rend();
const_reverse_iterator rend() const;
返回一个指向第一个字符的前一位的反向迭代器。
cbegin
const_iterator cbegin() const noexcept;
返回一个指向第一个字符的常量迭代器。
cend
const_iterator cend() const noexcept;
返回一个指向最后一个字符的下一位的常量迭代器。
crbegin
const_reverse_iterator crbegin() const noexcept;
返回一个指向最后一个字符的常量反向迭代器。
crend
const_reverse_iterator crend() const noexcept;
返回一个指向第一个字符的前一位的常量反向迭代器。
string类的查找与比较
find
size_t find (const string& str, size_t pos = 0) const;
size_t find (const char* s, size_t pos = 0) const;//字符串指针
size_t find (const char* s, size_t pos, size_t n) const;//字符数组指针指定大小
size_t find (char c, size_t pos = 0) const;
在字符串中查找字符或字符串,返回找到的第一个的位置,常用第一和最后一个。
rfind
size_t rfind (const string& str, size_t pos = npos) const;
size_t rfind (const char* s, size_t pos = npos) const;
size_t rfind (const char* s, size_t pos, size_t n) const;
size_t rfind (char c, size_t pos = npos) const;
在字符串中反向查找字符或字符串,返回反向找到的找到的第一个的位置,常用第一和最后一个。
find_first_of
size_t find_first_of (const string& str, size_t pos = 0) const;
size_t find_first_of (const char* s, size_t pos = 0) const;
size_t find_first_of (const char* s, size_t pos, size_t n) const;
size_t find_first_of (char c, size_t pos = 0) const;
在字符串中寻找与给定字符串或字符中任意一个字符相匹配的第一个的位置返回。
find_last_of
size_t find_last_of (const string& str, size_t pos = npos) const;
size_t find_last_of (const char* s, size_t pos = npos) const;
size_t find_last_of (const char* s, size_t pos, size_t n) const;
size_t find_last_of (char c, size_t pos = npos) const;
在字符串中反向寻找与给定字符串或字符中任意一个字符相匹配的第一个的位置返回。
find_first_not_of
size_t find_first_not_of (const string& str, size_t pos = 0) const;
size_t find_first_not_of (const char* s, size_t pos = 0) const;
size_t find_first_not_of (const char* s, size_t pos, size_t n) const;
size_t find_first_not_of (char c, size_t pos = 0) const;
在字符串中寻找与给定字符串或字符中任意一个字符都不相匹配的第一个的位置返回。
find_last_not_of
size_t find_last_not_of (const string& str, size_t pos = npos) const;
size_t find_last_not_of (const char* s, size_t pos = npos) const;
size_t find_last_not_of (const char* s, size_t pos, size_t n) const;
size_t find_last_not_of (char c, size_t pos = npos) const;
在字符串中反向寻找与给定字符串或字符中任意一个字符都不相匹配的第一个的位置返回。
compare
int compare (const string& str) const;
int compare (size_t pos, size_t len, const string& str) const;
int compare (size_t pos, size_t len, const string& str,
size_t subpos, size_t sublen) const;
int compare (const char* s) const;
int compare (size_t pos, size_t len, const char* s) const;
int compare (size_t pos, size_t len, const char* s, size_t n) const;
compare与C语言的cmopare类似,将两个字符串相比较,这里的比较是逐字符按ascii码值大小相比较,有一个不相等就按大小出结果,如果一条长一条段且短的比完了还是相等就算长的大,当返回0时表示两个字符串完全相等,大于0表示第一个参数大,小于0表示第二个参数大。
string类的非成员函数的操作符重载
string operator+ (const string& lhs, const string& rhs);
string operator+ (const string& lhs, const char* rhs);
string operator+ (const char* lhs, const string& rhs);
string operator+ (const string& lhs, char rhs);
string operator+ (char lhs, const string& rhs);
bool operator== (const string& lhs, const string& rhs);
bool operator== (const char* lhs, const string& rhs);
bool operator== (const string& lhs, const char* rhs);
bool operator!= (const string& lhs, const string& rhs);
bool operator!= (const char* lhs, const string& rhs);
bool operator!= (const string& lhs, const char* rhs);
bool operator< (const string& lhs, const string& rhs);
bool operator< (const char* lhs, const string& rhs);
bool operator< (const string& lhs, const char* rhs);
bool operator<= (const string& lhs, const string& rhs);
bool operator<= (const char* lhs, const string& rhs);
bool operator<= (const string& lhs, const char* rhs);
bool operator> (const string& lhs, const string& rhs);
bool operator> (const char* lhs, const string& rhs);
bool operator> (const string& lhs, const char* rhs);
bool operator>= (const string& lhs, const string& rhs);
bool operator>= (const char* lhs, const string& rhs);
bool operator>= (const string& lhs, const char* rhs);
istream& operator>> (istream& is, string& str);
ostream& operator<< (ostream& os, const string& str);
istream& getline (istream& is, string& str, char delim);
istream& getline (istream& is, string& str);
string类的模拟实现
代码整体展示
//My_String.h
#pragma once
#include<iostream>
#include<assert.h>
namespace jiunian
{
class string
{
public:
friend std::ostream& operator<<(std::ostream& _cout, const jiunian::string& s);
friend std::istream& operator>>(std::istream& _cin, jiunian::string& s);
typedef char* iterator;
void Expansion();//扩容
string(const char* str = "");
string(const string& s);
string& operator=(const string& s);
~string();
// iterator
iterator begin();
iterator end();
// modify
void push_back(char c);
string& operator+=(char c);
void append(const char* str);
string& operator+=(const char* str);
void clear();
void swap(string& s);
const char* c_str()const;
// capacity
size_t size()const;
size_t capacity()const;
bool empty()const;
void resize(size_t n, char c = '\0');
void reserve(size_t n);
// access
char& operator[](size_t index);
const char& operator[](size_t index)const;
//relational operators
bool operator<(const string& s);
bool operator<=(const string& s);
bool operator>(const string& s);
bool operator>=(const string& s);
bool operator==(const string& s);
bool operator!=(const string& s);
// 返回c在string中第一次出现的位置
size_t find(char c, size_t pos = 0) const;
// 返回子串s在string中第一次出现的位置
size_t find(const char* s, size_t pos = 0) const;
// 在pos位置上插入字符c/字符串str,并返回该字符的位置
string& insert(size_t pos, char c);
string& insert(size_t pos, const char* str);
// 删除pos位置上的元素,并返回该元素的下一个位置
string& erase(size_t pos, size_t len);
private:
char* _str;
size_t _capacity;
size_t _size;
static const size_t npos;
};
};
//My_String.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"My_String.h"
void jiunian::string::Expansion()
{
char* ptr = (char*)malloc(2 * (_capacity + 1) * sizeof(char));
if (ptr == nullptr)
{
perror("Expansion:malloc:");
return;
}
memset(ptr, 0, 2 * (_capacity + 1) * sizeof(char));
memcpy(ptr, _str, sizeof(char) * _size);
free(_str);
_str = ptr;
_capacity = _capacity * 2 + 1;
}
jiunian::string::string(const char* str)
{
char* ptr = (char*)malloc((strlen(str) + 1) * sizeof(char));
if (ptr == nullptr)
{
perror("string:malloc:");
return;
}
_str = ptr;
for (int i = 0; i < strlen(_str) + 1; ++i)
{
_str[i] = str[i];
}
_size = strlen(_str);
_capacity = strlen(_str);
}
jiunian::string::string(const string& s):
_size(s._size),
_capacity(s._capacity)
{
char* ptr = (char*)malloc((s._capacity + 1) * sizeof(char));
if (ptr == nullptr)
{
perror("string:malloc:");
return;
}
_str = ptr;
memcpy(_str, s._str, (s._capacity + 1) * sizeof(char));
}
jiunian::string& jiunian::string::operator=(const string& s)
{
string ex(s);
std::swap(ex._str, _str);
std::swap(ex._size, _size);
std::swap(ex._capacity, _capacity);
return *this;
}
jiunian::string::~string()
{
//std::cout << "~string";
free(_str);
_str = nullptr;
_size = 0;
_capacity = 0;
}
jiunian::string::iterator jiunian::string::begin()
{
return _str;
}
jiunian::string::iterator jiunian::string::end()
{
return _str + _size;
}
void jiunian::string::push_back(char c)
{
if (_size == _capacity)
Expansion();
_str[_size] = c;
++_size;
}
void jiunian::string::append(const char* str)
{
while (_size + strlen(str) > _capacity)
{
Expansion();
}
for (int i = _size; i < _size + strlen(str); ++i)
{
_str[i] = str[i - _size];
}
_size += strlen(str);
}
jiunian::string& jiunian::string::operator+=(char c)
{
push_back(c);
return *this;
}
jiunian::string& jiunian::string::operator+=(const char* str)
{
append(str);
return *this;
}
void jiunian::string::clear()
{
memset(_str, 0, _size * sizeof(char));
_size = 0;
}
void jiunian::string::swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
const char* jiunian::string::c_str()const
{
return _str;
}
size_t jiunian::string::capacity()const
{
return _capacity;
}
size_t jiunian::string::size()const
{
return _size;
}
bool jiunian::string::empty()const
{
return _size == 0 ? true : false;
}
void jiunian::string::resize(size_t n, char c)
{
while (n > _capacity)
{
Expansion();
}
if (n > _size)
{
for (int i = _size; i < n; ++i)
{
_str[i] = c;
}
}
else
{
for (int i = n; i < _size; ++i)
{
_str[i] = '\0';
}
}
_size = n;
}
void jiunian::string::reserve(size_t n)
{
if (n <= _capacity)
return;
while (n > _capacity)
{
Expansion();
}
}
char& jiunian::string::operator[](size_t index)
{
return _str[index];
}
const char& jiunian::string::operator[](size_t index)const
{
return _str[index];
}
bool jiunian::string::operator<(const string& s)
{
for (int i = 0; i < _size > s._size ? s._size : _size; ++i)
{
if (_str[i] < s._str[i])
{
return true;
}
if (_str[i] > s._str[i])
{
return false;
}
}
return _size < s._size ? true : false;
}
bool jiunian::string::operator==(const string& s)
{
if (_size != s._size)
return false;
for (int i = 0; i < _size; ++i)
{
if (_str[i] != s._str[i])
return false;
}
return true;
}
bool jiunian::string::operator<=(const string& s)
{
return *this < s || *this == s;
}
bool jiunian::string::operator>(const string& s)
{
return !(*this <= s);
}
bool jiunian::string::operator>=(const string& s)
{
return !(*this < s);
}
bool jiunian::string::operator!=(const string& s)
{
return !(*this == s);
}
size_t jiunian::string::find(char c, size_t pos) const
{
while (pos != _size)
{
if (_str[pos++] == c)
{
return --pos;
}
}
return npos;
}
size_t jiunian::string::find(const char* s, size_t pos) const
{
assert(strlen(s) != 0);
while (pos <= _size - strlen(s))
{
if (_str[pos] == *s && _size - pos > strlen(s))
{
int size = strlen(s);
size_t Pos = pos;
size_t Old_Pos = pos;
int record = 0;
for(int i = 0; i < size; ++i)
{
if (_str[Pos] == *s && record != 0 &&pos != 1)
{
pos = Pos - 1;
record = 1;
}
if (_str[Pos++] != s[i])
{
if (record == 0)
{
pos = Pos - 1 - 1;
}
record = -1;
break;
}
}
if (record != -1)
{
return Old_Pos;
}
}
++pos;
}
return pos;
}
jiunian::string& jiunian::string::insert(size_t pos, char c)
{
if (_size + 1 > _capacity)
Expansion();
memmove(_str + pos + 1, _str + pos, _size * sizeof(char));
_str[pos] = c;
++_size;
return *this;
}
jiunian::string& jiunian::string::insert(size_t pos, const char* str)
{
if (_size + strlen(str) > _capacity)
Expansion();
memmove(_str + pos + strlen(str), _str + pos, _size * sizeof(char));
memcpy(_str + pos, str, strlen(str) * sizeof(char));
_size += strlen(str);
return *this;
}
jiunian::string& jiunian::string::erase(size_t pos, size_t len)
{
memcpy(_str + pos, _str + pos + len, (_size - pos - len) * sizeof(char));
if (len > _size - pos - len)
{
memset(_str + _size - len, 0, 2 * len - (_size - pos));
}
_size -= len;
return *this;
}
std::ostream& jiunian::operator<<(std::ostream& _cout, const jiunian::string& s)
{
_cout << s.c_str();
return _cout;
}
std::istream& jiunian::operator>>(std::istream& _cin, jiunian::string& s)
{
s.clear();
char Get = 0;
_cin.get(Get);
while (Get != '\n')
{
s.push_back(Get);
_cin.get(Get);
}
return _cin;
}
实现思路
jiunian::string::string(const char* str)
{
char* ptr = (char*)malloc((strlen(str) + 1) * sizeof(char));
if (ptr == nullptr)
{
perror("string:malloc:");
return;
}
_str = ptr;
for (int i = 0; i < strlen(_str) + 1; ++i)
{
_str[i] = str[i];
}
_size = strlen(_str);
_capacity = strlen(_str);
}
对于string的构造函数,我们采取与顺序表一样的做法就行,使用malloc开辟开辟对应大小的空间,将各个值初始化即可。需要注意的是我们要留意将\0也存储下来方便后面的打印,而且为了后续的扩容检查,我将capacity的值定位实际容量减1,那个1是留给\0的空间,也就是capacity是当前字符数组那存放的有效字符的大小。
jiunian::string::string(const string& s):
_size(s._size),
_capacity(s._capacity)
{
char* ptr = (char*)malloc((s._capacity + 1) * sizeof(char));
if (ptr == nullptr)
{
perror("string:malloc:");
return;
}
_str = ptr;
memcpy(_str, s._str, (s._capacity + 1) * sizeof(char));
}
拷贝构造的实现也是相当简单,开辟一片一样大的空间再将s的字符串数组的数据全部拷贝过来就行。
jiunian::string& jiunian::string::operator=(const string& s)
{
string ex(s);
std::swap(ex._str, _str);
std::swap(ex._size, _size);
std::swap(ex._capacity, _capacity);
return *this;
}
复制运算符的重载我使用了相对巧妙地方法,利用了拷贝构造函数。我们先用s拷贝构造一个ex,再将ex的数据全部与自己(*this)交换,这样相当于交换了两者的控制权,这时自己变成了ex,ex变成了自己。在函数结束后返回时自己就变成了与s完全相等的string类类型对象,而交换完的ex因为是局部变量,会在结束时调用析构函数自动销毁,这样就完成了string的拷贝。
jiunian::string::~string()
{
//std::cout << "~string";
free(_str);
_str = nullptr;
_size = 0;
_capacity = 0;
}
没什么好说的,free后置空就行。
jiunian::string::iterator jiunian::string::begin()
{
return _str;
}
jiunian::string::iterator jiunian::string::end()
{
return _str + _size;
}
对于string这种存储空间连续的类来说,迭代器和指针差不多,我们这里返回指针来表示代替迭代器,再在类中将char* typedef成iterator就行。
void jiunian::string::Expansion()
{
char* ptr = (char*)malloc(2 * (_capacity + 1) * sizeof(char));
if (ptr == nullptr)
{
perror("Expansion:malloc:");
return;
}
memset(ptr, 0, 2 * (_capacity + 1) * sizeof(char));
memcpy(ptr, _str, sizeof(char) * _size);
free(_str);
_str = ptr;
_capacity = _capacity * 2 + 1;
}
对于string这种类似顺序表的结构,我们肯定少不了扩容函数,这里我们采取两倍扩的方式,由于先前我们说过的,capacity是实际存储大小减一,所以扩容后的大小是2 * (_capacity + 1) * sizeof(char),由于扩容的尺寸会越来越大,所以即使使用realloc也不会有多少原地扩的机会,所以我直接用malloc选择异地扩容,效率还更高,注意扩容后先将开辟好的空间中的数据全部设置为\0,这样在数据拷贝完之后剩余的空间就都是\0,打印时遇到末尾会直接停,而且之后尾插时,不用考虑多插一个\0了。
void jiunian::string::push_back(char c)
{
if (_size == _capacity)
Expansion();
_str[_size] = c;
++_size;
}
进来先检查是否需要扩容,之后尾插字符,++_size就行。
void jiunian::string::append(const char* str)
{
while (_size + strlen(str) > _capacity)
{
Expansion();
}
for (int i = _size; i < _size + strlen(str); ++i)
{
_str[i] = str[i - _size];
}
_size += strlen(str);
}
进来需要检查字符串当前长度加上要尾插的字符串的长度是否越界,越界了就要扩容,之后循环尾插字符,调整_size大小就行。
jiunian::string& jiunian::string::operator+=(char c)
{
push_back(c);
return *this;
}
jiunian::string& jiunian::string::operator+=(const char* str)
{
append(str);
return *this;
}
对于+=运算符重载我们给上一个函数重载,一个字符+=,一个字符串+=,内部用push_back和append来实现就行,这样尾插我们用+=就行。
void jiunian::string::clear()
{
memset(_str, 0, _size * sizeof(char));
_size = 0;
}
clear会置空字符串,但不会更改capacity的大小,我们使用memset将数据全部改为0之后再将_size置空就行。
void jiunian::string::swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
内部三个标准库的swap交换就能完成两个string类的交换。
const char* jiunian::string::c_str()const
{
return _str;
}
size_t jiunian::string::capacity()const
{
return _capacity;
}
size_t jiunian::string::size()const
{
return _size;
}
bool jiunian::string::empty()const
{
return _size == 0 ? true : false;
}
返回信息的四个函数,直接返回就行。
void jiunian::string::resize(size_t n, char c)
{
while (n > _capacity)
{
Expansion();
}
if (n > _size)
{
for (int i = _size; i < n; ++i)
{
_str[i] = c;
}
}
else
{
for (int i = n; i < _size; ++i)
{
_str[i] = '\0';
}
}
_size = n;
}
首先我们对于更改后的_size也就是n与capacity进行一个比较,不够就扩容。之后再与_size进行比较,如果n > _size就将后面直到_size为n位置的数据更改为c,如果n < _size就截断数组,for循环将n到_size之间的数据赋值为\0。
void jiunian::string::reserve(size_t n)
{
if (n <= _capacity)
return;
while (n > _capacity)
{
Expansion();
}
}
如果n <= _capacity,函数什么也不会做直接返回,如果n > _capacity就不断扩容到n <= _capacity为止。
char& jiunian::string::operator[](size_t index)
{
return _str[index];
}
const char& jiunian::string::operator[](size_t index)const
{
return _str[index];
}
对于[]的重载只需要将对应的字符返回就行,因为有_str指针,实现很方便。
bool jiunian::string::operator<(const string& s)
{
for (int i = 0; i < _size > s._size ? s._size : _size; ++i)
{
if (_str[i] < s._str[i])
{
return true;
}
if (_str[i] > s._str[i])
{
return false;
}
}
return _size < s._size ? true : false;
}
对于操作符<的重载,我们使用和compare一样的思路,先逐字符比较,有比一样返回结果,如果比到较短字符串结束和一样就比谁长,一样长就相等。
bool jiunian::string::operator==(const string& s)
{
if (_size != s._size)
return false;
for (int i = 0; i < _size; ++i)
{
if (_str[i] != s._str[i])
return false;
}
return true;
}
操作符=的重载就更为简单,先比较长度是否一样,不一样返回false,一样再逐字节比较,不一样就返回false,比完没问题返回true 。
bool jiunian::string::operator<=(const string& s)
{
return *this < s || *this == s;
}
bool jiunian::string::operator>(const string& s)
{
return !(*this <= s);
}
bool jiunian::string::operator>=(const string& s)
{
return !(*this < s);
}
bool jiunian::string::operator!=(const string& s)
{
return !(*this == s);
}
有了<和=就可以用这两个拼出其他所有的比较操作符。
size_t jiunian::string::find(char c, size_t pos) const
{
while (pos != _size)
{
if (_str[pos++] == c)
{
return --pos;
}
}
return npos;
}
size_t jiunian::string::find(const char* s, size_t pos) const
{
assert(strlen(s) != 0);
while (pos <= _size - strlen(s))
{
if (_str[pos] == *s && _size - pos > strlen(s))
{
int size = strlen(s);
size_t Pos = pos;
size_t Old_Pos = pos;
int record = 0;
for(int i = 0; i < size; ++i)
{
if (_str[Pos] == *s && record != 0 &&pos != 1)
{
pos = Pos - 1;
record = 1;
}
if (_str[Pos++] != s[i])
{
if (record == 0)
{
pos = Pos - 1 - 1;
}
record = -1;
break;
}
}
if (record != -1)
{
return Old_Pos;
}
}
++pos;
}
return pos;
}
find函数重载了两个版本,一个是针对字符的,一个是针对字符串的。针对字符的相对简单,顺次寻找,找到返回pos,找不到返回npos。而字符串版本的就相对复杂,我们首先取要匹配的字符串的首字符进行匹配,找到之后在进一步进行匹配,匹配成功再返回pos,不行继续找。我在这样的思路的基础之上进行了小小的优化,在字符串进一步匹配的过程中顺便对要匹配的字符串的首字符进行匹配,这样在匹配失败返回之后可以直接跳到匹配过程中找到的位置继续匹配,提高了效率,我使用Pos作为进一步匹配的位置指示器每个循环都++来访问字符数组,当遇到匹配的字符串的首字符且不是第一次(第一次还在原地)时就进入if语句,将Pos赋值给pos,record置为1表示pos已经到了下一个位置了(因为pos要++所以先减1和后面的抵消)之后就不会进入这个if语句给pos赋值了。当不匹配时,for循环会结束,进入下一个while循环,这没有问题,但是当if语句中的for循环顺利走完我们就应该返回pos了,如何判断呢,我们可以通过record来判断,匹配失败结束时record会被赋值为-1,所以record不为-1就说明匹配成功,要返回pos了,可是pos可能会在查找的过程中移动到下一个匹配好的位置,所以我们用Old_Pos保存一下pos方便返回。
jiunian::string& jiunian::string::insert(size_t pos, char c)
{
if (_size + 1 > _capacity)
Expansion();
memmove(_str + pos + 1, _str + pos, _size * sizeof(char));
_str[pos] = c;
++_size;
return *this;
}
jiunian::string& jiunian::string::insert(size_t pos, const char* str)
{
if (_size + strlen(str) > _capacity)
Expansion();
memmove(_str + pos + strlen(str), _str + pos, _size * sizeof(char));
memcpy(_str + pos, str, strlen(str) * sizeof(char));
_size += strlen(str);
return *this;
}
对于insert我也提供了一个函数重载,一个针对字符,一个针对字符串。insert的实现无非就是挪动数据,头插,注意好边界就行。
jiunian::string& jiunian::string::erase(size_t pos, size_t len)
{
memcpy(_str + pos, _str + pos + len, (_size - pos - len) * sizeof(char));
if (len > _size - pos - len)
{
memset(_str + _size - len, 0, 2 * len - (_size - pos));
}
_size -= len;
return *this;
}
erase函数的作用是消除字符串的一部分,我们直接使用memcpy覆盖就行,但也要考虑len过长覆盖完之后尾部没被覆盖掉的情况,我们使用if语句应对这种情况,将超出来的尾部赋值为\0就行。
std::ostream& jiunian::operator<<(std::ostream& _cout, const jiunian::string& s)
{
_cout << s.c_str();
return _cout;
}
std::istream& jiunian::operator>>(std::istream& _cin, jiunian::string& s)
{
s.clear();
char Get = 0;
_cin.get(Get);
while (Get != '\n')
{
s.push_back(Get);
_cin.get(Get);
}
return _cin;
}
最后是流插入和流提取操作符,流插入操作符没什么好说的。对于流提取操作符由于我们不知道用户事先要输入多少字符,所以只能逐个提取再尾插到字符串中,逐个提取字符的操作我们使用istream中的get函数,它可以不限字符内容读取一个字符,包括空格和换行符,我们检测提取的字符是否为换行符,是就结束循环返回_cin。