当前位置: 首页 > news >正文

【C++】string类的使用(万字详解)

文章目录

  • 上文链接
  • 一、string 类参考文档
  • 二、string 类简介
  • 三、构造函数
    • 1. string 构造函数简介
    • 2. 常用构造函数的使用
    • 3. 非常用构造函数的使用
      • (1) substring
      • (2) from sequence
  • 三、赋值重载
  • 四、string 类的遍历
    • 1. operator[]
    • 2. size
    • 3. 遍历一:[ ] +下标
    • 4. 遍历二:迭代器
      • (1) begin 与 end
      • (2) 正向遍历
      • (3) 反向迭代器、rbegin 与 rend
      • (4) 反向遍历
    • 5. 遍历三:范围 for(C++11)
      • (1) auto 关键字
      • (2) 范围 for 的使用
  • 五、容量相关
    • 1. size 与 length
    • 2. max_size
    • 3. capacity
    • 4. clear
    • 5. empty
    • 6. reserve
    • 7. resize
      • (1) size < n < capacity
      • (2) n > capacity
      • (3) n < size
    • 8. shrink_to_fit(C++11)
  • 六、获取元素
    • 1. operator[] 与 at
    • 2. back 与 front(C++11)
  • 七、修改操作
    • 1. push_back
    • 2. append
    • 3. operator+=(常用)
    • 4. insert
    • 5. erase
    • 6. replace
  • 八、其他操作
    • 1. c_str 与 data
    • 2. copy
    • 3. substr
    • 4. find 与 rfind
    • 5. find_first_of 与 find_last_of
    • 6. find_first_not_of 与 find_last_not_of
    • 7. compare
  • 九、其他非成员函数
    • 1. 关系运算符重载
    • 2. getline
    • 3. stoi(C++11)
    • 4. to_string
  • 十、string类补充知识
    • 1. buf 数组

上文链接

  • 【C++】模板(初阶)

一、string 类参考文档

  • string - C++ Reference

二、string 类简介

由于 string 比 STL 发明的要早,因此 string 没有被归到 STL 中,但是从归类上看也可以把它和 STL 中的容器归为一类。

string 是一个类,我们虽然平时使用的是叫 “string”,但实际上它是被 typedef 之后的。通过这个模板 basic_string 指定类型 char 定义出来的一个类 string,可以认为是是一个 char 类型的顺序表

请添加图片描述


三、构造函数

1. string 构造函数简介

在 C++98 版本中,string 类一共有 7 个构造函数,C++11 中又扩充了几个 (本篇暂且不展开讲解) 。在 C++ 98 版本的构造函数中我们需要重点掌握图中画了五角星的几个常用的构造函数。

请添加图片描述


2. 常用构造函数的使用

首先我们需要使用 string 类必须要包含头文件 #include<string> 才可以使用并且展开命名空间 using namespace std;,不展开话则需要在使用时指定命名空间域 std::

#include<iostream>
#include<string>using namespace std;int main()
{string s1;  // 构造一个空串 s1string s2("hello world");  // 用指定字符串初始化 s2string s3 = "hello world";  // 用指定字符串初始化 s3const string& s4 = "hello world";string s5(s4);  // 拷贝构造 string s6(10, '#');  // 用 10 个 '#' 初始化 s7return 0;
}

这里初始化 s2s3 的区别在于初始化 s2直接调用构造函数。而初始化 s3 则是隐式类型转换,先用指定的字符串调用构造函数构造出一个对象然后再通过拷贝构造拷贝给 s3,但是一般编译器会进行优化,从而变成直接构造

而隐式类型转换构造出来的对象是一个临时对象,临时对象具有常性,不可修改,因此我们只能用 const 类型的引用去接收它,所以就有了 s4 的初始化方式。强调一下没有 const 是不行的,因为这样就放大了权限

