用Visual C++操作INI文件
在我們寫的程序當中,總有一些配置信息需要保存下來,以便完成程序的功能,最簡單的辦法就是將這些信息寫入INI文件中,程序初始化時再讀入.具體應用如下:
一.將信息寫入.INI文件中.
1.所用的WINAPI函數原型為:
BOOL WritePrivateProfileString(
LPCTSTR lpAppName,
LPCTSTR lpKeyName,
LPCTSTR lpString,
LPCTSTR lpFileName
);
其中各參數的意義:
LPCTSTR lpAppName 是INI文件中的一個字段名.
LPCTSTR lpKeyName 是lpAppName下的一個鍵名,通俗講就是變量名.
LPCTSTR lpString 是鍵值,也就是變量的值,不過必須為LPCTSTR型或CString型的.
LPCTSTR lpFileName 是完整的INI文件名.
2.具體使用方法:設現有一名學生,需把他的姓名和年齡寫入 c:\stud\student.ini 文件中.
CString strName,strTemp;
int nAge;
strName="張三";
nAge=12;
::WritePrivateProfileString("StudentInfo","Name",strName,"c:\\stud\\student.ini");
此時c:\stud\student.ini文件中的內容如下:
[StudentInfo]
Name=張三
3.要將學生的年齡保存下來,只需將整型的值變為字符型即可:
strTemp.Format("%d",nAge);
::WritePrivateProfileString("StudentInfo","Age",strTemp,"c:\\stud\\student.ini");
二.將信息從INI文件中讀入程序中的變量.
1.所用的WINAPI函數原型為:
DWORD GetPrivateProfileString(
LPCTSTR lpAppName,
LPCTSTR lpKeyName,
LPCTSTR lpDefault,
LPTSTR lpReturnedString,
DWORD nSize,
LPCTSTR lpFileName
);
其中各參數的意義:
前二個參數與 WritePrivateProfileString中的意義一樣.
lpDefault : 如果INI文件中沒有前兩個參數指定的字段名或鍵名,則將此值賦給變量.
lpReturnedString : 接收INI文件中的值的CString對象,即目的緩存器.
nSize : 目的緩存器的大小.
lpFileName : 是完整的INI文件名.
2.具體使用方法:現要將上一步中寫入的學生的信息讀入程序中.
CString strStudName;
int nStudAge;
GetPrivateProfileString("StudentInfo","Name","默認姓名",strStudName.GetBuffer(MAX_PATH),MAX_PATH,"c:\\stud\\student.ini");
執行后 strStudName 的值為:"張三",若前兩個參數有誤,其值為:"默認姓名".
3.讀入整型值要用另一個WINAPI函數:
UINT GetPrivateProfileInt(
LPCTSTR lpAppName,
LPCTSTR lpKeyName,
INT nDefault,
LPCTSTR lpFileName
);
這里的參數意義與上相同.使用方法如下:
nStudAge=GetPrivateProfileInt("StudentInfo","Age",10,"c:\\stud\\student.ini");
三.循環寫入多個值,設現有一程序,要將最近使用的幾個文件名保存下來,具體程序如下:
1.寫入:
CString strTemp,strTempA;
int i;
int nCount=6;
file://共有6個文件名需要保存
for(i=0;i<nCount;i++)
{
strTemp.Format("%d",i);
strTempA=文件名;
//文件名可以從數組,列表框等處取得.
::WritePrivateProfileString("UseFileName","FileName"+strTemp,strTempA,"c:\\usefile\\usefile.ini");
}
strTemp.Format("%d",nCount);
::WritePrivateProfileString("FileCount","Count",strTemp,"c:\\usefile\\usefile.ini");
//將文件總數寫入,以便讀出.
2.讀出:
nCount=::GetPrivateProfileInt("FileCount","Count",0,"c:\\usefile\\usefile.ini");
for(i=0;i<nCount;i++)
{
strTemp.Format("%d",i);
strTemp="FileName"+strTemp;
::GetPrivateProfileString("CurrentIni",strTemp,"default.fil", strTempA.GetBuffer(MAX_PATH),MAX_PATH,"c:\\usefile\\usefile.ini");
//使用strTempA中的內容.
}
補充四點:
1.INI文件的路徑必須完整,文件名前面的各級目錄必須存在,否則寫入不成功,該函數返回 FALSE 值.
2.文件名的路徑中必須為 \\ ,因為在VC++中, \\ 才表示一個 \ .
3.也可將INI文件放在程序所在目錄,此時 lpFileName 參數為: ".\\student.ini".
//----------------------------------------------------------------------------------
/*
類名:CIni
版本:v2.0
加入高級操作的功能
v1.0
夢小孩于2003年某日
一般操作完成
類描述:
本類可以于.ini文件進行操作
*/
文件 1:
#pragma once
#include "afxTempl.h"
class CIni
{
private:
CString m_strFileName;
public:
CIni(CString strFileName):m_strFileName(strFileName)
{
}
public:
//一般性操作:
BOOL SetFileName(LPCTSTR lpFileName); //設置文件名
CString GetFileName(void); //獲得文件名
BOOL SetValue(LPCTSTR lpSection, LPCTSTR lpKey, LPCTSTR lpValue,bool bCreate=true); //設置鍵值,bCreate是指段名及鍵名未存在時,是否創建。
CString GetValue(LPCTSTR lpSection, LPCTSTR lpKey); //得到鍵值.
BOOL DelSection(LPCTSTR strSection); //刪除段名
BOOL DelKey(LPCTSTR lpSection, LPCTSTR lpKey); //刪除鍵名
public:
//高級操作:
int GetSections(CStringArray& arrSection); //枚舉出全部的段名
int GetKeyValues(CStringArray& arrKey,CStringArray& arrValue,LPCTSTR lpSection); //枚舉出一段內的全部鍵名及值
BOOL DelAllSections();
};
文件 2:
#include "StdAfx.h"
#include "ini.h"
#define MAX_ALLSECTIONS 2048 //全部的段名
#define MAX_SECTION 260 //一個段名長度
#define MAX_ALLKEYS 6000 //全部的鍵名
#define MAX_KEY 260 //一個鍵名長度
BOOL CIni::SetFileName(LPCTSTR lpFileName)
{
CFile file;
CFileStatus status;
if(!file.GetStatus(lpFileName,status))
return TRUE;
m_strFileName=lpFileName;
return FALSE;
}
CString CIni::GetFileName(void)
{
return m_strFileName;
}
BOOL CIni::SetValue(LPCTSTR lpSection, LPCTSTR lpKey, LPCTSTR lpValue,bool bCreate)
{
TCHAR lpTemp[MAX_PATH] ={0};
//以下if語句表示如果設置bCreate為false時,當沒有這個鍵名時則返回TRUE(表示出錯)
//!*&*none-value*&!* 這是個垃圾字符沒有特別意義,這樣亂寫是防止湊巧相同。
if (!bCreate)
{
GetPrivateProfileString(lpSection,lpKey,"!*&*none-value*&!*",lpTemp,MAX_PATH,m_strFileName);
if(strcmp(lpTemp,"!*&*none-value*&!*")==0)
return TRUE;
}
if(WritePrivateProfileString(lpSection,lpKey,lpValue,m_strFileName))
return FALSE;
else
return GetLastError();
}
CString CIni::GetValue(LPCTSTR lpSection, LPCTSTR lpKey)
{
DWORD dValue;
TCHAR lpValue[MAX_PATH] ={0};
dValue=GetPrivateProfileString(lpSection,lpKey,"",lpValue,MAX_PATH,m_strFileName);
return lpValue;
}
BOOL CIni::DelSection(LPCTSTR lpSection)
{
if(WritePrivateProfileString(lpSection,NULL,NULL,m_strFileName))
return FALSE;
else
return GetLastError();
}
BOOL CIni::DelKey(LPCTSTR lpSection, LPCTSTR lpKey)
{
if(WritePrivateProfileString(lpSection,lpKey,NULL,m_strFileName))
return FALSE;
else
return GetLastError();
}
int CIni::GetSections(CStringArray& arrSection)
{
/*
本函數基礎:
GetPrivateProfileSectionNames - 從 ini 文件中獲得 Section 的名稱
如果 ini 中有兩個 Section: [sec1] 和 [sec2],則返回的是 'sec1',0,'sec2',0,0 ,當你不知道
ini 中有哪些 section 的時候可以用這個 api 來獲取名稱
*/
int i;
int iPos=0;
int iMaxCount;
TCHAR chSectionNames[MAX_ALLSECTIONS]={0}; //總的提出來的字符串
TCHAR chSection[MAX_SECTION]={0}; //存放一個段名。
GetPrivateProfileSectionNames(chSectionNames,MAX_ALLSECTIONS,m_strFileName);
//以下循環,截斷到兩個連續的0
for(i=0;i<MAX_ALLSECTIONS;i++)
{
if (chSectionNames[i]==0)
if (chSectionNames[i]==chSectionNames[i+1])
break;
}
iMaxCount=i+1; //要多一個0號元素。即找出全部字符串的結束部分。
arrSection.RemoveAll();//清空原數組
for(i=0;i<iMaxCount;i++)
{
chSection[iPos++]=chSectionNames[i];
if(chSectionNames[i]==0)
{
arrSection.Add(chSection);
memset(chSection,0,MAX_SECTION);
iPos=0;
}
}
return (int)arrSection.GetSize();
}
int CIni::GetKeyValues(CStringArray& arrKey,CStringArray& arrValue, LPCTSTR lpSection)
{
/*
本函數基礎:
GetPrivateProfileSection- 從 ini 文件中獲得一個Section的全部鍵名及值名
如果ini中有一個段,其下有 "段1=值1" "段2=值2",則返回的是 '段1=值1',0,'段2=值2',0,0 ,當你不知道
獲得一個段中的所有鍵及值可以用這個。
*/
int i;
int iPos=0;
CString strKeyValue;
int iMaxCount;
TCHAR chKeyNames[MAX_ALLKEYS]={0}; //總的提出來的字符串
TCHAR chKey[MAX_KEY]={0}; //提出來的一個鍵名
GetPrivateProfileSection(lpSection,chKeyNames,MAX_ALLKEYS,m_strFileName);
for(i=0;i<MAX_ALLKEYS;i++)
{
if (chKeyNames[i]==0)
if (chKeyNames[i]==chKeyNames[i+1])
break;
}
iMaxCount=i+1; //要多一個0號元素。即找出全部字符串的結束部分。
arrKey.RemoveAll();//清空原數組
arrValue.RemoveAll();
for(i=0;i<iMaxCount;i++)
{
chKey[iPos++]=chKeyNames[i];
if(chKeyNames[i]==0)
{
strKeyValue=chKey;
arrKey.Add(strKeyValue.Left(strKeyValue.Find("=")));
arrValue.Add(strKeyValue.Mid(strKeyValue.Find("=")+1));
memset(chKey,0,MAX_KEY);
iPos=0;
}
}
return (int)arrKey.GetSize();
}
BOOL CIni::DelAllSections()
{
int nSection;
CStringArray arrSection;
nSection=GetSections(arrSection);
for(int i=0;i<nSection;i++)
{
if(DelSection(arrSection[i]))
return GetLastError();
}
return FALSE;
}
使用方法:
CIni ini("c:\\a.ini");
int n;
/*獲得值
TRACE("%s",ini.GetValue("段1","鍵1"));
*/
/*添加值
ini.SetValue("自定義段","鍵1","值");
ini.SetValue("自定義段2","鍵1","值",false);
*/
/*枚舉全部段名
CStringArray arrSection;
n=ini.GetSections(arrSection);
for(int i=0;i<n;i++)
TRACE("%s\n",arrSection[i]);
*/
/*枚舉全部鍵名及值
CStringArray arrKey,arrValue;
n=ini.GetKeyValues(arrKey,arrValue,"段1");
for(int i=0;i<n;i++)
TRACE("鍵:%s\n值:%s\n",arrKey[i],arrValue[i]);
*/
/*刪除鍵值
ini.DelKey("段1","鍵1");
*/
/*刪除段
ini.DelSection("段1");
*/
/*刪除全部
ini.DelAllSections();
*/
操作配置文件ini
1.基礎知識
INI文件(Initialization file ,又稱為初始化文件)是用來保存應用程序設置和選項的一種特殊的ASCII文件,以“.ini”作為文件擴展名,也被稱做配置文件或概要文件(Profile)。除了各個應用程序可以擁有自己私有的初始化文件外,Windows系統還提供有一個系統的初始化文件Win.ini,并由此對當前的Windows系統進行配置,同時也可以在其內記錄系統內其他應用程序在運行時的選項。
通常為應用程序所私有的初始化文件比較小,這樣可以減少程序在初始化時所讀取的信息量,從而提高程序的啟動速度。而系統初始化文件Win.ini由于除了記錄有關系統的大量信息外,還存儲著許多其他應用軟件的初始化數據,因此其通常比較龐大,訪問的數據量要遠比私有的配置文件大得多。如沒有必要,一般不建議對Win.ini文件進行操作,但如果待存取的信息涉及到Windows系統環境或是其他應用程序時, 就必須對Win.ini進行讀寫訪問,并在訪問的同時發送WM_WININICHANGE消息給所有的頂層窗口,通知其他進程系統初始化文件已被更改。
配置文件里的信息之所以能為系統和眾多不同類型的應用程序讀取并識別,是由于其內部對數據的存取采用了預先約定的“項-值對(Entry-value pairs)”存儲結構, 并對待存取的數據分門別類地進行存儲。下面是系統目錄下Win.ini文件的部分內容:
[windows]
load=
run=
NullPort=None
[Desktop]
WallpaperStyle=2
Pattern=(無)
在此,配置文件將信息分為若干“節”,節標題放在方括號中,如“[Desktop]”就是Desktop節,在每一個節中包含了一些與之相關的“項”,并通過等號對其進行賦值。一般形式如下:
[SECTION]
ENTRY=VALUE
在初始化文件中,VALUE值只能有兩種數據類型:數值和字符串。Windows分別為這兩種數據類型提供了兩套API函數對初始化文件進行數據讀取,在寫入初始化文件時則只支持對字符串的寫入,數值等類型必須先進行數據類型的轉換,然后才能寫入到初始化文件。私有初始化文件的訪問 對私有初始化文件的數據存取是由GetPrivateProfileInt()、GetPrivateProfileString()和WritePrivateProfileString()等三個API函數來完成的。其函數說明如下:
UINT GetPrivateProfileInt(LPCTSTR lpAppName, // 節名地址
LPCTSTR lpKeyName, // 項名地址
INT nDefault, // 在項名沒有找到時返回的缺省值
LPCTSTR lpFileName // 初始化文件名地址
);
DWORD GetPrivateProfileString(LPCTSTR lpAppName, // 節名地址
LPCTSTR lpKeyName, // 項名地址
LPCTSTR lpDefault, // 缺省字符串
LPTSTR lpReturnedString, // 存放字符串的緩沖區地址
DWORD nSize, // 緩沖區大小
LPCTSTR lpFileName // 初始化文件名地址
);
BOOL WritePrivateProfileString(LPCTSTR lpAppName, // 節名地址
LPCTSTR lpKeyName, // 項名地址
LPCTSTR lpString, // 要寫入的字符串地址
LPCTSTR lpFileName // 初始化文件名地址
);
其中,GetPrivateProfileInt()返回的是初始化文件lpFileName中lpAppName節內lpKeyName項的整數值,如果沒有找到該項則返回缺省值nDefault。如果此項目存在,但值不為整數,則返回0。如果某項目的值中含有非數字字符則只返回第一個非數字前的字符,例如對于“Value = 21century”則只返回數值21。初始化文件名lpFileName可以是全路徑也可以只是文件名,如果不指定具體路徑,Windows系統將在系統目錄對文件進行尋找。GetPrivateProfileString()和WritePrivateProfileString()的用法基本與之類似,只是處理對象的數據類型不同。
2 知識應用
私有初始化文件主要用來保存同應用程序當前狀態相關的一些信息,當程序退出后,這些信息由于已寫入到初始化文件而得以保留,當程序再次運行時,可以通過對此初始化文件各項數據的讀取而得知此應用程序在上次運行期間的相關信息。下面這段代碼即通過對私有初始化文件的訪問而對程序的運行次數和上一次的運行日期進行記錄:
CString sPath,sMsg,sTime,sDate;
char buffer[255];
// 獲取當前應用程序全路徑
GetModuleFileName(NULL, buffer, MAX_PATH);
sPath = CString(buffer);
sPath = sPath.Left(sPath.ReverseFind('\\'));
// 得到初始化文件的全路徑
sPath += "\\Sample04.ini";
// 得到程序累計運行次數
UINT Time = GetPrivateProfileInt("PROGRAM", "RUNTIME", 0, sPath);
// 得到上次運行日期
GetPrivateProfileString("DATE", "LAST", "2002-11-1", buffer, 1000, sPath);
// 顯示從初始化文件獲取到的文件信息
sMsg.Format("本軟件共運行過%d次,上次運行日期為%s", Time, CString(buffer));
AfxMessageBox(sMsg);
// 累加運行次數,并保存到初始化文件
Time++;
sTime.Format("%d", Time);
WritePrivateProfileString("PROGRAM", "RUNTIME", sTime, sPath);
// 獲取當前日期,并保存到初始化文件
CTime tm = CTime::GetCurrentTime();
sDate.Format("%d-%d-%d", tm.GetYear(), tm.GetMonth(), tm.GetDay());
WritePrivateProfileString("DATE", "LAST", sDate, sPath);
在程序執行后,初始化文件Sample04.ini的內容為:
[DATE]
LAST =2002-11-12
[PROGRAM]
RUNTIME =1
系統目錄下的Win.ini是一種特殊的初始化文件,主要為系統提供初始化服務,在系統啟動時將被系統所訪問,并根據其所保存的參數值對系統進行配置。Windows專門提供了三個API函數GetProfileInt()、GetProfileString()和WriteProfileString()對Win.ini進行讀寫訪問,其函數用法同訪問私有初始化文件的那幾個函數非常類似,只是不必再去指定初始化文件名。下面是這三個函數的原型聲明:
UINT GetProfileInt(LPCTSTR lpAppName, // 節名地址
LPCTSTR lpKeyName, // 項名地址
INT nDefault // 在項名沒有找到時返回的缺省值
);
DWORD GetProfileString(LPCTSTR lpAppName, // 節名地址
LPCTSTR lpKeyName, // 項名地址
LPCTSTR lpDefault, // 缺省字符串地址
LPTSTR lpReturnedString, // 存放字符串的緩存的地址
DWORD nSize // 緩存的大小
);
BOOL WriteProfileString(LPCTSTR lpAppName, // 節名地址
LPCTSTR lpKeyName, // 項名地址
LPCTSTR lpString // 要寫入字符串的地址
);
只要對前面對私有初始化文件進行訪問的代碼稍加改動即可將程序的配置信息添加到Win.ini中,改動后的代碼如下:
CString sPath,sMsg,sTime,sDate;
// 得到程序累計運行次數
UINT Time = GetProfileInt("PROGRAM", "RUNTIME", 0);
// 得到上次運行日期
GetProfileString("DATE", "LAST", "2002-11-1", buffer, 1000);
// 顯示從初始化文件獲取到的文件信息
sMsg.Format("本軟件共運行過%d次,上次運行日期為%s", Time, CString(buffer));
AfxMessageBox(sMsg);
// 累加運行次數,并保存到初始化文件
Time++;
sTime.Format("%d", Time);
WriteProfileString("PROGRAM", "RUNTIME", sTime);
// 獲取當前日期,并保存到初始化文件
CTime tm = CTime::GetCurrentTime();
sDate.Format("%d-%d-%d", tm.GetYear(), tm.GetMonth(), tm.GetDay());
WriteProfileString("DATE", "LAST", sDate);
由于Win.ini文件是系統初始化文件,在程序沒有運行前文件內不含“DATE”和“PROGRAM”等自定義的節以及其下各項,因此在程序第一次執行后,將由WriteProfileString()函數向Win.ini文件末尾創建相關節、項,并完成數據的寫入。
@class 聲明一個類 @fn 聲明一個函數 @brief 簡要說明 /** @file RenderMgr.h @brief 渲染管理類 @date 3/16/2006 @author lethean */ @param 聲明一個參數 @note 注解 @par paragraph,自定義一個段落標題,類似html中<p> @code 代碼示例,需要用@endcode封閉,代碼段中不需要加前置*號 /** @brief 讀取bmp文件 @remarks 測試一下 @param [in] dc 傳入一個dc用于臨時操作 @param [in] filename bmp文件名 @note 屏幕dc只能在一處被使用,請確保傳入屏幕dc后其他地方不會操作該dc @par 代碼示例: @code //使用 CDC* dc = AfxGetMainWindow()->GetDC(); Bitmap bmp( dc , "test.bmp"); AfxGetMainWindow()->ReleaseDC( dc ); @endcode */ 對于單行的前置注釋可以用///,或者/** blahblah */ 樣式 ///我是一個變量 int index; 對于單行的后置注釋,需要加一個"<"符號 int count; ///< 我也是 /** 協議標識 */ enum ProtoId { PROTOID_LOGIN, /**< 登陸協議*/ PROTOID_BROWSE, /**< 瀏覽協議*/ }; @defgroup是用來定義新模塊的 集中在某個頭文件中定義所有的模塊,然后在屬于這個模塊的類定義處使用@ingroup EnginePrerequisite.h中 /** @defgroup Render Render渲染模塊 @defgroup Resource Resource管理模塊 @defgroup Sound Sound聲音模塊 @defgroup Anim Anim動畫模塊 @defgroup Math Math數學庫模塊 @defgroup Script Script腳本模塊 */ RenderMgr.h中 /** @class RenderMgr @ingroup Render */ JavaDoc類型: /** * ... 程序注釋 ... */ Qt類型: /*! * ... 程序注釋... */ 單行型式: /// 這是一個簡潔型的注釋 //! 這也是一個簡潔型的注釋 用Doxygen的這幾種基本注釋類型就可以建立足以建立文檔了,如果需要更多復雜功能,Doxygen還可以提供高級格式如組(group)和列表。 Doxygen的常用注釋參數:
Doxygen的使用,如下: E:\>xstring目錄包含下列文件: Xstring.cpp Xstring.h 其中xstring.h里對xstring類的StrToInt方法的注釋如下: /** * 0. 序言 Doxygen是基于GPL的開源項目,是一個非常優秀的文檔系統,當前支持在大多數unix(包括linux),windows家族,Mac系統上運行,完全支持C++, C, Java, IDL(Corba和Microsoft 家族)語言,部分支持PHP和C#語言,輸出格式包括HTML、latex、RTF、ps、PDF、壓縮的HTML和unix manpage。有很多開源項目(包括前兩篇文章介紹的log4cpp和CppUnit)都使用了doxygen文檔系統。而國內的開發人員卻使用的不多,這里從開發人員使用的角度介紹這個工具,使開發人員用最少的代價盡快掌握這種技術,并結合這個工具探討如何撰寫注釋的問題。以下以linux下的C++語言為例進行介紹,以下討論基于doxygen1.3.3。 1. doxygen使用步驟 Doxygen在生成文檔時可以定義項目屬性以及文檔生成過程中的很多選項,使用下面命令能夠產生一個缺省的配置文件: 讓doxygen自動產生文檔,平常的注釋風格可不行,需要遵循doxygen自己的格式。具體如何寫doxygen認識的注釋在第3節詳細介紹。 OK,代碼編完了,注釋也按照格式寫好了,最后的文檔是如何的哪?非常簡單,運行下面的命令,相應的文檔就會產生在指定的目錄中。 需要注意的是doxygen并不處理所有的注釋,doxygen重點關注與程序結構有關的注釋,比如:文件、類、結構、函數、變量、宏等注釋,而忽略函數內變量、代碼等的注釋。 2. doxygen配置文件 對doxygen的配置文件的修改分為兩類:一種就是輸出選項,控制如何解釋源代碼、如何輸出;一種就是項目相關的信息,比如項目名稱、源代碼目錄、輸出文檔目錄等。對于第一種設置好后,通常所有項目可以共用一份配置,而后一種是每個項目必須設置的。下面選擇重要的,有可能需要修改的選項進行解釋說明,其他選項在配置文件都有詳細解釋。 TAG 缺省值 含義 3. doxygen注釋 注釋和代碼完全分離,放在其他地方也是允許的,但需要使用特殊的命令加上名稱或者聲明進行標識,比如:class、struct、union、enum、fn、var、def、file、namespace、package、interface(這些也就是doxygen關注的注釋類型)。這里不推薦使用,建議注釋盡量放在代碼前后。具體使用方式參見doxygen手冊。 3.2 doxygen常用注釋格式 對函數的注釋,是比較常常需要注釋的部分。除了定義其簡要說明以及詳細注釋,還可以使用param命令對其各個參數進行注釋,使用return命令對返回值進行注釋。常見的格式如下: 進行設計時,通常有模塊的概念,一個模塊可能有多個類或者函數組成,完成某個特定功能的代碼的集合。如何對這個概念進行注釋?doxygen提供了group的概念,生成的模塊的注釋會單獨放在一個模塊的頁面中。使用下面的格式定義一個group。 對于某幾個功能類似的代碼項(比如類、函數、變量)等,如果希望一起添加注釋,而又不想提升到模塊的概念,可以通過下面的方式: 3.3 doxygen常用注釋命令 到此為止,常用的doxygen的注釋格式討論完畢,我們能夠按照一定的格式撰寫doxygen認識的注釋,并能夠使用doxygen方便快捷的生成對應的文檔,不過注釋中應該寫些什么,如何撰寫有效的注釋可能是困擾開發人員的一個更深層次的問題。 4. 注釋的書寫 推薦的寫注釋的過程是首先使用注釋勾勒出代碼的主要框架,然后根據注釋撰寫相應的代碼。對各種主要的數據結構、輸出的函數、多個函數公用的變量進行詳細地注釋。對代碼中控制結構,單一目的的語句集進行注釋。下面是一些寫注釋時需要注意的要點: 雖然使用各種IDE或者Source Insight 可以方便地在windows下閱讀和分析C/C++代碼,但是一步步Go to Definetion 實在令人痛苦。Doxygen能夠生成函數調用關系圖,所有的函數調用關系可以一目了然,另外他還能統計文檔中所有的類,成員變量,成員函數等。總的來說,Doxygen不但能從局部把握代碼,還能從全局審視代碼,后者是一般IDE和Source Insight 不能做到的。所以,使用doxygen閱讀分析代碼可以達到事半功倍的效果。 doxygen的安裝很簡單,到官方網站下載doxygen的windows安裝程序即可。除了安裝doxygen外,還需要安裝graphviz,因為doxygen需要使用graphviz的dot.exe生成調用圖。graphviz在它的官方網站上也可以下載到安裝程序。不過我下載的安裝程序在安裝時老是報cab文件錯誤。最后我下載了graphviz的release文件,即安裝程序下面的zip壓縮文件,下載完成后解壓縮即可。 安裝完doxygen后即可進行適當的配置,然后運行doxyfile生成文檔。基本的配置前人早有說明,不再贅述。需要注意的是: 1.要勾選Dot選項卡下面的HAVE_DOT、CALL_GRAPH 、CALLER_GRAPH 選項。并在DOT_PATH下面填入dot.exe的路徑,也就是graphviz安裝目錄下的bin文件夾。 2.勾選Wizard->Project選項卡下面的Scan recursively! 3.勾選Build選項卡下面的EXTRACT_ALL、EXTRACT_PRIVATE、EXTRACT_STATIC、EXTRACT_LOCAL_CLASSES、EXTRACT_LOCAL_METHODS選項 4.如果程序里面有中文,將Project選項卡下的DOXYFILE_ENCODING和Input選項卡下面的INPUT_ENCODING改為GBK 5.可以勾掉LATEX輸出,這樣節省編譯時間 5. 參考資料 2. doxygen manual 3. Code Complete: A Practical Handbook of Software Construction. Redmond, Wa.: Microsoft Press, 880 pages, 1993. ISBN: 1-55615-484-4. 6. 使用doxygen |
代碼寫多了難免需要做文檔,給自己還是給別人看都需要如此,這次XBOX360制作,前期沒怎么寫注釋,回頭改Bug都要猜半天自己寫的代碼是什么意思。更別提別人寫的東西,100行代碼也沒有一句注釋,幸好不是我維護,否則要瘋掉了。
花了一天功夫嘗試了一下Doxygen的使用,還好不難,但是有些磕磕絆絆,它自己的文檔也說不清楚,網上搜出來的教程也只是給出樣子,遇到的問題還是靠自己嘗試了幾十次才搞定。
不管如何,常用的東西都可以弄出來了。貼在下面:
-----------------------------------------------------------------------------------
1.所有注釋都可以使用///開始(C++風格)。
2.類體前必須加上///描述,否則會產生警告【Compound 類名 is not documented】
描述中最好不要帶有此類的類名,否則會產生兩個鏈接(但指向同一個文件)影響美觀。
3.public和protected會自動生成,但是private要在Expert的Build選項里勾中EXTRACT_PRIVATE,static成員也是如此。
4.函數注釋方式
/// Constructor【函數描述】
/// @param [in] pos The position of Camera in world coordinate 【參數描述1】
/// @param [in] lookat The point Camera looks at in world coordinate 【參數描述2】
BaseCamera( const D3DXVECTOR3& pos, const D3DXVECTOR3& lookat );
5.變量注釋方式
D3DXVECTOR3 m_Position; /*!< Camera position point in world coordinate */ 或
D3DXVECTOR3 m_Position; ///< Camera position point in world coordinate
兩種方式產生的結果不同。前者會單獨產生一塊Member Data Documentation注釋,后者會在Pubilc/Protected/Private Attributes變量描述后緊跟注釋。
6.@參數和\參數相同
7.產生描述順序和注釋順序相同,一般風格為
/// 函數描述
/// @param 參數描述
/// @return 返回值描述
/// @retval 返回值1 返回值1描述
/// @retval 返回值2 返回值2描述
/// @remarks 注意事項
/// @note 注意事項,功能同@remarks,顯示字樣不同
/// @par 自定義圖塊,后面可跟示例代碼之類
/// @code(必須使用@endcode結束)
/// 示例代碼(無需縮進)
/// @endcode
/// @see 其他參考項【產生指向參考的鏈接】
函數代碼聲明
8.特殊符號
/// - 產生一個黑色圓點
9.定義在類體里面的enum
/// Camera types
enum CAMERA_TYPE
{
CAMERA_FIRST_VIEW,/*!< Camera that looks from the first view */
CAMERA_MODEL_VIEW,///< Camera that looks from the third view
};
兩種風格相同。
以下開始的項都是全局非類內定義,在文件最開始(我嘗試是在include之前) 必須加上【/// \file 文件名】,否則不會生成注釋【沒有File Member頁】。
10. 定義在文件里面的宏
#define CAMERA_TYPE_NUMBER ///< The number of camera types. 或
#define CAMERA_TYPE_NUMBER /*!< The number of camera types. */
風格說明見5。
11. 非類內enum定義同10. 兩種風格相同。見9。
12. 非類內typedef定義同10. 風格說明見5。

