Solidity智能合约开发(入门篇) |
您所在的位置:网站首页 › 以太坊智能合约开发实战pdf › Solidity智能合约开发(入门篇) |
一、版本声明
合约文件开头需要声明编译器的版本号,目的是为了该合约在未来版本的升级中引入了不兼容的编译器,其语法为: pragma solidity 版本号版本号应该遵循“0.x.0”或者“x.0.0”的形式,比如: // 代表不允许低于0.4.17版本的编译器,也不允许使用高于0.5.0版本的编译器 pragma solidity ^0.4.17 // 代表支持0.4.17以上的版本,但是不能超过0.9.0版本 pragma solidity >=0.4.17 =0.4.17 return a * b; } } 三、数据类型 3.1 值类型 3.1.1 布尔类型布尔类型使用bool关键字。它的值是true或false。布尔类型支持的运算符有: 名称符号逻辑与&&逻辑或||逻辑非!等于==不等于!= 3.1.2 整型整型使用int或uint表示。uint代表的是无符号整型。它们都支持uint8、uint16,…, uint256(以8位为步长递增)。如果没有指定多少位,默认为int256或uint256。 整型支持的运算符: 名称符号比较运算符= >位运算符& | ^ ~算术运算符+ - * / ++ -- % ** >注意:如果除数为0或者对0取模都会引发运行时异常。 3.1.3 定长浮点型定长浮点型使用fixed或ufixed表示。但是目前solidity支持声明定长浮点型的变量,不能够对该类型的变量进行赋值。 3.1.4 地址类型地址类型的关键字使用address表示,一般用于存储合约或账号,其长度一般为20个字节。地址类型的变量可以使用> >= < 位运算符& | ^ ~ >索引访问x[i]代表访问第i个字节的数据 定长字节数组包含length属性,用于查询字节数组的长度。 虽然可以使用byte[]表示字节数组,但是一般推荐使用bytes代替。 3.1.6 字面常量 地址字面常量像0x692a70d2e424a56d2c6c27aa97d1a86395877b3a这样通过了地址校验和测试的十六进制字面常量属于address类型。 数值字面常量solidity支持整数和任意精度小数的字面常量。1.或.1都是有效的小数字面常量。也可以使用科学计数法表示,比如:2e10。 如果数值字面常量赋值给了数值类型的变量,就会转换成非字面常量类型。 uint128 a = 2.5 + 1 + 0.5; // 编译成功 uint128 b = 1; uint128 c = 2.5 + b + 0.5; // 编译报错上面代码的第二行,将数值字面常量1赋值给了变量b。因为变量b是非字面常量,不同类型的字面常量和非字面常量无法进行运算,所以第三行报错。 字符串字面常量使用单引号或双引号引起来的一串字符。字符串字面常量可以隐式地转换成bytes类型。比如: bytes3 b = "abc"; bytes4 b = "abcd"; bytes4 b = "abcde"; // 报错,因为bytes4只能存储4个字节数据 3.1.7 函数类型solidity支持将一个函数赋值给一个变量,也可以作为参数传递给其他函数,或者作为函数的返回值。 声明语法:function (参数类型) [internal|external] [pure|constant|view|payable] [returns (返回值类型)] pragma solidity ^0.4.16; contract A { // 声明函数变量,并将mul函数赋给变量func function (uint, uint) pure returns (uint) func = mul; // 函数声明 function mul(uint a, uint b) public pure returns(uint) { return a * b; } }注意事项: 如果函数类型不需要返回,那么需要删除整个returns部分;如果没有指定internal|external,默认是internal内部函数。内部函数可以在当前合约或子合约中使用; pragma solidity ^0.4.16; contract A { function (uint, uint) pure returns (uint) func; } contract B is A { function mul(uint a, uint b) public pure returns(uint) { return a * b; } function test() public { // 对父合约的函数变量进行赋值 func = mul; // 通过父合约函数变量调用mul函数 func(10, 20); } }如果是external外部函数,可以通过函数调用传递,也可以通过函数返回。 pragma solidity ^0.4.16; contract A { struct Request { bytes data; // 外部函数类型 function (bytes memory) external callback; } Request[] requests; // 该函数的第二个参数类型为外部函数类型 function query(bytes memory data, function (bytes memory) external callback) public { requests.push(Request(data, callback)); } } contract Test { function getResponse(bytes result) public { // TODO ... } function test() public { // 创建合约A的实例 A a = new A(); // 调用合约A实例的query函数,第二个参数是函数类型 a.query("abc", this.getResponse); } } 3.2 其他类型 3.2.1 数组如果声明数组的时候指定数组长度,那么数组的大小就固定下来;如果声明数组时候没有指定长度,那么该数组的长度可以发生变化。 声明数组的方式: // 定义固定长度的数组 uint[3] a; // 定义长度可变的数组 uint[] a; uint[] a = new int[](3); // 通过字面量定义数组,数组长度也是可变的 uint[] c = [uint(1), 2, 3]; 注意事项: 1)如果是状态变量的数组类型,不能手动指定memory或storage,默认为storage数组; 2)如果是局部变量的数组类型,可以指定为memory或storage,但是如果指定为storage数组,那么就必须对数组变量进行初始化; contract SimpleContract { int[] aa; int[3] bb; int[] cc = new int[](3); int[] dd = [uint(1), 2, 3]; public test() public { int[] memory a1; int[] storage b1 = aa; } }➡ memory和storage关键字的作用是什么? memory和storage代表数据的存储位置,即数据是保存在内存中还是存储中(这里的存储可以简单理解为电脑的磁盘)。如果是状态变量和局部变量,默认为storage位置;如果是形参,默认位置为memory。另外如果是外部函数参数,其数据位置为calldata,效果跟memory差不多。数据位置的指定非常重要,它会影响着赋值行为。比如说,如果是状态变量赋值给storage的局部变量,实际上传递的是该变量的一个引用。 还有一个地方值得注意的是,定长数组只能赋给定长数组,并且两个数组的大小必须相同;定长数组不能赋值给变长数组,如果下面代码编译报错: 添加数组元素可以通过下标方式添加,比如说: function test() public pure { int[] memory arr; arr[0] = 100; }如果是变长的storage数组以及bytes类型,也可以通过push方法添加数组元素,该方法会返回数组的最新长度。 contract SimpleContract { int[] aa; // Define an array of variable length function test() public { int[] memory bb; bb[0] = 100; // 因为aa是一个变长的storage数组,因此可以通过push方法添加元素 aa.push(100); // 下面代码报错,因为bb的位置不是storage bb.push(100); } }另外一个值得注意的地方是,由于EVM限制,solidity不支持通过外部函数调用返回动态内容。比如下面代码: function test() public pure returns(int[] memory) { int[] memory arr; arr[0] = 100; return arr; }EVM编译合约时候不会报错,但是当我们调用该合约示例的test函数时候,提示下面错误信息call to SimpleContract.test errored: VM error: invalid opcode.。 小知识:可以把bytes看作是byte的数组形式,也可以将bytes当作string类型使用。但是,string无法通过length或索引来访问字符串中的每个字符,如果需要访问字符串中的某个字符,可以先把字符串转换成bytes形式,比如:uint size = bytes(“123”).length; byte c = bytes(“123”)[0]。 3.2.3 结构体可以使用结构体用来存储复杂的数据结构。其定义格式: struct 结构体名称 { 变量类型 变量名; ... }结构体中变量类型可以是任意类型,但是它不能够包含自身。 与结构体类似的是,枚举也是solidity中的一种自定义类型,其定义格式为: enum 类型名称 { 枚举值1, 枚举值2, ... }枚举类型中至少要包含一个枚举值。枚举值可以是任意有效的标识符(比如说:必须是字母或下划线开头)。 enum Week { Monday, Tuesday, ... }枚举值的类型默认为uint8。第一个枚举值会被解析成0,第二个枚举值会被解析成1,以此类推。枚举类型无法与其他类型进行隐式转换,只能进行显示转换。 Weekend weekend = Weekend(0);上面代码将数值0转换成枚举类型,对应Weekend.Monday枚举值。如果数值超过了最大枚举值,则运行合约时显式转换报错。 3.2.5 映射与Java的Map集合类型,映射类型用于存储的是一对有关系的数据,其定义格式为: mapping(_keyType => _valueType) 变量名;_keyType不可以是映射、变长数组、枚举、结构体类型;_valueType可以是任意类型。 在映射中,实际上存储的是key的keccak256哈希值。映射没有长度,也没有key和value的集合概念。 如果局部变量使用mapping类型,那么该变量的数据位置必须是storage,并且必须要对该变量进行初始化。 contract SimpleContract { mapping(string => string) m1; function test() public { mapping(string => string) storage m2 = m1; } } 3.2.6 元组类型元组类似于定长数组,它是一个元素数量固定的对象列表。但是与数组不同的是,元组中元素类型可以不一样。一般来说,可以在函数中通过元组返回多个值,使用元素类型的变量来接收函数的返回值。 function f() public pure returns(uint, bool) { return (10, false); } function test() public pure { (uint a, bool b) = f(); }元组之间可以进行赋值。 function test() public pure { uint x = 10; uint y = 20; (x, y) = (y, x); }上面代码通过元组之间赋值交互x和y的值。 如果元组只有一个元素,那么元素后的逗号不能省略。 function f() public pure returns(uint, bool) { return (10, false); } function test() public pure { (uint a, ) = f(); } 四、运算符 4.1 基本运算符 算数运算符:+ - * / % ++ --逻辑运算符:&& || !比较运算符:> < >= >>(右移后左边补0)赋值运算符:= += -= *= /= %=三目运算符:condition expression ? value1 : value2 4.2 左值变量左值变量lvalue,即一个可以赋值给它的变量。如果表达式中包含左值变量,其运算符都可以简写。比如: function test() public { int a = 10; int b = 5; a += b;a -= b;a *= b;a /= b; a++;a--; }与左值紧密相关的一个关键字delete。比如说delete a,如果a是一个整型,那么delete a代表将a的值恢复成初始状态;如果a是动态数组,那么delete a相当于将数组的长度length设置为0;如果a是静态数组,那么delete a会将数组中每个元素进行重置;如果a是结构体类型,delete a会将结构体中每个属性进行重置。 注意:delete对映射无效。 4.3 类型转换如果两个相同类型但精度不同的变量进行运算,那么低精度会自动转换成高精度的类型。 function test() public { int8 a = 10; int b = 20; int c = a + b; }上面代码中变量a的类型是int8,变量b的类型是int256,因此它们两个变量相加,低精度会自动转换成高精度,因此得到的结果是int256。同样地,低精度变量也可以赋给高精度变量,所以下面代码也是没问题的。 function test() public { int8 a = 10; int b = a; }但是如果需要将高精度转换成低精度的类型,那么就需要强制类型转换,如下所示: function test() public { int a = 10; int8 b = int8(a); }需要注意的是,显式类型转换可能会导致一些无法预料的结果,比如说: function test() public { int a1 = -10; uint a2 = uint(a); // 115792089237316195423570985008687907853269984665640564039457584007913129639926 uint32 b1 = 0x12345678; uint16 b2 = uint16(b1); // 22136,即0x5678的十进制表示形式 }运行上面代码,最终结果与我们的预期不一样。因此如果使用显式类型转换时候,必须要清楚转换的过程。 与javascript类似,定义变量时候也可以使用var关键字,比如说: int a = 10; var b = a;上面代码中,因为变量a的类型为int256,因此变量b的类型也是int256。而且一旦变量b的类型确定后,它的类型就不会再发生改变。因此下面程序编译失败: bool c = false; b = c;另外,solidity不支持对函数形参或反参使用var关键字。 solidity支持javascript的大部分语法,比如if…else、while…do、do…while、for等等。 pragma solidity ^0.4.16; contract SimpleContract { function test1(int week) public pure returns(string) { if (week == 1) { return "Monday"; } else if (week == 2) { return "Tuesday"; } else if (week == 3) { return "Wednesday"; } else if (week == 4) { return "Thursday"; } else if (week == 5) { return "Friday"; } else if (week == 6) { return "Saturday"; } else if (week == 7) { return "Sunday"; } else { return "invalid week"; } } function test2() public pure returns(int) { int sum = 0; for (int i = 0; i int i = 0; int sum = 0; while (i int i = 0; int sum = 0; do { if (i % 2 == 0) continue; sum += i; i++; } while(i |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |