第114讲 MString

本打算说boost的string_algorithm的,因为这个库是专门针对字符串处理的,而且原本的计划是说完这个之后我们就继续说正则表达式,但是细想了下,正好手头有这么一个库,他不但有boost的string_algorithm的常用操作方法,同时还提供对正则表达式的操作,还可以和标准库的算法无缝结合,所以,论实用性,该库拥有良好的操作界面,但论效率,自然是比不上标准库的string,主要体现在连续赋值的操作上,这个库原本打算使用标准库的分配器来进行内存分配的,但是后来没有采用,只是进行了char*的封装,测试过malloc和new的性能,结果最后的结果竟然是差不多,所以最后也连malloc和realloc也抛弃了,选择使用new来作为内存的分配器,这样对C++的同学来说如果要看源码也是很好理解的。


该库的下载链接如下:

http://yun.baidu.com/share/link?shareid=111953120&

uk=2114708594


同样是在vs2013下面编译的静态库,所以如果你们要使用的话须得在该平台下,当然如果你们想要源码自己编译自己的平台的话,那么我可以给你们源码。


好吧,我们就跑步进入主题吧,现在先来介绍该库的一些特征。


关于该库的界面,大家可以通过MString.h文件查看所有的接口,不过因为该库使用了模板的结合,又添加有大家的特化(特化是为了更好的提高效率),所以看来可能会比较辛苦,当然好处是可以看见源码。


该库最大的特点:

引用计数,内存共享,使用了完美的写时复制技术,在使用过程可以获得更好的性能体验和内存的控制,因为拥有更大的灵活性同时降低了封装性,当然,有得就会有失。


现在我们来看看怎么使用他:


1,关于构造

和std::string一样简单粗暴的构造方式,他还可以使用一个系列进行构造。


//=====================================

mj::MString str((std::istreambuf_iterator<char>(std::cin)),

std::istreambuf_iterator<char>());

std::cout <<str <<std::endl;


//=====================================

第114讲 MString

该操作将会由键盘输入来构造,遇到Ctrl+Z后停止输入,另一个另类的构建方式:


//=====================================

mj::MString str((std::istream_iterator<char>(std::cin)),

std::istream_iterator<char>());

std::cout <<str <<std::endl;


//=====================================

该构造方式和上面的一样,只是这一次的结果将所有空格都忽略了。

第114讲 MString

还可以通过std::string 的迭代器来构造:


//=====================================

std::string stdstr("

Hello World"

);

mj::MString str(stdstr.begin(),stdstr.begin()+5);

std::cout <<str <<std::endl;


//=====================================

第114讲 MString

所以自然也支持他自己的迭代器构造:


//=====================================

mj::MString stdstr("

Hello World"

);

mj::MString str(stdstr.begin(),stdstr.begin()+5);

std::cout <<str <<std::endl;


//=====================================


其他的都是简单粗暴的构造方式,所以这里就不细说了。


另外的复制构造,赋值操作和你们所想的一样,没啥好说,但是值得一提的是这里的赋值和复制是共享一块内存,所以这里的复制和赋值操作的代价是一个时间常量。


//====================================

mj::MString stdstr("

Hello World"

);

mj::MString str(stdstr);

mj::MString str1;

str1 = stdstr;

std::cout <<stdstr.GetAddr() <<std::endl;

std::cout <<str.GetAddr() <<std::endl;

std::cout <<str1.GetAddr() <<std::endl;

std::cout <<str.reference_count() <<std::endl;


//======================================

这里我们可以看到3个对象地址是一样的,同时也看到当前的引用计数为3

第114讲 MString

下面我们来对任意一个进行修改:


//=====================================

str[0] = 'W';

std::cout <<stdstr.GetAddr() <<std::endl;

std::cout <<str.GetAddr() <<std::endl;

std::cout <<str1.GetAddr() <<std::endl;

std::cout <<str.reference_count() <<std::endl;


//======================================

第114讲 MString

很明显,str的地址改了,而且当前他的引用计数变成了1,但是当我们换个操作方式:


//=====================================

char ch = str[0];

std::cout <<stdstr.GetAddr() <<std::endl;

std::cout <<str.GetAddr() <<std::endl;

std::cout <<str1.GetAddr() <<std::endl;

std::cout <<str.reference_count() <<std::endl;


//=====================================

第114讲 MString

这是并没有发生任何变化,这里也就是说,当我们对MString操作[]作为左值时当前的对象会被单独拿出来进行修改,但是当我们只是简单的读取时并没有发生任何变化,这就是完美的写时复制技巧,可以避免我们在操作字符串时不必要的拷贝和意外的修改。


那么接下来看看他的新特性:


//=====================================

mj::MString str;

str = str + "

Hello "

+ 123 + "

World "

<<789 <<"

This "

<<10 <<"

is only a test

"

;

std::cout <<str <<std::endl;


//=====================================

第114讲 MString

是不是很方便的操作呢,因为该库提供了operator+=,operator+,operator<<等等很多方便的操作符,所以使用起来相当的方便,因为这些操作都是泛型操作,所以只要可以使用ostream<<操作的类型都可以使用这些操作来变换成MString。但是有一点需要注意,看到上面我们的写法了吗?

我们使用:str = str + ………..;

你们可能在想,为什么不直接使用 str += …………….;

这里有个优先级问题,在C++里面, 右边的优先级总是高于左边,所以如果使用 str += ………的话,那么是后面的先执行这些操作,但是后面的这些类型不一定都可以有这些操作,所以就不会成功,但是我们使用 str = str + ………………的话,那么先计算的是 str+ X ,这里结果返回的是MString的引用,所以他可以继续一直操作下去。但是如果我们使用<<的话就更方便了,我们不用考虑什么,直接str<<…….就好。


提供了任意类型转换到MString,那么也提供了从MString转换到任意类型的方法:


ToOtherType(),名字难听了些,但直截了当,不过他的转换从开始遇到无法转换就停止,比如:

//===================================

mj::MString str0("

123 456 789"

);

int
num = str0.ToOtherType<int>();

std::cout <<num <<std::endl;


//结果
num = 123

//==================================

这里在遇到空格时发现无法转换为int于是转换结束,结果就是123,这种方法的好处是不修改原始数据,但是下面的操作就会对原始数据进行修改:


//==================================

mj::MString str0("

123 456 789"

);

int num;

str0 >>num;

std::cout <<num <<std::endl;

// 123

str0 >>num;

std::cout <<num <<std::endl;

// 456

str0 >>num;

std::cout <<num <<std::endl;

// 789

std::cout <<str0.empty() <<std::endl;

// 1


//==================================

operator>>操作符将MString当成一个数据源,>>不断地将数据输出来,为了很好的实现这个操作符,这里使用模板元技巧,将std::string和MString两种类型进行了特殊处理。


//==================================

mj::MString str0("

123 456 789"

);

std::string num;

str0 >>num;

std::cout <<num <<std::endl;

// "

123 456 789"

std::cout <<str0.empty() <<std::endl;

// 1

//==================================


如果需要输出的是std::string 或者是MString的话,会一下子将数据全部返回来。


下面来看看修剪功能:

//=================================

mj::MString str;

str = str + "

Hello "

+ 123 + "

World "

<<789 <<"

This "

<<10 <<"

is only a test

"

;

mj::MString str1 = str;

std::cout <<str <<1 <<std::endl;

str.trim_right();

// 删除右边的空格

std::cout <<str <<1 <<std::endl;

str.trim_left();

// 删除左边的空格

std::cout <<str <<1 <<std::endl;

str1.trim();

// 该操作等效率上面的两个操作

std::cout <<str1 <<std::endl;

//================================


下面再来看看分割操作,分割操作提供了泛型操作,可以支持带两个模板参数的模板容器,而且需要带有push_back操作的,就标准库而言就有vector,list,deque可以用来当作返回容器,该函数的声明为:


//================================

