目录
前言
1.string类
1.1.常用接口
2.string模拟实现?
2.1.默认成员函数
2.2.容量(capacity)、大小(size)、清理(clear)、判空(empty)、[]重载、c_str
2.3.迭代器(iterator)?
2.4.扩容(reserve)、调整大小(resize)
2.5.尾插(append、push_back、+=)
2.6.任意位置的插入删除(insert、erase)
2.7.查找(find)
2.8.运算符重载(==、<、<=、>、>=)
2.9.输入输出(cin、cout、getline)
前言
为什么C++中增加了string类型,而不继续使用C语言中的字符串?
C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问,可能会出现很多错误。
同时
在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。
1.string类
- string类在C++中是用来管理字符串的,任何使用 string 类的程序都必须使用 #include 包含 string 头文件。
- 为了方便使用,在类中实现了很多实用的接口。
在cplusplus中我们可以方便查看各种函数的实现和用法:https://www.cplusplus.com/reference/
string 的各种接口:https://www.cplusplus.com/reference/string/basic_string/
1.1.常用接口
string构造函数
定?义 | 用法/功能 |
---|
string() | 默认构造函数:创建空字符串。示例:string?str(); | string(const char *s) | 使用 C 字符串创建一个 string 对象。示例:string str("abcd"); | string(const string &s) | 拷贝构造函数:从现有字符串创建一个新的字符串。示例:?string s1(s); |
string 类运算符重载
重载运算符 | 用法/功能 |
---|
>> | 从流中提取字符并将其插入到字符串中。复制字符直至遇到空格或输入的结尾。 示例:string str(); cin >> str; | << | 将字符串插入流中。示例:string str("abcd"); cout << str << endl; | = | 将右侧的字符串赋值给左侧的 string 对象。示例:string s1("abc"); string s2("123"); s1 = s2; | += | 将右侧字符串的副本附加到左侧的 string 对象。示例:s += 'a'; s += "abcd"; | + | 返回将两个字符串连接在一起所形成的字符串。 | [] | 使用数组下标表示法,如 s[x]。返回 x 位置的字符的引用。 | 关系运算符 | 以下每个关系运算符都将被执行:< > <= >= = != |
string 类的成员函数
成员函数 | 用法/功能 | |
---|
iterator begin(); | 返回指向 string 中第一个字符的迭代器。 | |
---|
iterator end(); | 返回指向 string 中最后一个字符的下一个位置的迭代器(即'\0'位置)。 |
---|
const_iterator begin() | 返回指向 string 中第一个字符的迭代器。(const) |
---|
const_iterator end() | 返回指向 string 中最后一个字符的下一个位置的迭代器(即'\0'位置)。(const) |
---|
reverse_iterator rbegin() | 返回指向 string 中最后一个字符的反向迭代器。 |
---|
reverse_iterator rend() | 返回指向 string 中第一个字符前一个位置的反向迭代器(即-1位置)。 |
---|
size_t _capacity(); | 返回 string 被分配的容量的大小。 |
---|
size_t _size() | 返回 string 中字符串的长度。 |
---|
void clear(); | 删除 string 中存储的所有字符。 |
---|
char* c_str(): | 返回 string 对象的 C 字符串值(即以'\0'结束)。 |
---|
bool empty(); | 如果 string 为空则返回 true,否则返回 false。 |
---|
void reserve(size_t n) | 将string容量扩大至n,如果n<_capacity 则保持不变。 |
---|
void resize(size_t n) | 将string字符串长度扩大/ 缩小至n。 |
---|
string& append(const char* str) | 将str字符串插入string的末尾位置。 |
---|
string& push_back(char ch) | 将ch字符插入string的末尾位置。 |
---|
string& insert(size_t pos, char*str); string& insert(size_t pos, char ch) | 将 str 字符串或ch 字符 插入到 string 中指定位置,从位置 pos 开始。 |
---|
string& erase(size_t pos,? size_t n); | 从 string中_str字符串的pos位置??开始,删除 n 个字符。 |
---|
static const size_t npos | string 类中的静态成员,值为-1。 |
---|
size_t find(char*str, size_t pos); | 返回在 string 中找到的 str 字符串的第一个位置。如果没有找到 str,则返回 string 类的静态成员?string::npos。 |
---|
size_t find(char ch, size_t pos); | 返回在 string 中找到的 ch 字符的第一个位置。如果没有找到 ch,则返回?string::npos。 |
---|
string substr(size_t pos, size_t n); | 返回一个string 对象,_str为一个字串,子串长度为 n 个字符,从string 的 pos 位置开始。 |
---|
void swap(string& str); | 交换 string 和 str 的内容。 |
---|
istream& getline(istream&, string& s) | 获取一行字符,包括空格 |
---|
2.string模拟实现?
2.1.默认成员函数
namespace wt
{
class string
{
public:
string(const char* str);
string(string& s);
string& operator=(string s);
~string();
private:
char* _str;
size_t _size;
size_t _capacity;
}
}
由于标准库中也有string类,为防止命名冲突,这里可以加上命名空间。
构造函数:使用缺省值,无参时构造空字符串,_capacity不包括'\0'
string(const char* str = "")
:_size(strlen(str))
,_capacity(_size)
{
_str = new char[_size + 1]; // 给'\0'多开一个空间
strcpy(_str, str);
}
析构函数:清理全部空间
~string()
{
delete[] _str;
_size = _capacity = 0;
}
拷贝构造、赋值运算符重载:面临深拷贝问题
string(string& s)
:_str(s._str)
,_size(s._size)
,_capacity(_capacity)
{}
string& operator=(const string& s)
{
if (&s!=this)
{
_str = s._str;
_size = s._size;
_capacity = s._capcity;
}
return *this;
}
若直接这样拷贝或复制,会导致相同的空间被析构两次,程序会崩溃,所以这里应该使用深拷贝。
原始写法:按照正常思路深拷贝去拷贝和赋值
string(string& s)
:_size(strlen(s._str))
,_capacity(_size)
{
// 深拷贝
_str = new char[_size + 1]; // 申请与被拷贝字符串相同大小的空间
strcpy(_str, s._str); // 拷贝字符串
}
string& operator=(const string& s)
{
if (&s!=this) // 避免自己给自己复制(导致原地址发生变化)
{
char* tmp = new char[strlen(s._str) + 1]; // 创建临时空间保存字符串
strcpy(tmp, s._str);
delete[] _str; // 释放原空间
_str = tmp; // 直接使用临时空间地址
_size = s._size;
_capacity = _size;
}
return *this;
}
现代写法:思路巧妙,写法简单
void swap(string& s)
{
std::swap(_str, s._str); // 使用std中的swap库函数交换
std::swap(_capacity, s._capacity);
std::swap(_size, s._szie);
}
string(string& s)
:_size(nullptr) // 先将原对象初始化为空
,_capacity(0)
,_size(0)
{
string tmp(s._str); // 以被拷贝对象字符串创建临时对象
swap(tmp); // 将临时对象与原对象交换
}
string& operator=(string s) // 传参传临时对象,因为后面会改变该对象
{
swap(s); // 直接将临时对象和原对象交换
return *this; // 函数调用结束后临时对象会被销毁,而临时对象指向的是被交换后的空间
}
2.2.容量(capacity)、大小(size)、清理(clear)、判空(empty)、[]重载、c_str
这部分接口比较简单,不详细介绍,主要是为后面的接口铺垫。
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
void clear()
{
_str[0] = '\0'; // 直接将首位置置为'\0'
_size = 0; // 将大小置为0
}
bool empty()
{
return _size == 0;
}
char& operator[](size_t pos)
{
return _str[pos];
}
const char& operator[](size_t pos)const // 因为存在const 对象调用情况,需要实现const版本
{
return _str[pos];
}
const char* c_str()const
{
return _str;
}
2.3.迭代器(iterator)?
迭代器是STL容器遍历的通用方法。
typedef const char* const_iterator; // const 对象调用
typedef char* iterator;
const_iterator begin()const
{
return _str; // 本质是返回字符串起始位置的指针
}
const_iterator end()const
{
return _str + _size; // 本质是返回字符串'\0'位置的指针
}
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
用法:
void test()
{
wt::string s1("hello world!");
wt::string::iterator it = s1.begin();
// 迭代器遍历
while (it != s1.end())
{
std::cout << *it;
++it;
}
std::cout << std::endl;
// for循环遍历
for (size_t i = 0; i < s1.size(); ++i)
{
std::cout << s1[i];
}
std::cout << std::endl;
// 范围for遍历
for (auto ch : s1)
{
std::cout << ch;
}
std::cout << std::endl;
}
2.4.扩容(reserve)、调整大小(resize)
void reserve(size_t n)
{
if (n > _capacity) // n <= _capacity无需扩容
{
char* tmp = new char[n + 1];
strcpy(tmp, _str); // 拷贝原字符串
delete[] _str; // 释放原空间
_str = tmp;
_capacity = n;
}
}
void resize(size_t n, char ch = '\0') // 默认用'\0'填充
{
if (n <= _size) // 若n <= _size,只需将n位置处置为'\0'
{
_str[n] = '\0';
}
else
{
if (n > _capacity) // 否则需先扩容
{
reserve(n);
}
memset(_str + _size, ch, n - _size - 1);
_str[n] = '\0';
}
_size = n;
}
2.5.尾插(append、push_back、+=)
void push_back(char ch)
{
if (_size == _capacity) // 判断容量
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_str[++_size] = '\0'; // 尾部添'\0'
}
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity) // 检查容量是否足够
{
reserve(_capacity+strlen(str));
}
strcpy(_str + _size, str);
_size += len;
}
string& operator+=(char ch)
{
push_back(ch); // 复用
return *this;
}
string& operator+=(const char* str)
{
append(str); // 复用
return *this;
}
2.6.任意位置的插入删除(insert、erase)
string& insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity) // 检查容量
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
for (size_t i = _size + 1; i > pos; --i)
{
_str[i] = _str[i - 1]; // 移动字符
}
_str[pos] = ch;
_size++;
return *this;
}
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_capacity + strlen(str));
}
/*for (size_t i = _size + len; i >= pos + len; --i)
{
_str[i] = _str[i - len];
}*/
memmove(_str + pos + len, _str + pos, _size - pos + 1);
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
string& erase(size_t pos = 0, size_t n = npos)
{
assert(pos < _size);
if (n == npos || n > _size - pos) // n=pos或者删除的字符个数大于剩余个数则直接将pos位置后面的所有字符全部删除
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + n);
_size -= n;
}
return *this;
}
2.7.查找(find)
size_t find(char ch)
{
for (size_t i = 0; i < _size; ++i)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
size_t find(const char* str, size_t pos)
{
const char* ptr = strstr(_str + pos, str); // 借助strstr找字串
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
2.8.运算符重载(==、<、<=、>、>=)
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 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);
}
2.9.输入输出(cin、cout、getline)
std::ostream& operator<<(std::ostream& out, const string& s)
{
for (auto e : s)
{
out << e;
}
out << std::endl; // 这里不能使用out << s.c_str() 输出
return out;
}
std::istream& operator>>(std::istream& in, string& s)
{
s.clear(); // 输入前先清除字符串数据
char ch = 0;
ch = in.get(); // 获取一个字符
while (ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}
std::istream& getline(std::istream& in, string& s)
{
s.clear();
char ch = 0;
ch = in.get();
while ( ch != '\n')
{
s += ch;
in.get();
}
return in;
}
}
|