C++中的STL简介与string类

目录

STL简介

STL的版本

STL的六大组件

string类

标准库中的string类

string类的常用接口

string类对象对容量的操作

size()函数与length()函数

capacity()函数

capacity的扩容方式

reserve()函数

resize()函数

string类对象的操作

push_back()函数

append()函数

operator+=()函数(+=运算符重载函数)

operator+()函数(+运算符重载函数)

assign()函数

insert()函数

erase()函数

replace()函数

find()函数

swap()函数

c_str()函数

substr()函数

rfind()函数

find_first_of()函数

relational operators系列函数

getline()函数

string类对象的访问及遍历操作

operator[]与at()函数

string类迭代器遍历

正向遍历

逆向遍历


STL简介

STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架

STL的版本

  1. 原始版本

Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使用。 HP 版本--所有STL实现版本的始祖。

  1. P. J. 版本

由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低,符号命名比较怪异。

  1. RW版本

由Rouge Wage公司开发,继承自HP版本,被C+ + Builder 采用,不能公开或修改,可读性一般。

  1. SGI版本

由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版 本。被GCC(Linux)采用,可移植性好,可公开、修改甚至贩卖,从命名风格和编程 风格上看,阅读性非常高。

STL的六大组件

📌

在上面的图中,空间配置器实际上就是在内存池中申请空间。另外,string类型本不属于STL,因为出现得早,没被划分到STL中,但是其接口基本上和STL中其他容器的接口类似,故将其列入上图中的STL容器部分

string类

在C语言中,字符串是以'\0'结尾的字符数组,为了操作方便,C语言还提供了和字符串相关的函数放在string.h中,但是在C语言中,函数和字符串类型是分割的,不符合面向对象编程的思想,并且在操作字符串的过程中还需要注意防止越界

标准库中的string类

  1. 字符串是表示字符序列的类
  2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作
  3. 单字节字符字符串的设计特性
  4. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信息,请参阅basic_string)
  5. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traitsallocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)
📌

注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如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类的常用接口

构造函数

函数原型

无参构造函数

string();

拷贝构造函数

string (const string& str);

子串构造函数

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

使用字符串构造函数

string (const char* s);

使用部分字符串构造函数字符

string (const char* s, size_t n);

//无参构造函数
#include <iostream>
using namespace std;int main()
{string s1;return 0;
}//使用字符串构造函数
#include <iostream>
using namespace std;int main()
{string s2("hello world");cout << s2 << endl;return 0;
}
输出结果:
hello world//使用部分字符串构造函数
#include <iostream>
using namespace std;int main()
{string s3("hello world", 5);cout << s3 << endl;return 0;
}
输出结果:
hello//子串构造函数
#include <iostream>
using namespace std;int main()
{string s4 = "hello world";//当构造函数没有explicit时,可以使用赋值string s5(s4, 0, 7);cout << s5 << endl;return 0;
}
输出结果:
hello w
💡

对于函数string (const string& str, size_t pos, size_t len = npos);来说,pos为起始位置,len为需要取的字符串的长度,如果len参数大于字符串的可提供的长度,那么会一直取到字符串结尾;如果使用缺省值npos(缺省值为-1),那么同样会取到字符串结尾

string类也有析构函数~string()和赋值运算符重载函数

赋值运算符重载函数

string& operator= (const string& str);(对象赋值)

string& operator= (const char* s);(字符串赋值)

string& operator= (char c);(字符赋值)

string类对象对容量的操作

函数

功能

size()

返回字符串有效字符长度

length()

返回字符串有效字符长度

capacity()

返回当前为字符串开辟的空间大小

reserve()

为字符串预留指定的空间

resize()

将有效字符的个数修改成指定个数,多出的空间默认使用\0填充,也可以使用指定字符填充

size()函数与length()函数

📌

对于计算字符串有效字符长度的函数来说不会计算'\0',并且size()length()的效果相同

#include <iostream>
using namespace std;int main()
{string s1("hello world");cout << s1.size() << endl;cout << s1.length() << endl;return 0;
}
输出结果:
11
11

capacity()函数

#include <iostream>
using namespace std;int main()
{string s1 = "hello world";cout << s1.size() << endl;cout << s1.capacity() << endl;return 0;
}
输出结果:
11
15

在上面的代码中,size()capacity()并不相等,所以size()不等同于capacity(),可以类比到数据结构中顺序表的有效数据个数sizecapacity

capacity的扩容方式

在VS环境下面,运行下面的代码可以大致估算得到capacity的扩容倍数

#include <iostream>
using namespace std;int main()
{string s = "";size_t sz = s.capacity();cout << "start:" << 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';}}return 0;
}
输出结果:
start:15
making s grow:
capacity changed: 31
capacity changed: 47
capacity changed: 70
capacity changed: 105
💡

计算capacity时不会计算\0所存在的空间

在上面的代码中,因为并没有计算\0所在的空间,所以实际结果为:

start:16
making s grow:
capacity changed: 32
capacity changed: 48
capacity changed: 71
capacity changed: 106

观察到,除了第一次和第二次以外,其余均为大致1.5倍增长,但是第一次和第二次的增长并不是2倍,而是因为VS将第一次存储字符串的位置分成了两部分,第一个部分是_buf[16]数组,第二个部分是*ptr,指向超过16个字符时存储的动态开辟的内存空间

对于下面的代码中,有一个11个字符(不包括\0)的字符串

#include <iostream>
using namespace std;int main()
{string s = "hello world";size_t sz = s.capacity();cout << "start:" << 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';}}return 0;
}
输出结果:
start:15
making s grow:
capacity changed: 31
capacity changed: 47
capacity changed: 70
capacity changed: 105
capacity changed: 157

