252010
 

介绍

  • 这篇文档所给出的编码约定适用于在主要的Python发布版本中组成标准库的Python 代码.请查阅相关的关于在Python的C实现中C代码风格指南的描述. 这篇文档改编自Guido最初的《Python风格指南》一文. 并从《Barry's style guide》中添加了部分内容. 在有冲突的地方,Guide的风格规则应该是符合本PEP的意图 (译注:就是当有冲突时,应以Guido风格为准) 这篇PEP也许仍然尚未完成(实际上,它可能永远不会结束).

一致性的建议

愚蠢得使用一致性是无知的妖怪(A Foolish Consistency is the Hobgoblin of Little Minds)

呆板的坚持一致性是傻的没边了!
-- Zoomq
  • 在这篇风格指导中的一致性是重要的. 在一个项目内的一致性更重要. 在一个模块或函数内的一致性最重要. 但最重要的是:知道何时会不一致 — 有时只是没有实施风格指导.当出现疑惑时,
    • 运用你的最佳判断.看看别的例子,然后决定怎样看起来更好.并且要不耻下问!
  • 打破一条既定规则的两个好理由:
    1. 当应用这个规则是将导致代码可读性下降,即便对某人来说,他已经习惯于按这条规则来阅读代码了.
    2. 为了和周围的代码保持一致而打破规则(也许是历史原因)
      • – 虽然这也是个清除其它混乱的好机会(真正的XP风格).

Continue reading »

242010
 

最近做了一个串口通信的程序,PC到PC的通信,为之后的单片机和PC通信做点准备。很简单的小东西,就是实现了一下串口的通信连接、异步收发数据。虽然简单,还是遇到了些小问题,总结一下,供以后借鉴吧。

一、整体架构:

a) 全局变量

  1. 使用HANDLE m_hComm保存打开的端口句柄;
  2. BOOL m_bConnected来标示当前端口的状态,即是否已经连接成功;
  3. CWinThread* m_pThread来保存之后要开启的异步读取辅助线程;
  4. int nPort保存连接的端口号;
  5. 为了进行异步操作,还要给读取和写入分别定义一个OVERLAPPED结构变量,OVERLAPPED m_ovRead, m_ovWrite;
  6. 最后还要定义一个消息事件HANDLE m_hPostEvent,用于线程向窗体通知数据的到达;

b)函数概述:

  • BOOL ConfigPort();     //配置端口
  • BOOL ConnectToPort(int nPort);     //连接到端口
  • BOOL DisconnectToPort();     //从端口断开
  • DWORD WriteComm(char *buff, DWORD dwCount);     //向端口写入
  • DWORD ReadComm(char *buff, DWORD dwCount);     //从端口读出
  • UINT thCommProc(LPVOID pParam);      //全局函数,是一个线程的执行体

Continue reading »

202010
 

Dos的过程驱动与Windows的事件驱动

在讲本程序的消息循环之前,我想先谈一下Dos与Windows驱动机制的区别:

DOS程序主要使用顺序的,过程驱动的程序设计方法。顺序的,过程驱动的程序有一个明显的开始,明显的过程及一个明显的结束,因此程序能直接控制程序事件或过程的顺序。虽然在顺序的过程驱动的程序中也有很多处理异常的方法,但这样的异常处理也仍然是顺序的,过程驱动的结构。

而Windows的驱动方式是事件驱动,就是不由事件的顺序来控制,而是由事件的发生来控制,所有的事件是无序的,所为一个程序员,在你编写程序时,你并不知道用户先按哪个按纽,也不知道程序先触发哪个消息。你的任务就是对正在开发的应用程序要发出或要接收的消息进行排序和管理。事件驱动程序设计是密切围绕消息的产生与处理而展开的,一条消息是关于发生的事件的消息。

Windows编程的特点:

C语言编程至少有一个主程序,其名字是main()。Windows程序则至少两个主程序,一个是WinMain()

Continue reading »

202010
 

(二)创建窗口

注册窗口类后,就可以创建窗口了,本程序中创建窗口的有关语句如下:

 hwnd = CreateWindow (szAppName,                     // window class name

                          TEXT (“欢迎你的到来!”),    // window caption

                          WS_OVERLAPPEDWINDOW,        // window style

                          CW_USEDEFAULT,              // initial x position

                          CW_USEDEFAULT,              // initial y position

                          CW_USEDEFAULT,              // initial x size

                          CW_USEDEFAULT,              // initial y size

                          NULL,                       // parent window handle

                          NULL,                       // window menu handle

                          hInstance,                  // program instance handle

                          NULL) ;                     // creation parameters

