C++天才班

嗯~
雖然我不是一個以貌取「書」的人…
但這名字也太…

前天學長拿了一疊書來,其中有三本是圖書館借的
其中一本用C & DDraw寫小遊戲的,一本是C++基礎教學,而另一本就是「C++天才班(史帝芬‧戴維斯(Stephen R. Davix,) 作)」

說真的,在書架上看到,還真沒有動力想去翻
既然學長會挑上,應該不會雷到哪去吧~
看看目錄…
出現最多的單字「為什麼需要xxx?」(心底給它共鳴了一陣)
相信很多初學者也跟我一樣,看到xxx總是摸不著頭緒它為啥要出現?要怎麼用?
這本書就針對這種問題一一解答!

還指出一些寫程式常犯的錯誤,以及該如何去避免~
外加它使用的文字比較口語,但也並非廢言廢語,簡單來說,就是蠻平易近人的
沒看過的可以去看看哪!

我目前只看幾個主題,稍微提一下

#define 巨集
目前也偶爾動用到 #define
經它舉例之後,才發現可能有著嚴重的隱藏錯誤!

    #define df(a) a * a
   
    int main()
    {
       int i = df(2);
       // 此時 i == 4, 沒問題
      
       i = df( 1 + 2 );
       // 此時 i != 9, 問題大了!
    }

後者的算式可視為
    i = 1 + 2 * 1 + 2;

依照優先順序,變成
    i = 1 + (2 * 1) + 2;

所以 i 就等於 5 了!
恐怖啊!看到了整個心寒了一陣,大概知道為何以前有找不出來的問題了…Orz

窄字元 To 寬字元 To 窄字元

這篇早早就打算放上來的說…終於說服我自己別那麼墮落了O_O"
參考來源一樣在最底下^^

OK~
先看看兩者差別,第三項為代表編碼

    短字元、8bit、ANSI
    寬字元、16bit、UNICODE

先準備寬窄字元字串

    char cStr[] = "這是窄字元字串!";
    wchar_t wStr[] = L"寬字元、寬字元!";

在轉換之前,需要一個緩衝區去接受結果
首先要知道字串轉換後所需的長度,API提供了一個簡單的函式來查詢

    int iLen = MultiByteToWideChar( CP_ACP, 0, cStr, -1, NULL, 0 );
   
int iLen = WideCharToMultiByte( CP_ACP, 0, wStr, -1, NULL, 0, NULL, FALSE );

如同字面上所表示的,這就是用來轉換用的函式
只要將第4個參數設為 -1 ,就會回傳轉換後的寬窄字元長度
所以,可以向系統索取記憶體來用啦!

    wchar_t* lpwStr = new wchar_t[iLen];
    char* lpcStr = new char[
iLen];

接著就可以開始轉換了!

    MultiByteToWideChar( CP_ACP, 0, cStr, -1, lpwStr, iLen );
   
WideCharToMultiByte( CP_ACP, 0, wStr, -1, NULL, 0, lpcStr, iLen );

OK!這樣緩衝區的東西就可以用了!

順便看一下MSDN裡兩個函式的說明
    int MultiByteToWideChar(
        UINT CodePage,                            // code page
        DWORD dwFlags,                         // character-type options
        LPCSTR lpMultiByteStr,                // string to map
        int cbMultiByte,                             // number of bytes in string
        LPWSTR lpWideCharStr,              // wide-character buffer
        int cchWideChar                            // size of buffer
    );

    int WideCharToMultiByte(
        UINT CodePage,                           // code page
        DWORD dwFlags,                        // performance and mapping flags
        LPCWSTR lpWideCharStr,           // wide-character string
        int cchWideChar,                           // number of chars in string.
        LPSTR lpMultiByteStr,                  // buffer for new string
        int cbMultiByte,                             // size of buffer
        LPCSTR lpDefaultChar,                // default for unmappable chars
        LPBOOL lpUsedDefaultChar        // set when default char used
    );


其中共有的 CodePage 是個很重要的東西…
目前看到的文章,有的直接指定代碼(例如:936→簡中、950→繁中之類的)
或者都只用
CP_ACP ,也就是 ANSI code page …
總之…

我不知道怎麼用 囧
不過目前都還沒出問題…就請高人來為我解釋了
有錯誤記得說一下嘿!!

來源:
    酱坛子 – MultiByteToWideChar和WideCharToMultiByte用法详解
    明浚大大

