Solidity基础
Solidity基础数据类型
Solidity中,变量类型有以下几大类:
- 值类型
- 地址类型
- 引用类型
1. 值类型
字节型bytes8 to bytes32 8位到32位的字节型数据。
类型 | 保留字 | 取值 |
---|---|---|
布尔型 | bool | true/false |
整型 | int/uint | 有符号整数/无符号整数。 |
整型 | int8 to int256 | 8位到256位的带符号整型数。int256与int相同。 |
整型 | uint8 to uint256 | 8位到256位的无符号整型。uint256和uint是一样的。 |
定长浮点型 | fixed/unfixed | 有符号和无符号的定长浮点型 |
定长浮点型 | fixedMxN | 带符号的定长浮点型,其中M表示按类型取的位数,N表示小数点。M应该能被8整除,从8到256。N可以是0到80。fixed与fixed128x18相同。 |
定长浮点型 | ufixedMxN | 无符号的定长浮点型,其中M表示按类型取的位数,N表示小数点。M应该能被8整除,从8到256。N可以是0到80。fixed与fixed128x18相同。 |
值类型数据:
1 | uint x = 100; |
我们可以测量类型的最大值和最小值。
1 | // SPDX-License-Identifier: MIT |
2. 地址类型
地址类型表示以太坊地址,长度为20字节。地址可以使用 .balance 属性获得余额,也可以使用 .transfer() 方法将余额转到另一个地址。
1 | address x = 0x212; |
3. 引用类型/复合数据类型
Solidity中,有一些数据类型由值类型组合而成,相比于简单的值类型,这些类型通常通过名称引用,被称为引用类型。
引用类型包括:
- 数组 (字符串与bytes是特殊的数组,所以也是引用类型)
- struct (结构体)
- map (映射)
Solidity复杂数据类型
字符串string
Solidity 中,字符串值使用双引号(“)和单引号(‘)包括,字符串类型用 string 表示。
字符串是特殊的数组,是引用类型。
1 | // SPDX-License-Identifier: MIT |
Solidity 提供字节与字符串之间的内置转换,可以将字符串赋给 bytes 类型变量。
bytes到字符串的转换
可以使用 string() 构造函数将 bytes 转换为字符串。
示例:
1 | // SPDX-License-Identifier: MIT |
运行上述程序,调用trans后,查看data:
1 | 0: string: ab |
字符串到bytes的转换
可以使用bytes()构造函数将字符串转换为bytes。
1 | // SPDX-License-Identifier: MIT |
数组
重点:
- 在solidity中,数组大小可以是固定大小的,也可以是动态长度的。
- 对于 storage 数组,元素可以是任意类型(其他数组、映射或结构)。
- 对于 memory 数组,元素类型不能是映射类型,如果它是一个 public 函数的参数,那么元素类型必须是 ABI 类型。
- 类型为 bytes 和 string 的变量是特殊数组。bytes 类似于 byte[],但它在 calldata 中被紧密地打包。因此,相比于 byte[],bytes 应该优先使用,因为更便宜。
- 字符串 string 等价于 bytes,但不允许长度或索引访问。
1.声明数组
固定长度组
声明一个固定长度的数组,需要指定元素类型和数量
uint balance[10];
动态数组
声明一个动态数组,只需要指定元素类型,无需指定数量
uint balance[];
2. 初始化数组
初始化数组,可以使用下面的语句:
1 | uint balance[3] = [1, 2, 3]; // 初始化固定长度数组 |
3.动态数组和固定长度数据的区别
固定长度的数组在运行期间,是无法改变长度的。而动态数组可以改变。
动态数组可以使用 push 方法,在末尾追加一个元素;使用 pop 方法,截掉末尾元素。
1 | // SPDX-License-Identifier: MIT |
4.创建内存数组
我们可以使用 new 关键字在内存中创建动态数组。内存动态数组的长度一旦确定,不能改变。
1 | // SPDX-License-Identifier: MIT |
结构体
类似于C语言,solidity 也有结构体 struc 类型,用于表示复合型数据。
1.定义结构体
要定义结构,使用struct关键字。struct关键字定义了一个新的数据类型,包含多个成员。struct语句的格式如下
1 | struct Book { |
2. 访问结构体成员
要访问结构的任何成员,使用成员访问操作符(.)。
示例
1 | // SPDX-License-Identifier: MIT |
3.结构体操作方法
结构体变量共有三种初始化方式。我们还可以定义结构体数组、结构体状态变量,并对其进行操作。
示例
1 | // SPDX-License-Identifier: MIT |
mapping
Solidity 映射 mapping 用于以键值对的形式存储数据,等同于其它编程语言的哈希表或字典。
映射 mapping 是智能合约中很常用的一种数据类型,它是引用类型。
下面是声明映射类型的语法。
1 | mapping(_KeyType => _ValueType) |
- _KeyType:可以是任何内置类型,或者 bytes 和 字符串。不允许使用引用类型或复杂对象。
- _ValueType: 可以是任何类型。
Solidity变量
Solidity 支持三种类型的变量:
- 状态变量 – 变量值永久保存在智能合约存储空间中的变量。
- 局部变量 – 变量值仅在函数执行过程中有效的变量,函数退出后,变量无效。
- 全局变量 – 保存在全局命名空间,用于获取整个区块链相关信息的特殊变量,与当前合约无关,比如:时间戳、块高等。
Solidity 是一种静态类型语言,这意味着需要在声明期间指定变量类型。
每个变量声明时,都有一个基于其类型的默认值。没有 undefined 或 null 的概念。比如 int 类型的默认值为 0。
1. 状态变量
状态变量是变量值永久保存在合约存储空间中的变量。状态变量的定义形式类似于类中的成员变量。
1 | // SPDX-License-Identifier: MIT |
2. 局部变量
局部变量是变量值仅在函数执行过程中有效的变量,函数退出后,变量无效。函数参数也是局部变量。
局部变量不会上链,只存在于所处函数的生命周期。
1 | // SPDX-License-Identifier: MIT |
运行上述程序,输出:
1 | 0: uint256: 3 |
3. 全局变量
全局变量是指全局工作区中存在的特殊变量,提供有关区块链和交易属性的信息。
1 | // SPDX-License-Identifier: MIT |
名称 | 返回 |
---|---|
blockhash(uint blockNumber) returns (bytes32) | 给定区块的哈希值 – 只适用于256最近区块, 不包含当前区块。 |
block.coinbase (address payable) | 当前区块矿工的地址 |
block.difficulty (uint) | 当前区块的难度 |
block.gaslimit (uint) | 当前区块的gaslimit |
block.number (uint) | 当前区块的number |
block.timestamp (uint) | 当前区块的时间戳,为unix纪元以来的秒 |
gasleft() returns (uint256) | 剩余 gas |
msg.data (bytes calldata) | 完成 calldata |
msg.sender (address payable) | 消息发送者 (当前 caller) |
msg.sig (bytes4) | calldata的前四个字节 (function identifier) |
msg.value (uint) | 当前消息的wei值 |
now (uint) | 当前块的时间戳 |
tx.gasprice (uint) | 交易的gas价格 |
tx.origin (address payable) | 交易的发送方 |
使用示例
1 | pragma solidity ^0.5.0 |
4. Solidity 变量命名规则
在为变量命名时,请记住以下规则:
- 不应使用 Solidity 保留关键字作为变量名。例如:break 或 boolean 变量名无效。
- 不应以数字(0-9)开头,必须以字母或下划线开头。例如:123test 是一个无效的变量名,但是 _123test是一个有效的变量名。
- 变量名区分大小写。例如:Name和name是两个不同的变量。
5.变量默认值
Solidity 智能合约中所有的状态变量和局部变量,都有默认值。
这些变量在没有被赋值之前,它的值已默认值的形式存在。
其中:
bool 类型变量默认值为 false;
int 类型变量默认值为 0;
uint 类型变量默认值为 0;
address 类型变量默认值为:0x0000000000000000000000000000000000000000,共 40个 0;
bytes32 类型变量默认值为:0x0000000000000000000000000000000000000000000000000000000000000000,共 64个 0。
6.变量的4个作用域
局部变量的作用域仅限于定义它们的函数,但是状态变量可以有四种作用域类型:
- public – 公共状态变量可以在内部访问,也可以从外部访问。对于公共状态变量,将自动生成一个 getter 函数。
- private – 私有状态变量只能从当前合约内部访问,派生合约内不能访问。
- internal – 内部状态变量只能从当前合约或其派生合约内访问。
- external - 外部状态变量只能在合约之外调用 ,不能被合约内的其他函数调用
1 | // SPDX-License-Identifier: MIT |
7.常量(constant)
智能合约中,状态变量的值如果恒定不变,就可以通过 constant
进行修饰,定义为常量。
常量的命名常常使用大写字母表示,单词之间用下划线“_”连接。
常量有如下规定:
- 不是所有的类型都支持常量,当前仅支持
值类型
和字符串
。 constant常量
必须在编译期间通过一个表达式赋值- 编译器并不会为
constant常量
在storage
上预留空间
1 | // SPDX-License-Identifier: MIT |
常量constant的特点
- 常量与变量相对,需要硬编码在合约中,合约部署之后,无法改变。
- 常量更加节约gas,一般用大写来代表常量。
Solidity函数
函数基础构成
函数的定义
Solidity中, 定义函数的语法如下:
1
2
3
4
5function function_name(<parameter list>) <visibility> <state mutability> [returns(<return type>)] {
//语句
}
//函数由关键字function声明,后面跟函数名、参数、状态可变性、可见性、返回值的定义。可见性(visibility)
- Private(私有):函数只能在所定义的智能合约内部调用。
- Internal(内部):可以在所定义智能合约内部调用该函数,也可以从继承合约中调用该函数。
- External(外部):只能从智能合约外部调用。 如果要从智能合约中调用它,则必须使用 this。
- Public(公开):可以从任何地方调用。
状态可变性 mutability
- view:用view声明的函数只能读取状态,而不能修改状态。
- pure:用pure声明的函数既不能读取也不能修改状态。
- payable:用payable声明的函数可以接受发送给合约的以太币,如果未指定,该函数将自动拒绝所有发送给它的以太币
return 语句
Solidity中, 函数可以返回多个值。
1 | // SPDX-License-Identifier: MIT |
函数返回值
Solidity 函数的返回值可以使用名字,也可以采用匿名方式。
Solidity 函数的返回值可以通过名字赋值,也可以使用 return 返回。
Solidity 函数支持多个返回值。
特殊函数
Solidity pure 函数
solidity pure函数,也就是纯函数,是指函数不会读取或修改状态。
换言之,solidity pure函数不会操作链上数据。
如果函数中存在以下语句,则被视为读取状态,编译器将抛出警告。
- 读取状态变量。
- 访问
address(this).balance
或<address>.balance
- 访问任何区块、交易、msg等特殊变量(msg.sig 与 msg.data 允许读取)。
- 调用任何不是纯函数的函数。
- 使用包含特定操作码的内联程序集。
如果发生错误,pure函数可以使用revert()
和require()
函数来还原潜在的状态更改。
声明为pure函数,可以在函数声明里,添加pure
关键字。
示例1
1 | // SPDX-License-Identifier: MIT |
运行上述程序,输入参数 2 和 1,输出:
1 | 0: uint256: product 3 |
Solidity view 函数
solidity view函数,也就是视图函数,是指函数只会读取状态,不会修改状态。
换言之,solidity view函数只会读取链上数据,不会修改链上数据。
如果函数中存在以下语句,则被视为修改状态,编译器将抛出警告。
- 修改状态变量。
- 触发事件。
- 创建合约。
- 使用
selfdestruct
。 - 发送以太。
- 调用任何不是视图函数或纯函数的函数
- 使用底层调用
- 使用包含某些操作码的内联程序集。
状态变量的Getter方法默认是view函数。
声明为view图函数,可以在函数声明里,添加view
关键字。
示例
1 | // SPDX-License-Identifier: MIT |
运行上述程序,输入100,输出:
1 | 0: uint256: 200 |
在这个例子中,函数getResult中,只是读取了状态变量factor的值,而没有修改它的值,所以这个函数是一个view函数。
Solidity构造函数
Solidity构造函数是一个特殊函数,它仅能在智能合约部署的时候调用一次,之后就不能再次被调用。
Solidity构造函数常用来进行状态变量的初始化工作。
Solidity编译器中,使用关键词 constructor 作为构造函数。
示例1
1 | // SPDX-License-Identifier: MIT |
示例2
1 | // SPDX-License-Identifier: MIT |
Solidity modifier函数修改器
Solidity 中关键字 modifier 用于声明一个函数修改器。
我们可以将一些通用的操作提取出来,包装为函数修改器,来提高代码的复用性,改善编码效率。
函数修改器 modifier 的作用与 Java Spring 中的切面功能很相似,当它作用于一个函数上,可以在函数执行前或后预先执行 modifier 中的逻辑,以增强其功能。
函数修改器 modifier 常用于在函数执行前检查某种前置条件。
函数修改器 modifier 是一种合约属性,可被继承,同时还可被派生的合约重写(override)。
- 基本函数修改器
1 | // SPDX-License-Identifier: MIT |
add函数被修改器isNotPaused修饰,所以先执行 require(!paused),检查前置条件,然后再执行add函数的代码。
- _的作用
函数修改器中有一行代码只有下划线 _ ,我们认为下划线 _ 代表了被修饰函数的代码。
也就是说,下划线实际上帮我们标记了被 modifier 修饰函数的执行位置。
- 带参数的函数modifier
1 | // SPDX-License-Identifier: MIT |
solidity函数重载
Solidity的函数重载,是指同一个作用域内,相同函数名可以定义多个函数。
这些函数的参数(参数类型或参数数量)必须不一样,返回值可以一样。
1 | // SPDX-License-Identifier: MIT |
Solidity数学函数
Solidity 也提供了内置的数学函数。下面是常用的数学函数:
addmod(uint x, uint y, uint k) returns (uint)
计算(x + y) % k,计算中,以任意精度执行加法,且不限于2^256大小。mulmod(uint x, uint y, uint k) returns (uint)
计算(x * y) % k,计算中,以任意精度执行乘法,且不限于2^256大小。
Solidity加密函数
Solidity 提供了常用的加密函数,包括:
keccak256(bytes memory) returns (bytes32)
计算输入的Keccak-256散列。sha256(bytes memory) returns (bytes32)
计算输入的SHA-256散列。ripemd160(bytes memory) returns (bytes20)
计算输入的RIPEMD-160散列。ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)
从椭圆曲线签名中恢复与公钥相关的地址,或在出错时返回零。函数参数对应于签名的ECDSA值: r – 签名的前32字节; s: 签名的第二个32字节; v: 签名的最后一个字节。这个方法返回一个地址。
下面的例子说明了加密函数的用法。
示例
1 | // SPDX-License-Identifier: MIT |
运行上述程序,输出:
1 | 0: bytes32: result 0xe1629b9dda060bb30c7908346f6af189c16773fa148d3366701fbaa35d54f3c8 |