关系数组

2024-07-29

关系数组(共9篇)

关系数组 篇1

指针是C语言中的一个重要的概念, 不掌握指针就没有掌握C的精华。其中数组和指针的关系, 特别是多维数组和指针的关系更容易让人混淆。本文通过几条简单的结论, 来阐述两者的关系。

1 预备概念和结论

本文把指向具体元素的指针称为点指针;把指向包含n个具体元素一维数组的指针称为行指针。

int*p表示声明p是指向具体元素的指针, 为点的指针。

int (*pointer) [4]表示声明pointer是一个含4个具体元素一维数组的指针, 为行指针。

在C语言中, 地址可以认为是一个指针, 见下例:

运行结果如下:

&a是a的地址, aPtr是指向a变量的指针, 两者输出结果一致;&a+1和aPtr+1输出结果一致;*&a和*aPtr输出结果一致。

从结果看, 如果把&a视为一个指针, 一个指向变量a的指针, 那么很好解释&a和aPtr等价, &a+1和aPtr+1等价, *&a和a P t r等价, 因此他们输出结果分别一致。

地址就是指针, 该结论是理解数组和指针关系的基础。

2 一维数组

假设有:

(1) 数组名称就是地址和指针。

一维数组名称a r r a y代表数组中首元素 (array[0]) 的地址, p=array是把首元素地址赋给p。根据“地址就是指针”, array实际上就是一个指针, 一个指向首元素的指针, p=array也可以认为是把一个指针赋给另一个指针, 使得p跟array一样指向首元素。

p=array和p=&array[0]等价。&array[0]表示array[0]的地址, p=&array[0]是把首元素array[0]的地址赋给指针p, 使得p指向首元素。根据“地址就是指针”, &array[0]实际就是一个指针, 一个指向首元素的指针, 因此, p=&array[0]也可以认为是把一个指针赋给另一个指针, 使得p跟&array[0]一样指向首元素。因此两者是等价的。

(2) 一维数组名称是点指针。

一维数组名称a r r a y表示一个指针常量, 该指针指向首元素 (第0号元素, 为具体的元素) , 因此一维数组名称a r r a y是点指针。因为array为点指针, 那么array+1指向的是数组中第1号元素。

(3) *array跟array[0]等价, * (array+1) 和array[1]等价。

由于a r r a y为指向首元素的点指针, 那么*array表示该指针指向的元素 (首元素) , 而在一维数组中, array[0]表示首元素, 因此*array跟array[0]等价。

由于a r r a y表示指向首元素的点指针, 那么array+1指向下一个点 (即下一个具体元素) 。因此, * (array+1) 和array[1]等价, 其他依次类推。

3 二维数组

假设定义一个二维数组如下:

int a[3][4]={{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 16}}。

(1) 把二维数组a视为一维数组, 其元素为a[0], a[1]和a[2], 其中a[0], a[1]和a[2]分别是包含4个具体元素的一维数组 (见图1) 。这里面包含两个信息。

(1) 把a作为一维数组来理解, 那么名称a就是一个指针, 指针a指向的是a[0], a+1指向的是a[1], a+2指向的是a[2]。由于a[0], a[1]和a[2]并不是具体的元素, 而是包含4个具体元素的一维数组。因此a和a+1, a+2分别是行指针。

(2) a[0], a[1]和a[2]并不是具体的元素, 而是包含4个具体元素的一维数组。因此把a[0], a[1]和a[2]作为一维数组名称来使用。

一维数组名称就是一个指针, 其指向该数组的首元素。本文把指向具体元素的指针称为点的指针, 因此a[0], a[1], a[2]就是指向该数组中首元素的指针, 为点的指针。

(2) a[0]+1指向的是第0行第1列元素, a[1]+2指向的是第1行第2列元素。

由于a[0]表示指向第0行数组的首元素 (第0列元素) , 为点的指针, a[0]+1表示把该指针移动1个位置, 即指向该数组中的第1列元素, 即指向的是第0行第1列元素;同样, 由于a[1]表示指向第1行数组的首元素 (第0列元素) , 为点的指针, a[1]+2表示把该指针移动2个位置, 即指向该数组中的第2列元素, 即指向的是第1行第2列元素;

点指针为具体指向某个元素的指针, * (a[0]+2) 表示第0行第2列元素。

(3) a[1]+2和* (a+1) +2等价。

把a看成是一个含有3个元素的一维数组。采用下标法访问元素, 即a[0], a[1]和a[2];采用指针法访问元素, 如*a, * (a+1) 和* (a+2) 等。因此a[1]和* (a+1) 等价, 所以a[1]+2和* (a+1) +2等价。

a[1]+2和* (a+1) +2都表示指向第1行第2列元素的指针, 因此* (a[1]+2) 和* (* (a+1) +2) 均表示该指针指向的元素。

(4) *a和a输出值一样, 类型不一样。

测试如下的程序, 观察*a和a的输出值:

测试表明*a和a输出的值一样。a代表0行首地址, *a其代表第0行第0列的地址, 因此两者的输出值是一样的, 但是两者的类型不一样。

a是行指针, 其代表0行首地址。a[0]和*a是点的指针 (a[0]和*a等价) , 其代表第0行第0列的地址。因此a和*a输出的值一样, 但是前者是行指针, 后者是点指针。

进一步测试如下程序, 该例的目的是输入二维数组a的行和列的下标, 把相应位置的元素输出:

发现以上程序通不过编译, 错误在于指针类型不匹配。p是点指针, 而a是二维数组名, 表示一个行指针常量, 显然把个行指针常量赋给一个点指针, 类型上不一致。因此, 修改如下:

发现以上程序通过编译, 功能满足要求。int (*p) [4]表示p为一个指针变量, 它指向包含4个整型元素的一维数组, 为行指针, 因此p和a类型一致。

如果采用点指针来完成上述程序的功能, 可以把程序修改为:

*a其代表第0行第0列的地址, 根据地址就是指针, *a就是第0行第0列的元素的指针, 为点指针。p=*a实现把p指向第0行第0列的元素, 亦为点指针。本例子是采用点移动的方法, 找到相应行和列的元素。

同样, 函数调用就是把实参赋给形参。如果指针作为函数参数, 必须分清形参和实参的类型并判别两者类型是否一致。数组名的本质就是指针, 因此数组名作函数参数, 实质就是指针作为函数参数, 同样遵守以上的规则。

