指针、引用和常量

在了解了内置类型和对象类型以及 引用指针的概念之后,比较一下所有可用的类型修改可能很有意义。

MQL5 中的引用仅用于描述函数和方法的参数。此外,对象类型参数必须通过引用传递。

void function(ClassOrStruct &object) { }          // OK
void function(ClassOrStruct object) { }           // wrong
void function(double &value) { }                  // OK
void function(double value) { }                   // OK

这里的 ClassOrStruct 是类或结构体的名称。

只允许作为引用类型参数的自变量来传递变量(左侧值),不允许传递常量或表达式计算产生的临时值。

您无法创建引用类型的变量,也无法从函数返回引用。

ClassOrStruct &function(void) { return Class(); } // wrong
ClassOrStruct &object;                            // wrong
double &value;                                    // wrong

MQL5 中的指针仅适用于类对象。不支持指向内置类型变量或结构体变量的指针。

您可以将变量或函数参数声明为指向对象的指针,也可以从函数返回指向对象的指针。

ClassOrStruct *pointer;                                   // OK
void function(ClassOrStruct *object) { }                  // OK
ClassOrStruct *function() { return new ClassOrStruct(); } // OK

但您不能返回指向局部自动对象的指针,因为后者在函数退出时会被释放,并且指针将失效。

如果函数返回一个指向在函数内部使用 new 动态分配的对象的指针,则调用代码必须记住使用 delete 释放指针。

指针可以为 NULL,这一点与引用不同。指针参数可以具有默认值,但引用不能(“无法初始化引用”错误)。

void function(ClassOrStruct *object = NULL) { }          // OK
void function(ClassOrStruct &object = NULL) { }          // wrong

链接和指针可以在参数说明中组合使用。因此,函数可以接受指向指针的引用:然后函数中对该指针的更改将在调用代码中生效。特别是,负责创建对象的工厂函数能以这种方式实现。

void createObject(ClassName *&ref)
{
   ref = new ClassName();
   // further customization of ref
   ...
}

确实,要从函数返回单个指针,通常习惯使用 return 语句,因此这个例子有些刻意。但是,在需要将指针数组传递到外部的情况下,在参数中引用数组是首选方案。例如,在一些用于处理带有 [键, 值] 对的映射类型容器类的标准库类(MQL5/Include/Generic/SortedMap.mqhMQL5/Include/Generic/HashMap.mqh)中,有一些 CopyTo 方法可用于获取具有 CKeyValuePair 元素的数组。

int CopyTo(CKeyValuePair<TKey,TValue> *&dst_array[], const int dst_start 0);

dst_array 参数类型可能看起来不太熟悉:它是一个类模板。我们将在 下一章学习模板。目前,我们只关心这是一个对指针数组的引用。

const 修饰符对所有类型施加特殊行为。关于内置类型,我们已在 常量变量一节中进行了讨论。对象类型有其自身的特性。

如果变量或函数参数被声明为指针或对象的引用(引用仅在参数中有效),则其中的 const 修饰符会将可访问的方法和属性集限制为同样带有 const 修饰符的对象。换句话说,只有常量属性才能通过常量引用和指针访问。

当您尝试调用非常量方法或更改非常量字段时,编译器将生成错误:“对常量对象调用非常量方法”或“无法修改常量”。

非常量指针参数可以接受任何自变量(常量或非常量)。

需要注意的是,在指针说明中可以设置两个修饰符 const:一个引用对象,另一个引用指针:

  • Class *pointer 指向对象的指针;对象和指针正常运行,不受任何限制;
  • const Class *pointer 指向常量对象的指针;对于对象,只能使用常量方法和读取属性,但指针可以更改(为其赋予另一个对象的地址);
  • const Class * const pointer 指向常量对象的常量指针;对于对象,只能使用常量方法和读取属性;指针不能更改;
  • Class * const pointer 指向对象的常量指针;指针不能更改,但对象的属性可以更改。

以下面的 Counter 类 (CounterConstPtr.mq5) 为例。

class Counter
{
public:
   int counter;
   
   Counter(const int n = 0) : counter(n) { }
   
   void increment()
   {
      ++counter;
   }
   
   Counter *clone() const
   {
      return new Counter(counter);
   }
};

它人为地创建了公共变量 counter。该类还包含两个方法,其中一个是常量 (clone),另一个不是 (increment)。回想一下,常量方法无权更改对象的字段。

以下带有 Counter *ptr 类型参数的函数可以调用该类的所有方法并更改其字段。

void functionVolatile(Counter *ptr)
{
   // OK: everything is available
   ptr.increment();
   ptr.counter += 2;
   //remove the clone immediately so that there is no memory leak
   // the clone is only needed to demonstrate calling a constant method 
   delete ptr.clone(); 
   ptr = NULL;
}

以下带有 const Counter *ptr 参数的函数将抛出几个错误。

void functionConst(const Counter *ptr)
{
   // ERRORS:
   ptr.increment(); // calling non-const method for constant object
   ptr.counter = 1// constant cannot be modified
   
   // OK: only const methods are available, fields can be read
   Print(ptr.counter); // reading a const object
   Counter *clone = ptr.clone(); // calling a const method
   ptr = clone;     // changing a non-const pointer ptr
   delete ptr;      // cleaning memory
}

最后,以下带有 const Counter * const ptr 参数的函数执行的操作更少。

void functionConstConst(const Counter * const ptr)
{
   // OK: only const methods are available, the pointer ptr cannot be changed
   Print(ptr.counter); // reading a const object
   delete ptr.clone(); // calling a const method
   
   Counter local(0);
   // ERRORS:
   ptr.increment(); // calling non-const method for constant object
   ptr.counter = 1// constant cannot be modified
   ptr = &local;    // constant cannot be modified
}

OnStart 函数中,我们声明了两个 Counter 对象(一个是常量,另一个不是),您可以调用这些函数,但有一些例外:

void OnStart()
{
   Counter counter;
   const Counter constCounter;
   
   counter.increment();
   
   // ERROR:
   // constCounter.increment(); // call non-const method for constant object
   Counter *ptr = (Counter *)&constCounter// trick: type casting without const
   ptr.increment();
   
   functionVolatile(&counter);
   
   // ERROR: cannot convert from a const pointer...
   // functionVolatile(&constCounter); // to a non-const pointer
   
   functionVolatile((Counter *)&constCounter); // type casting without const
   
   functionConst(&counter);
   functionConst(&constCounter);
   
   functionConstConst(&counter);
   functionConstConst(&constCounter);
}

首先,请注意,在非常量对象上尝试调用常量方法 increment 时,变量也会产生错误。

其次,constCounter 不能传递给 functionVolatile 函数,否则我们会收到错误“无法从常量指针转换为非常量指针”。

不过,这两个错误可以通过不带 const 修饰符的显式类型转换来避免。尽管我们不建议这样做。