# 一、数据类型

## 1.1、值类型

### 1.1.1、布尔

``````pragma solidity ^0.4.25;

contract TestBool {
bool flag;
int num1 = 100;
int num2 = 200;
// default false
function getFlag() public view returns(bool) {
return flag;  // false
}
// 非
function getFlag2() public view returns(bool) {
return !flag;  // true
}
// 与
function getFlagAnd() public view returns(bool) {
return (num1 != num2) && !flag;  // true
}
// 或
function getFlagOr() public view returns(bool) {
return (num1 == num2) || !flag;  // true
}
}
``````

### 1.1.2、整数

``````pragma solidity ^0.4.25;

// 整型特性与运算
contract TestInteger {
int num1; // 有符号整型 int256
uint num2; // 无符号整型 uint256

function add(uint _a, uint _b) public pure returns(uint) {
return _a + _b;
}
function sub(uint _a, uint _b) public pure returns(uint) {
return _a - _b;
}
function mul(uint _a, uint _b) public pure returns(uint) {
return _a * _b;
}
function div(uint _a, uint _b) public pure returns(uint) {
return _a / _b;  // 在solidity中，除法是做整除，没有小数点
}
function rem(uint _a, uint _b) public pure returns(uint) {
return _a % _b;
}
function square(uint _a, uint _b) public pure returns(uint) {
return _a ** _b;  // 幂运算在0.8.0之后，变为右优先，即a ** b ** c => a ** (b ** c)
}
function max() public view returns(uint) {
return uint(-1);
// return type(uint).max;  // 0.8不再允许uint(-1)
}
}
``````

``````pragma solidity ^0.4.25;

// 位运算
contract TestBitwise {
uint8 num1 = 3;
uint8 num2 = 4;

function bitAdd() public view returns(uint) {
return num1 & num2;
}
function bitOr() public view returns(uint) {
return num1 | num2;
}
function unBit() public view returns(uint) {
return ~num1;
}
function bitXor() public view returns(uint) {
return num1 ^ num2;
}
function bitRight() public view returns(uint) {
return num1 >> 1;
}
function bitLeft() public view returns(uint) {
return num1 << 1;
}
}
``````

### 1.1.3、定长浮点型

``````fixed num; // 有符号
ufixed num; // 无符号
``````

### 1.1.4、地址类型

``````address addr = msg.sender;
``````

### 1.1.5、合约类型

``````TestBitwise t = TestBitwise(addr);
``````

### 1.1.6、枚举类型

``````enum ActionChoices { Up, Down, Left, Right }
``````

### 1.1.7、定长字节数组

``````pragma solidity ^0.4.25;

// 固定长度的字节数组（静态），以及转换为string类型
contract TestBytesFixed {
// public 自动生成同名的get方法
bytes1 public num1 = 0x7a;  // 1 byte = 8 bit
bytes1 public num2 = 0x68;
bytes2 public num3 = 0x128b;  // 2 byte = 16 bit
// 获取字节数组长度
function getLength() public view returns(uint) {
return num3.length; // 2
}
// 字节数组比较
function compare() public view returns(bool) {
return num1 < num2;
}
// 字节数组位运算
function bitwise() public view returns(bytes1,bytes1,bytes1) {  // 多返回值
return ((num1 & num2), (num1 | num2), (~num1));
}

// 先转为bytes动态数组，再通过string构造  0x7a7a -> zz
function toString(bytes2 _val) public pure returns(string) {
bytes memory buf = new bytes(_val.length);
for (uint i = 0; i < _val.length; i++) {
buf[i] = _val[i];
}
return string(buf);
}
}
``````

``````pragma solidity ^0.4.25;

// 固定长度的字节数组（静态）扩容和压缩
contract TestBytesExpander {
// public 自动生成同名的get方法
bytes6 name = 0x796f7269636b;

function changeTo1() public view returns(bytes1) {
return bytes1(name); // 0x79
}
function changeTo2() public view returns(bytes2) {
return bytes2(name); // 0x796f
}
function changeTo16() public view returns(bytes16) {
return bytes16(name); // 0x796f7269636b00000000000000000000
}
}
``````

### 1.1.8、函数类型

``````function (<parameter types>) {internal|external|public|private} [pure|constant|view|payable] [returns (<return types>)]
``````

## 1.2、引用类型

### 1.2.1、字符串

``````pragma solidity ^0.4.25;

// 修改string类型的数据
contract TestString {
string name = 'yorick';  // 字符串可以使用单引号或者双引号
string name2 = "!@#\$%^&";  // 特殊字符占1个byte
string name3 = "张三";  // 中文在string中使用utf8的编码方式存储，占用3个byte

function getLength() view public returns(uint) {
// 不可以直接获取string的length
return bytes(name).length; // 6
}
function getLength2() view public returns(uint) {
return bytes(name2).length; // 7
}
function getLength3() view public returns(uint) {
return bytes(name3).length; // 6
}

function getElmName() view public returns(bytes1) {
// 不可以直接通过数组下标name[0]获取
return bytes(name)[0]; // 0x79
}
function changeElmName() public {
bytes(name)[0] = "h";
}
function getName() view public returns(bytes) {
return bytes(name);
}
}
``````

### 1.2.2、变长字节数组

``````pragma solidity ^0.4.25;

// 动态的字节数组，以及转换为string类型
contract TestBytesDynamic {
bytes public dynamicBytes;

function setDynamicBytes(string memory val) public {
dynamicBytes = bytes(val);
}
function getVal() public view returns(string){
return string(dynamicBytes);
}
}
``````

### 1.2.3、数组

``````pragma solidity ^0.4.25;

