c++string字符串⽐较效率_深度优化C++中的string
string在C++中使⽤⾮常⼴泛,⽽且也⾮常好⽤,⼀般情况下基本不会引起什么问题.
但是string也很慢,尤其是在服务器这种逻辑执⾏密度⾮常⼤的项⽬中.⼀般服务器⽤到的string也会⽐较多,⽐如存储名字,⼯⼚模式中的查索引,⽇志显⽰等等.在这些情况下string也可能会拖慢服务器.
其实我之前也没有注意到string会带来什么影响.直到有⼀天使⽤VS的诊断⼯具查看当前进程的内存分配情况时,发现有⼤量的堆内存的分配.我就开始有点疑惑,明明没有多少地⽅在申请堆内存啊,怎么会有这么多.再仔细看每⼀条堆内存.原来都是由string内部分配出去的.
上图是查看当前char[]类型的分配实例,⼤概有两万个实例,总共11MB的堆内存.由下⾯的堆栈可以看出都是由string产⽣的堆内存.
如果⾃⼰写过string类的实现的话,应该知道,string内部实际上是⼀个动态扩容的char数组,所以每产⽣⼀个string对象,都会申请⼀次堆内存.如果频繁使⽤,会造成频繁申请释放堆内存.代价是很昂贵的.
什么时候会产⽣string对象.
在栈内存⾥⾯定义了⼀个string对象,很明显会触发⼀次.
把⼀个string赋值给另⼀个string,会触发.
两个string相加,产⽣⼀个新的string,也会触发⼀次.
把string传递到函数的参数⾥,会触发⼀次.
在函数中返回⼀个string对象,会有⼀次.
⽽这些也是⾮常常⽤的⽤法.
除了堆内存⽅⾯会⽐较慢,另外⼀个就是string类型的⽐较真的很慢.
string常常会⽤来作为map的索引,在搜索时会对⽐string的⼤⼩.⽽string的⽐较跟⼀般基础数据类型的⽐较不同,会先⽐较两个字符串的长度,如果长度⼀致,再逐个⽐较每个字符.所以所需要的操作⽐基础数据类型多.
所以,弄清楚了string到底慢在哪⼉以后,再着⼿优化.
每次使⽤ string都要申请堆内存,那就先想⼀想,使⽤string到底是为了什么.是只⽤来存储字符串内容,或者是⽤来做索引,或者是⽤来连接字符串制造新的字符串.
如果只是⽤来存储字符串内容的,其实可以不需要优化,因为这种使⽤⽅式不会引起很⼤的消耗,内存⽅⾯来说,不会对他进⾏其他操作,也就不会产⽣新的字符串.也不会作为索引去⽐较字符串.但是唯⼀注意⼀点,当获取string类型的类成员变量时,尽量返回const string&类型.&是直接返回成员变量本⾝,不会产⽣临时的string,const是为了确保返回出去的成员变量不会被修改.
如果是⽤来做索引,这⾥就是说明⼀个重点,这也是我想了很久才想出来的解决办法.使⽤常量字符串,也就是const char*类型的字符串.const char*类型的字符串在做⽐较时会直接⽐较地址,跟⼀般的int⽐较完全⼀样.速度⽐string的⽐较快很多,没有函数调⽤的消耗,也不会逐字符⽐较.但是理想虽然如此,现实也会⽐较残酷.
⼀般情况下,如果这样写:
const char* a = "123";
const char* b = "123";
a和b所指向的地址是⼀样的.好像在哪⼉看过⼀些关于debug和release对字符串常量的区别.release会合并相同的字符串常量,使相同的字符串常量的地址完全⼀样.但是实际测试完全相反.
根据我测试结果,在debug模式下,a和b所指向的地址确实⼀样.但是在release下,两个指向地址不同.
确实有点造化弄⼈.其实我们也不应该去指望编译器能提供这些功能,就应该脱离编辑器使我们⽤到的两个字符串常量的地址完全相同.
所以,我们要定义⾃⼰所⽤到的全部字符串常量.
在我写的项⽬中,我经常会使⽤字符串常量去索引某个类,⽐如⽤的很多的⼯⼚模式的索引.⽐如使⽤⼯⼚模式创建⽹络消息对象PacketLogin,⽤上⾯的⽅法就是需要先定义⼀个全局的字符串常量
const char* PACKET_LOGIN = "PacketLogin";
然后注册⼯⼚
mPacketFactory->registe<PacketLogin>(PACKET_LOGIN);
在需要创建这个消息的时候使⽤
PacketLogin* login = mPacketFctory->create<PacketLogin>(PACKET_LOGIN);
如果需要的字符串常量⽐较少还能接收,如果⽐较多了,那这种写法就太繁琐了.⽽且会引⼊定义新的变量.⽽且这种写法⼀旦哪个地⽅写错了,编辑器也不会提⽰.所以就需要其他的办法来优化.因为注意到字符串常量的内容跟类名⼀致,所以想到了把类名转换为字符串.就要⽤到⼀个⾮常强⼤我也⾮常喜欢的⼀个宏#,这个宏可以将任何参数名转换为字符串常量.
所以我是这么做的
#define STR(t) #t
// ⽣成字符串常量的名字
#define NAME(name) STR_##name
// 定义⼀个字符串常量
#define DEFINE_STRING(name) const char* NAME(name) = STR(name)
NAME宏只是为了⽣成⼀个变量名,保证不重复也不跟其他有冲突.DEFINE_STRING宏才是真正的定义⼀个字符串常量.
所以定义的时候可以这么写
DEFINE_STRING(PacketLogin);
同样,利⽤宏也可以简化后⾯的写法.
#define REGISTE_PACKET(T) mPakcetFactory->registe<T>(NAME(T)
#define NEW_PACKET(T,value) T* value = mPacketFactory->create<T>(NAME(T))
所以注册和创建就可以写成这样:
REGISTE_PACKET(PacketLogin);
NEW_PACKET(PacketLogin, login);
这样好处很多,写起来很快,代码也很简洁,如果字符串不对应,编译器也会提⽰未定义字符串变量.
⽽且也确保了同⼀个字符串常量的地址⼀定相同.
前两个字符串⽤法说了,最后⼀个⽤法,连接字符串.代码⾥经常会打印⼀些信息,所以就可能出现这种情况 string a = b + c + d;这种情况
下,b+c会有⼀个新的字符串e,然后e+d⼜会有⼀个新的字符串.所以就想,如果只分配⼀次内存该多好.
当然可以.
可以这样写.
char str[256] = {0};
strcat_s(str, 256, b.c_str());
strcat_s(str, 256, c.c_str());
strcat_s(str, 256, d.c_str());
string a(str);
是不是还是显得不够简洁,那就⽤宏来简化它.
c++string类型
#define STRCAT3(destBuffer, size, str0, str1, str2)
strcat_s(destBuffer, size, str0);
strcat_s(destBuffer, size, str1);
strcat_s(destBuffer, size, str2);
最后就是
char str[256] = {0};
STRCAT3(str, 256, b.c_str(), c.c_str(), d.c_str());
string a(str);
可以针对不同情况定义多个宏:STRCAT8
这样最多就可以⽀持8个字符串的连接了.
最重要的是,这⾥只⽤到了栈内存.要知道栈内存⽐堆内存快很多,⽽且也不需要担⼼内存泄漏.
当然,也可以根据⾃⼰的情况,再定义其他的宏来简化代码.
关于字符串使⽤的⼀些⼩技巧就先说这些了,其实还有很多,只是都太零碎,使⽤⾯不⼴.故只是介绍了使⽤范围⽐较⼴的⼀些做法.
还是要说⼀句,慎重使⽤string,虽然它会代码编写带了了很⼤的⽅便,但是如果使⽤不当,或者使⽤过度,仍然会造成不⼩的压⼒.所以就需要想尽各种办法来优化,去避免这些不必要的开销.
⽆论是宏也好,定义字符串常量也好,直接操作char数组也好,都只是为了去除⼀些不必要的计算和内存消耗,毕竟我们使⽤字符串的⽬的本⾝也很简单,只要能满⾜需求就可以了.

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。