# solidity学习笔记 **Published by:** [dami.eth](https://paragraph.com/@yidakoumi/) **Published on:** 2022-05-07 **URL:** https://paragraph.com/@yidakoumi/solidity ## Content 前言本学习笔记源于以下网址,对于想快速学习 solidity 语言的朋友,推荐看看: https://cryptozombies.io/zh/ 以下内容,有部分是直接从网站上整理的个人认为关键的地方,而加粗的文字,均为笔者的思考,可以选择性阅读~ PS:笔记的目的,主要是为了便于后续的学习回顾查找,还有可以加深一遍自己的理解。lesson 1 笔记版本指令所有的 Solidity 源码都必须在开头处标明 Solidity 编译器的版本. 以避免将来新的编译器可能不兼容你的代码。 代码示例:pragma solidity ^0.8.0; 上述代码限定了当前的 solidity 编译器的版本必须为指定的 0.8.0,不能使用其它版本的编译器,比如像 Java ,1.8 语法是能兼容 1.6 的,但 1.6 的 jdk 却用不了 Java8 的语法。合约Solidity 的代码都包裹在合约里面. 一份合约就是以太应币应用的基本模块, 所有的变量和函数都属于一份合约, 它是你所有应用的起点。 代码实例:pragma solidity ^0.8.0; contract HelloWorld { } 关键字 contract ,类似于 Java 的 Class 类,但是这里理解成类的概念,还不是很好,因为在 solidity 里,还有一个关键字,叫 【struct】,这个关键字给我的感觉更像是和类对应的。但 Java 里没有 Class 的类语法,你也没有办法往下写代码的对吧。。状态变量状态变量是被永久地保存在合约中。也就是说它们被写入以太币区块链中. 想象成写入一个数据库。 无符号整数: uint uint 无符号数据类型, 指其值不能是负数,对于有符号的整数存在名为 int 的数据类型。注: Solidity中, uint 实际上是 uint256代名词, 一个256位的无符号整数。你也可以定义位数少的uints — uint8, uint16, uint32, 等…… 但一般来讲你愿意使用简单的 uint, 除非在某些特殊情况下。代码示例:contract Example { // 这个无符号整数将会永久的被保存在区块链中 uint myUnsignedInteger = 100; } 这个写法有点类似 C 语言,但把关键字 uint 精简了, uint 默认是 256 位的,后续会有详细的笔记介绍,如何选择 uint 后面的指定单位。数学运算加法: x + y减法: x - y,乘法: x * y除法: x / y取模 / 求余: x % y (例如, 13 % 5 余 3, 因为13除以5,余3)Solidity 还支持 乘方操作 (如:x 的 y次方) // 例如: 5 ** 2 = 25 代码示例:uint x = 5 ** 2; // equal to 5^2 = 25 solidity基础的计算语法倒是和主流的高级语言类似,尤其是 Python。举个例子,比如我想截取一个数字,如3438141312的后3位,也就是312,用计算语法怎么写出来,而不是用字符串的形式。 代码示例:uint x = 3438141312 % 1000; // equal to 312 Python也是一样的:python示例结构体Solidity 提供了 结构体,结构体允许你生成一个更复杂的数据类型,它有多个属性。注:我们刚刚引进了一个新类型, string。 字符串用于保存任意长度的 UTF-8 编码数据。 如: string greeting = "Hello world!"。代码示例:contract HelloWorld { struct Person { uint age; string name; } } 上面提到过的,struct,从抽象层次来理解,这个关键字才更像是 Java 的类的概念。也就是对象。因为你的属性都放在了 struct 里,而不是合约 HelloWorld 里。除此之外,引入新的关键词 string ,注意,是小写。数组如果你想建立一个集合,可以用【数组】这样的数据类型. Solidity 支持两种数组: 【静态数组】 和【动态数组】:// 固定长度为2的静态数组: uint[2] fixedArray; // 固定长度为5的string类型的静态数组: string[5] stringArray; // 动态数组,长度不固定,可以动态添加元素: uint[] dynamicArray; // 这是动态数组,我们可以不断添加元素 Person[] people; 记住:状态变量被永久保存在区块链中。所以在你的合约中创建动态数组来保存成结构的数据是非常有意义的。(这个是课程的,觉得非常有用,加粗了)数组的使用:// 创建一个新的Person: Person satoshi = Person(172, "Satoshi"); // 将新创建的satoshi添加进people数组: people.push(satoshi); 和 Java 不同的是,创建数组可以直接在[]里定义有限长度的数组,动态的就不要写具体的数字长度了,而结构体也是可以被作为数组类型去创建的,可以理解为 Java 里的 List(用的是 List ,而没用数组表示,更容易被大家直观接受)。而关键词也不一样,用的 push(这个是我的个人思考) 公共数组 你可以定义 public 数组, Solidity 会自动创建 getter 方法. 语法如下: Person[] public people; 其它的合约可以从这个数组读取数据(但不能写入数据),所以这在合约中是一个有用的保存公共数据的模式。(这个是课程的,觉得非常有用,加粗了) 定义函数 这是一个名为 eatHamburgers 的函数,它接受两个参数:一个 string类型的 和 一个 uint类型的。现在函数内部还是空的。 代码示例: function eatHamburgers(string _name, uint _amount) { } 定义函数的语法和 Python 如出一辙,都是使用 function 作为关键字来标识,入参的变量名字,代码规范是带上下划线,以区分全局变量。 函数访问权限关键词 Solidity 定义的函数的属性默认为公共。 这就意味着任何一方 (或其它合约) 都可以调用你合约里的函数。显然,不是什么时候都需要这样,而且这样的合约易于受到攻击。 所以将自己的函数定义为私有是一个好的编程习惯,只有当你需要外部世界调用它时才将它设置为公共。 代码示例: uint[] numbers; //设置为私有的函数 function _addToArray(uint _number) private { numbers.push(_number); } 这意味着只有我们合约中的其它函数才能够调用这个函数,给 numbers 数组添加新成员。 可以看到,在函数名字后面使用关键字 private 即可。和函数的参数类似,私有函数的名字用(_)起始。 返回值 代码示例: string greeting = "What's up dog"; // Solidity 里,函数的定义里可包含返回值的数据类型(如本例中 string)。 function sayHello() public returns (string) { return greeting; } 说实话,solidity这种设计风格看起来就很奇怪….返回值的关键字放在权限后面可以理解…但返回的写法还要多个小括号,看着和需要传参似的….而且用的是 returns ,复数的形式…不能李姐啊!!!总之,多写,多看,多记,可能习惯了将就好了….这个真没有 Java 或者 Python 看着舒服…. 函数的修饰符 如果你的函数没有改变任何值或者写任何东西。 这种情况下我们可以把函数定义为 view, 意味着它只能读取数据不能更改数据: 代码示例: function sayHello() public view returns (string) { Solidity 还支持 pure 函数, 表明这个函数甚至都不访问应用里的数据,例如: 代码示例: function _multiply(uint a, uint b) private pure returns (uint) { return a * b; } 这个函数甚至都不读取应用里的状态 — 它的返回值完全取决于它的输入参数,在这种情况下我们把函数定义为 pure. 注:可能很难记住何时把函数标记为 pure/view。 幸运的是, Solidity 编辑器会给出提示,提醒你使用这些修饰符。 Solidity 里的函数是有状态的,你可以理解为你的代码有没有对数据进行操作。因为在以太坊上进行交互,每次写数据的操作,都需要花费真金白银…所以有了状态设计…像一些读操作的函数,就可以节省下金钱啊!!! solidity的代码,所有的数据大小,读写操作,都会和真金白银挂钩,所以,写 solidity 才是真正考验思维逻辑的语言….毕竟不同写法,可能节省的金钱真的天壤之别! 至于 view 、pure 的具体用法,后面的笔记里,还会有更详细的补充….在第一课的笔记里就先作为了解吧。 类型转换 有时你需要变换数据类型。例如: uint8 a = 5; uint b = 6; // 将会抛出错误,因为 a * b 返回 uint, 而不是 uint8: uint8 c = a * b; // 我们需要将 b 转换为 uint8: uint8 c = a * uint8(b); 上面, a * b 返回类型是 uint, 但是当我们尝试用 uint8 类型接收时, 就会造成潜在的错误。如果把它的数据类型转换为 uint8, 就可以了,编译器也不会出错。 类型转化没啥可说的,基本上就是高级语言的强制转化的写法。 事件 事件 是合约和区块链通讯的一种机制。你的前端应用“监听”某些事件,并做出反应。 代码示例: // 这里建立事件 event IntegersAdded(uint x, uint y, uint result); function add(uint _x, uint _y) public { uint result = _x + _y; //触发事件,通知app IntegersAdded(_x, _y, result); return result; } 你的 app 前端可以监听这个事件。JavaScript 实现如下: YourContract.IntegersAdded(function(error, result) { // 干些事 }) 事件这个动作,是合约和各类 web3 库实现的交互关键字。当你的合约部署到链上的时候,你通过 js 也好,python 也好,都有这样一个 web3 的类库,通过 web3 的库,可以从链上读取到你部署的合约,在用该合约进行你所创建的事件进行交互。 以客户端,服务端的架构理解的话,你可以理解为事件就是服务端主动触发的一个动作,这里的服务端可以理解为链上的合约。客户端就是你前端的代码。 以上,第一课的笔记告离段落….. ## Publication Information - [dami.eth](https://paragraph.com/@yidakoumi/): Publication homepage - [All Posts](https://paragraph.com/@yidakoumi/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@yidakoumi): Subscribe to updates - [Twitter](https://twitter.com/jacksu15ice): Follow on Twitter