RSS 2.0 Feed
2004-11 Entries
摘要:今天在一场“特殊的讨论”中引入了一个问题,如何在C#求出字符串中某字符的出现次数,比如求“ADSFGEHERGASDF”中“A”出现的次数。首先想到的方法当然是从头遍历字符串并统计: c1 = 0;for (int i = 0; i < str.Length; i++){    if (str[i] == 'A')    {        c1++;    }} 第二种方法也很容易想到,将字符串中所有要查找的字符去除,然后比较去除前后的字符串长度即可。这种方法遭到了某人的鄙视,据说性能很差而且多占空间。 c2 = str.Length - str.Replace("A", String.Empty).Length; 接下来某人又提出了第三种方法,是用要查找的字符为分隔符,将原字符串分隔为多个子串,然后求子串的数目即可。在C#中这是一个写起来很短的方法: c3 = str.Split(new char[] { 'A' }).Length - 1; 我们从原理可以推断出三者性能的顺序,但究竟差距是多少呢,还是要动手试验一下。这是非常经典的测试代码: string str = "SADTHDGSAFSDGTGHRDGSADFADDRHDFSGASDAA";Stopwatch sw = new Stopwatch();long t;int c = 0;GC.Collect();Application.DoEvents();sw.Start();for (int i = 0; i < 100000; i++){    c = 三种算法}sw.Stop();t = sw.ElapsedMilliseconds; 首先我们确保正确性,经测试三种方法都能正确处理多种情况,包括首尾、连续出现、不出现或串长度为0等,我所取的字符串是一个很普通的串。编译为Release版,预运行10次后获得以下结果: 遍历统计:13毫秒替换后比较长度:112毫秒断开字符串后计数:233毫秒 这里已经体现出差异,遍历统计比替换后比较要快10倍,断开字符串又要慢一些。接下来我又做了如下两个测试: 1、不改变字符串的长度,增加或减少要查找字符串的个数。2、不改变要查找字符出现的频率,但增长字符串的长度。 结果发现,三种方法都随字符串长度增加线性变慢,而后两种方法还随要查找的字符增加而变慢。断开字符串的方法还受要查找字符串分布情况的影响。 研究Replace函数和Split函数的实现可以彻底解决这个问题。不过我没有心情细细研究了,我还是决定选用第二种方法——替换后比较长度。虽然其速度比第一种方法慢,但易于改写为求长度不为1的子串出现次数的方法。第一种方法若改为求长度大于1的字串就要考虑很多因素(尽管不一定真的很麻烦),我懒得想了,呵呵。...[阅读全文]

posted @ | Feedback (16) | Filed Under [ 技术随笔 ]

