【区块链】以太坊Solidity编程:智能合约实现之基本语法

以太坊Solidity编程:智能合约实现之基本语法

基本数据类型

1. Solidity类型:

  • solidity为静态类型语言
  • 所有变量均需要预定义
  • 和大多数语言一样,提供几个基本类型和基本类型组成的复杂类型
  • 和现代语言相比,solidity仅覆盖最常见的类型。

2. Solidityt基本数据类型

  • 布尔类型
  1. 布尔:真或假
  2. 操作符:!(逻辑非)
  3. &&(逻辑与)
  4. || (逻辑或)
  5. == (相等)
  6. != (不等)
  • 整型
  1. int / uint : 是有符号和无符号的整数
  2. uint8到uint256步长8(从8到256位的无符号整数)
  3. uint和int分别是uint256和int256的别名
  • 地址
  1. 地址:20字节(一个以太坊地址)
  2. 可表示用户账号,合约地址
  3. 操作符:≤、<、==、!=,≥,>
  4. 地址成员: 账户余额(balance)
  5. 地址成员:发送(transfer),从合约发起方向某个地址转入以太币(单位是wei)
    地址
  • 注释
  • 字符串常量
  1. 字符串常量用双引号或者单引号括起来,如“abc”,'abc’均可
  2. 字符串常量可以隐式转换位bytes
  3. 字符串常量支持转义符,如\n,\xNN(16进制)and \nNNNN(Unicode)

补充:
整型操作符:

  • 比较: ≤,<,==,!=,≥,>(计量布尔量)
  • 位操作符:&,!,^(位异或),~(位取反)
  • 算术操作符:+,-,*,/,%(取余数),**(幂次方)
  • 除以0操作,会报异常
    枚举类型:
  • 用户自定义类型,基本同C++等语言的定义
  • 枚举类型中的枚举值到整型可显示转换,但不能隐式转换
  • 枚举类型不能为空,至少应当包含一个值
    枚举类型

基本类型转换

  1. 分为隐式转换和显示转换,如字符串转为整型等
  2. 隐式转换:如果运算符支持两边不同的类型,编译器会尝试隐式转换类型,包括赋值
  3. 隐式转换需要能保证不会丢失数据,且语义可通
  4. 显示转换:如果编译器不允许隐式的自动转换,但你知道转换没问题时,可以进行转换
  5. 不正确的转换会带来错误
    基本类型转换

array类型

数组

  • 数组是可以在编译时固定大小的,也可以是动态的
  • 对于storage存储数组来说,成员类型可以是任意的(也可以是其他数组,映射或结构)
  • 对于memory数组来说,成员类型不能是一个映射,如果是公开可见的函数参数,成员类型是必须是ABI类型的。

数组声明

  • T[k]:元素类型为T,固定长度为k的数组
  • T[]:动态大小的(变长)的数组
  • uint[3][5]:声明5个uint[3](与大部分语言相同).访问时,uint[4][0]:第5个数组的第1个元素
  • bytes类似于byte[]

数组成员函数

  • 成员函数:length,存放元素的数量
  • 动态length可变
  • 成员函数:push,可在数组的尾部添加一个元素。函数返回新的长度
  • Push目前仅storage变长数组可用
    数组成员函数

数组使用事项

数组作为返回参数使用

  • 暂时还无法在外部函数中使用数组的数组(多维数组)
  • 暂时无法从外部函数调用返回的动态内容
  • 目前可行的解决办法是使用较大的静态尺寸大小的数组

数组创建和初始化

  • 可使用new关键字创建或初始化memory的变长数组
  • 但不能通过.length的长度来修改memory数组大小属性
  • Storage数组可以修改长度
  • 目前暂时是这样规定的
    数组的创建和初始化
    要点:变长数组须先new初始化,分配空间
    变长数组先new
    要点:变长storage数组可以通过push初始化
    变长storage
    要点:暂不能通过外部函数返回变长数组
    不能通过外部函数返回变长数组
    要点:定长的数组不可直接赋值给变长数组
    定长的数组不可直接赋值给变长数组

