Solidity 学习笔记

学习 solidity 的笔记,只记录其中一些关键信息,会比较零散,用在回忆的时候翻一翻。

状态变量和局部变量

contract ContractName {
	//这是一个状态变量
    int a; 

    function add(int b) returns(int) {
        //b被定义为函数的输入参数,所以它不是状态变量
        //c是在函数中定义的,所以它也不是状态变量
        int c = a + b;	
        return c;
    }
}

状态变量是一种永久存在于区块链上的变量。

作用域

公共函数和私有函数

uint public a;
function aa() public { 
	//funciton body 
}

为什么需要区分 public 和 private ?

  1. 安全性:通过将某些函数标记为 private ,可以确保只有合约内部的其他函数可以调用它们。这可以防止外部恶意合约或攻击者调用可能对合约安全性构成威胁的内部函数。
  2. 隐私性:有时,合约可能包含处理敏感信息的函数。通过将这些函数标记为 private ,可以防止外部查看或访问这些敏感数据。这有助于保护用户的隐私。
  3. 优化和成本:public 函数通常需要更多的燃气( gas )来执行,因为它们需要处理许多安全性和访问控制检查。通过将某些非必要公开函数标记为 private ,可以减少合约执行时的燃气成本,从而提高效率。

定函数或变量的可见性或作用域 关键字还有 internal ,有时我们将限定某些变量或函数仅在内部合约使用。

function aa() internal{}

在编写合约时,某些情况下合约中的特定功能需要与其他合约共享,此时我们可以使用external关键词。

function aa() external{}

在本合约中使用时必须加上this关键词。

external 不能用于定义变量。

接口合约中的函数都必须是 external 的。

变量可以分为 状态变量 和 局部变量, 函数也可以分为3种 —— pure 函数、view 函数和其他函数。使用 pure 定义的函数被调用时不用花费 gas,并且可以保证该函数不会改变状态变量,有益于开发时的模块化管理。view 函数不会修改状态变量,但可能使用(读取)状态变量,而 pure 函数甚至不会读取状态变量。

payable:它表示该函数可以接收以太币( ETH ),payable 关键字可以使该函数成为一个可支付函数。只有 public 和 external 的函数支持 payable 修饰。因为如果函数在合约外部不可见的话,用户就无法调用函数,也就自然无法给支付以太给函数。

msg.sender:表示当前函数调用者的地址。

msg.value:用于获取当前函数调用时传递给合约的以太币( Ether )数量。它表示当前函数调用中附带的以太币金额,通过读取 msg.value 的值,合约可以确定用户向其发送的以太币数量,进而执行相应的逻辑。msg.value 的单位是 Wei,是以太坊最小的货币单位。1 Eth = 10^18 Wei。

调用支付函数:只需要在函数名和()之间插入一个{value : xx}语法即可,其中 xx 代表你需要附加的 ETH 数量。例如:

convert{value: 10}();

block.number:是指当前的区块高度,也就是当前区块在整个区块链中的位置。每个新的区块都会递增这个值,所以它可以用来确定某个区块在区块链中的相对位置。

block.timestamp:用来获取当前区块的时间戳。即当前区块生成时距离1970年1月1日的秒数。它反映了从1970年1月1日00:00:00 UTC 到当前区块生成时经过的时间。

event:在 solidity 中我们使用 event 关键字来声明一个事件,其后是事件名,随后用括号把参数括起来。

//在这里我们定义了一个名为EventName的事件,其有parameter1和parameter2两个参数。
event EventName(
  uint256 parameter1,
  uint256 parameter2
);

事件使用场景:假设你是一个电商平台的管理员,你有一个智能合约来处理用户下单的过程。当有人下单时,你需要通知所有相关方,例如买家、卖家和物流公司。

  1. 定义一个名为 Order 的事件,里面包括下单者的地址,下单的物品,下单的数量。