2

3

4

5

6

7

8

9

10



11

12

13

14

15

16

17



18

19



20

21

22

23

24



25

26



27

28

這一部分講述如何繪制一些簡單的圖元,包括直線、填充與筆畫操作、虛線、線端(Cap)與線的交合等圖形的繪制方法。
直線段
直線段是非常基礎的矢量圖形對象。畫一條直線段,需要調用兩個函數:cairo_move_to() 函數,用于設置線段起點;cairo_line_to() 用于設定線段終點。
#include
#include
double coordx[100];
double coordy[100];
int count = 0;
static gboolean
on_expose_event(GtkWidget *widget,
GdkEventExpose *event,
gpointer data)
{
cairo_t *cr;
cr = gdk_cairo_create(widget->window);
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_set_line_width (cr, 0.5);
int i, j;
for ( i = 0; i 1; i++ ) {
for ( j = 0; j -1; j++ ) {
cairo_move_to(cr, coordx, coordy);
cairo_line_to(cr, coordx[j], coordy[j]);
}
}
count = 0;
cairo_stroke(cr);
cairo_destroy(cr);
return FALSE;
}
gboolean clicked(GtkWidget *widget, GdkEventButton *event,
gpointer user_data)
{
if (event->button == 1) {
coordx[count] = event->x;
coordy[count++] = event->y;
}
if (event->button == 3) {
gtk_widget_queue_draw(widget);
}
return TRUE;
}
int
main (int argc, char *argv[])
{
GtkWidget *window;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_widget_add_events (window, GDK_BUTTON_PRESS_MASK);
g_signal_connect(window, "expose-event",
G_CALLBACK(on_expose_event), NULL);
g_signal_connect(window, "destroy",
G_CALLBACK(gtk_main_quit), NULL);
g_signal_connect(window, "button-press-event",
G_CALLBACK(clicked), NULL);
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_window_set_title(GTK_WINDOW(window), "lines");
gtk_window_set_default_size(GTK_WINDOW(window), 400, 300);
gtk_widget_set_app_paintable(window, TRUE);
gtk_widget_show_all(window);
gtk_main();
return 0;
}
該示例會創建一個支持鼠標交互繪制直線段的 GTK+ 窗口。在窗口中使用鼠標左鍵隨便點幾下,每一次點擊時,光標位置的坐標都會被記入長度為 100 的數組;然后點擊鼠標右鍵,所有由鼠標左鍵點擊所得到的點會被彼此連接形成直線段;在窗口中再次點擊鼠標右鍵時,會對窗口繪圖區域進行清除。
下面對該示例程序代碼進行分析:
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_set_line_width (cr, 0.5);
設置顏色為黑色,線寬為 0.5pt 為參數,繪制直線段。
int i, j;
for ( i = 0; i 1; i++ ) {
for ( j = 0; j -1; j++ ) {
cairo_move_to(cr, coordx, coordy);
cairo_line_to(cr, coordx[j], coordy[j]);
}
}
用 cairo_move_to() 和 cairo_line_to() 函數在 cr 中定義繪圖路徑 (path),連接 coordx[] 和 coordy[] 所記錄的每個點。
cairo_stroke(cr);
cairo_stroke() 函數會將 cr 中的路徑繪制出來。
g_signal_connect(window, "button-press-event",
G_CALLBACK(clicked), NULL);
設定 button-press-event 事件的回調函數為 clicked ()。
if (event->button == 1) {
coordx[count] = event->x;
coordy[count++] = event->y;
}
在 clicked () 函數中,當鼠標左鍵點擊事件發生時,講光標所在位置的 x 和 y 坐標分別記入數組 coordx 和 coordy。
if (event->button == 3) {
gtk_widget_queue_draw(widget);
}
在 clicked () 函數中,當鼠標右鍵單擊時,調用 gtk_widget_queue_draw () 函數重繪窗口區域。

