D语言中字符串的操作
字符串的操作在软件开发中是特别重要的一个事情,因为基本上的编程都会使用到字符串,字符串操作的好坏决定着一个语言的好与差。在我做过的一个项目中曾经就出现过字符串操作性能问题。
D语言字符串有 string,wstring,dstring三种类型,在D语言中字符串是使用字符数组定义的,三种类型分别对应char,wchar,dchar。char只有一个字节,wchar为双字节,dchar为三字节。对字符串的操作也相当于是对数组的操作,这跟其它语言不一样,C++中字符串是以string类来进行封装,它的操作是就string类提供的函数来完成,C#中也是一样。而D中的字符更像是c语言中的字符串。
在D语言要数组是做了很大的改进,这也使得D语言的string比其它语言的string的操作在性能上表现得更好。先来个例程看看:
import std.stdio; int main(string[] argv) { auto tmp = "Hello Honan!"; int n = 8; auto sub1 = tmp[0..$]; auto sub2 = tmp[0..5]; auto sub3 = tmp[0..n]; writeln(sub1); writeln(sub2); writeln(sub3); readln(); return 0; }
运行输出
看上面程序,auto sub1 = tmp[0..$]中,$表示数组的最大长度,这句也就是取整个字符串。
这一个取子字符串的操作,这样的取字符串写法简单,而且把数组的操作直接应用到字符串操作中来,不需要另外写个subString函数来完成。在编译器层面把字符串的操作进行要处理和优化,这也可以大大提高字符串操作的性能。
有人可能就会说,除了编译器可能会做一定优化,又能提高多少性能呢?哈哈,如果你这样想不错了,来看看下面一个例子:
import std.stdio; int main(string[] argv) { auto tmp = "Hello Honan!"; auto sub2 = tmp[0..5]; (cast(char[])(sub2))[3] = 'K'; writeln(tmp); writeln(sub2); readln(); return 0; }
运行输出
看看这个运行结果,看见没有,原字符串和子字符串第3个字符都是K,这是个神奇的地方,因为这说明sub2这个子字符串没有产生实际的内存拷贝。这在字符串操作的时候将大大提高运行性能。这是一个很了不起的改进。
看官在看到这句(cast(char[])(sub2))[3] = 'K'; 时可能会说,赋值一个字符都要字得这么复杂。哈哈,先不要看他复杂,这是很有用的,这种方式叫做强类型语言特征,这样做可以使得你编译时(而不是运行程序的时候)就会发现问题,不会轻易的操作字符串里面的内容,因为一般很少会直接修改字符串的内容。操作大部分都使用第三方函数来完成。
又有人会想,那如果我的子字符串需要拷贝怎么办,这很简单,使用dup函数就可以完成:
import std.stdio; int main(string[] argv) { auto tmp = "Hello Honan!"; auto sub2 = tmp[0..5].dup; (cast(char[])(sub2))[3] = 'K'; writeln(tmp); writeln(sub2); readln(); return 0; }
auto sub2 = tmp[0..5].dup; 这一句中的dup函数就可以让字符串进行拷贝。
让我们再来看看字符的连接操作:
import std.stdio; import std.string; int main(string[] argv) { auto tmp = "Hello Honan!"; auto tmp2 = tmp ~ " end"; writeln(tmp); writeln(tmp2); readln(); return 0; }
这几个功能远远不能字符串操作的全部功能,需要更多字符串操作功能需要导入std.string包,在这个包里包含的函数有:indexOf,lastIndexOf,lastIndexOfAny,representation,capitalize,splitLines,strip,stripLeft,stripRight,
chomp,chompPrefix,chop,equal,leftJustify,rightJustify,center,detab,entab,translate,
format,sformat,xformat,xsformat,inPattern,countchars,removechars,squeeze,munch,succ,tr,
isNumeric,soundex,abbrev,column,wrap,outdent。
这么多的字符串操作,实在是太丰富了,真是牛B人的作品呀,这么多不可能一个一个地介绍,这此函数的作在string.d里有unittest例程,看看就能明白,这里只做几个重点的函数介绍:
上图是一个string.d中的单元测试 这就是unittest语法的力量,不仅能在编程里进行测试,还能把测试代码放在程序一起,让后来的使用者很清楚地知道函数是如何使用的,真是一个牛B的发明。再去看看Go语言,真的只不过是一个玩具,到使用D语言的struct的时候,让那些使用Go的人明白D语言在那方面的表现一点也不差。这才是真正值得学习的语言,能简出,更能深入。
还是先来看看字符串的几个重要操作吧。
format函数:
在处理字符串时,我们经常需要使用format函数来对字符进行格式化。
import std.stdio; import std.string; int main(string[] argv) { int age = 28; auto name = "Honan"; auto str = format("Name:%s Age:%d",name,age); str.writeln(); readln(); return 0; }
嗯,很方便,格式也和c语言中的格式一样。
来看看format原码:
string format(Char, Args...)(in Char[] fmt, Args args) { auto w = appender!string(); auto n = formattedWrite(w, fmt, args); version (all) { // In the future, this check will be removed to increase consistency // with formattedWrite enforce(n == args.length, new FormatException( text("Orphan format arguments: args[", n, "..", args.length, "]"))); } return w.data; }
这个format函数是有一个问题的,在D语言中有三种字符串,string,wstring,dstring,format函数只支持string格式,这是很不科学的,需要改进。这样子的设计会使得非英文语系程序员很不愿意使用,因为字符串是个很关键的问题。
值得大家注意的是,这不是编译器的问题,这是标准库的问题,也就是说标准库提供format函数时不够完善。因为对于非英文语系国家,在使用字符串时主要还是使用wstring或dstring。我将对中文字符串做详细测试。
indexOf函数:
查找一个字节,或是一个子字符串出现的第一个位置。
import std.stdio; import std.string; int main(string[] argv) { auto tmp = "Hello Honan! "; auto idx1 = tmp.indexOf('n'); auto idx2 = tmp.indexOf("on"); auto str = format("n index:%d on index:%d \r\n", idx1, idx2); str.writeln(); readln(); return 0; }
toLower函数:将字母转化为小字:
import std.stdio; import std.string; int main(string[] argv) { auto name = "Hello Honan! "; auto str = name.toLower(); str.writeln(); readln(); return 0; }
运行结果:
import std.stdio; import std.string; import excode; int main(string[] argv) { wstring name = "HELLO honan! HELLO "; auto str = name.toLower(); str.UNI2GBK().writeln(); readln(); return 0; }
toUpper函数:将小写转化为大写,和toLower函数一样,没什么将的了。
count函数:数子字符串的数量
import std.stdio; import std.string; int main(string[] argv) { auto name = "HELLO honan!"; auto n1 = name.count('n'); auto n2 = name.count("na"); writeln(n1,",",n2); readln(); return 0; }
count与length是不是一样?有人可能会问这个问题,答案是当然不一样,count是以字符为单,比例一个汉字有在uft8格式里有3个字节,使用count的值是1,而使用length的值为3,为什么length的长度为3呢,那是因为length只是数组的长度。
import std.stdio; import std.string; import std.conv; import excode; int main(string[] argv) { auto str = "你好!Hello!"; auto wstr = str.to!wstring(); writeln(format("str length:%d count:%d", str.length, str.count())); writeln(format("wstr length:%d count:%d",wstr.length, wstr.count())); readln(); return 0; }
因此,在我们经常需要使用中文字符串时,使用wstring的需求远远要大于使用string,因为string是uft8格式的,而uft8是变长编码,即英文字母只有一个字节,而中文汉字则有3个字节,本来string为uft8是比较好的,因为不只能使用中文,还可以使用多国文字,但这也使得string在foreach的时候不方便,没办法按字符来循环。
而在wstring中使用的是Unicode编码,在Unicode中,length和count的值是一样的,dstring中更是一样了。
这个count函数并不在string.h包中,这是因为D语言中具有类型函数扩展功能。这是一个很了不起的功能。
例如:写一个函数
int findIndex(string str,string findstr) { return indexOf(str); }
那么类型string的对象就多了一个函数findIndex,使用时可以这样使用:
auto str = "Hello Honan";
auto idx = str.findIndex("Ho");
这就是相当于findIndex的第一个参数的类型就是它的类型,这可以使得你在任何地方都可以给任何类型进行扩展。
to转换函数:用于string,wstring,dstring的转换
import std.stdio; import std.string; import std.conv; import excode; int main(string[] argv) { auto str = "你好!Hello!"; auto wstr = str.to!wstring(); writeln(UNI2GBK(wstr)); readln(); return 0; }
writeln函数在Windows中使用的是GBK格式,而默认auto str = “abc”的字符串是UTF8格式,如果直接使用writeln输出string和wstring是会出现乱码的,所以个人认为writeln函数做得不怎么好。只说在linux下正常,那么应该就是在windows中的编码处理得不对了,这可能跟windows默认字体的关吧,需要看看writeln函数的源代码。
小结一下:
string : utf8格式
wstring : Unicode格式,即utf16
dstring : 四字节格式,即utf32
这三种类型的编译,直接可以使用to函数转换。
小问题:bug,writeln在windows下兼容性做得不好,是一个小小的bug,需要修正。
string,wstring,dstring的静态字符串
-------------------------------------------------------------------
附加: 使用string,wstring,dstring的字符串初始化
auto str = “hello”; //string
auto str = “hello”w; //wstring
auto str = “hello”d; //dstring
多行字符串的使用
--------------------------------------------------------------------
在使用字符串时,我们经常需要使用多行字符串,也可以叫文本。在C#中可以使用@””,而在D语言中也提供了比较方便的方法: