【STL】C++ 开发者必学字符类详解析:std::string
前言:为什么要学string类?
回顾C语言学习字符串,比如实现字符串拼接、查找、替换、比较等一些常用操作时,都需要我们手动实现。同时,我们就还需要手动地申请内存,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
C++ 的std::string
是标准库提供的字符串类,封装了字符串的存储、操作和管理逻辑,相比 C 语言,string类可以实现自动内存管理;同时,std::string
有丰富的成员函数,即封装了大量字符串操作接口,无需手动实现复杂逻辑。
在学习之前,先向大家推荐一个好用的文档,方便大家随时去查阅C/C++标准库中的库函数:cplusplus.com - The C++ Resources Network
cplusplus.com - The C++ Resources Network
string类的文档:http://www.cplusplus.com/reference/string/string/?kw=string
1、常用接口说明
1. string类对象的常见构造:
实现类对象的初始化
函数名称 | 功能 |
string() | 构造空的string类对象,即空字符串 |
string(const char* s) | 用字符串s来构造string类对象 |
string(const string&s) | 拷贝构造函数 |
string(size_t n, char c) | 用n个字符c初始化类对象 |
#include<string> // 头文件
using namespace std;void Test()
{string st1; // 构造空字符串st1char ch[] = "Hello World!";string st2("Hello World!"); // 用"Hello World!"初始化st2string st3(st2); // 拷贝构造string st4(st2, 6, 6); // 用st2中第六个字符以后的字符拷贝构造st4string st5(st2, 6); // 用st2的前6个字符拷贝构造st5string st6(ch); // 等价于用"Hello World!"初始化st2string st7(ch, 5); // 用字符串ch的前5个字符初始化st7string st8(10, 'X'); // 用10和字符'X'初始化st8
}
// 输出:
st2:Hello World!
st3:Hello World!
st4:World!
st5:World!
st6:Hello World!
st7:Hello
st8:XXXXXXXXXX
2. string类对象的容量操作
函数 | 功能 |
s.size() | 返回字符串有效字符长度 |
s.length() | 返回字符串有效字符长度 |
s.capacity() | 返回空间总大小,不包含'\0' |
s.empty() | 检测字符串释放为空串,是返回true,否则返回false |
s.clear() | 清空有效字符 |
s.reserve(n) | 为字符串预留空间,实际预留空间一般大于n |
s.resize (size_t n) resize (size_t n, char c) | 将有效字符的个数改成n个,多出的空间用字符c填充 |
int main()
{string str("hello world");cout << str.size() << endl;cout << str.length() << endl;cout << str.capacity() << endl;return 0;
}
3. string类对象的访问及遍历操作
函数 | 功能 |
operator[ ] | 重载运算符[],通过访问数组元素的方式返回pos位置的字符 |
s.begin() | begin获取一个字符的迭代器(地址),用于从前往后遍历字符串。 |
s.end() | end获取最后一个字符下一个位置的迭代器(地址) |
s.rbegin() | rbegin() 返回的是反向迭代器,指向字符串的最后一个字符,用于从后往前遍历字符串。 |
s.rend() | end获取第一个字符前一个位置的迭代器(地址) |
范围for | C++11支持更简洁的范围for的新遍历方式,与auto关键字联用 |
begin()函数有两种形式,分别对应
const
和非const
的std::string
对象:
#include <string>// 对于非const的std::string对象,返回指向首字符的正向迭代器(可读可写)
std::string::iterator begin(); // 对于const的std::string对象,返回指向首字符的常量正向迭代器(只读)
std::string::const_iterator begin() const;
示例:
void Test02() {string st2("Hello World!");string::iterator it1 = st2.begin();while (it1 != st2.end()){cout << *it1 << " ";++it1;}cout << endl;string::iterator it2 = st2.begin();// it指针指向的内容可以改变while (it2 != st2.end()){*it2 += 2;cout << *it2 << " ";++it2;}
}void test01() {const string st("Hello World!");// 需要使用const_iterator类型string::const_iterator it1 = st.begin();while (it1 != st.end()){cout << *it1 << " ";++it1;}cout << endl;//// 错误示范//string::const_iterator it2 = st.begin();//// st被const修饰,it指针指向的内容不可以改变//while (it2 != st.end())//{// *it2 += 2;// cout << *it2 << " ";// ++it2;//}
}
auto + 范围for:
int main()
{ string str("Hello World!");for (auto& e : str){cout << e << " ";}return 0;
}
4. string类对象的修改操作
函数 | 功能 |
push_back | 尾插一个字符ch |
append | 尾插一个字符串 |
operator+= | 尾插一个字符串 |
insert | 在指定位置插入字符ch,或者字符串str |
c_str | 返回字符串首地址 |
find | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置,找不到返回npos(整型的最大值) |
rfind | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的 位置,找不到返回npos(整型的最大值) |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
// push_back
string str("hello world");
str.push_back('!');// append
str.append("!!!!");str.append("xxxx",2); // 将字符串"xxxx"中的两个字符尾插str.append(5, 'y'); // 尾插5个字符'y'str.append("xxxx", 0, 4); // 将字符串"xxxx"从第一个开始往后数四个尾插// +=
str += '!'; //尾插'!'// insert
string str("hello world");
str.insert(0, "xxx");string str1("hds");
str.insert(0, str1);
2、string类的模拟实现
说明:我们依旧采用多文件的方式来模拟实现string类,头文件string.h创建一个string类,并在类里面实现一些需要经常调用的成员函数(如拷贝构造,析构等)以及完成函数的声明;string.cpp文件完成仅在string类中声明的成员函数的实现以及全局函数的实现;test.cpp文件完成对实现的string类的测试。同时,我们将所有的实现过程都放在一个我们自己的命名空间中。
class string {public:// ...private:// 成员变量char* _str; // 指向字符数组的指针size_t _size; // 字符串的字符个数size_t _capacity;// 空间大小(不包含'\0')static const size_t npos = -1; // 整型的最大值};
2.1、类对象的初始化
1. 构造函数
string(const char* str = "")
{_size = strlen(str);_capacity = _size; //_capacity不包括'\0'_str = new char[_capacity + 1]; //需要为'\0'开辟一个空间strcpy(_str, str);
}
使用全缺省参数,当不传参数时,用缺省值(空字符串)来初始化,同时,空字符串默认含有一个 '\0',可以防止将_str初始化为nullptr而对空指针的解引用。
2.拷贝构造函数
string(const string& s)
{_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;
}
拷贝构造,用一个已经存在的对象来初始化一个正在创建的对象。最重要的是对于一些指向了资源的内置类型需要完成深拷贝。
下面我们再来介绍拷贝构造的现代写法:
void swap(string& tmp)
{std::swap(_str, tmp._str);std::swap(_size, tmp._size);std::swap(_capacity, tmp._capacity);
}string(const string& s)
{string tmp(s.c_str());swap(tmp);
}
3.析构函数
~string()
{if(_str){delete[] _str;_str = nullptr;_size = _capacity = 0;}
}
2.2、功能函数接口
1.获取有效数据个数——_size
// 获取有效数据个数
size_t size()const
{return _size;
}
2.空间大小——_capacity
// 获取空间大小
size_t capacity()const
{return _capacity;
}
3.访问——重载[ ]
我们要访问pos位置的元素,就要检查pos的合法性,所以,先对pos进行断言。
char& operator[](size_t pos)
{assert(pos < _size);return _str[pos];
}
对于const对象,我们只能访问而不能修改,所以,对const对象单独重载一个[ ]运算符。
// const对象
const char& operator[](size_t pos)const
{assert(pos < _size);return _str[pos];
}
4.清理数据——clear
void clear()
{_str[0] = '\0'; _size = 0;
}
5.访问_str——c_str
const char* c_str()const
{return _str;
}
2.3、尾插——push_back&append&+=
// 头文件函数声明
void reserve(size_t n); // 申请空间
void push_back(char ch); // 尾插一个字符
void append(const char* str); // 尾插字符串
string& operator+=(char ch); // 尾插字符
string& operator+=(const char* str); // 尾插字符串
1、我们要插入数据,首先要检查空间是否足够,如果空间不足,就需要我们扩容。所以,我们先实现string类中的reserve函数,同时,reserve函数能够提前申请空间,减少扩容,提高效率。
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;}
}
2、push_back
void string::push_back(char ch)
{if (_size == _capacity){size_t capacity = _capacity == 0 ? 4 : _capacity * 2;reserve(capacity); // 扩容_capacity = capacity;}_str[_size] = ch; // 尾插++_size; _str[_size] = '\0'; // 手动添加'\0'
}
3、append:由于是插入一个字符串,所以在扩容时要考虑字符串长度
void string::append(const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){size_t capacity = (_size + len) > 2 * _capacity ? (_size + len) : 2 * _capacity;reserve(capacity);_capacity = capacity;}strcpy(_str + _size, str);_size += len;
}
4、+=
string& string::operator+=(char ch)
{push_back(ch);return *this;
}string& string::operator+=(const char* str)
{append(str);return *this;
}
2.4、指定位置插入——insert
首先检查pos的合法性,然后检查空间是否足够,最后移动字符串为要插入的字符或者字符串腾出合适的空间并插入。
void string::insert(size_t pos, char ch)
{assert(pos <= _size);if (_size == _capacity) {size_t capacity = _capacity == 0 ? 4 : _capacity * 2;reserve(capacity);_capacity = capacity;}// 将pos位置之后的字符串整体向后移动size_t end = _size + 1; //将'\0'也向后移动while (end > pos) {_str[end] = _str[end - 1];--end;}_str[pos] = ch;_size++;
}
void string::insert(size_t pos, const char* str)
{assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){size_t capacity = (_size + len) > 2 * _capacity ? (_size + len) : 2 * _capacity;reserve(capacity);_capacity = capacity;}// 将pos位置的字符串整体向后移动size_t end = _size+1;while (end > pos){_str[end+len-1] = _str[end-1];--end;}// 插入字符串for (size_t i = 0; i < len; i++){_str[pos + i] = str[i];}_size += len;
}
2.5、查找——find
从pos指向的位置向后查找对应的字符或者字符串是否存在,先对pos断言检查,对于单个字符直接遍历字符串,对于字符串,通过strstr函数来查找。
size_t string::find(char ch, size_t pos)
{assert(pos < _size);for (size_t i = 0; i < _size; i++){if (_str[i] == ch)return i;}return npos;
}size_t string::find(const char* str, size_t pos)
{assert(pos < _size);const char* ptr = strstr(_str + pos, str);if (ptr == nullptr){return npos;}elsereturn ptr - _str;
}
2.6、比较——<&<=&>&>=&==&!=
我们先实现<和==,然后复用来实现其他的。
bool operator < (const string& str1, const string& str2)
{return strcmp(str1.c_str(), str2.c_str()) < 0;
}
bool operator <= (const string& str1, const string& str2)
{return str1 < str2 || str1 == str2;
}
bool operator > (const string& str1, const string& str2)
{return !(str1 < str2);
}
bool operator >= (const string& str1, const string& str2)
{return str1 > str2 || str1 == str2;
}
bool operator == (const string& str1, const string& str2)
{return strcmp(str1.c_str(), str2.c_str()) == 0;
}
bool operator != (const string& str1, const string& str2)
{return !(str1 == str2);
}
2.7、流提取<< 和流插入>>
流提取<<:通过范围遍历str对象
ostream& operator<<(ostream& out, const string& str)
{for (auto ch : str){out << ch;}return out;
}
流插入>>:为了减少在不断提取的过程中的扩容次数和拷贝构造的次数,提高效率,我们先创建一个一定空间大小数组,将输入的字符先存放到数组中,当字符数组获取到足够的字符后,一次性将字符数组中的元素拷贝到str对象 。
istream& operator>>(istream& in, string& str)
{str.clear();const int N = 256; // 创建数组,减少扩容的次数char buff[N];int i = 0;char ch;ch = in.get(); // 获取输入的第一个字符while (ch != ' ' && ch != '\n') //字符串分隔符{buff[i++] = ch;if (i == N - 1) //当字符数组获取到足够的元素后,一次性拷贝到str对象{buff[i] = '\0';str += buff;i = 0;}ch = in.get(); // 逐个获取剩下的的字符}// 处理还没有提取到str对象中的字符if (i > 0){buff[i] = '\0';str += buff;}return in;
}
3、完整源码
// 头文件:string.h
#include<iostream>
#include<assert.h>
using namespace std;namespace hds
{class string{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;}string(const char* str = ""){_size = strlen(str);_capacity = _size; //_capacity不包括'\0'_str = new char[_capacity + 1]; //需要为'\0'开辟一个空间strcpy(_str, str);}//深拷贝/*string(const string& s){_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}*///深拷贝现代写法void swap(string& tmp){std::swap(_str, tmp._str);std::swap(_size, tmp._size);std::swap(_capacity, tmp._capacity);}string(const string& s){string tmp(s.c_str());swap(tmp);}// 写法一:/*string& operator=(const string& s){if (this != &s){delete[] this->_str;this->_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}return *this;}*/// 写法二:/*string& operator=(const string& s){if (this != &s){string tmp(s._str);swap(tmp);}return *this;}*/// 写法三:string& operator=(string& tmp){swap(tmp);return *this;}~string(){if(_str){delete[] _str;_str = nullptr;_size = _capacity = 0;}}// 获取有效数据个数size_t size()const{return _size;}// 获取空间大小size_t capacity()const{return _capacity;}// 重载[]char& operator[](size_t pos){assert(pos < _size);return _str[pos];}// const对象const char& operator[](size_t pos)const {assert(pos < _size);return _str[pos];}//清理_str中的数据void clear(){_str[0] = '\0'; _size = 0;}const char* c_str()const{return _str;}// 尾插void reserve(size_t n);void push_back(char ch);void append(const char* str);string& operator+=(char ch);string& operator+=(const char* str);//指定位置插入void insert(size_t pos, char ch);void insert(size_t pos, const char* str);void erase(size_t pos, size_t len = npos);//查找size_t find(char ch, size_t pos = 0);size_t find(const char* str, size_t pos = 0);string substr(size_t pos = 0, size_t len = npos);private:char* _str;size_t _size;size_t _capacity;static const size_t npos = -1;};//声明为全局函数//比较bool operator<(const string& str1, const string& str2);bool operator<=(const string& str1, const string& str2);bool operator>(const string& str1, const string& str2);bool operator<=(const string& str1, const string& str2);bool operator==(const string& str1, const string& str2);bool operator!=(const string& str1, const string& str2);//流提取&流插入ostream& operator<<(ostream& out, const string& str);istream& operator>>(istream& in, string& str);
}
// 功能实现文件:string.cpp
#include"string.h"
namespace hds
{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;}}void string::push_back(char ch){if (_size == _capacity){size_t capacity = _capacity == 0 ? 4 : _capacity * 2;reserve(capacity);_capacity = capacity;}_str[_size] = ch;++_size;_str[_size] = '\0';}void string::append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){size_t capacity = (_size + len) > 2 * _capacity ? (_size + len) : 2 * _capacity;reserve(capacity);_capacity = capacity;}strcpy(_str + _size, str);_size += len;}string& string::operator+=(char ch){push_back(ch);return *this;}string& string::operator+=(const char* str){append(str);return *this;}void string::insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){size_t capacity = _capacity == 0 ? 4 : _capacity * 2;reserve(capacity);_capacity = capacity;}size_t end = _size + 1; //将'\0'也向后移动while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;_size++;}void string::insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){size_t capacity = (_size + len) > 2 * _capacity ? (_size + len) : 2 * _capacity;reserve(capacity);_capacity = capacity;}size_t end = _size+1;while (end > pos){_str[end+len-1] = _str[end-1];--end;}for (size_t i = 0; i < len; i++){_str[pos + i] = str[i];}_size += len;}void string::erase(size_t pos, size_t len) //缺省值只在声明时加{assert(pos < _size);if (len >= _size - pos){_size = pos;_str[_size] = '\0';}else{for (size_t i = 0; i <= _size-pos-len; i++){_str[i + pos] = _str[pos+len + i];}}}size_t string::find(char ch, size_t pos){assert(pos < _size);for (size_t i = 0; i < _size; i++){if (_str[i] == ch)return i;}return npos;}size_t string::find(const char* str, size_t pos){assert(pos < _size);const char* ptr = strstr(_str + pos, str);if (ptr == nullptr){return npos;}elsereturn ptr - _str;}string string::substr(size_t pos, size_t len){assert(pos < _size);if (len > _size - pos){len = _size - pos;}string sub;sub.reserve(len);for (size_t i = 0; i < len; i++){sub += _str[pos + i];}return sub;}bool operator < (const string& str1, const string& str2){return strcmp(str1.c_str(), str2.c_str()) < 0;}bool operator <= (const string& str1, const string& str2){return str1 < str2 || str1 == str2;}bool operator > (const string& str1, const string& str2){return !(str1 < str2);}bool operator >= (const string& str1, const string& str2){return str1 > str2 || str1 == str2;}bool operator == (const string& str1, const string& str2){return strcmp(str1.c_str(), str2.c_str()) == 0;}bool operator != (const string& str1, const string& str2){return !(str1 == str2);}ostream& operator<<(ostream& out, const string& str){for (auto ch : str){out << ch;}return out;}istream& operator>>(istream& in, string& str){str.clear();const int N = 256; // 创建数组,减少扩容的次数char buff[N];int i = 0;char ch;ch = in.get();while (ch != ' ' && ch != '\n') //字符串分隔符{buff[i++] = ch;if (i == N - 1){buff[i] = '\0';str += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';str += buff;}return in;}
}
// 测试文件:test.cpp
#include"string.h"namespace hds
{void test01(){hds::string str("hello world");cout << str.c_str() << endl;cout << str.size() << endl;str.push_back('!');cout << str.c_str() << endl;}void test02(){hds::string str("hello");str.append("world");cout << str.c_str() << endl;}void test03(){hds::string str("hello");str += "world";cout << str.c_str() << endl;}void test04(){hds::string str("hello world");str.insert(0, 'x');str.insert(7, 'y');str.insert(13, '!');cout << str.c_str() << endl;}void test05(){hds::string str("hello world");str.insert(0, "***");str.insert(9, "hds");str.insert(17, "!!!");cout << str.c_str() << endl;}void test06(){hds::string s1("hello world");s1.erase(5, 10);cout << s1.c_str() << endl;hds::string s2("hello world");s2.erase(5);cout << s2.c_str() << endl;hds::string s3("hello world");s3.erase(6, 3);cout << s3.c_str() << endl;}void test07(){hds::string s1("hello world");cout << s1.find('o', 0) << endl;cout << s1.find("wor", 0) << endl;string tmp = s1.substr(6, 10);cout << tmp.c_str() << endl;}void test08(){hds::string s1("hello world");hds::string s2("hello world");cout << (s1 <= s2) << endl;}void test09(){string s1("hello world");string s2("hello world");cout << s1 << s2 << endl;string s3;cin >> s3;cout << s3 << endl;}void test(){string s1("hello world");string s2(s1);cout << s1 << endl;cout << s2 << endl;string s3 ("hds");s3 = s1;cout << s3 << endl;}
}
int main()
{//hds::test01();//hds::test02();//hds::test03();//hds::test04();//hds::test05();//hds::test06();hds::test();return 0;
}
本期的分享就到此结束,大家快去上手练习吧!!!如果觉得还不错,请大家点个赞支持一下吧(笔芯笔芯)。