C#专题之C#的变量

来源:互联网 发布:热血战歌羽化数据 编辑:程序博客网 时间:2024/06/05 02:50
思多雅[天行健] 发布时间:2008.11.24

上一章节,我们一起学习了C#变量与类型中的包装和解包,这一章,我们来一起学习C#的变量。
    变量代表数据的实际存储位置。各个变量所能存储的数值由它本身的类型决定。  在变量被赋值以前,变量自身的类型必须被明确地声明。
    在下面,我们将会提到,变量或者被初始化的或者未初始化的。一个初始化的变量在被定义时被赋予了一个确定的初始值,而未初始化的变量在定义时并未被赋予确定的初始值。对于一个在程序某处被认为具有确定数值的,必然在指向这一位置的所有可能的执行路径上存在赋值操作。

-------思多雅[天行健]版权所有,首发太平洋论论坛,转载请注明-------

一、变量类型
C#共有七种变量类型:静态变量,实例变量,数组元素,数值参数,引用参数,输出参数和局部变量。下面的部分将分别对每一种变量类型做相关描述。
举个例子:
     class A
    {
    public static int x;
     int y;
    void F(int[] v, int a, ref int b, out int c) {
        int i=1;
        c=a+b++;
     }
     }
  x 是一个静态变量,y 是一个实例变量,v[0]是一个数组元素,a 是数值参数,b 是引用参数,c 是一个输出参数,i是一个局部变量。

1.1 静态变量
    使用static修饰符定义的变量称为静态变量。静态变量在被创建并加载后生效,当被卸载后失效。
    静态变量的初始值为此类型变量的默认值。
    为了方便明确赋值检查,静态变量被认为是初始化过的。

1.2 实例变量
一个没有static 修饰符声明的域被称为实例变量。
1.2.1 类中的实例变量
当创建某类的一个实例的时候,隶属于该类的实例变量也被生成,当不再有关于这个实例的引用而且实例的析构函数执行了以后,此实例变量失效。类中实例变量的初始值为这种类型变量的默认值
    为了方便进行明确赋值检查,类中的实例变量是初始化过的。

1.2.2 结构体中的实例变量
    一个结构体中的实例变量与隶属与该结构体的结构体变量寿命相同。换句话说,结构体中的实例变量和其中的其他变量一样被同时创建,并且同时失效。而且该结构体中的实例变量的初始赋值状态和其中的其余变量一致。当一个结构体变量被是初始化过的,结构体的实例变量也是如此;反过来说,如果一个结构体变量是没有经初始化的时,结构体的实例变量也是没有经初始化的。

1.3  数组元素
    当任意一个数组实例被创建时,这个数组的元素也被同时创建,当不再有任何正对这个数组实例的引用时,它的元素也就此失效。
   数组中每个元素的初始值为该数字元素类型的默认值。为了方便明确赋值检查,所有的数字元素都会被认为是初始化的。

1.4  数值参数
    当一个不带有ref 或out 修饰参数被声明时,我们称它为数值参数。
    当被隶属的函数子句function member(method, constructor, accessor, operator )调用时,数值参数自动生成,同时被赋以调用中的参数值。当函数成员返回后,数值参数失效。
   为了方便明确赋值检查,所有的数值参数都被认为是初始化过的。

1.5 引用参数
    当一个带有ref 修饰语的参数被声明时,我们称之为引用参数。
    引用参数本身并不创建新的存储空间。同时,引用参数指向函数子句调用中作为参数给出的相关变量表征的存储空间。这样,此形式参数的数值总是等于它所指向的变量。
   下面时关于引用参数的赋值规则。请注意它们同下面所说的输出参数相关规则的区别。
在一个变量被传递给函数子句调用中相关引用参数之前,它自身必须被明确赋值。
在函数子句界定的范围内,引用参数被认为是初始化过的。

   在结构体类型的方法实例或存取程序实例中,关键字this 就象是此结构体类型的引用参数,相关的应用我们在后面还会继续说到。

