当前位置: 首页 > ai >正文

【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获取第一个字符前一个位置的迭代器(地址)
范围forC++11支持更简洁的范围for的新遍历方式,与auto关键字联用

begin()函数有两种形式,分别对应const和非conststd::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;
}

本期的分享就到此结束,大家快去上手练习吧!!!如果觉得还不错,请大家点个赞支持一下吧(笔芯笔芯)。

http://www.xdnf.cn/news/19742.html

相关文章:

  • [论文阅读] 人工智能 + 软件工程 | ReCode:解决LLM代码修复“贵又慢”!细粒度检索+真实基准让修复准确率飙升
  • 【序列晋升】27 Spring Cloud Sleuth给分布式系统装上透视镜
  • 彩笔运维勇闯机器学习--逻辑回归
  • JavaScript手录进阶01-跨域问题
  • Diamond基础3:在线逻辑分析仪Reveal的使用
  • 用AI做旅游攻略,真能比人肉整理靠谱?
  • iOS 上架 uni-app 流程全解析,从打包到发布的完整实践
  • LabVIEW软件全面解析:图形化编程的工业级应用指南
  • RL 大模型逆袭!搞定真实软件工程任务,成功率从 20% 飙到 39%,无需教师模型蒸馏
  • 如何将华为手机数据转移到OPPO手机
  • 2004-2023年各省生活垃圾无害化处理率数据(无缺失)
  • 07、上传jar包到 Linux 并启动项目
  • 9月3日星期三今日早报简报微语报早读
  • 深入解析Java Spliterator(Stream延迟、并行计算核心)
  • TensorFlow的Yes/No 关键词识别模型训练
  • LVGL9.3 vscode 模拟环境搭建
  • 多层环境室内定位系统综述总结
  • 如何获取easy-ui的表格的分页大小
  • VRRP协议
  • Deformable 3D Gaussians:把动态场景装进“可变形的静态世界”
  • 技术重构人力管理 —— 打造人力资源流程自动化、智能化专业服务方案
  • 解决git无法连接github
  • 打破信息洪流:微算法科技(NASDAQ:MLGO)推出一种移动互联网环境下数字媒体热点挖掘算法
  • 什么是量子计算?
  • HarmonyOS 声明式 UI 状态管理深度实践:从 @State 到持久化
  • STM32的时钟系统与时钟树的配置
  • 深兰科技AI问诊助手走访打浦桥街道社区卫生服务中心
  • 阅兵背后的科技:战场上的目标检测与无人机巡检
  • 刷新记录:TapData Oracle 日志同步性能达 80K TPS,重塑实时同步新标准
  • 腾讯云《意愿核身移动 H5》 接入完整示例