静态变量
有时有必要在函数内部描述一个变量,确保它在整个程序执行期间中都存在。例如,我们要计算该函数被调用的次数。
那么这个变量不能是局部的,这样它将失去“长期记忆”,因为局部变量在每次调用函数时被创建,并且在函数退出时被移除。从技术角度,它可以被声明为全局变量;但是,如果该变量只在该函数内部使用,这种方法在程序设计方面是不合理的。
首先,全局变量可能会在程序的任何位置被意外更改。
其次,如果我们动不动就声明一个全局变量,那么可以想到,在程序的全局区域中会产生多么“杂乱无章”的变量。因此,我们建议在使用变量的最小代码块(如果有几个嵌套代码块)中声明这些变量。
因此,应该在函数内部描述函数执行的计数器。此时,变量的新属性“静态属性”就能派上用场了。
通过在变量类型声明的前面输入一个特殊关键字(修饰符)static,可以将变量的生存期延长至整个程序执行期间,即让它类似于全局变量。通常,静态变量只在一个函数内局部定义。因此,与普通局部变量一样,它的可见性受到相关代码块的限制。
静态变量也可以在全局级别描述,与普通全局变量没有任何区别(至少在撰写本书时是这样)。静态变量的这一行为与其在 C++ 中的行为有所不同:在 C++ 中,静态变量的可见性仅限于其所在的文件。在 MQL5 中,程序包含一个主 mq5 文件,可能还包含一些头文件(参见 指令 #include);因此,静态变量和普通全局变量都可以从程序的所有源文件中获得。
局部静态变量只创建一次:在程序首次进入描述该变量的函数时创建。这种变量只有在卸载程序时才会被移除。如果一个函数从未被调用过,那么在其中描述的局部静态变量(如有)将永远不会被创建。
例如,我们修改第一章中的 Greeting 函数,以便在每次调用该函数时显示不同的问候语。我们将新脚本命名为 GoodTimes.mq5。
我们将移除 GreetingHour 脚本的输入并移除 Greeting 函数的参数。在 Greeting 函数内部,我们将描述一个新的静态变量 counter(整数类型),初始值为 0。请注意,因为变量是静态的,所以初始化只会执行一次。
string Greeting()
|
因为我们现在了解了 static 修饰符,所以可以合理地将其用于 messages 数组。问题是它之前被声明为局部变量,并且每次调用 Greeting 函数(被多次调用)时都会被重新创建(并在函数退出时被移除)。这样效率不高。
注意,数组是由多个相同类型的值组成的命名集,可以通过名称后方括号中指定的索引来访问。上面介绍的关于变量的大部分规则都直接适用于数组。关于使用数组的更多细微差别见 数组一节。
让我们回到当前的问题。我们根据 return 语句中 counter 变量的值从数组中选取一个项,这一机制目前看起来有点晦涩难懂:
return messages[counter++ % 3]; |
我们在第一章中曾经简要提到了使用字符 '%' 执行取模运算。使用该字符,我们可以保证元素索引不会超过数组大小:不论计数器的值如何,除以 3 的模可以是 0、1 或 2。
这同样适用于 counter++ 结构体,该结构体表示将变量值加 1(单次递增)。
需要注意的是,在这种表示法中,将在整个表达式计算完成之后执行递增操作,在本例中,是在除法 counter % 3 完成之后。这意味着计数将从初始值“零”开始。也有可能在计算表达式之前执行递增操作,代码为:++counter % 3。那么计数将从 1 开始。我们将在 递增和递减一节中讨论这种类型的运算。
我们从 OnStart 调用连续调用 Greeting 函数 3 次。
void OnStart()
|
然后我们将在日志中看到带所有问候语的三个字符串,与预期一致。
GoodTimes (EURUSD,H1) Good morning, EURUSD
|
如果我们继续调用函数,计数器将递增,消息将不断循环。
尝试在 OnStart(已注释)末尾引用 counter 变量将不允许编译代码,因为静态变量虽然继续存在,但只在 Greeting 函数内部可用。
请注意,花括号用于构成代码块和初始化数组。请区分不同的用途。我们将在相关章节中详细介绍数组。然而,这些并不是花括号的全部用途:我们将在后面学习如何使用花括号定义自定义类型、结构体和类。也可以在结构体和类的内部定义静态变量。