C#本质论笔记 第2章 数据类型

数据类型

预定义类型(predefined type)/基元类型(primitive type),也翻译为基本类型、基础类型或者原始类型。

类型 Size Range BCL name Signed 精度 后缀 例子
sbyte 8 bits -128 to 127 System.SByte
byte 8 bits 0 to 255 System.Byte
short 16 bits -32,768 to 32,767 System.Int16
ushort 16 bits 0 to 65,535 System.UInt16
int 32 bits -2,147,483,648 to 2,147,483,647 System.Int32
uint 32 bits 0 到 4,294,967,295 System.UInt32 U或u
long 64 bits -9,223,372,036,854,775,808
to
9,223,372,036,854,775,807
System.Int64 L或l
ulong 64 bits -9,223,372,036,854,775,808
to
9,223,372,036,854,775,807
System.UInt64 UL或ul
float 32 bits ±1.5e−45 到 ±3.4e38 System.Single 7 F或f
double 64 bits ±5.0e−324 到 ±1.7e308 System.Double 15-16 D或d
decimal 128 bits ±1.0e−28 到 ±7.9e28 System.Decimal 28-29 M或m
bool bits System.Boolean
char 16 bits
Unicode 字符
U+0000 到 U+ffff System.Char char test;
test = ‘A’
string System.String string test;
test = “A”
object System.Object
dynamic 无相应.NET类型 dynamic dyn = 1;

类型关键字和BCL name效果相同,规范建议指定数据类型时使用C#关键字,而不是BCL名称(例如,使用==string==而不是System.String或者==String==)。

后缀不区分大小写,一般建议采用大写。但对于long ,强烈建议使用大写字母L,因为小写字母l和数字1不好区分。

关于指数记数法,需要使用 e 或者 E 中缀,在中缀字母后添加正整数或者负整数,并在字面量的最后添加恰当的数据类型后缀。例如将阿伏加德罗常熟作为float输出,代码如下:

1
System.Console.WriteLine(6.023E23F);

输出结果

6.023E+23

经常用到的数字格式化输出例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class hello {
static void Main () {
double dValue = 12345.6789;

//几种格式转换输出方式
System.Console.WriteLine (dValue); //原值输出 输出 12345.6789
System.Console.WriteLine ("{0}", dValue);
System.Console.WriteLine (string.Format ("{0}", dValue));
System.Console.WriteLine (dValue.ToString ());

System.Console.WriteLine ("以下几种方法在数据有效范围内效果相同:");
System.Console.WriteLine ("WriteLine (dValue);");
System.Console.WriteLine ("WriteLine (\"{0}\", dValue);");
System.Console.WriteLine ("WriteLine (string.Format (\"{0}\", dValue));");
System.Console.WriteLine ("WriteLine (dValue.ToString ());");
}
}

输出结果

12345.6789
12345.6789
12345.6789
12345.6789
以下几种方法在数据有效范围内效果相同:
WriteLine (dValue);
WriteLine ("{0}", dValue);
WriteLine (string.Format ("{0}", dValue));
WriteLine (dValue.ToString ());

基本数值类型

C#基本数值类型包括整数类型、浮点类型以及decimal类型。

浮点数的精度是可变的。例如浮点数 0.1,可以表示成 0.0999999999 或者 0.100000001(或者任何非常接近0.1的数)。

decimal是一种特殊的浮点类型,是128位精度的10进制浮点类型,能够存书大数字而无表示错误,适合大而精确的计算。

  • 与==二进制==浮点数不同,decimal类型保证范围内所有的==十进制==数都是精确的,0.1就是0.1。

==关于浮点精度理解==,目前计算机是2进制(只有0,1状态)的运算机器,所有小于1的数,只能用除法计算得到结果,计算机中的除法就是移位。

  • 例如0.1,表示成分数是1/10,分母10不是2的整数次幂,因此1/10不能用有限的二进制小数表示。

字面值或字面量(literal value)

literal value 表示源代码中的固定值,就是数据值本身。Microsoft Docs 将 literal 翻译作 文本

  • 个人理解,字面量可以叫做常数,常数值,固定值等等,可以是用户自己定义的或者系统定义的常数、符号等(例如:==123==、==’A’==、==\n== 等)。

例如,下面代码中的数字值:

1
2
System.Console.WriteLine(42);
System.Console.WriteLine(1.618034);

输出结果

42
1.618034

用变量定义来实现上面的效果,定义int 类型变量 x 并赋值 42 ,定义 double 类型变量 y 并赋值 1.618034 。

1
2
3
4
int x = 42;
double y = 1.618034;
System.Console.WriteLine(x);
System.Console.WriteLine(y);

输出结果

42
1.618034

初学者主题:使用硬编码要慎重

直接将值放到源代码中成为硬编码(==hardcoding==),如果以后需要更改值,就必须重新编译代码。给予方便维护考虑,可以考虑从外部获取值,比如从一个配置文件中,这样以后修改值的时候,不需要重新编译代码。

带小数点的字面值,编译器自动解释成 double 类型。

整数值(没有小数点)通常默认为 int,前提是值在 int 类型存储范围内。如果值太大,编译器会把它解释成 long 。

C# 允许想非 int 类型赋值,前提是字面值对于目标数据类型来说是合法的。例如,short s = 42和byte b = 77都是允许的。但值一点进队字面值成立。如果不使用额外语法,b = s就是非法的。

数据精度相关代码示例

1
2
System.Console.WriteLine(1.234567890123456);
System.Console.WriteLine(1.012345678901234);

输出结果

1.23456789012346
1.01234567890123

受限于 double 类型的精度,超过精度的遵循==四舍五入==原则截取。

要显示具有完整精度的数字,必须将字面值显示生命为 decimal 类型,知识通过追加一个M(或者m)后缀来实现的。

下面代码指定一个 decimal 字面值

1
2
System.Console.WriteLine(1.234567890123456M);
System.Console.WriteLine(1.012345678901234m);

输出结果

1.234567890123456
1.012345678901234

初学者主题:十六进制表示法 Hexadecimal Notation

C#允许指定十六进制值,需要附加 0x 或者 0X 前缀(效果一样),如下示例:

1
2
3
4
5
//用16进制字面量显示数值10
System.Console.WriteLine(0x000A);
System.Console.WriteLine(0x000a);
System.Console.WriteLine(0X000A);
System.Console.WriteLine(0X000a);

输出结果

10
10
10
10

注意,代码输出结果是10,而不是0x000A 或者 0x000a 。

高级主题:将数格式化成十六进制

要以十六进制形式格式化输出一个数值,必须使用==x==或者==X==数值格式说明符。==大小写==决定了十六进制字母的大小写,数值字面量可采用十进制或者十六进制形式,结果一样。如下示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//显示数值 "0xA" 或者 “0xa”
//字面量为十进制数值,格式化为十六进制,大写
System.Console.WriteLine("0x{0:X}", 10);
//字面量为十进制数值,格式化为十六进制,小写
System.Console.WriteLine("0x{0:x}", 10);
//字面量为十六进制大写数值,格式化为十六进制,小写
System.Console.WriteLine("0x{0:x}", 0X000A);
//字面量为十六进制小写数值,格式化为十六进制,大写
System.Console.WriteLine("0x{0:X}", 0X000a);

int value;
value = 0X000a;
System.Console.WriteLine(value.ToString("X"));
System.Console.WriteLine("0x{0}", value.ToString("X"));

输出结果

0xA
0xa
0xa
0xA
A
0xA

高级主题:round-trip (往返过程)格式化

在格式化字符串时,使用round-trip格式说明符(R 或者 r),用于确保转换为字符串的数值将再次分析为相同的数值。只有 Single、 Double 和 BigInteger 类型支持此格式。

例如,string.Format(“{0}”,0.1234567890123125) 结果是 0.123456789012313

而, string.Format(“{0:R}”,0.1234567890123125) 结果是 0.1234567890123125 。

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
public class Program
{
public static void Main()
{
const double number = 0.1234567890123125;
double result;
string text;

text = string.Format("{0}",number);
//double.Parase 将数字的字符串表示形式转换为它的等效双精度浮点数。
result = double.Parse(text);

System.Console.WriteLine(number);
System.Console.WriteLine("text = {0}" , text);
System.Console.WriteLine(result);

//double.ToSring() 将此实例的数值转换为其等效的字符串表示形式。
System.Console.WriteLine(number.ToString());
System.Console.WriteLine("{0}: text = number", text == number.ToString());

System.Console.WriteLine("{0}: result = number", result != number);

text = string.Format("{0:R}",number);
System.Console.WriteLine("text = {0}" , text);
result = double.Parse(text);
System.Console.WriteLine("{0}: result = number", result == number);

System.Console.ReadKey();
}
}

输出结果

0.123456789012313
text = 0.123456789012313
0.123456789012313
0.123456789012313
True: text = number
True: result = number
text = 0.1234567890123125
True: result = number

标准数字格式字符串(Numeric Format Strings)

标准数字格式字符串用于格式化通用数值类型。 标准数字格式字符串采用 Axx 的形式,其中:

  • A 是称为格式说明符的单个字母字符。任何包含一个以上字母字符(包括空白)的数字格式字符串都被解释为自定义数字格式字符串。
  • xx 是称为精度说明符的可选整数。 精度说明符的范围从 0 到 99,并且影响结果中的位数。请注意,精度说明符控制数字的字符串表示形式中的数字个数。 它不舍入该数字。若要执行舍入运算,请使用 Math.Ceiling、 Math.Floor 或 Math.Round 方法。
  • 当精度说明符控制结果字符串中的小数位数时,结果字符串反映远离零的一侧舍入的数字(即,使用 MidpointRounding.AwayFromZero)。
  • 所有数字类型的 ToString 方法的某些重载支持标准数字格式字符串。 例如,可将数字格式字符串提供给 ToString(String) 类型的 ToString(String, IFormatProvider) 方法和 Int32 方法。 .NET Framework 复合格式化功能也支持标准数字格式字符串,该功能由 Write 和 WriteLine 类的某些 Console 和 StreamWriter 方法、 String.Format 方法以及 StringBuilder.AppendFormat 方法使用。复合格式功能允许你将多个数据项的字符串表示形式包含在单个字符串中,以指定字段宽度,并在字段中对齐数字。

