PEIGANG's profile铁匠铺PhotosBlogLists Tools Help

Blog


    July 02

    this指针

         在学java时,看到了static成员函数的问题。static成员函数在一般情况下不能调用非static成员函数,但如果创建了新的对象后,是可以调用非static成员函数的。看了之后,感觉没什么特别的。可能是因为学了C++,就想在C++中static成员函数的情况应该是差不多的吧。于是,上网搜了艘关于C++中静态函数的内容,看了两个都是最基本的介绍,没有什么新东西。但有一个是关于设计模式中Singleton的问题,在这其中牵扯到了this指针的问题,还是很有意思的。
         Singleton模式C++的简单实现如下:
    class   MazeFactory   {
    public:
            static   MazeFactory*   Instance();
    protected:
            MazeFactory();
    //--------------------------------------------------------------
    private:
            static   MazeFactory*   _instance;
    };
     
    MazeFactory*   MazeFactory::_instance   =   0;
    //静态成员函数实现
    MazeFactory*   MazeFactory::Instance   ()   {
            if   (_instance   ==   0)
            {
                  //-------------   提问1:难道静态成员函数可以调用构造函数吗   -----------------
                    _instance   =   new   MazeFactory;
            }
            return   _instance;
    }
    //-------------   提问2:构造函数属于非静态成员函数还是静态成员函数?   -----------------
    //-------------   提问3:构造函数存在默认的this指针吗?   -----------------
         三个提问自然不是我的了,但下面各位大虾们给出了很多有益的解释。从他们的答案中我归纳了一下:
    1.静态函数中不能直接调用非静态成员,是因为编译器没有将this指针隐式的传递给静态函数,也就  是说在静态成员函数中是不存在this指针的。如果程序员主动将this指针传递给静态函数,那么函数中 可以访问非静态成员。
    2.构造函数是非静态成员函数。
    3.在构造函数中是存在this指针的。
         关于在函数中存在this指针的问题,我又查了一下C++Primer,书中有这样的说明:
         每个类成员函数都含有一个指向被调用对象的指针,这个指针被称为this。在非const成员函数中,它的类型是指向该类类型的指针;在const成员函数中,是指向const类类型的指针。
     
    May 24

    关于memcpy函数的一个实验

         昨天夜里,和义明看他的一个在vc中调用matalb引擎的程序。在这个程序中使用了memcpy函数,也就是内存拷贝函数。在使用这个函数时遇到了意想不到的错误。拷贝的源地址,目的地址,拷贝的字节数都是对的,但调用就会出现内存错误。当时已经是凌晨了,我们也就没再细究,只是认为memcpy在拷贝大量数据时是有一定字节总数限制的。
         今天,忙完了模式设计的任务后,编了一个小程序验证memcpy函数。在进行了数目有限的几次实验后,发现memcpy函数并没有在拷贝字节总数上有什么限制。关键问题可能是出在了数组的维数上。
         结果1:如果在两个一维数组(在实验中我取的最大值是5000*5000的一维double数组)之间进行拷贝,memcpy函数不会出错。
     
         结果2:在一个一维数组和一个二维数组(它们所占字节总数相等)之间拷贝,如果字节数较少,memcpy不会出错,但字节数一旦较大,就会出错。
      
         结果3:在两个相同维数的动态分配的二维数组之间进行拷贝,无论字节数是多少,都会出错。
     
         这样的结果挺奇怪的,由于时间有限,我没再去查资料。希望以后能有机会彻底解决这个疑问。更希望能有哪位高手指点一下。在此先谢谢了。
     
     
    May 13

    险些失控的对象

         设计模式布置的课程作业是一个图形编辑器。现在已经用了四个模式,分别是参数化的工厂方法,组合模式,命令模式,和备忘录模式。在程序中用模式之前总感觉模式这东西挺神秘,但一旦在程序中使用了模式,感觉模式就是一种方法,一种管理对象的方法。
         现在程序正在实现Undo与Redo机制。Undo一次操作需要把一个指针从一个容器中移出,并把其他的指针移入这个容器。我在编写代码的时候尽力去控制好这些对象的指针,避免它们成为内存中的垃圾,失控的对象。这可能是每一个C++程序员必须要做好,但很难做好的一项工作。(有些羡慕那些有垃圾回收机制的语言,但更自豪能自己去控制所有对象的那种感觉。
         由于在程序中需要控制很多的对象和变量,一旦遇到了bug,问题出在哪里很难定位。昨天晚上碰到一个bug,调了很长时间都没有找到原因。今天下午,通过使用ASSERT,总算定位了bug的位置。是一个i++引起的问题。在一个函数中用i++来计算一个数量。第一次调用计算出了正确的结果,但再次调用后就会继续++,反倒把正确的结果弄成错误的结果。
          我的神啊,上帝及老天爷,保佑那些对象不要失控。
         
    April 16

    绘图程序中直线的选取

           参考了一位网友的方法编写了绘图程序中直线选取的函数(MFC)。基本思想是创建一个包围直线的矩形区域,判断点是否在这一区域中。也可以采用求点到直线的距离的办法,在网上有很多这样的Example。再次感谢那些大虾们的开源精神。
    /*采用http://spaces.msn.com/fei545/提出的办法*/
    BOOL CLine::PtInGlyph(CPoint point)
    {
     const int iDistance = 6;  //允许的距离范围 4个单位
     int iMoveX, iMoveY; //x, y延伸的大小
     BOOL bIsNear = FALSE;
     
     float fDeltaX = (float)abs(m_ptTopLeft.x - m_ptBottomRight.x);
     float fDeltaY = (float)abs(m_ptTopLeft.y - m_ptBottomRight.y);
     
    if (fDeltaX == 0.0) {    //线与Y轴垂直时
      iMoveX = iDistance;
      iMoveY = 0;
     }
     else if (fDeltaY == 0.0) {  //线与X轴垂直时
      iMoveX = 0;
      iMoveY = iDistance;
     }
     else {
      double dLineLength = sqrt(fDeltaX*fDeltaX + fDeltaY*fDeltaY); // 线段长度
      double dSin = fabs(fDeltaY/dLineLength);    
      double dCos = fabs(fDeltaX/dLineLength);
      iMoveX = (int)(floor(dLineLength * dSin)); //向小的方向取整数值
      iMoveY = (int)(floor(dLineLength * dCos));
     }

     //包围直线的区域的四个顶点
     CPoint ptVertex[4];
     /*根据斜率不同创建包围框*/
     /*负斜率*/
     if (m_ptTopLeft.x < m_ptBottomRight.x)
     {
      ptVertex[0].x = m_ptTopLeft.x + iMoveX;
      ptVertex[0].y = m_ptTopLeft.y - iMoveY;
      ptVertex[1].x = m_ptTopLeft.x - iMoveX;
      ptVertex[1].y = m_ptTopLeft.y + iMoveY;
      ptVertex[2].x = m_ptBottomRight.x - iMoveX;
      ptVertex[2].y = m_ptBottomRight.y + iMoveY;
      ptVertex[3].x = m_ptBottomRight.x + iMoveX;
      ptVertex[3].y = m_ptBottomRight.y - iMoveY;
     }
     /*正斜率*/
        else
     {
      ptVertex[0].x = m_ptTopLeft.x + iMoveX;
      ptVertex[0].y = m_ptTopLeft.y + iMoveY;
      ptVertex[1].x = m_ptTopLeft.x - iMoveX;
      ptVertex[1].y = m_ptTopLeft.y - iMoveY;
      ptVertex[2].x = m_ptBottomRight.x - iMoveX;
      ptVertex[2].y = m_ptBottomRight.y - iMoveY;
      ptVertex[3].x = m_ptBottomRight.x + iMoveX;
      ptVertex[3].y = m_ptBottomRight.y + iMoveY;
     }  
     
    //创建多边形区域
     CRgn region;
     VERIFY(region.CreatePolygonRgn(ptVertex, 4, ALTERNATE));
     if (region.PtInRegion(point))
     {
      bIsNear = TRUE;
     }
     else
     {
      bIsNear = FALSE;
     }
     region.DeleteObject();
       
     return bIsNear;
    }
    April 13

    什么是C++编程?

          大三的时候开始接触C++,到现在也有两年了。但直到最近,在看过一些编程大师的著作后,我不得不做出一个很令我悲观的结论,我的C++学得是一塌糊涂,连一些最基本的编程问题都没有搞清楚。以前用MFC做一些课程设计,那只能说是用带有C++语法的代码编写面向过程的程序,丝毫没有面向对象编程的思想在里面。
          C++与C的一个本质区别,就我现在的理解是:C是面向过程编程,而C++是面向对象编程。要想真正理解面向对象编程的本质思想,不是一朝一夕的事情。但根据梁老师的说法,面向对象是最自然的方式,看来还真是那句话:越是简单的东西它包含的内容就越复杂。
          书中也采访了一些编程大师,看一下他们的开发工具是什么。他们当中有很多人使用文本编辑器来开发软件,如vi,Emacs等。这让我这个一直都是在IDE集成开发环境中编程的人感到无语。一些大师说:这样编程才能让他们把主要的注意力放在业务上,而不是花大量的时间在熟悉开发环境上。我自然不能和那些大师们相提并论,也去使用文本编辑器。不过,不想当将军的士兵就不是好士兵,同样,不想当大师的程序员就不是好程序员,成为一个大师或哪怕是一个高手,我想这是每个程序员的理想。
          在看过那些大师和其他一些编程高手的故事后,我启动vs.net,继续我设计模式的课程设计。我在这些小小代码行中的艰难跋涉也就不能算是辛苦了。
    April 02

    指针与引用的区别

    指针与引用的区别

          指针与引用看上去完全不同(指针用操作符“*”“->”,引用使用操作符“. ”),但是它们似乎有相同的功能。指针与引用都是让你间接引用其他对象。你如何决定在什么时候使用指针,在什么时候使用引用呢?

       首先,要认识到在任何情况下都不能使用指向空值的引用。一个引用必须总是指向某些对象。因此如果你使用一个变量并让它指向一个对象,但是该变量在某些时候也可能不指向任何对象,这时你应该把变量声明为指针,因为这样你可以赋空值给该变量。相反,如果变量肯定指向一个对象,例如你的设计不允许变量为空,这时你就可以把变量声明为引用。

     “但是,请等一下,你怀疑地问,这样的代码会产生什么样的后果?

    char *pc = 0;          // 设置指针为空值

    char& rc = *pc;        // 让引用指向空值

        这是非常有害的,毫无疑问。结果将是不确定的(编译器能产生一些输出,导致任何事情都有可能发生)。应该躲开写出这样代码的人,除非他们同意改正错误。如果你担心这样的代码会出现在你的软件里,那么你最好完全避免使用引用,要不然就去让更优秀的程序员去做。我们以后将忽略一个引用指向空值的可能性。

        因为引用肯定会指向一个对象,在C++里,引用应被初始化。

    string& rs;             // 错误,引用必须被初始化

    string s("xyzzy");

    string& rs = s;         // 正确,rs指向s

    指针没有这样的限制。

    string *ps;             // 未初始化的指针

                            // 合法但危险

        不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针的要高。因为在使用引用之前不需要测试它的合法性。

    void printDouble(const double& rd)

    {

        cout << rd;         // 不需要测试rd,

    }                       // 肯定指向一个double

    相反,指针则应该总是被测试,防止其为空:

    void printDouble(const double *pd)

    {

      if (pd) {             // 检查是否为NULL

        cout << *pd;

     }

    }

        指针与引用的另一个重要的不同是指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化时被指定的对象,以后不能改变。

    string s1("Nancy");

    string s2("Clancy");

    string& rs = s1;          // rs 引用 s1

    string *ps = &s1;         // ps 指向 s1

    rs = s2;                 // rs 仍旧引用s1,

                           // 但是 s1的值现在是

                           // "Clancy"

    ps = &s2;               // ps 现在指向 s2;

                           // s1 没有改变

       总的来说,在以下情况下你应该使用指针,一是你考虑到存在不指向任何对象的可能(在这种情况下,你能够设置指针为空),二是你需要能够在不同的时刻指向不同的对象(在这种情况下,你能改变指针的指向)。如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,那么你应该使用引用。

       还有一种情况,就是当你重载某个操作符时,你应该使用引用。最普通的例子是操作符[]。这个操作符典型的用法是返回一个目标对象,其能被赋值。

    vector<int> v(10);       // 建立整形向量(vector),大小为10;

                             // 向量是一个在标准C库中的一个模板(见条款M35)

    v[5] = 10;               // 这个被赋值的目标对象就是操作符[]返回的值

        如果操作符[]返回一个指针,那么后一个语句就得这样写:

    *v[5] = 10;

    但是这样会使得v看上去象是一个向量指针。因此你会选择让操作符返回一个引用。(这有一个有趣的例外,参见条款M30

    当你知道你必须指向一个对象并且不想改变其指向时,或者在重载操作符并为防止不必要的语义误解时,你不应该使用指针。而在除此之外的其他情况下,则应使用指针。

                                                                                                                                   
    摘自  林锐《高质量C++编程》