C++刷怪笼(7)string类

目录

1.前言

2.正文

2.1标准库中的string类

2.1.1string类

2.1.2auto和范围for

2.1.3string类的常用接口说明

2.2string类的模拟实现

2.2.1经典的string类问题

2.2.2浅拷贝

2.2.3深拷贝

​编辑

2.2.4写时拷贝

3.小结


1.前言

前面我们对C++的封装这一大特性进行了详细的学习,接下来我们来欣赏C++的另一大杰作——STL容器。其包含许多工具,其中string类就是最常见的工具之一。

我们为什么要熟练掌握string类?

首先,C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。、

另外,在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。

2.正文

2.1标准库中的string类

2.1.1string类

我们可以在string - C++ Reference (cplusplus.com)里面详细了解string类(英文文档,较难阅读,需静心看)

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

2.1.2auto和范围for

auto关键字

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

2.用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
3.当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
4.auto不能作为函数的参数,可以做返回值,但是建议谨慎使用
5.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
1.对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。
2.范围for可以作用到数组和容器对象上进行遍历。
3.范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。

#include<iostream>
#include <string>
#include <map>using namespace std;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.1.3string类的常用接口说明


1. string类对象的常见构造

void Teststring()
{string s1; // 构造空的string类对象s1string s2("hello bit"); // 用C格式字符串构造string类对象s2string s3(s2); // 拷贝构造s3
}

2.string类对象的容量操作

 注意:
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, charc)用字符c来填充多的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。

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

 4. string类对象的修改操作

 注意:
1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好

 5. 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 onevalue_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;
};

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

2.2string类的模拟实现

2.2.1经典的string类问题

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

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

// 为了和标准库区分,此处使用String
class String
{ p
ublic:
/*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共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。

2.2.2浅拷贝

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致
多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该
资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。
就像一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,万一
不想分享就你争我夺,玩具损坏。

可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。父
母给每个孩子都买一份玩具,各自玩各自的就不会有问题了。
 

2.2.3深拷贝

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给
出。一般情况都是按照深拷贝方式提供。
 

传统写法下的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;
};

以下是现代版写法的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);
} r
eturn *this;
} */~String(){if (_str){delete[] _str;_str = nullptr;}}
private:char* _str;
};

2.2.4写时拷贝

写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。
引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该
资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,
如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有
其他对象在使用该资源。

3.小结

string类在日常学习和刷题中是很常见的,大家在看完后一定要多去刷题,掌握了解string的用法,这样才能达到真正的目的,刚看完确实觉得懂了,但过了一段时间再学就会生疏,大家一定要勤奋一点,练起来,一起进步,一起加油。


 

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

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

相关文章

题目:圆桌会议

Problem - 1214 (hdu.edu.cn) 解题思路&#xff1a; 结果的顺序就是原序列的逆序&#xff0c;例如12345就是54321为结果顺序。同时将一个顺序序列&#xff08;非环&#xff09;变成逆序需要的次数为。想要的得到最短的交换次数&#xff0c;只需要将环尽量对半分&#xff0c;然后…

【万字长文】Word2Vec计算详解(三)分层Softmax与负采样

【万字长文】Word2Vec计算详解&#xff08;三&#xff09;分层Softmax与负采样 写在前面 第三部分介绍Word2Vec模型的两种优化方案。 【万字长文】Word2Vec计算详解&#xff08;一&#xff09;CBOW模型 markdown行 9000 【万字长文】Word2Vec计算详解&#xff08;二&#xff0…

Chromium 中chrome.cookies扩展接口c++实现分析

chrome.cookies 使用 chrome.cookies API 查询和修改 Cookie&#xff0c;并在 Cookie 发生更改时收到通知。 更多参考官网定义&#xff1a;chrome.cookies | API | Chrome for Developers (google.cn) 本文以加载一个清理cookies功能扩展为例 https://github.com/Google…

针对考研的C语言学习(循环队列-链表版本以及2019循环队列大题)

题目 【注】此版本严格按照数字版循环队列的写法&#xff0c;rear所代表的永远是空数据 图解 1.初始化部分和插入部分 2出队 3.分部代码解析 初始化 void init_cir_link_que(CirLinkQue& q) {q.rear q.front (LinkList)malloc(sizeof(LNode));q.front->next NULL…

Ansible 工具从入门到使用

1. Ansible概述 Ansible是一个基于Python开发的配置管理和应用部署工具&#xff0c;现在也在自动化管理领域大放异彩。它融合了众多老牌运维工具的优点&#xff0c;Pubbet和Saltstack能实现的功能&#xff0c;Ansible基本上都可以实现。 Ansible能批量配置、部署、管理上千台主…

基于Zynq SDIO WiFi移植一(支持2.4/5G)

基于SDIO接口的WIFI&#xff0c;在应用上&#xff0c;功耗低于USB接口&#xff0c;且无须USB Device支持&#xff0c;满足某些应用场景 1 硬件连接 2 Vivado工程配置 3 驱动编译 3.1 KERNRL CONFIG (build ENV) 修改 export KERNELPATH<path of kernel header>export T…

一种压缩QRCode矩阵以用于存储的方法

