|
#
多數(shù)情況下,在游戲開發(fā)過程中,我們需要經(jīng)常用變換來設(shè)置角色的變換。下面以平移、縮放、旋轉(zhuǎn)來說明一點(diǎn)在矩陣變換中需要注意的地方。 假設(shè)有如下數(shù)據(jù): D3DXMatrix rotateMat; D3DXMatrix scaleMat; D3DXMatrix translateMat; ...... // 這里的 ...... 表示,經(jīng)過了一系列的變換,在接下來的代碼中,rotateMat、scalMat、translateMat已經(jīng)是經(jīng)過變換的了。 D3DXMatrix worldMat;//該矩陣用于保存上面三個(gè)合成的最終變換信息 D3DXMatrixIdentity(&worldMat); D3DXMatrixMultiply(&worldMat, &rotateMat, &worldMat);//注意格式,需要如此寫。 D3DXMatrixMultiply(&worldMat, &scaleMat, &worldMat); //注意格式,需要如此寫。 D3DXMatrixMultiply(&worldMat, &translateMat, &world);//注意格式,需要如此寫。
1) TdxBarApplicationMenu 設(shè)置 ------------------------------------------ 只需要設(shè)置:BarManager屬性即可。
2) TdxRibbon 設(shè)置 ------------------------- a) ApplicationButton -> Menu 設(shè)置為上面 1) 的 AppMenu b) ApplicationButton -> Visible := false; // 因?yàn)槲也恍枰笊辖堑哪莻€(gè)圓圓的菜單 c) BarManager 需要設(shè)置 c) PopupMenuItems 屬性下的所有項(xiàng)都設(shè)置為false // 因?yàn)槲也恍枰笊辖堑膱A圓的菜單右邊有那些小小的快捷鍵 d) QuickAccessToolbar -> ToolBar 需要設(shè)置 e) ShowTabHeaders 需要設(shè)置 f) SupportNonClientDrawing 屬性設(shè)置為 true g) Tabs 屬性展現(xiàn)后,將所有節(jié)點(diǎn)全部刪除掉
最終實(shí)現(xiàn)界面如下:

矩陣、歐拉角、軸-角對(duì)、四元數(shù)隨筆
一、矩陣 在 3D 游戲中,可以使用矩陣來表示一個(gè)物體的旋轉(zhuǎn)。 1) 優(yōu)點(diǎn): 個(gè)人認(rèn)為,理解起來最為直觀。 像現(xiàn)成的DXSDK庫中也提供了十分完善的相關(guān)接口 一個(gè)矩陣即可表示多種變換的組合 2) 缺點(diǎn): 每次計(jì)算都會(huì)產(chǎn)生誤差,因此,需要經(jīng)常規(guī)范化。 耗的內(nèi)存較多些。 二、歐拉角 歐拉角指的是:以世界坐標(biāo)系為參考坐標(biāo)系(一定記住是世界坐標(biāo)系),使用x,y,z三個(gè)值來分別表示繞(世界的)x軸、y軸、z軸旋轉(zhuǎn)的角度量值。其取值是在[0, 360]間。一般用roll, pitch, yaw來表示這些分量的旋轉(zhuǎn)值。因?yàn)槭且允澜缱鴺?biāo)系為參考坐標(biāo)系,因此每一次的旋轉(zhuǎn)都不會(huì)影響到后續(xù)的旋轉(zhuǎn)轉(zhuǎn)軸。即:它無法表示任意軸的旋轉(zhuǎn)。 1) 優(yōu)點(diǎn): 理解起來很直觀。 2) 缺點(diǎn): 會(huì)有萬向鎖問題。 三、軸-角對(duì) 其實(shí)軸-角對(duì)與歐拉角(個(gè)人認(rèn)為)是有一定的關(guān)系的。因?yàn)闅W拉角說的是分別(注意:是分別)繞(以世界坐標(biāo)系為參考坐標(biāo)系的)三個(gè)軸旋轉(zhuǎn)一定的角度。其實(shí)這三次的旋轉(zhuǎn)可以最終轉(zhuǎn)換到一次變換。即:最終可表示為:繞某一旋轉(zhuǎn)軸旋轉(zhuǎn)一定角度的變換。(意思就是說:那三次變換我們最終可以計(jì)算出旋轉(zhuǎn)軸以及繞該旋轉(zhuǎn)軸旋轉(zhuǎn)的角度量)。 1) 缺點(diǎn): 軸-角對(duì)表示法:插值不平滑,可能會(huì)有跳躍。(文檔上說,歐拉角同樣存在這個(gè)問題) 2) 優(yōu)點(diǎn): 可解決歐拉角的萬向鎖問題。 四、四元數(shù) 四元數(shù)定義:q = w + xi + yj + zk 注意: 1) 四元數(shù)可以歸一化,并且只有歸一化的四元數(shù)才用來描述旋轉(zhuǎn) 2) 四元數(shù)與軸-角對(duì)很像。因?yàn)樗脑獢?shù)描述的也是一個(gè)旋轉(zhuǎn)軸與一個(gè)繞著該旋轉(zhuǎn)軸旋轉(zhuǎn)的量值(即:角度或弧度)。但四元數(shù)與軸-角對(duì)不等價(jià)。它們的關(guān)系如下: 假如:軸-角對(duì)的值如下: 軸為:n 角為:theta 則,對(duì)應(yīng)的四元數(shù)中的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) 四元數(shù)的乘法意義: Q = Q1 * Q2表示的是:Q先做Q2的旋轉(zhuǎn),再做Q1的旋轉(zhuǎn)的結(jié)果,而且多個(gè)四元數(shù)的旋轉(zhuǎn)也是要以合并的。 4) 四元數(shù)做一次乘法需要16次乘法和加法,而3x3矩陣需要27次。所以有多次旋轉(zhuǎn)操作時(shí),使用四元數(shù)計(jì)算效率更高些。 5) 四元數(shù)的插值過度平滑。最常用的是線性插值。
原文轉(zhuǎn)自: http://www.gesoftfactory.com/developer/Transform.htm
(提示:原文有圖片。)
三維變換
在使用三維圖形的應(yīng)用程序中,可以用變換做以下事情:
- 描述一個(gè)物體相對(duì)于另一個(gè)物體的位置。
- 旋轉(zhuǎn)并改變物體的大小。
- 改變觀察的位置、方向和視角。
可以用一個(gè)4 x 4矩陣將任意點(diǎn)(x,y,z)變換為另一個(gè)點(diǎn)(x',y',z')。
對(duì)(x,y,z)和矩陣執(zhí)行以下操作產(chǎn)生點(diǎn)(x',y',z')。
最常見的變換是平移、旋轉(zhuǎn)和縮放。可以將產(chǎn)生這些效果的矩陣合并成單個(gè)矩陣,這樣就可以一次計(jì)算多種變換。例如,可以構(gòu)造單個(gè)矩陣,對(duì)一系列的點(diǎn)進(jìn)行平移和旋轉(zhuǎn)。更多信息,請(qǐng)參閱矩陣串接。
矩陣以行列順序書寫。一個(gè)沿每根軸均勻縮放頂點(diǎn)的矩陣,也稱為統(tǒng)一縮放,用如下數(shù)學(xué)符號(hào)表示。
在C++應(yīng)用程序中,Microsoft® Direct3D®使用D3DMATRIX結(jié)構(gòu),將矩陣聲明為一個(gè)二維數(shù)組。以下示例代碼顯示了如何初始化一個(gè)D3DMATRIX結(jié)構(gòu),使之成為一個(gè)統(tǒng)一縮放矩陣。
// 本例中,s為浮點(diǎn)類型的變量
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
};
平移
以下變換將點(diǎn)(x,y,z)平移到一個(gè)新的點(diǎn)(x',y',z')。
可以在C++應(yīng)用程序中手工創(chuàng)建一個(gè)平移矩陣。以下示例代碼顯示了的一個(gè)函數(shù)的源碼,該函數(shù)創(chuàng)建一個(gè)矩陣用于平移頂點(diǎn)。 D3DXMATRIX Translate(const float dx, const float dy, const float dz) { D3DXMATRIX ret; D3DXMatrixIdentity(&ret); // 由Direct3DX實(shí)現(xiàn) ret(3, 0) = dx; ret(3, 1) = dy; ret(3, 2) = dz; return ret; } // 平移結(jié)束
為了方便,Direct3DX工具庫提供了D3DXMatrixTranslation函數(shù)。
縮放
以下變換用指定值縮放點(diǎn)(x,y,z)的x-,y-和z-方向,產(chǎn)生新的點(diǎn)(x',y',z')。
旋轉(zhuǎn)
這里描述的變換是基于左手坐標(biāo)系的,也許和別處見到的變換矩陣不同。更多信息,請(qǐng)參閱三維坐標(biāo)系。
以下變換將點(diǎn)(x,y,z)圍繞x軸旋轉(zhuǎn),產(chǎn)生新的點(diǎn)(x',y',z')。
以下變換將點(diǎn)圍繞y軸旋轉(zhuǎn)。
以下變換將點(diǎn)圍繞z軸旋轉(zhuǎn)。
在這些示例矩陣中,希臘字母(θ)表示旋轉(zhuǎn)的角度,以弧度為單位。當(dāng)沿著旋轉(zhuǎn)軸朝原點(diǎn)看時(shí),角度以順時(shí)針方向計(jì)量。
C++應(yīng)用程序可以用Direct3D擴(kuò)展(D3DX)工具庫提供的D3DXMatrixRotationX,D3DXMatrixRotationX和D3DXMatrixRotationX函數(shù)創(chuàng)建旋轉(zhuǎn)矩陣。以下示例是D3DXMatrixRotationX函數(shù)的源碼。 D3DXMATRIX* WINAPI D3DXMatrixRotationX ( D3DXMATRIX *pOut, float angle ) {#if DBG if(!pOut) return NULL; #endif float sin, cos; sincosf(angle, &sin, &cos); // 計(jì)算角度的正弦和余弦值。 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;
矩陣串接
使用矩陣的一個(gè)優(yōu)勢就是可以通過把兩個(gè)以上的矩陣相乘,將它們的效果合并在一起。這意味著,要先旋轉(zhuǎn)一個(gè)建模然后把它平移到某個(gè)位置,無需使用兩個(gè)矩陣,只要把旋轉(zhuǎn)矩陣和平移矩陣相乘,產(chǎn)生一個(gè)包含了所有效果的合成矩陣。這個(gè)過程被稱為矩陣串接,可以寫成以下公式。
在這個(gè)公式中,C是將被創(chuàng)建的合成矩陣,M1到Mn是矩陣C包含的單個(gè)矩陣。雖然大多數(shù)情況下,只需要串接兩三個(gè)矩陣,但實(shí)際上并沒有數(shù)量上的限制。
可以使用D3DXMatrixMultiply函數(shù)進(jìn)行矩陣乘法。
進(jìn)行矩陣乘法時(shí)先后次序是至關(guān)重要的。前面的公式反映了矩陣串接從左到右的規(guī)則。也就是說,用來創(chuàng)建合成矩陣的每個(gè)矩陣產(chǎn)生的直觀效果會(huì)按從左到右的次序出現(xiàn)。下面顯示了一個(gè)典型的世界變換矩陣。想象一下給一個(gè)旋轉(zhuǎn)飛行的碟子創(chuàng)建世界變換矩陣。應(yīng)用程序也許想讓飛行的碟子繞它的中心——建模空間中的y軸——旋轉(zhuǎn),然后把它平移到場景中的另一個(gè)位置。要實(shí)現(xiàn)這樣的效果,首先創(chuàng)建一個(gè)旋轉(zhuǎn)矩陣,然后將它與平移矩陣相乘,如以下公式所示。
在這個(gè)公式中,Ry是繞y軸的旋轉(zhuǎn)矩陣,Tw是平移到世界坐標(biāo)中某個(gè)位置的矩陣。
矩陣相乘的順序很重要,因?yàn)榫仃嚦朔ㄊ遣豢山粨Q的,這和兩個(gè)標(biāo)量相乘不同。將矩陣以相反的順序相乘會(huì)產(chǎn)生這樣的直觀效果:先把飛行中的碟子平移到世界空間中的某個(gè)位置,然后將它圍繞世界坐標(biāo)的原點(diǎn)旋轉(zhuǎn)。
無論創(chuàng)建何種類型的矩陣,都要記住從左到右的規(guī)則,這樣才能保證得到想要的效果。
世界變換
對(duì)世界變換的討論介紹了基本概念,并提供了如何在Microsoft® Direct3D®應(yīng)用程序中設(shè)置世界變換矩陣的細(xì)節(jié)。
什么是世界變換
世界變換將坐標(biāo)從建模空間,在這個(gè)空間中的頂點(diǎn)相對(duì)于建模的局部原點(diǎn)定義,轉(zhuǎn)變到世界空間,在這個(gè)空間中的頂點(diǎn)相對(duì)于場景中所有物體共有的原點(diǎn)定義。本質(zhì)上,世界變換將一個(gè)建模放到世界中,并由此而得名。下圖描繪了世界坐標(biāo)系統(tǒng)和建模的局部坐標(biāo)系統(tǒng)間的關(guān)系。
世界變換可以包含任意數(shù)量的平移、旋轉(zhuǎn)和縮放的合并。有關(guān)對(duì)變換的數(shù)學(xué)討論,請(qǐng)參閱三維變換。
設(shè)置世界矩陣
同任何其它變換一樣,通過將一系列變換矩陣串接成單個(gè)矩陣,應(yīng)用程序可以創(chuàng)建包含這些矩陣全部效果的世界矩陣。最簡單的情況下,建模在世界的原點(diǎn)并且它的局部坐標(biāo)軸與世界空間的方向相同,這時(shí)世界矩陣就是單位矩陣。更通常的情況下,世界矩陣是一系列矩陣的合成,包含一個(gè)平移矩陣,并且根據(jù)需要可能有一個(gè)以上的旋轉(zhuǎn)矩陣。
以下示例,來自一個(gè)用C++編寫的假想三維建模類,使用Direct3D擴(kuò)展(D3DX)工具庫提供的幫助函數(shù)創(chuàng)建了一個(gè)世界矩陣,這個(gè)世界矩陣包含了三個(gè)旋轉(zhuǎn)矩陣,用于調(diào)整三維建模的方向,以及一個(gè)平移矩陣,用來根據(jù)建模在世界空間中的相對(duì)坐標(biāo)重新確定它的位置。 /* * 根據(jù)本示例的目的,假設(shè)以下變量都是有效的并經(jīng)過初始化。 * * 變量m_xPos,m_yPos,m_zPos包含了建模在世界坐標(biāo)中的位置。 * * 變量m_fPitch,m_fYaw和m_fRoll為浮點(diǎn)數(shù),包含了建模的方向, * 用pitch,yaw和roll旋轉(zhuǎn)角表示,以弧度為單位。 */ void C3DModel::MakeWorldMatrix( D3DXMATRIX* pMatWorld ) { D3DXMATRIX MatTemp; // 用于旋轉(zhuǎn)的臨時(shí)矩陣 D3DXMATRIX MatRot; // 最終的旋轉(zhuǎn)矩陣,應(yīng)用于pMatWorld. // 使用從左到右的矩陣串接順序,在旋轉(zhuǎn)之前對(duì)物體在世界空間中 // 的位置進(jìn)行平移。 D3DXMatrixTranslation(pMatWorld, m_xPos, m_yPos, m_zPos); D3DXMatrixIdentity(&MatRot); // 現(xiàn)在將方向變量應(yīng)用于世界矩陣 if(m_fPitch || m_fYaw || m_fRoll) { // 產(chǎn)生并合成旋轉(zhuǎn)矩陣。 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); // 應(yīng)用旋轉(zhuǎn)矩陣,得到最后的世界矩陣。 D3DXMatrixMultiply(pMatWorld, &MatRot, pMatWorld); } }
當(dāng)準(zhǔn)備好世界變換矩陣后,應(yīng)該調(diào)用IDirect3DDevice9::SetTransform方法設(shè)置它,并把第一個(gè)參數(shù)指定D3DTS_WORLD宏。
注意 Direct3D使用應(yīng)用程序設(shè)置的世界和觀察矩陣配置許多內(nèi)部數(shù)據(jù)結(jié)構(gòu)。應(yīng)用程序每次設(shè)置新的世界或觀察矩陣時(shí),系統(tǒng)都要重新計(jì)算相關(guān)的內(nèi)部數(shù)據(jù)結(jié)構(gòu)。頻繁地設(shè)置這些矩陣——例如,每幀上千次——是計(jì)算量很大的。通過將世界矩陣和觀察矩陣串接成一個(gè)世界/觀察矩陣,并將該矩陣設(shè)置為世界矩陣,然后將觀察矩陣設(shè)置為單位矩陣,應(yīng)用程序可以將所需的計(jì)算次數(shù)減到最少。最好保存一份單獨(dú)的世界矩陣和觀察矩陣的副本在高速緩存中,這樣就可以根據(jù)需要修改、串接及重置世界矩陣。為清晰起見,本文檔中的Direct3D示例很少使用這項(xiàng)優(yōu)化。
觀察變換
本節(jié)介紹觀察變換的基本概念,并提供有關(guān)如何在Microsoft® Direct3D®應(yīng)用程序中設(shè)置觀察矩陣的細(xì)節(jié)。信息被分為以下主題。
什么是觀察變換?
觀察變換根據(jù)觀察者在世界空間中的位置,把頂點(diǎn)變換到攝像機(jī)空間。在攝像機(jī)空間中,攝像機(jī),或觀察者,位于原點(diǎn),朝sz軸的正向看去。再次提醒一下,因?yàn)镈irect3D使用左手坐標(biāo)系,所以z軸的正向是朝著場景的。觀察矩陣根據(jù)攝像機(jī)的位置——攝像機(jī)空間的原點(diǎn)——和方向重定位世界中的所有物體。
有兩方法可以創(chuàng)建觀察矩陣。所有情況下,攝像機(jī)在世界空間中的邏輯位置和方向會(huì)被用作起始點(diǎn)來創(chuàng)建觀察矩陣,得到的觀察矩陣會(huì)被應(yīng)用于場景中的三維建模。觀察矩陣平移并旋轉(zhuǎn)物體,將它們放入攝像機(jī)空間中,攝像機(jī)位于原點(diǎn)。創(chuàng)建觀察矩陣的一種方法是把平移矩陣和圍繞每根坐標(biāo)軸旋轉(zhuǎn)的旋轉(zhuǎn)矩陣合并。這種方法使用了以下通用矩陣公式。
在這個(gè)公式中,V是要?jiǎng)?chuàng)建的觀察矩陣,T是在世界中重定位物體的平移矩陣,Rx到Rz分別是繞x軸,y軸和z軸旋轉(zhuǎn)物體的旋轉(zhuǎn)矩陣。平移和旋轉(zhuǎn)矩陣基于攝像機(jī)在世界空間中邏輯位置和方向。因此,如果攝像機(jī)在世界中的邏輯位置是<10,20,100>,那么平移矩陣的目的是沿x軸移動(dòng)物體-10單位,沿y軸移動(dòng)-20單位,沿z軸移動(dòng)-100單位。公式中的旋轉(zhuǎn)矩陣基于攝像機(jī)的方向,根據(jù)攝像機(jī)空間的坐標(biāo)軸與世界空間的坐標(biāo)軸間的夾角決定。例如,如果前面提到的攝像機(jī)是垂直向下放的,那么它的z軸與世界空間的z軸有90度夾角,如下圖所示。
旋轉(zhuǎn)矩陣將角度相同但方向相反的旋轉(zhuǎn)量應(yīng)用于場景中的建模。這個(gè)攝像機(jī)的觀察矩陣包含了一個(gè)繞x軸-90度的旋轉(zhuǎn)。旋轉(zhuǎn)矩陣與平移矩陣合并生成觀察矩陣,觀察矩陣調(diào)整物體在場景中的位置和方向,使它們的頂部朝著攝像機(jī),看起來就好像攝像機(jī)在建模的上方一樣。
設(shè)置觀察矩陣
D3DXMatrixLookAtLH和D3DXMatrixLookAtRH輔助函數(shù)根據(jù)攝像機(jī)的位置和被觀察點(diǎn)創(chuàng)建一個(gè)觀察矩陣。
以下示例代碼創(chuàng)建了一個(gè)用于右手系的觀察矩陣。 D3DXMATRIX out; D3DXVECTOR3 eye(2,3,3); D3DXVECTOR3 at(0,0,0); D3DXVECTOR3 up(0,1,0); D3DXMatrixLookAtRH(&out, &eye, &at, &up);
Direct3D使用應(yīng)用程序設(shè)置的世界矩陣和觀察矩陣配置許多內(nèi)部數(shù)據(jù)結(jié)構(gòu)。每次應(yīng)用程序設(shè)置一個(gè)新的世界矩陣或觀察矩陣,系統(tǒng)都要重新計(jì)算相關(guān)的內(nèi)部數(shù)據(jù)結(jié)構(gòu)。頻繁地設(shè)置這些矩陣——例如,每幀20,000次——計(jì)算量非常大。通過將世界矩陣和觀察矩陣串接成一個(gè)世界/觀察矩陣,并將之設(shè)置為世界矩陣,然后將觀察矩陣設(shè)為單位矩陣,應(yīng)用程序可以將所需的計(jì)算量減到最小。最好保存一份單獨(dú)的世界矩陣和觀察矩陣的副本在高速緩存中,這樣就可以根據(jù)需要修改、串接及重置世界矩陣。為清晰起見,Direct3D示例很少使用這項(xiàng)優(yōu)化。
投影變換
可以認(rèn)為投影變換是控制攝像機(jī)的內(nèi)部參數(shù),這選擇攝像機(jī)的鏡頭有些相似。這是三種類型的變換中最為復(fù)雜的。對(duì)投影變換的討論被分為以下主題。
什么是投影變換?
典型的投影變換就是一個(gè)縮放和透視投影。投影變換將視棱錐轉(zhuǎn)變?yōu)橐粋€(gè)立方體。因?yàn)橐暲忮F的近端比遠(yuǎn)端小,所以這就產(chǎn)生了離攝像機(jī)近的物體被放大的效果,這就是透視如何被應(yīng)用于場景的。
在視棱錐中,攝像機(jī)與觀察變換空間的原點(diǎn)之間的距離被定義為D,因此投影矩陣看起來是這樣:
通過在z方向平移-D,觀察矩陣將攝像機(jī)平移到原點(diǎn)。平移矩陣如下所示:
將平移矩陣與投影矩陣相乘(T*P),得到合成的投影矩陣。如下:
下圖描繪了透視投影如何將視棱錐轉(zhuǎn)變到新的坐標(biāo)空間。注意棱錐變成了立方體,同時(shí)原點(diǎn)從場景的右上角移到了中心。(譯注:應(yīng)該是從前裁剪平面的中心移到了原點(diǎn))
在透視變換中,x和y方向的邊界值是-1和1。Z方向的邊界值分別是,0對(duì)應(yīng)于前平面,1對(duì)應(yīng)于后平面。
這個(gè)矩陣根據(jù)指定的從攝像機(jī)到近裁剪平面的距離,平移并縮放物體,但沒有考慮視角(fov),并且用它為遠(yuǎn)處物體產(chǎn)生的z值可能幾乎相同,這使深度比較變得困難。以下矩陣解決了這些問題,并根據(jù)視區(qū)的縱橫比調(diào)整頂點(diǎn),這使它成為透視投影的一個(gè)很好的選擇。
在這個(gè)矩陣中,Zn是近裁剪平面的z值。變量w,h和Q有以下含義。注意fovw和fovh表示視區(qū)在水平和垂直方向上的視角,以弧度為單位。
對(duì)應(yīng)用程序而言,使用視角的角度定義x和y的比例系數(shù)可能不如使用視區(qū)在水平和垂直方向上的大小(在攝像機(jī)空間中)方便。可以用數(shù)學(xué)推導(dǎo),得出下面兩個(gè)使用視區(qū)大小計(jì)算w和h的公式,它們與前面的公式是等價(jià)的。
在這兩個(gè)公式中,Zn表示近裁剪平面的位置,Vw和Vh變量表示視區(qū)在攝像機(jī)空間的寬和高。
對(duì)于C++應(yīng)用程序而言,這兩個(gè)大小直接對(duì)應(yīng)于D3DVIEWPORT9結(jié)構(gòu)的Width和Height成員。(譯注:雖然直接對(duì)應(yīng),但是不等價(jià)的,因?yàn)?span>Vw和Vh位于攝像機(jī)空間,而Width和Height位于屏幕空間)
無論決定使用什么公式,非常重要的一點(diǎn)是要盡可能將Zn設(shè)得大,因?yàn)榻咏鼣z像機(jī)的z值變化不大。這使得用16位z緩存的深度比較變得有點(diǎn)復(fù)雜。
同世界變換和觀察變換一樣,應(yīng)用程序調(diào)用IDirect3DDevice9::SetTransform方法設(shè)置投影矩陣。
設(shè)置投影矩陣
以下ProjectionMatrix示例函數(shù)設(shè)置了前后裁剪平面,以及在水平和垂直方向上視角的角度。此處的代碼與什么是投影矩陣?主題中討論的方法相似。視角應(yīng)該小于弧度π。 D3DXMATRIX ProjectionMatrix(const float near_plane, // 到近裁剪平面的距離 const float far_plane, // 到遠(yuǎn)裁剪平面的距離 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
在創(chuàng)建矩陣之后,調(diào)用IDirect3DDevice9::SetTransform方法設(shè)置投影矩陣,要將第一個(gè)參數(shù)設(shè)為D3DTS_PROJECTION。
Direct3D擴(kuò)展(D3DX)工具庫提供了以下函數(shù),幫助應(yīng)用程序設(shè)置投影矩陣。
- D3DXMatrixPerspectiveLH
- D3DXMatrixPerspectiveRH
- D3DXMatrixPerspectiveFovLH
- D3DXMatrixPerspectiveFovRH
- D3DXMatrixPerspectiveOffCenterLH
- D3DXMatrixPerspectiveOffCenterRH
W友好投影矩陣
對(duì)于已經(jīng)用世界、觀察和投影矩陣變換過的頂點(diǎn),Microsoft® Direct3D®使用頂點(diǎn)的W成員進(jìn)行深度緩存中基于深度的計(jì)算或計(jì)算霧效果。類似這樣的計(jì)算要求應(yīng)用程序歸一化投影矩陣,使生成的W與世界空間中的Z相同。簡而言之,如果應(yīng)用程序的投影矩陣包含的(3,4)系數(shù)不為1,那么為了生成適用的矩陣,應(yīng)用程序必須用(3,4)系數(shù)的倒數(shù)縮放所有的系數(shù)。如果應(yīng)用程序不提供符合這樣要求的矩陣,那么會(huì)造成霧效果和深度緩存不正確。什么是投影矩陣?中推薦的矩陣符合基于w的計(jì)算的要求。
下圖顯示了不符合要求的投影矩陣,以及對(duì)同一個(gè)矩陣進(jìn)行縮放,這樣就可以啟用基于視點(diǎn)的霧。
我們假設(shè)在前面的矩陣中,所有變量都不為零。更多有關(guān)相對(duì)于視點(diǎn)的霧的信息,請(qǐng)參閱基于視點(diǎn)的深度與基于Z的深度的比較。更多有關(guān)基于w的深度緩存,請(qǐng)參閱深度緩存。
注意 Direct3D在基于w的深度計(jì)算中使用當(dāng)前設(shè)置的投影矩陣。因此,即使應(yīng)用程序不使用Direct3D變換流水線,但是為了使用基于w的特性,應(yīng)用程序必須設(shè)置一個(gè)符合要求的投影矩陣。
vs2008似乎有個(gè)bug。經(jīng)常新創(chuàng)建的mfc程序,編譯出來的exe會(huì)有兩個(gè)dll為“找不到”(即:用依賴庫查看是為黃色的)
用如下方法,可輕松該bug。(注:好像在vc6也,無此bug)
具體處理過程如下:
1、在Project\Properties\Configuration Properties\Project Defaults\Use of MFC中,選擇Use MFC in a Static Library
2、編譯,編譯不通過沒關(guān)系
3、在Project\Properties\Configuration Properties\Project Defaults\Use of MFC中,選擇Use Standard Windows Libraries(就是把設(shè)置改回去)
4、再次編譯
再使用dependency工具查看,OK了。
原文出自: http://www.cnitblog.com/zouzheng/archive/2007/08/31/32691.html
__FILE__,__LINE__,FUNCTION__實(shí)現(xiàn)代碼跟蹤調(diào)試(linux下c語言編程 )先看下簡單的初始代碼:注意其編譯運(yùn)行后的結(jié)果。
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 //函數(shù)a #include "global.h" int funca(void) { printf ("this is function\n"); return 0; } root@xuanfei-desktop:~/cpropram/2# cat funcb.c //函數(shù)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 //聯(lián)合編譯 root@xuanfei-desktop:~/cpropram/2# ./a.out //運(yùn)行 this is main this is function this is main this is function this is main
相同結(jié)果很難讓人看出那里出錯(cuò),下面我們用用 __FILE__,__LINE__,__FUNCTION__加入代碼,看看有什么區(qū)別嗎. 把 __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
上面的結(jié)果main.c(4)-main:this is main 表示在mian.c源代碼的第四行main函數(shù)里邊打印出來的 this is main 那樣的話就很方便的讓程序員對(duì)自己的程序進(jìn)行排錯(cuò)! 為了更方便的使用它我們可以通過在global.h代碼中進(jìn)行宏定義 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__的宏來簡單實(shí)現(xiàn)代碼的跟蹤調(diào)試:)
下面是一個(gè)可供調(diào)試用的頭文件 #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);
/*調(diào)試宏, 用于暫停*/ #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: 防止一個(gè)頭文件被重復(fù)包含 #ifndef COMDEF_H #define COMDEF_H //頭文件內(nèi)容 #endif 02: 重新定義一些類型,防止由于各種平臺(tái)和編譯器的不同,而產(chǎn)生的類型字節(jié)數(shù)差異,方便移植。 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: 得到指定地址上的一個(gè)字節(jié)或字 #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: 得到一個(gè)field在結(jié)構(gòu)體(struct)中的偏移量 #define FPOS(type,field) ((dword)&((type *)0)->field)
06: 得到一個(gè)結(jié)構(gòu)體中field所占用的字節(jié)數(shù) #define FSIZ(type,field) sizeof(((type *)0)->field)
07: 按照LSB格式把兩個(gè)字節(jié)轉(zhuǎn)化為一個(gè)Word #define FLIPW(ray) ((((word)(ray)[0]) * 256) + (ray)[1])
08: 按照LSB格式把一個(gè)Word轉(zhuǎn)化為兩個(gè)字節(jié) #define FLOPW(ray,val) (ray)[0] = ((val)/256); (ray)[1] = ((val) & 0xFF)
09: 得到一個(gè)變量的地址(word寬度) #define B_PTR(var) ((byte *) (void *) &(var)) #define W_PTR(var) ((word *) (void *) &(var))
10: 得到一個(gè)字的高位和低位字節(jié) #define WORD_LO(xxx) ((byte) ((word)(xxx) & 255)) #define WORD_HI(xxx) ((byte) ((word)(xxx) >> 8))
11: 返回一個(gè)比X大的最接近的8的倍數(shù) #define RND8(x) ((((x) + 7)/8) * 8)
12: 將一個(gè)字母轉(zhuǎn)換為大寫 #define UPCASE(c) (((c)>='a' && (c) <= 'z') ? ((c) - 0x20) : (c))
13: 判斷字符是不是10進(jìn)值的數(shù)字 #define DECCHK(c) ((c)>='0' && (c)<='9')
14: 判斷字符是不是16進(jìn)值的數(shù)字 #define HEXCHK(c) (((c) >= '0' && (c)<='9') ((c)>='A' && (c)<= 'F') \ ((c)>='a' && (c)<='f'))
15: 防止溢出的一個(gè)方法 #define INC_SAT(val) (val=((val)+1>(val)) ? (val)+1 : (val))
16: 返回?cái)?shù)組元素的個(gè)數(shù) #define ARR_SIZE(a) (sizeof((a))/sizeof((a[0])))
17: 返回一個(gè)無符號(hào)數(shù)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: 對(duì)于IO空間映射在存儲(chǔ)空間的結(jié)構(gòu),輸入輸出處理 #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: 使用一些宏跟蹤調(diào)試 ANSI標(biāo)準(zhǔn)說明了五個(gè)預(yù)定義的宏名。它們是: __LINE__ __FILE__ __DATE__ __TIME__ __STDC__ C++中還定義了 __cplusplus
如果編譯器不是標(biāo)準(zhǔn)的,則可能僅支持以上宏名中的幾個(gè),或根本不支持。記住編譯程序也許還提供其它預(yù)定義的宏名。
__LINE__ 及 __FILE__ 宏指示,#line指令可以改變它的值,簡單的講,編譯時(shí),它們包含程序的當(dāng)前行數(shù)和文件名。
__DATE__ 宏指令含有形式為月/日/年的串,表示源文件被翻譯到代碼時(shí)的日期。 __TIME__ 宏指令包含程序編譯的時(shí)間。時(shí)間用字符串表示,其形式為: 分:秒 __STDC__ 宏指令的意義是編譯時(shí)定義的。一般來講,如果__STDC__已經(jīng)定義,編譯器將僅接受不包含任何非標(biāo)準(zhǔn)擴(kuò)展的標(biāo)準(zhǔn)C/C++代碼。如果實(shí)現(xiàn)是標(biāo)準(zhǔn)的,則宏__STDC__含有十進(jìn)制常量1。如果它含有任何其它數(shù),則實(shí)現(xiàn)是非標(biāo)準(zhǔn)的。 __cplusplus 與標(biāo)準(zhǔn)c++一致的編譯器把它定義為一個(gè)包含至少6為的數(shù)值。與標(biāo)準(zhǔn)c++不一致的編譯器將使用具有5位或更少的數(shù)值。
可以定義宏,例如: 當(dāng)定義了_DEBUG,輸出數(shù)據(jù)信息和所在文件所在行 #ifdef _DEBUG #define DEBUGMSG(msg,date) printf(msg);printf(“%d%d%d”,date,_LINE_,_FILE_) #else #define DEBUGMSG(msg,date) #endif
20: 宏定義防止錯(cuò)誤使用小括號(hào)包含。 例如: 有問題的定義:#define DUMP_WRITE(addr,nr) {memcpy(bufp,addr,nr); bufp += nr;} 應(yīng)該使用的定義: #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前面的“;”時(shí)就認(rèn)為if語句已經(jīng)結(jié)束,因而后面的else不在if語句中。而采用do{} while(0)的定義,在任何情況下都沒有問題。而改為 #difne DO(a,b) do{a+b;a++;}while(0) 的定義則在任何情況下都不會(huì)出錯(cuò)。
原諒出自: http://www.yesky.com/445/1713445.shtml控制C++的內(nèi)存分配 在嵌入式系統(tǒng)中使用C++的一個(gè)常見問題是內(nèi)存分配,即對(duì)new 和 delete 操作符的失控。
具有諷刺意味的是,問題的根源卻是C++對(duì)內(nèi)存的管理非常的容易而且安全。具體地說,當(dāng)一個(gè)對(duì)象被消除時(shí),它的析構(gòu)函數(shù)能夠安全的釋放所分配的內(nèi)存。
這當(dāng)然是個(gè)好事情,但是這種使用的簡單性使得程序員們過度使用new 和 delete,而不注意在嵌入式C++環(huán)境中的因果關(guān)系。并且,在嵌入式系統(tǒng)中,由于內(nèi)存的限制,頻繁的動(dòng)態(tài)分配不定大小的內(nèi)存會(huì)引起很大的問題以及堆破碎的風(fēng)險(xiǎn)。
作為忠告,保守的使用內(nèi)存分配是嵌入式環(huán)境中的第一原則。
但當(dāng)你必須要使用new 和delete時(shí),你不得不控制C++中的內(nèi)存分配。你需要用一個(gè)全局的new 和delete來代替系統(tǒng)的內(nèi)存分配符,并且一個(gè)類一個(gè)類的重載new 和delete。
一個(gè)防止堆破碎的通用方法是從不同固定大小的內(nèi)存持中分配不同類型的對(duì)象。對(duì)每個(gè)類重載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); } |
這段代碼可以代替默認(rèn)的操作符來滿足內(nèi)存分配的請(qǐng)求。出于解釋C++的目的,我們也可以直接調(diào)用malloc() 和free()。
也可以對(duì)單個(gè)類的new 和 delete 操作符重載。這是你能靈活的控制對(duì)象的內(nèi)存分配。
|
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 對(duì)象的內(nèi)存分配都采用這段代碼。更進(jìn)一步,任何從TestClass 繼承的類也都采用這一方式,除非它自己也重載了new 和 delete 操作符。通過重載new 和 delete 操作符的方法,你可以自由地采用不同的分配策略,從不同的內(nèi)存池中分配不同的類對(duì)象。
為單個(gè)的類重載 new[ ] 和 delete[ ]
必須小心對(duì)象數(shù)組的分配。你可能希望調(diào)用到被你重載過的new 和 delete 操作符,但并不如此。內(nèi)存的請(qǐng)求被定向到全局的new[ ]和delete[ ] 操作符,而這些內(nèi)存來自于系統(tǒng)堆。
C++將對(duì)象數(shù)組的內(nèi)存分配作為一個(gè)單獨(dú)的操作,而不同于單個(gè)對(duì)象的內(nèi)存分配。為了改變這種方式,你同樣需要重載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; } |
但是注意:對(duì)于多數(shù)C++的實(shí)現(xiàn),new[]操作符中的個(gè)數(shù)參數(shù)是數(shù)組的大小加上額外的存儲(chǔ)對(duì)象數(shù)目的一些字節(jié)。在你的內(nèi)存分配機(jī)制重要考慮的這一點(diǎn)。你應(yīng)該盡量避免分配對(duì)象數(shù)組,從而使你的內(nèi)存分配策略簡單。
::GetCurrentDirectory()返回值末尾是不帶有 '\\' 的。
原文來自:http://www.shnenglu.com/totti1006/archive/2010/01/21/106118.html
1 引言
在大多數(shù)Windows應(yīng)用程序 設(shè)計(jì)中,都幾乎不可避免的要對(duì)內(nèi)存進(jìn)行操作和管理。在進(jìn)行大尺寸內(nèi)存的動(dòng)態(tài)分配時(shí)尤其顯的重要。本文即主要對(duì)內(nèi)存管理中的堆管理技術(shù)進(jìn)行論述。 堆(Heap)實(shí)際是位于保留的虛擬地址空間中的一個(gè)區(qū)域。剛開始時(shí),保留區(qū)域中的多數(shù)頁面并沒有被提交物理存儲(chǔ)器。隨著從堆中越來越多的進(jìn)行內(nèi)存分配,堆管理器將逐漸把更多的物理存儲(chǔ)器提交給堆。堆的物理存儲(chǔ)器從系統(tǒng)頁文件中分配,在釋放時(shí)有專門的堆管理器負(fù)責(zé)對(duì)已占用物理存儲(chǔ)器的回收。堆管理也是Windows提供的一種內(nèi)存管理機(jī)制。主要用來分配小的數(shù)據(jù)塊。與Windows的其他兩種內(nèi)存管理機(jī)制虛擬內(nèi)存和內(nèi)存映射文件相比,堆可以不必考慮諸如系統(tǒng)的分配粒度和頁面邊界之類比較煩瑣而又容易忽視的問題,可將注意力集中于對(duì) 程序功能代碼的設(shè)計(jì)上。但是使用堆去分配、釋放內(nèi)存的速度要比其他兩種機(jī)制慢的多,而且不具備直接控制物理存儲(chǔ)器提交與回收的能力。 在進(jìn)程剛啟動(dòng)時(shí),系統(tǒng)便在剛創(chuàng)建的進(jìn)程虛擬地址空間中創(chuàng)建了一個(gè)堆,該堆即為進(jìn)程的默認(rèn)堆,缺省大小為1MB,該值允許在鏈接程序時(shí)被更改。進(jìn)程的默認(rèn)堆是比較重要的,可供眾多Windows函數(shù)使用。在使用時(shí),系統(tǒng)必須保證在規(guī)定的時(shí)間內(nèi),每此只有一個(gè)線程能夠分配和釋放默認(rèn)堆中的內(nèi)存塊。雖然這種限制將會(huì)對(duì)訪問速度產(chǎn)生一定的影響,但卻可以保證進(jìn)程中的多個(gè)線程在同時(shí)調(diào)用各種Windows函數(shù)時(shí)對(duì)默認(rèn)堆的順序訪問。在進(jìn)程中允許使用多個(gè)堆,進(jìn)程中包括默認(rèn)堆在內(nèi)的每個(gè)堆都有一個(gè)堆句柄來標(biāo)識(shí)。與自己創(chuàng)建的堆不同,進(jìn)程默認(rèn)堆的創(chuàng)建、銷毀均由系統(tǒng)來完成,而且其生命期早在進(jìn)程開始執(zhí)行之前就已經(jīng)開始,雖然在程序中可以通過GetProcessHeap()函數(shù)得到進(jìn)程的默認(rèn)堆句柄,但卻不允許調(diào)用HeapDestroy()函數(shù)顯式將其撤消。 2 對(duì)動(dòng)態(tài)創(chuàng)建堆的需求 前面曾提到,在進(jìn)程中除了進(jìn)程默認(rèn)堆外,還可以在進(jìn)程虛擬地址空間中動(dòng)態(tài)創(chuàng)建一些獨(dú)立的堆。至于在程序設(shè)計(jì)時(shí)究竟需不需要?jiǎng)討B(tài)創(chuàng)建獨(dú)立的堆可以從是否有保護(hù)組件的需要、是否能更加有效地對(duì)內(nèi)存進(jìn)行管理、是否有進(jìn)行本地訪問的需要、是否有減少線程同步開銷的需要以及是否有迅速釋放堆的需要等幾個(gè)方面去考慮。 對(duì)于是否有保護(hù)組件的需要這一原則比較容易理解。在圖1中,左邊的圖表示了一個(gè)鏈表(節(jié)點(diǎn)結(jié)構(gòu))組件和一個(gè)樹(分支結(jié)構(gòu))組件共同使用一個(gè)堆的情況。在這種情況下,由于兩組件數(shù)據(jù)在堆中的混合存放,如果節(jié)點(diǎn)3(屬于鏈表組件)的后幾個(gè)字節(jié)由于被錯(cuò)誤改寫,將有可能影響到位于其后的分支2(屬于樹組件)。這將致使樹組件的相關(guān)代碼在遍歷其樹時(shí)由于內(nèi)存被破壞而無法進(jìn)行。究其原因,樹組件的內(nèi)存是由于鏈表組建對(duì)其自身的錯(cuò)誤操作而引起的。如果采用右圖所示方式,將樹組件和鏈表組件分別存放于一個(gè)獨(dú)立的堆中,上述情況顯然不會(huì)發(fā)生,錯(cuò)誤將被局限于進(jìn)行了錯(cuò)誤操作的鏈表組件,而樹組件由于存放在獨(dú)立的堆中而受到了保護(hù)。  圖1 動(dòng)態(tài)創(chuàng)建堆在保護(hù)組件中的作用 在上圖中,如果鏈表組件的每個(gè)節(jié)點(diǎn)占用12個(gè)字節(jié),每個(gè)樹組件的分支占用16個(gè)字節(jié)如果這些長度不一的對(duì)象共用一個(gè)堆(左圖),在左圖中這些已經(jīng)分配了內(nèi)存的對(duì)象已占滿了堆,如果其中有節(jié)點(diǎn)2和節(jié)點(diǎn)4釋放,將會(huì)產(chǎn)生24個(gè)字節(jié)的碎片,如果試圖在24個(gè)字節(jié)的空閑區(qū)間內(nèi)分配一個(gè)16字節(jié)的分支對(duì)象,盡管要分配的字節(jié)數(shù)小于空閑字節(jié)數(shù),但分配仍將失敗。只有在堆棧中分配大小相同的對(duì)象才可以實(shí)行更加有效的內(nèi)存管理。如果將樹組件換成其他長度為12字節(jié)的組件,那么在釋放一個(gè)對(duì)象后,另一個(gè)對(duì)象就可以恰好填充到此剛釋放的對(duì)象空間中。 進(jìn)行本地訪問的需要也是一條比較重要的原則。系統(tǒng)會(huì)經(jīng)常在內(nèi)存與系統(tǒng)頁文件之間進(jìn)行頁面交換,但如果交換次數(shù)過多,系統(tǒng)的運(yùn)行性能就將受很大的影響。因此在程序設(shè)計(jì)時(shí)應(yīng)盡量避免系統(tǒng)頻繁交換頁面,如果將那些會(huì)被同時(shí)訪問到的數(shù)據(jù)分配在相互靠近的位置上,將會(huì)減少系統(tǒng)在內(nèi)存和頁文件之間的頁面交換頻率。 線程同步開銷指的是默認(rèn)條件下以順序方式運(yùn)行的堆為保護(hù)數(shù)據(jù)在多個(gè)線程試圖同時(shí)訪問時(shí)不受破壞而必須執(zhí)行額外代碼所花費(fèi)的開銷。這種開銷保證了堆對(duì)線程的 安全性,因此是有必要的,但對(duì)于大量的堆分配操作,這種額外的開銷將成為一個(gè)負(fù)擔(dān),并降低程序的運(yùn)行性能。為避免這種額外的開銷,可以在創(chuàng)建新堆時(shí)通知系統(tǒng)只有單個(gè)線程對(duì)訪問。此時(shí)堆對(duì)線程的安全性將有應(yīng)用程序來負(fù)責(zé)。 最后如果有迅速釋放堆的需要,可將專用堆用于某些數(shù)據(jù)結(jié)構(gòu),并以整個(gè)堆去釋放,而不再顯式地釋放在堆中分配的每一個(gè)內(nèi)存塊。對(duì)于大多數(shù)應(yīng)用程序,這樣的處理將能以更快的速度運(yùn)行。 3 創(chuàng)建堆
在進(jìn)程中,如果需要可以在原有默認(rèn)堆的基礎(chǔ)上動(dòng)態(tài)創(chuàng)建一個(gè)堆,可由HeapCreate()函數(shù)完成:
HANDLE HeapCreate( DWORD flOptions, DWORD dwInitialSize, DWORD dwMaximumSize ); | 其第一個(gè)參數(shù)flOptions指定了對(duì)新建堆的操作屬性。該標(biāo)志將會(huì)影響一些堆函數(shù)如HeapAlloc()、HeapFree()、HeapReAlloc()和HeapSize()等對(duì)新建堆的訪問。其可能的取值為下列標(biāo)志及其組合:
| 屬性標(biāo)志 |
說明 |
| HEAP_GENERATE_EXCEPTIONS |
在遇到由于內(nèi)存越界等而引起的函數(shù)失敗時(shí),由系統(tǒng)拋出一個(gè)異常來指出此失敗,而不是簡單的返回NULL指針。 |
| HEAP_NO_SERIALIZE |
指明互斥現(xiàn)象不會(huì)出現(xiàn) | 參數(shù)dwInitialSize和dwMaximumSize分別為堆的初始大小和堆棧的最大尺寸。其中,dwInitialSize的值決定了最初提交給堆的字節(jié)數(shù)。如果設(shè)置的數(shù)值不是頁面大小的整數(shù)倍,則將被圓整到鄰近的頁邊界處。而dwMaximumSize則實(shí)際上是系統(tǒng)能為堆保留的地址空間區(qū)域的最大字節(jié)數(shù)。如果該值為0,那么將創(chuàng)建一個(gè)可擴(kuò)展的堆,堆的大小僅受可用內(nèi)存的限制。如果應(yīng)用程序需要分配大的內(nèi)存塊,通常要將該參數(shù)設(shè)置為0。如果dwMaximumSize大于0,則該值限定了堆所能創(chuàng)建的最大值,HeapCreate()同樣也要將該值圓整到鄰近的頁邊界,然后再在進(jìn)程的虛擬地址空間為堆保留該大小的一塊區(qū)域。在這種堆中分配的內(nèi)存塊大小不能超過0x7FFF8字節(jié),任何試圖分配更大內(nèi)存塊的行為將會(huì)失敗,即使是設(shè)置的堆大小足以容納該內(nèi)存塊。如果HeapCreate()成功執(zhí)行,將會(huì)返回一個(gè)標(biāo)識(shí)新堆的句柄,并可供其他堆函數(shù)使用。 需要特別說明的是,在設(shè)置第一個(gè)參數(shù)時(shí),對(duì)HEAP_NO_SERIALIZE的標(biāo)志的使用要謹(jǐn)慎,一般應(yīng)避免使用該標(biāo)志。這是同后續(xù)將要進(jìn)行的堆函數(shù)HeapAlloc()的執(zhí)行過程有關(guān)系的,在HeapAlloc()試圖從堆中分配一個(gè)內(nèi)存塊時(shí),將執(zhí)行下述幾步操作: 1) 遍歷分配的和釋放的內(nèi)存塊的鏈接表 2) 搜尋一個(gè)空閑內(nèi)存塊的地址 3) 通過將空閑內(nèi)存塊標(biāo)記為"已分配"來分配新內(nèi)存塊 4) 將新分配的內(nèi)存塊添加到內(nèi)存塊列表 如果這時(shí)有兩個(gè)線程1、2試圖同時(shí)從一個(gè)堆中分配內(nèi)存塊,那么線程1在執(zhí)行了上面的1和2步后將得到空間內(nèi)存塊的地址。但是由于CPU對(duì)線程運(yùn)行時(shí)間的分片,使得線程1在執(zhí)行第3步操作前有可能被線程2搶走執(zhí)行權(quán)并有機(jī)會(huì)去執(zhí)行同樣的1、2步操作,而且由于先執(zhí)行的線程1并沒有執(zhí)行到第3步,因此線程2會(huì)搜尋到同一個(gè)空閑內(nèi)存塊的地址,并將其標(biāo)記為已分配。而線程1在恢復(fù)運(yùn)行后并不能知曉該內(nèi)存塊已被線程2標(biāo)記過,因此會(huì)出現(xiàn)兩個(gè)線程軍認(rèn)為其分配的是空閑的內(nèi)存塊,并更新各自的聯(lián)接表。顯然,象這種兩個(gè)線程擁有完全相同內(nèi)存塊地址的錯(cuò)誤是非常嚴(yán)重而又是難以發(fā)現(xiàn)的。 由于只有在多個(gè)線程同時(shí)進(jìn)行操作時(shí)才有可能出現(xiàn)上述問題,一種簡單的解決的辦法就是不使用HEAP_NO_SERIALIZE標(biāo)志而只允許單個(gè)線程獨(dú)占地對(duì)堆及其聯(lián)接表擁有訪問權(quán)。如果一定要使用此標(biāo)志,為了安全起見,必須確保進(jìn)程為單線程的或是在進(jìn)程中使用了多線程,但只有單個(gè)線程對(duì)堆進(jìn)行訪問。再就是使用了多線程,也有多個(gè)線程對(duì)堆進(jìn)行了訪問,但這些線程通過使用某種線程同步手段。如果可以確保以上幾條中的一條成立,也是可以安全使用HEAP_NO_SERIALIZE標(biāo)志的,而且還將擁有快的訪問速度。如果不能肯定上述條件是否滿足,建議不使用此標(biāo)志而以順序的方式訪問堆,雖然線程速度會(huì)因此而下降但卻可以確保堆及其中數(shù)據(jù)的不被破壞。 4 從堆中分配內(nèi)存塊
在成功創(chuàng)建一個(gè)堆后,可以調(diào)用HeapAlloc()函數(shù)從堆中分配內(nèi)存塊。在此,除了可以從用HeapCreate()創(chuàng)建的動(dòng)態(tài)堆中分配內(nèi)存塊,也可以直接從進(jìn)程的默認(rèn)堆中分配內(nèi)存塊。下面先給出HeapCreate()的函數(shù)原型:
LPVOID HeapAlloc( HANDLE hHeap, DWORD dwFlags, DWORD dwBytes ); | 其中,參數(shù)hHeap為要分配的內(nèi)存塊來自的堆的句柄,可以是從HeapCreate()創(chuàng)建的動(dòng)態(tài)堆句柄也可以是由GetProcessHeap()得到的默認(rèn)堆句柄。參數(shù)dwFlags指定了影響堆分配的各個(gè)標(biāo)志。該標(biāo)志將覆蓋在調(diào)用HeapCreate()時(shí)所指定的相應(yīng)標(biāo)志,可能的取值為:
| 標(biāo)志 |
說明 |
| HEAP_GENERATE_EXCEPTIONS |
該標(biāo)志指定在進(jìn)行諸如內(nèi)存越界操作等情況時(shí)將拋出一個(gè)異常而不是簡單的返回NULL指針 |
| HEAP_NO_SERIALIZE |
強(qiáng)制對(duì)HeapAlloc()的調(diào)用將與訪問同一個(gè)堆的其他線程不按照順序進(jìn)行 |
| HEAP_ZERO_MEMORY |
如果使用了該標(biāo)志,新分配內(nèi)存的內(nèi)容將被初始化為0 | 最后一個(gè)參數(shù)dwBytes設(shè)定了要從堆中分配的內(nèi)存塊的大小。如果HeapAlloc()執(zhí)行成功,將會(huì)返回從堆中分配的內(nèi)存塊的地址。如果由于內(nèi)存不足或是其他一些原因而引起HeapAlloc()函數(shù)的執(zhí)行失敗,將會(huì)引發(fā)異常。通過異常標(biāo)志可以得到引起內(nèi)存分配失敗的原因:如果為STATUS_NO_MEMORY則表明是由于內(nèi)存不足引起的;如果是STATUS_ACCESS_VIOLATION則表示是由于堆被破壞或函數(shù)參數(shù)不正確而引起分配內(nèi)存塊的嘗試失敗。以上異常只有在指定了HEAP_GENERATE_EXCEPTIONS標(biāo)志時(shí)才會(huì)發(fā)生,如果沒有指定此標(biāo)志,在出現(xiàn)類似錯(cuò)誤時(shí)HeapAlloc()函數(shù)只是簡單的返回NULL指針。 在設(shè)置dwFlags參數(shù)時(shí),如果先前用HeapCreate()創(chuàng)建堆時(shí)曾指定過HEAP_GENERATE_EXCEPTIONS標(biāo)志,就不必再去設(shè)置HEAP_GENERATE_EXCEPTIONS標(biāo)志了,因?yàn)镠EAP_GENERATE_EXCEPTIONS標(biāo)志已經(jīng)通知堆在不能分配內(nèi)存塊時(shí)將會(huì)引發(fā)異常。另外,對(duì)HEAP_NO_SERIALIZE標(biāo)志的設(shè)置應(yīng)慎重,與在HeapCreate()函數(shù)中使用HEAP_NO_SERIALIZE標(biāo)志類似,如果在同一時(shí)間有其他線程使用同一個(gè)堆,那么該堆將會(huì)被破壞。如果是在進(jìn)程默認(rèn)堆中進(jìn)行內(nèi)存塊的分配則要絕對(duì)禁用此標(biāo)志。 在使用堆函數(shù)HeapAlloc()時(shí)要注意:堆在內(nèi)存管理中的使用主要是用來分配一些較小的數(shù)據(jù)塊,如果要分配的內(nèi)存塊在1MB左右,那么就不要再使用堆來管理內(nèi)存了,而應(yīng)選擇虛擬內(nèi)存的內(nèi)存管理機(jī)制。 5 再分配內(nèi)存塊
在程序設(shè)計(jì)時(shí)經(jīng)常會(huì)由于開始時(shí)預(yù)見不足而造成在堆中分配的內(nèi)存塊大小的不合適(多數(shù)情況是開始時(shí)分配的內(nèi)存較小,而后來實(shí)際需要更多的數(shù)據(jù)復(fù)制到內(nèi)存塊中去)這就需要在分配了內(nèi)存塊后再根據(jù)需要調(diào)整其大小。堆函數(shù)HeapReAlloc()將完成這一功能,其函數(shù)原型為:
LPVOID HeapReAlloc( HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, DWORD dwBytes ); | 其中,參數(shù)hHeap為包含要調(diào)整其大小的內(nèi)存塊的堆的句柄。dwFlags參數(shù)指定了在更改內(nèi)存塊大小時(shí)HeapReAlloc()函數(shù)所使用的標(biāo)志。其可能的取值為HEAP_GENERATE_EXCEPTIONS、HEAP_NO_SERIALIZE、HEAP_REALLOC_IN_PLACE_ONLY和HEAP_ZERO_MEMORY,其中前兩個(gè)標(biāo)志的作用與在HeapAlloc()中的作用相同。HEAP_REALLOC_IN_PLACE_ONLY標(biāo)志在內(nèi)存塊被加大時(shí)不移動(dòng)堆中的內(nèi)存塊,在沒有設(shè)置此標(biāo)志的情況下如果對(duì)內(nèi)存進(jìn)行增大,那么HeapReAlloc()函數(shù)將有可能將原內(nèi)存塊移動(dòng)到一個(gè)新的地址。顯然,在設(shè)置了該標(biāo)志禁止內(nèi)存快首地址進(jìn)行調(diào)整時(shí),將有可能出現(xiàn)沒有足夠的內(nèi)存供試圖增大的內(nèi)存塊使用,對(duì)于這種情況,函數(shù)對(duì)內(nèi)存塊增大調(diào)整的操作是失敗的,內(nèi)存塊將仍保留原有的大小和位置。HEAP_ZERO_MEMORY標(biāo)志的用處則略有不同,如果內(nèi)存快經(jīng)過調(diào)整比以前大,那么新增加的那部分內(nèi)存將被初始化為0;如果經(jīng)過調(diào)整內(nèi)存塊縮小了,那么該標(biāo)志將不起任何作用。 函數(shù)的最后兩個(gè)參數(shù)lpMem和dwBytes分別為指向再分配內(nèi)存塊的指針和再分配的字節(jié)數(shù)。如果函數(shù)成功執(zhí)行,將返回新的改變了大小的內(nèi)存塊的地址。如果在調(diào)用時(shí)使用了HEAP_REALLOC_IN_PLACE_ONLY標(biāo)志,那么返回的地址將與原內(nèi)存塊地址相同。如果因?yàn)閮?nèi)存不足等原因而引起函數(shù)的執(zhí)行失敗,函數(shù)將返回一個(gè)NULL指針。但是HeapReAlloc()的執(zhí)行失敗并不會(huì)影響原內(nèi)存塊,它將保持原來的大小和位置繼續(xù)存在。可以通過HeapSize()函數(shù)來檢索內(nèi)存塊的實(shí)際大小。 6 釋放堆內(nèi)存、撤消堆 在不再需要使用堆中的內(nèi)存塊時(shí),可以通過HeapFree()將其予以釋放。該函數(shù)結(jié)構(gòu)比較簡單,只含有三個(gè)參數(shù):
BOOL HeapFree( HANDLE hHeap, DWORD dwFlags, LPVOID lpMem ); | 其中,hHeap為要包含要釋放內(nèi)存塊的堆的句柄;參數(shù)dwFlags為堆棧的釋放選項(xiàng)可以是0,也可以是HEAP_NO_SERIALIZE;最后的參數(shù)lpMem為指向內(nèi)存塊的指針。如果函數(shù)成功執(zhí)行,將釋放指定的內(nèi)存塊,并返回TRUE。該函數(shù)的主要作用是可以用來幫助堆管理器回收某些不使用的物理存儲(chǔ)器以騰出更多的空閑空間,但是并不能保證一定會(huì)成功。 最后,在程序退出前或是應(yīng)用程序不再需要其創(chuàng)建的堆了,可以調(diào)用HeapDestory()函數(shù)將其銷毀。該函數(shù)只包含一個(gè)參數(shù)--待銷毀的堆的句柄。HeapDestory()的成功執(zhí)行將可以釋放堆中包含的所有內(nèi)存塊,也可將堆占用的物理存儲(chǔ)器和保留的地址空間區(qū)域全部重新返回給系統(tǒng)并返回TRUE。該函數(shù)只對(duì)由HeapCreate()顯式創(chuàng)建的堆起作用,而不能銷毀進(jìn)程的默認(rèn)堆,如果強(qiáng)行將由GetProcessHeap()得到的默認(rèn)堆的句柄作為參數(shù)去調(diào)用HeapDestory(),系統(tǒng)將會(huì)忽略對(duì)該函數(shù)的調(diào)用。 7 對(duì)new與delete操作符的重載
new與delete內(nèi)存空間動(dòng)態(tài)分配操作符是C++中使用堆進(jìn)行內(nèi)存管理的一種常用方式,在程序運(yùn)行過程中可以根據(jù)需要隨時(shí)通過這兩個(gè)操作符建立或刪除堆對(duì)象。new操作符將在堆中分配一個(gè)足夠大小的內(nèi)存塊以存放指定類型的對(duì)象,如果每次構(gòu)造的對(duì)象類型不同,則需要按最大對(duì)象所占用的空間來進(jìn)行分配。new操作符在成功執(zhí)行后將返回一個(gè)類型與new所分配對(duì)象相匹配的指針,如果不匹配則要對(duì)其進(jìn)行強(qiáng)制類型轉(zhuǎn)換,否則將會(huì)編譯出錯(cuò)。在不再需要這個(gè)對(duì)象的時(shí)候,必須顯式調(diào)用delete操作符來釋放此空間。這一點(diǎn)是非常重要的,如果在預(yù)分配的緩沖里構(gòu)造另一個(gè)對(duì)象之前或者在釋放緩沖之前沒有顯式調(diào)用delete操作符,那么程序?qū)a(chǎn)生不可預(yù)料的后果。在使用delete操作符時(shí),應(yīng)注意以下幾點(diǎn): 1) 它必須使用于由運(yùn)算符new返回的指針 2) 該操作符也適用于NULL指針 3) 指針名前只用一對(duì)方括號(hào)符,并且不管所刪除數(shù)組的維數(shù),忽略方括號(hào)內(nèi)的任何數(shù)字
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來建立和刪除堆對(duì)象外,還可以通過為C++類重載new和delete操作符來方便地利用堆棧函數(shù)。上面的代碼對(duì)它們進(jìn)行了簡單的重載,并通過靜態(tài)變量m_sHeap和m_sAllocedInHeap在類CVMShow的所有實(shí)例間共享唯一的堆句柄(因?yàn)樵谶@里CVMShow類的所有實(shí)例都是在同一個(gè)堆中進(jìn)行內(nèi)存分配的)和已分配類對(duì)象的計(jì)數(shù)。這兩個(gè)靜態(tài)變量在代碼開始執(zhí)行時(shí)被分別初始化為NULL指針和0計(jì)數(shù)。 重載的new操作符在第一次被調(diào)用時(shí),由于靜態(tài)變量m_sHeap為NULL標(biāo)志著堆尚未創(chuàng)建,就通過HeapCreate()函數(shù)創(chuàng)建一個(gè)堆并返回堆句柄到m_sHeap。隨后根據(jù)入口參數(shù)size所指定的大小在堆中分配內(nèi)存,同時(shí)已分配內(nèi)存塊計(jì)數(shù)器m_sAllocedInHeap累加。在該操作符的以后調(diào)用過程中,由于堆已經(jīng)創(chuàng)建,故不再創(chuàng)建堆,而是直接在堆中分配指定大小的內(nèi)存塊并對(duì)已分配的內(nèi)存塊個(gè)數(shù)進(jìn)行計(jì)數(shù)。 在CVMShow類對(duì)象不再被應(yīng)用程序所使用時(shí),需要將其撤消,由重載的delete操作符完成此工作。delete操作符只接受一個(gè)LPVOID型參數(shù),即被刪除對(duì)象的地址。該函數(shù)在執(zhí)行時(shí)首先調(diào)用HeapFree()函數(shù)將指定的已分配內(nèi)存的對(duì)象釋放并對(duì)已分配內(nèi)存計(jì)數(shù)遞減1。如果該計(jì)數(shù)不為零則表明當(dāng)前堆中的內(nèi)存塊沒有全部釋放,堆暫時(shí)不予撤消。如果m_sAllocedInHeap計(jì)數(shù)減到0,則堆中已釋放完所有的CVMShow對(duì)象,可以調(diào)用HeapDestory()函數(shù)將堆銷毀,并將堆句柄m_sHeap設(shè)置為NULL指針。這里在撤消堆后將堆句柄設(shè)置為NULL指針的操作是完全必要的。如果不執(zhí)行該操作,當(dāng)程序再次調(diào)用new操作符去分配一個(gè)CVMShow類對(duì)象時(shí)將會(huì)認(rèn)為堆是存在的而會(huì)試圖在已撤消的堆中去分配內(nèi)存,顯然將會(huì)導(dǎo)致失敗。 象CVMShow這樣設(shè)計(jì)的類通過對(duì)new和delete操作符的重載,并且在一個(gè)堆中為所有的CVMShow類對(duì)象進(jìn)行分配,可以節(jié)省在為每一個(gè)類都創(chuàng)建堆的分配開銷與內(nèi)存。這樣的處理還可以讓每一個(gè)類都擁有屬于自己的堆,并且允許派生類對(duì)其共享,這在程序設(shè)計(jì)中也是比較好的一種處理方法。 8 小結(jié) 在使用堆時(shí)有時(shí)會(huì)造成系統(tǒng)運(yùn)行速度的減慢,通常是由以下原因造成的:分配操作造成的速度減慢;釋放操作造成的速度減慢;堆競爭造成的速度減慢;堆破壞造成的速度減慢;頻繁的分配和重分配造成的速度減慢等。其中,競爭是在分配和釋放操作中導(dǎo)致速度減慢的問題。基于上述原因,建議不要在程序中過于頻繁的使用堆。
原文出處: http://www.cnblogs.com/penglink/archive/2009/02/19/1393838.html
Lua腳本語言入門(目前魔獸使用的可以寫在宏內(nèi)的語言)(中文)-zt
作者: 沐楓
Lua 程序設(shè)計(jì)初步
作者: 沐楓 (第二人生成員) 版權(quán)所有轉(zhuǎn)載請(qǐng)注明原出處
在這篇文章中,我想向大家介紹如何進(jìn)行Lua程序設(shè)計(jì)。我假設(shè)大家都學(xué)過至少一門編程語言,比如Basic或C,特別是C。因?yàn)長ua的最大用途是在宿主程序中作為腳本使用的。 Lua 的語法比較簡單,學(xué)習(xí)起來也比較省力,但功能卻并不弱。 在Lua中,一切都是變量,除了關(guān)鍵字。請(qǐng)記住這句話。
I. 首先是注釋 寫一個(gè)程序,總是少不了注釋的。 在Lua中,你可以使用單行注釋和多行注釋。 單行注釋中,連續(xù)兩個(gè)減號(hào)"--"表示注釋的開始,一直延續(xù)到行末為止。相當(dāng)于C++語言中的"http://"。 多行注釋中,由"--[["表示注釋開始,并且一直延續(xù)到"]]"為止。這種注釋相當(dāng)于C語言中的"/*…*/"。在注釋當(dāng)中,"[["和"]]"是可以嵌套的。 II. Lua編程 經(jīng)典的"Hello world"的程序總是被用來開始介紹一種語言。在Lua中,寫一個(gè)這樣的程序很簡單: print("Hello world") 在Lua中,語句之間可以用分號(hào)";"隔開,也可以用空白隔開。一般來說,如果多個(gè)語句寫在同一行的話,建議總是用分號(hào)隔開。 Lua 有好幾種程序控制語句,如:
條件控制:if 條件 then … elseif 條件 then … else … end While循環(huán):while 條件 do … end Repeat循環(huán):repeat … until 條件 For循環(huán):for 變量 = 初值,終點(diǎn)值,步進(jìn) do … end For循環(huán):for 變量1,變量2,… ,變量N in表或枚舉函數(shù) do … end
注意一下,for的循環(huán)變量總是只作用于for的局部變量,你也可以省略步進(jìn)值,這時(shí)候,for循環(huán)會(huì)使用1作為步進(jìn)值。 你可以用break來中止一個(gè)循環(huán)。 如果你有程序設(shè)計(jì)的基礎(chǔ),比如你學(xué)過Basic,C之類的,你會(huì)覺得Lua也不難。但Lua有幾個(gè)地方是明顯不同于這些程序設(shè)計(jì)語言的,所以請(qǐng)?zhí)貏e注意。
.語句塊 語句塊在C++中是用"{"和"}"括起來的,在Lua中,它是用do 和 end 括起來的。比如: do print("Hello") end 你可以在 函數(shù) 中和 語句塊 中定局部變量。
.賦值語句 賦值語句在Lua被強(qiáng)化了。它可以同時(shí)給多個(gè)變量賦值。 例如: a,b,c,d=1,2,3,4 甚至是: a,b=b,a -- 多么方便的交換變量功能啊。 在默認(rèn)情況下,變量總是認(rèn)為是全局的。假如你要定義局部變量,則在第一次賦值的時(shí)候,需要用local說明。比如: local a,b,c = 1,2,3 -- a,b,c都是局部變量
.?dāng)?shù)值運(yùn)算 和C語言一樣,支持 +, -, *, /。但Lua還多了一個(gè)"^"。這表示指數(shù)乘方運(yùn)算。比如2^3 結(jié)果為8, 2^4結(jié)果為16。 連接兩個(gè)字符串,可以用".."運(yùn)處符。如: "This a " .. "string." -- 等于 "this a string"
.比較運(yùn)算 < > <= >= == ~= 分別表示 小于,大于,不大于,不小于,相等,不相等 所有這些操作符總是返回true或false。 對(duì)于Table,F(xiàn)unction和Userdata類型的數(shù)據(jù),只有 == 和 ~=可以用。相等表示兩個(gè)變量引用的是同一個(gè)數(shù)據(jù)。比如: 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
.邏輯運(yùn)算 and, or, not 其中,and 和 or 與C語言區(qū)別特別大。 在這里,請(qǐng)先記住,在Lua中,只有false和nil才計(jì)算為false,其它任何數(shù)據(jù)都計(jì)算為true,0也是true! and 和 or的運(yùn)算結(jié)果不是true和false,而是和它的兩個(gè)操作數(shù)相關(guān)。 a and b:如果a為false,則返回a;否則返回b a or b:如果 a 為true,則返回a;否則返回b
舉幾個(gè)例子: 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,它相當(dāng)于:if not x then x = v end 。
.運(yùn)算符優(yōu)先級(jí),從高到低順序如下: ^ not - (一元運(yùn)算) * / + - ..(字符串連接) < > <= >= ~= == and or
III. 關(guān)鍵字 關(guān)鍵字是不能做為變量的。Lua的關(guān)鍵字不多,就以下幾個(gè): and break do else elseif end false for function if in local nil not or repeat return then true until while
IV. 變量類型 怎么確定一個(gè)變量是什么類型的呢?大家可以用type()函數(shù)來檢查。Lua支持的類型有以下幾種:
Nil 空值,所有沒有使用過的變量,都是nil。nil既是值,又是類型。 Boolean 布爾值 Number 數(shù)值,在Lua里,數(shù)值相當(dāng)于C語言的double String 字符串,如果你愿意的話,字符串是可以包含'\0'字符的 Table 關(guān)系表類型,這個(gè)類型功能比較強(qiáng)大,我們?cè)诤竺媛f。 Function 函數(shù)類型,不要懷疑,函數(shù)也是一種類型,也就是說,所有的函數(shù),它本身就是一個(gè)變量。 Userdata 嗯,這個(gè)類型專門用來和Lua的宿主打交道的。宿主通常是用C和C++來編寫的,在這種情況下,Userdata可以是宿主的任意數(shù)據(jù)類型,常用的有Struct和指針。 Thread 線程類型,在Lua中沒有真正的線程。Lua中可以將一個(gè)函數(shù)分成幾部份運(yùn)行。如果感興趣的話,可以去看看Lua的文檔。
V. 變量的定義 所有的語言,都要用到變量。在Lua中,不管你在什么地方使用變量,都不需要聲明,并且所有的這些變量總是全局變量,除非,你在前面加上"local"。 這一點(diǎn)要特別注意,因?yàn)槟憧赡芟朐诤瘮?shù)里使用局部變量,卻忘了用local來說明。 至于變量名字,它是大小寫相關(guān)的。也就是說,A和a是兩個(gè)不同的變量。 定義一個(gè)變量的方法就是賦值。"="操作就是用來賦值的 我們一起來定義幾種常用類型的變量吧。 A. Nil 正如前面所說的,沒有使用過的變量的值,都是Nil。有時(shí)候我們也需要將一個(gè)變量清除,這時(shí)候,我們可以直接給變量賦以nil值。如: var1=nil -- 請(qǐng)注意 nil 一定要小寫
B. Boolean 布爾值通常是用在進(jìn)行條件判斷的時(shí)候。布爾值有兩種:true 和 false。在Lua中,只有false和nil才被計(jì)算為false,而所有任何其它類型的值,都是true。比如0,空串等等,都是true。不要被C語言的習(xí)慣所誤導(dǎo),0在Lua中的的確確是true。你也可以直接給一個(gè)變量賦以Boolean類型的值,如: varboolean = true
C. Number 在Lua中,是沒有整數(shù)類型的,也不需要。一般情況下,只要數(shù)值不是很大(比如不超過100,000,000,000,000),是不會(huì)產(chǎn)生舍入誤差的。在很多CPU上,實(shí)數(shù)的運(yùn)算并不比整數(shù)慢。 實(shí)數(shù)的表示方法,同C語言類似,如: 4 0.4 4.57e-3 0.3e12 5e+20
D. String 字符串,總是一種非常常用的高級(jí)類型。在Lua中,你可以非常方便的定義很長很長的字符串。 字符串在Lua中有幾種方法來表示,最通用的方法,是用雙引號(hào)或單引號(hào)來括起一個(gè)字符串的,如: "This is a string." 和C語言相同的,它支持一些轉(zhuǎn)義字符,列表如下: \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
由于這種字符串只能寫在一行中,因此,不可避免的要用到轉(zhuǎn)義字符。加入了轉(zhuǎn)義字符的串,看起來實(shí)在是不敢恭維,比如: "one line\nnext line\n\"in quotes\", 'in quotes'" 一大堆的"\"符號(hào)讓人看起來很倒胃口。如果你與我有同感,那么,我們?cè)贚ua中,可以用另一種表示方法:用"[["和"]]"將多行的字符串括起來,如: page = [[ <HTML> <HEAD> <TITLE>An HTML Page</TITLE> </HEAD> <BODY> <A >Lua</A> [[a text between double brackets]] </BODY> </HTML> ]]
值得注意的是,在這種字符串中,如果含有單獨(dú)使用的"[["或"]]"就仍然得用"\["或"\]"來避免歧義。當(dāng)然,這種情況是極少會(huì)發(fā)生的。
E. Table 關(guān)系表類型,這是一個(gè)很強(qiáng)大的類型。我們可以把這個(gè)類型看作是一個(gè)數(shù)組。只是C語言的數(shù)組,只能用正整數(shù)來作索引;在Lua中,你可以用任意類型來作數(shù)組的索引,除了nil。同樣,在C語言中,數(shù)組的內(nèi)容只允許一種類型;在Lua中,你也可以用任意類型的值來作數(shù)組的內(nèi)容,除了nil。 Table的定義很簡單,它的主要特征是用"{"和"}"來括起一系列數(shù)據(jù)元素的。比如:
T1 = {} -- 定義一個(gè)空表 T1[1]=10 -- 然后我們就可以象C語言一樣來使用它了。 T1["John"]={Age=27, Gender="Male"} 這一句相當(dāng)于: T1["John"]={} -- 必須先定義成一個(gè)表,還記得未定義的變量是nil類型嗎 T1["John"]["Age"]=27 T1["John"]["Gender"]="Male" 當(dāng)表的索引是字符串的時(shí)候,我們可以簡寫成: T1.John={} T1.John.Age=27 T1.John.Gender="Male" 或 T1.John{Age=27, Gender="Male"} 這是一個(gè)很強(qiáng)的特性。
在定義表的時(shí)候,我們可以把所有的數(shù)據(jù)內(nèi)容一起寫在"{"和"}"之間,這樣子是非常方便,而且很好看。比如,前面的T1的定義,我們可以這么寫:
T1= { 10, -- 相當(dāng)于 [1] = 10 [100] = 40, John= -- 如果你原意,你還可以寫成:["John"] = { Age=27, -- 如果你原意,你還可以寫成:["Age"] =27 Gender=Male -- 如果你原意,你還可以寫成:["Gender"] =Male }, 20 -- 相當(dāng)于 [2] = 20 }
看起來很漂亮,不是嗎?我們?cè)趯懙臅r(shí)候,需要注意三點(diǎn): 第一,所有元素之間,總是用逗號(hào)","隔開; 第二,所有索引值都需要用"["和"]"括起來;如果是字符串,還可以去掉引號(hào)和中括號(hào); 第三,如果不寫索引,則索引就會(huì)被認(rèn)為是數(shù)字,并按順序自動(dòng)從1往后編;
表類型的構(gòu)造是如此的方便,以致于常常被人用來代替配置文件。是的,不用懷疑,它比ini文件要漂亮,并且強(qiáng)大的多。
F. Function 函數(shù),在Lua中,函數(shù)的定義也很簡單。典型的定義如下: function add(a,b) -- add 是函數(shù)名字,a和b是參數(shù)名字 return a+b -- return 用來返回函數(shù)的運(yùn)行結(jié)果 end
請(qǐng)注意,return語言一定要寫在end之前。假如你非要在中間放上一句return,那么請(qǐng)寫成:do return end。 還記得前面說過,函數(shù)也是變量類型嗎?上面的函數(shù)定義,其實(shí)相當(dāng)于: add = function (a,b) return a+b end 當(dāng)你重新給add賦值時(shí),它就不再表示這個(gè)函數(shù)了。你甚至可以賦給add任意數(shù)據(jù),包括nil (這樣,你就清除了add變量)。Function是不是很象C語言的函數(shù)指針呢?
和C語言一樣,Lua的函數(shù)可以接受可變參數(shù)個(gè)數(shù),它同樣是用"…"來定義的,比如: function sum (a,b,…) 如果想取得…所代表的參數(shù),可以在函數(shù)中訪問arg局部變量(表類型)得到。 如 sum(1,2,3,4) 則,在函數(shù)中,a = 1, b = 2, arg = {3, 4} 更可貴的是,它可以同時(shí)返回多個(gè)結(jié)果,比如: function s() return 1,2,3,4 end a,b,c,d = s() -- 此時(shí),a = 1, b = 2, c = 3, d = 4 前面說過,表類型可以擁有任意類型的值,包括函數(shù)!因此,有一個(gè)很強(qiáng)大的特性是,擁有函數(shù)的表,哦,我想更恰當(dāng)?shù)膽?yīng)該說是對(duì)象吧。Lua可以使用面向?qū)ο缶幊塘恕2恍牛磕俏遗e例如下:
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) 這一句實(shí)在是有點(diǎn)土對(duì)吧?沒關(guān)系,在Lua中,你可以簡寫成: t:add(10) -- 相當(dāng)于 t.add(t,10)
G. Userdata 和 Thread 這兩個(gè)類型的話題,超出了本文的內(nèi)容,就不打算細(xì)說了。
VI. 結(jié)束語 就這么結(jié)束了嗎?當(dāng)然不是,接下來,需要用Lua解釋器,來幫助你理解和實(shí)踐了。這篇小文只是幫助你大體了解Lua的語法。如果你有編程基礎(chǔ),相信會(huì)很快對(duì)Lua上手了。 就象C語言一樣,Lua提供了相當(dāng)多的標(biāo)準(zhǔn)函數(shù)來增強(qiáng)語言的功能。使用這些標(biāo)準(zhǔn)函數(shù),你可以很方便的操作各種數(shù)據(jù)類型,并處理輸入輸出。有關(guān)這方面的信息,你可以參考《Programming in Lua 》一書,你可以在網(wǎng)絡(luò)上直接觀看電子版,網(wǎng)址為:http://www.lua.org/pil/index.html 當(dāng)然,Lua的最強(qiáng)大的功能是能與宿主程序親蜜無間的合作,因此,下一篇文章,我會(huì)告訴大家,如何在你的程序中使用Lua語言作為腳本,使你的程序和Lua腳本進(jìn)行交互。 --------------------------------------------------------------------------------------------------------- 使用流程 1. 函數(shù)的使用 以下程序演示了如何在Lua中使用函數(shù), 及局部變量 例e02.lua -- functions function pythagorean(a, b) local c2 = a^2 + b^2 return sqrt(c2) end print(pythagorean(3,4))
運(yùn)行結(jié)果 5
程序說明 在Lua中函數(shù)的定義格式為: function 函數(shù)名(參數(shù)) ... end 與Pascal語言不同, end不需要與begin配對(duì), 只需要在函數(shù)結(jié)束后打個(gè)end就可以了. 本例函數(shù)的作用是已知直角三角形直角邊, 求斜邊長度. 參數(shù)a,b分別表示直角邊長, 在函數(shù)內(nèi)定義了local形變量用于存儲(chǔ)斜邊的平方. 與C語言相同, 定義在函數(shù)內(nèi)的代 碼不會(huì)被直接執(zhí)行, 只有主程序調(diào)用時(shí)才會(huì)被執(zhí)行. local表示定義一個(gè)局部變量, 如果不加local剛表示c2為一個(gè)全局變量, local的作用域 是在最里層的end和其配對(duì)的關(guān)鍵字之間, 如if ... end, while ... end等。全局變量的 作用域是整個(gè)程序。
2. 循環(huán)語句 例e03.lua -- Loops for i=1,5 do print("i is now " .. i) end
運(yùn)行結(jié)果 i is now 1 i is now 2 i is now 3 i is now 4 i is now 5
程序說明 這里偶們用到了for語句 for 變量 = 參數(shù)1, 參數(shù)2, 參數(shù)3 do 循環(huán)體 end 變量將以參數(shù)3為步長, 由參數(shù)1變化到參數(shù)2 例如: for i=1,f(x) do print(i) end for i=10,1,-1 do print(i) end
這里print("i is now " .. i)中,偶們用到了..,這是用來連接兩個(gè)字符串的, 偶在(1)的試試看中提到的,不知道你們答對(duì)了沒有。 雖然這里i是一個(gè)整型量,Lua在處理的時(shí)候會(huì)自動(dòng)轉(zhuǎn)成字符串型,不需偶們費(fèi)心。
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
運(yùn)行結(jié)果 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語言, 不過此處需要注意的是整個(gè)if只需要一個(gè)end, 哪怕用了多個(gè)elseif, 也是一個(gè)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循環(huán)以外, 還支持多種循環(huán), 請(qǐng)用while...do和repeat...until改寫本文中的for程序 ---------------------------------------------------------------------------------------------------------- 數(shù)組的使用
1.簡介 Lua語言只有一種基本數(shù)據(jù)結(jié)構(gòu), 那就是table, 所有其他數(shù)據(jù)結(jié)構(gòu)如數(shù)組啦, 類啦, 都可以由table實(shí)現(xiàn).
2.table的下標(biāo) 例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
輸出結(jié)果 0=foo 1=42 bar=baz
程序說明 首先定義了一個(gè)table myData={}, 然后用數(shù)字作為下標(biāo)賦了兩個(gè)值給它. 這種 定義方法類似于C中的數(shù)組, 但與數(shù)組不同的是, 每個(gè)數(shù)組元素不需要為相同類型, 就像本例中一個(gè)為整型, 一個(gè)為字符串.
程序第二部分, 以字符串做為下標(biāo), 又向table內(nèi)增加了一個(gè)元素. 這種table非常 像STL里面的map. table下標(biāo)可以為Lua所支持的任意基本類型, 除了nil值以外.
Lua對(duì)Table占用內(nèi)存的處理是自動(dòng)的, 如下面這段代碼 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, 只占用一塊內(nèi)存, 當(dāng)執(zhí)行到a = nil時(shí), b仍然指向table, 而當(dāng)執(zhí)行到b=nil時(shí), 因?yàn)闆]有指向table的變量了, 所以Lua會(huì)自動(dòng)釋放table所占內(nèi)存
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)
程序說明 首先建立一個(gè)table, 與上一例不同的是,在table的constructor里面有{x=0,y=0}, 這是什么意思呢? 這其實(shí)就是一個(gè)小table, 定義在了大table之內(nèi), 小table的 table名省略了. 最后一行myPolygon[2].x,就是大table里面小table的訪問方式. ----------------------------------------------------------------------------------------------------------- 如何簡化你的宏.
雖然以上介紹讓我們了解道宏可以完成非常強(qiáng)大的功能,但暴雪實(shí)在太小氣了,僅僅只給我們255個(gè)字符來編寫宏的內(nèi)容,假如你的宏的功能比較羅嗦,那就很麻煩了,所以以下我介紹一下一些簡化宏的小技巧:
1、定義全局變量 看完之前Lua介紹的人該都知道把,在Lua里,所有的變量都是全局變量,也就是說任何一個(gè)變量只要你在開始游戲后做過定義,那么到游戲結(jié)束時(shí)只要你不重新定義他都是有效的。但為了不讓我們自己不混淆做全局用的變量和局部使用的變量,我們可以采用大小寫區(qū)分的辦法,即大寫一律做為全局變量使用,小寫都用局部變量。 這樣,我們可以在一個(gè)宏里把自己常用的魔法/技能都定義成變量來表示,比如我是個(gè)術(shù)士,就可以這樣: F="腐蝕術(shù)(等級(jí) 3)" X="獻(xiàn)祭(等級(jí) 3)"....... 之后,我們要使用這樣魔法的時(shí)候,只要直接用F或X來代替就可以了,連""都可以省掉,是不是很方便呢~ 或者還可以把一些常見的API函數(shù)變量也自己定義: T="target" P="player"..... 使用的時(shí)候和上面一樣。
2、自定義函數(shù) 說實(shí)在話,魔獸的有些函數(shù)實(shí)在長的過頭,很多時(shí)候珍貴的字節(jié)都給函數(shù)占去了。所以必要的時(shí)候我們就得用自定義函數(shù)的方法去簡化這些函數(shù)。 自定義函數(shù)的語句為: function 函數(shù)名稱(函數(shù)變量1、函數(shù)變量2....) return 函數(shù)返回值 end 比如,使用法術(shù)的這個(gè)函數(shù)是CastByName(),我們可以在宏里這樣寫: /scirpt function C(a) CastByName(a) end 運(yùn)行后,我們其他宏使用法術(shù)就只要直接用C()就可以了,是不是很方便呢? 或是說話的函數(shù): /script function S(a) SendChatMessage(a,"SAY") end 之后你要控制人物說話就用S()就可以了。
如果是有返回值的函數(shù): /script function N(a) return UNitName(a) --return之后就是表示函數(shù)的返回值,但return必須在end前面. end 如果以后你要調(diào)用目標(biāo)的名字,直接用 x=N("target"),如果按前面第一點(diǎn)定義了全局變量的話,更簡單x=N(T)。
這樣,我們就可以把重要的字節(jié)都用在宏的判斷內(nèi)容上,而不是沉長的函數(shù)上了。如果你還有什么更好的簡化方法,可以跟貼哦。 ------------------------------------------------------------------------------------------------------- 關(guān)于背包物品使用整理類的宏的制作
由于游戲提供的函數(shù)無法直接由物品名稱調(diào)用該物品,所以通常簡單的使用物品宏是比較麻煩的,一定要把使用的物品放在背包內(nèi)特定的位置
;或則大多術(shù)士都需要的問題,能隨時(shí)監(jiān)視自己的靈魂碎片(當(dāng)然,有插件可以做到這一點(diǎn))。
以下我寫寫關(guān)于如何制作這類宏:
首先,我們要在背包里找到自己需要的東西,必須用循環(huán)里遍歷這些包。由于放的位置有2個(gè)參數(shù),1個(gè)是包的編號(hào),一個(gè)是包內(nèi)槽位的編號(hào),
所以我們需要一個(gè)循環(huán)嵌套來搜索:
以下假設(shè)我們身上都是16格的包: for bag=0,4,1 do --包的編號(hào)為從右到左,0,1,2,3,4 for cw=1,16,1 do --槽位的編號(hào)為上到下,左到右 1,2,3,4,5......16 .............. --這里我們可以寫如判斷物品是否為我們需要的東西的語句 end --表示內(nèi)循環(huán)結(jié)束 end --外循環(huán)結(jié)束
或者用其他方式做這個(gè)循環(huán): While循環(huán):while 條件 do … end
Repeat循環(huán):repeat … until 條件
然后,要處理的是物品的判斷: 我們有兩個(gè)函數(shù)可以使用 GetContainerItemLink() 和 GetContainerItemInfo() 這兩個(gè)函數(shù)使用的變量都是2個(gè),一個(gè)是包的編號(hào),一個(gè)是槽位的編號(hào),但他們的返回值不同
GetContainerItemLink()是返回一個(gè)帶著物品名字的連接,如果你用聊天函數(shù)把返回值說出來就可以看到,說出來的不光是物品的名稱,還是
一個(gè)可以連接到物品詳細(xì)內(nèi)容窗口的連接。
比如,你的包里4,1的位置放了一塊熊肉,那么用/script SendChatMessage(GetContainerItemLink(4,1),"SAY")后,就可以看到自己說“[熊
肉]”,而且用鼠標(biāo)點(diǎn)一下說的內(nèi)容,還可以彈出一個(gè)描寫這塊肉的窗口。
但要注意,直接用"[熊肉]"這樣字符串來判斷這個(gè)物品是不行的,例如:
if GetContainerItemLink(4,1)=="[熊肉]" then ..... end 這個(gè)判斷是無效的。
正確的方法是,先把要做判斷的物品的賦一個(gè)變量,再用變量做出判斷:
rou=GetContainerItemLink(4,1) --把物品連接值賦給rou
if GetContainerItemLink(4,1)==rou then ..... end --現(xiàn)在就可以正常判斷物品了
最后要注意的是,這個(gè)函數(shù)無法對(duì)術(shù)士的靈魂碎片做出正確的判斷,意思就是,雖然靈魂碎片用這個(gè)函數(shù)顯示出來是一樣的,但這個(gè)函數(shù)卻認(rèn)
為所有的靈魂碎片都是不同的東西,即你把這個(gè)靈魂碎片的連接賦給一個(gè)變量后,這個(gè)變量就只能判斷這個(gè)靈魂碎片,其他的靈魂碎片就無法
作出判斷,奇怪把。所以要判斷靈魂碎片,就必須用到第二個(gè)函數(shù)GetContainerItemInfo()
GetContainerItemInfo()的返回值非常多,幾乎所有的物品信息都可以返回,但我們這里判斷只用它返回的第一個(gè)值。 我們可以先用聊天函數(shù)來看看第一個(gè)返回值是什么樣子的: /script a=GetContainerItemInfo(4,1) SendChatMessage(a,"SAY")
可以看到,返回值相當(dāng)長的英文,但物品的關(guān)鍵字是在后面。
這樣,我們就有2種方法來使用這個(gè)函數(shù)來判斷物品。
1、和前一個(gè)函數(shù)的方法一樣,用變量存儲(chǔ)值后再判斷,前提是要把判斷的物品放在特定的位置賦一下值。 2、只使用特定物品,把物品的判斷關(guān)鍵字寫在函數(shù)里,然后用string.find()來判斷他。 例子:某物品的關(guān)鍵字是bd if string.find(GetContainerItemInfo(4,1),bd) then .....end --判斷包1,4位置是否存在關(guān)鍵字為bd物品。
接著要處理的是物品的使用和交換。 使用特定背包位置的物品函數(shù):UseContainerItem(index,slot) 這個(gè)好理解,不用多解釋了把。
拾取/放下物品的函數(shù):PickupContainerItem(index,slot) 這個(gè)函數(shù)有意思,你鼠標(biāo)上沒抓著東西的時(shí)候就是幫你拿起特定位置的物品,有的話就變成放下物品到特定的位置并交換拿起該位置的物品。
所以要完成2個(gè)物品在包內(nèi)的交換要使用3次這個(gè)函數(shù): PickupContainerItem(4,1) --拿起4,1位置的物品 PickupContainerItem(1,4) --放在1,4位置并拿起1,4位置的物品 PickupContainerItem(4,1) --把1,4位置的物品放在4,1位置
好拉,把以上幾點(diǎn)組合后宏就基本完成了:
下面的例子是關(guān)于靈魂碎片的整理,把前4個(gè)包的靈魂碎片全放到最后一個(gè)包內(nèi):
/script bag=0 cw=1 sc=1 --定義好變量,bag是包的編號(hào),cw表示查找包的槽位,sc指向最后一個(gè)包內(nèi)的槽位 for bag=0,3,1 do --從0號(hào)包開始,到3號(hào)包結(jié)束,最后一個(gè)包不搜索。 for cw=1,16,1 do --這里假設(shè)所有的包都是16個(gè)槽位的,如果沒那么多槽位的包也可以用。 if GetContainerItemLink(bag,cw)~=nil --判斷這個(gè)槽位是否是空的,是空就直接跳到下一個(gè)槽位 then if string.find(GetContainerItemInfo(bag,cw),"Gem") --判斷這個(gè)槽位里是否是靈魂碎片,Gem為靈魂碎片的關(guān)鍵字 then while string.find(GetContainerItemInfo(4,sc),"Gem") do sc=sc+1 end --這是一個(gè)小循環(huán),用于判斷最后一個(gè)包里原來是否已經(jīng)有靈魂碎片,有的話就指向包的下一個(gè)槽位 PickupContainerItem(bag,cw) PickupContainerItem(4,sc) PickupContainerItem(bag,cw) --這3句控制靈魂碎片和最后一個(gè)包內(nèi)物品的交換 sc=sc+1 --重要,不能忘記這個(gè),每放置好一個(gè)碎片后就要把最后一個(gè)包的 槽位指針指向下一個(gè)槽位,上面的小循環(huán)是無法判斷剛剛放好的碎片的。 end end end end -循環(huán)結(jié)束
完了么,當(dāng)然不行。。。因?yàn)楹甑南拗剖?55個(gè)字。所以要簡化我們的宏。
最長的內(nèi)容估計(jì)就是函數(shù)了,就先從簡化函數(shù)開始:
建立以下宏:
/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
現(xiàn)在寫的下了吧。呵呵,至于使用物品的宏我雖然已經(jīng)寫好了,但沒有測試過,等測試沒問題后再放出來把。有興趣的朋友也可以自己寫寫。
但要注意一點(diǎn),使用物品的宏只要找到物品就可以馬上跳出循環(huán),所以用Repeat循環(huán)做比較合適
|