好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

C++你可能不知道地方

C++你可能不知道地方

c++中编译器替我们完成了许多事情,我们可能不知道,但也可能习以为常。下面详细介绍

 

一、初始化与初始赋值

首先说说类的初始化与初始赋值之前的区别,这也许里面可能有我们不知道的事情。  

其实类初始化与初始赋值还是有区别的。

  1           class   People{
   2           public  :
   3                  People(std:: string  name, int  age, int   height);
   4           private  :
   5                  std:: string   m_sName;
   6                   int   m_iAge;
   7                   int   m_iHeight;
   8           }
   9           //  赋值 
 10          People::People(std:: string  name, int  age, int   height)
  11           {
  12                  m_sName= name;
  13                  m_iAge= age;
  14                  m_iHeight= height;
  15           }
  16           //  初始化列表 
 17          People::People(std:: string  name, int  age, int   height)
  18           :m_sName(name),m_iAge(age),m_iHeight(height)
  19          {}

 C++规定,对象的成员变量初始化动作发生在进入构造函数本体之前。在构造函数内成员变量赋值都不是初始化,而是赋值。

 赋值时首先调用默认构造函数为m_sName,m_iAge,m_iHeight赋初始值,然后在立刻调用赋值操作符进行赋新值。

 成员初始列表是将各个成员变量实参都作为复制构造函数的实参。

 所以看出赋值相对于初始化,多了一步就是使用赋值操作符进行赋值。所以初始化的效率比赋值的效率高多了。但是对于内置类型,它们效率是一样的。

 

二、空类

  想想你如果声明一个空类,C++编译器会对它做什么呢?编译器就会为它声明一个复制构造函数,赋值操作符和一个析构函数,以及默认构造函数。所有这些函数都是public而且inline函数。

编译器写的赋值构造函数和赋值操作符,只是单纯地将来源对象的每个non-static变量拷贝到目标对象,具体是进行位拷贝。

     如果声明了一个构造函数,编译器是不会创建默认构造函数。

  如果不希望类支持拷贝构造函数与赋值操作符怎么办?不声明?按照上面说明编译器会自动帮你生成。那么可以将它们声明为private,这样阻止编译器自动生成拷贝构造函数(public)。private成功阻止他人使用,但是这并不安全。因为类成员函数以及友元函数还是可以调用private的拷贝构造函数和赋值操作符。

     如果只在private下声明拷贝函数和赋值操作符,在有人通过类成员函数去以及member函数去调用它,会获得一个连接错误。那么这里能不能将错误在编译的时候体现出来呢?这里只用将拷贝函数声明为private,并且不在自身,就可以办到了。显然继承一个拷贝函数和赋值操作符为private的基类就办到了,基类如下:

 1           class   NonCopyable{
  2           protected  :
  3                    NonCopyable (){}
  4                  ~   NonCopyable (){}
  5           private  :
  6               NonCopyable ( const   NonCopyable & );
  7               NonCopyable & operater=( const   NonCopyable & );
  8          };

  原因是类成员函数或者友元函数尝试拷贝对象,编译器便会尝试生成一个复制构造函数与赋值操作符,并会调用基类的对应函数,但是会被拒绝,因为基类这些函数是private。

 

3、++函数

  下面说说“*++"与"++*"中你不知道的事情,c++规定后缀形式自加函数有一个int类型参数,当函数被调用时,便其一传递一个0作为int参数的值传递给该函数,而前缀形式自己函数,类型参数没有要求,所以这样就能区分一个++函数是前缀形式与后缀形式了,具体代码如下:

  1   class   UPInt{
   2   public 
  3      UPInt&  operator ++( ) ;                       //  ++ 前缀 
  4       const  UPInt  operator ++(  int  );           //  ++后缀 
  5      UPInt&  operator  --( );                        //   --前缀 
  6       const  UPInt  operator  --(  int  )            //  --后缀 
  7      UPInt&  operator  +=(  int  );                //
   8       ...
   9   };
  10  
 11  UPInt & UPInt:: operator ++ ( )
  12   {
  13      * this  +=  1  ;
  14       return  * this  ;
  15   }
  16  
 17   const  UPInt UPInt ::  operator ++(  int   )
  18   {
  19      UPInt oldValue = * this  ;
  20      ++(* this  );
  21       return   oldValue;
  22  }

