@@ -236,10 +236,11 @@ JavaScript 中的大部分控制结构在 Solidity 中都是可用的,除了 `
236
236
解构赋值和返回多值
237
237
-------------------------------------------------------
238
238
239
- Solidity 内部允许元组 (tuple) 类型,也就是一个在编译时元素数量固定的对象列表,列表中的元素可以是不同类型的对象。这些元组可以用来同时返回多个数值,也可以用它们来同时给多个变量(或通常的 LValues)
239
+ Solidity 内部允许元组 (tuple) 类型,也就是一个在编译时元素数量固定的对象列表,列表中的元素可以是不同类型的对象。这些元组可以用来同时返回多个数值,也可以用它们来同时给多个新声明的变量或者既存的变量(或通常的 LValues):
240
+
240
241
::
241
242
242
- pragma solidity ^ 0.4.16 ;
243
+ pragma solidity > 0.4.23 <0.5.0 ;
243
244
244
245
contract C {
245
246
uint[] data;
@@ -249,26 +250,22 @@ Solidity 内部允许元组 (tuple) 类型,也就是一个在编译时元素
249
250
}
250
251
251
252
function g() public {
252
- //声明变量并赋值。显式指定类型不可能。
253
- var (x, b, y) = f();
254
- //为已存在的变量赋值。
255
- (x, y) = (2, 7);
253
+ //基于返回的元组来声明变量并赋值
254
+ (uint x, bool b, uint y) = f();
256
255
//交换两个值的通用窍门——但不适用于非值类型的存储 (storage) 变量。
257
256
(x, y) = (y, x);
258
257
//元组的末尾元素可以省略(这也适用于变量声明)。
259
- //如果如果元组以空元素结束,
260
- //则其余的值将被丢弃。
261
- (data.length,) = f(); // 将长度设置为 7
262
- //左侧也可以做同样的事情。
263
- //如果元组以空元素开始,则初始值将被丢弃。
264
- (,data[3]) = f(); //将 data[3] 设置为 2
258
+ (data.length,,) = f(); // 将长度设置为 7
265
259
//省略元组中末尾元素的写法,仅可以在赋值操作的左侧使用,除了这个例外:
266
260
(x,) = (1,);
267
261
//(1,) 是指定单元素元组的唯一方法,因为 (1)
268
262
//相当于 1。
269
263
}
270
264
}
271
265
266
+ .. note ::
267
+ 直到 0.4.24 版本,给具有更少的元素数的元组赋值都可以可能的,无论是在左边还是右边(比如在最后空出若干元素)。现在,这已经不推荐了,赋值操作的两边应该具有相同个数的组成元素。
268
+
272
269
数组和结构体的复杂性
273
270
------------------------------------
274
271
赋值语义对于像数组和结构体这样的非值类型来说会有些复杂。
@@ -284,60 +281,56 @@ Solidity 内部允许元组 (tuple) 类型,也就是一个在编译时元素
284
281
变量声明后将有默认初始值,其初始值字节表示全部为零。任何类型变量的“默认值”是其对应类型的典型“零状态”。例如, ``bool `` 类型的默认值是 ``false `` 。 ``uint `` 或 ``int `` 类型的默认值是 ``0 `` 。对于静态大小的数组和 ``bytes1 `` 到 ``bytes32 `` ,每个单独的元素将被初始化为与其类型相对应的默认值。
285
282
最后,对于动态大小的数组, ``bytes `` 和 ``string `` 类型,其默认缺省值是一个空数组或字符串。
286
283
284
+ Solidity 中的作用域规则遵循了 C99(与其他很多语言一样):变量将会从它们被声明之后可见,直到一对 ``{ } `` 块的结束。作为一个例外,在 for 循环语句中初始化的变量,其可见性仅维持到 for 循环的结束。
287
285
288
- 无论变量在哪里声明,只要是在函数中,变量都将存在于 *整个函数 * 的范围内。这种情况是因为 Solidity 继承了 JavaScript 的范围规则。这与许多语言形成对比,在这些语言中变量的范围为变量声明处到函数体结束。
289
- 因此,下面的代码是非法的,并且会导致编译失败,报错为 ``Identifier already declared ``
290
- ::
291
-
292
- //此处不会编译
293
-
294
- pragma solidity ^0.4.16;
286
+ 那些定义在代码块之外的变量,比如函数、合约、自定义类型等等,并不会影响它们的作用域特性。这意味着你可以在实际声明状态变量的语句之前就使用它们,并且递归地调用函数。
295
287
296
- contract ScopingErrors {
297
- function scoping() public {
298
- uint i = 0;
288
+ 基于以上的规则,下边的例子不会出现编译警告,因为那两个变量虽然名字一样,但却在不同的作用域里。
299
289
300
- while (i++ < 1) {
301
- uint same1 = 0;
302
- }
303
-
304
- while (i++ < 2) {
305
- uint same1 = 0;// 非法,same1 的第二次声明
306
- }
307
- }
290
+ ::
308
291
309
- function minimalScoping() public {
292
+ pragma solidity >0.4.24;
293
+ contract C {
294
+ function minimalScoping() pure public {
310
295
{
311
296
uint same2 = 0;
312
297
}
313
298
314
- {
315
- uint same2 = 0;// 非法,same2 的第二次声明
299
+ {
300
+ uint same2 = 0;
316
301
}
317
302
}
303
+ }
304
+
305
+ 作为 C99 作用域规则的特例,请注意在下边的例子里,第一次对 ``x `` 的赋值会改变上一层中声明的变量值。如果外层声明的变量被“影子化”(就是说被在内部作用域中由一个同名变量所替代)你会得到一个警告。
306
+
307
+ ::
318
308
319
- function forLoopScoping() public {
320
- for (uint same3 = 0; same3 < 1; same3++) {
321
- }
322
- for (uint same3 = 0; same3 < 1; same3++) {// 非法,same3 的第二次声明
309
+ pragma solidity >0.4.24;
310
+ contract C {
311
+ function f() pure public returns (uint) {
312
+ uint x = 1;
313
+ {
314
+ x = 2; // 这个赋值会影响在外层声明的变量
315
+ uint x;
323
316
}
317
+ return x; // x has value 2
324
318
}
325
319
}
326
320
327
- 除此之外,如果声明了一个变量,它将在函数的开头初始化为其默认值。因此,下面的代码是合法的,尽管这种写法不推荐::
321
+ .. warning ::
322
+ 在 Solidity 0.5.0 之前的版本,作用域规则都沿用了 Javascript 的规则,即一个变量可以声明在函数的任意位置,都可以使他在整个函数范围内可见。而这种规则会从 0.5.0 版本起被打破。从 0.5.0 版本开始,下面例子中的代码段会导致编译错误。
328
323
329
- pragma solidity ^0.4.0;
324
+ ::
330
325
326
+ // 这将无法编译通过
327
+
328
+ pragma solidity >0.4.24;
331
329
contract C {
332
- function foo() public pure returns (uint) {
333
- // baz 被隐式初始化为 0
334
- uint bar = 5;
335
- if (true) {
336
- bar += baz;
337
- } else {
338
- uint baz = 10;// 不会执行
339
- }
340
- return bar;// returns 5
330
+ function f() pure public returns (uint) {
331
+ x = 2;
332
+ uint x;
333
+ return x;
341
334
}
342
335
}
343
336
@@ -354,7 +347,7 @@ Solidity 使用状态恢复异常来处理错误。这种异常将撤消对当
354
347
355
348
356
349
还有另外两种触发异常的方法: ``revert `` 函数可以用来标记错误并恢复当前的调用。
357
- 将来还可能在 ``revert `` 调用中包含有关错误的详细信息。 ``throw `` 关键字也可以用来替代 ``revert() `` 。
350
+ ``revert `` 调用中包含有关错误的详细信息是可能的,这个消息会被返回给调用者。已经不推荐的关键字 ``throw `` 也可以用来替代 ``revert()``(但无法返回错误消息) 。
358
351
359
352
360
353
.. 注意::
@@ -366,17 +359,17 @@ Solidity 使用状态恢复异常来处理错误。这种异常将撤消对当
366
359
.. 警告::
367
360
作为 EVM 设计的一部分,如果被调用合约帐户不存在,则低级函数 ``call`` , ``delegatecall`` 和 ``callcode`` 将返回 success。因此如果需要使用低级函数时,必须在调用之前检查被调用合约是否存在。
368
361
369
- Catching exceptions is not yet possible.
370
362
异常捕获还未实现
371
363
372
- In the following example, you can see how ``require `` can be used to easily check conditions on inputs
373
- and how ``assert `` can be used for internal error checking::
374
- 在下例中,你可以看到如何轻松使用``require``检查输入条件以及如何使用``assert``检查内部错误::
375
- pragma solidity ^0.4.0;
364
+ 在下例中,你可以看到如何轻松使用``require``检查输入条件以及如何使用``assert``检查内部错误,注意,你可以给 ``require `` 提供一个消息字符串,而 ``assert `` 不行。
365
+
366
+ ::
367
+
368
+ pragma solidity ^0.4.22;
376
369
377
370
contract Sharer {
378
371
function sendHalf(address addr) public payable returns (uint balance) {
379
- require(msg.value % 2 == 0); // 只接受偶数
372
+ require(msg.value % 2 == 0, "Even value required.");
380
373
uint balanceBeforeTransfer = this.balance;
381
374
addr.transfer(msg.value / 2);
382
375
//由于转移函数在失败时抛出异常并且不能在这里回调,因此我们应该没有办法仍然有一半的钱。
@@ -414,3 +407,31 @@ and how ``assert`` can be used for internal error checking::
414
407
在这两种情况下,都会导致 EVM 回退对状态所做的所有更改。回退的原因是不能继续安全地执行,因为没有实现预期的效果。
415
408
因为我们想保留交易的原子性,所以最安全的做法是回退所有更改并使整个交易(或至少是调用)不产生效果。
416
409
请注意, ``assert `` 式异常消耗了所有可用的调用 gas ,而从 Metropolis 版本起 ``require `` 式的异常不会消耗任何 gas。
410
+
411
+ 下边的例子展示了如何在 revert 和 require 中使用错误字符串:
412
+
413
+ ::
414
+
415
+ pragma solidity ^0.4.22;
416
+
417
+ contract VendingMachine {
418
+ function buy(uint amount) payable {
419
+ if (amount > msg.value / 2 ether)
420
+ revert("Not enough Ether provided.");
421
+ // 下边是等价的方法来做同样的检查:
422
+ require(
423
+ amount <= msg.value / 2 ether,
424
+ "Not enough Ether provided."
425
+ );
426
+ // 执行购买操作
427
+ }
428
+ }
429
+
430
+ 这里提供的字符串应该是经过 :ref: `ABI 编码 <ABI >` 之后的,因为它实际上是调用了 ``Error(string) `` 函数。在上边的例子里,``revert("Not enough Ether provided."); `` 会产生如下的十六进制错误返回值:
431
+
432
+ .. code ::
433
+
434
+ 0x08c379a0 // Error(string) 的函数选择器
435
+ 0x0000000000000000000000000000000000000000000000000000000000000020 // 数据的偏移量(32)
436
+ 0x000000000000000000000000000000000000000000000000000000000000001a // 字符串长度(26)
437
+ 0x4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 // 字符串数据("Not enough Ether provided." 的 ASCII 编码,26字节)
0 commit comments