Bootstrap

【C++初阶】string①:管理char的字符数组是一个模版(快速上手使用)

目录

​编辑

1.string是什么

2.string的构造

①空字符串构造函数

②字符串构造函数 

缺省参数npos:静态const成员变量:

③拷贝字符数组的前n个:

④用n个字符进行初始化

​编辑3.string的析构

4.string的赋值重载

5.string的遍历和访问:

5.1遍历方式①方括号

5.2:begin和end

5.3 遍历方式②:迭代器:iteraotor

5.4 反向迭代器

6.string的长度计算接口

7. string 逆置接口

7.1 算法库中的反转模板

8.string 字符交换位置

9.string可以开辟的最大长度:

10.string在不同平台下的扩容与缩容

10.1扩容接口 reserve

10.2 返回容量接口 capacity

10.3string的扩容机制:

10.3.1 resize接口 

10.3.1.1如果n>capcity

10.3.1.2 size < a=""> <>

10.3.1.3 n < a=""> <>

11.数据访问操作符重载 at

12.string的增删查改:

12.1增加

12.1.1  push_back    append接口

12.1.2 string重载+=运算符

12.1.3 insert 插入

12.2  删除:

12.3 修改

12.3.1 assain赋值:

12.3.2  replace替换:

   12.3.2.1 find可以和replace进行搭配来实现替换:

12.4 查找

12.4.1 find

12.4.1.1 接口1 

12.4.1.2 接口3

12.4.2 取某个子串,substr:在str中从pos位置开始,截取n个字符,然后将其返回

12.4.3 refind

12.4.4 find_first_of

12.4.5 find_last_of 

12.5 c_str

12.6  copy

13.string的非成员函数

13.1 operate+

13.1 getling 

14.字符转整形

15.string底层空间:VS与G++下的不同

16.题目收尾


 

1.string是什么

在我们学习c语言的时候,我们学习过操作字符串的函数,也就是str系列的库函数。但是这些函数,可以叫做数据的操作方法和数据本身是分开的,不符合封装的思想,而且在很大程度上,字符串的管理在C语言中更多的是以数组的形式来呈现,这就设计数据的底层空间需要用户自己管理,并且很容易会产生越界访问的情况。在C++中,将字符串作为一个类,作为表示字符序列的类。该类的接口与常规容器的接口基本相同,添加又专门操作字符串的函数,比如查询,寻找字符等等

string,简单来说就是字符顺序表,相当于字符数组,使用string的前提,包含#include<string>以及using namespace std;

2.string的构造

①空字符串构造函数

功能:构造空的string类对象,即是空字符串。

②字符串构造函数 

string (const string& str, size_t pos, size_t len = npos);

功能:复制从字符位置 pos 开始并跨越 len 字符的 str 部分(或者直到 str 的末尾,如果 str 太短或 len 为 string::npos)。

substring (3)	
string (const string& str, size_t pos, size_t len = npos);

拷贝pos位置开始的len个长度

缺省参数npos:静态const成员变量:

整型的最大值,也就是最后参数我们不给,拷贝到结尾那么长。

③拷贝字符数组的前n个:

string (const char* s, size_t n);

④用n个字符进行初始化

string (size_t n, char c);

3.string的析构

调用结束自动调用

4.string的赋值重载

 

5.string的遍历和访问:

 string本质是一个字符数组,只是类进行了封装

5.1遍历方式①方括号

库里提供了两个[]操作符的重载,如果只有const的,const和非oconst都可以调用,但是非const调用const属于权限的缩小

const修饰返回值表示返回值不可以被修改,而普通对象不可修改就不合适,所有就提供了两个,调用时编译器会去匹配最符合的。

 这样就如上图很符合,S1可读可写,但是S2只可以读,不可以写。

5.2:begin和end

有const版本:

注意:

这里的返回值类型整体是:const_iterator

const_iterator it 本质保护的是迭代器指向的数据*it不可被修改

const iterator it保护的是迭代器本身不能修改,也就是it不能修改提供两个,方便迭代器使用

用法:与迭代器配合

string::iterator it = s1.begin();

 在我们的string类中存储一个字符串,begin返回的是字符串的起始地址,给it由于底层存储逻辑是连续的,所以就可以进行遍历了。小标+[]这样的访问方式 ,需要底层有一定连续性,链式结构,树型、哈希结构只能用迭代器。迭代器才是容器访问的主流形式。

5.3 遍历方式②:迭代器:iteraotor

迭代器是一个类型 