Main、argc、argv

最近在看C++ Sockets Library時,竟然開始使用main的這兩個函數
以前一直都沒用過,也沒需要,所以一直沒去搞懂…
趁著這次,就去找找相關資料囉!
都是轉貼過來的O_O(原始連結在最底下)

C/C++語言中的main函數,經常帶有參數argc,argv,如下:

 int main(int argc, char** argv)
 int main(int argc, char* argv[])

這兩個參數的作用是什麼呢?argc 是指命令行輸入參數的個數,argv 存儲了所有的命令行參數。假如你的程式是hello.exe,如果在命令行(執行 > cmd)運行該程式,運行命令為:

 hello.exe Shiqi Yu

那麼,argc的值是 3,argv[0]是"hello.exe",argv[1]是"Shiqi",argv[2]是"Yu"。 Image:Hello-argc-argv.png

下面的程式演示 argc 和 argv 的使用:

    #include <stdio.h>

    int main(int argc, char ** argv)
    {
        int i;
        for ( i=0; i < argc; i++ )
        printf( "Argument %d is %s.\n", i, argv[i] );
        return 0;
    }

假如上述代碼編譯為 hello.exe,那麼運行

 hello.exe a b c d e

將得到

 Argument 0 is hello.exe.
Argument 1 is a.
Argument 2 is b.
Argument 3 is c.
Argument 4 is d.
Argument 5 is e.

運行

 hello.exe lena.jpg

將得到

 Argument 0 is hello.exe.
Argument 1 is lena.jpg.


來源:
       OpenCV China

DirectX – Acquire() 與 E_ACCESSDENIED 之章

啊哈哈~~
基本上在解決之前接近瘋狂狀態了

話說今天開始把 DirectInput 改寫成類別
在最後測試的時候,在 Acquire() 的地方出錯
回傳了 E_ACCESSDENIED 给我。
經過交叉測試,最後把先前的成功範例拿出來 compiler
還是給了我 E_ACCESSDENIED…

在暴走了許久後,終於在網路海海之中找到解決方案!

引用 上一章 的範例片段:

    m_pDIKeyboardDevice->Acquire();

初始化時,都會加上 FAILED() 檢查是否取得成功
現在改成不檢查了!
不然就是要使用的時候才測試是否取得囉!

嘿~這就是解決方案XD(要說是逃避方案也形吧!)

目前用來似乎真的可以這樣做,就暫定為正式解決的方案吧!
CSDN大好啊!一堆問題都有解答

◎參考:
    http://tag.csdn.net/Article/d15ea0e3-ffc2-48bb-9106-72ace270e5f0.html
    http://blog.csdn.net/Garfield/archive/2005/02/21/295317.aspx

DirectX – 輸入之章

DirectInput 應該是 DirectX 裡面最簡單的介面吧
相較於 Win32 的基本輸入,不用通過 windows 取得,所以相對較快速。

網站上的說法:

    * It enables an application to retrieve data from input devices even when the application is in the background
    * It provides full support for any type of input device, as well as for force feedback
    * Through action mapping, applications can retrieve input data without needing to know what kind of device is being used to generate it.

總而言之,就是有好處才會有人用咩~(不負責宣言)
對了,因為 DirectInput  從 8 版之後就沒更新了,所以底下都使用 8 版,而非 9 版唷!


DirectInput 也是 COM 物件,所以先來介紹主要的介面~

 物件  說明
 IDirectInput8  DirectInput 最主要的介面,其他相關介面從這可取得。
 IDirectInputDevice8  設備的介面,每種設備都有各自的介面。
 IDirectInputEffect  動力回饋(force feedback)介面,某些滑鼠或搖桿會用到。

首先,先引入 dinput.h 並連結 dinput8.lib 和 dxguid.lib (globally unique identifier for DX)

    #include <dinput.h>

接著宣告主要介面和設備介面的指標

    // 主要介面的指標
    LPDIRECTINPUT8 m_pDInput;

    // 鍵盤界面的指標,如需要滑鼠或搖桿,需要再另外增加!
    LPDIRECTINPUTDEVICE8 m_pDIKeyboardDevice;

接著創造主要介面

    DirectInput8Create(inst, DIRECTINPUT_VERSION,
                    IID_IDirectInput8, (void**)&
m_pDInput, NULL);