此时hello world\0存储的位置在_buf[16]数组中

如果字符串的长度大于16时,则不会存储到_buf[16]中,而是存储到ptr指针指向的内存空间

可以推测出,在设计string类时,底层的成员变量有下面几种

class string
{
private:char _buf[16];//长度为16的数组char* ptr;//指向存储char类型元素的空间size_t size;size_t capacity;
};

所以在VS下,第一次和第二次之间是两倍的关系并不是扩容两倍,而是改变了存储位置

reserve()函数

使用reserve()函数可以为字符串预留指定大小的空间,此时当指定大小远大于前面扩容的大小时一般不会再进行扩容

#include <iostream>
using namespace std;int main()
{string s = "hello world";s.reserve(100);size_t sz = s.capacity();cout << "start:" << 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';}}return 0;
}
输出结果:
start:111
making s grow:
📌

在VS下,使用reserve()函数指定的大小一般会被编译器扩大一小部分,但是在GCC下指定多少就是多少,所以在上面的代码中,虽然指定的大小为100,但是实际的大小为111

但是不论时哪种编译平台,都要至少保证开辟的大小不能小于指定大小

使用reserve()函数时,如果已经有空间时,对原始空间进行扩容时不会改变有效字符长度size值,只会改变capacity的值,从而达到扩容的效果

📌

注意,若扩容的值小于原始空间时,那么此时reserve()函数不起任何效果

#include <iostream>
using namespace std;int main()
{string s = "hello world";size_t sz = s.capacity();cout << "start_capacity:" << sz << '\n';cout << "start_size:" << s.size() << '\n';s.reserve(200);cout << "after_capacity:" << s.capacity() << '\n';cout << "after_size:" << s.size() << '\n';return 0;
}
输出结果:
start_capacity:15
start_size:11
after_capacity:207
after_size:11
📌

在扩容之后,尽管原始字符串的大小小于_buf[16]数组的长度,也会被移动到ptr指向的空间,如图所示

执行reserve()函数前:

执行reserve()函数后:

resize()函数

不同于reserve()函数,使用resize()函数可以将原始空间扩容并且初始化扩容的空间,默认初始化字符为\0,当指定为某一个字符后,则将初始化对应字符

#include <iostream>
using namespace std;int main()
{string s = "hello world";cout << "start_capacity:" << s.capacity() << '\n';cout << "start_size:" << s.size() << '\n';s.resize(200);cout << "after_capacity:" << s.capacity() << '\n';cout << "after_size:" << s.size() << '\n';return 0;
}
输出结果:
start_capacity:15
start_size:11
after_capacity:207
after_size:200

在上面的代码中,使用resize()函数时会同时改变size的大小和capacity的大小,size的大小刚好为resize()函数的参数值,并且默认将其余部分初始化为\0

也可以指定某一个字符

#include <iostream>
using namespace std;int main()
{string s = "hello world";cout << "start_capacity:" << s.capacity() << '\n';cout << "start_size:" << s.size() << '\n';s.resize(200, '*');cout << "after_capacity:" << s.capacity() << '\n';cout << "after_size:" << s.size() << '\n';return 0;
}

📌

使用resize()函数还可以达到删除字符的效果,当扩容值参数的大小小于已经存在的字符串的长度时

#include <iostream>
using namespace std;int main()
{string s = "hello world";cout << "start_capacity:" << s.capacity() << '\n';cout << "start_size:" << s.size() << '\n';s.resize(5);cout << s << endl;cout << "after_capacity:" << s.capacity() << '\n';cout << "after_size:" << s.size() << '\n';return 0;
}
输出结果:
start_capacity:15
start_size:11
hello
after_capacity:15
after_size:5
📌

注意,尽管使用resize()函数后改变了size的大小,但是对于capacity来说,一旦在开始确定好了大小,那么使用resize()改变size的值使其小于已经设定的值,此时不会改变capacity的大小,因为缩容是非法的,如果支持缩容,那么证明可以支持free释放部分空间

string类对象的操作

此处展示部分可能用得到的成员函数,全部成员函数参考文档:string - C++ Reference (cplusplus.com)

函数

功能

函数原型

push_back

在字符串后尾插指定字符

void push_back (char c);

append

在字符串后追加一个字符串

string& append (const char* s);(常用)

operator+=

在字符串后追加字符串或字符

string& operator+= (const string& str);(追加string类对象)

string& operator+= (const char* s);

(追加字符串)

string& operator+= (char c);

(追加字符)

push_back()函数

使用push_back()函数可以在字符串后追加字符

#include <iostream>
using namespace std;int main()
{string s = "hello";s.push_back('w');cout << s << endl;return 0;
}
输出结果:
hellow

append()函数

使用append()函数可以在字符串后追加字符串

#include <iostream>
using namespace std;int main()
{string s = "hello";s.append(" world");cout << s << endl;return 0;
}
输出结果:
hello world

operator+=()函数(+=运算符重载函数)

使用operator+=()函数可以达到append()push_back()的效果

#include <iostream>
using namespace std;int main()
{string s = "hello world";string s1 = "CPP";//追加字符s += 'c';cout << s << endl;//追加字符串s = "hello";s += " world";cout << s << endl;//追加对象s = "hello ";s += s1;cout << s << endl;return 0;
}
输出结果:
hello worldc
hello world
hello CPP

operator+()函数(+运算符重载函数)

使用operator+()函数可以将两个字符串相加(即合并但不改变原来的字符串)

函数

函数原型

operator+()

string operator+ (const string& lhs, const string& rhs);

(将两个string类对象的字符串相加)

string operator+ (const string& lhs, const char* rhs);

string operator+ (const char* lhs, const string& rhs);

(将字符串和对象的字符串相加)

string operator+ (const string& lhs, char rhs);

string operator+ (char lhs, const string& rhs);

(将字符和对象的字符串相加)

#include <iostream>
using namespace std;int main()
{string s1 = "hello";string s2 = " world";string s3 = s1 + s2;cout << s1 << endl;cout << s2 << endl;cout << s3 << endl;return 0;
}
输出结果:
helloworld
hello world

assign()函数

使用assign()函数可以将一个对象或者字符串赋值给另一个对象

函数

函数原型

assign()

string& assign (const string& str);(对象赋值给调用对象)

string& assign (const string& str, size_t subpos, size_t sublen = npos);(赋值对象中的部分字符给调用对象)

string& assign (const char* s);(将字符串赋值给调用对象)

string& assign (const char* s, size_t n);(将部分字符串赋值给调用对象)

string& assign (size_t n, char c);(使用字符c填充调用对象的空间)

//对象赋值给调用对象
#include <iostream>
using namespace std;int main()
{string s1 = "hello";string s2("world");s1.assign(s2);//将s2中的内容覆盖s1对象中的内容cout << s1 << endl;return 0;
}
输出结果:
world//赋值对象中的部分字符给调用对象
#include <iostream>
using namespace std;int main()
{string s1 = "world";string s2 = "Linux";s1.assign(s2, 2, 2);//从第二个位置开始赋值2个字符给s1对象(包括第二个位置的字符)cout << s1 << endl;return 0;
}
输出结果:
nu//将字符串赋值给调用对象
#include <iostream>
using namespace std;int main()
{string s1 = "xxxxxxx";cout << s1 << endl;s1.assign("hello world");cout << s1 << endl;return 0;
}
输出结果:
xxxxxxx
hello world//将部分字符串赋值给调用对象
#include <iostream>
using namespace std;int main()
{string s1 = "xxxxxx";s1.assign("hello world", 5);//默认从0开始,拷贝5个字符包括0位置的字符cout << s1 << endl;return 0;
}
输出结果:
hello//使用字符c填充调用对象的空间
#include <iostream>
using namespace std;int main()
{string s1 = "xxxxx";s1.assign(5, 'C');cout << s1 << endl;return 0;
}
输出结果:
CCCCC

insert()函数

使用insert()函数可以指定插入某个内容到字符串指定位置中

📌

在插入过程中会涉及挪动数据以及扩容问题,所以不建议大量使用

函数

函数原型

insert()

string& insert (size_t pos, const string& str);

(在调用对象的字符串pos位置中插入对象中的字符串)

string& insert (size_t pos, const string& str, size_t subpos, size_t sublen = npos);

(在调用对象的pos位置插入对象中subpos位置开始的指定个数的字符串)

