【C++】String的语法及常用接口底层模拟实现
- 一、string类(了解)
- 二、string类的常用接口
-
- 2.1 string类对象的常见构造
- 2.2 string类对象的容量操作
- 2.3 string类对象的访问及遍历操作
- 2.4 string类对象的修改操作
- 2.5 string类非成员函数
- 三、string类常用接口的模拟实现
-
- 3.1 初建构造
- 3.2 赋值重载
- 3.3 返回容量大小与【】
- 3.4 reserve,resize
- 3.5 插入(push_back,append,operator+=,insert)
- 3.6 删除(erase),清空(clear)
- 3.7 查找(find)
- 3.8 返回子串(substr)
- 3.9 比较符号(<,>,==,<=,>=,!=)
- 3.10 迭代器(iterator)
- 3.11 流插入(<<),流提取(>>)
- 四、总结
一、string类(了解)
- C++ 的 std::string 是一个动态字符数组,支持多种操作,且是可变的。在使用string类时,必须包含#include头文件以及using namespace std;
二、string类的常用接口
2.1 string类对象的常见构造
- 函数名称 ——-功能说明
- string() ——– 构造空的string类对象,即空字符串
- string(const char * s) ——– 用C-string来构造string类对象
- string(size_t n, char c) ——- string类对象中包含n个字符c
- string(const string&s) ——– 拷贝构造函数
void Teststring()
{
string s1; // 构造空的string类对象s1
string s2("hello bit"); // 用C格式字符串构造string类对象s2
string s3(s2); // 拷贝构造s3
}
2.2 string类对象的容量操作
- 函数名称 ——- 功能说明
- size ——- 返回字符串有效字符长度
- length ——- 返回字符串有效字符长度
- capacity ——-返回空间总大小
- empty ——- 检测字符串释放为空串,是返回true,否则返回false
- clear ——- 清空有效字符
- reserve ——- 为字符串预留空间
- resize ——- 将有效字符的个数该成n个,多出的空间用字符c填充
注意: 1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一 致,一般情况下基本都是用size()。 2. clear()只是将string中有效字符清空,不改变底层空间大小。 3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。 [注意]:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。 4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。
2.3 string类对象的访问及遍历操作
- 函数名称 ——- 功能说明
- operator[] ——- 返回pos位置的字符,const string类对象调用
- begin+ end ——- begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
- 范围for ——- C++11支持更简洁的范围for的新遍历方式
2.4 string类对象的修改操作
- 函数名称 ——- 功能说明
- push_back ——- 在字符串后尾插字符c
- append ——- 在字符串后追加一个字符串
- operator+= ——- 在字符串后追加字符串str
- c_str ——- 返回C格式字符串
- find + npos ——- 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
- rfind ——- 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
- substr ——- 在str中从pos位置开始,截取n个字符,然后将其返回
注意: 1. 在string尾部追加字符时,s.push_back© / s.append(1, c) / s += 'c’三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。 2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。
2.5 string类非成员函数
- 函数名称 ——- 功能说明
- operator+ ——- 尽量少用,因为传值返回,导致深拷贝效率低
- operator>> ——- 输入运算符重载
- operator<< ——- 输出运算符重载
- getline ——- 获取一行字符串
- <、>、=、>=、<=、== ——- 大小比较
三、string类常用接口的模拟实现
3.1 初建构造
- 我们运用一个字符数组来存string,私有部分包括字符大小size,和空间容量capacity,_str字符数组
- 构造函数的形参运用缺省参数,当不传参时,将会变为空串,运用strlen得到传入的字符串大小,再new一个容量较大的字符数组,让_str指向这个数组,最后用memcpy,将传入的字符串内容拷贝给_str
- 拷贝构造思路与构造函数思路相同
- 析构函数将_str释放,再将size与capacity置为0
- 再写一个c_str接口,方便打印查看,与c语言适配
class string
{
public:
const static size_t npos = –1;
//设置一个npos后面要用,类型为size_t所以这里的npos是一个非常大的整数
//构造函数
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
memcpy(_str, str, _size + 1);
}
//拷贝构造
string(const string& s)
{
_str = new char[s._capacity + 1];
memcpy(_str, s._str, s._size + 1);
_size = s._size;
_capacity = s._capacity;
}
//析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
//c_str
const char* c_str()const
{
return _str;
}
private:
size_t _size;
size_t _capacity;
char* _str;
};
3.2 赋值重载
先写一个自定义的交换string的函数,将传入的形参与this的数据交换,最后返回*this
//赋值重载
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string& operator=(string tmp)
{
swap(tmp);
return *this;
}
3.3 返回容量大小与【】
//size,capacity
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];
}
3.4 reserve,resize
- reserve:如果需要的空间n大于原空间,则要进行扩容,创建一个临时变量tmp指向new的新空间,运用memcpy将原数据拷贝给tmp,将原来_str所在空间释放,再将_str只向tmp所指向的空间,size不变,capacity变为扩容后的n
- resize:如果是缩小,n小于原来的size,直接在下标为n的位置置为\\0。如果是扩大,先用reserve扩容,再将ch放入字符数组
//reserve模拟实现
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
memcpy(tmp, _str,_size + 1);
delete[]_str;
_str = tmp;
_capacity = n;
}
}
//resize模拟实现
void resize(size_t n, char ch = '\\0')
{
if (n < _size)
{
_size = n;
_str[_size] = '\\0';
}
else
{
reserve(n);
for (size_t i = _size; i < n; i++)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\\0';
}
}
3.5 插入(push_back,append,operator+=,insert)
- 插入的主要思想,就是空间不够就扩容,然后将字符放入数组,写出push_back和append,就可以复用写出+=,insert
//push_back模拟实现
void push_back(char ch)
{
if (_size == _capacity)
{
//2倍扩容
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\\0';
}
//append模拟实现
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
memcpy(_str + _size, str, len + 1);
_size += len;
}
//+=模拟实现
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
//insert模拟实现
void insert(size_t pos, size_t n, char ch)
{
assert(pos <= _size);
if(_size + n > _capacity)
{
reserve(_size + n);
}
size_t end = _size;
while (end >= pos && end != npos)
{
//当end==-1时,因为类型为size_t 所以会变为整数的最大值
//也就是npos,所以多加一个条件控制whlie
_str[end + n] = _str[end];
—end;
}
for (int i = 0; i < n; i++)
{
_str[i + pos] = ch;
}
_size += n;
}
void insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (len + _size > _capacity)
{
reserve(len + _size);
}
size_t end = _size;
while (end >= pos && end != npos)
{
_str[end + len] = _str[end];
—end;
}
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
}
3.6 删除(erase),清空(clear)
- erase:实现的从某个位置开始删除,删除长度为 len 的字符。len有一个缺省参数,为npos(npos是一个很大的数,也就是不传参给 len 的话,默认删除到最后)。如果 len 本就很大,删除的长度超过从pos开始所剩余的长度,那么默认也是pos后的删除完。
- claer:与erase不同,它是将整个字符串全部清空
//erase模拟实现
void erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || len + pos >= _size)
{
_str[pos] = '\\0';
_size = pos;
}
else
{
size_t end = len + pos;
while (end <= _size)
{
_str[pos++] = _str[end++];
}
_size -= len;
}
}
//clear模拟实现
void clear()
{
_str[0] = '\\0';
_size = 0;
}
3.7 查找(find)
从pos位置开始查找,查找的内容可能是一个字符,也可能是一个子串。如果找到,则返回其下标。没找到就返回npos。
//find模拟实现
size_t find(char ch, size_t pos = 0)
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
size_t find(const char* str, size_t pos = 0)
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, str);
if (ptr)
{
return ptr – _str;
}
else
{
return npos;
}
}
3.8 返回子串(substr)
从某个位置开始查找,查找长度为 len 的字符。len有一个缺省参数,为npos(npos是一个很大的数,也就是不传参给 len 的话,默认返回到最后)。如果 len 本就很大,返回子串的长度超过从pos开始所剩余的长度,那么默认也是pos后的子串全部返回。
//substr模拟实现
string substr(size_t pos = 0, size_t len = npos)
{
assert(pos < _size);
size_t n = len;
if (len == npos || len + pos >=_size)
{
n = _size – pos;
}
string tmp;
tmp.reserve(n);
for (size_t i = pos; i < pos + n; i++)
{
tmp += _str[i];
}
return tmp;
}
3.9 比较符号(<,>,==,<=,>=,!=)
字符串的比较并非比较其长度,而是与其相同位置字符的大小有关,也就是我们所说的字典序。我们这里只需要实现其中的两个,其他均可复用。
bool operator<(const string& s)const
{
//先用memcmp比较两个字符串较短的部分(前半段长度相等的部分)
//如果相等,ret为0,
//则size小的字符串小
int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
// "hello" "hello" false
// "helloxx" "hello" false
// "hello" "helloxx" true
return ret == 0 ? _size < s._size : ret < 0;
}
bool operator==(const string& s)const
{
return _size == s._size
&& memcmp(_str, s._str, _size) == 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);
}
3.10 迭代器(iterator)
这里的begin()就是返回的字符串的首元素地址,end()返回的是字符串最后一个元素的后一个地址!
public:
typedef char* iterator;
typedef const 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;
}
3.11 流插入(<<),流提取(>>)
//流插入,流提取
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.get();
//处理前缓冲区前面的空格或者换行
while (ch == ' ' || ch == '\\n')
{
ch = in.get();
}
//每次输入的字符大小不确定,如果提前用reserve开空间可能导致浪费
//因此先用一个大小为128字符数组存输入的字符,当数组满时再放入string
char buff[128];
int i = 0;
while (ch != ' ' && ch != '\\n')
{
buff[i++] = ch;
if (i == 127)
{
buff[i] = '\\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i != 0)
{
buff[i] = '\\0';
s += buff;
}
return in;
}
四、总结
string是C++中比较重要的且是入门时必须所学的容器。在平常中使用的频率较高,所以我们不仅要掌握其简单的用法,更应该去了解其底层的实现。这有助于我们后续的使用和理解。string常用的语法和接口的底层实现,这些都是我们应该熟练掌握的内容。
关于string类的讲解就到这里,希望对你有所帮助,感谢观看ovo!
评论前必须登录!
注册