# 变量和表达式

# 基本语法

C#由一系列语句组成,每条语句都有一个分号结束。C#是一种块结构的语言,块使用花括号来界定,不像Python用缩进界定,代码块可包含任意多行语句。

在C#代码中,有两种注释,代码注释和xml注释。

# 代码注释


//块注释:
/*
comment
...
*/

//行注释:
// comment

1
2
3
4
5
6
7
8
9
10

# XML注释

参考资料:XML文档注释 (opens new window)。

可以通过这个方式为代码创建文档:将特殊注释字段中的XML元素包含在源代码中注释引用的代码块前面,使用-doc选项进行编译时,编译器会在源代码中搜索所有XML标记,并创建一个XML文档文件,若要基于编译器生成的文件创建最终文档,可以使用DocFX (opens new window)和Sandcastle (opens new window)。

//单行分隔符
///

//多行分隔符
/** <summary>text</summary> */  

/**  
<summary>text</summary>  
*/  

/**  
 * <summary>text</summary>  
*/  
1
2
3
4
5
6
7
8
9
10
11
12
13

C#代码区分大小写,下面的代码都不能正常工作:

console.WriteLine("hello world");
Console.Writeline("hello world");
1
2

# 控制台应用基本结构

using System;
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLien("hello world");
            Console.ReadKey();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 预处理指令

同C++一样,C#也有很多预处理指令,比如#region和#endregion,详情请看C# 预处理器指令 (opens new window)。

# 变量

假如把计算机内存比作成一个架子,那么变量就是架子上的盒子,而数值字符等就是盒子中的内容,因为有很多类型的数值,如果放在一种类型的盒子中,比如较小类型的数值放在较大类型数值所在的盒子中,会浪费空间,这就产生了很多种变量类型,也就说有很多种盒子。

声明C#的变量语法:

type name;

# 简单类型

简单类型是组成应用程序中的基本构件类型,比如数值和布尔值,同复杂类型不一样,简单类型没有子类型或特性。

C#中的每一种类型都利用了 .Net Framework中定义的标准类型,使用标准类型可以在语言之间交互操作,在C#中这些类型的名称是Framework中定义的类型的别名。下面来看下它们的类型:

# 整数类型

类型 别名 允许的值
sbyte System.SByte -2^7 ~ 2^7-1
byte System.Byte 0 ~ 2^8-1
short System.Int16 -2^15 ~ 2^15-1
ushort System.UInt16 0 ~ 2^16-1
int System.Int32 -2^31 ~ 2^31-1
uint System.UInt32 0 ~ 2^32-1
long System.Int64 -2^63 ~ 2^63-1
ulong System.UInt64 0 ~ 2^64-1

"u"是unsigned的缩写,表示不能在这些类型的变量中存储负数,这样就把符号位用作数值存储位,比有符号数多存储一倍。

# 浮点类型

浮点类型可以用 m*2^e形式来表示:

类型 别名 m的最小值 m的最大值 e的最小值 e的最大值 近似值的最小值 近似值的最大值
float System.Single 0 2^24 -149 104 1.5*10^-45 3.4*10^38
double System.Double 0 2^53 -1075 970 5*10^-324 1.7*10^308
decimal System.Decimal 0 2^96 -28 0 1*10^-28 7.9*10^28

# 文本和布尔类型

类型 别名 允许的值
char System.Char 一个Unicode字符,存储0~2^16-1直接的数
bool System.Boolean 布尔值:true或false
string System.String 一个字符序列

string的字符数量是没有上限的,因为它可以使用可变大小的内存。

# Nullable类型

在 .Net 2.0中,C#提供了一种Nullable类型,允许定义值类型包含null值,这对于处理数据库可空字段有很大的帮助,具体就是在定义变量时类型名后面添加?。

还提供了??运算符,当被操作的变量为空值时,表达式返回??后面的值,可以利用这点给变量初始化。

static void Main(string[] strs)
{
    int? intVal = null;
    Console.WriteLine($"intVal.HasValue:{intVal.HasValue}");
    int intVal2 = intVal ?? -1;
    Console.WriteLine($"intVal2:{intVal2}");
    int intVal3 = intVal.GetValueOrDefault();
    Console.WriteLine($"intVal3:{intVal3}");

    //Console.WriteLine($"intVal.HasValue:{intVal.Value}");
    intVal = 2;
    Console.WriteLine($"intVal.HasValue:{intVal.HasValue}");
}
1
2
3
4
5
6
7
8
9
10
11
12
13

注意:

  • 语法T?是System.Nullable<T>的简写,此处T为值类型,这两种形式可以互换。
  • 如果变量已被赋值,则Value属性返回值,如果没有被赋值,其值为null值,则抛出System.InvalidOperationException异常。