4 结语

综上所述, 在数组和指针关系中, 有如下三个主要结论。

(1) 地址就是指针。

(2) 数组名称是既表示地址, 又表示指针。一维数组名称是点指针, 二维数组名称是行指针。

(3) 假设有array[n][m], 二维数组array认为是包含array[0], array[1], array[2]…array[n-1]等n个元素的一维数组, 而这n个元素分别是包含了m个具体元素的一维数组, array[0], array[1], array[2]…array[n-1]等可以视为一维数组名称。

参考文献

[1] (美) 阿霍, 赵建华[译].编译原理 (第2版) [M].机械工业出版社, 2009, 1.

[2]张素琴.编译原理 (第2版) [M].清华大学出版社, 2005, 2.

[3]严蔚敏, 吴伟民.数据结构 (C语言版) [M].清华大学出版社, 1997, 4.

[4]刘景, 周玉龙.高级语言C++程序设计 (第2版) [M].高等教育出版社, 2006, 12.

关系数组 篇2

指针数组:是一个数组,数组的元素是指针。数组占多少个字节由数组本身决定。

数组指针:是一个指针,它指向一个数组。在32位系统下永远是占4 个字节。

举例说明:

1)int *p1[n];

2)int (*p2)[n];

1)“[]”的优先级比“*”要高。p1 先与“[]”结合,构成一个数组的定义,数组名为p1,int *修饰的是数组的内容,即数组的每个元素,

即p1是指针数组,其包含n个指向int 类型数据的指针。

2)“”的优先级比“[]”高,“*”号和p2 构成一个指针的定义,指针变量名为p2,int 修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。即p2是数组指针,它指向一个包含n 个int 类型数据的数组。

如要将二维数组赋给一指针,应这样赋值:

int a[3][4];

int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。

p=a; //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]

p++; //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]

所以数组指针也称指向一维数组的指针,亦称行指针。

C语言的指针运用和数组的关系 篇3

指针是C语言使用比较广泛的数据类型之一, 是C语言的一个重要特色。指针可以有效地表示复杂的数据结构、方便引用数据和字符串、动态分配内存、直接访问物理地址等等。正确而灵活地使用指针, 能够使程序精练, 高效运行。指针即是重点又是难点, 能否正确掌握和运用指针是是否掌握C语言的重要体现。有些学生在学习指针这部分时, 甚至学完C语言后仍然不明白指针是怎么回事, 为什么要用指针, 怎样用指针, 在什么情况下用指针, 复杂指针类型之间的区别和联系, 搞的不是很清楚。本文以指针和数组的关系为线索, 加以阐述。

2. 数组和指针的相关概念

数组是C语言的构造类型之一, 是同类数据的有序集合。数组元素可以是基本数据类型或是构造类型。C语言中规定, 数组只能静态定义, 定义时必须指定所占空间的大小。数组名表示数组在内存中的首地址。

指针这里是指指针变量。指针变量的值就是某个内存单元的地址。指针的实质就是通过对内存地址的操作来实现对数据的操作。

指针有指向简单变量的指针, 指向字符串的指针, 指向数组的指针即数组指针, 指向指针的指针等。本文将重点放到指针和数组的关系上。把上面的概念搞清楚, 在学习指向函数、结构体、文件等的指针就容易理解和掌握。

3. 数组与指针的关系

3.1 简单指针变量与数组的关系

例题1:输出一维整形数组a[4]的全部元素

方法一:用数组名访问数组元素

用下标法或者通过数组名计算数组元素地址, 找到数组元素。

for (i=0;i<4;i++) printf ("%3d", a[i]) ;或者for (i=0;i<4;i++) printf ("%3d", * (a+i) ) ;

方法二:用指针变量来访问数组元素

针对例题, 先定义一个指向整形变量的指针p并被赋初值p=a.。然后for (i=0;i<4;i++) printf ("%3d", *p++) ;

例题2:输出二维整形数组a[4][6]的全部元素

方法一:用数组名访问数组元素

方法二:用指针变量来访问数组元素

定义一个指向整形变量的指针p, 并被赋初值p=&a[0][0], 使用循环语句for (i=0;i<24;i++) printf ("%3d", *p++) ;来输出全部元素。

二维以上数组均可使用这两种方法。

分析:在对数组元素顺序访问时, 使用指针变量直接访问数组元素, 由于不用象使用数组名那样每次都要计算元素地址, 而是有规律地改变地址值, 大大提高了程序的执行效率。但在对数组元素进行随机访问时, 两者访问速度一样, 由于下标法更易理解, 不容易出错, 使用下标法更好。

3.2 数组指针、指针数组和指向指针的指针与二维数组的关系

例题1:对5个字符串进行排序

方法一:使用数组指针

在主函数中:

定义二维字符数组和指向由N个元素组成的一维数组的指针:char str[M][N];char (*p) [N];经输入语句输入各个字符串:for (i=0;i<M;i++) scanf ("%s", str[i]) ;再将指针p指向二维数组的首地址:p=str;执行调用语句:sort (p) ;最后输出结果:

在主函数中:

定义由M个指针类型元素组成的数组p:char*p[M];将各字符串的首地址赋给指针数组p的各元素:for (i=0;i<M;i++) p[i]=str[i];执行调用语句sort (p) ;通过循环语句for (i=0;i<M;i++) print ("%sn", p[i]) ;输出排序好的字符串。

方法三:使用指向指针的指针

在主函数中:

定义:char**p, *pstr[M], str[M][N];将第i个字符串的首地址赋给指针数组pstr的第i个元素:for (i=0;i<M;i++) pstr[i]=str[i];指针p指向指针数组pstr:p=pstr;执行调用语句:sort (p) ;通过循环语句:for (i=0;i<M;i++) printf ("%sn", pstr[i]) ;输出排序好的字符串。

分析:数组指针是指向含有多个元素组成的一维数组的指针变量, 即指向数组的指针, 而不是指向某个数组元素。因此可以理解为是一个行指针。使用数组指针时, 调用排序函数sort, 传递的参数是原字符数组的首地址。对原字符数组进行排序, 最后直接输出已经排序好的字符数组;

指针数组是其元素类型均为指针类型数据的数组。使用指针数组时, 由于数组的元素类型是指针型, 是各字符串的首地址, 调用sort函数后, 传递的参数是指针数组的首地址, sort函数是对指针数组排序, 原字符数组并无直接参与排序。通过输出已经排序好的指针数组来达到对原字符串的排序;

