.net 基础知识大杂烩(1) ——内建类型

          我们在思想、言词、行为方面的精确性程度是对我们是否忠于真理的初步检验。
                                                                                       ——巴兰德

摘要

许多公司在面试的时候都喜欢问一些非常基础的问题。例如面试官会问:“.net里有哪些内建类型?”,应聘者一边心里嘀咕着:“就不能整点4岁滴?”,一边回答说:“有int、long、double、decimal....喔,对了,还有String,常用的差不多就是这些了。”这个答案并没有错,但是它很难让你脱颖而出。在这个什么都讲优化的年代,我们也有必要做一点“面试优化”了^_^

内建类型

.Net Framework 类型位数范围C#VB
类型 示例 类型 示例
类型示例类型示例
Boolean1/boolbool a; // 编译错误:a 未必初始化就不能使用(对于所有基本类型都有这个约束,后面就不一一列举了)
bool b = true;
BooleanDim a As Boolean  ' OK. a = False (对于所有基本类型都会像这样被初始化为默认值,后面就不一一列举了)
Dim b As Boolean = True
SByte1-2^7 ~ 2^7-1sbytesbyte a = -3;
a = 128; // 编译错误:常量值“128”无法转换为“sbyte”
SByteDim a As SByte = -3
a = 128 ' 编译错误:常量表达式无法在类型“SByte”中表示。
Byte10 ~ 2^8-1bytebyte a = 128;
a = -3; // 编译错误:常量值“-3”无法转换为“byte”
ByteDim a As Byte = 128
a = -3 ' 编译错误:常量表达式无法在类型“Byte”中表示。
Int162-2^15 ~ 2^15-1shortshort a = -3;
a = 32768; // 编译错误:常量值“32768”无法转换为“short”
ShortDim a As Short = -3
a = 32768 ' 编译错误:常量表达式无法在类型“Short”中表示。
UInt1620 ~ 2^16-1ushortushort a = -3; // 编译错误:常量值“-3”无法转换为“ushort”
a = 32768;
UShortDim a As UShort = -3 ' 编译错误:常量表达式无法在类型“UShort”中表示。
a = 32768
Int324-2^31 ~ 2^31-1intint a = -3;IntegerDim a As Integer = -3
UInt3240 ~ 2^32-1uintuint a = 0;UIntegerDim a As UInteger = 0
Int648-2^63 ~ 2^63-1longlong a = 3;
long b = 3L;
LongDim a as Long = 3
Dim a as Long = 3L
UInt6480 ~ 2^64-1ulongulong a = 3;
ulong b = 3L;
ULongDim a as ULong = 3
Dim a as ULong = 3L
Single4±1.5*10^-45 ~ ±3.4*10^38
有效数字7位
floatfloat a = 21; // OK. a = 21
float b = 21.5; // 编译错误:不带后缀的小数被视为double类型。
float c = 21.5F; // c = 21.5
float t = 12345678L; // OK, t = 12345680.0。int 和 long 可被隐式转换为float, 但精度会有损失
SingleDim a As Single = 21 ' OK
Din b As Single = 21.5 'OK. VB的编译器能根据b的类型自行确定21.5的类型
Dim c As Single = 21.5F 'OK
Dim t As Single = 12345678L; ' OK, t = 12345680.0。int 和 long 可被隐式转换为float, 但精度会有损失
Double8±5.0*10^−324 ~ ±1.7*10^308
有效数字15~16位
doubledouble a = 21.2; // a = 21.2
double b = 21.2F; // b = 21.2000007629395
double c = 34.6 - 34.0; // c = 0.600000000000001
double d = 21; // d = 21
DoubleDim a As Double = 21.2 ' a = 21.2
Dim b As Double = 21.2F ' b = 21.2000007629395
Dim c As Double = 34.6 - 34.0 ' c = 0.600000000000001
Dim d As Double = 21 ' d = 21
Decimal16±1.0×10^−28 ~ ±7.9*10^28
有效数字28~29位
deicmaldecimal a = 21L; // a = 21
decimal b = 21;  // b = 21
decimal c = 1.5; // 编译错误:不能隐式地将 Double 类型转换为“decimal”类型
decimal d = 1.5M; // d = 1.5
DecimalDim a As Decimal = 21L ' a = 21
Dim b As Decimal = 21 ' b = 21
Dim c As Decimal = 1.5 ' OK VB的编译器能根据c的类型自行确定1.5的类型
Dim d As Decimal = 1.5D ' 注意在VB里“D”表示“Decimal”;而在C# 里“D”表示“Double”
Char20 ~ 2^16-1charchar c1 = 'a';
char c2 = '天';
int a = (int)c1; // a = 97
int b = (int)c2; // b = 22825 (0x5929)
char c3 = '\u5929'; // c3 = '天'
char c4 = '\x5929'; // c4 = '天'
CharDim c1 As Char = "a"
Dim c2 As Char = "天"
Dim a As Integer = AscW(c1)  ' a = 97
Dim b As Integer = AscW(c2)  ' b = 22825 (0x5929)
Dim c3 As Char = ChrW(22825) ' c3 = "天"
Dim c4 As Char = ChrW(&H5929) ' c4 = "天"
String//stringstring a = "abc";
string b = "123";
string c = a + b; // c = "abc123"
string t = null;
StringDim a As String = "abc"
Dim b As String = "123"
Dim c As String = a + b ' c = "abc123"
Dim d As String = a & b ' d = "abc123"
Din t as String = Nothing
Object//objectObject a = 9; // 装箱
int b = (int)a; // 拆箱
Dim a As Object = 9 ' 装箱
Dim b As Integer = a ' 拆箱

出彩

1. float、double、decimal之间有什么异同?

float和double是符合IEEE标准的二进制浮点数,它们的取值范围比decimal大得多,但是有效数字位数比decimal少,有舍入误差,只有符合“k/(2^n)  其中k和n均为整数”的数值能够被精确表示,其它形式的数值均有舍入误差。
例如下列数值不会产生舍入误差
double  a  =   0.5         //  a = (1/2^1)   被实际存储为 0.5
double  b  =   0.25 ;      //  a = (1/2^2)   被实际存储为 0.25
double  c  =   0.1875 ;    //  a = (3/2^4)   被实际存储为 0.1875
double  d  =   13.28125 //  a = (425/2^5) 被实际存储为 13.28125

而下列数值则会产生舍入误差
double  e  =   0.2 ;   //  a = (1/5)   被实际存储为 0.20000000000000001
double  f  =   0.3 ;   //  a = (3/10)  被实际存储为 0.29999999999999999
double  g  =   0.6 ;   //  a = (3/5)   被实际存储为 0.59999999999999998
double  h  =   34.6 //  a = (173/5) 被实际存储为 34.600000000000001

decimal 是专为金融和财务设计的十进制浮点数,它的取值范围没有float和Decimal那么大(但是对于财务计算足够了),但是有效数字比double多,没有舍入误差,所以对于金额等对精度要求高的数据应该使用decimal。

需要注意的是Decimal是一个非常特殊的类型。虽然C#和VB都把Decimal看作是一个primitive type,但CLR却不是这样。这意味着:
①操作Decimal值得执行效率将比其它primitive type低。请看下面的示例:


比较float和decimal这两种不同的类型所对应的IL代码,可以发现两个double类型的数字相减是使用的sub指令,而两个decimal类型的数字相减需要调用Decimal的operator-()函数。

②checked和unchecked操作符,以及相关的编译器命令行开关对Decimal没有任何影响。因为checked操作符指示编译器使用add.ovf、conv.ovf等进行溢出检查的指令;unchecked操作符指示编译器使用add、conv等不进行溢出检查的指令。由于这些指令只对primitive type有效,所以不管是否使用了unchecked操作符,只要发生溢出Decimal都会抛出异常。





2. 什么是装箱和拆箱?如何避免?

1) 装箱。装箱的核心是把值类型转换为对象类型,或者转换为由值类型执行的接口类型。常见的装箱转换有4种:

① 从任何值类型转换为对象类型。
举例:
    int i = 28;
    object age = i; // 装箱
问题是真会有人写这么BT的代码么?有的。因为在.net里值类型是不能被赋值为null的。例如
    int i = null; // 编译错误:无法将 NULL 转换成“int”,因为它是一种值类型
那么该如何表示数据库中的null值呢?方法之一就是像上面那样用一个object来表示数据库中的一个字段。只是这种方法不仅会造成装箱,而且可读性也不好,因为我们只能通过注释和文档才能知道age里面保存的什么类型的数据。

解决方法1(适用于.net 1.x):
可以约定一个“哨兵值”,例如约定int.MaxValue表示null:
    public static bool IsNull(int arg)
    {
        return arg == int.MaxValue;
    }

解决方法2(适用于.net 2.0):
在.net 2.0中新增了可空值类型 Nullable<T> ,可以很好地解决这个问题。
    Nullable<int> age = null; // 这是个语法糖,相当于 Nullable<int> age = new Nullable<int>();
                                        // 也可以使用C#提供的简便写法: int? age = null;
    bool b = age.HashValue; // b = false
    age = 9; // 这是个语法糖,相当于 age = new Nullable<int>(9);
    int i = age ?? 28; // 相当于 int i = age.HasValue ? age.GetValueOrDefault() : 28;