1.6  输出参数
    当一个带有out 修饰语的参数被声明时,我们称之为输出参数。
   输出参数本身并不创建新的存储空间。同时,输出参数指向函数子句调用中作为参数给出的相关变量表征的存储空间。这样,此输出参数的数值总是等于它所指向的变量。
   下面时关于输出参数的赋值规则。请注意它们后面所说的形式参数相关规则的区别。
在一个变量被传递给函数子句调用中相关输出参数之前,它自身不需要被明确地赋值,详见后面的说明。
在函数子句调用中,每个被传递给输出参数的变量被认为在该执行路径中已被赋值。
在函数子句界定的范围内,输出参数被认为是初始化过的。
在函数子句返回之前,每一个输出参数必须被明确地赋值,详见§5.3 节。

   在结构体类型的构造函数中,关键字this 就象是此结构体类型的输出参数,详见§7.5.7 节。

1.7  局部变量
   局部变量被局部变量声明语句创建,该语句可以在block 块,for 循环语句或者switch 分支语句中出现。当控制权进入block 块, for 循环语句或者switch 分支语句时,其中的相关局部变量被创建。当控制权离开block 块, for 循环语句或者switch 分支语句时,其中的相关局部变量随即失效。
  局部变量不会被自动初始化,也就是说它不会有缺省值。为了方便明确赋值检查,局部变量被认为是初始化过的。局部变量声明语句可以包括一个变量初始化器,此时该变量在除它的变量初始化器表达式内的完全的有效范围中被认为是明确赋值的。
   在一个局部变量的有效范围中,在它被声明之前的所有关于它的引用都被是错误的。

-------思多雅[天行健]版权所有,首发太平洋论论坛,转载请注明-------

二、默认值
   下面几种类型的变量在初始化时被自动赋予相应的默认值:
1. 静态变量
2. 类实例中的实例变量
3. 数组元素
变量的默认值直接取决于它自身的类型和下面几种因素:
1. 对于数值型的变量,默认值就是被此数值类型构造函数计算时使用的数值。
2. 对于形式型变量,默认值为null。

-------思多雅[天行健]版权所有,首发太平洋论论坛,转载请注明-------

三、明确赋值
在一个特定的函数子句可执行代码位置,如果通过静态流分析某一个变量被编译器证明为被自动初始化或者为至少一条赋值语句的执行对象,那么该变量将被认为是明确赋值的。关于明确赋值的相关规则如下所示:
一个初始化过的变量被认为是明确赋值的。
对于一个在特定位置的未初始化的变量,如果所有可能的指向该位置的执行路径满足以下若干条件中的任何一个,那么它也被认为是明确赋值的:
1.在某一个赋值语句中该变量作为左操作数出现。
2 .任何一个调用表达式或者目标创建表达式 (详见§7.10.1 节)将该变量作为一个输出参数传递。
3.作为局部变量,该变量的局部变量声明语句包含变量初始化器。

一个结构体类型的实例变量的明确赋值状态将同时被单独和共同跟踪。另外,除了声明的各项规则之外,下面的各项规则适用于结构体类型变量和它们的实例变量:
如果一个实例变量中包含被明确赋值的结构体类型变量,则该变量被认为是明确赋值。
如果一个结构体类型的变量中的所有实例变量都被认为是明确赋值的,那么这个结构体变量也是明确赋值的。

明确赋值是下列各项的前提:
任何时刻,当一个变量获得自身的值时,它就是明确赋值的。这样就可以杜绝不确定的数值出现。
    除去下列情况,表达式中的变量都会获得相关变量数值:
    1.变量作为简单赋值语句的左操作数。
    2.变量作为一个输出参数被传递
    3.变量作为结构体类型的变量并在成员访问中作为左操作数出现。
当变量被作为形式参数被传递时,它本身必须是被明确赋值的。这样才能确保被调用的函数子句认为该形式参数是被明确赋值的。
无论函数子句在何处返回(通过返回语句return 和程序执行到函数子句的末尾),所有函数子句中的输出参数都必须是被明确赋值的。这样就确保函数子句不会返回不具备明确数值的输出参数,也会使编译器认为函数子句把某一变量当作输出参数等同于都给变量赋值。
结构体类型的构造函数自何处返回,其中的this 变量都必须使被明确赋值的。

