【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


目录
相关文章
|
2月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
22 1
|
2月前
|
C语言 C++
深度剖析C++string(中)
深度剖析C++string(中)
50 0
|
2月前
|
存储 编译器 程序员
深度剖析C++string(上篇)(2)
深度剖析C++string(上篇)(2)
36 0
|
2月前
|
存储 Linux C语言
深度剖析C++string(上篇)(1)
深度剖析C++string(上篇)(1)
32 0
|
2月前
|
C++
|
6天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
21 2
|
12天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
40 5
|
19天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
49 4
|
20天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
46 4
|
2月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
28 4