三只小猪

longshanks 2008-09-18 07:22:03
三只小猪

莫华枫


小时候听说过三只小猪的故事,隐约记得故事是讲三只小猪用不同方法造房子,对抗老狼。这些天做软件,遇到一个无比简单的问题,但在三种不同的语言中,却有着截然不同的解法。

最近,冷不丁地接到公司下派的一个紧急任务,做手持POS和PC程序之间交换数据的程序。各种各样的麻烦中,有一个小得不起眼的问题,POS机中数据的字节序和PC相反。这可不算是什么啊。没错,是在太简单了。尽管如此,还是引发了一场争论。做POS程序的,希望PC程序做转换。做PC程序的,希望POS 程序做转换。(谁都想少做点,对吧;))。最终,作为和事佬的我,为了维护和谐的氛围,揽下了这件事。当然,到底在那里做,还是要决定的。最终选择在PC 上,毕竟PC上调试起来容易。(天煞的,这个POS机没有debug,也没有模拟器,显示屏还没我手机的大,做起来着实费事)。
其实,我的本意是想在POS上做这个转换。因为POS用的是C(一个不知什么年代的gcc),可以直接操作字节。基本的代码看起来差不多应该是这样:
unsigned long InvData(unsigned long val, int n) {
unsigned long t=val, res=0;
for(; n >0; n--)
{
res = res << 8;
res |= (unsigned char)t;
t = t >> 8;
}
return res;
}
n是数据类型的字节长度。这里使用了最长的无符号整数类型。这是核心转换函数,各种类型的转换函数都可以从中派生:
long InvDataLong(long val) {
return (long)InvData((unsigned long)val, sizeof(val));
}
short InvDataShort(short val) {
return (short)InvData((unsigned short)val, sizeof(val));
}
...
最后,有一个比较特殊的地方,float。float的编码不同于整型,如果直接用(unsigned long)强制类型转换,只会把float数值的整数部分赋予参数,得不到正确的结果。正确的做法,应当是把float占用的四个字节直接映射成一个 unsigned long:
float InvDataFloat(float val) {
float val=InvData(*(unsigned long*)(&val), sizeof(val));
return *(float*)(&val);
}
通过将float变量的地址强制转换成unsigned long*类型,然后再dereference成unsigned long类型。当然还有其他办法,比如memcpy,这里就不多说了。至于double类型,为了简化问题,这里将其忽略。如果有64位的整型,那么 double可以采用类似的解法。否则,就必须写专门的处理函数。
当然,最终我还是使用C#写这个转换。相比之下,C#的转换代码显得更具现代风味。基本算法还是一样:
public static ulong DataInv(ulong val, int n)
{
ulong v1_ = val, v2_ = 0;

for (; n > 0; n--)
{
v2_ <<= 8;
v2_ |= (byte)v1_;
v1_ >>= 8;
}

return v2_;
}
对于习惯于C/C++的同学们注意了,long/ulong在C#中不是4字节,而是8字节。也就是C/C++中的longlong。以这个函数为基础,其它整数类型的字节序转换也就有了:
public static ulong DataInv(ulong val)
{
return DataInv(val, sizeof(ulong));
}

public static uint DataInv(uint val)
{
return (uint)DataInv((ulong)val, sizeof(uint));
}

public static int DataInv(int val)
{
return (int)DataInv((uint)val);
}
...
然而,面对float,出现了麻烦。在C#中,没有指针,无法象C那样将float展开成ulong。(unsafe代码可以执行这类操作,但这不是C#嫡亲的特性,并且是违背C#设计理念的。这里不做考虑)。C#提供了另一种风格的操作:
public static float DataInv(float val)
{
float res_ = 0;

byte[] buf_ = BitConverter.GetBytes(val);
byte t = 0;

t = buf_[0];
buf_[0] = buf_[3];
buf_[3] = t;

t = buf_[1];
buf_[1] = buf_[2];
buf_[2] = t;

res_ = BitConverter.ToSingle(buf_, 0);

return res_;
}
这个做法尽管有些累赘,但道理上很简单:把float变量转换成一个字节流,然后把相应的位置对调,就获得了字节反序的float。相比C的float转换,C#明显不够简练。原因很简单,C#根本不是用来干这个的。C是一种非常底层的语言,它的内存模型是完全直观的,与硬件系统相对应的。因而,对于这种与机器相关的操作,当然也显得游刃有余。而C#定位于高层开发的高级语言,底层的内存模型是被屏蔽的,程序员无需知道和关心。
不过,C#的代码却拥有它的优势。只需看一眼这些函数的使用代码,便不言自明了:
//C代码
int x=234;
float y=789.89;
short z=332;
x=InvDataInt(x);
y=InvDataFloat(y);
z=InvDataShort(z);

