C++【STL】之string的使用

简介: C++ STL string类常用接口详细讲解,干货满满!

STL简介

STLC++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。STL由六大组件构成:仿函数算法迭代器空间配置器容器配接器

其中各种容器可以很大帮助的提升我们编写程序的效率,后续都会一一介绍,今天我们就先拿!string来小试牛刀,用上了它,我们就能对字符串的操作更加行云流水。

string使用

注意:string诞生于STL之前,所以会存在接口冗余的现象,本文介绍的是string的部分常用接口,大佬们想了解更多关于string类的细节,请前往官方文档(点击跳转)查阅

1. 构造函数

注意:string包含于iostream头文件中,使用时还需展开std命名空间

1.1 默认(无参)构造函数

int main()
{
   
   
    string s; //调用默认构造函数,只包含'\0'
    return 0;
}

调用默认构造函数时,默认将对象初始化为只包含 '\0' 的空串

1.2 带参构造函数

我们可以自己指定string对象中的内容

int main()
{
   
   
    string s1("happy"); //带参构造函数,指定字符串+'\0',下同
    string s2 = "happy";
    string s3(12, 'c'); //构造12个c
    return 0;
}

1.3 拷贝构造

int main()
{
   
   
    string s1("happy");
    string s2 = "happy";

    string s3(s1); //拷贝构造,下同
    string s4 = s1;
    return 0;
}

2. 容量操作

string类中包含容量、长度、字符指针等,我们可以将其看作一个存放字符的顺序表来处理

2.1 获取数据

c_str() 接口:获取string对象中指向字符串的指针,当指针指向对象为常量字符串时,编译器会直接打印内容

int main()
{
   
   
    string s1("hello");
    string s2 = "happy";
    //指针指向常量字符串时编译器直接打印
    cout << s1.c_str() << endl; //获取对象s1中的字符串指针
    cout << (void*)s2.c_str() << endl; //非常量字符指针
    return 0;
}

capacity()接口和size()接口:获取string对象的容量和大小

int main()
{
   
   
    string s(88, 'j');
    cout << "capacity: " << s.capacity() << endl;
    cout << "size: " << s.size() << endl;
    return 0;
}

2.2 空间扩容

new出来的空间不支持使用relloc直接扩容,而需要使用专门的接口来扩容

reserve()接口:实现string对象异地扩容

int main()
{
   
   
    string s(12, 's'); //初始容量为12
    cout << "capacity: " << s.capacity() << endl;

    s.reserve(24); //扩容为24
    cout << "capacity: " << s.capacity() << endl;
    return 0;
}

==如果我们不手动扩容,string 也会在识别到容量不够时,自动扩容==

VS下string 的扩容规则

  • 默认给一个大小为15的数组存储数据,当数组够用时,都是用的数组
  • 当数组容量不够时,改用指针,先2倍扩容至30,后续字符都是存在指针中,之后每次扩容,都是1.5倍扩容
  • 会多开辟一些空间

Linux下string 的扩容规则

  • 默认大小为0的空间
  • 当第一次扩容时,会先扩至1,之后每次都是2倍扩容
  • 不会多开空间

==注意:频繁的扩容会导致内存碎片问题,我们在使用string时,可以先提前计算好大致需要的空间,使用reserve提前进行扩容,来避免此问题==

2.3 长度调整

size()接口:改变string对象的长度

int main()
{
   
   
    string s(24, 'a'); //初始size = 24
    cout << "size: " << s.size() << endl;

    s.resize(12); //改变size为12
    //s.resize(36, 'b'); //改变后12块空间为b
    cout << "size: " << s.size() << endl;
    return 0;
}

resize()接口的两种情况:

  • 如果调整后空间比原空间大,就相当于扩容,resize() 还有初始化的功能,可以将第二个参数设置为指定字符,如果没有指定就默认为 \0,如以上示例。
  • 如果调整后空间比原空间小,此时将会size调整至目标空间,而capapcity不会改变,此时我们无法访问到size之外的数据

resize() 并不会达到缩容的效果,因为缩容的代价比较大,需要先开辟新空间,然后拷贝,释放原空间,因此 resize() 在处理时,若新空间比原空间小,不会改变capaciy

3. 字符串遍历

string字符串的遍历主要有三种方式:下标访问[]at()迭代器访问

3.1 下标访问

下标访问的原理就是运算符重载operator[]

int main()
{
   
   
    string s("hello sakura!");
    size_t pos = 0;
    while (pos < s.size())
    {
   
   
        cout << s[pos++]; //hellosakura
    }
    return 0;
}

at()访问字符串

int main()
{
   
   
    string s("hello sakura!");
    size_t pos = 0;
    while (pos < s.size())
    {
   
   
        cout << s.at(pos++);
    }
    return 0;
}

这里的运行结果和上面使用下标访问时一致的,二者实现的原理一样,区别是当出现越界访问时,at()抛异常,而下标访问会通过assert报错

3.2 迭代器

迭代器遍历iterator字符串

使用begin()获取第一个字符,end()获取最后一个字符的下一个字符即'\0'

int main()
{
   
   
    string s("hello world!");
    string::iterator it = s.begin(); //此时it相当于指向第一个字符的指针
    //auto it = s.begin(); //也可以用auto自动类型识别
    while (it != s.end())
    {
   
   
        cout << *it;
        it++;
    }
    return 0;
}

除了可以使用iterator进行正向遍历外,还可以使用reverse_iterator进行反向遍历

rbegin() 获取最后一个字符,rend() 获取第一个字符的前一个字符

int main()
{
   
   
    string s("hello world!");
    string::reverse_iterator rit = s.rbegin(); //此时rit相当于指向最后一个字符的指针
    while (rit != s.rend())
    {
   
   
        cout << *rit;
        rit++;
    }
    return 0;
}

除了上面两种普通迭代器外,还有两个 const 修饰的迭代器,用来遍历常量字符串

  • const_iterator正向遍历常量字符串
  • const_reverse_iterator反向遍历常量字符串

==注意:==

  • 迭代器遍历的区间都是左闭右开的
  • 迭代器名中 的 const 并不是 const 操作符,而是与普通迭代器构成重载
  • 迭代器不适合用来遍历顺序表,适合遍历链表
  • 范围for的底层就是调用迭代器遍历

4. 字符串修改

4.1 尾插字符/字符串

对于尾插字符/字符串,string提供了三种方式:

首先是push_back():尾插字符


int main()
{
   
   
    string s = "happ";
    cout << s << endl;
    //尾插字符
    s.push_back('y');
    cout << s << endl;
    return 0;
}

push_back()类似顺序表尾插,一次只能插入一个字符

第二种是append():尾插字符字符串

int main()
{
   
   
    string s = "happy";
    cout << s << endl;
    //尾插字符/字符串
    s.append(2, 'aa'); //插入两个a
    s.append(" cjc"); //插入字符串
    cout << s << endl;
    return 0;
}

第三种是operator+=:尾插字符/字符串

int main()
{
   
   
    string s = "happy";
    cout << s << endl;    
    //尾插字符/字符串
    s += 'c';
    s += "jc";
    cout << s << endl;

    return 0;
}

在日常使用中operator+=是使用频率最高,最方便的

4.2 任意位置插入字符/字符串

insert()接口:支持在string对象任意位置插入字符/字符串

int main()
{
   
   
    string s("xxxxxx");
    cout << s << endl; //xxxxxx
    s.insert(2, 1, 'c'); //在第2个位置插入1个c
    s.insert(3, "jc"); //在第3个位置插入字符串jc
    cout << s << endl; //xxxcjcxxx
    return 0;
}

4.3 任意位置删除字符/字符串

erase()接口:支持在string对象任意位置删除字符/字符串

int main()
{
   
   
    string s("happynewyear");
    cout << s << endl;
    s.erase(5, 1); //从第5个位置开始删除1个字符
    cout << s << endl;
    s.erase(5, 2); //从第5个位置开始删除2个字符
    cout << s << endl;
    s.erase(); //默认全部删除
    cout << s << endl; //空串
    return 0;
}

image-20230612004053893

erase() 是一个全缺省参数,第一个参数为0,表示默认从pos[0]开始,第二个参数为npos,这是无符号整型中的-1,为无符号整型最大值,意思是如果不写第二个参数,默认就全部删除

image-20230612004950247

4.4 查找字符/字符串位置

find()接口:查找string对象中指定字符/字符串的位置

int main()
{
   
   
    //返回的是目标字符/字符串第一次出现的下标
    string s("happycjchappy");
    //找字符c,默认从pos0位置开始找
    cout << s.find('c') << endl;
    //找字符串ha
    cout << s.find("ha") << endl;
    //找字符串ha,从pos3位置开始找
    cout << s.find("ha", 3) << endl;
    //没找到的情况
    cout << s.find("bb") << endl; //返回npos
    return 0;
}

find返回的是目标字符/字符串第一次出现的下标,这里可以看到,当目标不存在时,返回的就是 npos

find()还有另外几种使用方式:

  • rfind()从后往前找
  • find_first_of(str, pos = 0)pos位置往后,找str中出现的任意字符
  • find_last_of(str, pos = npos)npos位置往前,找str中出现的任意字符
  • find_first_not_of()反向查找
  • find_last_not_of()反向查找

4.5 截取字符串

substr()接口:截取string对象中的目标字符串

int main()
{
   
   
    string s("happy new year");
    cout << s.substr(s.find('n'), 3) << endl;
    return 0;
}