通常QRCode由服务器生成&#xff0c;以图片格式发送到客户端&#xff0c;由客户端直接展示&#xff0c;也可以由客户端使用javascript或其他内置的SDK直接生成。 0、需求 QRCode生成过程中往往是先生成矩阵&#xff0c;然后使用矩阵生成图片&#xff0c;矩阵就是由01组成的一…

[单master节点k8s部署]35.ingress 反向代理(二)

成功部署ingress controller [rootmaster 35ingress]# kubectl get pods -n kube-system NAME READY STATUS RESTARTS AGE calico-kube-controllers-7dc5458bc6-fpv96 1/1 Running 10 (4d16h ago) 9d calico-…

自动化测试selenium篇(二)

1. 操作测试对象 1.1 普通操作 测试代码如下所示&#xff1a; private static void Test03() throws InterruptedException {//创建一个驱动WebDriver webDriver new ChromeDriver();//打开百度首页webDriver.get("https://www.baidu.com");//找到百度搜索输入框…

通过AI技术克服自动化测试难点(下)

前面的文章里我们对可以应用到测试中的AI技术做了整体介绍&#xff0c;详细介绍了OpenCV技术、OCR技术和神经网络&#xff0c;本文我们继续为大家介绍卷积神经网络、数据集以及AI技术在其他方面和测试相关的创新。 卷积神经网络整体上的原理是这样的&#xff0c;首先在底层特征…

【linux系统】进程

文章目录 进程和PCBlinux与进程的相关命令PS linux下的PCB进程标识符父子进程fork 进程状态磁盘睡眠 -- D暂停和跟踪暂停 -- T和t僵尸进程 -- Z孤儿进程 进程优先级 进程地址空间再谈fork进程地址空间分布虚拟地址和页表mm_struct 进程控制进程终止进程退出码信号 进程等待进程…

【翻译】在 Python 应用程序中使用Qt Designer的UI文件

原文地址&#xff1a;Using a Designer UI File in Your Qt for Python Application 直接上图&#xff0c;上代码 将UI文件转为Python 为了演示&#xff0c;我们使用 Qt Widgets 简单示例说明。 这个应用程序由一个源文件 easing.py、一个 UI 文件 form.UI、一个资源文件 ea…

考研笔记之操作系统(四) - 文件管理

文件管理 1. 简介1.1 前情回顾1.2 文件的属性1.3 文件内部数据的组织方式1.4 操作系统向上提供的文件功能1.5 文件应如何放在外存 2. 文件的逻辑结构2.1 无结构文件2.2 有结构文件2.2.1 顺序文件2.2.2 索引文件2.2.3 索引顺序文件2.2.4 多级索引顺序文件 3. 文件目录3.1 基本概…

1422. 分割字符串的最大得分【字符串】

文章目录 1422. 分割字符串的最大得分解题思路Go代码 1422. 分割字符串的最大得分 1422. 分割字符串的最大得分 给你一个由若干 0 和 1 组成的字符串 s &#xff0c;请你计算并返回将该字符串分割成两个 非空 子字符串&#xff08;即 左 子字符串和 右 子字符串&#xff09;所…

使用3080ti运行blip2的

使用3080ti运行blip2的案例 注意&#xff01;blip2很吃显存&#xff0c;需要大于80GB显存的卡。我最后安装的所有包的版本信息&#xff08;python 3.9 &#xff09;以供参考&#xff1a; 首先&#xff0c;我在运行blip2的demo的时候显存用了80G以上&#xff0c;所以大家卡的显存…

moectf-Web题解

1、弗拉格之地的入口 2、垫刀之路01: MoeCTF&#xff1f;启动&#xff01; 3、ez_http 4、ProveYourLove 5、弗拉格之地的挑战 6、ImageCloud前置 7、垫刀之路02: 普通的文件上传 8、垫刀之路03: 这是一个图床 9、垫刀之路05: 登陆网站 10、垫刀之路06: pop base mini …

Java值传递、序列化详解

Java 值传递详解 说到参数&#xff0c;我们先来搞懂一下这两个概念 形参&实参 值传递&引用传递 形参&实参 方法的定义可能会用到 参数&#xff08;有参的方法&#xff09;&#xff0c;参数在程序语言中分为&#xff1a; 实参&#xff08;实际参数&#xff0c;…

QT实现Opencv图像处理

案例 基于QT的人脸识别 pro文件需要加以下代码 INCLUDEPATH E:/opencv/opencv3.4-qt-intall/install/include INCLUDEPATH E:/opencv/opencv3.4-qt-intall/install/include/opencv INCLUDEPATH E:/opencv/opencv3.4-qt-intall/install/include/opencv2 LIBS E:/opencv/o…

D34【python 接口自动化学习】- python基础之输入输出与文件操作

day34 文件关闭 学习日期&#xff1a;20241011 学习目标&#xff1a;输入输出与文件操作&#xfe63;-46 常见常新&#xff1a;文件的关闭 学习笔记&#xff1a; 文件关闭的内部工作过程 close&#xff08;&#xff09;函数 with语句 常用的打开关闭文件 # 文件关闭 # 方式…

值类型和引用类型的使用

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace ConsoleApp1 {class Program{static void Main(string[] args){/****值类型****/bool test;//必须赋值,否则报错test true;Console.WriteLin…