zoukankan      html  css  js  c++  java
  • 类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() << '
    ';
          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() << '
    ';
      return 0;
    }

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

    Circle base;

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

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

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

  • 相关阅读:
    实现IEnumberable接口和IEnumberator
    XAML-1
    Java基础00-Java概述1
    详解Lombok中的@Builder用法
    stream之map的用法
    stream之forEach的用法
    Java中map.getOrDefault()方法的使用
    BiPredicate的test()方法
    Function.identity()
    java 8 lamda Stream的Collectors.toMap 参数
  • 原文地址:https://www.cnblogs.com/guozqzzu/p/3628756.html
Copyright © 2011-2022 走看看