// 定长数组
contract TestArrFixed {

uint[5] arr = [1,2,3,4,5];

// 修改数组元素内容
function modifyElements() public {
arr[0] = 12;
arr[1] = 14;
}
// 查看数组
function watchArr() public view returns(uint[5]) {
return arr;
}
// 数组元素求和计算
function sumArr() public view returns(uint) {
uint sum = 0;
for (uint i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}

// 数组长度
function getLength() public view returns(uint) {
return arr.length;
}

// delete重置数组某下标的元素值，不会真正删除该元素
function deleteElm(uint idx) public {
delete arr[idx];
}
// delete重置整个数组
function deleteArr() public {
delete arr;
}

/**  定长数组不允许改变长度和push
// 压缩数组后，右侧多余元素被丢弃
function changeLengthTo1() public {
arr.length = 1;
}
// 扩容数组后，右侧元素补0
function changeLengthTo10() public {
arr.length = 10;
}
// 追加新元素
function pushElm(uint _elm) public {
arr.push(_elm);
}
*/
}
``````

``````pragma solidity ^0.4.25;

// 变长数组
contract TestArrDynamic {

uint[] arr = [1,2,3,4,5];

// 查看数组
function watchArr() public view returns(uint[]) {
return arr;
}
// 数组长度
function getLength() public view returns(uint) {
return arr.length;
}
// 压缩数组后，右侧多余元素被丢弃
function changeLengthTo1() public {
arr.length = 1;
}
// 扩容数组后，右侧元素补0
function changeLengthTo10() public {
arr.length = 10;
}
// 追加新元素
function pushElm(uint _elm) public {
arr.push(_elm);
}
// delete重置数组某下标的元素值，不会真正删除该元素
function deleteElm(uint idx) public {
delete arr[idx];
}
// delete重置整个数组
function deleteArr() public {
delete arr;
}
}
``````

``````pragma solidity ^0.4.25;

/**

solidity的二维数组与其他语言不同，[2] [3]表示3行2列，而其他语言为2行3列；

*/
contract TestArr2Dimensional {
uint[2][3] arr = [[1,2],[3,4],[5,6]];
function getRowSize() public view returns(uint) {
return arr.length; // 3
}
function getColSize() public view returns(uint) {
return arr[0].length; // 2
}
function watchArr() public view returns(uint[2][3]) {
return arr;  // 1,2,3,4,5,6
}
function sumArr() public view returns(uint) {
uint sum = 0;
for (uint i = 0; i < getRowSize(); i++) {
for (uint j = 0; j < getColSize(); j++) {
sum += arr[i][j];
}
}
return sum;
}
function modifyArr() public {
arr[0][0] = 99;
}
}
``````

``````pragma solidity ^0.4.25;

// 数组字面值
contract TestArrLiteral {
// 最小存储匹配，未超过255，所以使用uint8存储
function getLiteral8() pure public returns(uint8[3]) {
return [1,2,3];
}
// 超过255，所以使用uint16存储
function getLiteral16() pure public returns(uint16[3]) {
return [256,2,3];  // [255,2,3] 不被uint16允许
}
// 强制转换为uint256
function getLiteral256() pure public returns(uint[3]) {
return [uint(1),2,3];  // 给任意元素强转即可，否则不被允许
}
// 计算外界传入的内容
function addLiterals(uint[3] arr) pure external returns(uint) {
uint sum = 0;
for (uint i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
}
``````

### 1.2.4、结构体

``````pragma solidity ^0.4.25;

// 结构体初始化的两种方法
contract TestStruct {
struct Student {
uint id;
string name;
mapping(uint=>string) map;
}

// 默认为storage类型，只能通过storage类型操作结构体中的mapping类型数据
Student storageStu;

// mapping类型可以不用在定义的时候赋值，而其他类型不赋值则会报错
function init() public pure returns(uint, string) {
Student memory stu = Student(100, "Jay");
return (stu.id, stu.name);
}
function init2() public pure returns(uint, string) {
Student memory stu = Student({name: "Jay", id: 100});
return (stu.id, stu.name);
}
function init3() public returns(uint, string, string) {
Student memory stu = Student({name: "Jay", id: 100});
// 直接操作结构体中的mapping不被允许: Student memory out of storage
// stu.map[1] = "artist";
// 通过storage类型的变量操作结构体中的mapping
storageStu = stu;
storageStu.map[1] = "artist";
return (storageStu.id, storageStu.name, storageStu.map[1]);
}

// 结构体作为入参时，为memory类型，并且不能使用public或external修饰函数：Internal or recursive type is not allowed for public or external functions.
// 赋值时也要指定为memory，否则报错
function testIn(Student stu) internal returns(uint) {
return stu.id;
}
// 结构体作为出参，同样只能private或internal声明内部使用
function testOut(Student stu) private returns(Student) {
return stu;
}
}
``````

## 1.3、映射

``````contract TestMapping {
mapping(address => uint) private scores;  // <学生,分数>的单层映射
mapping(address => mapping(bytes32 => uint8)) private _scores;  // <学生,<科目,分数>>的两层映射
function getScore() public view returns(address, uint) {
}
function setScore() public {
scores[msg.sender] = 100;
}
}
``````

# 二、作用域（访问修饰符）

``````contract TestAccessCtrl {
constructor () public {}
uint public num1 = 1;  // 自动为public生成同名的get函数，但在编码时不可直接调用num1()
uint private num2 = 2;
uint num3 = 3;  // 不写则默认private
function funcPublic() public returns(string) {
return "public func";
}
function funcPrivate() private returns(string) {
return "private func";
}
function funcInternal() internal returns(string) {
return "internal func";
}
function funcExternal() external returns(string) {
return "external func";
}
function test1(uint choice) public returns(string) {
if (choice == 1) return funcPublic();
if (choice == 2) return funcPrivate();
if (choice == 3) return funcInternal();
//if (choice == 4) return funcExternal();  // external不允许直接在内部用
if (choice == 4) return this.funcExternal();  // 间接通过this才可以调用external
}
}
contract TestAccessCtrlSon is TestAccessCtrl {
function test2(uint choice) public returns(string) {
if (choice == 1) return funcPublic();  // public允许派生合约使用
//if (choice == 2) return funcPrivate();  // private不允许派生合约使用
if (choice == 3) return funcInternal();  // internal允许派生合约使用
//if (choice == 4) return funcExternal();  // external也不允许派生合约直接使用
}
}
contract TestAccessCtrl2 {
function test2(uint choice) public returns(string) {
TestAccessCtrl obj = new TestAccessCtrl();
return obj.funcExternal();  // external只允许在外部合约中这样间接调用
}
}
``````

# 三、函数修饰符

``````contract TestFuncDecorator {
uint public num = 1;
/// pure
function testPure(uint _num) public pure {
//uint num1 = num;  // pure不允许读状态变量
//num = _num;  // pure不允许修改状态变量
}
/// view
function testView(uint _num) public view {
uint num1 = num;  // 允许读状态变量
num = _num;  // 0.4语法上允许修改状态变量，但实际不会修改，所以num还是1
// 0.5及之后不允许在view中这样修改，否则编译不通过
}
/// payable
function () public payable {}
function getBalance() public view returns(uint) {  // balance获取合约地址下的以太币余额
}
// 充值函数payable，只有添加这个关键字，才能在执行这个函数时，给这个合约充以太币，否则该函数自动拒绝所有发送给它的以太币
function testPayable() payable public {  // transfer转账
}
}
``````

# 四、构造函数

``````pragma solidity ^0.4.25;

contract Test1 {
constructor() public {
_owner = msg.sender;
}
/**constructor(int num) public {  重载构造->编译错误
_owner = msg.sender;
}*/
}
contract Test2 {
uint public num;
constructor(uint x) public {  // 带参构造，在deploy时传入
num = x;
}
}
``````

# 五、修饰器modifier

``````pragma solidity ^0.4.25;

contract TestModifier {
bool public endFlag;  // 执行完test后的endFlag仍是true
constructor() public {
_owner = msg.sender;
}
modifier onlyOwner {  // 权限拦截器，非合约部署账号执行test()则被拦截
require(_owner == msg.sender, "Auth: only owner is authorized.");
_;  // 类似被代理的test()方法调用
endFlag = true;
}
function test() public onlyOwner {
endFlag = false;
}
}
``````

# 六、数据位置

## 6.3、calldata

calldata是不可修改的只读的非持久性数据位置，所有传递给函数的值，都存储在这里。此外，calldata是外部函数的参数(而不是返回参数)的默认位置。

0.4的external入参声明calldata则报错，0.5及之后的external入参必须声明calldata否则报错。

【总结】

# 七、事件event

``````contract Test {
event testEvent(uint a, uint b, uint c, uint result);
function calc(uint a, uint b, uint c) public returns(uint) {
uint result = a ** b ** c;
emit testEvent(a, b, c, result);
return result;
}
}
``````

``````[
{
"from": "0x19a0870a66B305BE9917c0F14811C970De18E6fC",
"event": "testEvent",
"args": {
"0": "2",
"1": "1",
"2": "3",
"3": "8",
"a": "2",
"b": "1",
"c": "3",
"result": "8"
}
}
]
``````

``````event testEvent(uint indexed a, uint b, uint c, uint result);
``````

# 八、单位和全局变量

``````function testUnit() pure public {
require(1 == 1 seconds);
require(1 minutes == 60 seconds);
require(1 hours == 60 minutes);
require(1 days == 24 hours);
require(1 weeks == 7 days);
require(1 years == 365 days);  // years 从 0.5.0 版本开始不再支持

require(1 ether == 1000 finney);
require(1 finney == 1000 szabo);    // 从0.7.0开始 finney 和 szabo 被移除了
require(1 szabo == 1e12 wei);
//require(1 gwei == 1e9);  // 0.7.0开始加入gwei
}
``````

``````pragma solidity ^0.8.0;
contract TestGlobalVariable {
function test1() public view returns(/**bytes32,*/ uint, uint, address, uint, uint, uint, uint) {
return (
// blockhash(block.number - 1),  // 指定区块的区块哈希，仅可用于最新的 256 个区块且不包括当前区块，否则返回0。0.5移除了block.blockhash
block.basefee,  // 当前区块的基础费用
block.chainid,  // 当前链 id
block.coinbase,  // 挖出当前区块的矿工地址
block.difficulty,  // 当前区块难度
block.gaslimit,  // 当前区块 gas 限额
block.number,  // 当前区块号
block.timestamp  // 自 unix epoch 起始当前区块以秒计的时间戳，0.7.0移除了now
);
}
function test2() public payable returns(bytes memory, address, bytes4, uint, uint256, uint, address) {
return (
msg.data,  // 完整的 calldata
msg.sender,  // 消息发送者（当前调用）
msg.sig,  // calldata 的前 4 字节（也就是函数标识符）
msg.value,  // 随消息发送的 wei 的数量
gasleft(),  // 剩余的 gas，0.5移除了msg.gas
tx.gasprice,  // 交易的 gas 价格
tx.origin  // 交易发起者（完全的调用链）
);
}
}
``````

# 九、异常处理

## 9.1、assert

``````assert(1 == 1 seconds);
``````

## 9.2、require

``````require(1 == 1 seconds);
require(1 == 1 seconds, "err");
``````

## 9.3、revert

``````if (x != y) revert("x should equal to b");
``````

## 9.4、try/catch

Solidity0.6版本之后加入。try/catch仅适用于外部调用，另外，try大括号内的代码是不能被catch捕获的。

``````pragma solidity ^0.6.10;

contract TestTryCatch {
function execute (uint256 amount) external returns(bool){
try this.onlyEvent(amount){
return true;
} catch {
return false;
}
}
function onlyEvent (uint256 a) public {
//code that can revert
require(a % 2 == 0, "Ups! Reverting");
}
}
``````

【assert和require的选择】

1. gas消耗不同。assert类型的异常会消耗掉所有剩余的gas，而require不会消耗掉剩余的gas（剩余的gas会返还给调用者）
2. 操作符不同。当assert发生异常时，Solidity会执行一个无效操作（无效指令0xfe）。当发生require类型的异常时，Solidity会执行一个回退操作（REVERT指令0xfd）
• 优先使用require()
• 用于检查用户输入。
• 用于检查合约调用返回值，如require(external.send(amount))。
• 用于检查状态，如msg.send == owner。
• 通常用于函数开头。
• 不知道使用哪一个的时候，就使用require。
• 优先使用assert()
• 用于检查溢出错误，如z = x + y; assert(z >= x)；
• 用于检查不应该发生的异常情况。
• 用于在状态改变之后，检查合约状态。
• 尽量少使用assert。
• 通常用于函数中间或者尾部。

# 十、重载

``````// 重载
function addNums(uint x, uint y) public pure returns(uint) {
return x + y;
}
function addNums(uint x, uint y, uint z) public pure returns(uint) {
return x + y + z;
}
``````

# 十一、继承

``````pragma solidity ^0.4.25;

contract TestExtendA { // 父类要写在子类之前
uint public a;
constructor() public {
a = 1;
}
}
contract TestExtend is TestExtendA {
uint public b;
constructor() public {
b = 2;
}
}
``````

``````contract A { // 父类要写在子类之前
uint public x;
constructor(uint _a) public {  // 带参构造
x = _a;
}
}
contract B is A(1) {  // 指定父类构造参数
uint public y;
constructor() public {
y = 2;
}
}
``````

``````contract A { // 父类要写在子类之前
uint public x;
constructor(uint _a) public {  // 带参构造
x = _a;
}
}
// 方式一：
contract B1 is A {
uint public b;
constructor() A(1) public {  // 子类构造使用父类带参修饰符A(1)
b = 2;
}
}
// 方式二：
contract B2 is A {
uint public b;
constructor(uint _b) A(_b / 2) public {  // 子类带参构造使用父类带参修饰符A(_b / 2)
b = _b;
}
}
``````

``````/// 连续继承，Z继承Y，Y又继承X
contract X {
uint public x;
constructor() public{
x = 1;
}
}
contract Y is X {
uint public y;
constructor() public{
y = 1;
}
}
// 如果是多个基类合约之间也有继承关系，那么is后面的合约书写顺序就很重要。顺序应该是，基类合约在前面，派生合约在后面，否则无法编译。
// 实际上Z只需要继承Y就行
contract Z is X,Y {  // 所以必须是X,Y而不是Y,X
}
/// 多重继承，子类可以拥有多个基类的属性
contract Father {
uint public x = 180;
}
contract Mother {
uint public y = 170;
}
contract Son is Father, Mother {
}
``````

# 十二、抽象合约

0.6开始支持。

``````//pragma solidity ^0.4.25;  // 0.4/0.5不兼容
pragma solidity ^0.6.10;

abstract contract TestAbstractContract {
uint public a;
constructor(uint _a) internal {
a = _a;
}
function get () virtual public;
}
``````

# 十三、重写

0.8以下不支持。

``````pragma solidity ^0.8.0;

contract TestOverride {
function get() virtual public{}
}
contract Middle is TestOverride {
}
contract Inherited is Middle{
function get() public override{
}
}
``````

``````contract Base1 {
function get () virtual public{}
}
contract Base2 {
function get () virtual public{}
}
contract Middle2 is Base1, Base2{  // 指定所有父合约名称
function get() public override( Base1, Base2){
}
}
``````

# 十四、接口

0.8以下不支持。

1. 无法继承其他合约或者接口

2. 无法定义构造函数

3. 无法定义变量

4. 无法定义结构体

5. 无法定义枚举

``````pragma solidity ^0.8.0;
interface TestInterface {
function transfer (address recipient, uint amount) external;
}
contract TestInterfaceSon {
function transfer(address recipient, uint amount) public {
}
}
``````

# 十五、库

``````pragma solidity >=0.4.0 <0.7.0;
//pragma solidity ^0.4.25;

library TestLibrary{
function add (uint a,uint b) internal pure returns (uint){
uint c = a + b;
require(c > a, "SafeMath: addition overflow");
return c;
}
}
``````

``````contract Test {
function add (uint x, uint y) public pure returns(uint){
}
}
``````

``````contract Test {
using TestLibrary for uint;
//using TestLibrary for *;
function add2 (uint x,uint y) public pure returns (uint){
}
}
``````

# 0x1、示例代码

https://remix.ethereum.org/下，基于不同版本语法的差异，分别用三个合约文件基本覆盖到了以上语法，

• Test04.sol => ^0.4.25
• Test06.sol => ^0.6.10
• Test08.sol => ^0.8.0
1. Test04.sol，
``````pragma solidity ^0.4.25;
//pragma solidity ^0.8.0;

/** 1.1.1 */
contract TestBool {
bool flag;
int num1 = 100;
int num2 = 200;
// default false
function getFlag() public view returns(bool) {
return flag;  // false
}
// 非
function getFlag2() public view returns(bool) {
return !flag;  // true
}
// 与
function getFlagAnd() public view returns(bool) {
return (num1 != num2) && !flag;  // true
}
// 或
function getFlagOr() public view returns(bool) {
return (num1 == num2) || !flag;  // true
}
}
/** 1.1.2 */
// 整型特性与运算
contract TestInteger {
int num1; // 有符号整型 int256
uint num2; // 无符号整型 uint256

function add(uint _a, uint _b) public pure returns(uint) {
return _a + _b;
}
function sub(uint _a, uint _b) public pure returns(uint) {
return _a - _b;
}
function mul(uint _a, uint _b) public pure returns(uint) {
return _a * _b;
}
function div(uint _a, uint _b) public pure returns(uint) {
return _a / _b;  // 在solidity中，除法是做整除，没有小数点
}
function rem(uint _a, uint _b) public pure returns(uint) {
return _a % _b;
}
function square(uint _a, uint _b) public pure returns(uint) {
return _a ** _b;  // 幂运算在0.8.0之后，变为右优先，即a ** b ** c => a ** (b ** c)
}
function max() public view returns(uint) {
return uint(-1);
// return type(uint).max;  // 0.8不再允许uint(-1)
}
}
// 位运算
contract TestBitwise {
uint8 num1 = 3;
uint8 num2 = 4;

function bitAdd() public view returns(uint) {
return num1 & num2;
}
function bitOr() public view returns(uint) {
return num1 | num2;
}
function unBit() public view returns(uint) {
return ~num1;
}
function bitXor() public view returns(uint) {
return num1 ^ num2;
}
function bitRight() public view returns(uint) {
return num1 >> 1;
}
function bitLeft() public view returns(uint) {
return num1 << 1;
}
}
/** 1.1.3 - 1.1.6 */
contract TestType {
fixed num;
ufixed num2;
fixed8x8 decimal;  // fixedMxN, M表示位宽，必须位8的整数倍，N表示十进制小数部分的位数
enum ActionChoices { Up, Down, Left, Right }
}
/** 1.1.7 */
// 固定长度的字节数组（静态），以及转换为string类型
contract TestBytesFixed {
// public 自动生成同名的get方法
bytes1 public num1 = 0x7a;  // 1 byte = 8 bit
bytes1 public num2 = 0x68;
bytes2 public num3 = 0x128b;  // 2 byte = 16 bit
// 获取字节数组长度
function getLength() public view returns(uint) {
return num3.length; // 2
}
// 字节数组比较
function compare() public view returns(bool) {
return num1 < num2;
}
// 字节数组位运算
function bitwise() public view returns(bytes1,bytes1,bytes1) {  // 多返回值
return ((num1 & num2), (num1 | num2), (~num1));
}

// 先转为bytes动态数组，再通过string构造  0x7a7a -> zz
function toString(bytes2 _val) public pure returns(string) {
bytes memory buf = new bytes(_val.length);
for (uint i = 0; i < _val.length; i++) {
buf[i] = _val[i];
}
return string(buf);
}
}
// 固定长度的字节数组（静态）扩容和压缩
contract TestBytesExpander {
// public 自动生成同名的get方法
bytes6 name = 0x796f7269636b;

function changeTo1() public view returns(bytes1) {
return bytes1(name); // 0x79
}
function changeTo2() public view returns(bytes2) {
return bytes2(name); // 0x796f
}
function changeTo16() public view returns(bytes16) {
return bytes16(name); // 0x796f7269636b00000000000000000000
}
}
/** 1.2.1 */
// 修改string类型的数据
contract TestString {
string name = 'yorick';  // 字符串可以使用单引号或者双引号
string name2 = "!@#\$%^&";  // 特殊字符占1个byte
string name3 = "张三";  // 中文在string中使用utf8的编码方式存储，占用3个byte  ->  切换到0.8则中文字符报错
// string memory str = unicode"Hello 😃";  // 0.7.0支持Unicode字符串
// string memory str2 = unicode"u20ac";  // 0.7.0支持Unicode字符串

function getLength() view public returns(uint) {
// 不可以直接获取string的length
return bytes(name).length; // 6
}
function getLength2() view public returns(uint) {
return bytes(name2).length; // 7
}
function getLength3() view public returns(uint) {
return bytes(name3).length; // 6
}

function getElmName() view public returns(bytes1) {
// 不可以直接通过数组下标name[0]获取
return bytes(name)[0]; // 0x79
}
function changeElmName() public {
bytes(name)[0] = "h";
}
function getName() view public returns(bytes) {
return bytes(name);
}
}
/** 1.2.2 */
// 动态的字节数组，以及转换为string类型
contract TestBytesDynamic {
bytes public dynamicBytes;

function setDynamicBytes(string memory val) public {
dynamicBytes = bytes(val);
}
function getVal() public view returns(string){
return string(dynamicBytes);
}
}
/** 1.2.3 */
// 定长数组
contract TestArrFixed {

uint[5] arr = [1,2,3,4,5];

// 修改数组元素内容
function modifyElements() public {
arr[0] = 12;
arr[1] = 14;
}
// 查看数组
function watchArr() public view returns(uint[5]) {
return arr;
}
// 数组元素求和计算
function sumArr() public view returns(uint) {
uint sum = 0;
for (uint i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}

// 数组长度
function getLength() public view returns(uint) {
return arr.length;
}

// delete重置数组某下标的元素值，不会真正删除该元素
function deleteElm(uint idx) public {
delete arr[idx];
}
// delete重置整个数组
function deleteArr() public {
delete arr;
}

/**  定长数组不允许改变长度和push
// 压缩数组后，右侧多余元素被丢弃
function changeLengthTo1() public {
arr.length = 1;
}
// 扩容数组后，右侧元素补0
function changeLengthTo10() public {
arr.length = 10;
}
// 追加新元素
function pushElm(uint _elm) public {
arr.push(_elm);
}
*/
}
// 变长数组
contract TestArrDynamic {

uint[] arr = [1,2,3,4,5];

// 查看数组
function watchArr() public view returns(uint[] memory) {
return arr;
}
// 数组长度
function getLength() public view returns(uint) {
return arr.length;
}
// 压缩数组后，右侧多余元素被丢弃
function changeLengthTo1() public {
arr.length = 1;
}
// 扩容数组后，右侧元素补0
function changeLengthTo10() public {
arr.length = 10;
}
// 追加新元素
function pushElm(uint _elm) public {
arr.push(_elm);
}
/**
// 弹出元素
function popElm(uint _elm) public {
arr.pop();  // 0.4不支持pop
}
*/
// delete重置数组某下标的元素值，不会真正删除该元素
function deleteElm(uint idx) public {
delete arr[idx];
}
// delete重置整个数组
function deleteArr() public {
delete arr;
}
}
/**

solidity的二维数组与其他语言不同，[2] [3]表示3行2列，而其他语言为2行3列；

*/
contract TestArr2Dimensional {
uint[2][3] arr = [[1,2],[3,4],[5,6]];
function getRowSize() public view returns(uint) {
return arr.length; // 3
}
function getColSize() public view returns(uint) {
return arr[0].length; // 2
}
function watchArr() public view returns(uint[2][3]) {
return arr;  // 1,2,3,4,5,6
}
function sumArr() public view returns(uint) {
uint sum = 0;
for (uint i = 0; i < getRowSize(); i++) {
for (uint j = 0; j < getColSize(); j++) {
sum += arr[i][j];
}
}
return sum;
}
function modifyArr() public {
arr[0][0] = 99;
}
}
// 数组字面值
contract TestArrLiteral {
// 最小存储匹配，未超过255，所以使用uint8存储
function getLiteral8() pure public returns(uint8[3]) {
return [1,2,3];
}
// 超过255，所以使用uint16存储
function getLiteral16() pure public returns(uint16[3]) {
return [256,2,3];  // [255,2,3] 不被uint16允许
}
// 强制转换为uint256
function getLiteral256() pure public returns(uint[3]) {
return [uint(1),2,3];  // 给任意元素强转即可，否则不被允许
}
// 计算外界传入的内容
function addLiterals(uint[3] arr) pure external returns(uint) {
uint sum = 0;
for (uint i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
}
/** 1.2.4 */
// 结构体初始化的两种方法
contract TestStruct {
struct Student {
uint id;
string name;
mapping(uint=>string) map;
}

// 默认为storage类型，只能通过storage类型操作结构体中的mapping类型数据
Student storageStu;

// mapping类型可以不用在定义的时候赋值，而其他类型不赋值则会报错
function init() public pure returns(uint, string) {
Student memory stu = Student(100, "Jay");
return (stu.id, stu.name);
}
function init2() public pure returns(uint, string) {
Student memory stu = Student({name: "Jay", id: 100});
return (stu.id, stu.name);
}
function init3() public returns(uint, string, string) {
Student memory stu = Student({name: "Jay", id: 100});
// 直接操作结构体中的mapping不被允许: Student memory out of storage
// stu.map[1] = "artist";
// 通过storage类型的变量操作结构体中的mapping
storageStu = stu;
storageStu.map[1] = "artist";
return (storageStu.id, storageStu.name, storageStu.map[1]);
}

// 结构体作为入参时，为memory类型，并且不能使用public或external修饰函数：Internal or recursive type is not allowed for public or external functions.
// 赋值时也要指定为memory，否则报错
function testIn(Student stu) internal returns(uint) {
return stu.id;
}
// 结构体作为出参，同样只能private或internal声明内部使用
function testOut(Student stu) private returns(Student) {
return stu;
}
}
/** 1.3 */
contract TestMapping {
mapping(address => uint) private scores;  // <学生,分数>的单层映射
mapping(address => mapping(bytes32 => uint8)) private _scores;  // <学生,<科目,分数>>的两层映射
function getScore() public view returns(address, uint) {
}
function setScore() public {
scores[msg.sender] = 100;
}
}
/** 二 */
contract TestAccessCtrl {
constructor () public {}
uint public num1 = 1;  // 自动为public生成同名的get函数，但在编码时不可直接调用num1()
uint private num2 = 2;
uint num3 = 3;  // 不写则默认private
function funcPublic() public returns(string) {
return "public func";
}
function funcPrivate() private returns(string) {
return "private func";
}
function funcInternal() internal returns(string) {
return "internal func";
}
function funcExternal() external returns(string) {
return "external func";
}
function test1(uint choice) public returns(string) {
if (choice == 1) return funcPublic();
if (choice == 2) return funcPrivate();
if (choice == 3) return funcInternal();
//if (choice == 4) return funcExternal();  // external不允许直接在内部用
if (choice == 4) return this.funcExternal();  // 间接通过this才可以调用external
}
}
contract TestAccessCtrlSon is TestAccessCtrl {
function test2(uint choice) public returns(string) {
if (choice == 1) return funcPublic();  // public允许派生合约使用
//if (choice == 2) return funcPrivate();  // private不允许派生合约使用
if (choice == 3) return funcInternal();  // internal允许派生合约使用
//if (choice == 4) return funcExternal();  // external也不允许派生合约直接使用
}
}
contract TestAccessCtrl2 {
function test2(uint choice) public returns(string) {
TestAccessCtrl obj = new TestAccessCtrl();
if (choice == 4) {
return obj.funcExternal();  // external只允许在外部合约中这样间接调用
} else return "0x0";
}
}
/** 三 */
contract TestFuncDecorator {
uint public num = 1;
/// pure
function testPure(uint _num) public pure {
//uint num1 = num;  // pure不允许读状态变量
//num = _num;  // pure不允许修改状态变量
}
/// view
function testView(uint _num) public view {
uint num1 = num;  // 允许读状态变量
num = _num;  // 0.4语法上允许修改状态变量，但实际不会修改，所以num还是1
// 0.5及之后不允许在view中这样修改，否则编译不通过
}
/// payable
function () public payable {}
function getBalance() public view returns(uint) {  // balance获取合约地址下的以太币余额
}
// 充值函数payable，只有添加这个关键字，才能在执行这个函数时，给这个合约充以太币，否则该函数自动拒绝所有发送给它的以太币
function testPayable() payable public {  // transfer转账
}
}
/** 四 */
contract TestConstruct1 {
constructor() public {
_owner = msg.sender;
}
/**constructor(int num) public {  // 重载构造->编译错误
_owner = msg.sender;
}*/
/**
function TestFuncDecorator(uint x) {}  // 0.5之前还可以用同名函数定义
*/
}
contract TestConstruct2 {
uint public num;
constructor(uint x) public {  // 带参构造，在deploy时传入
num = x;
}
}
/** 五 */
contract TestModifier {
bool public endFlag;  // 执行完test后的endFlag仍是true
constructor() public {
_owner = msg.sender;
}
modifier onlyOwner {  // 权限拦截器，非合约部署账号执行test()则被拦截
require(_owner == msg.sender, "Auth: only owner is authorized.");
_;  // 类似被代理的test()方法调用
endFlag = true;
}
function test() public onlyOwner {
endFlag = false;
}
}
/** 七 */
contract TestEvent {
event testEvent(uint indexed a, uint indexed b, uint indexed c, uint result); // indexed不能超过三个
function calc(uint a, uint b, uint c) public returns(uint) {
uint result = a ** b ** c;
emit testEvent(a, b, c, result);  // 事件会输出在logs中
return result;
}
}
/** 八 */
contract TestUnit {
function testUnit() pure public {
require(1 == 1 seconds);
require(1 minutes == 60 seconds);
require(1 hours == 60 minutes);
require(1 days == 24 hours);
require(1 weeks == 7 days);
require(1 years == 365 days); // years 从 0.5.0 版本开始不再支持

require(1 ether == 1000 finney);
require(1 finney == 1000 szabo);
require(1 szabo == 1e12 wei);
//require(1 gwei == 1e9);  // 0.7.0开始加入gwei
}
}
/** 九 */
contract TestException {
function testAssert(int x) public pure {
assert(x >= 0);
}
function testRequire(int x) public pure {
require(x >= 0);
//require(x >= 0, "x < 0");
}
function testRevert(int x, int y) public pure {
if (x != y) revert("x should equal to b");
}
}
/** 十 */
function addNums(uint x, uint y) public pure returns(uint) {
return x + y;
}
function addNums(uint x, uint y, uint z) public pure returns(uint) {
return x + y + z;
}
}
/** 十一 */
contract TestExtendA { // 父类TestExtendA要写在子类TestExtend之前
uint public a;
constructor() public {
a = 1;
}
}
contract TestExtend is TestExtendA {
uint public b;
constructor() public {
b = 2;
}
}
/// 直接在继承列表中指定基类的构造参数
contract A { // 父类要写在子类之前
uint public x;
constructor(uint _a) public {  // 带参构造
x = _a;
}
}
contract B is A(1) {  // 指定父类构造参数
uint public y;
constructor() public {
y = 2;
}
}
/// 通过派生合约（子类）的构造函数中使用修饰符方式调用基类合约
// 方式一：
contract B1 is A {
uint public b;
constructor() A(1) public {  // 子类构造使用父类带参修饰符A(1)
b = 2;
}
}
// 方式二：
contract B2 is A {
uint public b;
constructor(uint _b) A(_b / 2) public {  // 子类带参构造使用父类带参修饰符A(_b / 2)
b = _b;
}
}
/// 连续继承，Z继承Y，Y又继承X
contract X {
uint public x;
constructor() public{
x = 1;
}
}
contract Y is X {
uint public y;
constructor() public{
y = 1;
}
}
// 如果是多个基类合约之间也有继承关系，那么is后面的合约书写顺序就很重要。顺序应该是，基类合约在前面，派生合约在后面，否则无法编译。
// 实际上Z只需要继承Y就行
contract Z is X,Y {  // 所以必须是X,Y而不是Y,X
}
/// 多重继承，子类可以拥有多个基类的属性
contract Father {
uint public x = 180;
}
contract Mother {
uint public y = 170;
}
contract Son is Father, Mother {
}
/** 十五 */
library TestLibrary{
function add (uint a,uint b) internal pure returns (uint){
uint c = a + b;
require(c > a, "Math: addition overflow");
return c;
}
}
// 库的调用
contract TestLibraryCall {
function add(uint x, uint y) public pure returns(uint){
}
}
// 除了使用上面的TestLibrary.add(x, y)这种方式来调用库函数，还有一个是使用using LibA for B这种附着库的方式。
// 它表示把所有LibA的库函数关联到数据类型B，这样就可以在B类型直接调用库函数。
contract TestLibraryUsing {
using TestLibrary for uint;
//using TestLibrary for *;
function add2(uint x,uint y) public pure returns (uint){
}
}
``````
1. Test06.sol，
``````pragma solidity ^0.6.10;

/** 1.2.3 */
contract TestArrDynamic {
uint[] arr = [1,2,3,4,5];
// 弹出元素
function popElm() public {
arr.pop();
}
function watchArr() public view returns(uint[] memory) {
return arr;
}
/**
0.6开始不再可以通过修改length改变数组长度，需要通过push(),push(value),pop的方式，或者赋值一个完整的数组
*/
/**function changeLengthTo1() public {
arr.length = 1;
}*/
}
/** 九 */
// Solidity0.6版本之后加入。try/catch仅适用于外部调用，另外，try大括号内的代码是不能被catch捕获的。
contract TestTryCatch {
function execute (uint256 amount) external returns(bool) {
try this.run(amount) {  // 这里的函数异常会被捕获
return true;  // 这里的异常不再会被捕获
} catch {
return false;
}
}
function run(uint256 a) public {
//code that can revert
require(a % 2 == 0, "Ups! Reverting");
}
}
/** 十二 */
// 0.6后支持。抽象合约不能使用new创建
abstract contract TestAbstractContract {
uint public a;
constructor(uint _a) internal {
a = _a;
}
function get () virtual public;
}
``````
1. Test08.sol，
``````pragma solidity ^0.8.0;

/** 1.2.1 */
contract TestString {
function test() public view returns(string memory, string memory, string memory) {
string memory str = unicode"Hello 😃";  // Hello 😃
string memory str2 = unicode"u20ac";  // €
string memory str3 = hex"414243444546474849";  // ABCDEFGHI
// string memory name3 = "张三";  // 0.8不允许中文字符，必须改为unicode
return (str, str2, str3);
}
}
/** 八 */
contract TestGlobalVariable {
function test1() public view returns(/**bytes32,*/ uint, uint, address, uint, uint, uint, uint) {
return (
// blockhash(block.number - 1),  // 指定区块的区块哈希，仅可用于最新的 256 个区块且不包括当前区块，否则返回0。0.5移除了block.blockhash
block.basefee,  // 当前区块的基础费用
block.chainid,  // 当前链 id
block.coinbase,  // 挖出当前区块的矿工地址
block.difficulty,  // 当前区块难度
block.gaslimit,  // 当前区块 gas 限额
block.number,  // 当前区块号
block.timestamp  // 自 unix epoch 起始当前区块以秒计的时间戳，0.7.0移除了now
);
}
function test2() public payable returns(bytes memory, address, bytes4, uint, uint256, uint, address) {
return (
msg.data,  // 完整的 calldata
msg.sender,  // 消息发送者（当前调用）
msg.sig,  // calldata 的前 4 字节（也就是函数标识符）
msg.value,  // 随消息发送的 wei 的数量
gasleft(),  // 剩余的 gas，0.5移除了msg.gas
tx.gasprice,  // 交易的 gas 价格
tx.origin  // 交易发起者（完全的调用链）
);
}
}
/** 十三 */
// 合约中的虚函数（函数使用了virtual修饰的函数）可以在子合约重写该函数，以更改他们在父合约中的行为。重写的函数需要使用关键字override修饰。
// 0.8以下不支持。
contract TestOverride {
function get() virtual public{}
}
contract Middle is TestOverride {
}
contract Inherited is Middle {
function get() public override {
}
}
// 对于多重继承，如果有多个父合约有相同定义的函数，override关键字后必须指定所有的父合约名称
contract Base1 {
function get() virtual public {}
}
contract Base2 {
function get() virtual public {}
}
contract Middle2 is Base1, Base2 {  // 指定所有父合约名称
function get() public override (Base1, Base2){
}
}
/** 十四 */
// 0.8以下不支持。
interface TestInterface {
function transfer(address recipient, uint amount) external;
}
contract TestInterfaceSon {
function transfer(address recipient, uint amount) public {
}
}
``````

# 0x2、各版本主要变化

0.5.0

• sha3改用keccak256， keccak256只允许接收一个参数，使用abi.encodePacked等组合params
• 构造函数由同名空参方法变成constructor

0.6.0

• 仅标记virtual的接口才可以被覆盖，覆盖时需要使用新关键字override，如果多个基类同方法名时，需要像这样列出 override(Base1, Base2)
• 不能通过修改length来修改数组长度，需要通过push(),push(value),pop的方式，或者赋值一个完整的数组
• 使用abstract标识抽象合约，抽象合约不能使用new创建
• 新增try/catch，可对调用失败做一定处理
• 数组切片，例如: abi.decode(msg.data[4:], (uint, uint)) 是一个对函数调用payload进行解码底层方法

0.7.0

• call方式调用方法由x.f.gas(1000).value(1 ether)(arg1,arg2)改成 x.f{gas:1000,value:1 ether}(arg1,arg2)
• now 不推荐使用，改用block.timestamp
• gwei增加为关键字
• 字符串支持ASCII字符，Unicode字符串
• 构造函数不在需要 public修饰符，如需防止创建，可定义成abstract
• 不允许在同一继承层次结构中具有同名同参数类型的多个事件
• using A for B,只在当前合约有效， 以前是会继承的，现在需要使用的地方，都得声明一次

0.8.0

• 弃用safeMath,默认加了溢出检查，如需不要检查使用 `unchecked { ... }` , 可以节省丢丢手续费
• 默认支持ABIEncoderV2，不再需要声明
• 求幂是右结合的，即表达式`a**b**c`被解析为`a**(b**c)`。在 0.8.0 之前，它被解析为`(a**b)**c`
• assert 不在消耗完 完整的gas,功能和require基本一致，但是try/catch错误里面体现不一样，还有一定作用…
• 不再允许使用uint(-1),改用type(uint).max

THE END