首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

在上一节中,读者可能想知道为什么需要利用函数交换数据。原因是C#中的变量仅能从代码的本地作用域访问。给定的变量有一个作用域,访问该变量要通过这个作用域来实现。

变量的作用域是一个重要的主题,最好用一个示例来说明。下面的示例将演示变量在一个作用域中定义,但试图在另一个作用域中使用的情形。

试试看:定义和使用基本函数

(1) 对Ch06Ex01中的Program.cs进行如下修改:

class Program

{

static void Write()

{

Console.WriteLine("myString = {0}", myString);

}

static void Main(string[] args)

{

string myString = "String defined in Main()";

Write();

Console.ReadKey();

}

}

(2) 编译代码,注意显示在任务列表中的错误和警告:

The name 'myString' does not exist in the current context

The variable 'myString' is assigned but its value is never used

示例的说明

什么地方出错了?在应用程序主体(Main()函数)中定义的变量myString不能在Write()函数中访问。

原因是变量有一个作用域,在这个作用域中,变量才是有效的。这个作用域包括定义变量的代码块和直接嵌套在其中的代码块。函数中的代码块与调用它们的代码块是不同的。在Write()中,没有定义myString,在Main()中定义的myString则超出了作用域—— 它只能在Main()中使用。

实际上,在Write()中可以有一个完全独立的变量myString,修改代码,如下所示:

class Program

{

static void Write()

{

string myString = "String defined in Write()";

Console.WriteLine("Now in Write()");

Console.WriteLine("myString = {0}", myString);

}

static void Main(string[] args)

{

string myString = "String defined in Main()";

Write();

Console.WriteLine("\nNow in Main()");

Console.WriteLine("myString = {0}", myString);

Console.ReadKey();

}

}

这段代码就可以编译,结果如图6-4所示。

图 6-4

这段代码执行的操作如下:

● Main()定义和初始化字符串变量 myString。

● Main() 把控制权传送给Write()。

● Write()定义和初始化一个字符串变量myString,它与Main()中定义的myString变量完全不同。

● Write()把一个字符串输出到控制台上,该字符串包含在Write()中定义的myString的值。

● Write()把控制权传送回Main()。

● Main()把一个字符串输出到控制台上,该字符串包含在Main()中定义的myString的值。

作用域以这种方式覆盖一个函数的变量称为局部变量。还有一种全局变量,其作用域可覆盖几个函数。修改代码,如下所示:

class Program

{

static string myString;

static void Write()

{

string myString = "String defined in Write()";

Console.WriteLine("Now in Write()");

Console.WriteLine("Local myString = {0}", myString);

Console.WriteLine("Global myString = {0}", Program.myString);

}

static void Main(string[] args)

{

string myString = "String defined in Main()";

Program.myString = "Global string";

Write();

Console.WriteLine("\nNow in Main()");

Console.WriteLine("Local myString = {0}", myString);

Console.WriteLine("Global myString = {0}", Program.myString);

Console.ReadKey();

}

}

结果如图6-5所示。

图 6-5

这里添加了另一个变量myString,这次进一步加深了代码中的名称层次。这个变量定义如下:

static string myString;

注意这里也需要static关键字。在这种形式的控制台应用程序中,必须使用static 或 const关键字,来定义这种形式的全局变量。如果要修改全局变量的值,就需要使用static,因为const禁止修改变量的值。

为了区分这个变量和Main()与Write()中同名的局部变量,必须用一个完整限定的名称为变量名分类,参见第3章。这里把全局变量称为Program.myString。注意,在全局变量和局部变量同名时,这是必需的。如果没有局部myString变量,就可以使用myString表示全局变量,而不需要使用Program.myString。如果局部变量和全局变量同名,全局变量就会被屏蔽。

全局变量的值在Main()中设置如下:

Program.myString = "Global string";

在Write()中访问:

Console.WriteLine("Global myString = {0}", Program.myString);

为什么不能使用这个技术通过函数交换数据,而要使用前面介绍的参数来交换数据?有时,这确实是一种交换数据的首选方式,但在许多情况下不应使用这种方式。是否使用全局变量取决于函数的位置。使用全局变量的问题在于,它们一般不适合于“常规用途”的函数—— 这些函数能处理我们所提供的数据,而不仅限于处理特定全局变量中的数据。详见本章后面的内容。

6.2.1 其他结构中变量的作用域

在继续之前,应先注意一下上一节的一个要点总结了上述内容,并超出了函数之间的变量作用域。前面说过,变量的作用域包含定义它们的代码块和直接嵌套在其中的代码块。这也可以应用到其他代码块上,例如分支和循环结构的代码块。考虑下面的代码:

int i;

for (i = 0; i < 10; i++)

{

string text = "Line " + Convert.ToString(i);

Console.WriteLine("{0}", text);

}

Console.WriteLine("Last text output in loop: {0}", text);

