【C++杂货铺】string使用指南(一)

简介: 【C++杂货铺】string使用指南(一)

e25236b98bc84a8cbd88f1605a51f95c.gif

前言

在C语言中,字符串是以\0结尾的一些字符的集合,为了方便操作,C标准库中提供了一些str系列的库函数。但是这些库函数与字符串是分离开的,不太符合面向对象(OOP)的思想,而且底层空间需要用户自己去管理,稍不留神可能就会出现越界访问。为了解决上面的这些问题,C++中引入了string类,它给我们带来了极大的便利。

一、介绍

string是表示字符串的字符串类。

该类的接口与常规容器(vector、list等)的接口基本相同,再添加了一些专门用来操作string的常规操作。

string在底层实际是:用basic_string模板类实例化出来的一个类,typedef basic_string<char> string;。

不能操作多字节或者变长字符的序列。

小Tips:在使用string类时,需要包含头文件#include <string>,以及使用using namespace std展开命名空间。

二、string类的常用接口说明

2.1 常见的构造接口

📖string()

该类的默认构造函数,用于构造空的string类对象,即空字符串。

int main()
{
  string s1;
  cout << s1 << endl;
  return 0;
}


0e54239231cf466993f98cc3fb507857.png

小Tips:string类对象支持流插入和流提取,下文将进行介绍,这里大家直接使用即可。

📖string(const char* s)

用C-string来构造string类对象,即用一个C的字符串(或字符数组)来构造一个string类的对象。

int main()
{
  string s1("Hello C++!");
  cout << s1 << endl;
  return 0;
}

a45d8abab7f440faa77b09c6d3e36836.png

📖string(size_t n, char c)

用n个字符c来构建一个string类对象。

int main()
{
  string s1(5, 'x');
  cout << s1 << endl;
  return 0;
}

f9565e4d36a749468a4a36a6d17a3188.png

📖string(const string& s)

string类的拷贝构造,用于构建一个和已存在的string类对象s一模一样的对象。

int main()
{
  string s1(5, 'x');
  string s2(s1);
  cout << s1 << endl;
  cout << s2 << endl;
  return 0;
}

0aa11971860e4aa28d6fdb34fb688b99.png

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

复制str中从字符位置pos开始并跨越len字符的部分(如果 str 太短或 len 是字符串npos,则直到 str 的末尾)。简单来说就是使用一个已存在的string类对象的一部分来创建一个新的string类对象。

小Tips:nops是string类里面的一个静态成员变量,它是size_t类型,初始化为-1,即表示整型的最大值。此值如果在string的成员函数中作为形参len的缺省值,表示到字符串结束。如果作为string类中成员函数的返回值,一般表示没有匹配项。

int main()
{
  string s1("Hello C++!");
  string s2(s1, 0, 5);//用s1的部分来初始化创建s2
  cout << s1 << endl;
  cout << s2 << endl;
  return 0;
}

f900f3b1124b4cc7a252315127a2360e.png

注意:对一个string类对象来说,它的第一个有效字符的下标是0。

📖string (const char* s, size_t n)

用s所指向字符串(或字符数组)的前n个来初始化创建一个string类对象。

int main()
{
  char str[] = "Hello C++!";
  string s1(str, 5);//用字符数组str的前5个字符来构建一个string类对象
  cout << str << endl;
  cout << s1 << endl;
  return 0;
}

e96f04dfe2c943abbd1ede1cfeb4e7c6.png

2.2 与容量有关的接口

📖size()

返回字符串的有效字符长度。

int main()
{
  string s1("Hello C++!");
  string s2("Good morning!");
  cout << "s1的size:" << s1.size() << endl;
  cout << "s2的size:" << s2.size() << endl;
  return 0;
}

1c590b5a2c0e467bb553c7e7dff6637e.png

📖length()

返回字符串的有效字符长度。

int main()
{
  string s1("Hello C++!");
  string s2("Good morning!");
  cout << "s1的length:" << s1.length() << endl;
  cout << "s2的length:" << s2.length() << endl;
  return 0;
}

