可写的本地const

时间:2012-03-11 11:57:02

标签: delphi

请勿拍我,但这是我第一次看到使用本地可写 const(或者我可能只是太老了......):{ {3}}

查看本地const FullScreen: Boolean = False;然后FullScreen := not FullScreen;

起初我认为这是现代Delphi版本的新功能,但它也适用于我的D5。所以我的问题是:本地可写常量与声明全局可写常量完全相同吗?

e.g。

procedure TForm1.Button1Click(Sender: TObject);
Const
  LocalConst: Boolean = False;
begin
  LocalConst := not LocalConst;
  if LocalConst then Beep;
end;

像这段代码一样工作吗? :

Const
  GlobalConst_Button2Click: Boolean = False;

procedure TForm1.Button2Click(Sender: TObject);
begin
  GlobalConst_Button2Click := not GlobalConst_Button2Click;
  if GlobalConst_Button2Click then Beep;
end;

或者,LocalConst是本地的方法,即静态?这个恒定的线程是否安全? 任何人都可以对这个问题有所了解吗?

4 个答案:

答案 0 :(得分:11)

具有本地和全局类型常量的代码完全相同。

正如大卫已经说过的那样,整个程序中可以访问全局静态变量(也称为类型常量),而本地静态变量则不可以。 下面我将typed constants称为static variables,因为这就是他们真正的目标。

然而,本地静态变量确实以与全局静态var完全相同的方式在内存中保留。只是编译器不允许您从例程外部访问本地var 还要注意,如果你的本地静态var特别大(或者你有很多)它们会占用一块恒定的内存(即使我无法想象这可能是一个问题)

因为静态变量驻留在固定位置,所以它不是线程安全的。 它有效地变成了所有线程实例之间的共享变量 如果静态var不能在单个CPU周期中更改(即,如果它大于整数或者它是复杂类型),则两个线程可以同时更改变量的不同部分,通常会导致损坏。

它可以在一个循环中改变,例如一个布尔或整数,你永远不知道你所在的线程是最后一个更改的线程还是另一个线程,这在大多数情况下会导致不可预测的结果。

简而言之
除非您确切知道自己在做什么,否则在线程代码中使用静态变量是一个非常糟糕的主意。

此例外可能是一个整数计数器,您只需递增并测试以查看是否发生了多于x次的执行。
静态变量通常不适合在线程之间传递消息。

如果您想在线程之间共享数据,最好使用threadvar
见:http://en.wikipedia.org/wiki/Thread-local_storage
并且:https://stackoverflow.com/search?q=delphi+threadvar

<强>最后
很少有问题需要全局静态变量,因此它们是危险的,因此最好避免使用它们 本地静态变量在单线程代码中非常有用,可以跟踪例程的不同执行之间的状态。
由于竞争条件,它们在多线程代码中执行此操作毫无用处。

答案 1 :(得分:4)

  

本地可写常量是否与声明全局可写常量完全相同?

唯一的区别是范围。全局变量是全局变量,局部变量具有局部范围。可写类型常量是C静态局部变量的合理近似值。

可写类型常量的巨大缺点是没有像C中那样的关键字支持,你必须使用编译器选项来切换语言的含义!在我看来,这使得可写类型常量实际上无用。

答案 2 :(得分:4)

我参加派对有点晚了,但我仍想在可写的常数上添加一些信息。

首先,正如约翰和大卫所说,全局和局部常数的记忆没有差别。

对于那些对可写常量感兴趣的人: 我发现模拟“Promise”功能使函数“Lazy”很有用。 Ofcourse delphi不支持Promises,因此这只是部分有效。

考虑一个函数来计算字符串中的单词数量:

function CountWords(Input: String):Integer;
var
    Worker: TStringList;
begin
  Worker := TStringList.Create;
  Worker.DelimitedText := Input;
  Result := Worker.Count;
  Worker.Free;
end;

现在想象它在我们的计划中多次被召唤。 TStringList对象将在每次执行时创建并释放,从而执行额外的工作。 你可以通过创建一个全局变量Worker_CountWords来解决这个问题,在程序启动时初始化它并在你的函数中使用它,但看看这个:

function CountWords(Input: String):Integer;
{$J+} //Enable writable constants
const
    Worker: TStringList = nil;
{$J-} //Disable writable constants
begin
  if Worker = nil then
  begin
    Worker := TStringList.Create;
    //Other Initialization code here
  end;
  Worker.DelimitedText := Input;
  Result := Worker.Count;
end;

这个函数只会创建一次TStringList并在以后使用它,但永远不会释放它(这里有一种缺点)。但是对于可以在应用程序运行期间随时调用的函数,这是一种合适的选择。这可以让你的代码看起来更清洁,如果你... 现在,注意 - 这实际上不是一个承诺,但它实现了类似的结果。你可以用函数调用来做这个(我已经尝试过替换内存中的实际函数了,这是一个非常糟糕的主意,但你可以创建一个const来保存指向函数的指针,它在开始时保存指向初始化函数的指针,之后替换为实际的worker函数,父函数只能调用一个常量保持的函数。我现在想不出一个很好的例子,所以我会让你自己想出一个。

为了修改常量值,也不需要{$ WRITABLECONST ON},你也可以这样做:

procedure DoSomeWork;
const
  FirstCall : TDateTime = 0;
begin
  if FirstCall = 0 then
    PDateTime(@FirstCall)^ := Now;
  Writeln(TimeToStr(FirstCall));
  //some actual work here
end;

同样的事情适用于函数中的const参数,因为它们与var参数完全相同(通过引用传递以避免花时间创建单独的变量),唯一的区别是编译器没有' t允许您正常更改这些值。

P.S。注意const函数参数,因为你可以传递像foo(12)这样的实际常量,并尝试修改它可能会搞砸了......

答案 3 :(得分:0)

我完全不理解这个问题,所以我会回答所有可能发生的情况:

  1. 如果您不理解我的CountWords示例中有两个不同:第一个示例在每次调用时创建并释放类实例。第二个只在第一次调用时创建类实例,然后使用它直到程序终止。
  2. 如果你的意思是如果你创建一个单例并在其中实现CountWords会有什么不同:它在功能上没有区别,但你必须为它编写一个全新的类,然后在它上面初始化它程序启动并在以后使用它。除了对使用单身人士有很多批评(更多信息请参阅维基),所以我不会这样做。
  3. 如果您询问这两个示例函数正在执行的实际工作:它们显然会产生相同的结果,但第一个没有针对在调用此函数的环境中运行进行优化。正如我之前所说,你可以通过声明一个全局变量来达到相同的结果,但是如果你这样做 - 你将在你的程序中的任何地方看到你的全局变量,而只需要在一个特定的位置而不是其他地方。
  4. P.S。我对const函数参数实际上是错误的,事实证明这些东西在不同的delphi版本中表现不同。 Const参数与Delphi 6中的var参数的作用相同,但在Delphi XE2中,似乎无论如何都会创建局部变量。无论哪种方式,我都不建议弄乱const函数参数。