当前位置: 首页 > news >正文

Solidity 学习笔记

  • 主要参考网上资料学习,个人学习笔记有删改,参考出处在文末列出。

0 基础

  1. IDE: remix
  2. Type
  • Bool: bool public _bool = true; 默认false;
  • 整型:int、uint、uint256,默认0;
  • 地址类型:address,分为 payable 和普通地址,payable address 有 .balance 和 .transfer()。二者都有 .call()。默认address(0);
  • 字符串:string,默认"";
  • 字节数组:
    • 定长:byte, bytes1, bytes8, bytes32
    • 不定长:引用类型
  • 枚举类型: enum (几乎没人用)默认第一个元素
   // 用enum将uint 0, 1, 2表示为Buy, Hold, Sellenum ActionSet { Buy, Hold, Sell }// 创建enum变量 actionActionSet action = ActionSet.Buy;// enum可以和uint显式的转换function enumToUint() external view returns(uint){return uint(action);}
  • 引用类型:array、struct、mapping…
  • 注意:delete a会让 a 变为其类型的默认值;
  • constant & immutable
    • constant:声明的时候就需要指定该关键字;
    • immutable:在声明时或构造函数中初始化;
    • string 和 bytes 可以声明为 constant,但不能为 immutable;

函数 function

function <function name> (<parameter types>) {internal|external|public|private} [pure|view|payable] [returns (<return types>)]
  • pure & view:与 gas 有关,加上这俩标记的函数不修改链上状态,因此不消耗 gas。
    • 修改状态的操作举例:状态变量、链上event、create contract、selfdestruct、转账、调用其他非pure&view函数、任何低级调用(low-level calls)、使用包含某些opcode的内联汇编。
    • pure:不读也不写
    • view:只读不写
    • 其他:可读可写

pure不读也不写有啥用?举例如下:

 function addPure(uint256 _number) external pure returns(uint256 new_number){new_number = _number+1;}
  • {internal|external|public|private}:没标明函数类型的,默认internal
    • public: 内部外部均可见。(也可用于修饰状态变量,public变量会自动生成 getter函数,用于查询数值)。
    • private: 只能从本合约内部访问,继承的合约也不能用(也可用于修饰状态变量)。
    • external: 只能从合约外部访问(但是可以用this.f()来调用,f是函数名)。
    • internal: 只能从合约内部访问,继承的合约可以用(也可用于修饰状态变量)。
  • payable:可以在call这个function的时候同时转eth。
 //payable: 递钱,能给合约支付eth的函数function testPayable() external payable returns(uint256 balance) {balance = address(this).balance; // this -> contract address}
  • return & returns:returns 在 函数名 后,声明返回类型和变量名。return 在函数主体中,返回指定变量。
  // 返回多个变量
function returnMultiple() public pure returns(uint256, bool, uint256[3] memory){return(1, true, [uint256(1),2,5]);
}// 命名式返回function returnNamed() public pure returns(uint256 _number, bool _bool, uint256[3] memory _array){_number = 2;_bool = false; _array = [uint256(3),2,1];}
// 命名式返回,依然支持returnfunction returnNamed2() public pure returns(uint256 _number, bool _bool, uint256[3] memory _array){return(1, true, [uint256(1),2,5]);}
  • solidity 支持解构赋值:
        uint256 _number;bool _bool;uint256[3] memory _array;(_number, _bool, _array) = returnNamed();(, _bool2, ) = returnNamed(); // 只要部分返回值

存储

