【C++】string类

【C++】string类

C++ string 类全面解析

1. 为什么学习 string 类?

1.1 C语言中的字符串局限性

在C语言中,字符串是以\0结尾的字符数组,这种表示方式存在几个明显的缺陷:

C语言字符串的主要问题:

  • 安全性问题:容易发生缓冲区溢出,导致程序崩溃或安全漏洞
  • 内存管理复杂:需要手动管理内存分配和释放,容易造成内存泄漏
  • 功能有限:标准库函数功能相对基础,复杂的字符串操作需要自行实现
  • 不符合面向对象思想:数据与操作分离,不符合现代编程范式
// C语言字符串操作的典型问题char str[10];strcpy(str,"这个字符串太长了会导致溢出");// 潜在的安全风险

1.2 实际应用需求

在现代编程中,字符串处理占据了极大的比重。无论是Web开发、数据处理还是系统编程,都离不开高效的字符串操作。string类的出现正是为了解决C语言字符串的种种痛点。

面试题示例(后续详解):

  • 字符串转整型数字
  • 大数相加(字符串形式)
实践建议:在OJ题目和实际开发中,string类已成为字符串处理的首选工具,相比C字符串库函数更加安全高效。

2. 标准库中的string类

2.1 string类基础

string类是C++标准库中用于表示和操作字符串的类,封装了字符串的存储和常见操作。

基本用法:

#include<string>#include<iostream>usingnamespace std;intmain(){ string s1;// 空字符串 string s2 ="Hello";// 直接初始化 string s3("World");// 构造函数初始化return0;}

2.2 C++11新特性:auto和范围for

auto关键字详解

auto是C++11引入的类型推断关键字,让编译器自动推导变量类型。

auto的使用规则:

#include<iostream>#include<map>#include<string>usingnamespace std;intmain(){// 基本类型推断auto a =10;// intauto b =3.14;// doubleauto c ='A';// char// 指针和引用int x =100;auto y =&x;// int*auto* z =&x;// int* (与上面等价)auto& ref = x;// int&// 容器迭代器简化 map<string, string> dict ={{"apple","苹果"},{"banana","香蕉"}};// 传统写法(冗长) map<string, string>::iterator it1 = dict.begin();// auto写法(简洁)auto it2 = dict.begin();// 遍历mapfor(auto it = dict.begin(); it != dict.end();++it){ cout << it->first <<": "<< it->second << endl;}return0;}

auto的限制:

  • 必须初始化:auto x; // 错误
  • 多变量声明必须类型一致:auto a=1, b=2.0; // 错误
  • 不能用于函数参数(但可以用于返回值)
  • 不能声明数组:auto arr[] = {1,2,3}; // 错误
范围for循环(Range-based for loop)

范围for提供了更简洁的遍历语法,特别适合容器遍历。

#include<iostream>#include<vector>#include<string>usingnamespace std;intmain(){// 数组遍历int arr[]={1,2,3,4,5};// 传统遍历方式for(int i =0; i <sizeof(arr)/sizeof(arr[0]);++i){ cout << arr[i]<<" ";} cout << endl;// 范围for遍历(只读)for(auto elem : arr){ cout << elem <<" ";} cout << endl;// 范围for遍历(可修改)for(auto& elem : arr){ elem *=2;// 修改元素}// 字符串遍历 string str ="Hello";for(auto ch : str){ cout << ch <<" ";} cout << endl;return0;}

范围for的底层原理:编译器会将范围for转换为基于迭代器的普通循环。

2.3 string类常用接口详解