下表描述标准的数字格式说明符并显示由每个格式说明符产生的示例输出。

格式说明符 名称 描述 示例
“C”或“c” 货币 结果:货币值。
受以下类型支持:所有数值类型。
精度说明符:小数位数。
默认值精度说明符:
由 NumberFormatInfo.CurrencyDecimalDigits 定义。
更多信息: 货币(“C”)格式说明符。
123.456 (“C”, en-US) -> $123.46
123.456 (“C”, fr-FR) -> 123,46 €
123.456 (“C”, ja-JP) -> ¥123
-123.456 (“C3”, en-US) -> ($123.456)
-123.456 (“C3”, fr-FR) -> -123,456 €
-123.456 (“C3”, ja-JP) -> -¥123.456
“D”或“d” Decimal 结果:整型数字,负号可选。
受以下类型支持:==仅整型==。
精度说明符:最小位数。
默认值精度说明符:所需的最小位数。
更多信息: 十进制(“D”)格式说明符。
1234 (“D”) -> 1234
-1234 (“D6”) -> -001234
“E”或“e” 指数
(科学型)
结果:指数记数法。
受以下类型支持:所有数值类型。
精度说明符:小数位数。
默认值精度说明符:6。
更多信息: 指数(“E”)格式说明符。
1052.0329112756 (“E”, en-US) -> 1.052033E+003
1052.0329112756 (“e”, fr-FR) -> 1,052033e+003
-1052.0329112756 (“e2”, en-US) -> -1.05e+003
-1052.0329112756 (“E2”, fr_FR) -> -1,05E+003
“F”或“f” 定点 结果:整数和小数,负号可选。
受以下类型支持:所有数值类型。
精度说明符:小数位数。
默认值精度说明符:
由 NumberFormatInfo.NumberDecimalDigits 定义。
更多信息: 定点(“F”)格式说明符。
1234.567 (“F”, en-US) -> 1234.57
1234.567 (“F”, de-DE) -> 1234,57
1234 (“F1”, en-US) -> 1234.0
1234 (“F1”, de-DE) -> 1234,0
-1234.56 (“F4”, en-US) -> -1234.5600
-1234.56 (“F4”, de-DE) -> -1234,5600
“G”或“g” 常规 结果:最紧凑的定点表示法或科学记数法。
受以下类型支持:所有数值类型。
精度说明符:有效位数。
默认值精度说明符:取决于数值类型。
更多信息:常规(“G”)格式说明符。
-123.456 (“G”, en-US) -> -123.456
-123.456 (“G”, sv-SE) -> -123,456
123.4546 (“G4”, en-US) -> 123.5
123.4546 (“G4”, sv-SE) -> 123,5
-1.234567890e-25 (“G”, en-US) -> -1.23456789E-25
-1.234567890e-25 (“G”, sv-SE) -> -1,23456789E-25
“N”或“n” 数字 结果:整数和小数、组分隔符和小数分隔符,负号可选。
受以下类型支持:所有数值类型。
精度说明符:所需的小数位数。
默认值精度说明符:
由 NumberFormatInfo.NumberDecimalDigits 定义。
更多信息: 数字(“N”)格式说明符。
1234.567 (“N”, en-US) -> 1,234.57
1234.567 (“N”, ru-RU) -> 1 234,57
1234 (“N1”, en-US) -> 1,234.0
1234 (“N1”, ru-RU) -> 1 234,0
-1234.56 (“N3”, en-US) -> -1,234.560
-1234.56 (“N3”, ru-RU) -> -1 234,560
“P”或“p” 百分比 结果:乘以 100 并显示百分比符号的数字。
受以下类型支持:所有数值类型。
精度说明符:所需的小数位数。
默认值精度说明符:
由 NumberFormatInfo.PercentDecimalDigits 定义。
更多信息: 百分比(“P”)格式说明符。
1 (“P”, en-US) -> 100.00 %
1 (“P”, fr-FR) -> 100,00 %
-0.39678 (“P1”, en-US) -> -39.7 %
-0.39678 (“P1”, fr-FR) -> -39,7 %
“R”或“r” 往返过程 结果:可以往返至相同数字的字符串。
受以下类型支持: Single、 Double 和 BigInteger。
精度说明符:忽略。
更多信息: 往返过程(“R”)格式说明符。
123456789.12345678 (“R”) -> 123456789.12345678
-1234567890.12345678 (“R”) -> -1234567890.1234567
“X”或“x” 十六进制 结果:十六进制字符串。
受以下类型支持:仅整型。
精度说明符:结果字符串中的位数。
更多信息: 十六进制(“X”)格式说明符。
255 (“X”) -> FF
-1 (“x”) -> ff
255 (“x4”) -> 00ff
-1 (“X4”) -> 00FF
任何
其他单个字符
未知说明符 结果:在运行时引发 FormatException。 其他

自定义数字格式字符串

你可以创建自定义数字格式字符串,这种字符串由一个或多个自定义数字说明符组成,用于定义设置数值数据格式的方式。 自定义数字格式字符串是任何不属于 ==标准数字格式字符串== 的格式字符串。

所有数字类型的 ToString 方法的某些重载支持自定义数字格式字符串。 例如,可将数字格式字符串提供给 Int32 类型的 ToString(String) 方法和 ToString(String, IFormatProvider) 方法。 .NET Framework 复合格式化功能也支持自定义数字格式字符串,该功能由 Console 和 StreamWriter 类的某些 WriteWriteLine 方法、 String.Format 方法以及 StringBuilder.AppendFormat 方法所使用。

下表描述自定义数字格式说明符并显示由每个格式说明符产生的示例输出。