第一個參數需要視窗的 instance。
第二個參數是 DirectInput 的版本。
第三格參數是我們需要創造的介面種類,來自
dxguid.lib
第四個參數是我們的介面指標。
第五個參數是進階使用,通常設為 NULL。

接著創造鍵盤介面

    m_pDInput->CreateDevice(GUID_SysKeyboard, &m_pDIKeyboardDevice, NULL);

第一個參數是我們要創造的設備種類,鍵盤(GUID_SysKeyboard)、滑鼠(GUID_SysMouse)、搖桿(GUID_Joystick)。
第二個參數是我們的設備介面指標。
第三個是進階使用,通常設為 NULL。

接著設定設備的資料結構

    m_pDIKeyboardDevice->SetDataFormat( &c_dfDIKeyboard );

唯一的參數是我們需要的資料格式,鍵盤(c_dfDIKeyboard)、滑鼠(c_dfDIMouse)、搖桿(c_dfDIJoystick)。

接著設定合作等級

    m_pDIKeyboardDevice->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);

第一個參數需要程式的視窗代碼。
第二個參數是合作等級。

合作等即可分兩類,是否獨占與前背景讀取。

 等級  說明
 DISCL_EXCLUSIVE  獨占,就只能自己用啦!
 DISCL_NONEXCLUSIVE  共同使用,不會干擾到其他程式。
 DISCL_FOREGROUND  前景讀取,需要是(Focus)情況下才能讀取資料,失焦時會自動變成無法讀取。
 DISCL_BACKGROUND  不管在縮小或其他程式中,一樣可讀取資料。
 DISCL_NOWINKEY  取消 Windows 商標按鍵的作用。

OK!都準備完成了,接下來開始使用吧!



使用鍵盤設備

第一件事情就是先取得設備

    m_pDIKeyboar
dDevice
->Acquire();

並不需要每次讀取資料就取得一次,除非第一次或喪失該設備(縮小或Alt-Tab之類)才須取得。

我們需要一個 256 Byte 的緩衝區,使用 GetDeviceState 來接收鍵盤資訊

    BYTE keys[256];

    // 使用前先初使化緩衝區
    ZeroMemory(keys, sizeof(keys) );
   
m_pDIKeyboardDevice->GetDeviceState( sizeof(keys), keys );

和 Win32 一樣,每個按鍵都有一個巨集,在  DirectInput 裡面是以 DIK_ 開頭的。
例如按鍵 A 定義為 DIK_A,Esc 鍵則定義為 DIK_ESCAPE。

偵測 Esc 被按下

    if (keys[DIK_ESCAPE] & 0x80)
        //  Esc 被按下

偵測 X 被按下

    if (keys[DIK_X] & 0x80)
        // x 被按下

最後,使用完之後可別忘記要釋放他們唷!

    if (m_pDIKeyboardDevice)
    {
       
m_pDIKeyboardDevice->Unacquire();
       
m_pDIKeyboardDevice->Release();
    }

    if (m_pDInput)
       
m_pDInput->Release();



如何偵測設備喪失!

當我們在取得設備資訊時,如果該設備 lost 掉,會回傳 DIERR_INPUTLOST 的錯誤碼。
利用回圈檢查,直到取得為止才繼續執行。

    hr = m_pDIKeyboardDevice->GetDeviceState( sizeof(keys), keys );

    if (FAILED(hr))
    {
        hr = m_pDIKeyboardDevice->Acquire();

        while( hr == DIERR_INPUTLOST )
        {
            hr = m_pDIKeyboardDevice->Acquire();
        }

        // 成功取得後再取得一次資訊
        m_pDIKeyboardDevice->GetDeviceState( sizeof(keys), keys );
    }



使用滑鼠設備

前置作業都差不多,參數不同而已,不贅述囉~

    m_pDInput->CreateDevice(GUID_SysMouse, &m_pDIMouseDevice, NUL
L
);
   
m_pDIMouseDevice->SetDataFormat(&c_dfDIMouse2);
   
m_pDIMouseDevice->SetCooperativeLevel(hWnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);

如同鍵盤一樣,需要一塊緩衝區來使用 GetDeviceState 讀取資料

    DIMOUSESTATE2 m_mouseState;
    ZeroMemory( &m_mouseState, sizeof(m_mouseState) );
   
m_pDIMouseDevice->GetDeviceState( sizeof(DIMOUSESTATE2), &m_mouseState );

