Solidity 在编译事需指定变量(状态变量和局部变量)。
虽然 Solidity 受到了 c++、python以及 javascript 的影响,但 undefined
和null
是不存在的,当然咯,每个新声明的变量总是有默认值
的,显然这默认值
是跟其声明的类型相关的。
而 Solidity 将类型统归纳为值类型
、引用类型
、映射类型
。本文将先从值类型
讲起。
值类型
,顾名思义便是按值进行赋值变量或传递给函数,它们总是会将值拷贝(相对于指针传递,也可以说是引用传递)。
以下这些类型在 Solidity 中都属于 值类型:
true
和 false
int
和无符号整形uint
,这一点倒是和 c++、golang很相像。不管是无符号还有符号整数,它们都是以 8 位递增到 256 位,而int
和uint
分别是int256
和uint256
的别名/
ufixed):目前 Solidity 还没完成地支持它,也就是可以在文件中声明这种类型,但却不能赋值给这种类型,同样也不能赋值给其他变量的。ufixedMxN
和 fixedMxN
中,M
表示该类型占用的位数,N
表示可用的小数位数。M
是从 8 位 到 256位 ,N
则可以是从 0 到 80 之间的任意数。 ufixed
和 fixed
分别是 ufixed128x19
和 fixed128x19
的别名。address payable
,它与address
类型相同,不过它有成员函数 transfer
和 send
,以便它可以方便进行以太币转账以及查询。constract
,便是一个合约类型。bytes1
、bytes2
、bytes3
、...
、bytes32
。69
表示数字 69。 Solidity 中是没有八进制的,因此前置 0 是无效的。"foo"
或者 'bar'
)hex
打头,后面紧跟着用单引号或双引号引起来的字符串(例如,hex"001122FF"
)。 字符串的内容必须是一个十六进制的字符串,它们的值将使用二进制表示。它是一种非真即假
二选一值类型,其取值为true
和fase
。
// 布尔运算
bool public flag1 = !flag; //取非
bool public flag2 = flag && flag1; //与
bool public flag3 = flag || flag1; //或
bool public flag4 = flag == flag1; //相等
bool public flag5 = flag != flag1; //不相等
// 整型
int public num = -7; // 整数,包括负数
uint public unum = 7; // 正整数
uint256 public big_num = 20221206; // 256位正整数
这是一个 Solidity 特有的类型,用于存储合约地址,而地址又可细分为普通地址
和可支付地址
两类。
这种区别背后的思想是 address payable
可以向其发送以太币,而不能先一个普通的 address
发送以太币,例如,它可能是一个智能合约地址,并且不支持接收以太币。
允许从 address payable
到 address
的隐式转换,而从 address
到 address payable
必须显示的转换, 通过 payable()
进行转换。
// 地址
address public address1 = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
address payable public address2 = payable(address1); // payable address,可以转账、查余额
// 地址类型的成员
uint256 public balance = address1.balance; // balance of address
你可能也猜到了,没错,既然有定长字节数组
,那么便会有不定长字节数组
咯!是的,其实字节数组(bytes)
分为定长字节数组
和不定长字节数组
两类,而不定长字节数组
属于引用类型
,所以放在后面讲。且定常字节数组
存放数据,消耗燃料(gas)
比较少。
// 固定长度的字节数组
bytes32 public webSite = "qiucode.cn";
bytes1 public myByte = webSite[0];
枚举是在Solidity中创建用户定义类型的一种方法。 它们是显示所有整型相互转换,但不允许隐式转换。 从整型显式转换枚举,会在运行时检查整数时候在枚举范围内,否则会导致异常( Panic异常 )。 枚举需要至少一个成员,默认值是第一个成员,枚举不能多于 256 个成员。
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
// 创建enum变量 action
ActionChoices action = ActionChoices.GoLeft;
一个用户定义的值类型允许在一个基本的值类型上创建一个零成本的抽象。 这类似于一个别名,但有更严格的类型要求。
用户定义值类型使用 type C is V
来定义,其中 C
是新引入的类型的名称, V
必须是内置的值类型(”底层类型”)。 函数 C.wrap
被用来从底层类型转换到自定义类型。同样地,函数函数 C.unwrap
用于从自定义类型转换到底层类型。
类型 C
没有任何运算符或绑定成员函数。特别是,即使是操作符 ==
也没有定义。也不允许与其他类型进行显式和隐式转换。
自定义类型的值的数据表示则继承自底层类型,并且ABI中也使用底层类型。
函数类型是一种表示函数的类型。可以将一个函数赋值给另一个函数类型的变量,也可以将一个函数作为参数进行传递,还能在函数调用中返回函数类型变量。 函数类型有两类: - 内部(internal) 函数类型 - 外部(external) 函数类型
内部函数只能在当前合约内被调用(更具体来说,在当前代码块内,包括内部库函数和继承的函数中),因为它们不能在当前合约上下文的外部被执行。 调用一个内部函数是通过跳转到它的入口标签来实现的,就像在当前合约的内部调用一个函数。
外部函数由一个地址和一个函数签名组成,可以通过外部函数调用传递或者返回。
function () {internal|external|public|private} [pure|view|payable] [returns ()] {
}
function
:声明函数时的固定用法,想写函数,就要以function关键字开头。
:函数名。()
:圆括号里写函数的参数,也就是要输入到函数的变量类型和名字。{internal|external|public|private}
:函数可见性说明符,一共4种。没标明函数类型的,默认internal
。
public
: 内部外部均可见。(也可用于修饰状态变量,public变量会自动生成 getter
函数,用于查询数值).private
: 只能从本合约内部访问,继承的合约也不能用(也可用于修饰状态变量)。external
: 只能从合约外部访问(但是可以用this.f()
来调用,f
是函数名)internal
: 只能从合约内部访问,继承的合约可以用(也可用于修饰状态变量)。[pure|view|payable]
:决定函数权限/功能的关键字。payable
(可支付的)很好理解,带着它的函数,运行的时候可以给合约转入ETH
。constant pure
不能读取也不能写入存储在链上的状态变量。view
能读取但也不能写入状态变量。[returns ()]
:函数返回的变量类型和名称。与参数类型相反,返回类型不能为空 —— 如果函数类型不需要返回,则需要删除整个 returns (
部分。
internal
关键字。请注意,这仅适用于函数类型,合约中定义的函数明确指定可见性,它们没有默认值。
类型转换:
函数类型 A
可以隐式转换为函数类型 B
当且仅当: 它们的参数类型相同,返回类型相同,它们的内部/外部属性是相同的,并且 A
的状态可变性比 B
的状态可变性更具限制性,比如:
pure
函数可以转换为 view
和 non-payable
函数view
函数可以转换为 non-payable
函数payable
函数可以转换为 non-payable
函数关于 payable
和 non-payable
的规则可能有点令人困惑,但实质上,如果一个函数是 payable
,这意味着它 也接受零以太的支付,因此它也是 non-payable
。 另一方面,non-payable
函数将拒绝发送给它的 以太币Ether , 所以 non-payable
函数不能转换为 payable
函数。
如果当函数类型的变量还没有初始化时就调用它的话会引发一个 Panic 异常。 如果在一个函数被 delete
之后调用它也会发生相同的情况。
如果外部函数类型在 Solidity 的上下文环境以外的地方使用,它们会被视为 function
类型。 该类型将函数地址紧跟其函数标识一起编码为一个 bytes24
类型。。
请注意,当前合约的 public 函数既可以被当作内部函数也可以被当作外部函数使用。 如果想将一个函数当作内部函数使用,就用 f
调用,如果想将其当作外部函数,使用 this.f
。
一个内部函数可以被分配给一个内部函数类型的变量,无论定义在哪里,包括合约和库的私有、内部和public函数,以及自由函数。 另一方面,外部函数类型只与public和外部合约函数兼容。库是不可以的,因为库使用 delegatecall
,并且 [他们的函数选择器有不同的 ABI 转换 。 接口中声明的函数没有定义,所以指向它们也没有意义。