变量、常量和类型

变量、常量和类型

变量

变量其实是一个点位符,它引用了一块内存地址,但它存储的值是可以变化的。

变量声明

单个变量声明:string yourName;
多个相同类型变量声明: string x, y, z;
多个不同类型变量声明:

string x, y;
double z;

在声明时可以进行初始化: string yourName = “eric”;
声明时可以选择性地初始化:string x = “x”, y, z;

变量初始化

变量初始化时的一些要求

  • 所有的局部变量在被显式地初始化之前,如果使用它则会被编译器抛出编译异常
  • 所有字段级变量被编译器初始化为所属类型中的默认值

变量的作用域

  • 类的字段所处的作用域等同于该字段所属类所在的作用域
  • 局部变量的作用域仅限于声明它的方法或循环体内部,以大括号{}为界

作用域重叠

  • 同一个作用域内,存在两个同名的变量,这里不关心变量的类型是否相同。如果存在这种情况,编译器将会报错,编译将无法继续。
  • 局部变量和字段级变量同名,那么局部变量会将同名的字段级变量隐藏,就是说局部变量的作用域内,局部变量的值覆盖了字段级变量的值。

常量

  • 在声明时立即初始化,其值在初始化后将无法再进行更改
  • 使用显式类型声明,不能使用关键字var
  • 可以在类、结构、接口中进行声明
  • 可以作为类、结构、接口的字段,也可以是定义在类、结构中的方法内部的局部变量,事实上常量永远是静态的,虽然并没有使用static关键字(也不允许)
  • 无法接受变量的赋值,哪怕该变量是static并且是readonly也不行,在初始化时只能使用另一个常量为它赋值,当然直接赋予一个具体的值更好

静态常量和动态常量的区别

本小节内容转载自 静态常量和动态常量区别

在C#中定义常量的方式有两种:

  • 静态常量(Compile-time constant)
  • 动态常量(Runtime constant)

前者用“const”来定义,后者用“readonly”来定义。 对于静态常量(Compile-time constant),它的书写方式如下:

public const int MAX_VALUE = 10;

为什么称它为静态常量呢,因为如上声明可以按照如下理解(注意:如下书写是错误的,会出编译错误,这里只是为了方便说明)。

public static const int MAX_VALUE = 10;

用const定义的常量,对于所有类对象而言都是一样的,因此需要像访问静态成员那样去访问const定义的常量,而用对象的成员方式去访问会出编译错误。此外,对于静态常量的访问在编译的时候,是用常量的值去替换常量,例如:

int nValue = MAX_VALUE;

这句在编译之后,和如下这句所产生的中间语言代码是一样的。

int nValue = 10;

不过,在用const来定义常量的时候,在类型上有很多限制。首先,此类型必须属于简单的数据类型(内建的int和浮点型、枚举或字符串),同时此类型的初始化不能通过new来完成,因此一些用struct定义的值类型常量也不能用const来定义。

相对于const而言,用readonly来定义常量要灵活的多,它的书写方式如下:

public readonly int MAX_VALUE = 10;

为什么称为动态变量,因为系统要为readonly所定义的常量分配空间,即和类的其他成员一样拥有独立的空间。此外,readonly所定义的常量除了在定义的时候可以设定常量值外,还可以在类的构造函数中进行设定。由于readonly所定义的常量相当于类的成员,因此使用const来定义常量所受到的类型限制,在使用readonly去定义的时候全部消失,即可以用readonly去定义任何类型的常量。 综合上面所述,至于对比两者之间的区别具体如下。

readonly常量只能声明为类字段,支持实例类型或静态类型,可以在声明的同时初始化或者在构造函数中进行初始化,初始化完成后便无法更改。 const常量除了可以声明为类字段之外,还可以声明为方法中的局部常量,默认为静态类型(无需用static修饰,否则将导致编译错误),但必须在声明的同时完成初始化。
两者的比较