摘要:用过VB6的人都会对控件数组念念不忘,因为控件数组在处理多个控件统一事件上确实很方便。.NET Framework没有引入控件数组这一概念,这是因为.NET Framework的类型系统很完善,可以实现控件数组原来的功能。只是这样凭空增加了一些麻烦,我们不是需要在Handls字句后面写一长串控件名称,就是要用写数遍AddHandler或C#的+=语句给多个控件绑定同一个事件。为了减轻没有控件数组日子的痛苦,我写了这个替代方案:共享事件容器。在编写它的过程中,我用了泛型,但其实不用泛型也可以做到,只是泛型能够约束容器内控件的类型,减少可能的运行时错误。 在看实现方法之前,先来看看共享事件容器是怎么操作的。首先你先要放一些相同类型控件到窗体上,这些就是你的控件数组元素了,假设是button1,button2和button3。然后在窗体的构造函数或Load事件中添加如下代码: ba = New ShareEventsContainer(Of Button) '创建一个共享事件容器ba.AddRange(New Button() {Button1, Button2}) '用AddRange可以添加多个控件ba.Add(Button3) '当然Add方法也是支持的了 这就是共享事件容器了,就像是一般的集合一样,你可以用数组的语法操纵其中的对象。接下来就是重头戏了,我们要将容器内的全部控件的Click事件都绑定到同一个方法上,比如这个方法 Public Sub Buttons_Click(ByVal sender As Object, ByVal e As EventArgs)    MsgBox("Hello! I'm " & CType(sender, Button).Name)End Sub 则接着在刚才初始化共享事件容器语句的后面写事件绑定代码: ba.AddHandler("Click", New EventHandler(AddressOf Buttons_Click)) 有点像VB的语法,其实我就是取了个神似。这里事件名称是要用字符串表示的,可别写错了。而事件处理程序的委托也与一般的事件绑定语句不一样,这时必须将委托类型写出来,还得写正确了。VB和C#2.0的委托类型推定在这时是不起作用的。你还可以写更多的AddHandler,绑定其他事件。 好了,就这么简单,事件已经绑定好了。更进一步的是,将来继续往这个容器内添加新的按钮,他们的事件也会自动绑定到这时预定的所有处理程序上,而从容器中移除控件,其事件会自动解除绑定。你还可以在任意地方用AddHandler和RemoveHandler方法添加和删除新的事件处理程序,支持多播事件方法。 如果你觉得这个方法值得用用,那就可以看下面的实现代码了:VB2005 BETA1 Imports System.Collections.GenericImports System.Reflection'包含委托的列表类型Imports DelegateList = System.Collections.Generic.List(Of System.MulticastDelegate)''' <summary>''' 提供一个共享事件容器,容器内的控件可以共享事件处理过程''' </summary>''' <typeparam name="T">控件的类型,不支持Menu</typeparam>''' <remarks>''' 共享事件容器在事件处理上类似于Visual Basic 6.0或更早版本的控件数组,''' 它可以将多个控件的事件绑定到同一个处理过程上,方便进行功能一致的操作。''' </remarks>Public Class ShareEventsContainer(Of T)    Inherits Collection(Of T)    '保存当前共享事件容器所预定的全部事件    '为了支持多播事件,必须采用这种结构的存储方式    Private events As Dictionary(Of String, DelegateList)    Private controlType As Type    ''' <summary>    ''' 初始化一个默认的共享事件容器    ''' </summary>    Public Sub New()        MyBase.New()        controlType = GetType(T)        events = New Dictionary(Of String, DelegateList)    End Sub    ''' <summary>    ''' 初始化一个共享事件容器,并添加初始的控件    ''' </summary>    ''' <param name="controls"></param>    Public Sub......[阅读全文]

posted @ | Feedback (15) | Filed Under [ 灵感记录 ]

摘要:又度过了繁忙的一年,人也又老了一岁。今天的生日也没有认真过,白天都在进行培训,晚上实在没力气,买个游戏玩玩吧。 实在没什么好写的,明天但愿有力气写篇技术文章……...[阅读全文]

posted @ | Feedback (38) | Filed Under [ 闲话集锦 ]

摘要:为了了解Visual Basic和C++/CLI最新的变化,我安装了Visual Studio 2005 十月技术预览版(CTP)。Visual Basic变化并不大,增加了Code Expansion功能和泛型的引用类型/值类型约束。而C++/CLI则十分莫名其妙。IDE的智能感知全没了,全部都要凭记忆写。语法更加令人不解,IDisposable的Dispose方法被视为不可从代码中直接调用,而必须用delete语法调用Dispose,真是@!#$%&^%。而且我的Managed DirectX系列程序竟然不能编译了,提示是: Error 1  error C2535: 'Microsoft::DirectX::Direct3D::VertexBuffer::~VertexBuffer(void)' : member function already defined or declared e:\My Documents\Visual Studio\Projects\MDXTest2\MDXTest2\DirectXProgram.h 40     还有 Error 3  error C2535: 'Microsoft::DirectX::Direct3D::Device::~Device(void)' : member function already defined or declared e:\My Documents\Visual Studio\Projects\MDXTest2\MDXTest2\DirectXProgram.h 80     等等,即Device、VertexBuffer等类型都无法创建实例,只要一创建就会有这种错误。这Device和VertexBuffer又不是我编写的,什么~Device已经定义过了,简直是胡说八道嘛! 不管怎么说,这个版本的C++/CLI是没法用的了,我的Managed DirectX学习笔记也要暂时改用Visual Basic和C#来进行了(也好,所有的语言都用用)。由于要重写实验项目,所以今天是写不出来了,过两天继续。...[阅读全文]

posted @ | Feedback (30) | Filed Under [ 闲话集锦 ]

摘要:今天收到了这个“特别礼物”——印有Windows® Media标志的Creative MP3 。 它同时还是个U盘,只要从电池盒上拔下来即可,有120M的实际可用空间。经试验,对CBR和VBR的Windows Media格式支持得都不错,虽然音质不怎么样(也许是我用的WMA太烂了),但至少不像我以前见过的某一台低档MP3那样断断续续。那个耳机可不太好,还好我有一个PX100可以拿过来用用。按钮很少,除了开关音量,还有一个假滚轮可以前进和后退。同时这MP3还有一个LCD屏(支持中文!翻过来一看Made in China,怪不得-_-b,算了反正也不错了)。其他花哨的功能就没多少了。 还有不少人已经收到或者即将收到了吧。感谢Grace和微软公司带给我们这一“特别礼物”。...[阅读全文]