对引用类型的变量,由于变量复杂且存储消耗大,因此需要显示声明存储位置。合约中的状态变量默认存储在storage中

  • storage:存储在链上(类似硬盘)消耗 gas 多;
  • memory:临时存在内存中,消耗 gas 小;
  • calldata:临时存在内存中,消耗 gas 小;calldata 不能修改,一般用于函数参数。
    function fCalldata(uint[] calldata _x) public pure returns(uint[] calldata){//参数为calldata数组,不能被修改// _x[0] = 0 //这样修改会报错return(_x);}

简单来说,相同存储位置的会用引用方式,否则一般是副本。

  1. storage(合约的状态变量)赋值给本地storage(函数里的)时候,会创建引用,改变新变量会影响原变量。
    uint[] x = [1,2,3]; // 状态变量:数组 x,这里是 storagefunction fStorage() public{//声明一个storage的变量 xStorage,指向x。修改xStorage也会影响xuint[] storage xStorage = x;xStorage[0] = 100;}
  1. storage 赋值给 memory 或者 memory 赋值给 storage,会创建副本
    uint[] x = [1,2,3]; // 状态变量:数组 x,这里是 storagefunction fMemory() public view{//声明一个Memory的变量xMemory,复制x。修改xMemory不会影响xuint[] memory xMemory = x;xMemory[0] = 100;xMemory[1] = 200;uint[] memory xMemory2 = x;xMemory2[0] = 300;}
  1. memory赋值给memory,会创建引用,改变新变量会影响原变量。
  2. 其他情况,变量赋值给storage,会创建独立的复本,修改其中一个不会影响另一个。

作用域

状态变量(state variable),局部变量(local variable)和全局变量(global variable) 作用域。

1.状态变量
状态变量数据存储在链上,所有合约内函数都可以访问 ,gas消耗高。状态变量在合约内、函数外声明:

contract Variables {uint public x = 1;uint public y;string public z;...function foo() external{// 可以在函数里更改状态变量的值x = 5;y = 2;z = "0xAA";}
}

2.局部变量:略。
3.全局变量:solidity 预留关键字的全局变量,可以在函数内不声明直接使用。

    function global() external view returns(address, uint, bytes memory){address sender = msg.sender;uint blockNum = block.number;bytes memory data = msg.data;return(sender, blockNum, data);}

引用类型

Array

  • solidity中如果一个值没有指定type的话,默认就是最小单位的该type。如果指定,元素的type是以第一个元素为准。
// 固定长度uint[8] array1;bytes1[5] array2;address[100] array3;// 可变长长度uint[] array4;bytes1[] array5;address[] array6;bytes array7;// memory动态数组:memory修饰的动态数组,可以用new操作符来创建,但是必须声明长度,并且声明后长度不能改变uint[] memory array8 = new uint[](5);bytes memory array9 = new bytes(9);
// 如果创建的是动态数组,需要一个一个元素的赋值。uint[] memory x = new uint[](3);x[0] = 1;x[1] = 3;x[2] = 4;// 数组字面常数(Array Literals)是写作表达式形式的数组,用方括号包着来初始化array的一种方式,并且里面每一个元素的type是以第一个元素为准的
pragma solidity >=0.4.16 <0.9.0;
contract C {function f() public pure {g([uint(1), 2, 3]); // 元素的type是以第一个元素为准}function g(uint[3] memory) public pure {// ...}
}
  • length: 数组有一个包含元素数量的length成员,memory数组的长度在创建后是固定的。
  • push(): 动态数组和bytes拥有push(),在数组最后添加一个0元素。
  • push(x): 动态数组和bytes拥有push(x),数组最后添加一个x元素。
  • pop(): 动态数组和bytes拥有pop()成员,移除数组最后一个元素。

struct

    // 结构体struct Student{uint256 id;uint256 score; }Student student; // 初始一个student结构体//  给结构体赋值// 方法1:在函数中创建一个storage的struct引用function initStudent1() external{Student storage _student = student;_student.id = 11;_student.score = 100;}// 方法2:直接引用状态变量的structfunction initStudent2() external{student.id = 1;student.score = 80;}

映射类型 mapping

mapping(_KeyType => _ValueType)

  • 规则1:_KeyType 只能选择 solidity 默认的类型,_ValueType 可以用自定义类型。
  • 规则2:映射的存储位置必须是 storage,可用于合约的状态变量、函数中的 storage 变量和library函数的参数。不能用于 public 函数的参数或返回结果中,因为 mapping 记录的是一种关系 (key - value pair)。
  • 规则3:如果映射声明为 public,那么 solidity 会自动创建一个 getter,可以通过 Key 来查询对应的 Value。
  • Ethereum 会定义所有未使用的空间为0,所以未赋值(Value)的键(Key)初始值都是0。映射使用 keccak256(key) 当成 offset 存取 value。映射不储存任何键(Key)的信息,也没有 length 的信息。

控制流程

function ifElseTest(uint256 _number) public pure returns(bool){if(_number == 0){return(true);}else{return(false);}
}function forLoopTest() public pure returns(uint256){uint sum = 0;for(uint i = 0; i < 10; i++){sum += i;}return(sum);
}function whileTest() public pure returns(uint256){uint sum = 0;uint i = 0;while(i < 10){sum += i;i++;}return(sum);
}function doWhileTest() public pure returns(uint256){uint sum = 0;uint i = 0;do{sum += i;i++;}while(i < 10);return(sum);
}function ternaryTest(uint256 x, uint256 y) public pure returns(uint256){// return the max of x and yreturn x >= y ? x: y; 
}

插入排序 solidity 版【有坑需注意】

# python code
def insertionSort(arr):for i in range(1, len(arr)):key = arr[i]j = i-1while j >=0 and key < arr[j] :arr[j+1] = arr[j]j -= 1arr[j+1] = key

直接翻译:

    // 插入排序 错误版 ERRORfunction insertionSortWrong(uint[] memory a) public pure returns(uint[] memory) {for (uint i = 1;i < a.length;i++){uint temp = a[i];uint j=i-1;while( (j >= 0) && (temp < a[j])){a[j+1] = a[j];j--; // ERROR:j有可能会取到-1 但这里j是uint..报错!}a[j+1] = temp;}return(a);}// 插入排序 正确版function insertionSort(uint[] memory a) public pure returns(uint[] memory) {// note that uint can not take negative valuefor (uint i = 1;i < a.length;i++){uint temp = a[i];uint j=i;	// 注意这里,一个重要的小细节while( (j >= 1) && (temp < a[j-1])){ // 又一个小细节a[j] = a[j-1];j--;}a[j] = temp;}return(a);}

构造函数和修饰器

  • Ownable、constructor、modifier
  • constructor:每个合约可以定义一个,并在部署时自动运行一次,用来初始化合约参数,如初始化合约的owner地址。
   address owner; // 定义owner变量// 构造函数constructor() {owner = msg.sender; // 在部署合约的时候,将owner设置为部署者的地址}// 旧版构造函数:version<0.4.22pragma solidity =0.4.21;contract Parents {// 与合约名Parents同名的函数就是构造函数function Parents () public { // parents 就不是构造函数!}}

注意: 构造函数在不同的 solidity 版本中的语法并不一致,在Solidity 0.4.22之前,构造函数不使用 constructor 而是使用与合约名同名的函数作为构造函数而使用,由于这种旧写法容易使开发者在书写时发生疏漏(例如合约名叫 Parents,构造函数名写成 parents),使得构造函数变成普通函数,引发漏洞,所以0.4.22版本及之后,采用了全新的 constructor 写法。

  • modifier:
   // 定义modifiermodifier onlyOwner {require(msg.sender == owner); // 检查调用者是否为owner地址_; // 如果是的话,继续运行函数主体;否则报错并revert交易}function changeOwner(address _newOwner) external onlyOwner{owner = _newOwner; // 只有owner地址运行这个函数,并改变owner}
  • OppenZepplin 的 Ownable 标准实现

事件

链上 event 的特点:1. 应用程序(ether.js)可以通过RPC接口订阅和监听这些事件,并在前端做响应。2. 事件是EVM上比较经济的存储数据的方式,每个大概消耗 2,000 gas;相比之下,链上存储一个新变量至少需要 20,000 gas。

event Transfer(address indexed from, address indexed to, uint256 value);// 定义_transfer函数,执行转账逻辑function _transfer(address from,address to,uint256 amount) external {_balances[from] = 10000000; // 给转账地址一些初始代币_balances[from] -=  amount; // from地址减去转账数量_balances[to] += amount; // to地址加上转账数量// 释放事件emit Transfer(from, to, amount);}
  • indexed: 表示检索事件的key,在链上会对key单独索引和存储。每个事件最多带有3个indexed key。每个indexed variable固定256bits
  • event 的哈希以及这三个带 indexed 的变量在 EVM 日志中通常被存储为 topic。其中topic[0]是此事件的 keccak256 哈希,topic[1]~topic[3]存储了带 indexed 变量的 keccak256 哈希。
  • value 不带 indexed 关键字,会存储在事件的 data 部分中,可以理解为事件的“值”。data 部分的变量不能被直接检索,但可以存储任意大小的数据。因此一般 data 部分可以用来存储复杂的数据结构,例如数组和字符串等等,因为这些数据超过了 256 比特,即使存储在事件的 topic 部分中,也是以哈希的方式存储。另外,data 部分的变量在存储上消耗的 gas 相比于 topic 更少。
  • event 是链上分析工具如 Nansen、Dune Analysis 的基础。

继承

  • virtual: 父合约中的函数,如果希望子合约重写,需要加上virtual关键字。
  • override:子合约重写了父合约中的函数,需要加上override关键字。
contract Yeye {event Log(string msg);// 定义3个function: hip(), pop(), man(),Log值为Yeye。function hip() public virtual{emit Log("Yeye");}function pop() public virtual{emit Log("Yeye");}function yeye() public virtual {emit Log("Yeye");}
}contract Baba is Yeye{// 继承两个function: hip()和pop(),输出改为Baba。function hip() public virtual override{emit Log("Baba");}function pop() public virtual override{emit Log("Baba");}function baba() public virtual{emit Log("Baba");}
}
  • 多重继承:继承时要按辈分最高到最低的顺序排。 如果某一个函数在多个继承的合约里都存在,比如例子中的hip()和pop(),在子合约里必须重写,不然会报错。 重写在多个父合约中都重名的函数时,override关键字后面要加上所有父合约名字,例如override(Yeye, Baba)。 例子:
contract Erzi is Yeye, Baba{// 继承两个function: hip()和pop(),输出值为Erzi。function hip() public virtual override(Yeye, Baba){emit Log("Erzi");}function pop() public virtual override(Yeye, Baba) {emit Log("Erzi");}

修饰器的继承

contract Base1 {modifier exactDividedBy2And3(uint _a) virtual {require(_a % 2 == 0 && _a % 3 == 0);_;}
}contract Identifier is Base1 {//计算一个数分别被2除和被3除的值,但是传入的参数必须是2和3的倍数function getExactDividedBy2And3(uint _dividend) public exactDividedBy2And3(_dividend) pure returns(uint, uint) {return getExactDividedBy2And3WithoutModifier(_dividend);}//计算一个数分别被2除和被3除的值function getExactDividedBy2And3WithoutModifier(uint _dividend) public pure returns(uint, uint){uint div2 = _dividend / 2;uint div3 = _dividend / 3;return (div2, div3);}
}

构造函数的继承

// 构造函数的继承
abstract contract A {uint public a;constructor(uint _a) {a = _a;}
}

方式一:在继承时声明父构造函数的参数,例如:contract B is A(1)。
方式二:在子合约的构造函数中声明构造函数的参数。

contract C is A {constructor(uint _c) A(_c * _c) {}
}

调用父合约的函数

1.直接调用:子合约可以直接用父合约名.函数名()的方式来调用父合约函数
2.super.函数名()来调用最近的父合约函数。solidity继承关系按声明时从右到左的顺序是:contract Erzi is Yeye, Baba,那么Baba是最近的父合约,super.pop()将调用Baba.pop()而不是Yeye.pop()

    function callParent() public{Yeye.pop();}function callParentSuper() public{// 将调用最近的父合约函数,Baba.pop()super.pop();}

抽象合约和接口

  • 如果一个智能合约里至少有一个未实现的函数,即某个函数缺少主体{}中的内容,则必须将该合约标为 abstract。未实现的函数需要加virtual,以便子合约重写。
abstract contract InsertionSort{function insertionSort(uint[] memory a) public pure virtual returns(uint[] memory);
}
  • 接口:1.不能包含状态变量;2.不能包含构造函数;3.不能继承除接口外的其他合约;4.所有函数都必须是external且不能有函数体;5.继承接口的合约必须实现接口定义的所有功能;
  • 接口提供了两个重要的信息:1.合约里每个函数的bytes4选择器,以及基于它们的函数签名函数名(每个参数类型)。2.接口id(EIP165)。接口与合约ABI(Application Binary Interface)等价,可以相互转换:编译接口可以得到合约的ABI,利用abi-to-sol工具也可以将ABI json文件转换为接口sol文件。(这里所谓的选择器类似一个offset定位到这个函数)
interface IERC721 is IERC165 {
// Transfer事件:在转账时被释放,记录代币的发出地址from,接收地址to和tokenid。event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
// Approval事件:在授权时释放,记录授权地址owner,被授权地址approved和tokenid。event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
// ApprovalForAll事件:在批量授权时释放,记录批量授权的发出地址owner,被授权地址operator和授权与否的approved。event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
// balanceOf:返回某地址的NFT持有量balance。function balanceOf(address owner) external view returns (uint256 balance);
// ownerOf:返回某tokenId的主人owner。function ownerOf(uint256 tokenId) external view returns (address owner);
// transferFrom:普通转账,参数为转出地址from,接收地址to和tokenId。function safeTransferFrom(address from, address to, uint256 tokenId) external;
// safeTransferFrom:安全转账(如果接收方是合约地址,会要求实现ERC721Receiver接口)。参数为转出地址from,接收地址to和tokenId。function transferFrom(address from, address to, uint256 tokenId) external;
// approve:授权另一个地址使用你的NFT。参数为被授权地址approve和tokenId。function approve(address to, uint256 tokenId) external;
// getApproved:查询tokenId被批准给了哪个地址。function getApproved(uint256 tokenId) external view returns (address operator);
// setApprovalForAll:将自己持有的该系列NFT批量授权给某个地址operator。function setApprovalForAll(address operator, bool _approved) external;
// isApprovedForAll:查询某地址的NFT是否批量授权给了另一个operator地址。function isApprovedForAll(address owner, address operator) external view returns (bool);
// safeTransferFrom:安全转账的重载函数,参数里面包含了data。function safeTransferFrom( address from, address to, uint256 tokenId, bytes calldata data) external;
}
contract interactBAYC {// 利用BAYC地址创建接口合约变量(ETH主网)IERC721 BAYC = IERC721(0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D);// 通过接口调用BAYC的balanceOf()查询持仓量function balanceOfBAYC(address owner) external view returns (uint256 balance){return BAYC.balanceOf(owner);}// 通过接口调用BAYC的safeTransferFrom()安全转账function safeTransferFromBAYC(address from, address to, uint256 tokenId) external{BAYC.safeTransferFrom(from, to, tokenId);}
}

异常

solidity三种抛出异常的方法:error,require和assert,并比较三种方法的gas消耗。(require>assert>error)

  • Error(ver>0.8支持),省gas
error TransferNotOwner(); // 自定义errorfunction transferOwner1(uint256 tokenId, address newOwner) public {if(_owners[tokenId] != msg.sender){revert TransferNotOwner();}_owners[tokenId] = newOwner;}
  • Require:(ver<0.8之前常用方法,之后也支持) 缺点:gas与描述异常的字符串长度有关
    function transferOwner2(uint256 tokenId, address newOwner) public {require(_owners[tokenId] == msg.sender, "Transfer Not Owner");_owners[tokenId] = newOwner;}
  • Assert:
    function transferOwner3(uint256 tokenId, address newOwner) public {assert(_owners[tokenId] == msg.sender);_owners[tokenId] = newOwner;}

1 进阶

函数重载

  • solidity 允许函数重载,不允许修饰器(modifier)重载。重载函数在经过编译器编译后,由于不同的参数类型,都变成了不同的函数选择器(selector)。
function saySomething() public pure returns(string memory){return("Nothing");
}function saySomething(string memory something) public pure returns(string memory){return(something);
}

错误示例:

    function f(uint8 _in) public pure returns (uint8 out) {out = _in;}function f(uint256 _in) public pure returns (uint256 out) {out = _in;}
// 调用f(50),因为50既可以被转换为uint8,也可以被转换为uint256,因此会报错。

库合约和普通合约区别:1.不能存在状态变量 2.不能够继承或被继承 3.不能接收以太币 4.不可以被销毁。

  • String库合约:将 uint256 类型转换为相应的 string 类型的代码库
library Strings {bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";/*** @dev Converts a `uint256` to its ASCII `string` decimal representation.*/function toString(uint256 value) public pure returns (string memory) {// Inspired by OraclizeAPI's implementation - MIT licence// https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.solif (value == 0) {return "0";}uint256 temp = value;uint256 digits;while (temp != 0) {digits++;temp /= 10;}bytes memory buffer = new bytes(digits);while (value != 0) {digits -= 1;buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));value /= 10;}return string(buffer);}/*** @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.*/function toHexString(uint256 value) public pure returns (string memory) {if (value == 0) {return "0x00";}uint256 temp = value;uint256 length = 0;while (temp != 0) {length++;temp >>= 8;}return toHexString(value, length);}/*** @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.*/function toHexString(uint256 value, uint256 length) public pure returns (string memory) {bytes memory buffer = new bytes(2 * length + 2);buffer[0] = "0";buffer[1] = "x";for (uint256 i = 2 * length + 1; i > 1; --i) {buffer[i] = _HEX_SYMBOLS[value & 0xf];value >>= 4;}require(value == 0, "Strings: hex length insufficient");return string(buffer);}
}
  • 如何使用库合约:
    • 方式一:using for。用于附加库函数(从库 A)到任何类型(B)。添加完指令后,库A中的函数会自动添加为B类型变量的成员,可以直接调用。注意:在调用的时候,这个变量会被当作第一个参数传递给函数:
    • 方式二:库合约名称调用库函数
    // 利用using for指令using Strings for uint256;function getString1(uint256 _number) public pure returns(string memory){// 库函数会自动添加为uint256型变量的成员return _number.toHexString();}// 直接通过库合约名调用function getString2(uint256 _number) public pure returns(string memory){return Strings.toHexString(_number);}

常规的库:

  • String:将uint256转换为String
  • Address:判断某个地址是否为合约地址
  • Create2:更安全的使用Create2 EVM opcode
  • Arrays:跟数组相关的库函数

Import

文件结构
├── Import.sol
└── Yeye.sol// 通过文件相对位置import
import './Yeye.sol';
import {Yeye} from './Yeye.sol';// 通过网址引用
import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol';
// 通过npm的目录导入:
import '@openzeppelin/contracts/access/Ownable.sol';

接收和发送ETH

Solidity支持两种特殊的回调函数,receive() 和 fallback(),他们主要在两种情况下被使用:1)接收ETH;2)处理合约中不存在的函数调用(代理合约proxy contract)。
注意⚠️:在solidity 0.6.x版本之前,语法上只有 fallback() 函数,用来接收用户发送的ETH时调用以及在被调用函数签名没有匹配到时,来调用。 0.6版本之后,solidity才将 fallback() 函数拆分成 receive() 和 fallback() 两个函数。

  • 接收ETH函数 receive:receive()函数不能有任何的参数,不能返回任何值,必须包含external和payable。
    // 定义事件event Received(address Sender, uint Value);// 接收ETH时释放Received事件receive() external payable {emit Received(msg.sender, msg.value);}
  • 当合约接收ETH的时候,receive()会被触发。receive()最好不要执行太多的逻辑因为如果别人用send和transfer方法发送ETH的话,gas会限制在2300receive()太复杂可能会触发Out of Gas报错;如果用call就可以自定义gas执行更复杂的逻辑。
  • 有些恶意合约会在receive() 函数(老版本的是 fallback() 函数)嵌入恶意消耗gas的内容或者使得执行故意失败的代码,导致一些包含退款和转账逻辑的合约不能正常工作,因此写包含退款等逻辑的合约时候,一定要注意这种情况。
  • 回退函数 fallback() 函数会在调用合约不存在的函数时被触发。可用于接收ETH,也可以用于代理合约 proxy contract。fallback() 声明时不需要 function 关键字,必须由 external 修饰,一般也会用 payable 修饰,用于接收 ETH:fallback() external payable { … }。
    // fallbackfallback() external payable{emit fallbackCalled(msg.sender, msg.value, msg.data);}
  • receive & fallback 触发逻辑:
触发fallback() 还是 receive()?接收ETH|msg.data是空?/  \是    否/      \
receive()存在?   fallback()/ \是  否/     \
receive()   fallback()
  • transfer & send & call: 鼓励用 call
    • transfer:接收方地址.transfer(发送ETH数额);gas 限制是 2300,足够用于转账,但对方合约的 fallback() 或 receive() 函数不能实现太复杂的逻辑;转账失败,会自动 revert
    • send:接收方地址.send(发送ETH数额);gas限制是2300,足够用于转账,但对方合约的fallback()或receive()函数不能实现太复杂的逻辑;如果转账失败,不会revertsend()的返回值是bool,代表着转账成功或失败,需要额外代码处理一下; (几乎没人用)
    • call:接收方地址.call{value: 发送ETH数额}(""),没有gas限制,可以支持对方合约fallback()或receive()函数实现复杂逻辑,转账失败,不会revert,返回值是(bool, data)。
contract ReceiveETH {// 收到eth事件,记录amount和gasevent Log(uint amount, uint gas);// receive方法,接收eth时被触发receive() external payable{emit Log(msg.value, gasleft());}// 返回合约ETH余额function getBalance() view public returns(uint) {return address(this).balance;}
}contract SendETH {// 构造函数,payable使得部署的时候可以转eth进去constructor() payable{}// receive方法,接收eth时被触发receive() external payable{}// 用transfer()发送ETHfunction transferETH(address payable _to, uint256 amount) external payable{_to.transfer(amount);}}// send()发送ETH
function sendETH(address payable _to, uint256 amount) external payable{// 处理send的返回值bool success = _to.send(amount);if(!success){revert SendFailed();}
}// call()发送ETH
function callETH(address payable _to, uint256 amount) external payable{// 处理下call的返回值,如果失败,revert交易并发送error(bool success,) = _to.call{value: amount}("");if(!success){revert CallFailed();}
}

调用其他合约

// 目标合约
contract OtherContract {uint256 private _x = 0; // 状态变量_x// 收到eth的事件,记录amount和gasevent Log(uint amount, uint gas);// 返回合约ETH余额function getBalance() view public returns(uint) {return address(this).balance;}// 可以调整状态变量_x的函数,并且可以往合约转ETH (payable)function setX(uint256 x) external payable{_x = x;// 如果转入ETH,则释放Log事件if(msg.value > 0){emit Log(msg.value, gasleft());}}// 读取_xfunction getX() external view returns(uint x){x = _x;}
}// 发起调用的合约
contract CallContract{function callSetX(address _Address, uint256 x) external{OtherContract(_Address).setX(x); // 在函数里传入目标合约地址,生成目标合约的引用,然后调用目标函数。}function callGetX(OtherContract _Address) external view returns(uint x){ // 合约变量x = _Address.getX();}function callGetX2(address _Address) external view returns(uint x){OtherContract oc = OtherContract(_Address); // 合约变量x = oc.getX();}function setXTransferETH(address otherContract, uint256 x) payable external{OtherContract(otherContract).setX{value: msg.value}(x); // wei}
}
  • 如果目标合约的函数是payable的,那么我们可以通过调用它来给合约转账:_Name(_Address).f{value: _Value}()。wei 为单位。

call、delegatecall

call 是 address 类型的低级成员函数,用来与其他合约交互。它的返回值为(bool, data),分别对应call是否成功以及目标函数的返回值。

二进制编码参数 = abi.encodeWithSignature("函数签名", 逗号分隔的具体参数)
abi.encodeWithSignature("f(uint256,address)", _x, _addr)
目标合约地址.call(二进制编码);
目标合约地址.call{value:发送数额, gas:gas数额}(二进制编码);
  • call 是 solidity 官方推荐的通过触发 fallback 或 receive 函数发送 ETH 的方法。不推荐用 call 来调用另一个合约,因为当你调用不安全合约的函数时,你就把主动权交给了它。推荐的方法仍是声明合约变量后调用函数。
  • 当不知道对方合约的源代码或 ABI,就没法生成合约变量;这时仍可以通过 call 调用对方合约的函数。
contract OtherContract {uint256 private _x = 0; // 状态变量x// 收到eth的事件,记录amount和gasevent Log(uint amount, uint gas);fallback() external payable{}// 返回合约ETH余额function getBalance() view public returns(uint) {return address(this).balance;}// 可以调整状态变量_x的函数,并且可以往合约转ETH (payable)function setX(uint256 x) external payable{_x = x;// 如果转入ETH,则释放Log事件if(msg.value > 0){emit Log(msg.value, gasleft());}}// 读取xfunction getX() external view returns(uint x){x = _x;}
}contract test {// 定义 Response 事件,输出 call 返回的结果 success 和 dataevent Response(bool success, bytes data);function callSetX(address payable _addr, uint256 x) public payable {// call setX(),同时可以发送ETH(bool success, bytes memory data) = _addr.call{value: msg.value}(abi.encodeWithSignature("setX(uint256)", x));emit Response(success, data); //释放事件}function callGetX(address _addr) external returns(uint256){// call getX()(bool success, bytes memory data) = _addr.call(abi.encodeWithSignature("getX()"));emit Response(success, data); //释放事件return abi.decode(data, (uint256));}
}
// 如果我们给call输入的函数不存在于目标合约,那么目标合约的fallback函数会被触发。
  • Delegatecall委托调用与call的区别:委托调用改变的是B的状态。delegatecall 在调用合约时可以指定交易发送的 gas,但不能指定发送的ETH数额

目标合约地址.delegatecall(二进制编码);

注意:delegatecall有安全隐患,使用时要保证当前合约和目标合约的状态变量存储结构相同,并且目标合约安全,不然会造成资产损失。
使用场景:
1.代理合约(Proxy Contract):将智能合约的存储合约和逻辑合约分开:代理合约(Proxy Contract)存储所有相关的变量,并且保存逻辑合约的地址;所有函数存在逻辑合约(Logic Contract)里,通过delegatecall执行。当升级时,只需要将代理合约指向新的逻辑合约即可。
2.EIP-2535 Diamonds(钻石):钻石是一个支持构建可在生产中扩展的模块化智能合约系统的标准。钻石是具有多个实施合同的代理合同。

// 被调用的合约C
contract C {uint public num;address public sender;function setVars(uint _num) public payable {num = _num;sender = msg.sender;}
}
// 合约B必须和目标合约C的变量存储布局必须相同:变量名可以不同,但变量类型和声明顺序必须相同
contract B {uint public num;address public sender;// 通过call来调用C的setVars()函数,将改变合约C里的状态变量function callSetVars(address _addr, uint _num) external payable{// call setVars()(bool success, bytes memory data) = _addr.call(abi.encodeWithSignature("setVars(uint256)", _num));}// 通过delegatecall来调用C的setVars()函数,将改变合约B里的状态变量function delegatecallSetVars(address _addr, uint _num) external payable{// delegatecall setVars()(bool success, bytes memory data) = _addr.delegatecall(abi.encodeWithSignature("setVars(uint256)", _num));}}
}

在合约中创建新合约:create、create2

Contract x = new Contract{value: _value}(params)

简易的 Uniswap 代码:Uniswap V2 中包含了

  • UniswapV2Pair: 币对合约,用于管理币对地址、流动性、买卖。
  • UniswapV2Factory: 工厂合约,用于创建新的币对,并管理币对地址。
contract Pair{address public factory; // 工厂合约地址address public token0; // 代币1address public token1; // 代币2constructor() payable {factory = msg.sender;}// called once by the factory at time of deploymentfunction initialize(address _token0, address _token1) external {require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient checktoken0 = _token0;token1 = _token1;}
}

提问:为什么uniswap不在constructor中将token0和token1地址更新好?
答:因为uniswap使用的是create2创建合约,限制构造函数不能有参数。当使用create时,Pair合约允许构造函数有参数,可以在constructor中将token0和token1地址更新好。

contract PairFactory{mapping(address => mapping(address => address)) public getPair; // 通过两个代币地址查Pair地址address[] public allPairs; // 保存所有Pair地址function createPair(address tokenA, address tokenB) external returns (address pairAddr) {// 创建新合约Pair pair = new Pair(); // 调用新合约的initialize方法pair.initialize(tokenA, tokenB);// 更新地址mappairAddr = address(pair);allPairs.push(pairAddr);getPair[tokenA][tokenB] = pairAddr;getPair[tokenB][tokenA] = pairAddr;}
}
  • CREATE2 操作码使我们在智能合约部署在以太坊网络之前就能预测合约的地址。Uniswap 创建 Pair 合约用的就是 CREATE2 而不是 CREATE。
  • CREATE 如何计算地址
    智能合约可以由其他合约和普通账户利用 CREATE 操作码创建。 在这两种情况下,新合约的地址都以相同的方式计算:新地址 = hash(创建者地址, nonce)
  • CREATE2如何计算地址
    CREATE2的目的是为了让合约地址独立于未来的事件。不管未来区块链上发生了什么,你都可以把合约部署在事先计算好的地址上(与nonce无关):新地址 = hash("0xFF",创建者地址, salt, bytecode)
Contract x = new Contract{salt: _salt, value: _value}(params)
  • 基于 create2 的 uniswap demo:
contract PairFactory2{mapping(address => mapping(address => address)) public getPair; // 通过两个代币地址查Pair地址address[] public allPairs; // 保存所有Pair地址function createPair2(address tokenA, address tokenB) external returns (address pairAddr) {require(tokenA != tokenB, 'IDENTICAL_ADDRESSES'); //避免tokenA和tokenB相同产生的冲突// 计算用tokenA和tokenB地址计算salt(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); //将tokenA和tokenB按大小排序bytes32 salt = keccak256(abi.encodePacked(token0, token1));// 用create2部署新合约Pair pair = new Pair{salt: salt}(); // 调用新合约的initialize方法pair.initialize(tokenA, tokenB);// 更新地址mappairAddr = address(pair);allPairs.push(pairAddr);getPair[tokenA][tokenB] = pairAddr;getPair[tokenB][tokenA] = pairAddr;}// 提前计算pair合约地址function calculateAddr(address tokenA, address tokenB) public view returns(address predictedAddress){require(tokenA != tokenB, 'IDENTICAL_ADDRESSES'); //避免tokenA和tokenB相同产生的冲突// 计算用tokenA和tokenB地址计算salt(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); //将tokenA和tokenB按大小排序bytes32 salt = keccak256(abi.encodePacked(token0, token1));// 计算合约地址方法 hash()predictedAddress = address(uint160(uint(keccak256(abi.encodePacked(bytes1(0xff),address(this),salt,keccak256(type(Pair).creationCode))))));}

CREATE2 让我们可以在部署合约前确定它的合约地址,这是一些 Layer2 项目的基础。

删除合约

selfdestruct 命令可以用来删除智能合约,并将该合约剩余ETH转到指定地址。selfdestruct 是为了应对合约出错的极端情况而设计的。它最早被命名为 suicide,但是这个词太敏感。为了保护抑郁的程序员,改名为 selfdestruct。

contract DeleteContract {uint public value = 10;constructor() payable {}receive() external payable {}function deleteContract() external onlyOwner {// 调用selfdestruct销毁合约,并把剩余的ETH转给msg.senderselfdestruct(payable(msg.sender));}function getBalance() external view returns(uint balance){balance = address(this).balance;}
}
  • 当合约被销毁后与智能合约的交互也能成功,并且返回0。

ABI 编码解码

ABI (Application Binary Interface,应用二进制接口) 是与以太坊智能合约交互的标准。数据基于他们的类型编码;并且由于编码后不包含类型信息,解码时需要注明它们的类型。 Solidity中,ABI编码有4个函数:abi.encode, abi.encodePacked, abi.encodeWithSignature, abi.encodeWithSelector。而ABI解码有1个函数:abi.decode,用于解码abi.encode的数据。

// 与合约交互采用的方案:function encode() public view returns(bytes memory result) {result = abi.encode(x, addr, name, array); // 每个参数填充为32字节(256bits),不够的用前导0填充}// 省空间,不与合约交互,比如组合起来算hashfunction encodePacked() public view returns(bytes memory result) {result = abi.encodePacked(x, addr, name, array); // 根据每个参数所需最低空间编码,}
// 与 encode 类似,用于与合约交互,等同于在abi.encode编码结果前加上了4字节的函数选择器function encodeWithSignature() public view returns(bytes memory result) {result = abi.encodeWithSignature("foo(uint256,address,string,uint256[2])", x, addr, name, array);}
// 与abi.encodeWithSignature功能类似,只不过第一个参数为函数选择器,为函数签名Keccak哈希的前4个字节function encodeWithSelector() public view returns(bytes memory result) {result = abi.encodeWithSelector(bytes4(keccak256("foo(uint256,address,string,uint256[2])")), x, addr, name, array);}// 解码function decode(bytes memory data) public pure returns(uint dx, address daddr, string memory dname, uint[2] memory darray) {(dx, daddr, dname, darray) = abi.decode(data, (uint, address, string, uint[2]));}
  • 使用场景:在合约开发中,ABI常配合call来实现对合约的底层调用
    bytes4 selector = contract.getValue.selector;bytes memory data = abi.encodeWithSelector(selector, _x);(bool success, bytes memory returnedData) = address(contract).staticcall(data);require(success);return abi.decode(returnedData, (uint256));
  • 使用场景:ethers.js中常用ABI实现合约的导入和函数调用。
    const wavePortalContract = new ethers.Contract(contractAddress, contractABI, signer);const waves = await wavePortalContract.getAllWaves();
  • 使用场景:对不开源合约进行反编译后,某些函数无法查到函数签名,可通过ABI进行调用。

Hash

Hash 的抗碰撞性:

  • 弱抗碰撞性:给定一个消息x,找到另一个消息x’使得hash(x) = hash(x’)是困难的。
  • 强抗碰撞性:找到任意x和x’,使得hash(x) = hash(x’)是困难的。

Keccak256和sha3:
sha3由keccak标准化而来,在很多场合下Keccak和SHA3是同义词,但在2015年8月SHA3最终完成标准化时,NIST调整了填充算法。所以SHA3就和keccak计算的结果不一样,这点在实际开发中要注意。

以太坊在开发的时候sha3还在标准化中,所以采用了keccak,所以Ethereum和Solidity智能合约代码中的SHA3是指Keccak256,而不是标准的NIST-SHA3,为了避免混淆,直接在合约代码中写成Keccak256是最清晰的。

选择器 Selector

调用智能合约时,本质上是向目标合约发送了一段calldata。calldata中前4个字节是selector(函数选择器)。

  • msg.data是solidity中的一个全局变量,值为完整的calldata(调用函数时传入的数据)。
    // event 返回msg.dataevent Log(bytes data);function mint(address to) external{emit Log(msg.data);}
/*params: 0x2c44b726ADF1963cA47Af88B284C06f30380fC78calldata: 0x6a6278420000000000000000000000002c44b726adf1963ca47af88b284c06f30380fc78前4个字节为函数选择器selector:0x6a627842// method id定义为函数签名的Keccak哈希后的前4个字节,当selector与method id相匹配时,即表示调用该函数后面32个字节为输入的参数:0x0000000000000000000000002c44b726adf1963ca47af88b284c06f30380fc78
*/
    function callWithSignature() external returns(bool, bytes memory){(bool success, bytes memory data) = address(this).call(abi.encodeWithSelector(0x6a627842, "0x2c44b726ADF1963cA47Af88B284C06f30380fC78"));return(success, data);}

try-catch

  • 版本:solidity0.6+后支持
  • 在solidity中,try-catch只能被用于external函数或创建合约时constructor(被视为external函数)的调用。基本语法如下:
        try externalContract.f() {// call成功的情况下 运行一些代码} catch {// call失败的情况下 运行一些代码}// 可以使用this.f()来替代externalContract.f(),this.f()也被视作为外部调用,但不可在构造函数中使用,因为此时合约还未创建。try externalContract.f() returns(returnType){// call成功的情况下 运行一些代码} catch Error(string memory reason) {// 捕获失败的 revert() 和 require()} catch (bytes memory reason) {// 捕获失败的 assert()}

demo:

contract OnlyEven{constructor(uint a){require(a != 0, "invalid number");assert(a != 1);}function onlyEven(uint256 b) external pure returns(bool success){// 输入奇数时revertrequire(b % 2 == 0, "Ups! Reverting");success = true;}
}contract demo{// 成功eventevent SuccessEvent();// 失败eventevent CatchEvent(string message);event CatchByte(bytes data);// 声明OnlyEven合约变量OnlyEven even;constructor() {even = new OnlyEven(2);}// 在external call中使用try-catchfunction execute(uint amount) external returns (bool success) {try even.onlyEven(amount) returns(bool _success){// call成功的情况下emit SuccessEvent();return _success;} catch Error(string memory reason){// call不成功的情况下emit CatchEvent(reason);}}// 在创建新合约中使用try-catch (合约创建被视为external call)// executeNew(0)会失败并释放`CatchEvent`// executeNew(1)会失败并释放`CatchByte`// executeNew(2)会成功并释放`SuccessEvent`function executeNew(uint a) external returns (bool success) {try new OnlyEven(a) returns(OnlyEven _even){// call成功的情况下emit SuccessEvent();success = _even.onlyEven(a);} catch Error(string memory reason) {// catch失败的 revert() 和 require()emit CatchEvent(reason);} catch (bytes memory reason) {// catch失败的 assert()emit CatchByte(reason);}}}

2 应用(本文略)

考虑本文已经过长了,应用和安全的部分在后续的博客中列出。

Reference

  1. 官方文档中文版
  2. up主 崔棉大师
  3. wtf academy
  4. WTFSolidity github

相关文章:

Solidity 学习笔记

主要参考网上资料学习&#xff0c;个人学习笔记有删改&#xff0c;参考出处在文末列出。 0 基础 IDE: remixType Bool: bool public _bool true; 默认false;整型&#xff1a;int、uint、uint256&#xff0c;默认0;地址类型&#xff1a;address&#xff0c;分为 payable 和普…...

ThreadLocal原理

关键点总结&#xff1a; ThreadLocal更像是对其他类型变量的一层包装&#xff0c;通过ThreadLocal的包装使得该变量可以在线程之间隔离和当前线程全局共享。在Thread中有一个threadLocals变量&#xff0c;类型为ThreadLocal.ThreadLocalMap&#xff0c;ThreadLocalMap中key是Th…...

串操作指令详解 MOVS,LODS,STOS,CMPS,SCAS,REP

指令包括&#xff1a;MOVS&#xff0c;LODS&#xff0c;STOS&#xff0c;CMPS&#xff0c;SCAS&#xff0c;REP 串的概念&#xff1a;串是连续存放再内存中的字节块或字块。每个串有一个起始地址和长度&#xff0c; 待操作的数据串称为源串&#xff0c;目的地址称为目标串 目录…...

Java实现判断素数

1 问题 判断101-200之间有多少个素数&#xff0c;并输出所有素数。 2 方法 package homework04; public class Test05 { public static void main(String[] args) { for (int i 101; i < 201; i) { boolean flag true; for (int j 2; j…...

PHP初级教程------------------(2)

目录 运算符 赋值运算符 算术运算符 比较运算符 逻辑运算符 连接运算符 错误抑制符 三目运算符 自操作运算符 ​编辑 计算机码 位运算符 运算符优先级 流程控制 控制分类 顺序结构 分支结构 If分支 ​ Switch分支 循环结构 For循环 while循环 do-while循环 循环控制 ​ …...

【SQL开发实战技巧】系列(三十五):数仓报表场景☞根据条件返回不同列的数据以及Left /Full Join注意事项

系列文章目录 【SQL开发实战技巧】系列&#xff08;一&#xff09;:关于SQL不得不说的那些事 【SQL开发实战技巧】系列&#xff08;二&#xff09;&#xff1a;简单单表查询 【SQL开发实战技巧】系列&#xff08;三&#xff09;&#xff1a;SQL排序的那些事 【SQL开发实战技巧…...

springBoot自动配置过程介绍

什么是自动配置 以前整合spring mybatis框架时候&#xff0c;需要加很多的bean, 比如说sqlSessionFactory等等 现在springboot帮我们干了&#xff0c;我们只需要引入对应的starter就可以了。 springBoot可以帮我们配置好了一些bean. 如mysql, mogondb相关操作等等&#xff…...

PostgreSQL最后的救命稻草 — pg_resetwal

pg_resetwal— 重置 PostgreSQL 数据库集群的预写日志和其他控制信息 适用版本&#xff1a;PostgreSQL 12/13/14/15语法 pg_resetwal [ -f | --force ] [ -n | --dry-run ] [option...] [ -D | --pgdata ]datadir描述pg_resetwal清除预写日志 WAL&#xff0c;并可选地重置pg_c…...

彻底关闭Windows更新

一、关闭Windows Update服务 1、按“Windows R”键&#xff0c;打开运行对话框&#xff0c;并输入“services.msc”&#xff0c;然后再单击“确定”。 2、在弹出的服务窗口中&#xff0c;找到“Windows Update”选项并双击打开它。 3、在弹出的“Windows Update的属性”对话框…...

Java正则表达式语法

Java正则表达式的语法与示例 | |目录 1匹配验证-验证Email是否正确 2在字符串中查询字符或者字符串 3常用正则表达式 4正则表达式语法 1匹配验证-验证Email是否正确 public static void main(String[] args) { // 要验证的字符串 String str "servicexsoftlab.net&q…...

【2023-3-29】JavaScript使用promise顺序调用函数并抛出异常

JavaScript使用promise顺序调用函数并抛出异常 场景 新建或者编辑时&#xff0c;一个页面中存在多个表单&#xff0c;每个表单都有单独进行表单验证。点击提交时&#xff0c;若有一个表单校验失败&#xff0c;则不能提交。 ps&#xff1a;为啥不放在一个表单中&#xff1f; (…...

Python实现GWO智能灰狼优化算法优化随机森林分类模型(RandomForestClassifier算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 灰狼优化算法(GWO)&#xff0c;由澳大利亚格里菲斯大学学者 Mirjalili 等人于2014年提出来的一种群智能…...

从redis到epoll到mmap

redis为什么这么快&#xff1f; 比较容易答出的答案 1)纯粹的内存操作 2)单线程操作,不用考虑线程切换 其他优势 3)I/O 多路复用,使用epoll 4)Reactor 设计模式 I/O 多路复用有三种 select、poll、epoll select&#xff1a;使用数组存储轮询 poll&#xff1a;使用链表轮询 epo…...

STM32CubeMX快速构造工程模板(一)

STM32CubeMX作为一个免费开源的软件,能够可视化配置STM32或其他产品硬件资源,能过快速地构造工程模板,很是方便!!! 目录 STM32CubeMX快速构造工程模板 首先第一步,打开软件-点击按钮-输入型号-双击打开。...

Java Web中的ServletContext对象

目录 ServletContext对象 获取上下文初始化参数的相关方法 创建ServletContext对象 1&#xff09;通过 GenericServlet 提供的 getServletContext() 方法 2&#xff09;通过 ServletConfig 提供的 getServletContext() 方法 3&#xff09;通过 HttpSession 提供的 getServletCo…...

回归预测 | MATLAB实现PSO-RF粒子群算法优化随机森林多输入单输出回归预测

回归预测 | MATLAB实现PSO-RF粒子群算法优化随机森林多输入单输出回归预测 目录回归预测 | MATLAB实现PSO-RF粒子群算法优化随机森林多输入单输出回归预测效果一览基本介绍程序设计参考资料效果一览 基本介绍 MATLAB实现PSO-RF粒子群算法优化随机森林多输入单输出回归预测 粒子…...

在小公司工作3年,从事软件测试5年了,才发现自己还是处于“初级“水平,是不是该放弃....

毕业前三年&#xff0c;从早到晚&#xff0c;加班到深夜&#xff0c;一年又一年&#xff0c;直至刚入职场的首个黄金三年过年都去了&#xff0c;而职位却仍在原地踏步。尽管感觉自己努力过&#xff0c;但是实际上&#xff0c;自身的能力从没得到过多少提升。 所以在无数个夜晚…...

基于 OpenCV 与 Java 两个语言版本实现获取某一图片特定区域的颜色对比度

本文目录一、什么是对比度二、什么是颜色直方图三、如何通过RGB计算颜色对比度什么是HSV、Lab颜色空间四、OpenCV代码五、Java代码5.1 平滑处理5.2 完整代码一、什么是对比度 对比度是指图像中不同区域之间的明暗差异程度&#xff0c;它是图像质量中的重要指标之一。除了颜色对…...

Book:实战Java高并发程序设计(第二版)

实战Java高并发程序设计&#xff08;第二版&#xff09;为什么会有并行计算&#xff1f;并行计算需要回答的问题基本概念并发级别有哪些&#xff1f;Amdahl定律和Gustafson定律Java并发三特性进程和线程线程的生命周期Thread类run()与start()的区别为什么会有并行计算&#xff…...

LeetCode 831. Masking Personal Information【字符串,正则表达式】中等

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…...

递增三元组

[蓝桥杯 2018 省 B] 递增三元组 题目描述 给定三个整数数组 A[A1,A2,⋯,AN]A [A_1, A_2,\cdots, A_N]A[A1​,A2​,⋯,AN​]&#xff0c;B[B1,B2,⋯,BN]B [B_1, B_2,\cdots, B_N]B[B1​,B2​,⋯,BN​]&#xff0c;C[C1,C2,⋯,CN]C [C_1, C_2,\cdots,C_N]C[C1​,C2​,⋯,CN​…...

java源码阅读 - TreeSet

往期文章 用最简单的话讲最明白的红黑树java源码阅读 - HashMap数据结构 - 堆与堆排序 文章目录往期文章一、介绍二、类的声明三、成员变量四、构造函数五、常用方法1. NavigableSet接口的实现2. SortedSet接口的实现六、总结一、介绍 在上期文章中&#xff0c;我们从源码层面…...

写毕业论文经验贴

首先说一句不要靠近word&#xff0c;会变得不幸。最好用latex写&#xff0c;不过我当时懒得下载latex了&#xff0c;于是后期改格式花了点时间 写论文之前 事先把所有的论文都查好并且整理好&#xff0c;论文第一、二章写起来就会很快&#xff1b; 把实验做顺溜&#xff0c;实…...

2.7 进程退出、孤儿进程、僵尸进程+2.8 wait函数+2.9 waitpid函数

1.进程退出 子进程退出时&#xff1a;父进程帮助子进程回收内核区的资源 exit.c /*#include <stdlib.h>void exit(int status);#include <unistd.h>void _exit(int status);status参数&#xff1a;是进程退出时的一个状态信息。父进程回收子进程资源的时候可以获取…...

【新2023Q2模拟题JAVA】华为OD机试 - 预订酒店

最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧本篇题解:预订酒店 题目 放暑假了,橡…...

一个完整的渗透学习路线是怎样的?如何成为安全渗透工程师?

前言 1/我是如何学习黑客和渗透&#xff1f; 我是如何学习黑客和渗透测试的&#xff0c;在这里&#xff0c;我就把我的学习路线写一下&#xff0c;让新手和小白们不再迷茫&#xff0c;少走弯路&#xff0c;拒绝时间上的浪费&#xff01; 2/学习常见渗透工具的使用 注意&…...

刷完这60个标准库模块,成为Python骨灰级玩家

python强大&#xff0c;主要是因为包多&#xff0c;且不说第三方包&#xff0c;单是标准库就已让人望而生畏。 如果从第一篇整理标准库的博客算起&#xff0c;如今已有三个年头。在整理标准库的过程中&#xff0c;查阅了大量资料和官方文档&#xff0c;很多中文资料都有一个共…...

EasyExcel的简单使用(easyExcel和poi)

EasyExcel的简单使用 前言 Excel读 1.实体类 2.读监听器与测试类 3.输出结果 Excel写 1.实体类 2.写入Excel的测试类 3.输出结果 填充Excel 1.Excel模板 2.测试类 3.输出结果 前言 EasyExcel类是一套基于Java的开源Excel解析工具类&#xff0c;相较于传统的框架如Apache poi、…...

命名空间 namespace

一、命名空间的定义 定义命名空间&#xff0c;使用namespace关键字&#xff0c;后面跟命名空间的名字&#xff0c;然后接一对花括号{ } 即可&#xff0c;{ }中即为命名空间的成员。 1.一般定义 namespace test {int a 10;int b 100;int ADD(int x, int y){return x y;} }…...

我能“C”——初阶指针(上)

目录 1.什么是指针&#xff1f; 2. 指针和指针类型 3.野指针 3.1野指针的成因 3.2 如何规避野指针 1.什么是指针&#xff1f; 指针理解的2个要点&#xff1a; 1. 指针是内存中一个最小单元的编号&#xff0c;也就是地址 2. 平时口语中说的指针&#xff0c;通常指的是指针…...