同樣要檢查是否回傳 DIERR_INPUTLOST 嘿!
注意,這裏使用 c_dfDIMouse2 和  DIMOUSESTATE2 ,是擁有八顆鍵的滑鼠(應該…)

DIMOUSESTATE2  的資料結構:

    typedef struct _DIMOUSESTATE2 {
        LONG    lX;   // X
的偏移量
        LONG    lY;   // Y
的偏移量
        LONG    lZ;    // Z軸的偏移量
        BYTE    rgbButtons[8];   // 每個按鈕的狀態
    } DIMOUSESTATE2, *LPDIMOUSESTATE2;

記住,是偏移量,而不是位置
所以通常在初始化時會先利用 GetCursorPos 或者 WM_MOUSEMOVE 時取得一開始的位置
然後在隨時更新座標即可!

    // 宣告兩個變數儲存位置
    long g_MouseXPos = 0;
    long g_MouseYPos = 0;

    // 遊戲迴圈
    void Run()
    {
       // 讀取滑鼠資料

       // 更新滑鼠的絕對座標
      
g_MouseXPos += m_mouseState.lX;
       g_MouseYPos += m_mouseState.lY;

       // 其他程式碼
    }

檢查某按鈕狀態

    if (m_mouseState.rgbButtons[0] & 0x80)
       // 左鍵(0)被按下

最後,還是別忘記用完要釋放他們唷!



◎參考:
       http://www.toymaker.info/
       2D/3D RPG角色扮演遊戲程式設計 – 使用DirectX / Jim Adams著
   
當然,還有一個 搖桿設備 的部份,不過目前還用不到,而且前置動作很雜,以後用到在補上吧XD

高精度計時器

