【C++修炼之路】string 概述

news2024/11/17 22:42:30

👑作者主页:@安 度 因
🏠学习社区:StackFrame
📖专栏链接:C++修炼之路

文章目录

  • 一、string 为何使用模板
  • 二、string 类认识
    • 1、构造/析构/赋值运算符重载
    • 2、容量操作
    • 3、增删查改
    • 4、遍历
    • 5、迭代器
    • 6、非成员函数
    • 7、库函数※
  • 三、测试扩容
  • 四、写时拷贝
  • 五、win 下 string 的内存分布

如果无聊的话,就来逛逛 我的博客栈 吧! 🌹

一、string 为何使用模板

image-20230208161508298

string 是 typedef 后的模板,也就相当于是这样:

template<class T>

class basic_string
{
private:
	T* _str;
	// ... 
};

// typedef basic_string<char> string;

int main()
{
	string s("hello");

	return 0;
}

那 string 不就是字符串,管理字符不就是 char ,为什么使用模板?因为编码问题。

计算机是 usa 发明的,一开始对于计算机只需要显示英文就可以,编码简单,因为表示简单;通过计算机编码,将数据翻译为二进制序列,将其组合,一个字节表示一个 char ,将英文和符号进行一个映射,通过建立映射关系(编码表)完成编码。例如 ascii编码表 – 表示英文。

字符根据 ascii 码表中的 ascii 值,以二进制存储在计算机内的就是对应的数值,根据这些,就可以表示出英文。

例如 “helllo” 存储在内存中就是每个字符的 ascii 表对应的数组:

image-20230208162415414

早期计算机只有欧美在用,只有英文的编码方式,但是后来对于世界别国也需要用了,需要让电脑编码能适用于全球,所以后来就诞生了: u n i c o d e unicode unicode ,为了表示全世界文字的编码表(unicode 包含 ascii),它支持各种问题的编码,比如 unicode 就兼容 utf-8, utf-16, utf-32 .

所有的编码通过值与符号建立映射关系,对与英文比较简单,但是对于类似于中文的编码就比较困难。原先一字节存储一个英文字符,但是对于中文可能存不下;所以对于中文字符,可以存储为 2 个字节,这样子 256 * 256 就可以表示 65536 个汉字。但是如果想要扩展更多的话,对于空间的消耗就大了,所以 uft-8 就把常见的汉字用两个字节编,生僻的用若干个字节进行编(Linux 下默认编码就是 uft-8)。

比如:

image-20230208163147012

两个字节存储一个中文字符,根据编码表查阅字符。

编译器中也可以更改编码方式:

image-20230208164232145

如果编码对应不上就是所谓的乱码。

中文自己量身定做的编码表 gbk ,windows 下默认 gbk ,linux 下 utf-8,例如 GB2312 就是 gbk .

根据这种方式,也可以让一些不良用语,根据词库,隐藏为 **** .但是仍然可以使用同音字,比如如下现象:

image-20230208165325701

有时候,这种也可以吟唱国粹。由于这些同音字都是挨着的,所以也可以选中国粹中重音的字根据范围屏蔽。

多种编码:

image-20230208170858515

有些字符串会用两个字符表示一个值,例如 wchar_t 宽字节:

image-20230208170551006

就是 wstring . 另外两个也都是为了更好的表示编码。

而 string 就是 basic_string 的一个实例,用来存储 char 字符,我们日常使用 string 即可,特殊情况要灵活使用。

二、string 类认识

认识:

  1. 字符串是表示字符序列的类

  2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。

  3. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信息,请参阅basic_string)。

  4. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。

  5. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。

总结:

  1. string是表示字符串的字符串类

  2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。

  3. string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator>string;

  4. 不能操作多字节或者变长字符的序列。

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

1、构造/析构/赋值运算符重载

七个构造函数:

image-20230208171409660

1(s1),2(s2),4(s3)常用

image-20230208172159305

由于 string 重载了 >> 与 << 所以可以直接进行输入输出。

(3):从 pos 位置开始,截取 len 长度,初始化 string ,len 有缺省值 nops 为 -1 ,为静态成员变量。len 类型为 size_t ,转换为一个很大的数字,即截取整个字符串。同理,当 len 长度超过 string 本身长度时,都会截取从 pos 位置开始的整个字符串。

image-20230208172910423

(5):取字符串的前 n 个字符初始化

image-20230208173412755

(6):用 c 来填充 n 个字符

image-20230208173510428

析构函数

不用管,自动释放:

image-20230208173649362

赋值运算符重载

image-20230208173729547

2、容量操作

image-20230208181008877

