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

  • Post author:
  • Post category:其他




构造函数(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} { }

转载于:https://www.cnblogs.com/guozqzzu/p/3628756.html