目录
构造函数
析构函数
拷贝构造
赋值重载
迭代器( begin() 和 end() )
运算符重载流插入( operator << ( ) )
size()
capacity()
运算符重载operator[ ]
clear()
reserve ( )
push_back ( )
append ( )
运算符重载 operator += ( )
insert ( )
erase ( )
find ( )
substr ( )
c_str( )
比较大小相关函数
运算符重载流提取( operator >> ( ) )
完整源代码
String.h
String.c
StringTest.c
看一下string类常用函数的实现。其本质还是 string 类还是通过顺序表来进行存储的,其通过三个私有成员变量来存储字符串信息。
class string
{
private:
char* _str;
size_t _capacity;
size_t _size;
}
构造函数
由于字符串要以 '\0' 结尾,即使字符串长度为 0 时依旧还要储存 '\0' ,所以当其在初始化列表初始化时,_str不能初始化为 nullptr ,要开辟空间存储 '\0' 。
string()
:_str(new char[1]{'\0'})
,_size(0)
,_capacity(0)
{
}
带参的构造函数
string(const char* str)
{
_size = strlen(str);
_capacity = _size;
//多出的一个空间用于存储'\0'
_str = new char[_capacity + 1];
//将str的值拷贝给_str
strcpy(_str, str);
}
也可以加上缺省值,如空字符串。
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
//多出的一个空间用于存储'\0'
_str = new char[_capacity + 1];
// 将str的值拷贝给_str
strcpy(_str, str);
}
可以看到,上方的代码实现了默认构造和带参构造的功能,将两者结合为在一起。
析构函数
要释放为 _str 开辟的空间。
~string()
{
delete[] _str;
_str = nullptr;
_capacity = _size = 0;
}
拷贝构造
由于 _str 需要开辟空间,所以拷贝构造需要手动实现深拷贝。
string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_capacity = s._capacity;
_size = s._size;
}
赋值重载
赋值重载函数和拷贝构造相同,也需要深拷贝。
string& operator=(const string& s)
{
//要注意,如果是自己和自己赋值,就不能释放空间,直接返回即可
if (this != &s)
{
//先将原来的开辟的空间释放
delete[] _str;
//再重新开辟与s._str相同大小的空间
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_capacity = s._capacity;
_size = s._size;
}
return *this;
}
迭代器( begin() 和 end() )
迭代器功能强大,实现其也很复杂,但如果要简单模拟迭代器,却十分简单。在string 类中,其会返回字符串起始位置和结尾位置的下一位,其用法类似于指针。所以要简单模拟迭代器,我们可以使用指针来实现。
// 将类型重命名
typedef char* iterator;
iterator begin()
{
//返回起始位置的指针
return _str;
}
iterator end()
{
//返回结尾位置的下一位
return _str + _size;
}
还可以实现一个 const 版本的迭代器。
typedef const char* const_iterator;
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
运算符重载流插入( operator << ( ) )
其要实现传入一个实例化的对象就可以把其存储的字符串打印在屏幕上。可以通过循环解决。
ostream& operator<<(ostream& out, const string& s)
{
//通过遍历每个字符,把字符打印出来
for (auto ch : s)
{
out << ch;
}
return out;
}
size()
返回 string 类对象的 _size 。
size_t size() const
{
return _size;
}
capacity()
返回 string 类对象的 _capacity 。
size_t capacity() const
{
return _capacity;
}
运算符重载operator[ ]
取得第 i 位置元素的值。
char& operator[](size_t pos)
{
return _str[pos];
}
const char& operator[](size_t pos) const
{
return _str[pos];
}
clear()
要清空数据,可以直接把第一个位置置为 '\0' ,同时把 _size 置为 0 即可。
void clear()
{
_str[0] = '\0';
_size = 0;
}
reserve ( )
用于开辟一块空间,常用于扩容。
void string::reserve(size_t n)
{
if (n > _capacity)
{
//先开辟一块与_str大小相同的空间
char* tmp = new char[n + 1];
//拷贝_str的数据
strcpy(tmp, _str);
//销毁原来的空间
delete[] _str;
//再把重新开辟的空间指向_str,并改变容量
_str = tmp;
_capacity = n;
}
}
push_back ( )
在尾部插入一个字符。
//在尾部插入一个字符
void string::push_back(char c)
{
//考虑空间是否充足,是否要扩容
if (_capacity == _size)
{
//判断_capacity是否为0
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
//注意,此时要插入的字符将原来的'\0'覆盖,后面要手动添加'\0'
_str[_size] = c;
_size++;
//要手动添加'\0',
_str[_size] = '\0';
}
append ( )
在尾部插入一串字符。
//在尾部插入一串字符
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
//如果要_size + len > 2 * _capacity,就按_size + len扩容,如果没有,就按二倍扩容
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
注意 i <= len,要将'\0'也拷贝进去
//for (size_t i = 0; i <= len; i++)
//{
// _str[_size + i] = str[i];
//}
//可以直接用strcpy代替,其会把'\0'也拷贝进去
strcpy(_str + _size, str);
_size += len;
}
运算符重载 operator += ( )
直接 += 一个或一串字符。可以直接复用 push_back( ) 和 append( ) 。
//+= 一个字符
string& string::operator+=(char c)
{
push_back(c);
return *this;
}
//+= 一串字符
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
insert ( )
在任意位置插入 1 个字符。
//在pos位置上插入字符c
void string::insert(size_t pos, char c)
{
//先看是否扩容
if (_capacity == _size)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
//将'\0'也向后挪一位,同时有效防止pos=0出现数组越界的情况
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = c;
_size++;
}
在任意位置插入 n 个字符。
//在pos位置上插入字符串str
void string::insert(size_t pos, const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
size_t end = _size + len;
while (end > pos + len - 1)
{
_str[end] = _str[end - len];
end--;
}
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
}
erase ( )
从任意位置删除 n 个数据,如果没有指定长度,默认删除到结尾。
// 删除pos位置开始的 len 个元素
void string::erase(size_t pos, size_t len)
{
//如果从pos位置开始,要删除的数据大于剩余长度,
// 就说明从pos位置开始的字符都要删除
if (len >= _size - pos)
{
//可以直接把pos位置置为'\0'
_str[pos] = '\0';
_size = pos;
}
else
{
//如果不是,就把后面的一次向前挪动,
// 注意 i <= _size,'\0'也要挪动
for (size_t i = pos + len; i <= _size; i++)
{
_str[i - len] = _str[i];
}
_size -= len;
}
}
find ( )
查找字符或字符串第一次出现的位置,并返回下标,如果没有找到,就返回npos。
// 返回c在string中第一次出现的位置
size_t string::find(char c, size_t pos) const
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == c)
{
return i;
}
}
return npos;
}
查找一串字符,可以用C语言的库函数 strstr( ),也可以自己手动实现查找,(具体实现看这篇文章: 字符串匹配算法)。
// 返回子串s在string中第一次出现的位置
size_t string::find(const char* s, size_t pos) const
{
//可以直接使用C语言的库函数strstr()
const char* str = strstr(_str + pos, s);
//如果未找到相应的子串,就会返回 nullptr
if (str == nullptr)
{
return npos;
}
else
{
return str - _str;
}
}
substr ( )
从 pos 位置开始的 n 个字符构造子串,并返回。如果没有指定 n ,就默认到结尾。
string string::substr(size_t pos, size_t len)
{
if (len >= _size - pos)
{
len = _size - pos;
}
string sub;
//预先开好空间,减少扩容
sub.reserve(len);
for (size_t i = 0; i < len; i++)
{
sub += _str[pos + i];
}
return sub;
}
c_str( )
其是string类构造的一个函数,用于返回指向数组的指针,也就是说它返回当前字符串的首字符地址。
其实现也很简单。
//返回字符串第一个节点的位置
const char* c_str() const
{
return _str;
}
比较大小相关函数
比较字符串,可以使用C语言中的 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 s1 < s2 || s1 == s2;
}
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);
}
运算符重载流提取( operator >> ( ) )
先来看代码:
istream& operator>>(istream& in, string& s)
{
//重新输入数据之前要先把之前的数据清空
s.clear();
char ch;
in >> ch;
while (ch != ' ' && ch != '\n')
{
s += ch;
in >> ch;
}
return in;
}
会发现其无论如何输入,都不会停止。这是因为在输入时,in 会自动识别并跳过空格和换行,但我们要通过空格和换行来判断循环是否结束,所以导致陷入死循环。因此,我们要使用 istream 类当中的 get() 函数。
istream& operator>>(istream& in, string& s)
{
//重新输入数据之前要先把之前的数据清空
s.clear();
char ch;
//in >> ch;
ch = in.get();
while (ch != ' ' && ch != '\n')
{
s += ch;
//in >> ch;
ch = in.get();
}
return in;
}
完整源代码
String.h
#include<iostream>
#include<assert.h>
using namespace std;
namespace Friend
{
class string
{
public:
//构造函数
/*string()
:_str(new char[1]{'\0'})
,_size(0)
,_capacity(0)
{
}*/
//string(const char* str)
//{
// _size = strlen(str);
// _capacity = _size;
// //多出的一个空间用于存储'\0'
// _str = new char[_capacity + 1];
// //将str的值拷贝给_str
// strcpy(_str, str);
//}
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
//多出的一个空间用于存储'\0'
_str = new char[_capacity + 1];
// 将str的值拷贝给_str
strcpy(_str, str);
}
//拷贝构造s1(s2)
string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_capacity = s._capacity;
_size = s._size;
}
//赋值重载s1 = s2
string& operator=(const string& s)
{
if (this != &s)
{
//先将原来的开辟的空间释放
delete[] _str;
//再重新开辟与s._str相同大小的空间
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_capacity = s._capacity;
_size = s._size;
}
return *this;
}
//析构
~string()
{
delete[] _str;
_str = nullptr;
_capacity = _size = 0;
}
// 将类型重命名
typedef char* iterator;
iterator begin()
{
//返回起始位置的指针
return _str;
}
iterator end()
{
//返回结尾位置的下一位
return _str + _size;
}
typedef const char* const_iterator;
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
//返回字符串第一个节点的位置
const char* c_str() const
{
return _str;
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
char& operator[](size_t pos)
{
return _str[pos];
}
const char& operator[](size_t pos) const
{
return _str[pos];
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
//扩容
void reserve(size_t n);
void push_back(char c);
void append(const char* str);
string& operator+=(char c);
string& operator+=(const char* str);
// 在pos位置上插入字符c/字符串str,并返回该字符的位置
void insert(size_t pos, char c);
void insert(size_t pos, const char* str);
// 删除pos位置上的元素,并返回该元素的下一个位置
void erase(size_t pos, size_t len = npos);
// 返回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;
string substr(size_t pos = 0, size_t len = npos);
private:
char* _str;
size_t _capacity;
size_t _size;
const static size_t npos;
};
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);
ostream& operator<<(ostream& out, const string& s);
istream& operator>>(istream& in, string& s);
}
String.c
#include "String.h"
namespace Friend
{
//扩容
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 c)
{
//考虑空间是否充足,是否要扩容
if (_capacity == _size)
{
//判断_capacity是否为0
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
//注意,此时要插入的字符将原来的'\0'覆盖,后面要手动添加'\0'
_str[_size] = c;
_size++;
//要手动添加'\0',
_str[_size] = '\0';
}
//在尾部插入一串字符
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
//如果要_size + len > 2 * _capacity,就按_size + len扩容,如果没有,就按二倍扩容
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
注意 i <= len,要将'\0'也拷贝进去
//for (size_t i = 0; i <= len; i++)
//{
// _str[_size + i] = str[i];
//}
//可以直接用strcpy代替,其会把'\0'也拷贝进去
strcpy(_str + _size, str);
_size += len;
}
//+= 一个字符
string& string::operator+=(char c)
{
push_back(c);
return *this;
}
//+= 一串字符
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
//在pos位置上插入字符c
void string::insert(size_t pos, char c)
{
//先看是否扩容
if (_capacity == _size)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
//将'\0'也向后挪一位,同时有效防止pos=0出现数组越界的情况
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = c;
_size++;
}
//在pos位置上插入字符串str
void string::insert(size_t pos, const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
size_t end = _size + len;
while (end > pos + len - 1)
{
_str[end] = _str[end - len];
end--;
}
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
}
const size_t string::npos = -1;
// 删除pos位置开始的 len 个元素
void string::erase(size_t pos, size_t len)
{
//如果从pos位置开始,要删除的数据大于剩余长度,
// 就说明从pos位置开始的字符都要删除
if (len >= _size - pos)
{
//可以直接把pos位置置为'\0'
_str[pos] = '\0';
_size = pos;
}
else
{
//如果不是,就把后面的一次向前挪动,
// 注意 i <= _size,'\0'也要挪动
for (size_t i = pos + len; i <= _size; i++)
{
_str[i - len] = _str[i];
}
_size -= len;
}
}
// 返回c在string中第一次出现的位置
size_t string::find(char c, size_t pos) const
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == c)
{
return i;
}
}
return npos;
}
// 返回子串s在string中第一次出现的位置
size_t string::find(const char* s, size_t pos) const
{
//可以直接使用C语言的库函数strstr()
const char* str = strstr(_str + pos, s);
//如果未找到相应的子串,就会返回 nullptr
if (str == nullptr)
{
return npos;
}
else
{
return str - _str;
}
}
string string::substr(size_t pos, size_t len)
{
if (len >= _size - pos)
{
len = _size - pos;
}
string sub;
//预先开好空间,减少扩容
sub.reserve(len);
for (size_t i = 0; i < len; i++)
{
sub += _str[pos + i];
}
return sub;
}
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 || s1 == s2;
}
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, const string& s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
istream& operator>>(istream& in, string& s)
{
//重新输入数据之前要先把之前的数据清空
s.clear();
char ch;
//in >> ch;
ch = in.get();
while (ch != ' ' && ch != '\n')
{
s += ch;
//in >> ch;
ch = in.get();
}
return in;
}
}
StringTest.c
#include "String.h"
namespace Friend
{
void StringTest1()
{
string s;
cout << s << endl;
string s1("hello,world");
cout << s1 << endl;
s.push_back('x');
cout << s << endl;
s.append("*****");
cout << s << endl;
s += "OOOO";
cout << s << endl;
s1.insert(0, 'x');
cout << s1 << endl;
s1.insert(4, "MM");
cout << s1 << endl;
s1.erase(0, 3);
cout << s1 << endl;
s1.erase(6);
cout << s1 << endl;
size_t find1 = s1.find('M', 0);
cout << find1 << endl;
size_t find2 = s.find("OOO", 0);
cout << find2 << endl;
string suffix = s.substr(find2, 4);
cout << suffix << endl;
cout << s << endl;
cin >> s;
cout << s << endl;
}
}
int main()
{
Friend::StringTest1();
return 0;
}