1)size/length :计算 string 长度

length 先出现,后来出现的 size ,因为 size 比较适用,因为 length 不符合其他 ds 的大小说明,推荐使用 size .

int main()
{
	string s1;
	cin >> s1;

	cout << s1.size() << endl;
	cout << s1.length() << endl;

	return 0;
}

2)max_size:string 的最大长度,没有被界定,根据多种情况衡量

cout << s.max_size() << endl; // 2147483647

我的电脑是这么多。

3)capacity:算此刻容量

int main()
{
	string s1;
	cout << s1.capacity() << endl; // 15

	return 0;
}

实际上是 16 ,但是有一个给了 ‘\0’ ,为实际能存储的字符个数。

4)clear:清理数据,不清理空间;清理数据后,可以用 capacity ,检查空间是否被清理

image-20230209085842892

5)empty:判断是否为空,空返回1,非空返回0

关于容量的增长

除第一次二倍增长,其他均约呈 1.5 倍正常:

image-20230209104308835

起始空间是放到一个对象的数组(16大小,包含 \0 ,15是有效字符)中,数组满了,对象中就不存在这个数组,在堆上开了一个32容量的空间,之后呈 1,5 倍增长(PJ版本);而 linux 上的是呈二倍增长的(SGI版本)。

image-20230209105406535

一句话,版本不同,时代更新。虽然功能一样,但是每个版本的底层可能都不一样,因为不同版本的源码都在更新。

6)reserve :不是 reverse 逆置。reserve 是请求容量的改变,传递参数,来改变容量

频繁扩容有消耗,所以一次性把空间开大,就可以减少增容时的消耗:

image-20230209110658974

但是申请的元素会根据上面说的对齐方式,比如这边申请 1000 个,他会申请1008个,一个给 \0 ,可用 1007 个。

7)reszie:开辟空间并改变数据个数。可以给值对空间进行初始化,不给默认为 \0

image-20230209111348988

image-20230209111449611

reserve/resize 不会对已有的数据进行修改,是扩容/扩容+初始化,不是覆盖:

image-20230209112820055

如果 resize 给的空间比初始容量小,则会保留初始数据,后面的被删除:

image-20230209113156599

对于 reserve 给的大小比已有容量小时,则不会改变容量大小

image-20230209113848362

但是如果空间中的数据被清空,则可以减容,由此可见不可约束力:

杭哥说是:数据没有清空,所以仍然可能增容减容,所以并不会缩减容量,但是数据清空就没问题了,就认为不需要空间,就可以减容:

image-20230702121244975

image-20230702121858057

6、7总结:

  • reserve :开空间,影响容量
  • resize:开空间,改变数据个数 size ,对这些空间给一个初始值并初始化,不给值默认给 \0

3、增删查改

image-20230208181025567

改:

operator[] :可以像数组一样访问

image-20230208181455211

s1[i] <==> s1.operator[](i)

可以 s1[i] 修改 string 的内容,operator[] 类似:

char& operator[] (size_t pos)
{
    return _str[pos];
}

这里的引用返回不是为了减少拷贝(char 空间小),而是为了支持修改返回对象。

image-20230209093008481

at 和 operator[] 一样,以函数形式使用:

s1.at(i) -= 1; // 例如

它们检查越界的方式不一样,operator[] :使用断言;at:抛异常:

operator[] :s1[100]

image-20230208182941095

at:s1.at(100)

image-20230208183015208

增:

image-20230208183247171

push_back:尾插一个字符;append:尾插一个字符串

image-20230208183453764

operator+= 也可以起到插入的效果,字符和字符串都可以,推荐使用:

image-20230208183540993

insert:插入 string ,可以再任意位置插入,但是效率不高,一般是 O ( N ) O(N) O(N)

image-20230209214225464

(5):

image-20230209214548708

查:

c_str:c_str 返回的是 string 的首元素地址

image-20230209114646043

image-20230209114831766

与 c 库中函数或文件操作时配合使用 c_str

int main()
{
    string file("test.txt");
    FILE* out = fopen(file.c_str(), "w"); // 第一个参数为 char* 
}

find:在 string 中查找内容(看文档)

image-20230209115306867

image-20230209204506633

(2):找c字符串,返回第一个找到的位置下标;找不到返回 npos ,是无符号的 -1 ,是一个极大的值;当数字很大时,就认定这个位置不存在,因为 stirng 过大,也不实际了

substr:从 pos 位置开始,取 len 个字符,len 缺省值为 npos ,如果不给 len ,默认截取从 pos 位置开始的所有字符串

image-20230209204529731

find 和 substr 组合使用:

image-20230209205950965