5. 非成员函数

string中还有很多定义在类外的非成员函数

5.1 流操作

可以直接对string对象进行流插入operator<<和流提取operator>>

int main()
{
   
   
    string s;
    cin >> s;
    cout << s;
    return 0;
}

5.2 获取字符串

当输入的字符串中包含 ' '(空格)时,cin 会认为这是结束标志,而不再继续读取字符,因此单纯的流插入是无法满足字符串插入需要的,为此string专门的函数获取字符串 的接口getline()

int main()
{
   
   
    string s;
    getline(cin, s);
    cout << s;
    return 0;
}

5.3 比较函数

string类中的比较函数特别冗余,饱受吐槽,友友们需要时可以到官方文档查阅


C++【STL】之string的使用,到这里就介绍结束了,本篇文章对你由帮助的话,期待大佬们的三连,你们的支持是我最大的动力!

文章有写的不足或是错误的地方,欢迎评论或私信指出,我会在第一时间改正!

目录
相关文章
|
15天前
|
C++ 容器
【c++丨STL】stack和queue的使用及模拟实现
本文介绍了STL中的两个重要容器适配器:栈(stack)和队列(queue)。容器适配器是在已有容器基础上添加新特性或功能的结构,如栈基于顺序表或链表限制操作实现。文章详细讲解了stack和queue的主要成员函数(empty、size、top/front/back、push/pop、swap),并提供了使用示例和模拟实现代码。通过这些内容,读者可以更好地理解这两种数据结构的工作原理及其实现方法。最后,作者鼓励读者点赞支持。 总结:本文深入浅出地讲解了STL中stack和queue的使用方法及其模拟实现,帮助读者掌握这两种容器适配器的特性和应用场景。
47 21
|
2月前
|
编译器 C语言 C++
【c++丨STL】list模拟实现(附源码)
本文介绍了如何模拟实现C++中的`list`容器。`list`底层采用双向带头循环链表结构,相较于`vector`和`string`更为复杂。文章首先回顾了`list`的基本结构和常用接口,然后详细讲解了节点、迭代器及容器的实现过程。 最终,通过这些步骤,我们成功模拟实现了`list`容器的功能。文章最后提供了完整的代码实现,并简要总结了实现过程中的关键点。 如果你对双向链表或`list`的底层实现感兴趣,建议先掌握相关基础知识后再阅读本文,以便更好地理解内容。
37 1
|
2月前
|
算法 C语言 C++
【c++丨STL】list的使用
本文介绍了STL容器`list`的使用方法及其主要功能。`list`是一种双向链表结构,适用于频繁的插入和删除操作。文章详细讲解了`list`的构造函数、析构函数、赋值重载、迭代器、容量接口、元素访问接口、增删查改操作以及一些特有的操作接口如`splice`、`remove_if`、`unique`、`merge`、`sort`和`reverse`。通过示例代码,读者可以更好地理解如何使用这些接口。最后,作者总结了`list`的特点和适用场景,并预告了后续关于`list`模拟实现的文章。
61 7
|
3月前
|
存储 编译器 C语言
【c++丨STL】vector的使用
本文介绍了C++ STL中的`vector`容器,包括其基本概念、主要接口及其使用方法。`vector`是一种动态数组,能够根据需要自动调整大小,提供了丰富的操作接口,如增删查改等。文章详细解释了`vector`的构造函数、赋值运算符、容量接口、迭代器接口、元素访问接口以及一些常用的增删操作函数。最后,还展示了如何使用`vector`创建字符串数组,体现了`vector`在实际编程中的灵活性和实用性。
127 4
|
3月前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
121 5
|
3月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
87 2
|
3月前
|
存储 算法 Linux
【c++】STL简介
本文介绍了C++标准模板库(STL)的基本概念、组成部分及学习方法,强调了STL在提高编程效率和代码复用性方面的重要性。文章详细解析了STL的六大组件:容器、算法、迭代器、仿函数、配接器和空间配置器,并提出了学习STL的三个层次,旨在帮助读者深入理解和掌握STL。
95 0
|
2月前
|
存储 编译器 C语言
【c++丨STL】vector模拟实现
本文深入探讨了 `vector` 的底层实现原理,并尝试模拟实现其结构及常用接口。首先介绍了 `vector` 的底层是动态顺序表,使用三个迭代器(指针)来维护数组,分别为 `start`、`finish` 和 `end_of_storage`。接着详细讲解了如何实现 `vector` 的各种构造函数、析构函数、容量接口、迭代器接口、插入和删除操作等。最后提供了完整的模拟实现代码,帮助读者更好地理解和掌握 `vector` 的实现细节。
63 0
|
30天前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
67 19
|
30天前
|
存储 编译器 数据安全/隐私保护
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
46 13