描繪 (Stroke) 與填充 (Fill)
描繪 (Stroke) 可以繪制形狀的輪廓,填充 (Fill) 則用于向形狀內部灌注顏色。
#include
#include
#include
static gboolean
on_expose_event (GtkWidget * widget,
GdkEventExpose * event, gpointer data)
{
cairo_t *cr;
cr = gdk_cairo_create (widget->window);
int width, height;
gtk_window_get_size (GTK_WINDOW (widget), &width, &height);
cairo_set_line_width (cr, 9);
cairo_set_source_rgb (cr, 0.69, 0.19, 0);
cairo_arc (cr, width / 2, height / 2,
(width ) / 2 - 10, 0,
2 * M_PI);
cairo_stroke_preserve (cr);
cairo_set_source_rgb (cr, 0.3, 0.4, 0.6);
cairo_fill (cr);
cairo_destroy (cr);
return FALSE;
}
int
main (int argc, char *argv[])
{
GtkWidget *window;
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
g_signal_connect (G_OBJECT (window), "expose-event",
G_CALLBACK (on_expose_event), NULL);
g_signal_connect (G_OBJECT (window), "destroy",
G_CALLBACK (gtk_main_quit), NULL);
gtk_window_set_position (GTK_WINDOW (window),
GTK_WIN_POS_CENTER);
gtk_window_set_default_size (GTK_WINDOW (window), 200, 150);
gtk_widget_set_app_paintable (window, TRUE);
gtk_widget_show_all (window);
gtk_main ();
return 0;
}
這個示例繪制一個內部填充灰色的圓。
下面對代碼進行解析:
#include
之所以引入這個頭文件,是因為程序中使用了圓周率常量 M_PI。
int width, height;
gtk_window_get_size (GTK_WINDOW (widget), &width, &height);
獲取窗口的寬度與高度尺寸。程序中將使用這些值作為繪制圓形的參考尺寸,以實現窗口尺寸變化時,所繪制的圓的尺寸也會相應變化。
cairo_set_source_rgb (cr, 0.69, 0.19, 0);
cairo_arc (cr, width / 2, height / 2,
(width ) / 2 - 10, 0,
2 * M_PI);
cairo_stroke_preserve (cr);
描繪圓的輪廓。這里要注意一下 cairo_stroke_preserve () 函數與 cairo_stroke () 函數的區別(最好的辦法是用后者替換一下前者,看看程序執行效果)。cairo_stroke_preserve () 函數會將它繪制的路徑依然保存在 cairo 環境中,而 cairo_stroke () 所繪制的路徑,在繪制完成后,就從 cairo的環境中清除了。
cairo_set_source_rgb (cr, 0.3, 0.4, 0.6);
cairo_fill (cr);
對使用 cairo_stroke_preserve () 函數繪制的路徑進行藍色填充。

