实用指南:系统性学习C++-第七讲-string类

news/2025/11/21 14:19:49/文章来源:https://www.cnblogs.com/gccbuaa/p/19252664

系统性学习C++-第七讲- string类

  • 1. 为什么学习 string类?
    • 1.1 C语言中的字符串
    • 1.2 一个面试题(暂不做讲解)
  • 2. 标准库中的 string类
    • 2.1 string类(了解即可)
    • 2.2 ` auto ` 和 范围 ` for `
    • 2.3 string类的常用接口说明(注意下面我只讲解最常用的接口)
  • 3. string类的模拟实现
  • 3.1 经典的 string 类问题
    • 3.2 浅拷贝
    • 3.3 深拷贝
      • 3.3.1 传统版写法的 string 类
      • 3.3.2 现代版写法的 string 类
    • 3.3 写时拷贝(了解即可)
  • 4. 扩展阅读

1. 为什么学习 string类?

1.1 C语言中的字符串

C语言中,字符串是以 '\0' 结尾的一些字符的集合,为了操作方便,C 标准库中提供了一些 str 系列的库函数,

但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

1.2 一个面试题(暂不做讲解)

字符串相加

在 OJ 中,有关字符串的题目基本以 string 类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用 string 类,

很少有人去使用 C 库中的字符串操作函数。

2. 标准库中的 string类

2.1 string类(了解即可)

string类的文档介绍

在使用 string 类时,必须包含 #include头文件 以及 using namespace std;

2.2 auto 和 范围 for

auto 关键字

在这里补充 2 个 C++11 的小语法,方便我们后面的学习。

  • 在早期 C/C++ 中 auto 的含义是:使用 auto 修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。C++11 中,标准委员会变废为宝赋予了 auto 全新的含义即: auto 不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto 声明的变量必须由编译器在编译时期推导而得。

  • auto 声明指针类型时,用 autoauto* 没有任何区别,但用 auto 声明引用类型时则必须加 &

  • 当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

  • auto 不能作为函数的参数,可以做返回值,但是建议谨慎使用

  • auto 不能直接用来声明数组

#include<iostream>using namespace std;int func1(){return 10;}// 不能做参数void func2(auto a){}// 可以做返回值,但是建议谨慎使用auto func3(){return 3;}int main(){int a = 10;auto b = a;auto c = 'a';auto d = func1();// 编译报错:rror C3531: “e”: 类型包含“auto”的符号必须具有初始值设定项auto e;cout << typeid(b).name() << endl;cout << typeid(c).name() << endl;cout << typeid(d).name() << endl;int x = 10;auto y = &x;auto* z = &x;auto& m = x;cout << typeid(x).name() << endl;cout << typeid(y).name() << endl;cout << typeid(z).name() << endl;auto aa = 1, bb = 2;// 编译报错:error C3538: 在声明符列表中,“auto”必须始终推导为同一类型auto cc = 3, dd = 4.0;// 编译报错:error C3318: “auto []”: 数组不能具有其中包含“auto”的元素类型auto array[] = { 4, 5, 6 };return 0;}
#include<iostream>#include <string>#include <map>using namespace std;int main(){std::map<std::string, std::string> dict = { { "apple", "苹果" },{ "orange", "橙子" }, {"pear","梨"} };// auto的用武之地//std::map<std::string, std::string>::iterator it = dict.begin();auto it = dict.begin();while (it != dict.end()){cout << it->first << ":" << it->second << endl;++it;}return 0;}

范围 for

  • 对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此 C++11 中引入了基于范围的 for 循环。for 循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。

  • 范围 for 可以作用到数组和容器对象上进行遍历

  • 范围 for 的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。

int main()
{
int array[] = { 1, 2, 3, 4, 5 };
// C++98的遍历
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
{
array[i] *= 2;
}
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
{
cout << array[i] << endl;
}
// C++11的遍历
for (auto& e : array)
e *= 2;
for (auto e : array)
cout << e << " " << endl;
string str("hello world");
for (auto ch : str)
{
cout << ch << " ";
}
cout << endl;
return 0;
}

2.3 string类的常用接口说明(注意下面我只讲解最常用的接口)

1. string 类对象的常见构造

