在本次的工程实践中,我的题目是《基于opengl的车载虚拟仪表软件开发》,是一项校企合作的项目,在项目中以前做过的学长也向我们分享了以往做过的相关的项目的源代码,代码主要是由c语言写出,通过调用opengl的api接口规范来实现的图像旋转,拉伸,平移等操作,源代码如下 :
#include "config.h"
#include "main.h"
#include "./lib/lib_opengl.h"
#include "./font.h"
#include "./resource.h"
#ifdef DEBUG_LOCATION
#undef DEBUG_LOCATION
#endif
#define DEBUG_LOCATION "MAIN"
pthread_t taskTimer;
extern void *threadTimer(void);
int main(int argc, char* argv[])
{
float rotate = 0;
FT_ULong strW[10] = {0};
u32 delay = 0;
InitGL();
initFont();
initResource();
loadImage(IDIMG_BG);
enableGlStatus();
//多线程
//pthread_create(&taskTimer, NULL, threadTimer, NULL)
while(1)
{
delay++;
if(delay > 1000)
{
if(rotate < 9.0)
rotate += 1.0;
else
rotate = 0;
delay = 0;
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(0.0f, 0.0f, 0.0f, 255.0f);
#if 0
glPushMatrix();
//themeHandle();
glTranslatef(960, 360, 0);
glDisable(GL_TEXTURE_2D);
glRotatef(45, 1, 0, 0);
glRotatef( rotate, 0, 1, 0);
//glVertexPointer(3, GL_FLOAT, 0, vertices);
glColor4f(255, 0, 0, 255);
static GLfloat front[] = { SIZE_OF_BOX, -SIZE_OF_BOX, SIZE_OF_BOX,SIZE_OF_BOX, SIZE_OF_BOX, SIZE_OF_BOX,-SIZE_OF_BOX, -SIZE_OF_BOX, SIZE_OF_BOX,-SIZE_OF_BOX, SIZE_OF_BOX, SIZE_OF_BOX,};
glVertexPointer(3, GL_FLOAT, 0, front);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glColor4f(0, 255, 0, 255);
static GLfloat back[] = { -SIZE_OF_BOX, -SIZE_OF_BOX, -SIZE_OF_BOX,-SIZE_OF_BOX, SIZE_OF_BOX, -SIZE_OF_BOX,SIZE_OF_BOX, -SIZE_OF_BOX, -SIZE_OF_BOX,SIZE_OF_BOX, SIZE_OF_BOX, -SIZE_OF_BOX,};
glVertexPointer(3, GL_FLOAT, 0, back);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glColor4f(0, 0, 255, 255);
static GLfloat top[] = {SIZE_OF_BOX, SIZE_OF_BOX, SIZE_OF_BOX, SIZE_OF_BOX, SIZE_OF_BOX, -SIZE_OF_BOX,-SIZE_OF_BOX, SIZE_OF_BOX, SIZE_OF_BOX, -SIZE_OF_BOX, SIZE_OF_BOX, -SIZE_OF_BOX,};
glVertexPointer(3, GL_FLOAT, 0, top);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glColor4f(255, 255, 0, 255);
static GLfloat bottom[] = { -SIZE_OF_BOX, -SIZE_OF_BOX, SIZE_OF_BOX,-SIZE_OF_BOX, -SIZE_OF_BOX, -SIZE_OF_BOX,SIZE_OF_BOX, -SIZE_OF_BOX, SIZE_OF_BOX,SIZE_OF_BOX, -SIZE_OF_BOX, -SIZE_OF_BOX, };
glVertexPointer(3, GL_FLOAT, 0, bottom);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glColor4f(0, 255, 255, 255);
static GLfloat left[] = {-SIZE_OF_BOX, -SIZE_OF_BOX, SIZE_OF_BOX, -SIZE_OF_BOX, SIZE_OF_BOX, SIZE_OF_BOX, -SIZE_OF_BOX, -SIZE_OF_BOX, -SIZE_OF_BOX,-SIZE_OF_BOX, SIZE_OF_BOX, -SIZE_OF_BOX,};
glVertexPointer(3, GL_FLOAT, 0, left);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glColor4f(255, 0, 255, 255);
static GLfloat right[] = {SIZE_OF_BOX, -SIZE_OF_BOX, -SIZE_OF_BOX, SIZE_OF_BOX, SIZE_OF_BOX, -SIZE_OF_BOX,SIZE_OF_BOX, -SIZE_OF_BOX, SIZE_OF_BOX,SIZE_OF_BOX, SIZE_OF_BOX, SIZE_OF_BOX,};
glVertexPointer(3, GL_FLOAT, 0, right);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glPopMatrix();
#endif
glPushMatrix();
glEnable(GL_TEXTURE_2D);
drawImage(IDIMG_BG, 0, 0);
setFontColor(255, 255, 255, 255);
memset(strW, 0, sizeof(strW));
sprintl(strW, "%.0f", rotate);
drawFont(FONT_GEELY, 100, 100, 32, strW);
memset(strW, 0, sizeof(strW));
sprintl(strW, "%.0f", 9.0 - rotate);
drawFont(FONT_GEELY, 100, 150, 32, strW);
setFontColor(255, 255, 255, 255);
glPopMatrix();
swapBuffers();
}
}
return 0;
}
源代码目录结构:
目录结构中包括了主函数的.c和.h文件,此外还写了一些函数和方法,和.h文件作为头文件,有一个lib文件夹用于放一些opengl所需要的库,这些都构成了这个程序能够正确运行的基础。
文件名/类名/函数名/变量名等命名:
文件名主要突出了各个部件的功能,是按实际的效果命名的,比如像上图中的Layout_AnimyPlane.c文件主要就是负责界面的布局显示的效果,readme主要是这套代码的介绍;
类名中主要是调用了opengl库中的一些常用的类如下:
- BufferUtils
- TextureUtils
- Shader
- GLSurfaceView
- Renderer
- Model
- GLBitmap
- GLUnfoldBitmap
函数名中 glClear主要用于清除指定的缓存数据并重设为当前的清除值。mask是一个可以通过逻辑或操作来指定多个数值的参数;
glClearClolor用于设置当前使用的清除颜色值,用于RGBA模式下对颜色缓存的清除工作。这里的red,green,blue,alpha都会被截断到【0,1】的范围内。默认的清除颜色是(0,0,0,0)表示黑色。
glPushMatrix();glPushMatrix、glPopMatrix操作其实就相当于栈里的入栈和出栈。
接口定义规范和单元测试组织形式等方面的做法和特点:
OpenGL是Open Graphics Library(开放性图形库)的缩写,是一套三维图形处理库,也是该领域的工业标准。计算机三维图形是指将用数据描述的三维空间通过计算转换成二维图像并显示或打印出来的技术。
OpenGL就是支持这种转换的程序库,它源于SGI公司为其图形工作站开发的IRIS GL,在跨平台移植过程中发展成为OpenGL。SGI在1992年7月发布1.0版,后成为工业标准,由成立于1992年的独立财团OpenGL Architecture Review Board (ARB)控制。SGI等ARB成员以投票方式产生标准,并制成规范文档(Specification)公布,各软硬件厂商据此开发自己系统上的实现。只有通过了ARB规范全部测试的实现才能称为OpenGL。1995年12月ARB批准了1.1版本,最新版规范是1999.5通过的1.2.1。
OpenGL被设计成独立于硬件,独立于窗口系统的,在运行各种操作系统的各种计算机上都可用,并能在网络环境下以客户/服务器模式工作,是专业图形处理、科学计算等高端应用领域的标准图形库。它低端应用上的主要竞争对手是MS-Direct3D,该图形库是以COM接口形式提供的,所以极为较复杂,稳定性差,另外微软公司拥有该库版权,目前只在Windows平台上可用。D3D的优势在速度上,但现在低价显卡都能提供很好的OpenGL硬件加速,所以做3D使用Direct3D已没有特别的必要,在专业图形处理特别是高端应用方面目前还没有出现以Direct3D技术为基础的例子,而游戏等低端应用也有转向OpenGL的趋势。
微软在Windows NT对OpenGL的支持始于3.51,在Windows9x中的支持始于Win95 OEM Service Release 2。Windows下常用的OpenGL库有两种,MS实现的和SGI实现的,MS-OpenGL调用会自动检测是否存在显示卡制造商提供的ICD(Installable Client DeviceDriver)驱动程序,有则调用ICD中的例程,否则才用CPU进行计算,所以能利用显示卡的OpenGL加速能力。对开发者来说使用方法并没有区别,只是有ICD驱动时更快些。SGI的版本是纯软件实现不能利用硬件加速并且SGI已经在1999年宣布停止支持,但这套库便于调试程序,仍有不少开发者使用。
SGI曾经宣布研发OpenGL++,该图形库最大的特点是面象对象,提供了树形场景支持,大大减省了使用OpenGL处理复杂场景的工作量。后来(1999)SGI宣布与M$合作开发Ferihant,即Windows的下一代图形处理体系,包括DirectX与OpenGL的低级图形处理接口和以场景图支持为特点的高级接口,并且就此停止对其在Windows下的OpenGL实现的支持以示决心。此举世瞩目,大家都以为Windows图形处理快要过上幸福生活了,然而,不久,SGI宣布中止合作,并撤回派出的科学家,Ferihant基本上夭折。SGI 称终止合作的原因是M$不肯积极合作,光想把SGI 的技术合并进DirectX,真正内幕不详。不过以SGI在图形处理界的老大地位来说,还是有几分可信度的,因为M$初支持OpenGL就不积极。
OpenGL的特点
1.硬件无关的软件接口
可以在不同的平台如Windows 95、Windows NT、Unix、Linux、MacOS、OS/2之间进行移植。因此,支持OpenGL的软件具有很好的移植性,可以获得非常广泛的应用。
2.可以在客户机/服务器系统中工作,即具有网络功能。
这一点对于制作大型3D图形、动画非常有用。例如,《玩具总动员》、《泰坦尼克号》等电影的电脑特技画面就是通过应用OpenGL的网络功能,使120多台图形工作站共同工作来完成的。
由于OpenGL是与硬件无关的3D图形接口,在 Windows、Unix/X-Windows、 MacOS、OS/2等不同版本的窗口相关部分(系统相关)略有差异。由于OpenGL是3D图形的底层图形库,没有提供几何实体图元,不能直接用以描述场景。但是,通过一些转换程序,可以很方便地将AutoCAD、3DS等3D图形设计软件制作的DFX和3DS模型文件转换成OpenGL的顶点数组。
另外,在OpenGL的基础上还有Open Inventor、Cosmo3D、Optimizer等多种高级图形库,适应不同应用。其中,Open Inventor应用最为广泛。该软件是基于OpenGL面向对象的工具包,提供创建交互式3D图形应用程序的对象和方法,提供了预定义的对象和用于交互的事件处理模块,创建和编辑3D场景的高级应用程序单元,有打印对象和用其它图形格式交换数据的能力。
哪些做法符合代码规范和风格一般要求:
- 类的属性和方法 必须添加访问修饰符(private 、protected 以及 public ),abstract 以及 final 必须声明在访问修饰符之前,而 static必须声明在访问修饰符之后。
- 控制结构的关键字后 必须要有一个空格符,而调用方法或函数时则一定不能有。
- 控制结构的开始花括号 ({ )必须写在声明的同一行,而结束花括号 (} )必须写在主体后自成一行。
- 控制结构的开始左括号后和结束右括号前,都 一定不能有空格符。
- 行的长度 一定不能 有硬性的约束。
- 软性的长度约束 一定要限制在 120 个字符以内,若超过此长度,带代码规范检查的编辑器 一定要发出警告,不过 一定不可 发出错误提示。
- 每行不应该多于 80 个字符,大于 80 字符的行 应该折成多行。
- 非空行后 一定不能 有多余的空格符。
- 空行可以使得阅读代码更加方便以及有助于代码的分块。
- 每行一定不能 存在多于一条语句。
- 代码必须使用 4 个空格符的缩进, 一定不能 用 tab 键。 备注 : 使用空格而不是 tab 键缩进的好处在于,避免在比较代码差异、打补丁、重阅代码以及注释时产生混淆。并且,使用空格缩进,让对齐变得更方便。
列举哪些做法有悖于“代码的简洁、清晰、无歧义”的基本原则,及如何进一步优化改进并总结同类编程语言或项目在代码规范和风格的一般要求:
1 文件结构
1.1 头文件和定义文件的名称是否合理?
1.2 头文件和定义文件的目录结构是否合理?
1.3 版权和版本声明是否完整?
1.4 头文件是否使用了 ifndef/define/endif 预处理块?
1.5 头文件中是否只存放“声明”而不存放“定义”?
2 程序的版式
2.1 空行是否得体?
2.2 代码行内的空格是否得体?
2.3 长行拆分是否得体?
2.4 “{” 和 “}” 是否各占一行并且对齐于同一列?
2.5 一行代码是否只做一件事?如只定义一个变量,只写一条语句。
2.6 If、for、while、do 等语句自占一行,不论执行语句多少都要加“{}”。
2.7 在定义变量(或参数)时,是否将修饰符 * 和 & 紧靠变量名?
2.8 注释是否清晰并且必要?
2.9 注释是否有错误或者可能导致误解?
2.10 类结构的 public, protected, private 顺序是否在所有的程序中保持一致?
3 命名规则
3.1 命名规则是否与所采用的操作系统或开发工具的风格保持一致?
3.2 标识符是否直观且可以拼读?
3.3 标识符的长度应当符合“min-length && max-information”原则?
3.4 程序中是否出现相同的局部变量和全部变量?
3.5 类名、函数名、变量和参数、常量的书写格式是否遵循一定的规则?
3.6 静态变量、全局变量、类的成员变量是否加前缀?
4 表达式与基本语句
4.1 如果代码行中的运算符比较多,是否已经用括号清楚地确定表达式的操作顺序?
4.2 是否编写太复杂或者多用途的复合表达式?
4.3 是否将复合表达式与“真正的数学表达式”混淆?
4.4 是否用隐含错误的方式写 if 语句? 例如
(1) 将布尔变量直接与 TRUE、FALSE 或者 1、0 进行比较。
(2) 将浮点变量用“==”或“!=”与任何数字比较。
(3) 将指针变量用“==”或“!=”与 NULL 比较。
4.5 如果循环体内存在逻辑判断,并且循环次数很大,是否已经将逻辑判断移到循环体的外面?
4.6 Case 语句的结尾是否忘了加break?
4.7 是否忘记写 switch 的 default 分支?
4.8 使用 goto 语句时是否留下隐患? 例如跳过了某些对象的构造、变量的初始化、重要的计算等。
5 常量
5.1 是否使用含义直观的常量来表示那些将在程序中多次出现的数字或字符串?
5.2 在 C++ 程序中,是否用const 常量取代宏常量?
5.3 如果某一常量与其它常量密切相关,是否在定义中包含了这种关系?
5.4 是否误解了类中的const 数据成员?因为const 数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的。
6 函数设计
6.1 参数的书写是否完整?不要贪图省事只写参数的类型而省略参数名字。
6.2 参数命名、顺序是否合理?
6.3 参数的个数是否太多?
6.4 是否使用类型和数目不确定的参数?
6.5 是否省略了函数返回值的类型?
6.6 函数名字与返回值类型在语义上是否冲突?
6.7 是否将正常值和错误标志混在一起返回?正常值应当用输出参数获得,而错误标志用 return 语句返回。
6.8 在函数体的“入口处”,是否用 assert 对参数的有效性进行检查?
6.9 使用滥用了 assert? 例如混淆非法情况与错误情况,后者是必然存在的并且是一定要作出处理的。
6.10 return 语句是否返回指向“栈内存”的“指针”或者“引 用”?
6.11 是否使用const 提高函数的健壮性?const 可以强制保护函数的参数、返回值,甚至函数的定义体。“Use const whenever you need”
7 内存管理
7.1 用 malloc 或 new 申请内存之后,是否立即检查指针值是否为 NULL?(防止使用指针值为 NULL 的内存)
7.2 是否忘记为数组和动态内存赋初值?(防止将未被初始化的内存作为右值使用)
7.3 数组或指针的下标是否越界?
7.4 动态内存的申请与释放是否配对?(防止内存泄漏)
7.5 是否有效地处理了“内存耗尽”问题?
7.6 是否修改“指向常量的指针”的内容?
7.7 是否出现野指针?例如
I 指针变量没有被初始化。
II 用 free 或delete 释放了内存之后,忘记将指针设置为 NULL。
7.8 是否将 malloc/free 和 new/delete 混淆使用?
7.9 malloc 语句是否正确无误?例如字节数是否正确?类型转换是否正确?
7.10 在创建与释放动态对象数组时,new/delete 的语句是否正确无误?
8 C++ 函数的高级特性
8.1 重载函数是否有二义性?
8.2 是否混淆了成员函数的重载、覆盖与隐藏?
8.3 运算符的重载是否符合制定的编程规范?
8.4 是否滥用内联函数?例如函数体内的代码比较长,函数体内出现循环。
8.5 是否用内联函数取代了宏代码?
9 类的构造函数、析构函数和赋值函数
9.1 是否违背编程规范而让 C++ 编译器自动为类产生四个缺省的函数:
(1)缺省的无参数构造函数;
(2)缺省的拷贝构造函数;
(3)缺省的析构函数;
(4)缺省的赋值函数。
9.2 构造函数中是否遗漏了某些初始化工作?
9.3 是否正确地使用构造函数的初始化表?
9.4 析构函数中是否遗漏了某些清除工作?
9.5 是否错写、错用了拷贝构造函数和赋值函数?
9.6 赋值函数一般分四个步骤:
(1)检查自赋值;
(2)释放原有内存资源;
(3)分配新的内存资源,并复制内容;
(4)返回 *this。是否遗漏了重要步骤?
9.7 是否正确地编写了派生类的构造函数、析构函数、赋值函数?注意事项:
I 派生类不可能继承基类的构造函数、析构函数、赋值函数。
II 派生类的构造函数应在其初始化表里调用基类的构造函数。
III 基类与派生类的析构函数应该为虚(即加 virtual 关键字)。
IV 在编写派生类的赋值函数时,注意不要忘记对基类的数据成员重新赋值。
10 类的高级特性
是否违背了继承和组合的规则?
I 若在逻辑上 B 是 A 的“一种”,并且 A 的所有功能和属性对B 而言都有意义,则允许B 继承A 的功能和属性。
II 若在逻辑上 A 是 B 的“一部分”(a part of),则不允许 B 从A 派生,而是要用A 和其它东西组合出 B。
11 其它常见问题
11.1 数据类型问题:
(1)变量的数据类型有错误吗?
(2)存在不同数据类型的赋值吗?
(3)存在不同数据类型的比较吗?
11.2 变量值问题:
(1)变量的初始化或缺省值有错误吗?
(2)变量发生上溢或下溢吗?
(3)变量的精度够吗?
11.3 逻辑判断问题:
(1)由于精度原因导致比较无效吗?
(2)表达式中的优先级有误吗?
(3)逻辑判断结果颠倒吗?
11.4 循环问题:
(1)循环终止条件不正确吗?
(2)无法正常终止(死循环)吗?
(3)错误地修改循环变量吗?
(4)存在误差累积吗?
11.5 错误处理问题:
(1)忘记进行错误处理吗?
(2)错误处理程序块一直没有机会被运行?
(3)错误处理程序块本身就有毛病吗?如报告的错误与实际错误不一致,处理方式不正确等等。
(4)错误处理程序块是“马后炮”吗?如在被它被调用之前软件已经出错。
11.6 文件 I/O 问题:
(1)对不存在的或者错误的文件进行操作吗?
(2)文件以不正确的方式打开吗?
(3)文件结束判断不正确吗?
(4)没有正确地关闭文件吗?