第7章 C#语言基础.doc
第7章 C#语言基础本章概述C#是随.NET技术一起发布的新一代编程语言,兼备了C+与Java的许多优点,易学好用,功能强大。在Visual Studio .NET 2003中,微软推出了C#语言1.x版,随着后继推出的VS 2005和VS 2008,C#语言的版本也在不断更新,目前最新的VS版本为。使用ASP.NET开发Web应用程序,掌握C#语言是程序设计的关键。本章在C# 2005的基础上,介绍C#语言的数据类型、控制语句、类与对象等语言基础。学习目标:1了解C#语言特点及应用前景;2掌握VS.NET和ASP.NET编写C#程序的方法和步骤;3了解C#语法基础,包括数据类型、,变量、,流程控制、,错误处理等;4理解类和对象的概念,能熟悉使用C#创建和使用类。7.1 在ASP.NET中创建C#程序C#是整个.NET Framework的基础语言,可以在Visual Studio环境多种开发模式下使用C#语言,如Windows应用程序、类库等,图7-1是在Visual Studio 2005新建项目时可供选择的模板。图7-1 C#语言工作模板很多教材都以“控制台应用程序”讲解C#语言,以命令窗口方式演示结果,程序文档简单,直观方便。考虑到Web开发实际情况,本教材结合ASP.NET环境讲述C#语言。新建一个Web项目的流程如下:单击“文件”菜单”,选择“新建网站”。如图7-2中选择新建ASP.NET网站,并选择语言为C#,指定网站存放的文件路径。图7-2 新建网站新建网站后,默认会生成Default.aspx与Default.aspx.cs两个文件,即是网站的一个页面和对应的C#后台程序。默认情况下,ASP.NET网站项目中的页面文件与C#代码文件分离存放,在页面文件Default.aspx中通过指令“CodeFile="Default.aspx.cs"”,实现页面文件与对应代码文件的关联。开发人员可通过资源管理器新建或管理页面文件。如图7-3所示,在Visual Studio集成开发环境下,所有基本的代码框架已经自动生成,开发人员只需要在相应的代码框架中编写对应的C#程序。图7-3 Visual Studio 2005集成开发环境7.2 数据类型7.2.1数据类型概述在C#语言中,数据类型可以理解为内部数据类型与用户定义数据类型,根据数据存储形式也可分为值类型与引用类型。1内部类型与用户定义类型内部数据类型,如int(整型),char(字符型);用户定义类型,如用户定义的类或接口。2值类型与引用类型值类型,直接存放真正的数据,值类型都有固定的长度,值类型的变量都保存在堆栈(stack)上。作为值类型的变量,每个都有自己的数据,因此对一个变量的操作不会影响其它变量。简单类型、结构类型、枚举类型都属于C#值类型的范畴。如表7-1所示,简单类型包括整型、浮点型、布尔型、字符型(char)、字符串型等基本的数据类型。表 7-1 C#简单数据类型表类型关键字说明取值范围对应.NET类整型byte无符号 8 为整数,0255System.Bytesbyte有符号8位整数-128127System.SByteshort有符号16位整数-215215-1,即-3276832767System.Int16ushort无符号16位整数0216-1,即065535System.UInt16int有符号32位整数-231231-1System.Int32uint无符号32位整数0232-1System.UInt32long有符号64位整数-263263-1System.Int64ulong无符号64位整数0264-1System.UInt64布尔型bool8位true/falseSystem.Boolean字符型char16位Unicode 字符U+0000 to U+ffffSystem.Char浮点型float32位浮点值,7位小数±1.5×1045 ±3.4×1038System.Singledouble64位浮点值,有15到16位的小数±5.0×10324±1.7×10308System.Doubledecimal有符号128位数据,有28到29位有效位±1.0×1028 ±7.9×1028System.Decimal提示:更详细的C#数据类型与示例可访问微软MSN技术资源库: 引用类型:学过C语言的读者都知道指针的概念,指针实质上是对变量存储的地址进行操作。C#引用类型存储数据的内存地址的引用,引用类型位于受管制的堆(Heap)上,作为引用类型的变量可以引用同一对象,因此对一个变量的操作会影响另一个变量所引用的同一对象。类类型、对象类型、接口类型、委托类型、数组等都属于C#引用数据类型的范畴。7.2.2数据类型转换不同类型的C#数据在混合运算时需要进行数据类型转换,数据之间的转换可通过显示(强制)或隐式(自动)方式进行。1隐式转换一般是低精度类型向高精度型转换,能够保证值不发生变化。 例如本章范例程序DataCast中,有下列代码:int a;float f;a = 10;f = a;Response.Write("<br/>a=" + a.ToString();Response.Write("<br/>f=" + f.ToString();f = 12.5F;/a = f;/无法隐式地从高精度float类型向低精度int进行转换a = (int)f;Response.Write("<br/>a=" + a.ToString();/在页面中输出变量值Response.Write("<br/>f=" + f.ToString(); 整型变量i向浮点型 f进行转换,不会造成数据的丢失。C#不允许隐式地从高精度向低精度进行转换,去掉上例代码语句“/a=f;”注释,即直接将float变量f的值赋给int变量a,编译器会报程序出错,并提示有语法错误,如图7-4与图7-5所示。图片上有鼠标图7-4 ASP.NET编译错误图7-5 ASP.NET开发环境错误列表图片上有鼠标2显示转换从上面隐式转换的例子我们发现,直接将高精度值赋给低精度变量,即隐式转换,在VS编译环境中不是不允许的,出现这种情况的赋值语句,程序将不能正常编译执行,这样做其实是为了保证数据转换时的正确性。但有时,程序员确实需要将高精度值赋给低精度变量,这时,需要使用显示转换。显示转换也称强制类型转换,不能保证数据的正确性。显式转换的格式为(强制类型)变量,如(int)(12.54)会将double型常量转换为int型。在本章示例DataCast例2中,演示了一个通过ASP.NET页面进行加法运算的功能,具体实现过程非常简单。首先从工具栏中向页面拖放三个文本框控件,其ID值为自动生成的“TextBox1”、“TextBox2”、“TextBox3”,再拖放一个按钮控件,双击按钮,即可进行加法计算的编程,读取文本框控件的值与给文本框控件赋值方法非常简单,使用文本框控件ID的Text属性即可实现,如“this.TextBox1.Text=”24.3”;”。this.TextBox3.Text = this.TextBox1.Text + this.TextBox2.Text;如果使用上述语句实现加法运算,读者会发现,执行页面时,在前两个文本框中分别输入1和2,得到的结果并非3,而是12,如图7-6所示。图7-6 文本框字符运算图片上有鼠标这是因为文本控件的Text值为字符串类型,字符串类型的变量在执行“+”运算时,会进行字符串的拼接,因为上例中语句的功能实质上是将文本框1与文本框2的Text值进行字符串拼接,再赋给文本框3,从表面上看,读者会误认为是执行整数加法“1+2”,而实质上是执行字符串拼接"1"+"2"。要解决上述问题,必须使用显式数据转换,下面给出正确的代码。float a, b, sum;a = float.Parse(this.TextBox1.Text);b = float.Parse(this.TextBox2.Text);sum = a + b;this.TextBox3.Text = sum.ToString();请读者注意,当文本框控件的文本向float变量进行类型转换时,如果转换语句写成(float) this.TextBox1.Text,是无法编译通过的。C#语言进行这样的约束是有原因的,如果用户在文本框中输入非数值的字符串如“abc”,再强制转换成float类型,肯定会引发异常。因此,我们采用.NET Framework中float类的Parse方法进行转换,事实上,所有字符串转换成其他数值类型都可以使用该方法,如int.Parse("23.2")。此外,还可以使用Convert函数进行转换, Parse方法只支持字符串类型到其他类型的转换,而Convert函数可以支持各种类型之间的转换,下列代码是Convert函数进行数据转换的例子。float a,b;double c, d;c = Convert.ToDouble(b);d = Convert.ToDouble(this.TextBox1.Text);读者可以将上述代码中变量a,b,sum定义成int类型,观察数据转换时数据丢失的现象。7.2.3装箱与拆箱装箱(boxing)与拆箱(unboxing)在C#语言的值类型与引用类型之间提供一种有效的转换机制,通过装箱,可以将值类型显式或隐式地转换成引用类型,拆箱是将封装在引用类型的值拆解出来的过程。任何值类型、引用类型都可以和对象类型(object)之间进行转换。装箱与拆箱的原理如图7-7所示。图7-7 (a)装箱过程 (b)拆箱过程 在本章范例Boxing中,演示了装箱与拆箱的过程,下列代码实现了int型变量i装箱到object对象中,并使用了对象实例的GetType方法在页面中输出装箱对象的类型。int i=10;object obj = i;/隐式装箱/ object o = (int)i;/显示装箱Response.Write("装箱后类型:" + obj.GetType().ToString();下列代码演示了对象的拆箱过程。int i = 10;int j;string k;object obj = i;obj = 123;/j = obj;/无法将类型“object”隐式转换为“int”。j=(int)obj;/k = (string)obj;/编译可以通过,但会引发转换异常。Response.Write("<br/>j="+j.ToString();Response.Write("<br/>i="+i.ToString();/Response.Write("<br/>k=" + k);装箱以通过显式或隐式的方式实现,但拆箱必须使用显式方式,读者可以调试上述代码的注释语句,体验实际情况。有一点需要特别说明,拆箱过程,编译器并不检验封装在object对象封装数据与被拆箱对象数据类型的一致性,但如果两者类型不一致,程序执行过程中可能会引发转换异常,如图7-8所示。因此,开发人员在进行拆箱操作时应保证两者之间的数据类型匹配。图7-8 拆箱过程引发异常请读者思考如何保证拆箱语句k = (string)obj不引发程序异常。7.3 常量与变量7.3.1常量常量,顾名思义,就是在整个程序运行过程中值不能改变的量。C#中可以使用const关键字定义常量。const float Pai = 3.;/Pai=3.14;/无法编译通过常量必须在定义的同时赋初值,不允许在以后的程序段中改变常量值,如上述代码中重新给Pai赋值,程序将无法编译通过。C#程序中使用常量可以防止某些数据值被修改,并使整个程序结构更加清楚。7.3.2变量变量,就是在整个程序运行过程中,值可以发生变化的量。C#中变量定义的语法如下:访问级别 变量类型 变量名如protected int age,定义一个受保护的整型变量,变量名为age。变量一定要赋了初值才能使用,直接使用未赋初值的变量的程序无法编译通过。在本章范例ConstVari中,实现一个计算圆面积的功能,用户在文本框中输入半径,点击计算按钮后输出圆的面积,程序代码如下, 程序执行结果如7-9所示。const float Pai = 3.F;float r=0, area;/ area = Pai * r * r;/编译无法通过,使用未赋值的变量r。r = float.Parse(this.TextBox_R.Text);area = Pai * r * r;this.Label_result.Text += "<br/>" + area.ToString();图7-9 计算圆的面积7.3.3变量作用范围变量的作用范围是指一个变量在程序中的生命周期,全局变量在整个程序中都起作用,局部变量只在变量所在程序段中起作用。在本章范例ConstVaria中,定义了一个静态全局变量static int sum = 0,只要在这个页面程序的范围内,都可以访问或修改sum的值。public partial class ConstVaria : System.Web.UI.Pagestatic int sum = 0;protected void Page_Load(object sender, EventArgs e) protected void Button3_Click(object sender, EventArgs e) 一般情况下,在程序段的最前段声明变量,C#允许在代码块定义变量,该变量只在本代码块中起作用。例如在下面代码中,变量a和b是在函数Button2_Click中定义的变量,在整个函数中都起作用,而变量i和t是在程序段中if(a<b)中定义的,只在该程序段中起作用,在该程序段外的任何地方使用该变量,编译器都会提示“当前上下文不存在的名称”。而全局变量sum,在页面程序ConstVaria.aspx.cs的所有范围内都可以使用,读者可以查看Page_Load 、Button2_Click、Button3_Click三个事件函数中对sum变量的使用情况。protected void Button2_Click(object sender, EventArgs e) int a=0, b=10; Response.Write("sum=" + sum.ToString(); if(a<b) int i=5; a = i; b = sum; if (sum >= 4) bool t =true; Response.Write("<br/>t=" + t.ToString(); sum+; / Response.Write("i=" + i.ToString();/无法使用程序段中的局部变量i; / Response.Write("t=" + t.ToString();/无法使用程序段中的局部变量t; Response.Write("<br/>a=" + a.ToString(); Response.Write("<br/>b=" + b.ToString(); Response.Write("<br/>sum=" + sum.ToString(); 7.3.4 静态变量使用static关键字可以将变量声明为静态变量,一般情况下,在变量的生命周期之外,变量资源会被收回,再次调用变量所在的代码段时,系统重新生成变量资源,并赋给初始值,但使用static关键字之后,下次调用变量所在程序段时,上次变量值会被保留。在本章实例中,声明了静态全局变量sum,并赋初值为0,当页面被重新加载时,仍会保留Button3_Click被修改了的sum的值,可以利用这一特性,在页面加载事件Page_Load中,进行最大访问量的次数限制,代码如下:protected void Page_Load(object sender, EventArgs e) if (sum > 5) Response.Write("已经超过网站的最大访问量!"); Response.End(); 7.4 控制语句7.4.1分支C#中的分支控制包括二分支的if语句与多分支的switch语句。if语句的基本语法格式有两种基本形式,多个if语句可以嵌套使用。形式一:if(条件)语句块;形式二:if(条件)语名块;else语句块;switch 语句是一个控制语句,它通过将控制传递给其体内的一个 case 语句来处理多个选择和枚举。基本语法格式如下switch (表达式) case 表达式值1: 处理语句; break; case 表达式值2: 处理语句; break;case 表达式值n: 处理语句; break; default: 默认处理; break;本章范例Ifelse中给出了一个华氏和摄氏温度转换的程序,如果用户在文本框中输入了相应数字,并选择转换方式,可以进行相应的温度转换。下列代码判断用户是否在文本框中输入了值。if (this.TextBox1.Text.Length < 0) this.Label1.Text += "<br/>请先输入一个温度值"else /转换处理;RadioButtonList中有多个可选值,下例代码使用switch语句对每个选值进行了转换处理,并将结果显示在Label标签中。double F, C = 0;int selection =int.Parse(this.RadioButtonList1.SelectedValue);/获取转换方式switch (selection) case 1: F = double.Parse(this.TextBox1.Text); C = (F - 32) * 5 / 9; this.Label1.Text+="<br/>摄氏"+C.ToString(); break; case 2: C = double.Parse(this.TextBox1.Text); F = (C * 9 / 5) + 32; this.Label1.Text+="<br/>华氏"+F.ToString(); break; default : F = C = 0; this.Label1.Text += "<br/>转换失败!" break; 程序执行结果如图7-10所示。图7-10 分支语句7.4.2循环1. while与dowhile语句while语句基本语法:while(条件表达式)循环体语句;dowhile语句基本语法:do循环体语句; while(条件表达式);dowhile语句不管条件是否成立,至少被执行一次。2for语句下面程序演示了一个计数器,从1数到1000,当碰到能被17整除的数时退出循环。for (int counter = 1; counter <= 1000; counter+) if (counter%17= 0) break; Response.Write (counter.ToString();3foreach语句foreach语句可以遍历集合的每一个元素,基本语法如下:foreach(元素类型 元素名 in 集合)循环体语句;下列代码的功能是将数组array1的元素依次在页面中打印输出。Int array1 = 0, 1, 2, 3, 4, 5;foreach (int n in array1) Response.Write (n.ToString();本章范例Loop中演示了一个使用foreach进行数据筛选处理的功能。代码如下:foreach (ListItem item in this.ListBox1.Items) if (item.Value = "女") Response.Write("<br/>" +item.Text+" "+ item.Value.ToString(); ListBox1控件由许多元素Listitem组成,每个元素具有Text和Value两个属性。foreach遍历ListBox1控件的每个元素,并将value值为“女”的元素打印输出。运行效果如图7-11所示。图7-11 foreach语句 7.5 数组和集合7.5.1数组数组是相同类型数据的集合,可以在数组中存储多个元素,但必须在声明数组的时候指明数组元素的个数。数组定义格式如下:数据类型 数组名=new 数据类型元素个数常量例如:int array = new int5;该语句声明了一个含有5个元素的整型数组array,数组的5个元素分别是array0到array4。可以通过下列方式声明多维数组:int, array2D = new int2,3;数组的初始化方式如下:一维数组:int array1 = new int 1, 3, 5, 7, 9 ;int array2 = 1, 3, 5, 7, 9;二维数组:int, array2D = 1, 2, 3, 4, 5, 6 ;本章范例Array演示了对二维数组array2D的操作,代码如下:int, array2D = 1, 2, 3 , 4, 5, 6 ; /修改二维数组元素for (int i = 0; i < 2; i+) for (int j = 0; j < 3; j+) array2Di, j = (i + 1) * (j + 1); / 读取二维数组元素for (int i = 0; i < 2; i+) for (int j = 0; j < 3; j+) Response.Write(array2Di, j.ToString() + " "); Response.Write("<br/>"); 与C语言多维数组的定义不同,在C#中使用交错数组的概念,即由数组组成的数组。交错数组是一维数组,且每个元素自身是一个数组。作为元素的数组无需均为相同的大小。声明交错数组的方式如下:int jaggedArray = new int3;这样做会创建一个有 3 个数组的数组。这些数组可以按如下方式初始化:jaggedArray0 = new int5;jaggedArray1 = new int4;jaggedArray2 = new int2;上述方法相当于声明了一个每行都不等长的二维数组,可以使用jaggedArray04为第一行第五个元素赋值,但如果引用数组jaggedArray14则会报错,因为根据数组第二行只初始了四个元素。7.5.2 ArrayList数组只能存放相同类型的数据,而且数组的长度在定义初始化时必须固定,这多少带来一些编程限制。C#中提供了ArrayList类型,ArrayList的元素类型是Object类型,因此可以存放不同类型的数据,ArrayList对象可以进行插入、删除、清空、排序等多种操作,表7-2给出了ArrayList对象部分操作方法。表 7-2 ArrayList类部分操作方法名称说明Add将对象添加到 ArrayList 的结尾处。Clear从 ArrayList 中移除所有元素。Insert将元素插入 ArrayList 的指定索引处。Remove从 ArrayList 中移除特定对象的第一个匹配项。Reverse将 ArrayList 或它的一部分中元素的顺序反转。Sort对 ArrayList 或它的一部分中的元素进行排序。 int a = 1, 2, 3 ;string Str = "Banna"double d = 3.1415;ArrayList MyArray = new ArrayList();MyArray.Add("Apple");MyArray.Add(a);MyArray.Add(d); MyArray.Insert(0,Str);MyArray.Remove(MyArray.Count - 1);for (int i = 0; i < MyArray.Count; i+) Response.Write(MyArrayi.ToString()+"<br/>");本章范例程序Array中提供了ArrayList演示代码,该段程序声明了一个ArrayList对象MyArray,通过Add和Insert方法添加5个不同的数据,依次为在尾部追加string常量”Apple”、int数组a、double变量d,在数组前端插入string变量Str。使用Count属性可以计算出ArrayList的长度,MyArray.Remove(MyArray.Count - 1)语句可移除最后一个元素。程序执行结果如下:BannaAppleSystem.Int323.14157.6 字符串处理7.6.1 string对象string属于C#的引用类型,但C#提供“=”、“=”、“!=”、“+”等运算方式,这使字符串使用起来更加直观。但本质上,字符串常量是不可改变的,例如语句:string a=”good”+”morning”;表面上看,是把”good”与”morning”两个字符串连接起来赋值给a,而事实并非如此,编译器实际上会创建一个新字符串对象来保存新的字符序列。本章范例String有如下代码演示了string对象。Response.Write(a = b)打印出的结果是True,但Response.Write(object)a = (object)b)打印出的结果是False,这是因为“=”运算比较的是string对象的值,事实上,在b += "ello"过程中,b保存的仍是”h”,由编译器产生一个新的string对象保存”h”+”ello”拼接出的新字符串。string a = "hello"string b = "h"b += "ello"Response.Write(a = b);Response.Write(object)a = (object)b);由此可见,程序段使用过多的字符串拼接运算,编译器需要产生大量新的string对象用来存储运算后的字符串,从性能上看是不可取的。7.6.2 StringBuilder对象为了避免string对象在大量字符串运算消耗过多资源,可使用StringBuilder对象。表 7-3 StringBuilder类方法名称说明StringBuilder.Append 将信息追加到当前 StringBuilder 的结尾。StringBuilder.AppendFormat用带格式文本替换字符串中传递的格式说明符。StringBuilder.Insert 将字符串或对象插入到当前 StringBuilder 对象的指定索引处。StringBuilder.Remove 从当前 StringBuilder 对象中移除指定数量的字符。StringBuilder.Replace 替换指定索引处的指定字符。要使用StringBuilder必须引用“System.Text”名字空间。下列代码演示了使用StringBuilder对象进行字符串操作。 StringBuilder MyStringBuilder = new StringBuilder("Hello World!"); MyStringBuilder.Insert(6, "Beautiful "); MyStringBuilder.Append(" What a beauty day."); Response.Write(MyStringBuilder); MyStringBuilder.Replace("beauty", "beautiful"); Response.Write("<br/>"); Response.Write(MyStringBuilder); MyStringBuilder.Remove(6, "Beautiful ".Length); Response.Write("<br/>"); Response.Write(MyStringBuilder);首先使用StringBuilder类产生一个MyStringBuilder对象,并赋初值"Hello World!",使用Insert在指定位置插入一段字符串,Append方法在尾部追加一段字符串,Replace方法进行字符串的替换,Remove在指定位置移除一段指定长度的字符串,"Beautiful ".Length用来测算字符串"Beautiful"的长度。程序运行结果如下:Hello Beautiful World! What a beauty day.Hello Beautiful World! What a beautiful day.Hello World! What a beautiful day.7.6.3 字符串常见操作1字符替换Replace方法可以实现字符串内容的替换,Replace()方法需要提供两个string或char类型参数,表示把后一个参数替换源字符串中前一个参数内容。下列语句实现了将网页中Label标签文字中的红色两字以红色显示出来的效果。Label标签文本原有文本是:“网页中的红色表示重要信息的提示,红色的字可以使用font标签实现。”this.Label1.Text=this.Label1.Text.Replace("红色", "<font color='red'>红色</font>");替换后变成:“网页中的<font color='red'>红色</font>表示重要信息的提示,<font color='red'>红色</font>的字可以使用font标签实现。”标签文字在网页中显示效果如下.。:2字符定位与查找IndexOf方法可以定位字符串中某个字符或子串的位置,如果没有找到该子串,IndexOf方法返回-1。使用下列代码可以通过判断是否含有“”字符简要判定字符串是否为电子邮件。string StrEmail = "cxj#"if (StrEmail.IndexOf('')<0) Response.Write("非法电子邮件!");3字符串截取Substring方法可以截取字符串中任意子串,该方法有两个参数,参数1表示要截取子串的开始位置,参数2表示要截取子串的长度,例如Str1.Substring(2,2)表示截取Str1字符串从第2个字符开始,长度为2的子串。如果参数2表示的长度超过原字符串的边界,则会引发异常。下列代码将字符串Str1从右边第4个字符开始的子串截取,输出结果是“”。string Str1 = "cxj"string Str2=""Str2 = Str1.Substring(4, Str1.Length - 4);Response.Write(Str2);4字符串分析本章范例String中提供一段单词统计的程序,代码如下:char spliter = ' ', ',', '.', ':', '!','/'string text = this.TextBox1.Text; string words = text.Split(spliter);int count = 0;if (wordswords.Length - 1 = "") count = words.Length - 1;else count = words.Length;Response.Write("单词数:"+count.ToString();fore