# Nullable运算符
public class Person
{
    public int Age;
    public int GetAge()
    {
        return Age;
    }
}
static void Main(string[] args)
{
    int? org=1;
    int result = (int)org * 2;
    Console.WriteLine(result);

    //上面没有显式转换为int的话是编译不过去的。如果org为null,会抛出System.InvalidOperationException异常。
    int? org2 = 2;
    int result2 = org2.Value * 2;
    Console.WriteLine(result2);

    int? org3 = 3;
    int? result3 = org3 * 2;
    Console.WriteLine(result3.Value);

    bool? bool1 = true;
    bool? bool2 = false;
    //bool1* bool2;

    int? org4 = null;
    int result4 = org4 * 2 ?? 5;
    Console.WriteLine(result4);

    Person p1 = null ;
    Console.WriteLine(p1?.GetAge());

    //var onChanged = onChanged;
    //if (onChanged != null)
    //{
    //    onChanged(this, args);
    //}

    //上面检查事件是否为空的代码可用下面一行表示。

    //onChanged?.Invoke(this, args);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

注意:

  • 可空类型赋值给非可空类型是需要显式转换的。
  • ??运算符为空接合运算符(null coalescing operator),是一个二元运算符,允许给可能等于null的表达式提供另一个值。
  • ?.运算符通常称为Elvis运算符或空条件运算符,有助于避免繁杂的空值检查造成的代码歧义,它还可用于检查事件是否为空。

# 变量的命名

基本的命名规则如下:

  • 变量名的第一个字符必须是字母,下划线_或@
  • 其后的字符可以是字母,下划线或数字。

# 字面值

有很多后缀可以添加在值的后面来表示值的类型:

类型 类别 后缀 示例
bool 布尔 无 true或false
int,uint,long,ulong 整数 无 100
uint,ulong 整数 u或U 100U
long,ulong 整数 l或L 100L
ulong 整数 ul,uL,Ul,UL,lu,lU,Lu,或LU 100UL
float 实数 f或F 1.5F
double 实数 无,d或D 1.5
decimal 实数 m或M 1.5M
char 字符 无 'a'或转义序列
string 字符串 无 "a...a",或包含转义序列

上面整数的排列顺序意为解析的优先级,比如一个没有后缀的整数会根据大小先被解析为int,如果超出范围解析为uint,还超出范围解析为long,次之为ulong。

# 整数

整数分为二进制,八进制,十六进制,他们的前缀依次为0b,0,0x。

当字面值为二进制时阅读很费劲,C#提出了数字分隔符,它还可用于十进制,浮点数和双精度,就是使用一个下划线作为分割符:

float one = 3.14_15_926_3;
int two = 0b0002_0001;
1
2

# 字符串字面值

有一些字符在前面加\后会有一些其他含义,这些字符可能会直接表示特殊数据或某些功能。

转义序列 产生的字符 字符的Unicode值
' 单引号 0x0027
" 双引号 0x0022
\ 反斜杠 0x005C
\0 null 0x0000
\a 警告有蜂鸣声 0x0007
\b 退格 0x0008
\f 换页 0x000C
\n 换行 0x000A
\r 回车 0x000D
\t 水平制表符 0x0009
\v 垂直制表符 0x000B

可以直接使用转义序列或者前缀'\u'加上十六进制的Unicode值,下面两种形式是等价的:

"Hello\nWorld"
"Hello\u000AWorld"
1
2

当一个字符串中有很多转义字符的时候,比如路径需要很多的反斜杠,如果此时要把所有反斜杠转义会很麻烦,C#中可以在字符串前面加@符号,让所有的字符都不转义,这样会方便一点。

@"hello
world"

@"C:\temp\list\myfile.txt"
1
2
3
4

# 表达式

将变量和字面量与运算符结合起来,就可以创建表达式,它是计算的基本构件。它有两大类:一个是数学运算符,除了加,减,乘,除,取余,还有自增自减。其中"+"能用于字符串连接;另一类是赋值运算符,即"=","+=","-="等。

# 布尔逻辑

布尔类型有true或false两种,布尔比较需要使用布尔比较运算符,它们有==,!=,<,>,<=,>=。布尔比较直接返回布尔类型:

bool isTrue;
isTrue = myBool == true;
1
2

有四个条件布尔运算符,就是&,|,&&,||。&&和||同&和|的差别就是如果第一个条件能判断出结果,就不会比较第二个条件。比如&&运算符,如果第一个条件为false,那么它直接返回false。

# 布尔赋值运算符

布尔类型有三个赋值运算符,分别是&=,|=和^=。&=和|=赋值运算符并不是&&和||条件布尔运算符,而是使用&和|。

# 运算符优先级

运算符
++和--(前缀),+(一元),-(一元),(),!,~
*,/,%
+(二元),-(二元)
<<,>>
<,>,<=,=>
==,!=
&
^
|
&&
||
=,*=,/=,%=,+=,-=,<<=,=>>,&=,^=,|=
++和--(后缀)

这些运算符从上往下优先级依次降低。