C++—string类

news2024/11/16 18:05:42

本期我们来学习C++的string,本期内容相当的多,且有一定难度,需要大家静下心来看

目录

1.标准库中的string

1.1string类的介绍

1.2 string类的常用接口

构造函数、析构函数、赋值、拷贝构造

npos

push_back

append

operator[ ]

size

迭代器

reverse

sort

反向迭代器

const迭代器

max_size

capacity

reserve

resize

shrink_to_fit

at

insert

erase

replace

c_str

find

substr

rfind

find_first_of和 find_last_of

运算符重载

getline

to_string

模拟实现string

基本框架

增删查改

引用计数和写时拷贝

全部代码


我们日常生活中有很多信息只能用字符串表示,比如名字,住址,个人信息等等,我们之前在C语言学过str的各种函数,比如strlen,strcpy等等,但是这些函数不能满足我们的需求,所以C++以类的角度写了string,不仅可以完成C的功能,还能完成增删查改,以及各种算法,下面我们来对string进行学习

1.标准库中的string

1.1string类的介绍

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

 

1.2 string类的常用接口

 string的成员函数非常多,有一百多个

 我们来随便看一看,首先是构造函数

构造函数、析构函数、赋值、拷贝构造

 构造函数实现了7个

上面是析构函数 

还有赋值

这么多的函数我们只需掌握常用的,大概二十多个,其余的我们在需要是查一下即可

我们先来学习构造函数

这里就是无参构造和有参的构造

 

还可以以一个数字,一个字符,来完成多个字符的初始化,这样我们可以省点力气

 

还有拷贝构造(s4)

我们再简单了解一下这三个

 3的功能是从pos位置开始拷贝len长度

 比如这里我们就是从下标为6的位置开始拷贝5个字符

我们在C语言时比较字符串要用strcmp,不过现在就不需要了

string类实现了重载,这里比较s1和s2我们要加上括号,原因是运算符的优先级问题

我们继续看3,3里面有个缺省参数,len = npos

npos

npos是一个静态成员变量,值为-1,因为是size_t类型,所以是整形的最大值

这意味着我们如果不给第三个参数它会拷贝到结尾的位置

 如果len太长,超过字符串长度,或者len是npos,直接取到结尾

我们可以靠这个操作分割字符串,这里是人工数的,后续我们会加上别的操作,就不用我们去数了

因为npos是一个非常大的数,所以我们在取结尾时非常方便,如果没有npos,我们写起来就很麻烦了

.size()可以计算长度,再减去28就是剩余多少

 

我们再看5的功能 ,是拷贝s字符的前n个进行初始化,第7个涉及迭代器,我们暂时不介绍

我们再看看赋值

可以支持多种写法,我们看到第三种方法使用的是单引号,这个还被吐槽过,没有必要 

push_back

 push_back可以在string后添加一个字符

比如我们在hello后添加一个空格,那如果还要添加一个world的话,我们也要一个一个写吗?答案是不用

append

我们可以使用append 

非常的方便,而且不像C语言那会,string这里会自动扩容

这还不是最牛的地方,我们看下面

我们可以直接使用+=,非常的舒服,体验直接上升一个档次 

这里有一个x,如果我们要把x转成string对象,该怎么办呢? 

    size_t x = 0;
    cin >> x;
	string xstr;
	while (x) {
		size_t val = x % 10;
		xstr += ('0'+val);
		x /= 10;
	}
	cout << xstr << endl;

我们来看结果

其实还是有点问题的,我们逆置一下即可(一会儿会讲一些方便的函数)

string是一个管理字符数组的类(其实就是字符数组的顺序表)

下面我们来看一个比较重要的重载 

operator[ ]

重载的是方括号,方括号本质是一种解引用,让数组可以访问它的数据

现在我们就可以访问它的每一个字符了,用下标加方括号

我们可以看到,两种方式访问的结果是一样的,只是第一种是直接打印,第二种是一个一个的打印,第二种相对更加自由

上面我们用到了size,我们来看看size

size

 大家再想一想,我们知道字符串的结尾是\0,那我们上面可以访问到\0吗?

我们打印一下size,发生并没有统计\0,原因是\0不算有效字符,是特殊字符,是标识字符串结束的特殊字符,这里和C语言的strlen一样,都没有算\0

有人可能回想在size后+1是不是可以访问到\0? 我们看结果发现好像什么也没有变化

有些编译器下,\0是不会被显示的打印出来,其实这里已经访问到\0了,只是没有显示出来

我们打开监视也是看不到的 

但是在这里是可以看到的 ,我们要点开各种底层

这里就可以看到了,这块内容我们未来学习底层会进行讲解

 我们再回过头来看operator[ ],它实现了两个,一个是普通对象的,一个是const对象的

我们可以给每个字符都修改一下

我们可以像数组一样使用,增删查改,非常方便

这里看起来很像,其实底层是天差地别的 

a是数组名,代表首元素地址,s3[1]会变为*(s3+1)

而s1是自定义类型,会调用operator[ ](1);

 我们可以看到call是调用了operator[ ] 的,前面有一个很长的东西,这是string的类型,我们后续会讲这是为什么

 了解了上面的内容,我们下面开始学习迭代器

迭代器

我们现在可以把迭代器理解为像指针一样的东西

比如说我们有一个string对象,它大概就是这样的结构,在堆上开了空间,然后指针指向这个空间,然后把常量字符串拷贝过来

迭代器就是增加一种访问方式,我们先来看迭代器怎么使用

我们先记住迭代器怎么写,至于为什么这样写,我们后面会讲

 s1.begin会返回hello world的开头位置的指针(不一定是指针,我们先想象为指针)

end会返回\0的位置(左闭右开的区间),*it开始是h的位置,所以++it会让它往后走,下一次就是e,然后就是llo

迭代器是像指针一样的类型,有可能是指针,也有可能不是,我们后续会慢慢接触

任何容器,begin都是第一个位置的迭代器

那迭代器能否修改呢?答案是可以的

我们先解引用,再--,就修改了数据

我们的访问方式多种多样,但是我们之前学习过范围for,其实范围for才是我们的最爱 

我们再回忆一下,我们这里进行了++,但是发现输出结果没有改变,这是因为这里是依次取字符拷贝给ch,然后++,本体并没有变化,所以我们应该加引用

加上引用就变化了

其实,范围for的底层会替换为迭代器,也就是说根本就没有什么范围for,都是迭代器罢了

 我们看底层,其实也是begin和end

 如果一个类不支持迭代器,就不支持范围for,比如栈就不支持

迭代器的另一个好处是通用,如何容器都支持迭代器,并且用法是类似的

比如我们后面要学的vector,我们发现,代码基本是一样的 

list也同理,也就是说,我们学一个迭代器,其他就基本都会了

 这里还有一个问题,很多人可能会以为下标加方括号是主流的访问方式,但是string可以用,vector可以用,那list呢?

答案是不能,因为只有连续的空间(数组结构)才能重载方括号,比如树也是不行的,但是他们都是可以用迭代器的

总结一下:iterator是像指针一样的类型,有可能是指针,有可能不是指针,iterator提供了一种统一的方式访问和修改容器的数据

下面我们来简单看看算法,这里我们讲算法的主要目的是为了演示迭代器

首先,算法是怎么作用的数据上的呢?数据在容器里,算法是不能直接访问到容器的,因为容器里的数据是私有的

我们先简单看看

reverse

 reverse是逆置的意思,是通用的,无论列表,数组都可以使用,传的参数是左闭右开的区间

这里的两句代码调用的也不是一个函数,因为我们看上面的reverse,是函数模板 

有了迭代器,我们直接使用范围for,非常的舒服 

sort

 还有sort,传的也是迭代器

 另外,这里的列表是不支持sort的,具体原因我们之后再谈

上面我们讲了迭代器的第二个作用,跟算法进行配合

所以算法就可以通过迭代器出处理容器中的数据

迭代器除了普通的迭代器,还有反向的迭代器,比如有时候我们需要倒着遍历

反向迭代器

反向迭代器是reverse_iterator,我们来使用看看

 

 反向迭代器调用的自然也是rbegin和rend,我们发现确实反向了

rbegin是最后一个数据的位置,rend是第一个数据的前一个位置 

我们这里调用反向迭代器,非常的麻烦,前面写那么长,还要写string,reverse,所以我们可以用auto

 使用起来非常方便,我们想修改数据也是可以的

这里就进行了修改

仔细想想我们就可以发现,范围for是有局限性的,它是不可以倒着遍历的,只有反向迭代器才能倒着遍历

我们再看一些使用场景

我们这里传s1,其实是不愿意这样传的,因为这里会调用拷贝构造,这里是深拷贝,代价极大

所以我们要加const引用

 我们在里面写点代码

我们看,这里编译是不通过的

原因是const对象不能用普通迭代器,要使用const迭代器

const迭代器

 普通迭代器可以读可以写,const只能读不能写

这样就可以通过了 ,如果要倒着遍历,也是类似的

原因也是一样,需要使用const的迭代器 

 

这样就可以了,但是,这样写太长了,直接用auto就可以了,也就是我屏蔽掉的那一行auto

 总结一下,我们现在学习了四种迭代器

前两种是正向迭代器,后两种是反向迭代器,1和3可以读可写,2和4是只读(给const对象使用)

下面我们再看看容量相关的

 我们看到有size和length,发现他们的解释是一样的

运行结果也是相同,这其实是和C++的发展史有关

containers是容器的意思,但我们发现里面其实并没有string

严格来说,string不属于stl,是属于标准库的,stl是属于标准库的一部分,string比stl产生的早一点

上面的接口,string上面定义的是length,在stl出来后加了size,因为列表这些都可以叫length,但是树叫length是不合适的,所以就有了size

我们一般推荐使用size

max_size

我们来看max_size,意思是最大长度

 最大长度是21亿,但是我们换个编译器就可能有变化

比如vs的13下就是42亿,这个函数有点不靠谱 

stl是一个规范,有很多版本,底层实现大同小异,所以不同版本的底层都是有差异的

比如这里对于max_size就是不同的,max_size在实际中毫无意义,但因为向前兼容的问题,是不能删除的

capacity

我们再看capacity

 是15,我们再换一个编译器,换到Linux下

 

 结果是不一样的,我们再看扩容机制

 除了第一次是二倍,后续大概都是1.5倍,我们再看看Linux下的

 Linux是二倍扩容

vs下最开始的长度其实是16(还有一个\0),第一次扩容后其实是32,上面的没有算 \0

clear是清理数据的意思

大家先想一想clear可能会让size改变,那会不会让capacity改变呢?

 

答案是不会,同样的,我们来看下Linux下

 

 也是一样的 ,空间是很多的,一般情况下是不会轻易释放空间的,因为最后是有析构函数做保底的

下面我们做些练习题

这道题是大数相加,有些数太大,我们的int,甚至long long都保存不下,比如99个9,这时就有人想到把数据保存到字符串里,所以就有了这种题,我们下面来看代码

class Solution {
public:
    string addStrings(string num1, string num2) {
        int end1=num1.size()-1,end2=num2.size()-1;
        string strRet;
        int carry=0;
        while(end1>=0 || end2 >= 0){
            int val1 = end1 >= 0 ? num1[end1]-'0' : 0;
            int val2 = end2 >= 0 ? num2[end2]-'0' : 0;
            int ret = val1 + val2 + carry;
            carry = ret/10;
            ret = ret%10;
            strRet += ('0'+ret);
            --end1;
            --end2;
        }
        if(carry==1){
            strRet += '1';
        }
        reverse(strRet.begin(),strRet.end());
        return strRet;
    }
};

 我们设置end1和end2取两个字符串的尾部,val1和val2负责保存并转换为数字,strRet是我们最后返回的字符串,ret是我们临时保存val1+val2+carry的值,carry是进位的意思,比如9+9=18,这里carry就是1,然后让strRet保存8即可,因为是字符和整形转换,所以我们要注意加减字符0,最后还要判断一下,如果carry为1时,strRet最后还是要加1的, 因为我们是从最后一位开始保存的,所以最后需要逆置一下

我们回过头继续看capacity

我们知道,扩容是要付出代价的,那我们有什么办法可以减少代价呢?

reserve

所以就有了reserve,他的英语单词和reverse非常像,但reserve是保留的意思

我们看他的参数,有一个n,假设我们知道我们的string要插入100个字符,我们就可以

 就不需要我们后续开空间了,已经提取开好了,至于这里为什么是111,这是和vs的对齐机制有关的,另外,如果在Linux下的话,这里给的就是100,所以不同的平台走的是不同的,不管在哪一个平台,这里一定会大于等于100

我们仔细看reserve的文档,他说在一些情况是会缩容的,我们来验证一下

在某些情况下,他确实是会缩容的,比如我们先开100个空间,然后clear,再reverse,就缩容了,和clear也有点关系,这里是否用for循环填满空间不影响结果

如果有数据,就不会缩容,clear清空一下,就会缩容了

resize

我们再看resize,resize是扩容加填数据

 reserve只对capacity进行改变,是单纯的开空间,resize是开空间加填值初始化

 默认填的是0

当然我们想自己设置也是可以的 

resize开空间时,如果n比我们的空间大,他会扩容,如果比空间小的话,他是会删数据的

我们看到size从100变成了20,相当于把后面的数据全删了 

另外,这里是不会缩容的,resize(0)也不会缩容,缩容是要付出代价的,所以一般情况都不会缩容

 如果我们就想要缩容,我们可以

shrink_to_fit

他会将capacity减少到size,可能比size大,因为有对齐等等的原因

operator[ ]可以让我们把string像数组一样使用,返回pos位置的字符,我们可以随意修改,但是const不能修改,这两个接口构成重载 

at

 at的更能和[ ]是一样的,但我们平时不喜欢用,at是早年没有运算符重载时提供的,我们了解一下即可

 at和[ ]的区别是越界的检查,at是抛异常,[ ]是断言

这里[ ]就是断言错误

at是异常,异常需要我们捕获 

 append是字符串,push_back是一个字符,不过我们平时基本都使用+=

我们再看assign,是赋值的意思,我们来对比一下

实际中我们也基本不用assign,大家了解即可 

insert

 我们再看insert,他是在pos位置插入一个字符串,或者字符串的前n个

 方式多种多样

还可以是迭代器 ,如果我们也想用迭代器在中间位置插入,我们可以

 insert我们要谨慎对待,因为有数据挪动,不宜多用

erase

我们再看erase,是从pos位置删除n个字符

比如这里我们就把world删除了

如果我们只给其实位置,他就是一直删,直到结尾,如果我们连这个5也不写,就是全部删除 

 

给迭代器也是可以的,这里就变成了头删

同样因为数据挪动的原因,不宜多用

replace

再看replace ,是替换的意思,上面的(1)是把pos位置开始的len个字符替换为string

我们简单看看

 大致就是这样一个样子,同样的,还是因为数据挪动问题,不宜多用

c_str

 c_str是返回底层的存储的字符串

 第一个s1是调用string的流插入,下面的是char*

c_str是为了和c语言的一些接口更好的配合

比如我们打开一个文件,我们是不能直接传filename的,但是有了c_str就可以这样写 

find

 find就是查找,字符串和字符都可以查找,这里的pos从pos位置开始进行搜索,会返回找到的第一个位置,没有找到返回-1

我们来用find做个练习,随便给定一个网址,将协议,域名,资源名分开

substr

这里我们结合substr, 这是截取子串,pos位置开始截取len个

int main() {
	string url = "https://legacy.cplusplus.com/reference/string/string/";
	//协议,域名,资源名
	size_t pos1 = url.find("://");
	string protocol;
	if (pos1 != string::npos) {
		protocol = url.substr(0, pos1);//当前位置的下标是前面的数据个数
	}
	cout << protocol << endl;

	string domain;
	size_t pos2 =url.find('/',pos1+3);//pos1+3是查找的其实位置,防止返回://的/,我们要的是com后面的/
	if (pos2 != string::npos) {
		domain = url.substr(pos1+3,pos2-(pos1+3));
	}
	cout << domain << endl;

	string uri;
	uri = url.substr(pos2 + 1);//只给起始位置,会一直截取到结尾
	cout << uri << endl;
}

此时我们随便换网站都是可以的,我们之前写的是写死的

rfind

除了find,有时候我们还需要rfind,比如查找最后一个单词的长度或者查找文件的后缀

rfind和find完全是类似的,只不过rfind是从后往前找

find_first_of和 find_last_of

  再看find_first_of还有find_last_of,下面为了方便,简称firstof和lastof

firstof是找任意一个,我们来试一试

 这段代码就是把所有的aeiou替换为*

lastof和firstof的功能是一样的,只不过是从后往前找

还有find_first_not_of和 find_last_not_of,他们的是找不是这里面的,比如我们上面的aeiou,他查找的为不是aeiou的,和上面两个是反正来的,同样的,first是从前向后,last是从后向前

还有运算符重载,大家了解即可

运算符重载

+和+=,+是不改变自己 ,+=是改变自己

getline

然后就是getline 

我们借助这道题来讲解 

这里的代码非常简单,我们直接来看这个问题,这是什么原因?我们在vs下测试一下大家就明白了

原因是空格和空格之后的字符都没有被录入,scanf和cin都有一个特点,多个值之间遇到空格或者换行,认为这次读取结束,剩下都在缓冲区

我们读取两次就可以验证 

所以此时就需要getline ,我们看getline的参数,第一个是istream,第二个是字符串,第三个是结束符,我们可以自己控制在哪里结束,默认是换行符

大家还记得C语言中字符串和整形间的转换吗?还有字符串和浮点数之间的转换

整形是itoa和atoi函数,浮点数没有,要自己写,我们来看C++是怎么玩的

to_string

我们直接来试一试(C++11支持)

 非常强大,我们再看字符串转整形怎么实现

默认是按10进制转,这个我们是可以控制的

还有转浮点数的 

所以我们使用这些函数会非常方便

 

最后我们再看string,string是模板,是typedef的,他的本源是basic_string

为什么要写成模板呢?

 因为还有wstring,u16和u32,要兼容这些版本,这些东西和编码有关,目前我们先了解一下即可

我们接触过的编码就有ASCII码,ASCII是美国的,所以不能表示汉字,于是我们就自己设计了gbk编码,还有一个就是国际上的unicode,叫做万国码,他要将全世界的文字收录,所以上面的这几个都是为了更好的表示其他国家的文字而设计的

在实际中,我们使用较多的是utf-8,他是一个变长的,可以兼容ASCII等等,大家感兴趣可以了解一下

模拟实现string

下面我们来模拟实现string

基本框架

我们先把基本框架写下

  

我们先看构造函数,我们是不能这样初始化的,因为上面是const char*,下面是char*,涉及权限放大问题,另外,万一传入的是一个常量字符串,这里都不能修改了,所以string的构造函数不能用字符串去初始化,要去开空间,我们尽可能使用初始化列表初始化

 最后我们写成这样,有人可能会把初始化列表里的顺序换一下

这样看起来没有毛病,但是别忘了,初始化列表的初始的顺序不是按照我们写的顺序,而是声明的顺序,即按找private里属性的顺序来初始化,所以这里如果我们运行的话是会出现错误的

我们测试一下,为什么预期结果和我们想的不一样,没有字符?因为我们上面只开了空间,而没有把数据拷贝过去

 

此时我们再测试就没问题了,所以大家一定要注意细节

我们的代码还有一点问题,strlen是一个o(N)的接口,是实时去算的,我们初始化用了三遍是非常费时间的 ,还有为了解决乱移动代码顺序问题,我们修改一下

 下面我们再实现一下析构函数和c_str,实现c_str后我们就不用再一直看监视窗口了,方便一点

这两个实现非常简单

 

我们简单测试一下

我们再按照实际需求,假设我们需要一个无参的构造函数,所以我们要实现一下默认构造

先看这个,这样写是错误的,_str这里不能给nullptr,否则我们的c_str就悬空了,会崩掉

如果我们使用标准库的sting,定义一个无参的string,他的里面只有一个\0 

所以我们应该这样写,我们合并一下两个构造,无参的带参的使用全全省的即可

我们先看这两种错误的,这两种错误是新手们经常犯的,第一个错误的原因是\0是char,而str是char*,类型都不匹配 ,第二种如果给空的话,下面的strlen就直接崩了

其实我们什么都不用给就可以了 

 我们看到这里的空串其实是有\0的,这个\0是哪来的呢?其实是常量字符串末尾默认是有\0的

然后strcpy就把\0拷贝过去了

如果我们要遍历字符串,是需要知道size的

我们直接返回即可,这个函数和c_str我们最好在后边加上const ,原因是const可以被const对象调用,是权限的平移,他修饰的是this指针指向的对象,普通对象也可以调用,是权限的缩小

下面我们再实现一下operator[ ],这样我们就可以像数组一样去使用string

我们使用assert,返回pos位置的字符,并且这里可以返回引用,因为出了作用域_str还在,他是对象的成员,用引用返回我们可以读,也可以写

 而且我们要提供两个版本,这里我们要多看文档

 

 比如只有const版本,因为只用读,不用写

这个就有两个版本,第一个是可以读可以写,第二个个只读,当是const对象时,期望不能修改,所以只读,构成函数重载 

 我们简单测试一下,这里就可读可写

而当是const时就不能修改 ,编译器会自动寻找最匹配的

我们再来实现一下迭代器

我们知道迭代器有begin和end,begin是开始的位置,end是最后一个位置的下一个位置(\0不是有效字符)

就是这个样子

 

我们简单实现一下

 我们还可以用范围for

 写了迭代器就支持范围for,在底层范围for就被替换成了迭代器

我们可以证明一下,我们把迭代器的end屏蔽掉

然后就会出现这样的错误 ,编译器的底层就是傻瓜式的替换而已,名字都需要是一样的,我们改一下名字都不行

那const对象调用迭代器呢?const对象只能读不能写

 const char*即可

我们简单测试一下,*cit+=1是不允许的 ,const迭代器是只读的

这一串写起来太长了,我们是可以 使用auto的

增删查改

下面我们来实现push_back和append

void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}
		void push_back(char ch)
		{
			if (_size >= _capacity)
			{
				//2倍扩容
				reserve(_capacity == 0 ? 4 : _capacity * 2);//这里的判断是防止空串初始化容量为0
			}
			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}
		void append(const char* str)
		{
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				//至少扩容到_size + len
				reserve(_size + len);
			}
			strcpy(_str + _size, str);
			_size += len;
		}

 我们添加时都是需要先判断是否需要扩容,所以我们写一个reserve,逻辑也很简单,下面我们测试一下

是没有问题的,我们在实际中其实最喜欢的是+=这些,所以我们来实现一下

        string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}

我们基于前面的代码实现即可,然后我们测试一下

也没有问题

我们再实现一下insert

        void insert(size_t pos, size_t n, char ch)
		{
			assert(pos <= _size);
			if (_size + n > _capacity)
			{
				//至少扩容到_size + n
				reserve(_size + n);
			}
			//挪动数据
			int end = _size;
			while (end >= (int)pos)
			{
				_str[end + n] = _str[end];
				--end;
			}
			for (size_t i = 0; i < n; i++)
			{
				_str[pos + i] = ch;
			}
			_size += n;
		}

 我们先看pos位置插入n个字符的版本,这里挪动数据end设置为int,下面pos强转int,原因是pos如果为0,size_t类型是不会为负数,也就是说while循环是死循环了,我们只把end写成int也不行,因为pos不强转为int的话,在比较时就会发生整形提升,把end转换为无符号的,这是一个解决方案

第二种方法是我们挪动的时候直接把end设置为结尾位置,然后挪动,这样就不会有0的问题,不过这样写出来不太好看,我们来看第三种

我们给定一个npos 

然后我们在挪动数据这里加一个条件即可

我们测试一下,没有问题 

        void insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				//至少扩容到_size + len
				reserve(_size + len);
			}
			//挪动数据
			size_t end = _size;
			while (end >= pos && end != npos)
			{
				_str[end + len] = _str[end];
				--end;
			}
			for (size_t i = 0; i < len; i++)
			{
				_str[pos + i] = str[i];
			}
			_size += len;
		}

再来看插入字符串的版本,和上面的基本差不多,我们测试一下

没有问题 

        void erase(size_t pos, size_t len = npos)
		{
			assert(pos <= _size);
			if (len == npos || pos + len >= _size)//说明要删完
			{
				_str[pos] = '\0';
				_size = pos;
				_str[_size] = '\0';
			}
			else 
			{
				size_t end = pos + len;
				while (end <= _size)
				{
					_str[pos++] = _str[end++];
				}
				_size -= len;
			}
		}

我们再实现erase,也是非常简单

 测试一下也没有问题

增删查改我们还差一个查,我们来实现find

find查找字符串我们使用暴力匹配,为什么不用kmp呢?因为实际中kmp其实并不好,甚至strstr这个函数都是暴力匹配写的,一般不要求效率的话都使用暴力匹配,如果大家想换一种写法的话可以去了解一下BM算法

        size_t find(char ch,size_t pos = 0)
		{
			assert(pos < _size);
			for (size_t i = pos; i < _size; i++)
			{
				if (_str[i] == ch)
				{
					return i;
				}
			}
			return npos;
		}
		size_t find(const char* str, size_t pos = 0)
		{
			assert(pos < _size);
			const char* ptr = strstr(_str + pos, str);
			if (ptr)
			{
				return ptr - _str;
			}
			else
			{
				return npos;
			}
			return npos;
		}

我们借助strstr就可以轻松实现find,我们再测试一下

没有问题 

我们再写一个substr,截取子串,而且我们还要写拷贝构造

        string(const string& s)//拷贝构造
		{
			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}

我们要开一个一样大的空间来完成深拷贝

        string substr(size_t pos = 0, size_t len = npos)
   		{
			assert(pos < _size);
			size_t n = len;
			if (len == npos || pos + len > _size)//长度超过size
			{
				n = _size - pos;
			}
			string tmp;
			tmp.reserve(n);
			for (size_t i = pos; i < pos + n; i++)
			{
				tmp += _str[i];
			}
			return tmp;
		}

代码也非常简单,我们再测试一下

没有问题 

我们再实现一下resize

        void resize(size_t n, char ch = '\0')
		{
			if (n < _size)
			{
				_size = n;
				_str[_size] = '\0';
			}
			else
			{
				reserve(n);//直接调用reserve检查看是否扩容
				for (size_t i = _size; i < n; i++)
				{
					_str[i] = ch;
				}
				_size = n;
				_str[_size] = '\0';
			}
		}

代码逻辑也非常简单,我们再测试一下

我们和标准库的对比一下,没有问题 

我们再来插入一下流插入和流提取,流插入和流提取涉及抢占第一个位置的原因,不能写成成员函数,所以我们写成全局函数

 我们先这样写,发现报错了,原因是这里涉及到防拷贝的知识,大家现在先了解一下,后面我们会讲,我们修改一下 

ostream& operator<<(ostream& out, const bai::string& s)
{
	/*for (size_t i = 0; i < s.size(); i++)//两种方法都可以
	{
		out << s[i];
	}*/
	for (auto ch : s)
	{
		out << ch;
	}
	return out;
}

代代码一样很简单,我们再测试一下

 此时我们就可以不用c_str来打印了

这里再说一下,函数参数里的bai::string,我们也可以把他放到命名空间里,这样就不用加前缀了

那这样打印和c_str有什么区别呢?

c_str是返回c的字符串,打印的是const char*,是内置类型,遇到\0就终止

而流插入是不管\0的,有多少字符就打印多少字符,目前是有bug的,但我们目前不用管,我们简单实现就行,我们来测试看看区别就明白了

 这里大家就可以看到他们的区别了

这里的bug很麻烦,我们中间有\0的话,我们上面的实现就得改很多内容,我们使用了非常多的str函数,比如strcpy等等,遇到\0就终止,我们都得修改,如果不用strcpy的话,我们得用memcpy

我们来看一个bug,就用拷贝构造举例

 strlen是不用替换的,因为c语言的字符串结束位置是\0,而string的结束位置是看size的

所以我们把strcpy替换为memcpy

修改完后就没有问题了 ,大家要记得把strcpy都替换掉,比如reserve,append那里,也会有bug

在结尾我会把代码全都附上,大家也可以到时候去对比 

我们再看流提取,直接说结论,这样写是错误的,我们输入换行和空格是不能结束的,我们来看一段程序

我们要输入多个字符,中间是以换行或者空格为分割的,流是认为换行和空格是分隔符 

 这个问题在整形里尤为明显 

如果没有换行和空格,是不知道输入的到底是什么,比如第二张图的123,这是1,2,3还是123?

此时我们就需要get,无论是什么字符都可以拿过来

另外,我们使用cin对于同一个string每次输入是独立的,但我们现在不是,我们测试一下就明白区别了

我们的代码不会覆盖掉之前的内容,所以我们需要一个clear函数 

clear函数非常简单,这里就不多说了

istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char ch = in.get();
		//in >> ch;
		while (ch != ' ' && ch != '\n')
		{
			s += ch;
			//in >> ch;
			ch = in.get();
		}
		return in;
	}

 我们看流提取,再测试一下

此时就没有问题了 ,不过我们的代码还可以再优化一下,while里面有个+=,怎么看怎么难受,我们一次输入很长的字符串时,会多次扩容,所以我们修改一下

    istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char ch = in.get();
		//in >> ch;
		char buff[128];
		int i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 127) //要给\0留一个位置,所以不是128
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			//s += ch;
			//in >> ch;
			ch = in.get();
		}
		if (i != 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}

这里就相当我们用了一个桶,水满了或者接完了我们再放入, 而之前的代码相当于一滴一滴的接 

此时我们的问题都解决了码?没有,如果我们一上来就输入空格,然后输入数据呢?官方的库是可以拿到数据的,而我们的是拿不到数据的,我们下面来解决这个问题

我们加上框里的代码就可以了

我们平时运行代码时看着一行就完事了,其实当我们自己上手的话,会发现有很多很多的问题,所以,任重而道远啊

我们再来写比较大小,string的比较是按ASCII码比较的

 字符串的比较都是按ASCII比较的

我们简单写写,不过这样写还是有bug的,遇到中间\0的还是有问题

用memcmp也是有问题的,坑非常多,首先memcmp要给一个长度,我们给定两个字符串中长度小的那一个,但是还有问题,比如这两个字符串,hello和helloxxx,这样结果就变成相等了,长度+1也不行,比如前面的中间\0问题,一个是hello\0xxxx,一个是hello\0yyyy,问题非常多

        //bool operator<(const string& s)
		//{
		//	size_t i1 = 0;
		//	size_t i2 = 0;
		//	while (i1 < _size && i2 < s._size)
		//	{
		//		if (_str[i1] < s._str[i2])
		//		{
		//			return true;
		//		}
		//		else if (_str[i1] > s._str[i2])
		//		{
		//			return false;
		//		}
		//		else
		//		{
		//			++i1;
		//			++i2;
		//		}
		//	}
		//	//到这里说明字符串前面部分一样
		//	/*if (i1 == _size && i2 != s._size)
		//	{
		//		return true;
		//	}
		//	else
		//	{
		//		return false;
		//	}*/
		//	//return i1 == _size && i2 != s._size;//这2种更简洁
		//	return _size < s._size;
		//}
		bool operator<(const string& s)//复用版本
		{
			bool ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
			return ret == 0 ? _size < s._size : ret < 0;//ret<0是第一个小于第二个
		}
        //3个测试用例及答案
        //"hello" "hello" false
		//"helloxx" "hello" false
		//"hello" "helloxx" true 

我在这里提供了多种版本,代码的逻辑有点绕,各位最好自己画一下,再对比后面的三个测试例子就明白了

有了小于,大家就可以轻松写出其他的比较,记得使用复用可以写起来更加简便,比如我们写大于等于就可以用小于取反

        bool operator==(const string& s) const
		{
			return _size == s._size &&
				memcmp(_str, s._str,_size) == 0;
		}
		bool operator<=(const string& s) const
		{
			return *this < s || *this == s;
		}
		bool operator>(const string& s) const
		{
			return !(*this <= s);
		}
		bool operator>=(const string& s) const
		{
			return !(*this < s);
		}
		bool operator!=(const string& s) const
		{
			return !(*this == s);
		}

全部复用即可,最后再全部加上const

下面我们再实现赋值,也就是=,我们要实现深拷贝

        string& operator=(const string& s)
		{
			if (this != &s)
			{
				char* tmp = new char[s._capacity+1];
				memcpy(tmp, s._str, s._size+1);
				delete[] _str;
				_str = tmp;
                _size = s._size;
				_capacity = s._capacity;
			}
			return *this;
		}

这样就搞定了 

 不过上面是传统写法,都什么年代了还在写传统代码,接下来看现代的写法

        string& operator=(const string& s)
		{
			if (this != &s)
			{
				string tmp(s);
				std::swap(_str, tmp._str);
				std::swap(_size, tmp._size);
				std::swap(_capacity, tmp._capacity);
			}
			return *this;
		}

 这里是先用拷贝构造,把tmp开辟一个和s一样大的空间,然后tmp出了作用域就会销毁,所以把内容全部换一下就ok了

另外这里是不能和*this换的,会出现栈溢出的问题,我们这里是赋值,然后调用swap,swap的是两个string对象,又是赋值,死循环递归了

 要交换成员才可以

        void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
		string& operator=(const string& s)
		{
			if (this != &s)
			{
				string tmp(s);
				//this->swap(tmp);两种都可以
				swap(tmp);
			}
			return *this;
		}

最后我们把swap分出来,就变成了这样,当然,还可以继续简化

        string& operator=(string tmp)
		{
			swap(tmp);
			return *this;
		}

是不是很神奇?这个写法是全自动的,比如我们写s1=s2,这里tmp一上来就是s2的拷贝,还是深拷贝,也就是会先调用拷贝构造,出了作用域tmp还会调用析构函数

拷贝构造也可以使用现代写法,我们来实现一下

        string(const string& s)//拷贝构造
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{
			string tmp(s._str);
			swap(tmp);
		}

我们需要对数据进行初始化一下,因为内置类型编译器不做处理,这种写法是调用了构造函数

但是这种写法是有问题的,在中间是\0的时候就会出问题

s4这里后面是没有xxxx的,所以我们建议还是用传统的

引用计数和写时拷贝

写时拷贝这个概念我们了解一下即可,深拷贝的代价是很大的,有时候我们只拷贝,不做别的事情,是很浪费的,我们对比浅拷贝,浅拷贝的问题是会析构两次,一个对象修改会影响另一个对象,所以就出现延迟拷贝(写时拷贝)

他在这里加一个一个引用计数, 只有s1指向时是1,s2也指向时就变成了2,当s3也指向时就变成了3

这样做的话当s2释放时不析构,他会减一下引用计数,s1再释放时再次减少,引用计数变为0,此时才会析构,也就是最后走的一个人关灯

再看第二个问题,如果要修改,发现引用计数是2时,就不能修改,此时就会发生写时拷贝,即写的时候,引用计数如果不是1,就进行深拷贝,再修改

比如s1和s2指向一个空间,然后要修改s2,就会深拷贝,引用计数也会拷贝过来,然后就可以修改了 ,Linux下就使用的是这个

最后,我在这里附上我们模拟实现的全部代码

全部代码

#include<iostream>
#include<string>
#include<algorithm>
#include<assert.h>
using namespace std;
namespace bai
{
	class string
	{
	public:
		typedef char* iterator;//迭代器
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}

		typedef const char* const_iterator;//const迭代器
		const_iterator begin() const
		{
			return _str;
		}
		const_iterator end() const
		{
			return _str + _size;
		}

		/*string()
			:_size(0)
			,_capacity(0)
			,_str(new char[1])
		{
			_str[0] = '\0';
		}*/
		//string(const char* str = '\0')//构造函数
		//string(const char* str = nullptr)//构造函数
		string(const char* str = "")//构造函数
			:_size(strlen(str))
		{
			_capacity = _size;
			_str = new char[_capacity + 1];
			//strcpy(_str, str);
			memcpy(_str, str,_size+1);
		}
		string(const string& s)//拷贝构造
		{
			_str = new char[s._capacity + 1];
			//strcpy(_str, s._str);
			memcpy(_str, s._str, s._size + 1);
			_size = s._size;
			_capacity = s._capacity;
		}
		//string(const string& s)//拷贝构造
		//	:_str(nullptr)
		//	,_size(0)
		//	,_capacity(0)
		//{
		//	string tmp(s._str);
		//	swap(tmp);
		//}
		//string& operator=(const string& s)//赋值
		//{
		//	if (this != &s)
		//	{
		//		char* tmp = new char[s._capacity+1];
		//		memcpy(tmp, s._str, s._size+1);
		//		delete[] _str;
		//		_str = tmp;
		//		_size = s._size;
		//		_capacity = s._capacity;
		//	}
		//	return* this;
		//}
		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
		//string& operator=(const string& s)
		//{
		//	if (this != &s)
		//	{
		//		string tmp(s);
		//		//this->swap(tmp);两种都可以
		//		swap(tmp);
		//	}
		//	return *this;
		//}
		string& operator=(string tmp)
		{
			swap(tmp);
			return *this;
		}
		~string()
		{
			if (_str)
			{
				delete[] _str;
				_str = nullptr;
				_size = _capacity = 0;
			}
		}
		const char* c_str() const
		{
			return _str;
		}
		size_t size() const
		{
			return _size;
		}
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}
		const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				//strcpy(tmp, _str);
				memcpy(tmp, _str, _size + 1);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}
		void resize(size_t n, char ch = '\0')
		{
			if (n < _size)
			{
				_size = n;
				_str[_size] = '\0';
			}
			else
			{
				reserve(n);//直接调用reserve检查看是否扩容
				for (size_t i = _size; i < n; i++)
				{
					_str[i] = ch;
				}
				_size = n;
				_str[_size] = '\0';
			}
		}
		void push_back(char ch)
		{
			if (_size >= _capacity)
			{
				//2倍扩容
				reserve(_capacity == 0 ? 4 : _capacity * 2);//这里的判断是防止空串初始化容量为0
			}
			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}
		void append(const char* str)
		{
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				//至少扩容到_size + len
				reserve(_size + len);
			}
			//strcpy(_str + _size, str);
			memcpy(_str + _size, str, len + 1);
			_size += len;
		}
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}
		void insert(size_t pos, size_t n, char ch)
		{
			assert(pos <= _size);
			if (_size + n > _capacity)
			{
				//至少扩容到_size + n
				reserve(_size + n);
			}
			//挪动数据
			/*int end = _size;
			while (end >= (int)pos)
			{
				_str[end + n] = _str[end];
				--end;
			}*/
			size_t end = _size;
			while (end >= pos && end != npos)
			{
				_str[end + n] = _str[end];
				--end;
			}
			for (size_t i = 0; i < n; i++)
			{
				_str[pos + i] = ch;
			}
			_size += n;
		}
		void insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				//至少扩容到_size + len
				reserve(_size + len);
			}
			//挪动数据
			size_t end = _size;
			while (end >= pos && end != npos)
			{
				_str[end + len] = _str[end];
				--end;
			}
			for (size_t i = 0; i < len; i++)
			{
				_str[pos + i] = str[i];
			}
			_size += len;
		}
		void erase(size_t pos, size_t len = npos)
		{
			assert(pos <= _size);
			if (len == npos || pos + len >= _size)//说明要删完
			{
				_str[pos] = '\0';
				_size = pos;
				_str[_size] = '\0';
			}
			else 
			{
				size_t end = pos + len;
				while (end <= _size)
				{
					_str[pos++] = _str[end++];
				}
				_size -= len;
			}
		}

		size_t find(char ch,size_t pos = 0)
		{
			assert(pos < _size);
			for (size_t i = pos; i < _size; i++)
			{
				if (_str[i] == ch)
				{
					return i;
				}
			}
			return npos;
		}
		size_t find(const char* str, size_t pos = 0)
		{
			assert(pos < _size);
			const char* ptr = strstr(_str + pos, str);
			if (ptr)
			{
				return ptr - _str;
			}
			else
			{
				return npos;
			}
			return npos;
		}
		string substr(size_t pos = 0, size_t len = npos)
		{
			assert(pos < _size);
			size_t n = len;
			if (len == npos || pos + len > _size)//长度超过size
			{
				n = _size - pos;
			}
			string tmp;
			tmp.reserve(n);
			for (size_t i = pos; i < pos + n; i++)
			{
				tmp += _str[i];
			}
			return tmp;
		}
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}
		 
		//"hello" "hello" false
		//"helloxx" "hello" false
		//"hello" "helloxx" true 
		//bool operator<(const string& s)
		//{
		//	size_t i1 = 0;
		//	size_t i2 = 0;
		//	while (i1 < _size && i2 < s._size)
		//	{
		//		if (_str[i1] < s._str[i2])
		//		{
		//			return true;
		//		}
		//		else if (_str[i1] > s._str[i2])
		//		{
		//			return false;
		//		}
		//		else
		//		{
		//			++i1;
		//			++i2;
		//		}
		//	}
		//	//到这里说明字符串前面部分一样
		//	/*if (i1 == _size && i2 != s._size)
		//	{
		//		return true;
		//	}
		//	else
		//	{
		//		return false;
		//	}*/
		//	//return i1 == _size && i2 != s._size;//这2种更简洁
		//	return _size < s._size;
		//}
		bool operator<(const string& s) const//复用版本
		{
			bool ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
			return ret == 0 ? _size < s._size : ret < 0;//ret<0是第一个小于第二个
		}
		bool operator==(const string& s) const
		{
			return _size == s._size &&
				memcmp(_str, s._str,_size) == 0;
		}
		bool operator<=(const string& s) const
		{
			return *this < s || *this == s;
		}
		bool operator>(const string& s) const
		{
			return !(*this <= s);
		}
		bool operator>=(const string& s) const
		{
			return !(*this < s);
		}
		bool operator!=(const string& s) const
		{
			return !(*this == s);
		}
		
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
		static size_t npos;
	};
	size_t string::npos = -1;

	ostream& operator<<(ostream& out, const string& s)
	{
		/*for (size_t i = 0; i < s.size(); i++)//两种方法都可以
		{
			out << s[i];
		}*/
		for (auto ch : s)
		{
			out << ch;
		}
		return out;
	}
	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char ch = in.get();
		while (ch == ' ' || ch == '\n')//处理缓冲区前的空格或者换行
		{
			ch = in.get();
		}
		//in >> ch;
		char buff[128];
		int i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 127) //要给\0留一个位置,所以不是128
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			//s += ch;
			//in >> ch;
			ch = in.get();
		}
		if (i != 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
};

以上即为本期全部内容,希望大家可以有所收获

如有错误,还请指正

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

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

相关文章

什么是ASPICE认证

ASPICE&#xff1a; “AutomotiveSoftware ProcessImprovement and CapacityDetermination”&#xff0c;即汽车软件过程改进及能力评定。它是一个过程模型&#xff0c;由过程和能力度两个维度构成&#xff0c;用于评价汽车行业软件设计开发的能力水平。 ASPICE的6个级别&…

基于simulink进行场景变化检测(附源码)

一、前言 此示例演示如何及时分割视频。此示例中的算法可用于检测视频流中的重大变化&#xff0c;例如广告开始和结束的时间。场景变换在广告和营销中被广泛应用。通过改变场景&#xff0c;可以吸引消费者的注意力&#xff0c;传达产品或服务的特点和优势。例如&#xff0c;将…

包揽七项葵花奖 参编多项标准 萤石领跑智能家居+物联网云平台行业

7月9日&#xff0c;2023第七届“葵花奖”智能家居评选颁奖盛典在广交会展馆举行&#xff0c;萤石网络一举斩获7项重磅奖项。同时&#xff0c;萤石作为参编单位&#xff0c;受邀参与了《智能门锁测评标准》发布仪式及《智能开关测评标准》启动会&#xff0c;再次彰显了其在智能家…

【一些随笔】浅析 Linux和Windows:系统介绍、操作差异与使用技巧解析

一些随笔 文章内容1️⃣ 那些在Linux上顺理成章&#xff0c;换到Windows上就可能令人费解的事2️⃣ Linux系统介绍及使用技巧3️⃣ Windows系统介绍及使用技巧 文章内容 Linux和Windows系统的操作差异&#xff1b;Linux系统介绍、系统监控和优化技巧、Shell脚本编程技巧、一些…

掌握Python文件操作的绝招:打造数据之径,揭开文件操作的神秘面纱

文章目录 前言文件的基本操作打开文件操作关闭文件操作对文件进行操作1&#xff09;只读文件操作read()readlines()readline()seek() 2&#xff09;只写文件操作3&#xff09;文件追加操作读写、追加读写操作1. r 模式打开文件2. w 模式打开文件3. a 模式打开文件 以二进制的形…

走向 Native 化:SpringDubbo AOT 技术示例与原理讲解

作者&#xff1a;刘军 Java 应用在云计算时代面临“冷启动”慢、内存占用高、预热时间长等问题&#xff0c;无法很好的适应 Serverless 等云上部署模式&#xff0c;GraalVM 通过静态编译、打包等技术在很大程度上解决了这些问题&#xff0c;同时针对 GraalVM 的一些使用限制&a…

用了国产接口管理神器 Apifox 之后,我果断从 Postman “脱坑”了

在当前行业发展背景下&#xff0c;绝大部分项目都是基于前后端分离的架构进行的&#xff0c;由前后端、测试、运维等不同的团队共同开发&#xff0c;那么团队之间能否很好的 协同合作 无疑直接决定着项目的最终效果。 但是在实际开发流程中&#xff0c;团队之间的协同是很低效…

网络编程5——TCP协议的五大效率机制:滑动窗口+流量控制+拥塞控制+延时应答+捎带应答

文章目录 前言一、TCP协议段与机制TCP协议的特点TCP报头结构TCP协议的机制与特性 二、TCP协议的 滑动窗口机制 三、TCP协议的 流量控制机制 四、TCP协议的 拥塞控制机制 五、TCP协议的 延时应答机制 六、TCP协议的 捎带应答机制 总结 前言 本人是一个普通程序猿!分享一点自己的…

c语言进阶-printf的用法拓展

Printf函数打印方法拓展&#xff1a; 字符串赋值给指针&#xff0c;相当于把h的地址赋值给p了。 printf函数直接放字符串也是把首地址给printf&#xff0c;然后printf从首地址打印到\0。 打印时可以直接传p地址

IDEA使用教程

1. 查看代码历史版本 若要查看特定 Java 类的代码历史版本&#xff0c;请执行以下操作&#xff1a; 鼠标右键点击所需查看的 Java 类。 在弹出菜单中选择 "Local History"&#xff08;本地历史&#xff09; >> "Show History"&#xff08;显示历史…

云尚办公项目-搭建环境

硅谷项目&#xff0c;由尚硅谷分享&#xff0c;具体项目视频可以根据B站尚硅谷进行学习。搭建项目可以直接根据Spring Boot进行获取&#xff0c;本次主要是个人的的一些分析操作 后面代码主要以分析为主&#xff0c;相关内容不会的&#xff0c;可以观看尚硅谷视频 一、下载配套…

MySQL数据库——多表查询练习

一、练习素材 创建表 -- 创建部门表 create table if not exists dept3( deptno varchar(20) primary key , -- 部门号 name varchar(20) -- 部门名字 );-- 创建员工表 create table if not exists emp3( eid varchar(20) primary key , -- 员工编号 ename varchar(20), -- 员…

【雕爷学编程】Arduino动手做(153)---2.4寸TFT液晶触摸屏模块6

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

详解c++---布隆过滤器

目录标题 位图的优缺点为什么会有布隆过滤器&#xff1a;布隆过滤器的应用场景&#xff1a;布隆过滤器的实现布隆过滤器的测试 位图的优缺点 位图的优点&#xff1a; 1.位图可以节省空间&#xff0c;位图判断存储的数据是在还是不在只用一个比特位就可以记录数据出现的情况&a…

【毕业季·进击的技术er】大学生计算机毕业设计应该这样写

活动地址&#xff1a;毕业季进击的技术erhttps://marketing.csdn.net/p/f4a818f6455f3a9a7a20c89f60ad35f7 目录 扉页 摘要 目录 一 绪论 二、相关技术环境介绍 三、系统需求分析 四、系统架构设计 五、系统实现 六、系统测试 致谢 参考文献 以一个过来学长的角度来看…

SQl排序与分页

1. 排序数据 1.1 排序规则 使用 ORDER BY 子句排序 ASC&#xff08;ascend&#xff09;: 升序DESC&#xff08;descend&#xff09;:降序 ORDER BY 子句在SELECT语句的结尾。 1.2 单列排序 SELECT last_name, job_id, department_id, hire_date FROM employees ORDER…

元素配对----贪心1 (爱思创)

源代码 #include <bits/stdc.h> using namespace std; int main() {int n,data,sum0;cin>>n;vector<int> vec1,vec2;for(int i0; i<n; i){cin>>data;vec1.push_back(data);}for(int i0; i<n; i){cin>>data;vec2.push_back(data);}sort(ve…

linux下查看cpu使用率和内存占用

top top命令是Linux下常用的性能分析工具&#xff0c;能够实时显示系统中各个进程的资源占用状况&#xff0c;类似于Windows的任务管理器&#xff0c;下面详细介绍它的使用方法&#xff1b; top是一个动态显示过程&#xff0c;即可通过用户按键来不断刷新当前状态。如果在前台…

前端启动出现报错,提示vue-cli-service serve的解决办法

前端启动出现报错&#xff0c;提示vue-cli-service serve的解决办法 在命令行中使用命令 npm run dev运行从网上下载的一个vue项目时出现了以下报错&#xff1a; 原因&#xff1a; 原因是因为 node_modules文件的缺失 npm install再次执行 npm run dev启动成功