可以看出通过C#提供的一些语法糖,使代码看上去像是把null赋值给了age,其实不过是使用默认值初始化了age而已,而age的bool型的属性HashValue默认会被初始化为false,这样就正好可以表示age是“null”这一语义了。整个过程都没有发生装箱。

再举一个装箱的例子:
    int age = 17;
    Console.WriteLine(age);  // 没有发生装箱操作,因为Console有一个WriteLine(Int32)的函数定义。
    Console.WriteLine("{0}", age); // 装箱。因为符合这个调用的函数只有Console.WriteLine(String, Object);
                                                 // 所以相当于调用 Console.WriteLine("{0}", (Object)age)
                                                 // 这是一个很隐蔽“将值类型转换为对象类型”的操作
解决方法是:
    Console.WriteLine("{0}", age. ToString()); // 没有发生装箱操作
之所以没有发生装箱是因为String本身就是一个引用类型(后面会讲到),自然不会发生装箱了。不过在Console.WriteLine() 函数内部还是会再调用一次 arg.ToString(),所以Console.WriteLine("{0}", age.ToString())和Console.WriteLine("{0}", age)哪个更快取决于“把arg装箱”更快还是调用“arg.ToString() ”更快。我实测的结果是Console.WriteLine("{0}", age)稍微快一点,不信你也可以测试一下。

② 从任何值类型转换为System.ValueType类型。
举例:
    int a = 9;
    ValueType b = a; // 装箱

③ 从任何值类型转换为值类型实现的任何接口类型。
举例:
    decimal a = 9;
    IConvertible b = a;  // 装箱
    int c = b.ToInt32(null);
decimal等数值类型实现了IFormattable, IComparable, IConvertible等等很有用的接口,可是TNND对这些接口全部采用了显示接口实现,结果就是这些有用的函数不能直接调用,必须先转换成对应的接口才行。

④ 从枚举类型转换为System.Enum类型。
    enum Tricolor { Red, Green, Blue }

    Tricolor a = Tricolor.Blue;
    Enum b = a; // 装箱

2) 拆箱。拆箱相对于装箱是一个相反的过程,其核心是将引用类型显示转换为值类型,或者是将一个接口类型转换为一个实现该接口的值类型。
① 从对象类型转换为任何值类型。
举例:
    int a = 9;
    object b = a; // 装箱
    int c = (int)b; // 拆箱

② 从System.ValueType类型转换为任何值类型。
③ 从任何接口类型转换为实现该接口类型的任何值类型。
④ 从System.Enum类型转换为任何枚举类型。
    enum Tricolor { Red, Green, Blue }

    Tricolor a = Tricolor.Blue;
    Enum b = a; // 装箱
    Tricolor c = (Tricolor)b;  // 拆箱
    Tricolor red = (Tricolor)Enum.Parse(typeof(Tricolor), "Red"); // 拆箱, 因为Enum.Parse()返回值是Object类型

3. String到底是引用类型还是值类型?

String是引用类型,但是它(被故意设计成)具有值类型的行为。换句话说,人们希望String具有值类型的行为,但是出于性能的考虑需要它是引用类型。
例如
    string a = "对1-2-3的赞美之词(此处省略2000字...)";  // a 将占用约4k内存
    string b = a; // 如果 String 是值类型,就必须进行由 a 到 b 的逐位拷贝,
                       // 那么a和b加起来将占用约8K内存,且效率不佳。
                       // 如果String是引用类型,那么b就是一个指向a的引用,b只需要4个字节的内存。
    string b = b.Insert(1, "傻瓜"); // 我们想把 "傻瓜" 插入到b中,可是因为b是a的引用,修改b就相当
                                               // 于修改了a,而我们不喜欢这样(也就是说我们希望String具有值类
                                               // 型的行为)。于是Insert()函数被设计成并不直接修改b,而是先把
                                               // b克隆一份,然后修改这个克隆,然后返回这个克隆。这就是
                                               // ValueObject 模式
    Console.WriteLine(a); // 输出 "对1-2-3的赞美之词(此处省略2000字...)"; 
    Console.WriteLine(b); // 输出 "对傻瓜1-2-3的赞美之词(此处省略2000字...)";




转载于:https://www.cnblogs.com/1-2-3/archive/2008/01/26/net-basic-knowledge-1-cs-vb-native-type.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值