concentrate on c/c++ related technology

plan,refactor,daily-build, self-discipline,

  C++博客 :: 首页 :: 联系 :: 聚合  :: 管理
  37 Posts :: 1 Stories :: 12 Comments :: 0 Trackbacks

常用链接

留言簿(9)

我参与的团队

搜索

  •  

最新评论

阅读排行榜

评论排行榜

#

在编译工程的时候,遇到了一些恶心的问题:
A.h
#ifndef A_H
#define A_H
class A
{
   public:
    struct B
   {};
};
#endif
在B.h里面进行这样的调用
B.h
#ifndef B_H
#define B_H
#include "A.h"
class BB
{
    private:
        A::B* ab;
};
#endif
这里出现B.h里面不能找到A的问题了,我一直在想,如果将头文件包含进去了,那么编译器应该就要知道了吧,但是事实上都没有按照那样的做法去完成.
后来查找了一下,是因为出现了调用环. A.H调用了B.H,而B.h由调用了C.h,而C.h又调用了A.h就这样形成了调用环的关系,结果A.h里面的东西就出现了问题,连简单的类都不能被识别了,这样的问题出现是由于滥用包含头文件的原因,在头文件里面尽量要少包含东西,多前置声明,只将信息暴露给实现文件,让实现文件知道多一点,而让头文件暴露得信息少一点.
另外还值得说明的几点是:1)尽量多使用防止重复包含的做法,比如使用
#ifndef XXX_H
#define XXX_H
#endif
这样的话,就不会造成包含次数过多的情况.
2)保证防止重复包含的条件编译标志唯一性,因为按照其说明的要求,一旦声明了某个标志,那么就不会再编译了那个头文件,如果 存在相同的文件编译标志,那么就说明某个文件一定是漏过编译了.这样显然会出错的,之前遇到一个类似上面的问题,包含进去头文件了,却找不到头文件里面对应的声明,后来找了一下,才发现这个文件的条件编译标志跟另外一个文件的条件编译标志是一样的,因而跳过了编译当前文件.
头文件一定要把握好了,突然想起来一个建议:在写好代码以后,可以用doxygen来察看工程里面的文件组织格式和类结构格式,这样如果存在不妥的话,一定得要重新设计和规划了,呵呵,不写了,回家了.
posted @ 2008-07-14 22:03 jolley 阅读(472) | 评论 (0)编辑 收藏

选择static library工程.
lib.h
#ifndef LIB_H
#define LIB_H
void Print();
#endif

lib.cpp
#include "lib.h"
#include <iostream>
using std::cout;
using std::endl;

void Print()
{
 cout << "Hello,World" << endl;
}

可能会在路径上面存在问题,特别是在输出的时候,可以修改其中的生成事件,方便拷贝lib文件到制定目录.

在调用的时候,要求进行一些类似的修改:

#include "lib.h"
#pragma comment(lib,"lib.lib")

int _tmain(int argc, _TCHAR* argv[])
{
 Print();
 return 0;
}
不过这里生成或者调用的方式都要相同,比如MT,MD,或者是MTD,等.
感觉很简单的样子,但是在生成和调用的时候还存在这么多潜规则,让我深恶痛绝.
今天在调用的时候,发现了一个问题,可能要对基本运行时检查,以及缓冲区安全检查,之前的问题都没有纪录下来,后来编译通过了,也不能重现问题了,现在将这些网络上面的资源添加进来.
http://topic.csdn.net/u/20070816/19/9d4a23d6-e2f9-4a7b-8cd6-fcbbdb465bce.html
http://topic.csdn.net/t/20050719/11/4153745.html
http://forums.msdn.microsoft.com/zh-CN/vcgeneral/thread/644ea195-5a86-4beb-9e19-b38713d67074/
http://bytes.com/forum/thread616336.html





posted @ 2008-07-04 11:59 jolley 阅读(1363) | 评论 (0)编辑 收藏