指向指针的指针是指指向指针数据的指针变量。使用指向指针的指针变量时, 又使用了一个指向指针数组的指针变量, 函数调用后传的参数也是指针数组的首地址, 但是sort函数是将存储各字符串地址的指针数组进行了排序, 通过使用指向指针的指针变量来对指针数组排序的。而不是象使用指针数组那样直接对指针数组排序。因此也必须通过输出指针数组的内容来达到排序的目的, 原字符串无变化。

3.3 数组指针、指针数组和指向指针的指针与数组的区别与联系

(1) 当数组的各维大小确定时, 多用静态数组, 但浪费严重, 适合对空间需求不大的场合;当数组的低维确定, 高维需要动态生成的场合, 例如a[x][16], 使用数组指针。体现指针里的一个进步, 指向整个数组而不是某个元素;当数组高维确定, 低维许动态生成时, 例如a[20][x], 使用指针数组。好处在于, 指针的内容可以根据需要动态生成, 避免浪费空间, 并且由于指针呈数组形式排列, 方便索引。当处理对象是多个字符串时, 使用指针数组可以节省空间, 提高程序的执行效率;当数组的高低维均需动态生成时, 使用指向指针的指针。

(2) 二维数组名、数组指针和指向指针的指针是同类型的, 同一级指针。但是二维数组名是指针常量, 而数组指针和指向指针的指针是指针变量。

(3) 数组在生命期内, 地址和容量保持不变, 内容可以变;使用数组初始空间大, 但索引方便。指针初始空间小, 动态分配空间, 使用灵活方便。常用指针来操作动态内存, 但如果使用不谨慎, 容易出错。

谈到指针的应用, 与数组有关的还有利用动态分配函数malloc () 或calloc () 为指针变量分配内存, 建立动态数组。

4. 结束语

指针是C语言的重要特色, 但指针及相关概念不容易理解, 使用也易出错。对于初学者来说, 只有勤于实践, 多上机调试程序, 才能真正掌握, 才能充分利用指针灵活多变的特点, 编写出有特色、高效和精练的程序。

参考文献

[1].谭浩强.C语言程序设计 (第三版) 北京:清华大学出版社, 2005、7

数组总结 篇4

2012~2013学年度第一学期数学教研组工作总结

本学期以来,我们以现代教育思想、现代教育理念为指导,认真贯彻落实小学数学新课程标准为契机,认真贯彻落实学校本期工作意见,以学校办学目标为指针,以“创造适合教师发展的管理”和“创造适合学生发展的教育”为目标,以培养学生良好的学习习惯和浓厚的兴趣为重点,着眼于教师和学生创新意识的提高,着力于教师和学生自主发展机制的建立,加强数学教学流程管理,深化数学课堂教学改革,健全质量调控机制的建立,切实提高数学教学质量。

一、本期的工作目标:

1、继续抓好教育教学理论的学习,创设浓郁的学术氛围,不断提高教师的自身素质;

2、重点抓好教学常规管理与校本教研,进一步增强教师的自主创新意识,全面提高教育教学质量,提高学校的知名度;

3、组织学生开展丰富多彩的数学课外实践活动,使学生在实践活动中形成一定的数学能力,创新意识和实践能力;

4、改进教研活动的形式,方法和内容,切实提高教师教学科研能力,力争办“研有特色”的小数教研组。

三、本期的主要工作做法。

(一)、认真学习,大胆实践,深入推进自主参与式课堂教学模式的改革实验。

1、深化数学课堂教学改革,大胆实践探索。要确立以活动促发展的意识,通过举办不同层面的课堂教学展示研讨活动,多评议切磋,多交流碰撞,在活动中发现、研究、解决所遇问题。要切实转变学生学习的方式,将充分的自主学习、有效的合作学习、适度的探究学习紧密结合起来,重视兴趣的激发、独特体验的激活和动手作用的发挥。上课既要有课前的预设,又要开放地纳入弹性灵活的生成。对于师生互动中出现的创造火花,教师应敏锐地抓住它,并予以引燃,使不同的体验发散共享,从而超越预设的目标。课改要认真扎实地开展探索实践,注意积累典型课案和经验;也要用课改理念指导和改革课堂教学,深入领会教材修订的指导思想、编写思路、结构、体系以及教学要求的变化,用新观念、新教法去教老教材,懂得灵活处理与运用好教材。

2、认真学习《数学课程标准》等教育文件,转变观念。要潜心研究了解数学课程改革背景、指导思想、改革目标,熟悉课程标准的目标、内容和要求。继续注意转变以下教育观念和课程观:从“文本”走向“体验”;教学观,从“传道、授业、解惑”走向“教学相长”;人才观,从“封闭”走向“开放”;目标观,从单一目标走向三维整合的目标。要继续努力将教改基本理念转化为数学教学过程的具体行为,整合优化教学过程。

3、加强课改过程管理。教师要积极切磋教法学法,积极上好课改实践课、研究课,开展评课交流,及时反思、提炼和总结经验。充分发挥校内骨干教师的导航、引领作用,同时要努力将教研组建设为学习型组织,重视组内和组际之间的团队学习和合作学习,加强教研组信息化建设步伐,整体促进教研组业务水平的提高。

4、本学期按“备课――上课――说课――评课――结课”的程序,逐步完善主题式教研氛围,促进学校数学薄弱环节的发展 前期每人一节公开课,后期有目的地安排示范课、观摩课、汇报课等。坚持每人说课一次,中高年级教师上好一节公开课。专题交流、探讨,把实践上升为理论。

5、在学习教育理论基础上,及时总结、及时撰写论文,积极参加各种教研活动,展示自己的教研成果,在最短的时间内,尽量提高本组员的教学水平。

(二)、悉心指导,优化服务,规范数学教学常规管理。

1、注重培养学生的数学兴趣。充分调动他们的学习兴趣及学习积极性。让他们的天性和个性得以自由健康的发挥。让学生在视、听、触觉中培养了创造性思维方式,变“要我学”为“我要学”,极大地活跃了课堂气氛,相应提高了课堂教学效率。

2.重视学生良好学习习惯的养成。特别要重视上课专心听讲、按时完成作业、审题、读题、写字等习惯的养成,采取切实的措施经常抓,反复抓,为学生可持续发展打下良好的基础。

3.加强教学质量监控。教研组将配合教导处多次、小范围地进行随机质量调测,及时分析反馈调控。同时加强各类素质竞赛,以促进学生素质全面发展。

(三)、加大力度,催长骨干,积极推进名师工程。

1、加强教师的培训。教研组将在教导处指导下定期开展多层次的校本培训,切实转变教师观念,进一步推进课堂教学结构、内容、方法和手段的改革。同时,配合学校管理制度,组织教师多写并写好随笔与反思。

2、重视学科骨干、教学能手的跟踪培养和使用工作,注意做好推荐公开课、讲学培训活动。同时也要重视发挥非骨干、非青年教师在数学课改中不可低估的作用,注意教师的均衡性发展,落实教学管理中以人为本的理念,真正促进教师队伍建设。

数组排序算法浅析 篇5

顺序排序的主要思想是每一轮比较结束后都可以确定某一元素;在一轮的比较过程中, 将要确定的位置上的元素与其后所有的元素进行比较;对于一个长为N的数组, 需进行N-1轮比较。其第一轮的比较过程如下:

该轮中, a[0]与a[1]~a[n-1]的所有元素进行比较, 比较过程中, 如果发现哪个元素比a[0]小, 则与a[0]进行交换。一轮比较之后, 确定a[0]为数组中最小的元素。相同方法, 依次确定a[1]、a[2]、a[3]…a[n-2]。

由上表, 可以写出其实现代码

可以发现当数组原有的顺序是降序, 要实现其升序排序时, 每一轮中的交换的次数将会非常多, 严重影响排序效率。所以对该方法进行改进:先找出数组中最小值, 再与相应位置上的元素进行交换, 这就是选择排序。

选择排序的主要思想是每次从待排序的数据元素中选出最小的一个元素, 放在待排序数列的起始位置, 直到全部待排序列的数据元素全部排列完毕。

第一轮的比较过程如下:

选择排序的实现代码

选择排序相较于顺序排序有更高的执行效率, 而且思想同样利于理解。

冒泡排序的主要思想是“相邻元素”之间的比较, 如果前面的元素大于后面元素就把他们互换。一轮比较之后可以确定最后一个元素为最大, 第二轮比较之后可以确定最后一个元素为第二大的元素……依次类推, 第N-1轮比较, 可以确定倒数第二个元素, 这个时候数组的排序完成。冒泡排序的过程如下:

冒泡排序的实现代码

顺序排序算法, 思想简单易于理解且适于任何的数组, 无论什么情况下都可以使用;但是顺序排序效率较低, 可以采用选择排序法进行改进;即使如此选择排序的效率依然受到比较次数的影响, 所以对于比较元素比较少的数组, 可以采用冒泡排序法。

如果数组中99%的数值已经排序好, 即只有很少的元素需要进行排序, 可以选择冒泡排序法;如果你所要排序的数据数目相对较少并满足100个以下, 你就可以采用选择排序法;如果上述几种情况都不满足, 那么就选普遍适用的排序算法即顺序排序法即可。

以上所述只是三种常见排序, 在众多的排序算法中各有优缺点, 每一种算法只有在某一种情况下才表现的最好, 我们应当合理的根据实际情况选择算法。

参考文献

[1]张巍.基于Page Rank算法的搜索引擎优化策略研究[D].四川大学, 2005.

[2]郭敏杰.基于云计算的海量网络流量数据分析处理及关键算法研究[D].北京邮电大学, 2014.

数组越界的静态分析 篇6

软件测试是保证软件质量和可靠性的重要手段,在整个软件生命周期中占有重要位置。根据在测试过程中是否运行软件代码,可将软件测试分为静态测试和动态测试两种方法。

在静态测试中最常见的问题之一为数组越界,数组越界是缓冲区溢出故障中最常见的问题,缓冲区溢出是一种常见且危害很大的软件错误,如果程序的缓冲区被写入超出其长度的内容,就会造成缓冲区溢出,程序的堆栈被破坏,从而导致软件系统崩溃。数组越界带来的后果主要有两种:(1)对数组的访问超越了边界,使程序运行错误,甚至导致出现异常;(2)对数组以外的区域进行操作,有可能引入恶意代码,对软件安全造成威胁,主要针对数组越界问题给出了防范技巧及检测方法和工具,并对方法及工具进行了优缺点分析。

2 静态分析

静态分析(Static Analysis),也叫静态规则检查或代码规范检查,是指在不执行程序代码的方式下,通过控制流分析、数据流分析、表达式分析、接口分析等寻找程序代码中可能存在的错误或评估程序代码的过程。静态分析是验证代码是否满足规范性、可靠性、可维护性、安全性等指标的一种代码分析技术。它可以手工进行,也可以借助软件工具自动进行,以达到检测的目的。其主要优点就是在程序运行之前就可以对程序故障进行定位。

静态分析技术主要具有以下特点:

(1)不必动态地运行程序。静态分析只需通过工具或人工或二者相结合的方法对代码进行分析,不必进行测试用例的设计和测试结果的判读等工作。

(2)执行速度快、效率高。目前成熟的代码分析工具每秒可扫描上万行代码,具有检测速度快、效率高的特点。

3 数组越界

数组(array)是C或C++等语言常见的一种数据类型,是若干同类变量的聚合,可以存储相同类型的多个数据项,它是同一种数据类型在内存中的连续存放。数组变量在定义时就已经确定了所使有的内存空间大小,因此程序在运行过程中如果数组变量的索引超过了定义的内存空间大小,系统可能会将一个随机的值赋给变量,而得不到预期的结果;对于越界写数据就有可能修改相邻内存的一些重要的信息,从而造成程序的运行混乱,这类错误称之为数组越界(Array Bound),数组越界在使用数组类型进行程序设计的软件中属于常见的一类错误,它主要是程序设计中引用数组元素时,对内存的操作超过了初始指定的范围而引起,通常数组越界分为上溢和下溢。声明/定义一个长度为的数组,则其下标范围为如果以为i数组下标引用数组元素,当时,则发生故障,称之为下溢;当则称之为上溢。因此对数组边界的检查即要检查数组的上界还需检查数组的下界。

C/C++语言为了提高运行效率,不会自动检查数组越界,也就是说如果数组越界,程序编译时不会报错,从而在执行时产生非法操作或者得不到正确的结果。在C/C++语言中下标的引用超过声明的范围是数组越界常见的错误,在程序中还有其他的一些错误,也属于数组越界:

