2006年5月13日



前言:设计模式是软件开发领域的精髓之一。学好设计模式是目前每一个开发人员的必修课。目前关于设计模式的书很多,其中比较好的有GOF那本的中译本,但并不很适合初学者。还有一本是《JAVA与模式》,比较适合初学者使用,在此强烈推荐。但这本书的不足之处是一些地方讲的过于繁琐,很多地方只须简单说明一下即可,却大费笔墨,使得书籍很厚,看起来费力。而且是用JAVA描述的,这使得一些只懂C#的人无从下手。我是一个.net的拥护者,为了看这本书我特意看了些JAVA的书,感觉JAVA在书籍的多样性方面比 .net好很多,而且不少书籍的质量很高。可能是现在JAVA已经比较成熟的原因吧。为了方便.net的爱好者学习设计模式,在此把我学习《JAVA与模式》这本书的学习笔记发出来,并用C#语言重新描述,希望能对初学者有所帮助。
其实设计模式也并不是什么高深的理论,个人认为并不是象一些人所说的“没写过10万代码就不要谈设计模式”,只要用心学习与实践是完全能够掌握的。

简单工厂模式是类的创建模式,又叫做静态工厂方法模式。就是由一个工厂类根据传入的参量决定创建出哪一种产品类的实例。一般涉及到三种角色(如下图):

工厂类:担任这个角色的是工厂方法模式的核心,含有与应用紧密相关的商业逻辑。工厂类在客户端的直接调用下创建产品对象,它往往由一个具体的类实现。
抽象产品角色:担任这个角色的类是由工厂方法模式所创建的对象的父类,或她们共同拥有的接口。一般由接口或抽象类实现。
具体产品角色:工厂方法模式所创建的任何对
象都是这个角色的实例,由具体类实现。


简单工厂模式优缺点:
模式的核心是工厂类,这个类负责产品的创建,而客户端可以免去产品创建的责任,这实现了责任的分割。但由于工厂类集中了所有产品创建逻辑的,如果不能正常工作的话会对系统造成很大的影响。如果增加新产品必须修改工厂角色的源码。

以园丁种植水果为例讨论该模式的具体实现:
Fruit 水果接口,规定水果具有的一些共同特性
Apple 苹果类 派生自Fruit接口
Strawberry 草莓类 派生自Fruit接口
FruitGardener 园丁类 负责草莓与苹果的创建工作。
当Client要创建水果(苹果或草莓对象)的时候调用园丁类的factory方法创建:UML图如下:

代码如下:
Fruit.cs
namespace Simple_Factory
{
public interface Fruit
{
//生长
void grow();
//收获
void harvest();
//种植
void plant();
}
}
Apple.cs
namespace Simple_Factory
{
public class Apple:Fruit
{
public Apple()
{
}
#region Fruit 成员
public void grow()
{
Console.WriteLine ("Apple is growing.......");
}
public void harvest()
{
Console.WriteLine ("Apple is harvesting.......");
}
public void plant()
{
Console.WriteLine ("Apple is planting.......");
}
#endregion
}
}
Strawberry.cs
namespace Simple_Factory
{
public class Strawberry:Fruit
{
public Strawberry()
{
}
#region Fruit 成员
public void grow()
{
Console.WriteLine ("Strawberry is growing.......");
}
public void harvest()
{
Console.WriteLine ("Strawberry is harvesting.......");
}
public void plant()
{
Console.WriteLine ("Strawberry is planting.......");
}
#endregion
}
}
FruitGardener.cs
namespace Simple_Factory
{
public class FruitGardener
{
//静态工厂方法
public static Fruit factory(string which)
{
if(which.Equals ("Apple"))
{
return new Apple();
}
else if(which.Equals ("Strawberry"))
{
return new Strawberry ();
}
else
{
return null;
}
}
}
}
Client.cs
using System;
namespace Simple_Factory
{
class Client
{
[STAThread]
static void Main(string[] args)
{
Fruit aFruit=FruitGardener.factory ("Apple");//creat apple
aFruit.grow ();
aFruit.harvest ();
aFruit.plant();
aFruit=FruitGardener.factory ("Strawberry");//creat strawberry
aFruit.grow ();
aFruit.harvest ();
aFruit.plant();
}
}
}
输出如下:
Apple is growing.......
Apple is harvesting.......
Apple is planting.......
Strawberry is growing.......
Strawberry is harvesting.......
Strawberry is planting.......
posted @ 2006-05-13 22:26 leithon 阅读(33) 评论(0) 编辑

2006年5月10日

C# 中的委托类似于 C C++ 中的函数指针。使用委托使程序员可以将方法引用封装在委托对象内。然后可以将该委托对象传递给可调用所引用方法的代码,而不必在编译时知道将调用哪个方法。与 C C++ 中的函数指针不同,委托是面向对象、类型安全的,并且是安全的。

委托声明定义一种类型,它用一组特定的参数以及返回类型封装方法。对于静态方法,委托对象封装要调用的方法。对于实例方法,委托对象同时封装一个实例和该实例上的一个方法。如果您有一个委托对象和一组适当的参数,则可以用这些参数调用该委托。

委托的一个有趣且有用的属性是,它不知道或不关心自己引用的对象的类。任何对象都可以;只是方法的参数类型和返回类型必须与委托的参数类型和返回类型相匹配。这使得委托完全适合匿名调用。

此教程包括两个示例:

示例 1 展示如何声明、实例化和调用委托。

示例 2 展示如何组合两个委托。

此外,还讨论以下主题:

委托和事件

委托与接口

示例 1

下面的示例阐释声明、实例化和使用委托。BookDB 类封装一个书店数据库,它维护一个书籍数据库。它公开 ProcessPaperbackBooks 方法,该方法在数据库中查找所有平装书,并为每本书调用一个委托。所使用的 delegate 类型称为 ProcessBookDelegateTest 类使用该类输出平装书的书名和平均价格。

委托的使用促进了书店数据库和客户代码之间功能的良好分隔。客户代码不知道书籍的存储方式和书店代码查找平装书的方式。书店代码也不知道找到平装书后将对平装书进行什么处理。

// bookstore.cs

using System;

 

// A set of classes for handling a bookstore:

namespace Bookstore

{

   using System.Collections;

   // Describes a book in the book list:

   public struct Book

   {

      public string Title;        // Title of the book.

      public string Author;       // Author of the book.

      public decimal Price;       // Price of the book.

      public bool Paperback;      // Is it paperback?

      public Book(string title, string author, decimal price, bool paperBack)

      {

         Title = title;

         Author = author;

         Price = price;

         Paperback = paperBack;

      }

   }

   // Declare a delegate type for processing a book:

   public delegate void ProcessBookDelegate(Book book);

   // Maintains a book database.

   public class BookDB

   {

      // List of all books in the database:

      ArrayList list = new ArrayList();  

      // Add a book to the database:

      public void AddBook(string title, string author, decimal price, bool paperBack)

      {

         list.Add(new Book(title, author, price, paperBack));

      }

      // Call a passed-in delegate on each paperback book to process it:

      public void ProcessPaperbackBooks(ProcessBookDelegate processBook)

      {

         foreach (Book b in list)

         {

            if (b.Paperback)

            // Calling the delegate:

               processBook(b);

         }

      }

   }

}

// Using the Bookstore classes:

namespace BookTestClient

{

   using Bookstore;

   // Class to total and average prices of books:

   class PriceTotaller

   {

      int countBooks = 0;

      decimal priceBooks = 0.0m;

      internal void AddBookToTotal(Book book)

      {

         countBooks += 1;

         priceBooks += book.Price;

      }

      internal decimal AveragePrice()

      {

         return priceBooks / countBooks;

      }

   }

   // Class to test the book database:

   class Test

   {

      // Print the title of the book.

      static void PrintTitle(Book b)

      {

         Console.WriteLine("   {0}", b.Title);

      }

      // Execution starts here.

      static void Main()

      {

         BookDB bookDB = new BookDB();

         // Initialize the database with some books:

         AddBooks(bookDB);     

         // Print all the titles of paperbacks:

         Console.WriteLine("Paperback Book Titles:");

         // Create a new delegate object associated with the static

         // method Test.PrintTitle:

         bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(PrintTitle));

         // Get the average price of a paperback by using

         // a PriceTotaller object:

         PriceTotaller totaller = new PriceTotaller();

         // Create a new delegate object associated with the nonstatic

         // method AddBookToTotal on the object totaller:

        bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(totaller.AddBookToTotal));

         Console.WriteLine("Average Paperback Book Price: ${0:#.##}",

            totaller.AveragePrice());

      }

      // Initialize the book database with some test books:

      static void AddBooks(BookDB bookDB)

      {

         bookDB.AddBook("The C Programming Language",

            "Brian W. Kernighan and Dennis M. Ritchie", 19.95m, true);

         bookDB.AddBook("The Unicode Standard 2.0",

            "The Unicode Consortium", 39.95m, true);

         bookDB.AddBook("The MS-DOS Encyclopedia",

            "Ray Duncan", 129.95m, false);

         bookDB.AddBook("Dogbert's Clues for the Clueless",

            "Scott Adams", 12.00m, true);

      }

   }

}

输出

Paperback Book Titles:

   The C Programming Language

   The Unicode Standard 2.0

   Dogbert's Clues for the Clueless

Average Paperback Book Price: $23.97

代码讨论

声明委托 以下语句:

public delegate void ProcessBookDelegate(Book book);

声明一个新的委托类型。每个委托类型都描述参数的数目和类型,以及它可以封装的方法的返回值类型。每当需要一组新的参数类型或新的返回值类型时,都必须声明一个新的委托类型。

实例化委托 声明了委托类型后,必须创建委托对象并使之与特定方法关联。与所有其他对象类似,新的委托对象用 new 表达式创建。但创建委托时,传递给 new 表达式的参数很特殊:它的编写类似于方法调用,但没有方法的参数。

下列语句:

bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(PrintTitle));

创建与静态方法 Test.PrintTitle 关联的新的委托对象。下列语句:

bookDB.ProcessPaperbackBooks(new

   ProcessBookDelegate(totaller.AddBookToTotal));

创建与对象 totaller 上的非静态方法 AddBookToTotal 关联的新的委托对象。在两个例子中,新的委托对象都立即传递给 ProcessPaperbackBooks 方法。

请注意一旦创建了委托,它所关联到的方法便永不改变:委托对象不可改变。

调用委托 创建委托对象后,通常将委托对象传递给将调用该委托的其他代码。通过委托对象的名称(后面跟着要传递给委托的参数,括在括号内)调用委托对象。下面是委托调用的示例:

processBook(b);

示例 2

本示例演示组合委托。委托对象的一个有用属性是,它们可以+运算符来组合。组合的委托依次调用组成它的两个委托。只可组合相同类型的委托,并且委托类型必须具有 void 返回值。-运算符可用来从组合的委托移除组件委托。

// compose.cs

using System;

delegate void MyDelegate(string s);

class MyClass

{

    public static void Hello(string s)

    {

        Console.WriteLine(" Hello, {0}!", s);

    }

    public static void Goodbye(string s)

    {

        Console.WriteLine(" Goodbye, {0}!", s);

    }

    public static void Main()

    {

        MyDelegate a, b, c, d;

        // Create the delegate object a that references

        // the method Hello:

        a = new MyDelegate(Hello);

        // Create the delegate object b that references

        // the method Goodbye:

        b = new MyDelegate(Goodbye);

        // The two delegates, a and b, are composed to form c,

        // which calls both methods in order:

        c = a + b;

        // Remove a from the composed delegate, leaving d,

        // which calls only the method Goodbye:

        d = c - a;

        Console.WriteLine("Invoking delegate a:");

        a("A");

        Console.WriteLine("Invoking delegate b:");

        b("B");

        Console.WriteLine("Invoking delegate c:");

        c("C");

        Console.WriteLine("Invoking delegate d:");

        d("D");

    }

}

输出

Invoking delegate a:

 Hello, A!

Invoking delegate b:

 Goodbye, B!

Invoking delegate c:

 Hello, C!

 Goodbye, C!

Invoking delegate d:

 Goodbye, D!

委托和事件

委托非常适合于用作事件(从一个组件就该组件中的更改通知侦听器)。

委托与接口

委托和接口的类似之处是,它们都允许分隔规范和实现。多个独立的作者可以生成与一个接口规范兼容的多个实现。类似地,委托指定方法的签名,多个作者可以编写与委托规范兼容的多个方法。何时应使用接口,而何时应使用委托呢?

委托在以下情况下很有用:

调用单个方法。

一个类可能希望有方法规范的多个实现。

希望允许使用静态方法实现规范。

希望类似事件的设计模式。

调用方不需要知道或获得在其上定义方法的对象。

实现的提供程序希望只对少数选择组件分发规范实现。

需要方便的组合。

接口在以下情况下很有用:

规范定义将调用的一组相关方法。

类通常只实现规范一次。

接口的调用方希望转换为接口类型或从接口类型转换,以获得其他接口或类。
posted @ 2006-05-10 10:06 leithon 阅读(35) 评论(0) 编辑

2006年5月8日

解析C语言中的sizeof

一、sizeof的概念 
  sizeof是C语言的一种单目操作符,如C语言的其他操作符++、--等。它并不是函数。sizeof操作符以字节形式给出了其操作数的存储大小。操作数可以是一个表达式或括在括号内的类型名。操作数的存储大小由操作数的类型决定。 

二、sizeof的使用方法 
  1、用于数据类型 

  sizeof使用形式:sizeof(type) 

  数据类型必须用括号括住。如sizeof(int)。 

  2、用于变量 

  sizeof使用形式:sizeof(var_name)或sizeof var_name 

  变量名可以不用括号括住。如sizeof (var_name),sizeof var_name等都是正确形式。带括号的用法更普遍,大多数程序员采用这种形式。 

  注意:sizeof操作符不能用于函数类型,不完全类型或位字段。不完全类型指具有未知存储大小的数据类型,如未知存储大小的数组类型、未知内容的结构或联合类型、void类型等。 

  如sizeof(max)若此时变量max定义为int max(),sizeof(char_v) 若此时char_v定义为char char_v [MAX]且MAX未知,sizeof(void)都不是正确形式。 

三、sizeof的结果 
  sizeof操作符的结果类型是size_t,它在头文件

中typedef为unsigned int类型。该类型保证能容纳实现所建立的最大对象的字节大小。 

  1、若操作数具有类型char、unsigned char或signed char,其结果等于1。 

  ANSI C正式规定字符类型为1字节。 

  2、int、unsigned int 、short int、unsigned short 、long int 、unsigned long 、float、double、long double类型的sizeof 在ANSI C中没有具体规定,大小依赖于实现,一般可能分别为2、2、2、2、4、4、4、8、10。 

  3、当操作数是指针时,sizeof依赖于编译器。例如Microsoft C/C++7.0中,near类指针字节数为2,far、huge类指针字节数为4。一般Unix的指针字节数为4。 

  4、当操作数具有数组类型时,其结果是数组的总字节数。 

  5、联合类型操作数的sizeof是其最大字节成员的字节数。结构类型操作数的sizeof是这种类型对象的总字节数,包括任何垫补在内。 

  让我们看如下结构: 

  struct {char b; double x;} a; 

  在某些机器上sizeof(a)=12,而一般sizeof(char)+ sizeof(double)=9。 

  这是因为编译器在考虑对齐问题时,在结构中插入空位以控制各成员对象的地址对齐。如double类型的结构成员x要放在被4整除的地址。 

  6、如果操作数是函数中的数组形参或函数类型的形参,sizeof给出其指针的大小。 

四、sizeof与其他操作符的关系 
  sizeof的优先级为2级,比/、%等3级运算符优先级高。它可以与其他操作符一起组成表达式。如i*sizeof(int);其中i为int类型变量。 

五、sizeof的主要用途 
  1、sizeof操作符的一个主要用途是与存储分配和I/O系统那样的例程进行通信。例如: 

  void *malloc(size_t size), 

  size_t fread(void * ptr,size_t size,size_t nmemb,FILE * stream)。 

  2、sizeof的另一个的主要用途是计算数组中元素的个数。例如: 

  void * memset(void * s,int c,sizeof(s))。 

六、建议 
  由于操作数的字节数在实现时可能出现变化,建议在涉及到操作数字节大小时用sizeof来代替常量计算。
本文主要包括二个部分,第一部分重点介绍在VC中,怎么样采用sizeof来求结构的大小,以及容易出现的问题,并给出解决问题的方法,第二部分总结出VC中sizeof的主要用法。

1、 sizeof应用在结构上的情况

请看下面的结构:

struct MyStruct

{

double dda1;

char dda;

int type

};

对结构MyStruct采用sizeof会出现什么结果呢?sizeof(MyStruct)为多少呢?也许你会这样求:

sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13

但是当在VC中测试上面结构的大小时,你会发现sizeof(MyStruct)为16。你知道为什么在VC中会得出这样一个结果吗?

其实,这是VC对变量存储的一个特殊处理。为了提高CPU的存储速度,VC对一些变量的起始地址做了“对齐”处理。在默认情况下,VC规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。下面列出常用类型的对齐方式(vc6.0,32位系统)。

类型
对齐方式(变量存放的起始地址相对于结构的起始地址的偏移量)

Char
偏移量必须为sizeof(char)即1的倍数

int
偏移量必须为sizeof(int)即4的倍数

float
偏移量必须为sizeof(float)即4的倍数

double
偏移量必须为sizeof(double)即8的倍数

Short
偏移量必须为sizeof(short)即2的倍数

 

各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节VC会自动填充。同时VC为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。

下面用前面的例子来说明VC到底怎么样来存放结构的。

struct MyStruct

{

double dda1;

char dda;

int type

};

为上面的结构分配空间的时候,VC根据成员变量出现的顺序和对齐方式,先为第一个成员dda1分配空间,其起始地址跟结构的起始地址相同(刚好偏移量0刚好为sizeof(double)的倍数),该成员变量占用sizeof(double)=8个字节;接下来为第二个成员dda分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为8,是sizeof(char)的倍数,所以把dda存放在偏移量为8的地方满足对齐方式,该成员变量占用sizeof(char)=1个字节;接下来为第三个成员type分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为9,不是sizeof(int)=4的倍数,为了满足对齐方式对偏移量的约束问题,VC自动填充3个字节(这三个字节没有放什么东西),这时下一个可以分配的地址对于结构的起始地址的偏移量为12,刚好是sizeof(int)=4的倍数,所以把type存放在偏移量为12的地方,该成员变量占用sizeof(int)=4个字节;这时整个结构的成员变量已经都分配了空间,总的占用的空间大小为:8+1+3+4=16,刚好为结构的字节边界数(即结构中占用最大空间的类型所占用的字节数sizeof(double)=8)的倍数,所以没有空缺的字节需要填充。所以整个结构的大小为:sizeof(MyStruct)=8+1+3+4=16,其中有3个字节是VC自动填充的,没有放任何有意义的东西。

下面再举个例子,交换一下上面的MyStruct的成员变量的位置,使它变成下面的情况:

struct MyStruct

{

char dda;

double dda1;

int type

};

这个结构占用的空间为多大呢?在VC6.0环境下,可以得到sizeof(MyStruc)为24。结合上面提到的分配空间的一些原则,分析下VC怎么样为上面的结构分配空间的。(简单说明)

struct MyStruct

{

char dda;//偏移量为0,满足对齐方式,dda占用1个字节;

double dda1;//下一个可用的地址的偏移量为1,不是sizeof(double)=8

//的倍数,需要补足7个字节才能使偏移量变为8(满足对齐

//方式),因此VC自动填充7个字节,dda1存放在偏移量为8

//的地址上,它占用8个字节。

int type;//下一个可用的地址的偏移量为16,是sizeof(int)=4的倍

//数,满足int的对齐方式,所以不需要VC自动填充,type存

//放在偏移量为16的地址上,它占用4个字节。

};//所有成员变量都分配了空间,空间总的大小为1+7+8+4=20,不是结构

//的节边界数(即结构中占用最大空间的类型所占用的字节数sizeof

//(double)=8)的倍数,所以需要填充4个字节,以满足结构的大小为

//sizeof(double)=8的倍数。

 

所以该结构总的大小为:sizeof(MyStruc)为1+7+8+4+4=24。其中总的有7+4=11个字节是VC自动填充的,没有放任何有意义的东西。

 

VC对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。

VC中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;

否则必须为n的倍数。下面举例说明其用法。

#pragma pack(push) //保存对齐状态

#pragma pack(4)//设定为4字节对齐

struct test

{

char m1;

double m4;

int m3;

};

#pragma pack(pop)//恢复对齐状态

以上结构的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1占用1个字节。接着开始为m4分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n),m4占用8个字节。接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。这时已经为所有成员变量分配了空间,共分配了16个字节,满足为n的倍数。如果把上面的#pragma pack(4)改为#pragma pack(16),那么我们可以得到结构的大小为24。(请读者自己分析)

2、 sizeof用法总结

在VC中,sizeof有着许多的用法,而且很容易引起一些错误。下面根据sizeof后面的参数对sizeof的用法做个总结。

A. 参数为数据类型或者为一般变量。例如sizeof(int),sizeof(long)等等。这种情况要注意的是不同系统系统或者不同编译器得到的结果可能是不同的。例如int类型在16位系统中占2个字节,在32位系统中占4个字节。

B. 参数为数组或指针。下面举例说明.

int a[50]; //sizeof(a)=4*50=200; 求数组所占的空间大小

int *a=new int[50];// sizeof(a)=4; a为一个指针,sizeof(a)是求指针

//的大小,在32位系统中,当然是占4个字节。

C. 参数为结构或类。Sizeof应用在类和结构的处理情况是相同的。但有两点需要注意,第一、结构或者类中的静态成员不对结构或者类的大小产生影响,因为静态变量的存储位置与结构或者类的实例地址无关。

第二、没有成员变量的结构或类的大小为1,因为必须保证结构或类的每一

个实例在内存中都有唯一的地址。

下面举例说明,

Class Test{int a;static double c};//sizeof(Test)=4.

Test *s;//sizeof(s)=4,s为一个指针。

Class test1{ };//sizeof(test1)=1;

D. 参数为其他。下面举例说明。

int func(char s[5]);

{

cout<
//数的参数在传递的时候系统处理为一个指针,所

//以sizeof(s)实际上为求指针的大小。

return 1;

}

sizeof(func(“1234”))=4//因为func的返回类型为int,所以相当于

//求sizeof(int).

 

以上为sizeof的基本用法,在实际的使用中要注意分析VC的分配变量的分配策略,这样的话可以避免一些错误。

posted @ 2006-05-08 17:01 leithon 阅读(51) 评论(0) 编辑
 
摘要: 关于硬盘的一切!(结构-发展-参数-维护-修复)一:目录如下二:浅谈硬盘发展史三:硬盘“空间”与“文件大小”秘密四:新手学堂之看图识硬盘五:跳出硬盘认识误区/ 硬盘修复之低级格式化 /深入了解硬盘参数 六:硬盘常见参数讲解与常见误区七:硬盘的结构八:看图轻松学会硬盘安装方法九:硬盘损坏的种类十:高手:挽救硬盘的几个方法十一:厂家维修硬盘的方法十二:第...阅读全文
posted @ 2006-05-08 16:59 leithon 阅读(4251) 评论(0) 编辑
 
程序员在编写应用程序的时候往往要将程序的某些数据存储在内存中,然后将其写入某个文件或是将它传输到网络中的另一台计算机上以实现通讯。这个将程序数据转化成能被存储并传输的格式的过程被称为"序列化"(Serialization),而它的逆过程则可被称为"反序列化"(Deserialization)。

  .Net框架对序列化机制具有非常好的支持,它提供了两个名字空间(namespace):System.Runtime.Serialization和System.Runtime.Serialization.Formatters以完成序列化机制的大部分功能。系列化这项技术可以应用在将程序产生的结果数据存储到文件系统中,但是它更主要的应用是在于.Net Remoting和Web服务的实现上。

  序列化机制的实现是依靠格式器(Formatter)而完成的,它是一个从System.Runtime.Serialization.IFormatter继承下来的类的对象。格式器完成了将程序数据转化到能被存储并传输的格式的工作,同时也完成了将数据转化回来的工作。.Net框架为程序员提供了两种类型的格式器,一种通常是应用于桌面类型的应用程序的,它一个是System.Runtime.Serialization.Formatters.Binary.BinaryFormatter类的对象,而另一种则更主要的应用于.Net Remoting和XML Web服务等领域的,它一个是System.Runtime.Serialization.Formatters.Soap.SoapFormatter类的对象。从它们的名称来看,我们不妨将它们分别称为二进制格式器和XML格式器。

  本文将从这两个格式器入手,先向大家介绍分别用它们如何实现序列化和反序列化,然后比较两种格式器的不同点。接着我会向大家介绍实现序列化对对象类型的一些要求,同时还要向大家介绍两种不同的序列化方式:基本序列化(Basic Serialization)和自定义序列化(Custom Serialization)。最后,我还会给大家介绍一个实例程序以加深大家对序列化机制的理解程度。

  一.二进制格式器(Binary Formatter) vs XML格式器(XML Formatter):

  下面我先向大家介绍两种不同的格式器,分别用它们如何实现序列化机制和反序列化机制,请看下面的代码:

#region Binary Serializers
public static System.IO.MemoryStream SerializeBinary(object request) {
 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter serializer =
  new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
 System.IO.MemoryStream memStream = new System.IO.MemoryStream();
 serializer.Serialize(memStream, request);
 return memStream;
}

public static object DeSerializeBinary(System.IO.MemoryStream memStream) {
 memStream.Position=0;
 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter deserializer =
  new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
 object newobj = deserializer.Deserialize(memStream);
 memStream.Close();
 return newobj;
}
#endregion

#region XML Serializers
public static System.IO.MemoryStream SerializeSOAP(object request) {
 System.Runtime.Serialization.Formatters.Soap.SoapFormatter serializer =
  new System.Runtime.Serialization.Formatters.Soap.SoapFormatter();
 System.IO.MemoryStream memStream = new System.IO.MemoryStream();
 serializer.Serialize(memStream, request);
 return memStream;
}

public static object DeSerializeSOAP(System.IO.MemoryStream memStream) {
 object sr;
 System.Runtime.Serialization.Formatters.Soap.SoapFormatter deserializer =
  new System.Runtime.Serialization.Formatters.Soap.SoapFormatter();
 memStream.Position=0;
 sr = deserializer.Deserialize(memStream);
 memStream.Close();
 return sr;
}
#endregion

  从上面的代码我们可以发现无论运用哪种格式器,其基本的过程都是一样的,而且都是非常容易实现的,唯一的不同就是定义格式器的类型不同。不过在实际的应用中,二进制格式器往往应用于一般的桌面程序和网络通讯程序中,而XML格式器禀承了XML技术的优点,大多数被应用于.Net Remoting和XML Web服务等领域。下面我们来分析一下两种格式器各自的优点。

  二进制序列化的优点:

  1. 所有的类成员(包括只读的)都可以被序列化;

  2. 性能非常好。

  XML序列化的优点:

  1. 互操作性好;

  2. 不需要严格的二进制依赖;

  3. 可读性强。

  通过分析上面的代码,我们知道了选择二进制序列化的方式还是选择XML序列化的方式仅仅是对不同的格式器进行选择而已。你可以根据实际的需要选择相应的格式器完成序列化和反序列化工作。同时请注意,代码中的序列化函数和反序列化函数仅仅是在调用Serialize()和Deserialize()这两个核心函数上产生了差别,即它们的参数不同。因此以上的代码完成了一些最最基本但是很重要的功能,你可以将它们运用在你的程序中,或是将其进行适当扩充以满足程序的特定需要。

二.序列化机制对类的要求:

  如果你要对一个对象进行序列化,那么你必须将它的类型标记为[Serializable()],该操作是通过SerializableAttribute属性来实现的。将SerializableAttribute属性应用于一种数据类型可表明该数据类型的实例可以被序列化。如果正在序列化的对象图中的任何类型未应用SerializableAttribute属性,公共语言运行库则会引发SerializationException。默认情况下,类型中由SerializableAttribute标记的所有公共和私有字段都会进行序列化,除非该类型实现ISerializable接口来重写序列化进程(通过实现该接口我们便可以实现将在后面介绍的"自定义序列化")。默认的序列化进程会排除用NonSerializedAttribute属性标记的字段,即你可以将该类型标记为[NonSerialized()]以表明它是不可以被序列化的。如果可序列化类型的字段包含指针、句柄或其他某些针对于特定环境的数据结构,并且不能在不同的环境中以有意义的方式重建,则最好将NonSerializedAttribute属性应用于该字段。有关序列化的更多信息,请参阅System.Runtime.Serialization名字空间中的相关内容。

  下面我给大家介绍一个例子,以显示如何正确的运用SerializableAttribute属性和NonSerializedAttribute属性。该程序中运用到了XML格式器,不过同时给出了二进制格式器为参考(程序中将其用"//"标注),其实现的结果是一样的。该程序实现的功能是在序列化和反序列化操作前后测试对象因包含了[NonSerialized()]的字段而显示不同的屏幕打印结果。其代码如下:

 

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Soap;
//using System.Runtime.Serialization.Formatters.Binary;

public class Test {
 public static void Main() {
  // 创建一个新的测试对象
  TestSimpleObject obj = new TestSimpleObject();

  Console.WriteLine("Before serialization the object contains: ");
  obj.Print();

  // 创建一个文件"data.xml"并将对象序列化后存储在其中
  Stream stream = File.Open("data.xml", FileMode.Create);
  SoapFormatter formatter = new SoapFormatter();
  //BinaryFormatter formatter = new BinaryFormatter();

  formatter.Serialize(stream, obj);
  stream.Close();
  
  // 将对象置空
  obj = null;

  // 打开文件"data.xml"并进行反序列化得到对象
  stream = File.Open("data.xml", FileMode.Open);
  formatter = new SoapFormatter();
  //formatter = new BinaryFormatter();

  obj = (TestSimpleObject)formatter.Deserialize(stream);
  stream.Close();

  Console.WriteLine("");
  Console.WriteLine("After deserialization the object contains: ");
  obj.Print();
 }
}

// 一个要被序列化的测试对象的类
[Serializable()]
 public class TestSimpleObject {
 public int member1;
 public string member2;
 public string member3;
 public double member4;

 // 标记该字段为不可被序列化的
[NonSerialized()] public string member5;

public TestSimpleObject() {
 member1 = 11;
 member2 = "hello";
 member3 = "hello";
 member4 = 3.14159265;
 member5 = "hello world!";
}

public void Print() {
 Console.WriteLine("member1 = '{0}'", member1);
 Console.WriteLine("member2 = '{0}'", member2);
 Console.WriteLine("member3 = '{0}'", member3);
 Console.WriteLine("member4 = '{0}'", member4);
 Console.WriteLine("member5 = '{0}'", member5);
}
}

三.基本序列化(Basic Serialization) vs 自定义序列化(Custom Serialization):

  .Net框架为我们提供了两种方式的序列化:一种为基本序列化、另一种为自定义序列化。值得注意的是,序列化的方式和前面提到的序列化的格式是不同的概念。序列化的方式是指.Net框架将程序的数据转化为能被存储并传输的格式的实际过程,它是不管程序员运用了何种类型的格式器的(二进制格式器还是XML格式器)。而序列化的格式则指程序的数据是被转化成二进制格式了还是被转化成XML格式了。

  完成序列化的最简单的方法便是让.Net框架自动为我们完成整个过程,而我们不必去管它内部是如何具体实现的,这种方法便是前面提到的"基本序列化"。在这种方式下,我们需要做的仅仅是将类标记上[Serializable()]属性。然后.Net框架便调用该类的对象并将它转化为所需的格式。同时你还可以控制其中的某些字段不被序列化,方法就是前面所述的将该字段标记上[NonSerialized()]属性。这样,最最简单和基本的序列化工作就完成了,不过其内部是如何实现的你是不得而知的,同时你也不能进一步控制序列化过程的程序行为。

  如果你要获得对序列化的更大的控制权,那么你就得使用"自定义序列化"的方式。通过使用这种方式,你可以完全的控制类的哪些部分能被序列化而哪些部分不能,同时你还可以控制如何具体的进行序列化。运用该方式的好处就是能克服基本序列化所会遇到的问题。我们在运用基本序列化将一个类的对象序列化完毕并存储在文件中后,假设该对象原来有三个字段,如果此时该对象增加了一个字段,那么再将该对象从文件中反序列化出来时会发生字段数不一致的错误。这样的问题是基本序列化所不能解决的,只能运用自定义序列化的方式来解决。

  在介绍自定义序列化之前,我先给出介绍过程中所要用到的实例程序的代码。这是一个时间安排程序,其中要用到将不同的时间格式进行转化的操作。所以运用序列化的机制能很好的解决这个问题。

 

using System;
using System.Runtime.Serialization;

namespace SerializationSample {
[Serializable()]
 public class Schedule {
  protected System.DateTime start;
  protected System.DateTime end;
  // 每个时间间隔所要增加的毫秒数
  protected long interval;

  public System.DateTime Start {get{return start;}set{start=value;}}
  public System.DateTime End {get{return end;}set{end=value;}}
  public long Interval {get{return interval;}set{interval=value;}}
  public Schedule(System.DateTime Start, System.DateTime End, long Interval) {
   start=Start;
   end=End;
  interval=Interval;
}

// 如果已经到了结束的时间,则返回结束时间,否则返回下一次运行的时间
public System.DateTime NextRunTime {
 get {
  System.TimeSpan ts = new System.TimeSpan(end.Ticks-System.DateTime.Now.Ticks);
  if(ts.Milliseconds>0) {
   return System.DateTime.Now.AddMilliseconds(interval);
  } else {
   return end;
  }
 }
}
}
}

  自定义序列化:

  下面我就向大家介绍自定义序列化以及反序列化的具体过程。首先,程序的类必须实现System.Runtime.Serialization.ISerializable接口,该接口的功能就是允许对象控制其自己的序列化和反序列化过程。所以我们得重新定义上面的类:

[Serializable()]
public class ScheduleCustom : System.Runtime.Serialization.Iserializable

  接下来,我们必须对该接口调用GetObjectData()的实现,也即我们必须在上面的类中给出GetObjectData()的具体实现。其函数原型如下:

void GetObjectData(SerializationInfo info, StreamingContext context);

  上面的类中GetObjectData()的具体实现如下:

public void GetObjectData(SerializationInfo info,StreamingContext context) {
// 运用info对象来添加你所需要序列化的项
info.AddValue("start", start);
info.AddValue("end", end);
info.AddValue("interval", interval);
}

   然而对于这么一个简单的方法,读者可能不能理会到系列化带来的强大功能,所以下面我就给这个方法添加一些东西。如果在系列化过程中我们要查看类型为DateTime的"start"属性的输出的话,其结果会是.Net框架默认的格式:

  2002-12-19T14:09:13.3457440-07:00

  而对于没有.Net框架的用户,或是在其他时间区域内的用户而言,这么一个格式的时间可能是非常难以理解的,所以我们有必要将时间的格式转化为格林威治标准时间格式,于是修改GetObjectData()方法如下:

public void GetObjectData(SerializationInfo info,StreamingContext context) {
// 运用info对象来添加你所需要序列化的项
// 同时,将"start"和"end"属性的时间格式转化为格林威治标准时间格式
info.AddValue("start", System.TimeZone.CurrentTimeZone.ToUniversalTime(start));
info.AddValue("end", System.TimeZone.CurrentTimeZone.ToUniversalTime(end));
info.AddValue("interval", interval);
info.AddValue("timeformat", "utc");
}

  这样一来,我们在系列化过程中查看"start"属性时就会得到如下结果:

   8/19/2002 9:09:13 PM

  同时请注意我们在GetObjectData()方法中添加的一个名为"timeformat"的额外属性,通过它我们可以方便的知道系列化过程中所使用的时间格式。如果有兴趣的话,你还可以从System.Globalization.DateTimeFormatInfo这个名字空间中获取更多有关时间格式的信息。

  自定义反序列化:

  你可以通过调用一个自定义的构造函数来完成自定义反序列化的操作。该构造函数的定义如下:

public ScheduleCustom (SerializationInfo info,StreamingContext context);

  在上面的类中,我们的ScheduleCustom()方法将完成把时间格式从格林威治标准时间格式反序列化为当地时间的格式的操作,其函数实现如下:

public ScheduleCustom (SerializationInfo info,StreamingContext context) {
 this.start = info.GetDateTime("start").ToLocalTime();
 this.end = info.GetDateTime("end").ToLocalTime();
 this.interval = info.GetInt32("interval");
}

  在完成自定义序列化和自定义反序列化后,我们的时间安排程序变成了如下形式:

using System;
using System.Runtime.Serialization;

namespace SerializationSample {
[Serializable()]
 public class ScheduleCustom : System.Runtime.Serialization.ISerializable {
 protected System.DateTime start;
 protected System.DateTime end;
 // 每个时间间隔所要增加的毫秒数
 protected long interval;

 public System.DateTime Start {get{return start;}set{start=value;}}
 public System.DateTime End {get{return end;}set{end=value;}}
 public long Interval {get{return interval;}set{interval=value;}}

 public ScheduleCustom(System.DateTime Start, System.DateTime End, long Interval) {
  start=Start;
  end=End;
  interval=Interval;
 }

// 如果已经到了结束的时间,则返回结束时间,否则返回下一次运行的时间
public System.DateTime NextRunTime {
 get {
  System.TimeSpan ts = new System.TimeSpan(end.Ticks-System.DateTime.Now.Ticks);
  if(ts.Milliseconds>0) {
   return System.DateTime.Now.AddMilliseconds(interval);
  } else {
   return end;
  }
 }
}

public void GetObjectData(SerializationInfo info,StreamingContext context) {
 // 运用info对象来添加你所需要序列化的项
 // 同时,将"start"和"end"属性的时间格式转化为格林威治标准时间格式
 info.AddValue("start", System.TimeZone.CurrentTimeZone.ToUniversalTime(start));
 info.AddValue("end", System.TimeZone.CurrentTimeZone.ToUniversalTime(end));
 info.AddValue("interval", interval);
 info.AddValue("timeformat", "utc");
}

public ScheduleCustom (SerializationInfo info,StreamingContext context) {
 this.start = info.GetDateTime("start").ToLocalTime();
 this.end = info.GetDateTime("end").ToLocalTime();
 this.interval = info.GetInt32("interval");
 }
}
}

  四.总结:

  本文向大家介绍了.Net框架下系列化机制的一些基本概念和基本的运用方法,读者在读完本文后,应该对以下几个概念有个初步了解:二进制系列化、XML系列化、基本序列化和自定义系列化,并应能够完成一些基本的系列化应用。最后,希望大家能合理有效的运用系列化机制并发挥它的功效以更好地满足实际工作需要。

posted @ 2006-05-08 16:58 leithon 阅读(84) 评论(0) 编辑
 

<!--StartFragment-->//获得汉字的区位码
  byte[] array = new byte[2];
  array = System.Text.Encoding.Default.GetBytes("啊");

int i1 = (short)(array[0] - ''\0'');
  int i2 = (short)(array[1] - ''\0'');

//unicode解码方式下的汉字码
  array = System.Text.Encoding.Unicode.GetBytes("啊");
  i1 = (short)(array[0] - ''\0'');
  i2 = (short)(array[1] - ''\0'');


//unicode反解码为汉字
  string str = "4a55";
  string s1 = str.Substring(0,2);
  string s2 = str.Substring(2,2);


int t1 = Convert.ToInt32(s1,16);
  int t2 = Convert.ToInt32(s2,16);


array[0] = (byte)t1;
  array[1] = (byte)t2;


string s = System.Text.Encoding.Unicode.GetString(array);


//default方式反解码为汉字
  array[0] = (byte)196;
  array[1] = (byte)207;
  s = System.Text.Encoding.Default.GetString(array);


//取字符串长度
  s = "iam方枪枪";
  int len = s.Length;//will output as 6
  byte[] sarr = System.Text.Encoding.Default.GetBytes(s);
  len = sarr.Length;//will output as 3+3*2=9


//字符串相加
  System.Text.StringBuilder sb = new System.Text.StringBuilder("");
  sb.Append("i ");
  sb.Append("am ");
  sb.Append("方枪枪");

/////////////////////////////////////////////////////////////////////


string --> byte array

byte[] data=Syste.Text.Encoding.ASCII.GetBytes(string);

string --> byte

byte data = Convert.ToByte(string);

byte[]-->string

string string = Encoding.ASCII.GetString( bytes, 0, nBytesSize );

posted @ 2006-05-08 16:55 leithon 阅读(54) 评论(0) 编辑
 
序列化:
1、定义结构
[Serializable()]
public struct structname
{
...
}
2、序列化
public static byte[] ObjectToByteA(object obj)
{
MemoryStream fs = new MemoryStream();
byte[] tmp = null;
try
{
// 序列化
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(fs, obj);
tmp = fs.ToArray();
}
catch(Exception e)
{
MessageBox.Show(e.ToString(), "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
fs.Close();
}
return tmp;
}
3、反序列化
public static object ByteAToObject(byte[] ba)
{
MemoryStream fs = new MemoryStream();
object obj = null;
try
{
// 反序列化
fs = new MemoryStream(ba);
fs.Position = 0;
BinaryFormatter formatter = new BinaryFormatter();
obj = formatter.Deserialize(fs);
}
catch(Exception e)
{
MessageBox.Show(e.ToString(), "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
fs.Close();
}
return obj;
}
4、using引用
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;


以此为基础,可以完整实现Socket通信中结构或者对象的传输
posted @ 2006-05-08 16:24 leithon 阅读(43) 评论(0) 编辑

2006年5月1日

      对网络编程不熟,网络通信只略知一二,不知设计方法是否可行
1 .  服务器端与客户端的数据交互
      由于Remoting传输较慢,因此初步想用Socket来实现数据通信.
      又客户与服务器的通信数据较为多而杂,因此不宜使用字节频繁交互,比如客户端用户注册信息向服务器发送,我想可以先成包含有用户信息的xml文件,然后通过socket进行文件传输到服务器端,传输完毕后删除刚建立的xml文件.
 
2.服务器端数据在客户端的显示
       通过TreeView来显示文件夹及文件列表,每个文件夹为一个节点,可通过递规实现,问题是如何将服务器端扫描所得数据完整传输到客户端,使得客户端正确显示文件列表.
 
3.文件如何实现共享
       服务器用户文件保存路径与用户ID相关,比如用户ID 为00001,则用户文件的保存路径为根目录/0/0/0/0/1
,初步想法是专门建立一个文件来保存可共享文件的路径.

       想法太幼稚,还希望高手来指导.
posted @ 2006-05-01 02:06 leithon 阅读(382) 评论(1) 编辑

2005年11月10日

        对人要有责任,对事要有责任,同样对代码也要有责任,奇怪为什么作者会说代码让猫吃了,在外国人眼里猫很重要吗?扮演着什么样的角色?
        熵,即无序,放在代码里面就是bug。Don't live with broken windows.
        Be a catalyst  for change.
        Make quality a requirements issue.
        编程就像绘画,一层又一层重复的叠加就会让绘画消失在绘制中。
       
posted @ 2005-11-10 19:53 leithon 阅读(31) 评论(0) 编辑

2005年11月2日

摘要: 面向对象程序设计开题报告 第一篇文章竟然是关于JAVA的,无奈最近作业太多了,还得应付。好久没好好学.NET了,凭着对Petshop的理解,JAVA的这个项目就暂定为一个简易的二手信息交流平台,采用MVC模式,达到要求就可以了。 基于MVC模式的二手信息发布平台一、 功能描述该项目主要是给大众提供一个二手信息的发布平台,功能主要包括两个部分,其中之一就是用户基本信息的录入,也就是新用户注册,另外...阅读全文
posted @ 2005-11-02 22:07 leithon 阅读(201) 评论(0) 编辑