天天看点

从C/C++到C# (1)

    用了10多年的C/C++,因为之前一直在Linux下作项目,对C#少有涉猎。今年终于开始要正式学习C#了,将学习过程中的一些体会和笔记摘录成文和大家共享,由于个人经验的原因,本文可能更多地着眼在C#和C/C++不同的地方。本系列的第一篇主要列举了在基础类型、语句定义、函数方法等方面的差异。

  • C#中程序的入口点方法名为Main,这个函数必须使用static属性。
  • C#中所有的方法必须位于class内部。
  • Visual C#中的Windows Form程序的Form都有Code View和Design View两种视图。不要去改动*.Designer.cs文件,对这些文件的改动会在你使用Design View的时候被Visual Studio自动覆盖。
  • C#中所有的变量在使用前都必须被赋值/初始化,这被称为Definite Assignment Rule。

           int age;

           Console.WriteLine(age); // compile time error

  • C#中,带小数点的数缺省类型为double而不是float。
  • C/C++中,你不能对浮点数使用%,但是在C#中可以。
  • C#中每一个class都有一个ToString方法,你可以自己定义这个方法的实现。
  • C#中,如果一个方法没有返回值,必须使用void明确说明,不可省略。
  • C#方法中,你必须在使用一个变量前必须声明;但是方法可以在Class中的field定义语句之前使用相应的field。
  • C/C++中,你可以在if语句的判定条件中使用int表达式等来隐式转换bool;但是在C#中,你只能在判定表达式中使用bool类型表达式。
  • C#中的switch语句必须遵循如下要求:如果你得case条件下包含可执行语句,就必须包含break;只有不包含任何语句的case分支可以不使用break而fall through到下一个分支;default语句也必须包含break,且不一定是最后一个分支。
  • C#中有for each语句来遍历某个集合。
  • C#使用try{}catch{}finally{}来处理异常;发生异常时,第一个匹配的catch块被执行,其他catch块被忽略;在catch一个异常后,执行回到catch块的后一条语句,而不是产生异常的方法。
  • 缺省情况下,C#允许计算自然溢出,你可以通过checked{}/checked()和unchecked{}/unchecked()来局部调整这个设置,也可以通过build option来改变程序整体的设置。只有直接位于checked{}块内的语句会进行溢出检查,而块内语句调用的方法不进行检查。checked和unchecked只能用于整数,不能用于浮点。如果exception被本地catch,就先执行catch块,然后执行finally块;如果exception要被传递到上层,就先执行finally语句,然后再传递exception。
  • C#中,class内的变量被称为field。Class是类型,object是实例。C#的class中方法和变量的缺省属性是private。和C++不同的是,public/private/protected关键字后没有冒号,你必须对每一个声明都使用对应的关键字。C#构造函数不能返回任何值(void也不行),如果你不定义构造函数,编译器会自动生成一个缺省构造函数(无参数);如果你定义了任何构造函数,编译器就不再生成缺省构造函数。
  • C#中,class中的field都会被自动初始化成0/false/null。
  • 从C# 2.0开始,可以将一个class的代码切分到多个文件中,这时对每个文件都必须使用partial关键字声明class。
  • C# class中操作某个特定class实例内数据的方法称为instance method;static方法/变量不需要依附于instance,直接使用class名访问,static方法不能访问任何instance field/method,只能使用static field/method;如果你使用const关键字来声明field(这个field只能enum/primitive type/string),则这个field自动是static的。
  • C#中static class只能包含static成员;对static class生成实例是没有意义的;static class只能由static的缺省构造函数
  • C#中所有的primitive type和struct都是value type,使用中是按值传递的,使用的空间在declare的时候就分配;所有的class都是reference type,使用中按照引用传递,他们的实际空间只有在new的时候分配;在C#中,所有的引用都是强类型的,不能将一个引用变量指向一个不同类型的空间(C/C++中的指针几乎可以指向任何空间)。
  • C#中,当你向一个方法传递参数的时候,对应的参数被初始化成传入数据的拷贝(不论传入的是value type还是reference type);如果你在一个参数前加上ref关键字,参数就变成了传入值得alias而不是copy(ref关键字在定义和调用方法的时候都要出现),在使用ref参数的时候,使用之前必须赋值的规则一样适用;如果你想要在方法内部初始化参数,并将其传回给调用者,可以使用out关键字,这时参数也是alias而不是copy(这时方法内部必须初始化这个参数)。
  • 你调用方法的时候,其参数和内部变量都在stack中分配;当你创建一个class实例的时候,其使用的内存位于heap;所有的value type都创建在stack,所有的reference type都创建在heap(但是指向reference type空间的变量还是位于stack)。Stack是先进后出的,heap是一个flat的空间,自由分配和回收。
  • C#中所有的class都是System.Object的子类,System.Object/object类型的变量可以refer向任何reference type;object类型的变量也可以refer向value type,但是因为所有的reference必须位于heap,所以heap中会分配一块区域拷贝value type的value,然后让object变量指向heap中的这块区域。这种自动将stack中的item拷贝到heap的行为称为boxing。通过cast将object中指向的value type重新赋值给某个value type变量(二者的类型必须严格匹配,即使能隐式转换也不行)的过程叫unboxing。
  • 如果你想要在C#使用指针,必须在方法内部使用unsafe{}将使用指针的代码包含起来,或者对方法本身使用unsafe修饰;同时在编译代码的时候必须使用/unsafe选项。
  • C#中,enum和struct都是value type;可以指定enum的基础类型(byte/sbyte/short/ushort/int/uint/long/ulong);struct可以包含field/method/constructor;你不能为struct声明一个default constructor,因为编译器总是会替你生成一个(将所有的field设置成0/false/null),如果你自己定义了struct的非default constructor,其中必须明确初始化所有的field(class则不必);在class中,可以在声明instance field的时候初始化,struct则不允许;class可以有base class,struct不能继承;为了确保struct类型变量中所有field都被初始化,也要和class类型的变量一样使用new来调用构造函数;在初始化了某个struct类型变量后,可以将其assign给另一个struct变量。
  • C#中使用int[],int[,]来定义Array变量(不必说明大小),使用new来实际分配空间(说明大小,打下可以是动态生成的,同时会自动初始化元素);Array是reference type;可以如下初始化int[] pins = new int[4]{ 9, 3, 7, 2 }/int[] pins={9,3,7,2},初始化数值的个数必须和Array的size严格匹配;所有对Array的访问是有边界检查的;Array具有Length属性,可以用于确认其中包含了多少元素;foreach总是从Index 0开始只读地遍历整个Array;Array具有CopyTo/Copy/Clone方法用于拷贝Array的元素到另一个Array。
  • C#中Collection位于System.Collections,其包含的元素类型是object(这意味着你向其中插入/删除value type的时候总是需要boxing/unboxing)。
  • 如果想要动态操作Array,包括改变大小、插入、删除元素,最好使用ArrayList类(具有Remove/RemoveAt/Add/Insert方法,也具有Count属性);如果要实现一个FIFO的队列,可以使用Queue类,包含Enqueue/Dequeue方法;如果要实现一个LIFO的栈,可以使用Stack类,包含Push/Pop方法;如果想实现哈希表,可以使用Hashtable类,使用foreach遍历的时候得到DictionaryEntry类,其包含key/value元素;如果要实现一个排序列表,可以使用SortedList类,其也包含key/value,但总是按照key自动排序的
  • Array永远是可读可写的,而Collection中的所有类可以通过ReadOnly方法变为只读。
  • 如果你的方法希望使用可变数量的参数表,可以通过Parameter Array的方式,例如public static int Min(params int[] paramList),这点类似于C/C++中的varargs宏(位于stdarg.h);参数数组只能是一维的,方法重载的时候不能生成仅有params关键字不同的方法(也不能生成有潜在模糊性的重载函数),params array不能使用ref或out修饰,params array必须是最后一个参数,在函数重载的时候,non-params的方法总是优先于params方法。
  • 使用params object[]作为参数就可以向方法传递任意数量、任意类型的参数。

(待续:本系列第二篇将主要描述C#中继承、接口等方面的差异性)

继续阅读