String类
-
简介:在C语言中,字符串可以认为是以'\0'作为结尾的字符集合,而要使用字符串的一些方法,则需要单独导入
string.h这个头文件,这就带来极大的不便,而且用户对字符串操作时还要自己注意管理字符串内存\,以防稍不注意的访问越界行为,这不符合面向对象的思想,所以在C++的STL库中就支持了string类以专门表示字符串 -
注意事项:string类的底层是一个char*类型数组*;在C++中使用模板basic_string定义,要使用string类,需要导入std库(或者使用作用域限定符std::)和string***文件
String的常用成员
- String类框架搭建
class string
{
private:
char* _str;
size_t _size;
size_t _capacity;
// 静态成员npos
static size_t npos;
};
npos = -1;
默认的4个成员函数
- 按照惯例,我们先介绍一个类中最重要的4个默认函数,即:构造函数,拷贝构造函数,析构函数,还有operator=重载
-
构造和拷贝构造函数:在C++98中一共有7个构造函数
-
其中常用的有如下几种:
string():默认构造函数,创建一个不包含任何字符的string对象string(const string& str):拷贝构造函数,用str复制\构造一个新的string对象string(const char* s):利用C-string[即字符数组]来构造string对象string(size_t n, char c):使用n个相同的字符c\构造一个string对象
// 无参构造 string() : _str(new char[1]) , _size(0) , _capacity(0) {_str[0] = '\0';} /* 细节:在无参构造时为_str开辟了一个空间,而不是置为nullptr,这是为了防止在使用某些需要解引用_str的操作时产生解引用空指针而导致崩溃 */ // C-string构造函数 string(const char& str = "") ; _str(new char[strlen(str) + 1]) , _size(strlen(str)) , _capacity(_size) { for (int i = 0; i < _size + 1 ;i++) { _str[i] = str[i]; } } /* 细节:在C-string式构造函数的参数后加""空字符串作为全缺省参数,也是可以作为默认构造函数的*/ // 拷贝构造(介绍现代写法) string(const string& s) : _str(nullptr) , _size(0) , _capacity(0) { string tmp(s._str); // 通过C-string的方式构造一个字符串 ::swap(tmp._str, _str); // 使用C++内置的swap函数(深拷贝) } /* 什么是现代写法:就是通过设计好的函数,减少程序员的代码量,让函数逻辑书写和实现更简单的过程,下列是传统写法的拷贝构造*/ //// 拷贝构造(深拷贝) - 传统写法 //string(const string& s) // : _str(new char [strlen(s._str) + 1]) // TODO:在类里访问不受访问限制符的限制 // , _size(s._size) // , _capacity(s._capacity) //{ // for (int i = 0; i < _size + 1; i++) // { // _str[i] = s._str[i]; // } //} // n个字符c构造 string(size_t n, char c) : _str(new char[n + 1]) , _size(n) , _capacity(n) { for(int i = 0; i < n; i++) { _str[i] = c; } } -
这是上述构造函数的测试结果:
-
余下的:
string(const char* s, size_t n):使用一个C-string对象的前n个字符\构造一个string对象(即使前n个字符包含'\0',也会继续继续拷贝)string(const string& str, size_t pos, size_t n = npos):使用str从pos位置开始的n个字符构成的字串构造新的string对象- npos:是size_t默认的最大值 (
size_t npos = -1;)
- npos:是size_t默认的最大值 (
string(InputIterator first, InputIterator last):使用迭代器first至last范围内字符构造一个新的string对象- 迭代器:可以暂时理解为指针,但是对比指针,迭代器有更强的可迁移性,只要是内置类型都有迭代器,详情见下文
-
-
析构函数
-
由于string是一个给予char类型数组的类型,所以会有动态内存开辟,所以用户需要自己实现析构函数以释放存储在堆中的空间,这样对象在销毁时就会自动释放堆内存中的空间了
string():用于清空string对象申请的内存
~string() { if(_str != nullptr) { delete[] _str; _str = nullptr; } }
-
-
==operator=
-
和析构的原因一样,由于string需要进行动态内存开辟,所以要手动实现=运算符的重载
string& operator=(const string& s) { if(&s != this) // 防止重复赋值(!=符号在下文有定义) { delete[] _str; _str = new char[strlen(s._str) + 1]; for(int i = 0; i < s._size; i++) { _str[i] = s._str[i]; } _size = s._size; _capacity = s._capacity; return *this; } }
-
string的容器函数
- string的函数容器用于返回与string性质相关的数据
基础容器
size():返回容器的有效字符个数capacity():返回容器的游戏字符上限数c_str():返回C-stringsubstr(size_t pos = 0, size_t len = npos) const:从string对象的pos位置开始拷贝len长度(默认全拷贝)并创建一个全新的子字符串
// 获取有效字符个数
size_t size()
{
return _size;
}
// 获取容量(可容纳有效字符的最大个数)
size_t capacity()
{
return _capacity;
}
// 获取C-string
const char* c_str()
{
return _str; // 返回首地址
}
// 获取子字符串
string substr(size_t pos = 0, size_t len = npos) const
{
Checker(pos < _size);
if (len > _size - pos) // 如果长度过长
{
len = _size - pos;
}
char* tmp = new char[len + 1];
for (size_t i = 0; i < len; i++)
{
tmp[i] = _str[i + pos];
}
tmp[len] = '\0';
return string(tmp);
}
迭代器
- 下面就是字符串的迭代器(实质上是char指针)了
- 什么是迭代器:迭代器是一种检查容器内元素并遍历元素的数据类型,不同容器有不同的迭代器
// 先定义一下string类的迭代器和只读迭代器
typedef char* iterator;
typedef char* const_iterator;
// 迭代器开头(名字一定要定义为begin)
iterator begin()
{
return _str; // 返回头
}
// 迭代器结尾(名字一定要定义为end)
iterator end()
{
return _str + _size;
}
const_iterator cbegin() const
{
return _str;
}
const_iterator cend() const
{
return _str + _size;
}
- 要是要使用反转迭代器,则需要先定义反转迭代器这个类,并实现它的运算符重载
// 定义反转迭代器
struct __reverse_iterator
{
typedef __reverse_iterator Self;
char* _str;
__reverse_iterator(char* str)
: _str(str)
{ }
char& operator*()
{return *_str;}
// 先++
Self& operator++()
{
--_str;
return *this;
}
// 后++
Self operator++(int)
{
Self tmp = *this;
--_str;
return tmp;
}
// 先--
Self& operator--()
{
++_str;
return *this;
}
// 后--
Self operator--(int)
{
Self tmp = *this;
++_str;
return tmp;
}
bool operator(const Self& it) const
{return _str it._str;}
bool operator!=(const Self& it) const
{return _str != it._str;}
};
typedef __reverse_iterator reverse_iterator;
// 反转迭代器开头
reverse_iterator rbegin()
{
return reverse_iterator(_str + _size - 1);
}
// 反转迭代器结尾
reverse_iterator rend()
{
return reverse_iterator(_str - 1);
}
string的功能函数
- string的功能函数符合string类型的结构特征,这也是用户为什么要使用string对象的原因
容量管理
reserve(size_t newcap):如果newcap大于string对象的原容量,那么就会将原来的空间扩容至newcap大小resize(size_t n, char ch):重新设置string对象的size大小,如果size比原来大,就会将没有数据的空间填充为ch字符
// 预留空间/扩充容量
void reserve(size_t newcap)
{
if(newcap > _capacity)
{
char* tmp = new char[newcap + 1];
for (int i = 0; i < _size + 1; i++)
{
tmp[i] = _str[i];
}
delete[] _str; // 删除_str原来指向的空间
_str = tmp; // _str指向新空间
_capacity = newcap;
}
}
// 重新调整size大小
void resize(size_t n, char ch = '\0') // 默认ch填充'\0'
{
// 1.n < _size
if(n < _size)
{
_str[n] = '\0'; // 从n位置将字符串截断
_size[n] = n;
}
// 2.n _size
else if(n _size)
{
return;
}
// 3.n > size
else
{
// 4.n > capacity(扩容)
if (n > _capacity)
{
reserve(n);
}
for (int i = _size; i < n; i++)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
}
增删查改
- 增
push_back(char ch):在字符串尾部插入一个字符append(const char* str):在字符串尾部插入一段字符串
- 删
pop_back():删除string对象的最后一位有效字符erase(size_t pos, size_t len = npos):删除string对象从pos位置开始len长度的子字符串
- 查
find(char ch, size_t pos = 0):从string对象的pos(默认为0)位置开始查找直至找到第一个ch字符的位置,若未找到,则返回nposfind(const char* str, size_t pos = 0):从string对象的pos(默认为0)位置开始查找直至找到第一个出现子串str的位置[使用strstr函数],若未找到,则返回npos
- 改
insert(size_t pos, char ch):在string对象的pos位置处插入一个字符chinsert(size_t pos, const char* str):在string对象的pos位置处插入一个字符串str
// 增
void push_back(char ch)
{
if(_size _capacity)
{
size_t newcapctiy = _capacity 0 ? 4 : _capacity * 2;
reserve(newcapcity); // 如果空间不够要扩容
}
_str[_size] = ch;
_str[_size + 1] = '\0'; // 一定要注意在字符串的尾部一定要加上'\0'结束字符
++_size;
}
void append(const char* str)
{
size_t len = strlen(str);
// 如果容量不够
if(_size + len > _capacity)
{
reserve(_size + len); // *2倍不知道空间够不够,所以直接扩大到_size + len即可
}
for(size_t i = 0; i < len; i++)
{
_str[_size] = str[i];
++_size;
}
_str[_size] = '\0';
}
// 删
void pop_back()
{
if(_size 0)
{
exit(-1);
}
--_size;
_str[_size] = '\0';
}
string& erase(size_t pos, size_t len = npos)
{
if(!(pos < _size)) // pos要在合法范围内
{
exit(-1);
}
// 情况1:删掉len个字符之前就没有有效字符了
if (pos + len >= _size)
{
_str[pos] = '\0';
_size = pos; // 只剩pos个了
}
else
{
// 情况2:删掉len个字符后还有有效字符
for (size_t i = pos; i <= _size - len; i++)
{
_str[i] = _str[i + len]; // 顺便把'\0'也移过来
}
_size -= len;
}
return *this;
}
// 查
size_t find(char ch, size_t pos = 0) // 从pos位置开始查找
{
if(!(pos < _size)) // pos要在合法范围内
{
exit(-1);
}
for (size_t i = pos; i < _size; i++)
{
if (_str[i] ch)
{
return i;
}
}
return npos; // 没有找到就返回npos
}
size_t find(const char* str, size_t pos = 0) // 从pos位置开始查找
{
char* p = strstr(_str, str); // 从_str找到符合str的子串,找到就返回对应的指针,否则返回nullptr
if (p != nullptr)
{
size_t index = p - _str; // _str计算时退化
return index >= pos ? index : npos; // 索引值index应该大于等于pos
}
return npos; // 没找到也返回npos
}
// 改
string& insert(size_t pos, char ch)
{
if(!(pos < _size)) // pos要在合法范围内
{
exit(-1);
}
// 检查空间
if (_size _capacity);
{
size_t newcapacity = _capacity 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
int index = _size - 1; // 尾指针
while (index >= (int)pos)
{
_str[index + 1] = _str[index]; // 后面等于前面的
--index;
}
_str[pos] = ch; // 插入值
_str[_size + 1] = '\0'; // 结束符号
++_size;
return *this;
}
string& insert(size_t pos, const char* str)
{
if(!(pos < _size)) // pos要在合法范围内
{
exit(-1);
}
int len = strlen(str);
if (_size _capacity);
{
size_t newcapacity = _capacity + len; // 防止内存不足
reserve(newcapacity);
}
int index = _size - 1; // 尾指针
while (index >= (int)pos) // 防止size_t被赋值为-1 (如果index时一个int类型,为了防止数据截断,int和size_T其中一个会向范围更大的类型转[无符号的类型的绝对范围更大],如果还是要这样比较,就要把size_t强制转
{
_str[index + len] = _str[index]; // 后面等于前面的
--index;
}
for (int i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_str[_size + len] = '\0';
_size += len;
return *this;
}
-
测试结果
其他函数
- 还有一些除上述基本功能外的一些函数
swap(string s):交换函数,即将两个string对象的成员变量进行交换copy(char* dest, size_t len, size_t pos = 0) const:将string对象的部分拷贝赋值给C-string上,但是需要调用者提前安排好空间,且要调用者在C-string的末尾加上'\0'
// 交换
void swap(string s)
{
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
// 将字符串的一部分复制到C-string中(调用者要提前分配好内存)
size_t copy(char* dest, size_t len, size_t pos = 0) const
{
if (pos >= _size)
return 0;
len = len + pos > _size ? _size - pos : len;
for (size_t i = 0; i < len; i++) {
dest[i] = _str[pos + i];
}
return len;
// 返回实际复制的字符数
}
运算符重载
- 运算符重载是也是很重要的成员函数,能够大大方便一些对容器的操作
[]:是指针访问操作,相当于C-string中的[]的作用,可读可写+=:相当于push_back或者append函数等比较运算符:返回_str比较的结果(左比较大返回1,右比较大返回-1,相同返回0)- 如何比较:逐个比较两个字符串的每个字符的ASCII码(英文字母)或者Unicode码(汉字等等语言) [但是Unicode的比较效果可能不符合预期,因为编译器会将2个,3个或者字符组成的Unicode编码拆开],当前面的码值都相同时,就比较两个字符串的长度
// []运算符
char& operator[](int i)
{
if (i > _size - 1)
{
cout << "索引值不存在!" << endl;
exit(-1);
}
return _str[i];
}
// +=运算符
string& operator+=(char ch)
{
this->push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
this->append(str);
return *this;
}
// 运算符
bool operator(const string& s)
{
if (strcmp(_str, s._str) 0)
{
return true;
}
return false;
}
// !=运算符
bool operator!=(const string& s)
{
return !(*this s);
}
// >运算符
bool operator>(const string& s)
{
if (strcmp(_str, s._str) > 0) // strcmp比较的是较前字符的ASCII码
{
return true;
}
return false;
}
// <运算符
bool operator<(const string& s)
{
return !(*this s || *this > s);
}
// >=运算符
bool operator>=(const string& s)
{
return *this s || *this > s;
}
// <=运算符
bool operator<=(const string& s)
{
return *this s || *this < s;
}
非成员函数(友元)
-
在string中我们就了解其中3个非成员函数
operator<<(ostream& out, const string& s):cout输出的重载操作istream& operator>>(istream& in, string& s):cin输入的重载操作,输入空格和回车键停止输入getline(string& s, istream& in):为了区分Space空格停止输入和\n换行停止输入,所以单独设置一个getline函数用于接收空格输入
// <<运算符 ostream& operator<<(ostream& out, const string& s) { // out << s._str; // 推荐使用循环遍历输出for(size_t t = 0; t < s.size(); t++),这样就不需要使用友元函数 for (size_t i = 0; i < s._size; i++) { out << s._str[i]; // 摆脱了传统字符串后面一定要'/0'的限制 } return out; } // >>运算符 istream& operator>>(istream& in, string& s) // 如果s为const string&,还是可以修改它的_str,即引用本身是常量,但指向的内容可以修改,为了严谨还是要把const去掉 { if (strcmp(s._str, "") != 0) // 非空字符串 { s._str[0] = '\0'; // 置为空串 } while (1) { char ch; // 由于不知掉要输入多少个字符,所以我们采用死循环逐个地读字符 // in >> ch; // 内置类型默认遇见'\n'时直接跳过,不会读取 ch = in.get(); // 使用get函数可以读取'\n' if (ch ' ' || ch '\n') // 输入结束 { return in; } s += ch; } } // 获取一行输入 istream& getline(string& s, istream& in) // 如果s为const string&,还是可以修改它的_str,即引用本身是常量,但指向的内容可以修改,为了严谨还是要把const去掉 { if (strcmp(s._str, "") != 0) // 非空字符串 { s._str[0] = '\0'; // 置为空串 } while (1) { char ch; // 由于不知掉要输入多少个字符,所以我们采用死循环逐个地读字符 // in >> ch; // 内置类型默认遇见'\n'时直接跳过,不会读取 ch = in.get(); // 使用get函数可以读取'\n' if (ch == '\n') // 输入结束 { return in; } s += ch; } }
Comments NOTHING