————————————————————————————————————
/*
* Copyright (c) 2005 by DoZerg. ALL RIGHTS RESERVED.
* Consult your license regarding permissions and restrictions.
*/
#pragma once
#include<string>
namespace DoZerg{
namespace StringHelper{
int CheckBaseFromString(const std::string & value){
int base = 0;
for(std::string::const_iterator v = value.begin();v != value.end() && base != -1; ++v)
switch(base){
case 0:
base = ('+'==*v || '-'==*v)?1:('0'==*v)?2:(*v>'0' && *v<='9')?10:-1;
break;
case 1:
base = ('0'==*v)?2:(*v>'0' && *v<='9')?10:-1; //only '+' or '-'
break;
case 2:
base = ('x'==*v || 'X'==*v)?3:(*v>='0' && *v<'8')?8:-1; //just 0
break;
case 3:
base = ((*v>='0' && *v<='9')||(*v>='a' && *v<='f')||(*v>='A' && *v<='F'))?16:-1; //0x
break;
case 8:
base = (*v>='0' && *v<'8')?8:-1;
break;
case 10:
base = (*v>='0' && *v<='9')?10:-1;
break;
case 16:
base = ((*v>='0' && *v<='9')||(*v>='a' && *v<='f')||(*v>='A' && *v<='F'))?16:-1;
break;
default:;
}
return base;
}
bool DivideStringByTwo(std::string & value,bool & IsZero){ //return the highest bit,then divide the string by 2,in reverse order
bool carry(false);
for(std::string::reverse_iterator v=value.rbegin();v!=value.rend();++v){
if(carry)
*v+=10;
carry=*v&1;
*v>>=1;
}
std::string::size_type p(value.find_last_not_of(char(0))+1);
IsZero=!p;
value.erase(p);
return carry;
}
void MutiStringWithTwo(std::string & value,int AddNumber){ //multiple the string,then add the number(0 to 9),in reverse order
int carry(AddNumber);
for(std::string::iterator v=value.begin();v!=value.end();++v){
*v=(*v<<1)+carry;
if(carry=*v/10)
*v%=10;
}
if(carry)
value.push_back(carry); //this requires 0<=carry<=9
}
std::string & ReverseString(std::string & value,int AddValue = 0){ //reverse the string,and add AddValue to all elements
std::string::size_type l=value.length();
if(l&1)
value[l>>1]+=AddValue;
for(std::string::size_type i=0;i<(l>>1);++i){
value[i]^=value[l-i-1];
value[l-i-1]^=value[i];
value[i]^=value[l-i-1];
value[i]+=AddValue;
value[l-i-1]+=AddValue;
}
return value;
}
}//namespace StringHelper
}//namespace DoZerg
————————————————————————————————————
这个文件内的函数完成2个重要的任务:把10进制数字字符串转换成2进制比特位,把2进制比特位转换成10进制数字字符串。
首先是CheckBaseFromString。它接受一个8进制的、10进制的或16进制表示的数字字符串,按照C语言标准,8进制数字以'0'开头,后接小于8的数字序列;16进制数字以"0x"或"0X"开头,后接'0'-'9,'a'-'f','A'-'F'的字符序列。数字前面允许一个'-'或'+'表示数值的符号。
CheckBaseFromString按照这个标准来判断一个字符串是否是正确的数字表示,并得到它的基数(8,10,16)。它的实现原理则使用了有限状态机原理,每本《编译原理》都会讲到的东西。具体的流程我就不想多讲了,只要用一个例子走一遍就清楚了。
CheckBaseFromString的返回值中,只有8,10,16是表示数值的真正基数,返回2表示数值为0,其他的数值(0,1,3,-1)都表示字符串不是正确的数字表示。
然后我想介绍排在最后的函数ReverseString。这个函数从名字就可以看出来,它反转传入的字符串参数value,并返回value的引用。不过值得一提的是它的第二个参数AddValue,是需要加到value的每一个字符ASCII码上去的。
具体怎么样呢,看看代码会更清楚。
std::string::size_type l=value.length();
得到value的长度l。
if(l&1)
value[l>>1]+=AddValue;
如果l是奇数,那么把中间的那个字符ASCII码加AddValue。比如l=5,那么就先把value[2]加AddValue,显然在后面的交换过程中,是不会处理到value[2]的,所以这里先单独处理了。
然后的循环就是首尾交换字符了,
value[i]^=value[l-i-1];
value[l-i-1]^=value[i];
value[i]^=value[l-i-1];
这3行代码交换value[i]和value[l-i-1]的值,如果你不懂怎么做的,那么看看《离散数学》吧。
下面2行代码则给每个字符加AddValue。
这个函数其实非常简单,只要是稍微有经验的coder,一眼就能看出来。所以对不起,我啰嗦了!
好了,该到精彩部分了。
下面我讲DivideStringByTwo。它接受2个参数:
std::string & value,
bool & IsZero
第一个参数value是一个表示10进制数字的字符串,但是不是简单的"123"。如果value表示的是123,那么它包含的3个字符是
char[3] = {3,2,1} = {'3' - '0','2' - '0','1' - '0'};
是不是看到了ReverseString的影子?不错,从"123"到上面的形式的转换正是ReverseString的工作,当然它也负责相反方向的转换。
那么究竟为什么用这种形式来表示数字123呢?让我们温习一下10进制数转2进制数的过程。
比如数字123,它转化为2进制是(1111011),计算过程如下:
123 / 2 = 61 余 1
61 / 2 = 30 余 1
30 / 2 = 15 余 0
15 / 2 = 7 余 1
7 / 2 = 3 余 1
3 / 2 = 1 余 1
1 / 2 = 0 余 1
0 结束
于是我们把余数从下往上排列,就是(1111011)。
好了,现在我可以描述DivideStringByTwo的具体作用了,它执行上面的一次除法和求余的工作。
同样用上面的例子,假定输入的字符串value为{3,2,1},代码:
for(std::string::reverse_iterator v=value.rbegin();v!=value.rend();++v){
if(carry)
*v+=10;
carry=*v&1;
*v>>=1;
}
对value执行除以2的操作,于是value变成了{1,6,0}。代码:
std::string::size_type p(value.find_last_not_of(char(0))+1);
IsZero=!p;
value.erase(p);
得到最后一个非0字符(6)的下一个位置(0),并移除,为什么?显然{1,6,0}实际上表示的61,那么应该用{1,6}就够了,所以这样做的原因就是紧缩value的长度,显然DivideStringByTwo的时间复杂度是与value长度有关的。
于是这里又可以说明另一个设计决定——把123倒置成{3,2,1}来表示——的好处了:每次只需要在末尾erase元素。
IsZero虽然是作为一个参数传进来了,实际上的作用却是返回value是否已经成了0,这一点通过!p来判断。因为std::string::npos通常都是-1,所以如果“最后一个非0字符”不存在,p的值会是0。
最后就是函数的返回值。如果你看懂了上面除以2的代码,就应该知道此时carry的值表示了最后是否有余数(1为true,0为false),所以整个函数的返回值也就表示了“Divide String By Two”之后的余数是多少。
在把10进制数字字符串转化成2进制比特位的时候,DivideStringByTwo是最关键的调用函数之一。
最后我们来看函数MutiStringWithTwo。它的作用与DivideStringByTwo正好相反,它把字符串表示的数字乘以2。为了知道它怎么工作的,我们再来温习一下2进制数怎么转化成10进制数。
以上面的2进制序列(1111011)为例,转化过程如下:
1111011
111011 结果 1
11011 结果 1 + 1 * 2 = 3
1011 结果 1 + 3 * 2 = 7
011 结果 1 + 7 * 2 = 15
11 结果 0 + 15 * 2 = 30
1 结果 1 + 30 * 2 = 61
结果 1 + 61 * 2 = 123
上面的过程非常清晰,每次把10进制数乘以2,加上2进制序列的最高位。MutiStringWithTwo完成的就是每一步乘2加X的操作,其中AddNumber是需要加的数值。
代码我想不用讲解了,如果弄懂了上面的过程,看代码就像看小说一样容易。
同样在这个过程中,采用{3,2,1}的方式表示123是有优势的,最高位在后面,进位的时候只要push_back就行了。