6.2数据结构 数据结构是程序设计的基础。在设计程序之前,应该先考虑好所需要的数据结构。 前微软首席架构师Charles Simonyi:编程的第一步是想象。就是要在脑海中对来龙去脉有极为清晰的把握。在这个初始阶段,我会使用纸和铅笔。我只是信手涂鸦,并不写代码。我也许会画些方框或箭头,但基本上只是涂鸦,因为真正的想法在我脑海里。我喜欢想象那些有待维护的结构,那些结构代表着我想编码的真实世界。一旦这个结构考虑得相当严谨和明确,我便开始写代码。我会坐到终端前,或者换在以前的话,就会拿张白纸,开始写代码。这相当容易。我只要把头脑中的想法变换成代码写下来,我知道结果应该是什么样的。大部分代码会水到渠成,不过我维护的那些数据结构才是关键。我会先想好数据结构,并在整个编码过程中将它们牢记于心。 开发过以太网和操作系统SDS 940的Butler Lampson:(程序员)最重要的素质是能够把问题的解决方案组织成容易操控的结构。 开发CP/M操作系统的Gary.A:如果不能确认数据结构是正确的,我是决不会开始编码的。我会先画数据结构,然后花很长时间思考数据结构。在确定数据结构之后我就开始写一些小段的代码,并不断地改善和监测。在编码过程中进行测试可以确保所做的修改是局部的,并且如果有什么问题的话,能够马上发现。 微软创始人比尔·盖茨:编写程序最重要的部分是设计数据结构。接下来重要的部分是分解各种代码块。 编写世界上第一个电子表格软件的Dan Bricklin:在我看来,写程序最重要的部分是设计数据结构,此外,你还必须知道人机界面会是什么样的。 我们举个例子来说明。在介绍防御性编程的时候,提到公司使用的LCD显示屏抗干扰能力一般,为了提高LCD的稳定性,需要定期读出LCD内部的关键寄存器值,然后跟存在Flash中的初始值相比较。需要读出的LCD寄存器有十多个,从每个寄存器读出的值也不尽相同,从1个到8个字节都有可能。如果不考虑数据结构,编写出的程序将会很冗长。
1. void lcd_redu(void)
2. {
3. 读第一个寄存器值;
4. if(第一个寄存器值==Flash存储值)
5. {
6. 读第二个寄存器值;
7. if(第二个寄存器值==Flash存储值)
8. {
9. ...
10.
11. 读第十个寄存器值;
12. if(第十个寄存器值==Flash存储值)
13. {
14. 返回;
15. }
16. else
17. {
18. 重新初始化LCD;
19. }
20. }
21. else
22. {
23. 重新初始化LCD;
24. }
25. }
26. else
27. {
28. 重新初始化LCD;
29. }
30. }
我们分析这个过程,发现能提取出很多相同的元素,比如每次读LCD寄存器都需要该寄存器的命令号,都会经过读寄存器、判断值是否相同、处理异常情况这一过程。所以我们可以提取一些相同的元素,组织成数据结构,用统一的方法去处理这些数据,将数据与处理过程分开来。 我们可以先提取相同的元素,将之组织成数据结构:
1. typedef struct {
2. uint8_t lcd_command; //LCD寄存器
3. uint8_t lcd_get_value[8]; //初始化时写入寄存器的值
4. uint8_t lcd_value_num; //初始化时写入寄存器值的数目
5. }lcd_redu_list_struct;
这里lcd_command表示的是LCD寄存器命令号;lcd_get_value是一个数组,表示寄存器要初始化的值,这是因为对于一个LCD寄存器,可能要初始化多个字节,这是硬件特性决定的;lcd_value_num是指一个寄存器要多少个字节的初值,这是因为每一个寄存器的初值数目是不同的,我们用同一个方法处理数据时,是需要这个信息的。 就本例而言,我们将要处理的数据都是事先固定的,所以定义好数据结构后,我们可以将这些数据组织成表格:
1. /*LCD部分寄存器设置值列表*/
2. lcd_redu_list_struct const lcd_redu_list_str[]=
3. {
4. {SSD1963_Get_Address_Mode,{0x20} ,1}, /*1*/
5. {SSD1963_Get_Pll_Mn ,{0x3b,0x02,0x04} ,3}, /*2*/
6. {SSD1963_Get_Pll_Status ,{0x04} ,1}, /*3*
7. {SSD1963_Get_Lcd_Mode ,{0x24,0x20,0x01,0xdf,0x01,0x0f,0x00} ,7}, /*4*/
8. {SSD1963_Get_Hori_Period ,{0x02,0x0c,0x00,0x2a,0x07,0x00,0x00,0x00},8}, /*5*/
9. {SSD1963_Get_Vert_Period ,{0x01,0x1d,0x00,0x0b,0x09,0x00,0x00} ,7}, /*6*/
10. {SSD1963_Get_Power_Mode ,{0x1c} ,1}, /*7*/
11. {SSD1963_Get_Display_Mode,{0x03} ,1}, /*8*/
12. {SSD1963_Get_Gpio_Conf ,{0x0F,0x01} ,2}, /*9*/
13. {SSD1963_Get_Lshift_Freq ,{0x00,0xb8} ,2}, /*10*
14. };
至此,我们就可以用一个处理过程来完成数十个LCD寄存器的读取、判断和异常处理了:
1. /**
2. * lcd 显示冗余
3. * 每隔一段时间调用该程序一次
4. */
5. void lcd_redu(void)
6. {
7. uint8_t tmp[8];
8. uint32_t i,j;
9. uint32_t lcd_init_flag;
10.
11. lcd_init_flag =0;
12. for(i=0;i<sizeof(lcd_redu_list_str)/sizeof(lcd_redu_list_str[0]);i++)
13. {
14. LCD_SendCommand(lcd_redu_list_str.lcd_command);
15. uyDelay(10);
16. for(j=0;j<lcd_redu_list_str.lcd_value_num;j++)
17. {
18. tmp[j]=LCD_ReadData();
19. if(tmp[j]!=lcd_redu_list_str.lcd_get_value[j])
20. {
21. lcd_init_flag=0x55;
22. //一些调试语句,打印出错的具体信息
23. goto handle_lcd_init;
24. }
25. }
26. }
27.
28. handle_lcd_init:
29. if(lcd_init_flag==0x55)
30. {
31. //重新初始化LCD
32. //一些必要的恢复措施
33. }
34. }
通过合理的数据结构,我们可以将数据和处理过程分开,LCD冗余判断过程可以用很简洁的代码来实现。更重要的是,将数据和处理过程分开更有利于代码的维护。比如,通过实验发现,我们还需要增加一个LCD寄存器的值进行判断,这时候只需要将新增加的寄存器信息按照数据结构格式,放到LCD寄存器设置值列表中的任意位置即可,不用增加任何处理代码即可实现!这仅仅是数据结构的优势之一,使用数据结构还能简化编程,使复杂过程变的简单,这个只有实际编程后才会有更深的理解。
|