e4b5edc757b245b0a77ea75f91dba5bd.png

小Tips:从上面的打印结果可以看出,size()和length()接口的功能一模一样,甚至底层实现原理也完全相同,都是返回字符串的有效字符个数,最初只有length()接口,引入size()接口的原因是为了与其他容器的接口保持以致,一般情况下基本都使用size()接口。这也从侧面说明string诞生的时间比STL要早。

📖capacity()

返回一个string对象中空间的大小。

int main()
{
  string s1("Hello C++!");
  string s2("Good morning chunren!");
  cout << "s1的capacity:" << s1.capacity() << endl;
  cout << "s2的capacity:" << s2.capacity() << endl;
  return 0;
}

daf53c56f40944d5890719c47d3f354e.png

小Tips:同一个string对象,在不同平台下的capacity()(空间容量)可能不同,因为string在底层就是一个存储字符的动态顺序表,空间不够了要进行扩容,而不同平台底层的扩容机制可能有所不同,这就导致了最终capacity()的结果不同。将上面的代码放到Linux环境下使用g++编译器再来试试:

bd36da96c7084f17be14e150664ecd7a.png

小Tips:capacity()返回一个string对象中空间的大小,这个空间指的是可以存储有效字符的空间,底层实际上的空间会多一个,因为还要存储\0。

📖VS下的扩容机制

int main()
{
  string s1("Hello!");
  size_t old = s1.capacity();
  cout << s1.capacity() << endl;
  for (size_t i = 0; i < 100; i++)
  {
    s1 += 'v';
    if (old != s1.capacity())
    {
      cout << "扩容:" << s1.capacity() << endl;
      old = s1.capacity();
    }
  }
  return 0;
}

d4e7ca679b2d44168f8f2d6a4faca621.png

VS下一上来会有15个空间用来存储数据(本质上是开16个空间,因为还要存\0),第一次扩容是2倍,后面都是以1.5倍的大小去扩容。

ab1f956afc374521842745f9b773358d.png

📖Linux下的扩容机制

int main()
{
  string s1("Hello!");
  size_t old = s1.capacity();
  cout << s1.capacity() << endl;
  for (size_t i = 0; i < 100; i++)
  {
    s1 += 'v';
    if (old != s1.capacity())
    {
      cout << "扩容:" << s1.capacity() << endl;
      old = s1.capacity();
    }
  }
  return 0;
}

将上面的代码放到Linux环境下使用g++编译器再来试试:

e20b6ce9a6e341e9bb3b3feebe173dad.png

可见在Linux下,最初对象需要多少空间就开多少,后面一次按照2倍的大小进行扩容。

📖empty()

检测字符串是否为空串,是返回true,否则返回false

int main()
{
  string s1;//
  if (s1.empty())
  {
    cout << "s1是一个空串" << endl;
  }
  return 0;
}

35dbb97eae4748d48bc6f518a19c423d.png

📖clear()

清空有效字符。

int main()
{
  string s1("Hello C++!");
  cout << "清空之前的size:" << s1.size() << endl;
  cout << "清空之前的capacity:" << s1.capacity() << endl;
  s1.clear();//清空
  cout << "清空之后的size:" << s1.size() << endl;
  cout << "清空之后的capacity:" << s1.capacity() << endl;
  return 0;
}

a187231dc26340a6a18d7e971b81b28e.png

小Tips:从打印结果可以看出,clear()清空操作不会影响capacity()容量,也就是说string对象并不会主动的去缩容。

📖reserve (size_t n = 0)

为字符串预留空间。直接一次申请n个空间,可以用来存储n个有效字符,避免了每次都要去扩容。大部分的扩容都是异地扩容,扩容次数过多会影响效率。

int main()
{
  string s1;
  s1.reserve(100);//知道要尾插100个字符就先直接申请100个,避免后面再去扩容
  size_t old = s1.capacity();
  cout << s1.capacity() << endl;
  for (size_t i = 0; i < 100; i++)
  {
    s1 += 'v';
    if (old != s1.capacity())
    {
      cout << "扩容:" << s1.capacity() << endl;
      old = s1.capacity();
    }
  }
  s1.reserve(20);
  cout << "第二次执行reserve(20):" << s1.capacity() << endl;
  return 0;
}