参数1:登记的窗口类名,

这个类名刚才咱们在注册窗口时已经定义过了。

参数2:用来表明窗口的标题。

参数3: 用来表明窗口的风格,如有无最大化,最小化按纽啊什么的。

参数4,5: 用来表明程序运行后窗口在屏幕中的坐标值。

参数6,7: 用来表明窗口初始化时(即程序初运行时)窗口的大小,即长度与宽度。

参数8: 在创建窗口时可以指定其父窗口,这里没有窗口则参数值为0。

参数9: 用以指明窗口的菜单,菜单以后会讲,这里暂时为0。

最后一个参数是附加数据,一般都是0。

CreateWindow()的返回值是已经创建的窗口的句柄,应用程序使用这个句柄来引用该窗口。如果返回值为0,就应该终止该程序,因为可能某个地方出错了。如果一个程序创建了多个窗口,则每个窗口都有各自不同的句柄.

 

 

(三)显示和更新窗口

     API函数CreateWindow创建完窗口后,要想把它显示出现,还必须调用另一个API函数ShowWindows.形式为:

ShowWindow (hwnd, iCmdShow);

其第一个参数是窗口句柄,告诉ShowWindow()显示哪一个窗口,而第二个参数则告诉它如何显示这个窗口:最小化(SW_MINIMIZE),普通(SW_SHOWNORMAL),还是最大化(SW_SHOWMAXIMIZED)。WinMain在创建完窗口后就调用ShowWindow函数,并把iCmdShow参数传送给这个窗口。你可把iCmdShow改变为这些参数试试。

WinMain()调用完ShowWindow后,还需要调用函数UpdateWindow,最终把窗口显示了出来。调用函数UpdateWindow将产生一个WM_PAINT消息,这个消息将使窗口重画,即使窗口得到更新.

(四)创建消息循环

主窗口显示出来了,WinMain就开始处理消息了,怎么做的呢?

Windows为每个正在运行的应用程序都保持一个消息队列。当你按下鼠标或者键盘时,Windows并不是把这个输入事件直接送给应用程序,而是将输入的事件先翻译成一个消息,然后把这个消息放入到这个应用程序的消息队列中去。应用程序又是怎么来接收这个消息呢?这就讲讲消息循环了。

应用程序的WinMain函数通过执行一段代码从她的队列中来检索Windows送往她的消息。然后WinMain就把这些消息分配给相应的窗口函数以便处理它们,这段代码是一段循环代码,故称为”消息循环”。这段循环代码是什么呢?好,往下看:

在咱们的第二只小板凳中,这段代码就是:

……

MSG msg; //定义消息名

while (GetMessage (&msg, NULL, 0, 0))

     {

          TranslateMessage (&msg) ; //翻译消息

          DispatchMessage (&msg) ; //撤去消息

     }

     return msg.wParam ;

MSG结构在头文件中定义如下:

typedef struct tagMSG

{

HWND hwnd;

UINT message;

WPARAM wParam;

LPARAM lParam;

DWORD time;

POINT pt;

} MSG, *PMSG;

MSG数据成员意义如下:

参数1:hwnd是消息要发送到的那个窗口的句柄,这个窗口就是咱们用CreateWindows函数创建的那一个。如果是在一个有多个窗口的应用程序中,用这个参数就可决定让哪个窗口接收消息。

参数2:message是一个数字,它唯一标识了一种消息类型。每种消息类型都在Windows文件中定义了,这些常量都以WM_开始后面带一些描述了消息特性的名称。比如说当应用程序退出时,Windows就向应用程序发送一条WM_QUIT消息。

参数3:一个32位的消息参数,这个值的确切意义取决于消息本身。

参数4:同上。

参数5:消息放入消息队列中的时间,在这个域中写入的并不是日期,而是从Windows启动后所测量的时间值。Windows用这个域来使用消息保持正确的顺序。

参数6:消息放入消息队列时的鼠标坐标.

消息循环以GetMessage调用开始,它从消息队列中取出一个消息:

GetMessage(&msg,NULL,0,0),第一个参数是要接收消息的MSG结构的地址,第二个参数表示窗口句柄,NULL则表示要获取该应用程序创建的所有窗口的消息;第三,四参数指定消息范围。后面三个参数被设置为默认值,这就是说你打算接收发送到属于这个应用程序的任何一个窗口的所有消息。在接收到除WM_QUIT之外的任何一个消息后,GetMessage()都返回TRUE。如果GetMessage收到一个WM_QUIT消息,则返回FALSE,如收到其他消息,则返回TRUE。因此,在接收到WM_QUIT之前,带有GetMessage()的消息循环可以一直循环下去。只有当收到的消息是WM_QUIT时,GetMessage才返回FALSE,结束消息循环,从而终止应用程序。 均为NULL时就表示获取所有消息。

消息用GetMessage读入后(注意这个消息可不是WM_QUIT消息),它首先要经过函数TranslateMessage()进行翻译,这个函数会转换成一些键盘消息,它检索匹配的WM_KEYDOWN和WM_KEYUP消息,并为窗口产生相应的ASCII字符消息(WM_CHAR),它包含指定键的ANSI字符.但对大多数消息来说它并不起什么作用,所以现在没有必要考虑它。

下一个函数调用DispatchMessage()要求Windows将消息传送给在MSG结构中为窗口所指定的窗口过程。我们在讲到登记窗口类时曾提到过,登记窗口类时,我们曾指定Windows把函数WindosProc作为咱们这个窗口的窗口过程(就是指处理这个消息的东东)。就是说,Windows会调用函数WindowsProc()来处理这个消息。在WindowProc()处理完消息后,代码又循环到开始去接收另一个消息,这样就完成了一个消息循环。

下一个出场的东东就是窗口过程了,先歇一会儿再说吧??

 

202010
 

(五)终止应用程序:

Windows是一种非剥夺式多任务操作系统。只有的应用程序交出CPU控制权后,Windows才能把控制权交给其他应用程序。当GetMessage函数找不到等待应用程序处理的消息时,自动交出控制权,Windows把CPU的控制权交给其他等待控制权的应用程序。由于每个应用程序都有一个消息循环,这种隐式交出控制权的方式保证合并各个应用程序共享控制权。一旦发往该应用程序的消息到达应用程序队列,即开始执行GetMessage语句的下一条语句。

当WinMain函数把控制返回到Windows时,应用程序就终止了。应用程序的启动消息循环前要检查引导出消息循环的每一步,以确保每个窗口已注册,每个窗口都已创建。如存在一个错误,应用程序应返回控制权,并显示一条消息。

但是,一旦WinMain函数进入消息循环,终止应用程序的唯一办法就是使用PostQuitMessage把消息WM_QUIT发送到应用程序队列。当GetMessage函数检索到WM_QUIT消息,它就返回NULL,并退出消息外循环。通常,当主窗口正在删除时(即窗口已接收到一条WM_DESTROY消息),应用程序主窗口的窗口函数就发送一条WM_QUIT消息。

虽然WinMain指定了返回值的数据类型,但Windows并不使用返回值。不过,在调试一应用程序时,返回值地有用的。通常,可使用与标准C程序相同的返回值约定:0表示成功,非0表示出错。PostQuitMessage函数允许窗口函数指定返回值,这个值复制到WM_QUIT消息的wParam参数中。为了的结束消息循环之后返回这个值,我们的第二只小板凳中使用了以下语句:

return msg.wParam ; //表示从PostQuitMessage返回的值

例如:当Windows自身终止时,它会撤消每个窗口,但不把控制返回给应用程序的消息循环,这意味着消息循环将永远不会检索到WM_QUIT消息,并且的循环之后的语句也不能再执行。Windows的终止前的确发送一消息给每个应用程序,因而标准C程序通常会的结束前清理现场并释放资源,但Windows应用程序必须随每个窗口的撤消而被清除,否则会丢失一些数据。

(六)窗口过程,窗口过程函数

如前所述,函数GetMessage负责从应用程序的消息队列中取出消息,而函数DispatchMessage()要求Windows将消息传送给在MSG结构中为窗口所指定的窗口过程。然后出台的就是这个窗口过程了,这个窗口过程的任务是干什么呢?就是最终用来处理消息的,就是消息的处理器而已,那么这个函数就是WindowProc,在Visual C++6.0中按F1启动MSDN,按下面这个路径走下来:

PlatForm SDK–>User Interface services–>Windows user Interface–>Windowing–>Window Procedures–>Window Procedure Reference–>Windows Procedure Functions–>WindowProc 

啊,太累了,不过我们终于的MSDN中找到了这个函数,前几次我讲解这些API函数的时候,都是的知道的情况下搜索出来的,所以没有详细给出每个函数的具体位置,而这次我却是一点点去找的,还好,没被累死,体会到MSDN的庞大了吧,不过我用的是MSDN2000,是D版的,三张光盘装。你用的MSDN如果按这个路径走下去的话,可能会找不到,不过我想大致也是在这个位置了,找找看!!!

LRESULT CALLBACK WindowProc

(

HWND hwnd, // handle to window

UINT uMsg, // message identifier

WPARAM wParam, // first message parameter

LPARAM lParam // second message parameter

);

这个函数我们的第二只小板凳里被我们称为WndProc.

下面讲解:

不知你注意到了没有,这个函数的参数与刚刚提到的GetMessage调用把返回的MSG结构的前四个成员相同。如果消息处理成功,WindowProc的返回值为0.

Windows的启动应用程序时,先调用WinMain函数,然后调用窗口过程,注意:在我们的这个程序中,只有一个窗口过程,实际上,也许有不止一个的窗口过程。例如,每一个不同的窗口类都 有一个与之相对应的窗口过程。无论Windows何时想传递一个消息到一窗口,都将调用相应的窗口过程。当Windows从环境,或从另一个应用程序,或从用户的应用程序中得到消息时,它将调用窗口过程并将信息传给此函数。总之,窗口过程函数处理所有传送到由此窗口类创建的窗口所得到的消息。并且窗口过程有义务处理Windows扔给它的任何消息。我们在学习Windows程序设计的时候,最主要的就是学习这些消息是什么以及是什么意思,它们是怎么工作的。

令我们不解的是,在程序中我们看不出来是哪一个函数在调用窗口过程。它其实是一个回调函数.前面已经提到,Windows把发生的输入事件转换成输入消息放到消息队列中,而消息循环将它们发送到相应的窗口过程函数,真正的处理是在窗口过程函数中执行的,在Windows中就使用了回调函数来进行这种通信。

回调函数是输出函数中特殊的一种,它是指那些在Windows环境下直接调用的函数。一个应用程序至少有一个回调函数,因为在应用程序处理消息时,Windows调用回调函数。这种回调函数就是我们前面提到的窗口过程,它对对应于一个活动的窗口,回调函数必须向Windows注册,Windows实施相应操作即行回调。

每个窗口必须有一个窗口过程与之对应,且Windows直接调用本函数,因此,窗口函数必须采用FAR PASCAL调用约定。在我们的第二只小板凳中,我们的窗口函数为WndProc,必须注意这里的函数名必须是前面注册的窗口类时,向域wc.lpfnWndProc所赋的WndProc。函数WndProc就是前面定义的窗口类所生成的所有窗口的窗口函数。

在我们的这个窗口函数中,WndProc处理了共有两条消息:WM_PAINTWM_DESTROY.

窗口函数从Windows中接收消息,这些消息或者是由WinMain函数发送的输入消息,或者是直接来自Windows的窗口管理消息。窗口过程检查一条消息,然后根据这些消息执行特定的动作未被处理的消息通过DefWindowProc函数传回给Windows作缺海上处理。

可以发送窗口函数的消息约有220种,所有窗口消息都以WM_开头,这些消息在头文件中被定义为常量。引起Windows调用窗口函数的原因有很多,,如改变窗口大小啊,改变窗口在屏幕上的位置啊什么的。

Windows已经把任务扔给窗口过程了,窗口过程是怎么处理消息的呢?稍息一下,让我们进行下一节:处理消息……

 

 Posted by at 20:10
202010
 

(七)处理消息 

窗口过程处理消息通常以switch语句开始,对于它要处理的每一条消息ID都跟有一条case语句。大多数windows proc都有具有下面形式的内部结构:

switch(uMsgId)

{

case WM_(something):

//这里此消息的处理过程

return 0;

case WM_(something else):

//这里是此消息的处理过程

ruturn 0;

default:

//其他消息由这个默认处理函数来处理

return DefWindowProc(hwnd,uMsgId,wParam,lParam);

}