(1)指针变量的引用:在C或C++语言中由于指针引用较普遍,通过指针可直接访问变量的内存地址,并且可以改变内存地址的指向和相应的内容,且C或C++语言本身并没有对边界进行自动检测的操作,因此在程序代码实现中,就要保证指针的指向不会超越边界,否则程序有可能存在越界。

(2)在C或C++语言中使用字符串操作的库函数,如strcpy(),sprintf()等库函数,由于其本身没有越界操作判断,往往会发生越界故障。例如在执行strcpy()函数时,需要考虑源参数所占空间大小必须小于或等于目标空间的大小,否则将出现越界。

数组越界漏洞产生必须要具备3个条件:

1)存在数组的形式;

2)存在对数组的拷贝或读取操作;

3)引用数组下标,超过了数组下标的允许范围。

下面是3个数组越界的例子:

这3道题都存在数组越界的问题:

题(1)中,Array[i]明显发生了数组越界。

题(2)中,是一个含有10个元素的字符数组,

指向的字符串长度为10,在进行调用时,会将的结束符也复制到数组里,即复制的字符个数为11,这样会导致出现数组越界。程序不一定会因此而崩溃,但这是一个潜在的危险。解决办法是将的元素个数定义为11。

题(3)中,代码第6行,p指向a的第1个元素,所以p+4指向a的最后一个元素即即p+16,此时指向的是数组a的第17个元素,显然已经越界了,因此输出的打印结果是个随机数。

由于C或C++语言不进行边界的自动检测,因此在程序代码编写过程中就需要考虑这方面问题,下面罗列了一些防止数组越界漏洞的小技巧:

(1)每次要访问数组时用if或断言来判断。

(2)用try…catch…语句来写代码。

(3)使用迭代器(iterator),遍历STL里面vector的部分或全部元素。

(4)分配好数组的长度、类别、数据的多少,注意及时释放内存。

(5)为防止越界访问,可以写一个array类把数组封装起来,在其operator[]中检查其下标是否越界。

(6)在数组结束时设置标志。

(7)函数参数是数组时一般应该把数组大小也传进来。

4 数组越界的检测

4.1 检测方法

目前检测数组越界的方法主要分为两大类:动态检测方法和静态检测方法。动态检测是在源代码中可能出现数组越界的位置进行插装,即在程序中数组使用的地方加入逻辑断言或其他类型的语句,对插装后的代码编译运行,在运行中检查是否存在缓冲区溢出漏洞,存在数组越界错误的程序运行时,它的运行现象表现是不定的,有时候程序一直运行,可能什么异常情况都不会发生;有时候,则是程序运行会出现异常崩溃,且出现的问题是不可复现的。动态检测方法能够精确判断出程序中是否出现了缓冲区溢出,检测结果的可信度较高;但其缺点是仅可以部分实现对数组越界故障的测试,且降低了程序的运行效率,增加了开销,在覆盖率和效率方面存在问题。

静态检测是相对于动态检测的另一大类方法,它可以全面检测程序的控制结构,静态检测方法通过分析源代码来检测可能存在的安全漏洞,其主要是对源程序进行词法分析和语法分析,产生与程序对应的语法数,将相应的节点挂在语法树上进行检测分析,其一般是通过使用预先定义好的漏洞数据库,在程序源代码中进行匹配查找操作。如果匹配成功,就给出相应的警告或错误信息,但可能会给出错误的警告,给检查结果带来负面影响。

4.2 检测工具

下面简要介绍一些数组越界检查相关的方法和工具。

(1)向程序和标准库的源代码中添加注解,用注解辅助静态分析工具Splint以快速有效地检测越界漏洞,这种方法具有简单、快速、高效、可扩充的特点。它利用程序中注解提供的信息,能够检测出程序中可能的数组越界漏洞,但其检测漏洞的能力受其分析方法的限制,Splint检测工具需要程序员在测试程序中添加辅助注解来完成检测,对于一个工程来说在源码中添加额外的注解是非常烦琐的事情,而且稍微不小心就会有遗漏的地方。这种方法如果对于大型的程序就显得不灵活,实用性不强,检测不全面等弱点。

(2)ARCHER是美国Stanford大学开发的一个分析大型软件中数组越界漏洞的静态分析工具。ARCHER首先利用一个转换器将程序转换成中间表示形式,然后,工具的遍历模块采用上下文敏感、路径敏感的数据流分析方法,产生中间表示的数据流状态信息,最后遍历模块调用求解器,记录和求解遍历过程中产生的状态信息。为了实现数组越界的检查,对于内存访问语句,ARCHER遍历模块首先调用求解器检查该语句是否可能越界,然后更新求解器的状态。这种方法的误报率低,但复杂度为指数级(N条分支语句可能产生条执行路径)。

(3)David Larochelle和David Evans提出了基于LCLint的缓冲越界检查方法。该方法在3个方面扩展了LCLint:提高越界检查的效率、增加在过程内检测时所使用的语义注释及提出一种对循环语句简单高效的分析方法,但其额外的人工语义注释仍然给使用者带来较大负担。

5 结语

数组越界故障已经对软件的安全构成了很大的威胁,国内外对数组越界的相关研究还有很多,T.M.Austin指出了如何有效地检查到程序中存在的指针和越界故障,R.Gupta给出了使用数据流分析优化数组越界检查,C.Cowan等人开发了一个缓冲区溢出故障测试工具等,由于程序设计语言的复杂性,现有的检测工具或方法很难达到非常理想的效果(既具有高效率,又能找到尽可能多的漏洞),因此针对数组越界检测还需要进一步的研究与完善。

参考文献

[1]宫战云.软件测试[M].北京:国防工业出版社,2006.

[2]叶焰锋,叶俊民,詹泽梅,雷志翔.数组越界的故障模型及其检测方法研究[J].微计算机信息(测控自动化),2007,23(11-1):145-147.

[3]赵鹏宇,李建茹,宫云战.Java语言中数组越界故障的静态测试研究[J].计算机工程与应用,2008,44(27):87-90.

[4]高传平,宫云战.数组越界的静态测试分析[J].计算机工程,32(3):70-72.

VB中控件数组及其应用 篇7

