构造函数

类通常应定义一个默认构造函数

在某些情况下,默认构造函数是由编译器隐式应用的。如果类没有默认构造函数,则该类就不能用在这些环境中。

为了例示需要默认构造函数的情况,假定有一个 NoDefault 类,它没有定义自己的默认构造函数,却有一个接受

一个 string 实参的构造函数。因为该类定义了一个构造函数,因此编译器将不合成默认构造函数。

 

 

初级 C++ 程序员常犯的一个错误是,采用以下方式声明一个用默认构造函数初始化的对象:

Sales_item myobj();

编译 myobj 的声明没有问题。然而,当我们试图使用 myobj

     Sales_item myobj();   // ok: but defines a function, not an object
     if (myobj.same_isbn(Primer_3rd_ed))   // error: myobj is a function

编译器会指出不能将成员访问符号用于一个函数!问题在于 myobj 的定义被编译器解释为一个函数的声明,

该函数不接受参数并返回一个 Sales_item 类型的对象——与我们的意图大相径庭!

使用默认构造函数定义一个对象的正确方式是去掉最后的空括号:

     // ok: defines a class object ...
     Sales_item myobj;

另一方面,下面这段代码也是正确的:

     // ok: create an unnamed, empty Sales_itemand use to initialize myobj
     Sales_item myobj = Sales_item();

 

构造函数的初始化列表
代码
class Sales_item {
public:
// operations on Sales_itemobjects
// default constructor needed to initialize members of built-in type
         Sales_item(): units_sold(0), revenue(0.0) { }
private:
         std::
string isbn;
         unsigned units_sold;
         
double revenue;
};

 

这个构造函数使用构造函数初始化列表来初始化 units_sold revenue 成员。isbn 成员由 string 默认构造函数隐式初始化为空串。

在冒号和花括号之间的代码称为构造函数的初始化列表构造函数的初始化列表为类的一个或多个数据成员指定初值。它跟在构造函数的形参表之后,以冒号开关。构造函数的初始化式是一系列成员名,每个成员后面是括在圆括号中的初始值。多个成员的初始化用逗号分隔。

记住,可以初始化 const 对象或引用类型的对象,但不能对它们赋值。在开始执行构造函数的函数体之前,要完成初始化。初始化 const 或引用类型数据成员的唯一机会是构造函数初始化列表中。这也是构造函数初始化列表存在的意义。

 

函数体和参数之间的const

C++   Primer   第三版   p518   
类的设计者通过把成员函数声明为const   以表明它们不修改类对象例如
  class   Screen   {   
  public:   
  char   get()   const   {   return   _screen[_cursor];   }   
  //   ...   
  }   
只有被声明为const的成员函数才能被一个const类对象调用。

关键字const被放在成员函数的参数表和函数体之间。对于在类体之外定义的const成员函数我们必须在它的定义和声明中同时指定关键字const   
把一个修改类数据成员的函数声明为const是非法的。

任何不会修改数据成员的函数都应该声明为const类型。如果在编写const成员函数时,不慎修改了数据成员,或者调用了其它非const成员函数,编译器将指出错误,这无疑会提高程序的健壮性。   
以下程序中,类stack的成员函数GetCount仅用于计数,从逻辑上讲GetCount应当为const函数。编译器将指出GetCount函数中的错误。   

代码
  class   Stack   
  {   
      
public:   
  
void   Push(int   elem);   
  
int   Pop(void);   
  
int GetCount(void)     const//   const成员函数   
      private:   
  
int m_num;   
  
int m_data[100];   
  };   
    
  
int   Stack::GetCount(void)     const   
  {   
  
++   m_num; //   编译错误,企图修改数据成员m_num   
  Pop(); //   编译错误,企图调用非const函数   
  return   m_num;   
  }   

 

  const成员函数的声明看起来怪怪的:const关键字只能放在函数声明的尾部,大概是因为其它地方都已经被占用了。

 

 

Mutable Data Members
可变数据成员

可变数据成员(mutable data member)永远都不能为 const,甚至当它是 const 对象的成员时也如此。因此,const 成员函数可以改变 mutable 成员。要将数据成员声明为可变的,必须将关键字 mutable 放在成员声明之前:

     class Screen {
     public:
     // interface member functions
     private:
         mutable size_t access_ctr; // may change in a const members
         // other data members as before
      };

我们给 Screen 添加了一个新的可变数据成员 access_ctr。使用 access_ctr 来跟踪调用 Screen 成员函数的频繁程度:

     void Screen::do_display(std::ostream& os) const
     {
         ++access_ctr; // keep count of calls to any member function
         os << contents;
     }

尽管 do_display const,它也可以增加 access_ctr。该成员是可变成员,所以,任意成员函数,包括 const 函数,都可以改变 access_ctr 的值。

 

 

Defining Overloaded Member Functions
定义重载成员函数
class Screen {
public:
    typedef std::string::size_type index;
    // return character at the cursor or at a given position
    char get() const { return contents[cursor]; }
    char get(index ht, index wd) const;
    // remaining members
private:
    std::string contents;
    index cursor;
    index height, width;
};

As with any overloaded function, we select which version to run by supplying the appropriate number and/or types of arguments to a given call:

与任意的重载函数一样,给指定的函数调用提供适当数目或类型的实参来选择运行哪个版本:

     Screen myscreen;
     char ch = myscreen.get();// calls Screen::get()
     ch = myscreen.get(0,0); // calls Screen::get(index, index)

 

操作符重载
重载操作是具有特殊名称的函数,他跟其他函数一样,有返回类型和形参,只是定义的时候多了个"operator":
Sales_item operator+(const Sales_item&, const Sales_item&);
有些注意的地方下面列了出来:
1.不能重载的操作符有如下几个:
  ::
  .*
  .
  ?:
2.内置类型的操作符,其含义不能改变。比如说+的内置类型是int,你就不能用int型作为返回值和参数来重载+了。
  你只能用类类型或枚举类型作为返回值和参数来对int进行重载。
3.操作符优先级和结合性不变
4.作为类成员的重载函数,其形参看起来要比非类成员重载函数少一个。
  这是因为作为类成员的重载函数有一个隐含的this形参,限定为第一个操作数。
  //作为类成员的重载函数:
  Sales_item& Sales_item::operator+=(const Sales_item&);
  //作为非类成员的重载函数:
  Sales_item operator+(const Sales_item&, const Sales_item&);
5.非成员函数的重载操作符如果要访问私有成员的话,需要将该操作符定义成所操作类的友元。


Explicitly Specifying inline Member Functions
显式指定 inline 成员函数

在类内部定义的成员函数,例如不接受实参 get 成员,将自动作为 inline 处理。也就是说,当它们被调用时,编译器将试图在同一行内扩展该函数( 7.6 )。也可以显式地将成员函数声明为 inline

     class Screen {
     public:
         typedef std::string::size_type index;
         // implicitly inline when defined inside the class declaration
         char get() const { return contents[cursor]; }
         // explicitly declared as inline; will be defined outside the class declaration
         inline char get(index ht, index wd) const;
         // inline not specified in class declaration, but can be defined inline later
         index get_cursor() const;
         // ...
      };
     // inline declared in the class declaration; no need to repeat on the definition
     char Screen::get(index r, index c) const
     {
         index row = r * width;    // compute the row location
         return contents[row + c]; // offset by c to fetch specified character
     }
     // not declared as inline in the class declaration, but ok to make inline in definition
     inline Screen::index Screen::get_cursor() const
     {
         return cursor;
     }