wednesday, october 13, 2004
浮点数的比较运算
对浮点数进行比较,必须先舍入为相同精度再进行,因其的不精确性。
在需要测试某个float值或double值是否为无穷大或nan时,不能直接用single或double结构中的positiveinfinity、negativeinfinity或nan字段进行比较;应选用isinfinity等方法来完成,因其有二义。
建议最好不要用“==”操作符去比较两个布尔值,因为不小心少打一个等号,会致命的。可以使用异或操作符“^”将更稳妥。异或操作等价于“!=”操作符,所以有不少人把“^”称为逻辑“不等于”操作符。只要用“^”操作符对“!=”操作的结果再进行一次逻辑非处理,就能得到与“==”操作符同样的效果,如:
bwearecompatible = byoulikemahler == bilikemahler
bwearecompatible = !(byoulikemahler ^ bilikemahler)
局部变量只在对它做出了声明的那个{语句块}里才是可见的。这个语句块有多大,求证中....
c#没有“else语句”之类的东东,c#中的if语句由一个if部分和一个可选的else部分构成。c#也没有“else if语句”,是两条if组合在一起而矣。
条件操作符
条件与(&&)与条件或(||)
bexpression1 && bexpression2//如果bexpression1被求值为false,bexpression2将不会被求值
bexpression1 || bexpression2//如果bexpression1被求值为true,bexpression2将不会被求值
古怪“条件表达式”(?:),唯一三目操作符。
一、c#的异常处理所用到关键字
try 用于检查发生的异常,并帮助发送任何可能的异常。
catch 以控制权更大的方式处理错误,可以有多个catch子句。
finally 无论是否引发了异常,finally的代码块都将被执行。
throw 用于引发异常,可引发预定义异常和自定义异常。
二、c#异常处理的格式 以下是引用片段:
try
{
程序代码块;
}
catch(exception e)
{
异常处理代码块;
}
finally
{
无论是否发生异常,均要执行的代码块;
}
有效的调试技巧
没有漏网的鱼 "this statement should never be executed."
for语句以保留字"for"开始,后面跟着一对圆括号,括号里有三个用分号隔开的表达式。注意,这是分号在c#中惟一不被当作一个语句分隔符来对待的地方!这三个表达式分别叫作for循环的“初始化表达式”、“循环条件表达式”和“步长表达式”。
friday, october 15, 2004
算法“sieve of eratosthences”(eratosthences之筛)是一种用来生成素数的算法。素数是只能被1和它本身整除的整数。2是第一个素数,也是素数中惟一的一个偶数。
cpu耗时比较:方法调用 > 乘除运算 > 加减运算
switch语句
在java中,switch语句只能处理整数。但c#中的switch语句不同,它还能够处理字符变量。
switch (args[0]) {
case "老板":
console.writeline("早上好!我们随时准备为您效劳!");
break;
case "雇员":
console.writeline("早上好!你可以开始工作了!");
break;
default:
console.writeline("早上好!祝你好运!");
break;
}
与java中的switch不同,c#的switch语句要求每一个case块或者在块的末尾提供一个break语句,或者用goto转到switch内的其他case标签。这就是所谓c#的“不允许留下swicth分支漏斗”的规定,不过你可以让一个switch分支有多个switch标号。如:
....
case "*"://多种乘法运算符
case "x"://
case "x"://
dresult = dnum1 * dnum2
break;
....
编写c#程序的“基本工具”:
声明语句;
赋值语句;
选择语句(if, switch);//它们使程序能够根据一个比较操作或者其他布尔操作的结果有选择地改变执行路线
循环语句(while, do, for, foreach);//它们使程序能够反复多次地执行同一组语句。
跳转语句(return, break, continue, throw, goto);//它们使程序能够从一个地方跳转到另一个地方去继续执行。
c#没有>>>移位操作符
c#支持uint和ulong之类的无符号变量类型。因此,在c#中,右移操作符(即“>>”)对于无符号变量类型和带符号变量类型(比如int和long)的处理方式不同。右移uint和ulong丢弃低位并把空出的高位设置为零;但对于int和long类型的变量,“>>”操作符丢弃低位,同时,只有当变量值是正数时,“>>”才把空出的高位设置成零;如果“>>”操作的是一个负数,空出的高位被设置成为1。
java中不存在无符号的变量类型。因此,我们用“>>>”操作符在右移时引入负号位;否则,使用“>>”操作符。
枚举器
枚举器即enum类型(enumerator,或称为计数器),它是一个相关常量的集合。精确地说,enum类型声明为一组相关的符号常量定义了一个类型名字。例如,你可以创建一个名为fruit(水果)的枚举器,把它作为一个变量值的类型使用,从而把变量可能的取值范围限制为枚举器中出现的值。
public class demo {
public enum fruit {
apple, banana, cherry, durian
}
public void process(fruit fruit) {
switch (fruit) {
case fruit.apple:
...
break;
case fruit.banana:
...
break;
case fruit.cherry:
...
break;
case fruit.durian:
...
break;
}
}
}
sunday, october 17, 2004
c#方法调用传递的参数分四类:
默认的值参数(value parameter); //传递复制品
引用参数(reference parameter),关键字"ref";//传递引用指针
输出参数(output parameter),关键字"out"。//方法返回一个以上的返回值时使用
数组参数(array parameter),关键字"params"
引用参数与输出参数的区别:
“引用参数”与“输出参数”非常相似。out修饰符ref修饰符有很相似的地方:传址。事实上保留字“ref”和“out”本身在中间语言里的实现是一模一样的。但c#中的ref参数与out参数还是有区别的:
ref参数必须在进入方法之前得到赋值;
out参数则必须在离开方法之前得到赋值。需要记住输出参数与通常的函数返回值有一定的区别:函数返回值往往存在堆栈里,在返回时弹出;而输出参数需要用户预先制定存储位置,也就是用户需要提前声明变量--当然也可以初始化。
using system;
class test //拆分姓名
{
static void resolutename(string fullname,out string firstname,out string lastname)
{
string[] strarray=fullname.split(new char[]{' '});
firstname=strarray[0];
lastname=strarray[1];
}
public static void main()
{
//string myname="cornfield lee";
console.writeline("enter your name:");
string myname = console.readline();
string myfirstname,mylastname;
resolutename(myname,out myfirstname,out mylastname);
console.writeline("my first name: {0}, my last name: {1}",
myfirstname, mylastname);
}
}
数组参数(关键字"params")
using system;
class test //合计所有整数值
{
static int sum(params int[] args)
{
int s=0;
foreach(int n in args)
{
s+=n;
}
return s;
}
static void main()
{
int[] var=new int[]{1,2,3,4,5};
console.writeline("the sum:"+sum(var));//传数组变量
console.writeline("the sum:"+sum(10,20,30,40,50));//传能够隐式转化为数组的参数
}
}
注意以下两个规定:
数组参数只能是一维的;
如果有多个输入参数,就只允许一个输入参数是params参数,而且它必须是参数表中的最后一个。
tuesday, october 19, 2004
数据的封装-对象
从本质上讲,对象就是数据。随着程序设计语言的发展,当人们开始把不同的数据组合在一起并当作一个整体事物来对待时,“对象”出世了!(拿日期对象作理解)
在声明变量和常数时,你的程序其实就是在创建对象,数值常数和字符串都是对象。不过,有你的程序真正执行有关语句之前,对象是不会“出生的”。
日期对象:日期是由三个分立的数型变量组合而成的有机整体,这样会大大简化对日期数据的处理。
new操作符将为新实例分配内存并把它的的各个字段全部初始化为0;堆内永远是被初始化为的0的(就是堆栈可是值0或空指针null)。
date[] adate = new date[5];
如果date是一个结构:这条语句将从堆里为这个结构的5个实例(无需new初始化了,叫实例了嘛)分配内存并把各元素的所有字段全部初始化为0;
如果是一个类,这条语句将只为这个数组本身分配内存(上面也会为数组本身分本内存,不过还为结构实例分配)。这个数组的各个元素将是null。因为这个数组的各个元素都是一个引用(指针)。在使用这个数组的元素前,必须先用一个new构造实例(包括分配内存和初始化):
adate[3] = new date();
c#数组是一个引用类型,数组中的各个元素将存储在堆里。如 int[] ai = new int[12];//使用new关键字在heap分配内存
除了数组,c#还有以下引用类型:
堆栈的执行效率要比堆的执行效率高,可是堆栈的资源有限,不适合处理大的逻辑复杂的对象。所以结构处理作为基类型对待的小对象,而类处理某个商业逻辑
虽然结构的初始化也使用了new 操作符,可是结构对象依然分配在堆栈上而不是堆上,如果不使用“新建”(new),那么在初始化所有字段之前,字段将保持未赋值状态,且对象不可用
如何选择结构还是类
讨论了结构与类的相同之处和差别之后,下面讨论如何选择使用结构还是类:
堆栈的空间有限,对于大量的逻辑的对象,创建类要比创建结构好一些
结构表示如点、矩形和颜色这样的轻量对象,例如,如果声明一个含有 1000 个点对象的数组,则将为引用每个对象分配附加的内存。在此情况下,结构的成本较低。
在表现抽象和多级别的对象层次时,类是最好的选择
大多数情况下该类型只是一些数据时,结构时最佳的选择
面向过程到面向对象三步走:
封装对象简化输入参数列表:
static int dayofyear(int iyear, int imonth, int iday){....}//三个整型输入参数
static int dayofyear(date ddate){....}//date就好像是一个已预先定义好的简单类型。
封装与对象有关的方法到类(或结构)内部中去,成为类(或结构)的行为,进一步共享代码:
区分类(或结构)方法和字段的静态性和实例性,进一步简化使用对象的代码。
.net framewrok里的每一个类和每一个结构tostring方法,自已定义的类和结构也不例外,因为c#里所有类和结构都继承自同一个祖先——system.object类。虽然结构不支持继承,但是所有的结构都是直接或间接从system.valuetype类继承而来的。
若一个实例方法的声明中含有 virtual 修饰符,则称该方法为虚拟方法。若其中没有 virtual 修饰符,则称该方法为非虚拟方法。
非虚拟方法的实现是不会变的:无论是在声明它的类的实例上调用该方法还是在派生类的实例上调用,实现都是相同的。与此相反,一个虚拟方法的实现可以由派生类取代。
关键字"virtual"和"override"是密切相关的:如果a类继承b类,就可以在a类里用一个override(重写)方法覆盖掉b类里的同名的virtual方法。
不使用"override"关键字也能覆盖一个方法,但这种做法只有新、老方法的签名和访问性完全一致的情况下才能达到目的
wednesday, october 20, 2004
类的字段初始化方法:
在声明语句里直接初始化:
class date{
public int iyear = 2004;
public int imonth = 10;
public int iday = 20;
.......
}
使用构造器:
class date{
public int iyear, imonth, imonth;
public date(){//无参数构造器
iyear = 1;
imonth = 1;
iday = 1;
}
.......
}
为了加快那些基于结构的数组的创建速度,结构均不支持以上两种初始化结构的方法。
c#的结构是一个关系重大的语言功能。和类一样,结构也可以包含其他类型。由于结构在内部是值类型的,所以结构有时也被称为类的轻型版本。同时,结构不承担引用对象所带来的开销,除非在对其装箱时例外。
c#的结构结论:
结构的定义是封闭的(不能作为基类使用);
结构隐式地派生自system.valuetype,而system.valuetype是所有值类型的超类型(终极基类)。
结构没有默认的构造器。
阻止创建类实例的方法:一个私有的(private)空白无参数构造器,如:class clsa{...private clsa(){}...}
实际上如果我们能够深刻地把握类的构造器的唯一目的就是保证类内的成员变量能够得到正确的初始化,我们对各种c#中形形色色的构造器便有会心的理解--它没有理由不这样!
⒈﹜ ┣━┒ ; `.┣─┒` ..wǒ
個ボ ┟━┃┍╄┓┟━│ ╃━
人 ┝─┃┣╈┤┣━┃;/ ╈
┗━┘┗┸┛└━┛/┃┻
相等性:
当你自行创建一个类或结构时,一定要对相等性概念多做些思考。
要想成为一名合格的程序员,不仅要知道类对象和结构对象在相等性、赋值操作、方法调用等几个方面有何不同,还要了解new操作符对类对象和结构对象的处理有何不同。
要想创建出某个类的一个实例,就必须使用一个new操作符,new操作符将进行几项非常重要的处理:
从堆里为新对象分配内存;
有选择地调用那个类的某一个构造器。
创建出某个结构的一个实例,也使用一个new操作符,new操作符将调用结构的惟一的无参数构造器(不能重写),无参数构造器只是把所有的字段初始化0或null而已,其他什么都不做。不允许字段非零和上面结构创建速度有关的。
reference types versus value types
以下是引用片段:
// reference type (because of ’class’)
class ref { public int x, y, cx, cy; }
// value type (because of ’struct’)
struct val { public int x, y, cx, cy; }
static void somemethod {
ref r1 = new ref(); // allocated in heap
val v1; // allocated on stack (new optional)
r1.x = 10; // pointer dereference
v1.x = 10; // changed on stack
rectref r2 = r1; // copies pointer only
rectval v2 = v1; // allocate on stack & copies members
r1.x = 20; // changes r1 and r2
v1.x = 20; // changes v1, not v2
}
sunday, october 24, 2004
域的存取限制集中体现了面向对象编程的封装原则。
只读(readonly)域不能进行写操作,不变常量(const)不能被修改,这两者到底有什么区别呢?
只读域只能在初始化--声明初始化或构造器初始化--的过程中赋值,其他地方不能进行对只读域的赋值操作,否则编译器会报错。只读域可以是实例域也可以是静态域。只读域的类型可以是c#语言的任何类型。
但const修饰的常量必须在声明的同时赋值,而且要求编译器能够在编译时期计算出这个确定的值。const修饰的常量为静态变量,不能够为对象所获取。const修饰的值的类型也有限制,它只能为下列类型之一(或能够转换为下列类型的):sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, string, enum类型, 或引用类型。能够声明为const的引用类型只能为string或值为null的其他引用类型。
实际上,如果我们能够理解const修饰的常量是在编译时便被计算出确定的值,并代换到引用该常量的每一个地方,而readonly时在运行时才确定的量--只是在初始化后我们不希望它的值再改变,我们便能理解c#设计者们的良苦用心,我们才能彻底把握const和readonly的行为!
c#不提倡将域的保护级别设为public而使用户在类外任意操作--那样太不oo,或者具体点说太不安全!对所有有必要在类外可见的域,c#推荐采用属性来表达。属性不表示存储位置,这是属性和域的根本性的区别。
继承:
一个现有的类(基类)派生出一个新类(子类)时,子类将自动获得所有被声明在基类的非private方法、属性和字段(注意基类的构造器是不会被继承的。)。子类可以通过增加或者替换它继承到的各种方法、属性和字段的方式对基类的功能进行扩展。
继承能力是类和结构的重要区别之一。结构不支持继承机制。
学海无涯,面向对象的程序设计既是一门科学,也是一门艺术,如果这本书能让大家领略到它的一些奥妙,作者知足了!!
继承与预构造器(constructor initializer)
每一个构造器都会用一个预构造器
子类不能继承基类的构造器但是可以“拿来用”,用关键字“base”指定父类和“this”指定本类中的构造器作为本构造器的预构造器。
在没有指定时,父类的无参数构构造器为缺省的预构造器,即:“base()”
访问限定符理解:
很多新手认为代码共享就是最大限度的开放自己的代码,总是不假思索把所有东西都声明为“public”,其实类里面的public成员应该越少越好,因为可以降低调试难道,还遵循“尽量掩藏数据。使类成为一个代码黑箱”的原则。
monday, october 25, 2004
one of the more powerful concepts in object-oriented programming is polymorphism. the roots of this word, "poly-" and "-morph" mean, when put together, "many forms."
polymorphism(破利摩飞神):“poly-”表示多之意,“-morph”,形态形状
多态实例:
using system ;
public class drawingbase{
public virtual void draw(){//基类虚方法
console.writeline("i'm just a generic drawing object.");
}
}
public class line : drawingbase{
public override void draw( ){//子类重写虚方法1
console.writeline("i'm a line.");
}
}
public class circle : drawingbase{
public override void draw( ){//子类重写虚方法2
console.writeline("i'm a circle.");
}
}
public class square : drawingbase{
public override void draw( ){//子类重写虚方法3
console.writeline("i'm a square.");
}
}
public class drawdemo{
public static int main(string[] args){
drawingbase [] dobj = new drawingbase [4];//创建基类类型数组
dobj[0] = new line( );//赋值类型向上转换
dobj[1] = new circle( );//声明(编译时)为基类类型,实际类型(运行时)为子类类型
dobj[2] = new square( );
dobj[3] = new drawingbase( );
foreach (drawingbase drawobj in dobj)
drawobj.draw( );//运行时“后绑定”各个draw版本
return 0;
}
}
as 运算符
用于执行可兼容类型之间的转换。as 运算符用在以下形式的表达式中:expression as type
as 运算符类似于类型转换,所不同的是,当转换失败时,as 运算符将产生空,而不是引发异常。
请注意,as 运算符只执行引用转换和装箱转换。as 运算符无法执行其他转换。
is 运算符用于检查对象的运行时类型是否与给定类型兼容。
gettype()方法与操作符typeof()和system.type类
type t = instance.gettype();//一个实例方法,返回一个type对象
type t = typeof(class);//一个操作符,操作数为一种类型非某一实例,也是返回一个type对象
相等性比较:bool b = instance.gettype() == typeof(class);
所谓“实现一个接口”指的是把该接口所声 明的方法都在某个类里实现出来。
接口本质上是类需要如何响应的定义。接口描述类需要实现的方法、属性和事件,以及每个成员需要接收和返回的参数类型,但将这些成员的特定实现留给实现类去完成。
派生类可以重写(override)基类里的虚(virtual)方法和属性, 也可“隐藏”(new关键字)基类里的非虚成员。
操作符重载:()
operators are defined for the built-in types, but that's not all. you can add operators to your own types, allowing them to be used much like the operators with the built-in c# types.
requirement:you create a matrix type and then do a mathematical operation with them.
solution:
implement an add(), dotproduct(), and other methods to get the job done.
matrix result = mat1.add(mat2); // instanceormatrix result = matrix.add(mat1, mat2); // staticor event worsematrix result = mat1.dotproduct(mat2).dotproduct(mat3); // and so on...
much easier to have a + operator for the add operation and a * operator for the dot product operation.
matrix result = mat1 + mat2;or matrix result = mat1 * mat2;or even bettermatrix result = mat1 * mat2 * mat3 * mat4;