Solidity 第一周(上):合约基础、数据存储与函数核心

33 阅读6分钟

Solidity 第一周(上):合约基础、数据存储与函数核心

大家好!如果你正准备踏入Web3和智能合约开发的世界,那么这篇文章就是为你量身打造的。最近我系统性地学习了一套由浅入深的Solidity教程,从一个简单的计数器开始,逐步构建出功能丰富的去中心化应用。

在第一周**《上篇》**中,我将带你回顾整个学习路径的基础部分,将ClickCounterSaveMyNamePollStation等合约中的核心知识点进行系统性地梳理和总结。我们的目标是:为你构建一个坚不可摧的Solidity知识地基。

准备好了吗?让我们开始吧!


一、 智能合约的“骨架”:许可证、版本与合约结构

在我们写下任何逻辑之前,一个Solidity文件需要一个标准的“开场白”。这不仅是好的编码习惯,更是合约安全与兼容性的保证。

1. 许可证标识 (SPDX License Identifier)

// SPDX-License-Identifier: MIT

几乎所有教程的第一个合约 ClickCounter 都以这行注释开始。它的作用是声明代码的开源许可证。由于智能合约一旦部署,源码通常是公开可验证的,明确的许可证(如MIT)能告诉社区成员他们可以如何使用、修改和分发你的代码。

2. 编译器版本指令 (Pragma Directive)

pragma solidity ^0.8.0;

这行代码告诉编译器:“请使用0.8.0版本或任何不包含重大更新的更高版本(如0.8.1, 0.8.2)来编译我,但不要使用0.9.0或更高版本。” Solidity语言在快速迭代,pragma指令确保了你的代码不会在未来因为编译器的破坏性更新而出错。

3. 合约的定义

contract ClickCounter {
    // 合约的所有代码都写在这里...
}

contract关键字是定义一个智能合约的入口。它就像是Java或C#中的class,将所有相关的状态变量和函数包裹在一个独立的单元里。


二、 区块链的“记忆”:状态变量与数据类型

智能合约的核心价值在于它能够在区块链上永久存储状态。这些状态由状态变量 (State Variables) 承载。

1. 基础数据类型:uintstring

ClickCounter 合约中,我们遇到了第一个状态变量:

// 存储一个256位的无符号整数
uint256 public counter;

uint256是Solidity中最常用的整数类型。关键字public非常神奇,它会自动为我们生成一个免费的、可供任何人调用的counter() getter函数,用于读取变量的值。

接着,在 SaveMyName 合约中,我们学会了存储文本:

// 存储字符串,默认可见性为 internal
string name;
string bio;

string类型用于处理文本数据。与uint不同,字符串在Solidity中是复杂类型,处理它们时需要特别注意存储位置(我们稍后会讲)。

2. 复杂数据结构:数组 Array 与映射 Mapping

当我们需要管理一组数据时,PollStation 合约向我们展示了两种强大的工具:

  • 数组 (Array): 用于存储一个有序的、同类型的元素列表。

    // 一个存储字符串的动态数组,用于存放候选人列表
    string[] public candidateNames;
    

    数组非常适合需要遍历或按顺序访问元素的场景。我们可以使用.push()方法向动态数组末尾添加新元素。

  • 映射 (Mapping): 用于存储键值对,提供极其高效的查找。

    // 一个映射,键是候选人名字(string),值是票数(uint256)
    mapping(string => uint256) public voteCount;
    

    映射就像其他语言中的哈希表或字典。它没有“长度”的概念,也不能被遍历,但可以通过键(Key)在O(1)时间内直接定位到值(Value)。这在记录用户余额、投票数等场景下非常高效。


三、 合约的“大脑”:函数、可见性与关键字

如果说状态变量是合约的“记忆”,那么函数就是合约的“大脑”,负责处理逻辑和与外部世界交互。

1. 函数的定义与可见性

一个典型的函数定义如下:

// 一个公共函数,任何人都可以调用
function click() public {
    counter++;
}

public是函数的可见性修饰符之一,代表任何人(包括外部用户和其他合约)都可以调用。其他常见的可见性还有:

  • private: 只能在当前合约内部调用。
  • internal: 只能在当前合约及继承它的子合约中调用。
  • external: 只能从合约外部调用(不能在合约内部直接调用)。

2. 理解 viewpure:免费的读取操作

SaveMyName 合约中,我们遇到了一个特殊的关键字 view

function retrieve() public view returns (string memory, string memory) {
    return (name, bio);
}
  • view: 表明该函数只读取合约的状态,但不修改它。
  • pure: 比view更严格,表明该函数既不读取也不修改合约状态(例如,只对输入参数进行计算)。

标记为viewpure的函数,如果从外部调用(例如通过Web3.js),不会产生交易,也不消耗Gas,因为它们不对区块链产生任何“写”操作。这是区块链经济模型的核心:写操作昂贵,读操作免费

3. 理解 memory vs storage:数据的临时与永恒

处理 stringarray 等复杂类型时,我们必须明确它们的存储位置。

function add(string memory _name, string memory _bio) public {
    name = _name; // 将 memory 中的临时数据写入 storage
    bio = _bio;
}
  • storage: 指向状态变量,数据被永久存储在区块链上。这是默认的存储位置,操作它会消耗大量Gas。
  • memory: 临时存储,数据只在函数执行期间存在。函数参数、返回值和函数内部声明的复杂类型变量通常使用memory,它比storage便宜得多。

简单记忆storage是硬盘,memory是内存。


四、 访问控制的“守卫”:msg.sendermodifier

Admin-Only 合约中,我们学习了如何限制某些函数只能由特定的人调用。

1. 全局变量 msg.sender

msg.sender是一个非常重要的全局变量,它代表当前函数调用者的地址

address public owner;

constructor() {
    // 在合约部署时,将部署者的地址设为 owner
    owner = msg.sender;
}

constructor是一个特殊的函数,只在合约部署时执行一次。通过在这里设置owner,我们就确立了合约的“所有者”。

2. 可复用的权限检查:modifier

如果我们有很多函数都只允许owner调用,每次都写require(msg.sender == owner)会很繁琐。modifier就是为此而生的:

// 定义一个名为 onlyOwner 的修饰符
modifier onlyOwner() {
    require(msg.sender == owner, "Access denied: Only the owner can perform this action");
    _; // 如果检查通过,执行函数体
}

// 在函数上使用修饰符
function addTreasure(uint256 amount) public onlyOwner {
    treasureAmount += amount;
}

modifier就像一个函数的前置守卫。_;是一个占位符,代表原始函数体的代码。如果require条件不满足,函数将在此处中断;如果满足,则继续执行。这让我们的访问控制代码变得极其干净和可复用。

到这里,我们已经系统性地梳理了Solidity最核心的基础知识。你现在应该理解:

  • 一个标准Solidity合约的完整结构。
  • 如何使用uint, string, array, mapping等数据类型来存储链上状态。
  • 函数是什么,以及public, view, memory等关键字如何影响它们的行为和成本。
  • 如何通过msg.sendermodifier实现简单而强大的访问控制。