随笔 - 16  文章 - 0 评论 - 12 trackbacks - 0
<2008年10月>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

与我联系

搜索

 

常用链接

留言簿

随笔档案

最新评论

阅读排行榜

评论排行榜

  在多线程中,为了使数据保持一致性必须要对数据或是访问数据的函数加锁,在数据库中这是很常见的,但是在程序中由于大部分都是单线程的程序,所以没有加锁的必要,但是在多线程中,为了保持数据的同步,一定要加锁,好在Framework中已经为我们提供了三个加锁的机制,分别是Monitor类、Lock关键字和Mutex类。
        其中Lock关键词用法比较简单,Monitor类和Lock的用法差不多。这两个都是锁定数据或是锁定被调用的函数。而Mutex则多用于锁定多线程间的同步调用。简单的说,Monitor和Lock多用于锁定被调用端,而Mutex则多用锁定调用端。
例如下面程序:由于这种程序都是毫秒级的,所以运行下面的程序可能在不同的机器上有不同的结果,在同一台机器上不同时刻运行也有不同的结果,我的测试环境为vs2005, windowsXp , CPU3.0 , 1 G monery。
        程序中有两个线程thread1、thread2和一个TestFunc函数,TestFunc会打印出调用它的线程名和调用的时间(mm级的),两个线程分别以30mm和100mm来调用TestFunc这个函数。TestFunc执行的时间为50mm。程序如下:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace MonitorLockMutex
{
    class Program
    {
        #region variable
        Thread thread1 = null;
        Thread thread2 = null;
        Mutex mutex = null;
        #endregion
        static void Main(string[] args)
        {
            Program p = new Program();
            p.RunThread();
            Console.ReadLine();
        }
        public Program()
        {
            mutex = new Mutex();
            thread1 = new Thread(new ThreadStart(thread1Func));
            thread2 = new Thread(new ThreadStart(thread2Func));
        }
        public void RunThread()
        {
            thread1.Start();
            thread2.Start();
        }
        private void thread1Func()
        {
            for (int count = 0; count < 10; count++)
            {
                TestFunc("Thread1 have run " + count.ToString() + " times");
                Thread.Sleep(30);
            }
        }
        private void thread2Func()
        {
            for (int count = 0; count < 10; count++)
            {
                TestFunc("Thread2 have run " + count.ToString() + " times");
                Thread.Sleep(100);
            }
        }
        private void TestFunc(string str)
        {
            Console.WriteLine("{0} {1}", str, System.DateTime.Now.Millisecond.ToString());
            Thread.Sleep(50);
        }
    }
}
运行结果如下:
        可以看出如果不加锁的话,这两个线程基本上是按照各自的时间间隔+TestFunc的执行时间(50mm)对TestFunc函数进行读取。因为线程在开始时需要分配内存,所以第0次的调用不准确,从第1~9次的调用可以看出,thread1的执行间隔约是80mm,thread2的执行间隔约是150mm。
现在将TestFunc修改如下:
private void TestFunc(string str)
{
   lock (this)
   {
      Console.WriteLine("{0} {1}", str, System.DateTime.Now.Millisecond.ToString());
      Thread.Sleep(50);
   }
}
或者是用Monitor也是一样的,如下:
private void TestFunc(string str)
{
      Monitor.Enter(this);
      Console.WriteLine("{0} {1}", str, System.DateTime.Now.Millisecond.ToString());
      Thread.Sleep(50);
      Monitor.Exit(this);
}
其中Enter和Exit都是Monitor中的静态方法。
运行Lock结果如下:
        让我们分析一下结果,同样从第1次开始。相同线程间的调用时间间隔为线程执行时间+TestFunc调用时间,不同线程间的调用时间间隔为TestFunc调用时间。例如:连续两次调用thread1之间的时间间隔约为30+50=80;连续两次调用thread2之间的时间间隔约为100+50=150mm。调用thread1和thread2之间的时间间隔为50mm。因为TestFunc被lock住了,所以一个thread调用TestFunc后,当其它的线程也同时调用TestFunc时,后来的线程即进被排到等待队列中等待,直到拥有访问权的线程释放这个资源为止。
        这就是锁定被调用函数的特性,即只能保证每次被一个线程调用,线程优先级高的调用的次数就多,低的就少,这就是所谓的强占式。
        下面让我们看看Mutex类的使用方法,以及与Monitor和Lock的区别。