虛線 (Dash)
每條線都可以用不同的虛線筆 (dash pen) 來畫。虛線模式是通過 cairo_set_dash () 函數來設定。模式類型通過一個數組來定義,數組中的值均為正數,它們用于設置虛線的虛部分與實部分。數組的長度與偏移量可以在程序中設定。如果數組的長度 為 0,虛線模式就是被禁止了,那所繪制的線是實線。如果數組長度為 1,則對應著虛實均勻分布的虛線模式。偏移量是用來設置在虛線的始端在一個虛線周期(包含一個實部單元和一個虛部單元)內的起始位置。
#include
#include
static gboolean
on_expose_event (GtkWidget * widget,
GdkEventExpose * event, gpointer data)
{
cairo_t *cr;
cr = gdk_cairo_create (widget->window);
cairo_set_source_rgba (cr, 0, 0, 0, 1);
static const double dashed1[] = { 4.0, 1.0 };
static int len1 = sizeof (dashed1) / sizeof (dashed1[0]);
static const double dashed2[] = { 4.0, 10.0, 4.0 };
static int len2 = sizeof (dashed2) / sizeof (dashed2[0]);
static const double dashed3[] = { 1.0 };
cairo_set_line_width (cr, 1.5);
cairo_set_dash (cr, dashed1, len1, 0);
cairo_move_to (cr, 40, 60);
cairo_line_to (cr, 360, 60);
cairo_stroke (cr);
cairo_set_dash (cr, dashed2, len2, 10);
cairo_move_to (cr, 40, 120);
cairo_line_to (cr, 360, 120);
cairo_stroke (cr);
cairo_set_dash (cr, dashed3, 1, 0);
cairo_move_to (cr, 40, 180);
cairo_line_to (cr, 360, 180);
cairo_stroke (cr);
cairo_destroy (cr);
return FALSE;
}
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *darea;
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
darea = gtk_drawing_area_new ();
gtk_container_add (GTK_CONTAINER (window), darea);
g_signal_connect (darea, "expose-event",
G_CALLBACK (on_expose_event), NULL);
g_signal_connect (window, "destroy",
G_CALLBACK (gtk_main_quit), NULL);
gtk_window_set_position (GTK_WINDOW (window),
GTK_WIN_POS_CENTER);
gtk_window_set_default_size (GTK_WINDOW (window), 400, 300);
gtk_widget_show_all (window);
gtk_main ();
return 0;
}
該示例演示了三種虛線模式的設置及繪制。
下面分析一下關鍵代碼。
static const double dashed1[] = { 4.0, 1.0 };
設定第一條虛線的模式,它的實部是 4 個像素,虛部是 1 個像素。
static int len1 = sizeof (dashed1) / sizeof (dashed1[0]);
計算數組 dashed1 的長度。
cairo_set_dash (cr, dashed1, len1, 0);
設置虛線模式。
darea = gtk_drawing_area_new ();
gtk_container_add (GTK_CONTAINER (window), darea);
這次,我們是在 drawing_area 部件上繪圖,不再是窗口區域了。

