【C++】STL —— String类不会怎么办? 看文档(万字详解)(下)

简介: 【C++】STL —— String类不会怎么办? 看文档(万字详解)(下)

七、Modifiers 修改


🎨追加


0a2653c851af460fa595bd959398a8f1.png


+=最好用也最常用,因为既可以追加字符、也可追加字符串 ,其实底层调用了append和push_back


void test_string7()
{
  string s("hello");
  s.push_back('-');
  s.push_back('-');
  s.append("world");
  cout << s << endl;
  string str("我来了");
  s += '@';//字符
  s += str;//字符串
  s += "JDG总冠军";
  cout << s << endl;
}

0a2653c851af460fa595bd959398a8f1.png

其中append的迭代器可以选择性的打印字符串内容(了解即可)


string s("hello");
string str("我来了");
s.append(++str.begin(), --str.end());
string copy(s.begin() + 5, s.end() -5);

2d65d23f6d4748949b924e4057485923.png

下面来研究尾插扩容容量变化 ——


void test_string8()
{
  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';
  }
  }
}


我们可以看见在vs下增容,第一次是两倍,后面是1.5倍的增容


0a2653c851af460fa595bd959398a8f1.png


如果提前知道是多少空间,可以调用reserve预留空间,避免频繁增容的消耗


s.resize(1000 , 'x'); //开空间+初始化


2d65d23f6d4748949b924e4057485923.png


🎨插入和删除


🐋尽量少使用头部的插入和删除,因为要挪动,O(N)效率低


0a2653c851af460fa595bd959398a8f1.png


🤞1️⃣小练习:在字符串中空格的地方插入一个%


我的第一想法:遍历字符串,遇到' '的时候,直接插入


void test_string9()
{
  //在空格的地方插入一个%
  string str("JDG NB 总冠军");
  for (size_t i = 0; i < str.size(); ++i)
  {
  if (str[i] == ' ')
  {
    str.insert(i, "20%");
  }
  }
  cout << str << endl;
}


但这样有没有问题呢?


0a2653c851af460fa595bd959398a8f1.png


那怎么样处理比较好呢?只需要在插入的地方i额外的+3,


void test_string9()
{
  //在空格的地方插入一个%
  string str("JDG NB 总冠军");
  for (size_t i = 0; i < str.size(); ++i)
  {
  if (str[i] == ' ')
  {
    str.insert(i, "20%");
    i += 3;
  }
  }
  cout << str << endl;
}


2️⃣练习升级:把字符串中遇到的空格替换成20%


这就要引入erase:删除字符串中的字符


2d65d23f6d4748949b924e4057485923.png


void test_string9()
{
  //在空格的地方插入一个%
  string str("JDG NB 总冠军");
  for (size_t i = 0; i < str.size(); ++i)
  {
  if (str[i] == ' ')
  {
    str.insert(i, "20%");
    i += 3;
  }
  }
  //再把空格删除掉哦
  for (size_t i = 0; i < str.size(); ++i)
  {
  if (str[i] == ' ')
  {
    str.erase(i, 1);
  }
  }
  cout << str << endl;
}


ps:最好不要自行挪动数据,因为[]会检查下标位置必须要小于size的,如果真的要挪动,要resize一下,增加长度


也有一种空间换时间的方法:创建新的串遍历,效率O(N)


void test_string9()
{
  string str("JDG NB 总冠军");
  string newstr;
  for (size_t i = 0; i < str.size(); ++i)
  {
  if (str[i] != ' ')
  {
    newstr += str[i];
  }
  else
  {
    newstr += "20%";
  }
  }
  cout << newstr << endl;
}


八、String operations 字符串操作


打印字符串,都能打印,但意义不同 ——


0a2653c851af460fa595bd959398a8f1.png


🤞前者是string类的流插入运算符的重载,以对象size为准,size是多少打印多少

后者是以常量字符串对象\0为准,遇到\0就结束(符合c语言标准)


所以说\0不一定是结束标志,在string里会被忽视


主要作用还是与函数接口接合——


string file("test.txt");  
  FILE* fout = fopen(s.c_str(), "w");//打印文件