格式说明符 名称 描述 示例
“0” 零占位符 用对应的数字(如果存在)替换零;否则,将在结果字符串中显示零。 1234.5678 (“00000”) ->01235
0.45678 (“0.00”, en-US) -> 0.46
0.45678 (“0.00”, fr-FR) -> 0,46
“#” 数字占位符 用对应的数字(如果存在)替换“#”符号;
否则,不会在结果字符串中显示任何数字。
1234.5678 (“#####”) -> 1235
0.45678 (“#.##”, en-US) -> .46
0.45678 (“#.##”, fr-FR) -> ,46
“.” 小数点 确定小数点分隔符在结果字符串中的位置。 0.45678 (“0.00”, en-US) -> 0.46
0.45678 (“0.00”, fr-FR) -> 0,46
“,” 组分隔符和数字比例换算 用作组分隔符和数字比例换算说明符。
作为组分隔符时,它在各个组之间插入本地化的组分隔符字符。
作为数字比例换算说明符,对于每个指定的逗号,它将数字除以1000。
组分隔符说明符:
2147483647 (“##,#”, en-US) -> 2,147,483,647
2147483647 (“##,#”, es-ES) -> 2.147.483.647
比例换算说明符:
2147483647 (“#,#,,”, en-US) -> 2,147
2147483647 (“#,#,,”, es-ES) -> 2.147
“%” 百分比占位符 将数字乘以 100,并在结果字符串中插入本地化的百分比符号。 0.3697 (“%#0.00”, en-US) -> %36.97
0.3697 (“%#0.00”, el-GR) -> %36,97
0.3697 (“##.0 %”, en-US) -> 37.0 %
0.3697 (“##.0 %”, el-GR) -> 37,0 %
“‰” 千分比占位符 将数字乘以 1000,并在结果字符串中插入本地化的千分比符号。 0.03697 (“#0.00‰”, en-US) -> 36.97‰
0.03697 (“#0.00‰”, ru-RU) -> 36,97‰
“E0”
“E+0”
“E-0”
“e0”
“e+0”
“e-0”
指数表示法 如果后跟至少一个0(零),则使用指数表示法设置结果格式。
“E”或“e”指示指数符号在结果字符串中是大写还是小写。
跟在“E”或“e”字符后面的零的数目确定指数中的最小位数。
加号 (+)指示符号字符总是置于指数前面。
减号(-)指示符号字符仅置于负指数前面。
987654 (“#0.0e0”) -> 98.8e4
1503.92311 (“0.0##e+00”) -> 1.504e+03
1.8901385E-16 (“0.0e+00”) -> 1.9e-16
\ 转义符 使下一个字符被解释为文本而不是自定义格式说明符。 987654 (“###00#“) -> #987654#
‘string’
“string”
文本字符串分隔符 指示应复制到未更改的结果字符串的封闭字符。 68 (“# ‘ degrees’”) -> 68 degrees
68 (“# ‘ degrees’”) -> 68 degrees
; 部分分隔符 通过分隔格式字符串定义正数、负数和零各部分。 12.345 (“#0.0#;(#0.0#);-\0-“) -> 12.35
0 (“#0.0#;(#0.0#);-\0-“) -> -0-
-12.345 (“#0.0#;(#0.0#);-\0-“) -> (12.35)
12.345 (“#0.0#;(#0.0#)”) -> 12.35
0 (“#0.0#;(#0.0#)”) -> 0.0
-12.345 (“#0.0#;(#0.0#)”) -> (12.35)
其他 所有其他字符 字符将复制到未更改的结果字符串。 68 (“# °”) -> 68 °

标准数字格式化字符串 简单例子

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
45
46
47
48
49
50
using System;
using System.Globalization;
using System.Threading;

public class NumericFormats {
public static void Main () {
// Display string representations of numbers for en-us culture
CultureInfo ci = new CultureInfo ("en-us");

// Output floating point values
double floating = 10761.937554;
Console.WriteLine ("原始数字:10761.937554");
Console.WriteLine ("C: {0}",
floating.ToString ("C", ci)); // Displays "C: $10,761.94"
Console.WriteLine ("E: {0}",
floating.ToString ("E03", ci)); // Displays "E: 1.076E+004"
Console.WriteLine ("F: {0}",
floating.ToString ("F04", ci)); // Displays "F: 10761.9376"
Console.WriteLine ("G: {0}",
floating.ToString ("G", ci)); // Displays "G: 10761.937554"
Console.WriteLine ("N: {0}",
floating.ToString ("N03", ci)); // Displays "N: 10,761.938"
Console.WriteLine ("P: {0}",
(floating / 10000).ToString ("P02", ci)); // Displays "P: 107.62 %"
Console.WriteLine ("R: {0}",
floating.ToString ("R", ci)); // Displays "R: 10761.937554"
Console.WriteLine ();

// Output integral values
int integral = 8395;
Console.WriteLine ("原始数字:8395");
Console.WriteLine ("C: {0}",
integral.ToString ("C", ci)); // Displays "C: $8,395.00"
Console.WriteLine ("D: {0}",
integral.ToString ("D6", ci)); // Displays "D: 008395"
Console.WriteLine ("E: {0}",
integral.ToString ("E03", ci)); // Displays "E: 8.395E+003"
Console.WriteLine ("F: {0}",
integral.ToString ("F01", ci)); // Displays "F: 8395.0"
Console.WriteLine ("G: {0}",
integral.ToString ("G", ci)); // Displays "G: 8395"
Console.WriteLine ("N: {0}",
integral.ToString ("N01", ci)); // Displays "N: 8,395.0"
Console.WriteLine ("P: {0}",
(integral / 10000.0).ToString ("P02", ci)); // Displays "P: 83.95 %"
Console.WriteLine ("X: 0x{0}",
integral.ToString ("X", ci)); // Displays "X: 0x20CB"
Console.WriteLine ();
}
}

输出结果

原始数字:10761.937554
C: $10,761.94
E: 1.076E+004
F: 10761.9376
G: 10761.937554
N: 10,761.938
P: 107.62%
R: 10761.937554

原始数字:8395
C: $8,395.00
D: 008395
E: 8.395E+003
F: 8395.0
G: 8395
N: 8,395.0
P: 83.95%
X: 0x20CB

标准数字格式化字符串 详细例子

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
using System;
using System.Globalization;

class hello {
static void Main () {
double dValue = 12345.6789;
int iValue = 12345;

//几种格式转换输出方式
System.Console.WriteLine (dValue); //原值输出 输出 12345.6789
System.Console.WriteLine ("{0}", dValue);
System.Console.WriteLine (string.Format ("{0}", dValue));
System.Console.WriteLine (dValue.ToString ());

System.Console.WriteLine ("以下几种方法在数据有效范围内效果相同:");
System.Console.WriteLine ("WriteLine (dValue);");
System.Console.WriteLine ("WriteLine (\"{0}\", dValue);");
System.Console.WriteLine ("WriteLine (string.Format (\"{0}\", dValue));");
System.Console.WriteLine ("WriteLine (dValue.ToString ());");
System.Console.WriteLine ("======================================");

//■■ 货币 "C" 格式说明符 ■■//
Console.WriteLine ("{0:c}", dValue); //货币格式 默认两位小数 输出 ¥12,345.68
Console.WriteLine ("{0:c2}", dValue); //货币格式 输出 指定两位小数 ¥12,345.68
Console.WriteLine (dValue.ToString ("C")); //货币格式 输出 ¥12,345.68KT
Console.WriteLine (dValue.ToString ("C2")); //货币格式 输出 ¥12,345.68
Console.WriteLine (dValue.ToString ("C3")); //货币格式 指定三位小数 输出 ¥12,345.679

//货币格式 当前国家 默认位小数 输出 ¥12,345.68
//System.Globalization. CultureInfo.CurrentCulture
Console.WriteLine (dValue.ToString ("C", CultureInfo.CurrentCulture));
//货币格式 当前国家 指定两位小数 输出 ¥12,345.68
Console.WriteLine (dValue.ToString ("C2", CultureInfo.CurrentCulture));
//货币格式 中国货币 指定三位小数 输出 ¥12,345.679
Console.WriteLine (dValue.ToString ("C3",
CultureInfo.CreateSpecificCulture ("zh-CN")));
//货币格式 丹麦货币 指定三位小数 输出 12.345,679 kr.
Console.WriteLine (dValue.ToString ("C3",
CultureInfo.CreateSpecificCulture ("da-DK")));

//■■ 十进制 "D" 格式例子 ■■//
Console.WriteLine (iValue.ToString ("D")); //输出 12345
Console.WriteLine (iValue.ToString ("D8")); //输出 00012345
iValue = -12345;
Console.WriteLine (iValue.ToString ("D")); //输出 -12345
Console.WriteLine (iValue.ToString ("D8")); //输出 -00012345
//错误示例 “D”只接受整型 其他编译不通过
//Console.WriteLine(dValue.ToString("D8"));

//■■ 指数 "E" 格式说明符 ■■//
Console.WriteLine (dValue.ToString ("E")); //输出 1.234568E+004
Console.WriteLine (dValue.ToString ("E10")); //输出 1.2345678900E+004

//■■ 定点 “F” 格式说明符 ■■//
iValue = 12345;
Console.WriteLine (iValue.ToString ("F")); //输出 12345.00

iValue = -12345;
Console.WriteLine (iValue.ToString ("F3")); //输出 12345.000

dValue = 12345.6789;
Console.WriteLine (dValue.ToString ("F")); //输出 12345.68
Console.WriteLine (dValue.ToString ("F0")); //输出 12345

//■■ 常规 “G” 格式说明符 ■■//
dValue = 12345.6789;
Console.WriteLine (dValue.ToString ("G")); // Displays 12345.6789
Console.WriteLine (dValue.ToString ("G",
CultureInfo.CreateSpecificCulture ("fr-FR"))); // Displays 12345,6789

Console.WriteLine (dValue.ToString ("G7")); // Displays 12345.68

dValue = .0000023;
Console.WriteLine (dValue.ToString ("G")); // Displays 2.3E-06
Console.WriteLine (dValue.ToString ("G",
CultureInfo.CreateSpecificCulture ("fr-FR"))); // Displays 2,3E-06

dValue = .0023;
Console.WriteLine (dValue.ToString ("G")); // Displays 0.0023

dValue = 1234;
Console.WriteLine (dValue.ToString ("G2")); // Displays 1.2E+03

dValue = Math.PI;
Console.WriteLine (dValue.ToString ("G5")); // Displays 3.1416

//■■ 数字 “N” 格式说明符 ■■//
dValue = -12345.6789;
Console.WriteLine (dValue.ToString ("G")); // Displays 12345.6789N

Console.WriteLine (dValue.ToString ("N1",
CultureInfo.CreateSpecificCulture ("sv-SE"))); // Displays -12 445,7

iValue = 123456789;
Console.WriteLine (iValue.ToString ("N1",
CultureInfo.InvariantCulture)); // Displays 123,456,789.0

//■■ 数字 “N” 格式说明符 ■■//
dValue = -.2468013;
Console.WriteLine (dValue.ToString ("P")); // Displays -24.68 %

Console.WriteLine (dValue.ToString ("P1",
CultureInfo.CreateSpecificCulture ("sv-SE"))); // Displays -24,68%

Console.WriteLine (dValue.ToString ("P1",
CultureInfo.InvariantCulture)); // Displays -24.7 %

//■■ 数字 “R” 格式说明符 ■■//
dValue = Math.PI;
Console.WriteLine (dValue.ToString ("r")); // Displays 3.1415926535897931
Console.WriteLine (dValue.ToString ("r",
CultureInfo.CreateSpecificCulture ("fr-FR"))); // Displays 3,1415926535897931
dValue = 1.623e-21;
Console.WriteLine (dValue.ToString ("r")); // Displays 1.623E-21

//■■ 十六进制 “X” 格式说明符 ■■//
iValue = 0x2045e;
Console.WriteLine (iValue.ToString ("x")); // Displays 2045e
Console.WriteLine (iValue.ToString ("X")); // Displays 2045E
Console.WriteLine (iValue.ToString ("X8")); // Displays 0002045E

iValue = 123456789;
Console.WriteLine (iValue.ToString ("X")); // Displays 75BCD15
Console.WriteLine (iValue.ToString ("X2")); // Displays 75BCD15

Console.ReadKey ();
}
}

自定义数字格式化字符串例子

下面的示例演示两个自定义数字格式字符串。 在这两个示例中,数字占位符 (#) 显示数值数据,且所有其他字符被复制到结果字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System;
using System.Globalization;

public class Example {
public static void Main () {
double number1 = 1234567890;
string value1 = number1.ToString ("(###) ###-####");
Console.WriteLine (value1);

int number2 = 42;
string value2 = number2.ToString ("My Number = #");
Console.WriteLine (value2);
// The example displays the following output:
// (123) 456-7890
// My Number = 42
}
}

更多基本类型

char (字符)类型

char 关键字用于声明 System.Char 结构的实例,.NET Framework 使用该结构来表示 Unicode 字符。 Char 对象的值为 16 位的==数字(序号)值==(见Microsoft Docs C# 参考 char)。

  • Unicode 字符用于表示世界各地大多数的书面语言。
类型 范围 大小 .NET Framwork
char U+0000 到 U+FFFF Unicode 16 位字符 System.char
  • 后面经常用到的 \uxxxx 转义序列,XXXX是代表某个Unicode字符的16进制编码值。通过System.Text.Encoding.UTF8.GetBytes(char[])和System.Text.UnicodeEncoding.GetBytes(String)这些方法得到的值是10进制的,需要(通过 ==标准数字格式字符串== 中的 ==X2== )转换成16进制,就是相应的Unicode编码了。

char 可以隐式转换为 ushort、 int、 uint、 long、 ulong、 float、 double 或 decimal。 但是,不存在从其他类型到 char 类型的隐式转换。

char 类型的常数可以写成==字符==(例如 ‘x’)、==十六进制换码序列==(例如 ‘\t’)或 ==Unicode== (例如 ‘\u02C0’)表示形式。 您也可以显式转换整数字符代码。 在下面的示例中,几个 char 变量使用同一字符 X 初始化:

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
45
46
47
48
49
50
51
52
53
54
55
56
using System;
using System.Text;
class charType
{
static void Main()
{
char[] chars = new char[7];

chars[0] = 'X'; // Character literal
chars[1] = '\x0058'; // Hexadecimal
chars[2] = '\u0058'; // Unicode
chars[3] = (char)88; // Cast from integral type
chars[4] = (char)0x0058; // 转换自16进制整型
chars[5] = '\x5927'; //汉字 大 的Unicode编码
chars[6] = '大'; //汉字 大

foreach (char c in chars)
{
Console.Write(c + " ");
}

Console.WriteLine();

//获取汉字“大”的编码。保存为一个字节序列
byte[] bytes = Encoding.Unicode.GetBytes("大");
//得到编码的10进制字节数据 [39][89]
Console.WriteLine("[{0}][{1}]", bytes[0], bytes[1]);
//得到编码的16进制字节数据 [27][59] ,显示是litte-Endian方式
Console.WriteLine("[{0:X2}][{1:X2}]", bytes[0], bytes[1]);

//判断 大端 Big-Endian 小端 Little-Endian
if (BitConverter.IsLittleEndian)
Array.Reverse(bytes); // 转换为 大端模式 结果为 [59][27]

//输出 [59][27]
Console.WriteLine("[{0:X2}][{1:X2}]", bytes[0], bytes[1]);

Console.ReadKey();
}

//得到字符串中个字符(char)的Unicode编码
protected static string GetUnicode(string text)
{
string result = "";
for (int i = 0; i < text.Length; i++)
{
if ((int)text[i] > 32 && (int)text[i] < 127)
{
result += text[i].ToString();
}
else
result += string.Format("\\u{0:x4}", (int)text[i]);
}
return result;
}
}

输出结果

X X X X X 大 大
[39][89]
[27][59]
[59][27]

高级主题:解析字符串中字符的16进制编码(Unicode)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System;
public class Program {
static void Main (string[] args) {

string input = "你好 大世界!Hello World!";
for (int i = 0; i < input.Length; i++) {
// 占位符 方式
Console.Write ("{0} {1} ", input[i], ((int) input[i]).ToString ("X"));
// 字符串内插 $ 方式
Console.Write ($"{input[i]} {((int)input[i]).ToString("X")} ");
}
System.Console.WriteLine ();
Console.ReadKey ();
}
}

输出结果

你 4F60 好 597D   20 大 5927 世 4E16 界 754C ! FF01 H 48 e 65 l 6C l 6C o 6F   20 W 57 o 6F r 72 l 6C d 64 ! 21

高级主题:大端 Big-Endian 小端 Little-Endian

不同的计算机结构采用不同的字节顺序存储数据。” Big-endian”表示最大的有效字节位于单词的左端。” Little-endian”表示最大的有效字节位于单词的右端。

另以一种解释

  • Little-Endian 就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
  • Big-Endian 就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

例子,比如数字0x12 34 56 78在内存中的表示形式为:

  • 大端模式:
低地址 -----------------> 高地址
0x12  |  0x34  |  0x56  |  0x78
  • 小端模式:
低地址 ------------------> 高地址
0x78  |  0x56  |  0x34  |  0x12

可见,大端模式和字符串的存储模式类似。

  • 具体例子:汉字“大”(\u5927)的Unicode编码在 Little-endian 模式(以及 Big-endian 模式)CPU内存中的存放方式(假设从地址0x4000开始存放)为:
内存地址 小端模式存放内容 大端模式存放内容
0x4000 0x27 0x59
0x4001 0x59 0x27

转义序列 escape sequence

由反斜杠 ( \ ) 后接字母或数字组合构成的字符组合称为“转义序列”。要在字符常量中表示换行符,单引号或某些其他字符,你必须使用转义序列。 转义序列被视为单个字符,因此,它是有效的字符常量。

转义序列通常用于指定操作,例如终端和打印机上的回车和制表符移动。它们还用于提供非打印字符的文本表现形式和通常具有特殊意义的字符,例如双引号 ( “ )。 下表列出 ANSI 转义序列以及它们所表示的内容。

请注意,在字符序列将被错误解释为三元组的情况下,前接反斜杠的问号 ( ?) 指定文本问号。

转义序列 含义(字符名称) Unicode Encoding(Unicode 编码)
' 单引号 0x0027
" 双引号 0x0022
\\ 反斜杠 0x005C
\? 文本问号
\0 Null 0x0000
\a Alert 或 Bell 铃声(提醒) 0x0007
\b Backspace 退格 0x0008
\f 换页 0x000C
\n 换行 0x000A
\r 回车 0x000D
\t 水平制表符 0x0009
\v 垂直制表符 0x000B
\ ooo 在八进制表示法的 ASCII 字符
\uxxxx 16进制Unicode字符,Unicode 转义序列 \u0041 = “A”
\x [h][h][h]h 16进制Unicode字符,(前三个占位符可选),\uxxxx的长度可变版本。

除长度可变外,Unicode 转义序列与“\u”类似。
\u3A
\x hh 以十六进制表示法的 ASCII 字符
\x hhhh 十六进制表示法的 Unicode 字符

(如果此转义序列用于宽字符常量或 Unicode 字符串文本)。

以十六进制表示法,则此转义序列用于常量宽字符或 Unicode 字符串的 Unicode 字符。

例如,WCHAR f = L’\x4e00’

或 WCHAR b[] = L”The Chinese character for one is \x4e00”。
\x0041 = “A”
\Unnnnnnnn U代理项对的 Unicode 转义序列。 \Unnnnnnnn

字符串

常规字符串 regular

常规字符串

1
2
3
4
5
6
7
8
9
10
11
12
string columns = "Column 1\tColumn 2\tColumn 3";
//Output: Column 1 Column 2 Column 3

string rows = "Row 1\r\nRow 2\r\nRow 3";
/* Output:
Row 1
Row 2
Row 3
*/

string title = "\"The \u00C6olean Harp\", by Samuel Taylor Coleridge";
//Output: "The Æolean Harp", by Samuel Taylor Coleridge

@ 逐字字符串

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
string filePath = @"C:\Users\scoleridge\Documents\";
//Output: C:\Users\scoleridge\Documents\

string text = @"My pensive SARA ! thy soft cheek reclined
Thus on mine arm, most soothing sweet it is
To sit beside our Cot,...";
/* Output:
My pensive SARA ! thy soft cheek reclined
Thus on mine arm, most soothing sweet it is
To sit beside our Cot,...
*/

System.Console.Write (@"begin
/\
/ \
/ \
/ \
/________\
end");
/*输出
begin
/\
/ \
/ \
/ \
/________\
end
*/

string quote = @"Her name was ""Sara.""";
//Output: Her name was "Sara."

$ 字符串内插

借助==字符串内插==,可以将字符串中的占位符替换成字符串变量的值。 在 C# 6 中,我们最终实现了这种样式的字符串内插。 可以在字符串前面使用 $,以指明应使用变量/表达式替换相应的值。

  • 在低于 C# 6 的版本中,使用 System.String.Format 实现字符串内插。 虽然这样做是可行的,但由于要用到编号占位符,因此加大了读取难度且过程更为冗长。

先决条件

使用==字符串内插==,必须将计算机设置为运行 ==.Net Core==。

字符串内插简介

使用 System.String.Format在字符串中指定要被字符串后面的参数替换的“==占位符==”。 例如:

1
2
3
4
var firstName = "Matt";
var lastName = "Groves";
var str = String.Format("My name is {0} {1}", firstName, lastName);
Console.WriteLine(str);

输出结果

“My name is Matt Groves”

在 C# 6 中,定义内插字符串的方式为,在内插字符串前面添加 ==$== 符号,然后直接在字符串中使用变量,而不使用 String.Format。 例如:

1
2
3
4
var firstName = "Matt";
var lastName = "Groves";
var str = $"My name is {firstName} {lastName}";
Console.WriteLine(str);

输出结果

“My name is Matt Groves”

不必局限于变量。 ==可以在括号内使用任意表达式==。 例如:

1
2
3
for(var i = 0; i < 5; i++) {
Console.WriteLine($"This is line number {i + 1}");
}

输出结果

This is line number 1
This is line number 2
This is line number 3
This is line number 4
This is line number 5

字符串内插的工作方式

在后台,编译器将此类字符串内插语法转换成 String.Format。 因此,可以执行之前使用 String.Format 执行的相同操作。

例如,可以添加填充和数值格式:

1
2
3
4
5
6
var rand = new Random();
for(var i = 998; i < 1005; i++)
{
var randomDecimal = rand.NextDouble() * 10000;
Console.WriteLine($"{i, -10} {randomDecimal, 6:N2}");
}

输出结果

998        5,177.67
999        6,719.30
1000       9,910.61
1001       529.34
1002       1,349.86
1003       2,660.82
1004       6,227.77

字符串转字符数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System;
class TestRef {
static void Main () {
string a = "Hello World! 你好 大世界";
char[] b = a.ToCharArray ();
for (int i = 0; i < b.Length; i++) {
Console.Write (b[i]);
}
// 输出 Hello World! 你好 大世界

Console.WriteLine ();

Array.Reverse (b);
for (int i = 0; i < b.Length; i++) {
Console.Write (b[i]);
}
// 输出 界世大 好你 !dlroW olleH

System.Console.ReadKey ();
}
}

字符串数组

1
2
3
4
5
6
//只有在使用字符串数组初始化字符串时,才能使用 new 运算符串讲字符串对象
string[] xx = new string[8];
xx[0] = "aaaa";
xx[1] = "bbbb";
System.Console.WriteLine(xx[0]); //输出 aaaa
System.Console.WriteLine(xx[1]); //输出 bbbb

字符串对象的不可变性

1
2
3
4
5
6
7
8
9
string text;

System.Console.Write ("Enter text: ");
text = System.Console.ReadLine ();

// UNEXPECTED: Does not convert text to uppercase
text.ToUpper ();

System.Console.WriteLine (text);

输出结果

Enter text: This is a test of the emergency broadcast system.
This is a test of the emergency broadcast system.

字符串对象是“==不可变的==”:它们在创建后无法更改。 看起来是在修改字符串的所有 String 方法和 C# 运算符实际上都是在新的字符串对象中返回结果。

因此,text.ToUpper()不会改变 text 的内容为大写,而是返回了一个新的字符串,它需要保存到变量中,或者直接传给System.Console.WriteLine()。下面代码是改进后的:

1
2
3
4
5
string text, uppercase;
System.Console.Write ("Enter text: ");
text = System.Console.ReadLine (); // Return a new string in uppercase
uppercase = text.ToUpper ();
System.Console.WriteLine (uppercase);

输出结果

Enter text: This is a test of the emergency broadcast system.
THIS IS A TEST OF THE EMERGENCY BROADCAST SYSTEM.

访问单个字符

可以使用包含索引值的数组表示法来获取对单个字符的只读访问权限,如下面的示例中所示:

1
2
3
4
5
6
7
string s5 = "Printing backwards";

for (int i = 0; i < s5.Length; i++)
{
System.Console.Write(s5[s5.Length - i - 1]);
}
// Output: "sdrawkcab gnitnirP"

如果 String 方法不提供修改字符串中的各个字符所需的功能,可以使用 ==StringBuilder== 对象“就地”修改各个字符,再新建字符串来使用 StringBuilder 方法存储结果。 在下面的示例中,假定必须以特定方式修改原始字符串,然后存储结果以供未来使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
string question = "hOW DOES mICROSOFT wORD DEAL WITH THE cAPS lOCK KEY?";
System.Text.StringBuilder sb = new System.Text.StringBuilder(question);

for (int j = 0; j < sb.Length; j++)
{
if (System.Char.IsLower(sb[j]) == true)
sb[j] = System.Char.ToUpper(sb[j]);
else if (System.Char.IsUpper(sb[j]) == true)
sb[j] = System.Char.ToLower(sb[j]);
}
// Store the new string.
string corrected = sb.ToString();
System.Console.WriteLine(corrected);
// Output: How does Microsoft Word deal with the Caps Lock key?

使用 StringBuilder 快速创建字符串

.NET 中的字符串操作进行了高度的优化,在大多数情况下不会显著影响性能。 但是,在某些情况下(例如,执行数百次或数千次的紧密循环),字符串操作可能影响性能。 StringBuilder 类创建字符串缓冲区,用于在程序执行多个字符串操控时提升性能。 使用 StringBuilder 字符串,还可以重新分配各个字符,而内置字符串数据类型则不支持这样做。 例如,此代码更改字符串的内容,而无需创建新的字符串:

1
2
3
4
5
6
System.Text.StringBuilder sb = new System.Text.StringBuilder("Rat: the ideal pet");
sb[0] = 'C';
System.Console.WriteLine(sb.ToString());
System.Console.ReadLine();

//Outputs Cat: the ideal pet

在以下示例中,StringBuilder 对象用于通过一组数字类型创建字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class TestStringBuilder
{
static void Main()
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();

// Create a string composed of numbers 0 - 9
for (int i = 0; i < 10; i++)
{
sb.Append(i.ToString());
}
System.Console.WriteLine(sb); // displays 0123456789

// Copy one character of the string (not possible with a System.String)
sb[0] = sb[9];

System.Console.WriteLine(sb); // displays 9123456789
}
}

Null 字符串和空字符串

空字符串是包含零个字符的 System.String 对象实例。 空字符串常用在各种编程方案中,表示空文本字段。 可以对空字符串调用方法,因为它们是有效的 System.String 对象。 对空字符串进行了初始化,如下所示:

1
string s = String.Empty;

相比较而言,null 字符串并不指 System.String 对象实例,只要尝试对 null 字符串调用方法,都会引发 NullReferenceException。 但是,可以在串联和与其他字符串的比较操作中使用 null 字符串。 以下示例说明了对 null 字符串的引用会引发和不会引发意外的某些情况:

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
static void Main()
{
string str = "hello";
string nullStr = null;
string emptyStr = String.Empty;

string tempStr = str + nullStr;
// Output of the following line: hello
Console.WriteLine(tempStr);

bool b = (emptyStr == nullStr);
// Output of the following line: False
Console.WriteLine(b);

// The following line creates a new empty string.
string newStr = emptyStr + nullStr;

// Null strings and empty strings behave differently. The following
// two lines display 0.
Console.WriteLine(emptyStr.Length);
Console.WriteLine(newStr.Length);
// The following line raises a NullReferenceException.
//Console.WriteLine(nullStr.Length);

// The null character can be displayed and counted, like other chars.
string s1 = "\x0" + "abc";
string s2 = "abc" + "\x0";
// Output of the following line: * abc*
Console.WriteLine("*" + s1 + "*");
// Output of the following line: *abc *
Console.WriteLine("*" + s2 + "*");
// Output of the following line: 4
Console.WriteLine(s2.Length);
}

比较字符串

1
2
3
4
5
6
7
string sCompare = "h";
int result = string.Compare (sCompare, "a");
System.Console.WriteLine (result); //输出 1 "h" > "a"
result = string.Compare (sCompare, "h", true);
System.Console.WriteLine (result); //输出 0 "h" = "h"
result = string.Compare (sCompare, "x", true);
System.Console.WriteLine (result); //输出 -1 "h" < "x"

子字符串

1
2
3
4
5
6
7
8
string s3 = "Visual C# Express";
System.Console.WriteLine (s3.Substring (7, 2)); // Output: "C#"

System.Console.WriteLine (s3.Replace ("C#", "Basic"));
// Output: "Visual Basic Express"

// Index values are zero-based
int index = s3.IndexOf ("C"); // index = 7

null 和 void、

null 值表明变量不引用任何有效的对象。void 表示没有类型、或者没有任何值。

null

null 可以作为字符串类型的字面量。null 值只能赋给引用类型、指针类型和可空类型。变量设为null,会显式地设置引用,使它不指向任何位置。

null 不等于 “” 。”” 意味变量有一个叫做“空字符串”的值。null 以为这变量无任何值。

void

指定为 void 类型就无需传递任何数据了。void 本质上并不是一个数据类型,知识用于指出没有数据类型这一事实。

高级主题:隐式类型的局部变量

C# 3.0增加了上下文关键字 var 来声明隐式类型的局部变量。该变量在编译时仍然会接收一个类型,但该类型是由编译器提供的。

虽然允许使用 var 取代显示的数据类型,但是在数据类型已知的情况下,最好不要使用 var。

C# 3.0添加 var 的目的是支持匿名类型。匿名类型是在方法内部动态声明数据类型,而不是通过显式的类定义来生命的,如下例:

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
using System;
using System.Text;

public class SamplesString {

public static void Main () {

var patent1 =
new {
Title = "Bifocals",
YearOfPublication = "1784"
};
var patent2 =
new {
Title = "Phonograph",
YearOfPublication = "1877"
};

System.Console.WriteLine ("{0} ({1})",
patent1.Title, patent1.YearOfPublication);

System.Console.WriteLine ("{0} ({1})",
patent2.Title, patent2.YearOfPublication);

Console.ReadKey ();
}

}

输出结果

Bifocals (1784)
Phonograph (1877)

var 声明局部变量的各种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// i is compiled as an int
var i = 5;

// s is compiled as a string
var s = "Hello";

// a is compiled as int[]
var a = new[] { 0, 1, 2 };

// expr is compiled as IEnumerable<Customer>
// or perhaps IQueryable<Customer>
var expr =
from c in customers
where c.City == "London"
select c;

// anon is compiled as an anonymous type
var anon = new { Name = "Terry", Age = 34 };

// list is compiled as List<int>
var list = new List<int>();

在以下上下文中,使用 var 关键字

  • 在 for 初始化语句中。
1
for(var x = 1; x < 10; x++)
  • 在 foreach 初始化语句中。
1
foreach(var item in list){...}
  • 在 using 域间中。
1
using (var file = new StreamReader("C:\\myfile.txt")) {...}

可空修饰符 可以为null的类型 ?

C# 2.0 开始引入的这个特性,没有值表示“未定义”的意思。

可通过以下两种方式之一声明可为 null 的类型:

System.Nullable<T> variable

T? variable

T 是可以为 null 的类型的基础类型。 T 可以是包括 struct 在内的任意值类型;它不能是引用类型。

可为 null 的类型示例 int?

任何值类型都可用作 作为null的类型 的基础。例如:

1
2
3
4
5
int? i = 10;
double? d1 = 3.14;
bool? flag = null;
char? letter = 'a';
int?[] arr = new int?[10];

可为 null 的类型的成员

可以为 null 的类型的每个实例都有两个公共只读属性:

  • ==HasValue== 类型为 bool。 如果该变量包含非 null 值,则将其设置为 true。
  • ==Value== 与基础类型相同。 如果 HasValue 为 true,则 Value 包含有意义的值。 如果 HasValue 是 false,则访问 Value 将引发 InvalidOperationException。

将 null 赋给值类型,在数据库编程中尤其有用。在数据表中,经常出现值类型的列允许为空的情况。除非允许包含 null 值,否则在C#代码中检索这些列并将他们的值赋给对应的字段会出现问题。可控修饰符妥善地解决了这个问题。

高级主题:null 合并运算符 ??

?? 运算符定义一个默认值,若将一个可为 null 的类型赋给不可为 null 的类型,则会返回该值。

?? 运算符称作 null 合并运算符 (空接合操作符)。 如果此运算符的左操作数不为 null,则此运算符将返回左操作数;否则返回右操作数。

1
2
3
4
5
int? x = null;

// Set y to the value of x if x is NOT null; otherwise,
// if x == null, set y to -1.
int y = x ?? -1;

空接合操作符可以“链接”。例如,对于表达式 x ?? y ?? z ,如果 x 不为 null 则返回 x ;否则,如果 y 不为 null 则返回 y;否则返回 z。

高级主题:NULL条件运算符 ?.

用于在执行成员访问 ==(?.)== 或索引 ==(?[)== ==操作之前==,测试是否存在 NULL。 这些运算符可帮助编写更少的代码来处理 null 检查,尤其是对于下降到数据结构。

1
2
3
4
5
6
7
8
int? length = customers?.Length; // null if customers is null   
Customer first = customers?[0]; // null if customers is null

// null if customers, the first customer, or Orders is null
int? count = customers?[0]?.Orders?.Count();

// ?. 结合 ??
int? len = customers?.Length ?? 0;

高级主题:条件运算符 ?:

条件运算符 ?: 根据 Boolean 表达式的值返回两个值之一。下面是条件运算符的语法。

condition ? first_expression : second_expression;

condition 的计算结果必须为 truefalse。 如果 conditiontrue,则将计算 first_expression 并使其成为结果。 如果 conditionfalse,则将计算 second_expression 并使其成为结果。 只计算两个表达式之一。
first_expressionsecond_expression 的类型必须相同,或者必须存在从一种类型到另一种类型的隐式转换。

你可通过使用条件运算符表达可能更确切地要求 if-else 构造的计算。 例如,以下代码首先使用 if 语句,然后使用条件运算符将整数分类为正整数或负整数。

1
2
3
4
5
6
7
8
9
10
11
int input = Convert.ToInt32(Console.ReadLine());
string classify;

// if-else construction.
if (input > 0)
classify = "positive";
else
classify = "negative";

// ?: conditional operator.
classify = (input > 0) ? "positive" : "negative";

条件运算符为右联运算符。 表达式 a ? b : c ? d : e 作为 a ? b : (c ? d : e) 而非 (a ? b : c) ? d : e 进行计算。

无法重载条件运算符。

类型的分类

值类型

C# 中有两种类型:==引用类型==和==值类型==。 引用类型的变量存储对其数据(对象)的引用,而值类型的变量直接包含其数据。 对于引用类型,两种变量可引用同一对象;因此,对一个变量执行的操作会影响另一个变量所引用的对象。 对于值类型,每个变量都具有其自己的数据副本,对一个变量执行的操作不会影响另一个变量(ref 和 out 参数变量除外,请参阅 ref 和 out 参数修饰符)。

值类型直接包含值,变量引用的位置就是值在内存中的实际存储位置。

  • 将一个变量的值赋给另一个变量会导致在新变量的位置创建原始变量值的一个内存副本。
  • 类似的,将值类型的示例传给方法,如 Console.WriteLine(),也会产生一个内存副本。
  • 由于值类型需要创建内存副本,因此定义时不要让它们占用太多内存(通常应该给小于16字节)。

引用类型

引用类型的变量存储的时对数据存储位置的引用,而不是直接存储数据。

数据类型 转换

显式转换(explicit conversions | casts | 强制转换)

显式转换需要强制转换运算符。在==转换中可能丢失信息时或在出于其他原因转换可能不成功==时,必须进行强制转换。 典型的示例包括从数值到精度较低或范围较小的类型的转换和从基类实例到派生类的转换。

如果进行转换可能会导致信息丢失,则编译器会要求执行显式转换,显式转换也称为强制转换。 强制转换是显式告知编译器你打算进行转换且你知道可能会发生数据丢失的一种方式。 若要执行强制转换,请在要转换的值或变量前面的括号中指定要强制转换到的类型。下面的程序将 double 强制转换为 int。==如不强制转换则该程序不会进行编译==。

1
2
3
4
5
6
7
8
9
10
11
12
class Test
{
static void Main()
{
double x = 1234.7;
int a;
// Cast double to int.
a = (int)x;
System.Console.WriteLine(a);
}
}
// Output: 1234

有关支持的显式数值转换的列表,请参阅显式数值转换表

高级主题:checked 和 unchecked 转换

  • 在 cheecked 块内,如果在运行时发生一次溢出的赋值,就会引发异常。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System;
using System.Text;

public class Program {

public static void Main () {
checked {
// int.MaxValue equals 2147483647
int n = int.MaxValue;
n = n + 1;
System.Console.WriteLine (n);
}
Console.ReadKey ();
}
}

输出结果

未经处理的异常:  System.OverflowException: 算术运算导致溢出。
在 Program.Main()

unchecked 块,强制不进行一处检查,不会为块中溢出的赋值引发异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System;
using System.Text;

public class Program {

public static void Main () {
unchecked {
// int.MaxValue equals 2147483647
int n = int.MaxValue;
n = n + 1;
System.Console.WriteLine (n);
}
Console.ReadKey ();
}
}

输出结果

-2147483648

隐式转换(implicit conversions)

由于该转换是一种类型==安全的转换,不会导致数据丢失==,因此不需要任何特殊的语法。

对于内置数值类型,如果要存储的值==无需截断或四舍五入==即可适应变量,则可以进行隐式转换。 例如,long 类型的变量(8 字节整数)能够存储 int(在 32 位计算机上为 4 字节)可存储的任何值。

  • 隐式转换无需使用转换操作符
1
2
int intNumber = 31416;
long longNumber = intNumber;
  • 隐式转换 也可以强制添加转换操作符
1
2
int intNumber = 31416;
long longNumber = (long) intNumber;

不使用转型操作符的类型转换

==字符串到数值类型== 转换,需要使用 Parse()/TryParse() 这样的方法。每个数值类型都包含 Parse()/TryParse() 方法,它允许将字符串还换成对应的数值类型。

例子

1
2
string text = "9.11E-31";
float kgElectronMass = float.Parse(text);

还可以利用 ==特殊类型System.Convert== 将一种类型转换成另一种类型。不过,System.Convert只支持预定义类型,而且是不可扩展的。它允许从任何==基元(基本)类型==(bool, char, sbyte, short, int, long, ushort, uint, ulong, float, double, decimal, DateTime, and string)转换到任何其它基元(基本)类型。参考 MSDN Convert 类。

例子

1
2
3
string middleCText = "261.626";
double middleC = System.Convert.ToDouble(middleCText);
bool boolean = System.Convert.ToBoolean(middleC);

除了以上方法,所有类型都支持==ToString()方法== 转换,可以用它得到一个类型的值的字符串表示。

例子

1
2
3
4
5
6
7
bool boolean = true;
string text = boolean.ToString();
System.Console.WriteLine(text); // Display "True"

double dVal = -1.123456;
text=dVal.ToString();
System.Console.WriteLine(dVal);// Display "-1.123456"

高级主题:TryParse()

从 C# 2.0 开始,所有基元数据数据类型都包含静态 TryParse() 方法。该方法与 Parse() 非常相似,知识在缓缓失败的清康熙,它不引发异常,而是返回 false ,如下示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
double number;
string input;

System.Console.Write("Enter a number: ");
input = System.Console.ReadLine();
if (double.TryParse(input, out number))
{
// Converted correctly, now use number
// ...
}
else

{
System.Console.WriteLine(
"The text entered was not a valid number.");
}

高级主题:字符串转换为数字

字符串转数字的几种方法:

  • Parse 转换对象必须是组成合适数值类型(int、long、ulong、float、decimal 等)的字符。(例如:Int32.Parse(“-15”))
  • TryParse 转换对象必须是组成合适数值类型(int、long、ulong、float、decimal 等)的字符。(例如:Int32.TryParse(“-15”, out j))
  • Convert.ToInt32 针对各种数值类型(int、long、float等,例如:Convert.ToInt32(“-15”))

如果你具有字符串,则调用 TryParse 方法(例如 int.TryParse(“11”))会稍微更加高效且简单。 使用 Convert 方法对于实现 IConvertible 的常规对象更有用。

可以对预期字符串会包含的数值类型(如 ==System.Int32== 类型)使用 Parse 或 TryParse 方法。 Convert.ToUInt32 方法在内部使用 Parse。 如果字符串的格式无效,则 Parse 会引发异常,而 TryParse 会返回 false。

示例 Parse 和 TryParse 例:System.Int32.Parse

==Parse 和 TryParse== 方法会忽略字符串开头和末尾的空格,但所有其他字符必须是组成合适数值类型(int、long、ulong、float、decimal 等)的字符。 组成数字的字符中的任何空格都会导致错误。 例如,可以使用 decimal.TryParse 分析“10”、“10.3”、“ 10 ”,但不能使用此方法分析从“10X”、“1 0”(注意空格)、“10 .3”(注意空格)、“10e1”(float.TryParse 在此处适用)等中分析出 10。

下面的示例演示了对 Parse 和 TryParse 的成功调用和不成功的调用。

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
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

public class Program {

public static void Main () {
int numVal = Int32.Parse ("-105");
Console.WriteLine (numVal);
// Output: -105

double dVal = Double.Parse("105.12345");
Console.WriteLine(dVal);
// 输出 105.12345

// TryParse returns true if the conversion succeeded
// and stores the result in j.
int j;
if (Int32.TryParse ("-105", out j))
Console.WriteLine (j);
else
Console.WriteLine ("String could not be parsed.");
// Output: -105

try {
int m = Int32.Parse ("abc");
} catch (FormatException e) {
Console.WriteLine (e.Message);
}
// Output: Input string was not in a correct format.

string inputString = "abc";
int numValue;
bool parsed = Int32.TryParse (inputString, out numValue);

if (!parsed)
Console.WriteLine ("Int32.TryParse could not parse '{0}' to an int.\n", inputString);

// Output: Int32.TryParse could not parse 'abc' to an int.

Console.ReadKey ();
}
}

示例 Convert 例:System.Convert.ToInt32

下表列出了 Convert 类中可使用的一些方法。

数值类型 方法
decimal ToDecimal(String)
float ToSingle(String)
double ToDouble(String)
short ToInt16(String)
int ToInt32(String)
long ToInt64(String)
ushort ToUInt16(String)
uint ToUInt32(String)
ulong ToUInt64(String)

此示例调用 Convert.ToInt32(String) 方法将输入的 string 转换为 int。 代码将捕获此方法可能引发的最常见的两个异常:FormatException 和 OverflowException。 如果该数字可以递增而不溢出整数存储位置,则程序使结果加上 1 并打印输出。

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

public class Program {

static void Main (string[] args) {
int numVal = -1;
bool repeat = true;

while (repeat) {
Console.WriteLine ("Enter a number between −2,147,483,648 and +2,147,483,647 (inclusive).");

string input = Console.ReadLine ();

// ToInt32 can throw FormatException or OverflowException.
try {
numVal = Convert.ToInt32 (input);
} catch (FormatException e) {
Console.WriteLine ("Input string is not a sequence of digits.");
} catch (OverflowException e) {
Console.WriteLine ("The number cannot fit in an Int32.");
} finally {
if (numVal < Int32.MaxValue) {
Console.WriteLine ("The new value is {0}", numVal + 1);
} else {
Console.WriteLine ("numVal cannot be incremented beyond its current value");
}
}
Console.WriteLine ("Go again? Y/N");
string go = Console.ReadLine ();
if (go == "Y" || go == "y") {
repeat = true;
} else {
repeat = false;
}
}
// Keep the console open in debug mode.
Console.WriteLine ("Press any key to exit.");
Console.ReadKey ();
}
// Sample Output:
// Enter a number between -2,147,483,648 and +2,147,483,647 (inclusive).
// 473
// The new value is 474
// Go again? Y/N
// y
// Enter a number between -2,147,483,648 and +2,147,483,647 (inclusive).
// 2147483647
// numVal cannot be incremented beyond its current value
// Go again? Y/N
// Y
// Enter a number between -2,147,483,648 and +2,147,483,647 (inclusive).
// -1000
// The new value is -999
// Go again? Y/N
// n
// Press any key to exit.
}

高级主题:字节数组转换为数字 BitConverter

此示例演示如何使用 BitConverter 类将字节数组转换为 int 然后又转换回字节数组。 例如,在从网络读取字节之后,可能需要将字节转换为内置数据类型。 除了示例中的 ToInt32(Byte[],Int32) 方法之外,下表还列出了 BitConverter 类中将字节(来自字节数组)转换为其他内置类型的方法。

返回类型 方法
bool ToBoolean(Byte[],Int32)
char ToChar(Byte[],Int32)
double ToDouble(Byte[],Int32)
short ToInt16(Byte[],Int32)
int ToInt32(Byte[],Int32)
long ToInt64(Byte[],Int32)
float ToSingle(Byte[],Int32)
ushort ToUInt16(Byte[],Int32)
uint ToUInt32(Byte[],Int32)
ulong ToUInt64(Byte[],Int32)

Returns a 32-bit signed integer converted from four bytes at a specified position in a byte array.

1
public static int ToInt32 (byte[] value, int startIndex);

示例 BitConverter.ToInt32

此示例初始化字节数组,并在计算机体系结构为 ==little-endian==(即首先存储最低有效字节)的情况下==反转数组==,然后调用 ToInt32(Byte[],Int32) 方法以将数组中的四个字节转换为 int。 ToInt32(Byte[],Int32) 的第二个参数指定字节数组的起始索引。

1
2
3
4
5
6
7
8
9
10
byte[] bytes = { 0, 0, 0, 25 };

// If the system architecture is little-endian (that is, little end first),
// reverse the byte array.
if (BitConverter.IsLittleEndian)
Array.Reverse(bytes);

int i = BitConverter.ToInt32(bytes, 0);
Console.WriteLine("int: {0}", i);
// Output: int: 25

示例 BitConverter.GetBytes

在本示例中,将调用 BitConverter 类的 GetBytes(Int32) 方法,将 int 转换为字节数组。

1
2
3
byte[] bytes = BitConverter.GetBytes(201805978);
Console.WriteLine("byte array: " + BitConverter.ToString(bytes));
// Output: byte array: 9A-50-07-0C

高级主题:十六进制字符串与数值类型之间转换

以下示例演示如何执行下列任务:

  • 获取字符串中每个字符的十六进制值。
  • 获取与十六进制字符串中的每个值对应的 char。
  • 将十六进制 string 转换为 int。
  • 将十六进制 string 转换为 float。
  • 将字节数组转换为十六进制 string。> -

示例 解析字符得到16进制值

此示例输出 string 中每个字符的十六进制值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System;
public class Program {
static void Main (string[] args) {

string input = "你好 大世界!Hello World!";
for (int i = 0; i < input.Length; i++) {
// 占位符 方式
//Console.Write ("{0} {1} ", input[i], ((int) input[i]).ToString ("X"));
// 字符串内插 $ 方式
Console.Write ($"{input[i]} {((int)input[i]).ToString("X")} ");
}
System.Console.WriteLine ();
Console.ReadKey ();
}
}
你 4F60 好 597D   20 大 5927 世 4E16 界 754C ! FF01 H 48 e 65 l 6C l 6C o 6F   20 W 57 o 6F r 72 l 6C d 64 ! 21

示例 从16进制值得到对应字符

此示例分析十六进制值的 string 并输出对应于每个十六进制值的字符。 首先,调用 Split(Char[]) 方法以获取每个十六进制值作为数组中的单个 string。 然后,调用 ToInt32(String, Int32)将十六进制值转换为表示为 int 的十进制值。示例中演示了 2 种不同方法,用于获取对应于该字符代码的字符。 第 1 种方法是使用 ConvertFromUtf32(Int32),它将对应于整型参数的字符作为 string 返回。 第 2 种方法是将 int 显式转换为 char。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;
public class Program {
static void Main (string[] args) {

//将指定的 Unicode 码位转换为 UTF-16 编码字符串。
string hexValues = "5927 20 48 65 6C 6C 6F 20 57 6F 72 6C 64 21"; // 大 Hello World!
string[] hexValuesSplit = hexValues.Split(' ');

for (int i = 0; i < hexValuesSplit.Length; i++)
{
// 将 16 进制数值 转换为 整型(10进制)
int value = Convert.ToInt32(hexValuesSplit[i], 16);
// 得到 编码值对应的 字符 | 将指定的 Unicode 码位转换为 UTF-16 编码字符串。
string stringValule = char.ConvertFromUtf32(value);
System.Console.Write(stringValule);
//一句话表示,有点难于解读
//System.Console.Write(char.ConvertFromUtf32(Convert.ToInt32(hexValuesSplit[i],16)));
}

System.Console.WriteLine ();
Console.ReadKey ();
}
}

输出结果

大 Hello World!

数组

可以将同一类型的多个变量存储在一个数组数据结构中。 通过指定数组的元素类型来声明数组。

type[] arrayName;

数组具有以下属性:

  • 数组可以是一维(==Single-Dimesional==)、多维(==Multidimesional==)或交错(==Jagged==)的。
  • 创建数组实例时,将建立纬度数量和每个纬度的长度。这些值在实例的生存期内无法更改。
  • 交错数组是数组的数组,因此其元素为引用类型且被初始化为null
  • 数组从零开始编制索引:包含 n 元素的数组从 0 索引到 n-1
  • 数组元素可以是任何类型,其中包括数组类型。

数组的维数

使用 Rank 属性显示数组的维数。

1
2
3
4
// Declare and initialize an array:
int[,] theArray = new int[5, 10];
// 输出 这是一个 2 维数组。
System.Console.WriteLine("这是一个 {0} 维数组。", theArray.Rank);

一维数组

数组声明 一维

声明五个整数的一维数组,如以下示例所示:

1
int[] array = new int[5];

此数组包含从 array[0]array[4] 的元素。 new 运算符用于创建数组并将数组元素初始化为其默认值。在此示例中,所有数组元素都将被==初始化为零==。

声明字符串数组

1
string[] stringArray = new string[6];

数组初始化 一维

1
2
int[] array1 = new int[] { 1, 3, 5, 7, 9 };
string[] weekDays = new string[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };

声明初始化的缩写(快捷)方式

1
2
int[] array1 = { 1, 3, 5, 7, 9 };
string[] weekDays = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };

可以在不初始化的情况下声明数组变量,但必须使用 new 运算符向此变量分配数组。 例如:

1
2
3
int[] array3;
array3 = new int[] { 1, 3, 5, 7, 9 }; // OK
//array3 = {1, 3, 5, 7, 9}; // Error

多维数组

多为数组的每一维的大小都必须一致。而 ==交错数组== (jagged array)则不需要。

得到多维数组中某一维的长度,不是使用Length属性,而是使用数组的GetLength()示例方法。

1
2
3
bool[,,] cells;
cells = new bool[2,3,3];
System.Console.WriteLine(cells.GetLength(0)); // Displays 2

数组声明 多维

1
2
3
4
// 声明创建一个具有四行两列的二维数组。
int[,] array = new int[4, 2];
// 声明创建一个具有三个维度(4、2 和 3)的数组。
int[, ,] array1 = new int[4, 2, 3];

数组初始化 多维

声明后即可初始化数组,如以下示例所示。

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
// Two-dimensional array.
int[,] array2D = new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
// The same array with dimensions specified.
int[,] array2Da = new int[4, 2] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
// A similar array with string elements.
string[,] array2Db = new string[3, 2] { { "one", "two" }, { "three", "four" },
{ "five", "six" } };

// Three-dimensional array.
int[, ,] array3D = new int[,,] { { { 1, 2, 3 }, { 4, 5, 6 } },
{ { 7, 8, 9 }, { 10, 11, 12 } } };
// The same array with dimensions specified.
int[, ,] array3Da = new int[2, 2, 3] { { { 1, 2, 3 }, { 4, 5, 6 } },
{ { 7, 8, 9 }, { 10, 11, 12 } } };

// Accessing array elements.
System.Console.WriteLine(array2D[0, 0]);
System.Console.WriteLine(array2D[0, 1]);
System.Console.WriteLine(array2D[1, 0]);
System.Console.WriteLine(array2D[1, 1]);
System.Console.WriteLine(array2D[3, 0]);
System.Console.WriteLine(array2Db[1, 0]);
System.Console.WriteLine(array3Da[1, 0, 1]);
System.Console.WriteLine(array3D[1, 1, 2]);

// Getting the total count of elements or the length of a given dimension.
var allLength = array3D.Length;
var total = 1;
for (int i = 0; i < array3D.Rank; i++) {
total *= array3D.GetLength(i);
}
System.Console.WriteLine("{0} equals {1}", allLength, total);

// Output:
// 1
// 2
// 3
// 4
// 7
// three
// 8
// 12
// 12 equals 12

交错数组 jagged array

交错数组是元素为数组的数组。 交错数组元素的维度和大小可以不同。 交错数组有时称为“数组的数组”。

1
2
3
4
5
6
int[][] cells = {
new int[]{1, 0, 2, 0},
new int[]{1, 2, 0},
new int[]{1, 2},
new int[]{1}
};

声明一个具有三个元素的一维数组,其中每个元素都是一维整数数组:

1
int[][] jaggedArray = new int[3][];

必须初始化 jaggedArray 的元素后才可使用它。 可按下方操作初始化元素:

1
2
3
jaggedArray[0] = new int[5];
jaggedArray[1] = new int[4];
jaggedArray[2] = new int[2];

每个元素都是一维整数数组。 第一个元素是由 5 个整数组成的数组,第二个是由 4 个整数组成的数组,而第三个是由 2 个整数组成的数组。

也可使用初始化表达式通过值来填充数组元素,这种情况下不需要数组大小。 例如:

1
2
3
jaggedArray[0] = new int[] { 1, 3, 5, 7, 9 };
jaggedArray[1] = new int[] { 0, 2, 4, 6 };
jaggedArray[2] = new int[] { 11, 22 };

还可在声明数组时将其初始化,如:

1
2
3
4
5
int[][] jaggedArray2 = new int[][] {
new int[] { 1, 3, 5, 7, 9 },
new int[] { 0, 2, 4, 6 },
new int[] { 11, 22 }
};

可以使用下面的缩写形式。 请注意:不能从元素初始化中省略 new 运算符,因为不存在元素的默认初始化:

1
2
3
4
5
int[][] jaggedArray3 = {
new int[] { 1, 3, 5, 7, 9 },
new int[] { 0, 2, 4, 6 },
new int[] { 11, 22 }
};

可以混合使用交错数组和多维数组。 下面声明和初始化一个包含大小不同的三个二维数组元素的一维交错数组。

1
2
3
4
5
6
int[][,] jaggedArray4 = new int[3][,] 
{
new int[,] { {1,3}, {5,7} },
new int[,] { {0,2}, {4,6}, {8,10} },
new int[,] { {11,22}, {99,88}, {0,9} }
};

可以如本例所示访问个别元素,示例显示第一个数组的元素 [1,0] 的值(值为 5):

1
System.Console.Write("{0}", jaggedArray4[0][1, 0]);

方法 Length 返回包含在交错数组中的数组的数目。例如,假定已声明了前一个数组,则下行返回值 3

1
System.Console.WriteLine(jaggedArray4.Length);

隐式类型的数组

可以创建隐式类型化的数组,其中数组实例的类型通过数组初始值设定项中指定的元素来推断。 针对隐式类型化变量的任何规则也适用于隐式类型化数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ImplicitlyTypedArraySample
{
static void Main()
{
var a = new[] { 1, 10, 100, 1000 }; // int[]
var b = new[] { "hello", null, "world" }; // string[]

// single-dimension jagged array
var c = new[]
{
new[]{1,2,3,4},
new[]{5,6,7,8}
};

// jagged array of strings
var d = new[]
{
new[]{"Luca", "Mads", "Luke", "Dinesh"},
new[]{"Karen", "Suma", "Frances"}
};
}
}

在上个示例中,请注意对于隐式类型化数组,初始化语句的左侧没有使用方括号。 另请注意,和一维数组一样,通过使用 new [] 来初始化交错数组。

对数组使用循环 for foreach

一维数组

1
2
3
4
5
6
int[] numbers = { 4, 5, 6, 1, 2, 3, -2, -1, 0 };
foreach (int i in numbers)
{
System.Console.Write("{0} ", i);
}
// Output: 4 5 6 1 2 3 -2 -1 0

多维数组

1
2
3
4
5
6
7
8
9
int[,] numbers2D = new int[3, 2] { { 9, 99 }, { 3, 33 }, { 5, 55 } };
// Or use the short form:
// int[,] numbers2D = { { 9, 99 }, { 3, 33 }, { 5, 55 } };

foreach (int i in numbers2D)
{
System.Console.Write("{0} ", i);
}
// Output: 9 99 3 33 5 55

交错数组

1
2
3
4
5
6
7
8
9
10
11
12
int[][] jaggedArray3 = {
new int[] { 1, 3, 5, 7, 9 },
new int[] { 0, 2, 4, 6 },
new int[] { 11, 22 }
};

foreach (var item in jaggedArray3) {
for (int i = 0; i < item.Length; i++) {
System.Console.Write ("{0} ", item[i]);
}
}
// 输出:1 3 5 7 9 0 2 4 6 11 22

常见数组编码错误

代码中包含双重大括号,hexo无法编译,用转义的话Markdown文件不利阅读,所以采用图片替代。

image

枚举类型

声明定义

enum 关键字用于声明枚举,一种包含一组被称为枚举数列表的已命名常数的不同类型。
通常最好是直接在命名空间内定义枚举,以便命名空间中的所有类都可以同样方便地访问它。 但是,也可能会在类或结构中嵌套枚举。
默认情况下,枚举中每个元素的==基础类型都为 int==。默认情况下,==第一个枚举数具有值 0==,并且每个连续枚举数的值将增加 1。 例如,在以下枚举中, Sat 的值为 0, Sun 的值为 1, Mon 的值为 2,依次类推。

1
enum Days {Sat, Sun, Mon, Tue, Wed, Thu, Fri};

枚举数可以使用初始值设定项来替代默认值,如下面的示例中所示。

1
enum Days {Sat=1, Sun, Mon, Tue, Wed, Thu, Fri};

在此枚举中,强制元素的序列从 1 开始,而不是 0。 但建议包括一个值为 0 的常量。 有关详细信息,请参阅枚举类型

每个枚举类型都有一个基础类型,该基础类型可以是除 char 外的任何整型类型。 枚举元素的默认基础类型是 int。若要声明另一整型的枚举(如 byte),则请在后跟该类型的标识符后使用冒号,如以下示例所示。

1
enum Days : byte {Sat=1, Sun, Mon, Tue, Wed, Thu, Fri};

枚举的已批准类型有 byte、 sbyte、 short、 ushort、 int、 uint、 long或 ulong。有关可能的类型的完整列表,请参阅 enum(C# 参考)。

基础类型指定为每个枚举数分配多少存储空间。 但要将 enum 类型转换为整型,==则必须使用显示转换==。 例如,以下语句通过使用转换将 Sun 转换为 ,从而将枚举数 赋值为 enum int int类型的变量。

1
int x = (int)Days.Sun;

可以为枚举类型的枚举器列表中的元素分配任何值,也==可以使用计算值==,计算因子必须是已经有确定的值的枚举元素,不能包含后面的值未确定的元素:

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
using System;
public class EnumTest
{
enum Day { Sunday=6, Monday=3, Tuesday = Sunday + Monday, Wednesday, Thursday, Friday, Saturday };

static void Main()
{
Console.WriteLine(Day.Sunday);
Console.WriteLine(Day.Friday);
Console.WriteLine((int)Day.Sunday);
Console.WriteLine((int)Day.Monday);
Console.WriteLine((int)Day.Tuesday);
Console.WriteLine((int)Day.Wednesday);
Console.WriteLine((int)Day.Thursday);
Console.WriteLine((int)Day.Friday);
Console.WriteLine((int)Day.Saturday);

Console.ReadKey();
}
}
/* 输出
Sunday
Friday
6
3
9
10
11
12
13
*/

使用 System.Enum 方法来发现和操作枚举值

1
2
3
4
5
6
7
8
9
10
string s = Enum.GetName(typeof(Days), 16);
Console.WriteLine(s);

Console.WriteLine("The values of the Day Enum are:");
foreach (int i in Enum.GetValues(typeof(Days)))
Console.WriteLine(i);

Console.WriteLine("The names of the Day Enum are:");
foreach (string str in Enum.GetNames(typeof(Days)))
Console.WriteLine(str);

输出结果

Thursday
The values of the Day Enum are:
0
1
2
4
8
16
32
64
The names of the Day Enum are:
None
Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday

高级主题:作为位标志的枚举类型

创建==位标志枚举==的方法是,应用 FlagsAttribute 属性并适当定义一些值,以便可以对这些值执行 AND、OR、NOT 和 XOR 按位运算。 在位标志枚举中,包括一个值为零(表示“未设置任何标志”)的命名常量。 如果零值不表示“未设置任何标志”,请勿为标志指定零值。

在以下示例中,定义了名为 Days 枚举的另一个版本。 Days 具有 Flags 属性,且它的每个值都是 2 的若干次幂,指数依次递增。 这样,你就能够创建值为 Days.Tuesday | Days.Thursday 的 Days 变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Flags]
enum Days
{
None = 0x0,
Sunday = 0x1,
Monday = 0x2,
Tuesday = 0x4,
Wednesday = 0x8,
Thursday = 0x10,
Friday = 0x20,
Saturday = 0x40
}
class MyClass
{
Days meetingDays = Days.Tuesday | Days.Thursday;
}

若要在枚举上设置标志,请使用按位 OR 运算符,如以下示例所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Initialize with two flags using bitwise OR.
meetingDays = Days.Tuesday | Days.Thursday;

// Set an additional flag using bitwise OR.
meetingDays = meetingDays | Days.Friday;

Console.WriteLine("Meeting days are {0}", meetingDays);
// Output: Meeting days are Tuesday, Thursday, Friday

// Remove a flag using bitwise XOR.
meetingDays = meetingDays ^ Days.Tuesday;
Console.WriteLine("Meeting days are {0}", meetingDays);
// Output: Meeting days are Thursday, Friday

若要确定是否设置了特定标志,请使用按位 AND 运算,如以下示例所示:

1
2
3
4
// Test value of flags using bitwise AND.
bool test = (meetingDays & Days.Thursday) == Days.Thursday;
Console.WriteLine("Thursday {0} a meeting day.", test == true ? "is" : "is not");
// Output: Thursday is a meeting day.

结尾