【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;
}
这里初始化 s2
和 s3
的区别在于初始化 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& str
、const char* s
和 char 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()
返回字符串第一个字符前一个位置的反向迭代器。这里并没有直接访问第一个位置前一个位置的空间,不属于越界行为,实际上它是一个左开右闭得区间。而上面的 begin
和 end
则是一个左闭右开的区间。
(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;
}
data
和 c_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 位置开始,把在指定字符串中出现过的第一个字符找出来,返回第一个匹配项的下标。没有找到返回 npos
。find_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_of
与 find_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
只拿到了 hello
,world
就被丢弃了,尽管它们输入在同一行。所以为了弥补这个缺陷,我们可以使用 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)的作用。