后缀函数使用返回参数类型const,是为了避免下面代码生效

 1   UPInt i;
  2  i++++;

这个时候第一次调用++返回cosnt对象,并再次调用然后这个函数是non-const成员函数,所以const对象无法调用这个函数,那么i++++就无法生效了。

这里说说效率问题,我们可以看到后缀++函数建立一个临时对象以作为它返回值,这个临时对象经过构造并在最后被析构。而前缀++函数没有这样的临时变量,并且没有那样的操作。所以如果我们在程序中使用前缀++效率会更加高一些,没有了临时变量的构造与析构的动作。

 

4.虚析构函数

带有多态性质的base class应该声明一个virtual析构函数。

为什么这么说呢?看下面例子

         class   base  
        { ... }
          class  derived: public   base  
        {... }
        
          base  * p=  new  derived;

     假设这里基类的析构函数不是virtual,当使用完p指针,我们删除它的时候,想想会发生什么,因为基类的析构函数是non-virtual所以不会发生多态直接调用基类析构函数,仅仅删除继承类中基类那部分内容,那么继承类对象其他内存没有被销毁,从而资源泄漏。

    如果将其声明为virtual,那么就会发生多态,调用的是指向继承类的指针,那么就会销毁的是整个继承类象。

 

5.传递方式用引用

 缺省情况下c++以值传递方式传递对象至函数。函数参数都是以实际实参的复件为初值,而调用端所获得的是函数返回值的一个附件。这些复件都是由拷贝构造函数产出。看如下例子

  1           class   Person{
   2           public  :
   3               Person();
   4               virtual  ~ Person();
   5               ...
   6           private  :
   7              std:: string   name;
   8              std:: string   address;
   9           }
  10          
 11           class  Student: public   Person{
  12           public  :
  13               Student();
  14              ~ Student();
  15               ...
  16           private  :
  17              std:: string   schoolName;
  18              std:: string   schoolAddress;
  19          };

 那么如果有一个函数验证是否为学生

 1     bool   validateStudent(Student s);
  2     Student plato;
  3     bool  platoIsOK=validateStudent(plato);

  分析这3行代码,编译器到底做了什么?首先调用Student的copy构造函数,然后以plato为蓝本将s初始化,当validateStudent返回被销毁,所以成本为"一次Student copy构造函数调用,加上一次Student析构函数调用"。

      Student对象内部有两个string对象,所以构造了两个string对象。Student继承自Person对象,里面又有两个string对象。所以by value方式传递一个Student对象,总体成本是"六次构造函数和六次析构函数"!

      以by reference方式传递参数也可避免对象切割问题。当一个derived class对象以by value方式传递并被视为一个base class对象,base class的copy构造函数会被调用,造成像derived class对象全被切割掉了,仅仅留下base class对象。看如下代码通过传递引用参数完成多态

         class   Window{
          public  :
            ...
            std::  string  name()  const  ;
              virtual   void  display()  const  ;
        };
          class  WindowWithScrollBars: public   Window{
          public  :
            ...
              virtual   void  display()  const  ;
        };
        
          //  传入Windos类型,调用其display函数
          //  传入WindowWithScrollBars类型,调用其display函数
          //  体现多态 
         void  printNameAndDispaly( const  Window&  w)
        {
            std::cout << w.name();
            w.display();
        }     

窥视c++编译器的底层,reference往往以指针实现出来,因此pass by reference真正传递的是指针。如果对象属于内置型,pass by value往往比pass by reference 效率高些。




知识是一点一点积累起来的                  --小风

分类:  C++

标签:  C++ ,  C++初始化 ,  C++空类

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于C++你可能不知道地方的详细内容...

  阅读:39次