另外,流插入 << 和流提取 >> 对 string 类定义了重载函数(注意它不属于 string 类的成员函数)。我们可以直接通过 cout 打印我们定义出来的对象来查看这个字符串。

cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
cout << s5 << endl;
cout << s6 << endl;

请添加图片描述

我们也可以直接用 cin 输入一个字符串去初始化一个 string 类对象。

#include<iostream>
#include<string>using namespace std;int main()
{string s1;cout << "输入:";cin >> s1;cout << s1 << endl;return 0;
}

请添加图片描述


3. 非常用构造函数的使用

(1) substring

请添加图片描述

文档描述:

Copies the portion of str that begins at the character position pos and spans len characters (or until the end of str, if either str is too short or if len is string::npos.

意思就是从给定的字符串 str 中的 pos 位置开始拷贝 len 个长度所形成的字符串。如果这个字符串太短了,拷贝 len 个长度超出了字符串的范围,那么就拷贝到字符串的末尾结束。或者如果 len 是缺省值 npos 的话,也是拷贝到字符串末尾结束。并且注意这里的 str 的类型是 string 类

这里的 npos 在文档中也有描述:

请添加图片描述

它代表的意思是无符号整型 size_t 的最大值,大约是四十二亿九千万左右。

int main()
{string s1("hello world");string s2(s1, 0, 5);string s3(s1, 6, 100);string s4(s1, 6);cout << s2 << endl;cout << s3 << endl;cout << s4 << endl;return 0;
}

请添加图片描述


(2) from sequence

请添加图片描述

文档描述:

Copies the first n characters from the array of characters pointed by s.

意思就是从给定的字符串 s 中从头开始拷贝 n 个字符所形成的字符串。注意这里的 s 的类型是一个普通字符串,不是 string 类。

int main()
{string s1("hello world", 5);cout << s1 << endl;return 0;
}

请添加图片描述


三、赋值重载

这个比较简单,简单过一下就可以了。

请添加图片描述

和普通的赋值一样,把一个字符串直接赋给另一个字符串。并且赋值符号 =右侧支持三种类型,分别是 const string& strconst char* schar c。赋值符号的左侧必须是一个 string 类

int main()
{string s1("hello");const char* s2 = "你好";char s3 = 'h';string s4;s4 = s1;cout << s4 << endl;s4 = s2;cout << s4 << endl;s4 = s3;cout << s4 << endl;return 0;
}

四、string 类的遍历

1. operator[]

sting 类重载了 [],这样使得我们可以对一个 string 对象像数组一样直接进行访问。

请添加图片描述
注意到这里有两个函数重载,因为在 C++ 中字符串本身是可读可写的,因此一个版本可以对字符串进行修改,而加了 const 的版本就不能修改,只能读。

int main()
{string s1("hello world");s1[0] = 'H';s1[5]++;cout << s1;return 0;
}

请添加图片描述

并且这个 [] 还包含了越界检查,如果你通过 [] 所访问的位置越界了,那么就会触发断言

int main()
{string s1("hello world");cout << s1[13] << endl;return 0;
}

请添加图片描述


2. size

请添加图片描述

通过这个成员函数我们可以得到这个字符串的长度(不包含 \0)

int main()
{string s1("hello world");cout << "s1字符串的长度是" << s1.size() << endl;return 0;
}

请添加图片描述


3. 遍历一:[ ] +下标

结合上面两个我们所讲的两个成员函数,我们便可以通过 for 循环来遍历 string 类的字符串。

void Test_1()
{string s1("ifmmp!xpsme");for (int i = 0; i < s1.size(); i++){s1[i]--;}cout << s1 << endl;
}int main()
{Test_1();return 0;
}

请添加图片描述


4. 遍历二:迭代器

在 string 这个类域中有一个叫 iterator (迭代器) 的类型,初次理解的话我们我们可以把它理解为一个像指针一样的东西。我们可以对这个迭代器进行解引用访问到它所“指向”的内容。

(1) begin 与 end

在 string 中我们还有两个成员函数分别是 begin()end()。通过 begin() 可以返回字符串开始位置的迭代器;通过 end() 可以返回字符串最后一个字符的下一个位置的迭代器

请添加图片描述

请添加图片描述

请添加图片描述

注意到上面的 begin()end() 函数都是都两个函数重载的,如果你传的是普通的可读可写的对象,那么调用的就是普通的 begin()end() 函数,返回的也是可读可写的迭代器。而如果你传入的是 const 这样的只可读的类型,那么调用的就是第二个重载函数,返回的是一个指向内容不可修改的const_iterator。注意这个 const_iterator 是另一种迭代器类型,它不是简单地在 iterator 前加一个 const 修饰,中间还有一个 _。而 const iterator 表示的是迭代器本身不能修改。


(2) 正向遍历

通过将它们搭配使用,我们就可以使用迭代器来遍历字符串。

void Test_2()
{string s1("hello world");string::iterator it = s1.begin();  // 让 it “指向” 字符串的开始位置while (it != s1.end()){cout << *it << " ";  // 通过解引用给访问每个字符it++;}cout << endl;
}int main()
{Test_2();return 0;
}

请添加图片描述


(3) 反向迭代器、rbegin 与 rend

除了普通的迭代器 iterator 和 const迭代器 const_iterator 之外,还有两种种迭代器是反向迭代器 reverse_iterator 和 const反向迭代器 const_reverse_iterator。反向迭代器 reverse_iterator 和普通的迭代器 iterator 的遍历方向相反,它要搭配 rbegin()rend() 使用。

通过 rbegin() 返回字符串最后一个字符的反向迭代器。通过 rend() 返回字符串第一个字符前一个位置的反向迭代器。这里并没有直接访问第一个位置前一个位置的空间,不属于越界行为,实际上它是一个左开右闭得区间。而上面的 beginend 则是一个左闭右开的区间。

请添加图片描述

请添加图片描述

请添加图片描述


(4) 反向遍历

void Test_4()
{string s1("hello world");string::reverse_iterator rit = s1.rbegin();while (rit != s1.rend()){cout << (*rit) << " ";++rit;  // 这里一定注意是 ++}cout << endl;
}int main()
{Test_4();return 0;
}

请添加图片描述

别看这个反向迭代器是反向遍历的好像是 --,实际上它是 ++正向迭代器 iterator ++ 是往右走,而反向迭代器 reverse_iterator ++ 是往左走

既然我们有 [ ] +下标这样的比较简便的访问方式,我们为什么还要有迭代器这种访问方式呢?这是因为不是所有的容器都重载了 [ ],而 iterator 迭代器是容器的通用的访问方式,除了这里的 string,后面要学习的 vector 和 list 等容器都可以用迭代器进行遍历,并且使用迭代器遍历的写法都是几乎一样的


5. 遍历三:范围 for(C++11)

(1) auto 关键字

C++11 中新增了一个关键字 auto,用它可以自动识别一个变量的类型,不用我们手动去写。

int i = 1;
auto k = i;  // 自动通过右边初始化值推导 k 的类型auto p = &i;  // OK
auto* p = &i;  // OK
auto& ref = i;  // OK

auto 可以做函数参数和返回值(新一点的版本才支持)。

auto func(auto x)
{return x;
}

有时候我们的类型名称很长时就可以使用 auto 关键字去自动识别类型。

string s1("hello world");// string::iterator it = s1.begin();
auto it = s1.begin();

虽然比较好用,但是一定程度上也会降低可读性


(2) 范围 for 的使用

void Test_4()
{string s1("hello world");// 范围for// 自动取容器中的每个位置的数据赋值给e, 自动判断结束for (auto e : s1)  // 这里 e 只是一个变量名称,也可以取别的名称{cout << e;}cout << endl;
}int main()
{Test_4();return 0;
}

请添加图片描述

当然上面的范围 for 中 auto 关键字也可以写成 for(char e : s1),因为 s1 中每个位置的数据都是 char 类型,只不过我们一般都写的是 auto。

如果我们要对字符串中的字符进行修改,我们也可以采用范围 for。

void Test_4()
{string s1("hello world");for (auto e : s1){e++;}cout << s1 << endl;
}int main()
{Test_4();return 0;
}

请添加图片描述

但是从运行结果来看我们好像并没有修改成功,这是因为我们的范围 for 中是取字符串中每个位置的值赋值给的 e,相当于 e 只是一份拷贝,我们只对 e 进行了修改,而并没有对原字符串进行修改

所以为了解决这个问题,我们可以采用引用的方式,把 e 设计为字符串中每个位置字符的别名,这样一来修改 e 也就修改了原字符串中每个字符的值了。

void Test_4()
{string s1("hello world");for (auto& e : s1)  // 加一个引用{e++;}cout << s1 << endl;
}int main()
{Test_4();return 0;
}

请添加图片描述

最后简单提一句:范围 for 的底层原理是迭代器


五、容量相关

1. size 与 length

在上面我们已经讲过了 size() 成员函数,这里的 length()size() 是一模一样的,只是名字不同,都是返回字符串的长度。但是要强调的是我们之后获取字符串的长度一般都用 size() 而非 length()。因为在 STL 的容器中它们都有一个接口叫 size() 而不叫 length()。像字符串、数组这样的线性结构我们可以说它的数据个数的时候可以说 length,但是像树形结构的话说 length 就不合适,但是可以说 size,它是通用的,因此我们一般都用 size。

int main()
{string s1("hello world");cout << s1.size() << endl;  // 11 cout << s1.length() << endl;  // 11return 0;
}

2. max_size

请添加图片描述

返回一个字符串能达到的最大长度

int main()
{string s1("***");string s2("******");cout << s1.max_size() << endl;cout << s2.max_size() << endl;return 0;
}

请添加图片描述


3. capacity

请添加图片描述

返回字符串所能存储的有效字符个数

int main()
{string s1("hello world");cout << s1.capacity() << endl;return 0;
}

请添加图片描述

注意这里的实际能存储的空间总大小其实是 16,因为最后一个位置可能还要要存 \0。(比如用 c_str 转换成 C 形式的字符串时)。


4. clear

请添加图片描述

清除字符串中的数据内容,但不会销毁空间

int main()
{string s1("hello world");cout << s1.size() << endl;cout << s1.capacity() << endl;s1.clear();cout << s1.size() << endl;cout << s1.capacity() << endl;return 0;
}

请添加图片描述


5. empty

请添加图片描述

字符串的判空。

int main()
{string s1("hello world");cout << s1.empty() << endl;s1.clear();cout << s1.empty() << endl;return 0;
}

请添加图片描述


6. reserve

请添加图片描述

该函数只有一个参数 n,当你传入的这个 n 比该字符串的容量 (capacity) 更大时,该函数会将字符串的容量扩充至 n ,而又由于一些内存对齐的种种原因可能会扩充得比 n 更大一些。如果 n 比当前字符串容量小,那么该函数会视情况看要不要进行缩容至 n。并且该函数不会影响字符串本身得长度和它的内容

关于这里用 reserve 扩容与缩容,它们的消耗都不小。

扩容可以原地扩也可以异地扩,realloc 扩容是原地还是异地取决于当前空间的后面的位置有没有分配给别人,如果你要扩容的区域分配给了别人,那么只能异地扩容。而 new 只能异地扩。异地扩容是找一块新的足够大的空间然后把原来的数据拷贝过去。并且这个新空间到底扩成多大,不同平台也是有差别的,比如在 VS 下,第一次扩容扩 2 倍,再扩容就就都是 1.5 倍地扩。而在 Linux 下,每次扩容都是 2 倍

缩容的话只能是找另一块空间然后把原来的数据拷贝过去,因为我们不能简单地把原来空间后面的一部分给释放掉,这是因为释放空间不能分段释放,从哪里开的空间就要从哪里释放,相当于释放空间我们只能从头释放到尾。所以缩容我们只能找新空间然后拷贝,也有不小的消耗。不同的平台对于是否缩容也有所差异,在 VS 下是不会缩容的,但在 Linux 下,可能会缩容

总之在这里建议不要缩容,这个和编译平台有关。

int main()
{string s1(10, '#');cout << s1.capacity() << endl;s1.reserve(50);cout << s1.capacity() << endl;return 0;
}

请添加图片描述

int main()
{string s1(10, '#');cout << s1.capacity() << endl;s1.reserve(14);cout << s1.capacity() << endl;return 0;
}

请添加图片描述

可以看到在 VS 下是不会缩容的。


7. resize

请添加图片描述

传入一个参数 n,把一个字符串变成长度 (size) 为 n 的字符串。分为三种情况:

  • size < n < capacity:插入数据
  • n > capacity:扩容 + 插入数据
  • n < size:删除数据

具体插入什么数据,由第二个参数 char c 决定,如果没有传这个 char c,那么就插入 \0

(1) size < n < capacity

int main()
{string s1(10, '#');cout << s1.size() << endl;  // 10 cout << s1.capacity() << endl;  // 15s1.resize(12);s1.resize(14, 'x');return 0;
}

开始时 s1 由 10 个 # 组成。

请添加图片描述

执行 resize(12) ,没有指定要插入的字符所以插入了两个 \0

请添加图片描述

执行 resize(14, 'x'),插入两个 x

请添加图片描述


(2) n > capacity

这个和上面的原理一样,区别就是要扩容,扩容到多少这个取决于平台。

int main()
{string s1(10, '#');cout << s1.size() << endl;  // 10 cout << s1.capacity() << endl;  // 15s1.resize(50);return 0;
}

请添加图片描述

请添加图片描述


(3) n < size

实际上就是只保留前 n 个数据。

int main()
{string s1(10, '#');cout << s1.size() << endl;  // 10 cout << s1.capacity() << endl;  // 15s1.resize(5);return 0;
}

请添加图片描述


8. shrink_to_fit(C++11)

请添加图片描述

该函数主要用于缩容,使字符串的容量 (capacity) 去适配它的大小 (size),它不会影响字符串本身的大小 (size) 以及内容。这里的缩容也是一个不具有约束力的请求,到底缩不缩取决于平台。

int main()
{string s1(100, '#');cout << s1.capacity() << endl;s1.resize(10);cout << s1.capacity() << endl;s1.shrink_to_fit();cout << s1.capacity() << endl;return 0;
}

请添加图片描述


六、获取元素

1. operator[] 与 at

请添加图片描述

前面我们已经学过 []了,这里的 at() 函数和它一样,都可以用于通过下标引索访问成员函数at() 同样也有两个函数重载,一个可读可写,一个只能读。

int main()
{string s1("hello world");cout << s1[6] << endl;cout << s1.at(6) << endl;return 0;
}

请添加图片描述

两种方式的区别在于它们对于越界的检查方式不一样[] 是通过断言来检查的,而 at 则是通过抛异常的方式,是可以捕获的。


2. back 与 front(C++11)

back() 可以返回字符串最后一个有效字符,front() 可以返回第一个字符。

int main()
{string s1("hello world");cout << s1.front() << endl;  // hcout << s1.back() << endl;  // dreturn 0;
}

七、修改操作

1. push_back

请添加图片描述

尾插一个字符,同时字符串的长度 (size) + 1。注意不能尾插字符串或者 string 类,只能是一个字符。

int main()
{string s1("xxxxx");s1.push_back('y');cout << s1;  // xxxxxyreturn 0;
}

2. append

请添加图片描述

append() 的重载函数有点多,但是我们常用的也就第一个和第三个。

int main()
{string s1("hello ");s1.append("world ");cout << s1 << endl;  // hello worldstring s2("你好,世界 ");s1.append(s2);cout << s1 << endl;  // hello world 你好,世界 string s3("haha");s1.append(s3.begin(), s3.end());  // hello world 你好,世界 hahacout << s1 << endl;return 0;
}

请添加图片描述


3. operator+=(常用)

请添加图片描述

+= 操作支持 string 对象、常量字符串以及一个字符。同样也是追加操作。

int main()
{string s1;string s2("hello");s1 += s2;cout << s1 << endl;s1 += " world";cout << s1 << endl;s1 += '!';cout << s1 << endl;return 0;
}

请添加图片描述


4. insert

请添加图片描述

insert() 的接口有点冗余了,其实最常用的就第一个和第三个。

注意:谨慎使用该接口,因为该接口效率低下,要挪动数据再插入,时间复杂度为 O ( n ) O(n) O(n)

int main()
{string s1("xxx");string s2("yyy");s1.insert(0, s2);cout << s1 << endl;s1.insert(0, "zzz");cout << s1 << endl;string s3 = "---###---";s1.insert(0, s3, 3, 3);cout << s1 << endl;s1.insert(s1.size(), 3, '*');cout << s1 << endl;s1.insert(s1.begin(), 3, '*');cout << s1 << endl;return 0;
}

请添加图片描述


5. erase

请添加图片描述

这个接口比较简单,这里的 npos 之前已经说过了。

请添加图片描述

代表的是无符号整型 size_t 的最大值,大约是四十二亿九千万左右。

注意:谨慎使用该接口,因为它也要挪动数据,效率低。

int main()
{string s1("**@**xxxxxyyyy");s1.erase(10, 4);cout << s1 << endl;s1.erase(5);cout << s1 << endl;s1.erase(s1.begin() += 2, s1.end() -= 2);cout << s1 << endl;s1.erase(s1.begin());cout << s1 << endl;return 0;
}

请添加图片描述


6. replace

请添加图片描述

请添加图片描述

replace() 接口不太常用,而且重载函数也很多,就不细讲了,可以通过官方文档来查阅相关用法。下面简单演示一下用法。

int main()
{string s1("hello%world%nihao%shijie");for (int i = 0; i < s1.size(); i++){if (s1[i] == '%'){s1.replace(i, 1, " ");  // 将 i 位置后面 1 个长度的位置替换成空格}}cout << s1 << endl;return 0;
}

请添加图片描述

注意:replace() 的效率比较低,谨慎使用


八、其他操作

1. c_str 与 data

请添加图片描述

返回 C 语言形式下的字符串,相当于把一个 string 类型转换成了 const char* 类型,并且最后一个位置加上一个 \0

int main()
{string s1("string_1.cpp");FILE* fout = fopen(s1.c_str(), "r");char ch = fgetc(fout);while (ch != EOF){cout << ch;ch = fgetc(fout);}return 0;
}

请添加图片描述

datac_str 的用法一样,但是我们一般都用 c_str

请添加图片描述


2. copy

请添加图片描述

从 pos 位置开始拷贝 len 个长度给字符数组 s,并返回拷贝出来的字符数组的长度。注意这个拷贝出来的字符数组不包含 \0

int main()
{char buffer[20];string str("hello world");size_t length = str.copy(buffer, 5, 6);cout << buffer << endl;buffer[length] = '\0';cout << buffer << endl;return 0;
}

请添加图片描述


3. substr

请添加图片描述

用从 pos 位置开始截取长 len 个字符的子串去构建一个新的字符串。如果不传 pos 默认从头开始,不传 len 默认截取到尾。

int main()
{string s1("hello world");cout << s1.substr() << endl;cout << s1.substr(6) << endl;cout << s1.substr(0, 5) << endl;return 0;
}

请添加图片描述


4. find 与 rfind

请添加图片描述

请添加图片描述

从 pos 位置开始找指定 string 对象、字符串、字符的第一个匹配项,返回它的下标,如果没有找到匹配项,返回 npos(无符号整型的最大值)。find 是正着找,rfind 是倒着找。

int main()
{string s1("Test.cpp.rar.zip");string ret;size_t pos = s1.find('.');if (pos != string::npos){ret = s1.substr(pos);}cout << ret << endl;size_t rpos = s1.rfind('.');if (pos != string::npos);{ret = s1.substr(rpos);}cout << ret << endl;return 0;
}

请添加图片描述


5. find_first_of 与 find_last_of

请添加图片描述

请添加图片描述

从 pos 位置开始,把在指定字符串中出现过的第一个字符找出来,返回第一个匹配项的下标。没有找到返回 nposfind_first_of 正着找,find_last_out 倒着找。

int main()
{string str("Please, replace the vowels in this sentence by asterisks.");size_t found = str.find_first_of("aeiou");while (found != string::npos){str[found] = '*';found = str.find_first_of("aeiou", found + 1);}cout << str << endl;return 0;
}

请添加图片描述


6. find_first_not_of 与 find_last_not_of

请添加图片描述

请添加图片描述

find_first_offind_last_of 的作用相反。

int main()
{string str("hello world");size_t found = str.find_first_not_of("aeiou");while (found != string::npos){str[found] = '*';found = str.find_first_not_of("aeiou", found + 1);}cout << str << endl;return 0;
}

请添加图片描述


7. compare

请添加图片描述

请添加图片描述

将字符串对象(或子字符串)的值与其参数指定的字符序列进行比较。

比较的字符串是字符串对象的值,或者(如果使用的签名具有 pos 和 len 参数)从位置 pos 中的字符开始并跨越 len 字符的子字符串。

int main()
{string str1("green apple");string str2("red apple");if (str1.compare(str2) != 0)cout << str1 << " is not " << str2 << endl;if (str1.compare(6, 5, "apple") == 0)cout << "still, " << str1 << " is an apple" << endl;if (str2.compare(str2.size() - 5, 5, "apple") == 0)cout << "and " << str2 << " is also an apple" << endl;if (str1.compare(6, 5, str2, 4, 5) == 0)cout << "therefore, both are apples" << endl;return 0;
}

请添加图片描述


九、其他非成员函数

1. 关系运算符重载

请添加图片描述

我们很少用 compare 就是因为有关系运算符重载的原因,它用起来更方便。并且它是非成员函数,是重载在全局的。

int main()
{std::string foo = "alpha";std::string bar = "beta";if (foo == bar) std::cout << "1" << endl;if (foo != bar) std::cout << "2" << endl;if (foo < bar) std::cout << "3" << endl;if (foo > bar) std::cout << "4" << endl;if (foo <= bar) std::cout << "5" << endl;if (foo >= bar) std::cout << "6" << endl;return 0;
}

请添加图片描述


2. getline

我们输入数据的时候可以使用 cin,但是 cin 有一个不足就是它会以空格或者换行符作为分割,像比如输入的是 hello world 中间有一个空格这种情况那么实际上 cin 只拿到了 helloworld 就被丢弃了,尽管它们输入在同一行。所以为了弥补这个缺陷,我们可以使用 getline

请添加图片描述

getline 函数默认只以换行符作为分割,当你输入一串数据时,它会一直读取知道遇到换行。你也可以指定一个字符作为分割,比如 #,那它就会一直读取数据知道遇到 #

int main()
{string s1;string s2;getline(cin, s1);  // 以换行符作为结尾cout << "--------------" << endl;getline(cin, s2, '#');  // 以 # 作为结尾 cout << "--------------" << endl;cout << s1 << endl;cout << "--------------" << endl;cout << s2 << endl;return 0;
}

请添加图片描述


3. stoi(C++11)

请添加图片描述

把一个字符串转换为整型,这个字符串可能会包含一些不是数字的无效字符,那么你可以传一个指针 idx 进去,然后出这个函数之后这个指针就会指向第一个无效字符的位置。另外还可以通过 base 参数选择想要转换成的进制,默认是转换成 10 进制。

除了转换成整型之外还可以转换成其他类型,会用到其他函数,这里就不一一讲解了。

请添加图片描述

int main()
{string s1("123a****");size_t idx;cout << stoi(s1, &idx) << endl;cout << s1.substr(idx) << endl;return 0;
}

请添加图片描述


4. to_string

将其他类型转换为字符串。

请添加图片描述

注意浮点数在存储的时候是不能精确存储的,因此下面打印出来的 pi 会有一些偏差。

int main()
{string pi = "pi is " + to_string(3.1415926);string perfect = to_string(1 + 2 + 4 + 7 + 14) + " is a perfect number";cout << pi << endl;cout << perfect << endl;return 0;
}

请添加图片描述


十、string类补充知识

1. buf 数组

思考下面的程序的输出结果可能是什么

void test()
{std::string s1("111111111111111111111111111111");std::string s2("22222222");cout << sizeof(s1) << endl;cout << sizeof(s2) << endl;// vs2019 下是28,因为编译器有一个优化,为了避免频繁地在堆上开辟小块空间,类中还存了一个 16 字节的 buff 数组// 如果字符串的长度小于 16 那么这个字符串就会存在 buff 数组里,如果大了那就存在堆上// 不同的平台会有所差异
}int main()
{test();return 0;
}
  • 输出结果:

请添加图片描述

按理来说,对于 64 位机器,根据类对象大小的计算规则结果应该是 24。但是多出了 16,这时为什么?

因为 VS 的编译器有一个优化,为了避免频繁地在堆上开辟小块空间,类中还存了一个 16 字节的 buf 数组。如果字符串的长度小于 16 那么这个字符串就会存在 buf 数组里,如果大了那就存在堆上。起到一个缓冲区(buffer)的作用。

请添加图片描述

相关文章:

  • C语言循环结构实战:while和for到底用哪个?
  • JavaSE核心知识点04工具
  • 微信小程序关于截图、录屏拦截
  • MySQL问题:MVCC是什么?
  • 芯科科技推出首批第三代无线开发平台SoC,高度集成的解决方案推动下一波物联网实现突破
  • CS144 - Lecture 1 记录
  • EasyRTC嵌入式音视频实时通话SDK助力AI与IoT智能硬件打造音视频交互多场景应用
  • 初识 Pytest:测试世界的智能助手
  • ASP.NET MVC添加新控制器示例
  • 【网络通信】网络通信全解
  • 国产化Excel处理组件Spire.XLS教程:如何使用 C# 将 Excel(XLS 或 XLSX)文件转换为 PDF
  • Google Play的最新安全变更可能会让一些高级用户无法使用App
  • leetcode hot100刷题日记——23.数组中的第K个最大元素
  • day12 leetcode-hot100-21(矩阵4)
  • Docker容器启动失败的常见原因分析
  • Git使用手册保姆级教程
  • GC1267F:单相全波风扇电机预驱动芯片解析
  • ubuntu20.04安装NVIDIA显卡驱动(驱动官网下载安装包,解决开机启动黑屏问题,终极保姆式方案教程)
  • 【Android笔记】记一次 CMake 构建 Filament Android 库的完整排错过程(安卓交叉编译、CMake、Ninja)
  • King3399(ubuntu文件系统)iic(i2c)功能测试
  • html用表格来做网站布局/中国网站排名查询
  • 一流设计网站/百度竞价推广价格
  • 网站的空间是/免费十八种禁用网站
  • 做户外的网站/厦门seo公司
  • 网站宣传软文/推广营销大的公司
  • 做tcf法语听力题的网站/杭州网站优化效果