2009年12月30日
2007年8月28日
2007年6月25日
2007年4月29日
一般而言,如果基类定义了operator new,那么派生类也必须对应定义。 考虑下面的两个类 char * pAddress; class CBase { public: static void* operator new(size_t size){return pAddress;}; static void operator delete(void * p){}; };
class CDerive:public CBase { char buffer[1024]; public: CDerive() { for(int i=0; i < 1024; i ++) { buffer[i] = i %26 + 'a'; } } }; 当调用CDerive * p = new CDerive时,编译器首先尝试匹配该类自己的new,由于没有,编译器就尝试匹配在其祖先链上的new,于是调用CBase::operator new 但是基类其实不知道派生类任何信息,它仅仅根据CBase处理,因此构造了一个错误的类对象。
下面是我的测试代码,你可以发现在delete时程序报告p2指针被破坏,这就是因为CDerive得不到自己的1024字节内容,因此覆盖了后面的内容
void test() { char *p1, * p2; p1 = new char[10]; memset(p1,0,10); pAddress = new char[sizeof(CBase)]; p2 = new char[10]; memset(p2,0,10); CDerive *pDerive = new CDerive; TRACE(_T("%p\n"),pDerive); delete p1; delete p2; delete pAddress; }
2007年4月11日
前一阵子,我申请部门内部调动,被其他部门的人面试了一次,面试官让我写一段代码来对一个整形数组排序,我写了下面一段代码 #define SWAP(a,b) do {\ a = a +b;\ b = a - b;\ a = a - b;\ }while(0) void sort(int number, int vData[]) { int ii, jj; for(ii=0; ii < number ; ii++) { for(jj=ii+1; jj < number ; jj ++) { if(vData[ii] > vData[jj]) { SWAP(vData[ii], vData[jj]); } } } }
面试官让我优化一下代码,我又写出下面代码: #define SWAP(a,b) do {\ a = a +b;\ b = a - b;\ a = a - b;\ }while(0) void sort(int number, int vData[]) { int ii, jj; int min; for(ii=0; ii < number ; ii++) { min = ii; for(jj=ii+1; jj < number ; jj ++) { if(vData[min] > vData[jj]) { min = jj; } } if(min != ii) { SWAP(vData[min],vData[ii]); } } }面试官说能不能再优化一下.其实那时候我已经一脑糨糊,啥快速排序算法啊都不会,只好对面试官说不行了. 面试官指着我那段宏说:你如果用一个中间变量进行交换,会减少很多计算,对于大的排序可以优化很多.
感言:看起来神奇而水平高的算法不一定实用,要考虑算法的实用性.2006年9月23日
2006年9月17日
2006年5月24日
2005年12月7日
对于一个程序员而言,学习一种语言和一种算法是非常容易的(不包括那些上学花很多时间玩,上班说学习没时间的人)。但是,任何程序都可能是有瑕疵的,尤其有过团队协作编程经验的人,对这个感触尤为深刻。
在我前面的述及调试的文章里,我侧重于VC集成环境中的一些设置信息和调试所需要的一些基本技巧。但是,仅仅知道这些是不够的。一个成功的调试的开端是编程中的准备。
分离错误
很多程序员喜欢写下面这样的式子:
CLeftView* pView = ((CFrameWnd*)AfxGetApp()->m_pMainWnd)->m_wndSplitterWnd.GetPane(0,0);
如果一切顺利,这样的式子当然是没什么问题。但是作为一个程序员,你应该时刻记得任何一个调用在某些特殊的情况下都可能失败,一旦上面某个式子失败,那么整个级联式就会出问题,而你很难弄清楚到底哪儿出错了。这样的式子的结果往往是:省了2分钟编码的时间,多了几星期的调试时间。
对于上面的式子,应该尽可能的把式子分解成独立的函数调用,这样我们可以随时确定是哪个函数调用出问题,进口缩小需要检查的范围。
检查返回值
检查返回值对于许多编程者来说似乎是一个很麻烦的事情。但是如果你能在每个可能出错的函数调用处都检查返回值,就可以立刻知道出错的函数。
有些人已经意识到检查返回值的重要性,但是要记住,只检查函数是否失败是不够的,我们需要知道函数失败的确切原因。例如下面的代码:
if(connect(sock, (const sockaddr*)&addr,sizeof(addr)) == SOCKET_ERROR){ AfxMessageBox("connect failed");}
尽管这里已经检查了返回值,实际上没有多少帮助。正如很多在vckbase上提问的人一样,大概这时候只能喊“为什么连接失败啊?”。这种情况下,其实只能猜测失败的原因,即使高手,也无法准确说出失败的原因。
增加诊断信息
在知道错误的情况下,应该尽可能的告诉测试、使用者更多的信息,这样才能了解导致失败的原因。如果程序员能提供如下错误信息,对于诊断错误是非常有帮助的:
- 出错的文件:我们可以借助宏THIS_FILE和__FILE__。注意THIS_FILE是在cpp文件手工定义的,而__FILE__是编译器定义的。当记录错误的函数定义在.h中时,有时候用THIS_FILE更好,因为他能说明在哪个cpp中调用并导致失败的。
- 出错的行:我们可以借助宏__LINE__
- 出错的函数:如果设计的好,有以上两项已经足够。当然我们可以直接打印出出错的函数或者表达式,这样在大堆代码中搜索(尤其是不支持go to line的编辑器中)还是很有用的。大家可以参见我的文章中的方式进行处理,也许是一个基本的开端。
- 出错的原因:出错的原因很多只能由程序自己给出。如果出错只会问别人,那么你永远不可能成为一个合格的程序设计人员。很多函数失败时都会设置errno。我们可以用GetLastError获得错误码,并通过FormatMessage打印出具体错误的文字描述。
终了
给初学者一个忠告:编程时麻烦10分钟,调试时省却数小时,要想省时间,还是要从代码的可重用性和可维护性上下功夫,而不是两个代码上节省。