在混沌纷繁的尘世中寻找真正想要的是什么
极简Solidity语法总结

常规语法

文件结构:【预编译导入,合约库接口】

  1. 预编译:指定编译器版本,语法为 pragma Solidity ^0.4.19;,^是可选的脱字符号,意为这是此主版本号的最新版本
  2. 导入:导入的是其它solidity文件,语法 import ‘xxx.sol’;,还可以用“/”引用目录,“.”符号引用当前目录,“..”引用父目录
  3. 合约:contract 合约名 [is 继承语句] {xxx}
  4. 库:library 库名 {xxx}
  5. 接口:interface 接口名{xxx}
  6. 函数:function 函数名([形参表列]) 限定符 [return语句] {xxx}

注释:

  1. 单行注释 //,多行注释 /* */

自定义数据类型:

  1. 结构体:语法为 struct 结构体名{数据类型 变量名; \n 数据类型 变量名; },实例化一个结构体语法是 结构体名 = 变量名(”ritesh”,10,true, new unit[](3));

基本数据类型:

  1. 概述:基本数据类型还分为值类型和引用类型两种,前者符号memory后者storage,但不是运行在pc里而是EVM网络,值类型存在节点计算机内存里,用完就销毁,索引的时候也是和其它计算机语言一样根据标识符寻找,引用类型经常又称状态变量【应该是借用自函数式编程里面的类似概念】,存储在区块链上网络上,永久存在,根据指针索引。即函数外部声明的变量存储在stoage,内部变量存储在memory
  2. 对引用类型的变量(数组、结构体、字符串、映射),在声明的时候必须指定存储位置
  3. 值类型:
    1. 布尔型 boll,不能转换为整型,默认false
    2. 无符整型 uint和有符整型 int按8递加,例如最低 uint 8,最高 uint 256,int同
    3. 地址类型 address,20字节,有隐式的balance属性和发送以太币的transfer函数、返回发送以太币是否成功布尔值的send函数、调用合约函数的Call函数、DelegateCall函数、Callcode函数
    4. 数组类型可以任意数据类型,声明语法 数据类型[数量] 数组名,初始化方法在声明的时候 int[5] age = [1,2,3,4,5];,或者先声明 int[5] = 数组名;,再写 数组名 = [1,2,3,4,5];
      1. 动态数组:声明的时候不写有多少元素个数 int[] 数组名;,初始化的时候用new关键字 数组名 = new int[] (5);
      2. 字节数组:可以容纳任意数量字符的动态数组,声明语法和初始化语法与动态数组一样 bytes[] 数组名;数组名 = new bytes[] (5);另外有一种初始化语法是声明语句之后使用赋值语句 数组名 = “xxx”;
      3. 字符串数组
      4. 数组里有三个隐式成员属性,但不是每种数组都具有,index只有字符串数组没有,但只有动态数组、固定数组、字节型数组支持写入指定元素index属性,push只有动态数组支持,length只有字符串数组没有,但只有动态数组和字节型数组支持写入此属性
    5. 枚举类型声明语法 enum 变量名{枚举值1,枚举值2},初始化语法太抽象了没看懂,枚举类型不能在函数中声明只能在合约中声明
    6. 字节类型声明的时候要写多少个字节数量,示例 bytes 变量名;,默认值0x00,如果byte1也可以用byte表示,可以用按位操作符
    7. 枚举类型至少声明一个枚举值,第一个编号0,第二个编号1,以此类推
  4. 引用类型有数组、结构体、字符串、映射,其中映射就是键值对,声明语法 Mapping (键数据类型 => 值数据类型) 变量名;,读值语法 变量名[序号],写值语法变量名 [序号] = <<值>>,映射类型调用是要gas的所以使用时要避免迭代和循环
  5. 字面量:不同字面量的表示方法不同,整型没什么,字符串型单引号或者双引号,地址型0x开头,十六进制数用hex开头并用双引号包裹 hex”1A2B3F”,十进制小数直接点号即可 3.14

运算符:

  1. 比较:相等 ==,不相等 !=,大于 >,小于 <,大于等于 >=,小于等于 <=
  2. 逻辑:与 &&||!

程序结构:

  1. 选择:if(条件句1) {xxx} else if(条件句2) {xxx} else{xxx}
  2. 循环:while(条件句) {xxx}for(初始化;条件句;自增减) {xxx}do{xxx} while(条件句)
  3. break和continue:和C一样

返回:

  1. 如果函数要有返回值,必须在形参和大括号之间写 returns(返回值类型),注意有个s,然后在函数体内写return语句 return 变量名;,第二个方法可以允许写多个返回值,这叫元组返回

面向对象:

  1. 封装:Solidity通过可见性修改器,即限定符来实现封装
  2. 继承:继承会继承所有public和internal的状态变量、函数、修改器、事件,写法是写合约的时候使用is关键字 contract 子合约名 is 父合约名{xxx},多重继承遵循C3线性化顺序,Solidity合约里所有函数全部具有虚函数性,对没有按默认顺序继承的合约一样可以用 合约名.函数名()来调用函数
  3. 多态:多态即重载,Solidity分函数和合约两种重载,函数多态写法直接写同名函数即可,合约多态实现方法是合约继承

构造函数:想重写构造函数直接写一个和合约名一样的函数即可,但限定符只能是public或internal,构造函数没有返回值,只在合约部署的时候执行一次【这里也是Solidity和其它计算机语言的区别,前者编译的是区块链上永久存在的字节码,其它语言编译的只是运行的时候用完就丢的二进制编码】。0.4.22之后这种写法被废除,必须使用构造器constructor(),写法是 constructor(…) { … },不用function,中间也可以加函数限定符public, internal,但是如果合约是继承自父合约,“在派生合约构造函数中添加基合约所需要的参数”,写法是 constructor(…)constructor2(…) { … },其中 constructor2(…)是父合约的构造函数。两个构造函数都需要指定可见性限定符。

错误处理:以前只用throw语句进行错误处理,会消耗掉所有gas然后把状态回退到刚初始化,所以4.10版本之后被标记过时,现在使用几个错误处理函数,require函数写在函数体的刚开始,语法 require(条件句);条件句为true接着运行false抛出异常;assert函数语法和作用和require函数一样,用于require函数之后的第二次再判断,但是和throw语句一样会消耗掉所有gas然后把状态回退到刚初始化;revert()语法 assert();作用类似循环体中的berak语句,一般配合if条件判断,满足条件就直接break整个函数体