在处理完消息后,要返回0,这很重要—–它会告诉Windows不必再重试了。对于那些在程序中不准备处理的消息,窗口过程会把它们都扔给DefWindowProc进行缺省处理,而且还要返回那个函数的返回值。在消息传递层次中,可以认为DefWindowProc函数是最顶层的函数。这个函数发出WM_SYSCOMMAND消息,由系统执行Windows环境中多数窗口所公用的各种通用操作,例如,画窗口的非用户区,更新窗口的正文标题等等等等。

 

再提示一下,以WM_的消息在Windows头文件中都被定义成了常量,如WM_QUIT=XXXXXXXXXXX,但我们没有必要记住这个数值,也不可能记得住,我们只要知道WM_QUIT就OK了。

在第二只小板凳中我们只让窗口过程处理了两个消息:一个是WM_PAINT,另一个是WM_DESTROY,先说说第一个消息—WM_PAINT.

关于WM_PAINT:

无论何时Windows要求重画当前窗口时,都会发该消息。也可以这样说:无论何时窗口非法,都必须进行重画。 哎呀,什么又是”非法窗口”?什么又是重画啊?你这人有没有完,嗯?

稍安勿燥,我比你还烦呢?我午饭到现在还没吃呢!你有点耐心,来点专业精神好不好???我开始在MSDN里面找有关这个方面的内容了,别急,我找找看:

Platform SDK–>Graphics and Multimedia Services–>Windows GDI–>Painting and Drawing–>Using the WM_PAINT Message—–终于找到了。

下面是一大套理论:

让我们把Windows的屏幕想像成一个桌面,把一个窗口想像成一张纸。当我们把一张纸放到桌面上时,它会盖住其他的纸,这样被盖住的其他纸上的内容都看不到了。但我们只要把这张纸移开,被盖住的其他纸上的内容就会显示出来了—这是一个很简单的道理,谁都明白。

对于我们的屏幕来说,当一个窗口被另一窗口盖住时,被盖住的窗口的某些部分就看不到了,我们要想看到被盖住的窗口的全部面貌,就要把另一个窗口移开,但是当我们移开后,事情却起了变化—–很可能这个被盖住的窗口上的信息被擦除了或是丢失了。当窗口中的数据丢失或过期时,窗口就变成非法的了—或者称为”无效”。于是我们的任务就来了,我们必须考虑怎样在窗口的信息丢失时”重画窗口”–使窗口恢复成以前的那个样子。这也就是我们在这第二只小板凳中调用UpdateWindow的原因。

你忘记了吗?刚才我们在(三)显示和更新窗口中有下面的一些文字:

WinMain()调用完ShowWindow后,还需要调用函数UpdateWindow,最终把窗口显示了出来。调用函数UpdateWindow将产生一个WM_PAINT消息,这个消息将使窗口重画,即使窗口得到更新.—这是程序第一次调用了这条消息。

为重新显示非法区域,Windows就发送WM_PAINT消息实现。要求Windows发送WM_PAINT的情况有改变窗口大小,对话框关闭,使用了UpdateWindows和ScrollWindow函数等。这里注意,Windows并非是消息WM_PAINT的唯一来源,使用InvalidateRect或InvalidateRgn函数也可以产生绘图窗口的WM_PAINT消息……

通常情况下用BeginPaint()来响应WM_PAINT消息。如果要在没有WM_PAINT的情况下重画窗口,必须使用GetDC函数得到显示缓冲区的句柄。这里面不再扩展。详细见MDSN。

这个BeginPaint函数会执行准备绘画所需的所有步骤,包括返回你用于输入的句柄。结束则是以EndPaint();

在调用完BeginPaint之后,WndProc接着调用GetClientRect:

GetClientRect(hwnd,&rect);

第一个参数是程序窗口的句柄。第二个参数是一个指针,指向一个RECT类型的结构。查MSDN,可看到这个结构有四个成员。

WndProc做了一件事,他把这个RECT结构的指针传送给了DrawText的第四个参数。函数DrawText的目的就是在窗口上显示一行字—-”你好,欢迎你来到VC之路!“,有关这个函数的具体用法这里也没必要说了吧。

关于WM_DESTROY 

这个消息要比WM_PAINT消息容易处理得多:只要用户关闭窗口,就会发送WM_DESTROY消息(在窗口从屏幕上移去后)。

