1 C#基础知识1.1 简介C#是一门纯面向对象的新语言,这门语言是专为.NET这个平台开发的。它不仅是语言,也是.NET平台不可缺少的组成部份。C#提高了安全性,同时还支持组件对象模型(COM)和基于Windows的API。C#还允许有限制的使用本机指针。 本章主要探讨C#中的基本编程构造和基本数据类型,简单说说装箱和取消装箱的概念,最后讲下怎样编写和编译简单的C#程序。 1.2 C#程序的基本流程请研究下面的示例1,这是一个使用C#编写的“Hello World”程序,运行后将会在屏幕上显示出“Hello World”。现在我们来分析这个程序。 示例1: /*这是我的第一个C#程序*/ using System; class myFirst { public static void Main() { Console.WriteLine(“Hello World”); } } 第一行:此行为注释,注释以“/*”开头,以“*/”结尾。注释可以包含多个行。 第二行:using System;些行与C/C++中的#include语句非常相似。在这里using是导入指令,System是命名空间。我们可以将命名空间视为一组类,System类是所有类的基类,包含大多数应用程序与操作系统进行交第操作时所需要用到的类。请注意C#每条语句后都有一个“;”号。C#中的所有代码行都必须以分号结尾。 第三行:class myFirst中的class是关键字,代表我们定义了一个类,这个类的名字叫做myFirst。基本语法class<name>{},“{”与“}”之间为类的作用域,为了养成良好的编程习惯,在定义类以后就跟上{},以免忘记。 第五行:public static void Main()函数是C#程序的入口点,我们也称为主函数。即,程序开始执行后首先调用的函数是Main()函数。Public访问修饰符代表此函数声明为公共作用域,因此可以从程序中的任何位置对它进行访问。static为静态成员(稍后章节我们会对它进行详细的讨论),void代表此函数不返回任何值。特别注意C#是严格区分大小写。在此行代码中所有关键字都使用小写字母,只有Main()函数的首字母大写。例如:using与Using是完全不同的。 第七行:这一行为主函数的内容,我们也用{}把它们包含在这个主函数的作用域中,Console.WriteLine(“Hello World”);调用了Console类中的WriteLine方法,并将文本“Hello World”作为参数传递。WriteLine函数将文本显示在控制台或DOS窗口中。请注意:Console是属于System命名空间。如果我们没有导入命名空间,即没有写using System;那么我们将以下列方式实现文本打印。 System.Console.WriteLine(“Hello Word”); 那么我们为什么要预先导入命名空间呢?因为使用完全限定名称表示对象可能很乏味,而且易引发错误。为了减轻这一负担,C#提供了using指令。一个程序中可以导入多个命名空间,但必须在文件头部指定。 以上程序只用于帮助理解C#中的程序执行的基本流程。本章后面的部份将更细的讨论C#。 1.3 C#中的变量声明C#中的变量声明格式: AccessModifer DataType VariableName; AccessModifer:访问修饰符 DataTyep:变量类型 AriableName:变量名 各修饰符的访问级别如表1.1 表1.1:C#的访问修饰符
表1.2:C#数据类型
变量命名 示例2: using System; class Test { static void Main() { string @string() @string=“此例中string是一个关键字,但在本例中作用一个变量名”; Console.WriteLine(@string); } } 注意:上面的示例中声明了一个string类型的变量,因为string是关键字,所以我们在变量前加上前缀@以表示这是一个变量名。 与C#中的变量相关的另一功能是,创建静态变量、类实例变量和数组元素时,会自动为其指定一个默认值。请研究下列示例: 示例3: using System; class Test { static void Main() { int[] array1 = new int[5]; Console.WriteLine (10 * array1[2]); } } 以上示例输出为0 表1.3:数据类型及其默认值
对于所有其它变量,编译器将引发错误。因此,在编程的时候尽量在使用变量前就为其赋值。 1.4 C#中的基本输入与输出在执行C#的基本输入和输出的操作时,需要使用System命名空间的Console类的方法。 两个最常用的方法如下所示。 Console.WriteLine() Console.ReadLine() 示例1中已经使用了Console.WriteLine。但是Console.WriteLine的功能十分强大,可以用它在显示文本之前设置文本的格式。它还带有附加参数、占位符,指定显示方式。 请仔细研究下面的示例4。 示例4: using System; class Teest { static void Main() { int number,result; number=5; result=100 * number; Console.WriteLine(“数字100乘上{0}时,结果为{1}”,number , result); } } 示例输出为:数字100乘上5时,结果为500 示例5使用Console.ReadLine() 示例5: using System class InputStringTest { static void Main() { string input; Console.WriteLine(“请输入数字:”); input=Console.ReadLine(); Console.WriteLine(“{0}”,input); } } 示例5中的ReadLine()方法可读取回车符之前的所有字符。输入将以字符串的形式返回。 1.5 C#中的判断语句C#中的条件检查与C中相同,可以使用if结构执行条件分支。请记住,C#语法中,条件永远都是boolean值语法如下: If(expreession) { //表达式的结果为true时执行的语句 } else { //表达式的结果为false时执行的语句 } 代码段1: string str=”条件换算”; if(str) { System.Console.WriteLine(“判断值为True”); } if(str = = “条件换算”) { System.Console.WriteLine(“判断值为 True”); } else { System.Console.WriteLine(“判断值为 False”); } 当编译遇到第一个if语句时,将生成下列错误: 错误CS0029:无法将类型“string” 隐式转换为 “bool” 这个错误的原因是第一个if语句的表达式str的计算结果不是Boolean值。 swich结构的语法如下: switch(weekday) { case 1: Console.WriteLine(“星期一”); break; case 2: Console.WriteLine(“星期二”); break; case 3: Console.WriteLine(“星期三”); break; default: Console.WriteLine(“默认星期日”); break; } 以上语法与C基本想同,只有一个增强功能,它允许将switch结构与字符串一起使用。 1.6 C#中的循环结构(也称迭代结构)C#提供了下列循环结构类型。 while循环 do循环 for循环 foreach循环 除foreach循环外,其它的所有类型都与C中的相应类型相似。以下为各种循环结构的解释。 while循环 while循环执行一组语句,直至指定的条件为false为止。循环条件要求boolean条件。 语法如下: while(condition) { //语句 } 可在while循环中指定break语句跳出循环。continue语句可用于跳过当前这次循环进入下一次循环。 do循环 do循环与while循环非常相似,不同之处是在while循环中,首先计算条件,然后执行语句,而在do循环中,条件是在循环第一次结束时计算的。在do循环中,至少要执行一次循环才会检查条件。语法如下: do { //语句 }while(condition) break和continue语句也可以用在do循环中。 for循环 for循环与其它的循环区别不大。循环变量可以作为for语句的一部份进行声明。 语法如下: for(intialization;condition;increment/decrement) { //语句 } 例子: for( int i ; i <=1;i++) { Console.WriteLine(i); } 变量必须是整型 foreach循环 foreach循环是C#中嫁接自VB的概念,该结构常用于能通过集合或数组来循环。 语法如下: foreach(Type Identifier in expression) { //语句 } 请仔细研究下面的示例6: 示例6: using System; public class ForEachLoop { static void Main (String [] args) { int index; String [] array1=new String [3]; For (index=0;index<3;index++) { array1[index]=args[index]; } foreach (String strName in array1) { Console.WriteLine (strName); } } } 输入参数one two ABC得到one two ABC for循环通过数组args循环,并将其中包含的值指定给数组array1。然后foreach循环通过数组array1循环,并将其中所包含的值一次一个的指定给变量strName。 1.7 C#的构造函数构造函数是特殊的方法,与包含它们的类同名。创建类实例时,首先执行构造函数。下面是语法。 … class MyConstructorEx { public MyConstructorEx() { //MyConstructorEx构造函数 } } … C#中的析构函数 C#中的析构函数与构造函数的编写方式相同,它们与类同名,不同之外是有一个颚化(~)符号作为前缀。那么什么时候会执行析构函数呢?在类的实例被删除或超出程序的作用域时,将执行析构函数。 代码段4: … class MyConstructorEx { public MyConstructorEx() { //MyConstructorEx构造函数 } public ~MyConstructorEx() { //~MyConstructorEx析构函数 } } … 注意:C#中的析构函数虽然与C++中的定义相同,但是行为不同,C#中它们由垃圾回收器调用。垃圾回收器是CLR(.NET运行库)的一种服务。 1.8 C#中数据类型的分类C#中的数据类型分为两个基本类别,即值类型和引用类型。值类型表示实际数据,存储在堆栈中,而引用类型则表示指向该数据的指针或引用,存储在堆上。Int,char,结构等都是值类型。类、接口、数组和字符串都是引用类型。 Using System; Class DataTypeTest { static void Main() { int variableVal=100; funcTest(variableVal); Console.WriteLine(variableVal); } static void funcTest(int variableVal) { int tempVar=10; variableVal=tempVar * 20; } } 输出结果为:100 因为变量variableVal的值在函数funcTest()中被修改后,没有反应到Main()函数。出现这种情况是因为int为值类型,因此当传递至函数funcTest()时,仅传递一个变量的值的副本。 示例8: using System; class DataTypeTest { public int variableVal; } class DataTypeTestRef { static void Main() { DataTypeTest dataTest=new DataTypeTest(); dataTest.variableVal=100; funcDataTypeTest(dataTest); Console.WriteLine(dataTest.variableVal); } static void funcDataTypeTest(DataTypeTest dataTest) { int tempVar=10; dataTest.variableVal=tempVar * 20; } } 输出结果:200 这是因为这一次传递至函数的参数是一个对象。这个对象属于引用类型,因此传递该对象时,传弟的是对象的引用(也称地址),而不是其值的副本。所在以函数内所做的修改会反应到Main()函数。 表 3.4 数值数据类型和引用数据类型的常见特征
从3.4表中可以看出: 值类型的变量保存实际值,而引用类型的变量则保存对象的地址。 值类型内存分配在堆栈上,而引用类型的则分配在堆上。 前面部分中曾讲到C#会为变量分配一个默认值。值类型的默认值为零,而引用类型的默认值为null引用。 对值类型执行“=”运算会将值复制至目标变量,而引用类型执行同一运算则会将对像的引用复制至目标。 1.8.1 装箱和取消装箱简单的说,装箱就是从值类型到引用类型的转换。同样,取消装箱便是从引用类型到值类型的转换。这是C#非常强大的一个功能。可以让你像简单的操作值类型一样操作复杂的引用类型。这对于实现多态性非常重要。 我们来研究下概念,看看代码段5。 代码段5: … class BoxEx { int objsTaker(object objectX) { //objsTaker接收一个对象 //并在此处对其进行处理 } object objsConverter() { //objsConverter执行处理过程 //并返回对象 } } … //代码实现 int variable1; variable1=5; boxVar.objsTaker(variable1);//第1行 int convertVar=(int)boxVar.objsConverter();//第2行 … 上面定义了一个包含两个方法的类。第一个方法objsTaker()将一个对象作为其参数。第二个方法variable1的int型值类型变量,并赋值勤5,创建了一个类BoxEx的新对象boxVar。在指定为“第一行”的行中,调用了objsTaker()方法并将variable1作为参数传递。请注意,objsTaker方法使用的一个对象作为其参数。对象是引用类型,而程序中是将值类型的variable1作为参数传弟。通常情况下这样会产生错误,但在C#中,值类型variable1将被隐式转换为引用类型。这个值类型转换为引用类型的过程(无论是隐式还是显式)即称为“装箱”。在接下来的标记为“第2行”的行中,创建了一个新的名为convertVar的int型变量,然后对该变量与objsConverter方法执行相等运算。ObjsConverter()不带参数,但返回一个对象。由于将方法int类型的值类型变量进行了赋值运算,因此必须将此对象显式地转换为int类型(转换是通过将所需的数据类型括到圆括号中来完成的)。这个将引用类型转换为值类型的过程我们称为“取消装箱”。 特别需要注意的是,取消装箱需要进行显式的转换。运行库将执行检查,以确保指定值类型与引用类型中包含的类型匹配。如果此检查失败,将引发异常,而如果该异常没有得到妥善处理,应用程序将终止。 1.9 数据类型处理C#提供了一个“统一类型系统”。也就是说,C#中的所有数据类型(引用类型或值类型)都是从一个类(object类)派生而来的。这意味着,C#中的一切全都是对象(因为所有类型都是object类的派生物)。 下面我们来阐释这一点。 示例9: using System; class ObjectProff { static void Main() { string objectVal; objectVal=7.ToString(); Console.WriteLine(“该值现在是”+objectVal); } } 此示例输出结果是:该值现在是7 请注意粗体显示部份,7是一个数字,如果是一个字符串,那么我们应该用双引号把它引起来。这里没引,说明一个整型。为何数字可以拥有方法。在C#,一切都是对象,即便是数字7也被认为是对象。因此,它可以拥有方法,而ToString()就是方法之一。此方法是将数字7转换为字符串值。 C#的另一个激动人心的功能:由于所有类型都是由共同的一个类,object 类派生而来,因此他们具有一些相同的特征。 1.10 C#中的静态成员某此情况下,需要在类中定义这样的成员:这些成员不与任何其他特定对象相关联(类本身除外)。也就是说,无论有多少该类的对象,只会有此方法/字段的一个实例。在C#中可以使用关键字static定义静态成员,如下代码所示: 代码段6: … static int staticMem; … static int instanceCount() { //instanceCount实现 } … 1.11 数组数组是一组具有类似数据类型的值。这些值存储在相邻的内存位置,因此访问和操纵这些值更为简便。数组下标由零开始,C#中的数组属于引用类型,因此存储在堆中。语法如下: DataTyep[number of elements] variableName; 下面的代码段显示了一个数组声明示例。 代码段7: … int[6] array1 … 此示例声明了一个名为array1的整型类型数组,该数组包含6个元素。 代码段8: … string[] array2; array2 = new string[5]; … 该示例表明,数组中元素的个数可在程序中稍后部份指定。 代码段9: … string[] array3 = {“top”, “down”,“left”,“right” }; … 该示例表明,可以在声明阶段进行数组初始化。 1.12 结构可以使用类来实现对象。但某些情况下,可能需要使对象具有内置数据类型的行为,以加快分配,避免过多重载和引用开销。这个问题可以通过使用结构来解决。结构与类很相似,都表示可以包含数据成员和函数成员的数据结构。与类不同的是,结构是值类型并且不需要堆分配。结构类型的变量直接包含结构结构的数据,而类类型的变量包含对数据的引用(该变量称为对象)。C#的结构内可以定义方法,并且还可以有一个构造函数。 类与结构有很多相似之处:结构可以实现接口,并且可以具有与类相同的成员类型。然而,结构在几个重要方面不同于类:结构为值类型而不是引用类型,并且结构不支持继承。结构的值存储在“在堆栈上”或“内联”。 下面我们来研究一下下面的代码段: 代码段10: … struct structEx { public int structDataMember; public void structEx() { //构造函数实现 } public void structMethod1() { //structMethod1实现 } } … 上面代码段所示,名为structEr的结构中有一个构造函数、一个方法和一个数据成员。结构也有自己的局限性,它们不能像类那样实现继承。更为重要的是,结构是值类型,而类则是引有类型。在C#中,值类型实际是属于System命名空间的结构。例如:long类型是System.Int64结构的别名。也就是说,使用简单类型时,实际上是在使用基类中的结构。 例如,将 Point 定义为结构而不是类在运行时可以节省很多内存空间。下面的程序创建并初始化一个 100 点的数组。对于作为类实现的 Point,出现了 101 个实例对象,因为数组需要一个,它的 100 个元素每个都需要一个。 class Point { public int x, y; public Point(int x, int y) { this.x = x; this.y = y; } } class Test { static void Main() { Point[] points = new Point[100]; for (int i = 0; i < 100; i++) points = new Point(i, i*i); } } 如果将 Point 改为作为结构实现,如 struct Point { public int x, y; public Point(int x, int y) { this.x = x; this.y = y; } } 则只出现一个实例对象(用于数组的对象)。Point 实例在数组中内联分配。此优化可能会被误用。使用结构而不是类还会使应用程序运行得更慢或占用更多的内存,因为将结构实例作为值参数传递会导致创建结构的副本。 1.13 枚举类型枚举类型是声明一组命名常数的独特类型。当程序中某些数值可以具有一组特定值时,可以使用枚举类型。假设需要使程序仅接受五个值,例如:Monday、Tuesday、Wednesday、Thursday和Friday作为Weekdays的值。要强制实现此要求,仅需要指定包含指定值的枚举类型Weekdays,并编写一个仅接受此枚举数作为参数的方法。下段的代码段对此进行了说明。 代码段11: … public class Holiday { public enum WeekDays { Monday, Tuesday, Wednesday, Thursday, Friday } public void GetWeekDays (String EmpName,WeekDays DayOff) { //处理WeekDays … } static void Main() { Holiday myHoliday = new Holiday(); MyHoliday.GetWeekDays(“Richie”,Holiday.WeekDays.Wednesday); } } … 与C一样,C#中的枚举数拥有与值关联的数字。默认情况下,向枚举数的第一个元素赋值0,而其后各个元素的值较前一个元素的值都递增1。不过这些值可以重写,并且可以在初始化阶段指定其他的值。 代码段12: … public enum WeekDays { Monday=1, Tuesday=2, Wednesday=3, Thursday=4, Friday=5 } … 1.14 编译和执行C#程序首先要运行C#程序必须要有C#运行的环境。即安装Microsoft .NET Framework SDK v1.1或以上版本。 csc.exe是.net用来编译.cs文件的,但必须要在安装目录下使用。 所以我们要设置一下环境变量。 C#环境变量设置: 1、在桌面右击[我的电脑]->[属性]->[高级]->[环境变量] 2、在下面的系统变量栏点击“新建” 3、变量名输入“csc” 4、变量值输入:“C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\” (2000是C:\WINNT\Microsoft.NET\Framework\v1.1.4322\) 5、然后在系统变量列表框中双击“Path” 6、在变量名文本框的最后面加入 ; “%csc%” 恩,现在可以在任意目录下调试.cs文件了。 下面给出命令行示例 编译 File.cs 以产生 File.exe: csc File.cs 编译 File.cs 以产生 File.dll: csc /target:library File.cs 编译 File.cs 并创建 My.exe: csc /out:My.exe File.cs 通过使用优化和定义 DEBUG 符号,编译当前目录中所有的 C# 文件。输出为 File2.exe: csc /define:DEBUG /optimize /out:File2.exe *.cs 编译当前目录中所有的 C# 文件,以产生 File2.dll 的调试版本。不显示任何徽标和警告: csc /target:library /out:File2.dll /warn:0 /nologo /debug *.cs 将当前目录中所有的 C# 文件编译为 Something.xyz(一个 DLL): csc /target:library /out:Something.xyz *.cs 编译 File.cs 以产生 File.dll: csc /target:library File.cs这个就是我们使用最多的一个命令,其实可以简单的写成csc /t:library File.cs,另外的一个写法是 csc /out:mycodebehind.dll /t:library mycodebehind.cs,这个可以自己指定输出的文件名。 csc /out:mycodebehind.dll /t:library mycodebehind.cs mycodebehind2.cs,这个的作用是把两个cs文件装到一个.dll文件里,很有用哦。 1.15 小结C#变量的声明方式如下: AccessModifier DataType VariableName; C#中,通过添加前缀@符号,可以将关键字用作变量名称。此@符号不是标识符的一部份。 在C#,静态变量、类实例变量和数组元素在创建时自动赋值。 选择语句可用于根据表达式的值执行各种操作。 C#中的swhitch语句要求为每个case块都使用一个break语句。 C# 允许将switch结构与字符串一起使用。 C# 提供了下列循环结构类型: while循环 do循环 for循环 foreach循环 在C#中,数据类型分为两种基本类型,即值类型和引用类型。 在C#中,多数基本数据类型(如 int、char),包括结构在内,为值类型。引用类型包括类、接口、数组和字符串。 装箱是指从值类型到引用类型的转换,而取消装箱是指从引用类型到值类型的转换。 C#中的所有数据类型都是从一个类即object类派生而来的。 C#结构内部可以定义方法,也可以拥有构造函数。 枚举类型是声明一组命名常数的独特类型。 1.16 练习1. C#中的标识符可以是保留关建了。 a.对 b.错 2. ___________方法用于接受用户输入。 a.Console.Read b.Console.RealdLine c.Console.WriteLine 3. 在C#中,if结构始终要求条件的计算结果为boolean类型。 a.对 b.错 4. 值类型存储于____________________。 a.堆栈 b.堆 c.队列 5. 与C/C++不同,C#允许结构拥有____________。 a.仅构造函数 b.方法和构造函数 1.17 作业编写C#程序,实现下列目的: 1. 显示一条消息,要求用户输入自己的姓名、雇员编号和部门。接受输入的值并显示在屏幕上。(提示:使用多个变量存储这些值。) 2. 修改上面的程序,以下列方式显示这些值。如果输入的值为Samuel、7576和HR,则输出应为:HR部门的Samuel的雇员编号为7576。 3. 在上述程序中,接受用户输入的值作为命令行参数。对命令行参数编号并将其显示在屏幕上。 假如,假设用户在<Filename>中输入:Samuel 7576 HR 则输出应为 1:Samuel 2:7576 3:HR 4. 在屏幕上显示下面的项列表: 1. 加 2. 减 3. 乘 4. 除 接受用户提供的三个integer值。前两个integer值为数字,按照用户的选择对其进行数学运算,第三个integer值为用户要执行的数学运算。此值不能小于1且不能大于4。根据用户的选择(用户提供的第三个interger值)执行下列运算。 如果此值为 则执行 加 将前两个integer值相加,并显示结果 减 从第一个integer值减去第二个integer值,并显示结果 乘 将前两个integer值相乘,并显示结果 除 用第二个integer除第一个integer,并显示结果 提示:使用switch结构或多个if 语句 5. 编写一个结构,以华氏和摄氏的形式存储温度。编写一个函数,转换温度表示形式,如从华氏温度转换为摄氏温度。
|
|