Solidity智能合约开发(入门篇)

您所在的位置:网站首页 以太坊智能合约开发实战pdf Solidity智能合约开发(入门篇)

Solidity智能合约开发(入门篇)

2023-10-14 21:55| 来源: 网络整理| 查看: 265

一、版本声明

合约文件开头需要声明编译器的版本号,目的是为了该合约在未来版本的升级中引入了不兼容的编译器,其语法为:

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的局部变量,实际上传递的是该变量的一个引用。

还有一个地方值得注意的是,定长数组只能赋给定长数组,并且两个数组的大小必须相同;定长数组不能赋值给变长数组,如果下面代码编译报错:在这里插入图片描述 对于storage动态数组,可以通过它的length属性改变数组的大小,所以下面第15行代码编译通过。但是对于memory动态数组,则无法通过length属性改变数组的大小,所以下面第14行代码报错: 在这里插入图片描述 上面第14行代码会提示TypeError: Expression has to be an lvalue.。lvalue可以理解为等号左边的值。上面错误提示信息提示我们,等号左边的值a1.length必须是一个lvalue,即可以被修改的值。明显a1.length不是一个lvalue。

添加数组元素可以通过下标方式添加,比如说:

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.。 在这里插入图片描述 但是,如果是通过web3调用test函数,它会返回一些动态内容。

小知识:可以把bytes看作是byte的数组形式,也可以将bytes当作string类型使用。但是,string无法通过length或索引来访问字符串中的每个字符,如果需要访问字符串中的某个字符,可以先把字符串转换成bytes形式,比如:uint size = bytes(“123”).length; byte c = bytes(“123”)[0]。

3.2.3 结构体

可以使用结构体用来存储复杂的数据结构。其定义格式:

struct 结构体名称 { 变量类型 变量名; ... }

结构体中变量类型可以是任意类型,但是它不能够包含自身。 在这里插入图片描述 如果函数中将结构体类型数据赋给一个局部变量,这个过程并没有创建这个结构体对象的副本,而且将该结构体对象的引用赋给了局部变量(即引用传递)。

3.2.4 枚举

与结构体类似的是,枚举也是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