当前位置: 首页 > article >正文

【语法】C++的string

目录

4个默认成员函数

迭代器

string的扩容:

capacity():

reserve(): 

resize():

插入与删除:

c_str:

find()和substr:

getline():


在C语言中,要想存储一串字符,往往用的都是char arr[],也就是字符数组,而在C++中,引入了一个比字符数组更方便,可执行的操作也更丰富的类:string,可以把string归类到STL里面,但实际上string出现的要比STL早。本篇将介绍string类中常用的20几个接口

4个默认成员函数

string构造函数可以说是相当的丰富,每个都有不同的用法

//默认构造函数
    string s1;
    //其他构造函数
    string s2("hello");//将s4初始化为hello
    string s3(5,'a');//将s5初始化为5个'a',即"aaaaa" (不重要)
    string s4("hello",1,3);//将s6初始化为“hello”中从第1个字符开始的后3个字符  (不重要)
    string s5(s2,1,3);//将s6初始化为s2中从第1个字符开始的后3个字符  (不重要)
    string s6("hello",3);//将s7初始化为“hello”的前3个字符 (不重要) 
    //拷贝构造函数
    string s7(s4);
    string s8 = s5;
    //默认赋值运算符重载
    s1 = "world";
    cout << s1 << endl << s2 << endl << s3 << endl << s4 << endl << s5 << endl << s6 << endl << s7 << endl;

输出结果是

要特别注意的是,在string s5("hello",1,3)这样用时,如果最后一个参数给的值超过了原字符串的总数,就会使用“npos” 这个值来代替它

npos是string中的一个静态const变量,指的就是直到结束的位置,它的值是-1,但是仔细看下图就可以发现,该变量是size_t类型,size_t也就是unsigned int,是没有负数的,所以-1也就是正整数的最大值的意思

当我们想往string类的后面尾插字符或字符串时,可以用接口函数

s1.push_back('A');//尾插一个字符A
s1.append("edd");//尾插一个字符串edd

 push_back()是尾插字符,append()是尾插字符串

可以看到append有很多种调用方法,和一开始讲到的构造函数的调用很相似,这里就不多讲述了(不重要)

但还有更简便的方法

s1+='A';//尾插一个字符A
s1+="edd";//尾插一个字符串edd

这是string类提供的operator+=运算符重载函数

可以看到有3个重载函数,分别用于字符,字符串(c-string)和string类 (c-string是指的以'\0'结尾的字符串)

迭代器

定义了一个string类后,如果想要遍历它,有三种方法

1. 用C语言阶段就学过的[]+下标的方式

string s1("Hello World");
for(size_t i=0;i<s1.size();i++)
{
    cout << s1[i] << " ";
} 

size()也是string的一个接口函数,会返回字符串的字符个数

其实还有一个接口函数叫length,这是早期string用来求字符个数的函数,和size一样,但后面STL出了之后也出了size,就用size比较多了

顺带一提,max_size()是用来求string类最多可以容纳的字符的,它的值是固定的

string s1;
cout << s1.max_size() << endl;

x86环境:2147483647        x64环境:9223372036854775807

2. 迭代器

string s1("Hello World");
string::iterator it = s1.begin();
while(it != s1.end())
{
    cout << *it << " ";
    it++;
}

string::iterator就是string类的迭代器类型,用这个类型定义的变量就是迭代器变量

begin()和end()也是string类中的接口函数,分别返回第一个字符的迭代器和最后一个字符的迭代器

可以看到begin和end都有两个函数,带const的和不带const的

其实迭代器也有分const和非const,上面演示的就是非const,如果在iterator的前面加上const_,即const_iterator,这就是const类型的迭代器

可以看到,如果it是const类型的迭代器,就不允许修改了,const迭代器存在的意义就是只让读不让写

而且,你如果是const类型的迭代器,再去调用begin和end函数,就会调用const类型的函数了

那如果想要从后往前遍历呢?还有个专门的反向迭代器,具体实现如下

string s1("Hello World");
string::reverse_iterator it = s1.rbegin();
while(it != s1.rend())
{
    cout << *it << " ";
    it++;
}

reverse_iterator就是反向迭代器,而rbegin就是reverse_begin的缩写,下面的rend也如此,既然迭代器变量是反向的,那就只能用rbegin和rend,如果用begin和end会报错

反向迭代器和const迭代器也可以组合,const_reverse_iterator就是const类型的反向迭代器

3.范围for

在C++11标准中,引入了一个新的遍历方法

string s1("Hello World");
string::iterator it = s1.begin();
for(auto a : s1)//范围for
{
    cout << a << " ";
}

即定义一个auto类型的a,来自于s1,编译器会将a推演成s1的迭代器类型的“解引用形式”(即*iterator),a会自动++,直到遍历完为止

string的扩容:

在C语言中,如果想要像string一样存储字符串,又可以插入删除操作,就要malloc空间并不断扩容(参考C语言实现容器(例如stack,heap,seqlist等)),而在C++中也一样

capacity():

string s1("Hello World");
cout << s1.capacity() << endl;

输出结果为15,但是s1里面只有11个字符啊?

因为string的容量并不是和字符数一致的,string扩容的流程是申请一块更大的空间->将原数据复制到新申请的空间上->释放原来的空间。    如果每次插入字符都需要再去扩容,那效率消耗就太大了。所以capacity的初始值为15,只要你的字符数还在15以下,就不会扩容。

那如果到了16,capacity会是多少呢?

string s1("Hello World11111");
cout << s1.capacity() << endl;

 会输出31

15到31,差不多2倍,真的是这样吗?

string s;
size_t sz = s.capacity();
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';
	}
}

 这是一个用于检测capacity变化的程序

可以看到31后面是47而不是60,47后面不是90,但他们也并不是无规律可言,31*1.5=46.5, 47*1.5=70.5, 70*1.5是105,而且一般来说都会在1.5倍的基础上多开一个字符,用于存'\0'

(注:这是在VS环境下的倍增策略,如果是别的编译器,初始化时可能capacity和size相等)

reserve(): 

 一般来说,capacity是随着size的大小来1.5倍扩容的,但你也可以在size==capacity之前就给capacity扩容好指定的值

string s;
s.reserve(100);
size_t sz = s.capacity();
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';
	}
}

reserve可以给capacity分配n个字符的空间,现在再执行,就会发现capacity不会再有变化了

需要注意的是,该函数也是有缺省值的,如果()里不给数,那默认就是0

resize():

既然capacity可以提前指定,那么size也是可以提前指定的

string s;
s.resize(100);
cout << "size:" << s.size() << endl << "capacity:" << s.capacity() << endl;
cout << s << endl;

 

可以看到size直接是100了,capacity也跟着增容了,但为什么最后输出s确是空的呢?

可以看到resize有两个重载,我们刚才使用的就是第一种方式,第二种方式后面需要跟一个字符

string s;
s.resize(100,'A');
cout << "size:" << s.size() << endl << "capacity:" << s.capacity() << endl;
cout << s << endl;

 这次我给了resize第2个参数'A',可以看到成功的输出了100个A,如果不给默认就是'\0'。也就是说,如果直接resize(100)的话,就是存了100个'\0'在s里面

下面这段代码会输出什么?

string s("Apex Legends");
s.resize(5);
cout << "size:" << s.size() << endl;
cout << s << endl;
s.resize(15,'S');
cout << "size:" << s.size() << endl;
cout << s << endl;

可以看到,如果resize的值比原来size要小,就会把多余的删掉

如果本来字符串中有值,resize了一个更大的空间,原来的值也会保留。

插入与删除:

string里也可以和别的数据结构一样插入和删除

这是string的插入接口函数,可以看到一共有6个重载一个模板,但实际只需要记住两种用法就可以了

string s("Apex Legends");
s.insert(4, "Rainbow");//在下标为4的地方插入"Rainbow"(只能插入字符串)
cout << s << endl;
s.insert(s.begin(), 'L');//在最前面插入一个字符'L'(只能插入单个字符)
cout << s << endl;
s.insert(5, "Zelda", 3);//在下标为5的地方插入"Zelda"的前3个字符(只能插入字符串)(了解即可)
cout << s << endl;

string s("Apex Legends");
s.erase(2, 3);//在下标为2的地方删除3个字符
cout << s << endl;
s.erase(2);//如果不给第2个参数,缺省值是npos,也就是把下标为2以后的字符都删除
cout << s << endl;

第一个erase上两个参数都有缺省值,如果第2个参数不给,就代表删除从pos开始的所有字符,如果第1个参数也不给,就代表删除整个字符串

c_str:

string其实和C语言中的字符串还是不一样的,我们来看下面代码

string s1("Hello ");
s1+='\0';
s1+="World";
cout << s1 << endl;
cout << s1.c_str() << endl;

可以看到在给s1初始化为Hello了之后,又给后面加了\0World,那这个时候输出的结果是

Hello World
Hello

第一行输出的是完整的这就是c_str的作用了,可以将string转换成c语言类型的字符串进行输出,即char*,而C语言的字符串是以\0作为结束标志,所以只会输出到Hello,而cout会输出string类中的全部字符,所以\0后面的也会输出

find()和substr:

顾名思义,是用来找字符的,返回的是该字符所在的下标 ,如果没有找到,就返回npos(string能表示的最大字符长度)

string s1("text.cpp");
string s2("string.c");
string s3("string.txt");

如果现在让你分别输出这三个字符串中的文件格式后缀,就可以先用find()找到.所在的位置,然后再输出.以及以后的字符

那怎么样输出.以及以后的字符呢

