- A+
一、一个简单的 C# 程序
本文将为学习 C# 打基础,所以我们先来看 C# 程序的样子,还有它的不同部分分别代表什么意思。
我们先从一个简单程序开始,逐个解释它的各组成部分,从 C# 程序的结构到产生屏幕输出程序的方法。
让我们先观察一个简单的 C# 程序。代码包含在一个名称为 SimpleProgram.cs 的文本文件里。
using System; namespace Simple { class Program // 声明一个类 { static void Main() { Console.WriteLine("Hi there!"); } } }
输出
Hi there!
当你阅读它时,不用太担心是否能够理解所有的细节。
下表对代码进行了逐行描述。
行号 | 描述 |
---|---|
行 1 | 告诉编译器这个程序使用 System 命名空间中的类型 |
行 3 | 声明一个新的命名空间,名称为 Simple 新的命名空间从第 4 行的左大括号开始,一直延伸到第 12 行与之对应的右大括号 在这部分里声明的任何类型都是该命名空间的成员 |
行 5 | 声明一个新的类类型,名称为 Program 任何在第 6 行和第 11 行的两个大括号中间声明的成员都是组成这个类的成员 |
行 5 | 第 5 行包含两个连续的斜杠,这两个字符以及这一行中它们之后的所有内容都会被编译器忽略。这叫做单行注释 |
行 7 | 声明一个名称为 Main 的方法作为类 Program 的成员在这个程序中, Main 是 Program 类的唯一成员Main 是一个特殊的函数,编译器用它作为程序的起始点 |
行 9 | 只包含一条单独的、简单的语句,这一行组成了 Main 的方法体简单语句以一个分号结束 这条语句使用命名空间 System 中的一个名称为 Console 的类,将消息输出到屏幕窗口如果没有第 1 行的 using 语句,编译器就不知道在哪里寻找类 Console |
补充说明
C# 程序由一个或多个类型声明组成。程序中的类型可以以任何顺序声明。在 SimpleProgram
中,只声明了 class
类型。
命名空间是与某个名称相关联的组类型声明。 SimpleProgram
使用了两个命名空间。它创建了一个名称为 Simple
的新命名空间,并在其中声明了其类型(类 program
),还使用了 System
命名空间中定义的 Console
类。
要编译这个程序,可以使用 Visual Studio 或命令行编译器。如果使用命令行编译器,最简单的形式是在命名窗口使用下面的命令:
csc SimpleProgram.cs
在这条命念中,csc
是命令行编泽器的名称,SimpleProgram.cs 是源文件的名称。csc 是指“C-sharp 编译器”。
二、标识符
标识符是一种字符串,用来命名变量、方法、参数和许多后面将要阐述的其他程序结构。
可以通过把有意义的词连接成一个单独的描述性名称来创建自文档化(self-documenting)的标识符,可以使用大写和小写字母(如 CardDeck、PlayersHand、FirstName 和 SocialSecurityNum)。某些字符能否在标识符中特定的位置出现是有规定的。
- 字母和下划线(a-z、A-Z 和 _)可以用在任何位置。
- 数字不能放在首位,但可以放在其他的任何地方。
@
字符只能放在标识符的首位。虽然允许使用,但不推荐将@
作为常用字符。
标识符区分大小写。例如,变量名 myVar
和 MyVar
是不同的标识符。
举个例子,在下面的代码片段中,变量的声明都是有效的,并声明了不同的整型变量。但使用如此相似的名称会使代码更易出错并更难调试,后续调试代码的人会很不爽。
// 语法上有效,但非常混乱 int totalCycleCount; int TotalCycleCount; int TotalcycleCount;
三、关键字
关键字是预定义的保留标识符,对编译器有特殊意义。下表列出了完整的 C# 关键字表。
关于关键字,一些应该知道的重要内容如下。
- 关键字不能被用作变量名或任何其他形式的标识符,除非以
@
字符开始。例如,@if
是有效标识符,而if
则不是,因为if
是关键字。 - 所有 C# 关键字全部都由小写字母组成(但是 .NET 类型名使用 Pascal 大小写约定)。
abstract | as |
break | byte |
char | checked |
continue | decimal |
do | double |
event | explicit |
finally | fixed |
foreach | goto |
in | int |
is | lock |
new | null |
out | override |
protected | public |
return | sbyte |
sizeof | stackalloc |
struct | switch |
true | try |
ulong | unchecked |
using | virtual |
while |
上下文关键字是仅在特定的语言结构中充当关键字的标识符。在那些位置,它们有特别的含义。两者的区别是,关键字不能被用作标识符,而上下文关键字可以在代码的其他部分被用作标识符。上下文关键字如下表所示。
add |
async |
descending |
from |
group |
let |
orderby |
remove |
unmanaged(泛型类型约束) |
when(筛选条件) |
yield |
四、Main:程序的起始点
每个 C# 程序必须有一个类带有 Main
方法(函数)。在先前所示的 SimpleProgram 程序中,它被声明在 Program
类中。
- 每个 C# 程序的可执行起始点在 Main 中的第一条指令。
Main
必须首字母大写。
Main
的最简单形式如下:
static void Main() { // 更多语句 }
五、空白
程序中的空白指的是没有可视化输出的字符。程序员在源代码中使用的空白将被编译器忽略,但它会使代码更清晰易读。空白字符包括:
- 空格(Space)
- 制表符(Tab)
- 换行符
- 回车符
例如,下面的代码会被编译器相同对待,尽管它们看起来有所不同。
// 很好的格式 static void Main() { Console.WriteLine("Hi there!"); } // 连在一起 static void Main(){Console.WriteLine("Hi there!");}
六、语句
C# 的语句和 C、C++ 的语句非常相似。本节将介绍语句的常用形式,详细的语句形式将在后面的文章中介绍。
语句是描述个类型或告诉程序去执行某个动作的一条源代码指令。
简单语句以一个分号结束。例如,下面的代码是一个由两条简单语句组成的序列。第一条语句定义了一个名为 var1
的整型变量,并将它的值初始化为 5
。第二条语句将变量 var1
的值打印到屏幕窗口。
int var1 = 5; System.Console.Writeline("The value of var1 is {0}", var1);
块
块是一个由成对大括号包围的 0 条或多条语可序列,它在语法上相当于一条语句。
可以使用之前示例中的两条语向创建一个块。用大括号把语向包围起来,如下面的代码所示。
{ int var1 = 5; System.Console.Writeline("The value of var1 is {0}", var1); }
关于块,一些应该知道的重要内容如下。
- 语法上只需要一条语句,而你需要执行的动作无法用条简单的语句表达的情况下,考虑使用块。
- 有些特定的程序结构只能使用块。在这些结构中,不能用简单语句替代块。
- 虽然简单语句以分号结束,但块后面不跟分号。(实际上,由于被解析为一条空语句,所以编译器允许这样,但这不是好的风格。)
{ int var1 = 5; // 以分号结束 System.Console.Writeline("The value of var1 is {0}", var1); // 以分号结束 } // 没有分号
七、从程序中输出文本
控制台窗口是一种简单的命令提示窗口,允许程序显示文本并从键盘接受输人。BCL 提供一个名为 Console
的类(在 System
命名空间中),该类包含了将数据输入和输出到控制台窗口的方法。
7.1 Write
Write
是 Console
类的成员,它把一个文本字符串发送到程序的控制台窗口。最简单的情况下,Write
将文本的字符串字面量发送到窗口,字符串必须使用双引号括起来。
下面这行代码展示了一个使用 Write
成员的示例:
Console.Write("This is trivial text.");
这段代码在控制台窗口产生如下输出:
This is trivial text.
另外一个示例是下面的代码,它发送了 3 个文本字符串到程序的控制台窗口:
Console.Write("This is text1. "); Console.Write("This is text2. "); Console.Write("This is text3. ");
这段代码产生的输出如下,注意,Write
没有在字符串后面添加换行符,所以三条语句都是输出到同一行。
This is text1. This is text2. This is text3.
7.2 WriteLine
WriteLine
是 Console
的另外一个成员,它和 Write
实现相同的功能,但是会在每个输出的字符串的结尾添加一个换行符。
例如,如果使用先前的代码,用 WriteLine
替换掉 Write
,输出就会分为多行:
Console.WriteLine("This is text1. "); Console.WriteLine("This is text2. "); Console.WriteLine("This is text3. ");
这个简单的例子演示了 BCL 的价值,其中包含了你可以在程序中使用的各种功能。
这段代码在控制台窗口产生如下输出:
This is text1. This is text2. This is text3.
7.3 格式字符串
Write
语句和 WriteLine
语句的常规形式中可以有一个以上的参数。
- 如果不止一个参数,参数间用逗号分隔。
- 第一个参数必须总是字符串,称为格式字符串。格式字符串可以包含替代标记。
- 替代标记在格式字符串中标出位置,在输出串中该位置将用一个值来替代。
- 替代标记由一个整数及括住它的一对大括号组成,其中整数就是替换值的数字位置。跟着格式字符串的参数称为替换值,这些替换值从 0 开始编号。
语法如下:
Console.WriteLine(格式字符串(含替代标记),替换值 0,替换值 1,替换值 2,……);
例如,下面的语句有两个替代标记,编号 0 和 1;以及两个替换值,分别是 3 和 6。
Console.WriteLine("Two sample integers are {0} and {1}.",3,6);
这段代码在屏幕上产生如下输出:
Two sample integers are 3 and 6.
C# 6.0 引入了一种允许你以更简单易懂的方式表述参数化字符串的语法,称为字符串插值,它是通过直接在替代标记内插人变量名实现的。实际上,替代标记告诉编译器这个变量名将被视为一个变量,而不是字符串字面量——前提是在字符串前面加上了 $
符号。
int var1 = 3; int var2 = 6; Console.WriteLine($"Two sample integers are {var1} and {var2}.")
上面的代码产生如下输出:
Two sample integers are 3 and 6.
在更复杂的例子中,字符串插值的价值会更明显:
int latitude = 43; int longitude = 11; string north = "N"; string south = "S"; string east = "E"; Console.WriteLine($"The city of Florence, Italy is located at latitude {latitude}{north} and longitude {longitude}{east}. By comparison, the city of Djibouti (in the country of Djibouti) is located at latitude {longitude}{north} and longitude {latitude}{east}. The city of Moroni in the Comoros Islands is located at latitude {longitude}{south} and longitude {latitude}{east}.");
输出
The city of Florence, Italy is located at latitude 43N and longitude 11E. By comparison, the city of Djibouti (in the country of Djibouti) is located at latitude 11N and longitude 43E. The city of Moroni in the Comoros Islands is located at latitude 11S and longitude 43E.
可以看到,在复杂的情况下,使用变量的方式会更加自然。
7.4 多重标记和值
在 C# 中,可以使用任意数量的替代标记和任意数量的值。
- 值可以以任何顺序使用。
- 值可以在格式字符串中替换任意次。
例如,下面的语句使用了 3 个标记但只有两个值。请注意,值 1 被用在了值 0 之前,而且被用了两次。
Console.WriteLine("Three integers are {1}, {0} and {1}.",3,6);
输出
Three integers are 6, 3 and 6.
标记不能试图引用超出替换值列表长度以外位置的值。如果引用了,不会产生编译错误,但会产生运行时错误(称为异常)。
例如,在下面的语句中有两个替换值,分别在位置 0 和位置 1。然而,第二个标记引用了位置 2,但位置 2 并不存在。这将会产生一个运行时错误。
Console.WriteLine("Two integers are {0} and {2}.",3,6); // 错误
7.5 格式化数字字符串
贯穿本系列博文的示例代码将会使用 WriteLine
方法来显示值。每次,我们都使用由大括号包围整数组成的简单替代标记形式。
然而在很多时候,我们更希望以更合适的格式而不是一个简单的数字来呈现文本字符串的输出。例如,把值作为货币或者某个小数位数的定点值来显示。这些都可以通过格式化字符串来实现。
例如,下面的代码由两条打印值 500 的语句组成。第一行没有使用任何其他格式化来打印数字,而第二行的格式化字符串指定了数字应该被格式化成货币。
Console.WriteLine("The value:{0}.",500); // 输出数字 Console.WriteLine("The value:{0:C}.",500); // 格式化为货币格式
输出
The value:500. The value:$500.00.
使用字符串插值,下面的代码产生的结果跟之前的示例相同。
int myInt = 500; Console.WriteLine($"The value:{myInt}."); Console.WriteLine($"The value:{myInt:C}.");
两条语句的不同之处在于,格式项以格式说明符形式包括了额外的信息。大括号内的格式说明符的语法由 3 个字段组成:索引号、对齐说明符和格式字段(format field)。语法如下所示:
{index(索引号,必须,指定列表中的某一项),alignment(对齐说明符,可选,指定字段宽度,以及是否是右对齐或左对齐):format(格式字段,可选,指定项的格式)}
注意标点符号的区别:
- 对齐使用逗号
- 格式使用冒号
格式说明符的第一项是索引号或字符串插值变量。如你所知,索引指定了之后的格式化字符串应该格式化列表中的哪一项。除非指定了字符串插值变量,否则索引号是必需的,并且列表
项的数字必须从 0 开始。
-
对齐说明符
对齐说明符表示字段中字符的最小宽度。对齐说明符有如下特性:
- 对齐说明符是可选的,并且使用逗号来和索引号分离。
- 它由一个正整数或负整数组成:
- 整数表示字段使用字符的最少数量。
- 符号表示右对齐或左对齐。正数表示右对齐,负数表示左对齐。
Console.WriteLine("{0,10}",500);
例如,如下格式化
int
型变量myInt
的值的代码显示了两个格式项。在第一个示例中,myInt
的值以在 10 个字符宽度的字符串中右对齐的形式进行显示;第二个示例中则是左对齐。格式项放在两个竖杠中间,这样在输出中就能看到它们的左右边界。int myInt = 500; Console.WriteLine("|{0,10}|",myInt); Console.WriteLine("|{0,-10}|",myInt);
这段代码产生了如下的输出,在两个竖杠的中间有 10 个字符宽度:
| 500| |500 |
使用字符串插值,下面的代码跟之前的示例产生相同的结果:
int myInt = 500; Console.WriteLine($"|{myInt,10}|"); Console.WriteLine($"|{myInt,-10}|");
值的实际表示可能会比对齐说明符指定的字符数多一些或少一些:
- 如果要表示的字符数比对齐说明符中指定的字符数少,那么其余的字符会使用空格填充;
- 如果要表示的字符数多于指定的字符数,对齐说明符会被忽略,并且使用所需的字符进行表示。
Console.WriteLine("|{0,-20}|","其余字符会使用空格填充"); Console.WriteLine("|{0,-2}|","对齐说明符会被忽略,并且用实际的字符进行表示"); Console.WriteLine("|{0,-10}|","使用空格填充"); Console.WriteLine("|{0,18}|","使用空格填充");
输出
|其余字符会使用空格填充 | |对齐说明符会被忽略,并且用实际的字符进行表示| |使用空格填充 | | 使用空格填充|
-
格式字段
格式字段指定了数字应该以哪种形式表示。例如,应该表示为货币、十进制数字、十六进制数字还是定点符号?
格式字段有三部分,如下图所示。
- 冒号后必须紧跟着格式说明符,中间不能有空格。
- 格式说明符是一个字母字符,是 9 个内置格式之一。字符可以是大写或小写形式。
- 精度说明符是可选的,由 1~2 位数字组成。它的实际意义取决于格式说明符。
如下代码是格式字符串组件语法的一个示例:
Console.WriteLine("{0:F4}",12.345678);
如下代码给出了不同格式字符串的一些示例:
double myDouble = 12.345678; Console.WriteLine("{0, -10:G} -- General",myDouble); Console.WriteLine("{0,-10} -- Default, same as General",myDouble); Console.WriteLine("{0,-10:F4} -- Fixed Point, 4 dec places" ,myDouble); Console.WriteLine("{0, -10:C} -- Currency",myDouble); Console.WriteLine("{0,-10:E3} -- Sci. Notation, 3 dec places", myDouble); Console.WriteLine("{0,-10:x} -- Hexadec imal integer",1194719 ) ;
这段代码产生了如下的输出:
12.345678 -- General 12.345678 -- Default, same as General 12.3457 -- Fixed Point, 4 dec places ¤12.35 -- Currency 1.235E+001 -- Sci. Notation, 3 dec places 123adf -- Hexadec imal integer
如以下代码所示,使用字符串插值会产生跟之前的示例相同的结果:
double myDouble = 12.345678; Console.WriteLine($"{myDouble, 10:G} -- General"); Console.WriteLine($"{myDouble,-10} -- Default,same as General"); Console.WriteLine($"{myDouble, -10:F4} -- Fixed Point, 4 dec places"); Console.WriteLine($"{myDouble,-10:C} -- Currency"); Console.WriteLine($"{myDouble,-10:E3} -- Sci. Notation, 3 dec places"); Console.WriteLine($"{1194719,- 10:x} -- Hexadec imal integer");
-
标准数字格式说明符
下表总结了 9 总标准数字格式说明符。第一列在说明符名后列出了说明符字符。如果说明符字符根据它们的大小写会有不同的输出,就会标注为区分大小写。
名字和字符 意义 货币
C、c使用货币符号把值格式化为货币,货币符号取决于程序所在的 PC 的区域设置
精度说明符:小数位数
示例:Console.WriteLine("{0:C}",12.5);
输出:$12.50十进制数
D、d十进制数字字符串,需要的情况下有负数符号。只能和整数类型配合使用
精度说明符:输出字符串中的最少位数。如果实际数字的位数更少,则在左边以 0 填充
示例:Console.WriteLine("{0:D4}",12);
输出:0012定点
F、f带有小数点的十进制数字字符串。如果需要也可以有负数符号
精度说明符:小数的位数
示例:Console.WriteLine("{0:F4}",12.3456789);
输出:12.3457常规
G、g在没有指定说明符的情况下,会根据值转换为定点或科学计数法表示的紧凑形式
精度说明符:根据值
示例:Console.WriteLine("{0,G4}",12.345678);
输出:12.35十六进制数
X、x
区分大小写十六进制数字的字符串。十六进制数字 A~F 会匹配说明符的大小写形式
精度说明符:输出字符串中的最少位数。如果实际数的位数更少,则左边以 0 填充
示例:Console.WriteLine("{0:x}",180026);
输出:2bf3a数字
N、n和定点表示法相似,但是在每三个数字的一组中间有逗号或空格分隔符。从小数点开始往左数。使用逗号还是空格分隔符取决于程序所在 PC 的区域设置
精度说明符:小数的位数
示例:Console.WriteLine("{0:N2}",12345678.54321);
输出:12,345,678.54百分比
P、p表示百分比的字符串。数字会乘以 100
精度说明符:小数的位数
示例:Console.WriteLine("{0:P2}",0.1221897);
输出:12.22 %往返过程
R、r保证输出字符串后如果使用 Parse
方法将字符串转化为数字,那么该值和原始值一样。
精度说明符:忽略
示例:Console.WriteLine("{0:R}",1234.21897);
输出:1234.21897科学计数法
E、e
区分大小写具有和位数和指数的科学计数法。指数前面加字母 E。E 的大小写和说明符一致
精度说明符:小数的位数
示例:Console.WriteLine("{0:e4}",12.3456789);
输出:1.2346e+001对齐说明符和格式说明符在字符串插值中仍然可用。
double var1 = 3.14159; System.Console.WriteLine($"The value of var1 is {var1,10:f5}");
八、注释
你已经见过单行注释了,所以这里将讨论第二种行内注释——带分隔符的注释,并提及第三种类型,称为文档注释。
- 带分隔符的注释有两个字符的开始标记(/*)和两个字符的结束标记(*/)。
- 标记对之间的文本会被编译器忽略。
- 带分隔符的注释可以跨任意多行。
例如,下面的代码展示了一个跨多行的带分隔符的注释。
/* 这段文本将被编译器忽略 带分隔符的注释与单行注释不同 带分隔符的注释可以跨越多行 */
带分隔符的注释还可以只包括行的一部分。例如,下面的语句展示了行中间注释出的文本。该结果就是只声明了一个变量 var2
。
int /*var 1,*/ var2;
8.1 关于注释的更多内容
关于注释,有其他几点重要内容需要知道。
- 不能嵌套带分隔符的注释,一次只能有一个注释起作用。如果你打算嵌套注释,首先开始的注释直到它的范围结束都有效。
- 注释类型的范围如下。
- 对于单行注释,一直到行结束都有效。
- 对于带分隔符的注释,直至遇到第一个结束分隔符都有效。
下面的注释方式是不正确的:
↓ 创建注释 /* 尝试嵌套注释 /* ← 它将被忽略,因为他在一个注释的内部 内部注释 */ ← 注释结束,因为他是遇到的第一个借宿分隔符 */ ← 产生语法错误,因为没有开始分隔符 ↓ 创建注释 ↓ 它将被忽略,因为他在一个注释的内部 // 单行注释 /* 嵌套注释? */ ← 产生语法错误,因为没有开始分隔符
8.2 文档注释
C# 还提供了第三种注释类型:文档注释。文档注释包含 XML 文本,可以用于产生程序文档。这种类型的注释看起来像单行注释,但它们有三个斜杠而不是两个。
下面的代码展示了文档注释的形式:
/// <summary> /// This class does... /// <summary> class Program { ... }
8.3 注释类型总结
行内注释是被编译器忽略但被包含在代码中以说明代码的文本片段。程序员在他们的代码中插人注释以解释和文档化代码。下表总结了注释的类型。
类型 | 开始、结束 | 描述 |
---|---|---|
单行注释 | // | 从开始标记到该行行尾的文本被编译器忽略 |
带分隔符的注释 | /* */ | 从开始标记到结束标记之间的文本被编译器忽略 |
文档注释 | /// | 这种类型的注释包含 XML 文本,可以使用工具生成程序文档 |
虽然用来重复变量名或者方法名的注解基本没什么价值,但是用来解释一段代码的目的的注释对后续的维护还是很有用的。因为有价值的代码不可避免地都需要维护,所以通过阅读开发者写这段代码时的想法可以节省很多精力。
---(完)