C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列
的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户
自己管理,稍不留神可能还会越界访问。如果我们经常在leetcode上刷OJ题,我们会发现字符串类型的题目基本都是以string的形式给初始字符串。
cplusplus.com/reference/string/string/?kw=string
上面的网址可以帮助我们了解string类,同时需要记得在使用string类的时候需要包含头文件以及展开命名空间,下面我们来介绍些常用的string类函数。蓝字部分点击即可直接跳转至介绍该函数的文档,我们这里只做较为简单的介绍。
一,std::string::string(初始化函数)
string::string - C++ Reference (cplusplus.com)
(以上是从文档中截取的图片,基本上是我们定义对象时常用的)单看这里我们便可以大致推出:
1.不传任何字符给字符串,定义一个空对象。
2.用一个现有对象通过拷贝构造初始化我们要定义的对象。
3.取现有对象中的一部分用来初始化我们即将定义的对象。
4.使用一个字符串来初始化我们的对象。
5.取字符串的前N个字符来初始化我们的对象。
6.以N个字符C来初始化我们即将定义的对象。
我们平时使用1,2,4居多,以下是我们对其的模拟实现:
string::string()
:_str(new char[15] {'\0'}),
_size(0),
_capacity(14)
{}
string::string(const char* s)
:_size(strlen(s)),
_capacity(strlen(s))
{
_str = new char[_capacity + 1];
strcpy(_str, s);
}
string::string(const string& s)
:_size(s._size),
_capacity(s._capacity)
{
_str = new char[_capacity + 1];
strcpy(_str, s._str);
}
class string
{
public:
string();
string(const char* s);
string(const string& s);
string& operator=(const string& s);
string& operator=(const char* s);
~string();
private:
char* _str;
size_t _size;
size_t _capacity;
static const size_t npos = -1;
};
由于库中已经有string类,这里我们是另外开了一块命名空间去定义我们的构造和拷贝构造,当然strcpy函数不安全会报警告,不过我们这里只是简单实现,目的是为了大致了解它们的底层结构。需要注意的是,由于我们的字符串实际上结尾还有个\0字符,所以我们需要多开一个空间来储存。
当然,成对才是最舒服的,这里我们也顺便的模拟实现下赋值运算符重载,以及析构函数:
string& string::operator=(const string& s)
{
if (_str != s._str)
{
char* tmp = new char[s._capacity + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
string& string::operator=(const char* s)
{
while (strlen(s) > _capacity)
{
char* tmp = new char[(_capacity + 1) * 2] {'\0'};
delete[] _str;
_str = tmp;
_capacity = (_capacity + 1) * 2 - 1;
}
strcpy(_str, s);
_size = strlen(s);
return *this;
}
string::~string()
{
delete[]_str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
再次提醒new与delete必须要匹配使用,同时为了避免或不小心造成的自己给自己初始化的无趣行为, 我们需要判断传入的对象是否与this指针解引用一样在上图第一个实现的重载中。
二,std::string::(r)begin与std::string::(r)end(范围函数)
虽然我们简单的去模拟实现这两者不难,但在此之前,我们需要去了解什么是迭代器:
iterator - C++ Reference (cplusplus.com)
C++标准库中的迭代器是一种用于访问容器(如数组、链表、集合等)元素的对象,类似于指针。它们提供了一种统一的方法来遍历不同类型的容器,而无需关心容器的具体实现。
而我们这里所介绍的这两个函数也是基于迭代器的,先来简要的介绍下string中的与迭代器相关的一些函数用法:
begin()
:返回指向字符串第一个字符的迭代器。end()
:返回指向字符串末尾后一个位置的迭代器(不指向任何有效字符)。rbegin()
:返回指向字符串最后一个字符的反向迭代器。rend()
:返回指向字符串首个字符前一个位置的反向迭代器。cbegin()
:返回常量迭代器,指向第一个字符。cend()
:返回常量迭代器,指向末尾后一个位置。crbegin()
:返回常量反向迭代器,指向最后一个字符。crend()
:返回常量反向迭代器,指向首个字符前一个位置 。
这里我们无法同时模拟这么多,因为一迭代器遍历数据的方式是通用与我们别的一些STL的,所以需要结合后面的STL的使用来学习,二是毕竟我们的迭代器类似与指针,我们要简单模拟实现只需要实现最开始两个,后面便可迎刃而解:
using iterator = char*;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const iterator begin() const
{
return _str;
}
const iterator end() const
{
return _str + _size;
}
这里我们模拟实现迭代器iterator之后,便可以使用范围for来遍历我们的字符串(只要我们定义了迭代器,并有begin与end函数便可以在我们模拟实现的string中使用范围for,下面是范围for的使用实例(注意范围for是在C++11中才使用的):
当我们使用范围for
循环遍历std::string
时,element_declaration
可以是一个字符类型,如char
或者auto
。
当我们使用范围for循环遍历std::string时,我们的元素可以是一个字符类型,如char或者auto(auto不能作为函数的参数,可以做返回值,但是建议谨慎使用,auto不能直接用来声明数组)。
#include <iostream>
#include <string>
int main() {
std::string str = "Hello, World!";
// 使用范围 for 遍历字符串
for (char c : str) {
std::cout << c << " "; // 输出每个字符
}
std::cout << std::endl;
return 0;
}
/
#include <iostream>
#include <string>
int main() {
std::string str = "Hello, C++";
// 使用 auto 自动推导类型
for (auto c : str) {
std::cout << c << " ";
}
std::cout << std::endl;
return 0;
}
#include <iostream>
#include <string>
int main() {
std::string str = "Range for loop!";
// 使用 const 引用遍历字符串,保证字符串不会被修改
for (const char& c : str) {
std::cout << c << " ";
}
std::cout << std::endl;
return 0;
}
补充一个[]操作符的重载的模拟实现,虽然简单但它可以让我们像访问字串中的数据一样去访问我们string类对象。
char& operator[](size_t n)
{
assert(n >= 0 && n < _size - 1);
return _str[n];
}
const char& operator[](size_t n)const
{
assert(n >= 0 && n < _size);
return _str[n];
}
三,std::string::size/length/capacity/empty/clear/reserve/resize(容量操作函数)
以下是它们的英文介绍文档(按照分割符顺序):
cplusplus.com/reference/string/string/size/重要
cplusplus.com/reference/string/string/length/
cplusplus.com/reference/string/string/capacity/
cplusplus.com/reference/string/string/empty/ 重要
cplusplus.com/reference/string/string/clear/ 重要
cplusplus.com/reference/string/string/reserve/ 重要
cplusplus.com/reference/string/string/resize/ 重要
注意:
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不会改变容量大小。
以下是这些函数的模拟实现(只模拟实现重要的函数):
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
bool empty()const
{
assert(_str);
return (_size > 0);
}
void string::resize(size_t n, char c)
{
if (n > _capacity)
{
reserve(n);
for (size_t i = _size; i < _capacity; i++)
{
_str[i] = c;
}
_str[_capacity] = '\0';
_size = _capacity;
}
else if (n >= _size && n <= _capacity)
{
for (size_t i = _size; i < n; i++)
{
_str[i] = c;
_size++;
}
}
else if (n < _size)
{
_str[n] = '\0';
_size = n;
}
}
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1] {'\0'};
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
四,std::string::push_back/append/operator+=/c_str/find + npos/rfind/substr(对象修改函数)
cplusplus.com/reference/string/string/push_back/
cplusplus.com/reference/string/string/append/
cplusplus.com/reference/string/string/operator+=/
cplusplus.com/reference/string/string/c_str/
cplusplus.com/reference/string/string/find/
cplusplus.com/reference/string/string/npos/
cplusplus.com/reference/string/string/rfind/
cplusplus.com/reference/string/string/substr/
1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差
不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可
以连接字符串。
2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留
好。 (表格中的+应为+=,创表格的时候少输入了)。
以下是模拟实现代码:
void string::push_back(char c)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : (_capacity+1) * 2 - 1);
}
_str[_size] = c;
_size++;
_str[_size] = '\0';
}
void string::append(const char* str)
{
size_t len = strlen(str);
while(len + _size > _capacity)
{
char* tmp = new char[(_capacity + 1) * 2] {'\0'};
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = (_capacity + 1) * 2 - 1;
}
strcpy(_str + _size, str);
_size += len;
}
size_t string::find(char c, size_t pos) const
{
assert(pos < _size);
for (size_t i = pos; _str[i]; i++)
{
if (_str[i] == c)
return i;
}
return npos;
}
size_t string::find(const char* s, size_t pos) const
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, s);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
void clear()
{
memset(_str, '\0', _size + 1);
_size = 0;
}
const char* c_str()const
{
return _str;
}
下面是几道练习题:
387. 字符串中的第一个唯一字符 - 力扣(LeetCode)
字符串最后一个单词的长度_牛客题霸_牛客网 (nowcoder.com)
557. 反转字符串中的单词 III - 力扣(LeetCode)
找出字符串中第一个只出现一次的字符_牛客题霸_牛客网 (nowcoder.com)
标签:capacity,函数,STL,char,String,str,const,string,size From: https://blog.csdn.net/xiuwoaiailixiya/article/details/142479731