该文为转载.原文来自http://baike.baidu.com/view/161302.htm
1. 虚函数的定义
虚函数用来表现基类和派生类的成员函数之间的一种关系.
虚函数的定义在基类中进行,在需要定义为虚函数的成员函数的声明前冠以关键字 virtual.
基类中的某个成员函数被声明为虚函数后,此虚函数就可以在一个或多个派生类中被重新定义.
在派生类中重新定义时,其函数原型,包括返回类型,函数名,参数个数,参数类型及参数的先后顺序,都必须与基类中的原型完全相同.
虚函数是重载的一种表现形式,是一种动态的重载方式.
2. 为什么使用虚函数
#include
class CBase{
public:
void who( )
{cout<<"this is the base class!\n";}
};
class CDerive1 : public CBase{
public:
void who( )
{cout<<"this is the derive1 class!\n";}
};
class CDerive2 : public CBase{
public:
void who( )
{cout< who( );
p = &obj2;
p -> who( );
p = &obj3;
p -> who( );
obj2.who( );
obj3.who( );
return 1;
}
运行结果:
this is the base class!
this is the base class!
this is the base class!
this is the derive1 class!
this is the derive2 class!
通过对象指针进行的普通成员函数调用,仅仅与指针的类型有关,而与此刻正指向什么对象无关.要想实现当指针指向不同对象时执行不同的操作,就必须将基类相应中的成员函数定义为虚函数.
3. 虚函数与重载函数的关系
一般的重载函数,函数的返回类型及所带的参数必须至少有一样不完全相同,只需函数名相同即可.
基类中定义的虚函数在派生类中重新定义时,其函数原型,包括返回类型,函数名,参数个数,参数类型及参数的先后顺序,都必须与基类中的原型完全相同.
重载虚函数时,若与基类中的函数原型出现不同,系统将根据不同情况分别处理:
(1)仅仅返回类型不同,其余相同,系统会当作出错处理;
(2)函数原型不同,仅仅函数名相同,系统会认为是一般的函数重载,将丢失虚特性.
3.3.4 虚基类
#include
class x{
protected:
int a;
public:
void f ( ) ;
};
class x1 : public x {
public:
x1( ){cout<};
class x2 : public x {
public:
x2( ){ cout<};
class y : public x1, public x2{
public:
y( ){ cout<};
main( )
{
y obj; //error
obj . f ( ) ; //error
return ;
}
二义性错误
非虚基类的类层次
虚基类的类层次
当在多条继承路径上有一个公共的基类,在这些路径中的某几条汇合处,这个公共的基类就会产生多个实例(或多个副本),若只想保存这个基类的一个实例,可以将这个公共基类说明为虚基类.
class x1 : virtual public x
{
// … …
};
class x2 : virtual public x
{
// … …
};
虚基类的初始化
虚基类的初始化与一般多继承的初始化在语法上是一样的,但构造函数的调用次序不同.
派生类构造函数的调用次序有三个原则:
(1) 虚基类的构造函数在非虚基类之前调用;
(2) 若同一层次中包含多个虚基类,这些虚基类的构造函数按它们说明的次序调用;
(3) 若虚基类由非虚基类派生而来,则仍先调用基类构造函数,再调用派生类的构造函数.


纯虚函数
仅仅用来为要从基类中派生的函数占据一个位置。
纯虚函数在基类中没有定义,它们被初始化为0。
任何用纯虚函数派生的类,都要自己提供该函数的具体实现。

定义纯虚函数
virtual void myMethod(void) = 0;
posted @ 2008-06-30 13:46 jolley 阅读(215) | 评论 (0)编辑 收藏

#include <iostream>
using namespace std;

class SomeObject
{
public:
 void SetValue(int value)
 {
  this->value = value;
 }
 int GetValue()const
 {
  return this->value;
 }
private:
 int value;
};
int main()
{
 SomeObject* so = new SomeObject;
 if (NULL == so)
 {
  return 1;
 }
 so->SetValue(1000);
 SomeObject *cp = so;
cout << cp->GetValue() << endl;//1)
 delete so;
 so = NULL;
 cout << cp->GetValue() << endl;//2)
 return 0;
}
2)处出错,因为删除掉了,虽然本意是删除某个数值,但是他们是共用一个地址的,在删除的时候也对原来占用的指针删除掉了.低级错误,不可忽视.

posted @ 2008-06-11 14:37 jolley 阅读(72) | 评论 (0)编辑 收藏

游戏中经常会出现崩溃地址的情况,这让我很恼火,在网络上面看到了一些处理方法,主要有这几种: 1)map file,2) crashFinder,3)BoundsChecker,4)dump文件,主要是这些信息,现在主要说明第四种方法的处理.
这种方法主要是用一个MiniDumpWriteDump函数将信息写入到dmp文件里面去,以及系统的宏__try,和__except.在__except里面就进行异常的处理-写MiniDumpWriteDump函数.
今天在写好了一个小demo之后,想将他移植到游戏工程里面去的时候,发现一个很诡异的做法:
error C2712: Cannot use __try in functions that require object unwinding
感觉很惊讶,
后来在网络上面查找了一些资料,主要涉及到几个点:命令参数(GX,SEH,EHSc),
GX-允许异常处理.SEH-结构化异常处理,EHSc-
http://msdn.microsoft.com/en-us/library/1deeycx5(VS.80).aspx

详细的情况请查阅MSDN.
因为这样C++和SEH的东西(__try/__except)不能混淆起来,所以将他在另外一个函数里面给出来.
http://www.codeguru.com/forum/archive/index.php/t-199810.html
http://www.codeproject.com/KB/cpp/exception.aspx
http://msdn.microsoft.com/en-us/library/xwtb73ad(VS.80).aspx

posted @ 2008-06-11 10:56 jolley 阅读(5550) | 评论 (0)编辑 收藏

两个超恶心的错误,现在网络上面针对这些错误都有很多种做法:
http://blog.csai.cn/user1/16781/archives/2006/6412.html
http://www.qqgb.com/Program/VC/VCJQ/Program_165466.html
http://topic.csdn.net/u/20071105/11/673EBD2D-CD29-419D-8486-433D6C1A28FA.html
http://www.ureader.com/msg/145357.aspx
http://topic.csdn.net/u/20071229/21/490eb437-9693-4666-8a97-f236c81b0036.html
等等这些,都提出了很多,但是总结起来vc2005里面主要要修改的地方还是有一些的.
项目属性页->常规->配置类型,->MFC的使用,->字符集(Unicode/Multi-byte)
项目属性页->调试->工作目录.
项目属性页->C/C++->优化.
项目属性页->预处理器->预处理器定义
项目属性页->C/C++->代码生成->运行时库
项目属性页->C/C++->预编译头文件->创建/使用预编译头文件
项目属性页->连接器->常规->启用增量连接(这个主要是影响map文件信息的声称)
项目属性页->连接器->常规->附加库目录
项目属性页->连接器->清单文件->生成清单
项目属性页->连接器->系统->子系统
项目属性页->连接器->高级->入口点
项目属性页->连接器->命令行(添加附加的库).
当然还有清单工具等等.
基本上一些工程的配置都在上面进行,以前总是在弄弄,但是没发觉什么,今天弄烦了,索性将他全部列出来.
winMain相关错误除了前面的解决办法之外,还要注意一些模板的设置,比如,win32 console和win32 app的主函数就是不一样的,而mfc的主函数就更加不一样了,所以不能将一些工程模板的主函数混淆起来.
之前我出现的一个错误就是在win32 console里面用win32 app的主函数.
而对于msvcr80d.dll的错误一般是要处理:项目属性页->C/C++->代码生成->运行时库将mtd改为md或者其它的,在上面的url里面也有相应的说明,只希望这两个错误以后不能再犯了.呵呵
PRJ0019: 这个错误以前在编译ut的时候就出现过,ut2004,后来一直都没有重视它,现在又出现了,就不能不重视了.
这个错误的报告是"生成后事件目录错误".具体的解决办法是工程的目录太深,需要将之清除.
项目属性页->生成事件->生成后事件.出现这个错误的时候,命令行上面有一个很长串的目录,需要将这个清除掉后再重新编译即可.
posted @ 2008-06-10 11:10 jolley 阅读(6928) | 评论 (2)编辑 收藏

void royLog(const char*string, ...)
{
 FILE* fp = NULL;
 // 打开文件,如果没有的话,那么就创建该文件
 fp = fopen("log.txt","a+");
 // 进行有效性判断
 if (NULL == fp)
 {
  printf("opening file fails! ");
  return;
 }

 va_list list;

 va_start(list,string);

 // 申请内存
 int len = strlen(string);
 char* temp = (char*)malloc(sizeof(char)*(len));
 // 判断分配内存是否有效
 if (NULL == temp)
 {
  printf("allocation fails!");
  return;
 }
 //初始化内存
 memset(temp,0,len);
 // 执行拷贝
 memcpy(temp,string,len);
 int result = vfprintf(fp,temp,list);
 // 释放内存和将指针置为为空
 free(temp);
 temp = NULL;
 
 va_end(list);

 //关闭文件指针
 fclose(fp);
 fp = NULL;

}
感觉公司里面的log不怎么好用,直接用OutputDebugString到输出窗口怎么能跟我的这个log做法相比较呢?呵呵
呵呵.感觉很羞耻的是上面的log文件存在严重问题.
特别是vfprintf. int vfprintf( FILE *stream, const char *format, va_list argptr );
而我在上面的log里面是将log信息传递给format的,这样就存在严重的问题,不过感觉离奇的是,我在之前的测试中居然没有报错,在我将log添加到游戏工程里面以后就却报错了.(代码还是要进行路径覆盖的,不能光看功能或者做一些黑盒测试,只看输入和输出,而忽视了正确的功能).
正确的文件log要更加简单些:
FILE* fp = NULL;
 // 打开文件,如果没有的话,那么就创建该文件
 fp = fopen("file.txt","a+");
 // 进行有效性判断
 if (NULL == fp)
 {
  printf("opening file fails! ");
  return;
 }
 va_list list;
 va_start(list,format);
 vfprintf(fp,format,list);
 va_end(list);
 //关闭文件指针
 fclose(fp);
 fp = NULL; 
请大家如果发现bug的话,一定要告诉我,我一定请他吃饭,呵呵.

posted @ 2008-06-03 20:48 jolley 阅读(269) | 评论 (0)编辑 收藏

基本变换

平移变换
平移矩阵= (1 0 0 0   )
          (0 1 0 0   )
          (0 0 1 0   )
          (tx ty tz 1)
点p(px,py,pz,1)与平移矩阵相乘以后,就可以得到新点p' = (px+tx,py+ty,pz+tz,1),在计算机实时图形学上面的平移矩阵好象是错误的呀,要转置以后才对的。
在书中记录的平移是:
(1 0 0 tx)
(0 1 0 ty)
(0 0 1 tz)
(0 0 0 1 )
另外平移矩阵的逆矩阵应该是:
(1 0 0 -tx)
(0 1 0 -ty)
(0 0 1 -tz)
(0 0 0  1 )
这里采用代数行列式的形式计算出来的,具体来说,就是将平移矩阵转置,获得转置矩阵pt(t),那么p-1(t) = pt(-t).
在D3D里面一般用函数D3DXMatrixTranslation.

旋转变换

绕x轴旋转
(1 0 0 0 )
(0 cosA -sinA 0)
(0 sinA cosA 0)
(0 0 0 1)
假设存在某个点p(px,py,pz,1),那么经过绕x旋转以后,就可以得到
(px,pycosA + pzsinA, pzcosA-pysinA,1)
在D3D里面一般用函数D3DXMatrixRotationX

绕y轴旋转
(cosA 0 sinA 0)
(0 1 0 0 )
(-sinA 0 cosA 0)
(0 0 0 1)
假设存在某个点p(px,py,pz,1),那么经过绕y旋转以后,就可以得到
(pxcosA-pzsinA,py,pxsinA+pzcosA,1)
在D3D里面一般用函数D3DXMatrixRotationY

绕z轴旋转
(cosA -sinA 0 0)
(sinA cosA 0 0)
(0 0 1 0)
(0 0 0 1)
假设存在某个点p(px,py,pz,1),那么经过z旋转以后,就可以得到
(pxcosA + pysinA, pycosA - pxsinA, pz, 1) 
在D3D里面一般用函数D3DXMatrixRotationZ

当然还有一个可以自定义的旋转轴做法:
D3DXMatrixRotationAxis

对绕任意旋转角度A的3x3旋转矩阵RL来说,其对角线元素之和是一个与坐标轴无关的常量,称为迹
tr(R) = 1 + 2cosA

缩放变换
缩放矩阵
s = (sx 0 0 0)
    (0  sy 0 0)
    (0  0 sz 0)
    (0  0  0  1)
假设存在某个点p(px,py,pz,1),那么经过缩放变换以后,就可以得到
(pxsx,pyxy,pzsz,1)
在齐次坐标系里面,创建一个一致性缩放矩阵的另外一种方式是通过改变(3,3)处的元素实现
比如
s = (1 0 0 0)
    (0 1 0 0)
    (0 0 1 1)
    (0 0 0 factor)
在这里这个factor将起主要作用.
如果对缩放矩阵的一个或者三个分量置为空,就会产生一个反射矩阵,或者成为镜像矩阵。如果其中两个缩放因子是-1,那么将会旋转180度

错切变换
错切变换是使图形产生一个扭变。分为x和y方向的错切变换。

    图形沿x方向的错切矩阵表示为 

     [1 0 0]
[x' y' 1] = [x y 1][b 1 0] = [ x+by y 1]
                   [0 0 1]

    此时,图形的y坐标不变,x坐标随坐标(x y)和系数b作线性变化。b>0,图形沿+x方向做错切;b<0,图形沿-x方向做错切;b≠0。

    图形沿y方向的错切矩阵表示为     

     [1 d 0]
[x' y' 1] = [x y 1][1 1 0] = [ x dx+y 1]
                   [0 0 1] 

   此时,图形的x坐标不变,y坐标随坐标(x y)和系数d作线性变化。d>0,图形沿+y方向做错切;d<0,图形沿-y方向做错切;d≠0。 
一般在游戏中被用来扭曲整个场景,从而产生某种虚幻效果,或者抖动来产生模糊反射效果。
Hxy:第一下标是由错切矩阵改变的坐标,第二个下标表示要进行错切操作的坐标。

          (1 0 s 0)
Hxz(s) =  (0 1 0 0)
          (0 0 1 0)
          (0 0 0 1)
假设存在P点(px,py,pz,1)与矩阵相乘可以得到(px + s pz, py, pz,1)
任何错切矩阵的行列式值总为1。

变换级联
矩阵乘法存在不可交换性,将多个矩阵级联为单个矩阵可以获得比较好的效率。

刚体变换
仅由平移和旋转两种级联所做成的变换称为刚体变换,这种变换具有长度和角度不变的特性。
可以将刚体矩阵X写成一个平移矩阵T(t)和一个旋转矩阵R的级联。
            ( r00 r01 r02 tx)
            ( r10 r11 r12 ty)
X = T(t)R = ( r20 r21 r22 tz)
            ( 0   0   0   1)
X的逆矩阵 = (T(t)R)的逆矩阵 = R的逆矩阵*T(t)的逆矩阵 = R的逆矩阵 * T(-t)
首先对矩阵R左上角的3x3矩阵进行转置,然后改变平移矩阵T的平移值符号,最后将这两个矩阵进行相乘即可以得到相应的逆矩阵。
另外一种求X的逆矩阵方法:
                      (r,0转置)
R = ( r,0 r,1 r,2) = (r,1转置)
                      (r,2转置)

X = ( R     t)
    ( 0转置 1)

法线变换
法线必须通过用变换集合图形的逆矩阵的转置矩阵进行变换。
在实际应用中,如果变换矩阵是正交的,就没有必要计算该矩阵的逆矩阵
在这种情况下,正交矩阵的逆矩阵就是转置矩阵,可以用这个矩阵本身对法线进行变换。

如果使用一个或者多个一致性缩放矩阵进行变换,就不需要计算相应的逆矩阵,因为这个缩放只影响变换后的法线长度,而不影响其方向,在这种情况下,一般都要在矩阵变换对法线进行变化之后,需要对法线进行归一化。

逆矩阵计算
1) 如果矩阵是单个变换或者一些给定参数的变换,那么很容易通过调换参数和矩阵顺序计算出相应的逆矩阵,比如M = T(t)R(s), 那么M的逆矩阵为R(-s)T(-t)
2) 如果矩阵是正交,那么其逆矩阵就是其转置,任何的旋转变换都是正交的。
3) 在不知道任何特殊信息的情形下,可以采用伴随矩阵,克莱姆法则,LU分解,或者高斯消去法来计算逆矩阵


欧拉变换
主要用来构造一个自定位(如相机)或者任何实体处于特定方向的矩阵。
首先,需要建立一种默认的观察方向,通常情况下,头部沿着y轴方向,面部朝向为z轴负方向。
当使用欧拉变换的时候,会出现所谓万向锁(Gimbal Lock),这种现象通常出现在旋转变换的时候出现,其中会缺少一个自由度。

改变head就是使观察者摇头说不,改变pitch就是点头,改变roll就是头向一边歪。

矩阵分解
主要应用场合:
1)提取缩放因子
2)确定特定系统所需要的变换
3)确定模型是否只经历过刚体变换
4)在动画的关键帧之间插值
5)从旋转矩阵中消除其中的错切变换

四元组
四元数是一个用来构造强制变换的有力工具,在某些情况下,要比欧拉角和欧拉矩阵更具有优势,特别是在遇到旋转和定向的情况时更是如此。
数学背景
q = (qv,qw) = iqx + jqy + kqz + qw = qv + qw
qv = iqx + jqy + kqz = (qx,qy,qz)
i*i = j*j = k*k = -1, jk = -kj = i, ki = -ik = j, ij = -ji = k
乘法:
QR = (iqx + jqy + kqz + qw)(irx + jry + krz + rw)
   = i*(qyrz - qzry + rwqz + qwrx)
     + j*(qzrx - qxrz + rwqy + qwry)
     + k*(qxry - qyrx + rwqz + qwrz)
     + qwrw - qxrx - qyry - qzrz
   = qv * rv + rwqw - qxrx - qyry - qzrz

加法:
Q + R = (qv, qw) +(rv,rw) = (qv + rv, qw + rw)

共厄:
Q = (qv,qw)共厄 = (-qv,qw)

范数:
n(Q) = qx * qx + qy * qy + qz * qz + qw * qw

同一性:
i = (0,1)

逆:
q的逆 = 1/n(Q) *Q共厄

共轭法则:

Q共轭 = Q共轭

( Q + R)共轭 = Q共轭 + R共轭

(QR)共轭 = R共轭Q共轭

范数法则:
n(Q共厄) = n(Q)

n(QR) = n(Q)n(R)

乘法定律:
  线性关系: P(sQ + tR) = sPQ + tPR
             (sP +tQ)R = sPR + tQR
  组合关系:P(QR) = (PQ)R

单位四元组Q=(qv,qw)的模n(Q)为1,基于此,Q可以表示为:
Q = (sinAuq,cosA) = sinAuq + cosA

对于一些三维向量uq来说,有||uq|| = 1,因为:
n(Q) = n(sinAuq,cosA) = sinA*sinA*(uq.uq) + cosA*cosA = 1
当且仅当uq*uq = ||uq|| *||uq||.

球面线性插值
球面线性插值是在给定两个单位四元组Q和R,以及一个参数t(0<=t<=1)的情况下计算插值四元组,
该运算的代数形式可以用下面的复合四元组S表示:
S(Q,R,t) = (RQ的逆)tQ

slerp表示球面线性插值
slerp(Q,R,t) = sinA(1-t)/sinA*Q + sinAt/sinA *R
cosA = qxrx + qyry + qzrz + qwrw
对于0<=t<=1来说,slerp函数计算是唯一的插值四元组
这些四元组构成了四维单位球面上从Q(t=0)到R(t=1)之间的最短弧。
这条弧位于一个圆上,这个圆由Q,R,以及原点组成的平面与四维单位球面相交形成的

Ai = Bi = Qi exp(-(log(QiQi-1)+log(QiQi+1))/4)

使用Qi,Ai和Bi对使用平滑三次样条的四元组进行球面插值
squad(Qi,Qi+1,Ai,Ai+1,t) = slerp(slerp(Qi,Qi+1,t),slerp(Ai,Ai+1,t),2t(1-t))

squad函数是由slerp函数重复使用球面插值而构成的,该插值通过初始化方位Qi而不是Ai,Ai主要用来表示初始方位的切线方向。

从一个向量到另外一个向量的旋转
四元组计算规则:
首先对s和t归一化,然后,计算出单位旋转轴u,具体通过式u = (s*t)/||s x t||来计算。
其次e = s.t = cos2A,||s * t|| = sin2A
其中2A表示s与t之间的夹角.
这样,从s到t旋转的四元组可以表示为Q =(sinAu,cosA).

posted @ 2008-05-25 15:58 jolley 阅读(1838) | 评论 (1)编辑 收藏

 

编译器基本技巧:

 

F10: 单步调试,按步执行程序,一般用来察看程序执行流程,如果程序程序从中断掉了,就可以用单步调试。

F9: 设置断点,程序在执行到设置断点的地方就会停下。

F5: 执行调试程序,Debug|Go

F11: 进入block内部进行调试。

Ctrl+F5: 在使用的时候,执行调试程序,Debug|Execute

