目录
- string的基本实现
- 一. string的基本架构
- 二. 常用方法及实现
- 1.初始化和清理
- a. 构造函数
- b. 析构函数
- c. swap
- d. 拷贝构造
- e. operator=
- 2.成员访问
- a. operator[]
- b. size
- c. c_str
- 3.迭代器iterator
- a. begin
- b. end
- 4.增删查改
- a. reserve
- b. resize
- c. insert
- d. push_back
- e. append
- f. operator+=
- g. erase
- h. find
- i. substr
- j. clear
- 5.比较运算
- 6.小结
- 三.IO
- a. operator<<
- b. operator>>
- c. getline
string的基本实现
一. string的基本架构
class string
{
private:
char* _str;//指向动态申请的空间
size_t _size;//实际存储的字符串大小
size_t _capacity;//有效数据的空间大小
};
使用库中的string类需要包含头文件#inlcude<string>
,并且使用std::
命名空间
_size:h~d的字符个数; _capacity:动态申请的空间 - 1(去除’\0’)
(所以实际动态申请的空间是:_capacity+1,多开辟一个来存放’\0’标识结尾)
二. 常用方法及实现
以实现的顺序来依次进行介绍
1.初始化和清理
a. 构造函数
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity+1];
strcpy(_str,str);
}
当没有传参时,缺省值为
""
:一个空的字符串。字符串结尾都隐藏了一个字符\0
,因此空的string对象中,也有一个\0。
b. 析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
析构函数,来完成对动态申请空间的释放。
c. swap
void swap(string& temp)
{
std::swap(_str, temp._str);
std::swap(_size, temp._size);
std::swap(_capacity, temp._capacity);
}
交换两个string对象的内容,通过作用域限定符
::
来调用库函数中的swap函数,完成对内置类型的成员变量的交换。
d. 拷贝构造
传统写法:
string (const string& s)
:_str(new char[s._capacity + 1])
,_size(s._size)
,_capacity(s._capacity)
{
strcpy(_str,s._str);
}
现代写法:
通过一个局部对象,和swap来完成
string(const string& s)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string temp(s._str);
swap(temp);
}
通过初始化列表,对内置类型的成员变量进行初始化,然后和经构造函数得到的temp对象进行交换内容,完成拷贝构造。该函数执行完,局部对象temp会自动调用析构函数,并销毁。
e. operator=
传统写法:
string& operator=(const string& s)
{
//如果是自己给自己赋值,则不需要进行操作
if(this != &s)
{
char* temp = new char[s._capacity + 1];
strcpy(temp, s._str);
delete[] _str;
_str = temp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
现代写法
string& operator=(string s)
{
swap(s);
return *this;
}
传参时,会调用拷贝构造函数完成对局部对象s的初始化,然后交换*this和s的内容,完成对自身对象的赋值。
2.成员访问
a. operator[]
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
返回pos下标字符的引用(可以读取、修改),如果pos越界,则直接assert,使程序崩溃。
b. size
size_t size() const
{
return _size;
}
c. c_str
const char* c_str() const
{
return _str;
}
3.迭代器iterator
typedef char* iterator;
//const迭代器不能进行写操作
typedef const char* const_iterator;
a. begin
iterator begin()
{
return _str;
}
const_iterator begin() const
{
return _str;
}
返回第一个元素
b. end
iterator end()
{
return _str+_size;
}
const_iterator end() const
{
return _str + _size;
}
返回最后一个元素的下一个位置
4.增删查改
a. reserve
void reserve(size_t n)
{
if(n > _capacity)
{
//多申请1个空间,用于存放'\0
char* temp = new char[n+1];
strcpy(temp,_str);
delete[] _str;
_str = temp;
_capacity = n;
}
}
申请n个字节的空间,如果n<原空间大小,则不做改变
b. resize
void resize(size_t n, char ch = '\0')
{
if(n > _size)
{
resize(n);
//将新增的存放字符ch
for(size_t i = _size; i < n; ++i)
{
_str[i] = ch;
}
}
//将最后一个元素存放\0
_str[n] = '\0';
_size = n;
}
调整字符串n个字符,如果n<原字符长度,则_str[n] = ‘\0’,只保留前n个元素。
如果n>原字符长度,保留原字符,新增的字符为ch。
c. insert
string& insert(size_t pos, char ch)
{
//断言插入的位置
assert(pos <= _size);
//空间以满就扩容
if(_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
//末尾_size处的'\0'也要一起向后挪动1位
size_t end = _size;
while(end >= pos)
{
_str[end+1] = _str[end];
--end;
}
//pos位置插入字符ch
_str[pos] = ch;
++_size;
return *this;
}
string& insert(size_t pos, const char* s)
{
assert(pos <= _size);
size_t len = strlen(s);
//判断空间是否足够
if(_size+len > _capacity)
{
reserve(_size + len);
}
//末尾_size处的'\0'也要一起向后挪动len位
size_t end = _size;
while(end >= pos)
{
_str[end+len] = _str[end];
--end;
}
//从pos位置开始,拷贝s的前len个字符(不要s的\0)
strncpy(_str+pos, s, len);
_size += len;
return *this;
}
d. push_back
void push_back(char ch)
{
insert(_size,ch);
}
在字符串尾加一个字符ch
e. append
void append(const char* s)
{
insert(_size,s);
}
在字符串尾加一个字符串s
f. operator+=
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* s)
{
append(s);
return *this;
}
g. erase
const static size_t npos = -1;
size_t是无符号整型,-1则其是最大的数
string& erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
//删除从pos位开始到最后的字符
if(len == npos || len+pos >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
//删除从pos位置开始len个字符
strcpy(_str+pos,_str+pos+len);
_size -= len;
}
return *this;
}
h. find
size_t find(char ch, size_t pos = 0) const
{
assert(pos < _size);
for(size_t i = 0; i < _size; ++i)
{
if(ch == _str[i])
{
return i;
}
}
return npos;
}
size_t find(const char* s, size_t pos = 0) const
{
assert(s);
assert(pos < _size);
//查找s是不是_str+pos的子字符串,不是返回nullptr
const char* ptr = strstr(_str+pos, s);
if(ptr != nullptr)
{
return ptr - _str;
}
else
{
return npos;
}
}
i. substr
string substr(size_t pos, size_t len = npos) const
{
assert(pos < _size);
size_t end = len;
if(len == npos || len+pos > _size)
{
end = _size;
}
//substr 是*this字符串从pos位置开始的len个字符组成的字符串
string temp;
for(size_t i = pos; i < end; ++i)
{
temp += _str[i];
}
return temp;
}
j. clear
void clear()
{
_str[0] = '\0';
_size = 0;
}
清空字符串
5.比较运算
bool operator==(const string& s) const
{
return strcmp(_str, s._str) == 0;
}
bool operator<(const string& s) const
{
return strcmp(_str, s._str)<0;
}
bool operator<=(const string& s) const
{
return *this < s || *this == s;
}
bool operator>(const string& s) const
{
return !(*this <= s);
}
bool operator>=(const string& s) const
{
return !(*this < s);
}
bool operator!=(const string& s) const
{
return !(*this == s);
}
6.小结
- 上述的函数都是放在自己实现的
class string
类中,最好将该类用自己的命名空间给包含,避免冲突。
例如:
#include <iostream>
#include <string.h>
#include <assert.h>
namespace yyjs
{
class string
{}
}
-
在实现的过程中,总是担心处理不好’\0’。其实只需注意:初始化时结尾有无\0,在进行增删时\0是否在合适位置。所以只要构造函数、insert、erase解决好‘\0’的问题,其他地方不需要担心。(值得一提的是,申请空间时,总是会多申请1字节,用来存放\0)
-
对于成员函数是否用const修饰,需要考虑到使用场景。如果是没有写的函数操作都可以加上const(普通对象也可以调用const成员函数);但是对于insert、reserve等有修改内容的函数,不加上const(const对象本身不能修改内容,因此不能调用这些函数)。
对于begin()函数,由于其差异主要是其返回值类型是否为const,因此需要对函数用const修饰并构造重载。
当此值用作字符串成员函数中的 len(或 sublen)参数的值时,表示“直到字符串的末尾”。
三.IO
对于string对象使用
<<
和<<
是需要实现其运算符重载的但是如果写在成员函数中,就需要调用的对象处于第一个操作数的位置,例如:str << cout;
因此放在类外实现,例如:
using std::ostream;
using std::istream;
namespace yyjs
{
ostream& operator<<(ostream& _cout, const string& s);
istream& operator>>(istream& _cin, string& s);
istream& getline(istream& _cin, string& s)
}
a. operator<<
ostream& operator<<(ostream& _cout, const string& s)
{
for (auto& e : s)
{
_cout << e;
}
return _cout;
}
b. operator>>
istream& operator>>(istream& _cin, string& s)
{
s.clear();
char ch;
//_cin >> ch;
//cin>> 不会读到空格和换行,因此用get()
ch = _cin.get();
//读到空格或换行就结束
while (ch != ' ' || ch != '\n')
{
s += ch;
ch = in.get();
}
return _cin;
}
c. getline
istream& getline(istream& _cin, string& s)
{
s.clear();
char ch;
ch = _cin.get();
//到换行符截止
while (ch != '\n')
{
s += ch;
ch = _cin.get();
}
return _cin;
}
每次增加一个字符,可能会导致多次扩容(有strcpy)效率较低
istream& getline(istream& _cin, string& s)
{
s.clear();
const int N = 64;
char buff[N];
char c;
c = _cin.get();
int num = 0;
while (c != '\n')
{
buff[num++] = c;
if (num == N - 1)
{
buff[num] = '\0';
s += buff;
num = 0;
}
c = _cin.get();
}
buff[num] = '\0';
s += buff;
return _cin;
}
用一个buff数组先缓存一下cin输入,可以减少扩容次数。
🦀🦀观看~~