下面的例子告诉我们try 语句的不同block 会使如何影响明确赋值的。
    class A
     {
        static void F() {
           int i, j;
           try {
               // neither i nor j definitely assigned
               i = 1;
               // i definitely assigned
               j = 2;
               // i and j definitely assigned
            }
           catch  {
               // neither i nor j definitely assigned
               i = 3;
               // i definitely assigned
            }
           finally {
               // neither i nor j definitely assigned
               i = 4;
               // i definitely assigned
               j = 5;
               // i and j definitely assigned
            }
           // i and j definitely assigned
        }
     }
     静态流分析在检测明确赋值状态使将考虑&&,||,?:这些运算符的特殊运算。在下面例程中的每一个method 我们将会看到
     class A
     {
        static void F(int x, int y) {
            int i;
            if (x >= 0 && (i = y) >= 0) {
                // i definitely assigned
             }
            else {
                // i not definitely assigned
             }
            // i not definitely assigned
         }
     static void G(int x, int y) {
            int i;
            if (x >= 0 || (i = y) >= 0) {
                // i not definitely assigned
             }
            else {
                // i definitely assigned
             }
            // i not definitely assigned
         }
     }
变量i 在if 语句中的一个嵌套语句中是被明确赋值的,而在其余位置并不如此。在F method 中的if 语句的第一个嵌套语句中,因为表达式i=y 被事先执行,所以变量i 在是被明确赋值的。而在这个if 语句的第二个嵌套语句中,由于变量i 未被赋值,所以它被认为是未被明确赋值的。请注意,如果变量x 的数值是负的,那么变量i 是不会被赋值的。
  同样,在G 方法中,变量i 在第二个嵌套语句中是被明确赋值的而在第一个嵌套语句中并不是这样。

3.1  初始赋值变量
下面所列各种类型的变量属于初始赋值变量:
静态变量
类实例中的实例变量
被初始赋值的结构体类型变量中的实例变量
数组元素
数值参数
形式参数

3.2 非初始赋值变量
下面类型的变量属于非初始赋值变量:
未被初始赋值的结构体变量中的实例变量
输出参数,包括结构体construc 到r 中的this 变量
局部变量

-------思多雅[天行健]版权所有,首发太平洋论论坛,转载请注明-------

四、变量引用
   变量引用(variable-reference)是一种我们归类为变量的表达式。变量引用指向一个特定的存储地址,我们可以从这里获得它存储的当前值或者存入一个新的数值。
   下面的结构需要一个表达式充当变量引用:
赋值表达式的左侧(可以是属性获取或索引获取程序)。
一个在方法或构造函数构造器调用中作为ref 或out 参数被传递的变量

-------思多雅[天行健]版权所有,首发太平洋论论坛,转载请注明-------

五、变量与局部变量的一些使用及注意事项:
每个变量都属于一种类型,它确定什么样的值可以存储在该变量中。局部变量是在方法、属性或索引器中声明的变量。局部变量是通过指定类型名称和声明符(它指定变量名和可选的初始值)定义的,
例如:
int i;
int j = 1;但局部变量声明也可以包含多个声明符。i 和 j 的声明可以重写为:
int i, j = 1;一个变量必须先赋值,然后才能使用它的值。
class Test
{
   static void Main() {
      int i;
      int j = 1;
      int y = i + j;  
      ...
   }
}
这将导致编译时错误,因为它试图在给变量 i 赋值之前使用它。

字段是与类或结构或与类或结构的实例关联的变量。用 static 修饰符声明的字段定义静态变量,不用此修饰符声明的字段则定义实例变量。静态字段与类型关联,而实例变量与实例关联。
示例
using Personnel.Data;
class Employee
{
   private static DataSet ds;
   public string Name;
   public decimal Salary;
   ...
}显示了具有一个私有静态变量和两个公共实例变量的 Employee 类。

