《C#和NET学习的总结(一)--数据类型》
作者:xhanwu
日期:2007-05-23
----------------------------------------------------------------------------------------------------
使用sizeof操作符
C#中的sizeof操作符是从C/C++中借用来的,它用来返回某种数据类型所占用的字节数。在C#中sizeof操作符只能用来获取值类型数据的大小。返回的结果是存储这种数据类型的变量所占用的内存空间的大小。不能用sizeof操作符来取得引用类型数据的大小,比如类或者数组。
另外,你只能在标记为unsafe的方法或者代码块中使用sizeof操作符。
你还可以对结构使用sizeof操作符,因为结构是一种值类型的数据类型。但是,如果结构中含有像字符串或者数组这样的引用类型的成员,那么编译器就不允许对这个结构使用sizeof操作符。Sizeof结果将返回结构的大小,包括编译器填充在结构中的字节。之所以会填充一些字节是为了让结构的大小能够适合字节的边界。(在32位的window操作系统中,编译器通常会在结构中填充字节使得结构的大小能被一个字(word)的大小也就是4整除)。
下面的代码中的结构的大小实际上只有14个字节,但是sizeof返回的结果将是16个字节:
struct TestStruct
{
int x;
double y;
char ch;
}
C#中的数据类型
在C#中,所有的数据类型都被分成两大类:值类型和引用类型。一个值类型的变量直接储存的是数值,而引用类型的变量中存放的是对象的存储地址,即对实际值的引用。
你可以在方法,结构或者类中声明值类型变量。不管在那种情况中,当你声明值类型变量的时候,C#不会对变量进行初始化。声明变量之后你必须对它进行初始化,而且C#通过赋值规则来强制的进行初始化。(之所以这样规定的一个原因是C#是一种类型安全的语言,C#要保证某个类型变量中存放的必须是这种类型的数据值)
C#编译器在编译过程中会对变量进行跟踪,当你试图使用未经初始化的变量的时候,C#编译器将会抛出编译错误。
对象概念简介
在面向对象编程中,简单数据类型指的是存放值的变量,这种变量关心的是值,所以在C#中被称之为值类型。而对象中可以包含一个或者多个值,此外还包含了如何赋值和取值的信息。
C#在堆空间中创建和维护对象。当C#在堆中创建对象的时候,程序将使用对象的存储地址来引用这个对象。所以在C#中用来保存地址的变量被称为引用类型变量。
C#中声明一个对象要经过两个步骤的处理:
1 你必须要声明一个引用类型的变量来存放对象的地址。这个引用类型变量存放在堆栈区。
引用类型变量实际上是一种特殊的值类型变量,而且也受到值类型变量作用域影响。(如果你在方法中声明一个引用类型的变量,那么当方法执行完毕之后,这个变量会像值类型变量一样因为超出了它的作用域而被自动的销毁。)
2 你需要用new操作符来创建对象。New操作符会在堆中为该对象分配内存空间,并且获得该对象的存储地址。那么这个由new操作符返回的存储地址将赋值给引用类型变量。
注意:声明的引用类型变量只是用来存放对象的地址,并不能创建对象。只有使用new操作符才能创建对象。
C#中声明和初始化变量
当在C#的方法中声明一个值类型变量,或者在C#类中声明一个字段时,编译器并不会对这些变量进行初始化。你必须在对变量进行初始化之后才能使用它。
当你声明引用类型的变量时,C#编译器会自动将它初始化为null。然而,编译器仍然不允许你使用给引用类型变量,除非你对它进行初始化。例如下面的程序,编译器将会报告你试图使用未赋值的局部变量:
public void method()
{
myClass class1;
object obj = class1;
}
再看看下面的程序,演示了一中错误的操作,这种操作一定会产生NullReferenceException异常。
public void method()
{
myClass class1=null;
// some statements in between to make you forget about the null
object obj = class1;
Console.WriteLine("class1 = " + obj.ToString());
}
这个例子表明,虽然你可以再声明引用类型变量的同时对它进行初始化,但是最好还是让编译器来完成这个工作。当你将null赋值给引用类型变量时,赋值语句满足了编译器的显示赋值的要求,但是它并不会检查所赋的值是否合法。
Null其实是一个表示值(literal value),它表示一个不指向任何对象的空引用。
使用值类型变量
值类型包括所有的简单数据类型,结构类型和枚举类型。
1 在C#中定义结构
在声明结构的实例时,使用new操作符并不会使得C#从堆中为给结构实例分配内存空间,结构的实例创建在堆栈区的。New操作符的执行效果仅仅是对结构中的所有成员进行初始化。
如果你只是声明结构的实例的话,你并没有对结构中的成员字段进行初始化,在使用这个结构实例之前,你必须对结构中的所有成员字段进行初始化。哪怕有一个成员没有被赋值,编译器也会报告错误说你试图使用没有赋值的结构变量,但是编译器并不会告诉你那个成员字段没有被初始化。
using System;
namespace aa
{
class myCalss
{
public static void Mian()
{
myStruct Pt;
pt.x=12;
pt.y=23;
Console.WriteLine (pt);
}
}
struct myStruct
{
public int x;
public int y;
public int var;
public override string ToString()
{
retrun ("("+ x +", " + y + ")");
}
}
}
当你编译程序时,你将看到编译错误说你没有初始化myStruct结构的实例,即pt变量。但是错误信息没有告诉你没有初始化var变量。要编译这段程序,你必须对pt.var进行赋值,即使你没有使用它。
但是在下面这种情况下,你可以单独使用结构中的某个变量。将程序中的Console.WriteLine语句改为如下:
Console.WriteLine ("("+ pt.x +", " + pt.y + ")");
那么编译器会发出警告说你没有对var成员进行初始化,但是程序仍然可以通过编译。
在System名字空间中有一组预定义的结构来代表简单的数据类型,在C#你用来声明的简单数据类型实际上是相关结构定义的别名。
数据类型 | 对应的结构 | 数据类型 | 对应结构 |
sbye | System.Sbye | uint | System.Uint32 |
byte | Sytem.Byte | long | System.Int64 |
char | System.Char | ulong | System.Uint64 |
bool | System.Boolean | float | System.Single |
short | System.Int16 | double | System.Double |
ushort | System.Uint16 | decimal | System.Decimal |
int | System.Int32 |
|
|
每个结构还包含有定义和操作值的方法。你声明的任何一个结构都包含MinValue和MaxValue字段来保存这个数据类型的最大值和最小值。当你用checked关键字来防止数据的上溢出和下溢出时,程序就会用这些值来进行检查。你也可以在程序中读取这些值,例如下面的程序片断:
Console.WritleLine("The maximum is"+ Int32.MaxValue);
Console.WritleLine("The maximum is"+ Int32.MInValue);
结构中还包括了操作值的方法。例如Parse()方法用于字符串和数值之间的转换。
2 创建枚举类型
枚举类型中的值是只读的,一旦被创建,你就不能修改它的值。
枚举类型可以是除了char和bool类型之外的所有的整数数据类型。它可以是sbyte,short,long 和int以及他们的无符号类型。如果你没有明确的指出枚举列表的数据类型,默认是Int类型。
枚举值默认是从0开始,咱枚举列表中按1递增。
enum number:long
{
zero,one,two,three
}
你也可以对枚举列表中的标量进行赋值,并且列表将以新的值继续赋值。
enum number:long
{
zero,three=3,four,fourtyTwo=42,fortyThree,fourtyFour
}
要特别强盗的一点是:虽然枚举值在本质上是整数数据类型,枚举类型本身是一种新的数据类型所以你不能直接将枚举类型中的值赋值给整数数据类型。如果要将枚举类型转换称整数数据类型必须使用显示转换:
int x=(int) number.three; //the cast is required
如果在基类中定义了一个非私有的枚举类型,那么从这个基类派生出来的子类也将继承改枚举类型。在派生类型可以重载或者用new操作符来修改枚举值。
使用引用类型
在C#中,引用类型的变量包括类,数组,委托和接口。
使用内部字符串表来降低内存消耗
C#通过System.String类来实现字符串数据类型。当声明字符串变量以及赋值时,实际上是在创建String类的实例。
由于字符串变量是类的实例,所以它是引用类型变量。例如:
string str1="This is string";
该程序片断将使得C# 程序从堆中分配足够大的内存空间来储存字符串值“This is string”。而str1是一个引用类型变量,该变量存放的是字符串值的地址,该变量存放在堆栈区。
在程序中书写实际代码的字符串时,编译器必须在程序代码中储存这个字符串,以便在运行程序的时候获取这个字符串的值。如果在程序中重复出现这些字符串的次数很多,那么为保存这些字符串所分配的储存空间的开销将非常的大。所以在C#中使用字符串表来节约内存的开销。CLR为程序维护着一张内部字符串表(string interning)。这张表也叫内池表,其中保存着程序所用到的每个实义字符串。当程序中使用实义字符串的时候,C#会在这张表中检查是否有匹配的字符串存在。如果存在,那么字符串引用会指向表中已有的那个字符串。如果不存在,那么在表中会创建一个新的字符串表项。这种处理方法可以节约内存,尤其实那些使用大量字符串的程序。
System.String类的字符串以字符数组的方式来实现的。String类是不可改变的,一旦声明并初始化了字符串变量,就不能改变它的值了。但是在任何时候都可以把一个新的字符串赋值给另外一个字符串变量。在这种情况下,并不会改变字符串的值,C#程序将从堆中分配出新的内存空间来存放字符串对象,并且将旧的字符串的引用指向的新的字符串对象的内存块。
因为字符串是以字符数组的方式来实现的,所以你可以以下标的形式从Sring对象中读出某个字符,然而你不能用这种方式来修改字符串中的某个字符。下面的程序中第三条语句是错误的:
String str=”This is String”;
char ch=str[5]; //char is equal to f now
str[5]=“f”; //Error.Cannot set the indexer value