線帽 (Line caps)
線帽是針對直線段的端點形狀而言的,分為三種:
- CAIRO_LINE_CAP_SQUARE
- CAIRO_LINE_CAP_ROUND
- CAIRO_LINE_CAP_BUTT
對應形狀如下圖所示:

同一條直線段,CAIRO_LINE_CAP_SQUARE 線帽與 CAIRO_LINE_CAP_BUTT 線帽會導致直線段長度有所差別,前者會比后者長一個線寬尺寸。
#include
#include
static gboolean
on_expose_event (GtkWidget * widget,
GdkEventExpose * event, gpointer data)
{
cairo_t *cr;
cr = gdk_cairo_create (widget->window);
cairo_set_source_rgba (cr, 0, 0, 0, 1);
cairo_set_line_width (cr, 10);
cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
cairo_move_to (cr, 40, 60);
cairo_line_to (cr, 360, 60);
cairo_stroke (cr);
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
cairo_move_to (cr, 40, 150);
cairo_line_to (cr, 360, 150);
cairo_stroke (cr);
cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
cairo_move_to (cr, 40, 240);
cairo_line_to (cr, 360, 240);
cairo_stroke (cr);
cairo_set_line_width (cr, 1.5);
cairo_move_to (cr, 40, 40);
cairo_line_to (cr, 40, 260);
cairo_stroke (cr);
cairo_move_to (cr, 360, 40);
cairo_line_to (cr, 360, 260);
cairo_stroke (cr);
cairo_move_to (cr, 365, 40);
cairo_line_to (cr, 365, 260);
cairo_stroke (cr);
cairo_destroy (cr);
return FALSE;
}
該示例繪制三條具有不同線帽的直線段,同時也展示了不同線帽對線的長度的影響。
下面對關鍵代碼進行簡單分析:
cairo_set_line_width (cr, 10);
設置線的寬度為 10px。
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
cairo_move_to (cr, 40, 150);
cairo_line_to (cr, 360, 150);
cairo_stroke (cr);
畫了一條線帽為 CAIRO_LINE_CAP_ROUND 的直線段。
cairo_move_to (cr, 40, 40);
cairo_line_to (cr, 40, 260);
cairo_stroke (cr);
這是三條豎線之一,用于表現線帽對線的長度的影響。