将代码修改如下:
        private void thread1Func()
        {
            for (int count = 0; count < 10; count++)
            {
                mutex.WaitOne();
                TestFunc("Thread1 have run " + count.ToString() + " times");
                mutex.ReleaseMutex();
            }
        }
 
        private void thread2Func()
        {
            for (int count = 0; count < 10; count++)
            {
                mutex.WaitOne();
                TestFunc("Thread2 have run " + count.ToString() + " times");
                mutex.ReleaseMutex();
            }
        }
 
        private void TestFunc(string str)
        {
            Console.WriteLine("{0} {1}", str, System.DateTime.Now.Millisecond.ToString());
            Thread.Sleep(50);
        }
运行结果如下:
 
        可以看出,Mutex只能互斥线程间的调用,但是不能互斥本线程的重复调用,即thread1中waitOne()只对thread2中的waitOne()起到互斥的作用,但是thread1并不受本wainOne()的影响,可以调用多次,只是在调用结束后调用相同次数的ReleaseMutex()就可以了。
        那么如何使线程按照调用顺序来依次执行呢?其实把lock和Mutex结合起来使用就可以了,改代码如下:
        private void thread1Func()
        {
            for (int count = 0; count < 10; count++)
            {
                lock (this)
                {
                    mutex.WaitOne();
                    TestFunc("Thread1 have run " + count.ToString() + " times");
                    mutex.ReleaseMutex();
                }
            }
        }
 
        private void thread2Func()
        {
            for (int count = 0; count < 10; count++)
            {
                lock (this)
                {
                    mutex.WaitOne();
                    TestFunc("Thread2 have run " + count.ToString() + " times");
                    mutex.ReleaseMutex();
                }
            }
        }
posted @ 2008-09-16 19:07 朝阳 阅读(141) | 评论 (0)编辑

快毕业了,就要找工作了,还有老多东西都给忘了,先整理一下,备忘,也给需要的朋友和我一个方便,如果你有好的资料,希望能和我分享!

**黑板模式**

      黑板模式是一种常用的架构模式,应用中的多种不同数据处理逻辑相互影响和协同来完成数据分析处理。就好像多位不同的专家在同一黑板上交流思想,每个专家都可以获得别的专家写在黑板上的信息,同时也可以用自己的分析去更新黑板上的信息,从而影响其它专家。
在实际应用中常见的实现模式有:
A 利用数据库
利用数据库充当黑板,不同的应用共享数据库中信息,并且可以更新数据信息。这也是最常见的实现方式。
特点:
1 便于实现信息的查询,筛选和统计,这方面关系数据库提供了SQL 92的强大支持。
2 不能用于较高实时性要求的环境,这种实现是工作在“拉模式”下的,并且高频率的访问数据库会导致严重的系统性能问题。
B 利用发布—订阅模式
这种实现方式通常采用消息队列作为黑板,队列工作在主题模式(Topic),专家作为队列的订阅者,同时可以向队列发送消息,消息会被发送至所有订阅者。以上过程实现了专家间的信息交流。
特点:
1 可以有效应用于实时性要求较高的系统,这种实现工作在“推模式”下。
2 难于实现信息的统计分析,不像实现方式一那样可以通过SQL支持,这些工作必须开发者自己完成。

**MVC模式(模型-视图-控制器)**

 用户界面,特别是图形用户界面,承担着向用户显示问题模型和与用户进行操作和I/O交互的作用。用户希望保持交互操作界面的相对稳定,但更希望根据需要改变和调整显示的内容和形式。例如,要求支持不同的界面标准或得到不同的显示效果,适应不同的操作需求。这就要求界面结构能够在不改变软件的功能和模型情况下,支持用户对界面构成的调整。

  要做到这一点,从界面构成的角度看,困难在于:在满足对界面要求的同时,如何使软件的计算模型独立于界面的构成。模型-视图-控制(MVC:Model-View-Controller)就是这样的一种交互界面的结构组织模型

   对于界面设计可变性的需求,MVC把交互系统的组成分解成模型、视图、控制三种部件。 

  模型部件是软件所处理问题逻辑在独立于外在显示内容和形式情况下的内在抽象,封装了问题的核心数据、逻辑和功能的计算关系,他独立于具体的界面表达和I/O操作。 

  视图部件把表示模型数据及逻辑关系和状态的信息及特定形式展示给用户。它从模型获得显示信息,对于相同的信息可以有多个不同的显示形式或视图。 

  控制部件是处理用户与软件的交互操作的,其职责是控制提供模型中任何变化的传播,确保用户界面于模型间的对应联系;它接受用户的输入,将输入反馈给模型,进而实现对模型的计算控制,是使模型和视图协调工作的部件。通常一个视图具有一个控制器。

  模型、视图与控制器的分离,使得一个模型可以具有多个显示视图。如果用户通过某个视图的控制器改变了模型的数据,所有其它依赖于这些数据的视图都应反映到这些变化。因此,无论何时发生了何种数据变化,控制器都会将变化通知所有的视图,导致显示的更新。这实际上是一种模型的变化-传播机制。 

