1 基本语法的学习
与法学习主要包含了以下几个方面:
(1) 基本的数据类型;
(2) 字符串;
(3) 控制语句;
(4) c#特殊的关键字;
(5) 异常处理机制;
1.1 基本的数据类型
比较特殊的几个类型有:decimal;非齐整数组;
定义常量的方式:
(1)常量
public const double gravitationalConstant = 6.673e-11;
不要和直接量混合了。
所谓的直接量其实就是直接给出一个数值,类似于汇编里头的立即数。
(2)变量的作用域;
这点和delphi稍微有点不同,在c#中,变量的作用域是按照大括号来划分的,比如:
public static void main()
{
int x=10;
if (x==10)
{
int y=100; //这个变量只在这个if语句内部有效
}
}
(3)类型转换
类型转换主要分为:自动类型转换和强制类型转换。
强制类型转换采用和java一样的方式;
int I=(int)(10/3);
也就是这个类型一定要用括号括起来。
自动类型转换则必须满足两个条件:格式兼容;目标类型的取值范围大于源类型。
1.2 数组
数组,c#中的数组是作为对象来实现的,所有具备了许多优点
数组的定义格式如下:
int[] testArray = new int[10];
数组可以在创建的时候初始化;
int[] nums = {100,101,102,103,104,105,106,108,109};
等同于
int[] nums = new int[] {100,101,102,103,104,105,106,108,109};
还等同于
int[] nums = new int[10] {100,101,102,103,104,105,106,108,109};
多维数组的声明,感觉有点怪异:
int[,] Two = new int[3,4];
int[,,] Three = new int[3,4,5];
非齐整数组;就是允许每一维的长度不同;
声明的格式如下:
int[][] jagged = new int[3][];
jagged[0] = new int[3];
jagged[1] = new int[4];
jagged[2] = new int[5];
这样的格式我好像也在java里头看到过,不过有点不确定了。
数组的length属性;
这里又分为一般的数组和非齐整数组,非齐整数组其实是数组的数组;
foreach循环;
在循环的过程中,迭代变量是只读的,因为不能通过迭代变量来改变数组的内容。
使用方法如下:
int[] temp = {3,4,5} ;
foreach(int x in temp)
{
Console.WriteLine(“temp[i] is : {0}”,x);
}
1.3 字符串
字符串的格式化输出;
Console.WriteLine(“My Name is {0}”,”Yangguichun”);
在c#中,字符串并不像在其他的语言中一样,是字符的数组,它是对象。我记得java中的字符串也是对象。毫无疑问,c#学的是java的那一套,因为将字符串作为对象在java中已经被验证是可行的,所以为什么不用呢。既然学习的是java的那一套,我估计java中给字符串提供的那一套方法在这里也是有的。
虽然字符串不是字符的数组,但是字符串还是允许使用如下的形式;
string s =” this is a string”;
Console.WriteLine(s[0]); //将输出t
不过却有点例外,就是不能够通过下标来给字符串赋值。因为据说一个字符串一旦创建,就不能修改了,是个常量。
字符串还允许使用==和!=,因为字符串类重载了这两个运算符。其他的运算符则和一般的应用一样,比较的是引用。
字符串一旦创建之后是不可改变的,如果要使用可改变的字符串,那么可以使用System.Text中的StringBuilder类。
在switch中可以使用字符串,这是所学过的其他语言中都不具备的功能,这个功能很棒。
2 面向对象特性的学习
面向对象特性的学习主要包含以下几个方面:
(1) 接口;
(2) 虚方法;
(3) 面向对象方面的特殊的关键字;
2.1 类、对象和方法
2.1.1 重载
首先来看一下重载,c#中的重载和delphi也有些不同,首先它并不需要overload关键字;其次一般方法的重载和构造函数的重载有所不同,重载的构造函数中的一个要调用另外一个的方法是:
class TestConstructor
{
public TestConstructor()
{
Console.WriteLine("Constructor 1");
}
public TestConstructor(string name):this()
{
Console.WriteLine("Constructor 2 "+ name );
}
}
而一般函数的方式则和delphi的相似。
class TestConstructor
{
public TestConstructor()
{
Console.WriteLine("Constructor 1");
}
public void TestOverload()
{
Console.WriteLine("overload 1");
}
public void TestOverload( string name )
{
Console.WriteLine( " before " );
TestOverload();
Console.WriteLine( " after " );
}
public static void Main()
{
TestConstructor test = new TestConstructor("3");
test.TestOverload("test");
}
}
则输出为:
before
Overload 1
after
2.1.2 ref和out关键字。
这两个关键字和delphi的var和out关键字相似,ref和var唯一的不同在于:如果某个方法的参数有ref,那么在调用该方法的时候传送进来的参数也要用ref来修饰。
此外,对象的因用也可以用ref来修饰。
2.1.3 可变元参数
(1)一般函数的变元
有些类似于delphi的format函数的那个参数的声明方式。
在这里采用的声明方式为:
public void TestParams(params int[] args)
{
for (int i = 0; i < args.Length; i++)
{
Console.WriteLine(args[i]);
}
}
这里给出的只是int类型的情况,其实和delphi的format函数的参数还不完全一样,如果要在一个数组中兼容各种类型,那么就要采用如下的声明方式。
class TestParams
{
public void Test(params object[] args)
{
for (int i = 0; i < args.Length; i++)
{
Console.WriteLine(args[i]);
}
}
}
(2)Main函数的变元
这个变元和一般函数的变元有所不同,格式如下:
public static void Main(string[] args)
{
for (int i = 0; i < args.Length; i++)
{
Console.WriteLine(args[i]);
}
}
2.1.4 函数的返回类型可以是数组
以前我所学过的其他的语言的函数都无法返回数组,但是c#可以,因为在c#中,数组是作为对象来实现的。
public int[] TestArray()
{
int[] result = new int[10];
for (int i = 0; i < result.Length; i++)
{
result[i] = i;
}
return result;
}
如果要返回多维数组,则用
public int[,] TestArray()
{
…
}
2.2 索引与属性
(1)索引
索引的主要用途就是支持那种受特殊条件限制的数组的创建。
声明的方式为:
public int this[int index]
{
get
{
if (index< MyArray.Length)
return MyArray[index];
else
return -1 ;
}
set
{
if (index
MyArray[index] = value;
}
}
索引不仅有一维的,还有多维的,这是c#比delphi进步的地方。
(2)属性
这里的属性和delphi的属性其实是一回事,因为c#的设计者也正是delphi的设计者,所以他会把在设计delphi是的许多思想带到这里来。
这里的get和set就类似于delphi中的read和write;
和delphi一样,这里也允许只存在set和get只存在一个,这样就变成了只读或者只写的。
属性的定义格式如下:
class TestProperty
{
string FName;
public string Name
{
get
{
return FName;
}
set
{
FName = value;
}
}
}
2.3 继承
将到继承可能主要会想到以下几个方面的内容:
(1) 子类如何访问父类的成员变量;
(2) 虚方法的使用;
2.3.1 父类成员变量的访问
在c#中要访问父类的成员变量有两种方法:通过属性;通过protected关键字。
这和delphi当然有些类似,可是又有些不同,在delphi中子由于存在友元类型,所以如果父类和子类在同一个单元中,那么不管父类的成员变量是不是私有的,子类都可以访问。而在c#这样是不允许的。
2.3.2 虚方法的使用
在c#中,如果子类override了父类的方法,但是子类又想要调用父类的这个方法,格式如下:
base.MethodNamexxx,和delphi 大同小异,delphi中用的是inherited关键字。
2.3.3 Base关键字的使用
在c#中有this和base,在delphi中有self和inherited。这两个是一一对应的。
2.3.4 Sealed关键字
该关键字用来定义类,以防止该类被继承。这个和java中的finally是一样地,在java中也允许这种不允许被继承的类的声明。
Sealed和abstract是互斥的。
2.3.5 object关键字
理论上object只是System.Object的另一种表达方式,它是所有类的父类。
Object存在一个封箱和拆箱的操作;
所谓封箱指的是:将一个一般数据类型赋值给object变量,因为object是对象,所以如果把一个数值给他,就要经过一个一般类型到类的转换过程;
所谓拆箱指的是:将object类型的变量赋值给一个一般类型数据变量。
另外,由于object的存在,使得一个object类型的数组用来存储多种数据类型成为可能。就和delphi的Variable类型的效果一样。
例子如下:
class TestObject
{
object[] OArray = new object[7];
public TestObject()
{
for (int i = 0; i < 5; i++)
{
OArray[i] = i;
}
OArray[5] = "name";
OArray[6] = "age";
for (int i = 0; i < OArray.Length; i++)
{
Console.WriteLine(OArray[i]);
}
}
}
2.4 接口、结构和枚举
2.4.1 接口
接口定义了签名不定义实现,对我来说java,delphi,c#的接口功能都是大同小异的。我一向很少使用接口,所以也没法看出他们有什么不同。但是基于接口的编程早已经是大势所趋了,我要开始学习了。
在c#中,接口是允许有属性的。这里的属性也只是定义了签名不定义实现。来看一下定义的方式:
interface Person
{
int Age { get;set;}
}
还有更厉害的,c#中的接口还允许有索引。还是一样,只定义签名不定义实现。
interface Person
{
int this[int index]
{
set;
get;
}
}
如果一个类实现了多个接口,那么这些不同的接口可能会有一些方法的名字是相同的,此时就要采用所谓的显式实现 ,所谓显示实现格式如下:
interface ITestInterface
{
int cal(int x,int y);
}
class TestInterface : ITestInterface
{
int ITestInterface.cal(int x,int y ) //显示实现
{
return x * y;
}
}
显示实现的代码在类的实例中是不能直接调用的,因为它是私有的,但是可以通过借口的对象来调用。如以下的代码是合法的。
public static void Main(string[] args)
{
TestInterface temp = new TestInterface();
ITestInterface tempI = temp;
Console.WriteLine(tempI.cal(3,4));
}
在.net的框架中定义了大量的用户可以使用的接口。
2.4.2 结构(struct)
在c#中的结构和delphi的结构的声明就有些不同了,下面给出一个例子,很快就一目了然了。
struct Person
{
public string Name;
public int Age;
public string Tel;
public Person(string name, int age, string tel)
{
this.Name = name;
this.Age = age;
this.Tel = tel;
}
}
是不是很怪,他竟然还有一个构造函数。
而且使用的时候也是采用new的形式。
此外结构也可以使用属性;
这里要说明一下结构和类的区别:和类一样,结构可以有属性,有方法有构造函数,结构可以继承一个或多个接口;但是结构不能被继承也不能继承别的类和结构,结构的成员不能是abstract,virtual,protected。我觉得结构不能继承和被继承是他和类的最大的区别。
2.4.3 枚举
枚举没什么好说的,和delphi的差不多。
2.5 异常处理机制
System.Exception
四个关键字:Try catch throw finally.
捕捉了异常之后会有副作用:避免了程序的异常中止。
异常被处理之后就直接从系统中删除。
要捕捉所有的异常,只要使用一个没有参数的catch语句就可以了。
Try语句是允许嵌套的。
和java一样,throw是用来抛出异常的。
有的时候捕捉到异常不想处理,就可以重新抛出,这时只要在这个catch语句内简单条用一个throw就可以了。
自定义异常类其实没什么,十分的简单,和delphi中的大同小异,而且一般来说自定异常类并不需要附加什么功能,除了名字不同之外,其他基本相同。
C#有两个关于算术计算的关键字:checked和unchecked,所谓的checked就是说要检查某个赋值操作是否溢出,比如把1000赋值给一个byte类型的变量,那么就会产生溢出,而unchecked与checked相反,不检查这类溢出。
默认情况下是要检查溢出的。也就是会抛出溢出的异常。
2.6 事件
大概看了一下,c#中的事件的概念和delphi中的大同小异,其实也就是那天看的那个委托(delegate),还可以说是面向对象中的函数指针,但是c#中的这个函数指针比delphi的更加强大,他提供了多重委托的能力。也就是一个委托可以挂接多个方法实体,这样调用一次这个委托就同时调用了多个方法,我估计这个委托对象的内部有一个数组。
Delegate其实是一个类。我的理解是,当一个委托对象被赋值了一个方法引用之后,其实是把该方法引用存储在该委托对象内部的一个数组中,由于委托对象内部用来存放方法引用的是一个数组,所以可以允许多重委托的存在。
2.7 组织应用程序
(1) C#程序是通过名字空间来组织应用程序的。
(2) 动态连接库的一个用途是:软件产品的国际化,开发人员将依赖于各国语言的资源分离开来,各自放进专门的动态链接库中。各种不同的用户可以在安装和使用的使用调用不同的动态链接库。这样主程序就可以不经改变而在全球范围使用。
(3) 编译单元的using指示符只对本单元的名字空间和成员声明产生影响。
(4) 名字空间提供了一个逻辑上的层次体系结构。
(5) 装配用于应用程序的打包和部署。装配有两种形式:exe,dll。
两种编译的指令如下:
csc /target:library HelloWorld.cs //编译成dll
csc /reference: HelloWorld.cs //编译成exe
2.7.1 名字空间
(1)名字空间是可以嵌套的;
namespace N1
{
namespace N2
{
class Sample
{
}
}
}
等同于
namespace N1.N2
{
class Sample
{
}
}
(2)名字空间不能使用访问修饰符号来修饰,但是他其实默认采用了public修饰符。
(3)名字空间内的类型的使用。
假如存在如下代码:
namespace Myprogram.CSharp.FirstApp
{
class HelloWorld
{
}
}
那么在另外一个单元中要使用该HelloWorld类时,则应该采用Myprogram.CSharp.FirstApp.HelloWorld的形式,不过我们一般采用先导入名字空间的方式,也就是在这个单元的开始先:
using Myprogram.CSharp.FirstApp;
这样在后面要使用HelloWorld的时候,就只要直接使用名字就可以了.
2.7.2 using指示符
(1) 别名;
所谓别名就是为某个名字空间或者类型定义一个别名,这样在这个名字空间但中,凡是遇到这个别名指的就是那个名字空间或者类型。下面是一个简单的例子。
namespace N1.N2
{
class A
{ }
}
namespace N3
{
using A = N1.N2.A;
class B:A
{ }
}
上面的例子的第二部分还等同于:
namespace N3
{
using N = N1.N2;
class B: N.A
{ }
}
使用名别的时候有一点要注意:就是别名不能和本名字空间中的某一个类型或者别名同名。
别名在哪里声明就在哪里有效,离开了就无效。
(2)使用指示符
如果某个名字空间主体中定义的成员和名字空间using指示符导入的成员名字相同,则外部的成员被隐藏。
2.8 学习c#的流
一提起流就不可避免的想起java的流控,那真是。。。。。乱成马了。
和java的类似,c#的流控也牵涉到字节和字符,字节到字符、字符到字节的转换问题。
C#中的标准数据流是字符。
字符数据流逻辑上是独立的,但实际上是基于字节数据流的。他将默认字节数据刘转换为字符数据流,自动处理各种转换过程。
2.8.1 FileStream(用来读取字节的)
FileStream的一些相关信息;
FileMode.Append,FileMode.Create……….
FileAccess.Read ; FileAccess.Write ; FileAccess.ReadWrite
我觉得这样的形式特别好,编写程序的时候不会容易忘记名字。
这种基于字节的文件有一个好处就是可以用来读取任何类型的文件,用来复制拷贝任何类型的文件。
和java中不同,在java中如果要对一个文件进行随机存储,则要新建一个随机存储的类的对象,在c#中没有这么麻烦,直接用FileStream就可以了,该类提供了一个seek方法,使用这个方法就可以定为到文件的任何位置。
2.8.2 基于字符的文件i/o
对于想要存取unicode编码的字符的操作时,使用基于字符的数据流似乎就更加合适一些了。对于这样的文本,一不小心就可能造成乱码。
和java、delphi一样,c#中的流也是允许嵌套的。
比如允许字符流包装字节流,这样输出来的就是字符,当然反过来估计也可以。
2.8.3 重新定向标准数据流
这里提到的主要是console,他主要提供了一下三个方法:
SetIn(TestReader input)
SetOut(TestWriter output);
SetError(TextWriter output);
2.8.4 读写二进制数据
首先需要理解下面一点:
使用数据的内部二进制格式,而不是用户可读的文本格式来读些这些数据。
主要在调用了write之后,一般要调用flush(),才会真正的将数据写入到文件中。
在前面的各种流中,允许用户将各种数字类型的变量直接输出到终端,并且一般是以字符的形式来显示,可是c#并不提供反向操作。
还好,在c#中提供了一系列的类似于delphi中的strToInt这类的函数,不过在c#中组织的更为严密,比如要将一个字符串转换为一个32位的整数,那么就可以采用Int32.Parse来对这个字符串进行转换。
这里还要另外说明一点,由于c#和.net的紧密关系,所有的c#的数据类型都对应着.net框架中的一个类型,而.net中的类型是通过结构的形式来实现的。下面给出一个对应表:
.net |
C# |
Decimal |
decimal |
Double |
double |
Single |
float |
Int16 |
short |
Int32 |
int |
Int64 |
long |
UInt16 |
unshort |
UIint32 |
uint |
UInt64 |
ulong |
Byte |
byte |
SByte |
sbyte |
其实是否了解这个表没有关系,所有的parse都可以通过对应的c#类型来调用。
2.9 委托与事件
把事件作为单独的一章来学习,与他的重要性相比一点也不过分;
代理与事件是c#的第二个革新性质的特征。
所有的代理都是从System.Delegate 类派生出来的。
2.9.1 使用代理的两点好处
(1)代理支持事件;
(2)代理允许程序在运行时执行方法而无需确切知道编译时该方法是什么。
2.9.2 事件
什么事件呢?事件就是出现某种行为的自动通知。
和代理一样,事件也是允许多点传送的,所谓的多点传送就是允许一个事件对象挂接上多个事件处理函数。这样就允许多个对象来相应事件。
如果将一个静态方法作为事件处理方法,那么这个事件发生的时候将把事件通知到该类的所有对象。其实大多数的情况都只是使用实例方法。
除了一般的采用:
delegate void MyEventHandler(int Num);
class MyEvent
{
public event MyEventHandler SomeEvent;
public void OnSomeEvent()
{
if (SomeEvent != null)
SomeEvent(3);
}
}
这样的形式来定义事件以外,还可以采用下面的形式:
delegate void MyEventHandler(int Num);
class MyEvent
{
MyEventHandler[] e = new MyEventHandler[3];
public event MyEventHandler SomeEvent
{
add
{
e[0] = value;
}
remove
{
if (e[0] == value)
e[0] = null;
}
}
public void OnSomeEvent()
{
for (int i = 0; i < 3; i++)
{
if (e[i] != null)
e[i](i);
}
}
}
这就类似于属性的定义方式。
2.9.3 .net事件
void handler(object source, EventArgs arg) {//…}
2.10 运行时类型id、映射和属性
这其实又是.net的三个功能强大的特征。
2.10.1 三个关键字is、 as、 typeof
用as进行类型转换的时候即时无法转换也不会抛出异常,只会返回null而已。
其中typeof关键字要好好关注一下,这方面的东西我在学习java和delphi的时候没有留意。Typeof返回的是一个System.Type类型的对象,用该对象就可以获得许多关于那个类型的信息。
感觉这章好难啊。
2.10.2 使用映射
我尝试使用getMethods()的参数BindingFlags.DeclareOnly,可是一个方法也显示不出来。
阅读了一些关于反射的程序,暂时也不知道这些东西在实际中都有什么用,不过总体感觉他的反射功能比delphi的强大。常用的一些属性和方法有:
Type类的getMethods(),getConstructors()。
MethodInfo类的getParameters()方法。
Type类,ParameterInfo类,MethodInfo类,ConstructorInfo类。
大概就是这些,至于实际应用中怎么应用这些东西,就等到实际应用的时候再来学吧,现在只要知道有这么一个东西就可以了。