Ctrl+F7: 编译单个文件,而不编译所有文件,这样可以避免编译一些不必要的文件而增加编译时间。

Clean: 清除工程

Rebuild all : 删除之前产生的中间生成文件以后,重新编译整个工程

Watch 窗口:将变量添加到watch窗口,并且可以查看变量值,但是不能察看函数的返回值,一般要看函数的返回值,应该将其保存在变量,然后进行察看。

Call Stack: 调用函数栈,在call stack里面可以看到各级函数的调用关系,并且这样方便定位bug报错位置。

Shift+F5: 取消调试过程。

Ctrl+ B: 设置数据断点和位置断点。

 

 

位置断点:


这是我们经常采用的办法,在我们要调试的代码行上设置一个断点,然后按F5或者F10进行调试,这种方法在非循环的代码block里面是可行的,但是如果在一个循环里面,那么就有点麻烦了,这样的话,你要多次地按F5,假如循环次数超过1000的话,想想看,不说你按F10来单步调试,就是按F5也要耗掉你不少体力.这里介绍一个方法:
即断点的条件判断法.
设置你要调试的代码行.
ctrl+b打开断点设置对话框,这样的话,我们就看到了一个location,我们可以在下面的breakpoints列表里选择我们所要调试的代码行,这样的话,在上面我们就可以在break at这个框里面看到我们刚才选择的代码行,接着在下面的condition按钮中选中,"Enter the expression to be evaluated"这一项中输入一个表达式,这个表示只有在该表达式成立的情况下,这个断点才能被启动.当然如果是输入一个变量名的话,那么在这个变量被修改的时候,断点才能被启动.另外还有两项:"Enter the number of elements to watch in an array or structure" 以及"Enter the number of times to skip before stopping".在这里都可以设置一些选项,这些对于循环的block非常有用.第一项表明要输入某个数组或者结构中要观测的项目数,第二项表示在终止前要跳过的次数,显然在循环次数很多的情况下是很有用的,在这里我们可以设置他为循环的次数,这样的话,等所有信息都设置好以后,F5以后,程序就会被中断,那么我们在"breakpoints"这里就可以看到一个信息"remaining xx"这表示说在循环进行100-xx+1项后就终止了,我们当然还可以采用"Enter the expression to be evaluated"这种方法,直接从值这个方面来获取信息,而不是从次数上获取信息.
for (int i = 100;i >0;i--) {
                printf("%d",100/(i-5));
        }
这就是个例子来的.


数据断点:


就是我们开始说的"Enter the expression to be evaluated"这种方法,即在满足某个条件值时,断点才会启动.
其实位置断点和数据断点是互补的,相对而言,数据断点更加适合于判断数据时候被修改这种的情况,而位置断点一般用在循环中,来说明程序执行的情况.在那个位置出错了,我们找到这个位置再去定位错误.
另外还有一种方法:callstack里面为某个函数设置断点,这样的话,就可以将函数调用从深度层次中返回.
还有可以在调试状态下,在某个代码行A处单击右键菜单中选择set next statement这个项,这样的话,下次执行的代码行就是A,而不是其它代码行,其实从字面意思就可以了解了.
另外watch窗口也可以用来监视变量的值变化,很管用的,特别对于一些数组,结构之类的,你可以查看数组/结构里面所有元素的值情况,不过我们用完了就得删除我们选择的变量,因为下次使用的时候它们还会出现的.

 

打印log:

可以通过输出函数打印相关信息,将log信息输出到屏幕,这些方法主要有:

Prinf, cout, MessageBox, 以及OutputDebugString等。当然也可以将信息记录在文本里面供执行以后察看,用fopen之类的,将信息写入文件。

 

查找崩溃地址:

可以采用dbghelp,具体信息可以到微软的官网上查找

 

也可以使用map 文件,这里有一篇介绍不错的文章。

http://www.codeproject.com/KB/debug/mapfile.aspx

 

使用断言:

断言可以帮助你更加检查数据的有效性,不过要注意的一点是,不要在断言里面使用函数,因为在非debug模式下面,比如release模式下面assert是无效的,要跳过去执行的。

 

使用异常:

Try/catch/throw, 以及__try/__catch/__throw

 

其他调试信息和相关编译器设置信息:

http://ei.cs.vt.edu/~cs1205/c_debug/intro.html

http://www.hermetic.ch/cfunlib/debug.htm

http://www.gamedev.net/reference/articles/article1344.asp

 

编程习惯建议:

多使用断言,assert对数据前驱和后继进行检查,使用防御编程,在函数入口,和函数出口处都进行数据检查。

在申请内存或者分配空间的时候,要检查数据是否分配成功,如果没有分配成功的话,要做相关的处理。

在声明变量的时候,最好也要进行初始化,防止没有经过初始化获得一些未知的数值。

未初始化的内存区一般被0xcccccccc填充,已经释放掉内存的区域一般是0xcdcdcdcd.

如果你试着访问一个数据值为0xcdcdcdcd的指针,那么意味着你已经在某个地方将该之珍释放掉了,如果是0xcccccccc,那么意味着该指针一直没有被分配空间。
 

(1)指针消亡了,并不表示它所指的内存会被自动释放。(摘自林博士的高质量C++编程)

 

