# .NET基础
# C#数据类型
C# 是面向对象的强类型高级语言,内置用于存储不同类型数据的内置数据类型。每种数据类型包含特定的取值范围,使用这些数据类型来表示在应用程序中存储的数据。数据类型进一步又被分为:
• 值类型(Value types)
• 引用类型(Reference types)
• 指针类型(Pointer types)
2
3
值类型特点:变量直接存储其值,派生于 System.ValueType
。值类型又细分为整数值类型、浮点类型、简单类型、枚举类型、结构类型、可以为 null
的值类型。
# 一、*值类型(Value types)
值类型变量可以直接分配给一个值。它们是从类 System.ValueType
中派生的。值类型直接包含数据。比如 int
、char
、float
,它们分别存储数字、字母、浮点数。当您声明一个 int
类型时,系统分配内存来存储值。
类型 | 描述 | 范围 | 默认值 |
---|---|---|---|
bool | 布尔值 | True 或 False | False |
byte | 8 位无符号整数 | 0 到 255 | 0 |
char | 16 位 Unicode 字符 | U +0000 到 U +ffff | '\0' |
decimal | 128 位精确的十进制值,28-29 有效位数(浮点型) | (-7.9 x 1028 到 7.9 x 1028) / 100 到 28 | 0.0M |
double | 64 位双精度浮点型 | (+/-)5.0 x 10-324 到 (+/-)1.7 x 10308 | 0.0D |
float | 32 位单精度浮点型 | -3.4 x 1038 到 + 3.4 x 1038 | 0.0F |
int | 32 位有符号整数类型 | -2,147,483,648 到 2,147,483,647 | 0 |
long | 64 位有符号整数类型 | -923,372,036,854,775,808 到 9,223,372,036,854,775,807 | 0L |
sbyte | 8 位有符号整数类型 | -128 到 127 | 0 |
short | 16 位有符号整数类型 | -32,768 到 32,767 | 0 |
uint | 32 位无符号整数类型 | 0 到 4,294,967,295 | 0 |
ulong | 64 位无符号整数类型 | 0 到 18,446,744,073,709,551,615 | 0 |
ushort | 16 位无符号整数类型 | 0 到 65,535 | 0 |
如需得到一个类型或一个变量在特定平台上的准确尺寸,可以使用 sizeof
方法。表达式 sizeof(type)
产生以字节为单位存储对象或类型的存储尺寸。下面举例获取任何机器上 int
类型的存储尺寸:
字节(Byte)是计算机信息技术用于计量存储容量的一种计量单位,也表示一些计算机编程语言中的数据类型和语言字符 [1] 。
一个字节存储8位无符号数,储存的数值范围为0-255。如同字元一样,字节型态的变数只需要用一个位元组(8位元)的内存空间储存 [1] 。
namespace DataTypeApplication
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Size of int: {0}", sizeof(int));
Console.ReadLine();
}
}
}
2
3
4
5
6
7
8
9
10
11
当上面的代码被编译和执行时,它会产生下列结果:
Size of int: 4
# 二、引用类型(Reference types)
引用类型不包含存储在变量中的实际数据,但它们包含对变量的引用。换句话说,它们指的是一个内存位置。使用多个变量时,引用类型可以指向一个内存位置。如果内存位置的数据是由一个变量改变的,其他变量会自动反映这种值的变化。内置的 引用类型有:object
、dynamic
和 string
。
# (1)、对象(Object)类型
对象(Object
)类型 是 C# 通用类型系统(Common Type System - CTS)中所有数据类型的终极基类。Object
是 System.Object
类的别名。所以对象(Object
)类型可以被分配任何其他类型(值类型、引用类型、预定义类型或用户自定义类型)的值。但是,在分配值之前,需要先进行类型转换。当一个值类型转换为对象类型时,则被称为 装箱;另一方面,当一个对象类型转换为值类型时,则被称为 拆箱。
object obj; obj = 100; // 这是装箱
# (2)、动态(Dynamic)类型
您可以存储任何类型的值在动态数据类型变量中。这些变量的类型检查是在运行时发生的。声明动态类型的语法:
dynamic <variable_name> = value;
例如:
dynamic d = 20;
动态类型与对象类型相似,但是对象类型变量的类型检查是在编译时发生的,而动态类型变量的类型检查是在运行时发生的。
# (3)、字符串(String)类型
字符串(String
)类型 允许您给变量分配任何字符串值。字符串(String
)类型是 System.String
类的别名。它是从对象(Object
)类型派生的。字符串(String
)类型的值可以通过两种形式进行分配:引号和 @引号。
例如:
String str = "runoob.com";
一个 @引号字符串:
@"runoob.com";
C# string
字符串的前面可以加 @(称作"逐字字符串")将转义字符(\)当作普通字符对待,比如:
string str = @"C:\Windows";
等价于:
string str = "C:\\Windows";
@ 字符串中可以任意换行,换行符及缩进空格都计算在字符串长度之内。
string str = @"<script type=""text/javascript""> <!-- --> </script>";
用户自定义引用类型有:class
、interface
或 delegate
。我们将在以后的章节中讨论这些类型。
# 三、*指针类型(Pointer types)
指针类型变量存储另一种类型的内存地址。C# 中的指针与 C 或 C++ 中的指针有相同的功能。
声明指针类型的语法:
type* identifier;
例如:
复制代码 char* cptr; int* iptr;
# 四、枚举
enum
是值类型数据类型。枚举用于声明命名整数常量的列表。可以直接在命名空间,类或结构中使用 enum
关键字定义。
- 枚举用于为每个常量指定一个名称,以便可以使用其名称引用常量整数默认情况下,枚举的第一个成员的值为 0,每个连续的枚举成员的值增加 1
- 枚举可以包括数字数据类型的命名常量,例如
byte
,sbyte
,short
,ushort
,int
,uint
,long
或ulong
- 枚举不能与字符串类型一起使用
Enum
是一个抽象类,包含用于枚举的静态帮助器方法
Enum method | Description |
---|---|
Format | 将指定的枚举类型值转换为指定的字符串格式 |
GetName | 返回指定枚举的指定值的常量的名称 |
GetNames | 返回指定枚举的所有常量的字符串名称数组 |
GetValues | 返回指定枚举的所有常量值的数组 |
object Parse(type, string) | 将一个或多个枚举常量的名称或数值的字符串表示形式转换为等效的枚举对象 |
bool TryParse(string, out TEnum) | 将一个或多个枚举常量的名称或数值的字符串表示形式转换为等效的枚举对象,返回值表示转换是否成功 |
# c#变量
编译器需要用某个初始值对变量进行初始化之后才能在操作中使用该变量。
在这里,data_type
必须是一个有效的 C# 数据类型,可以是 char
、int
、float
、double
或其他用户自定义的数据类型。variablename
可以由一个或多个用逗号分隔的标识符名称组成。
// 语法
<datatype><variablename>=<value>;
// 示例
string name = "wang";
// 同时声明多个
string name1,name2 = "wang";
int d = 3, f = 5; /* 初始化 d 和 f. */
byte z = 22; /* 初始化 z. */
double pi = 3.14159; /* 声明 pi 的近似值 */
char x = 'x'; /* 变量 x 的值为 'x' */
2
3
4
5
6
7
8
9
10
11
12
13
# 接受来自用户的值
System
命名空间中的 Console
类提供了一个函数 ReadLine()
,用于接收来自用户的输入,并把它存储到一个变量中。
例如:
int num;
num = Convert.ToInt32(Console.ReadLine());
Console.WriteLine(num);
2
3
注意:
- 变量是类或结构中的字段,如果没有显式初始化,创建这些变量时,默认值就是类型默认值
- 方法的局部变量必须在代码中显式初始化才能在语句中使用
- 在C#中实例化一个引用对象需要使用
new
关键字把该引用指向存储在堆上的一个对象
# c# 运算符
运算符是一种告诉编译器执行特定的数学或逻辑操作的符号。C# 有丰富的内置运算符,分类如下:
• 算术运算符
• 关系运算符
• 逻辑运算符
• 位运算符
• 赋值运算符
• 其他运算符
2
3
4
5
6
# 一、算术运算符
下表显示了 C# 支持的所有算术运算符。假设变量 A 的值为 10,变量 B 的值为 20,则:
运算符 | 描述 | 实例 |
---|---|---|
+ | 把两个操作数相加 | A + B 将得到 30 |
- | 从第一个操作数中减去第二个操作数 | A - B 将得到 -10 |
* | 把两个操作数相乘 | A B 将得到 200 |
/ | 分子除以分母 | B / A 将得到 2 |
% | 取模运算符,整除后的余数 | B % A 将得到 0 |
++ | 自增运算符,整数值增加 1 | A++ 将得到 11 |
— | 自减运算符,整数值减少 1 | A— 将得到 9 |
8 % 4 2余0 0
9 % 4 2余1 1
9 % 5 1余4 4
11 % 5 2余1 1
8 % 5 1余3 3
得到的结果为做除法后的余数,%是求余运算符
int a = 21;
int b = 10;
int c;
c = a + b;
Console.WriteLine("Line 1 - c 的值是 {0}", c);
c = a - b;
Console.WriteLine("Line 2 - c 的值是 {0}", c);
c = a * b;
Console.WriteLine("Line 3 - c 的值是 {0}", c);
c = a / b;
Console.WriteLine("Line 4 - c 的值是 {0}", c);
c = a % b;
Console.WriteLine("Line 5 - c 的值是 {0}", c);
// ++a 先进行自增运算再赋值
c = ++a;
Console.WriteLine("Line 6 - c 的值是 {0}", c);
// 此时 a 的值为 22
// --a 先进行自减运算再赋值
c = --a;
Console.WriteLine("Line 7 - c 的值是 {0}", c);
Console.ReadLine();
Line 1 - c 的值是 31
Line 2 - c 的值是 11
Line 3 - c 的值是 210
Line 4 - c 的值是 2
Line 5 - c 的值是 1
Line 6 - c 的值是 22
Line 7 - c 的值是 21
• c = a++: 先将 a 赋值给 c,再对 a 进行自增运算。
• c = ++a: 先将 a 进行自增运算,再将 a 赋值给 c 。
• c = a--: 先将 a 赋值给 c,再对 a 进行自减运算。
• c = --a: 先将 a 进行自减运算,再将 a 赋值给 c 。
示例:
int a = 1;
int b;
// a++ **先赋值再进行自增运算**
b = a++;
Console.WriteLine("a = {0}", a);
Console.WriteLine("b = {0}", b);
Console.ReadLine();
// ++a **先进行自增运算再赋值**
a = 1; // 重新初始化 a
b = ++a;
Console.WriteLine("a = {0}", a);
Console.WriteLine("b = {0}", b);
Console.ReadLine();
// a-- 先赋值再进行自减运算
a = 1; // 重新初始化 a
b= a--;
Console.WriteLine("a = {0}", a);
Console.WriteLine("b = {0}", b);
Console.ReadLine();
// --a 先进行自减运算再赋值
a = 1; // 重新初始化 a
b= --a;
Console.WriteLine("a = {0}", a);
Console.WriteLine("b = {0}", b);
Console.ReadLine();
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
# 二、关系运算符
下表显示了 C# 支持的所有关系运算符。假设变量 A 的值为 10,变量 B 的值为 20,则:
运算符 | 描述 | 实例 |
---|---|---|
== | 检查两个操作数的值是否相等,如果相等则条件为真。 | (A == B) 不为真。 |
!= | 检查两个操作数的值是否相等,如果不相等则条件为真。 | (A != B) 为真。 |
> | 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 | (A > B) 不为真。 |
< | 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 | (A < B) 为真。 |
>= | 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 | (A >= B) 不为真。 |
<= | 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。 | (A <= B) 为真。 |
# 三、逻辑运算符
下表显示了 C# 支持的所有逻辑运算符。假设变量 A 为布尔值 true
,变量 B 为布尔值 false
,则:
运算符 | 描述 | 实例 |
---|---|---|
&& | 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 | (A && B) 为假。 |
II | 称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。 | (A II B) 为真。 |
! | 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。 | !(A && B) 为真。 |
# 四、赋值运算符
下表列出了 C# 支持的赋值运算符:
运算符 | 描述 | 实例 |
---|---|---|
= | 简单的赋值运算符,把右边操作数的值赋给左边操作数 | C = A + B 将把 A + B 的值赋给 C |
+= | 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 | C += A 相当于 C = C + A |
-= | 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 | C -= A 相当于 C = C - A |
*= | 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 | C = A 相当于 C = C A |
/= | 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 | C /= A 相当于 C = C / A |
%= | 求模且赋值运算符,求两个操作数的模赋值给左边操作数 | C %= A 相当于 C = C % A |
<<= | 左移且赋值运算符 | C <<= 2 等同于 C = C << 2 |
>>= | 右移且赋值运算符 | C >>= 2 等同于 C = C >> 2 |
&= | 按位与且赋值运算符 | C &= 2 等同于 C = C & 2 |
^= | 按位异或且赋值运算符 | C ^= 2 等同于 C = C ^ 2 |
I= | 按位或且赋值运算符 | C I= 2 等同于 C = C I 2 |
int a = 21;
int c;
c = a;
Console.WriteLine("Line 1 - = c 的值 = {0}", c);
c += a;
Console.WriteLine("Line 2 - += c 的值 = {0}", c);
c -= a;
Console.WriteLine("Line 3 - -= c 的值 = {0}", c);
c *= a;
Console.WriteLine("Line 4 - *= c 的值 = {0}", c);
c /= a;
Console.WriteLine("Line 5 - /= c 的值 = {0}", c);
c = 200;
c %= a;
Console.WriteLine("Line 6 - %= c 的值 = {0}", c);
c <<= 2;
Console.WriteLine("Line 7 - <<= c 的值 = {0}", c);
c >>= 2;
Console.WriteLine("Line 8 - >>= c 的值 = {0}", c);
c &= 2;
Console.WriteLine("Line 9 - &= c 的值 = {0}", c);
c ^= 2;
Console.WriteLine("Line 10 - ^= c 的值 = {0}", c);
c |= 2;
Console.WriteLine("Line 11 - |= c 的值 = {0}", c);
Console.ReadLine();
Line 1 - = c 的值 = 21
Line 2 - += c 的值 = 42
Line 3 - -= c 的值 = 21
Line 4 - *= c 的值 = 441
Line 5 - /= c 的值 = 21
Line 6 - %= c 的值 = 11
Line 7 - <<= c 的值 = 44
Line 8 - >>= c 的值 = 11
Line 9 - &= c 的值 = 2
Line 10 - ^= c 的值 = 0
Line 11 - |= c 的值 = 2
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
# 六、其他运算符
下表列出了 C# 支持的其他一些重要的运算符,包括 sizeof
、typeof
和 ? :
。
运算符 | 描述 | 实例 |
---|---|---|
sizeof() | 返回数据类型的大小。 | sizeof(int) ,将返回 4. |
typeof() | 返回 class 的类型。 | typeof(StreamReader); |
& | 返回变量的地址。 | &a; 将得到变量的实际地址。 |
变量的指针。 | a; 将指向一个变量。 | |
? : | 条件表达式 | 如果条件为真 ? 则为 X : 否则为 Y |
is | 判断对象是否为某一类型。 | If( Ford is Car) // 检查 Ford 是否是 Car 类的一个对象。 |
as | 强制转换,即使转换失败也不会抛出异常。 | Object obj = new StringReader("Hello");``StringReader r = obj as StringReader; |
? | 可空类型和运算符 | int? a = null; int? c = a + 4; //c=null |
?? | 空合并运算符 | 空合并运算符 ?? 提供了快捷方式处理可空类型和引用类型时表示 null 可能的值。如果第一个操作数不是null ,值就等于第一个操作数的值/如果第一个操作数是null ,值就等于第二个操作数的值 |
checked/unchecked | 如果把代码块标记为 checked , CLR 就会执行栈溢出检测,如果要禁止栈溢出,则可以把代码标记 unchecked 。 |
/* sizeof 运算符的实例 */
Console.WriteLine("int 的大小是 {0}", sizeof(int));
Console.WriteLine("short 的大小是 {0}", sizeof(short));
Console.WriteLine("double 的大小是 {0}", sizeof(double));
/* 三元运算符符的实例 */
int a, b;
a = 10;
b = (a == 1) ? 20 : 30;
Console.WriteLine("b 的值是 {0}", b);
b = (a == 10) ? 20 : 30;
Console.WriteLine("b 的值是 {0}", b);
// 空合并运算符
int? a = null;
int b;
b = a ?? 10;//第一个操作数是null,值为第二个操作数.10
a = 3;
b = a ?? 10;//第一个操作数不是null,值为第一个操作数.3
// checked/unchecked
//byte类型最大取值255
byte a = 255;
checked
{
a++;
}
//这里如果不加checed.++后输出0(不会抛异常,但会丢失数据,溢出的位会被舍弃,所以值为0),加上后会抛出栈溢出异常
Console.WriteLine(a);
Console.ReadLine();
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
int 的大小是 4
short 的大小是 2
double 的大小是 8
b 的值是 30
b 的值是 20
2
3
4
5
# 七、C# 中的运算符优先级
运算符的优先级确定表达式中项的组合。这会影响到一个表达式如何计算。某些运算符比其他运算符有更高的优先级,例如,乘除运算符具有比加减运算符更高的优先级。
例如 x = 7 + 3 *2*
,在这里,x 被赋值为 13,而不是 20,因为运算符 具有比 + 更高的优先级,所以首先计算乘法 3*2,然后再加上 7。
下表将按运算符优先级从高到低列出各个运算符,具有较高优先级的运算符出现在表格的上面,具有较低优先级的运算符出现在表格的下面。在表达式中,较高优先级的运算符会优先被计算。
类别 | 运算符 | 结合性 |
---|---|---|
后缀 | () [] -> . ++ - - | 从左到右 |
一元 | + - ! ~ ++ - - (type) *& sizeof* | 从右到左 |
乘除 | / % | 从左到右 |
加减 | + - | 从左到右 |
移位 | << >> | 从左到右 |
关系 | < <= > >= | 从左到右 |
相等 | == != | 从左到右 |
位与 AND | & | 从左到右 |
位异或 XOR | ^ | 从左到右 |
位或 OR | I | 从左到右 |
逻辑与 AND | && | 从左到右 |
逻辑或 OR | II | 从左到右 |
条件 | ?: | 从右到左 |
赋值 | = += -= *= /= %=>>= <<= &= ^= I= | 从右到左 |
逗号 | , | 从左到右 |
示例:
int a = 20;
int b = 10;
int c = 15;
int d = 5;
int e;
e = (a + b) * c / d; // ( 30 * 15 ) / 5
Console.WriteLine("(a + b) * c / d 的值是 {0}", e);
e = ((a + b) * c) / d; // (30 * 15 ) / 5
Console.WriteLine("((a + b) * c) / d 的值是 {0}", e);
e = (a + b) * (c / d); // (30) * (15/5)
Console.WriteLine("(a + b) * (c / d) 的值是 {0}", e);
e = a + (b * c) / d; // 20 + (150/5)
Console.WriteLine("a + (b * c) / d 的值是 {0}", e);
Console.ReadLine();
2
3
4
5
6
7
8
9
10
11
12
13
14
# 八、*预处理器指令
#region/#endregion
指令用于把一段代码标记为有给定名称的一个块define/#undef
结合#if/#elif/endif
实现条件编译
#define debug
using System;
namespace CSharp.Study.Test
{
class Program
{
static void Main(string[] args)
{
#if debug
Console.WriteLine("debug");
#else
Console.WriteLine("other");
#endif
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# c#分支语句
分支结构要求程序员指定一个或多个要评估或测试的条件,以及条件为真时要执行的语句(必需的)和条件为假时要执行的语句(可选的)。下面是大多数编程语言中典型的分支结构的一般形式:
分支语句大致有if
else
switch
变量作用域
变量作用域指:可以访问该变量的代码区域
注意:
- 只要类在某个作用域内,其字段(也称为成员变量)也在该作用域内
- 局部变量存在于表示声明该变量的块语句或方法结束的右花括号之前的作用域内
- 在
for
、while
if
else
switch
或类似语句中声明的局部变量存在于该循环体内
# 一、if…else 语句(可以延续if else 使用 else if则须再加判断)
一个 if
语句 后可跟一个可选的 else
语句,else
语句在布尔表达式为假时执行。
if(boolean_expression)
{
/* 如果布尔表达式为真将执行的语句 */
}
else if((boolean_expression)
{
/* 如果布尔表达式为真将执行的语句 */
}
else{
/* 如果布尔表达式为假将执行的语句 */
}
2
3
4
5
6
7
8
9
10
11
# 二、switch 语句
一个 switch
语句允许测试一个变量等于多个值时的情况。每个值称为一个 case
,且被测试的变量会对每个 switch case
进行检查。
/* 局部变量定义 */ char grade = 'B'; switch (grade) { case 'A': Console.WriteLine("很棒!"); break; case 'B': case 'C': Console.WriteLine("做得好"); break; case 'D': Console.WriteLine("您通过了"); break; case 'F': Console.WriteLine("最好再试一下"); break; default: Console.WriteLine("无效的成绩"); break; } Console.WriteLine("您的成绩是 {0}", grade); Console.ReadLine();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
switch 语句必须遵循下面的规则:
switch
语句中的expression
必须是一个整型或枚举类型,或者是一个class
类型,其中class
有一个单一的转换函数将其转换为整型或枚举类型。- 在一个
switch
中可以有任意数量的case
语句。每个case
后跟一个要比较的值和一个冒号。 case
的constant-expression
必须与switch
中的变量具有相同的数据类型,且必须是一个常量。- 当被测试的变量等于
case
中的常量时,case
后跟的语句将被执行,直到遇到break
语句为止。 - 当遇到
break
语句时,switch
终止,控制流将跳转到switch
语句后的下一行。 - 不是每一个
case
都需要包含break
。如果case
语句为空,则可以不包含break
,控制流将会 继续 后续的case
,直到遇到break
为止。 - C# 不允许从一个开关部分继续执行到下一个开关部分。如果
case
语句中有处理语句,则必须包含break
或其他跳转语句。 - 一个
switch
语句可以有一个可选的default case
,出现在switch
的结尾。default case
可用于在上面所有case
都不为真时执行一个任务。default case
中的break
语句不是必需的。 - C# 不支持从一个
case
标签显式贯穿到另一个case
标签。如果要使 C# 支持从一个case
标签显式贯穿到另一个case
标签,可以使用goto
一个switch-case
或goto default
。
# 三、?:三目运算符
我们已经在前面的章节中讲解了 条件运算符 ? :
,可以用来替代 if…else
语句。它的一般形式如下:
Exp1 ? Exp2 : Exp3;
其中,Exp1
、Exp2
和 Exp3
是表达式。请注意,冒号的使用和位置。
?
表达式的值是由 Exp1
决定的。如果 Exp1
为真,则计算 Exp2
的值,结果即为整个 ?
表达式的值。如果 Exp1
为假,则计算 Exp3
的值,结果即为整个 ?
表达式的值。
# c#循环语句
# 一、while 循环
只要给定的条件为真,C# 中的 while
循环语句会重复执行一个目标语句。
在这里,Console.WriteLine("a 的值: {0}", a);
可以是一个单独的语句,也可以是几个语句组成的代码块。a < 20
可以是任意的表达式,当为任意非零值时都为真。当条件为真时执行循环。
当条件为假时,程序流将继续执行紧接着循环的下一条语句。
在这里,while
循环的关键点是循环可能一次都不会执行。当条件被测试且结果为假时,会跳过循环主体,直接执行紧接着 while
循环的下一条语句。
/* 局部变量定义 */
int a = 10;
/* while 循环执行 */
while (a < 20)// 也就是说a=20或a>20则不会执行循环
{
Console.WriteLine("a 的值: {0}", a);
a++;
}
Console.ReadLine();
因为是a++
所以执行结果是
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 二、for /foreach 循环
下面是 for 循环的控制流:
int a = 10
会首先被执行,且只会执行一次。这一步允许您声明并初始化任何循环控制变量。您也可以不在这里写任何语句,只要有一个分号出现即可。- 接下来,会判断
a < 20
。如果为真,则执行循环主体。如果为假,则不执行循环主体,且控制流会跳转到紧接着 for 循环的下一条语句。 - 在执行完 for 循环主体后,控制流会跳回上面的
a = a + 1
语句。该语句允许您更新循环控制变量。该语句可以留空,只要在条件后有一个分号出现即可。 - 条件再次被判断。如果为真,则执行循环,这个过程会不断重复(循环主体,然后增加步值,再然后重新判断条件)。在条件变为假时,for 循环终止。
/* for 循环执行 */
for (int a = 10; a < 20; a = a + 1)
{
Console.WriteLine("a 的值: {0}", a);
}
Console.ReadLine();
2
3
4
5
6
# foreach
C# 也支持 foreach
循环,使用foreach
可以迭代数组或者一个集合对象。
以下实例有三个部分:
- 通过
foreach
循环输出整型数组中的元素。 - 通过
for
循环输出整型数组中的元素。 foreach
循环设置数组元素的计算器。
int[] fibarray = new int[] { 0, 1, 1, 2, 3, 5, 8, 13 };
foreach (int element in fibarray)
{
System.Console.WriteLine(element);
}
System.Console.WriteLine();
// 类似 foreach 循环
for (int i = 0; i < fibarray.Length; i++)
{
System.Console.WriteLine(fibarray[i]);
}
System.Console.WriteLine();
// 设置集合中元素的计算器
int count = 0;
foreach (int element in fibarray)
{
count += 1;
System.Console.WriteLine("Element #{0}: {1}", count, element);
}
System.Console.WriteLine("Number of elements in the array: {0}", count);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 三、do…while 循环
不像 for
和 while
循环,它们是在循环头部测试循环条件。do…while
循环是在循环的尾部检查它的条件。
do…whil
e 循环与 while
循环类似,但是 do…while
循环会确保至少执行一次循环。
请注意,条件表达式出现在循环的尾部,所以循环中的 Console.WriteLine("a 的值: {0}", a); a = a + 1;
会在条件被测试之前至少执行一次。
如果条件为真,控制流会跳转回上面的 do
,然后重新执行循环中的 Console.WriteLine("a 的值: {0}", a); a = a + 1;
。这个过程会不断重复,直到给定条件变为假为止。
/* 局部变量定义 */
int a = 10;
/* do 循环执行 */
do
{
Console.WriteLine("a 的值: {0}", a);
a = a + 1;
} while (a < 20);// 条件
Console.ReadLine();
2
3
4
5
6
7
8
9
# 四、循环控制语句
循环控制语句更改执行的正常序列。当执行离开一个范围时,所有在该范围中创建的自动对象都会被销毁。
C# 提供了下列的控制语句:
break
语句。终止loop
或switch
语句,程序流将继续执行紧接着loop
或switch
的下一条语句。continue
语句。引起循环跳过主体的剩余部分,立即重新开始测试条件。
# break语句
C# 中 break 语句有以下两种用法:
- 当
break
语句出现在一个循环内时,循环会立即终止,且程序流将继续执行紧接着循环的下一条语句。 - 它可用于终止
switch
语句中的一个case
。
如果您使用的是嵌套循环(即一个循环内嵌套另一个循环),break 语句会停止执行最内层的循环,然后开始执行该块之后的下一行代码。
/* 局部变量定义 */
int a = 10;
/* while 循环执行 */
while (a < 20)
{
Console.WriteLine("a 的值: {0}", a);
a++;
if (a > 15)
{
/* 使用 break 语句终止 loop */
break;
}
}
Console.ReadLine();
2
3
4
5
6
7
8
9
10
11
12
13
14
# continue 语句
C# 中的 continue
语句有点像 break
语句。但它不是强迫终止,continue
会跳过当前循环中的代码,强迫开始下一次循环。对于 for
循环,continue
语句会导致执行条件测试和循环增量部分。对于 while
和 do…while
循环,continue
语句会导致程序控制回到条件测试上。
/* 局部变量定义 */
int a = 10;
/* do 循环执行 */
do
{
if (a == 15)
{
/* 跳过迭代 */
a = a + 1;
continue;
}
Console.WriteLine("a 的值: {0}", a);
a++;
} while (a < 20);
Console.ReadLine();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# c# *访问修饰符
封装 被定义为"把一个或多个项目封闭在一个物理的或者逻辑的包中"。在面向对象程序设计方法论中,封装是为了防止对实现细节的访问。
抽象和封装是面向对象程序设计的相关特性。抽象允许相关信息可视化,封装则使开发者实现所需级别的抽象。
C# 封装根据具体的需要,设置使用者的访问权限,并通过 访问修饰符 来实现。
一个 访问修饰符 定义了一个类成员的范围和可见性。C# 支持的访问修饰符如下所示:
- Public:所有对象都可以访问;
- Private:对象本身在对象内部可以访问;
- Protected:只有该类对象及其子类对象可以访问
- Internal:同一个程序集的对象可以访问;
- Protected internal:该程序集内的派生类访问,是protected和internal的交集;
# 一、Public 访问修饰符
Public
访问修饰符允许一个类将其成员变量和成员函数暴露给其他的函数和对象。任何公有成员可以被外部的类访问。
class Rectangle
{
//成员变量
public double length;
public double width;
public double GetArea()
{
return length * width;
}
public void Display()
{
Console.WriteLine("长度: {0}", length);
Console.WriteLine("宽度: {0}", width);
Console.WriteLine("面积: {0}", GetArea());
}
}// Rectangle 结束
class ExecuteRectangle
{
static void Main(string[] args)
{
Rectangle r = new Rectangle();
r.length = 4.5;
r.width = 3.5;
r.Display();
Console.ReadLine();
}
}
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
在上面的实例中,成员变量 length
和 width
被声明为 public
,所以它们可以被函数 Main()
使用 Rectangle
类的实例 r
访问。
成员函数 Display()
和 GetArea()
可以直接访问这些变量。
成员函数 Display()
也被声明为 public
,所以它也能被 Main()
使用 Rectangle
类的实例 r
访问。
# 二、Private 访问修饰符
Private
访问修饰符允许一个类将其成员变量和成员函数对其他的函数和对象进行隐藏。只有同一个类中的函数可以访问它的私有成员。即使是类的实例也不能访问它的私有成员。
//成员变量
private double length;
private double width;
public void Acceptdetails()
{
Console.WriteLine("请输入长度:");
length = Convert.ToDouble(Console.ReadLine());
Console.WriteLine("请输入宽度:");
width = Convert.ToDouble(Console.ReadLine());
}
public double GetArea()
{
return length * width;
}
public void Display()
{
Console.WriteLine("长度: {0}", length);
Console.WriteLine("宽度: {0}", width);
Console.WriteLine("面积: {0}", GetArea());
}
}//end class Rectangle
class ExecuteRectangle
{
static void Main(string[] args)
{
Rectangle r = new Rectangle();
r.Acceptdetails();
r.Display();
Console.ReadLine();
}
}
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
在上面的实例中,成员变量 length
和 width
被声明为 private
,所以它们不能被函数 Main()
访问。
成员函数 AcceptDetails()
和 Display()
可以访问这些变量。
由于成员函数 AcceptDetails()
和 Display()
被声明为 public
,所以它们可以被 Main()
使用 Rectangle
类的实例 r
访问。
# 三、Protected 访问修饰符
Protected
访问修饰符允许子类访问它的基类的成员变量和成员函数。这样有助于实现继承。我们将在继承的章节详细讨论这个。更详细地讨论这个。
# 四、Internal 访问修饰符
Internal
访问说明符允许一个类将其成员变量和成员函数暴露给当前程序中的其他函数和对象。换句话说,带有 internal
访问修饰符的任何成员可以被定义在该成员所定义的应用程序内的任何类或方法访问。
class Rectangle
{
//成员变量
internal double length;
internal double width;
double GetArea()
{
return length * width;
}
public void Display()
{
Console.WriteLine("长度: {0}", length);
Console.WriteLine("宽度: {0}", width);
Console.WriteLine("面积: {0}", GetArea());
}
}//end class Rectangle
class ExecuteRectangle
{
static void Main(string[] args)
{
Rectangle r = new Rectangle();
r.length = 4.5;
r.width = 3.5;
r.Display();
Console.ReadLine();
}
}
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
在上面的实例中,请注意成员函数 GetArea()
声明的时候不带有任何访问修饰符。如果没有指定访问修饰符,则使用类成员的默认访问修饰符,即为 private
。
# 五、Protected Internal 访问修饰符
Protected Internal
访问修饰符允许在本类,派生类或者包含该类的程序集中访问。这也被用于实现继承。
# c#方法
当定义一个方法时,从根本上说是在声明它的结构的元素。在 C# 中,定义方法的语法如下:
<Access Specifier> <Return Type> <Method Name>(Parameter List) { Method Body }
下面是方法的各个元素:
- Access Specifier:访问修饰符,这个决定了变量或方法对于另一个类的可见性。
- Return type:返回类型,一个方法可以返回一个值。返回类型是方法返回的值的数据类型。如果方法不返回任何值,则返回类型为 void。
- Method name:方法名称,是一个唯一的标识符,且是大小写敏感的。它不能与类中声明的其他标识符相同。
- Parameter list:参数列表,使用圆括号括起来,该参数是用来传递和接收方法的数据。参数列表是指方法的参数类型、顺序和数量。参数是可选的,也就是说,一个方法可能不包含参数。
- Method body:方法主体,包含了完成任务所需的指令集。
# 一、创建方法
public int FindMax(int num1, int num2)
{
/* 局部变量声明 */
int result;
if (num1 > num2)
result = num1;
else
result = num2;
return result;
}
2
3
4
5
6
7
8
9
10
# 二、C# 中调用方法
public int FindMax(int num1, int num2)
{
/* 局部变量声明 */
int result;
if (num1 > num2)
result = num1;
else
result = num2;
return result;
}
static void Main(string[] args)
{
/* 局部变量定义 */
int a = 100;
int b = 200;
int ret;
NumberManipulator n = new NumberManipulator();
//调用 FindMax 方法
ret = n.FindMax(a, b);
Console.WriteLine("最大值是: {0}", ret );
Console.ReadLine();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 三、递归方法调用
一个方法可以自我调用。这就是所谓的 递归。下面的实例使用递归函数计算一个数的阶乘:
using System;
namespace CalculatorApplication
{
class NumberManipulator
{
public int factorial(int num)
{
/* 局部变量定义 */
int result;
if (num == 1)
{
return 1;
}
else
{
result = factorial(num - 1) * num;
return result;
}
}
static void Main(string[] args)
{
NumberManipulator n = new NumberManipulator();
//调用 factorial 方法
Console.WriteLine("6 的阶乘是: {0}", n.factorial(6));
Console.WriteLine("7 的阶乘是: {0}", n.factorial(7));
Console.WriteLine("8 的阶乘是: {0}", n.factorial(8));
Console.ReadLine();
}
}
}
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
当上面的代码被编译和执行时,它会产生下列结果:
6 的阶乘是: 720 7 的阶乘是: 5040 8 的阶乘是: 40320
计算过程:
1 2 6 24 120 120 * 6 6 的阶乘是: 720 1 2 6 24 120 720 720 * 7 7 的阶乘是: 5040 1 2 6 24 120 720 5040 5040*8 8 的阶乘是: 40320
# 四、*参数传递
当调用带有参数的方法时,您需要向方法传递参数。在 C# 中,有三种向方法传递参数的方式:
方式 | 描述 |
---|---|
值参数 | 这种方式复制参数的实际值给函数的形式参数,实参和形参使用的是两个不同内存中的值。在这种情况下,当形参的值发生改变时,不会影响实参的值,从而保证了实参数据的安全。 |
引用参数 | 这种方式复制参数的内存位置的引用给形式参数。这意味着,**当形参的值发生改变时,同时也改变实参的值。**ref 初始化必须有值 |
输出参数 | 这种方式可以返回多个值。out输出参数 不管是否传递这个参数都会从清0 |
# 按值传递参数
这是参数传递的默认方式。在这种方式下,当调用一个方法时,会为每个值参数创建一个新的存储位置。实际参数的值会复制给形参,实参和形参使用的是两个不同内存中的值。所以,当形参的值发生改变时,不会影响实参的值,从而保证了实参数据的安全。下面的实例演示了这个概念:
using System;
namespace CalculatorApplication
{
class NumberManipulator
{
public void swap(int x, int y)
{
int temp;
temp = x; /* 保存 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 temp 赋值给 y */
}
static void Main(string[] args)
{
NumberManipulator n = new NumberManipulator();
/* 局部变量定义 */
int a = 100;
int b = 200;
Console.WriteLine("在交换之前,a 的值: {0}", a);
Console.WriteLine("在交换之前,b 的值: {0}", b);
/* 调用函数来交换值 */
n.swap(a, b);
Console.WriteLine("在交换之后,a 的值: {0}", a);
Console.WriteLine("在交换之后,b 的值: {0}", b);
Console.ReadLine();
}
}
}
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;
namespace CalculatorApplication
{
class NumberManipulator
{
public void swap(ref int x, ref int y)
{
int temp;
temp = x; /* 保存 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 temp 赋值给 y */
}
static void Main(string[] args)
{
NumberManipulator n = new NumberManipulator();
/* 局部变量定义 */
int a = 100;
int b = 200;
Console.WriteLine("在交换之前,a 的值: {0}", a);
Console.WriteLine("在交换之前,b 的值: {0}", b);
/* 调用函数来交换值 */
n.swap(ref a, ref b);
Console.WriteLine("在交换之后,a 的值: {0}", a);
Console.WriteLine("在交换之后,b 的值: {0}", b);
Console.ReadLine();
}
}
}
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
# 按输出传递参数
return
语句可用于只从函数中返回一个值。但是,可以使用 输出参数 来从函数中返回两个值。输出参数会把方法输出的数据赋给自己,其他方面与引用参数相似。
using System;
namespace CalculatorApplication
{
class NumberManipulator
{
public void getValue(out int x )
{
int temp = 5;
x = temp;
}
static void Main(string[] args)
{
NumberManipulator n = new NumberManipulator();
/* 局部变量定义 */
int a = 100;
Console.WriteLine("在方法调用之前,a 的值: {0}", a);
/* 调用函数来获取值 */
n.getValue(out a);
Console.WriteLine("在方法调用之后,a 的值: {0}", a);
Console.ReadLine();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
提供给输出参数的变量不需要赋值。当需要从一个参数没有指定初始值的方法中返回值时,输出参数特别有用。请看下面的实例,来理解这一点:
using System;
namespace CalculatorApplication
{
class NumberManipulator
{
public void getValues(out int x, out int y )
{
Console.WriteLine("请输入第一个值: ");
x = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("请输入第二个值: ");
y = Convert.ToInt32(Console.ReadLine());
}
static void Main(string[] args)
{
NumberManipulator n = new NumberManipulator();
/* 局部变量定义 */
int a , b;
/* 调用函数来获取值 */
n.getValues(out a, out b);
Console.WriteLine("在方法调用之后,a 的值: {0}", a);
Console.WriteLine("在方法调用之后,b 的值: {0}", b);
Console.ReadLine();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# ORM
object Realation Maping 数据库关系映射工具
其实是为了关系性数据库和C# 语言的映射 让开发者用对象操作关系数据库
ORM 就是将C#代码 转换成sql语句来操作数据
EFCore 官方推荐 尽量屏蔽底层数据库差异(模型驱动)(约定大于配置)
Dapper 数据库驱动
EFCore 与EF 差异
EF 有DBFirst、ModelFirst 、CodeFrist
EFCore 不支持模型优先 推荐使用代码优先
EF不会再有新的特性增加 未来.net 有ORM的更新都会添加到EFCore
EFCore 是对于底层ADO.NETCore的封装,因此ADO.NETCore支持的数据库不一定被EFCore支持
概念:Migration 数据库迁移
面向对象的ORM开发种,数据库不是程序员手动创建的而是由Migration 工具生成的。关系数据库只是盛放模型数据的一个媒介而已,理想状态下程序员不需要关心数据库的操作
根据对象的定义变化,自动更新数据库中的表以及表结构的操作叫做Migration (迁移)
迁移可以分为多步(项目进化),也可以回滚
# EFCore 基本使用
Microsoft.EntityFrameworkCore.SqlServer
下载对应nuget
包 (这个包安装不需要安装efcore的包单独装也可以)建表中对应实体
public class SysUser { /// <summary> /// 账号 /// </summary> public string UserName { get; set; } /// <summary> /// 密码 /// </summary> public string PassWord { get; set; } /// <summary> /// 头像 /// </summary> public string Avatar { get; set; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17实体的配置类
public class EntityConfiguration : IEntityTypeConfiguration<SysUser> { public void Configure(EntityTypeBuilder<SysUser> builder) { builder.ToTable("SysUser"); } }
1
2
3
4
5
6
7DbContext配置
public class MyDbContext : DbContext { public DbSet<SysUser> Users { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); optionsBuilder.UseSqlServer("Server=WIN-OBVK2686PPL;uid=sa;pwd=123456;Database=CoreSchool;MultipleActiveResultSets=true;pooling=true;min pool size=5;max pool size=32767;connect timeout=20;Encrypt=True;TrustServerCertificate=True;"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // 从当前程序集反射加载所有实现IEntityTypeConfiguration modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16使用
Migration
工具生成表 可以每次都对每次的命令写上对应的别名安装对应 nuget 包 Microsoft.EntityFrameworkCore.Tools 初次加载(需要在DbContext所在的层使用) Add-Migration Init 操作数据库 Update-Database (增删改字段都是这个流程) 之后 不用再执行 Add-Migration Init 可以改为 Add-Migration 别名(操作列等方便记录)
1
2
3
4
5
6
7
8
9
10