template<typename T,typename A, template<typename T1, typename A1>class C>

void split(is_any_of any, C<T, A>&

result);


//===============================

is_any_of 需要分割的标志,用法很简单:


//===============================

mj::MString str;

str = str + "

Hello "

+ 123 + "

World "

<<789 <<"

This "

<<10 <<"

is only a test

"

;

std::vector<std::string>vv;

str.split("

t"

, vv);

std::cout <<str <<std::endl;

for (auto&

v : vv){

std::cout <<v <<std::endl;

}

//==============================

第114讲 MString

如同结果显示一样,我们按照给定的空格或者t来将数据分割下来。


还有一个分割方式使用正则表达式来进行分割,使用正则表达式来操作的方法都带有reg_作为前缀,如下:


//===============================

mj::MString str;

str = str + "

Hello "

+ 123 + "

World "

<<789 <<"

This "

<<10 <<"

is only a test

"

;

std::vector<std::string>vv;

str.reg_split("

\s+"

, vv);

std::cout <<str <<std::endl;

for (auto&

v : vv){

std::cout <<v <<std::endl;

}

//================================

结果和上面的是一样的,当然使用正则表达式分割方式我们还可以将数据都消除,如下:


//=================================

str.reg_split("

(\s+)|(\d+)"

, vv);

std::cout <<str <<std::endl;

for (auto&

v : vv){

std::cout <<v <<std::endl;

}

//=================================

这次,我们将所有的数字和空格都消除了,剩下的就是字符。


分割操作在平时使用过程中是使用得最为频繁的了。


关于查找功能,如同名字一样,都是以find_为开头,find_first查找第一次出现的位置,find_last查找最后一次出现的位置,find_nth查找第n次出现的位置,这里需要说的是纠结了很久,最后决定让索引从1开始,这些函数都是泛型函数,为的就是更方便的操作,比如:


//==================================

mj::MString str;

str = str + "

Hello "

+ 123 + "

World "

<<789 <<"

This "

<<10 <<"

is only a test "

;

size_t pos = str.find_first(123);

// 我们查找的是123.而不是"

123"

if(pos != mj::MString::npos){

std::cout<<pos<<std::endl;

}

else{

std::cout<<123<<"

isn't exist"

<<std::endl;

}

//==================================

该库的查找核心是KMP算法,其实本来大可以使用标准库的serch或者find等等更为高效的算法,虽然说KMP算法也算是很高效的查找方式,但是终究是自己手写的,没有标准库的那些待优化的做得好,但是该库的目的只是为了操作方便而已,但是对于现在的硬件来说,这点性能的累赘应该不会成为问题,只是如果我们选择使用std::serch的话会获得更高的效率,以后有时候这个会更新。


提供有一系列的替换函数簇,一如名字一样,可以很好的理解:


//==================================

mj::MString str;

str = str + "

Hello "

+ 123 + "

World "

<<789 <<"

This "

<<10 <<"

is only a test "

;

str.replace_first("

123"

, 789);

std::cout <<str <<std::endl;


//=================================

还有一个更为强大系列,使用正则表达式来替换:

//=================================

str.reg_replace("

(\s+)|(\d+)"

, "

"

);

std::cout <<str <<std::endl;

//==================================

该方法将数字和空格全部替换掉。


关于替换一簇函数还有一个带_copy后缀的,使用该系列不会修改原始数据,而且返回一个新的MString对象。


append,push_back等等这些泛型函数也就顾名思义了,size和length这些函数都是时间常量复杂度的。


start_with;

以什么开头

end_with ;

以什么结尾

istart_with;

不区分大小写的start_with

iend_with;

不区分大小写的end_with


erase_系列函数和replace_系列对应,也有带有_copy的系列。


all,判断是否全部满足条件,比如:


//==================================

mj::MString str("

Hello"

);

std::cout<<str.all([&

](char c){


return c >80;

}) <<std::endl;


//==================================


结果为false,因为H<80


to_upper;

全部转换为大写。

to_lower;

全部转换为小写。他们同样有_copy的版本。