实现:

分析应用问题,对系统进行分离 

  分析应用问题,分离出系统的内核功能、对功能的控制输入、系统的输出行为三大部分。设计模型部件使其封装内核数据和计算功能,提供访问显示数据的操作,提供控制内部行为的操作以及其他必要的操作接口。以上形成模型类的数据构成和计算关系。这部分的构成与具体的应用问题紧密相关。 

设计和实现每个视图 

  设计每个视图的显示形式,它从模型中获取数据,将它们显示在屏幕上。 

设计和实现每个控制器 

  对于每个视图,指定对用户操作的响应时间和行为。在模型状态的影响下,控制器使用特定的方法接受和解释这些事件。控制器的初始化建立起与模型和视图的联系,并且启动事件处理机制。事件处理机制的具体实现方法依赖于界面的工作平台。 

使用可安装和卸载的控制器 

  控制器的可安装性和可卸载性,带来了更高的自由度,并且帮助形成高度灵活性的应用。控制器与视图的分离,支持了视图与不同控制器结合的灵活性,以实现不同的操作模式,例如对普通用户、专业用户、或不使用控制器建立的只读视图。这种分离还为在应用中集成新的I/O设备提供了途径。 

类似的结构模式还有PAC(Presentation-Abstraction-Control)、Forward-Receiver、Publisher-Subscriber、各类可视化用户界面控件等。 

  其中,"表示-抽象-控制"结构模式(PAC)也是从数据模型及其可是化关系的处理上提出的。其中,表示与视图对应,抽象与模型对应,控制与控制对应。从逻辑本质上,两者没有太大区别。但是,MVC和PAC还是存在着不同的地方。 

  (1) MVC的控制更侧重于在视图上的用户的I/O处理,而PAC的控制主要指从抽象到表示的传递和协调作用。 

  (2) 此外,PAC把系统分割为协作但松散耦合的智能体,而MVC是专门处理交互界面的,各个部件之间的关联更密切一些。 

  (3) 另外,从体系结构上看,PAC是属于系统级别的,因为它解决的问题更倾向于系统及部件之间的协作和关联关系。 

 


 


 

posted @ 2008-09-11 15:05 朝阳 阅读(12) | 评论 (0)编辑

     快毕业了,就要找工作了,还有老多东西都给忘了,先整理一下,备忘,也给需要的朋友和我一个方便,如果你有好的资料,希望能和我分享!

**getchar()和getch()**

       getchar()是stdio.h中的函数,等待用户输入直到按下enter键(前提是前提是stdin缓冲区无任何数据,包括回车符,如果stdin有数据,则直接读取前面的一个数据(scanf语句也受此影响,也是直接读取前面的一个数据,包括回车符),读取玩就结束,不等回车,),有屏幕显示

      getch()是conio.h( console io)中的函数,直接从键盘活动键值,不等用户按回车,不会在屏幕上显示,多用于程序调试,在关键位置显示有关的结果(他的输入不会影响下一个的读取,其他的就和getchar()没有什么区别了)

      stdin, 若要清除 C 中键盘缓冲区使用函数 rewind() 该要与默认键盘。 函数 fflush() 清除缓冲区, 在流级 I/O C 程序使用。 它并不清除设备缓冲区。 下面是示例显示当键盘缓冲区不清除, 发生什么以及如何清除它然后:

**scanf("%s",a)h和gets(a)的区别

     scanf不能输入空格,碰到空格截断

     gets可以输入空格,即把空格当成其输入的一部分

**操作字符串**

    首先需要引入命名空间<string.h>,虽然c不支持直接的string型,但是要使用strcpy,strcat等操作这个命名空间是必需的,后来在c++中引入了string类型,同时命名空间也变成了#include "string"和using namespace std;记住是string而不能写成string.h否则会报错的,这样呢在c++中操作字符串就得到了很多简化,如:直接用string声明变量,变量可直接赋值;可用“+”号连接字符串,不必用strcat;可直接用“==”“>=”“<=”逻辑比较,不必用strcmp(-1表示小于,0等于,1大于);可直接用“=”“+=”进行传值,不必用strcpy;可直接用“<<”“>>”进行字符串的流输入、输出。值得注意的是如果混用c和c++的各种操作符就非常有 可能会报错了,最简单的如声明了string变量却用printf输出等。