在开发Visual Basic 6.0的应用程序时, 往往要使用一些类型相同、功能相似的控件。如果对每一个控件都单独处理, 不免多做一些麻烦而重复的工作, 而且也会消耗更多的资源。这时可以将这种同一类型的控件定义成一个控件数组。例如, 可以将一组命令按钮定义成一个控件数组, 也可以将一组文本框定义成一个控件数组。控件数组为处理功能相近的控件提供了极大方便。

1 控件数组基本概念

控件数组是由一组相同类型的控件组成, 这些控件共用一个控件名称 (Name属性) , 具有相似的属性设置, 共享同样的事件过程。控件数组中各个控件相当于普通数组中的各个元素, 同一控件数组中各个控件的Index属性相当于普通数组中的下标。例如, 由一个包含3个单选按钮的控制数组Option1, 它的3个元素就是Option1 (0) 、Option1 (1) 、Option1 (2) 。

一个控件数组至少应有一个元素 (控件) , 元素数目可在系统资源和内存允许的范围内增加, 可用到的最大索引值为32 767。同一控件数组中的不同控件可以有自己的属性值设置。在建立控件数组时, 每个元素 (控件) 被自动赋予一个唯一的索引号 (Index属性, 相当于普通数组的下标) , 索引号从0开始, 通过索引号引用控件数组中的某个控件, 即控件数组名 (索引号) 。

使用控件数组的好处是: (1) 便于控件管理, 它们共用一个名称, 通过Index进行区分; (2) 运行期间可以添加控件, 使用控件数组添加控件所消耗的资源比直接向窗体添加多个相同类型的控件消耗的资源要少; (3) 控件数组中的不同控件共享相同的事件过程, 因为控件数组的事件可以共享, 如果要使多个同类型控件在一个事件中执行相同或类似的代码, 使用控件数组比单独创建多个控件更方便, 代码也更容易维护。

2 控件数组建立方法

控件数组中的每一个元素都是控件, 其定义方式与普通数组不同。可以在设计阶段创建控件数组, 也可以在运行期间动态创建控件数组。

2.1 在设计阶段创建控件数组

设计阶段可以通过以下两种方法创建控件数组:

(1) 复制现有控件, 将其粘贴到所在容器中。绘制或选择要作为控件数组的第1个控件, 执行“编辑|复制”命令, 选中容器, 再执行“编辑|粘贴”命令, 系统会给出一个对话框, 询问是否建立控件数组, 单击“是”按钮即可。绘制或选择的第1个控件的Index属性值为0, 新粘贴控件的Index属性值为1, 每个新数组元素的Index属性值与其添加到控件数组中的次序相同。用这种方法添加控件时, 大多数可视属性, 如高度、宽度和颜色等, 将从数组中的第1个控件复制到新控件中。

(2) 将窗体上已有类型相同的多个控件的Name属性设置为同一值。先选定一个控件作为数组的第1个元素, 并将其Name属性值设置成数组名 (或使用其原有的Name属性值) , 将其它同类型控件的Name属性值改成同一个名称。这时, 系统同样会询问是否建立控件数组, 选择“是”按钮将控件添加到控件数组中, 这样操作后, 控件数组中控件的Index属性值分别变成了0、1、2、…… (即控件数组元素的索引) 。用这种方法建立的控件数组元素仅仅具有相同的Name属性和控件类型, 其它属性与最初绘制控件时的值相同。

2.2 在运行期间创建控件数组

在运行时, 使用Load语句向现有控件数组中添加控件。通常设计时在窗体上创建一个Index属性为0的控件, 然后在运行时使用Load语句添加控件, Load语句格式如下:

Load控件数组名 (索引号)

例如, 假设已经在设计时建立了一个控件Option1 (0) , 在运行时可以用以下语句向控件数组中添加一个新的控件:

Load Option1 (1)

使用Load语句添加的控件数组元素, 其大多数属性值将由数组中具有最小下标的现有元素复制, 而且新添加的控件是不可见的, 必须编写代码将其Visible属性值设置为True, 通常还要调整其位置 (比如Top或Left属性的值) , 才可以在界面上显示出来。例如:

Option1 (1) .Visible=True

Option1 (1) .Top=500

Option1 (1) .Left=1000'Top或Left的值视情况而定

使用Unload语句可以删除在运行期间创建的控件数组元素, Unload语句的格式如下:

Unload控件数组名 (索引号)

例如, 要删除上面创建的Option1 (1) 控件, 可以使用语句:

Unload Option1 (1)

3 控件数组应用示例

通过以下几个示例, 介绍控件数组的应用。

例1:设置文字颜色。

在窗体上建立一个标签Label1和一个框架Frame1, 在框架Frame1中创建一个控件数组Option1 (0) ~Option1 (3) , 单击这4个单选按钮, 分别将标签Label1的文字颜色设置为“黑色”、“红色”、“蓝色”和“黄色”。

设计界面、运行界面分别如图1和如图2所示。

为实现题目功能的要求, 编写控件数组Option1的Click事件过程如下:

控件数组Option1包含4个单选按钮, 无论单击哪个单选按钮, 都将执行相同的事件过程。系统会将被单击的单选按钮的Index属性值传递给事件过程, 由事件过程根据不同的Index值执行不同的操作。

例2:使用控件数组创建电影胶片特效。

向窗体上添加一个Image控件Image1, 通过设置其Picture属性添加一幅图片, 调整好大小, 然后用复制、粘贴的方法创建一个控件数组Image1 (0) ~Image1 (3) 。添加一个Timer控件Timer1, 设置Timer1控件的Interval属性值为20。

设计界面、运行界面分别如图3和图4所示。

在窗体的Load事件过程中编写代码, 调整窗体的宽度和各图像框的初始位置, 使得运行初始时, 窗体宽度正好可以显示3幅水平并排的图片Image1 (0) -Image1 (2) , 使第4幅图片Image1 (3) 在窗体左侧的不可见区域, 其右侧与窗体左边界对齐。Load事件过程的代码如下:

通过定时器控制每隔20ms将各图像框向右移动20缇, 如果图像框的左侧已经大于窗体的内部宽度, 说明图像框已经移出窗体, 则将相应的图像框移回窗体左侧不可见区域, 使其右侧与窗体左边对齐。Timer1控件的Timer事件过程如下:

运行时, 各图片沿窗体左侧逐渐向右侧移动, 产生电影胶片的效果。

例3:控件的添加和删除。

