在第 1 章中,您看到了如何编写、编译和执行 C# 程序。示例程序在Main
方法中只有一条语句。在这一章中,您将学习如何编写更多的语句并向程序中添加逻辑。为了提高效率,本书其余部分中的许多示例都是片段,但是您仍然可以将这些语句添加到Main
方法中进行编译,从而更好地理解 C# 语法。也会有很多完整的程序。
通过结合语言运算符和语法,您可以用 C# 构建表达式和语句。这里有几个简单的 C# 语句的例子。
int count = 7;
char keyPressed = 'Q';
string title = "Weekly Report";
代码清单 4
前面代码清单中的每个示例都有共同的语法元素:类型、变量标识符、赋值运算符、值和语句完成。类型有int
、char
和string
,分别代表一个数字、一个字符和一个字符序列。这是 C# 提供的几个内置类型中的几个。变量是一个可以在以后的代码中使用的名称。=
运算符将表达式的右侧指定给左侧。每个语句都以分号结束。
前面的例子展示了如何声明一个变量并同时执行赋值,但这不是必需的。只要你在尝试使用一个变量之前声明它,你就没事了。这里有一个单独的声明。
string title;
代码清单 5
以及变量后来的赋值。
title = "Weekly Report";
代码清单 6
| | 注意:C# 区分大小写,所以“title”和“Title”是两个独立的变量名。 |
C# 是一种强类型语言,这意味着编译器不会在不兼容的类型之间隐式转换。例如,您不能将string
分配给int
,或者将int
分配给string
—至少不能隐式分配。下面的代码不会编译。
int total = "359";
string message = 7;
代码清单 7
带双引号的“359
”是字符串,不带引号的7
是int
。虽然您不能隐式执行转换,但是有一些方法可以显式执行转换。例如,您经常会收到来自用户的文本输入,应该是int
或其他类型。下面的代码清单显示了如何显式执行这些任务的几个示例。
int total = int.Parse("359");
string message = 7.ToString();
代码清单 8
在前面的列表中,如果字符串代表有效的int
,则Parse
会将字符串转换为int
。对任何值调用ToString
都会产生一个会编译的string
。
除了前面的转换示例之外,C# 还有一个强制转换运算符,允许您在允许显式转换的类型之间进行转换。假设你有一个double
,它是一个 64 位的浮点类型,你想把它分配给一个int
,它是一个 32 位的整数。你可以这样投:
double preciseLength = 5.61;
int roundedLength = (int)preciseLength;
代码清单 9
如果没有强制转换操作符,您将会收到一个编译器错误,因为double
不是int
。本质上,C# 编译器是在保护你不搬起石头砸自己的脚,因为给int
赋值double
意味着你失去了精确性。在前面的例子中,roundedLength
变成了5
。使用强制转换操作符可以让您告诉 C# 编译器,您知道这个操作在错误的情况下可能是危险的,但是对于您的特定情况是有意义的。
下表列出了内置类型,以便您可以查看可用的类型:
表 1:内置类型
类型(文字后缀) | 描述 | 值/范围 |
---|---|---|
字节 | 8 位无符号整数 | 0 到 255 |
sbyte(字节) | 8 位有符号整数 | -128 到 127 |
短的 | 16 位有符号整数 | -32,768 至 32,767 |
乌肖特 | 16 位无符号整数 | 0 至 65,535 |
(同 Internationalorganizations)国际组织 | 32 位有符号整数 | -2,147,483,648 至 2,147,483,647 |
uint | 32 位无符号整数 | 0 至 4,294,967,295 |
长(l) | 64 位有符号整数 | –9,223,372,036,854,775,808 至 9,223,372,036,854,775,807 |
乌龙(乌卢) | 64 位无符号整数 | 0 至 18,446,744,073,709,551,615 |
浮动(f) | 32 位浮点 | -3.4 × 10 38 至+3.4 × 10 38 |
双(d) | 64 位浮点 | 5.0×10324至 1.7 × 10 308 |
十进制(m) | 128 位、28 位或 29 位精度(非常适合金融应用) | (-7.9 × 10 28 至 7.9×1028)/(100 至 28 |
弯曲件 | 布尔代数学体系的 | 对还是错 |
茶 | 16 位 Unicode 字符(使用单引号) | U+0000 至 U+FFFF |
线 | Unicode 字符序列(使用双引号) | 例如“abc” |
当意思不明确时,你应该给数字加上后缀。在以下示例中,m
后缀确保了9.95
文字被视为一个decimal
数字:
decimal price = 9.95m;
代码清单 10
您可以将 Unicode 值直接分配给字符。下面的示例显示了如何分配回车。
char cr = '\u0013';
代码清单 11
您也可以使用强制转换运算符获取字符的 Unicode 值,如下所示。
int crUnicode = (int)cr;
代码清单 12
到目前为止,您只看到了带有赋值操作符的语句,但是 C# 有许多其他操作符,允许您执行任何通用编程语言所期望的所有逻辑操作。下表列出了一些可用的运算符。
表 2: C# 运算符
种类 | 描述 |
---|---|
主要的 | x.y x?.y f(x) a[x] x++ x-- new typeof default checked unchecked nameof |
一元的 | + - ! ~ ++ x --x (T)x await x |
增加的 | * / % |
添加剂 | + - |
变化 | << >> |
关系测试和类型测试 | < > <= >= is as |
平等 | == != |
逻辑“与” | & |
逻辑异或 | ^ |
逻辑或 | | |
条件“与” | && |
条件或 | || |
零合并 | ?? |
有条件的 | ?: |
分配 | = *= /= %= += -= <<= >>= &= ^= |= => |
前缀运算符在赋值前更改变量的值,后缀运算符在赋值后更改变量,如下例所示。
int val1 = 5;
int val2 = ++ val1;
int val3 = 2;
int val4 = val3--;
代码清单 13
在前面的代码清单中,val1
和val2
都是6
。val3
变量是1
,但是val4
是2
,因为后缀运算符在赋值后求值。
三元运算符为 if-then-else 逻辑提供了简单的语法。这里有一个例子:
decimal priceGain = 2.5m;
string action = priceGain > 2m ? "Buy" : "Sell";
代码清单 14
?
左侧是布尔表达式,priceGain > 2m
。如果这是真的,在本例中,三元运算符返回第一个介于?
和:
之间的值,即"Buy"
。否则,三元运算符将在:
之后返回值,即"Sell"
。该语句将三元运算符"Buy"
的结果赋给字符串变量action
。
除了内置类型之外,FCL 还有许多类型可供您日常使用。其中一个是DateTime
,代表日期和时间。这里有一个快速演示,展示了一些你可以用DateTime
做的事情。
DateTime currentTime = DateTime.Now;
string shortDateString = currentTime.ToShortDateString();
string longDateString = currentTime.ToLongDateString();
string defaultDateString = currentTime.ToString();
DateTime tomorrow = currentTime.AddDays(1);
代码清单 15
前面的代码展示了如何获取当前的DateTime
,日期的短表示(例如 12/8/2014),日期和时间的长表示(所有内容都拼写出来),默认的数字表示,以及如何使用DateTime
方法进行计算。
| | 提示:在创建自己的类型库之前,搜索 FCL。您每天使用的许多常见类型,如日期时间,将已经存在。 |
在表 2 中列出的 C# 运算符按照它们的一般优先顺序列出了运算符。优先级定义了首先计算哪些运算符。优先级较高的运算符在优先级较低的运算符之前求值。
赋值和条件运算符是右关联的,所有其他运算符都是左关联的。您可以通过使用括号来更改正常的操作顺序,如下面的代码清单所示。
int result1 = 2 + 3 * 5;
int result2 = (2 + 3) * 5;
代码清单 16
在之前的代码中,result1
是17
,但是result2
是25
。
在 C# 中有不同的方法来构建和格式化字符串:串联、数字格式字符串或字符串插值。下面的代码清单演示了字符串连接。
string name = "Joe";
string helloViaConcatenation = "Hello, " + name + "!";
Console.WriteLine(helloViaConcatenation);
代码清单 17
这上面印着**“你好,乔!”**到控制台。下面的例子做了同样的事情,但是使用了string.Format
。
string helloViaStringFormat = string.Format("Hello, {0}!", name);
Console.WriteLine(helloViaStringFormat);
代码清单 18
string.Format
取一个格式字符串,在花括号中有数字占位符。它是从 0 开始的,所以第一个占位符是{0}
。字符串后面的参数按照出现的顺序放入格式字符串中。由于name
是第一个(也是唯一的)参数,string.Format
将{0}
替换为Joe
以创建一个字符串"Hello, Joe!"
。为了方便控制台应用程序,WriteLine
使用相同的格式。下面的代码完成了与前面代码清单中两行代码相同的任务。
Console.WriteLine("Hello, {0}!", name);
代码清单 19
更进一步,字符串格式更强大,允许您指定列长度、对齐方式和值格式,如下面的代码所示。
string item = "bread";
decimal amount = 2.25m;
Console.WriteLine("{0,-10}{1:C}", item, amount);
代码清单 20
在本例中,第一个占位符的长度为 10 个字符。默认对齐方式是右对齐,但减号会将它更改为左对齐。在第二个占位符上,C
是货币格式字符串。
| | 注意:有许多字符串格式选项。可以访问https://msdn . Microsoft . com/en-us/library/dwhawy9k(v = vs . 110)。aspx 对于标准格式,https://msdn . Microsoft . com/en-us/library/0c 899 ak8(v = vs . 110)。自定义格式的 aspx 和https://msdn . Microsoft . com/en-us/library/az4se 3 k1(v = vs . 110)。日期时间格式的 aspx 。 |
C# 6 引入了一种格式化字符串的新方法,称为字符串插值。这是一种速记语法,允许您用表达式替换数字占位符,如下所示:
Console.WriteLine($"{item} {amount}");
代码清单 21
需要$
前缀。这里,item
变量的值代替{item}
,而amount
变量的值代替{amount}
。类似于数字占位符,您可以包含其他格式。
Console.WriteLine($"{nameof(item)}: {item,-10} {nameof(amount)}: {amount:C}");
代码清单 22
nameof
运算符打印出名称 【项目】 ,演示如何在占位符中使用表达式。您还可以在item
和amount
上看到空间和货币格式。
您可以在代码中使用if—else
或switch
语句进行分支逻辑。当您只需要为真实条件执行代码时,使用if
语句,如下例所示。
string action2 = "Sell";
if (priceGain > 2m)
{
action2 = "Buy";
}
代码清单 23
在这个例子中花括号是可选的,因为如果priceGain > 2m
只有一条语句要执行。然而,它们对于多个语句是必需的。所有分支和逻辑语句都是如此。您也可以有一个else
案例,如下列表所示。
string action3 = "Do Nothing";
if (priceGain <= 2m)
{
action3 = "Sell";
}
else
{
action3 = "Buy";
}
代码清单 24
每当if
语句的布尔条件为假时,就像在前面的代码示例中priceGain <= 2m
一样,else
子句执行。在这种情况下,action3
就变成了"Buy"
。当然,你可以通过增加更多的else if
子句来拥有多个条件。
string action4 = null;
if (priceGain <= 2m)
{
action4 = "Sell";
}
else if (priceGain > 2m && priceGain <= 3m)
{
action4 = "Do Nothing";
}
else
{
action4 = "Sell";
}
代码清单 25
在前面的例子中,您可以在else if
子句中看到一个更复杂的布尔表达式。当priceGain
为2.5
时,action4
的值变为"Do Nothing"
。&&
是一个逻辑运算符,如果左边和右边的表达式都为真,则成功。如果左侧或右侧的表达式为真,则逻辑||
运算符成功。这些运算符还执行短路操作,如果左边的表达式导致整个表达式不为真,右边的表达式就不执行。在代码清单 25 中的else if
的情况下,如果priceGain
等于或小于2m
,则&&
运算符不会计算priceGain <= 3
表达式,因为整个操作已经是假的。一旦执行了if
语句的一个分支,就不会评估或执行其他分支。
请注意,我将action4
设置为null
。null
关键词意味着没有价值。下一章我会讲到null
并说明你可以在哪里使用。
一个if
语句适用于简单的分支或者复杂的条件,比如前面的else if
子句。但是,当您有多个案例并且所有表达式都是常量值时,例如int
或string
,您可能更喜欢switch
语句。以下示例使用switch
语句根据天气预报选择合适的设备。
string currentWeather = "rain";
string equipment = null;
switch (currentWeather)
{
case "sunny":
equipment = "sunglasses";
break;
case "rain":
equipment = "umbrella";
break;
case "cold":
default:
equipment = "jacket";
break;
}
代码清单 26
switch
语句试图用它的一个case
语句来匹配一个值,在这个例子中是currentWeather
。它使用default
不匹配的情况。所有case
语句必须以break
语句结束。唯一允许的时间是当case
没有身体时,如"cold"
case
和default
所示,它们都将equipment
设置为"jacket"
。由于currentWeather
为"rain"
,equipment
变为"umbrella"
,其他情况不执行。
除了分支语句之外,您还需要多次执行一组操作的能力,这就是 C# 循环的由来。在讨论循环之前,让我们看看数组和集合,它们保存着循环可以使用的数据。
有时,您需要将集合中的许多项组合在一起,以便在内存中管理它们。为此,可以使用数组或。NET 框架。下面的示例演示如何创建数组。
int[] oddNumbers = { 1, 3, 5 };
int firstOdd = oddNumbers[0];
int lastOdd = oddNumbers[2];
代码清单 27
在这里,我已经用三个值声明并初始化了数组。数组和集合是从 0 开始的,所以firstOdd
是1
,lastOdd
是5
。[x]
语法,其中x
是一个数字,被称为索引器,因为它允许您在索引指定的位置访问数组。这里还有一个用string
代替int
的例子。
string[] names = new string[3];
names[1] = "Joe";
代码清单 28
在这个例子中,我实例化了一个数组来保存三个字符串。默认情况下,所有字符串都等于null
。该代码将第二个字符串设置为"Joe"
。
除了数组之外,您还可以使用所有类型的数据结构,例如List
、Stack
、Queue
等等,它们都是 FCL 的一部分。下面的例子展示了如何使用List
。记得为System.Collections.Generic
添加一个using
子句来使用List<T>
类型。
List<decimal> stockPrices = new List<decimal>();
stockPrices.Add(56.23m);
stockPrices.Add(72.80m);
decimal secondStockPrice = stockPrices[1];
代码清单 29
在这个示例中,我实例化了一个新的List
集合。<decimal>
是一个泛型类型,表示这是一个强类型列表,只能保存类型为decimal
的值;这是decimal
的List
。那份清单有两项。请注意我是如何使用类似数组的索引器语法来读取(从 0 开始)stockPrices
列表中的第二项的。
C# 支持多个循环,包括for
、foreach
、while
和do
。下面的代码清单执行类似的逻辑。
double[] temperatures = { 72.3, 73.8, 75.1, 74.9 };
for (int i = 0; i < temperatures.Length; i++)
{
Console.WriteLine(i);
}
代码清单 30
for
循环将i
初始化为0
,确定i
小于temperature
数组中的项目数,执行Console.WriteLine
,然后递增i
。它继续执行,直到条件(i < temperatures.Length
)为假,然后继续执行程序中的下一条语句。
foreach (int temperature in temperatures)
{
Console.WriteLine(temperature);
}
代码清单 31
代码清单 31 中使用的foreach
循环更简单,将对temperatures
数组中的每个值执行。
接下来是一个while
循环的例子。
int tempCount = 0;
while (tempCount < temperatures.Length)
{
Console.WriteLine(tempCount);
tempCount++ ;
}
代码清单 32
while
循环评估条件,如果条件为真,则执行。请注意,我在每次迭代中将tempCount
初始化为0
,并在循环内部增加tempCount
。
最后,下面的例子展示了如何编写一个do
- while
循环。
int tempCount2 = 0;
do
{
Console.WriteLine(tempCount2++);
}
while (tempCount2 <= temperatures.Length);
代码清单 33
一个do
- while
循环适合你至少要执行一次逻辑的时候。本示例将tempCount2
作为参数增加到Console.WriteLine
。请记住,后缀运算符会在求值后更改变量。
这是一个计算器程序,汇集了本章的一些概念,加上一些额外的功能。你可以把它输入到你的编辑器中,然后执行它来练习。
using System;
using System.Text;
/*
Title: Calculator
By: Joe Mayo
*/
class Calculator
{
/// <summary>
/// This is the entry point.
/// </summary>
static void Main()
{
char firstChar = 'Q';
bool keepRunning = true;
do
{
Console.WriteLine();
Console.Write("What do you want to do? (Add, Subtract, Multiply, Divide, Quit): ");
string input = Console.ReadLine();
firstChar = input[0];
// This is used in both the if statement and the do-while loop.
keepRunning = !(firstChar == 'q' || firstChar == 'Q');
double firstNumber = 0;
double secondNumber = 0;
if (keepRunning)
{
Console.Write("First Number: ");
string firstNumberInput = Console.ReadLine();
firstNumber = double.Parse(firstNumberInput);
Console.Write("Second Number: ");
string secondNumberInput = Console.ReadLine();
secondNumber = double.Parse(secondNumberInput);
}
double result = 0;
switch (firstChar)
{
case 'a':
case 'A':
result = firstNumber + secondNumber;
break;
case 's':
case 'S':
result = firstNumber - +secondNumber;
break;
case 'm':
case 'M':
result = firstNumber * secondNumber;
break;
case 'd':
case 'D':
result = firstNumber / secondNumber;
break;
default:
result = -1;
break;
}
Console.WriteLine();
Console.WriteLine("Your result is " + result);
} while (keepRunning);
}
}
代码清单 34
上一个程序演示了一个do
- while
循环、if
语句、switch
语句以及与用户的基本控制台通信。
这里有几个你还没见过的字符串特性。第一种是程序使用Console.ReadLine
从用户那里读取输入字符串的输入文本。请注意从字符串中读取第一个字符的索引器语法。您可以通过这种方式读取字符串的任何字符。另外,看看程序底部打印"Your result is " + result
的地方,它将一个字符串和数字连接起来。使用+
运算符进行连接是构建字符串的简单方法。另一种构建字符串的方法是使用名为StringBuilder
的类型,您可以这样使用:
StringBuilder sb = new StringBuilder();
sb.Append("Your result is ");
sb.Append(result.ToString());
Console.WriteLine(sb.ToString());
代码清单 35
您还需要在文件顶部添加一个using System.Text;
子句。在同一个字符串上使用了大约四次 concatenate 运算符+
之后,您可以考虑改为使用StringBuilder
重写。string
类型是不可变的,意味着不能修改。这也意味着每个串联操作都会导致 CLR 在内存中创建一个新的字符串。
计算器程序也有不编译的多行和单行注释,但是可以帮助您根据需要记录代码。以下是多行注释:
/*
Title: Calculator
By: Joe Mayo
*/
代码清单 36
以下是单行注释:
// This is used in both the if statement and the do-while loop.
代码清单 37
单行注释的扩展是使用三个斜线和一组 XML 标记的约定,称为文档注释。
/// <summary>
/// This is the entry point.
/// </summary>
代码清单 38
C# 有一整套运算符和类型,允许您编写各种表达式和语句。使用分支语句和循环,您可以编写自己选择的逻辑。本章中的所有代码都在Main
方法中,但显然这是不够的,你会很快从中受益。下一章将探索一些新的 C# 特性,以帮助用方法和属性组织代码。