C++ 带你吃透string容器的使用(下)

简介: C++ 带你吃透string容器的使用

5.指定位置的修改操作

1.insert

关于单参数隐式类型转换的问题,请看这篇博客当中的explicit关键字部分的介绍

C++类和对象下(初始化列表,静态成员,explicit关键字,友元)

下面我们来演示一下insert的用法

string s("[hello world]");
s.insert(0, 1, 'w');//从0位置头插一个字符:'w'
s.insert(0, "20231122");//从0位置头插一个字符串
cout << s << endl;

string s("[hello world]");
s.insert(2, "0123456789", 3, 4);//在s串的下标为2的位置开始插入一个子串
//这个子串是"0123456789"的从下标为3的位置开始,长度为4的子串:也就是3456
cout << s << endl;//[h3456ello world]

因为insert的插入效率不高(因为string是顺序表,物理空间是连续的,插入一个字符或者删除一个字符会造成大量数据的挪动,因此效率不高)

所以insert能少用就少用

2.erase

下面我们来演示一下:

string s("0123456789");
s.erase(3, 4);//从3号下标位置开始删除4个字符,也就是删除了3456
cout << s << endl;
s.erase(2);//默认从2号下标开始的删除所有字符
cout << s << endl;
s.erase();//默认删除所有字符
cout << s << endl;

跟insert一样,效率低,能少用就少用

3.replace

其实replace并不好用,因为效率低

频繁挪动数据,偶尔用一次就算了

但是不建议一次性使用很多次,效率会非常低,因为会造成数据频繁重复性地挪动

我们介绍完find之后会结合find跟replace来介绍一个场景

在那里我们将会对replace的低效性,重复性有更深的理解

下面我们先来演示一下replace的用法

string s1 = "ABCDEFGHI";
string s2 = "abcdefghi";
s1.replace(2, 3, s2);//把s1的从下标为2位置开始长度为3的字符替换为字符串s2
//也就是把CDE替换为abcdefghi
cout << s1 << endl;
s2.replace(s2.begin() + 2, s2.begin() + 4, "------");//把s2的[2,4)区间内的字符(也就是cd)替换为"------"
cout << s2 << endl;

6.查找,交换,截取操作

1.find

string s1("abcd 1234 xxxx");
size_t index1 = s1.find("cd", 1);//从1号下标开始查找字符串cd
cout << index1 << endl;
size_t index2 = s1.find("cdef", 1, 2);//从1号下标开始查找字符串cdef的前2个字符:cd
cout << index2 << endl;
size_t index3 = s1.find(' ', 1);//从1号下标开始查找字符' '
cout << index3 << endl;
//查不到的情况:
size_t index4 = s1.find("abcd", 1);//从1号下标开始查找字符串abcd  ->  查不到,返回npos
cout << index4 << endl;//无符号整形最大值

2.其他跟find相关的函数

用法相同,注意跟find的区别即可

1.rfind

下面的这几个大家知道有这么个函数即可

不常用

2.了解即可

下面是cplusplus网站上的几个用例

大家可以了解一下

//这个功能就是把所有的aeiou都替换为空格
string str("abcdefghigklmnopqrstuvwxyz");
size_t found = str.find_first_of("aeiou");
while (found != std::string::npos)
{
  str[found] = ' ';
  found = str.find_first_of("aeiou", found + 1);
}
cout << str << endl;

void SplitFilename(const std::string& str)
{
  std::cout << "Splitting: " << str << '\n';
  std::size_t found = str.find_last_of("/\\");
  std::cout << " path: " << str.substr(0, found) << '\n';
  std::cout << " file: " << str.substr(found + 1) << '\n';
}
注意:
1.C语言中'\'是转义字符
比如'\0','\n'等等,但是'\0','\n'都是一个字节,也就是说被转义字符修饰的字符仍然是一个字符,而不是两个
如果我们想要表示这个字符的话,就要对这个转义字符进行转义
也就是说'\\'这个才是真正的'\'
2.文件路径分隔符
Windows下的文件路径分隔符是'\'
Linux下的文件路径分隔符是'/'
而这个SplitFilename函数的作用就是既可以分割Windows下的文件路径,也可以分割/Linux下的文件路径
int main()
{
  std::string str1("/usr/bin/man");
  std::string str2("c:\\windows\\winhelp.exe");
  SplitFilename(str1);
  SplitFilename(str2);
  return 0;
}

3.find和replace的应用场景

《剑指offer》上面有这么一道题:

下面我们先使用find和replace来实现一下:

string s("We are happy");
cout << s << endl;
size_t pos = s.find(' ');
while (pos != string::npos)
{
  s.replace(pos, 1, "%20");
  pos = s.find(' ');
}
cout << s << endl;

下面我们调试看看过程

我们可以看出,每次执行replace,对应空格之后的字符都要向后挪动

这就会导致出现频繁的空格移动

如果是比较长的字符:像是这样的

那这个效率就太低了

大家也可以用计算空格数来扩容后移的方法去做

但是那样还是比较麻烦的

下面介绍一种非常好用的方法:

空间换时间:

string s("We are happy and i am wzs and today is 2023 11 22");
cout << s << endl;
string s1;
for (auto& e : s)
{
  if (e == ' ')
  {
    s1 += "%20";
  }
  else
  {
    s1 += e;
  }
}
s = s1;
cout << s << endl;

效率高,而且非常好实现

4.swap

这个swap有两个,

一个是成员函数

一个是非成员函数

我们常用作为成员函数的那个swap

5.substr

下面我们来演示一下:

6.find和substr的应用场景

find和substr也能够很好地放到一起使用

比方说下面这个场景

对于这个网址来说:

http://www.baidu.com/index.html?name=mo&age=25#dowell

我们想要截取它的

1.协议:http

2.域名:www.baidu.com

3.剩下的这个 index.html?name=mo&age=25#dowell

(包括:端口、路径(虚拟路径)、携带的参数、哈希值)

浏览器地址栏的完整URL都包含哪些内容都各代表什么?

大家感兴趣的话可以看看这位大佬的文章

这些东西我们以后会介绍的

下面我们回归这个需求,开始实现一下

string s("http://www.baidu.com/index.html?name=mo&age=25#dowell");
string substr1, substr2, substr3;
//我们的目标是:
//substr1:http
//substr2:www.baidu.com
//substr3:index.html?name=mo&age=25#dowell
size_t pos1 = s.find(':', 0);//从0下标开始出发查找':'
substr1 = s.substr(0, pos1 - 0);//[0,pos1):左闭右开的区间:长度是右区间-左区间 也就是pos1-0
size_t pos2 = s.find('/',pos1 + 3);//pos1位置此时是':'  我们下一次要从pos1+3的位置开始查找 也就是第一个'w'的位置
substr2 = s.substr(pos1 + 3, pos2 - (pos1 + 3));//[pos1+3,pos2):这个区间内的子串
substr3 = s.substr(pos2 + 1);//从pos2+1开始一直截取到最后即可
cout << substr1 << endl;
cout << substr2 << endl;
cout << substr3 << endl;

还有一个场景:

取文件后缀名

这是我随便编的一个文件

string s("filename.cpp.txt.zip");
//它的真实后缀名是zip
//我现在就是只想要它的真实后缀名
//你给我放到str1这个string中
size_t pos = s.rfind('.');
string str1 = s.substr(pos);
cout << str1 << endl;

7.string类型转为const char*类型

1.c_str

我们在文件操作的时候提到过fopen函数

今天我们想这样去读取一个文件:

这时c_str就排上用场了

这时关于fgetc函数的使用:读取一个文件的内容

关于C语言文件操作的内容,大家可以看我的这一篇博客:

C语言文件操作详解

这样我们就成功读取了

2.data

8.非成员函数

1.比较运算符重载

2.+运算符重载

3.getline

要介绍这个getline

我们可以通过一道题目来深刻理解getline的价值

牛客:字符串最后一个单词的长度

#include <iostream>
using namespace std;
int main() {
    string s;
    cin>>s;
    size_t pos=s.rfind(' ');
    //没有找到,返回npos
    //说明该字符串只有一个单词,返回size即可
    if(pos==string::npos)
    {
        cout<<s.size()<<endl;
    }
    //找到了
    //hello nowcoder
    //pos指向空格  size指向'\0'  最后一个单词是:(pos,size)左开右开区间内的
    //长度是右侧-左侧-1  也就是size-pos-1
    else 
    {
        cout<<s.size()-pos-1<<endl;
    }
    return 0;
}

我们发现,我们输出的答案总是第一个单词的长度,为什么呢?

因为:

string s;
getline(cin,s);//getline的用法
#include <iostream>
using namespace std;
int main() 
{
    string s;
    getline(cin,s);//getline的用法
    size_t pos=s.rfind(' ');
    if(pos==string::npos)
    {
        cout<<s.size()<<endl;
    }
    else 
    {
        cout<<s.size()-pos-1<<endl;
    }
    return 0;
}

