概述问题字符串实际上是字符数组,所以对于string的主要问题,可能就是字符串格式化以及字符串的操作,这是因为它们都要分配内存
之前说过内存分配在堆和栈上的区别和建议:能分配在栈上就别分配到堆上,因为把内存分配到堆上会降低程序的速度。
然而,std::string和它的很多函数都喜欢分配在堆上,这实际上并不理想
#include#include void PrintName(const std::string& name) { std::cout << name << "n"; } int main() { std::string name = "Cherno"; PrintName(name); return 0; }
而就像上面的这么简单的程序,系统便为我们分配了八个字节的内存,堆分配了一次
而我们堆分配发生的地方,就是在创建字符串的地方std::string name = "CHerno";
而即使是最简单的
PrintName("Cherno");
在调用这个函数时,其构造函数依旧会分配其内存
然而这个例子还不够猛,还要再来点东西
#include#include void PrintName(const std::string& name) { std::cout << name << "n"; } int main() { std::string name = "Cherno";//1 //加了俩substr切割字符串 std::string firstName = name.substr(0, 3);//2 std::string lastName = name.substr(3, 3);//3 PrintName(name); return 0; }
而在上面的例子中,程序分配了三次8字节的内存,每一次都分配到堆上
而做了这么点事情就有三次了,便可以想象这种事可能会在你的程序里经常发生了,相当损害速度
而如何解决呢?
解决方案在使用substr时,这个函数会自己处理完原字符串后创建出一个全新的字符串,它可以变换并有自己的内存
但是我们真正要想的是那个字符串的视图(意思是我只想看看原字符串剪完以后是什么样子的,不想再利用剪完后的全新的串)
而这就是string_view发挥作用的地方了std::string_view是C++17中的一个新类(但是还是可以用别的原始的方法去实现这个做法)
它的本质上,只是一个指向现有内存的指针,换句话说,就是一个const char指针,指向其他人拥有的现有字符串,再加上一个大小size
- 我可以有一个指向第一个字符的指针,然后大小是3,而这就是我的子字符串。
- 我可以有一个指针,指向那个字符串的开头加上四个字节,把我带到子字符串的开头
换句话说,我在创建一个窗口,一个进入现有内存的小视图,而不是分配一个新的字符串,不是用substr()创建一个新的字符串
- 意思便是在观察一个已有的字符串
做这种事其实很简单,所以早在C++17之前,人们就在做这样的事情,这实际上是很常见的。观察已有字符串是没有内存分配的,按值传递字符串视图是非常轻量级的。
所以重写上面的程序
#include#include //在传递字符串的时候,不需要通过引用或类似的方式传递 //直接string_view void PrintName(std::string_view name) { std::cout << name << "n"; } int main() { std::string name = "Cherno"; //这里可以用构造函数来指定子字符串,而这里也的确这样用了,通过name.c_str() //name.c_str(),意思是:属于字符串name的const char* 类型 //作用就是得到了C语言风格的字符串,仅仅一个指针 std::string_view firstName(name.c_str(), 0); std::string_view lastName(name.c_str() + 3, 3); //可以不用从开头开始,可以直接从name.c_str()上加数字 PrintName(name); return 0; }
利用string_view去观察子字符串,去分配函数形参之后,测试出来的内存分配结果是:我们只是从这个原始字符串中得到仅仅一次堆上的分配
这很好了,但还可以更好,可以完全不需要分配内存!
做到这一点,则需要完全不使用std::string
#include#include void PrintName(std::string_view name) { std::cout << name << "n"; } int main() { //不用标准库里的std::string,而是用const char* const char* name = "Cherno"; //这里就不需要专门针对std::string的c_str()了,就直接上数字便可 //因为name这里代表的是指向字符串第一个字符的指针 std::string_view firstName(name, 0); std::string_view lastName(name + 3, 3); PrintName(name); return 0; }
这般,完成在堆上完全0分配内存
这里的"Cherno"其实还是字符串,但是被分配到了栈上
亦可笼统称为字符串视图
所以如果不是有特殊的需求,尽量开始用string_view去替换掉这些字符串