線的交合 (Line joins)
線的交合存在以下三種風格:
- CAIRO_LINE_JOIN_MITER
- CAIRO_LINE_JOIN_BEVEL
- CAIRO_LINE_JOIN_ROUND
對應形狀如下圖所示。

#include
#include
static gboolean
on_expose_event (GtkWidget * widget,
GdkEventExpose * event, gpointer data)
{
cairo_t *cr;
cr = gdk_cairo_create (widget->window);
cairo_set_source_rgb (cr, 0.1, 0, 0);
cairo_rectangle (cr, 30, 30, 100, 100);
cairo_set_line_width (cr, 14);
cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
cairo_stroke (cr);
cairo_rectangle (cr, 160, 30, 100, 100);
cairo_set_line_width (cr, 14);
cairo_set_line_join (cr, CAIRO_LINE_JOIN_BEVEL);
cairo_stroke (cr);
cairo_rectangle (cr, 100, 160, 100, 100);
cairo_set_line_width (cr, 14);
cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
cairo_stroke (cr);
cairo_destroy (cr);
return FALSE;
}
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *darea;
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
darea = gtk_drawing_area_new ();
gtk_container_add (GTK_CONTAINER (window), darea);
g_signal_connect (darea, "expose-event",
G_CALLBACK (on_expose_event), NULL);
g_signal_connect (window, "destroy",
G_CALLBACK (gtk_main_quit), NULL);
gtk_window_set_position (GTK_WINDOW (window),
GTK_WIN_POS_CENTER);
gtk_window_set_default_size (GTK_WINDOW (window), 300, 280);
gtk_widget_show_all (window);
gtk_main ();
return 0;
}
該示例采用不同的交合類型繪制了三個矩形。
下面對關鍵代碼進行簡單分析:
cairo_rectangle (cr, 30, 30, 100, 100);
cairo_set_line_width (cr, 14);
cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
cairo_stroke (cr);
繪制了一個線寬為 14px,交合類型為 CAIRO_LINE_JOIN_MITER 的矩形。