形参声明也可以定义变量。有四种类型的参数:值参数、引用参数、输出参数和参数数组。
值参数用于“in”参数传递。在此过程中,自变量的值被传入方法中。因为一个值参在方法中用自己的变量存储(而非原自变量),所以对此参数的修改不会影响到原自变量。值参的变量是通过复制原自变量的值来初始化的。
示例
using System;
class Test {
   static void F(int p) {
      Console.WriteLine("p = {0}", p);
      p++;
   }
   static void Main() {
      int a = 1;
      Console.WriteLine("pre:  a = {0}", a);
      F(a);
      Console.WriteLine("post: a = {0}", a);

   }
}显示了一个具有名为 p 的值参数的方法 F。示例输出结果如下
pre:  a = 1
p = 1
post: a = 1即使值参数 p 已修改。
“引用参数”用于“by reference”参数传递。在此过程中,“引用参数”就是调用者提供的自变量的别名。“引用参数”并不定义自己的变量,而是直接引用原自变量,因此对“引用参数”的修改就将直接影响相应原自变量的值。引用参数用 ref 修饰符来声明。
来看看例子:
using System;
class Test {
   static void Swap(ref int a, ref int b) {
      int t = a;
      a = b;
      b = t;
   }
   static void Main() {
      int x = 1;
      int y = 2;
      Console.WriteLine("pre:  x = {0}, y = {1}", x, y);
      Swap(ref x, ref y);
      Console.WriteLine("post: x = {0}, y = {1}", x, y);
   }
}显示了一个具有两个引用参数的 Swap 方法。产生的输出为:
pre:  x = 1, y = 2
post: x = 2, y = 1在形参声明和形参的使用中都必须使用 ref 关键字。在调用位置使用 ref 可以引起对参数的特别注意,这样阅读代码的开发人员就会知道参数值可以因调用而更改。

对于输出参数来说,调用者提供的自变量的初始值并不重要,除此之外,输出参数与引用参数类似。输出参数是用 out 修饰符声明的。
再来看看这个例子
using System;
class Test {
   static void Divide(int a, int b, out int result, out int remainder) {
      result = a / b;
      remainder = a % b;
   }
   static void Main() {
      for (int i = 1; i < 10; i++)
         for (int j = 1; j < 10; j++) {
            int ans, r;
            Divide(i, j, out ans, out r);
            Console.WriteLine("{0} / {1} = {2}r{3}", i, j, ans, r);
         }
   }
}
显示了一个 Divide 方法,该方法包含两个输出参数:一个参数用于除法的结果,另一个参数用于余数。
对于值、引用和输出这三种类型的参数,在调用方提供的自变量和用于表示它们的参数之间存在一对一的对应关系。参数数组则允许存在多对一关系:多个参数可以用一个自变量来表示。换言之,参数数组允许可变长度的自变量列表。
参数数组用 params 修饰符声明。一个给定的方法只能有一个参数数组,而且它必须始终是最后一个指定的参数。参数数组的类型总是一维数组类型。调用方可以传递一个属同一类型的数组变量,或任意多个与该数组的元素属同一类型的自变量。
例如
using System;
class Test
{
   static void F(params int[] args) {
      Console.WriteLine("# of arguments: {0}", args.Length);
      for (int i = 0; i < args.Length; i++)
         Console.WriteLine("\targs[{0}] = {1}", i, args);
   }
   static void Main() {
      F();
      F(1);
      F(1, 2);
      F(1, 2, 3);
      F(new int[] {1, 2, 3, 4});
   }
}

-------思多雅[天行健]版权所有,首发太平洋论论坛,转载请注明-------

小知识
大家知道,C++语言是一种类型安全语言(type-safe language,TSL),而且C++编译器保证每一个数值被保存在相应的变量中。变量的数值可以通过赋值或者++或--运算符改变。同是类型安全语言,C#跟C++又有没有些区别呢?大家体会一下。