b495d58cf46b45c195dbbaf851eff35b.png

小Tips:实际申请到的有效空间可能比我们需要的多,但是一定不可能比我们需要的少,如s1.reserve(100)去申请100个有效空间,但实际上申请了111个有效空间。当n小于当前对象的容量时,一般的编译器都不会执行缩容。(视具体情况而定)

📖resize

85d600255f1d415eb8796186abac2cc0.png

将字符串大小调整为n个长度的大小,当n小于当前字符串的长度size(),会保留前n个字符,将第n个字符后面的所以字符删除;当n大于当前字符串的长度size(),先会进行扩容,以满足存储n个有效字符的需求,如果指定了字符c,会将新元素初始化为c的副本,否则全部初始化为空字符,即\0。

int main()
{
  string s1("Hello C++!");
  cout << "最初的s1.size():" << s1.size() << endl;
  cout << "最初的s1.capacity():" << s1.capacity() << endl;
  string s2 = s1;//将s1拷贝一份
  s1.reserve(100);
  cout << "reserve(100)后的s1.size():" << s1.size() << endl;
  cout << "reserve(100)后的s1.capacity():" << s1.capacity() << endl;
  s2.resize(100);
  cout << "resize(100)后的s1.size():" << s2.size() << endl;
  cout << "resize(100)后的s1.capacity():" << s2.capacity() << endl;
  return 0;
}

9de3f083e1114bd7ae7b5281aa88b075.png

小Tips:reserve和resize的区别可总结为:前者只会影响容量,负责申请空间,不会改变字符串的有效字符个数,即会改变capacity,不会改变size;后者在申请空间的基础上还会对空间进行初始化,这样就会对有效字符的个数产生影响,所以它即会改变capacity也会改变size


目录
相关文章
|
3天前
|
算法 C++ 容器
【C++】string模拟实现
【C++】string模拟实现
10 1
|
1天前
|
编译器 C++
【C++】继续学习 string类 吧
首先不得不说的是由于历史原因,string的接口多达130多个,简直冗杂… 所以学习过程中,我们只需要选取常用的,好用的来进行使用即可(有种垃圾堆里翻美食的感觉)
15 4
|
1天前
|
算法 安全 程序员
【C++】STL学习之旅——初识STL,认识string类
现在我正式开始学习STL,这让我期待好久了,一想到不用手撕链表,手搓堆栈,心里非常爽。接下来我们先来介绍一下STL:
17 4
|
1天前
|
存储 C++ 容器
【C++从练气到飞升】09---string语法指南(二)
【C++从练气到飞升】09---string语法指南(二)
|
1天前
|
存储 Linux C语言
【C++从练气到飞升】09---string语法指南(一)
【C++从练气到飞升】09---string语法指南(一)
|
29天前
|
Java API 索引
Java基础—笔记—String篇
本文介绍了Java中的`String`类、包的管理和API文档的使用。包用于分类管理Java程序,同包下类无需导包,不同包需导入。使用API时,可按类名搜索、查看包、介绍、构造器和方法。方法命名能暗示其功能,注意参数和返回值。`String`创建有两种方式:双引号创建(常量池,共享)和构造器`new`(每次新建对象)。此外,列举了`String`的常用方法,如`length()`、`charAt()`、`equals()`、`substring()`等。
16 0
|
1月前
|
缓存 Java
Java中循环创建String对象的内存管理分析
Java中循环创建String对象的内存管理分析
25 2
|
3天前
|
存储 缓存 安全
【 Java中String源码分析(JVM视角你不来看看?】
【 Java中String源码分析(JVM视角你不来看看?】
10 0
|
9天前
|
Java
Java String类型转换成Date日期类型
Java String类型转换成Date日期类型
|
10天前
|
Java 索引
Java String应用与开发
Java String应用与开发
21 0