迭代器的意义:定义在类域中,类中可能tupedef过,所以使用要包含类域。然后理解为一个类型,迭代器类型,像指针

5.4 反向迭代器

 为了简化写法,可以配合auto来推演迭代器的类型:

缺陷:读代码如果类型中有connst会不知道。 cbegin返回const的迭代器

传参的时候最容易出现const对象(不用引用的情况下):

编译器的原则就是会去寻找自己最为匹配的进行调用,外部传入的s1是普通对象,返回的是reverse_iterator,但是形式参数对象是const对象,所以返回的是const reverse_iterator,类型不匹配。

 普通迭代器和const迭代器的区别就是所指向的内容是否可以修改。迭代器本身是可以修改的,++,--都是可以的。

当然,如果觉得判断类型麻烦,可以使用auto进行类型自匹配优化:

 这里auto并不需要指定类领域,他是由rebegin的返回值自己去推演的类型,而这个类型是定义再string类中的。

6.string的长度计算接口

字符串长度的计算使用length和size是一样的:

主要是为了区分链表这些和树的计量,两个函数功能一样

 size和lenth的计算结果不包含\0.

7. string 逆置接口

7.1 算法库中的反转模板

 算法中也有一个逆置,但是这是一个模板

这个算法同样适应vector list。迭代器可以配合算法。这里实现的算法也是泛型编程,是一个函数模版针对各个容器的迭代器。

8.string 字符交换位置

这是string中的交换,可以将当前string的内容与另外一个对象进行交换。

算法库中的交换:

9.string可以开辟的最大长度:

 和有没有值没有关系。返回的是整形的一半 (不同编译器不一样)这个值没有参考意义,可能也没有办法开那么大。

10.string在不同平台下的扩容与缩容

10.1扩容接口 reserve

做扩容

10.2 返回容量接口 capacity

返回容量:

 

空间开了16,15表示能存储多少有效字符,末尾标识\0不是有用字符

10.3string的扩容机制:

现象:越扩越慢,刚开始是2倍,实际上是1.5倍扩容。开始从15开始

linux下是两倍扩容,最开始从0开始

 reserve确定需要多少空间就可以使用提前开好。扩容的代价很大。由于对齐规则的不同,vs下需要500可能开辟500+,linux下开辟就是500。

rserve会不会缩容呢?

有数据和没数据都不会缩容

linux下会缩容:但是缩容不会修改数据也就是说最多缩到能装下数据的大小。

10.3.1 resize接口 

resize最大的用处是开空间+初始化

如果传入第二个参数,会用传入字符填充剩余空间,如果不传,那么就使用空字符来进行填满,空字符也就是\0.

resize既影响空间也影响数据

10.3.1.1如果n>capcity

这些\0不是标识符,有效字符是由size来限制的,也就是size范围内是。

10.3.1.2 size<n<capcity

windows下

size变化,capcity不变

linux下容量不变,size变化

10.3.1.3 n<size

 容量不变,size变小,数据删除,保留前n个数据

linux也不缩容

11.数据访问操作符重载 at

at和operate []在越界的时候有区别:

operate []会直接崩溃

at只是抛出异常

12.string的增删查改:

改的话,[],at,迭代器都算,查到的话可以使用[].

12.1增加

12.1.1  push_back    append接口

12.1.2 string重载+=运算符

+=运算符重载了三个接口:

+=一个字符,+=一个字符串。+=一个string

12.1.3 insert 插入

从pos开始位置插入一个字符串 

 不允许这样写:

 但是可以这样写:

也可以使用迭代器:

12.2  删除:

 要是第二个参数不传,默认给的是npos,整型的最大值。也就是删除到结束

12.3 修改

12.3.1 assain赋值:

12.3.2  replace替换:

从pos位置开始的len个字符开始替换成str

   12.3.2.1 find可以和replace进行搭配来实现替换:

find  搜索默认从头开始,找到返回下标,找不到返回整型的最大值。

使用find和replace完成字符串的替换:

replac效率很低,使用范围for,时间复杂度为O(1)的方法:

12.4 查找

12.4.1 find

12.4.1.1 接口1 

size_t find (const string& str, size_t pos = 0) const;

从pos位置开始寻找一个字符串,返回匹配项的第一个字符的位置

12.4.1.2 接口3

size_t find (const char* s, size_t pos, size_t n) const;

从pos位置开始查找string的前n个

12.4.2 取某个子串,substr:在str中从pos位置开始,截取n个字符,然后将其返回

