函数
Solidity 函数的定义格式如下:
function
函数名(参数类型1 参数名名称1, ...)
{internal|external|public|private}
[pure|view|payable]
[returns (返回值类型1 [返回值名称1], ...)] {
// 函数体
}
构造函数
构造函数仅在合约部署时调用一次,主要用于初始化状态变量。
contract Demo {
address public owner;
uint public num;
constructor(uint _num) {
owner = msg.sender;
num = _num;
}
}
使用键值输入调用函数
键值输入允许你在调用函数时指定参数的名称,而不是仅仅按顺序提供参数值。这在参数较多或参数顺序容易混淆的情况下特别有用。
contract Demo {
// 定义一个函数, 接受两个参数
function setValues(uint key, uint value) public pure returns (uint, uint) {
return (key, value);
}
function callSetValues() public pure returns (uint, uint) {
// 使用键值输入调用 setValues 函数
return setValues({value: 42, key: 7});
}
}
函数输出
- 单输出:
contract Demo {
function getSingleNumber() public pure returns (uint) {
return 42;
}
}
- 多输出:
contract Demo {
function getMultipleNumbers()
public
pure
returns (uint, uint, uint[3] memory)
{
return (1, 2, [3, uint(4), 5]);
// [3, 4, 5] 默认被视为 uint8[3]; 要创建 uint 数组, 需要至少一个元素显式转换 uint
}
}
- 命名输出:
contract Demo {
function getNamedNumbers1() public pure returns (uint first, uint second) {
first = 3;
second = 4;
}
// 相当于
function getNamedNumbers2() public pure returns (uint, uint) {
uint first;
uint second;
first = 3;
second = 4;
return (first, second);
}
}
当然啦,即使使用了命名输出,也仍可在函数体中使用 return 显式返回,且 return 的返回值优先级更高。
- 获取函数返回值:
contract Demo {
// ...
function callFunctions()
public
pure
returns (uint, uint, uint, uint[3] memory, uint)
{
uint singleNumber = getSingleNumber(); // 获取单个返回值
(uint a, uint b, uint[3] memory c) = getMultipleNumbers(); // 获取多个返回值
(, uint y) = getNamedNumbers1(); // 获取多个返回值的一部分
return (singleNumber, a, b, c, y);
}
}
Modifier
函数装饰器 (Modifier) 可以在函数执行之前或之后插入代码逻辑,比如检查条件、限制访问权限等。
contract Demo {
address public owner;
constructor() {
owner = msg.sender;
}
// 定义装饰器
modifier onlyOwner() {
require(msg.sender == owner, "Not the contract owner");
_;
}
// 使用装饰器
function changeOwner(address newOwner) public onlyOwner {
owner = newOwner;
}
}
装饰器可以接收参数 & 函数可以同时使用多个装饰器:
contract Demo {
address public owner;
constructor() {
owner = msg.sender;
}
// 定义装饰器
modifier onlyOwner() {
require(msg.sender == owner, "Not the contract owner");
_;
}
// 定义带参数的装饰器
modifier validAddress(address _addr) {
require(_addr != address(0), "Invalid address");
// address(0) is often used to represent an invalid or uninitialized address.
_;
}
// 同时使用多个装饰器
function changeOwner(
address newOwner
) public onlyOwner validAddress(newOwner) {
owner = newOwner;
}
}
三明治装饰器:可以在函数执行之前和之后插入代码逻辑的装饰器。demo - 防止重入攻击:
contract Demo {
bool private locked = false;
// 定义一个防止重入攻击的装饰器
modifier noReentrant() {
require(!locked, "No Reentrant");
locked = true;
_;
locked = false;
}
// 使用装饰器
function withdraw() public noReentrant {
// ... 提现逻辑 ...
}
}
异常处理
Solidity 中有 3 个异常处理方法:
contract Demo {
// 1. require
function testRequire(uint a) public pure returns (uint) {
require(a > 10, "a must be greater than 10"); // 若条件不满足, 则抛出 `Error(string)`; 会退还剩余的 gas
// 参数 2 可选; 参数 2 越长, 消耗的 gas 越多
return a;
}
// 2. revert
function testRevert(uint a) public pure returns (uint) {
if (a <= 10) {
revert("a must be greater than 10"); // 抛出 `Error(string)`, 会退还剩余的 gas
// 参数 1 可选; 参数 1 越长, 消耗的 gas 越多
}
return a;
}
// 3. assert
function testAssert(uint a) public pure returns (uint) {
assert(a > 10); // 若条件不满足, 则抛出 `Panic(uint256)`; 会消耗剩余的 gas
return a;
}
}
自定义异常 - 可以自定义异常信息,且通常比字符串描述更节省 gas;搭配 revert 使用:
contract Demo {
// 声明自定义异常
error MyError(address sender, uint value);
function testCustomError(uint a) public view returns (uint) {
if (a <= 10) {
revert MyError(msg.sender, a); // 通过 revert 使用自定义异常
}
return a;
}
}
try catch - 适用于处理外部函数调用或合约创建中的错误,支持 Error
和 Panic
错误:
interface DataFeed {
function getData(address token) external returns (uint value);
}
contract FeedConsumer {
// 声明一个 DataFeed 类型的变量 feed
DataFeed feed;
// 声明一个用于记录错误次数的变量 errorCount
uint errorCount;
// 定义一个函数 rate, 接收一个地址类型的参数 token, 返回一个 uint 和一个 bool 类型的值
function rate(address token) public returns (uint, bool) {
// 使用 require 语句检查 errorCount 是否小于 10, 如果不满足条件则抛出错误并返回消息
require(errorCount < 10, "Too many errors");
// 使用 try/catch 语句尝试调用 feed.getData 函数
try feed.getData(token) returns (uint v) {
return (v, true);
} catch Error(
string memory reason // 捕获 Error 类型的错误, 并返回错误原因
) {
errorCount++;
return (0, false);
} catch Panic(
uint errorCode // 捕获 Panic 类型的错误, 并返回错误代码
) {
errorCount++;
return (0, false);
} catch (
bytes memory // 捕获其他类型的错误
) {
errorCount++;
return (0, false);
}
}
}