![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/631a160b51da4d7e9729bbeb05c5295d.png


🌍substr 子串

➰· 取当前串的一个子串


4cebaac233b3433da32a72337a77fc60.png


len:如果len比能取到的串长或使用缺省值npos,都是能取多少取多少


🌍查找 find & rfind

🌊 1. 从字符串pos位置从前向后找字符c/字符串,返回该字符在字符串中的位置,找不到就返回npos


6de278e6d6694ce5bb08e7e842b7e74b.png


🌊 2. 从字符串pos位置倒着找找字符c/字符串,返回该字符在字符串中的位置


8ec4f2997fb246878c34ecd6d122b7c6.png


💢小练习:取字符串的后缀


void test_string11()
{
  string str("test.cpp");
  //找出后缀
  size_t pos = str.rfind('.');//面对多后缀的最好用 rfind
  if (pos != string::npos)
  {
  //string buff = str.substr(pos, str.size() - pos);
  string buff = str.substr(pos);//因为是取到结束
  cout << buff << endl;
  }
}


但是这样写有没有说明问题呢?如果后缀是test.cpp.tar.zip呢?要取最后一个后缀


这样就要将find ——》 rfind,即可解决


💢解析出网址的这三个部分:协议 - 域名 - 资源


#include<iostream>
#include<string>
using namespace std;
int main()
{
  string url = "https://cplusplus.com/reference/string/string/substr/";
  size_t pos1 = url.find("://");
  if (pos1 == string::npos)
  {
  cout << "非法字符串" << endl;
  return;
  }
  //取协议
  string protocol = url.substr(0, pos1);
  cout << protocol << endl;
  size_t pos2 = url.find('/', pos1 + 3);
  if (pos1 == string::npos)
  {
  cout << "非法字符串" << endl;
  return;
  }
  string domain = url.substr(pos1 + 3, pos2 - pos1 - 3);//取域名
  cout << domain << endl;
  string uri = url.substr(pos2 + 1);//取资源
  cout << uri << endl;
}


💢find 和find_first_of 区别

0a2653c851af460fa595bd959398a8f1.png

find_first_of:只要出现要寻找的串里的任意字符都找出来


吐槽一下:find_first_of 更应该叫find_any_of,但我们要尊重语法


2d65d23f6d4748949b924e4057485923.png


九、非成员函数重载


💦流插入&流提取

注意注意:流插入和流提取都是以空格、回车作为结束标志的。这意味着如果想要输入一个字符串,最终可能只读入了一个单词,剩余的留在缓冲区里。


0a2653c851af460fa595bd959398a8f1.png


于是我们引入了getline,做题会遇到


💦getline


2d65d23f6d4748949b924e4057485923.png


遇到换行才结束


💢小练习:输出一个整数,表示输入字符串最后一个单词的长度


#include <iostream>
#include <string>
using namespace std;
int main() {
    string str;
    getline(cin,str);
    size_t pos = str.rfind(' ');
    if(pos != string::npos)
    {
        cout<<str.size()-pos-1<<endl;
    }
    else{
        cout<<str.size()<<endl;
    }
}


💦to_string


0a2653c851af460fa595bd959398a8f1.png

void test_string12()
{
  //整形转字符串
  int ival;
  double dval;
  cin >> ival >> dval;
  string istr = to_string(ival);
  string dstr = to_string(dval);
  cout << istr << endl;
  cout << dstr << endl;
  //字符串转整形
  istr = "9999";
  dstr = "9999.99";
  ival = stoi(istr);
  dval = stod(dstr);
}


重要的事情说三遍:不会的查文档,不会的查文档,不会的查文档!!

2d65d23f6d4748949b924e4057485923.png


十、 vs和g++下string结构的说明(了解)


🎉VS下string结构的说明:


string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字符串的存储空间:


当字符串长度小于16时,使用内部固定的字符数组来存放

当字符串长度大于16时,从堆上开辟空间

union _Bxty
{ // storage for small buffer or pointer to larger one
  value_type _Buf[_BUF_SIZE];
  pointer _Ptr;
  char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;


这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。

其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量, 最后:还有一个指针做一些其他事情。

故总共占16+4+4+4=28个字节。


2d65d23f6d4748949b924e4057485923.png


🎉g++下string的结构

G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:


空间总大小

字符串有效长度

引用计数

指向堆空间的指针,用来存储字符串。

struct _Rep_base
{
  size_type _M_length;
  size_type _M_capacity;
  _Atomic_word _M_refcount;
};


📢写在最后

1024程序节,快要来了,大家会怎么样过呢


相关文章
|
3天前
|
编译器 C语言 C++
【c++丨STL】list模拟实现(附源码)
本文介绍了如何模拟实现C++中的`list`容器。`list`底层采用双向带头循环链表结构,相较于`vector`和`string`更为复杂。文章首先回顾了`list`的基本结构和常用接口,然后详细讲解了节点、迭代器及容器的实现过程。 最终,通过这些步骤,我们成功模拟实现了`list`容器的功能。文章最后提供了完整的代码实现,并简要总结了实现过程中的关键点。 如果你对双向链表或`list`的底层实现感兴趣,建议先掌握相关基础知识后再阅读本文,以便更好地理解内容。
13 1
|
16天前
|
算法 C语言 C++
【c++丨STL】list的使用
本文介绍了STL容器`list`的使用方法及其主要功能。`list`是一种双向链表结构,适用于频繁的插入和删除操作。文章详细讲解了`list`的构造函数、析构函数、赋值重载、迭代器、容量接口、元素访问接口、增删查改操作以及一些特有的操作接口如`splice`、`remove_if`、`unique`、`merge`、`sort`和`reverse`。通过示例代码,读者可以更好地理解如何使用这些接口。最后,作者总结了`list`的特点和适用场景,并预告了后续关于`list`模拟实现的文章。
32 7
|
2月前
|
存储 编译器 C语言
【c++丨STL】vector的使用
本文介绍了C++ STL中的`vector`容器,包括其基本概念、主要接口及其使用方法。`vector`是一种动态数组,能够根据需要自动调整大小,提供了丰富的操作接口,如增删查改等。文章详细解释了`vector`的构造函数、赋值运算符、容量接口、迭代器接口、元素访问接口以及一些常用的增删操作函数。最后,还展示了如何使用`vector`创建字符串数组,体现了`vector`在实际编程中的灵活性和实用性。
60 4
|
2月前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
75 5
|
2月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
57 2
|
19天前
|
存储 编译器 C语言
【c++丨STL】vector模拟实现
本文深入探讨了 `vector` 的底层实现原理,并尝试模拟实现其结构及常用接口。首先介绍了 `vector` 的底层是动态顺序表,使用三个迭代器(指针)来维护数组,分别为 `start`、`finish` 和 `end_of_storage`。接着详细讲解了如何实现 `vector` 的各种构造函数、析构函数、容量接口、迭代器接口、插入和删除操作等。最后提供了完整的模拟实现代码,帮助读者更好地理解和掌握 `vector` 的实现细节。
29 0
|
2月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
109 5
|
2月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
107 4
|
2月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
136 4
|
3月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
34 4