【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

MCP 工具速成:npx vs. uvx 全流程安装指南

MCP 工具速成:npx vs. uvx 全流程安装指南

在现代 AI 开发中,Model Context Protocol(MCP)允许通过外部进程扩展模型能力,而 npx(Node.js 生态)和 uvx(Python 生态)则是两种即装即用的客户端工具,帮助你快速下载并运行 MCP 服务器或工具包,无需全局安装。本文将从原理和对比入手,提供面向 Windows、macOS、Linux 的详细安装、验证及使用示例,确保你能在本地或 CI/CD 流程中无缝集成 MCP 服务器。 1. 工具简介 1.1 npx(Node.js/npm) npx 是 npm CLI(≥v5.2.0)

By Ne0inhk
解锁Dify与MySQL的深度融合:MCP魔法开启数据新旅程

解锁Dify与MySQL的深度融合:MCP魔法开启数据新旅程

文章目录 * 解锁Dify与MySQL的深度融合:MCP魔法开启数据新旅程 * 引言:技术融合的奇妙开篇 * 认识主角:Dify、MCP 与 MySQL * (一)Dify:大语言模型应用开发利器 * (二)MCP:连接的桥梁 * (三)MySQL:经典数据库 * 准备工作:搭建融合舞台 * (一)环境搭建 * (二)安装与配置 Dify * (三)安装与配置 MySQL * 关键步骤:Dify 与 MySQL 的牵手过程 * (一)安装必要插件 * (二)配置 MCP SSE * (三)创建 Dify 工作流 * (四)配置 Agent 策略 * (五)搭建MCP

By Ne0inhk
如何在Cursor中使用MCP服务

如何在Cursor中使用MCP服务

前言 随着AI编程助手的普及,越来越多开发者选择在Cursor等智能IDE中进行高效开发。Cursor不仅支持代码补全、智能搜索,还能通过MCP(Multi-Cloud Platform)服务,轻松调用如高德地图API、数据库等多种外部服务,实现数据采集、处理和自动化办公。 本文以“北京一日游自动化攻略”为例,详细讲解如何在 Cursor 中使用 MCP 服务,完成数据采集、数据库操作、文件生成和前端页面展示的全流程。 学习视频:cursor中使用MCP服务 一、什么是MCP服务? MCP(Multi-Cloud Platform)是Cursor内置的多云服务接口,支持调用地图、数据库、文件系统等多种API。通过MCP,开发者无需手动写HTTP请求或繁琐配置,只需在对话中描述需求,AI助手即可自动调用相关服务,极大提升开发效率。 二、环境准备 2.1 cursor Cursor重置机器码-解决Too many free trials. 2.

By Ne0inhk
MCP客户端与服务端初使用——让deepseek调用查询天气的mcp来查询天气

MCP客户端与服务端初使用——让deepseek调用查询天气的mcp来查询天气

本系列主要通过调用天气的mcp server查询天气这个例子来学习什么是mcp,以及怎么设计mcp。话不多说,我们开始吧。主要参考的是B站的老哥做的一个教程,我把链接放到这里,大家如果有什么不懂的也可以去看一下。 https://www.bilibili.com/video/BV1NLXCYTEbj?spm_id_from=333.788.videopod.episodes&vd_source=32148098d54c83926572ec0bab6a3b1d https://blog.ZEEKLOG.net/fufan_LLM/article/details/146377471 最终的效果:让deepseek-v3使用天气查询的工具来查询指定地方的天气情况 技术介绍 MCP,即Model Context Protocol(模型上下文协议),是由Claude的母公司Anthropic在2024年底推出的一项创新技术协议。在它刚问世时,并未引起太多关注,反响较为平淡。然而,随着今年智能体Agent领域的迅猛发展,MCP逐渐进入大众视野并受到广泛关注。今年2月,

By Ne0inhk