我想,大多數的人都只用到 GetTickCount 這個函式吧?
(好啦!只有我是這樣啦!
我在書上看過的也只有它咩…(畫圓圈)

除了 GetTickCount 之外,最常用的應該就是 timeGetTime QueryPerformanceCounter 吧!
這三種的精準度眾說紛紜,在我電腦上對
GetTickCount timeGetTime 做出來的準度都在 1ms
至於
QueryPerformanceCounter 就很明顯的可以到毫毫…秒(這是誇示法
所以準到哪裡就看個人吧~

1.
timeGetTime

    和 GetTickCount 資料型態都是 DWORD 用法也很簡單
    但是,需要包含
<mmsystem.h> 和連結 winmm.lib
    連結的方式嘛~
    (1)加入程式碼
            #pragma comment(lib,"winmm.lib")

    (2)從VC裡面調整
          專案(P) → 屬性(P) → 組態屬性 → 連結器 → 輸入 → 其他相依性
          加入 winmm.lib 即可

    有部份文章提到另外兩個搭配使用的函數 timeBeginPeriod timeEndPeriod
    好像是可以調整精確度的,但是我電腦上測試沒反應
    所以這邊就不另作說明哩!

    一個簡單的 Delay 函數:

    void DelayByGetTime(DWORD delay)
    {
        timeBeginPeriod(100);

        DWORD TempTime, StartTime, DelayTime;

        DelayTime = delay;

        StartTime = timeGetTime();
        cout << "起始: " << StartTime << endl;

        do
        {
            TempTime = timeGetTime();

            if (TempTime < StartTime)
                StartTime = TempTime;

        } while( (TempTime StartTime) < DelayTime );

        cout << "結束: " << TempTime << endl;

        timeEndPeriod(100);
    }

2. QueryPerformanceCounter
   
    這個計時器是隨著系統有著不同的精準度
    而
QueryPerformanceCounter 使用的資料型態是 LARGE_INTEGER

    LARGE_INTEGER 裡便包含了:
    LowPart
        Low-order 32 bits.
    HighPart
        High-order 32 bits.
    u
        LowPart
            Low-order 32 bits.
        HighPart
            High-order 32 bits.
    QuadPart
        Signed 64-bit integer.
   
    如果編譯器支援 64-bit ,就使用
QuadPart 。不支援時,使用 LowPart 和 HighPart 去表示 QuadPart
    (不太確定是不是這意思,詳見 LARGE_INTEGER

    QueryPerformanceCounter 通常要和 QueryPerformanceFrequency 才能計算時間
   
QueryPerformanceCounter 取得到目前為止的次數
   
QueryPerformanceFrequency 取得計時器的頻率(次數/秒),目前我的是 3579545 次/秒
    照這樣計算的話,大概可以計算到毫毫秒了吧XD

    int main(void)
    {
        LARGE_INTEGER ThisTime, ThatTime, PinTime;

        QueryPerformanceFrequency(&PinTime);

        QueryPerformanceCounter(&ThisTime);
   
        Sleep(1111);

        QueryPerformanceCounter(&ThatTime);

        cout << "起始值: " << ThisTime.QuadPart << endl;
        cout << "結束值: " << ThatTime.QuadPart << endl;
        cout << "差值: " << ThatTime.QuadPart ThisTime.QuadPart << endl;
        cout << "秒: " << (ThatTime.QuadPartThisTime.QuadPart) / PinTime.QuadPart << endl;
        PinTime.QuadPart /= 1000;
        cout << "毫秒: " << (ThatTime.QuadPart ThisTime.QuadPart) / PinTime.QuadPart << endl;
        PinTime.QuadPart
/= 1000;
        cout << "毫毫秒: " << (ThatTime.QuadPart ThisTime.QuadPart) / PinTime.QuadPart << endl;

        system("pause");
        return 0;
    }

◎參考:


哎呀~在這樣下去我啥時才能寫完程式啊

檔案輸入與輸出(下)


11. 文字檔與二進位檔

     

 

 文字檔

 二進位檔

 資料可讀性

 容易

困難 

 輸入/輸出效率

 差

 佳

 檔案空間大小

 通常較大

 通常較小

 檔案可攜帶性

 通行

 差

 浮點數存取誤差

 有

 無

12. istream::read

        讀取物件.read(資料區塊的指標, 長度);
        
        例:
        ifstream InFoo("data.dat", ios::in | ios::binary);
        int buffer;
         
        InFoo.read( reinterpret_cast<char*>(&buffer), sizeof(int) );        // 讀取資料,reinterpret_cast<T>(S)後面介紹

        InFoo.close();

        同樣的,可以一次讀取整個 結構(struct)類別(class)
        例:
        struct aa
        {
                int i;
                char c[24];
                DWORD d;
        };
        
        aa* bb = new aa;

        InFoo.read( reinterpret_cast<char*>(bb), sizeof(aa) );   

13. ostream::write
        
        輸出物件.write(資料區塊的指標, 長度);

        例:
        // 複製檔案
        #include <fstream> 
        using namespace std; 

        int main () { 

                char * buffer; 
                long size; 

                ifstream infile ("test.txt",ifstream::binary); 
                ofstream outfile ("new.txt",ofstream::binary); 

                // 取得檔案大小 
                infile.seekg(0,ifstream::end); 
                size=infile.tellg(); 
                infile.seekg(0); 

                // 挖一塊與檔案大小相同的記憶體 
                buffer = new char [size]; 

                // 讀取資料 
                infile.read (buffer,size); 

                // 輸出資料
                outfile.write (buffer,size); 

                // 釋放記憶體
                delete[] buffer; 

                outfile.close(); 
                infile.close(); 
                return 0; 
        }

14. 讀寫位置
        在C++ I/O系統掌管兩個與檔案相關的指標,
一個是讀取指標,一個則是輸出指標,都是在表示目前讀寫的位置。
        接著介紹如何控制這些指標。

15. istream::seekg && ostream::seekp 
        
        這兩個分別用於輸出輸入,方式一樣

        讀取物件.seekg(相對位置, 基準值);
        輸出物件.seekp(相對位置, 基準值);

        基準值在 iostream.h 定義:
        ios::beg               // 檔案開頭 
        ios::cur                // 目前位置 
        ios::end               
// 檔案結尾 

        例:
        file1.seekg(1234,ios::cur);        // 把檔案讀取指標從目前位置向後移1234個字元
        file2.seekp(1234,ios::beg);       // 把檔案的輸出指標從開頭向後移1234個字元 


        例:
        // 讀取檔案到記憶體

        #include <iostream>
        #include <fstream>
        using namespace std;

        int main () {
                int length;
                char * buffer;

                ifstream is;
                is.open ("test.txt", ios::binary );

                // 取得檔案大小
                is.seekg (0, ios::end);
                length = is.tellg();
                is.seekg (0, ios::beg);

                // 挖記憶體
                buffer = new char [length];

                // 讀取資料
                is.read (buffer,length);

                is.close();

                cout.write (buffer,length);

                return 0;
        }

16. istream::tellg &&  ostream::tellp 

        讀取物件.tellg();
        輸出物件.tellp();

        這兩個一樣分別用於輸入輸出,用來取得目前指標的位置。
        例:
        #include <fstream.h> 

        void main() 
        { 
                // 假如我们已经在test_file.txt中存有了“Hello”的内容 
                ifstream File("test_file.txt"); 

                char arr[10]; 

                File.read(arr,10); 

                // 由于Hello占5个字符,因此这里将返回5 
                cout << File.tellg() << endl; 

                File.close(); 
        }

17. istream::ignore

        讀取物件.ignore(長度, 條件);
        
        和 seekg 很類似,但是 ignore 可以限定條件讓動作停下
        例:

        #include <fstream.h> 

        void main() 
        { 
                // 假设t
est_file.txt中已经存有"Hello World"这一内容
 
                ifstream File("test_file.txt"); 

                static char arr[10]; 

                // 假如一直没有遇到字符"l",则向前定位直到跳过6个字符 
                // 而如果期间遇到"l",则停止向前,定位在该处 
                File.ignore(6,’l’); 

                File.read(arr,10); 

                cout << arr << endl; // 它将显示"lo World!" 

                File.close(); 
        }

18. reinterpret_cast<T>(S)

        static_cast<Type> 可以轉換變數的靜態型態,跟舊式C的強制轉型蠻類似的. 
        dynamic_cast<Type> 可以把指向base class object的指標或reference轉換成指向derived class的指標或reference, 當指到的物件真的是derived class object. 
        const_cast<Type> 可以改變某物的常數性或是變易性. 
        reinterpret_cast<Type> 大多是用在轉換函式指標型別的.
        從http://www.cis.nctu.edu.tw/~is92004/article/cast轉來的@@~
        
        簡單來說,就是把S的型別看作T。

◎參考:
        
http://blog.donews.com/sunnny/
        http://www.cplusplus.com/
        PPT01
        PPT02


其實還有一些沒介紹到,但是我自己也沒過,就不多說啦
話說這裡的編輯器我還真不熟

檔案輸入與輸出(上)


文章太長,分兩篇放

1. 開啟檔案

檔案物件.open(“檔案名稱”, ios::開啟模式);

例如:

 #include <fstream.h>

 ifstream 輸入物件;           // 建立輸入檔案物件
 ofstream 輸出物件;          // 建立輸出檔案物件
 fstream 輸出入物件;        // 建立輸入輸出檔案物件

也可以建立物件時順便開啟,例如:

 ifstream 輸入物件(“檔案名稱”, ios::開啟模式);

2. 開啟模式

 ios::開啟模式  說明
 in  開啟檔案準備輸入
 out  開啟檔案準備輸出
 app  將輸出的資料接續在檔案尾端
 ate  開啟檔案,並且移到檔案尾端
 binary  以二近位的方式輸入/輸出檔案
 trunc  如果檔案存在,複寫現存的檔案
 nocreate  如果檔案不存在,則開啟檔案失敗
 noreplace  如果檔案存在,則開啟檔案失敗

※ios::trunk => ios::trunc。感謝yammilk提醒。

3. 判斷開啟檔案成功或失敗

ifstream myFile;                                      // 建立輸入檔案物件
 myFile.open("a:\\textIn.txt", ios::in);      // 開啟輸入檔案
 if(!myFile)                                             // 測試檔案是否開啟成功
 cout << "開啟檔案失敗!\n";                    // 檔案代號錯誤

另一個類似的成員「is_open()」,用來判斷該檔案流是否已經開啟檔案,避免重複開檔。

 ifstream myFile;
 if(!myFile.is_open( ))                                      // 測試檔案是否開啟成功
 cout << "尚未開啟\n";
 ifstream myFile("a:\\textIn.txt", ios::in);       // 建立並開啟檔案
 if(myFile.is_open())                                       // 測試檔案是否開啟成功
 cout << "檔案已經開啟\n";

4. fstream::close

物件名稱.close();

例如:

 myFile.close();


關閉檔案後,物件仍然存在,可以繼續開啟其他檔案!
如果要這樣做,請記得和 clear() 搭配使用。

 ifstream myFile("text.txt");
 // 努力用myFile
 myFile.clear();
 myFile.close();
 myFile.open(text2.txt);


5. ostream::operator <<

物件名稱 << 輸出字串;    // 寫入檔案

例如:

 ofstream filePtr;                                      // 建立檔案物件filePtr
 filePtr.open("a:\\textIO.txt", ios::out);     // 開啟檔案a:\textIO.txt
 filePtr << "Life is not easy, but in the long run \n";
 filePtr << "it's easier than going to elaborate ends \n";
 filePtr << "to deny it.\n";                           // 寫入字串到檔案
 filePtr.close();                                          // 關閉filePtr檔案物件

6. istream::operator >>

物件名稱 >> 緩衝區1 >> 緩衝區2;    //讀取資料

例如:

 ifstream inFile;                                     // 建立檔案物件inFile
 int id;                                                   // 存放資料緩衝區
 char name[40];                                      // 存放資料緩衝區
 inFile.open("a:textIO.dat", ios::in);        // 開啟輸入檔a:\textIO.dat
 inFile >> id >> name;                               // 讀取檔案存入緩衝區
 
while(!inFile.eof())                                // 是否已到檔尾
 {
     cout << id << '\t' << name << endl;         // 顯示緩衝區資料
     inFile >> id >> name;                          // 讀取檔案存入緩衝區
 }

 inFile.close();                                       // 關閉檔案物件inFile

7. ostream::put

物件名稱.put(字元緩衝區);

例如:

 ofstream filePtr;                                         // 建立檔案物件filePtr
 char inData[ ] = "Life is not easy, but in the long run \n“;

 filePtr.open("a:\\textIO.txt", ios::out);        // 開啟輸入檔a:\textIO.txt
 int len = strlen(inData);                               // 取得inData陣列的長度

 for(int i=0; i<len; i++)                                  // 寫入字元迴圈
 {
     filePtr.put(inData[i]);                             // 寫入字元到textIO.txt
 }

 filePtr.close();                                           // 關閉檔案物件filePtr

8. istream::get

物件名稱.get(字元緩衝區);

例如:

 ifstream filePtr;                                         // 建立檔案物件filePtr
 char inData;                                               // 存放資料緩衝區

 filePtr.open("a:\\textIO.txt", ios::in);         // 開啟輸出檔a:\textIO.txt

 while(filePtr.get(inData))                            // 取得資料並存入緩衝區
 {
     cout << inData;                                        // 顯示資料
 }

 filePtr.close();                                            // 關閉檔案物件filePtr

9. iso::eof

輸入檔案物件.eof();

例如:

 ifstream filePtr;      // 建立檔案物件filePtr
 char inData;            // 存放資料緩衝區

 filePtr.open("a:\\textIO.txt", ios::in);


為了改一個字,竟然導致整個語法垮掉
所以重新整理了一次,下篇有空再遵照這篇的模式整理。

— Next

BMP檔解析筆記

話說,上禮拜下了個決心,那就是寫一個封裝的程式,就是把一堆圖檔音樂什麼鬼的都丟在一個檔案裡面那樣
既然如此,就得深入該檔案的格式和內容了
本來很狂妄的想直接從PNG開始,最終還是不敵英文的猛攻,轉戰最通用也最多資料也最簡單的BMP檔
雖說簡單,但也搞了兩三天,不斷的爬文在爬文,還真是辛苦哪XD

 

點陣圖檔主要分為四部份:

  • 點陣圖標頭檔:點陣圖檔案資訊。
    Bitmap Header: stores general information about the bitmap file.
  • 點陣圖資訊:點陣圖影像的資訊。
    Bitmap Information: stores detailed information about the bitmap image.
  • 調色盤:所用到的顏色資料。
    Color Palette: stores the definition of the colors being used.
  • 點陣圖資料:每個像素的資料。
    Bitmap Data: stores the actual image, pixel by pixel.

其中,24位元的圖檔,每個像素都可視為一個調色盤,故沒有調色盤的資料。

 

以下用程式說明,我覺得比較簡單…

BITMAPFILEHEADER pBmp;  // 用來儲存標頭檔的結構

typedef struct tagBITMAPFILEHEADER {
        WORD bfType;
        DWORD bfSize;
        WORD bfReserved1;
        WORD bfReserved2;
        DWORD bfOffBits;
} BITMAPFILEHEADER;

BITMAPINFOHEADER pBMIH;  // 用來儲存資訊的結構

typedef struct tagBITMAPINFOHEADER{
        DWORD biSize;
        LONG biWidth;
        LONG biHeight;
        WORD biPlanes;
        WORD biBitCount;
        DWORD biCompression;
        DWORD biSizeImage;
        LONG biXPelsPerMeter;
        LONG biYPelsPerMeter;
        DWORD biClrUsed;
        DWORD biClrImportant;
} BITMAPINFOHEADER

structRGB TempRGB;  // 用來儲存點陣圖資料的結構

// 當然要先開一個BMP檔哩!
ifstream infile("data\\Title.bmp", ios_base::in | ios_base::binary);

《點陣圖標頭檔》

// [0~1][WORD] 點陣圖檔案的辨識符號,通常都是 0x42 0x4D ( ‘B‘ & ‘M’)
infile.read(reinterpret_cast<char*>(&pBmp.bfType), sizeof(WORD));

// [2~5][DWORD] 整個點陣圖檔案的大小
infile.read(reinterpret_cast<char*>(&pBmp.bfSize), sizeof(DWORD));

// [6~7][WORD] & [8~9][WORD] 保留的空間,應該沒啥用
infile.read(reinterpret_cast<char*>(&pBmp.bfReserved1), sizeof(WORD));
infile.read(reinterpret_cast<char*>(&pBmp.bfReserved2), sizeof(WORD));

// [10~13][DWORD] 點陣圖標頭+資訊的大小
// 換句話說,檔案開頭的位址 + 這個大小 = 點陣圖資料的起始位址
infile.read(reinterpret_cast<char*>(&pBmp.bfOffBits), sizeof(DWORD));

《點陣圖資訊》

// [14~17][DWORD] 資訊部分的大小
infile.read(reinterpret_cast<char*>(&pBMIH.biSize), sizeof(DWORD));

// [18~21][DWORD] 影像的寬度
infile.read(reinterpret_cast<char*>(&pBMIH.biWidth), sizeof(DWORD));

// [22~25][DWORD] 影像的高度
infile.read(reinterpret_cast<char*>(&pBMIH.biHeight), sizeof(DWORD));

// [26~27][WORD] 色彩面的個數(硬翻的),通常沒在用
infile.read(reinterpret_cast<char*>(&pBMIH.biPlanes), sizeof(WORD));

// [28~29][WORD] 每個像素的位元數,也就是色彩深度,通常為 1, 4, 8, 24, 32
infile.read(reinterpret_cast<char*>(&pBMIH.biBitCount), sizeof(WORD));

// [30~33][DWORD] 壓縮演算法,未壓縮則為 0,詳請看wiki
infile.read(reinterpret_cast<char*>(&pBMIH.biCompression), sizeof(DWORD));

// [34~37][DWORD] 點陣圖影像的大小(和檔案大小不同)
infile.read(reinterpret_cast<char*>(&pBMIH.biSizeImage), sizeof(DWORD));

// [38~41][LONG] 水平解析度
// [42~45][LONG] 垂直解析度
infile.read(reinterpret_cast<char*>(&pBMIH.biXPelsPerMeter), sizeof(LONG));
infile.read(reinterpret_cast<char*>(&pBMIH.biYPelsPerMeter), sizeof(LONG));

// [46~49][DWORD] 所用顏色的數目
infile.read(reinterpret_cast<char*>(&pBMIH.biClrUsed), sizeof(DWORD));

// [50~53][DWORD] 重要顏色的數目,如果都很重要,則與 "所用顏色的數目" 相等
infile.read
(reinterpret_cast<char*>(&pBMIH.biClrImportant), sizeof(DWORD));

到此總共 54 位元,基本上點陣圖檔的標頭+資訊都這麼大

調色盤,目前用途只需要24位元即可,所以省略調色盤~

接著就是最重要的像素資料啦~

24位元的像素資料由 4 個 DWORD 組成,分別為:(保留)、R、G、B,四個值表示如下:

typedef struct
{
        DWORD reserve;
        DWORD r;
        DWORD g;
        DWORD b;
}structRGB;

資料參考:

http://www.thethirdmedia.com/pc/200407/20040722117029.shtm
http://www.shengfang.org/blog/p/bmpformat.php
http://zh.wikipedia.org/wiki/BMP
http://en.wikipedia.org/wiki/Windows_and_OS/2_bitmap
http://andrew.csie.ncyu.edu.tw/DOC2/BMP.doc
http://big5.yesky.com/b5/dev.yesky.com/164/2291664.shtml

還有最重要的 Yahoo!Google !!

◎補充:

點陣圖的像素資料儲存的方法是:由下往上,由左往右。
所以第一筆像素資料是在圖片的左下角,會後一筆資料在右上角。