4.<< 和 >>

对于流插入和流提取

我们只需要知道可以直接对string容器进行cout和cin即可

9.其他不太重要的函数

1.assign

2.copy

三.揭秘string容器的迭代器

1.string容器迭代器的本质

其实对于string容器来说

我们完全可以使用一个char*指针去自己实现这个迭代器

也就是这样:

typedef char* iterator
iterator begin()
{
  return _str;
}
iterator end()
{
  return _str + _size;
}

然后我们就可以使用了

2.iterator迭代器使用

在这里我先模拟实现了string的构造函数,析构函数,还有这个迭代器

关于string的模拟实现,我们以后会单独出一篇博客的

namespace wzs
{
  class string
  {
  public:
    //构造函数
    string(const char* str = "")
    {
      _size = strlen(str);
      _capacity = _size;
      _str = new char[_capacity + 1];
      strcpy(_str, str);
    }
    //析构函数
    ~string()
    {
      delete[]_str;
      _str = nullptr;
      _size = 0;
      _capacity = 0;
    }
    //迭代器和范围for
    typedef char* iterator;
    iterator begin()
    {
      return _str;
    }
    iterator end()
    {
      return _str + _size;
    }
  private:
    char* _str;
    int _size;
    int _capacity;
  };
  void test()
  {
    string s2 = "hello iterator";
    string::iterator it = s2.begin();
    while (it != s2.end())
    {
      (*it)++;
      cout << *it << " ";
      it++;
    }
    cout << endl;
    for (auto& e : s2)
    {
      cout << e << " ";
    }
  }
}
int main()
{
  wzs::test();
  return 0;
}

然后我们就可以自己去玩这个迭代器了

我们仅仅使用我们的这个string类,就可以实现iterator和范围for了

3.范围for的本质

其实范围for就是迭代器

我们可以通过查看反汇编看到begin()和end()的身影

为什么说范围for式"傻瓜式"的替换呢?

因为我们只有当我们遵守迭代器的规范

把迭代器命名为begin(),end()时才会有用

否则就没用了

比如我现在给它改名为Begin

4.iterator的补充

但是所有的iterator都是指针吗?

并不是这样的

string容器之所以可以使用char*来当作迭代器是因为string容器在物理空间上是连续的

对于那些物理空间并不连续的容器来说,迭代器就不是这么简单了

对于那些容器的迭代器我们以后会说明的

补充最前面的构造函数的最后一个重载版本

了解了string容器的迭代器之后

我们再来谈一下最后一个构造函数的重载版本

这个其实就是传入迭代器区间进行构造

注意:get_allocator以后会介绍

这个get_allocator涉及到适配器的知识,我们以后会介绍的

以上就是C++ 带你吃透string容器的使用的全部内容,希望能对大家有所帮助!


相关文章
|
1天前
|
存储 缓存 C++
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(`std::vector`、`std::array`、`std::list`、`std::deque`)、关联容器(`std::set`、`std::map`)和无序容器(`std::unordered_set`、`std::unordered_map`),全面解析它们的特点、用法
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
|
1天前
|
存储 安全 C语言
C++ String揭秘:写高效代码的关键
在C++编程中,字符串操作是不可避免的一部分。从简单的字符串拼接到复杂的文本处理,C++的string类为开发者提供了一种更高效、灵活且安全的方式来管理和操作字符串。本文将从基础操作入手,逐步揭开C++ string类的奥秘,帮助你深入理解其内部机制,并学会如何在实际开发中充分发挥其性能和优势。
|
3天前
|
C++
模拟实现c++中的string
模拟实现c++中的string
|
3月前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
142 5
|
3月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
90 2
|
3月前
|
存储 设计模式 C++
【C++】优先级队列(容器适配器)
本文介绍了C++ STL中的线性容器及其适配器,包括栈、队列和优先队列的设计与实现。详细解析了`deque`的特点和存储结构,以及如何利用`deque`实现栈、队列和优先队列。通过自定义命名空间和类模板,展示了如何模拟实现这些容器适配器,重点讲解了优先队列的内部机制,如堆的构建与维护方法。
63 0
|
4月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
46 1
|
4月前
|
C语言 C++
深度剖析C++string(中)
深度剖析C++string(中)
73 0
|
4月前
|
存储 编译器 程序员
深度剖析C++string(上篇)(2)
深度剖析C++string(上篇)(2)
57 0
|
4月前
|
存储 Linux C语言
深度剖析C++string(上篇)(1)
深度剖析C++string(上篇)(1)
43 0