Effective STL (2)——vector 和 string

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>所能做的一切事情。

posted @ 2014-04-20 14:10 Daywei 阅读(2485) | 评论 (0)编辑 收藏

Effective STL (1)

 慎重选择容器类型
 标准序列容器: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库会把你从手工同步控制中解脱出来,而且你不能依赖于任何线程的支持。
  在容器所返回的每个迭代器的生存期结束前,都锁住容器
  对于作用于容器的每个算法,都锁住该容器,直到算法结束。  多个线程读是安全的
  多个线程对不同的容器作写入操作时安全的。
  对容器成员函数的每次调用,都锁住容器直到调用结束。

posted @ 2014-04-04 21:10 Daywei 阅读(2098) | 评论 (0)编辑 收藏

vim使用总结ing

VIM官网:
http://www.vim.org/
里面已经包含了很多插件,可随意配置。
先是一些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)
              

posted @ 2013-04-06 12:12 Daywei 阅读(1149) | 评论 (0)编辑 收藏

How to create a Simple Lock Framework for C++ Synchronization

How to create a Simple Lock Framework for C++ Synchronization

posted @ 2012-08-21 14:37 Daywei 阅读(747) | 评论 (0)编辑 收藏

C、C++写二进制数据到mysql的Blob字段

-- 引子--

由于调试需要,需直接往数据库里写入二进制数据。本来这些数据是由上层软件来写的,用的是C#。为了熟悉C语言的数据库操作,还是决定用C来写这段调试代码。

概况:

表名:Task

涉及的字段及属性:

NumDestint(11) 用于存储目标数目

destIDs: blob 用于存储具体的目标ID

废话不多说,入正题。



--二进制数据写入--

二进制数据最为常见的就是图片等一些文件信息。虽然我这里不是这类型信息,但确实是二进制数据。

具体步骤:

1、 定义一个buffer(如数组)来存储sql语句

2、 把涉及到二进制数据之前的sql语句添加到buffer中,可用sprintfstrcpy等。

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

posted @ 2012-07-05 16:06 Daywei 阅读(7169) | 评论 (0)编辑 收藏

任何时候都适用的20个C++技巧(转载)

     摘要: 这些小技巧之所以特别,是因为这些信息通常吧不能在C++书籍或者网站上找到。比如说,成员指针,即使对于高级程序员也是比较棘手,和易于产生bugs的,是应该尽量避免的问题之一。 <翻 by凌云健笔> What makes these tips special is that the information they provide usually cannot be found in ...  阅读全文

posted @ 2012-06-29 17:29 Daywei 阅读(409) | 评论 (0)编辑 收藏

《人月神话》经典语录

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.
我们理解也好,不理解也好,描述都应该简短精炼。 

posted @ 2012-05-12 11:05 Daywei 阅读(570) | 评论 (0)编辑 收藏

Recording DirectX and OpenGL Rendered Animations(翻译)

记录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 HBitmap

Recording a Movie from OpenGL Rendered Sequence
先暂时不翻译了,以后再翻。

注:第一次翻译,水平比较差,还望各位看客见谅。
Reference: Recording DirectX and OpenGL Rendered Animations

posted @ 2012-04-28 15:08 Daywei 阅读(1303) | 评论 (1)编辑 收藏

Linux下C/C++学习4——程序调试

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

posted @ 2012-04-04 14:26 Daywei 阅读(470) | 评论 (0)编辑 收藏

linux下C/C++学习3——Makefile

假设我们有下面这样的一个程序,源代码如下:
/* 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 规则可以查看相应的文档。

posted @ 2012-03-26 14:56 Daywei 阅读(536) | 评论 (0)编辑 收藏

仅列出标题
共4页: 1 2 3 4 
<2024年4月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

导航

统计

常用链接

留言簿

随笔分类

随笔档案

文章档案

牛人博客

搜索

积分与排名

最新评论

阅读排行榜