2.3.1 构造函数
构造函数功能说明示例
string()创建空字符串string s1;
string(const char* s)用C字符串构造string s2("hello");
string(size_t n, char c)n个字符c组成的字符串string s3(5, 'A'); // "AAAAA"
string(const string& str)拷贝构造string s4(s2);
voidtestConstructors(){ string s1;// 空字符串 string s2("Hello World");// 从C字符串构造 string s3(s2);// 拷贝构造 string s4(10,'*');// "**********" string s5 ="直接赋值";// 赋值初始化 cout <<"s2: "<< s2 << endl; cout <<"s4: "<< s4 << endl;}
2.3.2 容量操作

重要容量方法:

方法功能说明
size()/length()返回字符串长度两者功能相同,推荐size()
capacity()返回分配的内存大小通常 ≥ size()
empty()判断是否为空空返回true,否则false
clear()清空内容不释放内存,size=0
reserve(size_t n)预留空间避免频繁重新分配
resize(size_t n, char c)调整大小多出部分用c填充
voidtestCapacity(){ string str ="Hello"; cout <<"长度: "<< str.size()<< endl;// 5 cout <<"容量: "<< str.capacity()<< endl;// 15(编译器相关) cout <<"是否为空: "<< str.empty()<< endl;// 0(false) str.resize(10,'!'); cout <<"调整后: "<< str << endl;// "Hello!!!!!" str.reserve(100); cout <<"预留后容量: "<< str.capacity()<< endl; str.clear(); cout <<"清空后长度: "<< str.size()<< endl;// 0}

resize()详解:

string s ="Hello"; s.resize(3);// "Hel"(截断) s.resize(8,'!');// "Hel!!!!!"(扩展并填充) s.resize(10);// "Hel!!!!! "(扩展,默认填充空格)
2.3.3 元素访问和遍历

多种遍历方式:

voidtestTraversal(){ string str ="ABCDE";// 1. 下标操作符[]for(size_t i =0; i < str.size();++i){ cout << str[i]<<" ";// A B C D E} cout << endl;// 2. 迭代器for(auto it = str.begin(); it != str.end();++it){ cout <<*it <<" ";// A B C D E} cout << endl;// 3. 反向迭代器for(auto rit = str.rbegin(); rit != str.rend();++rit){ cout <<*rit <<" ";// E D C B A} cout << endl;// 4. 范围for(推荐)for(auto ch : str){ cout << ch <<" ";// A B C D E} cout << endl;}
2.3.4 修改操作

常用修改方法:

方法功能示例
push_back(char c)尾部添加字符str.push_back('!')
append(const string& str)追加字符串str.append(" World")
operator+=追加(最常用)str += "!!"
insert(size_t pos, const string& str)插入字符串str.insert(5, "插入")
erase(size_t pos, size_t len)删除子串str.erase(5, 2)
replace(size_t pos, size_t len, const string& str)替换子串str.replace(0, 5, "Hi")
voidtestModification(){ string str ="Hello";// 追加操作 str.push_back('!');// "Hello!" str.append(" World");// "Hello! World" str +="!!";// "Hello! World!!"(最常用)// 插入和删除 str.insert(6,"C++ ");// "Hello! C++ World!!" str.erase(0,7);// "C++ World!!" str.replace(4,5,"String");// "C++ String!!" cout <<"最终结果: "<< str << endl;}
2.3.5 字符串操作

查找和子串操作:

voidtestStringOperations(){ string str ="Hello World, Hello C++";// 查找操作 size_t pos1 = str.find("Hello");// 0 size_t pos2 = str.find("Hello",1);// 13(从位置1开始找) size_t pos3 = str.rfind("Hello");// 13(从后往前找)// 子串提取 string sub1 = str.substr(6,5);// "World" string sub2 = str.substr(6);// "World, Hello C++"// 比较操作 string s1 ="apple", s2 ="banana";int result = s1.compare(s2);// 负数(apple < banana) cout <<"find结果: "<< pos1 <<", "<< pos2 <<", "<< pos3 << endl; cout <<"子串: "<< sub1 <<", "<< sub2 << endl;}

2.4 不同编译器下的string实现

VS下的string实现(小字符串优化)

Visual Studio采用小字符串优化(SSO)策略:

内存布局(32位平台):

  • 16字节缓冲区:长度<16时使用栈空间
  • 4字节:字符串长度
  • 4字节:总容量
  • 4字节:其他信息
  • 总计28字节

优势:短字符串无需堆分配,提高性能。

// VS中小字符串优化的效果 string shortStr ="short";// 使用内部缓冲区(栈) string longStr ="这是一个很长的字符串...";// 使用堆分配
g++下的string实现(写时拷贝)

g++采用写时拷贝(Copy-On-Write)技术:

内存布局:

  • 4字节指针:指向堆上的结构体
  • 结构体包含:长度、容量、引用计数、字符串数据

优势:拷贝时不立即复制数据,提高拷贝效率。

// g++中的写时拷贝 string s1 ="hello"; string s2 = s1;// 不复制数据,只增加引用计数 s2[0]='H';// 此时才真正复制数据(写时拷贝)

2.5 实战练习

示例1:仅反转字母
classSolution{public:boolisLetter(char ch){return(ch >='a'&& ch <='z')||(ch >='A'&& ch <='Z');} string reverseOnlyLetters(string s){if(s.empty())return s;int left =0, right = s.size()-1;while(left < right){// 找到左边的字母while(left < right &&!isLetter(s[left]))++left;// 找到右边的字母while(left < right &&!isLetter(s[right]))--right;// 交换字母if(left < right){swap(s[left], s[right]);++left;--right;}}return s;}};
示例2:字符串相加(大数加法)
classSolution{public: string addStrings(string num1, string num2){int i = num1.size()-1, j = num2.size()-1;int carry =0; string result;while(i >=0|| j >=0|| carry >0){int digit1 =(i >=0)? num1[i--]-'0':0;int digit2 =(j >=0)? num2[j--]-'0':0;int sum = digit1 + digit2 + carry; carry = sum /10; result.push_back('0'+(sum %10));}reverse(result.begin(), result.end());return result;}};
示例三:字符串最后一个单词的长度
在这里插入图片描述
#include<iostream>usingnamespace std;intmain(){ string s;getline(cin,s);//这里不能用流提取。流提取识别不了空格 size_t pos=s.rfind(' '); cout<<s.size()-(pos+1)<<endl;}
在这里插入图片描述

getlin默认三个参数,输入两个时,默认最后一个是空

3. string类的模拟实现

3.1 浅拷贝问题

浅拷贝的危险性:

// 有问题的String类实现classString{public:String(constchar* str =""){ _str =newchar[strlen(str)+1];strcpy(_str, str);}~String(){delete[] _str;}private:char* _str;};voidtestProblem(){ String s1("hello"); String s2(s1);// 浅拷贝:s1和s2指向同一内存// 析构时:同一内存被删除两次 → 程序崩溃}

3.2 深拷贝实现

传统版String类
classString{public:// 构造函数String(constchar* str =""){if(str ==nullptr) str =""; _str =newchar[strlen(str)+1];strcpy(_str, str);}// 拷贝构造函数(深拷贝)String(const String& other){ _str =newchar[strlen(other._str)+1];strcpy(_str, other._str);}// 赋值运算符重载 String&operator=(const String& other){if(this!=&other){// 防止自赋值char* temp =newchar[strlen(other._str)+1];strcpy(temp, other._str);delete[] _str;// 释放原有资源 _str = temp;}return*this;}// 析构函数~String(){delete[] _str;}private:char* _str;};
现代版String类(更优雅)
classString{public:String(constchar* str =""){if(str ==nullptr) str =""; _str =newchar[strlen(str)+1];strcpy(_str, str);}// 拷贝构造函数:利用临时对象交换String(const String& other):_str(nullptr){ String temp(other._str);// 用C字符串构造临时对象swap(_str, temp._str);// 交换资源}// 赋值运算符:参数为值传递,利用交换技术 String&operator=(String other){// 注意:参数为值传递swap(_str, other._str);// 交换资源return*this;// other析构时会释放原有资源}~String(){delete[] _str;}private:char* _str;};

3.3 写时拷贝(Copy-On-Write)

写时拷贝是一种优化技术,在读取时共享数据,在修改时才进行实际拷贝。

基本原理:

  1. 多个对象共享同一数据
  2. 引用计数跟踪共享者数量
  3. 当有修改操作时,才进行实际拷贝
// 简化的写时拷贝实现classCowString{private:structStringData{char* data;int refCount;// 引用计数StringData(constchar* str){ data =newchar[strlen(str)+1];strcpy(data, str); refCount =1;}~StringData(){delete[] data;}}; StringData* _data;public:// 实现细节略...};

4. 最佳实践和性能建议

4.1 字符串操作优化

// 不推荐的写法(性能差) string result;for(int i =0; i <1000;++i){ result +="data";// 可能多次重新分配内存}// 推荐的写法 string result; result.reserve(5000);// 预先分配足够空间for(int i =0; i <1000;++i){ result +="data";// 无重新分配}

4.2 选择合适的方法

string str ="hello";// 尾部添加字符的三种方式 str.push_back('!');// 方式1 str.append(1,'!');// 方式2  str +='!';// 方式3(最常用)// 查找操作选择 size_t pos1 = str.find('e');// 查找字符 size_t pos2 = str.find("ll");// 查找子串 size_t pos3 = str.rfind('l');// 反向查找

5. 总结

string类是C++中最重要的工具类之一,它:

  1. 解决了C字符串的安全性问题:自动内存管理,防止缓冲区溢出
  2. 提供了丰富的操作方法:查找、替换、分割等常用操作一应俱全
  3. 具有高效的实现:小字符串优化、写时拷贝等技术提升性能

推荐的写法

string result; result.reserve(5000);// 预先分配足够空间for(int i =0; i <1000;++i){ result +="data";// 无重新分配}

4.2 选择合适的方法

string str ="hello";// 尾部添加字符的三种方式 str.push_back('!');// 方式1 str.append(1,'!');// 方式2  str +='!';// 方式3(最常用)// 查找操作选择 size_t pos1 = str.find('e');// 查找字符 size_t pos2 = str.find("ll");// 查找子串 size_t pos3 = str.rfind('l');// 反向查找

5. 总结

string类是C++中最重要的工具类之一,它:

  1. 解决了C字符串的安全性问题:自动内存管理,防止缓冲区溢出
  2. 提供了丰富的操作方法:查找、替换、分割等常用操作一应俱全
  3. 具有高效的实现:小字符串优化、写时拷贝等技术提升性能
  4. 支持现代C++特性:与STL算法、范围for等完美配合

Read more

走进Java:学生管理系统进阶

走进Java:学生管理系统进阶

❀❀❀  大佬求个关注吧~祝您开心每一天  ❀❀❀ 目录 一、系统设计图  二、添加用户类 三、系统功能设计 3.1 系统菜单打印 3.2 系统注册 3.2.1 检测账号是否存在 3.2.2 检测密码是否满足要求 3.2.3 检测身份证号是否满足要求 3.3 系统登陆 3.3.1 限制登录次数 3.4 忘记密码 3.4.1 重置密码 上一篇文章当中,写了一个简单的学生管理系统,今天在这个系统的基础上,实现一个基于用户登录注册的学生管理系统。 走进Java:实现一个简单的学生管理系统-ZEEKLOG博客 一、系统设计图 系统流程图大致如上,

By Ne0inhk
飞算JavaAI 2.0.0测评:自然语言编程如何颠覆传统开发?

飞算JavaAI 2.0.0测评:自然语言编程如何颠覆传统开发?

飞算JavaAI 2.0.0测评:自然语言编程如何颠覆传统开发? 🌟 嗨,我是IRpickstars! 🌌 总有一行代码,能点亮万千星辰。 🔍 在技术的宇宙中,我愿做永不停歇的探索者。 ✨ 用代码丈量世界,用算法解码未来。我是摘星人,也是造梦者。 🚀 每一次编译都是新的征程,每一个bug都是未解的谜题。让我们携手,在0和1的星河中,书写属于开发者的浪漫诗篇。 目录 一、前言 二、飞算JavaAI:AI赋能的Java开发助手 2.1 飞算JavaAI的来源 2.2 飞算JavaAI的“超能力” 三、实战体验:飞算JavaAI如何成为我的“开发搭档”? 3.1 IntelliJ IDEA安装与配置 3.2 用自然语言生成代码:九九乘法表 3.3 复杂逻辑生成:冒泡排序 3.

By Ne0inhk
飞算JavaAI全链路实战:智能构建高可用电商系统核心架构

飞算JavaAI全链路实战:智能构建高可用电商系统核心架构

飞算JavaAI全链路实战:智能构建高可用电商系统核心架构 前言:AI编程新时代的电商系统开发范式变革 最近学习人工智能时遇到一个好用的网站给大家分享一下 人工智能学习 在当今数字经济时代,电商系统作为企业数字化转型的核心载体,其复杂度和技术要求与日俱增。一个完整的电商系统不仅需要处理商品、订单、用户等基础业务,还要应对高并发、分布式事务、数据一致性等复杂技术挑战。传统开发模式下,从需求分析到系统上线往往需要耗费大量人力和时间成本。 本次我通过飞算JavaAI平台,深入探索"电商系统核心功能模块"这一实战赛道,全面体验了从需求分析到代码生成的全链路开发过程。本文将完整呈现如何借助AI辅助开发工具,高效构建一个包含用户管理、商品系统、订单流程、支付集成等核心模块的电商平台,严格遵循"需求分析-开发实录-优化调试-成果总结"的四大核心框架,为开发者提供一份AI辅助全栈开发的完整实践指南。 一、需求分析与规划:构建电商系统的业务架构蓝图 在启动飞算JavaAI之前,需要进行全面的业务需求梳理和系统架构设计,这是确保AI生成代码符合预期的基础。 1.(理解需求)系统核心模块与

By Ne0inhk
【Java 开发日记】我们来说一下无锁队列 Disruptor 的原理

【Java 开发日记】我们来说一下无锁队列 Disruptor 的原理

目录 一、为什么需要 Disruptor?—— 背景与问题 二、核心设计思想 三、核心组件与原理 1. 环形缓冲区(Ring Buffer) 2. 序列(Sequence) 3. 序列屏障(Sequence Barrier) 4. 等待策略(Wait Strategy) 5. 事件处理器(EventProcessor) 6. 生产者(Producer) 四、工作流程示例(单生产者 -> 单消费者) 五、多消费者与依赖关系 六、总结:Disruptor 高性能的秘诀 一、为什么需要 Disruptor?—— 背景与问题 在高并发编程中,传统的队列(如 java.

By Ne0inhk