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

C++ STL之string类

C++string类

  • (一)C++ 的 string 类基本构造
    • 1、回顾C语言中的字符串
    • 2、string类基本概述
      • 1、首先使用string类必须包含std命名空间
      • 2、string类的构造函数分析
        • 1、默认构造函数
        • 2、从字符串字面量创建字符串
        • 3、使用字符串字面量前n个字符创建
        • 4、拷贝构造函数
        • 5、从一个字符串对象创建子字符串对象
        • 6、自动填充字符创建
        • 7、多参数构造函数分析
        • 8、加餐.size()成员函数
  • (二)C++ 的string类的赋值运算符重载
    • 1、string类的提供的三种赋值运算符重载函数
    • 2、从string类重载
    • 3、从字符串字面量重载
    • 4、从字符重载
  • (三)C++ 的string类对象中字符串的怎么修改
    • 1、遍历+修改的方式
      • 1、修改单个字符
      • 2、遍历修改多个字符
    • 2、通过迭代器iterator进行修改
      • 关于迭代器的小知识
    • 3、auto关键字
      • 1、auto关键字的基本用法
      • 2、auto关键字在迭代器中的应用-范围for

(一)C++ 的 string 类基本构造

C++ 标准库中的string类提供了强大而灵活的字符串处理功能,是处理文本数据的首选方式。它位于头文件中,是basic_string的特化版本。
基本特性
动态管理内存:自动处理内存分配和释放,避免了 C 风格字符串的内存泄漏问题。
安全易用:提供了丰富的成员函数和操作符重载,使字符串操作更加直观。
与 C 风格字符串兼容:可以与 C 风格字符串(const char*)相互转换。
支持国际化:通过wstring、u16string和u32string支持宽字符和 Unicode 字符串。

1、回顾C语言中的字符串

#include <stdio.h>
#include <string.h>int main()
{char str1[] = {"abcdefg"};const char* str2 = "abcdefg";printf("%p\n",str1);printf("%p\n",str2);printf("strlen(str1) = %d \n",strlen(str1)); // strlen计算\0之前的字符数量 // 7}

2、string类基本概述

1、首先使用string类必须包含std命名空间

为什么:C++ 标准库中的所有组件(包括string类)都被放置在std命名空间中。

#include <string>
#include <iostream>int main()
{// std::string str("hello world"); // 正确写法,string在std命名空间中string str("hello world"); // 错误写法,未指定命名空间// [Error] 'string' was not declared in this scope; did you mean 'std::string'?return 0;
}

2、string类的构造函数分析

C++string类首先是一个类,他既然是个类就会有构造函数、析构函数、赋值运算符重载等主勾成员函数。
首先我们看如何使用string类的构造函数去实例化对象的方法
在C++string类中主要有7中构造函数,这里介绍前5个

默认构造函数(1) string(); 重点!
拷贝构造函数(2) string(const string& str); 重点!
从一个字符串创建子字符串(3) string(const string& str, size_t pos, size_t len = npos);
使用字符串字面量创建(4) string(const char* s ); 重点!
使用字符串字面量前n个字符创建(5) string(const char* s, size_t n);重点!
自动填充字符创建(6) string(size_t n, char c);

1、默认构造函数
#include <string>
#include <iostream>
using namespace std;int main()
{string s1;// 实例化了一个string对象s1// s1的初始状态是一个空字符串(长度为 0)// 该对象已经分配了内部结构,但没有存储任何字符数据cout << str.size() << endl;// 可以通过length()或size()方法验证其长度为 0// .size()成员函数后面会详细分析cout << s1 << endl; // 默认构造无输出
}
2、从字符串字面量创建字符串
#include <string>
#include <iostream>
using namespace std;int main()
{string s1("hello world");// 实例化了一个string对象s1// s1内存储了一个字符串 "hello world"cout << s1 << endl; // 输出hello world// 因为<< 已经实现了运算符重载// 当使用流操作符<<时,会直接调用 << 重载后的函数// 简化的流输出运算符重载实现ostream& operator<<(ostream& os, const string& str) {// 遍历字符串中的每个字符并写入流for (size_t i = 0; i < str.length(); ++i) {os.put(str[i]);}return os;}cout << str.size() << endl;// 输出为7
}
3、使用字符串字面量前n个字符创建
#include <string>
#include <iostream>
using namespace std;int main()
{// string(const char* s, size_t n);// 函数原型string s1("hello world",5);// 实例化一个string类对象s1// s1从"hello world"这个字符串字面量中前5个字符中读取元素cout << s1 << endl; // 输出hello}
4、拷贝构造函数
#include <string>
#include <iostream>
using namespace std;int main()
{string s1("hello world");// 实例化一个string类对象s1// s1中存储"hello world"string s2(s1);// 调用拷贝构造函数,使用s1去构造s2对象cout << s2 << endl; // 输出hello world}
5、从一个字符串对象创建子字符串对象
#include <string>
#include <iostream>
using namespace std;int main()
{// 函数原型详解:// 从一个string对象引用,拷贝从pos开始的len个字符// string(const string& str, size_t pos, size_t len = npos);// static const size_t npos = -1 所以npos是len的缺省参数// -1 是二进制补码, 是整形的最大值 即全1,即len最大化string s1("hello world");// 实例化一个string类对象s1// s1中存储"hello world"string s2(s1,0,5);// 从s1对象的第0个字符开始拷贝5个字符// 即s2中存储hellocout << s2 << endl;// 输出hellostring s3(s1,0);// string s3(s1,0) 如果调用 string s3(s1, 1) 时省略 len// 会默认使用 npos,此时会拷贝从位置 1 到 s2 末尾的所有字符。cout << s3 << endl;// 输出hello world}
6、自动填充字符创建
#include <string>
#include <iostream>
using namespace std;int main()
{// string(size_t n, char c);// 函数原型 string s1(10,'A');// 使用10个字符A创建对象s1cout << s1 << endl;// 输出 AAAAAAAAAA}
7、多参数构造函数分析
const char* str = "hello world";
string s5(str, 5);// 使用字符串字面量创建对象
string s4(s2, 1, 6); // 从string对象s2创建子对象s4// 特别是string s4(s2, 1); s4如果不给第三个参数,与string s5(str, 5);长得很像
// 但是他们是不同构造函数!!!// 因为 
// string s5(str, 5);会调用string(const char* s, size_t n);
// 而
//string s4(s2,  1);会调用string(const string& str, size_t pos, size_t len = npos);
// 这里
// 一个是const char* 类型的,一个是cosnt string& 类型的
8、加餐.size()成员函数

str.size() 是string类的成员函数,他的返回值是s1的长度。
1、首先size()和length()是完全等价的成员函数,都会返回对象的长度,但是在以后我们学容器的时候,像list、vector等其他STL计算长度的时候,其长度返回值都是使用.size()函数的,所以推荐使用.size()函数。
2、看到返回长度,这里是不是想起了C语言中的strlen函数?但是.size()函数与strlen有本质的区别。因为strlen计算的是字符数组\0之前的字符个数,而.size()函数不受\0影响直接返回对象字符串总长度。
3、为什么size()与 C 语言的strlen()会有区别呢?
因为实际上 string类在内部在底层维护了一个动态数组,自动管理内存,字符串长度存储在对象内部(如一个size_t类型的成员变量),因此size()直接返回该值,不受字符串内容影响。换句话说string类可以存储任意字符,包括\0,size()返回的是字符串的真实长度,不关心内容中是否有\0。
而C 风格字符串:以\0结尾的字符数组,strlen()需要遍历数组直到遇到\0,时间复杂度为 O (n)。strlen()一旦遇到\0就停止计数,后续字符会被忽略。
4、从这里可以窥见string类的内部:
至少有一个_char*类型指针_str成员变量维护的动态数组
一个size_t 类型的_size成员变量存储当前字符串长度
一个size_t类型的_capacity成员变量记录当前分配的容量,避免频繁重新分配内存。
看到这里学过数据结构的同学可以看出一个问题,这不就是一个顺序表么?
没错!实际上std::string的底层实现确实类似于顺序表(数组),严格来说string类的底层维护了一个动态顺序表

(二)C++ 的string类的赋值运算符重载

1、string类的提供的三种赋值运算符重载函数

从string类重载 string& operator= (const string& str);
从字符串字面量重载 string& operator= (const char* s);
从字符重载 string& operator= (char c);

2、从string类重载

#include <string>
#include <iostream>
using namespace std;int main()
{// 函数原型: string& operator= (const string& str);string s1("hello world");string s2 = s1;cout << s2 << endl;// 输出hello world
}

3、从字符串字面量重载

#include <string>
#include <iostream>
using namespace std;int main()
{// 函数原型: string& operator= (const char* s);const char* str = "hello world"string s1 = str;cout << s1 << endl;// 输出hello world
}

4、从字符重载

#include <string>
#include <iostream>
using namespace std;int main()
{// 函数原型: string& operator= (char c);// string s1 = 'A'; 注意这么写会调用构造函数string s1;s1 = 'A'; // 调用赋值运算符重载cout << s1 << endl;// 输出A
}

(三)C++ 的string类对象中字符串的怎么修改

string类提供了3中解决方案去修改对象中字符串的值:

1、遍历+修改的方式

string类提供了运算符重载函数operator[],这个函数有2个版本
非 const 版本(允许修改字符):
char& operator[](size_t pos);

onst 版本(用于 const 对象,返回只读字符):
const char& operator[](size_t pos) const;

下面是这个运算符重载函数的简化版本

class string {
private:char* data;    // 指向字符数组的指针size_t size;   // 当前字符串长度size_t capacity;  // 当前分配的容量
// 之前说了string类底层维护了一个动态数组public:// 非 const 版本char& operator[](size_t pos) {// 注意:operator[] 不检查越界,由用户自己确保 pos < sizereturn data[pos]; // 因为data是char*类型的动态指针// *(data + pos) 即取得pos位置的字符// 然后返回pos位置字符的引用}// 因为返回的是字符数组中pos位置的引用// 所以修改返回值就是修改对象pos位置的字符// const 版本(用于 const 对象)// const修饰this指针,所以不能修改被引用的对象const char& operator[](size_t pos) const {return data[pos];// 因为 data 是 char* 类型的动态指针// *(data + pos) 即取得 pos 位置的字符// 因为函数被 const 修饰(等价于 const string* this)// 所以不能修改对象的任何成员(包括 data 指向的字符)// 返回 const char& 确保通过返回值也无法修改字符}// 其他成员...
};

1、修改单个字符

#include <string>
#include <iostream>
using namespace std;int main()
{	// 非const类型[]重载string s1("hello world");s1[0] = 'H';// 这里使用了运算符重载[]// []返回了对象s1,0位置的字符引用// 对字符引用进行赋值操作,将0位置的字符‘h’变为‘H’cout << s1 << endl;// 输出Hello world// const类型[]重载const string s2('hello world');// s2[0] = 'H';// 报错,无法修改的右值cout << s2 << endl;// 因为返回的pos位置字符引用被const修饰// 所以只能进行读取,不能修改
}

2、遍历修改多个字符

#include <string>
#include <iostream>
using namespace std;int main()
{	string s1("hello world");for(int i = 0; i<s1.size();i++){s1[i]++;}// 通过s1.size()函数获得字符串长度// 通过[]对返回字符引用进行修改cout << s1 << endl;// 输出 jfmmp!xpsme// 注意在dev c++中无法通过编译,在vs2022中可以通过编译
}

2、通过迭代器iterator进行修改

迭代器是所有容器的主流遍历+修改方式,但是在string中,[]更为方便
首先什么是迭代器?
在 C++ 标准库中,std::string类提供了 ** 迭代器(Iterator)** 来遍历和操作字符串中的字符。
迭代器是一种抽象的 “指针”,它允许我们以统一的方式访问不同容器中的元素,而不必关心底层实现细节。
1、作用:迭代器提供了一种遍历容器元素的方式,隐藏了容器内部的数据结构和访问机制。
2、类比:可以将迭代器想象成一个 “游标” 或 “指针”,它指向容器中的某个元素,并可以在容器中移动。
3、与指针的关系:迭代器的行为类似于指针,但更安全、更抽象。在某些实现中,string的迭代器可能直接封装了字符指针。

正向迭代器:
begin():返回指向字符串第一个字符的迭代器
end():返回指向字符串末最后一个字符的下一个位置,这是一个无效位置,不能直接解引用,仅用于迭代终止条件。

反向迭代器:
rbegin():返回指向字符串最后一个字符的迭代器
rend():返回指向字符串第一个字符的前一个位置的迭代器

迭代器支持以下常见操作:
解引用:*it 返回迭代器指向的元素
递增:++it 或 it++ 移动到下一个元素
比较:it1 == it2 或 it1 != it2 判断两个迭代器是否指向同一位置
指针运算:it + n 或 it - n 移动迭代器

看图非常直观:
string类的正向迭代器
在这里插入图片描述
string类的反向迭代器
在这里插入图片描述

运用正向迭代器对string对象进行遍历

#include <string>
#include <iostream>
using namespace std;int main()
{	string s1("hello world");string::iterator it1 = s1.begin();// begin()返回s1第一个字符的迭代器string::iterator it2 = s1.end();// end返回s1字符串末尾后一个位置的迭代器while(it1 != it2){(*it1)--; // 对迭代器解引用后--,注意优先级问题it1++; // 迭代器++}cout << s1 << endl;// 输出 gdkknvnqkc}

关于迭代器的小知识

1、迭代器的左闭右开是什么意思?
对于 string s,其迭代器区间 [s.begin(), s.end()) 表示:
s.begin():指向字符串的第一个字符(例如 s[0]),是可以被访问和遍历。
对于上面的例子来说就是可以取到字符’h’

s.end():指向字符串的最后一个字符的下一个位置(即尾后迭代器),通常是字符串结束符 ‘\0’ 的位置,不能被访问,仅作为遍历结束的标记。换句话说,尾后迭代器取不到字符,只能取到\0

对于字符串 s = “abc”,迭代器区间为:
s.begin() → ‘a’
s.begin() + 1 → ‘b’
s.begin() + 2 → ‘c’
s.end() → 指向 ‘c’ 的下一个位置(不包含任何字符)即\0的位置

2、为什么采用左闭右开?
空区间统一表示
当 begin == end 时,表示区间为空(例如空字符串 “”)。
区间长度计算简单
区间长度 = end - begin。例如:
std::string s = “abc”;
auto len = std::distance(s.begin(), s.end()); // len = 3

3、auto关键字

在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。
C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&。
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际
只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
auto不能作为函数的参数,可以做返回值,但是建议谨慎使用
auto不能直接用来声明数组

1、auto关键字的基本用法

#include <string>
#include <iostream>
using namespace std;int main()
{	string s1("hello world");string s2("hello world");int x = 10;int& z = x; // z是x的引用auto m = z; // 将x的值赋给m// 等价于int m = z// auto关键字替代的int类型,由编译器自动推导auto& n = z;// 将n设为z的引用// 等价于 int& n = z;auto p1 = &x;// 等价于 int* p1 = &x;auto* p2 = &x;// 等价于 int* p1 = &x;// 上下两种写法一样,但是显然下边这个更严谨一点cout << p1 << endl;// 0x74fec4cout << p2 << endl;// 0x74fec4// p1 和p2 的输出结果是一样的
}

2、auto关键字在迭代器中的应用-范围for

1、使用范围for对字符串进行操作

#include <string>
#include <iostream>
using namespace std;int main()
{	string s1("hello world");for(auto e:s1){cout << e << " ";// 只能对e进行读操作// 等价于 char e : s1// 为什么?看下面的程序}// 范围for循环后的括号由帽号:分为2部分// 第一部分 e  是范围内用于迭代的变量// 第二部分 s1 则表示被迭代的范围cout << endl;// h e l l o   w o r l d// 范围for等价于下面这个程序// 编译器会自动将范围 for 循环转换为迭代器形式// 提高了代码的可读性和简洁性。for(auto it = s1.begin(); it != s1.end(); ++it){auto e = *it;// 因为对迭代器接引用会取得字符本身// 所以auto是char类型的// 等价于 char e = *it;std::cout << e << " ";}
}

2、使用范围for对字符串进行读写操作

#include <string>
#include <iostream>
using namespace std;int main()
{	string s1("0123456789");for(auto& e:s1) // 注意这里相比上面多了一个引用// for(char& e:s1) // 这么写程序也能运行{e++;cout << e << " ";// 对e进行读写操作// 等价于 char& e : s1// 为什么?看下面的程序}// 范围for循环后的括号由帽号:分为2部分// 第一部分 e  是范围内用于迭代的变量// 第二部分 s1 则表示被迭代的范围cout << endl;// 1 2 3 4 5 6 7 8 9 :// 范围for等价于下面这个程序// 编译器会自动将范围 for 循环转换为迭代器形式// 提高了代码的可读性和简洁性。for(auto it = s1.begin(); it != s1.end(); ++it){auto& e = *it;// 因为对迭代器接引用会取得字符本身// 所以auto是char&类型的// 等价于 char& e = *it;// 对*it的引用进行修改等于修改*it本身std::cout << e << " ";}
}

3、范围for在其他stl上的应用

#include <string>
#include <iostream>
using namespace std;int main()
{	// auto for 在普通数组的应用int  array[] = { 1,2,3,4,5 };// 使用普通for循环遍历int sz = sizeof(array) / sizeof(array[0]);for (int i = 0; i < sz; i++){cout << array[i] << " ";}cout << endl;for (auto e : array) // 使用auto for进行变量// 注意如果需要修改数组内部元素的话,必须使用引用,auto& e : array{cout << e << " ";}cout << endl;
}
http://www.lqws.cn/news/578593.html

相关文章:

  • 如何让宿主机完全看不到Wi-Fi?虚拟机独立联网隐匿上网实战!
  • Webpack优化详解
  • 赋能低压分布式光伏“四可”建设,筑牢电网安全新防线
  • 爬虫详解:Aipy打造自动抓取代理工具
  • UI前端与数字孪生融合新趋势:智慧医疗的可视化诊断辅助
  • 2025年XXE攻击全面防御指南:从漏洞原理到智能防护实践
  • python 利用socketio(WebSocket协议)实现轻量级穿透方案
  • GO 语言学习 之 Map
  • PyTorch 中 nn.Linear() 参数详解与实战解析(gpt)
  • K8s环境下基于Nginx WebDAV与TLS/SSL的文件上传下载部署指南
  • 极易搭建的自助Git服务Gogs
  • LeetCode 594. Longest Harmonious Subsequence
  • Hyperledger Fabric 入门笔记(二十一)Fabric V2.5 使用K8S部署测试网络
  • UI_NGUI_三大基础控件
  • 祛魅 | 在祛魅中成长,在成长中祛魅
  • DAY 43 预训练模型
  • 完整的ROS节点来实现果蔬巡检机器人建图与自主避障系统
  • 《从量子奇境到前端优化:解锁卡西米尔效应的隐藏力量》
  • API接口安全-1:身份认证之传统Token VS JWT
  • VMware 在局域网环境将虚拟机内部ip 端口开放
  • 使用SRS+ffmpeg实现https推流flv
  • python+uniapp基于微信小程序面向品牌会员的在线商城系统
  • 如何让Excel自动帮我们算加减乘除?
  • 基于llama-factory+ollama+vllm加速大模型训推生产
  • 深入 ARM-Linux 的系统调用世界
  • C++ std::list详解:深入理解双向链表容器
  • 分库分表之实战-sharding-JDBC
  • 【数论 拆位法】P10308 「Cfz Round 2」Osmanthus|普及+
  • 车辆工程中的压力传感技术:MEMS与薄膜传感器的实战应用
  • 从设计到开发一个小程序页面