C#速成

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

C#速成
一、绪论
C#是这样的一种语言,具有C++的特点,象Java一样的编程风格, 并且象Basic一样的快速开发模型。

如果你已经知道了C++,本文会在不到一个小时的时间内让你迅速掌握C#的语法。

熟悉Java的括会更好,因为Java的程序结构、打包(Packages)和垃圾收集的概念有助于你更快的了解C#。

因此在讨论C#的构造时,我会假定你了解C++。

本文会讨论C#语言的构造与特点,同时会采取简洁的和你能理解的方式使用些代码示例,我们会尽量让你能稍微看看这些代码就能理解这些概念。

注意:本文不是为C#高手(C# gurus)所写. 这是针对在C#学习上还是初学者的文章。

下面是将要讨论的C#问题的目录:
程序结构
命名空间
数据类型
变量
运算符和表达式
枚举
语句(Statements )
类(Classes)和结构(Structs)
修饰符(Modifiers)
属性(Properties)
接口(Interfaces)
方法参数(Function Parameters)
数组(Arrays)
索引器(Indexers)
装箱及拆箱操作
委托(Delegates)
继承和多态
下面的内容将不会在被讨论之列:
C++与C#谁更通用
诸如垃圾回收、线程以及文件处理等概念
数据的类型转换
异常处理
.NET库
二、程序结构
这一点象C++,C#是一种对大小写字母敏感的语言,分号“;”是语句间的分隔符。

与C++不同的是,C#当中声明代码文件(头文件)与实现代码文件(cpp文件)不是独立存在的,所有代码(类声明和类实现)都位于一个扩展名为cs的文件内。

让我们瞧瞧C#当中的 Hello world 程序是怎样的。

using System;
namespace MyNameSpace
{
class HelloWorld
{
static void Main(string[] args)
{
Console.WriteLine ("Hello World");
}
}
}
在C#当中的每样东西都被封装到一个类中,C#的类又被封装到一个命名空间当中(就象一个文件夹中的文件)。

类似于 C++,main方法是你的程序的入口点。

C++的main函数调用名称是"main",而C#的main函数是以大写字母M为起点的名称是"Main"。

没有必要把分号分隔符放在类语句块或者结构定义语句块后。

这在C++当中被要求,但在C#当中却不是。

三、命名空间
每一个类都被包装进一个命名空间。

命名空间的概念与C++的完全相同,但在C#当中使用命名空间的频率较C++还高。

你可以使用点限定符(dot qulifier)访问一个类。

在上面的hello world程序当中MyNameSpace就是一个命名空间。

现在思考这样的一个问题,你想从某些别的类的命名空间当中来访问HelloWorld这个类该如何操作。

这有一个例子:
using System;
namespace AnotherNameSpace
{
class AnotherClass
{
public void Func()
{
Console.WriteLine ("Hello World");
}
}
}
现在,从你的HelloWorld类里你能象这样去访问上面的这个AnotherNameSpace的命名空间:using System;
using AnotherNameSpace; // you will add this using statement
namespace MyNameSpace
{
class HelloWorld
{
static void Main(string[] args)
{
AnotherClass obj = new AnotherClass();
obj.Func();
}
}
}
在.NET库当中,System是位于顶层的命名空间,别的命名空间都存在这个命名空间之下。

默认状态下,存在一个全局的命名空间,因此一个在命名空间外定义的类将直接在这个全局命名空间之下;因此,你能在没有任何点限定符的情况下访问这个类。

四、变量
除以下区别外,C#当中的变量几乎与C++同:
与C++不同,C#变量被访问之前必须被初始化;否则编译时会报错。

因此,访问一个未初始化变量是不可能的事。

C#中你不会访问到一个不确定的指针。

