1.vector和string優先于動態分配的數組。
2.使用reserve來避免不必要的重新分配
關于stl容器會自動增長以便容納下你放入其中的數據,只要沒有超過它們的最大限制就可以。對于vector和string,增長過程是這樣實現的:每當需要更多空間時,就調用與realloc類似的操作。這一類似于relloc的操作分為如下4部分:
(1)分配一塊大小為當前容量的某個倍數的新內存。在大多數實現中,vector和string的容量每次以2的倍數增長,即每當容器需要擴張時,它們的容量即加倍。
(2)把容器的所有元素從舊的內存復制到新的內存中。
(3)析構掉舊內存中的元素
(4)釋放舊內存
reserve成員函數能使你把重新分配的次數減少到最低限度,從而避免了重新分配和指針迭代器引用失效帶來的開銷。
簡單概括一下四個相互關聯、但有時會被混淆的成員函數。在標準容器中,只有vector和string提供了所有這四個函數:
(1)size() 告訴你容器中有多少個元素,它不會告訴你該容器為自己所包含的元素分配了多少內存。
(2)capacity()告訴你容器利用已經分配的內存可以容納多少元素。這是容器所能容納的元素總數,而不是它還能容納多少個元素。如果你想知道一個vector有多少未被使用的內存,就得從capacity()中減去size()。如果size和capacity返回同樣的值,就說明容器中不再有剩余空間了,因此下一個插入操作(push_back)將導致上面所提到的重新分配過程。
(3)resize(xx)強迫容器改變到包含n個元素的狀態。在調用resize之后,size將返回n。如果n比當前的大小(size)要小,則容器尾部的元素將會被析構掉。如果n比當前的大小要大,則通過默認構造函數創建的新元素將被添加到容器的末尾。如果n比當前的容量要大,那么在添加元素之前,將先重新分配內存。
(4)reserve(xx)強迫容器把它的容量變為至少是n,前提是n不小于當前的大小。這通常會導致重新分配,因為容量需要增加。(如果n比當前的容量小,則vector什么也不做)
因此,避免重新分配的關鍵在于,盡早的只用reserve,把容器的容量設為足夠大的值,最好是在容器剛被構造出來之后就使用reserve。
3.注意string實現的多樣性
4.了解如何把vector和string數據傳給舊的API
5.使用“swap技巧”除去多余的容量。
6.避免使用vector<bool>
vector<bool>不是一個stl容器,也不存儲bool。在一個典型的實現中,儲存在vector中的每個bool僅占一個二進制位,一個8位的字節可容納8g個“bool”。在內部vector<bool>使用了與位域一樣的思想,來表示它所存儲的那些bool;實際上只是假裝存儲了這些bool。
vector<bool>不完全滿足STL容器的要求;你最好不要使用它;你可以使用deque<bool>和bitset來替代它,這兩個數據結構幾乎能做vector<bool>所能做的一切事情。
慎重選擇容器類型
標準序列容器:vector string deque list
標準關聯容器 : set multiset map multimap
非標準序列容器: slist rope. slist 是一個單向鏈表,rope 本質上市一個“重型”string
非標準的關聯容器 hash_set hash_nultiset hash_map hash_multimap vector 作為string的替代。 vector 作為標準關聯容器的替代
幾種標準的非STL容器 包括 數組、bitset valarray stack queue 和 priority_queue
容器可分類為 連續內存容器和基于節點的容器
連續內存容器把它的元素存放在一塊或多塊(動態分配的)內存中,每塊內存中存有多個元素。當有新元素插入或已有的元素被刪除時,同一內存塊中的其他元素要向前或向后移動,以便為新元素讓出空間,或者填充被刪除元素所留下的空隙。
基于節點的容器在每一個(動態分配的)內存塊中只存放一個元素。容器中元素的插入或刪除只影響到指向節點的指針,而不影響節點本身的內容,所以當有插入或刪除操作時,元素的值不需要移動。
你是否需要在容器的任意位置插入新元素?如果需要,就選擇序列容器;關聯容器是不行的。
你是否關心容器中的元素師排序的?如果不關心,則哈希容器室一個可行的選擇方案;否則,你要避免哈希容器。
容器中數據的布局是否需要和C兼容?如果需要兼容,就只能選擇vector。
元素的查找速度是否是關鍵的考慮因素?如果是,就要考慮哈希容器、排序的vector和標準關聯容器——或許這就是優先順序。
確保容器中的對象拷貝正確而高效
調用empty而不是檢查size()是否為 0
理由很簡單:empty對所有的標準容器都是常數時間操作,而對一些list實現,size耗費線性時間。由于list所獨有的鏈接操作。
區間成員函數優先于與之對應的單元素成員函數。
如果容器中包含了通過new操作創建的指針,切記在容器對象析構前將指針delete掉。切勿創建包含auto_ptr的容器對象。問題的根源只是在于auto_ptr不是這樣的智能指針。
永遠都不要錯誤的認為:你可以通過創建auto_ptr的容器使指針被自動刪除。
慎重選擇刪除元素的方法
erase是指向緊隨被刪除元素的下一個元素的有效迭代器。
要刪除容器中有特定值得所有對象
如果容器使vector、string或deque,則使用earse-remove習慣用法。
如果容器是list,則使用list::remove
如果容器是一個標準關聯容器,則使用它的erase成員函數。
要刪除容器中滿足特定判別式(條件)的所有對象
如果容器是vector、string或deque,則使用erase-remove_if的習慣用法。
如果容器是list,則使用list::remove_if。
如果容器使一個標準關聯容器,則使用remove_copy_if和swap,或者寫一個循環來遍歷容器中的元素,記住當把迭代器傳給erase時,要對它進行后綴遞增。
要在循環內部做某些(除了刪除對象之外的)操作
如果容器使一個標準序列容器,則寫一個循環來遍歷容器中的元素,記住每次調用erase時,要用它的返回值更新迭代器。
如果容器是一個標準關聯容器,則寫一個循環來遍歷容器中的元素,記住當把迭代器傳給erase時,要對迭代器作后綴遞增。
切勿對STL容器的線程安全性有不切實際的依賴。
你不能指望STL庫會把你從手工同步控制中解脫出來,而且你不能依賴于任何線程的支持。
在容器所返回的每個迭代器的生存期結束前,都鎖住容器
對于作用于容器的每個算法,都鎖住該容器,直到算法結束。 多個線程讀是安全的
多個線程對不同的容器作寫入操作時安全的。
對容器成員函數的每次調用,都鎖住容器直到調用結束。
VIM官網:
先是一些vim基本配置設置
vim語法高亮顯示和自動縮進
1、配置文件的位置
在目錄 /etc/ 下面,有個名為vimrc的文件,這是系統中公共的vim配置文件,對所有用戶都有效。而在每個用戶的主目錄下,都可以自己建立私有的配置文件,命名為:“.vimrc”。例如,/root目錄下,通常已經存在一個.vimrc文件。
2、設置語法高亮顯示
1) 打開vimrc,添加以下語句來使得語法高亮顯示:
syntax on
2) 如果此時語法還是沒有高亮顯示,那么在/etc目錄下的profile文件中添加以下語句:
export TERM=xterm-color
3、設置Windows風格的C/C++自動縮進(添加以下set語句到vimrc中)
1)設置(軟)制表符寬度為4:
set tabstop=4
set softtabstop=4
2)設置縮進的空格數為4
set shiftwidth=4
3)設置自動縮進:即每行的縮進值與上一行相等;使用 noautoindent 取消設置:
set autoindent
4)設置使用 C/C++ 語言的自動縮進方式:
set cindent
5)設置C/C++語言的具體縮進方式(以我的windows風格為例):
set cinoptions={0,1s,t0,n-2,p2s,(03s,=.5s,>1s,=1s,:1s
6)如果想在左側顯示文本的行號,可以用以下語句:
set nu
7)最后,如果沒有下列語句,就加上吧:
if &term=="xterm"
set t_Co=8
set t_Sb=^[[4%dm
set t_Sf=^[[3%dm
endif
安裝ctags+taglist 1.ctags
(1)到
http://ctags.sourceforge.net/下載ctags源碼ctags-5.6.tar.gz
http://prdownloads.sourceforge.net/ctags/ctags-5.6.tar.gz(2)解壓并安裝
tar zxvf ctags-5.6.tar.gz
cd ctags-5.6
./configure && make && make install
(3)使用
[/home/brimmer/src]$ ctags -R
"-R"表示遞歸創建,也就包括源代碼根目錄下的所有子目錄下的源程序。"tags"文件中包括這些對象的列表:
l 用#define定義的宏
l 枚舉型變量的值
l 函數的定義、原型和聲明
l 名字空間(namespace)
l 類型定義(typedefs)
l 變量(包括定義和聲明)
l 類(class)、結構(struct)、枚舉類型(enum)和聯合(union)
l 類、結構和聯合中成員變量或函數
VIM用這個"tags"文件來定位上面這些做了標記的對象,下面介紹一下定位這些對象的方法:
1) 用命令行。在運行vim的時候加上"-t"參數,例如:
[/home/brimmer/src]$ vim -t foo_bar
這個命令將打開定義"foo_bar"(變量或函數或其它)的文件,并把光標定位到這一行。
2) 在vim編輯器內用":ta"命令,例如:
:ta foo_bar
3) 最方便的方法是把光標移到變量名或函數名上,然后按下"Ctrl-]"。用"Ctrl-o"退回原來的地方。
注意:運行vim的時候,必須在"tags"文件所在的目錄下運行。否則,運行vim的時候還要用":set tags="命令設定"tags"文件的路徑,這樣vim才能找到"tags"文件。
在函數中移動光標
[{ 轉到上一個位于第一列的"{"
}] 轉到下一個位于第一列的"{"
{ 轉到上一個空行
} 轉到下一個空行 ([ and ] 也分別是兩個指令)
gd 轉到當前光標所指的局部變量的定義
* 轉到當前光標所指的單詞下一次出現的地方
# 轉到當前光標所指的單詞上一次出現的地方
Vim 的創造者是一名計算機程序員,因此這就不奇怪 Vim 中有許多幫助編寫程序的功能:
跳轉到標識符被定義和使用的地方;在另一個窗口中預覽有關的聲明等等。
(ctags使用部分參考了 文章“ctags和vim”,原文在
http://hi.baidu.com/original/blog/item/2cf8d53f00b7fcc27d1e71f0.html,
更多使用也請參考原文)
2. taglist
能夠列出源文件中的tag(function, class, variable, etc)并跳轉.
注意:taglist依賴于ctags,所以要先裝ctags,否則taglist裝了也沒法用!
(1)到
http://vim.sourceforge.net/scripts/script.php?script_id=273下載taglist_42.zip,即
http://vim.sourceforge.net/scripts/download_script.php?src_id=6416(2)解壓得到兩個文件
# unzip -d taglist taglist_42.zip
# cd taglist
# tree
.
|-- doc
| `-- taglist.txt
`-- plugin
`-- taglist.vim
(3)安裝
cp doc/taglist.txt /usr/share/vim/vim61/doc/
cp plugin/taglist.vim /usr/share/vim/vim61/plugin/
(4)配置和使用
cd /usr/share/vim/vim61/doc/
啟動vim,用 “:helptags .”來配置好幫助文件
重啟vim,用“:TlistToggle”來打開和關閉taglist窗口。
可以用“:help taglist”來獲得更多幫助信息
set tags=./tags,./../tags,./http://www.cnblogs.com/tags,./**/tags
let Tlist_Use_Left_Window=1
let Tlist_Auto_Update=1
let Tlist_Exit_OnlyWindow=1
let Tlist_Show_One_File=1
nmap <F7> :TlistToggle <CR>
其次安裝配置基本的插件1.安裝好Vim和Vim的基本插件。在ubuntu下這些使用apt-get安裝即可:
lingd@ubuntu:~/arm$sudo apt-get install vim vim-scripts vim-doc
其中vim-scripts是vim的一些基本插件,包括語法高亮的支持、縮進等等。
vim中文幫助文檔tar包下載地址:
http://sourceforge.net/projects/vimcdoc/files/vimcdoc/
解壓后其中有個doc文件夾, 將其中的內容全部復制到~/.vim/doc, 或者vim安裝目錄下的doc目錄中, 此時vim中的help信息已經是中文的了.
網頁版中文幫助文檔網址http://vimcdoc.sourceforge.net/doc/help.html
首頁就時vim幫助文檔的目錄,閱讀起來更方便有效、更有針對性!
2.管理vim插件——vim-addons
通過vim-addons,我們可以管理vim插件。我們在sudo apt-get install vim vim-scripts vim-doc時,一般會自動安裝上vim-addons。若未安裝可通過sudo apt-get install vim-addon-manager手動安裝。安裝完成后,就可以用vim-addons管理vim插件了。
# 系統中已有的vim-scripts中包含的插件及其狀態:
lingd@ubuntu:~$ vim-addons status
# Name User Status System Status
align removed removed
alternate removed removed
bufexplorer removed removed
calendar removed removed
closetag removed removed
colors sampler pack removed removed
cvsmenu removed removed
debPlugin removed removed
detectindent removed removed
doxygen-toolkit removed removed
editexisting removed removed
enhanced-commentify removed removed
gnupg removed removed
info removed removed
justify removed removed
lbdbq removed removed
markdown-syntax removed removed
matchit removed removed
minibufexplorer installed removed
nerd-commenter removed removed
omnicppcomplete installed removed
po removed removed
project installed removed
python-indent removed removed
secure-modelines removed removed
snippetsEmu removed removed
sokoban removed removed
supertab removed removed
surround removed removed
taglist installed removed
tetris removed removed
utl removed removed
vcscommand removed removed
vimplate removed removed
whatdomain removed removed
winmanager removed removed
xmledit removed removed
Reference:vim配置為C/C++開發環境 一步步將vim改造成C/C++開發環境(IDE)
-- 引子--
由于調試需要,需直接往數據庫里寫入二進制數據。本來這些數據是由上層軟件來寫的,用的是C#。為了熟悉C語言的數據庫操作,還是決定用C來寫這段調試代碼。
概況:
表名:Task
涉及的字段及屬性:
NumDest:int(11) 用于存儲目標數目
destIDs: blob 用于存儲具體的目標ID
廢話不多說,入正題。
--二進制數據寫入--
二進制數據最為常見的就是圖片等一些文件信息。雖然我這里不是這類型信息,但確實是二進制數據。
具體步驟:
1、 定義一個buffer(如數組)來存儲sql語句
2、 把涉及到二進制數據之前的sql語句添加到buffer中,可用sprintf或strcpy等。
3、 用mysql_real_escape_string()函數添加二進制數據到buffer中。
4、 加上剩余的sql語句,形成完整的sql語句。
5、 利用mysql_real_query()函數來執行sql語句。
具體代碼如下:
#include <stdio.h> #include <stdlib.h> #include <mysql/mysql.h> #include <stdint.h> #include <string.h>
int main(int argc, char *argv[]) { MYSQL mysql; char sql[256], *end; int index, i; uint32_t *destIDs;
if(argc != 2) { printf("enter error!\n"); exit(1); } index = atoi(argv[1]); printf("index: %d\n", index); destIDs = (uint32_t *)malloc(index * sizeof(uint32_t)); if(destIDs == NULL) printf("malloc error\n"); for(i=0; i<index; i++) destIDs[i] = i + 1; mysql_init(&mysql); if(!(mysql_real_connect(&mysql, "localhost", "root", "654321", "dbname", 0, NULL, 0))) { fprintf(stderr, "Couldn't connect to engine!\n%s\n", mysql_error(&mysql)); perror(""); exit(1); }
sprintf(sql, "INSERT INTO Task(NumDest, DestIDs) VALUE (%u, ", index ); end = sql + strlen(sql); *end++ = '\''; end += mysql_real_escape_string(&mysql, end,(char *)destIDs, index*sizeof(uint32_t)); *end++ = '\''; *end++ = ')';
printf("end - sql: %d\n", (unsigned int)(end - sql));
if(mysql_real_query(&mysql, sql, (unsigned int)(end - sql))) { fprintf(stderr, "Query failed (%s)\n", mysql_error(&mysql)); exit(1); } mysql_close(&mysql); exit(0); #endif return 0; }
|
--讀取二進制文件--
對于二進制文件的讀取,也類似。
具體步驟:
1,構造查詢字串.
2,執行mysql _query查詢. (網上有說用mysql_real_query,未實驗)
3,用mysql_store_result存儲結果.
4,用mysql_fetch_row取出一條記錄處理.
具體代碼如下:
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <mysql/mysql.h> #include <string.h>
int main(void) { int ret, i; char sql[256]; MYSQL mysql; MYSQL_RES *result; MYSQL_ROW row; uint32_t *destIDs, *temp; unsigned int destNum = 0;
mysql_init(&mysql); if(!(mysql_real_connect(&mysql, "localhost", "root", "654321", "dbname", 0, NULL, 0))) { fprintf(stderr, "Couldn't connect to engine!\n%s\n", mysql_error(&mysql)); perror(""); exit(1); }
sprintf(sql, "SELECT TaskID, NumDest, DestIDs FROM Task"); ret = mysql_query(&mysql, sql); if(ret != 0) { printf( "Failed to query task table: %s\n", mysql_error(&mysql)); return ret; }
result = mysql_store_result(&mysql); if(result == NULL) { ret = mysql_errno(&mysql); printf( "Failed to store query result from task table:%s\n", mysql_error(&mysql)); return ret; }
if((row = mysql_fetch_row(result)) != NULL) { sscanf(row[1], "%u", &destNum);
destIDs = (uint32_t *)malloc(destNum * sizeof(uint32_t)); if(destIDs == NULL) { printf("malloc error!\n"); exit(1); } memcpy(destIDs, row[2], destNum * sizeof(uint32_t)); }
mysql_free_result(result);
printf("destNum: %d\n", destNum); temp = destIDs; for(i=0; i<destNum; i++) { printf("destIDs[%d]:%d\t", i+1, *temp++); }
return ret; }
|
由于我這里可以根據NumDest獲取到二進制的長度,所以不用再用函數去獲取。
據網上信息,獲取二進制信息長度應該這樣:“如果取出來的是二進制的數據,要確定它的長度,必須要用mysql_fetch_lengths函數取得其長度”
int num_fields = mysql_num_fields(result); unsigned long *lengths = mysql_fetch_lengths(result); for(i=0; i<num_fields; i++) printf("Column: %u\t %lu bytes\n", i+1, lengths[i]); destIDs = (uint32_t *)malloc(lengths[2]); if(destIDs == NULL) { printf("malloc error!\n"); exit(1); } memcpy(destIDs, row[2], lengths[2]);
|
取二進制數據:
一樣的sql語句,查詢出來即可。只不過二進制數據是個數據塊,需要得到數據塊的大小和數據指針。
bool CMySqlAccess::GetBinaryField(int nCol,char* &pDataOut,int& nDataLen)
{
if (m_ItemMySqlRow[nCol] != NULL)
{
unsigned long *FieldLength = mysql_fetch_lengths(m_pMySqlResult);
nDataLen = (int)FieldLength[nCol];
pDataOut = (char*)(m_ItemMySqlRow[nCol]);
return true;
}
else
{
return false;
}
}
像通常一樣查詢后,得到結果集,然后得到第nCol列結果,返回二進制指針結果和二進制長度。返回后必須立馬處理或者存儲一份。否則mysql將數據銷毀,指針所指數據則無效了。
存二進制數據:
mysql語句接受的sql語句都是string,以'\0'結尾的。如果冒然插入二進制數據到sql語句中,要么報錯,要么存儲錯誤。此處可以通過mysql提供的函數將數據轉換一下即可。
char* CMySqlAccess::ConvertBinaryToString(char* pBinaryData,int nLen)
{
static char s_BinaryData[10240];
mysql_real_escape_string(m_pMySqlConn,s_BinaryData,pBinaryData,nLen);
return s_BinaryData;
}
上面這個函數只能單線程使用啊,將一塊二進制數據轉換為mysql可識別的string數據。這樣就直接可以通過mysql的sql語句insert,update來對blob數據進行更新和插入了,sql語句用法不變。
用例:
std::ostringstream strSQL;
strSQL<<"INSERT INTO "<<m_strTableName<<"(roleid,playerdata,dynamicdata) VALUES("<<dwDBRoleID
<<",'"<<m_pDBAccess->ConvertBinaryToString(pData,nLen)<<"','')";
assert(m_pDBAccess);
m_pDBAccess->ExecuteSQL(strSQL.str());
playerdata是blob二進制類型,pData是指向一個結構體的指針,nLen是結構體的大小。
上面就可以實現二進制的存儲了。
方法二:
上面的方法,你會發現,你每次都需要轉換數據,傳指針,傳大小等一系列復雜操作,是不是順序很混亂,過程很繁雜。mysql也為你提供了另外一種方法,那就是MYSQL_BIND。將數據操作統一化,統一麻煩化。mysqlbind是一個結構體,根據個人不同需求填充各個數據成員可以存儲任意類型數據,當然包括blob。
bool CMySqlAccess::SetBinaryField(std::string& strCondition,void* pDataIn,int nDataLen)
{
if( ! mysql_stmt_prepare( m_pMySqlStmt, strCondition.c_str(), strCondition.length() ) )
{
memset(&m_MySqlBind,0,sizeof(MYSQL_BIND));
m_MySqlBind.buffer_type = MYSQL_TYPE_BLOB;
(*m_MySqlBind.length) = nDataLen;
memcpy(m_MySqlBind.buffer,pDataIn,nDataLen);
if(!mysql_stmt_bind_param(m_pMySqlStmt, (MYSQL_BIND *)&m_MySqlBind))
{
if(!mysql_stmt_execute(m_pMySqlStmt))
{
return true;
}
}
}
int nRes=GetDBErrorCode();
CLogOutStream errLog(crazy::ERROR_LEVEL,THIS_CLASS_NAME);
errLog<<"MySql Query Failed:\""<<strCondition<<"\" ,ErrorCode:"<<nRes<<crazy::logEnd;
return false;
}
這個是對某一列blob數據進行存操作。pDataIn和nDataLen分別是一個struct結構體和結構體大小。填充完畢mysqlbind之后即可對數據庫二進制列進行存儲了??赡苣銜枺瑳]有指定哪一列呢,對。哪一列是在strCondition語句里面的,這是一個預處理語句。在預處理語句里面,有一個符號: ? 。問號,問號的位置代表了mysqlbind數據對應的位置。
INSERT INTO test_table(date_field, time_field, timestamp_field) VALUES(?,?,?)
上面這個語句,有3個問號,三個問號分別對應test_table的三列.每個問號呢又對應一個mysqlbind數據結構。那么我們在mysql_stmt_bind_param函數調用時,就應該傳入一個mysql_bind 數組。MYSQL_BIND m_MySqlBind[3].
填充整個數組數據,即對應三個問號內容。
用例:
MYSQL_BIND bind[3];
MYSQL_STMT *stmt;
strmov(query, "INSERT INTO test_table(date_field, time_field, timestamp_field) VALUES(?,?,?");
//初始化stmt
stmt = mysql_stmt_init(mysql);
//預處理語句
mysql_stmt_prepare(mysql, query, strlen(query));
//初始化參數
bind[0].buffer_type= MYSQL_TYPE_DATE;
bind[0].buffer= (char *)&ts;
bind[0].is_null= 0;
bind[0].length= 0;
bind[1]= bind[2]= bind[0];
//綁定參數123
mysql_stmt_bind_param(stmt, bind);
//執行預處理mysql語句
mysql_stmt_execute(stmt);
還沒看懂就個人去看mysql文檔了,其實里面講得很清楚,只要找對幾個函數,就可以把search出來了
轉自:http://blog.chinaunix.net/uid-23842323-id-2656614.html
Reference:http://topic.csdn.net/u/20090316/11/ac003f13-d1da-49a5-b12f-90e57cbe5ac9.html
摘要: 這些小技巧之所以特別,是因為這些信息通常吧不能在C++書籍或者網站上找到。比如說,成員指針,即使對于高級程序員也是比較棘手,和易于產生bugs的,是應該盡量避免的問題之一。
<翻 by凌云健筆>
What makes these tips special is that the information they provide usually cannot be found in ...
閱讀全文
Good cooking takes time. If you are made to wait, it is to serve you better, and to please you.
美食的烹飪需要時間;片刻等待,更多美味,更多享受。
Adding manpower to a late software project makes it later.
向進度落后的項目中增加人手,實惠使進度更加落后。
對結構師的建議:
- 牢記是開發人員承擔創造性和發明性的實現責任,所以結構師只能建議,而不能支配
- 時刻準備著為所指定的說明建議一種實現的方法,同樣準備接受其他任何能達到目標的方法
- 對上述的建議保持低調和不公開
- 準備放棄堅持所做的改進建議
一般開發人員會反對體系結構上的修改建議。通常他是對的——當正在實現產品時,某些次要特性的修改會造成意料不到的成本開銷。
Practice is the best of all instructors.
實踐是最好的老師。
Experence is a dear teacher, but fools will learn at no other.
實踐是最好的老師,但智者還能從其他地方有所收獲。
There is nothing in this world constant but inconstancy.
不變只是愿望,變化才是永恒。
It is commeon sense to take a method and try it. If it fails, admit it frankly and try another. But above all, try something.
普遍的做法是,選擇一種方法,試試看;如果失敗了,沒關系,再試試別的。不管怎么樣,重要的是先去嘗試。
What we do not understand we do not possess.
不了解,就無法真正擁有。
For brevity is very good, Where we are , or are not understood.
我們理解也好,不理解也好,描述都應該簡短精煉。
記錄DirectX和OpenGL渲染的動畫簡介 當我們創建游戲和仿真模擬時,有時我們有必要記錄渲染的內容。在某些情況下渲染過于復雜和耗時,這是不可避免的。
在DirectX中,庫函數D3DXSaveSurfaceToFile()保存表面為一張圖片文件。對OpenGL,我們用glReadPixels()來讀渲染的圖像像素然后手動的保存它們為一張圖片文件。然而這些表面只是針對單幀記錄的,對記錄一段連續幀沒有簡單的方法存在。換句話說,沒有庫函數來記錄我們的完整存在渲染動畫效果。
在這方面,本文提出了幾類,這有助于創造電影DirectX的方法和動畫。用類CDxToMovie和 CGLToMovie電影可以選擇性地或連續的從DirectX和OpenGL渲染幀來創建。一般來說,一個典型的電影創作過程涉及復雜的任務,例如讀圖的內容,選擇幀速率設置,編解碼器的設置,初始化媒體流,寫媒體流等(詳細討論關于如何創建位圖圖像序列的電影,請參考這篇文章
Create Movie from HBitmap)。類CDxToMovie和CGLToMovie這里介紹的抽象出所有不必要的復雜性和易于使用的界面,提供簡單方法解釋如下
從DirectX渲染序列記錄一個電影
類CDxToMovie可以記錄DirectX渲染序列成電影文件。該類用到DirectX 9.0接口例如LPDIRECT3DSURFACE9,因此你應該用DirectX 9.0 SDK 或者其他的兼容的地方使用這個類 。
開始從本文中的DirectX代碼拷貝文件DxToMovie.h,RenderTarget.h,AviFile.h和AviFile.cpp到你的工程目錄下然后添加他們到你的工程中,然后添加vfw.lib,一旦添加到你的工程中,你可以通過#include "DxToMovie.h"訪問。CDxToMovie構造函數接受不同的參數如輸出電影文件名,電影幀的寬度和高度的要求,每像素比特數等…如下所示,
CDxToMovie(LPCTSTR lpszOutputMovieFileName = _T("Output.avi"),

int nFrameWidth = GetSystemMetrics(SM_CXSCREEN), /**//*Movie Frame Width*/

int nFrameHeight = GetSystemMetrics(SM_CYSCREEN), /**//*Movie Frame Height*/

int nBitsPerPixel = 32, /**//*Bits per Pixel*/

DWORD dwCodec = mmioFOURCC('M','P','G','4'), /**//*Video Codec for Compression*/

DWORD dwFrameRate = 1) /**//*Frame Rate (FPS) setting for the Movie*/

然而,應該注意到的是,這是一個時間設置,后來在電影記錄時候不能改變 。每個CDxToMovie對應一個不同的電影文件和再造一個CDxToMovie對象具有相同的輸出文件的名字不會追加以前的電影內容,將覆蓋它。
CDxToMovie g_MovieRecorder("Output.Avi", 320, 240); 一旦創建CDxToMovie對象,方法CDxToMovie::OnCreateDevice()在你的程序Direct3D設備創建的時候會被調用。類似的,CDxToMovie::OnLostDevice(),CDxToMovie::OnResetDevice()和CDxToMovie::OnDestroyDevice()也會在設備丟失銷毀的時候各自被調用。這些函數的原型顯示如下
class CDxToMovie

{
HRESULT OnCreateDevice(LPDIRECT3DDEVICE9 pd3dDevice);
HRESULT OnDestroyDevice(LPDIRECT3DDEVICE9 pd3dDevice);
HRESULT OnLostDevice();
HRESULT OnResetDevice(LPDIRECT3DDEVICE9 pd3dDevice,
const D3DSURFACE_DESC* pBackBufferSurfaceDesc);
};函數OnCreateDevice()和OnDestroyDevice()接受一個單單參數,指向你程序的Direct3D設備對象。OnLostDevice()沒有參數,但是OnResetDevice()還要有一個指針指向你設備的后緩沖區表面 D3DSURFACE_DESC*。CDxToMovie對象提供一些信息在D3DSURFACE_DESC里 創造一個合適的offscreen渲染目標,可以用來記錄你的應用程序的渲染。
真正記錄的功能是通過函數CDxToMovie::StartRecordingMovie()和CDxToMovie::PauseRecordingMovie().這兩個函數必須每一幀都要在
IDirect3DDevice9::BeginScene() 和
IDirect3DDevice9::EndScene()之間。如下所示
g_pd3dDevice->BeginScene();

// Capture the Rendering onto CDxToMovie's Render Target
g_MovieRecorder.StartRecordingMovie(g_pd3dDevice);
// Render as usual
..
g_pd3dDevice->Clear(0,NULL,D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,0,200),1,0);
g_pd3dDevice->SetStreamSource(0,g_pVB,0,sizeof(CUSTOMVERTEX));
g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST,0,1);
g_MovieRecorder.PauseRecordingMovie(g_pd3dDevice);

// Copy the CDxToMovie's Render Target content back onto BackBuffer's Surface
g_pd3dDevice->StretchRect(g_MovieRecorder.RecordingSurface(),
NULL,pBackSurface,
0,D3DTEXF_NONE);

g_pd3dDevice->EndScene();
在上面的代碼段中,
g_MovieRecorder.StartRecordingMovie(g_pd3dDevice)
將所有隨后的渲染在CDxToMovie的內部渲染目標直到
g_MovieRecorder.PauseRecordingMovie(g_pd3dDevice)被調用。所有的渲染工作在CDxToMovie的內部渲染目標上做,你的程序back surface將沒有任何有效的內容顯示在你的應用程序窗口。這不要緊,如果你的申請不只是創作這部電影沒有任何動畫直接呈現在屏幕上。然而,如果你記錄電影從一個互動游戲會話上,它會變壞屏幕不更新到最新的渲染的內容(因為通過重新渲染目標,CDxToMovie內容被偷了)。為了避免它,你可以選擇性地復制回CDxToMovie內部的渲染目標的內容到你的應用程序的back surface 使用方法IDirect3DDevice9:StretchRect(),其次是常見的g_pd3dDevice - > EndScene()和g_pd3dDevice - >Present()的要求會更新內容的呈現在屏幕上背緩沖區,使屏幕上更新。
如果你想避免一些幀被選擇性記錄在這部電影,只是不要叫g_MovieRecorder.StartRecordingMovie和g_MovieRecorder.PauseRecordingMovie()(相應的 g_pd3dDevice - > StretchRect())對那些幀,并會直接渲染動畫在屏幕上(沒有被重定向到CDxToMovie內部的渲染目標)。
演示代碼提供這個項目提供了一個簡單的應用程序,使得DirectX屏幕上的一個三角形的動作,鼠標移動窗戶上,這將simulatenously被渲染成電影文件和記錄(名叫output.avi)。跑演示的可執行程序,確保你有MPG4編解碼器的計算機上安裝,目錄有寫權限去創建輸出電影文件。詳情請設置解碼器和平衡,請參考這篇文章
Create Movie from HBitmapRecording a Movie from OpenGL Rendered Sequence先暫時不翻譯了,以后再翻。
注:第一次翻譯,水平比較差,還望各位看客見諒。
Reference:
Recording DirectX and OpenGL Rendered Animations
gcc編譯的幾種錯誤信息及其解決方法
1.語法錯誤
一般實在輸入代碼時括號不匹配或者使用了關鍵字。遇到語法錯誤,可以仙劍次錯誤提示中出現的第一個行號,如果該行沒有問題,就檢查該行所開始的語法模塊是否完整,然后修正該結構。
2.頭文件錯誤
如果編譯器出的錯誤提示說can not find include file ***.h,就說明是指定的包含文件有問題,系統在編譯過程中找不到指定的頭文件
3.類庫錯誤
如果出現類似“ld:-lm:No such file or directory”的錯誤,可能是在默認的目錄內找不到相應的類庫。這種問題的解決方法是在編譯時使用-I參數指定要使用的類庫所在的目錄。
4.未定義符號
出現類似Undefined symbol 的提示,說明在編譯過程中發現了沒有被定義的符號變量
gdb簡介
gdb 程序名
gdb
這兩種方式均可進入gdb的交互式調試界面。在交互模式中可以使用許多命令:
- file;加載要調試的程序
- kill;終止正在調試的程序
- list;列出10行程序的源代碼
- next;單步執行程序
- step;單步執行程序,與next不同的是,其會進入調用的函數內部。而next只需要調用函數的結果。
- run;運行加載的程序
- quit;退出gdb
- watch;監視一個變量的值
- break;在代碼里設置斷點,程序運行到斷點處時會停下來,然后用戶可用next或step單步執行程序。但使用break的前提是程序在編譯時使用了g參數
- make;不用退出gdb,重新編譯代碼,然后在gdb中運行
- shell;可調用shell命令
- bt;查看函數堆棧
- c函數;繼續運行
- finish;退出函數
- info;查看相關信息,如info break
假設我們有下面這樣的一個程序,源代碼如下:

/**//* main.c */
#include "mytool1.h"
#include "mytool2.h"
int main(int argc,char **argv)


{
mytool1_print("hello");
mytool2_print("hello");
}

/**//* mytool1.h */
#ifndef _MYTOOL_1_H
#define _MYTOOL_1_H
void mytool1_print(char *print_str);
#endif

/**//* mytool1.c */
#include "mytool1.h"
void mytool1_print(char *print_str)


{
printf("This is mytool1 print %s\n",print_str);
}

/**//* mytool2.h */
#ifndef _MYTOOL_2_H
#define _MYTOOL_2_H
void mytool2_print(char *print_str);
#endif

/**//* mytool2.c */
#include "mytool2.h"
void mytool2_print(char *print_str)


{
printf("This is mytool2 print %s\n",print_str);
}

當然由于這個程序是很短的我們可以這樣來編譯
gcc -c main.c
gcc -c mytool1.c
gcc -c mytool2.c
gcc -o main main.o mytool1.o mytool2.o
這樣的話我們也可以產生main 程序,而且也不時很麻煩.但是如果我們考慮一下如果有一天我們修改了其中的一個文件(比如說mytool1.c)那么我們難道還要重新輸入上面的命令?也許你會說,這個很容易解決啊,我寫一個SHELL 腳本,讓她幫我去完成不就可以了.是的對于這個程序來說,是可以起到作用的,但是當我們把事情想的更復雜一點,如果我們的程序有幾百個源程序的時候,難道也要編譯器重新一個一個的去編譯?
為此,聰明的程序員們想出了一個很好的工具來做這件事情,這就是make.我們只要執行以下make,就可以把上面的問題解決掉.在我們執行make 之前,我們要先編寫一個非常重要的文件.--Makefile.對于上面的那個程序來說,可能的一個Makefile 的文件是:
# 這是上面那個程序的Makefile 文件
main:main.o mytool1.o mytool2.o
gcc -o main main.o mytool1.o mytool2.o
main.o:main.c mytool1.h mytool2.h
gcc -c main.c
mytool1.o:mytool1.c mytool1.h
gcc -c mytool1.c
mytool2.o:mytool2.c mytool2.h
gcc -c mytool2.c
有了這個Makefile 文件,不過我們什么時候修改了源程序當中的什么文件,我們只要執行make 命令,我們的編譯器都只會去編譯和我們修改的文件有關的文件,其它的文件她連理
都不想去理的。
下面我們學習Makefile 是如何編寫的。
在Makefile 中也#開始的行都是注釋行.Makefile 中最重要的是描述文件的依賴關系的說明.一般的格式是:
target: components
TAB rule
第一行表示的是依賴關系.第二行是規則.
比如說我們上面的那個Makefile 文件的第二行
main:main.o mytool1.o mytool2.o
表示我們的目標(target)main 的依賴對象(components)是main.o mytool1.o mytool2.o
當倚賴的對象在目標修改后修改的話,就要去執行規則一行所指定的命令.就象我們的上面那個Makefile 第三行所說的一樣要執行 gcc -o main main.o mytool1.o mytool2.o
注意規則一行中的TAB 表示那里是一個TAB 鍵Makefile 有三個非常有用的變量.分別是
$@,$^,$<代表的意義分別是:
$@--目標文件,$^--所有的依賴文件,$<--第一個依賴文件.
如果我們使用上面三個變量,那么我們可以簡化我們的Makefile 文件為:
# 這是簡化后的Makefile
main:main.o mytool1.o mytool2.o
gcc -o $@ $^
main.o:main.c mytool1.h mytool2.h
gcc -c $<
mytool1.o:mytool1.c mytool1.h
gcc -c $<
mytool2.o:mytool2.c mytool2.h
gcc -c $<
經過簡化后我們的Makefile 是簡單了一點,不過人們有時候還想簡單一點.這里我們學習一個Makefile 的缺省規則
..c.o:
gcc -c $<
這個規則表示所有的 .o 文件都是依賴與相應的.c 文件的.例如mytool.o 依賴于mytool.c
這樣Makefile 還可以變為:
# 這是再一次簡化后的Makefile
main:main.o mytool1.o mytool2.o
gcc -o $@ $^
..c.o:
gcc -c $<
Makefile的處理規則
make命令在處理makefile時是遞歸處理的。同時,make在處理makefile時會檢測目標文件與依賴文件的時間戳。這個特性降低了編譯文件時的時間開銷,因為其只增量編譯更新過的文件。還有一點要注意的是,makefile文件必須以makefile或Makefile為名。
對簡單Makefile文件的擴充
Makefile文件就像是一種小型的腳本語言,所以其也支持變量的定義,而靈活使用變量,可以增強Makefile的適應性與靈活性。下面是一個使用變量的Makefile。
##########################################
NAME = myfirst
cc = gcc
ac = as
CFLAG = -Wall -o1 -g
#這是編譯源程序的編譯選項,具體含義可參見前面gcc參數介紹
${NAME} asfile : ${NAME}.o asfile.o
#使用變量時,應該使用$提取符,然后用大括號將變量名括起來
${cc} ${CFLAG} ${NAME}.o -o ${NAME}
${cc} ${CFLAG} asfile.o -o asfile
${NAME}.o : ${NAME}.c
${cc} -c ${NAME}.c -o ${NAME}.o
asfile.o : ${NAME}.s
${ac} ${NAME}.s -o asfile.o
#由匯編代碼生成目標文件
${NAME}.s : ${NAME}.c
${cc} -S ${NAME}.c -o ${NAME}.s
#生成匯編代碼的方法
other : ${NAME}.o
#other選項并未出現在最終目標中,所以直接使用make命令不會執行這一行。要執行這一行,必須使用make other來執行
${cc} ${CFLAG} ${NAME}.o -o other
#這里并未使用顯示規則來指定${NAME}.o的生成方式,因為對于make命令而言,如果在規則中發現name.o文件,其會自動尋找同名的c代碼(name.c),然后自動根據找到的代碼調用相應的編譯器編譯生成name.o文件
好了,我們的Makefile 也差不多了,如果想知道更多的關于Makefile 規則可以查看相應的文檔。