加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

C#的三大难点之一:byte与char,string与StringBuilder

(2015-11-08 20:03:29)
标签:

it

相关文章:

C#的三大难点之前传:什么时候应该使用C#?

C#的三大难点之一:byte与char,string与StringBuilder

C#的三大难点之二:托管与非托管

C#的三大难点之三:消息与事件

一、byte与char

byte

首先,我们讲一下C#中的byte类型。

JAVA中也有byte类型,和C#中的byte类型类似。

而C/C++中没有byte的基本类型,使用BYTE关键字。



 typedef unsigned char BYTE

C#中的byte,与C++中的unsigned char类似,都是存储一个0-255的数。(而C++中char的取值范围为-128-127。)
C++中与C#中,都可以强制转换为int类型。

例:



 //C++

 char c = 'a';

 int i = (int) c;

 // i = 97



//C#

byte c = 97;

int a = (int)c;

// i = 97

与C++中的char区别在于:

byte类型本质上只是一个数值,并不能代表一个字母。

因此,在C#中,类似byte c = ‘a’是错的。

同样,在C#中,如果你查看一个byte[]的内容,你只能看到一个0-255的值。而在C++中,查看一个char[]的内容时,你可以看到每个char所代表的字母。例如:



 char ca[3] = {'a', 'b', 'c'};

 char ca2[4] = "abc";

 //This is also right:

 //char ca2[] = "abc";

http://s14/mw690/0018KZGFgy6WQrntUHH9d&690

http://s10/mw690/0018KZGFgy6WQrnxANz99&690



 byte[] ba = {97, 98, 99};

 //or byte[] ba = new byte[]{97, 98, 99};

http://s13/mw690/0018KZGFgy6WQrnzRUw7c&690

注意两种数组定义方式的区别:C#定义的类型是byte[]。

另外注意一个string是以\0结尾的。所以直接用string赋值给char[]要多一位。

由于byte只表示数值,因此,只有指定了编码方式,才能将byte转化为一个可见的字符串。此外,你不能通过任何方式输出字符串:既没有类似C++中printf(%s)的用法,也不能直接byte[].toString()。

如果想转化为字符串,需要给定编码,并进行如下调用:



 Encoding.ASCII.GetString(ba);

但是,这个转换也同样存在着问题:由于ASCII编码的范围是0-127,因此,对于byte[]中大于127的值,在转换之后会直接变为字符’?’

编码的问题又是一个很复杂的问题了,本篇文章只是带过,不做详细讲解。

由于byte不能直接显示为字符,因此,byte并不是作为字符的存储格式,而是常用于存储数据流,例如:

  1. 通信中的数据包。由于C#和C++可能采用不同的编码方式,因此用纯数值的byte传输,可以避免通信过程中编码的问题。C#中通信类NetworkStream的函数Read()和Write()的声明分别为:

    
    
    public override int Read(byte[] buffer, int offset, int size);
    
    public override void Write(byte[] buffer, int offset, int size);
    
    
  2. 加解密的数据。C#中,加解密类DESCryptoServiceProvider中的函数TransformFinalBlock()的声明为:

    
    
    byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
    
    
  3. 调用C/C++编写的DLL,对于char*,在C#中可以采用byte[]接收。这一点具体会在下一段介绍。

char

C#中的char与C++中的char也是不同的。

这是因为编码方式存在着不同,C#中采用Unicode编码,因此,char的取值范围为0-65535。如下代码是正确的



 char c = (char)0x4e00;

这段代码的结果是中文汉字“一”(Unicode编码19968)。实际上,这段代码在C++中也是正确的,但是,C++会将这个数字截断为char的取值范围(-127-128),然后输出为\0。

正由于C#中的char与C++中不是完全对应的,因此,当C#调用C/C++编写的DLL时,不能用char[]与对应C/C++中的char*类型,而是要根据实际情况选择byte,string或StringBuilder。

  1. 如果DLL中char*不是用来存储ASCII字符,而是用作buffer(即可能出现0-127以外的值),C#端应该用byte[]。

  2. 如果DLL中char存储一般的ASCII字符,且参数用作输入,C#端应该用string。

  3. 如果DLL中char存储一般的ASCII字符,且参数用作输出,C#端应该用StringBuilder。其原因在后文会有叙述。

    因为此时char*的长度是不确定的,可以用类似

    
    
    StringBuilder sb = new StringBuilder(Constants.MAXLEN);
    
    

    达到类似C++中

    
    
    char* cp = new char[MAXLEN];
    
    

    及C中

    
    
    char* cp = (char *)malloc(sizeof(char) * MAXLEN);
    
    

    的效果。

StringBuilder的变长特性在后面会有详细介绍。

二、string与stringBuilder

JAVA中也存在同名类型,用法与C#类似。

在C++中,操作字符串的方式有两种:C风格字符串(继承自C,强制以\0结尾的char*)和string。

(实际上,string类也是以char*为基础的。如果自己写string类,操作的基本类型就是char[]。参见《程序员面试宝典》10.5:拷贝构造函数和赋值函数的第一题。)

string相对于C风格字符串有了一定的改进,比如强制以\0结尾,以及不需要额外处理字符串所占的内存。《C++ Primer》中4.3:C风格字符串举了一个拼接字符串的例子,在C风格字符串中,需要先仔细考虑每个字串所占的内存,再使用strcat或strncat。



 char largeStr[16 + 18 + 2]; // to hold cp1 a space and cp2

 strncpy(largeStr, cp1, 17); // size to copy includes the null

 strncat(largeStr, " ", 2); // pedantic, but a good habit

 strncat(largeStr, cp2, 19); // adds at most 18 characters, plus a null

而用string的话,我们只需要简单使用



 string largeStr = cp1; // initialize large Str as a copy of cp1

 largeStr += " "; // add space at end of largeStr

 largeStr += cp2; // concatenate cp2 onto end of largeStr

即可。

但是,string仍然有其局限性。比如,上面举的字符串拼接的例子,实际上拼接得到的是一个新的字符串largeStr,而原有的字符串cp1和cp2仍然存在,这在对字符串进行大量修改的场合,会导致严重的资源浪费。

而C#中采用StringBuilder解决这个问题。对StringBuilder的操作,如拼接Append(),插入Insert(),删除Remove(),替换Replace()(不过string也可以用s[0] = ‘a’的方式替换),都是对现有的字符串进行操作,而不会引入新的字符串,避免了新建string类所造成的系统开销。

那么,在C#调用C的DLL时,为什么对于作为输出的参数要用StringBuilder呢?

因为string不能改变自身的值,如果用string的话,函数调用之后string的值不会发生改变。

一个例子:

DLL中



 void CallFromDLL(char* cp)

 {

 printf (cp);

 printf ("\n");

 *cp='a';

 printf (cp); // If in C# we use string, cp can also be changed here

 printf ("\n");

 }

C#中



         [DllImport(@"TestLib.dll", CallingConvention = CallingConvention.Cdecl)]

         //public static extern void CallHelloFromDLL(string s);

         public static extern void CallHelloFromDLL(StringBuilder s);



         static void Main()

         {

             Console.WriteLine("This is C# program");

             //string s = new string('0', 100);

             //CallFromDLL(s);

             //Console.WriteLine(s);

             StringBuilder sb = new StringBuilder(100);

             CallFromDLL(sb);

             Console.WriteLine(sb);

           }

此外,JAVA中还有一个类型为StringBuffer(C#没有),是为了解决StringBuilder线程不安全的问题,此处不再赘述。

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有