程序通过调用PostQuitMessage以标准方式响应WM_DESTROY消息:

PostQuitMessage (0) ;

这个函数在程序的消息队列中插入一个WM_QUIT消息。(四)创建消息循环中我们曾有这么一段话:

消息循环以GetMessage调用开始,它从消息队列中取出一个消息:

…….

在接收到除WM_QUIT之外的任何一个消息后,GetMessage()都返回TRUE。如果GetMessage收到一个WM_QUIT消息,则返回FALSE,如收到其他消息,则返回TRUE。因此,在接收到WM_QUIT之前,带有GetMessage()的消息循环可以一直循环下去。只有当收到的消息是WM_QUIT时,GetMessage才返回FALSE,结束消息循环,从而终止应用程序。

182010
 

忽然对结构体以及指向结构体的指针来了兴趣,就找了个程序逆向了一下,学到一些东西。 不看源程序,反汇编代码得到这样两条语句:           *(*(Data + 4) + 8 * **(Data + 4) + 4 ) = v1;           *(*(Data + 4) + 8 * **(Data + 4) + 8 ) = v2; 其中,Data是指向某个结构体的指针,也即一个32位的地址。 首先要理解的是,Data作为指向结构体的指针,它本身只占4字节内存,即一个32位;而这32位的含义是它所指向的结构体那个类型的一个变量的内存首地址。 呵呵,这句话比较拗口,总之是存了一个变量(假定名字为A)的首地址;而这个变量是个结构体类型——也就是说,这个变量A可能占用4个、8个、12个乃至更多字节的内存,因为结构体内是可能包括很多不同数据类型的变量的。 理解了这一点,就可以看一下 Data + 4,这显然仍然是一个地址,而且正如上面说的,是Data所指向的变量A内部的某个元素(假定为B)的地址。 OK,加个星号,*(Data + 4)取出这个元素B。 B = *(Data + 4) 由于上面的反汇编中出现了**(Data + 4)这样的代码,所以可以断定元素B存放的也是一个指针,它也指向某个变量(假定为C)的地址(或首地址)。 所以**(Data + 4)取出了C的内容。 C= *(B + 0) = *(*(Data + 4) + 0 ) 接下来,变量C乘以了8,这说明了什么呢,说明C这个地址内存放的是某个数组的index值,而这个数组中元素的大小为8字节。而B是指向这个数组(假定为D)首地址的。 之后,这个乘积与B相加,可以看出,结果会是B所指向的这个数组的某个元素的地址。但是为什么后面还要再加一个4呢? 原来是这样的,前面曾经提到,B所指向的内容C并不是一个数组的指针,而只是一个索引值,那么这个数组的地址自然就存到了 B + 4 这个内存中。进一步又说明了B指向的也是一个结构体。这个结构体第一个元素是一个数组的index值,第二个元素是这个数组D的地址,假定为E。 D= E = *(Data + 4) + 4 到这里,似乎分析基本已经清楚了。但是仔细看一下会发现第二行代码有一个加8,这是什么情况呢?这里应该有两种可能性,第一种是结构体B还有第三个元素,而且也是一个数组指针;第二种则是数组D的元素也是结构体类型,加8可以看成两个加4,第一个是从B中取得D的内容;第二个则是在D中取一个偏移,获得D的第index个元素的第2个变量的地址。 好了,至此真相大白,最后用一个*号得到内容,直接操作之~ 所以其实上面的代码应该是:           *( (*(Data + 4) + 4) + 8 * ( *(*(Data + 4) + 0 ) ) + 0 ) = v1;           *( (*(Data + 4) + 4) + 8 * ( *(*(Data + 4) + 0 ) ) + 4 ) = v2; 真实C语言源代码如下:           Data->Object->Entry[Data->Object->Index].Name =v1;           Data->Object->Entry[Data->Object->Index].Id =v2; 这里Data是指向结构体B的指针A, Object就是我们分析的结构体B,它自身仅是一个地址。 这个地址处有两个内容:第一个是数组的索引index,刚才我们称之为C,第二个是数组的首地址Entry,刚才称之为D。 数组Entry的元素都是结构体,有两个数据项,Name和Id。 眼花不?呵呵,我一开始也眼花,多看看就好了。做个记录,免得以后忘了。由于本人水平有限,以上分析肯定有很多疏漏之处,望诸位不吝赐教,非常感谢!