substr()就可以输出子串, 第一个参数是定义从下标多少开始输出,第二个参数则是定义输出多少个

string s1("text.cpp");
string s2("string.c");
string s3("string.txt");
size_t pos1 = s1.find('.');
if(pos1 != string::npos)//先确保找到了,再输出
    cout << s1.substr(pos1) << endl;
size_t pos2 = s2.find('.');
if(pos2 != string::npos)
    cout << s2.substr(pos2) << endl;
size_t pos3 = s3.find('.');
if(pos3 != string::npos)
    cout << s3.substr(pos3) << endl;

那如果是text.cpp.rar这种呢?要怎么找到rar而不是cpp.rar?

这时候就要拿出另外一个函数了

rfind也就是reverse_find的意思,就是从后往前找

string s1("text.cpp.rar");
string s2("string.c.txt");
string s3("string.txt.c");
size_t pos1 = s1.rfind('.');
if(pos1 != string::npos)//先确保找到了,再输出
    cout << s1.substr(pos1) << endl;
size_t pos2 = s2.rfind('.');
if(pos2 != string::npos)
    cout << s2.substr(pos2) << endl;
size_t pos3 = s3.rfind('.');
if(pos3 != string::npos)
    cout << s3.substr(pos3) << endl;

把之前的find换成rfind就可以了

string url("https://cplusplus.com/reference/string/string/rfind/");

现在需要把这个url的协议,域名和资源名称分别分离出来换行输出,就可以用到上面学到的find和substr来完成 

string url("https://cplusplus.com/reference/string/string/rfind/");
size_t pos1 = url.find(':');
if(pos1 != string::npos)
    cout << url.substr(0,pos1) << endl;//输出协议
size_t pos2 = url.find('/',pos1+3);
if(pos2 != string::npos)
    cout << url.substr(pos1+3,pos2-(pos1+3)) << endl;//输出域名
cout << url.substr(pos2+1);//输出资源名称

getline():

 

在讲getline之前,我们先来看一下这道题

int main()
{
    string s1;
    cin >> s1;
    int pos = s1.rfind(' ');//总后往前找空格,就能找到最后一个单词的开头
    //如果只输入了一个单词,rfind找不到空格,就会返回-1(npos的值)
    cout << s1.size()-(pos+1);//pos+1就是截止到最后一个空格时的长度(pos=-1时也适用)
    return 0;
}

 乍一看没什么问题,但当程序运行起来,如果只输入了一个单词,那结果还是可以正常输出的,但只要是输入了超过一个单词的字符串,不管怎么样,它输出的长度都是第一个单词的长度,这是怎么回事?

在C语言里,我们都知道scanf在输入时是以空格,tab或enter作为结束符,cin其实也一样,所以在输入了第一个单词并且空格后,后面的单词就没有意义了。

在C语言中要想解决这个问题,一般都会用gets(已经被废除)或fgets,在C++中则是用getline,它在输入时只会以换行符作为结束标志

int main()
{
    string s1;
    getline(cin,s1);
    int pos = s1.rfind(' ');//总后往前找空格,就能找到最后一个单词的开头
    //如果只输入了一个单词,rfind找不到空格,就会返回-1(npos的值)
    cout << s1.size()-(pos+1);//pos+1就是截止到最后一个空格时的长度(pos=-1时也适用)
    return 0;
}

 只需要把第二行的cin换成getline这种格式,就可以读取空格了


http://www.kler.cn/a/563232.html

相关文章:

  • Linux 权限系统和软件安装(二):深入理解 Linux 权限系统
  • Redis 高可用性:如何让你的缓存一直在线,稳定运行?
  • HTTP非流式请求 vs HTTP流式请求
  • Linux系统之DHCP网络协议
  • 深入探讨K8s资源管理和性能优化
  • Python 网络编程全攻略:核心知识与实战应用、高级应用场景、问题剖析、行业未来趋势等全解析
  • SpringBoot接入DeepSeek(硅基流动版)+ 前端页面调试
  • 【论文笔记-ECCV 2024】AnyControl:使用文本到图像生成的多功能控件创建您的艺术作品
  • 二十三种设计模式详解
  • 一周掌握Flutter开发--4、导航与路由
  • 清华大学DeepSeek赋能职场教程下载,清华大学DeepSeek文档下载(完成版下载)
  • 银河麒麟高级服务器操作系统通用rsync禁止匿名访问操作指南
  • RIP-AV:使用上下文感知网络进行视网膜动脉/静脉分割的联合代表性实例预训练
  • 【深度学习神经网络学习笔记(三)】向量化编程
  • python想学好你一定要掌握已下知识(新手)
  • RK3588开发板本地部署DeepSeek-R1
  • 向量数据库milvus部署
  • React 高阶组件教程
  • 链表的奇偶重排(C++)
  • wordpress使用CorePress主题设置项总结