#
多數情況下,在游戲開發過程中,我們需要經常用變換來設置角色的變換。下面以平移、縮放、旋轉來說明一點在矩陣變換中需要注意的地方。 假設有如下數據: 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.shnenglu.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++語言中的"http://"。 多行注釋中,由"--[["表示注釋開始,并且一直延續到"]]"為止。這種注釋相當于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 >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循環做比較合適
|