(2)内存被释放了,并不表示指针会消亡或者成了NULL指针。

这里可以这样来理解:
1)对于栈上的内存区域,那么在该指针离开了该作用域以后,就无效了,但是并不意味着该指针所占据的内存被自动释放了,相反,出现内存泄漏了。
2)在释放内存以后,该指针仍旧未非空的,所以还要将之置空,避免出现野指针。

posted @ 2008-05-14 15:38 jolley 阅读(829) | 评论 (0)编辑 收藏

1) 使用windows 头文件
API 头文件允许32位和64位应用程序,包含了ANSI版本和UNICODE版本的声明。
如果安装更新SDK的话,那么就可能有头文件的多个版本在机器上面。
一些函数的使用可能会通过使用条件编译代码依赖于某个特定版本的操作系统,为了编译成功,你得定义比较合适的macro.头文件使用宏来指示哪个版本系统支持编程元素。
http://msdn2.microsoft.com/en-us/library/aa383745(VS.85).aspx
2) 初始化类中的成员模板
i) 使用非模板的方式
template <typename Argtype>
class Option
{
public:
Option( void (*func_name)(Argtype), Argtype Arg1 )
  : MenuFunction<Argtype>( (*func_name)(Argtype), Argtype Arg1 )
 {
 } 
private:
 MenuFunction<Argtype> function;
};
template <typename Argtype>
Option<Argtype> makeOption(void (*func_name)(Argtype), Argtype Arg1 )
{
return Option<Argtype>(func_name, Arg1);
}

ii) 使用多态
class Option
{
private:
class FunctionBase
{
public:
virtual ~FunctionBase() {}
virtual void call() = 0;
};

template <typename Argtype>
class Function : public FunctionBase
{
public:
Function(void (*func_name)(Argtype), Argtype arg) :
m_func(func_name, arg)
{
}
void call()
{
m_func(); // or whatever
}
private:
MenuFunction<Argtype> m_func;
};

public:
template<typename Argtype> Option( void (*func_name)(Argtype), Argtype Arg1 )
{
 // of course, this means you need a destructor, copy constructor, and assignment operator
// function->call() would invoke the function
function = new Function<Argtype>(func_name, Arg1);

}  
FunctionBase * function;
};
3 大小写字符串比较大小(考虑区域性语言的问题)
#include <iostream>
#include<algorithm>
#include<functional>
#include<boost/bind.hpp>
#include<string>
#include<locale>

struct CaseSensitiveString
{
  public:
       bool operator()(const std::string & lhs,const std::string & rhs)
       {
              std::string lhs_lower;
              std::string rhs_lower;
              std::transform(lhs.begin(),lhs.end(),std::back_inserter(lhs_lower),bind(std::tolower<char>,_1,_loc));
              std::transform(rhs.begin(),rhs.end(),std::back_inserter(rhs_lower),bind(std::tolower<char>,_1,_loc));
              return lhs_lower < rhs_lower;
       }
      CaseSensitiveString(const std::locale & loc):_loc(loc){}
     private:
       std::locale _loc;
};
详细内容见:
http://learningcppisfun.blogspot.com/2008/04/case-insensitive-string-comparison.html

4 找不到msctf.h问题
在用DX自带的dxut做界面程序的时候,整个程序编制下来就出现了这个错误
fatal error C1083: Cannot open include file: 'msctf.h': No such file or directory
很诡异的,在dxsdk里面也找不到,想了很久,才发现自己没有安装platform sdk.因为win32程序之类的,最好都要安装这些sdk之类的。具体的信息可以在这里得到
http://www.gamedev.net/community/forums/topic.asp?topic_id=481358
5 重载 , 覆盖,隐藏
重载与覆盖有以下的区别:
重载:同一类,相同函数名,不同函数参数,不一定要有virtual 关键字
覆盖:子类和父类,相同函数名,  相同函数参数,一定要有virtual 关键字
隐藏:1)如果派生类的函数名与基类的函数名相同,但是参数不同,不论有无virtual关键字,基类的函数将被隐藏(与重载区别开来)
            2)如果派生类的函数名与基类的函数名相同,并且参数相同,但是基类没有virtual关键字,基类的函数将被隐藏(与覆盖区别开来)
6 快速加载文件
在游戏里面,一般对从硬盘或者DVD加载资源要求比较高的,一般采用这样的方法:

for(int i = 0; < NumBlocks; i++)
{
   // VirtualAlloc() creates storage that is page aligned
   // and so is disk sector aligned
   blocks[i] = static_cast<char *>
      (VirtualAlloc(0, BlockSize, MEM_COMMIT, PAGE_READWRITE));

   ZeroMemory(&overlapped[i], sizeof(OVERLAPPED));
   overlapped[i].hEvent = CreateEvent(0, false, false, 0);
}

HANDLE hFile = CreateFile(FileName, GENERIC_READ, 0, 0, OPEN_EXISTING,
   FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING |
   FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, 0);

int iWriterPos = 0;
int iReaderPos = 0;
int iIOPos = 0;
int iPos = 0;

do
{
   while(iWriterPos - iReaderPos != NumBlocks && iIOPos < FileSize)
   {
      overlapped[iWriterPos & NumBlocksMask].Offset = iIOPos;

      int iLeft = FileSize - iIOPos;
      int iBytesToRead = iLeft > BlockSize ? BlockSize: iLeft;

      const int iMaskedWriterPos = iWriterPos & NumBlocksMask;
      ReadFile(hFile, blocks[iMaskedWriterPos], iBytesToRead, 0,
         &overlapped[iMaskedWriterPos]);

      iWriterPos++;
      iIOPos += iBytesToRead;
   }

   const int iMaskedReaderPos = iReaderPos & NumBlocksMask;

   WaitForSingleObject(overlapped[iMaskedReaderPos].hEvent, INFINITE);

   int iLeft = FileSize - iPos;
   int iBytesToRead = iLeft > BlockSize ? BlockSize: iLeft;

   memcpy(&g_buffer[iPos], blocks[iMaskedReaderPos], iBytesToRead);

   iReaderPos++;
   iPos += iBytesToRead;

}
while(iPos < FileSize);

CloseHandle(hFile);

for(int i = 0; i < NumBlocks; i++)
{
   VirtualFree(blocks[i], BlockSize, MEM_COMMIT);
   CloseHandle(overlapped[i].hEvent);
}

char* s vs char s[]
char s1[] = "abcd";// 定义一个未指定长度的char型数组,并使用字符串"abcd"将之初始化
char *s2  = "abcd";// 定义一个char型指针,并将其指向字符串"abcd",该字串位于静态存储区

s1[0] = 'm';// 无编译期、运行期错误
s2[0] = 'm';// 无编译器错误,但运行期试图修改静态内存,所以发生运行期错误
char s*只是被赋予了一个指针,char s[]是在栈中重新开辟了空间,可以在程序中写,而不引起程序崩溃。
所以相比较而言,使用字符串数组要比字符指针要安全的多,要慎用char*s 和char s[].
7 can not find MSVCR80.dll
在安装了vc2005之后,发现错误报告说MSVCR80.dll,以为又要重新安装vc2005了,但是在网络上面搜索到另外一个例子说,其实可以不用安装vc2005,直接改变配置就好了,于是就有这个了:
http://blogs.msdn.com/seshadripv/archive/2005/10/30/486985.aspx
http://www.codeguru.com/forum/showthread.php?t=439964
工程架构:
新建一个空白的 solution.
然后在新建的solution上面添加vcproject.
并且也可以在子空白solution上面添加vcproject.

1>        ]
1>正在编译资源...
1>正在链接...
1>LINK : warning LNK4075: 忽略“/INCREMENTAL”(由于“/OPT:ICF”规范)
1>fatal error C1900: Il mismatch between 'P1' version '20060201' and 'P2' version '20050411'
1>LINK : fatal error LNK1257: 代码生成失败
1>生成日志保存在“file://e:\demo-work\LocalVersionTianJi\_out\DragoonApp\Release\BuildLog.htm”
1>DragoonApp - 1 个错误,5382 个警告
========== 全部重新生成: 0 已成功, 1 已失败, 0 已跳过 ==========

http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1512436&SiteID=1
http://forum.codecall.net/c-c/6244-fatal-error-c1900-il-mismatch-between-p1-version-20060201-p2-version-2005-a.html
http://www.codeguru.com/forum/archive/index.php/t-144030.html

timeGetTime: 头文件 mmsystem.h,库文件 winmm.lib
获得当前窗口的句柄。
HWND hwnd=::GetForegroundWindow();
hwnd就保存了当前系统的最顶层窗口的句柄
GetSafehWnd 取你程序所在窗口类的句柄
GetActiveWindow 取当前活动窗口句柄
AfxGetMainWnd 取主窗口句柄
GetForegroundWindow 取前台窗口句柄
FindWindow
EnumWindow
改变窗口属性:
SetWindowLong
SetClassLong.

strcpy strncpy memcpy.
strcpy:按照msdn的话说是:No overflow checking is performed when strings are copied or appended,即没有严格的长度检查,所以即使是溢出也无法被检查出来,以及The behavior of strcpy is undefined if the source and destination strings overlap.

strncpy:虽然加入了size这个来限制,但是这个size小于或者等于字符长度的话,那么该信息是不被加上字符串结束符的.并且仍旧存在跟strcpy一样的问题, The behavior of strncpy is undefined if the source and destination strings overlap

memcpy:具体的用法跟strncpy类似,也加入了size的成分在里面,但是却比strncpy好用得多.

If the source and destination overlap, this function does not ensure that the original source characters in the overlapping region are copied before being overwritten.

Use memmove to handle overlapping regions.

显然它能够处理重叠的部分,安全可靠.
并且:

The first argument, dest, must be large enough to hold count characters of src; otherwise, a buffer overrun can occur.


上次遇到的问题是我将一串汉字用strcpy来拷贝到缓冲里面,结果发现出现了乱码.
strncpy, strcpy还是建议少用,换用memcpy+memmove(如果存在重叠的情况)吧:)
未完待续.

posted @ 2008-05-12 10:01 jolley 阅读(2742) | 评论 (1)编辑 收藏

仅列出标题
共4页: 1 2 3 4