(constructor)函数名称功能说明
string() (重点)构造空的 string 类对象,即空字符串
string(const char* s) (重点)用C-string来构造string类对象
string(const string&s) (重点)拷贝构造函数
void Teststring()
{
string s1; // 构造空的string类对象s1
string s2("hello bit"); // 用C格式字符串构造string类对象s2
string s3(s2); // 拷贝构造s3
}

2. string类对象的容量操作

函数名称功能说明
size(重点)返回字符串有效字符长度
length返回字符串有效字符长度
capacity返回空间总大小
empty(重点)检测字符串释放为空串,是返回 true,否则返回 false
clear(重点)清空有效字符
reserve(重点)为字符串预留空间
resize(重点)将有效字符的个数该成 n 个,多出的空间用字符 c 填充

注意:

  1. size()length() 方法底层实现原理完全相同,引入 size() 的原因是为了与其他容器的接口保持一致,一般情况下基本都是用 size()

  2. clear() 只是将 string 中有效字符清空,不改变底层空间大小。

  3. resize(size_t n)resize(size_t n, char c) 都是将字符串中有效字符个数改变到 n 个,不同的是当字符个数增多时: resize(n) 用0来填充多出的元素空间, resize(size_t n, char c) 用字符 c 来填充多出的元素空间。注意: resize 在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。

  4. reserve(size_t res_arg=0) :为 string 预留空间,不改变有效元素个数,当 reserve 的参数小于 string 的底层空间总大小时, reserver 不会改变容量大小。

3. string类对象的访问及遍历操作

函数名称功能说明
operator[](重点)返回 pos 位置的字符,const string 类对象调用
begin + endbegin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
rbegin + rendbegin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
范围 forC++11 支持更简洁的范围 for 的新遍历方式

4. string类对象的修改操作

函数名称功能说明
push_back在字符串后尾插字符
append在字符串后追加一个字符串
operator+= (重点)在字符串后追加字符串 str
c_str(重点)返回 C 格式字符串
find + npos (重点)从字符串 pos 位置开始往后找字符 c ,返回该字符在字符串中的位置
rfind从字符串 pos 位置开始往前找字符 c ,返回该字符在字符串中的位置
resize(重点)将有效字符的个数该成 n 个,多出的空间用字符 c 填充
substr在 str 中从 pos 位置开始,截取 n 个字符,然后将其返回

意:

  1. string 尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c' 三种的实现方式差不多,一般情况下 string 类的 += 操作用的比较多,+= 操作不仅可以连接单个字符,还可以连接字符串。

  2. string 操作时,如果能够大概预估到放多少字符,可以先通过 reserve 把空间预留好。

5. string 类非成员函数

函数名称功能说明
operator+尽量少用,因为传值返回,导致深拷贝效率低
operator>> (重点)输入运算符重载
operator<< (重点)输出运算符重载
getline(重点)获取一行字符串
relational operators (重点)大小比较

上面的几个接口大家了解一下,下面的OJ题目中会有一些体现他们的使用。string类中还有一些其他的操作,这里不一一列举,

大家在需要用到时不明白了查文档即可。

6. vs 和 g++ 下 string 结构的说明

注意:下述结构是在 32 位平台下进行验证,32 位平台下指针占 4 个字节。

  • vs 下 string 的结构:
    string 总共占 28 个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义 string 中字符串的存储空间:
    • 当字符串长度小于 16 时,使用内部固定的字符数组来存放
    • 当字符串长度大于等于 16 时,从堆上开辟空间
union _Bxty
{ // storage for small buffer or pointer to larger one
value_type _Buf[_BUF_SIZE];
pointer _Ptr;
char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;

这种设计也是有一定道理的,大多数情况下字符串的长度都小于 16 ,那 string 对象创建好之后,

内部已经有了 16 个字符数组的固定空间,不需要通过堆创建,效率高。其次:还有一个 size_t 字段保存字符串长度,

一个 size_t 字段保存从堆上开辟空间总的容量

最后:还有一个指针做一些其他事情。故总共占 16 + 4 + 4 + 4 = 28字节

在这里插入图片描述

  • g++ 下 string 的结构
    g++下,string 是通过写时拷贝实现的,string 对象总共占 4 个字节,内部只包含了一个指针,该指针将来指向一块堆空间,
    内部包含了如下字段:

    • 空间总大小
    • 字符串有效长度
    • 引用计数
struct _Rep_base
{
size_type _M_length;
size_type _M_capacity;
_Atomic_word _M_refcount;
};

指向堆空间的指针,用来存储字符串。

7. 牛刀小试
仅仅反转字母

class Solution {
public:
bool isLetter(char ch)
{
if(ch >= 'a' && ch <= 'z')
return true;
if(ch >= 'A' && ch <= 'Z')
return true;
return false;
}
string reverseOnlyLetters(string s)
{
if(s.empty())
return s;
size_t begin = 0, end = s.size() - 1;
while(begin < end)
{
while(begin < end && !isLetter(s[begin]))
begin++;
while(begin < end && !isLetter(s[end]))
end--;
swap(s[begin], s[end]);
begin++;
end--;
}
return s;
}
};

对于这道题,要注意的点是在进行 swap 交换后,对于 beginend 仍然要分别进行自增,自减操作,否则在下一次进入循环后,

会陷入到死循环中。

找字符串中第一个只出现一次的字符

class Solution {
public:
int firstUniqChar(string s) {
int count[256] = {0};
size_t size = s.size();
for(int i = 0; i < size; i++)
{
count[s[i]]++;
}
for(int i = 0;i < size; i++)
{
if(count[s[i]] == 1)
{
return i;
}
}
return -1;
}
};

对于这道题注意的点在于,解题的思路在于我们将所有出现的字符与 count 数组中的下标一一对应,数组中存储的数值,

代表在字符串中出现的次数,当次数统计完成时,我们便需要找出第一个只出现一次的字符,对应就是在数组中数组存储为 1 的字符,

但出现次数为 1 的字符可能有多个,此时我们就该从字符串入手,以字符在字符串中出现的顺序去 count 数组中依次查找,

这样第一个数值为 1 的字符对应在 s 中的下标,就是我们要找出的字符。

字符串最后一个单词的长度

#include <iostream>#include <string>using namespace std;int main(){string line;while(getline(cin, line)){size_t pos = line.rfind(' ');cout << line.size() - pos - 1 << endl;}return 0;}

验证回文串

class Solution {
public:
bool isLetter(char ch)
{
return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');}bool isPalindrome(string s){for(auto& a : s){if(a >= 'a' && a <= 'z')a -= 32;}int begin = 0, end = s.size() - 1;while(begin < end){while(begin < end && !isLetter(s[begin]))begin++;while(begin < end && !isLetter(s[end]))end--;if(s[begin] != s[end])return false;else{begin++, end--;}}return true;}};

在这道题的解答中,我们要注意如何让大写字符与小写字符判断为相等,这里我们采用暴力解决的方式,在用范围 for 进行遍历时,

我们将所有小写字符转换为大写字符,所以在下面的判断中,我们就无需进行更加复杂的操作。

3. string类的模拟实现

3.1 经典的 string 类问题

上面已经对 string 类进行了简单的介绍,大家只要能够正常使用即可。在面试中,面试官总喜欢让学生自己来模拟实现 string 类,

最主要是实现 string 类的构造、拷贝构造、赋值运算符重载以及析构函数。大家看下以下 string 类的实现是否有问题?

// 为了和标准库区分,此处使用String
class String
{
public:
/*String()
:_str(new char[1])
{*_str = '\0';}
*/
//String(const char* str = "\0") 错误示范
//String(const char* str = nullptr) 错误示范
String(const char* str = "")
{
// 构造String类对象时,如果传递nullptr指针,可以认为程序非
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
// 测试
void TestString()
{
String s1("hello bit!!!");
String s2(s1);
}

在这里插入图片描述
说明:上述 String 类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用 s1 构造 s2 时,

编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2 共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,

这种拷贝方式,称为浅拷贝。

3.2 浅拷贝

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,

当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,

就会发生发生了访问违规。就像一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,

万一不想分享就你争我夺,玩具损坏。

在这里插入图片描述
可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。

父母给每个孩子都买一份玩具,各自玩各自的就不会有问题了。

在这里插入图片描述

3.3 深拷贝

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。

一般情况都是按照深拷贝方式提供。

在这里插入图片描述

3.3.1 传统版写法的 string 类

class String
{
public:
String(const char* str = "")
{
// 构造String类对象时,如果传递nullptr指针,可以认为程序非
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
String(const String& s)
: _str(new char[strlen(s._str) + 1])
{
strcpy(_str, s._str);
}
String& operator=(const String& s)
{
if (this != &s)
{
char* pStr = new char[strlen(s._str) + 1];
strcpy(pStr, s._str);
delete[] _str;
_str = pStr;
}
return *this;
}
~String()
{
if (_str)
{
delete[]_str;
_str = nullptr;
}
}
private:
char* _str;
}

3.3.2 现代版写法的 string 类

class String
{
public:
String(const char* str = "")
{
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
String(const String& s)
: _str(nullptr)
{
String strTmp(s._str);
swap(_str, strTmp._str);
}
// 对比下和上面的赋值那个实现比较好?
String& operator=(String s)
{
swap(_str, s._str);
return *this;
}
/*
String& operator=(const String& s)
{
if(this != &s)
{
String strTmp(s);
swap(_str, strTmp._str);
}
return *this;
}
*/
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};

3.3 写时拷贝(了解即可)

在这里插入图片描述
写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。

引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成 1 ,每增加一个对象使用该资源,就给计数增加 1 ,

当某个对象被销毁时,先给该计数减 1 ,然后再检查是否需要释放资源,如果计数为 1 ,说明该对象时资源的最后一个使用者,

将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。

写时拷贝
写时拷贝在读取时的缺陷

4. 扩展阅读

面试中 string 的一种正确写法
STL 中的 string 类怎么了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/972114.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025 年 11 月烘焙食品包装盒,烘焙包装盒订制,月饼盒包装盒厂家最新推荐,聚焦资质、案例、售后的五家机构深度解读!

引言 在全球消费升级与品牌溢价竞争的浪潮下,烘焙食品包装盒、定制烘焙包装盒及月饼盒包装盒已成为产品附加值提升的核心载体,其品质直接影响消费者购买决策与品牌口碑。本次推荐榜单基于国际包装协会(IPA)年度测评…

2025 年必知!十大景区灯光秀、音乐喷泉设计公司大揭秘,哪家才是你的心头好?

在文旅产业蓬勃发展的当下,景区的吸引力早已不局限于自然景观与历史底蕴。一场绚丽的灯光秀能让古建筑在夜色中焕发生机,一曲灵动的音乐喷泉可让湖面成为游客驻足的焦点 —— 景区灯光秀与音乐喷泉,已成为提升景区辨…

2025 年 11 月注塑厂家推荐排行榜,塑胶注塑,模具注塑,精密注塑,定制注塑公司推荐,专业工艺与高效生产口碑之选

2025 年 11 月注塑厂家推荐排行榜,塑胶注塑,模具注塑,精密注塑,定制注塑公司推荐,专业工艺与高效生产口碑之选 随着制造业向智能化、精密化方向转型,注塑行业作为现代工业的重要支撑,正迎来新一轮技术革新与市场…

告别云端依赖!ComfyUI本地化视频生成实战教程+cpolar实战 - 教程

告别云端依赖!ComfyUI本地化视频生成实战教程+cpolar实战 - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "…

质量信息化与知识管理:让企业少走弯路的智慧宝库‌

大家好!今天我们来聊聊质量信息化和知识管理这两个听起来很专业,但实际上跟每个企业都息息相关的话题。 一、质量信息化的本质:从"结绳记事"到智能系统‌ 说到信息化,很多人可能觉得这是最近几年才兴起的…

Windows10 开启FTP配置

控制面板-程序-启用或关闭Windows功能打开IIS-选中网站-右键-添加FTP站点FTP配置完成 后记 报错 【将文件复制到ftp服务器时发生错误...】 将允许utf8 改为false惜秦皇汉武,略输文采;唐宗宋祖,稍逊风骚。 一代天骄,…

min-document原型污染漏洞分析与修复

本文详细分析了min-document包在2.19.1之前版本存在的原型污染漏洞,该漏洞源于removeAttributeNS方法对命名空间操作处理不当,攻击者可通过恶意输入操纵JavaScript对象原型链,导致拒绝服务或任意代码执行。min-docu…

gcc linux安装

要安装 GCC(GNU Compiler Collection) 在 Linux 系统上,通常需要安装 GCC 编译器。GCC 是 Linux 系统上用于编译 C、C++、Fortran 等语言的编译器。? 一、安装 GCC(常用方法)1. 使用包管理器安装(推荐)大多数 …

质量管理系统(QMS)的价值究竟在哪里?‌

在企业管理中,质量管理系统(QMS)已经从传统的人工记录方式,转型升级为现代化的数字化解决方案。那么这种转变能带来哪些实质性的价值呢? 一、告别传统管理的痛点‌ 过去企业主要依赖纸质记录和Excel表格进行质量管…

gcc linux

你提到的 gcc 是 GNU Compiler Collection 的一部分,用于编译 C、C++、Java、Fortran 等语言的源代码。gcc 是 Linux 系统中常用的编译工具,用于将源代码转换为可执行文件或库。一、基本用法1. 编译 C 语言源文件gcc…

2025 最新打包带源头厂家权威推荐榜:ISO 认证 + 日产 20 吨级产能,物流仓储优选品牌全解析PET/纯新料/压花/彩色/透明/全自动/手工打包带公司推荐

引言 在全球物流与制造业高速迭代的背景下,打包带作为货物安全运输的核心保障,其品质稳定性、适配性与环保合规性成为企业采购的关键考量。据国际包装协会(IOPA)2025 年度测评数据显示,全球打包带市场不合格产品占…

L1 Loss、L2 Loss、Cross-Entropy Loss

深度学习中常用的三种主要损失函数:L1 Loss (平均绝对误差)、L2 Loss (均方误差) 和 Cross-Entropy Loss (交叉熵损失)。 这三种损失函数各有特点,并适用于不同的任务和场景。1. L1 Loss (平均绝对误差 / Mean Absol…

Day3:2025年9月24日,星期三,上班。

今天上班,本来以为可以很休闲的,结果值班那天的小情侣纠纷又来要求我们依法处理,不愿意接受调解,你说你们这是何苦呢?最终不也落得个两败俱伤吗?两个人在一起,是缘份,分开了也是缘份,只是这种缘份浅了,浅到无…

2025旗舰级项目管理平台优中选优(10大),匹配主流业务场景需求​

本文将为你详细解析10款旗舰级项目管理平台——禅道、伙伴智慧、筑程通、数智工坊、优企办、智联研发、快建通、云启协同、易趋、泛微e-cology,它们精准匹配2025年主流业务场景需求,是各行业团队数字化转型的核心助力…

ARM AXI-stream、ACE-Lite 与 CMN 的区别解析 - ENGINEER

ARM AXI-stream、ACE-Lite 与 CMN 的区别解析 在 ARM 架构中,AXI-stream、ACE-Lite​ 和 CMN(Coherent Memory Network)是三种不同的总线协议或一致性管理机制,分别针对不同场景设计。以下是它们的核心区别与适用…

Calculus Review

Trivial things...Chapter 0. The requirement of our college is that the process should be written in English... The content of this essay is very very trivial, so should you find yourself with some leis…

2025 最新酸菜厂家推荐!优质酸菜厂家权威排行榜,传统工艺与现代标准兼具的靠谱品牌全解析切丝酸菜/正宗东北酸菜/酸菜丝/酸菜芯/酸菜馅/大缸酸菜/老式酸菜公司推荐

引言 酸菜作为极具地域特色的经典农产品,凭借酸香醇厚的风味、脆嫩爽口的口感,不仅成为日常饮食中不可或缺的食材,更广泛应用于连锁餐饮、预制菜加工等多元领域。然而行业快速发展中,部分品牌存在原料筛选宽松、发…

Linux系统云服务器被入侵如何排查解决?

当 Linux 系统的云服务器被入侵时,及时排查和解决问题是防止更大损失的关键。以下是一个系统化的排查和解决步骤,包括入侵检测、分析取证、修复系统和加强安全防护。1. 确认入侵迹象 首先,需要判断服务器是否真的被…

跨节点协同、合规可控:隐语SecretFlow在运营商架构中的应用解析

在数字化深入推进的当下,政企和运营商行业正面临越来越复杂的数据协同需求:一方面,分支机构众多、系统各有不同,横向数据难以打通;另一方面,监管趋严、数据敏感性强,使得数据出域的风险和门槛持续升高。 如何通…

2025年江苏厨房橱柜厂家全面评测与行业趋势分析

摘要 2025年江苏厨房橱柜行业正迎来智能化、定制化浪潮,随着消费者对家居品质要求的提升,厨房橱柜不再仅是储物工具,而是融合设计、环保与科技的生活空间核心。本文基于行业数据和用户反馈,深度评测江苏地区顶级厨…