区块链(二)
Solidity编程基础
合约
简单的合约
在这里我们先看一个合约的例子
这是一份名为 SimpleStorage 的合约。
第一行是 pragma 指令,它告诉我们源代码是为 Solidity version 0.4.0 及以上版本编写的,但不包括 version 0.6.0 及以上版本。
其实还有一种语法是 pragram solidity ^0.4.2 , 它告诉我们源代码是为 Solidity version 0.4.2 及以上版本编写的,但是小于0.5.0的版本,因为我们知道这个程序能在 0.5.0 之前编译通过。
我们接下来要开发一个类似于QQ宠物的游戏,这个游戏将运行在去中心化的以太坊上,不用担心第三方服务器关停的问题。
为了孵化我们的宠物,我们先建立一个名为 PetIncubator 的基础合约,作为玩家的宠物孵化器。
以下是你要开始创建区块链宠物游戏的第一个练习:
运用我们讲解的 pragram solidity ^x.x.x 语法,在右边的输入框里确定我们的合约将是基于 0.4.19以上的版本。
建立一个名为 PetIncubator 的空合约。
整型、算数运算符和状态变量
整型
int 为有符号整数,uint 为无符号整数。
int8 – int256:8位到256位的带符号整型数。int256 与 int 相同。(包括int8, int16, int32, int64,…, int256)
uint8 – uint256:8位到256位的无符号整型。uint256 和 uint 是一样的。(包括uint8, uint16, uint32, uint64,…, uint256)
状态变量
Solidity 支持三种类型的变量:状态变量、局部变量和全局变量。目前我们先学习状态变量。状态变量的变量值永久保存在合约存储空间中,也就是说它们被写入区块链中。可以想象成写入一个数据库。
我们宠物的DNA由十九位数字组成,定义 dnaDigits 为 uint 数据类型,并赋值 19。
算数运算符
让我们看一个简单的表达式:
4 + 5 = 9
这里 4 和 5 称为操作数,+ 称为运算符。Solidity 支持以下类型的运算符:
算术运算符
比较运算符
逻辑(或关系)运算符
赋值运算符
条件(或三元)运算符
假设变量A的值为10,变量B的值为20。
求和: + (加) 例: A + B = 30
相减: – (减) 例: A – B = -10
相乘: * (乘) 例: A * B = 200
相除: / (除) 例: B / A = 2
取模运算: % (取模) 例: B % A = 0
递增:++ (递增) 例: A++ = 11
递减:-- (递减) 例: A-- = 9
Solidity 还支持 乘方操作 (如:A 的 B 次方) // 例如: 5 ** 2 = 25
uint x = 5 ** 2; // equal to 5^2 = 25
为了保证我们宠物的DNA只含有19个字符,我们先造一个 uint 数据,让它等于10^19。这样一来以后我们可以用取模 % 把一个整数变成19位。
建立一个 uint 类型的变量,名字叫 dnaModulus, 令其等于 10 的 dnaDigits 次方。
结构体、字符串和数组
结构体
类似于C语言,Solidity 也有结构体(struct)类型。结构体允许你生成一个更复杂的数据类型,它有多个属性。结构体是引用类型。
例如,一本书的信息:
Title
Author
Subject
Book ID
就可以用结构体来表示
struct Book { string title; string author; uint book_id; }
字符串
在上面的例子中你会发现一个新的类型 string。
Solidity 中,字符串值使用双引号(“)和单引号(‘)包括,字符串类型用 string 表示。字符串是特殊的数组,是引用类型。
string data = "test";
在上面的例子中,"test" 是一个字符串,data 是一个字符串变量。
我们的每个宠物都会拥有多个属性。
1.建立一个 struct 命名为 Pet。
2.我们的 Pet 结构体有两个属性, name (类型为 string), 和 dna (类型为 uint)。
数组
如果你想建立一个集合,可以用数组这样的数据类型。数组是一种数据结构,它是存储同类元素的有序集合。
数组中的特定元素由索引访问,索引值从0开始。例如,声明一个数组变量,如 numbers,可以使用 numbers[0] 、numbers[1] … 和 numbers[99] 表示单个变量。
Solidity 支持两种数组:静态数组和动态数组。
// 固定长度为2的静态数组:
uint[2] fixedArray;
// 动态数组,长度不固定,可以动态添加元素:
uint[] dynamicArray;
你也可以建立一个结构体类型的数组。例如,上一节提到的 Book:
Book[] book; // 这是动态数组,我们可以不断添加元素
另外,你可以定义 public 数组, 语法如下:
Person[] public people;
其它的合约可以从这个数组读取数据(但不能写入数据),所以这在合约中是一个有用的保存公共数据的模式。
为了把所以的宠物保存在我们的APP里,并且能够让其它APP看到这些宠物,我们需要一个公共数组。
1.创建一个数据类型为 Pet 的结构体数组,用 public 修饰,命名为:pets 。
函数
函数的定义
Solidity中, 定义函数的语法如下:
function function-name(parameter-list) scope returns() { //语句 }
函数由关键字 function 声明,后面跟函数名、参数、可见性、返回值的定义。但不是每一个函数都需要这些定义。
下面的例子,定义一个函数内部为空,名为 testResult 的函数,该函数接受两个参数:一个 string 类型的和一个 uint 类型的。
习惯上函数里的变量都是以 “_” 开头 (但不是硬性规定) 以区别全局变量。我们整个教程都会沿用这个习惯。
我们的函数使用如下:
testResult("Jason", 95);
私有/公共函数
Solidity 定义的函数的属性默认为公共。 这就意味着任何一方 (或其它合约) 都可以调用你合约里的函数。
显然,不是什么时候都需要这样,而且这样的合约易于受到攻击。 所以将自己的函数定义为私有是一个好的编程习惯,只有当你需要外部世界调用它时才将它设置为公共。
可以看到,在函数名字后面使用关键字 private 即可。和函数的参数类似,私有函数的名字用 “_” 起始。
在我们的APP里,我们要能孵化宠物,同时要避免任何人都可以去创建宠物。让我们来写一个私有函数。
建立一个函数内部为空且接受两个参数的私有函数 _createPet, 它的两个参数分别是:_name (类型为 string), 和 _dna (类型为uint)。
数组成员push
在前面的学习里我们了解到Solidity支持两种数组:静态数组和动态数组。
动态数组有一个名为 push 的成员函数,可用于在数组末尾追加一个元素,函数返回新的长度
例如:
uint[] numbers; numbers.push(5); numbers.push(10); numbers.push(15); // numbers 现在为 [5, 10, 15]
构建新的结构体
struct Book { string title; string author; uint book_id; }
这是之前关卡例子中的 Book 结构,现在我们学习创建新的 Book 结构,然后把它加入到名为 books 的数组中。
你也可以两步并一步,用一行代码更简洁:
books.push(Book("Walden", "Henry David Thoreau", 1619493918);
经过学习,我们也可以在 _createPet 的函数里创建新的结构体。
在函数体里创建一个 Pet, 然后把它加入到 pets 的数组中。新创建的宠物的 name 和 dna 来自于函数的参数。我们用一行代码来完成。
返回值
function function-name(parameter-list) scope returns() { //语句
经过上面关卡,我们对关键字 function 声明,函数名、参数、可见性应该有了相应的理解。现在我们来学习返回值。
在Solidity里,如果想让函数返回一个数值,要在函数的定义里包含返回值的数据类型。如以下例子中的 string:
另外,在Solidity中函数可以返回多个值。
函数的修饰符
在上面涉及函数的关卡里,我们学习了函数的语法,函数可见范围的设定等,接下来我们来学习一下影响函数行为的关键词 view 和 pure。
当函数被标为 view 时,它承诺不会对任何状态进行修改。它只能读取数据不能更改数据。
当函数被标为 pure 时,它表示这个函数不会读取或者更改数据。这样的函数返回值完全取决于它的输入参数。
当我们有了孵化宠物的合约,定义不同宠物属性的结构体,在应用里存储宠物的数组和生成宠物的函数。现在我们需要一个帮助型的函数,可以根据一个字符串随机生成一个DNA,同时确保新的宠物DNA为19位。
1.创建一个 private 函数,命名为 _generateRandomDna。它只接收一个类型为 string 的输入变量 _str , 返回一个 uint 类型的数值。
2.此函数只读取我们合约中的一些变量,所以标记为 view。
类型转换
加密函数
Solidity 提供了一些常用的加密函数。本次我们将重点介绍散列函数keccak256。一个散列函数基本上就是把一个字符串转换为一个256位的16进制数字。字符串的一个微小变化会引起散列数据极大变化。
例如:
//6e91ec6b618bb462a4a6ee5aa2cb0e9cf30f7a052bb467b0ba58b8748c00d2e5
keccak256("aaaab");
//b1f078126895a1424524de5321b339ab00408010b7cf0e6ed451514981e58aa9
keccak256("aaaac");
现在我们只是用它来造伪随机数,帮助生成随机的DNA。
类型转换
Solidity允许类型之间进行隐式转换和显式转换。
隐式转换时必须符合一定条件,不能导致信息丢失。例如,uint8 可以转换为 uint16 ,但是 int8 不可以转换为 uint256 ,因为 int8 可以包含 uint256 中不允许的负值。
实战演习
1.完善_generateRandomDna函数 ,第一行代码取 _str 的 keccak256 散列值生成一个伪随机十六进制数,类型转换为 uint, 最后保存在类型为 uint 名为 rand 的变量中。注意这里要有类型转换。
2.完善_generateRandomDna函数,因为我们只想让我们的DNA的长度为19位。所以第二行代码应该 return 上面计算的数值对 dnaModulus 求余数(%)。
3.创建一个 public 函数,命名为 createRandomPet. 它将被传入一个变量 _name (数据类型是 string)。
4.函数的第一行应该调用 _generateRandomDna 函数,传入 _name 参数, 结果保存在一个类型为 uint 的变量里,命名为 randDna。
5.第二行调用 _createPet 函数, 传入参数: _name 和 randDna。
事件
事件是智能合约发出的“信号”。智能合约的前端UI,例如,DApps、web.js,或者任何与Ethereum JSON-RPC API连接的东西,都可以侦听这些事件。事件可以被索引,以便以后可以搜索事件记录。
当每次宠物被孵化出来的时候,我们的前端需要能监听到这个事件,并将它显示出来。
定义一个 事件 叫做 NewPet。 它有3个参数: petId (uint), name (string), 和 dna (uint)。
修改 _createPet 函数使得当新宠物孵化出来并加入pets数组后,生成事件 NewPet。
需要定义宠物的索引。在Solidity 0.4.x 和 0.5.x 版本中, array.push() 返回动态数组的新长度, 返回值类型是 uint。数组的第一个元素的索引是 0, 所以array.push() - 1 是我们新加入的宠物的索引值(id)。 我们需要修改那行代码获得宠物索引,将pets.push() - 1 作为索引值存在变量id中,数据类型是 uint。在下一行中你可以把它用到 NewPet 事件中。
地址类型、映射和全局变量
地址类型 Address
如果你在以太坊区块链或者智谷CTIS区块链上创建过账户并完成过转账,那么你对地址应该一点也不陌生。
地址类型表示以太坊地址,长度为20字节。地址可以使用 .balance 方法获得余额,也可以使用 .transfer 方法将余额转到另一个地址。
现在你只需要了解地址属于特定用户(或智能合约)。
address myAddress = 0xCf19b00EF0B330b562faE68BC92a88dAB55ca2cB;
映射 Mapping
与数组和结构体一样,映射也是引用类型。下面是声明映射类型的语法。
mapping(_KeyType => _ValueType)
_KeyType 可以是任何类型,但不允许是映射类型。
_ValueType 可以是任何类型。
//对于金融应用程序,将用户的余额保存在一个 uint类型的变量中: mapping (address => uint) public accountBalance; //或者可以用来通过userId 存储/查找的用户名 mapping (uint => string) userIdToName;
在第一个例子中,键是一个 address,值是一个 uint,在第二个例子中,键是一个uint,值是一个 string。
我们可以指定“地址”作为宠物主人的 ID。当用户通过与我们的应用程序交互来创建新的宠物时,新宠物的所有权被设置到调用者的地址下。
我们通过给数据库中的宠物指定“主人”, 来支持“多玩家”模式。
为了存储宠物的所有权,我们会使用到两个映射:
一个记录宠物拥有者的地址。创建一个属性为 public 的 petToOwner 的映射。键是一个 uint,值是一个 address。
一个记录某地址所拥有宠物的数量。创建一个名为 ownerPetCount的映射。键是一个address,值是一个 uint。
mapping(键 =>值) 映射名
全局变量 Msg.sender
在 Solidity 中,有 block、msg 和 tx 为对象的全局变量。本关卡我们将重点介绍可以被所有函数调用的全局变量 msg.sender。
msg.sender 代表发起合约调用的用户钱包地址 address。
注意:除非有人调用其中的函数,否则合约在区块链上什么也不做,但是调用合约的用户地址msg.sender总是存在的。
使用 mapping 和 msg.sender
在这例子中,任何人都可以调用 setMyNumber 在我们的合约中存下一个 uint 并且与他们的地址相绑定。 然后,他们调用 whatIsMyNumber 就会返回他们存储的 uint。
我们已经有了一套映射来记录宠物的所有权了,我们可以修改 _createPet 方法,将宠物分配给函数调用者。
我们得到了新的宠物 id 后,更新 petToOwner 映射,在 id 对应的键值里存入 msg.sender。
我们为这个 msg.sender 名下的 ownerPetCount 加 1。
请参考算数运算符,用 ++ 来使 uint 递增。
本文来自博客园,作者:Rescal_子轩,转载请注明原文链接:https://www.cnblogs.com/zx-demo/p/17456128.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理