将string.cpp两个test测试函数放在test.cpp文件中,原来的删除代码,将.h中两个测试函数的申明删除
单个字符扩容,当pos=0时,即出现bug
分析 end不会小于pos(停止条件是end<pos),end--为0,进入下一次循环,将_str[1] = _str[0],end--后不是-1,因为size_t是无符号数,end为0时减一为最大整型,end永远不会小于pos,会造成死循环。
将end的size_t改为int,依旧,为什么?当一个操作符两边的两个操作数类型不一样时,编译器会进行类型提升。让两边的两个操作数类型中范围小的类型提升成另外的范围大的类型。
法1. 将pos的size_t改为int 法2.进行强转,如下图,推荐
法3.end原本指向\0,_size也指向\0,现在重新建一个新的size_t end=_size+1,即\0之后,相当于将连带\0在内的整个数组向后移(推荐用这种方法)
pos后扩容字符串时 从前往后挪
下图中while也可以写成下图while是为了不出现= 进行了修改
错
对
左闭右开。右开数相当于是实际下标的下一个位置,左闭数是下标。右开数-左闭数=元素个数(指从左闭数对应元素开始算起,往后存在的元素个数。若往后的元素个数为0,则始终有一个左闭数对应元素,元素个数最少为1)
左闭右闭。左闭数,右闭数就是指下标。右闭数+1=元素个数
从后往前挪
find
str1是母字串,str2是目标子串,返回子串在母串中第一次出现的位置指针
substr
程序奔溃
sub和suffix因为没有写拷贝构造,外加这里sub是传值返回,编译器会对sub传值返回进行优化(vs2022 dbug下省略拷贝构造一个临时对象,再用临时对象拷贝构造suffix,优化后直接用sub拷贝构造suffix,sub都指向同一块空间,浅拷贝。sub先销毁后suffix指向野指针,所以程序奔溃。但是relese下会直接将sub拷贝构造suffix省略,即不产生sub,直接将sub当作suffix,虽然是浅拷贝,但程序不会崩溃)。所以要写拷贝构造函数,为suffix也开辟空间,将suffix中的_str指向他自己的空间,再进行数据拷贝(深拷贝),让他们两个是不同空间,相同数据
所以string也要显示提供拷贝构造
赋值运算=
这里先直接测试自带的=
这里没有自己写operator=重载,用的是编译器默认提供的赋值函数,默认赋值只做浅拷贝。
编译器默认生成的operator=,只会逐个拷贝类的成员变量: 你的自定义string类成员:char* _str、size_t _size、size_t _capacity默认赋值执行逻辑:
s._str = suffix._str(直接把指针地址赋值,只复制地址值,不新开辟堆内存)s._size = suffix._sizes._capacity = suffix._capacity
执行完s = suffix后:
s._str和suffix._str两个指针指向堆上同一块字符数组内存(调试截图能看到二者_str地址完全一致:0x00fffff50)
- 赋值前 s 本身持有一块堆内存 初始化
s("test.cpp.zip")时,构造函数会new一块堆空间给s._str存字符串test.cpp.zip。 - 默认浅拷贝不会先释放 s 原来的旧堆内存 编译器默认赋值运算符没有
delete[] _str;这一步,直接把s._str改成suffix._str的地址。 → s 原来指向的那片存test.cpp.zip的堆空间,再也没有任何指针能找到、释放它,这块堆内存就永久丢失,产生内存泄漏。
函数结束局部变量析构顺序:先析构s、再析构suffix
- 析构
s:执行delete[] s._str,把suffix和s共用的那块.cpp.zip内存释放; - 析构
suffix:又执行delete[] suffix._str,对已经释放过的内存再次释放,程序直接崩溃(double free)。
这里手动模拟完成=
手动模拟实现运算符
这里只要实现< =就可以实现其他功能
关系运算符>、<、==、>=、<=、!=,逻辑运算符的运算结果在 C++ 中统一都是bool布尔类型,本身就用真 / 假描述结果。
我们只写了一个版本,为什么这里写其他类型参数也能比较?
因为单参数构造函数支持隐式类型转换,
这里我们手动模拟引用的是临时对象,官方提供的全局函数直接用传过来的字符串,不创建临时对象
标准库string的operator==重载仅在至少有一个操作数是string对象时才会被匹配;两个操作数全为const char*时,不会触发该组重载,只会执行原生指针比较逻辑。表达式比较的是两个字符串首元素的内存地址是否相等
流插入和流提取
左操作数ostream& out与右图ostream&同理
这里仅需访问传入对象的公共元素计即可,不需要友元访问私有变量
这里输入的最后两行空格 换行都给了,为什么不结束提取?
当cin scanf 提取、输入任何类型的数据赋值时,默认换行和空格是分隔符,然后将它忽略
这里使用get函数,即一个字符一个字符的读(可以获取到任何字符D)
每次调用只读输入流里接下来的 1 个字符,读完就立刻返回,不会自动继续读下一个
输入1234 56结果正确
表示
为什么有hellow world?因为s1之前就有hellow world。若目标对象只想要输入后的数据,不保留之前的数据,用 自己在类内写的clear函数(清掉所有数据不是放空间)
若要提取的字符串过大,s要频繁扩容,用reserve提前开辟大空间。若要提取的字符串过小,reserve提前开辟大空间就浪费了。这里采用折中办法。提前开辟一块大小适中数组,将取的字符不断放入数组,若数组满了,将数组给s(即S扩容一次),i=0,重复;若提取的字符串比数组小,就不扩;
去掉if(i>0),强制执行:buff[0] = '\0'; s += buff;
1.buff[0]赋值空结束符,字符串长度为 0,s += buff不会新增任何可见字符;
但代码依然要执行数组赋值、字符串追加的整套流程,属于白白运行无效代码,产生多余 CPU 开销,也就是逻辑冗余。
2.栈数组 buff 不会自动清空
若上次输入了abc,存入buff[0]='a'、buff[1]='b'、buff[2]='c',后续正常写入s,最后i归零。
仅把buff[0]改成\0,buff[1]、buff[2]残留的旧数据还在;因为首字符就是\0,正常不会追加脏字符。但极端特殊情况: 若中途缓冲区被其他栈变量改写、出现buff[0]意外没被成功赋值\0,string会顺着buff向后读取残留旧字节,把之前遗留的bc这类脏数据、随机乱码拼接到字符串里,最终结果出现莫名多余字符。
getline
(1)从输入流逐个读取字符,存入str;
读到delim分隔符时立刻结束;
分隔符delim会从输入流中被读走,但不会存入字符串str;
读到流末尾 EOF、流出错,也会终止读取;
返回输入流is,支持链式判断if(getline(cin, s, '#'))。
string s; getline(cin, s, '#');输入:hello world#123
- 读取
hello world存入s; #被读取并丢弃,不会放进字符串;- 缓冲区剩余
123留待后续输入读取。
(2)本质:重载 1 的默认版本,默认分隔符是换行\n等价于:getline(is, str, '\n');
读取当前整行所有字符(包含中间的空格、Tab);碰到回车换行\n结束;换行符被消费丢弃,不会存入字符串;
string s; getline(cin, s);输入:my name is tom回车s完整保存my name is tom,中间空格全部保留,不会被截断
模拟实现getline时只需将while改为下图划线即可
1byt用二进制可以表示256个数字(也可以用其他进制表示),可以表示0-255,-128-127,我们取-128-127中正数(0-127 0xxxxxx 0开头的二进制编码)表示成十进制时为ascll码值,美国将ascll码值与他们的常见符号一一对应。所以打印数据时,会将内存中的进制数转换成ascll码,再根据ascll码打印出对应字符。
万国码(统一码)-UTF8编码
一个字节为单位,单个单位可以表示一个符号(ascll码)。两,三,四个单位也可以表示一个符号。支持中英混用,中文大多数汉字可以用两个单位表示出来
万国码(统一码) utf-16以2个字节为单位 ,
万国码(统一码) utf-32以4个字节为单位 ,
中文编码
window系列要用
出现乱码原因之一,存储的数据用不适用于它的对应关系(编码表)去对应找字符,找到的只能是预想之外的字符。也可能存储的数据错误。