pos 找到 . 开始的位置,从 . 位置开始截取后面的所有元素。pos 返回的是下标,总长 - 下标 = . 之后的长度;;如果想要直接截到结尾,可以 substr(pos) 一步到位。

如果连续后缀,要取最后一个?可以使用 rfind ,反向取:

image-20230209210757600

第三个 find 使用

image-20230209213934533

erase,三种重载:

image-20230209214707363

第一个比较常用:从 pos 位置开始删除 len 个字符,如果不给 len ,则默认从该位置删完

image-20230209215039641

pop_back 是尾删,就不演示了,很简单。

4、遍历

三种遍历和修改的方法:

1)operator[]

void test_str1()
{
	string s1("hello");
	for (size_t i = 0; i < s1.size(); i++)
	{
		s1[i] += 1;
	}

	for (size_t i = 0; i < s1.size(); i++)
	{
		cout << s1[i] << ' ';
	}
}

2)迭代器

void test_str1()
{
	string s1("hello");
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
        *it += 1; // 修改
		cout << *it << ' ';
        ++it;
	}
	cout << endl;
}

image-20230209093505724

当前理解为 it 就是 h 位置的指针,s1.begin() 返回第一个位置 h 的地址;s1.end() 返回最后一个位置下一个位置的地址 \0 . 迭代器是内嵌类型,是在 类中定义的,类型名称就是 iterator ,全局没有这个迭代器,所以要指定类域。

对于 string 来说,while(it < s1.end()) 也可以进行遍历,因为 string 是连续的;但是推荐用 != ,因为其他容器的迭代器可能不支持。

这里我们先用,把迭代器想象成:像指针一样的类型 ,之后模拟实现时,慢慢理解清楚。

3)范围 for(C++11)

void test_str1()
{
	string s1("hello");
	for (auto& e : s1) // 加引用修改
	{
		e += 1;
		cout << e << ' ';
	}
}

自动取出元素,作为别名,往后迭代,自动判断结束。(底层类似被替换为迭代器遍历)

5、迭代器

image-20230209095850447

正向迭代器 begin 和 end 我们已经说过使用方式,下面讲解别的。

rbegin/rend 是反向迭代器

void test_str2()
{
	string s1("hello world");
	string::reverse_iterator rit = s1.rbegin();
	while (rit != s1.rend())
	{
		*rit += 1;
		cout << *rit << ' ';
		++rit;
	}
}

image-20230209100131570

类似于:

image-20230209100303563

++rit 是往左边走的,与正向迭代器相反。

const 的正向迭代器

image-20230209102342866

给 const 对象用的,const 对象调用 const 的迭代器,可读但不可写

image-20230209102804855

对于普通的 string 对象,也可以使用 const 迭代器,权限缩小是可以的:

image-20230209102910279

同理,对于 const 的反向迭代器也是一样的。

C++11 中有 cbegin,cend等专门区分 const 的迭代器:

因为当用 auto 进行类型推导时,比如这样:

void foo(const string& s)
{
    auto it = s.begin();
}

这里 it 是 auto 推导的,是不是 const 迭代器需要根据参数才能看出,所以后来规定 const 迭代器可以使用 cbegin 等,但是一般不常用。

迭代器遍历的意义是什么?对于 string ,无论是正着遍历还是倒着遍历,下标 + [] 都足够好用,为什么还要迭代器?

迭代器是适用于所有容器的,都可以通过迭代器访问。

对于 string ,下标和 [] 就足够好用,确实可以不用迭代器,但是如果是其他容器(数据结构),其他容器就不支持,比如 list ,map 等,它们都是不支持下标遍历的。

迭代器是用统一的方式支持遍历的。

结论:对于 string ,得会用迭代器,一般用下标。

6、非成员函数

image-20230209215230075

getline :

cin 在遇到空白字符时,会停止读取,其他数据留在缓冲器;getline 可以读取带空格的一行 string :

image-20230210101858079

第一个参数是 istream ,即 cin .

原理类似于:

int main()
{
	string s1;
	char ch = cin.get();
	while (ch != '\n')
	{
		s1 += ch;
		ch = cin.get(); // cin 的成员函数,一个字符一个字符拿
	}
	cout << s1 << endl;

	return 0;
}

一个字符一个字符读取。

image-20230210102106641

image-20230211092903484

string 的比较;可以支持 string 和 string ,string 和 char,因为重载了:

image-20230211093215427

根据 ascii 码值比较,大的则大一旦比较时不相等,直接返回结果;类似于 strcmp

7、库函数※

image-20230211093750072