本文來自ChinaUnix博客,如果查看原文請點:
| |||||||||
日 | 一 | 二 | 三 | 四 | 五 | 六 | |||
---|---|---|---|---|---|---|---|---|---|
30 | 31 | 1 | 2 | 3 | 4 | 5 | |||
6 | 7 | 8 | 9 | 10 | 11 | 12 | |||
13 | 14 | 15 | 16 | 17 | 18 | 19 | |||
20 | 21 | 22 | 23 | 24 | 25 | 26 | |||
27 | 28 | 1 | 2 | 3 | 4 | 5 | |||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
常用鏈接
留言簿(4)
隨筆分類
隨筆檔案
- 2012年9月 (1)
- 2012年8月 (1)
- 2012年5月 (1)
- 2011年2月 (1)
- 2010年12月 (4)
- 2010年11月 (1)
- 2010年10月 (2)
- 2010年9月 (3)
- 2010年6月 (1)
- 2010年4月 (1)
搜索
最新評論

- 1.?re: c++用WinForm做界面的實現
-
@wenluderen
項目屬性, 添加新引用"System", 和文章第11步一樣 - --Taoism
- 2.?re: c++用WinForm做界面的實現
- 評論內容較長,點擊標題查看
- --wenluderen
- 3.?re: c++用WinForm做界面的實現
- 我按你的步驟 怎么操作不成功呢
- --劉
- 4.?re: c++用WinForm做界面的實現
-
非常感謝@fei
- --hooknn
- 5.?re: c++用WinForm做界面的實現[未登錄]
- 樓主寫的很實用,也很詳細,只是有一點點沒有說,在地33點中的WInformCpp是HelloWord中的命名空間,一般默認生成,我剛開始就沒找到,對菜鳥來說還真的不好找啊
- --菜鳥