下面来看看正则表达式的一些操作:


reg_match 检查字符串是否匹配给定的表达式,必须全匹配才得算正确:

//==================================

mj::MString str("

Hello 123"

);

std::cout <<str.reg_match("

\w+"

) <<std::endl;

// false

std::cout <<str.reg_match("

\w+\s+\d+"

) <<std::endl;

// true

//==================================

reg_serch 只要有一个匹配就返回true

//==================================

mj::MString str("

Hello 123"

);

std::cout <<str.reg_serch("

\w+"

) <<std::endl;

// true

std::cout <<str.reg_serch("

\w+\s+\d+"

) <<std::endl;

// true

//==================================


有时候我们不光只是检查是否有匹配项,还需要获取匹配的结果,下面的两个函数就是满足这个功能的:

//=================================

template<class T,class A,template<class T1,class A1>class C>

bool reg_match(const std::string&

reg, C<T, A>&

result);

// 要求全匹配


template<class T,class A,template<class T1,class A1>class C>

bool reg_serch(const std::string&

reg, C<T, A>&

result) // 只返回第一个匹配的信息


template<class T, class A, template<class T1, class A1>class C>

void reg_serch_all(const std::string&

reg, C<T, A>&

result) // 返回所有匹配的信息

//===================================


看下面例子:

//===================================

mj::MString str("

Hello 123 World 789 hah 102 "

);

std::vector<std::string>vv;

str.reg_serch("

\d+"

, vv);

for (auto v : vv){

std::cout <<v <<std::endl;

} // 结果只有 123


str.reg_serch_all("

\d+"

, vv);

for (auto v : vv){

std::cout <<v <<std::endl;

} // 结果只有 123 789 102


//==================================


copy功能,为什么有这个功能呢,因为该库的特点就是内存共享,所以我们使用普通的复制赋值操作是不能完成有时候我们想要的copy的,所以特点提供有copy功能,该功能将数据重新拷贝一份出来,如果不想让他共享的,可以使用markunshare函数标记一下,该对象的内存就不再被共享。


//==================================

mj::MString str("

Hello 123 World 789 hah 102 "

);

mj::MString str1 = str.copy(0, 4);

std::cout <<str1 <<std::endl;

// Hello

mj::MString str2 = str.copy(str.begin(), str.begin() + 5);

std::cout <<str2 <<std::endl;

// Hello

//==================================

reference_count() 查看当前数据被引用的次数。


如同std::string一样,可以有迭代器操作,begin()和end()的操作方式一样,因为他们,所以MString可以和标准库的算法实现完美的结合,例如:


//=================================

mj::MString str("

Hello 123 World 789 hah 102 "

);

auto it = std::find(str.begin(), str.end(), '1');

if (it != str.end()){

std::cout <<*it <<std::endl;

}

else{

std::cout <<"

find fail …. "

<<std::endl;

}

// 输出 1


std::iter_swap(str.begin(), str.begin() + 4);

std::cout <<str <<std::endl;

// oellH …….


//=================================


最后说一下带类型安全检查的格式化功能,我没有提供其他的函数,而是之间简单的重载operator(),但是通常如果使用格式化的时候最好先使用clear,否则会将数据添加在后面,如下:


//=================================

mj::MString str("

Hello"

);

str("

%1 %2 %3 %4"

, 123, "

world"

, 789, "

Haha"

);

std::cout <<str <<std::endl;

str.clear();

str("

%1 %2 %3 %4"

,"

Hello"

, 123, "

world"

, 789);

std::cout <<str <<std::endl;

//=================================

第114讲 MString

使用起来非常方便,大家可以慢慢探索,如果还有不能满足需求的,可以提出你们的意见。这一讲的内容好像有些多得过分了,不过没关系,大家可以慢慢消化,其实也不多,就像平时使用一样使用就好。


//===============================

回复D或者d查看目录,回复数字查看相应的章节


原文始发于微信公众号(

C/C++的编程教室

):第114讲 MString

|

发表评论