**sizeof和strlen**

   这两个都是c的,在c++中是stringName.size()和stringName.length();参数形式为sizeof(char *)和strlen(char *);需要说明的是sizeof是算符,strlen是函数,sizeof可以用类型做参数,strlen只能用char*做参数,且必须以"\0"结尾,sizeof还可以用函数做参数,数组做sizeof的参数不退化,传递给strlen就退化为指针,即sizeof的为数组的大小,而strlen的为指针的大小,为常数了!

**c++中的全局宏**

The following macro names are defined at any time:

 

macro value
__LINE__ Integer value representing the current line in the source code file being compiled.
__FILE__ A string literal containing the presumed name of the source file being compiled.
__DATE__ A string literal in the form "Mmm dd yyyy" containing the date in which the compilation process began.
__TIME__ A string literal in the form "hh:mm:ss" containing the time at which the compilation process began.
__cplusplus An integer value. All C++ compilers have this constant defined to some value. If the compiler is fully compliant with the C++ standard its value is equal or greater than 199711L depending on the version of the standard they comply.

**模板,多态,继承,接口**

  这些都是实现重用的一些方法(自定义类型的转换都是通过指针实现的),详细如下:

  模板:模板将类型参数化,实现的是源代码的重用,使用方法,就是在函数模板或者是类模板的签名加上 templete<class name>或者template<typename name>,他们是等效的,然后在其下的过程中用name来替代所需的类型名称,在调用时,需要指定类型,在调用时如:func<int>(参数);或者myclass<int>.什么的,其实一般都不需要指定那个<int>之类的,他是自己去替换的。

  多态:多态实现的是接口的重用,即用同一个函数名根据不同情况去调用不同的功能。有函数多态和动态多态,还有和模板啊,宏之类的结合产生的多态,比如,函数多态包含重置和宏多态,因为宏本来就是没有类型的,也算多态的一种了,动态多态则是及继承和虚函数为基础的。

  继承:公有继承可以理解为is-a关系,私有继承可以理解为has-a关系,这是因为共有继承后可以在子类的对象里面访问父类中的public成员,只是在其上加了一些东西而已,表现上为is-a关系,而私有继承则在子类的对象中访问不到任何父类中的成员,只是在类的内部可以操作父类的共有成员,表现为has-a关系.如果要实现接口的重用,即声明父类来调用子类的对象,fahher *myson=new son(构造函数的参数);(这样以来就将操作限定在father类中的公共操作上了)

**数据结构**

  数据结构的作用可以简单的理解为,为方便按照我们的方式完成需要的功能,我们需要让数据结构化,一个好的数据结构将为后期的处理带来事半功倍的效果

**单链表**

    用一组任意的存储单元来存放线性表的节点(由于是任意的存储单元所以其虽然是逻辑上连续的,但是在物理上却不一定就是连续的,所以它至少包含两部分:值和下一个节点的位置)

    链表由头指针唯一确定,但列表可以用头指针的名字来命名,但链表中的每个节点的存储地址是存放在其前驱的next域中,而开始节点无前驱,故头指针head指向开始节点。终端节点无后继,故终端节点的指针域为空,即null

**malloc**realloc**calloc

void* malloc(unsigned size); void* calloc(size_t nelem, size_t elsize); 和void* realloc(void* ptr, unsigned newsize);都在stdlib.h函数库内,是C语言的标准内存分配函数。

1. 函数malloc()和calloc()都可以用来动态分配内存空间。malloc()函数有一个参数,即要分配的内存空间的大小,malloc 在分配内存时会保留一定的空间用来记录分配情况,分配的次数越多,这些记录占用的空间就越多。另外,根据 malloc 实现策略的不同,malloc 每次在分配的时候,可能分配的空间比实际要求的多些,多次分配会导致更多的这种浪费。当然,这些都和 malloc 的实现有关;calloc()函数有两个参数,分别为元素的数目和每个元素的大小,这两个参数的乘积就是要分配的内存空间的大小。如果调用成功,它们都将返回所分配内存空间的首地址。

2. 函数malloc()和函数calloc()的主要区别是前者不能初始化所分配的内存空间,而后者能。

3. realloc可以对给定的指针所指的空间进行扩大或者缩小,无论是扩张或是缩小,原有内存的中内容将保持不变。当然,对于缩小,则被缩小的那一部分的内容会丢失。

4. realloc 并不保证调整后的内存空间和原来的内存空间保持同一内存地址。相反,realloc 返回的指针很可能指向一个新的地址。所以在代码中,我们必须将realloc返回的值,重新赋值给 p :

p = (int *) realloc (p, sizeof(int) *15);

 




posted @ 2008-09-06 21:58 朝阳 阅读(105) | 评论 (0)编辑

初学线程的使用,整理一下,备忘,也给我和需要的朋友一个方便!

       我现在还是一学生,欢迎交流,指教!

      线程的创建的关键是要给他指定一段要执行的代码段,这段代码可以是任意可以访问到的函数,如其他类的静态类,自己类定义的函数。

      首先生成一个threadStart类的实例,将要线程执行的代码和这个对象关联,类threadStart是一个代表,标志着当一个线程开始时就开始执行定义的方法!

以下是代码:

 

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;//引入命名空间

namespace 线程的使用
{
    class Program
    {
        static void Main(string[] args)
        {
            a aa = new a();
            aa.start();

        }  
    }
   public class a
   {
       public void start()
       {
           Thread th1, th2;//定义线程
           th1 = new Thread(new ThreadStart(Method1));//将线程和方法关联
           th1.Start();//执行线程的方法
           th2 = new Thread(new ThreadStart(Method2));
           th2.Start();
           System.Console.ReadLine();
       }
       public void Method1()
       {
           for (int i = 0; i < 100; i++)
               Console.WriteLine("这是类AClass方法method1的内容", i);
       }
       public void Method2()
       {
           for (int i = 0; i < 100; i++)
               Console.WriteLine("这是类AClass方法method2的内容", i);
       }
   }

}

可以看到为了执行定义的方法,实际上是调用线程的start()方法,除了该方法线程还有下面常用的方法:

Abort():停止线程的运行。
Suspend():暂停线程的运行。
Resume():继续线程的运行。
Sleep():停止线程一段时间(单位为毫秒)//类方法,阻止当前线程指定时间

 

在计算量比较大时可以显著的提高运行速度,我们可以用Stopwatch 来测量一下,所在命名空间:System.Diagnostics

使用方法:

Stopwatch timer = new Stopwatch();
timer.Start();

{程序段}

timer.Stop();
decimal micro = timer.Elapsed.Ticks / 10m.WriteLine("Execution time was {0:F1} microseconds.", micro);

     decimal 类型存储为一个 12 字节的整数部分、一个 1 位的符号和一个刻度因子。decimal 类型可以精确地表示非常大或非常精确的小数。大至 1028(正或负)以及有效位数多达 28 位的数字可以作为 decimal 类型存储而不失其精确性。该类型对于必须避免舍入错误的应用程序(如记账)很有用。如果希望实数被视为 decimal 类型,需要加后缀 m 或 M,例如:decimal myMoney = 300.5m;如果没有后缀 m,数字将被视为 double 类型,从而导致编译器错误。

    decimal(12,4) 的意思是一个数总共(最大)12位,但缩小10^4倍,也就是有4位小数,后面的也可用负值,表示扩大倍数,就相当于科学计数法的样子。


      Thread.ThreadState属性,这个属性代表了线程运行时状态,在不同的情况下有不同的值,于是我们有时候可以通过对该值的判断来设计程序流程。ThreadState在各种情况下的可能取值如下:

Aborted:线程已停止
AbortRequested:线程的Thread.Abort()方法已被调用,但是线程还未停止
Background:线程在后台执行,与属性Thread.IsBackground有关
Running:线程正在正常运行
Stopped:线程已经被停止
StopRequested:线程正在被要求停止
Suspended:线程已经被挂起(此状态下,可以通过调用Resume()方法重新运行)
SuspendRequested:线程正在要求被挂起,但是未来得及响应
Unstarted:未调用Thread.Start()开始线程的运行
WaitSleepJoin:线程因为调用了Wait(),Sleep()或Join()等方法处于封锁状态

 

      当线程之间争夺CPU时间时,CPU按照是线程的优先级给予服务的。在C#应用程序中,用户可以设定5个不同的优先级,由高到低分别是Highest,AboveNormal,Normal,BelowNormal,Lowest,在创建线程时如果不指定优先级,那么系统默认为ThreadPriority.Normal。给一个线程指定优先级
,我们可以使用如下代码:

  //设定优先级为最低
  myThread.Priority=ThreadPriority.Lowest;

  通过设定线程的优先级,我们可以安排一些相对重要的线程优先执行,例如对用户的响应等等。

 

 

**线程的同步和通讯--生产者和消费者**

  假设这样一种情况,两个线程同时维护一个队列,如果一个线程对队列中添加元素,而另外一个线程从队列中取用元素,那么我们称添加元素的线程为生产者,称取用元素的线程为消费者。生产者与消费者问题看起来很简单,但是却是多线程应用中一个必须解决的问题,它涉及到线程之间的同步和通讯问题。

  前面说过,每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数。但是多线程环境下,可能带来的问题就是几个线程同时执行一个函数,导致数据的混乱,产生不可预料的结果,因此我们必须避免这种情况的发生。C#提供了一个关键字lock,它可以把一段代码定义为互斥段(critical section),互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待。在C#中,关键字lock定义如下:

  lock(expression) statement_block

     expression代表你希望跟踪的对象,通常是对象引用。一般地,如果你想保护一个类的实例,你可以使用this;如果你希望保护一个静态变量(如互斥代码段在一个静态方法内部),一般使用类名就可以了。而statement_block就是互斥段的代码,这段代码在一个时刻内只可能被一个线程执行。                            点击跳转到引用网页 

 

posted @ 2008-08-27 16:01 朝阳 阅读(79) | 评论 (0)编辑

初学事件和委托,整理一下,备忘,也给我和需要的朋友一个方便!

       我现在还是一学生,欢迎交流,指教!

一个完整的事件处理包含以下几个要素:

一:事件的激发者;

二:事件的处理者;

三:指定委托。

下面通过一个程序来分别解说他的整个使用过程(程序是从网上下的,感谢原创者,我只是在他的解说的基础上加上我的理解整理出来而已!)

***事件的激发***

在一个函数中激发一个事件:

 

       public void 玩游戏()
        {
            Console.WriteLine("小张开始玩游戏了..");
            Console.WriteLine("小张:CS好玩,哈哈哈! 我玩..");
            System.Threading.Thread.Sleep(500);
            System.EventArgs e = new EventArgs();
            OnPlayGame(e);            
        }

        protected virtual void OnPlayGame(EventArgs e)
        {
            if (PlayGame != null)
            {
                PlayGame(this, e);
            }
        }

为了便于理解可以将其简化一下:

      public void 玩游戏()
        {
            Console.WriteLine("小张开始玩游戏了..");
            Console.WriteLine("小张:CS好玩,哈哈哈! 我玩..");
            System.Threading.Thread.Sleep(500);
            System.EventArgs e = new EventArgs();            
            PlayGame(this, e);
        }

PlayGame为一个事件,定义如下:

public event PlayGameHandler PlayGame;

这样我们就可以方便的看出这个事件是怎么激发的了,

         首先,定义一个事件public event PlayGameHandler PlayGame;(PlayGameHandler是必须先定义好的一个委托public delegate void PlayGameHandler(object sender,System.EventArgs e);)

         然后,在需要的地方激发一个事件:

         System.EventArgs e = new EventArgs();            
         PlayGame(this, e);

         那个被简化掉了的函数是用于测试这个PlayGame事件是否已经被其他对象注册。因为小张类的PlayGame事件有可能没有其他类通知需要处理,那么这个代码:
 if(PlayGame != null)就探测没有对象需要发送事件通知,否则表示已经有对象需要处理这个事件,就发送事件通知,让对象进行处理。

**事件的处理者**

        public void 扣钱(object sender, EventArgs e)
        {
            Console.WriteLine("小王:好小子,上班时间胆敢玩游戏");
            Console.WriteLine("小王:看看你小子有多少钱");
            小张 f = (小张)sender;
            Console.WriteLine("小张的钱: " + f.钱.ToString());
            Console.WriteLine("开始扣钱");
            System.Threading.Thread.Sleep(500);
            f.钱 = f.钱 - 500;
            Console.WriteLine("扣完了.现在小张还剩下:" + f.钱.ToString());
        }
      这里最值得注意的是:小张 f = (小张)sender,这个代码表示让小王扣小张的钱,那么,小王扣钱的时候,必须要操作小张的对象实例。方法参数里面有个object sender对象,这个对象表示激发事件的对象,在这个程序里面就表示这个sender其实是小张,但是传递的对象类型是object,所以必须使用对象类型转换语句把sender从object转成小张,所以有了:
小张 f = (小张)sender;其他的好像就没什么值得注意的了!

**指定委托**

 // 生成小王
 小王 w = new 小王();
// 生成小账
小张 z = new 小张();

z.PlayGame += new PlayGameHandler(w.扣钱);

z.PlayGame就是开始开始声明的那个事件了,而PlayGameHandler就是定义的委托,w.扣钱就是那个事件处理函数了!

 

事实上我们可以看一下winform里面自动生成的代码如button,如果你也是初学的话,不妨自己去看看它自己生成的代码是怎么样的,应该有所帮助!

this.btn_ok.Click += new System.EventHandler(this.btn_ok_Click);

转到click的定义:

// 摘要:
//     在单击控件时发生。
 public event EventHandler Click;(也就是一个事件了)

转到EventHandler的定义:

 public delegate void EventHandler(object sender, EventArgs e);(一个委托)

转到btn_ok_Click的定义,哈哈,不用怀疑,那就是你写的事件处理代码了!

 

                                                                                                                                                                        点击跳转到我引用的出处! 

 

 

 

posted @ 2008-08-16 17:00 朝阳 阅读(267) | 评论 (0)编辑

一直以为自己算是比较理解什么是面向对象了,而在今天写代码的过程中发现了一些问题,原来自己不是那么的懂,整理了一下,给我和需要的朋友一个方便!

       我现在还是一学生,欢迎交流,指教!

和以前一样以问题开头,如何在父窗体中操作子窗体的变量,如何在子窗体中操作父窗体的变量,又如何在这些操作中保持较好的高内聚和低耦合呢?

而这里面就 要提一下面向对象的特点了:封装、继承和多态

**封装**

        在C#中可使用类来达到数据封装的效果,这样就可以使数据与方法封装成单一元素,以便于通过方法存取数据。除此之外,还可以控制数据的存取方式。
在面向对象的世界中,大多数都是以类作为数据封装的基本单位。类将数据和操作数据的方法结合成一个单位。设计类时,不直接存取类中的数据,而是通过方法来存取数据,如此就可以达到封装数据的目的,方便以后的维护升级,也可以在操作数据时多一层判断。
       此外,封装还可以解决数据存取的权限问题,可以使用封装将数据隐藏起来,形成一个封闭的空间,然后就可以设置哪些数据只能在这个空间中使用,哪些数据可以在空间外部使用。如果一个类中包含敏感数据,有些人可以访问,有些人却不能访问。如果不对这些数据的访问加以限制,后果是很严重的。所以在编写程序时,要对类的成员使用不同的访问修饰符,从而定义他们的访问级别。

      在这个使用过程中需要提到一下的是get和set访问器以及字段变量的区别了,这大概是最常用的了吧

class myclass 

    
private int age;//私有的字段 
    public  int Age //属性 
    
        
get 
        

            
return this.age; 
        }
 
        
set 
        

            
this.age=value; 
        }
 
    }
 
}
 

      属性中能实现各种复杂的逻辑,而这些是直接把一个字段共有所不能实现的.从这里就能体现出get和set访问器带来的好处了;

     具体有:

1、属性可以只读或只写,比如下面代码,共有字段变量一定是可读写的。
2、属性可附带错误检查或额外的的代码,共有字段变量的读写不能。
3、虽然这些都是编译器给带来的好处,但封装有很多好处,像细节隐藏,实现隐藏等。比如说有一天人可以活到200岁,更改if(value > 200)不会影响其他调用Age的函数。

class myclass 

    
private int age;//私有的字段 
    public int Age //属性 
    
        
set 
        
{
            
if(value < 0 || value > 200)
            
{
               
throw new Exception("");    // checking
            }
 
            
if(this.age != value)
            
{
               
this.ageChanged = true;        // extra routine
            }

            
this.age=value; 
        }
 
    }
 
}

 总结:属性除了公布字段外,还可以在属性上添加对字段的约束规则,比如只读,只写,读写规则,还可以对属性的值进行逻辑验证等等.另外从软件设计的角度来看,属性是方法,应该被 公开,而字段是数据 ,应该被封装.

 

 

 

 

 

 

 

posted @ 2008-08-15 21:49 朝阳 阅读(258) | 评论 (3)编辑
     摘要: 数据库操作语句是很常用的,但是总是给忘记了具体的代码,现在我整理一下,,给我和需要的朋友一个方便!不过呢,我现在还是一学生,如有高见请指教!数据操作 SELECT --从数据库表中检索数据行和列INSERT --向数据库表添加新数据行DELETE --从数据库表中删除数据行UPDATE --更新数据库表中的数据示例: * select * from table1, table2 where tab... 阅读全文
posted @ 2008-08-10 19:26 朝阳 阅读(126) | 评论 (0)编辑

今天在写一个小代码时遇到一个问题:如何将一个dataTable中的一行拷贝到另外一个结构相同的dataTable中,如果直接采用如下方式就会报错:

            DataRow drGround = ds.Tables[0].Rows[0];
            dt.Rows.Add(drGround);(提示drGround已经属于另外一张表了)

正解:

            DataRow myDataRow = (DataRow)ds.Tables[0].Rows[0];
            DataRow drGround = dt.NewRow();
            drGround.ItemArray = myDataRow.ItemArray;           
            dt.Rows.InsertAt(drGround, 0);//插到指定位置和报表保持一致
      

 

事实上这就涉及到值和引用的概念了,因为dataRow是引用类型的。为加深理解我从网上找了一些资料并将他们给整理了一下,给我和需要的朋友一个方便!
不过呢,我现在还是一学生,如有高见请指教!

 

C# 支持两种类型:“值类型”和“引用类型”。

值类型,例如:int、float、bool之类的基础类型,以及用struct定义的类型,如:DateTime。除此外,如string,数组,以及用class定义的类型等都是引用类型。对于C#来说,很难罗列出所有类型进行一一分别,这需要自己在编码过程中进行分析总结。

       值类型与引用类型的区别在于值类型的变量直接包含其数据,而引用类型的变量则存储对象引用。对于引用类型,两个变量可能引用同一个对象,因此对一个变量的操作可能影响另一个变量所引用的对象。对于值类型,每个变量都有自己的数据副本,对一个变量的操作不可能影响另一个变量。

 不过,无论是对于值类型还是引用类型来说,对于其作为函数参数或者返回值的时候,都是容易犯错误的地方。

  对于值类型来说,当其作为函数参数的时候,希望在函数中被修改,那么直接如下操作是不能被修改的。

public void Increment( int i )
{
  i++;
}


  要想在函数中对传进去的参数做真正的修改,需要借助于ref这个关键字,那么正确的形式如下。

public void Increment( ref int i )
{
 i++;
}


  也就是说,如果需要在函数中对值类型参数进行修改,需要用ref或者out进行标识才能真正实现。
    而对于引用类型来说,当其作为函数参数的时候,它所遇到的情况恰恰与值类型相反,即不希望在函数中被修改,举例如下。

public void AddValue( MyType typValue )
{
 typValue.Count = typValue.Count + 15;
}


  由于对于引用类型对象来说,其的赋值操作只是对原有对象的引用,因此在函数对其修改,实际上是直接修改了原有对象数据,这是很多情况不希望发生的(这里例如对数组或者DataTable操作这类)。
     为了防止这种事发生,需要给此类型提供clone函数。例如对于如上的类型,可以入下实现。

public class MyType:ICloneable
{
 private int nCount = 0;
 public int Count
 {
  set{ nCount = value;}
  get{ return nCount;}
 }
 public MyType()
 {}
 public MyType( int Value)
 {
  nCount = Value;
 }

 #region ICloneable Members

 public object Clone()
 {
  // TODO: Add MyType.Clone implementation
  return new MyType( nCount );
 }

 #endregion
}


  那么在调用的时候,用当前的对象的clone作为参数即可。

  不过对于引用类型来说,提供一个clone函数不是一件容易的事情,尤其出现引用类型嵌套的时候,所以说去实现一个完全clone功能是件很费事又不讨好的活,这也就是在论坛中常说的深copy和浅copy的问题。话虽如此,如果对于前面所说的有个大概了解,相信实现也不是不可能。

   

 

posted @ 2008-08-03 20:02 朝阳 阅读(146) | 评论 (2)编辑
今天突然要写一些代码,发现其中的一些一些控件的使用很不灵活,现在我要把他们给积累起来,给我和需要的朋友一个方便!
不过呢,我现在还是一学生,如有高见请指教!
listbox
     一.根据需要自己定义item的颜色
       1.首先要设置其DrawMode属性,设置DrawMode.OwnerDrawFixed 或 DrawMode.OwnerDrawVariable (有大小可变的项时使用)
       2.实现其DrawItem事件响应
我写的代码如下:
        private void listBox1_DrawItem(object sender, DrawItemEventArgs e)
        {
            listBox1.DrawMode = DrawMode.OwnerDrawFixed;
            e.DrawBackground();  

            Brush myBrush = Brushes.Red;
            Brush otherBrush=Brushes.Black;
            if (e.Index == 2)
                e.Graphics.DrawString(listBox1.Items[e.Index].ToString(), e.Font, myBrush, e.Bounds, StringFormat.GenericDefault);
            else
                e.Graphics.DrawString(listBox1.Items[e.Index].ToString(), e.Font, otherBrush, e.Bounds, StringFormat.GenericDefault);
        }

注:e.DrawBackground();  一定要写哦,否则你都不知道自己选择了哪个的!

dataGridView

一.设置行颜色

1、首先设置selectionMode为FullRowSelect

2、设置AllowUserToAddRows属性为false(否则会发生索引错误的)

下面就是我写的代码了(省略了具体的应用设置,通过对.rows[e.RowIndex]的判断就可以了):

 DataGridViewCellStyle style = new DataGridViewCellStyle();

private void dataGridView1_RowPrePaint(object sender, DataGridViewRowPrePaintEventArgs e)
        {

          style.BackColor = Color.Red;
                dataGridView1.Rows[e.RowIndex].DefaultCellStyle = style;