与find集合使用:

12.4.3 refind

默认从pos位置向前去寻找

12.4.4 find_first_of

可以找字符,找字串,可以叫find  any of ,也就是说找字符串中的任意一个。

将字符串中是目标字符串的元素的统统进行替换。

12.4.5 find_last_of 

从后往前寻找,是匹配的就进行替换

 find_first_not_of就是处理这些字符其他的都返回。

12.5 c_str

为了支持C语言和 c++语言共同编码:

修改如下: 

string filename("test.cpp");
FILE* fout = fopen(filename.c_str(), "r");
char ch = fgetc(fout);
while (ch != EOF)
{
	cout << ch;
	ch = fgetc(fout);
}

原因:

c形式的字符串特点末尾是斜杆0

fopen要求传递参数类型是const char *

string和这个类型就不匹配,c_str返回c形式的字符指针

12.6  copy

把当前string的一部分值拷贝给一个地址,返回值是拷贝成功的个数

13.string的非成员函数

13.1 operate+

这里重载不是冗余,而是因为一个操作符拥有多个操作数的时候,默认第一个参数是左操作数,第二个参数是右操作数,“xx"+s1这样的操作理论上不支持,如果只支持第一个,这个和六插入一样,所以写了另外一个版本。

13.1 getling 

 获取输入,遇到空格不结束,遇到换行才结束

引入一道牛客题目:

思路:使用rfind从后往前找空格,进行长度计算输入,如果找不到空格,说明只有一个单词,输出长度就可以。

答案不对,这就是因为,在我们输入多个值的时候,默认是用换行和空格作为分隔符的。比如:

默认将word以分隔符分割给了s2. 所有有空格就得使用getling1,遇到空格不结束,遇到换行才结束。

	string s1;
	cout << sizeof(s1) << endl;
	string s2("1234");
	cout << sizeof(s2) << endl;
}

以上代码执行结果分别为多少。sizeof计算1是整个类的大小,也就是计算两个成员变量和一个指针,和这个string中有没有数据是没有关系的。(64位平台测算)

14.字符转整形

库中又一个atoi 和itoa,可以将字符转为整形

string有一个 to_string,可以将整形数据转化为字符串,int long log double都可以

stoi  互相转换:将字符串转化为整数

15.string底层空间:VS与G++下的不同

我们写一段代码来计算一下我们string类对象的大小:

 

输出结果一样,算的是一个对象的大小,就是计算的成员变量,一个指针  两个size_t类型为什么是28,不是12这个正常大小,这是VS的优化 

当string的数据小于16的时候就会将数据存储到buff这段空间,但是最多存15个,还有一个\0,如果大于15就存储在ptr对象中,减少去堆上开小空间。如果大于15个直接存ptr

设计大概为这样:

private:

char _buf[16];

size_t _capacity = 0;

size_t _size = 0;

char* _str = nullptr;

const static size_t npos = 1;

但是并不是所有的设计都是这样,string的大小所以是不确定的,在linux下string可能是8z字节

private:

char* _str = nullptr;

const static size_t npos = 1;

linux默认64位,只有一个指针是八字节

实际G++下的string是这样的:

内部只包含了一个指针,该指 针将来指向一块堆空间,内部包含了如下字段: 空间总大小 字符串有效长度 引用计数 指向堆空间的指针,用来存储字符串。也就是和数据存储在一起去了。

struct _Rep_base
{
 size_type _M_length;
 size_type _M_capacity;
 _Atomic_word _M_refcount;
};

存在引用计数的原因:

在我们使用传值拷贝的时候,我们知道会生成临时对象也就是会发生拷贝构造和赋值重载,虽然编译器会将连续的构造和拷贝构造进行优化但是还是会有空间损耗,划算不来,而且

浅拷贝的问题:1.会有析构两次的问题2.一个修改数据可能会修改另外一个对象的数据。所以在linux中有了引入计数,有几个对象指向这块空间记录,当引用计数为0就可以释放这篇空间了。 析构多次的问题解决,但是数据修改还需要解决。写时拷贝,谁要写数据谁就去调用拷贝。这样的处理很像父子进程

16.题目收尾

反转字母

. - 力扣(LeetCode)

找字符串中的第一个唯一字符:

https://leetcode.cn/problems/first-unique-character-in-a-string/

思路:使用一个字符数组来开辟26g因为只有小写字母,第一遍遍历按,将每个字母如果有数组位置就存储1,然后再一次遍历,数组为1的地方就是答案。

;