//C#代码
int x=234;
float y=789.89;
short z=332;
x=DataInv(x);
y=DataInv(y);
z=DataInv(z);
在C代码中,对于不同的类型,需要使用不同命名的函数。而在C#代码中,则只需使用DataInv这样一个函数名。至于届时选用那个版本的函数,编译器会根据实际的类型自动匹配。C#运用函数重载这个特性,使得调用代码可以采用统一的形式。即便是数据的类型有所变化,也无需对调用代码做任何修改。(这在我的开发过程中的得到了验证,传输数据的成员类型曾经发生变化,我也只是修改了数据结构的定义,便将问题搞定)。这一点,在C中是无法做到的。
归结起来,C由于侧重于底层,在数据转换方便的灵活性,使得转换代码的构建更加容易。而C#则得益于函数重载,在转换代码使用方面,有独到的优势。
迄今为止,三只小猪中,还只有两只出现。下面就要第三只出场了。
作为C++的粉丝,我会自然而然地想到使用C++来实现这个转换功能。于是便有了如下的代码:
unsigned long InvData(unsigned long val, int n) {
unsigned long t=val, res=0;
for(; n >0; n--)
{
res = res << 8;
res |= (unsigned char)t;
t = t >> 8;
}
}
long InvData(long val) {
return (long)InvData((unsigned long)val, sizeof(val));
}
short InvData(short val) {
return (short)InvData((unsigned short)val, sizeof(val));
}
...
float InvData(float val) {
float val=InvData(*(unsigned long*)(&val), sizeof(val));
return *(float*)(&val);
}
这些代码就好象是C和C#代码的杂交后代。既有C的底层操作,也有C#的函数重载,兼有两者的优点。
不过,还能做得更好:
template<typename T>
T InvData(T val) {
T t=val, res=0;
int n=sizeof(T);
for(; n >0; n--)
{
res = res << 8;
res |= (unsigned char)t;
t = t >> 8;
}
return (T)res;
}
这样,就把所有的整型都一网打尽了,仅用一个函数模板,便完成了原先诸多函数所做的工作。而float版本的函数则保持不变,作为InvData()的一个重载。按照C++的函数模板-重载规则,float版的函数重载将被优先使用。

好了,三只小猪的故事讲完了。前两只小猪各有优点,也各有缺点。而第三只小猪则杂合和前两者的优点,并且具有更大的进步。尽管第三只小猪存在各种各样的缺陷,但毕竟它的众多特性为我们带来了很多效率和方便,这些还是应当值得肯定的。
...全文
726 61 打赏 收藏 转发到动态 举报
写回复
用AI写文章
61 条回复
切换为时间正序
请发表友善的回复…
发表回复
品茶 2010-06-30
  • 打赏
  • 举报
回复
决定了
打酱油去
toma2008 2010-06-30
  • 打赏
  • 举报
回复
看看


MARK
hrx1989 2010-06-30
  • 打赏
  • 举报
回复
顶了
虽然不是很明白
messitotti 2010-06-30
  • 打赏
  • 举报
回复
学习了
xuke535010084 2010-06-30
  • 打赏
  • 举报
回复
看了。
Simao 2010-06-30
  • 打赏
  • 举报
回复
学习中...
huwenfeng2001hf 2010-06-30
  • 打赏
  • 举报
回复
好强大
有没有用gprs的pos的
sagegz 2008-11-21
  • 打赏
  • 举报
回复
MARK!
CA_HA_M 2008-11-21
  • 打赏
  • 举报
回复
up
deerwin1986 2008-11-21
  • 打赏
  • 举报
回复
学习知识和精神!!
小猴饲养员 2008-11-21
  • 打赏
  • 举报
回复
很好,很强大
roadblossom 2008-11-21
  • 打赏
  • 举报
回复
mark
nullah 2008-11-21
  • 打赏
  • 举报
回复
顶原创
lp3331 2008-11-21
  • 打赏
  • 举报
回复
mark 之
qq675927952 2008-11-21
  • 打赏
  • 举报
回复
UP
xianyuxiaoqiang 2008-11-21
  • 打赏
  • 举报
回复
顶之。
xjy1204 2008-11-21
  • 打赏
  • 举报
回复
顶~~
taodm 2008-11-21
  • 打赏
  • 举报
回复
按网络序传递。由pc转换是更合理的。
另外,用模板自动转调用ntohX/htonX系列函数才是更合理解。
就呆在云上 2008-11-21
  • 打赏
  • 举报
回复
哈哈
好啊
很不错啊
难得
cruxsky 2008-11-21
  • 打赏
  • 举报
回复
UP
加载更多回复(41)

64,682

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