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.mzph.cn/news/779630.shtml

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

相关文章

【01-20】计算机网络基础知识(非常详细)从零基础入门到精通,看完这一篇就够了

【01-20】计算机网络基础知识&#xff08;非常详细&#xff09;从零基础入门到精通&#xff0c;看完这一篇就够了 以下是本文参考的资料 欢迎大家查收原版 本版本仅作个人笔记使用1、OSI 的七层模型分别是&#xff1f;各自的功能是什么&#xff1f;2、说一下一次完整的HTTP请求…

记录何凯明在MIT的第一堂课:神经网络发展史

https://www.youtube.com/watch?vZ5qJ9IxSuKo 目录 表征学习 主要特点&#xff1a; 方法和技术&#xff1a; LeNet 全连接层​ 主要特点&#xff1a; 主要特点&#xff1a; 网络结构&#xff1a; AlexNet 主要特点&#xff1a; 网络结构&#xff1a; Sigmoid Re…

经典永不过时 Wordpress模板主题

经得住时间考验的模板&#xff0c;才是经典模板&#xff0c;带得来客户的网站&#xff0c;才叫NB网站。 https://www.jianzhanpress.com/?p2484

第十五届蓝桥杯第三期模拟赛第十题 ← 上楼梯

【问题描述】 小蓝要上一个楼梯&#xff0c;楼梯共有 n 级台阶&#xff08;即小蓝总共要走 n 级&#xff09;。小蓝每一步可以走 a 级、b 级或 c 级台阶。 请问小蓝总共有多少种方案能正好走到楼梯顶端&#xff1f;【输入格式】 输入的第一行包含一个整数 n 。 第二行包含三个整…

vulfocus环境搭建(kali搭建)

Vulfocus 是一个漏洞集成平台&#xff0c;将漏洞环境 docker 镜像&#xff0c;放入即可使用&#xff0c;开箱即用。 安装docker环境 个人不建议随意更换apt源&#xff0c;我换了几次遇到很多问题。 apt-get update apt-get upgrade&#xff08;时间很久&#xff09; apt-get i…

基于springboot的人事管理系统

人事管理系统 摘 要 人事管理系统理工作是一种繁琐的&#xff0c;务求准确迅速的信息检索工作。随着计算机信息技术的飞速发展&#xff0c;人类进入信息时代&#xff0c;社会的竞争越来越激烈&#xff0c;人事就越显示出其不可或缺性&#xff0c;成为学校一个非常重要的模块。…

防止恶意软件和网络攻击的简单贴士

如今&#xff0c;缺少互联网的生活是难以想象的。然而&#xff0c;互联网的匿名性导致了网络攻击和恶意软件很猖獗。恶意软件会损坏我们的设备、窃取个人数据&#xff0c;并导致金钱损失。因此&#xff0c;保护计算机免受这些威胁显得至关重要。 一、确保操作系统和软件是最新版…

企业数据资产管理的战略价值与实施策略

一、引言 数据资产不仅记录了企业的历史运营情况&#xff0c;更能够揭示市场的未来趋势&#xff0c;为企业的决策提供有力支持。因此&#xff0c;如何有效地管理和利用数据资产&#xff0c;已经成为企业竞争力的重要体现。本文将探讨企业数据资产管理的战略价值与实施策略&…

URL编码:原理、应用与安全性

title: URL编码&#xff1a;原理、应用与安全性 date: 2024/3/29 18:32:42 updated: 2024/3/29 18:32:42 tags: URL编码百分号编码特殊字符处理网络安全应用场景标准演变未来发展 在网络世界中&#xff0c;URL&#xff08;统一资源定位符&#xff09;是我们访问网页、发送请求…

设计模式之单例模式精讲

UML图&#xff1a; 静态私有变量&#xff08;即常量&#xff09;保存单例对象&#xff0c;防止使用过程中重新赋值&#xff0c;破坏单例。私有化构造方法&#xff0c;防止外部创建新的对象&#xff0c;破坏单例。静态公共getInstance方法&#xff0c;作为唯一获取单例对象的入口…

