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#的访问修饰符
访问修饰符 |
描述 |
Public |
使成员可以从任何位置访问 |
Protected |
使成员可以从声明它的类及其派生类内部访问 |
private |
使成员仅可以从声明它的类内部访问 |
internal |
使成员仅可以从声明它的程序集内部访问 |
表1.2:C#数据类型
C#数据类型 |
描述 |
Int |
声明存储整数值的变量 |
String |
声明存储字符串值的变量。 |
float |
声明存储实数值(包括整数和小数部份)的变量 |
变量命名
示例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:数据类型及其默认值
类型 |
默认值 |
Numeric(int、float、short) |
0 |
bool |
False |
char |
‘\0’ |
enum |
0 |
引用 |
null |
对于所有其它变量,编译器将引发错误。因此,在编程的时候尽量在使用变量前就为其赋值。
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 数值数据类型和引用数据类型的常见特征
值类型 |
引用类型 |
|
变量存储 |
实际值 |
引用 |
保存位置 |
内联(椎栈) |
堆 |
默认值 |
零 |
Null |
传递给函数的参数 |
值的副本 |
引用的副本 |
从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. 编写一个结构,以华氏和摄氏的形式存储温度。编写一个函数,转换温度表示形式,如从华氏温度转换为摄氏温度。