event Order(address sender, string goods, uint count);
  1. 在有人下单的时候广播 Order 事件,这样所有人都可以收到下单者的地址,下单的物品,下单的数量这三个信息。

emit:要广播一个事件,你需要使用 emit 关键字。emit 用于初始化事件,并根据事件的定义设置所有需要的信息,然后广播这个事件。这样,所有订阅了该事件的人或系统就会收到通知。

//在这里我们提交了一个名为MessageSent的事件,参数分别为msg.sender和message。
emit MessageSent(msg.sender, message);

indexed:事件的参数默认是不可搜索的,也就是说,你不能直接根据事件参数的值来过滤和搜索事件。然而,当你将某个参数标记为 indexed 时,Solidity 会为该参数创建一个额外的索引,使得你可以根据该参数的值进行过滤和搜索。

event LogData(uint indexed id);

合约变量:合约类型的变量就是一个合约的实例。这个实例可以访问合约的所有公共函数和变量。该变量可以调用其对应的合约,并且可以与地址类型 address 相互转换。从类型来讲,他是一个引用类型的变量。

modifier:函数修饰符允许开发人员在函数执行前后或期间插入代码,以便修改函数的行为或确保特定的条件得到满足,函数修饰符内的代码的不能被独立执行。_; 被用来在 modifier 中指定一个地方执行被修饰的函数的代码。在一个 modifier 中,如果缺少 _; 语句,编译器会报错。因为你必须明确的告诉编译器在什么时候需要重新执行被修饰的函数代码。

modifier demo() {
 ...  // 函数执行前执行的代码
 _;   // 执行被修饰的函数
 ...  // 函数执行结束后执行的代码
}

require:断言,终止当前函数并抛出异常。主要用于验证用户输入。

require(locked == 0, "error message");

revert:立即停止当前函数的执行,并撤销所有对状态的更改。

error:一种自定义的错误类型:错误(error),用于表示合约执行过程中的异常情况。它可以让开发者定义特定的错误状态,以便在合约的执行过程中进行特定的异常处理。

error SystemInsufficient(address account);

assert:语句应用于检查一些被认为永远不应该发生的情况(例如除数为0),如果发生这种情况,则说明你的合约中存在错误,你应该修复。

多重继承

contract A is B, C{ }

抽象合约和接口的最大区别在于,抽象合约可以包含变量和实现,而接口只包含没有实现的函数。抽象合约和普通合约的唯一区别在于能否被部署。

keccak256:是一个全局函数,可以在函数中直接使用该函数进行哈希计算。哈希计算是一种将任意长度的数据转换为固定长度哈希值的过程。而哈希的特点是——不同的字符串哈希出来的结果几乎不可能相同。这在生成数据唯一标识、加密签名等领域有重大意义。

bytes32 hash = keccak256(bytes("Hello, World!"));

receive:receive 函数只用于处理接收 ETH,一个合约最多只有一个,而且不能有任何的参数,不能返回任何值,必须包含 external 和 payable 。

//这里我们定义一个空的receive函数。
receive() external payable { ... }

receive不是必须得,你可以选择定义 receive 也可以不定义,但是如果不定义,在合约收到转账时可能会报错。

fallback:函数充当了合约的默认处理函数,用于处理没有明确定义处理方式的消息。

fallback 函数会在三种情况下被调用

  1. 调用者尝试调用一个合约中不存在的函数时
  2. 用户给合约发 Ether 但是 receive 函数不存在
  3. 用户发 Ether,receive 存在,但是同时用户还发了别的数据( msg.data 不为空)
//这里我们定义了一个空的fallback函数。
fallback() external { }
fallback() external payable {...}

函数签名计算并赋值给选择器

bytes4 selector = bytes4(keccak256(transfer(address,uint256)));

Examples

本博客采用 知识共享署名-禁止演绎 4.0 国际许可协议 进行许可

本文标题:Solidity 学习笔记

本文地址:https://jizhong.plus/post/2025/11/solidity.html