类Class(二):构造函数(constructors)

 构造函数(constructors)

 

对象(object)在生成过程中通常需要初始化变量或分配动态内存,以便我们能够操作,或防止在执行过程中返回意外结果。

例如,在前面的例子 类Class(一) 中,如果我们在调用函数 set_values( ) 之前就调用了函数 area(),将会产生什么样的结果呢?

可能会是一个不确定的值,因为成员 width 和 height 还没有被赋于任何值。

 

为了避免这种情况发生,一个 class 可以包含一个特殊的函数:构造函数 constructor,它可以通过声明一个与 class 同名的函数来定义。

当且仅当要生成一个 class 的新的实例 (instance)的时候,也就是当且仅当声明一个新的对象,或给该 class 的一个对象分配内存的时候,

这个构造函数将自动被调用。下面,我们将实现包含一个构造函数的Rectangle class:

 

 

#include <iostream>
using namespace std;

class Rectangle {
    int width, height;
  public:
    Rectangle (int, int);
    int area () {return (width*height);}
};

Rectangle::Rectangle (int a, int b) {
  width = a;
  height = b;
}

int main () {
  Rectangle rect (3,4);
  Rectangle rectb (5,6);
  cout << "rect area: " << rect.area() << endl;
  cout << "rectb area: " << rectb.area() << endl;
  return 0;
}
rect area: 12
rectb area: 30

 

这个例子的输出结果与前面一个没有区别。在这个例子中,我们只是把函数 set_values 换成了class 的构造函数 constructor。注意这里参数是如何在 class 实例 (instance)生成的时候传递给构造函数的:

 

    Rectangle rect (3,4);

    Rectangle rectb (5,6);

 

同时你可以看到,构造函数的原型和实现中都没有返回值(return value),也没有 void 类型声明。构造函数必须这样写。一个构造函数永远没有返回值,也不用声明 void,就像我们在前面的例子中看到的



一致性初始化

 

上面调用构造函数的方式:Rectangle rect (3,4);  被称为函数形式(functional form)。除此之外,构造函数的调用还有其它的语法:

 

- 如果构造函数只有一个参数,调用时,可以用等式语法:

   class_name object_name = initialization_value; 

 

- 最近,C++ 介绍了另外一种调用语法:一致性初始化(Uniform initialization)。语法是把括号() 换成 大括号 {}

   class_name object_name { value, value, value, ... } 

    - object_name 和 大括号之间的等号可有可无

 

下面这个例子,使用4种方式调用构造函数:

#include <iostream>
using namespace std;

class Circle {
 
      double radius;
   public:
      Cricle(double r) { radius = r; }
      double circum() { return 2*radius*3.14159265;}
};

int main () {

      Circle foo (10.0);   //functional form
      Circle bar = 20.0;  //等式语法
      Circle baz {30.0}; //uniform init
      Circle qux = {40.0}; //带等式的 uniform init

      cout << "foo's circumference: " << foo.circum() << '\n';
      return 0;
}

 

使用一致性初始化的优势是:使用函数形式调用构造函数(使用括号()),会和函数调用方式混淆,但使用大括号{} 这种语法就不会了。并且这种语法会显示调用默认构造函数:

Rectangle rectb;   // default constructor called
Rectangle rectc(); // function declaration (default constructor NOT called)
Rectangle rectd{}; // default constructor called

 

关于默认构造函数,在构造函数重载这一篇里面有所提及。大部分开发者习惯使用函数形式调用构造函数,一些新的风格指南推荐使用大括号的方式。

 

 

构造函数中的成员初始化

 

使用构造函数初始化其他成员变量的时候,有两种方式:

  1. 在方法体(body)中对这些变量赋值,

  2. 通过在方法体前插入冒号(:),冒号后跟上一系列初始化值 (用逗号隔开)。这种方式被称为成员初始化(member initialization)

比如下面的例子:

class Rectangle {
    int width,height;
  public:
    Rectangle(int,int);
    int area() {return width*height;}
};

 

对构造函数声明后,接下来进行函数实现。按照 方式1 的做法是:

Rectangle::Rectangle(int x, int y) { width=x, height=y;}

 

方式2 的做法如下:

Rectangle::Rectangle(int x, int y) : width(x) {height=y;}

 

更激进一点:

Rectangle::Rectangle (int x, int y) : width(x), height(y) {};

 

使用成员初始化的情形

 

对于基本类型的初始化,上面两种方式没有区别。但是,如果某个成员变量是 类对象呢?这种成员变量在声明的时候,默认调用它的默认构造函数初始化的。这会产生两个问题:

  1. 假设类 A 的某个成员变量是 类对象 b,属于类B。如果 b 在 类A 的构造函数中被重新初始化了(比如重新赋值),那么之前 b 走的默认构造路线就是一种浪费

  2. 如果类B 此时没有默认构造函数怎么办?就会报错no matching function for call to 'B::B()'

 

通过冒号(:) 的形式使用 成员初始化 可以避免上面的问题

#include <iostream>
using namespace std;

class Circle {
    double radius;
  public:
    Circle(double r) : radius(r) { }
    double area() {return radius*radius*3.14159265;}
};

class Cylinder {
    Circle base;
    double height;
  public:
    Cylinder(double r, double h) : base (r), height(h) {}
    double volume() {return base.area() * height;}
};

int main () {
  Cylinder foo (10,20);

  cout << "foo's volume: " << foo.volume() << '\n';
  return 0;
}

 

上面的代码,在类 Cylinder 中有类型是 Circle 的成员变量 base。类 Circle 没有默认构造函数,只有一个带参数的构造函数。在类 Cylinder 中,声明对象 base 的时候,它的默认构造函数就会被调用:

Circle base;

类 Cylinder 的构造函数又需要调用类 Circle 的构造函数去初始化 base,唯一的办法就是把初始化放在 成员初始化列表里面,也就是冒号(:) 后面的用逗号隔开的列表中。这样就避免了上面提到的问题。

 

成员初始化的语法中还可以使用一致性初始化语法(大括号{}):

Cylinder::Cylinder (double r, double h) : base{r}, height{h} { }

 

 

posted on 2014-03-27 17:42  guozqzzu  阅读(2613)  评论(0编辑  收藏  举报