Solidity 官方文档中文版(四)

淡淡的烟草味﹌ 2022-06-01 23:44 699阅读 0赞

#

数据位置(Data location)

复杂类型,如数组(arrays)数据结构(struct)在Solidity中有一个额外的属性,数据的存储位置。可选为memorystorage

memory存储位置同我们普通程序的内存一致。即分配,即使用,越过作用域即不可被访问,等待被回收。而在区块链上,由于底层实现了图灵完备,故而会有非常多的状态需要永久记录下来。比如,参与众筹的所有参与者。那么我们就要使用storage这种类型了,一旦使用这个类型,数据将永远存在。

基于程序的上下文,大多数时候这样的选择是默认的,我们可以通过指定关键字storagememory修改它。

默认的函数参数,包括返回的参数,他们是memory。默认的局部变量是storage的1。而默认的状态变量(合约声明的公有变量)是storage

另外还有第三个存储位置calldata。它存储的是函数参数,是只读的,不会永久存储的一个数据位置。外部函数的参数(不包括返回参数)被强制指定为calldata。效果与memory差不多。

数据位置指定非常重要,因为不同数据位置变量赋值产生的结果也不同。在memorystorage之间,以及它们和状态变量(即便从另一个状态变量)中相互赋值,总是会创建一个完全不相关的拷贝。

将一个storage的状态变量,赋值给一个storage的局部变量,是通过引用传递。所以对于局部变量的修改,同时修改关联的状态变量。但另一方面,将一个memory的引用类型赋值给另一个memory的引用,不会创建另一个拷贝。

  1. pragma solidity ^0.4.0;
  2. contract DataLocation{
  3. uint valueType;
  4. mapping(uint => uint) public refrenceType;
  5. function changeMemory(){
  6. var tmp = valueType;
  7. tmp = 100;
  8. }
  9. function changeStorage(){
  10. var tmp = refrenceType;
  11. tmp[1] = 100;
  12. }
  13. function getAll() returns (uint, uint){
  14. return (valueType, refrenceType[1]);
  15. }
  16. }

下面来看下官方的例子说明:

  1. pragma solidity ^0.4.0;
  2. contract C {
  3. uint[] x; // the data location of x is storage
  4. // the data location of memoryArray is memory
  5. function f(uint[] memoryArray) {
  6. x = memoryArray; // works, copies the whole array to storage
  7. var y = x; // works, assigns a pointer, data location of y is storage
  8. y[7]; // fine, returns the 8th element
  9. y.length = 2; // fine, modifies x through y
  10. delete x; // fine, clears the array, also modifies y
  11. // The following does not work; it would need to create a new temporary /
  12. // unnamed array in storage, but storage is "statically" allocated:
  13. // y = memoryArray;
  14. // This does not work either, since it would "reset" the pointer, but there
  15. // is no sensible location it could point to.
  16. // delete y;
  17. g(x); // calls g, handing over a reference to x
  18. h(x); // calls h and creates an independent, temporary copy in memory
  19. }
  20. function g(uint[] storage storageArray) internal {}
  21. function h(uint[] memoryArray) {}
  22. }

总结

强制的数据位置(Forced data location)

  • 外部函数(External function)的参数(不包括返回参数)强制为:calldata
  • 状态变量(State variables)强制为: storage