一些库函数,比如 atoi 和 itoa ,在 C++ 中并不好用;并且它们并不是标准库下的函数,可能换个 ide 就不好用了。这时这些库函数就发挥了作用。

stoi :string 转 int(默认十进制),平时我们一般用整形,所以其他的可以先不用管。

image-20230211094612908

image-20230211094419767

同理,剩下几个函数,是对不同类型数据的转换。

image-20230211094906123

将数据转换为字符串(C++11):

image-20230211094931829

image-20230211095145766

(double 默认转六位)

三、测试扩容

void TestPushBackReserve()
{
	string s;
	s.reserve(100);
	size_t sz = s.capacity();
	cout << "capacity changed: " << sz << '\n';


	cout << "making s grow:\n";
	for (int i = 0; i < 100; ++i)
	{
		s.push_back('c');
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

vs:大约 1.5 倍

初始容量为 15,其实有 16 个,一个是 \0

image-20230709082227345

linux :2 倍

image-20230709082605785

四、写时拷贝

image-20230709082707299

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

引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。

当修改对象时,发现引用计数不为 1,则对对象进行深拷贝,并将两个引用计数都置为 1 。

Linux 下为写时拷贝:

image-20230709083313100

image-20230709083338939

一开始为浅拷贝,修改对象时,进行深拷贝。

五、win 下 string 的内存分布

win 下当 string 中有效字符 <= 15 时,sizeof(str) 大小为 28

image-20230709084858341

image-20230709085009154

win 财大气粗,在初识状态有一个 16 字节的 _Buf(能存 15 个,有一个给 \0) ,根据对齐,大小为 15 + 1 + 4 + 4 + 4 ,为 28

当 size < 16 时,字符存在 _buf 数组中;size >= 16 存在 _Ptr 指向的堆空间中:

image-20230709085303604

优点:string 小时,效率高

缺点:string 大时,存在空间浪费

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

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

相关文章

[NSSRound#13 Basic]flask?jwt?解题思路过程

过程 打开题目链接&#xff0c;是一个登录框&#xff0c;不加验证码&#xff0c;且在注册用户名admin时提示该用户名已被注册&#xff0c;因此爆破也是一种思路。不过根据题目名字中的提示&#xff0c;jwt&#xff0c;且拥有注册入口&#xff0c;注册一个用户先。 注册完用户…

Flink DataStream之使用filter实现分流

新建类 package test01;import org.apache.flink.api.common.JobExecutionResult; import org.apache.flink.configuration.Configuration; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.datastream.SingleOut…

Pygame Zero(pgzrun)游戏库介绍

Pygame Zero&#xff08;pgzrun&#xff09;游戏库介绍 pgzero是python的一个第三方库。pgzrun 是 python game zero run 的缩写, 它对 Pygame 进行了封装, 屏蔽了繁琐枯燥的框架代码, 让学习者可以更专注于游戏的实现逻辑, 并且更快看到成果。 官网https://pygame-zero.read…

单样本微调给ChatGLM2注入知识~

前方干货预警&#xff1a;这可能也是一篇会改变你对LLM微调范式&#xff0c;以及对LLM原理理解的文章。 同时这也是一篇非常有趣好玩&#xff0c;具有强大实操性的ChatGLM2微调喂饭级教程。 我们演示了使用AdaLoRA算法&#xff0c;使用1条样本对ChatGLM2-6b实施微调。几分钟就成…

【Redis】五大数据类型(操作命令)

&#x1f3af;Redis 命令 &#x1f6a9;Redis 键(key) 这些是 Redis 数据库中的命令&#xff0c;用于对数据类型进行操作和管理。以下是每个命令的含义和用法&#xff1a; DEL&#xff1a;删除一个或多个键。DUMP&#xff1a;将一个键的值转储到一个字符串中。EXPIRE&#x…

【数据结构二叉树OJ系列】4、翻转二叉树(又称求二叉树的镜像)

目录 法一、 法二、 题述&#xff1a; 翻转一颗二叉树。 输入&#xff1a; 输出&#xff1a; 题中已给&#xff1a; struct TreeNode {int val;struct TreeNode* left;struct TreeNode* right; }; TreeNode* invertTree(struct TreeNode* root) 法一、 思路&#xff1a;…

操作指南 | 如何使用Foundry在Moonbeam上进行部署

Foundry是一种以太坊开发环境&#xff0c;可帮助构建者管理依赖项、编译项目、测试或部署合约以及通过指令与区块链进行交互。Foundry已成为流行的开发智能合约开发环境&#xff0c;仅需要使用Solidity即可进行操作。Moonbeam在官方文档网站提供了有关将Foundry与Moonbeam网络结…

vector [] 赋值出现的报错问题

下面这段代码的作用是创建了一个整数类型的vector&#xff08;std::vector<int>&#xff09;并对其进行操作。以下是代码的详细说明&#xff1a; 使用reserve(10)方法为向量分配至少10个元素的存储空间。reserve() 预留了额外的存储空间&#xff0c;以避免后续添加元素时…

C++之typeof和typeid用法(一百五十三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

动态规划--Fibonacci数列 III

描述 众所周知&#xff0c;Fibonacci数列是一个著名数列。它的定义是&#xff1a; 本题要求采用第三种方法&#xff1a;简单的动态规划。 用数组把求出来的 Fibonacci 数列保存下来&#xff0c;以免后面要的时候再算一次。 输入描述 每行一个整数 i &#xff0c;表示 Fibona…

【C++修炼之路】string 模拟实现

&#x1f451;作者主页&#xff1a;安 度 因 &#x1f3e0;学习社区&#xff1a;StackFrame &#x1f4d6;专栏链接&#xff1a;C修炼之路 文章目录 一、默认成员函数1、全缺省构造2、析构3、拷贝构造&#xff08;深拷贝&#xff09;4、赋值重载&#xff08;深拷贝&#xff09;…

Langchain 新手完全指南

Langchain 可能是目前在 AI 领域中最热门的事物之一&#xff0c;仅次于向量数据库。 它是一个框架&#xff0c;用于在大型语言模型上开发应用程序&#xff0c;例如 GPT、LLama、Hugging Face 模型等。 它最初是一个 Python 包&#xff0c;但现在也有一个 TypeScript 版本&…

Git gui教程---第五篇 Git gui的使用 查看提交历史

查看提交历史 1.点击菜单栏的“版本库”&#xff0c;选择“图示master分支的历史” 2.出现的界面就是显示当前分支的提交历史了

Java基础---Java中创建对象方式

目录 使用new关键字 使用反射机制 使用clone方法 使用反序列化 使用方法句柄 使用Unsafe分配内存 使用new关键字 这是最常见的也是最简单的创建对象的方式通过这种方式还可以调用任意的构造函数&#xff08;无参的和有参的&#xff09; 使用反射机制 运用反射手段&#…

单个电源模块带电感的直流压降仿真(一)

单个电源模块带电感的直流压降仿真(一) 下面实例分析单个电源模块带电感的直流压降仿真分析,以下图为例 具体操作如下 创建新的workspaceLoad a New/Different layout(把PCB文件加载进来)

【滑动窗口】209. 长度最小的子数组

209. 长度最小的子数组 解题思路 滑动窗口设置前后指针滑动窗口内的元素之和总是大于或者等于s滑动窗口的起始位置: 如果窗口的值大于等于s 窗口向前移动窗口结束位置:for循环的j class Solution {public int minSubArrayLen(int target, int[] nums) {int left 0;// 滑动窗口…

UDS统一诊断服务【七】DTC控制0X85服务

文章目录 前言一、DTC控制服务介绍二、数据格式2.1 请求报文2.2 子功能2.3响应格式 三、举例总结 前言 大家好&#xff0c;我是嵌入式老林&#xff0c;从事嵌入式软件开发多年&#xff0c;今天分享的内容是UDS诊断故障码控制0X85服务介绍&#xff0c;希望能对你有所帮助 一、D…

[LeetCode周赛复盘] 第 353 场周赛20230709

[LeetCode周赛复盘] 第 353 场周赛20230709 一、本周周赛总结6451. 找出最大的可达成数字1. 题目描述2. 思路分析3. 代码实现 6899. 达到末尾下标所需的最大跳跃次数1. 题目描述2. 思路分析3. 代码实现 6912. 构造最长非递减子数组1. 题目描述2. 思路分析3. 代码实现 6919. 使…

人工智能与Chat GPT

一本书全面掌握ChatGPT&#xff0c;既有向ChatGPT提问的技巧&#xff0c; 也有构建自己的ChatGPT模型的方法&#xff0c;涵盖开发背景、关联技术、使用方法、应用形式、实用案例等 人工智能是我们这个时代最热门的话题&#xff0c;人们既希望它能代替我们做一些工作&#xff0c…

Python使用SQLAlchemy

Python使用SQLAlchemy 1 安装SQLAlchemy 备注&#xff1a;本文适用于SQLAlchemy>2.0 # 安装SQLAlchemy pip install SQLAlchemy# 安装pymysql pip install pymysql参考文档&#xff08;SQLAlchemy>2.0&#xff09; https://docs.sqlalchemy.org/en/20/创建数据库 # …