(译者注:严格说起来C#已经把指针概念异化,限制更严格。

所以有些资料上会说C#取消了指针概念)
一个超出数组边界的表达式是不可访问的。

C#中没有全局(整个Application)的变量或全局函数,全局方式的操作是通过静态函数和静态变量来实现的。

五、数据类型
所有C#数据类型都派生自基类Object。

这里有两类数据类型:
基本型/内置型用户自定义型
下面一个C#内置类型列表:
类型字节数解释
byte 1 无符号字节型
sbyte 1 有符号字节型
short 2 有符号短字节型
ushort 2 无符号短字节型
int 4 有符号整型
uint 4 无符号整型
long 8 有符号长整型
ulong 8 无符号长整型
float 4 浮点数
double 8 双精度数
decimal 8 固定精度数
string unicode字串型
char unicode字符型
bool 真假布尔型
注意:C#当中的类型范围与C++有所不同;例如,C++的long型是4个字节,而在C#当中是8个字节。

同样地,bool型和string型都不同于C++。

bool型只接受true和false两种值。

不接受任何整数类型。

用户定义类型包括:
类类型(class)
结构类型(struct)
接口类型(interface)
数据类型的内存分配形式的不同又把它们分成了两种类型:
值类型(Value Types)
引用类型(Reference Types)
值类型:
值类型数据在栈中分配。

他们包括:所有基本或内置类型(不包括string类型)、结构类型、枚举类型(enum type)
引用类型:
引用类型在堆中分配,当它们不再被使用时将被垃圾收集。

它们使用new运算符来创建,对这些类型而言,不存在C++当中的delete操作符,根本不同于C++会显式使用delete这个运算符去释放创建的这个类型。

C#中,通过垃圾收集器,这些类型会自动被收集处理。

引用类型包括:类类型、接口类型、象数组这样的集合类型类型、字串类型、枚举类型
枚举类型与C++当中的概念非常相似。

它们都通过一个enum关键字来定义。

示例:
enum Weekdays
{
Saturday, Sunday, Monday, Tuesday, Wednesday, Thursday, Friday
}
类类型与结构类型的比较
除了在内存分配形式上外,类与结构的概念完全与C++相同。

类的对象被分配在堆中,并且通过new来创建,结构也是被new创建但却被分配在栈当中。

C#当中,结构型适于快速访问和
拥有少量成员的数据类型。

如果涉及量较多,你应该创建一个类来实现他。

(译者注:这与堆和栈内存分配结构的特点有关。

简而言之,栈是一种顺序分配的内存;堆是不一定是连续的内存空间。

具体内容需要大家参阅相关资料)
示例:
struct Date
{
int day;
int month;
int year;
}
class Date
{
int day;
int month;
int year;
string weekday;
string monthName;
public int GetDay()
{
return day;
}
public int GetMonth()
{
return month;
}
public int GetYear()
{
return year;
}
public void SetDay(int Day)
{
day = Day ;
}
public void SetMonth(int Month)
{
month = Month;
}
public void SetYear(int Year)
{
year = Year;
}
public bool IsLeapYear()
{
return (year/4 == 0);
}
public void SetDate (int day, int month, int year) {
}
...
}
六、属性
如果你熟悉C++面象对象的方式,你就一定有一个属性的概念。

在上面示例当中,以C++的观点来看,Data类的属性就是day、month和year。

用C#方式,你可以把它们写成Get和Set 方法。

C#提供了一个更方便、简单、直接的方式来访问属性。

因此上面的类可以被写成:
using System;
class Date
{
int day;
public int Day{
get {
return day;
}
set {
day = value;
}
}
int month;
public int Month{
get {
return month;
}
set {
month = value;
}
}
int year;
public int Year{
get {
return year;
}
set {
year = value;
}
}
public bool IsLeapYear(int year)
{
return year%4== 0 ? true: false;
}
public void SetDate (int day, int month, int year)
{
this.day = day;
this.month = month;
this.year = year;
}
}
你可在这里得到并设置这些属性:
class User
{
public static void Main()
{
Date date = new Date();
date.Day = 27;
date.Month = 6;
date.Year = 2003;
Console.WriteLine("Date: {0}/{1}/{2}", date.Day, date.Month,
date.Year);
}
}
七、修饰符
你必须已经知道public、private、protected这些常在C++当中使用的修饰符。

这里我会讨论一些C#引入的新的修饰符。

readonly(只读)
readonly修饰符仅在类的数据成员中使用。

正如这名字所提示的,readonly 数据成员仅能只读,它们只能在构造函数或是直接初始化操作下赋值一次。

readonly与const数据成员不同,const 要求你在声明中初始化,这是直接进行的。

看下面的示例代码:
class MyClass
{
const int constInt = 100; //直接初始化
readonly int myInt = 5; //直接初始化
readonly int myInt2; //译者注:仅做声明,未做初始化
public MyClass()
{
myInt2 = 8; //间接的
}
public Func()
{
myInt = 7; //非法操作(译者注:不得赋值两次)
Console.WriteLine(myInt2.ToString());
}
}
sealed(密封)
密封类不允许任何类继承,它没有派生类。

因此,你可以对你不想被继承的类使用sealed关键字。

sealed class CanNotbeTheParent
{
int a = 5;
}
unsafe(不安全)
你可使用unsafe修饰符来定义一个不安全的上下文。

在不安全的上下文里,你能写些如C++指针这样的不安全的代码。

看下面的示例代码:
public unsafe MyFunction( int * pInt, double* pDouble)
{
int* pAnotherInt = new int;
*pAnotherInt = 10;
pInt = pAnotherInt;
...
*pDouble = 8.9;
}
八、interface(接口)
如果你有COM方面的概念,你会立亥明白我要谈论的内容。

一个接口就是一个抽象的基类,这个基类仅仅包含功能描述,而这些功能的实现则由子类来完成。

C#中你要用interface关键字来定义象接口这样的类。

.NET就是基于这样的接口上的。

C#中你不支持C++所允许的类多继承(译者注:即一个派生类可以从两个或两个以上的父类中派生)。

但是多继承方式可以通过接口获得。

也就是说你的一个子类可以从多个接口中派生实现。

interface myDrawing
{
int originx
{
get;
set;
}
int originy
{
get;
set;
}
void Draw(object shape);
}
class Shape: myDrawing
{
int OriX;
int OriY;
public int originx
{
get{
return OriX;
}
set{
OriX = value;
}
}
public int originy
{
get{
return OriY;
}
set{
OriY = value;
}
}
public void Draw(object shape)
{
... // do something
}
// class's own method
public void MoveShape(int newX, int newY)
{
.....
}
}
九、Arrays(数组)
C#中的数组比C++的表现更好。

数组被分配在堆中,因此是引用类型。

你不可能访问超出一个数组边界的元素。

因此,C#会防止这样类型的bug。

一些辅助方式可以循环依次访问数组元素的功能也被提供了,foreach就是这样的一个语句。

与C++相比,C#在数组语法上的特点如下:方括号被置于数据类型之后而不是在变量名之后。

创建数组元素要使用new操作符。

C#支持一维、多维以及交错数组(数组中的数组)。

示例:
int[] array = new int[10]; // 整型一维数组
for (int i = 0; i < array.Length; i++)
{
array[i] = i;
}
int[,] array2 = new int[5,10]; // 整型二维数组
array2[1,2] = 5;
int[,,] array3 = new int[5,10,5]; // 整型的三维数组
array3[0,2,4] = 9;
int[][] arrayOfarray = = new int[2]; // 整型交错数组(数组中的数组)
arrayOfarray[0] = new int[4];
arrayOfarray[0] = new int[] {1,2,15};
十、索引器
索引器被用于写一个访问集合元素的方法,集合使用"[]"这样的直接方式,类似于数组。

你所要做的就是列出访问实例或元素的索引清单。

类的属性带的是输入参数,而索引器带的是元素的索引表,除此而外,他们二者的语法相同。

示例:
注意:CollectionBase是一个制作集合的库类。

List是一个protected型的CollectionBase 成员,储存着集合清单列表。

class Shapes: CollectionBase
{
public void add(Shape shp)
{
List.Add(shp);
}
//indexer
public Shape this[int index]
{
get {
return (Shape) List[index];
}
set {
List[index] = value ;
}
}
}
十一、装箱和拆箱操作(Boxing/Unboxing)
C#的装箱思想是全新的。

上面提到过所有的数据类型,不论内置或用户自定义,全都从命名空间System的一个基类object派生出来。

因此把基本的或者原始类型转换成object类型被称做装箱,反之,这种方式的逆操作被称为拆箱。

示例:
class Test
{
static void Main()
{
int myInt = 12;
object obj = myInt ; // 装箱
int myInt2 = (int) obj; // 拆箱
}
}
示例展示了装箱和拆箱操作。

一个整型值转换成object类型,然后又转换回整型。

当一个值类型的变量需要转换成引用类型时,一个object的箱子会被分配容纳这个值的空间,这个值会被复制进这个箱子。

拆箱与此相反,一个object箱子中的数据被转换成它的原始值类型时,这个值将被从箱中复制到适当的存储位置。

十二、方法参数
C#中有三种类型的参数:
值参数/输入型参数
引用型参数/输入输出型参数
Out参数
如果你有COM接口和它的参数类型的概念,你会很容易理解C#参数类型。

值参数/输入型参数
值概念与C++相同。

所要传递的值会被复制到一个位置上并被传递给函数。

示例:
SetDay(5);
void SetDay(int day)
{
....
}
引用型参数/输入输出参数
C#中的引用参数既不是C++中的指针也不是引用操作符(&)来传递的。

C#的引用型参数减少了出错的可能。

引用型参数也被称作输入输出参数,因为你传递了一个引用地址,因此你可以从函数中传递一个输入值并且可以获得一个输出值。

你不能把一个未经初始化的引用型参数传递给函数。

C#用ref这个关键字来声明引用型参数。

当你传递一个变量给函数要求的引用参数时必须使用一个ref关键字说明。

示例:
int a= 5;
FunctionA(ref a); // 要用ref声明变量,否则你会得到
// 一个编译错误
Console.WriteLine(a); // 指向地址的值为20
void FunctionA(ref int Val)
{
int x= Val;
Val = x* 4;
}
Out参数
Out型参数仅仅从函数当中返回一个值。

不要求有输入值。

C#用关键字out来描声明这个参数示例:
int Val;
GetNodeValue(Val);
bool GetNodeValue(out int Val)
{
Val = value;
return true;
}
可变数量的参数和数组
数组在C#当中是通过关键字params来描述传递的。

作为数组类型的变量,你能传递任意数量的元素。

从下面示例中你可以理解的更好。

示例:
void Func(params int[] array)
{
Console.WriteLine("number of elements {0}",array.Length);
}
Func(); // prints 0
Func(5); // prints 1
Func(7,9); // prints 2
Func(new int[] {3,8,10}); // prints 3
int[] array = new int[8] {1,3,4,5,5,6,7,5}; Func(array); // prints 8
十三、运算符和表达式
运算符和表达式概念与C++完全相同。

但是一些新的有用的运算符被填加了进来。

我将在这里讨论其中的某些部分。

is 运算符
is 运算符被用于检查操作数的类型是否相同或者是否可以转换。

is 运算符在多态环境下特别有用。

它有两个操作数,运算结果是一个布尔型。

看这个示例:
void function(object param)
{
if(param is ClassA)
//do something
else if(param is MyStruct)
//do something
}
}
as 运算符
as 运算符检查操作数的类型是否可被转换或者是否相等(这些 as通过 is 运算符来完成。

如果结果是可转换的,则结果将被转换或者被装箱,成object(关于as运算符进行装箱成目标类型的操作请看前面的装箱/拆箱操作)。

如果不可转换或者装箱,则返回值是null。

瞧一瞧下面的例子我们会更好地理解这个概念。

Shape shp = new Shape();
Vehicle veh = shp as Vehicle; // 结果是null, 类型不可转换
Circle cir = new Circle();
Shape shp = cir;
Circle cir2 = shp as Circle; //会被转换
object[] objects = new object[2];
objects[0] = "Aisha";
object[1] = new Shape();
string str;
for(int i=0; i&< objects.Length; i++)
{
str = objects[i] as string;
if(str == null)
Console.WriteLine("can not be converted");
else
Console.WriteLine("{0}",str);
}
输出:
Aisha
can not be converted
十四、语句
除了对某些新增语句和对某些语句的修改以外,C#语句与C++非常相象。

下面是新增的语句:
foreach
用于循环依次访问集合元素,比如象数组等。

示例:
foreach (string s in array)
Console.WriteLine(s);
lock
用于锁住代码块,使线程在临界争区内,别的线程无法进入锁定的临界区。

checked/unchecked
用于数值运算中的溢出检测。

示例:
int x = Int32.MaxValue; x++; // 溢出检测
{
x++; // 异常
}
unchecked
{
x++; // 溢出}
}
下面的语句在C#当中已经被修改:
Switch
执行一个case语句后,程序流程不允许跳到下一个相邻case语句。

这在C++当中是被允许的。

示例:
int var = 100;
switch (var)
{
case 100: Console.WriteLine("<Value is 100>");
// 没有break语句
case 200: Console.WriteLine("<Value is 200>"); break;
}
C++编译后的输出:
<Value is 100><Value is 200>
C#下,编译时会报错:
error CS0163: Control cannot fall through from one case label
('case 100:') to another
但是你仍然能做C++类似的事
switch (var)
{
case 100:
case 200: Console.WriteLine("100 or 200<VALUE is 200>");
break;
}
你也可以常数变量作为case 的值:
示例:
const string WeekEnd = "Sunday";
const string WeekDay1 = "Monday";
....
string WeekDay = Console.ReadLine();
switch (WeekDay )
{
case WeekEnd: Console.WriteLine("It's weekend!!"); break; case WeekDay1: Console.WriteLine("It's Monday"); break; }
十五、委托
委托让我们把一个函数引用存储在一个变量里。

C++当中,这类似于使用typedef定义的函数指针,我们通常用存储一个函数指针。

声明委托使用的关键字是 delegate。

瞧瞧这个示例,你会理解什么是委托:
示例:
delegate int Operation(int val1, int val2);
public int Add(int val1, int val2)
{
return val1 + val2;
}
public int Subtract (int val1, int val2)
{
return val1- val2;
}
public void Perform()
{
Operation Oper;
Console.WriteLine("Enter + or - ");
string optor = Console.ReadLine();
Console.WriteLine("Enter 2 operands");
string opnd1 = Console.ReadLine();
string opnd2 = Console.ReadLine();
int val1 = Convert.ToInt32 (opnd1);
int val2 = Convert.ToInt32 (opnd2);
if (optor == "+")
Oper = new Operation(Add);
Else
Oper = new Operation(Subtract);
Console.WriteLine(" Result = {0}", Oper(val1, val2));
}
十六、继承和多态
C#仅允许单继承,多继承要通过接口来实现。

示例:
class Parent
{
}
class Child : Parent
{
}
十七、虚拟方法
除了在子类中实现虚拟方法采用override关键字外,虚拟方法实现多态的概念C#与C++相同。

父类使用相同的virtual关键字。

从重载虚拟方法的每个类都要使用override关键字。

class Shape
{
public virtual void Draw()
{
Console.WriteLine("Shape.Draw") ;
}
}
class Rectangle : Shape
{
public override void Draw()
{
Console.WriteLine("Rectangle.Draw");
}
}
class Square : Rectangle
{
public override void Draw()
{
Console.WriteLine("Square.Draw");
}
}
class MainClass
{
static void Main(string[] args)
{
Shape[] shp = new Shape[3];
Rectangle rect = new Rectangle();
shp[0] = new Shape();
shp[1] = rect;
shp[2] = new Square();
shp[0].Draw();
shp[1].Draw();
shp[2].Draw();
}
}
输出t:
Shape.Draw
Rectangle.Draw
Square.Draw
十八、使用"new"来隐藏父方法
你可以定义一个子类成一个新方法版本,隐藏基类当中的那个版本。

使用new关键字就可以定义一个新版本。

思考下面的示例,它是上面示例的修改后的版本。

注意当我用Rectangle 类中的new关键字代替override关键字时示例的输出情况。

class Shape
{
public virtual void Draw()
{
Console.WriteLine("Shape.Draw") ;
}
}
class Rectangle : Shape
{
public new void Draw()
{
Console.WriteLine("Rectangle.Draw");
}
}
class Square : Rectangle
{
//没在这里让你重载
public new void Draw()
{
Console.WriteLine("Square.Draw");
}
}
class MainClass
{
static void Main(string[] args)
{
Console.WriteLine("Using Polymorphism:");
Shape[] shp = new Shape[3];
Rectangle rect = new Rectangle();
shp[0] = new Shape();
shp[1] = rect;
shp[2] = new Square();
shp[0].Draw();
shp[1].Draw();
shp[2].Draw();
Console.WriteLine("Using without Polymorphism:");
rect.Draw();
Square sqr = new Square();
sqr.Draw();
}
}
输出:
Using Polymorphism
Shape.Draw
Shape.Draw
Shape.Draw
Using without Polymorphism:
Rectangle.Draw
Square.Draw
这里的多态性不会把Rectangle类的Draw方法当做Shape的Draw方法多态性的一种表现。

相反,它会认为这是一种不同的方法。

因此,为了避免父类与子类间的命名冲突,我们使用了new修饰符。

注意:你不能使用同一类下面一种方法的两个版本,即一个是用new修饰符的版本,另一个是用override或virtual修饰符的版本。

正象上面示例所说明的,我不能再在拥有virtual 或override方法的Rectangle类中添加另一个命名为Draw的方法。

同样地,在Square类中,我也不能重载Square类的虚拟的Draw方法。

十九、调用基类成员
如果子类与基类有同名的数据成员,为避免命名冲突,访问基类数据成员和函要使用一个关键字base。

在下面的示例中我们来看看如何调用基类的构造函数以及如何使用数据成员。

public Child(int val) :base(val)
{
myVar = 5;
base.myVar;
}
或者
public Child(int val)
{
base(val);
myVar = 5 ;
base.myVar;
}。

相关文档
最新文档