语言特性

  1. 以太坊自然规范注释法:格式为 /// @xxx 注释文本,其中 xxx如果是title意为标题,author意为作者,notice用于解释函数或者合约的作用,dev用于向开发者诉说的话,param用于解释关于形参的信息,return用于解释返回值的信息

  2. var类型:隐式类型变量,只能在函数体中使用,会通知编译器为第一次赋予它的值的类型,一旦通知不可修改

  3. Solidity不强制规定变量声明和使用的顺序,因为编译器生成字节码的时候第一步会先提取所有变量声明放在顶部

  4. 数据位置:因为Solidity运行在EVM虚拟机上,所以变量有不同存放位置,状态变量放在存储位置,其永久存在所有节点上,内存位置是所有函数都能访问的本地内存,函数执行完就销毁,传参放在调用位置上,使用以太坊指令集的变量和中间值会存在堆栈位置

  5. fallback函数:无名函数,如果调用的函数根本不存在或合约没有收到任何数据例如只收到打过来的以太币时触发,没有名字,无法显式调用,没有传参和返回值,默认消耗2300gas,如果要用它接收以太币要标记payable

  6. 修改器:修改器专门承接判断某些值以及要不要执行函数这些工作,语法 modifier 修改器名(){xxx},一般里面接if判断句,其中有个语法糖,_意为替代目标函数,使用修改器写函数首部的形参和大括号之间,语法 function 函数名() 修改器名() 限定符 {xxx}

  7. 事件:链上有一个特殊数据结构叫日志,专门用来记载区块中发生的事件信息,智能合约不能访问,其提供一个事件接口,事件是一个触发器,条件发声就存储抛出异常到交易日志中,声明语法 event 事件名(抛出异常);,其中抛出异常可以是数据类型或者字符串,事件声明之后在整个合约范围内有效,使用方法在函数里写 事件名(所要抛出异常);即可

  8. 隐式转换:支持从较小类型到较大类型的隐式转换,直接赋值即可

  9. 显式转换:不鼓励显式转换因为容易出错,但是提供几个显式转换函数,ConvertionExplicitUINT8toUINT256函数、ConvertionExplicitUINT256toUINT8函数、ConvertionExplicitUINT256toUINT81函数分别按函数名字面意思提供从前到后的类型转换,还有一个conversions函数不太明白

  10. 散列函数:solidity提供散列函数将输入转换为散列,SHA2算法是sha256(),SHA3算法是sha3()和keccak256(),后者是SHA3算法的别名。语法 sha256(”r”)sha3(”r”)keccak256(”r”)

  11. 地址数据类型:外部地址和合约地址都有5个全局函数和一个余额成员变量

    1. ddress.transfer(uint256 amount):向指定地址发送以wei做单位的以太币,返回成功与否的布尔值
    2. address.send(uint256 amount):向指定地址发送以wei做单位的以太币,返回成功与否的布尔值
    3. address.call(…):调用低级别的call函数,返回成功与否的布尔值
    4. address.callcode(…):调用低级别的callcode函数,返回成功与否的布尔值
    5. address.delegatecall(…):调用低级别的delegatecall函数,返回成功与否的布尔值
  12. 合约数据类型:合约数据类型提供一个this成员和selfdestruct函数,接收一个地址把钱全部转过去

  13. 合约内调用已创建合约的两种方法:如果是同一个文件里写了两个合约,第二个合约里用new创建一个新的对象,语法 Hellw world myobj = new Helloworld();。如果想要调用已经部署过的合约,直接把这个合约的地址赋值给地址类型数据即可。

  14. 方法覆盖对同名函数追加view关键字,这个关键字意思是承诺不修改状态,语法 function 函数名() public view returns(uint){ }

  15. 抽象合约:抽象合约的用途只有被继承,无法创建合约实例,语法是依旧正常的合约结构,但是所有的函数都没有大括号的函数体只有一个分号,例如 function 函数名(形参) public;

  16. 截断模式和检查模式:0.8的新特性,前者使用unchecked关键字后者使用checked,在算数运算中,后者会对运算结果进行溢出(超过数据结构定义的数字上下限)检查,如果超过就抛出异常然后回退,而前者不会,所以前者需要调用第三方库如openzeppelin的safemath.sol。语法上是 unchecked{xxx运算语句}checked{xxx运算语句}

  17. 内联汇编:编译的时候solc会优化二进制代码,但有时候尤其是循环语句的时候这种优化是负优化,使用内联汇编语句可以阻止优化

    1
    2
    3
    assembly{
     内联汇编语句
    }
  18. abi编码函数:

  19. 区块和交易全局变量:因为合约无权访问整个账本所以提供一些关于区块和交易的全局变量以供调用,并且没有在合约当中显式声明

变量名称 描述
block.coinbase(address) 与etherbase相同,指矿工的地址
block.difficulty(uint) 当前区块难度等级
block.gaslimit(unit) 当前区块gas限制
block.number(unit) 顺序表示的区块编号
block.timestamp(unit) 区块创建时间
msg.data(bytes) 与创建交易相关的函数及其参考信息
msg.gas(uint) 执行交易后未花费gas
msg.sender(address) 调用函数的地址
msg.sig(bytes4) 函数标识符使用散列函数签名后的前四个字节
msg.value(uint) 随交易发送的以太币数量,以wei作为单位
now(uint) 当前时间
tx.gasprice(uint) 调用者准备支付的每一种gas单位下的gas价格
tx.origin(address) 交易的发起者
block.blockhash(uint blockNumber) 包含交易的区块散列值
returns(bytes32) 包含交易的区块散列值
string.concat 0.8.13版本的新全局变量(可能有误)
abi.encodeCall 0.8.13版本的新全局变量(可能有误)
remix本地安装
© 2024 Dal
「 就在那个时刻,你记得这并非幻觉,的确就在那个时刻,那只手和那块石头的接触面,她突然回过头冲你说:我也爱着你。 」