在窗体上绘制一个复选框Check1, 将其Index属性值设置为0, 使其成为控件数组的第一个元素。再在窗体上添加3个命令按钮Command1、Command2和Command3, 分别实现添加控件、删除控件和关闭窗体的功能。

设n表示添加的控件数 (本例假设其最大值为3) , n应作为窗体级变量进行声明:

4 结语

控件数组提供了一种方便的控件组合方法, 这种组合控件的方法, 既可方便地对同类型控件进行管理, 又可使这些控件共享事件过程代码, 不仅代码编写高效, 而且代码也容易维护。

摘要:控件数组是一种方便组合同类型控件的方法, 这些控件具有共同的名称, 共享同一事件过程代码, 根据不同的Index属性值执行不同的处理程序段。在介绍控件数组基本概念及其建立方法的基础上, 着重研究了VB中控件数组的应用。控件数组不仅利于管理同类型控件, 而且可以优化程序代码。

关键词:Visual Basic,控件数组,Index属性值,共享事件代码

参考文献

[1]求是科技.Visual Basic 6.0程序设计与开发技术大全[M].北京:人民邮电出版社, 2004.

[2]王建忠.Visual Basic程序设计[M].北京:科学出版社, 2013.

基于数组的两端选择排序算法 篇8

排序是数据处理中经常遇到的一种重要运算,而要在多种排序算法中选择合适的排序算法,就要分析算法的时间复杂度。由于排序的过程就是对记录值进行比较和记录移动的过程,因此在分析时间复杂度时通常只考虑记录值的比较次数和记录的移动次数,即以记录值的比较和记录移动为标准进行操作。一种排序方法的排序过程在最坏情况下所进行的比较和移动次数越少,则说明该排序方法的时间复杂度就越好,否则就越坏。除了分析排序算法的时间复杂度外,有时还必须分析算法的空间复杂度以及结构的复杂性等。因为这些也是衡量一个排序算法好坏的重要指标。[2]

2 选择排序法

选择排序是一种最常用的、简单的、最为广大用户熟悉的一种排序方法。[3]在数组序列a[n]中,将第1个数与第2个数到第n个数依次比较,找出最大值,记下其位置pm,比较完第一轮后,pm存放了1~n个数中最大值所在的位置。交换a[0]与a[pm]的值。这时a[0]为原a[0]到a[n-1]的n个数据元素中的最大值。然后,将第2个数与第3个数到第n个数依次比较,找出第2个数到第n个数中的最大值,记其位置pm,交换a[1]与a[pm]的值。依此类推,共重复了n-1轮,即完成了数据序列从大到小的排序过程。在语言中,该排序算法也为双重循环问题,其中外循环控制选择的轮数,内循环控制每轮内的比较次数。从运算时间上看,在第一趟排序中关键字的比较次数为n-1,第二趟排序中关键字的比较次数为n-2。在第i趟排序中,关键字的比较次数为n-i,所以总比较次数为:

3 两端选择排序算法分析与实现

选择排序是从首端进行排序,而两端选择排序是首端和尾端同时进行,即找出最大数的同时,也找出最小的那个数,然后把最大的那个数与第一个数交换,把最小的那个数与最后一个数交换,按此方法再找出第二大和第二小的元素,分别与第二位和倒数第二位的元素进行交换,依此类推完成整个排序过程。

因为两组数在一个循环内同时移动,必须考虑定位的最小数有可能因为最大数的移动,而变换位置,所以在两端排序过程中,要充分考虑这种特殊情况。

已知无序数列有N个数,若是采用选择排序,外循环的次数应该为N-1,若是采用两端同时排序,循环次数可以减半。有读者会问外循环的次数是否要考虑N的奇偶性呢,答案是可以不用考虑。若是奇数,外循环的次数应为N/2。若是按选择排序的思维方式,就少循环了一次。但两端同时排序时,留下来的那个数正好是最中间的一位数,所以不需要单独考虑其排序了。

下面给出一组数,其长度为奇数,且把最小的数放在首位,把最大的数放在末位(两种特例都考虑进去),要求从大到小进行排序,其他数据杂乱无章排列。通过C语言实现的程序如下:

4 两端选择排序算法优势分析

两种算法都是通过语言的双重循环来实现,但两端选择排序外循环和内循环都只进行一半,虽然内循环进行了两次比较,总比较次数为:。总体比较次数只有单纯选择排序的一半,故其时间复杂度相对要好得多。至于空间复杂度,由于两种算法除了程序指令、原始数据等所占存储空间外,前者只多出一个定位变量空间,因此,对相同问题规模n,它们的空间复杂度基本相同。算法稳定性方面,由于两种算法均是数据序列中相邻数据的两两比较算法,故均是非常稳定的算法。本文探讨两端同时排序对优化其他排序算法可提供借鉴。

