d只赋值一次循环变量
原文
我正在用CSFML的D绑定,并且创建了自己的UI库,来将元素绘画到屏幕上.
其中一个我创建的类是,包含用户点击按钮时调用的叫点击按钮时(onButtonClick)的函数的按钮类.
目前,一切运行良好.
我想添加几个并排按钮,来表示列表中元素,并为列表中指定元素们每个分配唯一的λ式,这是我当前的代码:
foreach (BoardSize boardSize; arr) {
Button button = new Button();
button.text = format("%sx%s", boardSize[0], boardSize[1]);
button.onButtonClick = {
eventHandler.settingsWindow_onBoardSizeButtonClick(boardSize);
};
button.onButtonClick();
_boardSizeRow.addChild(button);
}
运行这段代码,原以为一切都会正常工作,但可惜,在运行代码时,点击每个按钮,只返回最大的最后迭代的板大小(boardSize)值.
我不确定为什么会这样?
更新:唯一解决方法:似乎是用静每一数组:
Button[3] b;
static foreach (indx, BoardSize boardSize; arr) {
b[indx] = new Button();
b[indx].text = format("%sx%s", boardSize[0], boardSize[1]);
b[indx].onButtonClick = {
eventHandler.settingsWindow_onBoardSizeButtonClick(boardSize);
};
_boardSizeRow.addChild(b[indx]);
}
还有其他方法可解决该恼人问题吗?
问题有两个方面:
1,D中闭包按引用抓环境.
2,在单个函数调用中的每个循环迭代中,D(我认为是错误的)认为循环局部变量都有相同标识.
所以,在你的事件处理器中,板大小(boardSize),是对单个变量的引用,在循环的每次迭代中,都会覆盖该变量值.因为,在循环终止前不会调用事件处理器,所以只看见函数调用中循环的最后一次迭代所设置的值.
至少有两种可能解决方法:
1,使用构来明确按值而不是按引用捕捉板大小:
static struct ClickHandler {
//如果`eventHandler`不是全局变量,则为它另外添加一个字段
BoardSize iteration_boardSize;
this(BoardSize iteration_boardSize) {
this.iteration_boardSize = iteration_boardSize;
}
void opCall() {
eventHandler.settingsWindow_onBoardSizeButtonClick(iteration_boardSize);
}
}
foreach (BoardSize loop_boardSize; arr) {
Button button = new Button();
button.text = format("%sx%s", loop_boardSize[0], loop_boardSize[1]);
button.onButtonClick = &(new ClickHandler(loop_boardSize)).opCall;
button.onButtonClick();
_boardSizeRow.addChild(button);
}
2,在循环每次迭代上,用板大小(boardSize)调用嵌套函数,来用唯一标识创建板大小副本:
foreach (BoardSize loop_boardSize; arr) {
Button button = new Button();
button.text = format("%sx%s", loop_boardSize[0], loop_boardSize[1]);
button.onButtonClick = (BoardSize iteration_boardSize) {
return {
eventHandler.settingsWindow_onBoardSizeButtonClick(iteration_boardSize);
};
}(loop_boardSize);
button.onButtonClick();
_boardSizeRow.addChild(button);
}
启用优化时,这两种方法应编译成大致相同的运行时代码.前者更明确,而后者更简洁.
静每一语义上等价于复制和粘贴循环体arr.length次数,用boardSize替换arr[indx],及indx替换indx字面值.
与运行时(非静态)循环不同,事件处理器闭包不抓板大小或indx的索引,因为运行时它们并不存在,因此无法引用.
在编译时创建arr.length个不同的都用适当indx字面替换的事件处理器函数.
所以,虽然只要编译时已知arr.length,它确实有效,但除非arr.length非常小,它膨胀了大量不必要代码.
啊,我想你遇见了闭包错误.试试以下代码:
foreach (BoardSize boardSize; arr) (){//注意括号
Button button = new Button();
button.text = format("%sx%s", boardSize[0], boardSize[1]);
button.onButtonClick = {
eventHandler.settingsWindow_onBoardSizeButtonClick(boardSize);
};
button.onButtonClick();
_boardSizeRow.addChild(button);
}() // 注意括号
这不是你的错,这是dmd的一种"优化",以便用户发现在每一中抓发出的每个变量,将分配新的堆闭包,不会感到惊讶.
祝你阅读愉快:问题.
这是经典的D陷阱:只分配一次循环变量,闭包抓了循环变量所在的唯一位置,因此每次循环迭代的每个闭包,在以后运行时都看到相同的循环索引的最后值.你要这样:
foreach (BoardSize boardSize; arr) {
Button button = new Button();
button.text = format("%sx%s", boardSize[0], boardSize[1]);
BoardSize size = boardSize; // 强制分开捕捉
button.onButtonClick = {
eventHandler.settingsWindow_onBoardSizeButtonClick(size);
};
button.onButtonClick();
_boardSizeRow.addChild(button);
}
这可说是语言错误(我想不到理智用例),但没人成功说服沃尔特.
你的抓变量代码,似乎不管用
啊,显然循环体内部局部变量也会受到同样古怪行为的影响.>:-(以下是一个可行的变通方法.
foreach (BoardSize boardSize; arr) {
Button button = new Button();
button.text = format("%sx%s", boardSize[0], boardSize[1]);
void wowThisIsDumb(BoardSize size) {
button.onButtonClick = {
eventHandler.settingsWindow_onBoardSizeButtonClick(size);
};
}
wowThisIsDumb(boardSize);
button.onButtonClick();
_boardSizeRow.addChild(button);
}
需要嵌套函数(或内联λ)来强制为抓分配动态环境.
真是尴尬.为什么还没有解决该问题?😦
好吧,不好意思写错了代码,正确的代码是:
foreach (BoardSize boardSizeIter; arr) (Boardsize boardSize){ // 注意括号,我稍微更改了变量名
Button button = new Button();
button.text = format("%sx%s", boardSize[0], boardSize[1]);
button.onButtonClick = {
eventHandler.settingsWindow_onBoardSizeButtonClick(boardSize);
};
button.onButtonClick();
_boardSizeRow.addChild(button);
}(boardSizeIter)
//注意括号,我首先更改了`foreach`参数的名称
这与HSTeoh和Tsbockman的代码相同:通过创建按参数取结构的函数字面,来在当前在foreach的特定迭代中,创建值的副本.这是有效的,因为结构是值类型,因此传递它给函数会创建副本.
浙公网安备 33010602011771号