速通汇编(二)汇编mov、addsub指令

一&#xff0c;mov指令 mov指令的全称是move&#xff0c;从字面上去理解&#xff0c;作用是移动&#xff08;比较确切的说是复制&#xff09;数据&#xff0c;mov指令可以有以下几种形式 无论哪种形式&#xff0c;都是把右边的值移动到左边 mov 寄存器&#xff0c;数据&#…

AI 创新领跑者,KIP Protocol 如何理解 Decentralized AI

随着 OpenAI 的 Sora 推动 AI 赛道的热度攀升&#xff0c;AI 领域再次成为科技和投资界的焦点。KIP Protocol 是面向 AI 模型制作者、App 开发者和数据所有者构建的去中心化 Web3 协议层&#xff0c;使数据可在 Web3 中安全地进行交易和货币化。KIP Protocol 由 Animoca Ventur…

Jenkins实现CICD

Jenkins实现CICD JenkinsCI简介环境安装新建任务源码管理构建配置发送邮件配置自动化项目定时构建 JenkinsCD简介配置ssh保证其可以免登录接下来配置github的webhook正式实现自动化打包master主分支的代码将前端三剑客代码文件发送到网站服务器对应的tomcat Jenkins面试题 Jenk…

个人简历主页搭建系列-04:网站初搭建

准备工作差不多了&#xff0c;该开始搭建网站了&#xff01; 这次我们先把网站搭建部署起来&#xff0c;关于后续主题内容等更换留到后续。 创建源码文件夹 首先通过 hexo 创建本地源码文件夹。因为最终部署的 github 仓库格式为 websiteName.github.io&#xff08;websiteN…

Gartner发布新兴技术指南:生成式人工智能和深度伪造对身份验证的影响

使用生成式人工智能(GenAI)技术生成的 Deepfakes&#xff08;深度伪造&#xff09; 对身份验证的完整性构成了根本威胁。身份验证产品领导者必须了解这一新兴威胁&#xff0c;并采取积极主动的方法来区分和保护其解决方案产品。 主要发现 活体检测技术对于防御深度伪造以及在身…

【每日力扣】332. 重新安排行程与51. N 皇后

&#x1f525; 个人主页: 黑洞晓威 &#x1f600;你不必等到非常厉害&#xff0c;才敢开始&#xff0c;你需要开始&#xff0c;才会变的非常厉害。 332. 重新安排行程 给你一份航线列表 tickets &#xff0c;其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你…

代码随想录算法训练营 DAY 24 | 回溯理论基础 77.组合 + 剪枝优化

回溯理论 回溯法就是递归函数&#xff0c;纯暴力搜索 解决的问题 组合&#xff08;无顺序&#xff09; 1 2 3 4 给出大小为2的所有组合 切割字符串 子集问题 1 2 3 4&#xff0c;子集有1 2 3 4,12,13,14&#xff0c;…123 124… 排列&#xff08;有顺序&#xff09; 棋盘…

wpf 自定义命令

自定义命令 MyCommand.cs public class MyCommand : ICommand {private readonly Action<Object> execAction;private readonly Func<Object,bool> changedFunc;public event EventHandler? CanExecuteChanged;public MyCommand(Action<object> execAction…

尾盘拉升超8个点,速腾聚创交出来一份怎样的超预期答卷?

“如果说2024年是智驾加速渗透&#xff0c;L3级智能驾驶陆续落地的一年&#xff0c;那么激光雷达将是这股潮流中不可缺失的那一份。” 2024年开年&#xff0c;速腾聚创以相当“闪亮的姿态”成为“港股2024年首只IPO上市成功”的企业。 然而&#xff0c;其上市之后的市场表现却…

【MySQL】DQL-基础查询-语句&演示(查询多个字段 / 所有字段/并设置别名/去重)

前言 大家好吖&#xff0c;欢迎来到 YY 滴MySQL系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C Linux的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的…