lesson0 : Academy 学习
第一章 入门
一、hello world
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
contract HelloWeb3{
string public helloString = "Hello Web3!";
}
二、solidity类型
solidity中的变量类型
- 数值类型(Value Type):包括布尔型,整数型等等,这类变量赋值时候直接传递数值。
- 引用类型(Reference Type):包括数组和结构体,这类变量占空间大,赋值时候直接传递地址(类似指针)。
- 映射类型(Mapping Type): Solidity里的哈希表。
- 函数类型(Function Type):Solidity文档里把函数归到数值类型,但我觉得他跟其他类型差别很大,所以单独分一类。
数值类型
布尔型
布尔型是二值变量,取值为true或false。
整型
整型是solidity中的整数,最常用的包括
// 整型
int public _int = -1; // 整数,包括负数
uint public _uint = 1; // 正整数
uint256 public _number = 20220330; // 256位正整数
常用的整型运算符包括:
比较运算符(返回布尔值): <=, <, ==, !=, >=, >
算数运算符: +, -, 一元运算 -, +, *, /, %(取余),**(幂)
地址类型
地址类型(address)存储一个 20 字节的值(以太坊地址的大小)。地址类型也有成员变量,并作为所有合约的基础。有普通的地址和可以转账ETH的地址(payable)。payable的地址拥有balance和transfer()两个成员,方便查询ETH余额以及转账。
address payable addr;
addr.transfer(1);
表示合约向addr转账1wei
定长字节数组
字节数组bytes分两种,一种定长(byte, bytes8, bytes32),另一种不定长。定长属于数值类型,不定长属于引用类型
// 固定长度的字节数组
bytes32 public byte32Type = "MiniSolidity";
bytes1 public oneByte = byte32Type[0];
枚举 enum
枚举(enum)是solidity中用户定义的数据类型。它主要用于为uint分配名称,使程序易于阅读和维护。它与C语言中的enum类似,使用名称来代替从0开始的uint:
// 用enum将uint 0, 1, 2表示为Buy, Hold, Sell
enum ActionSet { Buy, Hold, Sell }
// 创建enum变量 action
ActionSet action = ActionSet.Sell;
它可以显式的和uint相互转换,并会检查转换的正整数是否在枚举的长度内,不然会报错:
// 打印上面的action变量
function enumToUint() external view returns(uint){
return uint(action);//输出2
}
enum的一个比较冷门的变量,几乎没什么人用。
引用类型
数组(array)和结构体(struct)
数组
- 固定长度数组(静态数组):
// 固定长度为2的静态数组定义
uint[2] fixedArray;
//定长数组实例化
fixedArray = [4, 6];
- 可变长度数组(动态数组):
//声明
uint[] dynamicArray;
//初始化,这里实例化一个长度为2的数组,值为0。
dynamicArray = new uint[](2);
//bytes比较特殊,是数组,但是不用加[]
bytes array7;
创建数组的规则
memory 修饰的动态数组,可以用new来创建,但必须声明长度,并且长度不可变。
函数类型
访问控制修饰符
- public: 内部外部均可见。(也可用于修饰状态变量,public变量会自动生成 getter函数,用于查询数值).
- private: 只能从本合约内部访问,继承的合约也不能用(也可用于修饰状态变量)。
- external: 只能从合约外部访问(但是可以用this.f()来调用,f是函数名)
- internal: 只能从合约内部访问,继承的合约可以用(也可用于修饰状态变量)。
Pure和View
使用 pure 跟 view 关键字的函数是不改写链上状态的,不消耗gas
以下语句被视为修改链上状态:
写入状态变量。
释放事件。
创建其他合同。
使用selfdestruct.
通过调用发送以太币。
调用任何未标记view或pure的函数。
使用低级调用(low-level calls)。
使用包含某些操作码的内联汇编。
pure
不能读取也不能写入存储在链上的状态变量,如:
// pure: 纯纯牛马 只修改入参或方法本身的变量,与全局变量无关
function addPure(uint256 _number) external pure returns(uint256 new_number){
return new_number = _number+1;
}
带有pure的函数也不能引用修改状态的方法
如果要在pure中进行修改合约状态,那么则会报错:(让你使用view或不加修饰)

view
比pure的权限要高一级,能读取但不能修改合约状态,如:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
contract HelloWeb3{
uint hello = 100;
// view: 能读取全局变量hello (看客)
function addPure(uint256 _number) external view returns(uint256 new_number){
_number = hello+1;
return _number;
}
}
internal 与 external
在部署的时候,无法直接调用合约的internal函数,必须使用external函数进行调用。
我们定义一个internal的minus()函数,每次调用使得number变量减1。由于是internal,只能由合约内部调用,而外部不能。因此,我们必须再定义一个external的minusCall()函数,来间接调用内部的minus()。 Example:

payable
当一个函数被 payable 修饰,表示调用这个函数时,可以附加发送一些ETH(当然也可以不发)。
没有加payable的函数,则没有方法接受 ETH, 附加ETH调用会出错。

函数返回
方法声明中使用 returns 进行声明返回的变量类型及变量名
方法体中使用 return 返回变量
// 返回多个变量
function returnMultiple() public pure returns(uint256, bool, uint256[3] memory){
return(1, true, [uint256(1),2,5]);
}
命名式返回
指在方法返回声明中添加需要返回的变量名,则在方法中末尾自动将这些变量名进行返回,不需要在方法体中显式声明
如上可改写为下方法,自动将三个变量return
// 命名式返回
function returnNamed() public pure returns(uint256 _number, bool _bool, uint256[3] memory _array){
_number = 2;
_bool = false;
_array = [uint256(3),2,1];
}
解构式赋值
可以在读取方法返回值时,部分读取或全部读取方法返回值。如:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract HelloWeb3{
//解构式赋值方式1,将函数返回值赋值给方法变量
function getValue1() public pure{
uint256 _number;
bool _bool;
uint256[3] memory _array;
(_number, _bool, _array) = returnNamed();
}
//解构式赋值2,将函数返回值赋值给方法变量,省略不需要的返回值
function getValue2() public pure{
bool _bool;
(, _bool, ) = returnNamed();
}
function returnNamed() public pure returns(uint256 _number, bool _bool, uint256[3] memory _array){
return(1, true, [uint256(1),2,5]);
}
}
变量数据存储和作用域
storage/memory/calldata
数组(array),结构体(struct)和映射(mapping),这类引用类型变量占空间大,赋值时候直接传递地址(类似指针)。由于这类变量比较复杂,占用存储空间大,我们在使用时必须要声明数据存储的位置。
storage,memory和calldata
solidity数据存储位置有三类:storage,memory和calldata。不同存储位置的gas成本不同。
storage类型的数据存在链上,类似计算机的硬盘,消耗gas多
memory和calldata类型的临时存在内存里,消耗gas少。大致用法:
1. storage:合约里的状态变量默认都是storage,存储在链上。
2. memory:函数里的参数和临时变量一般用memory,存储在内存中,不上链。
3. calldata:和memory类似,存储在内存中,不上链。(只读)。与memory的不同点在于calldata变量不能修改(immutable),一般用于函数的参数。例子:
function fCalldata(uint[] calldata _x) public pure returns(uint[] calldata){
//参数为calldata数组,不能被修改
// _x[0] = 0 //这样修改会报错
return(_x);
}
数据位置和赋值规则
1、合约的 storage 类型变量赋值给方法中的 storage 变量时,时引用赋值,如果改动,则都会改动。如下代码,调用modify方法时,会更改x 和 a 数组的第二个数:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract HelloWeb3{
event Log(uint[]);
uint[] public x = [1,2,3];
function modify() public {
uint[] storage a = x;
x[1] = 1;
emit Log(x);
emit Log(a);
}
}
日志打印
日志打印:
如果想要实现类似打印的功能,在合约中创建一个event,起名为 Log
在想要打印日志的地方调用事件 emit Log(...)如:
event log(int);
emit log();

2、storage 与 memory 相互赋值时,会创建独立副本,修改其中一个不会影响另一个。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract HelloWeb3{
event Log(uint[]);
uint[] public x = [1,2,3];
function modify() public {
uint[] memory a = x;
a[1] = 1;
emit Log(x);
emit Log(a);
}
}

3、memory赋值给memory,会创建引用,改变新变量会影响原变量。

4、变量赋值给storage,会创建独立的复本,修改其中一个不会影响另一个。
变量作用域
1、全局变量:solidity预留的关键字,可以不声明直接使用。如下:
function global() external{
address sender = msg.sender;
uint blockNum = block.number;
bytes memory data = msg.data;
}
2、状态变量:声明在合约内,函数外。(类似于java的全局变量)gas消耗高。存储在链上。
3、局部变量:晟敏在方法中,存储在内存中,不上链,gas消耗低。
一些预置的全局变量:链接
| 方法 | 说明 | 类型 |
|---|---|---|
| blockhash(uint blockNumber) | 给定区块的哈希值 – 只适用于256最近区块, 不包含当前区块。 | bytes32 |
| block.coinbase | 当前区块矿工的地址 | address payable |
| block.gaslimit | 当前区块的gaslimit | uint |
| block.number | 当前区块的number | uint |
| block.timestamp | 当前区块的时间戳,为unix纪元以来的秒 | uint |
| gasleft() | 剩余 gas | uint256 |
| msg.data | 完整call data | bytes calldata |
| msg.sender | 消息发送者 (当前 caller) | address payable |
| msg.sig | calldata的前四个字节 (function identifier) | bytes4 |
| msg.value | 当前交易发送的wei值 | uint |

浙公网安备 33010602011771号