1.类型
值类型
变量直接包含它们自己的数据
局部变量总是放在栈(stack)中
引用类型
变量间接指向它们的数据
局部变量指向堆(heap)中的对象
枚举(enum) 值类型
结构(struct) 值类型
类(class) 引用类型
接口(interface) 引用类型
数组([ ]array ) 引用类型
委托(delegate) 引用类型
你可能对上面的例子感到奇怪,c#中的内在类如int,double怎么没有。C#规定这些内在类属于结构,C#称之为简单类型。简单类型和用户自定义类型之间的最大区别是前者有字面表达式,而后者没有。
当然,还有第三种类型:指针。但指针只用在由unsafe关键字标识的非安全的代码中。
2.枚举类型
· 它是一个用户声明的值类型
enum Suit
{
Clubs, Diamonds, Hearts, Spades
}
//Suit表示一副牌,它有4个花色:梅花(Clubs),方块(Diamonds),红心(Hearts),//黑桃(Spades)
sealed class Example
{
static void Main()
{
...
Suit lead = Spades; //错误
...
Suit trumps = Suit.Clubs; //正确
...
}
}
枚举的声明可以出现在类声明的相同地方。
枚举的声明包括名字、访问权限、内在的类型和枚举的成员。
枚举中声明的常量的范围是定义它们的枚举,换言之,下面的例子是错误的:
Suit trumps = Clubs;
Clubs必须被限制为Suit的一个成员,就如下面:
Suit trumps = Suit.Clubs;
3.枚举的注意点
枚举值缺省为int
你可以选择任一内在的整数类型
但不能是字符型
enum Suit : int //内在类型是int,可以省略
{
Clubs,
Diamonds,
Hearts = 42, //成员的取值缺省为前一个成员取值+1,但可以自己赋初值
Spades, //最后一个分号是可选的
};//可以有结尾分号
枚举类可以显式的声明它的内在类型是sbyte, byte, short, ushort, int, uint, long, ulong。如果一个枚举类没有显式声明它的内在类型,则缺省为int。
成员的取值必须和枚举声明的内在类型相同,并且必须在内在类型的范围之内(例如,你不能让成员的取值为负数,而枚举的内在类型是uint)。
如果成员没有被赋值, 那么它的取值是前一个成员取值+1,第一个成员的缺省值是1。枚举的成员的取值可以有相同的取值。
最后一个枚举成员可以使用一个结尾分号,这使得你将来可以很方便地加入更多的成员。
枚举成员的访问权限隐含为public
以下是使用枚举时几条好的建议。
优先考虑使用枚举,而不是类的静态常量
比如:
public static class Day
{
public static int Sun = 1;
public static int Mon = 2;
public static int Tue = 3;
//...
}
应该使用如下的枚举:
enum Day { Sun = 1, Mon, Tue, Wed, Thu, Fri, Sat };
如果参数、返回值、变量等类型可以是枚举,则不要使用其它基础类型
比如:
Range r = Range.MAX; //好
int r = (int)Range.MAX; //不好
枚举命名
枚举一般使用名词或名词组合,简单枚举使用单数,标志枚举使用复数。
大多数情况下不需要更改枚举的默认类型
也就是说大多数情况下,使用 int(System.Int32)作为枚举类型。除非:
- 枚举是标志枚举,且标志多于 32 个(此时 int 类型装不下)。
- 枚举被非常大量且频繁地使用,为了节约空间使用小于 int 的类型。
- 不得不使用其它类型的情况。
不要在枚举中设置哨兵
我们可能觉得在枚举的两端加上哨兵,这样在判断一个数是否在枚举中时,只需要判断是否在哨兵之中。非常不幸,我们不应该这么做,这破坏了枚举的意义。
enum Day {FirstValue, Sun, Mon, Tue, Wed, Thu, Fri, Sat, LastValue }; //FirstValue、LastValue 应该去掉
- 简单枚举包含的值不用于组合,也不用于按位比较。
- 标志枚举应使用按位 OR 操作进行组合。
简单枚举
前面提到的 Day、Range 都可以称之为简单枚举,因为不能将他们各自的值组合起来。
标志枚举
标志枚举的设计有两点要注意。
- 指明 FlagsAttribute,以指示可以将枚举作为位域(即一组标志)处理。
- 枚举中各标志的值应该是以 2 的幂来赋值,即:1、2、4、8、16、32……
举个例子,假如我们在设计 Windows 窗口程序,窗口有最小化、最大化、关闭按钮,我们想任意组合显示,也就是说我们可以显示其中的任意 0 个或一个或多个按钮。
如果使用简单枚举,按照排列组合,我们要使用 1 + 3 + 3 + 1 = 8 个枚举数,如果这里不是三个按钮,而是四个按钮,枚举数就更多了。所以这样不现实。
为什么这里使用简单枚举不现实呢?因为简单枚举不能组合,采用标志枚举就可以轻松解决了。
[Flags]
public enum WindowStyle
{
MINIMUM_BUTTON = 1, //十六进制表示为 0x0001
MAXIMUM_BUTTON = 2,
CLOSE_BUTTON = 4
}
我们在设置窗口样式时,利用 OR 自由组合:
WindowStyle ws = WindowStyle.MINIMUM_BUTTON | WindowStyle.CLOSE_BUTTON; //表示既有 MINIMUM_BUTTON 也有 CLOSE_BUTTON
这就是为什么标志的值要按 2 的幂排列的原因了,也是为什么标志多于 32 个时不能使用 int 类型的原因了。
通常我们为常用的标志组合提供特殊的枚举值
仍然以上述窗口为例,可知大多数情况下,我们均要显示这三个按钮,所以每次使用时都要用:
WindowStyle ws = WindowStyle.MINIMUM_BUTTON | MAXIMUM_BUTTON | WindowStyle.CLOSE_BUTTON;
实在有些繁琐,我们可以修改枚举为如下:
[Flags]
public enum WindowStyle
{
MINIMUM_BUTTON = 1,
MAXIMUM_BUTTON = 2,
CLOSE_BUTTON = 4,
ALL_BUTTON = 7
}
增加一个 ALL_BUTTON 为前三个标志的值。使用时直接用 ALL_BUTTON 就可以了。
C#结构体和类的区别问题
在C#编程语言中,类属于引用类型的数据类型,结构体属于值类型的数据类型,这两种数据类型的本质区别主要是各自指向的内存位置不同。传递类的时候,主要表现为是否同时改变了源对象。
C#结构体和类的区别技术要点:
◆类在传递的时候,传递的内容是位于托管内存中的位置,结构体在传递的时候,传递的内容是位于程序堆栈区的内容。当类的传递对象修改时,将同时修改源对象,而结构体的传递对象修改时,不会对源对象产生影响。
◆在一个类中,可以定义默认的、不带参数的构造函数,而在结构体中不能定义默认的、不带参数的构造函数。两者都可以定义带有参数的构造函数,通过这些参数给各自的字段赋值或初始化。
C#结构体和类的区别之实现步骤
(1)创建控制台应用程序项目,命名为“ClassAndStruct”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System;
using System.Collections.Generic;
using System.Text;
namespace ClassAndStruct
{
class program
{
static void Main(string[] args)
{
//使用带参数的构造函数创建员工类的实例
classEmployee clsEmpA = new classEmployee("Pony","Smith",43);
classEmployee clsEmpB = clsEmpA;
//修改引用数据
clsEmpB.Age = 33;
//使用带参数的构造函数创建员工结构体的实例
structEmployee strctEmpA = new structEmployee("Pony", "Smith", 43);
structEmployee strctEmpB = strctEmpA;
//修改
strctEmpB.Age = 33;
Console.WriteLine("类的数据:姓名-{0} {1} 年龄-{2}", clsEmpA.FirstName,clsEmpA.LastName,clsEmpA.Age);
Console.WriteLine("结构体的数据:姓名-{0} {1} 年龄-{2}", strctEmpA.FirstName, strctEmpA.LastName, strctEmpA.Age);
Console.ReadLine(); } } class classEmployee
//表示员工的类
{
private string firstname;
public string FirstName
{
get
{
return firstname;
}
set
{
firstname = value;
}
}
private string lastname;
public string LastName
{
get
{
return lastname;
}
set
{
lastname = value;
}
}
private int age;
public int Age
{
get
{
return age;
}
set
{
age = value;
}
}
//类的默认构造函数,可以在类中重新定义
public classEmployee()
{
firstname = ""; lastname = ""; age = 0;
}
//C#结构体和类的区别
//类的带参数的构造函数,在构造类实例的同时给字段赋值
public classEmployee(string strFirstNamem, string strLastName, int iAge)
{
firstname = strFirstNamem;
lastname = strLastName;
age = iAge;
}
}
struct structEmployee
//表示员工的结构体
{
private string firstname;
public string FirstName
{
get
{
return firstname;
}
set
{
firstname = value;
}
}
private string lastname;
public string LastName
{
get
{
return lastname;
}
set
{
lastname = value;
}
}
//C#结构体和类的区别
private int age;
public int Age
{
get
{
return age;
}
set
{
age = value;
}
}
//在结构体中不能定义默认的不带参数的构造函数,只能定义结构体的带参数的构造函数
public structEmployee(string strFirstNamem, string strLastName, int iAge)
{
firstname = strFirstNamem;
lastname = strLastName;
age = iAge;
}
}
}
}
(3)按F5键运行程序,运行结果如下所示。
类的数据:姓名-Pony Smith 年龄-33
结构体的数据:姓名-Pony Smith 年龄-43
C#结构体和类的区别之源程序解读
(1)本示例为了说明在传递时C#结构体和类的区别,在程序中分别定义了表示员工的类classEmployee类和表示员工的结构体structEmployee,并定义了各自的字段和构造函数。在主程序入口Main方法中,声明类的实例clsEmpA和clsEmpB,并使用构造函数创建clsEmpA类实例,然后将clsEmpA类实例传递给clsEmpB类实例,修改clsEmpB类实例的字段值,最后打印clsEmpA类实例中的字段,查看字段的值是否随clsEmpB类实例字段的修改而变化。同时,声明结构体的实例strctEmpA和strctEmpB,并使用构造函数创建strctEmpA结构体实例,然后将strctEmpA结构体实例传递给strctEmpB结构体实例,修改strctEmpB结构体实例的字段值,最后打印strctEmpA结构体实例中的字段,查看字段的值是否随strctEmpB结构体实例字段的修改而变化。程序的流程图如图8.1所示。
(2)C#结构体和类的区别还有一个比较明显的区别,就是类能够定义默认的、不带参数的构造函数,并能在该构造函数中初始化字段。而结构体不允许定义默认的、不带参数的构造函数。
数组是具有相同数据类型的项的有序集合。要访问数组中的某个项,需要同时使用数组名称及该项与数组起点之间的偏移量。在 C# 中,声明和使用数组的方法与 Java 有一些重要区别。
一维数组
一维数组以线性方式存储固定数目的项,只需一个索引值即可标识任意一个项。在 C# 中,数组声明中的方括号必须跟在数据类型后面,且不能放在变量名称之后,而这在 Java 中是允许的。因此,类型为 integers 的数组应使用以下语法声明:
int[] arr1;
下面的声明在 C# 中无效:
//int arr2[]; //compile error
声明数组后,可以使用 new 关键字设置其大小,这一点与 Java 相同。下面的代码声明数组引用:
int[] arr;
arr = new int[5]; // create a 5 element integer array
然后,可以使用与 Java 相同的语法访问一维数组中的元素。C# 数组索引也是从零开始的。下面的代码访问上面数组中的最后一个元素:
Console.WriteLine(arr[4]); // access the 5th element
初始化
int[] arr2Lines;
arr2Lines = new int[5] {1, 2, 3, 4, 5};
C# 初始值设定项的数目必须与数组大小完全匹配,这与 Java 不同。可以使用此功能在同一行中声明并初始化 C# 数组:
int[] arr1Line = {1, 2, 3, 4, 5};
此语法创建一个数组,其大小等于初始值设定项的数目。
在程序循环中初始化
在 C# 中初始化数组的另一个方法是使用 for 循环。下面的循环将数组的每个元素都设置为零:
int[] TaxRates = new int[5];
for (int i=0; i<TaxRates.Length; i++)
{
TaxRates[i] = 0;
} 交错数组
C# 支持创建交错(非矩形)数组,即每一行包含的列数不同的数组。例如,在下面的交错数组中,第一行有四项,而第二行有三项:
int[][] jaggedArray = new int[2][];
jaggedArray[0] = new int[4];
jaggedArray[1] = new int[3];
多维数组
可以使用 C# 创建规则的多维数组,多维数组类似于同类型值的矩阵。 C# 还支持多维数组(数组的数组)。
使用以下语法声明多维矩形数组:
int[,] arr2D; // declare the array reference
float[,,,] arr4D; // declare the array reference
声明之后,可以按如下方式为数组分配内存:
arr2D = new int[5,4]; // allocate space for 5 x 4 integers
然后,可以使用以下语法访问数组的元素:
arr2D[4,3] = 906;
由于数组是从零开始的,因此此行将第四行第五列中的元素设置为 906。
初始化
可以使用以下一种方法,在同一个语句中创建、设置并初始化多维数组:
int[,] arr4 = new int [2,3] { {1,2,3}, {4,5,6} };
int[,] arr5 = new int [,] { {1,2,3}, {4,5,6} };
int[,] arr6 = { {1,2,3}, {4,5,6} };
浙公网安备 33010602011771号