string& insert (size_t pos, const char* s);(在调用对象pos位置插入字符串s

string& insert (size_t pos, const char* s, size_t n);

(在调用对象pos位置插入指定个数个字符串中的字符)

string& insert (size_t pos, size_t n, char c);(在调用对象pos位置插入n个字符c

void insert (iterator p, size_t n, char c);(迭代器p位置开始插入n个字符c

iterator insert (iterator p, char c);(在调用对象迭代器p指向位置插入字符c

📌

注意插入字符串时不会覆盖原来pos位置存在的字符,原来pos位置及以后的字符向后移动

//在调用对象的字符串pos位置中插入对象中的字符串
#include <iostream>
using namespace std;int main()
{string s1 = "hello-world";string s2 = " my ";s1.insert(5, s2);cout << s1 << endl;return 0;
}
输出结果:
hello my -world//在调用对象的pos位置插入对象中subpos位置开始的指定个数的字符串
#include <iostream>
using namespace std;int main()
{string s1 = "hello world";string s2 = "hello Linux";s1.insert(5, s2, 5, 6);cout << s1 << endl;return 0;
}
输出结果:
hello Linux world//在调用对象pos位置插入字符串s
#include <iostream>
using namespace std;int main()
{string s1 = "hello world";s1.insert(6, "CPP ");cout << s1 << endl;return 0;
}
输出结果:
hello CPP world//在调用对象pos位置插入指定个数个字符串中的字符
#include <iostream>
using namespace std;int main()
{string s1 = "hello world";s1.insert(6, "CPP is", 4);cout << s1 << endl;return 0;
}
输出结果:
hello CPP world//迭代器p位置开始插入n个字符c
#include <iostream>
using namespace std;int main()
{string s1 = "hello world";s1.insert(s1.begin() + 6, 3, 'c');//在下标6位置开始连续插入字符'c'cout << s1 << endl;return 0;
}
输出结果:
hello cccworld//在调用对象迭代器p指向位置插入字符c
#include <iostream>
using namespace std;int main()
{string s1 = "hello world";s1.insert(s1.begin() + 5, 'c');//在下标5位置插入字符'c'cout << s1 << endl;return 0;
}
输出结果:
helloc world

erase()函数

使用erase()函数可以删除指定对象中的内容

函数

函数原型

erase()

string& erase (size_t pos = 0, size_t len = npos);

(删除调用对象pos位置开始len长度的字符)

iterator erase (iterator p);

(删除迭代器p指向位置的字符)

📌

因为删除字符会涉及到挪动数据,所以不推荐大量使用erase()函数

//删除调用对象pos位置开始len长度的字符
#include <iostream>
using namespace std;int main()
{string s1 = "hello world";s1.erase(5, 3);cout << s1 << endl;return 0;
}
输出结果:
hellorld//删除迭代器p指向位置的字符
#include <iostream>
using namespace std;int main()
{string s1 = "hello world";s1.erase(s1.begin() + 5);cout << s1 << endl;return 0;
}
输出结果:
helloworld

replace()函数

使用replace()函数可以替换调用对象的字符串中的字符

📌

因为替换的字符串可能比被替换的字符串长,所以可能涉及到空间的扩容和数据的移动,不推荐大量使用replace()函数

函数

函数功能

replace()

string& replace (size_t pos, size_t len, const string& str);

(使用对象中的字符串替换调用对象pos位置开始len长度的字符串)

string& replace (size_t pos, size_t len, const string& str, size_t subpos, size_t sublen);

(使用对象中的subpos位置开始的sublen长度的字符串替换调用对象pos位置开始的len长度的字符串)

string& replace (size_t pos, size_t len, const char* s);

(使用字符串s替换调用对象pos位置开始的len长度的字符串)

string& replace (size_t pos, size_t len, const char* s, size_t n);

(使用字符串中的n个字符替换调用对象pos位置开始的len长度的字符串)

string& replace (size_t pos, size_t len, size_t n, char c);

(使用n个字符替换调用对象pos位置开始的len长度的字符串)

//使用对象中的字符串替换调用对象pos位置开始len长度的字符串
#include <iostream>
using namespace std;int main()
{string s1 = "hello world";string s2 = "Linux";s1.replace(6, 5, s2);cout << s1 << endl;return 0;
}
输出结果:
hello Linux//使用对象中的subpos位置开始的sublen长度的字符串替换调用对象pos位置开始的len长度的字符串
#include <iostream>
using namespace std;int main()
{string s1 = "hello world";string s2 = "hello CPP";s1.replace(6, 3, s2, 6, 3);cout << s1 << endl;s1.replace(6, 5, s2, 6, 3);cout << s1 << endl;return 0;
}
输出结果:
hello CPPld
hello CPP//使用字符串s替换调用对象pos位置开始的len长度的字符串
#include <iostream>
using namespace std;int main()
{string s1 = "hello world";s1.replace(6, 3, "Linux");cout << s1 << endl;return 0;
}
输出结果:
hello Linuxld//使用字符串中的n个字符替换调用对象pos位置开始的len长度的字符串
#include <iostream>
using namespace std;int main()
{string s1 = "hello world";s1.replace(6, 3, "Linux", 3);cout << s1 << endl;return 0;
}
输出结果:
hello Linld//使用n个字符替换调用对象pos位置开始的len长度的字符串
#include <iostream>
using namespace std;int main()
{string s1 = "hello world";s1.replace(6, 5, 5, 'c');cout << s1 << endl;return 0;
}
输出结果:
hello ccccc

find()函数

使用fine()函数可以在string对象中从前往后找某一个字符或者字符串

函数

函数原型

string()

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

(在调用对象的字符串中从pos位置开始找出现对象字符串的开始位置)

size_t find (const char* s, size_t pos = 0) const;

(在调用对象的字符串中从pos位置找出出现字符串的开始位置)

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

(在调用对象的字符串中从pos位置开始找字符串s中的n个字符的开始位置)

size_t find (char c, size_t pos = 0) const;

(在调用对象的字符串中从pos位置开始找字符c的开始位置)

//在调用对象的字符串中从pos位置开始找出现对象字符串的开始位置
#include <iostream>
using namespace std;int main()
{string s1 = "This is a test string: hello world.";string s2 = "test";size_t index = s1.find(s2);cout << index << endl;return 0;
}
输出结果:
10//在调用对象的字符串中从pos位置找出出现字符串的开始位置
#include <iostream>
using namespace std;int main()
{string s1 = "This is a test string: hello world.";size_t pos = s1.find("is");while (pos != string::npos){cout << pos << endl;pos = s1.find("is", pos + 1);//注意+1更改pos位置防止死循环}return 0;
}
输出结果:
2
5//在调用对象的字符串中从pos位置开始找字符串s中的n个字符的开始位置
#include <iostream>
using namespace std;int main()
{string s1 = "this is a test string: hello world.";size_t pos = s1.find("this is a test string", 0, 1);while (pos != string::npos){cout << pos << " ";pos = s1.find("this is a test string", pos + 1, 1);}return 0;
}
输出结果:
0 10 13 16//在调用对象的字符串中从pos位置开始找字符c的开始位置
//替换对象的字符串中的空格为'--'
#include <iostream>
using namespace std;int main()
{string s1 = "this is a test string: hello world.";size_t pos = s1.find(' ');while (pos != string :: npos){s1.replace(pos, 1, "--");pos = s1.find(' ');//因为替换了空格,所以每一次从头找时不会死循环}cout << s1 << endl;return 0;
}
输出结果:
this--is--a--test--string:--hello--world.

针对第三个题目做出的一点优化(包含之前的接口函数的使用)

第一种优化:

#include <iostream>
using namespace std;int main()
{string s1 = "this is a test string: hello world.";size_t pos = s1.find(' ');//第一优化思路:提前开辟足够大小的空间避免replace的扩容//统计空格的个数int count = 0;for (auto ptr : s1){if (ptr == ' '){count++;}}s1.reserve(s1.size() + count);//因为替换的字符由两个,去掉替换的空格的位置之后多出来的空间即为需要的额外空间while (pos != string::npos){s1.replace(pos, 1, "--");pos = s1.find(' ', pos + 2);//第二优化思路:因为空格被替换为了--,为了使pos指向下一个要找的位置//直接让pos跳过替换字符个数个下标}cout << s1 << endl;return 0;
}
输出结果:
this--is--a--test--string:--hello--world.

第二种优化思路:以空间换时间

#include <iostream>
using namespace std;int main()
{string s1 = "this is a test string: hello world.";string newString;for (auto ptr : s1){if (ptr != ' '){newString += ptr;}else{newString += "--";}}cout << newString << endl;return 0;
}
输出结果:
this--is--a--test--string:--hello--world.

swap()函数

使用swap()函数可以交换调用对象的字符串和另一个对象中的字符串

📌

对于任意类型,标准库中还有个全局函数swap(),对比于string类中的swap()来说,类string中的swap()函数执行效率会被全局函数swap()效率更高,因为全局的swap()函数在拷贝过程中会调用对应对象类的拷贝构造函数,此时会有额外的空间和时间消耗

全局函数swap()定义:

在前面推测过字符串存储的位置可能是数组或者指针,但是不论是那种,实际上都是地址,只要改变指向两个字符串的指针的指向即可完成交换,所以效率会比全局函数的swap()

函数

函数原型

swap()

void swap (string& str);

#include <iostream>
using namespace std;int main()
{string s1 = "hello world";string s2 = "hello Linux";cout << "交换前:" << s1 << endl;cout << "交换前:" << s2 << endl;s1.swap(s2);cout << "交换后:" << s1 << endl;cout << "交换后:" << s2 << endl;return 0;
}
输出结果:
交换前:hello world
交换前:hello Linux
交换后:hello Linux
交换后:hello world

c_str()函数

使用c_str()函数可以按照C语言字符串的形式返回调用对象中的字符串

函数

函数原型

c_str()

const char* c_str() const;

#include <iostream>
using namespace std;int main()
{string s1("hello world");s1 += '\0';s1 += '\0';s1 += "-----";//s1 += "--\0----";//注意这样子写编译器会认为是\0字符串的终止,后面内容不会读入cout << s1 << endl;//调用重载的operator<<函数打印自定义类型cout << s1.c_str() << endl;//使用内置<<打印,按照size的大小打印cout << (void*)s1.c_str() << endl;//打印字符串所在地址,找\0结束return 0;
}
输出结果:
hello world-----
hello world
0000016D82281D70

使用场景:

//打开文件
#include <iostream>
#include <cassert>
using namespace std;int main()
{string filename = "text.txt";FILE* fout = fopen(filename.c_str(), "w");assert(fout);return 0;
}

substr()函数

使用substr()函数可以截取字符串中的某一部分字符串作为子字符串返回

函数

函数原型

subStr()

string substr (size_t pos = 0, size_t len = npos) const;

//找文件的后缀名
#include <iostream>
using namespace std;int main()
{string s1 = "text.txt";//先找到后缀点size_t pos = s1.find('.');//再取pos位置开始的字符到结尾string suffix = s1.substr(pos);cout << suffix << endl;return 0;
}
输出结果:
.txt//找网站域名
#include <iostream>
using namespace std;int main()
{string s1 = "https://blog.csdn.net/m0_73281594?spm=1010.2135.3001.5343";//首先跳过网站协议部分size_t pos = s1.find("//");//再找网站第二个/size_t pos1 = s1.find('/', pos + 2);//取域名string web = s1.substr(pos + 2, pos1 - (pos + 2));cout << web << endl;return 0;
}
输出结果:
blog.csdn.net

rfind()函数

使用rfinde()函数可以从字符串的最后一个字符向前找指定字符或字符串

函数

函数原型(与find()类似)

rfind()

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

size_t rfind (const char* s, size_t pos = npos) const;

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

size_t rfind (char c, size_t pos = npos) const;

//使用rfind()函数优化找文件后缀
#include <iostream>
using namespace std;int main()
{string s1 = "test.txt.tar.zip";//找最后一个后缀点位置size_t pos = s1.rfind('.');//取pos位置开始到结尾的字符串string suffix = s1.substr(pos);cout << suffix << endl;return 0;
}
输出结果:
.zip

find_first_of()函数

使用find_first_of()函数可以在对象字符串中匹配指定字符串中的内容

函数

函数原型

find_first_of()

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

(从pos位置开始在调用对象的字符串中匹配对象的字符串中的字符)

size_t find_first_of (const char* s, size_t pos = 0) const;

(从pos位置开始在调用对象的字符串中匹配字符串中的字符)

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

(从pos位置开始,在调用对象的字符串中匹配字符串中n位置之前的字符串的字符)

size_t find_first_of (char c, size_t pos = 0) const;

(从pos位置开始,在调用对象的字符串中匹配字符c

//从pos位置开始在调用对象的字符串中匹配对象的字符串中的字符
//返回aeiou五个字符在字符串中出现的下标位置
#include <iostream>
using namespace std;int main()
{string s1 = "this is a test string: hello world.";string s2 = "aeiou";size_t index[50] = {0};size_t count = 0;int i = 0;while (count != string::npos){count = s1.find_first_of(s2, count + 1);index[i++] = count;}for (auto num : index){if (num != 0 && num != string::npos){cout << num << " ";}}return 0;
}
输出结果:
2 5 8 11 18 24 27 30//从pos位置开始,在调用对象的字符串中匹配字符串中的n个字符
#include <iostream>
using namespace std;int main()
{string s1 = "this is a test string: hello world.";size_t index[50] = { 0 };size_t count = 0;int i = 0;while (count != string::npos){count = s1.find_first_of("iscpp", count + 1, 2);index[i++] = count;}for (auto num : index){if (num != 0 && num != string::npos){cout << num << " ";}}return 0;
}
输出结果:
2 3 5 6 12 15 18

同样的函数还有:

  1. find_last_of()函数:在字符串中匹配指定字符串的字符最后一次在原始字符串中的位置
  2. find_first_not_of()函数:在字符串中找出指定字符串中不存在的字符第一次出现的位置
  3. find_last_not_of()函数:在字符串中找出指定字符串中不存在的字符最后一次在原始字符串中的位置

relational operators系列函数

比较字符串之间关系的运算符:(底层调用compare()函数)

函数

函数原型

operator==()

bool operator== (const string& lhs, const string& rhs);
bool operator== (const char* lhs, const string& rhs);
bool operator== (const string& lhs, const char* rhs);

(判断两个字符串/对象的字符串是否相等)

operator!=()

bool operator!= (const string& lhs, const string& rhs);

bool operator!= (const char* lhs, const string& rhs);

bool operator!= (const string& lhs, const char* rhs);

(判断两个字符串/对象的字符串是否不相等)

operator<()

bool operator< (const string& lhs, const string& rhs);
bool operator< (const char* lhs, const string& rhs);
bool operator< (const string& lhs, const char* rhs);

(判断第一个字符串/对象的字符串是否小于第二个字符串/对象的字符串)

operator<=()

bool operator<= (const string& lhs, const string& rhs);

bool operator<= (const char* lhs, const string& rhs);

bool operator<= (const string& lhs, const char* rhs);

(判断第一个字符串/对象的字符串是否小于等于第二个字符串/对象的字符串)

operator>()

bool operator> (const string& lhs, const string& rhs);

bool operator> (const char* lhs, const string& rhs);

bool operator> (const string& lhs, const char* rhs);

(判断第一个字符串/对象的字符串是否大于第二个字符串/对象的字符串)

operator>=()

bool operator>= (const string& lhs, const string& rhs);

bool operator>= (const char* lhs, const string& rhs);

bool operator>= (const string& lhs, const char* rhs);

(判断第一个字符串/对象的字符串是否大于等于第二个字符串/对象的字符串)

getline()函数

使用getline()函数可以获取到指定字符(默认'\n')结尾之前的字符串

📌

不同于cincin遇到空白字符时就会停止读入,getline()函数遇到'\n'(或者指定字符)才结束

函数

函数原型

getline()

istream& getline (istream& is, string& str, char delim);

(获取以字符delim结尾之前的字符串放入对象str中)

istream& getline (istream& is, string& str);

(获取以字符'\n'结尾之前的字符串放入对象str中)

#include <iostream>
#include <string>
using namespace std;int main()
{string s1;string s2;getline(cin, s1);cin >> s2;cout << s1 << endl;cout << s2 << endl;return 0;
}
输入:
hello world[Enter]
hello world[Enter]
输出结果:
hello world
hello
📌

使用getline()函数需要包头文件string

getline()函数的使用练习:字符串最后一个单词的长度_牛客题霸_牛客网 (nowcoder.com)

参考代码:

#include <cstddef>
#include <iostream>
#include <string>
using namespace std;int main() {string str;getline(cin, str);//倒着找空格size_t pos = str.rfind(' ');//取出字符if(pos != string::npos){string str1 = str.substr(pos);cout << str1.size() - 1 << endl;}else{cout << str.size()<< endl;}
}

string类对象的访问及遍历操作

函数

功能

operator[]

返回pos位置的字符,const string类对象调用

begin+end

begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器

范围for

C++11支持更简洁的范围for的新遍历方式

at()

访问字符串中指定位置的字符

📌

注意迭代器中的end为最后一个字符的下一个位置,一般指向\0,形成左闭右开区间

#include <iostream>
using namespace std;int main()
{string s = "hello world";//下标遍历for (size_t i = 0; i < s.size(); i++){cout << s[i] << " ";}cout << endl;//迭代器遍历//获取起始地址for (string::iterator ptr = s.begin(); ptr != s.end(); ptr++){cout << *ptr << "-";}cout << endl;//范围forfor (auto ptr : s){cout << ptr << "*";}cout << endl;//at()函数for (int i = 0; i < s1.size(); i++){cout << s1.at(i) << " ";}return 0;
}
输出结果:
h e l l o   w o r l d
h-e-l-l-o- -w-o-r-l-d-
h*e*l*l*o* *w*o*r*l*d*
h e l l o   w o r l d

在上面的代码中是,使用了三种遍历字符串的方式,但是实际上常用的方式为下标访问的方式,并且范围for的本质也是迭代器,auto ptr本质就是调用s.begin()函数给string : iterator ptr,s的本质即为s.end(),通过汇编代码可以验证这一点

直接使用迭代器:

范围for

operator[]at()函数

对于string类来说,可以使用下标进行访问

#include <iostream>
using namespace std;int main()
{string s = "hello world";//下标遍历for (size_t i = 0; i < s.size(); i++){cout << s[i] << " ";}cout << endl;//at()函数for (int i = 0; i < s1.size(); i++){cout << s1.at(i) << " ";}return 0;
}
输出结果:
h e l l o   w o r l d
h e l l o   w o r l d

operator[]at()函数均可以使用下标的形式访问字符串,但是at()函数可以在对字符串进行越界访问时抛出异常,而不是像operator[]越界访问一样断言终止程序

//operator[]
#include <iostream>
using namespace std;int main()
{string s1 = "hello world";cout << s1[100] << endl;return 0;
}

operator[]越界访问断言报错:

#include <iostream>
using namespace std;int main()
{string s1 = "hello world";try{//使用try_catch捕获越界访问异常cout << s1.at(100) << endl;}catch (const exception& e){cout << e.what() << endl;}return 0;
}
输出结果:
invalid string position

string类迭代器遍历

正向遍历

  • 使用beginend可以从前往后遍历字符串,使用迭代器iterator

函数

函数原型

begin()

iterator begin();(针对于普通对象)

const_iterator begin() const;(针对于const对象)

end()

iterator end();(针对于普通对象)

const_iterator end() const;(针对于const对象)

#include <iostream>
using namespace std;int main()
{string s1 = "hello world";string::iterator ptr = s1.begin();while (ptr != s1.end()){cout << *ptr << " ";ptr++;}return 0;
}
输出结果:
h e l l o   w o r l d

在上面的代码中,因为begin()end()均返回iterator,所以迭代器类型需要选择iterator

对于const对象来说,需要使用const类型的迭代器

#include <iostream>
using namespace std;int main()
{const string s2 = "hello world";string::const_iterator cptr = s2.begin();while (cptr != s2.end()){cout << *cptr << " ";++cptr;}
}
输出结果:
h e l l o   w o r l d

在上面的代码中,使用了const类型的迭代器,但是注意该迭代器对于指针来说只是修饰指针指向的内容不可以修改,但是指针可以改变指向

📌

对于const修饰的迭代器来说,还可以使用cbegin()cend()

逆向遍历

  • 使用rbeginrend可以从后往前遍历字符串,此时使用的迭代器为reverse_iterator

函数

函数原型

rbegin()

reverse_iterator rbegin();(针对普通对象)

const_reverse_iterator rbegin() const;(针对const对象)

rend()

reverse_iterator rend();(针对普通对象)

const_reverse_iterator rend() const;(针对const对象)

#include <iostream>
using namespace std;int main()
{string s1 = "hello world";string::reverse_iterator ptr1 = s1.rbegin();while (ptr1 != s1.rend()){cout << *ptr1 << " ";ptr1++;}return 0;
}

在上面的代码中,使用rbegin()rend(),返回类型为reverse iterator,故迭代器类型需要选择reverse_iterator

📌

注意此时ptr1并不是--,而是++,因为此时的rbegin为字符串最后一个有效字符的前一个位置,而rend为字符串第一个有效字符的前一个位置,形成左闭右开

当使用迭代器的对象为const修饰时,迭代器需要同样使用const修饰的迭代器

#include <iostream>
using namespace std;int main()
{string::const_reverse_iterator cptr1 = s2.rbegin();while (cptr1 != s2.rend()){cout << *cptr1 << " ";cptr1++;}return 0;
}
输出结果:
d l r o w   o l l e h

在上面的代码中,使用了const类型的迭代器,但是注意该迭代器对于指针来说只是修饰指针指向的内容不可以修改,但是指针可以改变指向

📌

对于const修饰的迭代器来说,还可以使用crbegin()crend()

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LLM推理入门指南②:深入解析KV缓存

在本系列文章《LLM推理入门指南①&#xff1a;文本生成的初始化与解码阶段》中&#xff0c;作者对Transformer解码器的文本生成算法进行了高层次概述&#xff0c;着重介绍了两个阶段&#xff1a;单步初始化阶段&#xff0c;即提示的处理阶段&#xff0c;和逐个生成补全词元的多…

【Go】六、函数

文章目录 1、函数的定义2、内存分析3、注意点4、函数数据类型5、自定义数据类型&#xff08;起别名&#xff09;6、支持对返回值命名 1、函数的定义 语法&#xff1a; func 函数名&#xff08;形参列表)&#xff08;返回值类型列表&#xff09;{执行语句..return 返回值列…

HarmonyOS实战开发-Stage模型下Ability的创建和使用

介绍 本篇Codelab基于Stage模型&#xff0c;对Ability的创建和使用进行讲解。首先在课程中我们将带领大家使用DevEco Studio创建一个Stage模型Ability&#xff0c;并使用UIAbilityContext启动另一个Ability&#xff0c;然后借助Want&#xff0c;在Ability之间传递参数&#xf…

.Net 知识杂记

记录平日中琐碎的.net 知识点。不定期更新 目标框架名称(TFM) 我们创建C#应用程序时&#xff0c;在项目的工程文件(*.csproj)中都有targetFramework标签&#xff0c;以表示项目使用的目标框架 各种版本的TFM .NET Framework .NET Standard .NET5 及更高版本 UMP等 参考文档&a…

云主机8核16G配置租用优惠价格1198元1年、4688元三年

京东云8核16G租用优惠价格1198元1年、4688元三年&#xff0c;配置为8C16G-270G SSD系统盘-5M带宽-500G月流量&#xff0c;华北-北京地域。京东云8核16G服务器活动页面 atengyun.com/go/jd 京东云8核16G租用优惠价格 京东云&#xff1a;轻量云主机CPU内存&#xff1a;8C16G公网带…

从TCP/IP协议到socket编程详解

​ 我的所有学习笔记&#xff1a;https://github.com/Dusongg/StudyNotes⭐⭐⭐ ​ 文章目录 1 网络基础知识1.1 查看网络信息1.2 认识端口号1.3 UDP1.4 TCP1.4.1 确认应答机制1.4.2 TCP三次握手/四次挥手为什么是三次握手为什么是四次挥手listen 的第二个参数 backlog—— 全…

【MySQL探索之旅】MySQL数据表的增删查改——约束

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 《MySQL探索之旅》 |《Web世界探险家》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更…

A Little Is Enough: Circumventing Defenses For Distributed Learning

联邦学习的攻击方法&#xff1a;LIE 简单的总结&#xff0c;只是为了能快速想起来这个方法。 无目标攻击 例如总共50个客户端&#xff0c;有24个恶意客户端&#xff0c;那么这个时候&#xff0c;他需要拉拢2个良性客户端 计算 50 − 24 − 2 50 − 24 0.923 \frac{50-24-2}{…

主干网络篇 | YOLOv8更换主干网络之EfficientNet

前言:Hello大家好,我是小哥谈。EfficientNet是一种高效的卷积神经网络架构,由Mingxing Tan和Quoc V. Le在2019年提出,其设计思想是在不增加计算复杂度的情况下提高模型的准确性。它引入了一个称为"复合系数"的概念,该系数用于同时缩放网络的深度、宽度和分辨率。…

Qt 图形视图 /图形视图框架坐标系统的设计理念和使用方法

文章目录 概述Qt 坐标系统图形视图的渲染过程Item图形项坐标系Scene场景坐标系View视图坐标系map坐标映射场景坐标转项坐标视图坐标转图形项坐标图形项之间的坐标转换 其他 概述 The Graphics View Coordinate System 图形视图坐标系统是Qt图形视图框架的重要组成部分&#xf…

自定义类型:【联合体和枚举】

一.联合体 1.联合体类型的声明 联合体像结构体一样&#xff0c;也是有一个或者多个成员组成&#xff0c;当然也可以不同的类型。但不同的是&#xff0c;比编译器只为最大的成员分配足够的内存空间&#xff0c;所有成员共用同一块内存空间。所以联合体也叫做&#xff1a;共用体…

[Linux初阶]which-find-grep-wc-管道符命令

目录 一.which 二.find a.-name b.-size 三.grep 四.wc 五.管道符(|) 五.总结 一.which 语法格式: which [命令] Linux中的一个个命令,本体上就是一个个的二进制可执行程序(相当于windows中的.exe文件). 在Linux中,一切皆文件. which命令:用于查看指定命令的可执行…

centos 7 安装磐维(PanWeiDB)数据库(单机)

前置环境准备 文件系统环境要求 文件系统环境所要求的扇区必须为512bytes&#xff0c;查看方法如下&#xff1a; [rootdevops-core-highapp3-b-32 ~]#df -h /apps/ [rootdevops-core-highapp3-b-32 ~]#ll /dev/mapper/vg--docker-lvapp [rootdevops-core-highapp3-b-32 ~]#f…

C++命名空间详解

背景 大型程序往往会使用多个独立开发的库&#xff0c;这些库又会定义大量的全局名字&#xff0c;如类、函数和模板等。当应用程序用到多个供应商提供的库时&#xff0c;不可避免地会发生某些名字相互冲突的情况。 多个库将名字放置在全局命名空间中将引发命名空间污染。 传…

dnf手游攻略:如何利用PVP竞技赛季赚取泰拉与金币?

在DNF手游中&#xff0c;PVP竞技赛季是玩家赚取泰拉和金币的重要途径之一。本攻略将介绍如何通过参与PVP竞技赛季来获取丰厚的游戏收益&#xff0c;帮助玩家在游戏中获得更多的经济收益和游戏资源。 一、认识PVP竞技赛季 PVP竞技赛季是DNF手游中的一个重要活动&#xff0c;每个…

FFmpeg将绿幕视频处理成透明视频播放

怎么在网页端插入透明视频呢&#xff0c;之前在做Web3D项目时&#xff0c;使用threejs可以使绿幕视频透明显示在三维场景中&#xff0c;但是在网页端怎么让绿幕视频透明显示呢&#xff1f; 如图上图&#xff0c;视频背景遮挡住后面网页内容 想要如下图效果 之前有使用过ffmpeg…

选择华为HCIE培训机构有哪些注意事项

选择软件培训机构注意四点事项1、口碑&#xff1a;学员和社会人士对该机构的评价怎样&#xff1f; 口碑对于一个机构是十分重要的&#xff0c;这也是考量一个机构好不好的重要标准&#xff0c;包括社会评价和学员的评价和感言。誉天作为华为首批授权培训中心&#xff0c;一直致…

Vue中使用Vuex(超详细)基本使用方法

在vue中使用vuex&#xff0c;不同的vue版本要对应使用不同的vuex&#xff0c;在这里不做详情介绍&#xff0c;想具体了解的&#xff0c;请自行度娘或者必应一下。 在使用vuex之前&#xff0c;我们创建一个新的项目&#xff0c;这里我们使用的是vue的脚手架创建一个vue项目。 …

基于java高校社团招新系统设计与实现

摘要 &#xff1a;大学学生社团的不断壮大发展&#xff0c;让对社团的招新管理越来越重要&#xff0c;如何高效的管理社团&#xff0c;促进社团有效的运行和发展变得尤为关键。学生社团在学生的成长发展过程中有着一定的积极作用&#xff0c;要发挥好社团的优势&#xff0c;管…

机器学习基础——模型评估与选择(部分)

目录 一、前言&#xff1a;误差与拟合 &#xff08;一&#xff09;经验误差 &#xff08;二&#xff09;过拟合、欠拟合 二、评估方法 &#xff08;一&#xff09;评估总体的思路 &#xff08;二&#xff09;如何划分训练集和测试集 1.留出法 2.k折交叉验证 3.自助法 …