mapping类型

映射定义

  • 映射或字典类型,一种键值对的映射关系存储结构
  • 定义方式为mapping(KeyType=>KeyValue)
  • 键的类型一般为基本数据类型,暂不支持映射、变长数组、结构体等复杂类型。值的类型无限制
  • 映射可以视为哈希表。所有可能的键会被虚拟化的创建,映射到一个类型的默认值
  • 映射表中,我们并不存储键的数据,仅存储它的keccak256哈希值,用来查找值时使用
  • 映射并没有长度,键集合(或列表),值集合(或列表)这样的概念。

映射定义的例子:
映射定义的例子
映射的赋值:
映射的赋值

映射使用要点

  1. 映射类型,仅能用来定义状态变量,或者是在内部函数中作为storage类型的引用
  2. 可以通过将映射标记为public,来让Solidity创建一个访问器
  3. 访问映射,需要提供一个键值作为参数
  4. 映射暂未提供迭代输出的方法

struct类型

结构体

  • Solidity提供struct来定义自定义类型
  • struct可以用于映射和数组中作为元素
  • 其本身也可以包含映射和数组等类型
  • 但struct内不能有struct。

结构体示例
结构体示例

结构体初始化

直接赋值(按顺序)

结构体初始化

命名初始化

命名初始化

忽略mapping

忽略mapping

数据的位置和引用类型

值类型:布尔、整型、地址、定长字节数组

  1. 在传值时,总是值传递,完全拷贝

引用类型:不定长数组、字符串、数组、结构体

  1. 复杂类型,占用空间较大,在拷贝时占用空间较大,一般都通过引用传递。

引用传递

  • 值传递,传递的是内存的内容
  • 引用传递,传递的是内存的地址
  • 引用的改变,修改后会改变内存地址对应储存的值,也就是变量和其引用会同时改变。

数据的位置

  • 复杂/引用类型,如数组(arrays)和结构体(struct)有一个额外的属性,数据的存储位置,可选为memory或storage
  • Memory存储位置为内存
  • Storage保存永久记录,存储在链上

数据的默认位置

  • 基于程序的上下文,大多数时候这样的选择是默认的,也通过指定关键字storage和memory修改它
  • 函数参数,包括返回的参数,默认是memory
  • 局部复杂变量默认是storage
  • 状态变量强制为storage

不同数据位置的赋值

  • 在memory和storage之间,完全拷贝
  • 任何位置的变量,赋值给状态变量,完全拷贝
  • 状态变量(storage),赋值storage局部变量,引用传递
  • memory的引用类型赋值给另一个memory的引用,不会创建另一个拷贝

注意事项

  1. 不能将memory赋值给局部变量
  2. memory只能用于函数内部
  3. 对于值类型,总是会进行拷贝
  4. storage在区块链中是用key/value的形式存储,而memory则表现为字节数组
  5. 值类型的局部变量是存储在栈上
  6. Gas消耗storage>>memory>stack

不同位置复杂类型赋值图

不同位置复杂类型赋值图

常规用法

  • storage为合约级变量,在合约创建时就确定了,但内容可以被(交易)改变
  • solidity认为交易就是改变了合约状态,故合约级变量称为“状态”变量。函数内部只能定义storage的引用
  • memory只能用于函数内部,其声明EVM在运行时创建一块内存区域给变量使用
    数据的位置示例

特殊变量和函数

地址相关(Address Related)

  • <address>.balance(uint256): address的余额,以wei为单位
  • <address>.transfer(uint256 amount): 从合约(地址)向address发送一定数量的ether,以wei为单位。
  • <address>.send(uint256 amount) returns (bool): 同transfer。不建议。
    示例:
    地址相关示例

合约相关

  • this:当前合约的类型,可以显示的转换为Address
  • selfdestruct(address recipt):销毁当前合约,并把它所有资金发送到给定的地址。
  • 如果一个函数需要进行货币操作,必须要带上payable关键字。