posted @ | Feedback (16) | Filed Under [ 闲话集锦 ]

摘要:上一次我们在Managed DirectX世界里接触到了Direct3D设备的概念,它是Direct3D中几乎所有功能的开始。从这次我要开始在DirectX中绘出3D图形了。3D图形常常用一组包围形状表面的多边形来表示,无论形状多么复杂,我们都可以用一定数目的多边形来逼近。目前用于游戏的显卡多使用三角形网格系统,即用一系列三角形来表示物体的表面。在电影级渲染中,还常常用到四边形网格。关于网格(mesh)这个重要的概念以后还会遇到,这次我将学习如何绘制三角形。 Direct3D支持的图元(Primitive)类型有点、线段、线条、三角、成片三角和三角扇形等。要绘制图元,必须将点集载入到数据流中。顶点缓冲区(Vertex Buffer)即是DirectX保存顶点数据的常用方式。在顶点缓冲区内我们可以对顶点进行各种操作,比如坐标变换、光照等。Direct3D支持灵活顶点格式(FVF),用户可以自行决定在一个顶点数据中包含那些数据,比如顶点的坐标、颜色、法向量等。可以自由搭配各种属性,以至于我们可用自定义的结构体来保存顶点数据。这次我选择的是一种已经预定义在DirectX类库中的类型:CustomVertex::TransformedColored。它包含顶点的坐标、经变换后的坐标以及顶点的漫反射颜色。 创建顶点缓冲区需要使用VertexBuffer类。要定义一个顶点缓冲区,需要下列参数:顶点的结构体类型、个数、设备、顶点缓冲区属性、顶点格式和顶点缓冲区的资源位置。顶点的结构体类型是一个System::Type类型参数(主要是获取顶点结构体的内存大小)。顶点缓冲区属性可以是Usage枚举中的任意值的组合,我这次就用None。顶点格式是对于灵活顶点格式的描述,必须准确指出顶点结构体中包含哪些数据。最后一个顶点缓冲区的资源位置表示将顶点缓冲区置于内存还是显存中。基于这些设置,就可以写出一个初始化顶点缓冲区的函数: //顶点缓冲区全局对象static VertexBuffer^ vb; static Boolean InitializeVertexBuffer(){    //初始化一个三角形的顶点数组    array<CustomVertex::TransformedColored>^ vertices =         gcnew array<CustomVertex::TransformedColored>(3);     vertices[0] = CustomVertex::TransformedColored(150.0, 50.0, 0.5, 1.0, 0xffffff00);    vertices[1] = CustomVertex::TransformedColored(250.0, 250.0, 0.5, 1.0, 0xff00ff00);    vertices[2] = CustomVertex::TransformedColored(50.0, 250.0, 0.5, 1.0, 0xff0000ff);     try    {        //创建顶点缓冲区        vb = gcnew VertexBuffer(            typeid<CustomVertex::TransformedColored>,                          3,  /* 顶点个数 */            d3dDevice,  /* 要使用的Direct3D设备 */            Usage::None,  /* 顶点缓冲区属性 */            CustomVertex::TransformedColored::Format,  /* 灵活顶点格式 */            Pool::Default /* 顶点缓冲区资源位置,这里选择显存 */        );         //填充顶点缓冲区        GraphicsStream^ gs = vb->Lock(0, 0, LockFlags::None);          gs->Write(vertices);         vb->Unlock();         return true;    }    catch(DirectXException^)    {        return false;    }} 别忘了在CleanUp方法中清理使用过的VertexBuffer对象。接下来,我们需要在渲染函数中增加绘制图元的代码。注意:这些代码一定要放在对设备BeginScene函数和EndScene函数的调用之间,否则就会出错。 d3dDevice->SetStreamSource(0, vb, 0);d3dDevice->VertexFormat = CustomVertex::TransformedColored::Format;d3dDevice->DrawPrimitives(PrimitiveType::TriangleList, 0, 1); 最后,我们只要在main函数中添加调用创建顶点缓冲区函数的代码即可: int Main(){    TestMDXForm^ f = gcnew TestMDXForm();    if (DirectXProgram::InitializeDirect3D(f)......[阅读全文]

posted @ | Feedback (20) | Filed Under [ 技术随笔 Managed DirectX学习笔记 ]