#
多数情况下,在游戏开发过程中,我们需要经常用变换来设置角色的变换。下面以平移、缩放、旋转来说明一点在矩阵变换中需要注意的地方。 假设有如下数据: D3DXMatrix rotateMat; D3DXMatrix scaleMat; D3DXMatrix translateMat; ...... // 这里的 ...... 表示,经过了一系列的变换,在接下来的代码中,rotateMat、scalMat、translateMat已经是经过变换的了。 D3DXMatrix worldMat;//该矩阵用于保存上面三个合成的最终变换信息 D3DXMatrixIdentity(&worldMat); D3DXMatrixMultiply(&worldMat, &rotateMat, &worldMat);//注意格式,需要如此写。 D3DXMatrixMultiply(&worldMat, &scaleMat, &worldMat); //注意格式,需要如此写。 D3DXMatrixMultiply(&worldMat, &translateMat, &world);//注意格式,需要如此写。
1) TdxBarApplicationMenu 设置 ------------------------------------------ 只需要设置:BarManager属性即可。
2) TdxRibbon 设置 ------------------------- a) ApplicationButton -> Menu 设置为上面 1) 的 AppMenu b) ApplicationButton -> Visible := false; // 因为我不需要左上角的那个圆圆的菜单 c) BarManager 需要设置 c) PopupMenuItems 属性下的所有项都设置为false // 因为我不需要左上角的圆圆的菜单右边有那些小小的快捷键 d) QuickAccessToolbar -> ToolBar 需要设置 e) ShowTabHeaders 需要设置 f) SupportNonClientDrawing 属性设置为 true g) Tabs 属性展现后,将所有节点全部删除掉
最终实现界面如下:
矩阵、欧拉角、轴-角对、四元数随笔
一、矩阵 在 3D 游戏中,可以使用矩阵来表示一个物体的旋转。 1) 优点: 个人认为,理解起来最为直观。 像现成的DXSDK库中也提供了十分完善的相关接口 一个矩阵即可表示多种变换的组合 2) 缺点: 每次计算都会产生误差,因此,需要经常规范化。 耗的内存较多些。 二、欧拉角 欧拉角指的是:以世界坐标系为参考坐标系(一定记住是世界坐标系),使用x,y,z三个值来分别表示绕(世界的)x轴、y轴、z轴旋转的角度量值。其取值是在[0, 360]间。一般用roll, pitch, yaw来表示这些分量的旋转值。因为是以世界坐标系为参考坐标系,因此每一次的旋转都不会影响到后续的旋转转轴。即:它无法表示任意轴的旋转。 1) 优点: 理解起来很直观。 2) 缺点: 会有万向锁问题。 三、轴-角对 其实轴-角对与欧拉角(个人认为)是有一定的关系的。因为欧拉角说的是分别(注意:是分别)绕(以世界坐标系为参考坐标系的)三个轴旋转一定的角度。其实这三次的旋转可以最终转换到一次变换。即:最终可表示为:绕某一旋转轴旋转一定角度的变换。(意思就是说:那三次变换我们最终可以计算出旋转轴以及绕该旋转轴旋转的角度量)。 1) 缺点: 轴-角对表示法:插值不平滑,可能会有跳跃。(文档上说,欧拉角同样存在这个问题) 2) 优点: 可解决欧拉角的万向锁问题。 四、四元数 四元数定义:q = w + xi + yj + zk 注意: 1) 四元数可以归一化,并且只有归一化的四元数才用来描述旋转 2) 四元数与轴-角对很像。因为四元数描述的也是一个旋转轴与一个绕着该旋转轴旋转的量值(即:角度或弧度)。但四元数与轴-角对不等价。它们的关系如下: 假如:轴-角对的值如下: 轴为:n 角为:theta 则,对应的四元数中的w、x、y、z的值分别为: w = cos(theta / 2) x = nx * sin(theta / 2) // nx 是轴 n 的 x 分量 y = ny * sin(theta / 2) // ny 是轴 n 的 y 分量 z = nz * sin(theta / 2) // nz 是轴 n 的 z 分量
3) 四元数的乘法意义: Q = Q1 * Q2表示的是:Q先做Q2的旋转,再做Q1的旋转的结果,而且多个四元数的旋转也是要以合并的。 4) 四元数做一次乘法需要16次乘法和加法,而3x3矩阵需要27次。所以有多次旋转操作时,使用四元数计算效率更高些。 5) 四元数的插值过度平滑。最常用的是线性插值。
原文转自: http://www.gesoftfactory.com/developer/Transform.htm
(提示:原文有图片。)
三维变换
在使用三维图形的应用程序中,可以用变换做以下事情:
- 描述一个物体相对于另一个物体的位置。
- 旋转并改变物体的大小。
- 改变观察的位置、方向和视角。
可以用一个4 x 4矩阵将任意点(x,y,z)变换为另一个点(x',y',z')。
对(x,y,z)和矩阵执行以下操作产生点(x',y',z')。
最常见的变换是平移、旋转和缩放。可以将产生这些效果的矩阵合并成单个矩阵,这样就可以一次计算多种变换。例如,可以构造单个矩阵,对一系列的点进行平移和旋转。更多信息,请参阅矩阵串接。
矩阵以行列顺序书写。一个沿每根轴均匀缩放顶点的矩阵,也称为统一缩放,用如下数学符号表示。
在C++应用程序中,Microsoft® Direct3D®使用D3DMATRIX结构,将矩阵声明为一个二维数组。以下示例代码显示了如何初始化一个D3DMATRIX结构,使之成为一个统一缩放矩阵。
// 本例中,s为浮点类型的变量
D3DMATRIX scale = {
s, 0.0f, 0.0f, 0.0f,
0.0f, s, 0.0f, 0.0f,
0.0f, 0.0f, s, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
};
平移
以下变换将点(x,y,z)平移到一个新的点(x',y',z')。
可以在C++应用程序中手工创建一个平移矩阵。以下示例代码显示了的一个函数的源码,该函数创建一个矩阵用于平移顶点。 D3DXMATRIX Translate(const float dx, const float dy, const float dz) { D3DXMATRIX ret; D3DXMatrixIdentity(&ret); // 由Direct3DX实现 ret(3, 0) = dx; ret(3, 1) = dy; ret(3, 2) = dz; return ret; } // 平移结束
为了方便,Direct3DX工具库提供了D3DXMatrixTranslation函数。
缩放
以下变换用指定值缩放点(x,y,z)的x-,y-和z-方向,产生新的点(x',y',z')。
旋转
这里描述的变换是基于左手坐标系的,也许和别处见到的变换矩阵不同。更多信息,请参阅三维坐标系。
以下变换将点(x,y,z)围绕x轴旋转,产生新的点(x',y',z')。
以下变换将点围绕y轴旋转。
以下变换将点围绕z轴旋转。
在这些示例矩阵中,希腊字母(θ)表示旋转的角度,以弧度为单位。当沿着旋转轴朝原点看时,角度以顺时针方向计量。
C++应用程序可以用Direct3D扩展(D3DX)工具库提供的D3DXMatrixRotationX,D3DXMatrixRotationX和D3DXMatrixRotationX函数创建旋转矩阵。以下示例是D3DXMatrixRotationX函数的源码。 D3DXMATRIX* WINAPI D3DXMatrixRotationX ( D3DXMATRIX *pOut, float angle ) { #if DBG if(!pOut) return NULL; #endif float sin, cos; sincosf(angle, &sin, &cos); // 计算角度的正弦和余弦值。 pOut->_11 = 1.0f; pOut->_12 = 0.0f; pOut->_13 = 0.0f; pOut->_14 = 0.0f; pOut->_21 = 0.0f; pOut->_22 = cos; pOut->_23 = sin; pOut->_24 = 0.0f; pOut->_31 = 0.0f; pOut->_32 = -sin; pOut->_33 = cos; pOut->_34 = 0.0f; pOut->_41 = 0.0f; pOut->_42 = 0.0f; pOut->_43 = 0.0f; pOut->_44 = 1.0f; return pOut; }
( D3DXMATRIX *pOut, float angle )
if(!pOut)
return NULL;
float sin, cos;
sincosf(angle, &sin, &cos); // Determine sin and cos of angle.
pOut->_11 = 1.0f; pOut->_12 = 0.0f; pOut->_13 = 0.0f; pOut->_14 = 0.0f;
pOut->_21 = 0.0f; pOut->_22 = cos; pOut->_23 = sin; pOut->_24 = 0.0f;
pOut->_31 = 0.0f; pOut->_32 = -sin; pOut->_33 = cos; pOut->_34 = 0.0f;
pOut->_41 = 0.0f; pOut->_42 = 0.0f; pOut->_43 = 0.0f; pOut->_44 = 1.0f;
return pOut;
矩阵串接
使用矩阵的一个优势就是可以通过把两个以上的矩阵相乘,将它们的效果合并在一起。这意味着,要先旋转一个建模然后把它平移到某个位置,无需使用两个矩阵,只要把旋转矩阵和平移矩阵相乘,产生一个包含了所有效果的合成矩阵。这个过程被称为矩阵串接,可以写成以下公式。
在这个公式中,C是将被创建的合成矩阵,M1到Mn是矩阵C包含的单个矩阵。虽然大多数情况下,只需要串接两三个矩阵,但实际上并没有数量上的限制。
可以使用D3DXMatrixMultiply函数进行矩阵乘法。
进行矩阵乘法时先后次序是至关重要的。前面的公式反映了矩阵串接从左到右的规则。也就是说,用来创建合成矩阵的每个矩阵产生的直观效果会按从左到右的次序出现。下面显示了一个典型的世界变换矩阵。想象一下给一个旋转飞行的碟子创建世界变换矩阵。应用程序也许想让飞行的碟子绕它的中心——建模空间中的y轴——旋转,然后把它平移到场景中的另一个位置。要实现这样的效果,首先创建一个旋转矩阵,然后将它与平移矩阵相乘,如以下公式所示。
在这个公式中,Ry是绕y轴的旋转矩阵,Tw是平移到世界坐标中某个位置的矩阵。
矩阵相乘的顺序很重要,因为矩阵乘法是不可交换的,这和两个标量相乘不同。将矩阵以相反的顺序相乘会产生这样的直观效果:先把飞行中的碟子平移到世界空间中的某个位置,然后将它围绕世界坐标的原点旋转。
无论创建何种类型的矩阵,都要记住从左到右的规则,这样才能保证得到想要的效果。
世界变换
对世界变换的讨论介绍了基本概念,并提供了如何在Microsoft® Direct3D®应用程序中设置世界变换矩阵的细节。
什么是世界变换
世界变换将坐标从建模空间,在这个空间中的顶点相对于建模的局部原点定义,转变到世界空间,在这个空间中的顶点相对于场景中所有物体共有的原点定义。本质上,世界变换将一个建模放到世界中,并由此而得名。下图描绘了世界坐标系统和建模的局部坐标系统间的关系。
世界变换可以包含任意数量的平移、旋转和缩放的合并。有关对变换的数学讨论,请参阅三维变换。
设置世界矩阵
同任何其它变换一样,通过将一系列变换矩阵串接成单个矩阵,应用程序可以创建包含这些矩阵全部效果的世界矩阵。最简单的情况下,建模在世界的原点并且它的局部坐标轴与世界空间的方向相同,这时世界矩阵就是单位矩阵。更通常的情况下,世界矩阵是一系列矩阵的合成,包含一个平移矩阵,并且根据需要可能有一个以上的旋转矩阵。
以下示例,来自一个用C++编写的假想三维建模类,使用Direct3D扩展(D3DX)工具库提供的帮助函数创建了一个世界矩阵,这个世界矩阵包含了三个旋转矩阵,用于调整三维建模的方向,以及一个平移矩阵,用来根据建模在世界空间中的相对坐标重新确定它的位置。 /* * 根据本示例的目的,假设以下变量都是有效的并经过初始化。 * * 变量m_xPos,m_yPos,m_zPos包含了建模在世界坐标中的位置。 * * 变量m_fPitch,m_fYaw和m_fRoll为浮点数,包含了建模的方向, * 用pitch,yaw和roll旋转角表示,以弧度为单位。 */ void C3DModel::MakeWorldMatrix( D3DXMATRIX* pMatWorld ) { D3DXMATRIX MatTemp; // 用于旋转的临时矩阵 D3DXMATRIX MatRot; // 最终的旋转矩阵,应用于pMatWorld. // 使用从左到右的矩阵串接顺序,在旋转之前对物体在世界空间中 // 的位置进行平移。 D3DXMatrixTranslation(pMatWorld, m_xPos, m_yPos, m_zPos); D3DXMatrixIdentity(&MatRot); // 现在将方向变量应用于世界矩阵 if(m_fPitch || m_fYaw || m_fRoll) { // 产生并合成旋转矩阵。 D3DXMatrixRotationX(&MatTemp, m_fPitch); // Pitch D3DXMatrixMultiply(&MatRot, &MatRot, &MatTemp); D3DXMatrixRotationY(&MatTemp, m_fYaw); // Yaw D3DXMatrixMultiply(&MatRot, &MatRot, &MatTemp); D3DXMatrixRotationZ(&MatTemp, m_fRoll); // Roll D3DXMatrixMultiply(&MatRot, &MatRot, &MatTemp); // 应用旋转矩阵,得到最后的世界矩阵。 D3DXMatrixMultiply(pMatWorld, &MatRot, pMatWorld); } }
当准备好世界变换矩阵后,应该调用IDirect3DDevice9::SetTransform方法设置它,并把第一个参数指定D3DTS_WORLD宏。
注意 Direct3D使用应用程序设置的世界和观察矩阵配置许多内部数据结构。应用程序每次设置新的世界或观察矩阵时,系统都要重新计算相关的内部数据结构。频繁地设置这些矩阵——例如,每帧上千次——是计算量很大的。通过将世界矩阵和观察矩阵串接成一个世界/观察矩阵,并将该矩阵设置为世界矩阵,然后将观察矩阵设置为单位矩阵,应用程序可以将所需的计算次数减到最少。最好保存一份单独的世界矩阵和观察矩阵的副本在高速缓存中,这样就可以根据需要修改、串接及重置世界矩阵。为清晰起见,本文档中的Direct3D示例很少使用这项优化。
观察变换
本节介绍观察变换的基本概念,并提供有关如何在Microsoft® Direct3D®应用程序中设置观察矩阵的细节。信息被分为以下主题。
什么是观察变换?
观察变换根据观察者在世界空间中的位置,把顶点变换到摄像机空间。在摄像机空间中,摄像机,或观察者,位于原点,朝sz轴的正向看去。再次提醒一下,因为Direct3D使用左手坐标系,所以z轴的正向是朝着场景的。观察矩阵根据摄像机的位置——摄像机空间的原点——和方向重定位世界中的所有物体。
有两方法可以创建观察矩阵。所有情况下,摄像机在世界空间中的逻辑位置和方向会被用作起始点来创建观察矩阵,得到的观察矩阵会被应用于场景中的三维建模。观察矩阵平移并旋转物体,将它们放入摄像机空间中,摄像机位于原点。创建观察矩阵的一种方法是把平移矩阵和围绕每根坐标轴旋转的旋转矩阵合并。这种方法使用了以下通用矩阵公式。
在这个公式中,V是要创建的观察矩阵,T是在世界中重定位物体的平移矩阵,Rx到Rz分别是绕x轴,y轴和z轴旋转物体的旋转矩阵。平移和旋转矩阵基于摄像机在世界空间中逻辑位置和方向。因此,如果摄像机在世界中的逻辑位置是<10,20,100>,那么平移矩阵的目的是沿x轴移动物体-10单位,沿y轴移动-20单位,沿z轴移动-100单位。公式中的旋转矩阵基于摄像机的方向,根据摄像机空间的坐标轴与世界空间的坐标轴间的夹角决定。例如,如果前面提到的摄像机是垂直向下放的,那么它的z轴与世界空间的z轴有90度夹角,如下图所示。
旋转矩阵将角度相同但方向相反的旋转量应用于场景中的建模。这个摄像机的观察矩阵包含了一个绕x轴-90度的旋转。旋转矩阵与平移矩阵合并生成观察矩阵,观察矩阵调整物体在场景中的位置和方向,使它们的顶部朝着摄像机,看起来就好像摄像机在建模的上方一样。
设置观察矩阵
D3DXMatrixLookAtLH和D3DXMatrixLookAtRH辅助函数根据摄像机的位置和被观察点创建一个观察矩阵。
以下示例代码创建了一个用于右手系的观察矩阵。 D3DXMATRIX out; D3DXVECTOR3 eye(2,3,3); D3DXVECTOR3 at(0,0,0); D3DXVECTOR3 up(0,1,0); D3DXMatrixLookAtRH(&out, &eye, &at, &up);
Direct3D使用应用程序设置的世界矩阵和观察矩阵配置许多内部数据结构。每次应用程序设置一个新的世界矩阵或观察矩阵,系统都要重新计算相关的内部数据结构。频繁地设置这些矩阵——例如,每帧20,000次——计算量非常大。通过将世界矩阵和观察矩阵串接成一个世界/观察矩阵,并将之设置为世界矩阵,然后将观察矩阵设为单位矩阵,应用程序可以将所需的计算量减到最小。最好保存一份单独的世界矩阵和观察矩阵的副本在高速缓存中,这样就可以根据需要修改、串接及重置世界矩阵。为清晰起见,Direct3D示例很少使用这项优化。
投影变换
可以认为投影变换是控制摄像机的内部参数,这选择摄像机的镜头有些相似。这是三种类型的变换中最为复杂的。对投影变换的讨论被分为以下主题。
什么是投影变换?
典型的投影变换就是一个缩放和透视投影。投影变换将视棱锥转变为一个立方体。因为视棱锥的近端比远端小,所以这就产生了离摄像机近的物体被放大的效果,这就是透视如何被应用于场景的。
在视棱锥中,摄像机与观察变换空间的原点之间的距离被定义为D,因此投影矩阵看起来是这样:
通过在z方向平移-D,观察矩阵将摄像机平移到原点。平移矩阵如下所示:
将平移矩阵与投影矩阵相乘(T*P),得到合成的投影矩阵。如下:
下图描绘了透视投影如何将视棱锥转变到新的坐标空间。注意棱锥变成了立方体,同时原点从场景的右上角移到了中心。(译注:应该是从前裁剪平面的中心移到了原点)
在透视变换中,x和y方向的边界值是-1和1。Z方向的边界值分别是,0对应于前平面,1对应于后平面。
这个矩阵根据指定的从摄像机到近裁剪平面的距离,平移并缩放物体,但没有考虑视角(fov),并且用它为远处物体产生的z值可能几乎相同,这使深度比较变得困难。以下矩阵解决了这些问题,并根据视区的纵横比调整顶点,这使它成为透视投影的一个很好的选择。
在这个矩阵中,Zn是近裁剪平面的z值。变量w,h和Q有以下含义。注意fovw和fovh表示视区在水平和垂直方向上的视角,以弧度为单位。
对应用程序而言,使用视角的角度定义x和y的比例系数可能不如使用视区在水平和垂直方向上的大小(在摄像机空间中)方便。可以用数学推导,得出下面两个使用视区大小计算w和h的公式,它们与前面的公式是等价的。
在这两个公式中,Zn表示近裁剪平面的位置,Vw和Vh变量表示视区在摄像机空间的宽和高。
对于C++应用程序而言,这两个大小直接对应于D3DVIEWPORT9结构的Width和Height成员。(译注:虽然直接对应,但是不等价的,因为Vw和Vh位于摄像机空间,而Width和Height位于屏幕空间)
无论决定使用什么公式,非常重要的一点是要尽可能将Zn设得大,因为接近摄像机的z值变化不大。这使得用16位z缓存的深度比较变得有点复杂。
同世界变换和观察变换一样,应用程序调用IDirect3DDevice9::SetTransform方法设置投影矩阵。
设置投影矩阵
以下ProjectionMatrix示例函数设置了前后裁剪平面,以及在水平和垂直方向上视角的角度。此处的代码与什么是投影矩阵?主题中讨论的方法相似。视角应该小于弧度π。 D3DXMATRIX ProjectionMatrix(const float near_plane, // 到近裁剪平面的距离 const float far_plane, // 到远裁剪平面的距离 const float fov_horiz, // 水平视角,用弧度表示 const float fov_vert) // 垂直视角,用弧度表示 { float h, w, Q; w = (float)1/tan(fov_horiz*0.5); // 1/tan(x) == cot(x) h = (float)1/tan(fov_vert*0.5); // 1/tan(x) == cot(x) Q = far_plane/(far_plane - near_plane); D3DXMATRIX ret; ZeroMemory(&ret, sizeof(ret)); ret(0, 0) = w; ret(1, 1) = h; ret(2, 2) = Q; ret(3, 2) = -Q*near_plane; ret(2, 3) = 1; return ret; } // End of ProjectionMatrix
在创建矩阵之后,调用IDirect3DDevice9::SetTransform方法设置投影矩阵,要将第一个参数设为D3DTS_PROJECTION。
Direct3D扩展(D3DX)工具库提供了以下函数,帮助应用程序设置投影矩阵。
- D3DXMatrixPerspectiveLH
- D3DXMatrixPerspectiveRH
- D3DXMatrixPerspectiveFovLH
- D3DXMatrixPerspectiveFovRH
- D3DXMatrixPerspectiveOffCenterLH
- D3DXMatrixPerspectiveOffCenterRH
W友好投影矩阵
对于已经用世界、观察和投影矩阵变换过的顶点,Microsoft® Direct3D®使用顶点的W成员进行深度缓存中基于深度的计算或计算雾效果。类似这样的计算要求应用程序归一化投影矩阵,使生成的W与世界空间中的Z相同。简而言之,如果应用程序的投影矩阵包含的(3,4)系数不为1,那么为了生成适用的矩阵,应用程序必须用(3,4)系数的倒数缩放所有的系数。如果应用程序不提供符合这样要求的矩阵,那么会造成雾效果和深度缓存不正确。什么是投影矩阵?中推荐的矩阵符合基于w的计算的要求。
下图显示了不符合要求的投影矩阵,以及对同一个矩阵进行缩放,这样就可以启用基于视点的雾。
我们假设在前面的矩阵中,所有变量都不为零。更多有关相对于视点的雾的信息,请参阅基于视点的深度与基于Z的深度的比较。更多有关基于w的深度缓存,请参阅深度缓存。
注意 Direct3D在基于w的深度计算中使用当前设置的投影矩阵。因此,即使应用程序不使用Direct3D变换流水线,但是为了使用基于w的特性,应用程序必须设置一个符合要求的投影矩阵。
vs2008似乎有个bug。经常新创建的mfc程序,编译出来的exe会有两个dll为“找不到”(即:用依赖库查看是为黄色的)
用如下方法,可轻松该bug。(注:好像在vc6也,无此bug)
具体处理过程如下:
1、在Project\Properties\Configuration Properties\Project Defaults\Use of MFC中,选择Use MFC in a Static Library
2、编译,编译不通过没关系
3、在Project\Properties\Configuration Properties\Project Defaults\Use of MFC中,选择Use Standard Windows Libraries(就是把设置改回去)
4、再次编译
再使用dependency工具查看,OK了。
原文出自: http://www.cnitblog.com/zouzheng/archive/2007/08/31/32691.html
__FILE__,__LINE__,FUNCTION__实现代码跟踪调试(linux下c语言编程 )先看下简单的初始代码:注意其编译运行后的结果。
root@xuanfei-desktop:~/cpropram/2# cat global.h //头文件 #ifndef CLOBAL_H #define GLOBAL_H #include <stdio.h> int funca(void); int funcb(void); #endif root@xuanfei-desktop:~/cpropram/2# cat funca.c //函数a #include "global.h" int funca(void) { printf ("this is function\n"); return 0; } root@xuanfei-desktop:~/cpropram/2# cat funcb.c //函数b #include "global.h" int funcb(void) { printf ("this is function\n"); return 0; } root@xuanfei-desktop:~/cpropram/2# gcc -Wall funca.c funcb.c main.c //联合编译 root@xuanfei-desktop:~/cpropram/2# ./a.out //运行 this is main this is function this is main this is function this is main
相同结果很难让人看出那里出错,下面我们用用 __FILE__,__LINE__,__FUNCTION__加入代码,看看有什么区别吗. 把 __FILE__,__LINE__,__FUNCTION__加入到mail.c中 root@xuanfei-desktop:~/cpropram/2# cat main.c #include "global.h" int main(int argc, char **argv) { printf("%s(%d)-%s: this is main\n",__FILE__,__LINE__,__FUNCTION__); funca(); printf("%s(%d)-%s: this is main\n",__FILE__,__LINE__,__FUNCTION__); funcb(); printf("%s(%d)-%s: this is main\n",__FILE__,__LINE__,__FUNCTION__); return 0; } root@xuanfei-desktop:~/cpropram/2# gcc -Wall funca.c funcb.c main.c root@xuanfei-desktop:~/cpropram/2# ./a.out main.c(4)-main: this is main this is function main.c(6)-main: this is main this is function main.c(8)-main: this is main
上面的结果main.c(4)-main:this is main 表示在mian.c源代码的第四行main函数里边打印出来的 this is main 那样的话就很方便的让程序员对自己的程序进行排错! 为了更方便的使用它我们可以通过在global.h代码中进行宏定义 root@xuanfei-desktop:~/cpropram/2# cat global.h #ifndef CLOBAL_H #define GLOBAL_H #include <stdio.h> int funca(void); int funcb(void); #define DEBUGFMT "%s(%d)-%s" #define DEBUGARGS __FILE__,__LINE__,__FUNCTION__ #endif root@xuanfei-desktop:~/cpropram/2# cat funca.c #include "global.h" int funca(void) { printf (DEBUGFMT " this is function\n",DEBUGARGS); return 0; } root@xuanfei-desktop:~/cpropram/2# cat funcb.c #include "global.h" int funcb(void) { printf (DEBUGFMT " this is function\n",DEBUGARGS); return 0; } root@xuanfei-desktop:~/cpropram/2# cat main.c #include "global.h" int main(int argc, char **argv) { printf(DEBUGFMT "this is main\n", DEBUGARGS); funca(); printf(DEBUGFMT "this is main\n", DEBUGARGS); funcb(); printf(DEBUGFMT "this is main\n", DEBUGARGS); return 0; } root@xuanfei-desktop:~/cpropram/2# gcc -Wall funca.c funcb.c main.c root@xuanfei-desktop:~/cpropram/2# ./a.out main.c(4)-mainthis is main funca.c(4)-funca this is function main.c(6)-mainthis is main funcb.c(4)-funcb this is function main.c(8)-mainthis is main root@xuanfei-desktop:~/cpropram/2#
这就是通过定义__FILE__,__LINE__,FUNCTION__的宏来简单实现代码的跟踪调试:)
下面是一个可供调试用的头文件 #ifndef _GOLD_DEBUG_H #define _GOLD_DEBUG_H
#ifdef __cplusplus #if __cplusplus extern "C"{ #endif #endif /* __cplusplus */
//#define GI_DEBUG
#ifdef GI_DEBUG
#define GI_DEBUG_POINT() printf("\n\n[File:%s Line:%d] Fun:%s\n\n", __FILE__, __LINE__, __FUNCTION__) #define dbg_printf(arg...) printf(arg);
#define GI_ASSERT(expr) \ do{ \ if (!(expr)) { \ printf("\nASSERT failed at:\n >File name: %s\n >Function : %s\n >Line No. : %d\n >Condition: %s\n", \ __FILE__,__FUNCTION__, __LINE__, #expr);\ } \ }while(0);
/*调试宏, 用于暂停*/ #define GI_DEBUG_PAUSE() \ do \ { \ GI_DEBUG_POINT(); \ printf("pause for debug, press 'q' to exit!\n"); \ char c; \ while( ( c = getchar() ) ) \ { \ if('q' == c) \ { \ getchar(); \ break; \ } \ } \ }while(0); #define GI_DEBUG_PAUSE_ARG(arg...) \ do \ { \ printf(arg); \ GI_DEBUG_PAUSE() \ }while(0);
#define GI_DEBUG_ASSERT(expression) \ if(!(expression)) \ { \ printf("[ASSERT],%s,%s:%d\n", __FILE__, __FUNCTION__, __LINE__);\ exit(-1); \ } #else #define GI_ASSERT(expr) #define GI_DEBUG_PAUSE() #define GI_DEBUG_PAUSE_ARG(arg...) #define GI_DEBUG_POINT() #define dbg_printf(arg...) #define GI_DEBUG_ASSERT(expression)
#endif
#ifdef __cplusplus #if __cplusplus } #endif #endif /* __cplusplus */
#endif
C语言常用宏定义
01: 防止一个头文件被重复包含 #ifndef COMDEF_H #define COMDEF_H //头文件内容 #endif 02: 重新定义一些类型,防止由于各种平台和编译器的不同,而产生的类型字节数差异,方便移植。 typedef unsigned char boolean; /* Boolean value type. */ typedef unsigned long int uint32; /* Unsigned 32 bit value */ typedef unsigned short uint16; /* Unsigned 16 bit value */ typedef unsigned char uint8; /* Unsigned 8 bit value */ typedef signed long int int32; /* Signed 32 bit value */ typedef signed short int16; /* Signed 16 bit value */ typedef signed char int8; /* Signed 8 bit value */
//下面的不建议使用 typedef unsigned char byte; /* Unsigned 8 bit value type. */ typedef unsigned short word; /* Unsinged 16 bit value type. */ typedef unsigned long dword; /* Unsigned 32 bit value type. */ typedef unsigned char uint1; /* Unsigned 8 bit value type. */ typedef unsigned short uint2; /* Unsigned 16 bit value type. */ typedef unsigned long uint4; /* Unsigned 32 bit value type. */ typedef signed char int1; /* Signed 8 bit value type. */ typedef signed short int2; /* Signed 16 bit value type. */ typedef long int int4; /* Signed 32 bit value type. */ typedef signed long sint31; /* Signed 32 bit value */ typedef signed short sint15; /* Signed 16 bit value */ typedef signed char sint7; /* Signed 8 bit value */
03: 得到指定地址上的一个字节或字 #define MEM_B(x) (*((byte *)(x))) #define MEM_W(x) (*((word *)(x)))
04: 求最大值和最小值 #define MAX(x,y) (((x)>(y)) ? (x) : (y)) #define MIN(x,y) (((x) < (y)) ? (x) : (y))
05: 得到一个field在结构体(struct)中的偏移量 #define FPOS(type,field) ((dword)&((type *)0)->field)
06: 得到一个结构体中field所占用的字节数 #define FSIZ(type,field) sizeof(((type *)0)->field)
07: 按照LSB格式把两个字节转化为一个Word #define FLIPW(ray) ((((word)(ray)[0]) * 256) + (ray)[1])
08: 按照LSB格式把一个Word转化为两个字节 #define FLOPW(ray,val) (ray)[0] = ((val)/256); (ray)[1] = ((val) & 0xFF)
09: 得到一个变量的地址(word宽度) #define B_PTR(var) ((byte *) (void *) &(var)) #define W_PTR(var) ((word *) (void *) &(var))
10: 得到一个字的高位和低位字节 #define WORD_LO(xxx) ((byte) ((word)(xxx) & 255)) #define WORD_HI(xxx) ((byte) ((word)(xxx) >> 8))
11: 返回一个比X大的最接近的8的倍数 #define RND8(x) ((((x) + 7)/8) * 8)
12: 将一个字母转换为大写 #define UPCASE(c) (((c)>='a' && (c) <= 'z') ? ((c) - 0x20) : (c))
13: 判断字符是不是10进值的数字 #define DECCHK(c) ((c)>='0' && (c)<='9')
14: 判断字符是不是16进值的数字 #define HEXCHK(c) (((c) >= '0' && (c)<='9') ((c)>='A' && (c)<= 'F') \ ((c)>='a' && (c)<='f'))
15: 防止溢出的一个方法 #define INC_SAT(val) (val=((val)+1>(val)) ? (val)+1 : (val))
16: 返回数组元素的个数 #define ARR_SIZE(a) (sizeof((a))/sizeof((a[0])))
17: 返回一个无符号数n尾的值MOD_BY_POWER_OF_TWO(X,n)=X%(2^n) #define MOD_BY_POWER_OF_TWO( val, mod_by ) ((dword)(val) & (dword)((mod_by)-1))
18: 对于IO空间映射在存储空间的结构,输入输出处理 #define inp(port) (*((volatile byte *)(port))) #define inpw(port) (*((volatile word *)(port))) #define inpdw(port) (*((volatile dword *)(port))) #define outp(port,val) (*((volatile byte *)(port))=((byte)(val))) #define outpw(port, val) (*((volatile word *)(port))=((word)(val))) #define outpdw(port, val) (*((volatile dword *)(port))=((dword)(val)))
19: 使用一些宏跟踪调试 ANSI标准说明了五个预定义的宏名。它们是: __LINE__ __FILE__ __DATE__ __TIME__ __STDC__ C++中还定义了 __cplusplus
如果编译器不是标准的,则可能仅支持以上宏名中的几个,或根本不支持。记住编译程序也许还提供其它预定义的宏名。
__LINE__ 及 __FILE__ 宏指示,#line指令可以改变它的值,简单的讲,编译时,它们包含程序的当前行数和文件名。
__DATE__ 宏指令含有形式为月/日/年的串,表示源文件被翻译到代码时的日期。 __TIME__ 宏指令包含程序编译的时间。时间用字符串表示,其形式为: 分:秒 __STDC__ 宏指令的意义是编译时定义的。一般来讲,如果__STDC__已经定义,编译器将仅接受不包含任何非标准扩展的标准C/C++代码。如果实现是标准的,则宏__STDC__含有十进制常量1。如果它含有任何其它数,则实现是非标准的。 __cplusplus 与标准c++一致的编译器把它定义为一个包含至少6为的数值。与标准c++不一致的编译器将使用具有5位或更少的数值。
可以定义宏,例如: 当定义了_DEBUG,输出数据信息和所在文件所在行 #ifdef _DEBUG #define DEBUGMSG(msg,date) printf(msg);printf(“%d%d%d”,date,_LINE_,_FILE_) #else #define DEBUGMSG(msg,date) #endif
20: 宏定义防止错误使用小括号包含。 例如: 有问题的定义:#define DUMP_WRITE(addr,nr) {memcpy(bufp,addr,nr); bufp += nr;} 应该使用的定义: #difne DO(a,b) do{a+b;a++;}while(0) 例如: if(addr) DUMP_WRITE(addr,nr); else do_somethong_else(); 宏展开以后变成这样: if(addr) {memcpy(bufp,addr,nr); bufp += nr;}; else do_something_else();
gcc 在碰到else前面的“;”时就认为if语句已经结束,因而后面的else不在if语句中。而采用do{} while(0)的定义,在任何情况下都没有问题。而改为 #difne DO(a,b) do{a+b;a++;}while(0) 的定义则在任何情况下都不会出错。
原谅出自: http://www.yesky.com/445/1713445.shtml控制C++的内存分配 在嵌入式系统中使用C++的一个常见问题是内存分配,即对new 和 delete 操作符的失控。
具有讽刺意味的是,问题的根源却是C++对内存的管理非常的容易而且安全。具体地说,当一个对象被消除时,它的析构函数能够安全的释放所分配的内存。
这当然是个好事情,但是这种使用的简单性使得程序员们过度使用new 和 delete,而不注意在嵌入式C++环境中的因果关系。并且,在嵌入式系统中,由于内存的限制,频繁的动态分配不定大小的内存会引起很大的问题以及堆破碎的风险。
作为忠告,保守的使用内存分配是嵌入式环境中的第一原则。
但当你必须要使用new 和delete时,你不得不控制C++中的内存分配。你需要用一个全局的new 和delete来代替系统的内存分配符,并且一个类一个类的重载new 和delete。
一个防止堆破碎的通用方法是从不同固定大小的内存持中分配不同类型的对象。对每个类重载new 和delete就提供了这样的控制。
重载全局的new 和delete 操作符
可以很容易地重载new 和 delete 操作符,如下所示:
void * operator new(size_t size) { void *p = malloc(size); return (p); } void operator delete(void *p); { free(p); } |
这段代码可以代替默认的操作符来满足内存分配的请求。出于解释C++的目的,我们也可以直接调用malloc() 和free()。
也可以对单个类的new 和 delete 操作符重载。这是你能灵活的控制对象的内存分配。
class TestClass { public: void * operator new(size_t size); void operator delete(void *p); // .. other members here ... };
void *TestClass::operator new(size_t size) { void *p = malloc(size); // Replace this with alternative allocator return (p); } void TestClass::operator delete(void *p) { free(p); // Replace this with alternative de-allocator } |
所有TestClass 对象的内存分配都采用这段代码。更进一步,任何从TestClass 继承的类也都采用这一方式,除非它自己也重载了new 和 delete 操作符。通过重载new 和 delete 操作符的方法,你可以自由地采用不同的分配策略,从不同的内存池中分配不同的类对象。
为单个的类重载 new[ ] 和 delete[ ]
必须小心对象数组的分配。你可能希望调用到被你重载过的new 和 delete 操作符,但并不如此。内存的请求被定向到全局的new[ ]和delete[ ] 操作符,而这些内存来自于系统堆。
C++将对象数组的内存分配作为一个单独的操作,而不同于单个对象的内存分配。为了改变这种方式,你同样需要重载new[ ] 和 delete[ ]操作符。
class TestClass { public: void * operator new[ ](size_t size); void operator delete[ ](void *p); // .. other members here .. }; void *TestClass::operator new[ ](size_t size) { void *p = malloc(size); return (p); } void TestClass::operator delete[ ](void *p) { free(p); } int main(void) { TestClass *p = new TestClass[10];
// ... etc ...
delete[ ] p; } |
但是注意:对于多数C++的实现,new[]操作符中的个数参数是数组的大小加上额外的存储对象数目的一些字节。在你的内存分配机制重要考虑的这一点。你应该尽量避免分配对象数组,从而使你的内存分配策略简单。
::GetCurrentDirectory()返回值末尾是不带有 '\\' 的。
原文来自:http://www.cppblog.com/totti1006/archive/2010/01/21/106118.html
1 引言
在大多数Windows应用程序 设计中,都几乎不可避免的要对内存进行操作和管理。在进行大尺寸内存的动态分配时尤其显的重要。本文即主要对内存管理中的堆管理技术进行论述。 堆(Heap)实际是位于保留的虚拟地址空间中的一个区域。刚开始时,保留区域中的多数页面并没有被提交物理存储器。随着从堆中越来越多的进行内存分配,堆管理器将逐渐把更多的物理存储器提交给堆。堆的物理存储器从系统页文件中分配,在释放时有专门的堆管理器负责对已占用物理存储器的回收。堆管理也是Windows提供的一种内存管理机制。主要用来分配小的数据块。与Windows的其他两种内存管理机制虚拟内存和内存映射文件相比,堆可以不必考虑诸如系统的分配粒度和页面边界之类比较烦琐而又容易忽视的问题,可将注意力集中于对 程序功能代码的设计上。但是使用堆去分配、释放内存的速度要比其他两种机制慢的多,而且不具备直接控制物理存储器提交与回收的能力。 在进程刚启动时,系统便在刚创建的进程虚拟地址空间中创建了一个堆,该堆即为进程的默认堆,缺省大小为1MB,该值允许在链接程序时被更改。进程的默认堆是比较重要的,可供众多Windows函数使用。在使用时,系统必须保证在规定的时间内,每此只有一个线程能够分配和释放默认堆中的内存块。虽然这种限制将会对访问速度产生一定的影响,但却可以保证进程中的多个线程在同时调用各种Windows函数时对默认堆的顺序访问。在进程中允许使用多个堆,进程中包括默认堆在内的每个堆都有一个堆句柄来标识。与自己创建的堆不同,进程默认堆的创建、销毁均由系统来完成,而且其生命期早在进程开始执行之前就已经开始,虽然在程序中可以通过GetProcessHeap()函数得到进程的默认堆句柄,但却不允许调用HeapDestroy()函数显式将其撤消。 2 对动态创建堆的需求 前面曾提到,在进程中除了进程默认堆外,还可以在进程虚拟地址空间中动态创建一些独立的堆。至于在程序设计时究竟需不需要动态创建独立的堆可以从是否有保护组件的需要、是否能更加有效地对内存进行管理、是否有进行本地访问的需要、是否有减少线程同步开销的需要以及是否有迅速释放堆的需要等几个方面去考虑。 对于是否有保护组件的需要这一原则比较容易理解。在图1中,左边的图表示了一个链表(节点结构)组件和一个树(分支结构)组件共同使用一个堆的情况。在这种情况下,由于两组件数据在堆中的混合存放,如果节点3(属于链表组件)的后几个字节由于被错误改写,将有可能影响到位于其后的分支2(属于树组件)。这将致使树组件的相关代码在遍历其树时由于内存被破坏而无法进行。究其原因,树组件的内存是由于链表组建对其自身的错误操作而引起的。如果采用右图所示方式,将树组件和链表组件分别存放于一个独立的堆中,上述情况显然不会发生,错误将被局限于进行了错误操作的链表组件,而树组件由于存放在独立的堆中而受到了保护。 图1 动态创建堆在保护组件中的作用 在上图中,如果链表组件的每个节点占用12个字节,每个树组件的分支占用16个字节如果这些长度不一的对象共用一个堆(左图),在左图中这些已经分配了内存的对象已占满了堆,如果其中有节点2和节点4释放,将会产生24个字节的碎片,如果试图在24个字节的空闲区间内分配一个16字节的分支对象,尽管要分配的字节数小于空闲字节数,但分配仍将失败。只有在堆栈中分配大小相同的对象才可以实行更加有效的内存管理。如果将树组件换成其他长度为12字节的组件,那么在释放一个对象后,另一个对象就可以恰好填充到此刚释放的对象空间中。 进行本地访问的需要也是一条比较重要的原则。系统会经常在内存与系统页文件之间进行页面交换,但如果交换次数过多,系统的运行性能就将受很大的影响。因此在程序设计时应尽量避免系统频繁交换页面,如果将那些会被同时访问到的数据分配在相互靠近的位置上,将会减少系统在内存和页文件之间的页面交换频率。 线程同步开销指的是默认条件下以顺序方式运行的堆为保护数据在多个线程试图同时访问时不受破坏而必须执行额外代码所花费的开销。这种开销保证了堆对线程的 安全性,因此是有必要的,但对于大量的堆分配操作,这种额外的开销将成为一个负担,并降低程序的运行性能。为避免这种额外的开销,可以在创建新堆时通知系统只有单个线程对访问。此时堆对线程的安全性将有应用程序来负责。 最后如果有迅速释放堆的需要,可将专用堆用于某些数据结构,并以整个堆去释放,而不再显式地释放在堆中分配的每一个内存块。对于大多数应用程序,这样的处理将能以更快的速度运行。 3 创建堆
在进程中,如果需要可以在原有默认堆的基础上动态创建一个堆,可由HeapCreate()函数完成:
HANDLE HeapCreate( DWORD flOptions, DWORD dwInitialSize, DWORD dwMaximumSize ); | 其第一个参数flOptions指定了对新建堆的操作属性。该标志将会影响一些堆函数如HeapAlloc()、HeapFree()、HeapReAlloc()和HeapSize()等对新建堆的访问。其可能的取值为下列标志及其组合:
属性标志 |
说明 |
HEAP_GENERATE_EXCEPTIONS |
在遇到由于内存越界等而引起的函数失败时,由系统抛出一个异常来指出此失败,而不是简单的返回NULL指针。 |
HEAP_NO_SERIALIZE |
指明互斥现象不会出现 | 参数dwInitialSize和dwMaximumSize分别为堆的初始大小和堆栈的最大尺寸。其中,dwInitialSize的值决定了最初提交给堆的字节数。如果设置的数值不是页面大小的整数倍,则将被圆整到邻近的页边界处。而dwMaximumSize则实际上是系统能为堆保留的地址空间区域的最大字节数。如果该值为0,那么将创建一个可扩展的堆,堆的大小仅受可用内存的限制。如果应用程序需要分配大的内存块,通常要将该参数设置为0。如果dwMaximumSize大于0,则该值限定了堆所能创建的最大值,HeapCreate()同样也要将该值圆整到邻近的页边界,然后再在进程的虚拟地址空间为堆保留该大小的一块区域。在这种堆中分配的内存块大小不能超过0x7FFF8字节,任何试图分配更大内存块的行为将会失败,即使是设置的堆大小足以容纳该内存块。如果HeapCreate()成功执行,将会返回一个标识新堆的句柄,并可供其他堆函数使用。 需要特别说明的是,在设置第一个参数时,对HEAP_NO_SERIALIZE的标志的使用要谨慎,一般应避免使用该标志。这是同后续将要进行的堆函数HeapAlloc()的执行过程有关系的,在HeapAlloc()试图从堆中分配一个内存块时,将执行下述几步操作: 1) 遍历分配的和释放的内存块的链接表 2) 搜寻一个空闲内存块的地址 3) 通过将空闲内存块标记为"已分配"来分配新内存块 4) 将新分配的内存块添加到内存块列表 如果这时有两个线程1、2试图同时从一个堆中分配内存块,那么线程1在执行了上面的1和2步后将得到空间内存块的地址。但是由于CPU对线程运行时间的分片,使得线程1在执行第3步操作前有可能被线程2抢走执行权并有机会去执行同样的1、2步操作,而且由于先执行的线程1并没有执行到第3步,因此线程2会搜寻到同一个空闲内存块的地址,并将其标记为已分配。而线程1在恢复运行后并不能知晓该内存块已被线程2标记过,因此会出现两个线程军认为其分配的是空闲的内存块,并更新各自的联接表。显然,象这种两个线程拥有完全相同内存块地址的错误是非常严重而又是难以发现的。 由于只有在多个线程同时进行操作时才有可能出现上述问题,一种简单的解决的办法就是不使用HEAP_NO_SERIALIZE标志而只允许单个线程独占地对堆及其联接表拥有访问权。如果一定要使用此标志,为了安全起见,必须确保进程为单线程的或是在进程中使用了多线程,但只有单个线程对堆进行访问。再就是使用了多线程,也有多个线程对堆进行了访问,但这些线程通过使用某种线程同步手段。如果可以确保以上几条中的一条成立,也是可以安全使用HEAP_NO_SERIALIZE标志的,而且还将拥有快的访问速度。如果不能肯定上述条件是否满足,建议不使用此标志而以顺序的方式访问堆,虽然线程速度会因此而下降但却可以确保堆及其中数据的不被破坏。 4 从堆中分配内存块
在成功创建一个堆后,可以调用HeapAlloc()函数从堆中分配内存块。在此,除了可以从用HeapCreate()创建的动态堆中分配内存块,也可以直接从进程的默认堆中分配内存块。下面先给出HeapCreate()的函数原型:
LPVOID HeapAlloc( HANDLE hHeap, DWORD dwFlags, DWORD dwBytes ); | 其中,参数hHeap为要分配的内存块来自的堆的句柄,可以是从HeapCreate()创建的动态堆句柄也可以是由GetProcessHeap()得到的默认堆句柄。参数dwFlags指定了影响堆分配的各个标志。该标志将覆盖在调用HeapCreate()时所指定的相应标志,可能的取值为:
标志 |
说明 |
HEAP_GENERATE_EXCEPTIONS |
该标志指定在进行诸如内存越界操作等情况时将抛出一个异常而不是简单的返回NULL指针 |
HEAP_NO_SERIALIZE |
强制对HeapAlloc()的调用将与访问同一个堆的其他线程不按照顺序进行 |
HEAP_ZERO_MEMORY |
如果使用了该标志,新分配内存的内容将被初始化为0 | 最后一个参数dwBytes设定了要从堆中分配的内存块的大小。如果HeapAlloc()执行成功,将会返回从堆中分配的内存块的地址。如果由于内存不足或是其他一些原因而引起HeapAlloc()函数的执行失败,将会引发异常。通过异常标志可以得到引起内存分配失败的原因:如果为STATUS_NO_MEMORY则表明是由于内存不足引起的;如果是STATUS_ACCESS_VIOLATION则表示是由于堆被破坏或函数参数不正确而引起分配内存块的尝试失败。以上异常只有在指定了HEAP_GENERATE_EXCEPTIONS标志时才会发生,如果没有指定此标志,在出现类似错误时HeapAlloc()函数只是简单的返回NULL指针。 在设置dwFlags参数时,如果先前用HeapCreate()创建堆时曾指定过HEAP_GENERATE_EXCEPTIONS标志,就不必再去设置HEAP_GENERATE_EXCEPTIONS标志了,因为HEAP_GENERATE_EXCEPTIONS标志已经通知堆在不能分配内存块时将会引发异常。另外,对HEAP_NO_SERIALIZE标志的设置应慎重,与在HeapCreate()函数中使用HEAP_NO_SERIALIZE标志类似,如果在同一时间有其他线程使用同一个堆,那么该堆将会被破坏。如果是在进程默认堆中进行内存块的分配则要绝对禁用此标志。 在使用堆函数HeapAlloc()时要注意:堆在内存管理中的使用主要是用来分配一些较小的数据块,如果要分配的内存块在1MB左右,那么就不要再使用堆来管理内存了,而应选择虚拟内存的内存管理机制。 5 再分配内存块
在程序设计时经常会由于开始时预见不足而造成在堆中分配的内存块大小的不合适(多数情况是开始时分配的内存较小,而后来实际需要更多的数据复制到内存块中去)这就需要在分配了内存块后再根据需要调整其大小。堆函数HeapReAlloc()将完成这一功能,其函数原型为:
LPVOID HeapReAlloc( HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, DWORD dwBytes ); | 其中,参数hHeap为包含要调整其大小的内存块的堆的句柄。dwFlags参数指定了在更改内存块大小时HeapReAlloc()函数所使用的标志。其可能的取值为HEAP_GENERATE_EXCEPTIONS、HEAP_NO_SERIALIZE、HEAP_REALLOC_IN_PLACE_ONLY和HEAP_ZERO_MEMORY,其中前两个标志的作用与在HeapAlloc()中的作用相同。HEAP_REALLOC_IN_PLACE_ONLY标志在内存块被加大时不移动堆中的内存块,在没有设置此标志的情况下如果对内存进行增大,那么HeapReAlloc()函数将有可能将原内存块移动到一个新的地址。显然,在设置了该标志禁止内存快首地址进行调整时,将有可能出现没有足够的内存供试图增大的内存块使用,对于这种情况,函数对内存块增大调整的操作是失败的,内存块将仍保留原有的大小和位置。HEAP_ZERO_MEMORY标志的用处则略有不同,如果内存快经过调整比以前大,那么新增加的那部分内存将被初始化为0;如果经过调整内存块缩小了,那么该标志将不起任何作用。 函数的最后两个参数lpMem和dwBytes分别为指向再分配内存块的指针和再分配的字节数。如果函数成功执行,将返回新的改变了大小的内存块的地址。如果在调用时使用了HEAP_REALLOC_IN_PLACE_ONLY标志,那么返回的地址将与原内存块地址相同。如果因为内存不足等原因而引起函数的执行失败,函数将返回一个NULL指针。但是HeapReAlloc()的执行失败并不会影响原内存块,它将保持原来的大小和位置继续存在。可以通过HeapSize()函数来检索内存块的实际大小。 6 释放堆内存、撤消堆 在不再需要使用堆中的内存块时,可以通过HeapFree()将其予以释放。该函数结构比较简单,只含有三个参数:
BOOL HeapFree( HANDLE hHeap, DWORD dwFlags, LPVOID lpMem ); | 其中,hHeap为要包含要释放内存块的堆的句柄;参数dwFlags为堆栈的释放选项可以是0,也可以是HEAP_NO_SERIALIZE;最后的参数lpMem为指向内存块的指针。如果函数成功执行,将释放指定的内存块,并返回TRUE。该函数的主要作用是可以用来帮助堆管理器回收某些不使用的物理存储器以腾出更多的空闲空间,但是并不能保证一定会成功。 最后,在程序退出前或是应用程序不再需要其创建的堆了,可以调用HeapDestory()函数将其销毁。该函数只包含一个参数--待销毁的堆的句柄。HeapDestory()的成功执行将可以释放堆中包含的所有内存块,也可将堆占用的物理存储器和保留的地址空间区域全部重新返回给系统并返回TRUE。该函数只对由HeapCreate()显式创建的堆起作用,而不能销毁进程的默认堆,如果强行将由GetProcessHeap()得到的默认堆的句柄作为参数去调用HeapDestory(),系统将会忽略对该函数的调用。 7 对new与delete操作符的重载
new与delete内存空间动态分配操作符是C++中使用堆进行内存管理的一种常用方式,在程序运行过程中可以根据需要随时通过这两个操作符建立或删除堆对象。new操作符将在堆中分配一个足够大小的内存块以存放指定类型的对象,如果每次构造的对象类型不同,则需要按最大对象所占用的空间来进行分配。new操作符在成功执行后将返回一个类型与new所分配对象相匹配的指针,如果不匹配则要对其进行强制类型转换,否则将会编译出错。在不再需要这个对象的时候,必须显式调用delete操作符来释放此空间。这一点是非常重要的,如果在预分配的缓冲里构造另一个对象之前或者在释放缓冲之前没有显式调用delete操作符,那么程序将产生不可预料的后果。在使用delete操作符时,应注意以下几点: 1) 它必须使用于由运算符new返回的指针 2) 该操作符也适用于NULL指针 3) 指针名前只用一对方括号符,并且不管所删除数组的维数,忽略方括号内的任何数字
class CVMShow{ private: static HANDLE m_sHeap; static int m_sAllocedInHeap; public: LPVOID operator new(size_t size); void operator delete(LPVOID pVoid); }
…… HANDLE m_sHeap = NULL; int m_sAllocedInHeap = 0; LPVOID CVMShow::operator new(size_t size) { if (m_sHeap == NULL) m_sHeap = HeapCreate(HEAP_GENERATE_EXCEPTIONS, 0, 0); LPVOID pVoid = HeapAlloc(m_sHeap, 0, size); if (pVoid == NULL) return NULL; m_sAllocedInHeap++; return pVoid; } void CVMShow::operator delete(LPVOID pVoid) { if (HeapFree(m_sHeap, 0, pVoid)) m_sAllocedInHeap--; if (m_sAllocedInHeap == 0) { if (HeapDestory(m_sHeap)) m_sHeap = NULL; } }
| 在程序中除了直接用上述方法使用new和delete来建立和删除堆对象外,还可以通过为C++类重载new和delete操作符来方便地利用堆栈函数。上面的代码对它们进行了简单的重载,并通过静态变量m_sHeap和m_sAllocedInHeap在类CVMShow的所有实例间共享唯一的堆句柄(因为在这里CVMShow类的所有实例都是在同一个堆中进行内存分配的)和已分配类对象的计数。这两个静态变量在代码开始执行时被分别初始化为NULL指针和0计数。 重载的new操作符在第一次被调用时,由于静态变量m_sHeap为NULL标志着堆尚未创建,就通过HeapCreate()函数创建一个堆并返回堆句柄到m_sHeap。随后根据入口参数size所指定的大小在堆中分配内存,同时已分配内存块计数器m_sAllocedInHeap累加。在该操作符的以后调用过程中,由于堆已经创建,故不再创建堆,而是直接在堆中分配指定大小的内存块并对已分配的内存块个数进行计数。 在CVMShow类对象不再被应用程序所使用时,需要将其撤消,由重载的delete操作符完成此工作。delete操作符只接受一个LPVOID型参数,即被删除对象的地址。该函数在执行时首先调用HeapFree()函数将指定的已分配内存的对象释放并对已分配内存计数递减1。如果该计数不为零则表明当前堆中的内存块没有全部释放,堆暂时不予撤消。如果m_sAllocedInHeap计数减到0,则堆中已释放完所有的CVMShow对象,可以调用HeapDestory()函数将堆销毁,并将堆句柄m_sHeap设置为NULL指针。这里在撤消堆后将堆句柄设置为NULL指针的操作是完全必要的。如果不执行该操作,当程序再次调用new操作符去分配一个CVMShow类对象时将会认为堆是存在的而会试图在已撤消的堆中去分配内存,显然将会导致失败。 象CVMShow这样设计的类通过对new和delete操作符的重载,并且在一个堆中为所有的CVMShow类对象进行分配,可以节省在为每一个类都创建堆的分配开销与内存。这样的处理还可以让每一个类都拥有属于自己的堆,并且允许派生类对其共享,这在程序设计中也是比较好的一种处理方法。 8 小结 在使用堆时有时会造成系统运行速度的减慢,通常是由以下原因造成的:分配操作造成的速度减慢;释放操作造成的速度减慢;堆竞争造成的速度减慢;堆破坏造成的速度减慢;频繁的分配和重分配造成的速度减慢等。其中,竞争是在分配和释放操作中导致速度减慢的问题。基于上述原因,建议不要在程序中过于频繁的使用堆。
原文出处: http://www.cnblogs.com/penglink/archive/2009/02/19/1393838.html
Lua脚本语言入门(目前魔兽使用的可以写在宏内的语言)(中文)-zt
作者: 沐枫
Lua 程序设计初步
作者: 沐枫 (第二人生成员) 版权所有转载请注明原出处
在这篇文章中,我想向大家介绍如何进行Lua程序设计。我假设大家都学过至少一门编程语言,比如Basic或C,特别是C。因为Lua的最大用途是在宿主程序中作为脚本使用的。 Lua 的语法比较简单,学习起来也比较省力,但功能却并不弱。 在Lua中,一切都是变量,除了关键字。请记住这句话。
I. 首先是注释 写一个程序,总是少不了注释的。 在Lua中,你可以使用单行注释和多行注释。 单行注释中,连续两个减号"--"表示注释的开始,一直延续到行末为止。相当于C++语言中的"//"。 多行注释中,由"--[["表示注释开始,并且一直延续到"]]"为止。这种注释相当于C语言中的"/*…*/"。在注释当中,"[["和"]]"是可以嵌套的。 II. Lua编程 经典的"Hello world"的程序总是被用来开始介绍一种语言。在Lua中,写一个这样的程序很简单: print("Hello world") 在Lua中,语句之间可以用分号";"隔开,也可以用空白隔开。一般来说,如果多个语句写在同一行的话,建议总是用分号隔开。 Lua 有好几种程序控制语句,如:
条件控制:if 条件 then … elseif 条件 then … else … end While循环:while 条件 do … end Repeat循环:repeat … until 条件 For循环:for 变量 = 初值,终点值,步进 do … end For循环:for 变量1,变量2,… ,变量N in表或枚举函数 do … end
注意一下,for的循环变量总是只作用于for的局部变量,你也可以省略步进值,这时候,for循环会使用1作为步进值。 你可以用break来中止一个循环。 如果你有程序设计的基础,比如你学过Basic,C之类的,你会觉得Lua也不难。但Lua有几个地方是明显不同于这些程序设计语言的,所以请特别注意。
.语句块 语句块在C++中是用"{"和"}"括起来的,在Lua中,它是用do 和 end 括起来的。比如: do print("Hello") end 你可以在 函数 中和 语句块 中定局部变量。
.赋值语句 赋值语句在Lua被强化了。它可以同时给多个变量赋值。 例如: a,b,c,d=1,2,3,4 甚至是: a,b=b,a -- 多么方便的交换变量功能啊。 在默认情况下,变量总是认为是全局的。假如你要定义局部变量,则在第一次赋值的时候,需要用local说明。比如: local a,b,c = 1,2,3 -- a,b,c都是局部变量
.数值运算 和C语言一样,支持 +, -, *, /。但Lua还多了一个"^"。这表示指数乘方运算。比如2^3 结果为8, 2^4结果为16。 连接两个字符串,可以用".."运处符。如: "This a " .. "string." -- 等于 "this a string"
.比较运算 < > <= >= == ~= 分别表示 小于,大于,不大于,不小于,相等,不相等 所有这些操作符总是返回true或false。 对于Table,Function和Userdata类型的数据,只有 == 和 ~=可以用。相等表示两个变量引用的是同一个数据。比如: a={1,2} b=a print(a==b, a~=b) -- true, false a={1,2} b={1,2} print(a==b, a~=b) -- false, true
.逻辑运算 and, or, not 其中,and 和 or 与C语言区别特别大。 在这里,请先记住,在Lua中,只有false和nil才计算为false,其它任何数据都计算为true,0也是true! and 和 or的运算结果不是true和false,而是和它的两个操作数相关。 a and b:如果a为false,则返回a;否则返回b a or b:如果 a 为true,则返回a;否则返回b
举几个例子: print(4 and 5) --> 5 print(nil and 13) --> nil print(false and 13) --> false print(4 or 5) --> 4 print(false or 5) --> 5
在Lua中这是很有用的特性,也是比较令人混洧的特性。 我们可以模拟C语言中的语句:x = a? b : c,在Lua中,可以写成:x = a and b or c。 最有用的语句是: x = x or v,它相当于:if not x then x = v end 。
.运算符优先级,从高到低顺序如下: ^ not - (一元运算) * / + - ..(字符串连接) < > <= >= ~= == and or
III. 关键字 关键字是不能做为变量的。Lua的关键字不多,就以下几个: and break do else elseif end false for function if in local nil not or repeat return then true until while
IV. 变量类型 怎么确定一个变量是什么类型的呢?大家可以用type()函数来检查。Lua支持的类型有以下几种:
Nil 空值,所有没有使用过的变量,都是nil。nil既是值,又是类型。 Boolean 布尔值 Number 数值,在Lua里,数值相当于C语言的double String 字符串,如果你愿意的话,字符串是可以包含'\0'字符的 Table 关系表类型,这个类型功能比较强大,我们在后面慢慢说。 Function 函数类型,不要怀疑,函数也是一种类型,也就是说,所有的函数,它本身就是一个变量。 Userdata 嗯,这个类型专门用来和Lua的宿主打交道的。宿主通常是用C和C++来编写的,在这种情况下,Userdata可以是宿主的任意数据类型,常用的有Struct和指针。 Thread 线程类型,在Lua中没有真正的线程。Lua中可以将一个函数分成几部份运行。如果感兴趣的话,可以去看看Lua的文档。
V. 变量的定义 所有的语言,都要用到变量。在Lua中,不管你在什么地方使用变量,都不需要声明,并且所有的这些变量总是全局变量,除非,你在前面加上"local"。 这一点要特别注意,因为你可能想在函数里使用局部变量,却忘了用local来说明。 至于变量名字,它是大小写相关的。也就是说,A和a是两个不同的变量。 定义一个变量的方法就是赋值。"="操作就是用来赋值的 我们一起来定义几种常用类型的变量吧。 A. Nil 正如前面所说的,没有使用过的变量的值,都是Nil。有时候我们也需要将一个变量清除,这时候,我们可以直接给变量赋以nil值。如: var1=nil -- 请注意 nil 一定要小写
B. Boolean 布尔值通常是用在进行条件判断的时候。布尔值有两种:true 和 false。在Lua中,只有false和nil才被计算为false,而所有任何其它类型的值,都是true。比如0,空串等等,都是true。不要被C语言的习惯所误导,0在Lua中的的确确是true。你也可以直接给一个变量赋以Boolean类型的值,如: varboolean = true
C. Number 在Lua中,是没有整数类型的,也不需要。一般情况下,只要数值不是很大(比如不超过100,000,000,000,000),是不会产生舍入误差的。在很多CPU上,实数的运算并不比整数慢。 实数的表示方法,同C语言类似,如: 4 0.4 4.57e-3 0.3e12 5e+20
D. String 字符串,总是一种非常常用的高级类型。在Lua中,你可以非常方便的定义很长很长的字符串。 字符串在Lua中有几种方法来表示,最通用的方法,是用双引号或单引号来括起一个字符串的,如: "This is a string." 和C语言相同的,它支持一些转义字符,列表如下: \a bell \b back space \f form feed \n newline \r carriage return \t horizontal tab \v vertical tab \\ backslash \" double quote \' single quote \[ left square bracket \] right square bracket
由于这种字符串只能写在一行中,因此,不可避免的要用到转义字符。加入了转义字符的串,看起来实在是不敢恭维,比如: "one line\nnext line\n\"in quotes\", 'in quotes'" 一大堆的"\"符号让人看起来很倒胃口。如果你与我有同感,那么,我们在Lua中,可以用另一种表示方法:用"[["和"]]"将多行的字符串括起来,如: page = [[ <HTML> <HEAD> <TITLE>An HTML Page</TITLE> </HEAD> <BODY> <A HREF="http://www.lua.org">Lua</A> [[a text between double brackets]] </BODY> </HTML> ]]
值得注意的是,在这种字符串中,如果含有单独使用的"[["或"]]"就仍然得用"\["或"\]"来避免歧义。当然,这种情况是极少会发生的。
E. Table 关系表类型,这是一个很强大的类型。我们可以把这个类型看作是一个数组。只是C语言的数组,只能用正整数来作索引;在Lua中,你可以用任意类型来作数组的索引,除了nil。同样,在C语言中,数组的内容只允许一种类型;在Lua中,你也可以用任意类型的值来作数组的内容,除了nil。 Table的定义很简单,它的主要特征是用"{"和"}"来括起一系列数据元素的。比如:
T1 = {} -- 定义一个空表 T1[1]=10 -- 然后我们就可以象C语言一样来使用它了。 T1["John"]={Age=27, Gender="Male"} 这一句相当于: T1["John"]={} -- 必须先定义成一个表,还记得未定义的变量是nil类型吗 T1["John"]["Age"]=27 T1["John"]["Gender"]="Male" 当表的索引是字符串的时候,我们可以简写成: T1.John={} T1.John.Age=27 T1.John.Gender="Male" 或 T1.John{Age=27, Gender="Male"} 这是一个很强的特性。
在定义表的时候,我们可以把所有的数据内容一起写在"{"和"}"之间,这样子是非常方便,而且很好看。比如,前面的T1的定义,我们可以这么写:
T1= { 10, -- 相当于 [1] = 10 [100] = 40, John= -- 如果你原意,你还可以写成:["John"] = { Age=27, -- 如果你原意,你还可以写成:["Age"] =27 Gender=Male -- 如果你原意,你还可以写成:["Gender"] =Male }, 20 -- 相当于 [2] = 20 }
看起来很漂亮,不是吗?我们在写的时候,需要注意三点: 第一,所有元素之间,总是用逗号","隔开; 第二,所有索引值都需要用"["和"]"括起来;如果是字符串,还可以去掉引号和中括号; 第三,如果不写索引,则索引就会被认为是数字,并按顺序自动从1往后编;
表类型的构造是如此的方便,以致于常常被人用来代替配置文件。是的,不用怀疑,它比ini文件要漂亮,并且强大的多。
F. Function 函数,在Lua中,函数的定义也很简单。典型的定义如下: function add(a,b) -- add 是函数名字,a和b是参数名字 return a+b -- return 用来返回函数的运行结果 end
请注意,return语言一定要写在end之前。假如你非要在中间放上一句return,那么请写成:do return end。 还记得前面说过,函数也是变量类型吗?上面的函数定义,其实相当于: add = function (a,b) return a+b end 当你重新给add赋值时,它就不再表示这个函数了。你甚至可以赋给add任意数据,包括nil (这样,你就清除了add变量)。Function是不是很象C语言的函数指针呢?
和C语言一样,Lua的函数可以接受可变参数个数,它同样是用"…"来定义的,比如: function sum (a,b,…) 如果想取得…所代表的参数,可以在函数中访问arg局部变量(表类型)得到。 如 sum(1,2,3,4) 则,在函数中,a = 1, b = 2, arg = {3, 4} 更可贵的是,它可以同时返回多个结果,比如: function s() return 1,2,3,4 end a,b,c,d = s() -- 此时,a = 1, b = 2, c = 3, d = 4 前面说过,表类型可以拥有任意类型的值,包括函数!因此,有一个很强大的特性是,拥有函数的表,哦,我想更恰当的应该说是对象吧。Lua可以使用面向对象编程了。不信?那我举例如下:
t = { Age = 27 add = function(self, n) self.Age = self.Age+n end } print(t.Age) -- 27 t.add(t, 10) print(t.Age) -- 37
不过,t.add(t,10) 这一句实在是有点土对吧?没关系,在Lua中,你可以简写成: t:add(10) -- 相当于 t.add(t,10)
G. Userdata 和 Thread 这两个类型的话题,超出了本文的内容,就不打算细说了。
VI. 结束语 就这么结束了吗?当然不是,接下来,需要用Lua解释器,来帮助你理解和实践了。这篇小文只是帮助你大体了解Lua的语法。如果你有编程基础,相信会很快对Lua上手了。 就象C语言一样,Lua提供了相当多的标准函数来增强语言的功能。使用这些标准函数,你可以很方便的操作各种数据类型,并处理输入输出。有关这方面的信息,你可以参考《Programming in Lua 》一书,你可以在网络上直接观看电子版,网址为:http://www.lua.org/pil/index.html 当然,Lua的最强大的功能是能与宿主程序亲蜜无间的合作,因此,下一篇文章,我会告诉大家,如何在你的程序中使用Lua语言作为脚本,使你的程序和Lua脚本进行交互。 --------------------------------------------------------------------------------------------------------- 使用流程 1. 函数的使用 以下程序演示了如何在Lua中使用函数, 及局部变量 例e02.lua -- functions function pythagorean(a, b) local c2 = a^2 + b^2 return sqrt(c2) end print(pythagorean(3,4))
运行结果 5
程序说明 在Lua中函数的定义格式为: function 函数名(参数) ... end 与Pascal语言不同, end不需要与begin配对, 只需要在函数结束后打个end就可以了. 本例函数的作用是已知直角三角形直角边, 求斜边长度. 参数a,b分别表示直角边长, 在函数内定义了local形变量用于存储斜边的平方. 与C语言相同, 定义在函数内的代 码不会被直接执行, 只有主程序调用时才会被执行. local表示定义一个局部变量, 如果不加local刚表示c2为一个全局变量, local的作用域 是在最里层的end和其配对的关键字之间, 如if ... end, while ... end等。全局变量的 作用域是整个程序。
2. 循环语句 例e03.lua -- Loops for i=1,5 do print("i is now " .. i) end
运行结果 i is now 1 i is now 2 i is now 3 i is now 4 i is now 5
程序说明 这里偶们用到了for语句 for 变量 = 参数1, 参数2, 参数3 do 循环体 end 变量将以参数3为步长, 由参数1变化到参数2 例如: for i=1,f(x) do print(i) end for i=10,1,-1 do print(i) end
这里print("i is now " .. i)中,偶们用到了..,这是用来连接两个字符串的, 偶在(1)的试试看中提到的,不知道你们答对了没有。 虽然这里i是一个整型量,Lua在处理的时候会自动转成字符串型,不需偶们费心。
3. 条件分支语句 例e04.lua -- Loops and conditionals for i=1,5 do print(“i is now “ .. i) if i < 2 then print(“small”) elseif i < 4 then print(“medium”) else print(“big”) end end
运行结果 i is now 1 small i is now 2 medium i is now 3 medium i is now 4 big i is now 5 big
程序说明 if else用法比较简单, 类似于C语言, 不过此处需要注意的是整个if只需要一个end, 哪怕用了多个elseif, 也是一个end. 例如 if op == "+" then r = a + b elseif op == "-" then r = a - b elseif op == "*" then r = a*b elseif op == "/" then r = a/b else error("invalid operation") end
4.试试看 Lua中除了for循环以外, 还支持多种循环, 请用while...do和repeat...until改写本文中的for程序 ---------------------------------------------------------------------------------------------------------- 数组的使用
1.简介 Lua语言只有一种基本数据结构, 那就是table, 所有其他数据结构如数组啦, 类啦, 都可以由table实现.
2.table的下标 例e05.lua -- Arrays myData = {} myData[0] = “foo” myData[1] = 42
-- Hash tables myData[“bar”] = “baz”
-- Iterate through the -- structure for key, value in myData do print(key .. “=“ .. value) end
输出结果 0=foo 1=42 bar=baz
程序说明 首先定义了一个table myData={}, 然后用数字作为下标赋了两个值给它. 这种 定义方法类似于C中的数组, 但与数组不同的是, 每个数组元素不需要为相同类型, 就像本例中一个为整型, 一个为字符串.
程序第二部分, 以字符串做为下标, 又向table内增加了一个元素. 这种table非常 像STL里面的map. table下标可以为Lua所支持的任意基本类型, 除了nil值以外.
Lua对Table占用内存的处理是自动的, 如下面这段代码 a = {} a["x"] = 10 b = a -- `b' refers to the same table as `a' print(b["x"]) --> 10 b["x"] = 20 print(a["x"]) --> 20 a = nil -- now only `b' still refers to the table b = nil -- now there are no references left to the table b和a都指向相同的table, 只占用一块内存, 当执行到a = nil时, b仍然指向table, 而当执行到b=nil时, 因为没有指向table的变量了, 所以Lua会自动释放table所占内存
3.Table的嵌套 Table的使用还可以嵌套,如下例 例e06.lua -- Table ‘constructor’ myPolygon = { color=“blue”, thickness=2, npoints=4; {x=0, y=0}, {x=-10, y=0}, {x=-5, y=4}, {x=0, y=4} }
-- Print the color print(myPolygon[“color”])
-- Print it again using dot -- notation print(myPolygon.color)
-- The points are accessible -- in myPolygon[1] to myPolygon[4]
-- Print the second point’s x -- coordinate print(myPolygon[2].x)
程序说明 首先建立一个table, 与上一例不同的是,在table的constructor里面有{x=0,y=0}, 这是什么意思呢? 这其实就是一个小table, 定义在了大table之内, 小table的 table名省略了. 最后一行myPolygon[2].x,就是大table里面小table的访问方式. ----------------------------------------------------------------------------------------------------------- 如何简化你的宏.
虽然以上介绍让我们了解道宏可以完成非常强大的功能,但暴雪实在太小气了,仅仅只给我们255个字符来编写宏的内容,假如你的宏的功能比较罗嗦,那就很麻烦了,所以以下我介绍一下一些简化宏的小技巧:
1、定义全局变量 看完之前Lua介绍的人该都知道把,在Lua里,所有的变量都是全局变量,也就是说任何一个变量只要你在开始游戏后做过定义,那么到游戏结束时只要你不重新定义他都是有效的。但为了不让我们自己不混淆做全局用的变量和局部使用的变量,我们可以采用大小写区分的办法,即大写一律做为全局变量使用,小写都用局部变量。 这样,我们可以在一个宏里把自己常用的魔法/技能都定义成变量来表示,比如我是个术士,就可以这样: F="腐蚀术(等级 3)" X="献祭(等级 3)"....... 之后,我们要使用这样魔法的时候,只要直接用F或X来代替就可以了,连""都可以省掉,是不是很方便呢~ 或者还可以把一些常见的API函数变量也自己定义: T="target" P="player"..... 使用的时候和上面一样。
2、自定义函数 说实在话,魔兽的有些函数实在长的过头,很多时候珍贵的字节都给函数占去了。所以必要的时候我们就得用自定义函数的方法去简化这些函数。 自定义函数的语句为: function 函数名称(函数变量1、函数变量2....) return 函数返回值 end 比如,使用法术的这个函数是CastByName(),我们可以在宏里这样写: /scirpt function C(a) CastByName(a) end 运行后,我们其他宏使用法术就只要直接用C()就可以了,是不是很方便呢? 或是说话的函数: /script function S(a) SendChatMessage(a,"SAY") end 之后你要控制人物说话就用S()就可以了。
如果是有返回值的函数: /script function N(a) return UNitName(a) --return之后就是表示函数的返回值,但return必须在end前面. end 如果以后你要调用目标的名字,直接用 x=N("target"),如果按前面第一点定义了全局变量的话,更简单x=N(T)。
这样,我们就可以把重要的字节都用在宏的判断内容上,而不是沉长的函数上了。如果你还有什么更好的简化方法,可以跟贴哦。 ------------------------------------------------------------------------------------------------------- 关于背包物品使用整理类的宏的制作
由于游戏提供的函数无法直接由物品名称调用该物品,所以通常简单的使用物品宏是比较麻烦的,一定要把使用的物品放在背包内特定的位置
;或则大多术士都需要的问题,能随时监视自己的灵魂碎片(当然,有插件可以做到这一点)。
以下我写写关于如何制作这类宏:
首先,我们要在背包里找到自己需要的东西,必须用循环里遍历这些包。由于放的位置有2个参数,1个是包的编号,一个是包内槽位的编号,
所以我们需要一个循环嵌套来搜索:
以下假设我们身上都是16格的包: for bag=0,4,1 do --包的编号为从右到左,0,1,2,3,4 for cw=1,16,1 do --槽位的编号为上到下,左到右 1,2,3,4,5......16 .............. --这里我们可以写如判断物品是否为我们需要的东西的语句 end --表示内循环结束 end --外循环结束
或者用其他方式做这个循环: While循环:while 条件 do … end
Repeat循环:repeat … until 条件
然后,要处理的是物品的判断: 我们有两个函数可以使用 GetContainerItemLink() 和 GetContainerItemInfo() 这两个函数使用的变量都是2个,一个是包的编号,一个是槽位的编号,但他们的返回值不同
GetContainerItemLink()是返回一个带着物品名字的连接,如果你用聊天函数把返回值说出来就可以看到,说出来的不光是物品的名称,还是
一个可以连接到物品详细内容窗口的连接。
比如,你的包里4,1的位置放了一块熊肉,那么用/script SendChatMessage(GetContainerItemLink(4,1),"SAY")后,就可以看到自己说“[熊
肉]”,而且用鼠标点一下说的内容,还可以弹出一个描写这块肉的窗口。
但要注意,直接用"[熊肉]"这样字符串来判断这个物品是不行的,例如:
if GetContainerItemLink(4,1)=="[熊肉]" then ..... end 这个判断是无效的。
正确的方法是,先把要做判断的物品的赋一个变量,再用变量做出判断:
rou=GetContainerItemLink(4,1) --把物品连接值赋给rou
if GetContainerItemLink(4,1)==rou then ..... end --现在就可以正常判断物品了
最后要注意的是,这个函数无法对术士的灵魂碎片做出正确的判断,意思就是,虽然灵魂碎片用这个函数显示出来是一样的,但这个函数却认
为所有的灵魂碎片都是不同的东西,即你把这个灵魂碎片的连接赋给一个变量后,这个变量就只能判断这个灵魂碎片,其他的灵魂碎片就无法
作出判断,奇怪把。所以要判断灵魂碎片,就必须用到第二个函数GetContainerItemInfo()
GetContainerItemInfo()的返回值非常多,几乎所有的物品信息都可以返回,但我们这里判断只用它返回的第一个值。 我们可以先用聊天函数来看看第一个返回值是什么样子的: /script a=GetContainerItemInfo(4,1) SendChatMessage(a,"SAY")
可以看到,返回值相当长的英文,但物品的关键字是在后面。
这样,我们就有2种方法来使用这个函数来判断物品。
1、和前一个函数的方法一样,用变量存储值后再判断,前提是要把判断的物品放在特定的位置赋一下值。 2、只使用特定物品,把物品的判断关键字写在函数里,然后用string.find()来判断他。 例子:某物品的关键字是bd if string.find(GetContainerItemInfo(4,1),bd) then .....end --判断包1,4位置是否存在关键字为bd物品。
接着要处理的是物品的使用和交换。 使用特定背包位置的物品函数:UseContainerItem(index,slot) 这个好理解,不用多解释了把。
拾取/放下物品的函数:PickupContainerItem(index,slot) 这个函数有意思,你鼠标上没抓着东西的时候就是帮你拿起特定位置的物品,有的话就变成放下物品到特定的位置并交换拿起该位置的物品。
所以要完成2个物品在包内的交换要使用3次这个函数: PickupContainerItem(4,1) --拿起4,1位置的物品 PickupContainerItem(1,4) --放在1,4位置并拿起1,4位置的物品 PickupContainerItem(4,1) --把1,4位置的物品放在4,1位置
好拉,把以上几点组合后宏就基本完成了:
下面的例子是关于灵魂碎片的整理,把前4个包的灵魂碎片全放到最后一个包内:
/script bag=0 cw=1 sc=1 --定义好变量,bag是包的编号,cw表示查找包的槽位,sc指向最后一个包内的槽位 for bag=0,3,1 do --从0号包开始,到3号包结束,最后一个包不搜索。 for cw=1,16,1 do --这里假设所有的包都是16个槽位的,如果没那么多槽位的包也可以用。 if GetContainerItemLink(bag,cw)~=nil --判断这个槽位是否是空的,是空就直接跳到下一个槽位 then if string.find(GetContainerItemInfo(bag,cw),"Gem") --判断这个槽位里是否是灵魂碎片,Gem为灵魂碎片的关键字 then while string.find(GetContainerItemInfo(4,sc),"Gem") do sc=sc+1 end --这是一个小循环,用于判断最后一个包里原来是否已经有灵魂碎片,有的话就指向包的下一个槽位 PickupContainerItem(bag,cw) PickupContainerItem(4,sc) PickupContainerItem(bag,cw) --这3句控制灵魂碎片和最后一个包内物品的交换 sc=sc+1 --重要,不能忘记这个,每放置好一个碎片后就要把最后一个包的 槽位指针指向下一个槽位,上面的小循环是无法判断刚刚放好的碎片的。 end end end end -循环结束
完了么,当然不行。。。因为宏的限制是255个字。所以要简化我们的宏。
最长的内容估计就是函数了,就先从简化函数开始:
建立以下宏:
/script function P(c,d) PickupContainerItem(c,d) end /script function I(e,f) if GetContainerItemInfo(e,f) then return string.find(GetContainerItemInfo(e,f),"Gem") else return nil end end
原来的宏就变成了:
/script bag=0 cw=1 sc=1 for bag=0,3,1 do for cw=1,16,1 do if G(bag,cw)~=nil then if I(bag,cw) then while I(4,sc) do sc=sc+1 end P(bag,cw) P(4,sc) P(bag,cw) sc=sc+1 end end end end
多余的变量定义和过长的变量都可以更改:
/script s=1 for g=0,3 do for w=1,16 do if G(g,w) then if I(g,w) then while I(4,s) do s=s+1 end P(g,w) P(4,s) P(g,w) s=s+1 end end end end
现在写的下了吧。呵呵,至于使用物品的宏我虽然已经写好了,但没有测试过,等测试没问题后再放出来把。有兴趣的朋友也可以自己写写。
但要注意一点,使用物品的宏只要找到物品就可以马上跳出循环,所以用Repeat循环做比较合适
|