C# language Study - 1
本节讲叙了一些基本的C#语法上的知识。这些知识一般都是在C语言中接触不到的,最基础的,C语言中也存在的知识点,就略过不提了。
1. C#中预定义的值类型
a.整型
sbyte -- System.SByte : 8位有符号整数
short -- System.Int16 : 16位有符号整数
int -- System.Int32 : 32位有符号整数
long -- System.Int64 : 64位有符号整数
byte -- System.Byte : 8位无符号整数
ushort-- System.UInt16 : 16位无符号整数
uint -- System.UInt32 : 32位无符号整数
ulong -- System UInt64 : 64位无符号整数
以上,整数可以用10进制或16进制进行赋值。如
int i = 10; long ul = 0x12b
如果一个显式整数,默认为int类型,但是可以在整数后加上U,L为其定义.
8989U --> 表示uint
112UL --> 表示ulong.
b.浮点型
float -- System.Single : 32位单精度浮点数
double-- System.Double : 64位单精度浮点数
如果在代码中没有对某个非整数值(如12.3)进行硬编码,则编译器假定为double型。如果想指定为float,则加F符号进行限定。如:
123.1F
c.Decimal型
decimal -- System.Decimal : 128位高精度十进制数表示法。
要把数字指定为Decimal型,需要加M符号进行限定。如: 20.11M
d.bool型
bool -- System.Boolean
e.字符类型
char -- System.Char 表示一个16位Unicode字符。
char类型是用单引号''括起来的。如果用双引号,编译器会解释成字符串类型,而非字符类型。
f.转义字符
\' \" \\ \0 \a(警告) \b \f \n \r \t \v
2. C#中预定义的引用类型
a. object类型
object -- System.Object 根类型,CTS中所有其它类型都是从它派生。
●在C#中, object类型为最终的父类型,所有内在的和用户定义的类型都是从它派生而来。
●可以使用object类型引用绑定任何子类型对象。
【You can use an object reference to bind to an object of any particular subtype.
For example, in Chapter 5, "Operators and Casts," you see how you can use the object type to box a value
object on the stack to move it to the heap. object references are also useful in reflection, when code must
manipulate objects whose specific types are unknown. This is similar to the role played by a void pointer in
C++ or by a Variant data type in VB.】
●object类型执行许多基本的一般用途的方法,包括Equals(), GetHashCode(), GetType(), and ToString(). 用户定义的类
需要一种面向对象的技术--重写(override),提供其中一些方法的替代执行代码。
特殊的类型:string -- System.String Unicode字符串
string是引用类型,String对象保存在堆上,而不是栈上.因此,当把一个字符串变量赋值给另一个变量时,会得到内存中同一字符串的
两个引用对象.但是.string与一般的引用对象也有区别的地方:修改一个字符中变量的值,会创建一个全新的string对象,而另一个字符
串没有改变.\\
string字面量放在""中,字符串和char类型一样,可以用Unicode,16进制数转义序列.转义序列用\开头,不能在字符串中使用\的表示非转义的字符.
如果要使用,请使用\\,如:
string str = "C:\\windows\\a.exe";
或者,在字符串前加上@符号,表示其后的字符都为其原有的含义.
string str = @"C:\windows\a.exe";
甚至可以在字符串中直接敲入回车表示换行.
string str = @"c:\windows\a.exe"
3.流控制
a. if/else
if 语句只能对判断true/false,不能对整数进行判断.这点与C不同.
b.switch/case
case中有内容时,就算没有break的场合,也不能在case间跳转.
case中没有内容时(也没有break),可以在case间跳转.如:
switch(a){
case 1:
case 2:
case 3:
DoSomething;break;
case 4:
case 5:
DoSomething;break;
default:
DoSomething;break;
}
注意几点,有内容时,一定要加break; case的判断条件一定是常量,而不能为可变量.
c.四种循环(for,while,do..while,foreach)
●for(initializer;condition;iterator)
statement;
●while(condition)
statement;
●do{
statement;
}while(condition);
以上三种与C/C++中用法都是相同的.唯有foreach,C/C++中没有
●foreach(iter in Array)
如: foreach(int temp in ArrayOfInts) ...;
foreach有几个注意事项:
1.foreach每次迭代数组中的一个元素.
2.foreach循环中不能改变iter的值.如
foreach(int temp in Array)
temp++; //Error
d.枚举类型
2 Morning = 0;
3 Afternoon = 1;
4 Evening = 2;
5}
6TimeOfDay timeOfDay = TimeOfDay.Morning;
7switch(timeOfDay) {
8 case TimeOfDay.Morning:
9
10 case TimeOfDay.Afternoon:
11
12}
枚举的强大之处在于它们会在后台实例化为派生于基类System.Enum的结构。这表示可以对它们调用方法,执行任务。
一旦代码编译好,枚举就成为基本类型,与int,float相似。
2String st1 = t1.toString(); //返回"afternoon"
e.数组
2int[] integers = new int[32]; //初始化特定大小的数组
3int[] copy = integers; //把变量copy指向一个特定的数组,注意,这里不会创建一个新的数组。
4int len = integers.Length; //获得数组中的无素个数。
4. using语句
using语句的两种用法
1). 导入命名空间,如:
2 using DawnSoft.Basic;
2). 命名空间的别名,如:
2 alias::NameSpaceExample NSEx = new alias::NameSpaceExample();
5.控制台I/O
Console.ReadLine()从控制台中取一个输入流(在用户按下回车键为止)。
Console.Write()方法将指定的值写入控制台窗口
Console.WriteLine()方法将指定的值写入控制台窗口,并添加一个回车符
Console.WriteLine()允许用与C语言中的printf()函数类似的方式,显示格式化结果,如:
可以为值指定宽度,调整文本在宽度中的位置,正值表示右对齐,负责表示左对齐。
Console.WriteLine("{0,4}\n+{1,4}\n----\n{2,4}",i,j,i+j);
其它的格式化祥见《C#高级编程》p58
6.XML文档说明
XML文档说明以///打头。可以根据特定的注释自动创建XML格式的文档说明。编译器可以识别以下XML标识符:
<c> : 把行中的文本标记为代码
<code> : 把多行文本标记为代码
<example> : 标记为一个代码示例
<exception> : 说明一个异常类(编译器需要验证语法)
<include> : 包含其它文档说明的注释(编译器需要验证语法)
<list> : 把列表插入到文档说明中
<param> : 标记方法的参数(编译器需要验证语法)
<paramref> : 表示一个单词是方法的参数(编译器需要验证语法)
<permission>: 说明对成员的访问(编译器需要验证语法)
<remarks> : 给成员添加描述
<returns> : 说明方法的返回值
<see> : 提供对另一个参数的交叉引用(编译器需要验证语法)
<seealso> : 提供描述中的参见部份(编译器需要验证语法)
<summary> : 提供类型或成员的简单小结
<value> : 描述属性
以下举例:
2 namespace Wrox.ProCSharp.Basics
3 {
4 ///<summary>
5 /// Wrox.ProCSharp.Basics.Math class.
6 /// Provides a method to add two integers.
7 ///</summary>
8 public class Math
9 {
10 ///<summary>
11 /// The Add method allows us to add two integers
12 ///</summary>
13 ///<returns>Result of the addition (int)</returns>
14 ///<param name="x">First number to add</param>
15 ///<param name="y">Second number to add</param>
16 public int Add(int x, int y)
17 {
18 return x + y;
19 }
20 }
21 }
对以上代码进行编译,使用以下命令:
csc /t:library /doc:Math.xml Math.cs
编译后,生成xml文档,具体的情况可以查阅《C#高级编程》。
7. c#预处理指令
a. #define 和 #undef
分别表示定义符号和消除符号定义,注意的是,#define与#undef必须放在C#源代码的开头,在声明要编译的任何代码之前。
b. #if , #elif , #else , #endif
告诉编译器是否要编译某块代码,即条件编译。与#define结合起来用。
c. #warning ,#error
非常有用的预处理指令,编译器遇到他们,会分别产生警告或错误。也会显示其后的文本。
2#error "You've defined DEBUG and RELEASE
3#endif
4
5#warning "Don't forget to remove this line before
6Console.WriteLine("*I hate this job*");
d. #region , #endregion
把一段代码标记为有给定名称的一个块。
可以被编译器识别,例代码在屏幕上更好的显示
e.#pragma
可以抑制或恢复指定的编译警告,与命令选项不同,#pargma可以在类或方法上执行,对警告进行更精细的控制。
下面的例子是在MyClass类中禁止使用169警告,类外恢复警告
2public class MyClass
3{
4 int neverUsedField;
5}
6#pragma warning restore 169
8. C#给方法传递参数
参数可以通过引用或值传递给方法,在C#中,所有的参数都是通过按值传递的。除非用ref指示符特别说明。
另外,对于定义的对象,数组(但不包括字符串),默认是通过按引用传递的,在其中变更其参数的值会影响到其真实变量的值。
ref : 迫使值参数通过引用传递给方法。在C#中添加ref关键字等同于在C++中使用&语法指定按引用传递参数
out : 当在方法的输入参数前面加上out时,传递给该方法的变量可以不初始化。该变量通过引用传递,
所以在被调用的方法返回时,方法对该变量进行的任何变更都会被保存下来。另外,调用该方法时,也需要使用out关键字.
out关键字表示该参数还没有被份配内存,在方法中会被分配内存。
9. C#中的属性
C#中的属性表示一个方法或一对方法,但访问属性就像访问或设定其变量一样,不过可以加入限制。
2 public string ForeName
3 {
4 get
5 {
6 return foreName;
7 }
8 set
9 {
10 if (value.Length > 20)
11
12 else
13 foreName = value;
14 }
15 }
在属性中省略了set访问器,就可以创建只读属性。省略get访问器,就创建了只写属性。
10. 类中的构造函数
●如果创建类没有提供构造函数,则编译器会在其后创建一个默认的构造函数,这是一个非常基本的构造函数。
它只把所有成员初始化为标准默认值(引用类型为空引用,数据类型为0,bool类型为false)
构造函数可以按照参数的不同进行重载。
●c#中新添加了“静态构造函数”的功能。
与静态构造函数对应的是“实例构造函数”,这种构造函数只调用一次,就是创建了一个对象的时候。
而静态构造函数是类中有一些静态字段或属性,需要在第一次使用类之前,从外部源中初始化这些静态字段或属性。
静态函数:
①由.net运行库调用,调用的时间不确定,也不能确定不同类的静态构造函数按照什么顺序执行。
但是,可以确保静态构造函数至多运行一次,在代码引用类之前执行。C#中,它在第一次调用类成员之前执行。
②静态构造函数没有访问修饰符,public,private修饰没有意义。
③静态构造函数不带任何参数。
④一个类只能有一个静态构造函数。
●从其它构造函数中调用构造函数
C#有一个特殊语法,称为构造函数初始化器,如下所示:
2{
3 this.description = description;
4 this.nWheels = nWheels;
5}
6public Car(string model) : this(model, 4)
7{
8}
11. C#中的只读字段 readonly
常量的概念是一个包含不能修改的值的变量。而有时需要这样一种变量:
其值不应改变,但在运行之前其值是未知的。
C#提供了这样一种readonly修饰的变量。
它允许把一个字段设置为常量,也可以执行一些计算,以确定它的初始值。
它只能在构造函数中赋值,不能在其它地方赋值。只读字段不能是静态字段。
12. c#中的结构
结构用struct关键字表示。结构与类在C#中有许多不同点。
①结构是值类型,说明了定义变量时,可以不使用new分配空间.传递参数时,按值传递.
②不能从结构中继承,但是,结构本身一样派生于System.Object
③结构不允许定义无参数的构造函数.
13. C#中的静态类
静态类在功能上与使用私有静态构造函数创建的类相同.
静态类不能创建静态类的实例.
2 {
3 public static void HelperMethod()
4 {
5 }
6 }
7 StaticUtilities.HelperMethod();
14. Object类
所有的.net类都派生于System.Object.
System.Object有一些方法和属性,可以访问Object类定义的成员方法.
string ToString() -- public virtual -- 返回对象的字符串表示
int GetHashTable() -- public virtual -- 在实现字典(散列表)时使用
bool Equals(object obj) -- public virtual -- 对对象实例相等进行比较
bool Equals(object objA,object objB) -- public static -- 对对象实例相等进行比较
bool ReferenceEquals(object objA,object objB) -- public static -- 比较两个引用是否指向同一对象
Type GetType() -- public -- 返回对象类型的详细信息.
15. C#中的继承
C#中有两种继承,实现继承和接口继承.
●实现继承: 表示一个类型派生于基类型,拥有该类型的所有成员字段和函数.在实现继承中,派生类型的每
个函数采用基类型的实现代码,除非在派生类型的定义中指定重写该函数的实现代码.
●接口继承: 表示一个类型只继承了函数的签名,没有继承任何的代码.在需要指定该类型具有某种可用
特性时,最好使用这种类型的继承.
接口继承常常被看做提供了一种契约,让类型派生于接口,来保证为客户提供某种功能.
例子:
class MyDerivedClass : MyBaseClass
{
// functions and data members here
}
C#不支持私有继承,所以继承的类名上没有public和private之分.
如果类也派生于接口,则用逗号分开基类和接口.
public class MyDerivedClass : MyBaseClass, IInterface1, IInterface2
如果类没有指定基类,则默认派生于System.Object
●Virutal Method.
把一个基类声明为Virutal,函数就可以在派生类中重写了.
2 {
3 public virtual string VirtualMethod()
4 {
5 return "This method is virtual and defined in MyBaseClass";
6 }
7 }
也可以把属性也声明为virutal.如:
2 {
3 get { return fName;}
4 set { fName = value;}
5 }
private string foreName;
C#要求在派生类的函数重写另一个函数时,要用override关键字显式声明.
如果已经存在派生的情况下,要调用基类的函数,使用base标识符.如:
2 {
3 public virtual decimal CalculatePrice()
4 {
5 // implementation
6 return 0.0M;
7 }
8 }
9 class GoldAccount : CustomerAccount
10 {
11 public override decimal CalculatePrice()
12 {
13 return base.CalculatePrice() * 0.9M;
14 }
15 }
16. 抽象类和抽象函数
C#允许把类和函数声明为abstract表示抽象类和抽象函数,抽象类不能实例化,抽象函数不能实现代码.
2 {
3 public abstract decimal CalculateHeatingCost(); // abstract method
4 }
5 abstract class Building
6 {
7 private bool damaged = false; // field
8 public abstract decimal CalculateHeatingCost(); // abstract method
9 }
17. 密封类和密封方法
使用saled标识符可以把类或函数声明为密码类或密封方法.
对于类来说,密封类表示不能被继承;密封方法表示不能被重写.
18. 派生类的构造函数
在创建派生类的实例时,实际上有多个构造函数在起作用.
要实例化类的构造函数本身不能只初始化本类,有时还必须调用基类的构造函数.
构造函数的调用顺序是先System.Object,再按照层次由上向下进行.直到到达编译器要实例化的类为止.
要注意在这个过程中,每个构造函数都初始化自已的类中的字段.
这里仅举一例就可以看出其如何在派生类中调用构造函数.
2 {
3 private string name;
4 public GenericCustomer(string name)
5 {
6 this.name = name;
7 }
8 }
9
10 class Nevermore60Customer:GenericCustomer
11 {
12 private uint highCustomerUsed;
13 private string refferName;
14
15 public Nevermore60Customer(string name,string refferName)
16 :base(name) // 这里用base表示调用基类的构造函数
17 {
18 this.refferName = refferName;
19 }
20
21 public Nevermore60Customer(string name)
22 :this(name,"<None>") // 这里用this表示调用自身的构造函数
23 {
24
25 }
26 }
19. C#中的运算符
a. is 运算符
is 运算符可以检查对象是否与特定的类型兼容,兼容包括对象是该类型,或派生于该类型.如:
if( i is object) ...
b. as 运算符
as 运算符用于执行引用类型的显式类型转换,如果要转换的类型与指定的类型兼容,则转换成功.
object o2 = 1;
string s1 = o1 as string; // s1="string"
string s2 = o2 as string; // s2 = null
c. sizeof运算符
可以决定堆栈中的值类型需要的长度.只有在不安全的代码中才能使用Sizeof运算符
d. typeof运算符
typeof运算符返回一个表示特定类型的System.Type对象.
typeof(string)返回表示System.String类型的Type对象.
e. 空连接运算符??
为处理可空类型或引用类型表示null值的可能性提供了一种快捷方式.
20. C#中的类型转换
a.隐式类型转换
只要保证值不会发生任何变化,隐式类型自动转换可以自动进行.
2int i1 = t1+t2; //隐式自动转换
b. 显式类型转换
许多场合不能隐式的转换类型,编译器会报错.如int转化为short.
可以使用cast显式执行这些转换.
long val = 3000; int val1 = (int)val;
进行显式转换时,如果希望数据转化不会出现异常的结果,就使用checkedr指示符
long val = 30000000000;
int val1 = checked((int)val);
以上,如果数据类型不安全,会抛出溢出异常.
如果需要在数字和字符在进行转换,则使用Object类提供的ToString功能,及int.Parse功能.
int i = 10; string s = i.ToString();
i = int.Parse(s); //如果s不能转化成整数,则抛出一个异常.
21.装箱和拆箱
C#的数据类型可以分为在堆栈上分配的值类型和在堆上分配的引用类型.
装箱和拆箱就是在这两种类型之间转换.装箱用于把值类型转换成引用类型,拆箱用于把引用类型转换成值类型.
该转换是隐式进行的,可以手工进行转换:
int i = 20;
object o = i; // boxing
int j = (int)o; // unboxing
22. 对象相等比较
System.Object定义了四种不同的方法,来比较对象的相等性.分别是:
ReferenceEquals(); Equals()两个版本; ==
●ReferenceEquals()是一个静态方法,测试两个引用是否指定类的同一个实例.即两个引用是否包含内存中的相同地址.作为静态方法,它不能重写,只能使用System.Object实现代码:
x = new SomeClass();
y = new SomeClass();
bool B1 = ReferenceEquals(null, null); // returns true
bool B2 = ReferenceEquals(null,x); // returns false
bool B3 = ReferenceEquals(x, y); // returns false because x and y
●虚拟的Equals方法
因为其虚拟性,可以在自己的类中重写,按值来比较对象.
例如,如果类的实例用作字典的键,就需要重写这个方法.
●静态的Equals方法
作用与虚拟Equals方法相同,都是按值来比较对象.但是其有两个参数.
●比较运算符==
可以看作严格值与严格引用之间的中间选项,
如果两个变量为引用类型,则比较其引用是否指向类的同一实例.
如果两个变量为值类型,则比较其值是否一致.
如果想把引用类型变量按值比较,则需要进行操作符重载.
23. 运算符重载
....Chapter 5
24. C#中的委托
在C/C++中有回调函数(callback)的概念,回调函数也称为函数指针。
.Net以委托的形式实现了函数指针的概念。
其特殊之处在于:.NET的委托是代码安全的。C中的函数指针不过是一个指向存储单元的指针变量,无法得知其指向的内容是什么,参数和返回值都无从知晓。
比如,对于启动线程,需要让系统知道线程启动的函数是什么。如:
// do whatever the new thread needs to do
}
Thread NewThread = new Thread();
Thread.Start(EntryPoint); // WRONG
但是,这种使用方法在.NET中会导致一些问题,进行面向对象编程时,方法很少孤立存在,都是通过与实例相关联。
在.NET上不允许这样的使用方法,如果要传递方法,必须把方法的细节封装在一个新类型的对象中,这就是委托。
★委托,就是一种特殊的对象类型。
把委托可以看成一个class,首先,定义它,其次,定义相应的实体。
委托也可以这样理解:给一个method起另外一个名字,对它进行签名。
One good way of understanding delegates is by thinking of a delegate as something that gives a name to a method signature.
①声明委托
在C#中使用一个类时,分两个阶段,1为定义这个类,2为实例化类。
使用委托时,也分两个阶段,首先定义要使用的委托,定义就是告诉编译器这种类型的委托代表了哪种类型的方法。然后创建一个或多个实例委托
定义委托如下:
delegate void VoidOperation(uint x);
定义委托基本上就相当于定义一个新类,可以在任何定义类的地方定义新的委托。
可以根据可见性,给委托定义加上一般的访问修饰符,public,protect,private.
②实例化委托
委托定义好后,创建一个委托的实例就是实例化委托. 实例化时指定参数就是一个函数名称.例:
public string GetDeleString()
{
int x = 40;
GetAString fristStringMethod = new GetAString(x.ToString);
return "String is " + fristStringMethod();
}
注意,x.ToString不能写为x.ToString()
★委托在语法上总是带有一个参数的构造函数,这个参数就是委托引用的方法.