2.1 C#基础
理解了C#的用途后,就可以学习如何使用它了。本章将介绍C#的基础知识,本章的内容也是后续章节的基础,好的开端等于成功的一半。阅读完本章后,读者就有足够的C#知识编写简单的程
序了,但还不能使用继承或其他面向对象的特征。这些内容将在后面的几章中讨论。
2.2 第一个C#程序
下面编译并运行最简单的C#程序,这是一个简单的控制台应用程序,它由把某条消息写到屏幕上的一个类组成。
2.2.1 代码
在文本编辑器(如Notepad)中输入下面的代码,把它保存为后缀名为.cs 的文件(如First.cs)。Main()方法如下所示(更多信息参见2.7 节):
using System;
namespace Wrox
{
public class MyFirstClass
{
static void Main()
{
Console.WriteLine("Hello from Wrox.");
Console.ReadLine();
return;
}
}
}
2.2.2 编译并运行程序
对源文件运行C#命令行编译器(csc.exe),编译这个程序:后面几章会介绍许多代码示例。编写C#程序最常用的技巧是使用Visual Studio2013 生成一个基本项目,再添加自己的代码。但是,第Ⅰ部分的目的是讲授C#语言,为了简单起见,在第17 章之前避免涉及Visual Studio 2013。我们使代码显示为简单的文件,这样就可以使用任何文本编辑器输入它们,并在命令行上编译。
csc First.cs
如果使用csc 命令在命令行上编译代码,就应注意.NET 命令行工具(包括csc)只有在设置了某些环境变量后才能使用。根据安装.NET(和Visual Studio)的方式,这里显示的结果可能与你计算机上的结果不同。
编译代码,会生成一个可执行文件First.exe。在命令行或Windows Explorer 上,像运行任何可执行文件那样运行该文件,得到如下结果:
csc First.cs
Microsoft (R) Visual C# Compiler version 12.021005.1
For C# 5.0
Copyright (C) Microsoft Corporation. All rights reserved.
First.exe
Hello from Wrox.
2.2.3 详细介绍
首先对C#语法做几个一般性的解释。在C#中,与其他C 风格的语言一样,大多数语句都以分号(;)结尾,语句可以写在多个代码行上,不需要使用续行字符。用花括号({})把语句组合为块。单行注释以两个斜杠字符开头(//),多行注释以一条斜杠和一个星号(/*)开头,以一个星号和一条斜杠(*/)结尾。在这些方面,C#与C++和Java 一样,但与Visual Basic 不同。分号和花括号使C#代码与VisualBasic 代码有差异很大的外观。如果你以前使用的是Visual Basic,就应特别注意每条语句结尾的分号。对于新接触C 风格语言的用户,忽略分号常常是导致编译错误的一个最主要的原因。另一个方面是,C#区分大小写,也就是说,变量myVar 与MyVar 是两个不同的变量。
在上面的代码示例中,前几行代码与名称空间有关(如本章后面所述),名称空间是把相关类组合在一起的方式。namespace 关键字声明了应与类相关的名称空间。其后花括号中的所有代码都被认为是在这个名称空间中。编译器在using 语句指定的名称空间中查找没有在当前名称空间中定义但在代码中引用的类。这非常类似于Java 中的import 语句和C++中的using namespace 语句。
using System;
namespace Wrox
{
在First.cs 文件中使用using 指令的原因是下面要使用一个库类System.Console。using System语句允许把这个类简写为Console(System 名称空间中的其他类也与此类似)。如果没有using,就必如果没有设置环境变量,有两种解决方法。第1 种方法是在运行csc 之前,从命令提示符窗口上运行批处理文件%Microsoft Visual Studio 2013%\Common7\Tools\vsvars32.bat。其中%Microsoft Visual Studio 2013%是Visual Studio 2013 的安装文件夹。
第2 种方法(更简单)是使用Visual Studio 2013 命令提示符代替通常的命令提示符窗口。
Visual Studio 2013 命令提示符在菜单“开始”|“程序”| Microsoft Visual Studio 2013|VisualStudio Tools 子菜单下。它只是一个命令提示符窗口,打开时会自动运行vsvars32.bat。
第2 章 核 心 C#须完全限定对Console.WriteLine()方法的调用,如下所示:
System.Console.WriteLine("Hello from Wrox.");
标准的System 名称空间包含了最常用的.NET 类型。在C#中做的所有工作都依赖于.NET 基类,认识到这一点非常重要;在本例中,我们使用了System 名称空间中的Console 类,以写入控制台窗口。C#没有用于输入和输出的内置关键字,而是完全依赖于.NET 类。接着,声明一个类MyFirstClass。但是,因为该类位于Wrox 名称空间中,所以其完整的名称是
Wrox.MyFirstCSharpClass:
class MyFirstCSharpClass
{
所有的C#代码都必须包含在一个类中。类的声明包括class 关键字,其后是类名和一对花括号。
与类相关的所有代码都应放在这对花括号中。
下面声明方法Main()。每个C#可执行文件(如控制台应用程序、Windows 应用程序和Windows服务)都必须有一个入口点——Main()方法(注意M大写):
public static void Main()
{
在程序启动时调用这个方法。该方法要么没有返回值(void),要么返回一个整数(int)。注意,在
C#中方法的定义如下所示:
[modifiers] return_type MethodName([parameters])
{
// Method body. NB. This code block is pseudo-code.
}
第一个方括号中的内容表示可选关键字。修饰符(modifiers)用于指定用户所定义的方法的某些特性,如可以在什么地方调用该方法。在本例中,有两个修饰符public 和static。修饰符public 表示可以在任何地方访问该方法,所以可以在类的外部调用它。修饰符static 表示方法不能在类的实例上执行,因此不必先实例化类再调用。这非常重要,因为我们创建的是一个可执行文件,而不是类库。把返回类型设置为void,在本例中,不包含任何参数。
最后,看看代码语句:
Console.WriteLine("Hello from Wrox.");
Console.ReadLine();
return;
在本例中,我们只调用了System.Console 类的WriteLine()方法,把一行文本写到控制台窗口上。WriteLine()是一个静态方法,在调用之前不需要实例化Console 对象。
Console.ReadLine()读取用户的输入,添加这行代码会让应用程序等待用户按回车键,之后退出几乎所有的C#程序都使用System 名称空间中的类,所以假定本章所有的代码文
件都包含using System;语句。
应用程序。在Visual Studio 2013 中,控制台窗口会消失。
然后调用return 退出该方法(因为这是Main 方法,所以也退出了程序)。在方法头中指定void,因此没有返回值。
对C#基本语法有了大致的认识后,下面就详细讨论C#的各个方面。因为没有变量不可能编写出重要的程序,所以首先介绍C#中的变量。
2.3 变量
在C#中声明变量使用下述语法:
datatype identifier;
例如:
int i;
该语句声明int 变量i。编译器不允许在表达式中使用这个变量,除非用一个值初始化了该变量。
声明i 之后,就可以使用赋值运算符(=)给它赋值:
i = 10;
还可以在一行代码中声明变量,并初始化它的值:
int i = 10;
如果在一条语句中声明和初始化了多个变量,那么所有的变量都具有相同的数据类型:
int x = 10, y =20; // x and y are both ints
要声明不同类型的变量,需要使用单独的语句。在多个变量的声明中,不能指定不同的数据类型:
int x = 10;
bool y = true; // Creates a variable that stores true or false
int x = 10, bool y = true; // This won't compile!
注意上面例子中的“//”和其后的文本,它们是注释。“//”字符串告诉编译器,忽略该行后面的文本,这些文本仅为了让人更好地理解程序,它们并不是程序的一部分。本章后面会详细讨论代码中的注释。
2.3.1 变量的初始化
变量的初始化是C#强调安全性的另一个例子。简单地说,C#编译器需要用某个初始值对变量进行初始化,之后才能在操作中引用该变量。大多数现代编译器把没有初始化标记为警告,但C#编译器把它当作错误来看待。这就可以防止我们无意中从其他程序遗留下来的内存中获取垃圾值。
C#有两个方法可确保变量在使用前进行了初始化:
● 变量是类或结构中的字段,如果没有显式初始化,创建这些变量时,其默认值就是0(类和结构在后面讨论)。
● 方法的局部变量必须在代码中显式初始化,之后才能在语句中使用它们的值。此时,初始化不是在声明该变量时进行的,但编译器会通过方法检查所有可能的路径,如果检测到局部变量在初始化之前就使用了它的值,就会产生错误。
例如,在C#中不能使用下面的语句:
public static int Main()
{
int d;
Console.WriteLine(d); // Can't do this! Need to initialize d before use
return 0;
}
注意在这段代码中,演示了如何定义Main(),使之返回一个int 类型的数据,而不是void。在编译这些代码时,会得到下面的错误消息:
Use of unassigned local variable 'd'
考虑下面的语句:
Something objSomething;
在C#中,这行代码仅会为Something 对象创建一个引用,但这个引用还没有指向任何对象。对该变量调用方法或属性会导致错误。
在C#中实例化一个引用对象需要使用new 关键字。如上所述,创建一个引用,使用new 关键字把该引用指向存储在堆上的一个对象:
objSomething = new Something(); // This creates a Something on the heap
2.3.2 类型推断
类型推断(type inference)使用var 关键字。声明变量的语法有些变化。编译器可以根据变量的初始化值“推断”变量的类型。例如:
int someNumber = 0;
就变成:
var someNumber = 0;
即使someNumber 从来没有声明为int,编译器也可以确定,只要someNumber 在其作用域内,就是一个int。编译后,上面两个语句是等价的。
下面是另一个小例子:
using System;
namespace Wrox
{
class Program
{
static void Main(string[] args)
{
var name = "Bugs Bunny";
var age = 25;
var isRabbit = true;
Type nameType = name.GetType();
Type ageType = age.GetType();
Type isRabbitType = isRabbit.GetType();
Console.WriteLine("name is type " + nameType.ToString());
Console.WriteLine("age is type " + ageType.ToString());
Console.WriteLine("isRabbit is type " + isRabbitType.ToString());
}
}
}
这个程序的输出如下:
name is type System.String
age is type System.Int32
isRabbit is type System.Bool
需要遵循一些规则:
● 变量必须初始化。否则,编译器就没有推断变量类型的依据。
● 初始化器不能为空。
● 初始化器必须放在表达式中。
● 不能把初始化器设置为一个对象,除非在初始化器中创建了一个新对象。
第3 章在讨论匿名类型时将详细探讨。声明了变量,推断出了类型后,就不能改变变量类型了。变量的类型确定后,就遵循其他变量类型遵循的强类型化规则。
2.3.3 变量的作用域
变量的作用域是可以访问该变量的代码区域。一般情况下,确定作用域遵循以下规则:
● 只要类在某个作用域内,其字段(也称为成员变量)也在该作用域内。
● 局部变量存在于表示声明该变量的块语句或方法结束的右花括号之前的作用域内。
● 在for、while 或类似语句中声明的局部变量存在于该循环体内。
1. 局部变量的作用域冲突
大型程序在不同部分为不同的变量使用相同的变量名很常见。只要变量的作用域是程序的不同部分,就不会有问题,也不会产生多义性。但要注意,同名的局部变量不能在同一作用域内声明两
次,所以不能使用下面的代码:
int x = 20;
// some more code
int x = 30;
考虑下面的代码示例:
using System;
namespace Wrox.ProCSharp.Basics
{
public class ScopeTest
{
public static int Main()
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i);
} // i goes out of scope here
// We can declare a variable named i again, because
// there's no other variable with that name in scope
for (int i = 9; i >= 0; i—)
{
Console.WriteLine(i);
} // i goes out of scope here.
return 0;
}
}
}
这段代码使用两个for 循环打印0~9 的数字,再逆序打印0~9 的数字。重要的是在同一个方法中,代码中的变量i 声明了两次。可以这么做的原因是i 在两个不同的循环内部声明,所以变量i
对于各自的循环来说是局部变量。
下面是另一个例子:
public static int Main()
{
int j = 20;
for (int i = 0; i < 10; i++)
{
int j = 30; // Can't do this — j is still in scope
Console.WriteLine(j + i);
}
return 0;
}
如果试图编译它,就会产生如下错误:
ScopeTest.cs(12,15): error CS0136: A local variable named 'j' cannot be declared in
this scope because it would give a different meaning to 'j', which is already used
in a 'parent or current' scope to denote something else.
其原因是:变量j是在for循环开始前定义的,在执行for循环时应处于其作用域内,在Main()方法结束执行后,变量j才超出作用域,第2个j(不合法)则在循环的作用域内,该作用域嵌套在Main()方法的作用域内。因为编译器无法区分这两个变量,所以不允许声明第2个变量。
2. 字段和局部变量的作用域冲突
某些情况下,可以区分名称相同(尽管其完全限定的名称不同)、作用域相同的两个标识符。此时编译器允许声明第2 个变量。原因是C#在变量之间有一个基本的区分,它把在类型级别声明的变
量看作字段,而把在方法中声明的变量看作局部变量。
考虑下面的代码:
using System;
namespace Wrox
{
class ScopeTest2
{
static int j = 20;
public static void Main()
{
int j = 30;
Console.WriteLine(j);
return;
}
}
}
虽然在Main()方法的作用域内声明了两个变量j,这段代码也会编译——在类级上定义的j,在该类删除前是不会超出作用域的(在本例中,当Main()方法终止,程序结束时,才会删除该类);以
及在Main()中定义的j。此时,在Main()方法中声明的新变量j 隐藏了同名的类级变量,所以在运行这段代码时,会显示数字30。但是,如果要引用类级变量,该怎么办?可以使用语法object.fieldname,在对象的外部引用类或结构的字段。在上面的例子中,我们访问静态方法中的一个静态字段(静态字段详见下一节),所以不能使用类的实例,只能使用类本身的名称:
..
public static void Main()
{
int j = 30;
Console.WriteLine(j);
Console.WriteLine(ScopeTest2.j);
}
..
如果要访问一个实例字段(该字段属于类的一个特定实例),就需要使用this 关键字。
……
展开