6.Solidity字面常量
字面常量就是它字面上一个具体的数据值,表现为一条数字或一条文本等,如:69、4.13或HelloWorld。字面常量常用于给变量赋值或参与运算,如给变量赋值:int num = 69、string str = "HelloWorld";如参与运算:10+5、10/5。
字面常量名称 |
描述 |
特殊说明 |
布尔字面常量 |
布尔字面常量是指对与错。在Solidity语言中分别用true与false表示对与错。 |
|
数值字面常量 |
数值字面常量包括十进制整数字面常量和十进制小数字面常量。 十进制整数字面常量是指数学中的正整数、0、负整数。 十进制小数字面常量是指数学中的正小数、负小数。 |
为了提高可读性,在数字之间支持加上下划线,例如:54_34等同于5434、23_33.23_33等同于2333.2333。 |
字符串字面常量 |
Solidity语言采用的是ASCII字符集和UTF-8字符集,若字符串只包含ASCII字符集的字符,则字符串需要用双引号或单引号引起来,如:"hello World";若字符串含有UTF-8字符集的字符(一般是指汉字或汉语标点符号),则字符串必须以关键字unicode开头,后面紧跟着用单引号或双引号引起来的字符串,如:uincode"你今天真nice"。 在Solidity语言中,一些字符有了特定含义,如:双引号(")和单引号('),不能够直接输出这些字符,如:要输出一个双引号或单引号,不能直接写为"""或"'",否则编译器会报错。要想将这些字符按要求正确的输出需要转义,如:写为"\""或"\'",像这样的字符被称为转义字符。在Solidity中转义字符有: 单引号:\' 双引号:\" 反斜杠:\\ 退格:\b 换页:\f 回车:\r 制表符:\t 垂直制表符:\v 表示十六进制值并插入适当的字节:\xNN 表示unicode值并插入UTF-8序列:\uNNNN |
注意:ASCII字符集的字符占用一个字节,UTF-8字符集占用3个字节。 为了提高可读性,字符串字面常量可以分为多个连续的部分,链接的部分用空格链接,如:"HelloWorld"可分为"Hello" "World",unicode"你今天真nice"可分为unicode"你今天" unicode"真nice"。 |
十六进制字面常量 |
十六进制字面常量是以0x开头,后面紧跟着字符串,如:0x1122ff。 |
注意:十六进制的一个字符占用4个位,一个字节占用8个位,因此十六进制的字符个数必须是偶数,否则编译器会报错,如0x22334,编译器不会编译通过。 为了提高可读性,十六进制字面常量支持在字符间加上下划线,如:0x11_22_33等同于0x112233; |
十六进制字符串字面常量 |
十六进制字符串字面常量是指以关键字hex开头,后面紧跟着单引号或双引号引起来的字符串,如:hex"112233"。 |
注意:十六进制的一个字符占用4个位,一个字节占用8个位,因此十六进制的字符个数必须是偶数,否则编译器会报错,如hex"22334",编译器不会编译通过。 为了提高可读性,十六进制字符串字面常量可以分为多个连续的部分,链接的部分用空格链接,如:hex"11" hex"22" hex"33"等同于hex"112233"。 |
地址字面常量 |
是一个特殊的十六进制字面常量,需要通过地址校验,如:0x5B38Da6a701c568545dCfcB03FcB875f56beddC4。 |
|
数组字面常量 |
在方括号中包含一个或多个逗号分隔的表达式。如:[1,2,3,4]、['a', 'b', 'c', 'd']或[f(2), g(3), h(4)]。 数组的类型是由列表上的第一个表达式的类型决定,其他表达式需要隐式地转换成此类型,如果不可以转换,将出现类型错误。 如:[-8, -3, 8, 9]数组字面常量,其第一个表达式-8是int8类型,因此剩余的表达式必须能隐式地转换成int8类型,否则编译器会报错。 注意:像[-8, -3, 8, 1000]这样第一个表达式-8是int8类型,第四个表达式1000是int16类型的数组字面常量,由于存在了两个类型,这是不允许的,因此编译器会报错。由于int16的取值范围(-2^16~2^16-1)包含int8的取值范围(-2^8~2^8-1),可以将第一个表达式-8显示的转换成int16类型来解决上述问题,如:[int16(-8), -3, 8, 1000]。(隐式转换和显式转换在“8.8.数据类型转换”章节详细介绍) |
7.Solidity合约
Solidity合约类似于Java语言中的类。每个合约中可以包含状态变量、函数、函数修改器、事件、异常、结构体和枚举类型这7种合约结构,且这7种合约结构可以从其他合约中继承(继承在“16.Solidity继承”章节详细介绍)
7.1.声明合约
contract ContractName{
// 语句
}
相关字段 |
描述 |
contract |
声明合约的关键字 |
ContractName |
自定义的合约名称 |
7.2.创建合约
创建合约即实例化合约,类似于Java语言中的创建对象。创建合约有两种方式:常规方式创建合约和加“盐”方式创建合约。
7.2.1.常规方式创建合约
new ContractName(<parameter list>)
相关字段 |
描述 |
new |
创建合约的关键字 |
ContractName |
声明合约时的合约名称 |
<parameter list> |
与合约的构造函数有关,在声明合约时,若构造函数有设置输入参数,则需要在创建合约时传入值。<parameter list>是指传入的值列表,值间用逗号隔。(本章节可先忽略,在“9.6.构造函数”章节详细介绍) |
// 测试常规方式创建合约的合约
contract TestCreateContract{
// 声明状态变量时,创建Test合约
Test testA = new Test();
// 用于将在createContract函数内创建的Test合约赋值于testB状态变量
Test testB ;
// 在函数内创建合约
function createContract() public{
// 创建Test合约并赋值给testB变量
testB = new Test();
}
// 通过testA和testB状态变量调用Test合约中的testFun函数
function callTestFun() public view returns(string memory, string memory){
return (testA.testFun(), testB.testFun());
}
}
// 声明Test合约
contract Test{
// 输出“Test合约创建成功”文案
function testFun() public pure returns(string memory){
return unicode"Test合约创建成功";
}
}
7.2.2.加“盐”方式创建合约
在上述创建合约时,将根据创建合约的地址和此地址计数器(nonce)来计算新合约的地址。但此方法创建的合约地址依赖于交易者交易数量不断增长的nonce变量,这种方式很难确定一个未来要部署的合约地址(即合约地址在部署后才可获得,部署前很难获得)。
要想在合约部署前就获取合约地址,需要使用另一种机制来生成新合约的地址(这种机制不再使用计数器nonce),这种机制生成的合约地址有4部分决定,如下表格,然后对这四部分做keccak256哈希后转换成address类型。
生成合约的部分 |
描述 |
bytes1(0ff) |
一个常数,为了避免加“盐”方式创建合约生成的地址与常规方式创建合约生成的地址发生冲突冲突。 |
创建者地址 |
|
saltValue |
盐值,一个自定义的bytes32值。 |
待部署合约的字节码 |
是指待部署合约的创建时字节码和欲传入此合约构造函数的参数值做编码后的keccak256哈希。 |
上述方法是提前获取合约的地址,但没有部署合约。使用加“盐”方式部署合约的方式如下:
new ContractName{salt: saltValue}(<parameter list>)
相关字段 |
描述 |
new |
创建合约的关键字 |
ContractName |
声明合约时的合约名称 |
{salt: saltValue} |
使用加“盐”方式部署合约的盐值,saltValue是一个bytes32值 |
<parameter list> |
与合约的构造函数有关,在声明合约时,若构造函数有设置输入参数,则需要在创建合约时传入值。<parameter list>是指传入的值列表,值间用逗号隔。(本章节可先忽略,在“9.6.构造函数”章节详细介绍) |
// 测试加“盐”创建合约的合约
contract TestSaltContract{
// 两种方式获取加“盐”合约的地址
function createDSalted() public returns(address, address, string memory){
/*不部署只获取合约地址*/
// 常量
bytes1 bytes1Constant = 0xff;
// 创建者地址
// this表示当前合约
address creatorAddress = address(this);
// 盐值
// keccak256(param):计算输入参数param的哈希,返回值为bytes32数据类型
bytes32 saltValue = keccak256(unicode"自定义盐值");
// 待部署合约的字节码
// type(C).creationCode:获取合约C的创建时字节码。此处是获取Test合约的运行时字节码
// abi.encode(<param list>):对输入的参数列表<param list>执行abi编码。目前Test合约构造函数的输入参数为空,所以<param list>为空
// abi.encodePacked(<param list):对输入的参数列表<param list>执行紧打包编码
bytes32 bytecode = keccak256(abi.encodePacked(type(Test).creationCode,abi.encode()));
// 对上述4部分紧打包编码后执行keccak256哈希
bytes32 hash = keccak256(abi.encodePacked(bytes1Constant, creatorAddress, saltValue, bytecode));
// 数据类型转化,获取Test合约的合约地址
address addrA = address(uint160(uint(hash)));
/*部署并获取合约地址*/
// 部署Test合约
Test test = new Test{salt: saltValue}();
// 将Test合约转化为地址类型
address addrB = address(test);
// 判断addrA和addrB地址是否相同
string memory isEqual;
if(addrA == addrB){
isEqual = unicode"地址相同";
}else{
isEqual = unicode"地址不相同";
}
return (addrA, addrB, isEqual);
}
}
// 待部署合约
contract Test{
// 输出“Test合约创建成功”文案
function testFun() public pure returns(string memory){
return unicode"Test合约创建成功";
}
}
8.Solidity变量
变量是用于存储信息的"容器",如:unit num = 10,声明unit类型且名称为num的变量,并将10存储于num变量。
8.1.变量分类
根据变量的作用和声明位置可以将变量分为全局变量、状态变量、局部变量。
- 全局变量
声明在全局工作区中的变量被称为全局变量。全局变量是Solidity语言预制好的,提供有关区块链和交易属性的信息。(全局变量在“20.Solidity全局属性”章节详细介绍)
- 状态变量
声明在合约内,在函数和函数修改器之外的变量被称为状态变量。状态变量在合约内任何地方都可访问,状态变量的值会被永久存储在以太坊区块链上相应账户的account storage空间中。
- 局部变量
声明在函数或函数修改器内的变量被称为局部变量。局部变量仅在函数或函数修改器执行过程中有效,函数或函数修改器执行结束后局部变量便失效。输入参数和返回参数也是局部变量。
下图红色圈为状态变量、黄色圈为局部变量、青色圈为全局变量
8.2.状态变量和局部变量的声明
- 声明状态变量
<dataType> <visibility> [constant | immutable] <dataLocation> [override] varName = value
相关字段 |
描述 |
<dataType> |
是指数据类型,Solidity语言提供了值类型和引用类型两大类型。 |
<visibility> |
是指状体变量可见性,有public、internal、private三种。若不写则默认为internal。 |
[constant | immutable] |
constant是指声明常量的关键字。 immutable是声明不可变量的关键字,也是常量的一种。 |
<dataLocation> |
是指数据位置,来说明数据存储的位置。数据位置种类分为storage(称为存储)、memory(称为内存)、calldata(称为调用数据)三种位置。状态变量只使用storage且默认也是storage,因此在声明状态变量时,不必且不可标注数据位置。 |
[override] |
在继承中用到的关键字,被override修饰的状态变量要重写父合约中对应的函数(本章节可先忽略,在“16.1.1.重写函数”章节详细介绍) |
varName |
自定义的变量名称。 |
value |
是指为变量赋的值,若不写则会初始化为默认值。 |
- 声明局部变量
<dataType> <dataLocation> varName = value
相关字段 |
描述 |
<dataType> |
是指数据类型,包括值类型和引用类型。 |
<dataLocation> |
是指数据位置,来说明数据存储的位置。数据位置分为storage(称为存储)、memory(称为内存)、calldata(称为调用数据)三种位置。 |
varName |
自定义的变量名称。 |
value |
是指为变量赋的值,若不写则会初始化为默认值。 |
8.3.变量命名规则
- 变量名称只能使用字母、数字、下划线、美元符号组成。
- 变量名必须以字母、下划线开头。
- 变量名区分大小写。
- 不可使用Solidity语言的保留关键字作为变量名。
8.3.数据类型
Solidity语言是一种静态类型的语言(即编译时已知变量类型),因此需要在编写程序时指定变量的类型。Solidity语言提供了值类型和引用类型两大数据类型。
值类型:由于值类型占用的存储空间固定,因此值类型的变量按值传递(即一个变量的值赋值给另一个变量,则这个值会复制一份赋值给后者变量,其中一个变量的值发生改变不会影响另一个变量的值)。
引用类型:由于引用类型占用的存储空间不固定,为节省存储空间,引用类型的变量按引用传递(即一个变量的值赋值给另一个变量,则后者变量用引用直接指向前者变量的值,其中一个变量值的某个元素发生改变另一个变量值对应的元素跟随变化,但是其中一个变量值整体发生改变另一个变量值不会发生改变)。
// 测试值类型和引用类型的合约,值类型和引用类型赋值流程如下图:
contract Test{
function fun() public pure returns(uint, uint, uint[5] memory, uint[5] memory, uint[5] memory){
// uint是值类型
uint numA = 10;
// 将numA变量的值复制一份赋给numB变量,此时两个变量的值都是10
uint numB = numA;
// 将numA和numB两个变量分别赋值为20和30
numA = 20;
numB = 30;
// uint[5]数组是引用类型
uint[5] memory numsA = [uint(1),2,3,4,5];
// 将numsA变量赋值给numsB变量,此时numsA和numsB两个变量的引用都指向[uint(1),2,3,4,5]
uint[5] memory numsB = numsA;
// 给numsB变量重新赋值为[uint(6),7,8,9,10]
// 则numsA变量的引用还是指向[uint(1),2,3,4,5],numsB变量的引用指向[uint(6),7,8,9,10]
numsB = [uint(6),7,8,9,10];
// 将numsA变量赋值给numsC变量,此时numsA和numsC两个变量的引用都指向[uint(1),2,3,4,5]
uint[5] memory numsC = numsA;
// 通过numsA变量将数组的第二个元素设置为20
// 通过numsC变量将数组的第三个元素设置为30
numsA[1] = 20;
numsC[2] = 30;
// numA变量的输出为20
// numB变量的输出为30
// numsA变量的输出为[1,20,30,4,5]
// numsB变量的输出为[6,7,8,9,10]
// numsC变量的输出为[1,20,30,4,5]
return (numA, numB, numsA, numsB, numsC);
}
}
值类型和引用类型赋值流程如下图
8.4.1. 值类型
8.4.1.1.布尔类型
名称 |
描述 |
关键字 |
bool |
存储大小 |
1字节 |
值范围 |
布尔字面常量 |
默认值 |
false |
支持的运算符 |
非(!)、与(&&)、或(||)、等于(==)、不等于(!=) |
示例 |
bool bl = false; |
8.4.1.2.无符号整型
名称 |
描述 |
关键字 |
uintN(N是指8、16、24 ... 256,分别表示8位无符号整型、16位无符号整型、24位无符号整型 ... 256位无符号整型。uint是uint256的别名。) |
存储大小 |
和N有关,uint8大小为1字节、uint16大小为2字节、uint24大小为3字节 ... uint256大小为32字节 |
值范围 |
|
默认值 |
0 |
支持的运算符 |
小于不等于(<=)、小于(<)、等于(=)、不等于(!=)、大于不等于(>=)、大于(>) 按位与运算(&)、按位或运算(|)、按位异或运算(|)、按位反运算(|) 左移位(<<)、右移位(>>) 加(+)、减(-)、乘(*)、除(/)、取余(%)、幂(**) |
示例 |
uint8 num = 10; |
8.4.1.3.有符号整型
名称 |
描述 |
关键字 |
intN(N是指8、16、24 ... 256,分别表示8位有符号整型、16位有符号整型、24位有符号整型 ... 256位有符号整型。int是int256的别名。) |
存储大小 |
和N有关,int8大小为1字节、int16大小为2字节、int24大小为3字节 ... int256大小为32字节 |
值范围 |
|
默认值 |
0 |
支持的运算符 |
小于不等于(<=)、小于(<)、等于(=)、不等于(!=)、大于不等于(>=)、大于(>) 按位与运算(&)、按位或运算(|)、按位异或运算(|)、按位反运算(|) 左移位(<<)、右移位(>>) 加(+)、减(-)、乘(*)、除(/)、取余(%)、幂(**)、一元运算负(-) |
示例 |
iny8 num = -10; |
8.4.1.4.无符号定长浮点型
名称 |
描述 |
关键字 |
ufixedMxN(M是指8、16、24 ... 256,表示该类型占用的位数;N可以是从0至80之间的任意整数,表示可用的小数位数。ufixed是ufixed128x19的别名。) |
存储大小 |
和M有关,ufixed8xN大小为1字节、ufixed16xN大小为2字节、ufixed24xN大小为3字节 ... ufixed256xN大小为32字节 |
值范围 |
十进制小数字面常量 |
默认值 |
0 |
支持的运算符 |
小于不等于(<=)、小于(<)、等于(=)、不等于(!=)、大于不等于(>=)、大于(>) 加(+)、减(-)、乘(*)、除(/)、取余(%)、幂(**) |
示例 |
ufixed128x19 decimal; |
注意: Solidity语言还没有完全支持无符号定长浮点型。可以声明定长浮点型的变量,但不能给它们赋值或把它们赋值给其他变量。 |
8.4.1.5.有符号定长浮点型
名称 |
描述 |
关键字 |
fixedMxN(M是指8、16、24 ... 256,表示该类型占用的位数;N可以是从0至80之间的任意整数,表示可用的小数位数。fixed是fixed128x19的别名。) |
存储大小 |
和M有关,fixed8xN大小为1字节、fixed16xN大小为2字节、fixed24xN大小为3字节 ... fixed256xN大小为32字节 |
值范围 |
十进制小数字面常量 |
默认值 |
0 |
支持的运算符 |
小于不等于(<=)、小于(<)、等于(=)、不等于(!=)、大于不等于(>=)、大于(>) 加(+)、减(-)、乘(*)、除(/)、取余(%)、幂(**) |
示例 |
fixed128x19 decimal; |
注意: Solidity语言还没有完全支持有符号定长浮点型。可以声明定长浮点型的变量,但不能给它们赋值或把它们赋值给其他变量。 |
8.4.1.6.普通地址类型
名称 |
描述 |
关键字 |
address |
存储大小 |
20字节 |
值范围 |
地址字面常量 |
默认值 |
0x0000000000000000000000000000000000000000 |
支持的运算符 |
小于不等于(<=)、小于(<)、等于(=)、不等于(!=)、大于不等于(>=)、大于(>) |
示例 |
address addr = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; |
8.4.1.7.支付地址类型
名称 |
描述 |
关键字 |
address payable |
存储大小 |