字符串变量text是for循环的局部变量,这段代码不能编译,因为在该循环外部调用的Console.WriteLine()试图使用该变量text,这超出了循环的作用域。修改代码,如下所示:

int i;

string text;

for (i = 0; i < 10; i++)

{

text = "Line " + Convert.ToString(i);

Console.WriteLine("{0}", text);

}

Console.WriteLine("Last text output in loop: {0}", text);

这段代码也会失败,原因是变量必须在使用前声明和初始化,而text是在for循环中初始化的。赋给text的值在循环块退出时就丢失了。但是还可以进行如下修改:

int i;

string text = "";

for (i = 0; i < 10; i++)

{

text = "Line " + Convert.ToString(i);

Console.WriteLine("{0}", text);

}

Console.WriteLine("Last text output in loop: {0}", text);

这次text是在循环外部初始化的,可以访问它的值。这段简单代码的结果如图6-6所示。

图 6-6

在循环中最后赋给text的值可以在循环外部访问。

可以看出,这个主题的内容需要花一点时间来掌握。在前面的示例中,循环之前赋给text空字符串,而在循环之后的代码中,该text就不会是空字符串了,其原因不能立即看出。

这种情况的解释涉及到分配给text变量的内存空间,实际上任何变量都是这样。只声明一个简单的变量类型,并不会引起其他的变化。只有在给变量赋值后,这个值才占用一块内存空间。如果这种占据内存空间的行为在循环中发生,该值实际上定义为一个局部值,在循环的外部会超出了其作用域。

即使变量本身没有局部化到循环上,循环所包含的值也局部化到该循环上。但是,在循环外部赋值可以确保该值是主体代码的局部值,在循环内部它仍处于其作用域中。这意味着变量在退出主体代码块之前是没有超出作用域的,所以可以在循环外部访问它的值。

幸而,C#编译器可检测变量作用域的问题,它生成的响应错误信息可以帮助我们理解变量作用域的问题。

最后一个要注意的问题是,应采用“最佳实践”。一般情况下,最好在声明和初始化所有的变量后,再在代码块中使用它们。一个例外是把循环变量声明为循环块的一部分,例如:

for (int i = 0; i < 10; i++)

{

...

}

其中i局部化于循环代码块中,但这是可以的,因为我们很少需要在外部代码中访问这个计数器。

6.2.2 参数和返回值与全局数据

本节详细介绍如何通过全局数据以及参数和返回值,与函数交换数据。先看看下面的代码:

class Program

{

static void showDouble(ref int val)

{

val *= 2;

Console.WriteLine("val doubled = {0}", val);

}

static void Main(string[] args)

{

int val = 5;

Console.WriteLine("val = {0}", val);

showDouble(ref val);

Console.WriteLine("val = {0}", val);

}

}

注意:

这段代码与本章前面的代码略有不同,在前面的示例中,在Main()中使用了变量名myNumber,这说明了局部变量可以有相同的名称,且不会相互干涉。这里列出的两个代码示例比较类似,以便我们集中精力研究它们的区别,而无需担心变量名。

和下面的代码比较:

class Program

{

static int val;

static void showDouble()

{

val *= 2;

Console.WriteLine("val doubled = {0}", val);

}

static void Main(string[] args)

{

val = 5;

Console.WriteLine("val = {0}", val);

showDouble();

Console.WriteLine("val = {0}", val);

}

}

这两个showDouble()函数的结果是相同的。

现在,使用哪种方法并没有什么硬性规定,这两种方法都是有效的。但是,需要考虑一些规则。

首先,在第一次讨论这个问题时,使用全局值的showDouble()版本只使用全局变量val。为了使用这个版本,必须使用这个全局变量。这会对该函数的多样性有轻微的限制,如果要存储结果,就必须总是把这个全局变量值复制到其他变量中。另外,全局数据可以在应用程序的其他地方由代码修改,这会导致预料不到的结果(即使我们没有认识到这一点,值也是可以改变的)。

但是,损失了多样性常常是有好处的。我们常常希望把一个函数只用于一个目的,使用全局数据存储能减少在函数调用中犯错的可能性,例如把它传递给错误的变量。

当然,也可以说,这种简化实际上使代码更难理解。显示指定参数可以一眼看出发生了什么改变。例如myFunction(val1, out val2)函数调用,其中val1和val2都是要考虑的重要变量,在函数执行结束后,val2就会被赋予一个新值。反之,如果这个函数不带参数,就不能对它处理了什么数据做任何假设。

最后,记住并不总是能使用全局数据。本书的后面将介绍在不同的文件中编写的代码,以及不同命名空间中的代码如何通过函数彼此通信。像这样的情况,代码常常要分开编写,显然不能使用全局存储方式。

总之,可以自由选择使用哪种技术来交换数据。一般情况下,最好使用参数,而不使用全局数据,但有时使用全局数据更合适,使用这个技术并没有错。

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论