【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++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
81 5
|
2月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
65 2
|
3月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
33 1
|
3月前
|
C语言 C++
深度剖析C++string(中)
深度剖析C++string(中)
62 0
|
3月前
|
存储 编译器 程序员
深度剖析C++string(上篇)(2)
深度剖析C++string(上篇)(2)
49 0
|
4月前
|
Java 索引
java基础(13)String类
本文介绍了Java中String类的多种操作方法,包括字符串拼接、获取长度、去除空格、替换、截取、分割、比较和查找字符等。
49 0
java基础(13)String类
|
3月前
|
Java
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
本文深入探讨了Java中方法参数的传递机制,包括值传递和引用传递的区别,以及String类对象的不可变性。通过详细讲解和示例代码,帮助读者理解参数传递的内部原理,并掌握在实际编程中正确处理参数传递的方法。关键词:Java, 方法参数传递, 值传递, 引用传递, String不可变性。
74 1
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
|
3月前
|
安全 Java 测试技术
Java零基础-StringBuffer 类详解
【10月更文挑战第9天】Java零基础教学篇,手把手实践教学!
66 2
|
4月前
|
安全 Java
String类-知识回顾①
这篇文章回顾了Java中String类的相关知识点,包括`==`操作符和`equals()`方法的区别、String类对象的不可变性及其好处、String常量池的概念,以及String对象的加法操作。文章通过代码示例详细解释了这些概念,并探讨了使用String常量池时的一些行为。
String类-知识回顾①
|
3月前
|
数据可视化 Java
让星星月亮告诉你,通过反射创建类的实例对象,并通过Unsafe theUnsafe来修改实例对象的私有的String类型的成员属性的值
本文介绍了如何使用 Unsafe 类通过反射机制修改对象的私有属性值。主要包括: 1. 获取 Unsafe 的 theUnsafe 属性:通过反射获取 Unsafe类的私有静态属性theUnsafe,并放开其访问权限,以便后续操作 2. 利用反射创建 User 类的实例对象:通过反射创建User类的实例对象,并定义预期值 3. 利用反射获取实例对象的name属性并修改:通过反射获取 User类实例对象的私有属性name,使用 Unsafe`的compareAndSwapObject方法直接在内存地址上修改属性值 核心代码展示了详细的步骤和逻辑,确保了对私有属性的修改不受 JVM 访问权限的限制
71 4