静态常量 动态常量
定义 声明的同时要设置常量值 声明的时候可以不需要进行设置常量值,可以在类的构造函数中进行设置
类型限制 首先类型必须属于值类型范围,且其值不能通过new来进行设置 没有限制,可以用它定义任何类型的常量。对于类对象而言 对于所有类的对象而言,常量的值是一样的。 对于类的不同对象而言,常量的值可以是不一样的。
内存消耗 要分配内存,保存常量实体
综述 性能要略高,无内存开销,但是限制颇多,不灵活 灵活,方便,但是性能略低,且有内存开销

对于在定义常量的时候,到底是用const来定义还是readonly来定义,我以前为了追求性能,因此尽量用const来定义。但是在此书中,提到了一个关于使用const会产生潜在的bug。就是在程序中使用DLL类库某个类的静态常量时,如果在类库中修改静态常量的值,其它接口没有发生变化,一般来说,程序调用端是不需要重新编译,直接执行就可以调用新的类库。不过就是在此情况下,会产生潜在的bug。这是由于静态常量在编译的时候,是用它的值去替换常量,因此在调用端的程序也是这样进行替换的。例如:在类库中定义了一个静态常量,如下:

public const int MAX_VALUE = 10;

那么对于程序中调用此静态常量这段代码,在编译后产生的中间语言代码中,是用10来进行替换,即使用静态常量的地方,改为10了。 那么当类库的静态变量发生变化后,例如:

public const int MAX_VALUE = 15;

那么对于调用端程序是可以在没有重新编译的情况下进行运行,不过此时程序的中间语言代码对应于静态变量的值是10,而不是新类库中的15。因此这样产生的不一致,程序会引发潜在的bug。解决此类问题的方法,就是调用端程序在更新类库之后重新编译一下,即生成新的中间语言代码。

对于如上在const定义常量时所存在的潜在bug,在用readonly定义常量时是不会发生的。因为readonly定义的常量类似于类的成员,因此在访问的时候需要根据具体常量地址来访问,从而避免此类bug。

鉴于此,建议用readonly来替换const去定义常量。

数据类型

C#中的内置类型有bool, byte, sbyte, char, decimal, double, float, int, uint, long, ulong, object, short, ushort, string, 分为值类型(也称为简单类型)和引用类型

值类型

结构类型、数值型(整型(sbyte, byte, char, short, ushort, int, uint, long, uling)、浮点型(float, double),十进制型(decimal))、布尔型(bool)和自定义结构类型和枚举类型

整数初始化时, 如果整数没有后缀, 则其类型为以下类型中可表示其值的第一个类型:int, uint, long, ulong, 同时可以根据后缀进行指定, L或l表示, 再根据实际大小来确定是long还是ulong, U或u表示, 根据实际大小, 判断其类型是uint还是ulong, 如果用UL以其组合, 则表示ulong

浮点数初始化时, 小数默认被视为double, double myDouble = 2.3; 若希望整数被视为double, 使用后缀d或D, double x = 10d; 如希望实数被视为decimal类型, 使用后缀m或M, decimal myMoney = 1023.5m; 如希望被视为float, 使用后缀f或F, float y = 3.14F;

后缀中有大写和小写, 但推荐使用大写, 毕竟小写的L容易与1混淆

引用类型

引用类型的变量存储在托管栈中,而实际的数据存储在托管堆中, 主要由接口、数组、Object类型、类、委托、字符串、null类型构成, 另外还有指针类型(只能在unsafe标记的上下文中使用)、dynamic类型(大多数情况下与object相同, 不同在于编译器对于dynamic类型的表达式不做进一步解析和类型检查, 实际上会被编译成object, 因此只存在于编译期, 在运行时并不存在)

参考引用

[1]: C# 权威指南
[2]: 静态常量和动态常量区别

猜你喜欢

转载自www.cnblogs.com/home-wang/p/10943092.html