4/)1()(2/1-(28)-(28)nnin n i

参考文献

[1]安朝辉,钱剑敏.一种新的排序算法——端点排序算法[J].现代电子技术,,2011,34(24):80-81.

[2]杨勇虎,刘振宇,宋桂娟.数据结构(C语言)[M].大连:东软电子出版社,2010.8:340.

关系数组 篇9

数据立方体是一种多维数据模型, 支持感念分层, 可以通过上钻与下卷在不同的概念层次上查看数据, 主要用于O- LAP, 也可以用于高维数据挖掘。由于OLAP需要在大规模的数据仓库上执行复杂的操作, 为了缩短访问时间, 高效地分析数据, 一般要对数据立方体进行预计算, 也称为立方体物化。立方体物化是指在不同维组合上对基本单元进行聚集, 每次聚集会产生一个m维聚集单元, 所有在相同维组合上聚集产生的聚集单元组成一个m维方体。对于一个n维的数据立方体, 可以产生2n种不同的m维方体。立方体物化可以分为完全立方体、冰山立方体、 闭立方体和立方体外壳。其中完全立方体是指计算出数据立方体的所有不同的聚集单元, 可以完整反映数据立方体所蕴含的信息, 降低查询响应时间、提高联机分析处理的性能。对完全立方体的计算一般采用多路数组聚集技术, 下面介绍多路数组聚集技术。

多路数组聚集技术基本思想可以分为两部分: 首先将完整的数据立方体切分成多个块, 块大小要求可以完全放进内存进行计算, n维数据立方体的分块仍是n维数据立方体; 然后每次将一块子方体读入内存, 计算这个这个子方体的所有聚集单元; 根据读入数据块的顺序, 某些维度组合的聚集结果会被计算出来, 在所有块都被计算之后原始完全立方体的所有聚集单元就都已经计算完成。这种技术只对原始数据立方体进行一遍扫描, 就可以计算出所有的聚集单元, 可以有效地计算完全立方体, 但是随着数据量的增加和数据维度的增加, 在单机上运行该算法需要花费的时间和需要占用的内存空间都远远超出了用户的忍受范围。为了解决这些问题,有必要对算法进行并行化改造, 使其适应分布式计算环境, 下面介绍一种在大数据时代普遍使用的MapReduce分布式计算框架。

2 MapReduce 技术

MapReduce是一种编程模型 , 主要用于大规模数据集的并行计算, Map和Reduce分别指代映射和规约, 这两个函数需要用户自己定义, 并且以参数的形式传递给计算框架, 而对任务的分派和调度则由该计算框架完全自动处理或者在用户干预下自动处理, 使得用户可以简单地开发并行应用。简单地说, Map函数的作用是对用户输入的原始键值对进行初步处理, 产生中间键值对, 这些键值对会暂时储存在某些磁盘分区里; Reduce函数的作用是对调度程序指定的磁盘分区中的Map函数产生的中间键值对进行规约计算, 并产生最终结果。以词频统计为例, 用户要求是输入一组文章, 然后输出所有文章中出现的单词, 以及出现的频数; 使用MapRe- duce框架解决这个问题时 , 用户首先需要定义Map函数 : 接收文章, 并以键值对的方式返回文章中出现的单词和出现频数; 然后定义Reduce函数: 接收键值对, 进行合并之后返回最终的统计结果。定义了函数之后, 用户就可以将所有文章交给MapReduce框架处理, 计算框架会自动将输入文章划分为许多组, 然后交给多个计算节点并执行Map函数, 然后再将Map函数产生的中间键值对按照键的值进行聚集, 然后将每一组键值相同的键值对交个一个计算节点执行Reduce函数, 由Reduce函数输出最终结果。

用户所能做的事情除了定义Map和Reduce函数之外, 还可以指定Map函数与Reduce函数的个数, 以及通过以下接口干预应用的执行:

an input reader: 这个接口可以用来定义如何划分原始数据, 产生原始键值对。

aoutput writer: 这个接口可以定义如何将结果写入文件系统。

a combiner function: 这个函数相当于在本地对Map结果进行预处理, 目的是提高Reduce函数的效率。

a partition function: 这个函数用于指定将Map函数产生的中间结果存入哪个磁盘分区。

3 将 MapReduce 技术应用到多路数组计算

MapReduce就是通过控制一组计算机 , 对原始任务切分 , 并行计算, 然后并行规约得到最终结果的计算过程。可以看出, 这与多路数组聚集技术所使用的思想是相同的, 因此可以将MapReduce技术应用到多路数组计算中, 需要做的是设计Map函数和Reduce函数应该做什么, 以及对一些接口函数进行定义。

由于数据 立方体的 基础数据 结构是多 维数组 , 因此MapReduce计算框架在接收了完整的数据立方体之后 , 会按照系统默认值或者用户指定的值对数据立方体进行块划分, 然后将每个子方体储存在计算节点上等待计算。由于每个子方体也会使用多维数组储存, 在每个计算节点上数组每一维的起始下标都为零, 但是许多子方体在原始数据立方体的数组中的起始下标并不为零, 这就会导致Map函数产生的中间键值对的键的值与这个中间键值对应该有的键的值不符, 导致Reduce函数无法正常规约。因此需要一个长度与数据立方体维数相同的一维数组来记录一个子方体的每一维相对原始数据立方体的偏移量, 可以称之为偏移数组。用户可以通过对input reader接口进行定义, 将每个子方体对应的偏移数组和子方体组合成原始键值对传递给Map函数; 在Map函数产生了中间键值对之后, 可以在本地使用combiner函数使用偏移数组的值对中间键值对的键进行修正, 这样就可以保证re- duce函数产生正确的结果。对于Reduce函数的设计则比较简单, 与上文提到的词频统计应用相同。

下面以三维数据立方体为例说明上述过程: 如图1所示, 维A、B、C的基数都是4, 使用数组cube [4] [4] [4] 存储。定义每个子立方体的每一维的基数为2, 每个子方体用sub- cubei表示, 可以将原始数据立方体分为8块, 例如第一块由图1中编号为1,2,5,6,17,18,21,22的基本单元组成。定义每i个子方体的偏移数组为offseti, 通过input reader函数, 可以得到如下键值对:

定义通过Map函数处理之后, 中间键值对的键是聚集单元的描述, 形式是 (a,b,c), 其中a,b,c可以是数字或者“*”, 数字表示在相应维的下标, 对于〖subcube〗_1, a=0表示在A维的下标为0, “*”表示聚集维; 键值对的值是相应聚集单元的聚集值。这样对每个子方体进行Map处理后都会产生如下的键值对:

< (*,*,*) ,value>,< (*,*,0) ,value>,< (*,*,1) ,value>,< (*,0,*) ,value>,

……,< (0,0,*) ,value>,< (1,0,*) ,value>,……,< (0,0,0) ,value>.

以subcube2的< (1,0,*) ,value>为例, 键值 (1,0,*) 中a= 1, 代表在A维的下标为1, 但是实际下标应该是3。为解决这个问题使用combiner函数, 并利用原始键值对的键的值对这些中间键值对进行修正, 方法就是在中间键值对的键的值, 除了聚集维之外的每一维在原始值之上加上相应的偏移量。修正之 后subcube2的< (1,0,*) ,value>更改为subcube2的< (3,0,*) ,value>, 键表示的就与实际值相符了。

最后, 通过Reduce函数对中间键值对的键相同的那些键值对的value简单相加就可以得出每个聚集单元的最终值。

为了实现这个算法, 可以使用开源软件系统Hadoop来搭建分布式计算环境。Hadoop是实现了MapReduce技术的分布式软件框架, 通过这套软件系统, 可以将上述算法实现, 同时Hadoop开源社区还提供了例如Hive (数据仓库工具)、Pig (大数据分析平台) 等工具, 可以方便地对完全立方体计算的到的数据进行分析利用。

4 结语

上一篇:生物固体下一篇:高职院校绩效