数学和加密函数

  • assert(bool condition):如果条件不满足,抛出异常
  • keccak256(…) returns (bytes32):使用以太坊的(Keccak-256)计算HASH值。常用来做字符串相等判别。

特殊变量及函数

  1. msg.sender(address) 当前调用发起人的地址
  2. msg.value(uint) 这个消息所附带的货币量,单位为wei
  3. tx.origin(address) 交易的发送者。不建议
  4. msg.data(bytes) 完整的调用数据(calldata)。
  5. now(uint) 当前块的时间戳。

时间单位

  • seconds、minutes、hours、days、weeks、years均可做为后缀,并进行相互转换,默认是seconds为单位。
  • 后缀不能用于变量
    时间单位

货币单位

  • 一个字面量的数字,可以使用后缀wei、finney、szabo或ether来在不同面额中转换
  • 不含任何后缀的默认单位是wei
    单位换算
    货币单位换算
    货币使用示例
    货币使用示例

实战:课程积分

项目需求

  • 为了活跃课程的气氛,促进同学之间的交流,决定建立课程积分体系:
  1. 每个同学都能领到100个课程积分
  2. 为别的同学答疑解惑,可以从对方那里获得一定的课程积分
  3. 课程结束后,积分最高的同学获得小红花

典型设计

基于sql数据库设计实现:

用户表(User)

  • userId: 用户id
  • email: 用户email

  • 用户积分表(UserCoin)
  • userId: 用户id
  • coin:积分数量

  • 用户积分交易表i(UserTrade)
  • userId: 发送方用户id
  • toUserId: 接收方用户id
  • coin:发送数量

不足

  1. 发放积分方式不透明,需要老师的信誉背书
  2. 积分交易数据不透明,可能出现刷分现象
  3. 服务器可能挂了…

Dapp设计的优势

  1. 去信任。代码透明,积分体系和发放形式完全公开,无需老师信用背书
  2. 去中心。数据透明,交易数据公共可读,无法随意篡改,公共监督。不用担心机器崩溃。
  3. 隐私保护。暂不需要。

Dapp数据结构设计

用状态变量实现数据结构
用户表(User)

struct User {
	address userId; // 用户id
}

User[] userList;

用户积分表(UserCoin)

struct UserCoin {
	address userId; //用户id
	uint coin; //积分数量
}

UserCoin[] userCoinList;

用户积分交易表(UserTrade)

struct UserTrade {
	address userId; // 发送方userid
	address toUserId; // 接收方userid
	uint coin; //发送数量
}

UserTrade[] userTradeList;

Dapp数据结构设计改进原则

  • 基本思路同“SQL数据库映射到KV数据库”
  • 根据查询/索引需求,在基本数据结构上,建立mapping
  • 为节省存储,去掉冗余数据

功能需求1

  1. 每个同学都能领100个课程积分
  2. 函数 getCoin()
  3. 功能:由合约为调用者发送100个积分
  4. 查询需求:只能领一次,需要根据合约和调用者地址,查询对应的交易记录。

功能需求2

  1. 同学之间可以互送积分
  2. 函数 sendCoin()
  3. 功能:调用者向一个地址发送积分
  4. 查询需求:一个用户地址拥有的积分

Dapp数据结构的改进

用户积分交易

struct UserTrade {
	address userId; // 发送方userid
	address toUserId; // 接收方userid
	uint coin; //发送数量
}

UserTrade[] userTradeList;

查询需求:根据“合约和调用者地址”,查询交易

mapping (address => mapping(address=>UserTrade[])) trans;

去除冗余数据

mapping (address => mapping(address=>uint[])) trans;

Dapp数据结构设计结果

用户

address[] userList;
mapping (address => uint8) userDict;

用户积分(address为key)

mapping (address => uint) balances;

用户积分交易表(address+address为key)

mapping (address => mapping (address => uint[])) trans;
发布了279 篇原创文章 · 获赞 169 · 访问量 32万+

猜你喜欢

转载自blog.csdn.net/ARPOSPF/article/details/104516643
今日推荐