默认数据位置(Default data location)

  • 函数参数(括返回参数:memory
  • 所有其它的局部变量:storage

更多请查看关于数据位置的进一步挖掘: http://me.tryblockchain.org/solidity-data-location.html

参考资料


  1. 本地变量但是为什么是stroage的,没有太想明白。 http://ethereum.stackexchange.com/questions/9781/solidity-default-data-location-for-local-vars ↩

引用类型(Reference Types)

复杂类型。不同于之前值类型,复杂类型占的空间更大,超过256字节,因为拷贝它们占用更多的空间。由此我们需要考虑将它们存储在什么位置内存(memory,数据不是永久存在的)存储(storage,值类型中的状态变量)

后面我们会讲到常见的引用类型。

函数(Function Types)

函数类型1即是函数这种特殊的类型。

  • 可以将一个函数赋值给一个变量,一个函数类型的变量。
  • 还可以将一个函数作为参数进行传递。
  • 也可以在函数调用中返回一个函数。

函数类型有两类;可分为internalexternal函数。

内部函数(internal)

因为不能在当前合约的上下文环境以外的地方执行,内部函数只能在当前合约内被使用。如在当前的代码块内,包括内部库函数,和继承的函数中。

外部函数(External)

外部函数由地址和函数方法签名两部分组成。可作为外部函数调用的参数,或者由外部函数调用返回。

函数的定义

完整的函数的定义如下:

  1. function (<parameter types>) {
  2. internal(默认)|external} [constant] [payable] [returns (<return types>)]

若不写类型,默认的函数类型是internal的。如果函数没有返回结果,则必须省略returns关键字。下面我们通过一个例子来了解一下。

  1. pragma solidity ^0.4.0;
  2. contract Test{
  3. //默认是internal类型的
  4. function noParameter() returns (uint){}
  5. //无返回结果
  6. function noReturn1(uint x) {}
  7. //如果无返回结果,必须省略`returns`关键字
  8. //function noReturn2(uint x) returns {}
  9. }

如果一个函数变量没有初始化,直接调用它将会产生异常。如果delete了一个函数后调用,也会发生同样的异常。

如果外部函数类型在Solidity的上下文环境以外的地方使用,他们会被视为function类型。编码为20字节的函数所在地址,紧跟4字节的函数方法签名2的共占24字节的bytes24类型。

合约中的public的函数,可以使用internalexternal两种方式来调用。下面来看看,两种方式的不同之处。

函数的internalexternal

调用一个函数f()时,我们可以直接调用f(),或者使用this.f()。但两者有一个区别。前者是通过internal的方式在调用,而后者是通过external的方式在调用。请注意,这里关于this的使用与大多数语言相背。下面通过一个例子来了解他们的不同:

  1. pragma solidity ^0.4.5;
  2. contract FuntionTest{
  3. function internalFunc() internal{}
  4. function externalFunc() external{}
  5. function callFunc(){
  6. //直接使用内部的方式调用
  7. internalFunc();
  8. //不能在内部调用一个外部函数,会报编译错误。
  9. //Error: Undeclared identifier.
  10. //externalFunc();
  11. //不能通过`external`的方式调用一个`internal`
  12. //Member "internalFunc" not found or not visible after argument-dependent lookup in contract FuntionTest
  13. //this.internalFunc();
  14. //使用`this`以`external`的方式调用一个外部函数
  15. this.externalFunc();
  16. }
  17. }
  18. contract FunctionTest1{
  19. function externalCall(FuntionTest ft){
  20. //调用另一个合约的外部函数
  21. ft.externalFunc();
  22. //不能调用另一个合约的内部函数
  23. //Error: Member "internalFunc" not found or not visible after argument-dependent lookup in contract FuntionTest
  24. //ft.internalFunc();
  25. }
  26. }

函数例子(官方)

  1. pragma solidity ^0.4.0;
  2. library ArrayUtils{
  3. function range(uint length) internal returns (uint[] memory r){
  4. r = new uint[](length);
  5. for(uint i = 0; i < length; i++){
  6. r[i] = i;
  7. }
  8. }
  9. function map(uint[] memory self, function(uint) returns (uint) f ) internal returns (uint[] memory r) {
  10. r = new uint[](self.length);
  11. for(uint i = 0; i < self.length; i++){
  12. r[i] = f(self[i]);
  13. }
  14. }
  15. function reduce(uint[] memory self, function(uint x, uint y) returns(uint) f ) internal returns (uint r) {
  16. r = self[0];
  17. for(uint i = 1; i < self.length; i++){
  18. r = f(r, self[i]);
  19. }
  20. }
  21. }
  22. contract Pyramid{
  23. using ArrayUtils for *;
  24. function pryamid(uint length) returns (uint){
  25. return ArrayUtils.range(length).map(square).reduce(sum);
  26. }
  27. function square(uint x) returns (uint){
  28. return x * x;
  29. }
  30. function sum(uint x, uint y) returns (uint){
  31. return x + y;
  32. }
  33. }

Question?

  • library是什么呢。
  • library引入时为什么使用using,这和文件引入的import有何区别。
  • library内的函数全是internal的。
  • library内的函数,他的参数函数为什么是internal的,不应该是external的?
  • uint[]是什么类型,不能写做[]uint
  • memory又是什么呢,为什么map函数明明是两个参数,但只需要传一个呢。

  1. http://solidity.readthedocs.io/en/develop/types.html#function-types ↩
  2. 函数签名的编码方式可查看函数选择器相关章节,【文档翻译系列】ABI详解 ↩

枚举

枚举类型是在Solidity中的一种用户自定义类型。他可以显示的转换与整数进行转换,但不能进行隐式转换。显示的转换会在运行时检查数值范围,如果不匹配,将会引起异常。枚举类型应至少有一名成员。我们来看看下面的例子吧。

  1. pragma solidity ^0.4.0;
  2. contract test {
  3. enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
  4. ActionChoices choice;
  5. ActionChoices constant defaultChoice = ActionChoices.GoStraight;
  6. function setGoStraight() {
  7. choice = ActionChoices.GoStraight;
  8. }
  9. // Since enum types are not part of the ABI, the signature of "getChoice"
  10. // will automatically be changed to "getChoice() returns (uint8)"
  11. // for all matters external to Solidity. The integer type used is just
  12. // large enough to hold all enum values, i.e. if you have more values,
  13. // `uint16` will be used and so on.
  14. function getChoice() returns (ActionChoices) {
  15. return choice;
  16. }
  17. function getDefaultChoice() returns (uint) {
  18. return uint(defaultChoice);
  19. }
  20. }

十六进制字面量

十六进制字面量,以关键字hex打头,后面紧跟用单或双引号包裹的字符串。如hex"001122ff"。在内部会被表示为二进制流。通过下面的例子来理解下是什么意思:

  1. pragma solidity ^0.4.0;
  2. contract HexLiteral{
  3. function test() returns (string){
  4. var a = hex"001122FF";
  5. //var b = hex"A";
  6. //Expected primary expression
  7. return a;
  8. }
  9. }

由于一个字节是8位,所以一个hex是由两个[0-9a-z]字符组成的。所以var b = hex"A";不是成双的字符串是会报错的。

转换

十六进制的字面量与字符串可以进行同样的类似操作:

  1. pragma solidity ^0.4.0;
  2. contract HexLiteralBytes{
  3. function test() returns (bytes4, bytes1, bytes1, bytes1, bytes1){
  4. bytes4 a = hex"001122FF";
  5. return (a, a[0], a[1], a[2], a[3]);
  6. }
  7. }

可以发现,它可以隐式的转为bytes,上述代码的执行结果如下:

  1. Result: "0x001122ff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000ff00000000000000000000000000000000000000000000000000000000000000"
  2. Transaction cost: 21857 gas.
  3. Execution cost: 585 gas.
  4. Decoded:
  5. bytes4: 0x001122ff
  6. bytes1: 0x00
  7. bytes1: 0x11
  8. bytes1: 0x22
  9. bytes1: 0xff

字符串(String literal)

字符串字面量

字符串字面量是指由单引号,或双引号引起来的字符串。字符串并不像C语言,包含结束符,foo这个字符串大小仅为三个字节。

定长字节数组

正如整数一样,字符串的长度类型可以是变长的。特殊之处在于,可以隐式的转换为byte1,…byte32。下面来看看这个特性:

  1. pragma solidity ^0.4.0;
  2. contract StringConvert{
  3. function test() returns (bytes3){
  4. bytes3 a = "123";
  5. //bytes3 b = "1234";
  6. //Error: Type literal_string "1234" is not implicitly convertible to expected type bytes3.
  7. return a;
  8. }
  9. }

上述的字符串字面量,会隐式转换为bytes3。但这样不是理解成bytes3的字面量方式一个意思。

转义字符

字符串字面量支持转义字符,比如\n\xNN\uNNNN。其中\xNN表式16进制值,最终录入合适的字节。而\uNNNN表示Unicode码点值,最终会转换为UTF8的序列。

小数

定点数

//文档上称,暂不支持

小数字面量

如果字面量计算的结果不是一个整数,那么将会转换为一个对应的ufixed,或fixed类型。Solidity会选择合适的大小,以能尽量包含小数部分。

例,在var x = 1 / 4中,x的实际类型是ufixed0x8。而在var x = 1/ 3中,类型会是ufixedox256,因为这个结果表示是无限的,所以他只能是无限接近。

支持的运算符

适用于整型的操作符,同时适用于数字的字面量运算表达式,当操作结果是整数时。如果有任何一方是有理数,将不允许使用位操作符。如果指数是小数,还将不能进行取幂运算。

数字字面量

Solidity对每一个有理数都有一个数值字面量类型。整数字面量和有理数字面量从属于数字面量。所有的数字字面表式的结果都属于数字字面类型。所以1 + 22 + 1都属于同样的有理数的数字字面类型3

二进制表示

大多数含小数的十进制,均不可被二进制准确表达,比如5.3743的类型可能是ufixed8*248。如果你想使用这样的值,需要明确指定精度x + ufixed(5.3743),否则会报类型转换错误。

字面量截断

整数上的字面量除法,在早期的版本中是被截断的,但现在可以被转为有理数了,如5 /2的值为 2.5

字面量转换

数字的字面量表达式,一旦其中含有非字面量表达式,它就会被转为一个非字面量类型。下面代码中表达式的结果将会被认为是一个有理数:

  1. pragma solidity ^0.4.0;
  2. contract IntegerLiteralConvert{
  3. function literalTest(){
  4. uint128 a = 1;
  5. //uint128 b = 2.5 + a + 0.5;
  6. //Error: Operator + not compatible with types rational_const 5/2 and uint128
  7. }
  8. }

虽然我们知道上述表达式运算的结果将是一个整型,但最终被编译器认为是小数型,所以上述代码编译不能通过。

字节数组(byte arrays)

定长字节数组(Fixed-size byte arrays)

bytes1, … ,bytes32,允许值以步长1递增。byte默认表示byte1

运算符

比较:<=<==!=>=>,返回值为bool类型。

位运算符:&|^(异或),~

支持序号的访问,与大多数语言一样,取值范围[0, n),其中n表示长度。

成员变量

.length表示这个字节数组的长度(只读)。

动态大小的字节数组

bytes: 动态长度的字节数组,参见数组(Arrays)。非值类型1。

string: 动态长度的UTF-8编码的字符类型,参见数组(Arrays)。非值类型1。

一个好的使用原则是:

  • bytes用来存储任意长度的字节数据,string用来存储任意长度的UTF-8编码的字符串数据。
  • 如果长度可以确定,尽量使用定长的如byte1byte32中的一个,因为这样更省空间。

  1. 值类型与引用类型与引用类型的区别参见这里 ↩

地址(Address)

地址1

地址: 以太坊地址的长度,大小20个字节,160位,所以可以用一个uint160编码。地址是所有合约的基础,所有的合约都会继承地址对象,也可以随时将一个地址串,得到对应的代码进行调用。当然地址代表一个普通帐户时,就没有这么多丰富的功能啦。

支持的运算符

  • <=<==!=>=>

地址类型的成员

属性:balance
函数:send()call()delegatecall()callcode()

地址字面量

十六进制的字符串,凡是能通过地址合法性检查(address checksum test)2,就会被认为是地址,如0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF。需要注意的是39到41位长的没有通过地址合法性检查的,会提示一个警告,但会被视为普通的有理数字面量。

balance

通过它能得到一个地址的余额。

  1. pragma solidity ^0.4.0;
  2. contract addressTest{
  3. function getBalance(address addr) returns (uint){
  4. return addr.balance;
  5. }
  6. }

我们可以把上述代码放入remix中,看看效果,参见下面的操作演示:

address\_demo

演示中的一个核心要点是,编译后,我们能得到当前合约的地址,并将该地址复制到输入框中,记得录入地址项时要加英文的双引号,否则会报Error encoding arguments: SyntaxError: JSON Parse error: Expected ']'

this

如果只是想得到当前合约的余额,其实可以这样写:

  1. pragma solidity ^0.4.0;
  2. contract addressTest{
  3. function getBalance() returns (uint){
  4. return this.balance;
  5. }
  6. }

原因是对于合约来说,地址代表的就是合约本身,合约对象默认继承自地址对象,所以内部有地址的属性。

地址的方法send()

用来向某个地址发送货币(货币单位是wei)。

  1. pragma solidity ^0.4.0;
  2. //请注意这个仅是Demo,请不要用到正式环境
  3. contract PayTest {
  4. //得到当前合约的余额
  5. function getBalance() returns (uint) {
  6. return this.balance;//0
  7. }
  8. //向当前合约存款
  9. function deposit() payable returns(address addr, uint amount, bool success){
  10. //msg.sender 全局变量,调用合约的发起方
  11. //msg.value 全局变量,调用合约的发起方转发的货币量,以wei为单位。
  12. //send() 执行的结果
  13. return (msg.sender, msg.value, this.send(msg.value));
  14. }
  15. }

这个合约实现的是充值。this.send(msg.value)意指向合约自身发送msg.value量的以太币。msg.value是合约调用方附带的以太币。

下面是操作演示:

address\_demo\_2

关于发送者的帐号,发送的以太币数量设置,需切换到Remix的小飞机图标的配置页。要在调用deposit前在Value输入项填入要发的以太币数量。在getBalance时要记得将value项内填的值去掉,因为getBalance方法,并不是payable的,不支持货币3。

send()方法执行时有一些风险

  1. 调用递归深度不能超1024。
  2. 如果gas不够,执行会失败。
  3. 所以使用这个方法要检查成功与否。或为保险起见,货币操作时要使用一些最佳实践。

如果执行失败,将会回撤所有交易,所以务必留意返回结果。

call()callcode()delegatecall()

为了同一些不支持ABI协议的进行直接交互(一般的web3.jssoldity都是支持的)。可以使用call()函数,用来向另一个合约发送原始数据。参数支持任何类型任意数量。每个参数会按规则(规则是按ABI4)打包成32字节并一一拼接到一起。

call()方法支持ABI协议4定义的函数选择器。如果第一个参数恰好4个字节,在这种情况下,会被认为根据ABI协议定义的函数器指定的函数签名4。所以如果你只是想发送消息体,需要避免第一个参数是4个字节。

call方法返回一个bool值,以表明执行成功还是失败。正常结束返回true,异常终止返回false。我们无法解析返回结果,因为这样我们得事前知道返回的数据的编码和数据大小(这里的潜在假设是不知道对方使用的协议格式,所以也不会知道返回的结果如何解析,有点祼协议测试的感觉)。

同样我们也可以使用delegatecall(),它与call方法的区别在于,仅仅是代码会执行,而其它方面,如(存储,余额等)都是用的当前的合约的数据。delegatecall()方法的目的是用来执行另一个合约中的工具库。所以开发者需要保证两个合约中的存储变量能兼容,来保证delegatecall()能顺利执行。

在homestead阶段之前,仅有一个受限的多样的callcode()方法可用,但并未提供对msg.sendermsg.value的访问权限。

上面的这三个方法call()delegatecall()callcode()都是底层的消息传递调用,最好仅在万不得已才进行使用,因为他们破坏了Solidity的类型安全。

关于call()函数究竟发的什么消息体,函数选择器究竟怎么用,参见这个文章的挖掘。

上述的函数都是底层的函数,使用时要异常小心。当调用一个未知的,可能是恶意的合约时,当你把控制权交给它,它可能回调回你的合约,所以要准备好在调用返回时,应对你的状态变量可能被恶意篡改的情况。


  1. 如果你想了解更多关于地址的由来,UTXO等,可以参考: http://me.tryblockchain.org/Solidity%E7%9A%84%E5%9C%B0%E5%9D%80%E7%B1%BB%E5%9E%8B.html ↩
  2. 为防止录入地址有误,一种格式化地址后来确认地址有效性的方案,https://github.com/ethereum/EIPs/issues/55 ↩
  3. 原因详见实现以太币支付的文章,http://me.tryblockchain.org/%E6%94%AF%E4%BB%98%E7%9B%B8%E5%85%B3.html ↩
  4. 关于ABI协议的详细说明:http://me.tryblockchain.org/Solidity-abi-abstraction.html ↩

整型(Integer)

int/uint:变长的有符号或无符号整型。变量支持的步长以8递增,支持从uint8uint256,以及int8int256。需要注意的是,uintint默认代表的是uint256int256

支持的运算符:

  • 比较:<=<==!=>=>,返回值为bool类型。
  • 位运算符:&|,(^异或),(~非)。
  • 数学运算:+-,一元运算+*/,(%求余),(**平方)。

整数除法总是截断的,但如果运算符是字面量,则不会截断(后面会进一步提到)。另外除0会抛异常 ,我们来看看下面的这个例子:

  1. pragma solidity ^0.4.0;
  2. // simple store example
  3. contract simpleStorage{
  4. uint valueStore; //
  5. function add(uint x, uint y) returns (uint z){
  6. z = x + y;
  7. }
  8. function divide() returns (uint z){
  9. uint x = 1;
  10. uint y = 2;
  11. z = x / y;
  12. }
  13. }

整数字面量

整数字面量,由包含0-9的数字序列组成,默认被解释成十进制。在Solidity中不支持八进制,前导0会被默认忽略,如0100,会被认为是100

小数由.组成,在他的左边或右边至少要包含一个数字。如1..11.3均是有效的小数。

字面量本身支持任意精度,也就是可以不会运算溢出,或除法截断。但当它被转换成对应的非字面量类型,如整数或小数。或者将他们与非字面量进行运算,则不能保证精度了。

  1. pragma solidity ^0.4.0;
  2. contract IntegerLiteral{
  3. function integerTest() returns (uint, uint){
  4. //超出运算字长了
  5. var i = (2**800 + 1) - 2**800;
  6. var j = 1/3*3;
  7. //小数运算
  8. var k = 0.5*8;
  9. return (i, j);
  10. }
  11. }

总之来说就是,字面量怎么都计算都行,但一旦转为对应的变量后,再计算就不保证精度啦。

布尔(Booleans)

bool: 可能的取值为常量值truefalse.

支持的运算符:

  • !逻辑非
  • && 逻辑与
  • || 逻辑或
  • == 等于
  • != 不等于

备注:运算符&&||是短路运算符,如f(x)||g(y),当f(x)为真时,则不会继续执行g(y)

值类型与引用类型

由于Solidity是一个静态类型的语言,所以编译时需明确指定变量的类型(包括本地变量状态变量),Solidity编程语言提供了一些基本类型(elementary types)可以用来组合成复杂类型。

类型可以与不同运算符组合,支持表达式运算,你可以通过表达式的执行顺序(Order of Evaluation of Expressions)来了解执行顺序。

值类型(Value Type)

值类型包含

  • 布尔(Booleans)
  • 整型(Integer)
  • 地址(Address)
  • 定长字节数组(fixed byte arrays)
  • 有理数和整型(Rational and Integer LiteralsString literals)
  • 枚举类型(Enums)
  • 函数(Function Types)

为什么会叫值类型,是因为上述这些类型在传值时,总是值传递1。比如在函数传参数时,或进行变量赋值时。

引用类型(Reference Types)

复杂类型,占用空间较大的。在拷贝时占用空间较大。所以考虑通过引用传递。常见的引用类型有:

  • 不定长字节数组(bytes)
  • 字符串(string)
  • 数组(Array)
  • 结构体(Struts)

  1. 关于参数传递的相关方式的进一步了解: http://baike.baidu.com/item/参数传递 ↩

智能合约源文件的基本要素概览(Structure of a Contract)

  • 合约类似面向对象语言中的类。
  • 支持继承

每个合约中可包含状态变量(State Variables)函数(Functions),函数修饰符(Function Modifiers),事件(Events),结构类型(Structs Types)枚举类型(Enum Types)

状态变量(State Variables)

变量值会永久存储在合约的存储空间

  1. pragma solidity ^0.4.0;
  2. // simple store example
  3. contract simpleStorage{
  4. uint valueStore; //state variable
  5. }

详情见类型(Types)章节,关于所有支持的类型和变量相关的可见性(Visibility and Accessors)。

函数(Functions)

智能合约中的一个可执行单元。

示例

  1. pragma solidity ^0.4.0;
  2. contract simpleMath{
  3. //Simple add function,try a divide action?
  4. function add(uint x, uint y) returns (uint z){
  5. z = x + y;
  6. }
  7. }

上述示例展示了一个简单的加法函数。

函数调用可以设置为内部(Internal)的和外部(External)的。同时对于其它合同的不同级别的可见性和访问控制(Visibility and Accessors)。具体的情况详见后面类型中关于函数的章节。

函数修饰符(Function Modifiers)

函数修饰符用于增强语义。详情见函数修饰符相关章节。

事件(Events)

事件是以太坊虚拟机(EVM)日志基础设施提供的一个便利接口。用于获取当前发生的事件。

示例

  1. pragma solidity ^0.4.0;
  2. contract SimpleAuction {
  3. event aNewHigherBid(address bidder, uint amount);
  4. function bid(uint bidValue) external {
  5. aNewHigherBid(msg.sender, msg.value);
  6. }
  7. }

关于事件如何声明和使用,详见后面事件相关章节。

结构体类型(Structs Types)

自定义的将几个变量组合在一起形成的类型。详见关于结构体相关章节。

示例

  1. pragma solidity ^0.4.0;
  2. contract Company {
  3. //user defined `Employee` struct type
  4. //group with serveral variables
  5. struct employee{
  6. string name;
  7. uint age;
  8. uint salary;
  9. }
  10. //User defined `manager` struct type
  11. //group with serveral variables
  12. struct manager{
  13. employee employ;
  14. string title;
  15. }
  16. }

枚举类型

特殊的自定义类型,类型的所有值可枚举的情况。详情见后续相关章节。

示例

  1. pragma solidity ^0.4.0;
  2. contract Home {
  3. enum Switch{
  4. On,Off}
  5. }

Solidity智能合约文件结构

版本申明

  1. pragma solidity ^0.4.0

说明:
1 版本要高于0.4才可以编译
2 号表示高于0.5的版本则不可编译,第三位的版本号但可以变,留出来用做bug可以修复(如0.4.1的编译器有bug,可在0.4.2修复,现有合约不用改代码)。

引用其它源文件

  • 全局引入 *

    import “filename”;

  • 自定义命名空间引入 *

    import * as symbolName from “filename”

分别定义引入

  1. import {symbol1 as alias, symbol2} from filename

非es6兼容的简写语法

  1. import filename as symbolName

等同于上述

  1. import * as symbolName from filename

关于路径

引入文件路径时要注意,非.打头的路径会被认为是绝对路径,所以要引用同目录下的文件使用

  1. import “./x as x

也不要使用下述方式,这样会是在一个全局的目录下

  1. import x as x;

为什么会有这个区别,是因为这取决于编译器,如果解析路径,通常来说目录层级结构并不与我们本地的文件一一对应,它非常有可能是通过ipfs,http,或git建立的一个网络上的虚拟目录。

编译器解析引用文件机制

各编译器提供了文件前缀映射机制。

  1. 可以将一个域名下的文件映射到本地,从而从本地的某个文件中读取
  2. 提供对同一实现的不同版本的支持(可能某版本的实现前后不兼容,需要区分)
  3. 如果前缀相同,取最长,
  4. 有一个”fallback-remapping”机制,空串会映射到“/usr/local/include/solidify”

solc编译器

命令行编译器,通过下述命令命名空间映射提供支持

  1. context:prefix=target

上述的context:=target是可选的。所有context目录下的以prefix开头的会被替换为target
举例来说,如果你将github.com/ethereum/dapp-bin拷到本地的/usr/local/dapp-bin,并使用下述方式使用文件

  1. import github.com/ethereum/dapp-bin/library/iterable_mapping.sol as it_mapping;

要编译这个文件,使用下述命令:

  1. solc github.com/ethereum/dapp-bin=/usr/local/dapp-bin source.sol

另一个更复杂的例子,如果你使用一个更旧版本的dapp-bin,旧版本在/url/local/dapp-bin_old,那么,你可以使用下述命令编译

  1. solc module1:github.com/ethereum/dapp-bin=/usr/local/dapp-bin \ modeule2:github.com/ethereum/dapp-bin=/usr/local/dapp-bin_old \
  2. source.sol

需要注意的是solc仅仅允许包含实际存在的文件。它必须存在于你重映射后目录里,或其子目录里。如果你想包含直接的绝对路径包含,那么可以将命名空间重映射为=\
备注:如果有多个重映射指向了同一个文件,那么取最长的那个文件。

browser-solidity编译器:

browser-solidity编译器默认会自动映射到github上,然后会自动从网络上检索文件。例如:你可以通过下述方式引入一个迭代包:

  1. import github.com/ethereum/dapp-bin/library/iterable_mapping.sol as it_mapping

备注:未来可能会支持其它的源码方式

代码注释

两种方式,单行(//),多行使用(/*…*/)

示例

  1. // this is a single-line comment
  2. /* this is a mulit-line comment */

文档注释

写文档用。三个斜杠////** … */,可使用Doxygen语法,以支持生成对文档的说明,参数验证的注解,或者是在用户调用这个函数时,弹出来的确认内容。

示例

  1. pragma solidity ^0.4.0
  2. /** @title Shape calculator.*/
  3. contract shapeCalculator{
  4. /** *@dev calculate a rectangle's suface and perimeter *@param w width of the rectangles *@param h height of the rectangles *@return s surface of the rectangles *@return p perimeter of the rectangles */
  5. function rectangles(uint w, uint h) returns (uint s, uint p){
  6. s = w * h;
  7. p = 2 * ( w + h) ;
  8. }
  9. }

感谢您的支持

发表评论

表情:
评论列表 (有 0 条评论,699人围观)

还没有评论,来说两句吧...

相关阅读