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

标签:
it |
相关文章:
C#的三大难点之一:byte与char,string与StringBuilder
一、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并不是作为字符的存储格式,而是常用于存储数据流,例如:
-
通信中的数据包。由于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);
-
加解密的数据。C#中,加解密类DESCryptoServiceProvider
中的函数TransformFinalBlock()的声明为: byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
-
调用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。
-
如果DLL中char*不是用来存储ASCII字符,而是用作buffer(即可能出现0-127以外的值),C#端应该用byte[]。
-
如果DLL中char存储一般的ASCII字符,且参数用作输入,C#端应该用string。
-
如果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线程不安全的问题,此处不再赘述。