Solidity 学习历程


从计数器到投票站:我的Solidity智能合约入门三部曲

大家好,我是C。最近我一头扎进了Web3的世界,从最基础的Solidity智能合约开始学起。学习曲线或许陡峭,但每当一个合约在测试链上成功运行,那种成就感是无与伦比的。

今天,我想和大家分享我的入门学习路径------三个难度递增的经典合约:点击计数器 (ClickCounter)名字存储器 (SaveMyName)投票站 (PollStation)。这"三部曲"就像是为新手铺设的阶梯,每一步都建立在前一步的基础上,让你平滑地掌握核心概念。

希望这篇文章能像一本清晰的笔记,帮助正在入门的你,也欢迎各位大佬指点!


第一站:ClickCounter - 智能合约的 "Hello, World!"

万事开头难,但ClickCounter合约完美地解决了这个问题。它的功能极其简单:在区块链上记录一个数字,并且允许任何人调用一个函数来让这个数字加一。这不就是我们学习任何编程语言时都会写的计数器吗?但在区块链上,它有了全新的意义。

核心知识点:

  1. 状态变量(State Variables): 存储在区块链上的数据。
  2. 函数(Functions): 与合约交互的入口。
  3. public 可见性修饰符: 允许任何人(包括外部账户和其他合约)调用或读取。

合约代码解析:

下面是ClickCounter的完整代码,它非常简洁,是理解合约基本结构的绝佳范例。

solidity 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/**
 * @title ClickCounter
 * @dev 一个简单的计数器合约,每次调用increment函数,count就会加1。
 */
contract ClickCounter {
    // 定义一个名为 count 的公共无符号整型变量
    // `public` 关键字会自动为我们创建一个同名的 getter 函数 (count())
    uint public count;

    /**
     * @dev 构造函数,在合约部署时执行一次。
     *      这里我们将 count 初始化为 0。
     */
    constructor() {
        count = 0;
    }

    /**
     * @dev 将 count 的值加 1。
     *      这是一个会修改状态的函数,所以它是一笔"交易"(Transaction)。
     */
    function increment() public {
        count++;
    }
}

学习总结: 通过这个合约,我第一次理解了什么是"状态"。count变量的值被永久地记录在了区块链上,任何人都可以通过读取count()来查看它的当前值。而每次调用increment()函数,都是在发起一笔交易(Transaction),这笔交易会消耗Gas,因为它改变了链上的状态。

这个小小的计数器,让我直观地感受到了区块链的"全局共享状态机"这个概念。


第二站:SaveMyName - 开始与字符串和映射打交道

掌握了基础的数字操作后,我们进入第二站:SaveMyName。这个合约的功能是让每个用户(由他们的以太坊地址标识)都能在链上存储一个属于自己的名字(字符串)。

核心知识点:

  1. 字符串(string): Solidity中的一种数据类型,用于存储文本。
  2. 映射(Mapping): 强大的键值对存储结构,类似其他语言中的哈希表或字典。
  3. address: 以太坊账户地址的专属类型。
  4. msg.sender: 一个全局变量,代表当前调用函数的账户地址。

合约代码解析:

这个合约引入了mapping,这是Solidity中极其重要和常用的数据结构,完美地用于"一个地址对应一个数据"的场景。

solidity 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/**
 * @title SaveMyName
 * @dev 允许每个地址存储一个与之关联的名字。
 */
contract SaveMyName {
    // 定义一个映射,键是 address 类型,值是 string 类型
    // `names` 将存储从用户地址到其名字的映射关系
    mapping(address => string) public names;

    /**
     * @dev 存储调用者的名字。
     * @param _name 要存储的名字字符串。
     *      注意:函数参数需要指定内存位置,对于 string 类型通常是 `memory` 或 `calldata`。
     */
    function saveName(string memory _name) public {
        // msg.sender 是一个全局变量,代表调用此函数的账户地址
        // 这行代码将调用者的地址和他传入的名字关联起来
        names[msg.sender] = _name;
    }

    /**
     * @dev 读取指定地址的名字。
     * @param _userAddress 要查询的地址。
     * @return 返回该地址存储的名字。
     *      `view` 关键字表示这是一个只读函数,不修改状态,调用它不需要消耗Gas。
     */
    function getName(address _userAddress) public view returns (string memory) {
        return names[_userAddress];
    }
}

学习总结: SaveMyName合约让我领悟到了智能合约如何实现"身份"与"数据"的绑定。通过mapping(address => string),我们创造了一个去中心化的数据库。msg.sender的使用是关键,它确保了用户只能修改属于自己的数据,这是构建多用户DApp的基础。

同时,view函数的概念也让我明白了读(Read)操作和写(Write)操作的区别。读操作是免费的,而写操作需要支付Gas费,这是区块链经济模型的核心之一。


第三站:PollStation - 构建一个简单的去中心化应用

前两站是基础,第三站PollStation则是一个小型的"应用"了。它实现了一个简单的投票系统,具备以下功能:

  • 任何人都可以创建一个投票提案。
  • 每个地址只能对一个提案投一票。
  • 可以查询每个提案的票数。

核心知识点:

  1. 结构体(Struct): 用于定义复杂的数据类型,将多个变量组合在一起。
  2. 数组(Array): 用于存储一系列同类型的数据。
  3. require(): 用于检查条件,如果条件不满足,则撤销交易。这是保证合约逻辑正确性的重要工具。
  4. 事件(Events): 用于在链上留下"日志",方便前端应用监听合约状态的变化。

合约代码解析:

这个合约的复杂性明显提升,它组合运用了前面学到的所有知识,并引入了结构体和数组来管理更复杂的数据。

solidity 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/**
 * @title PollStation
 * @dev 一个简单的投票站合约。
 */
contract PollStation {
    // 定义一个结构体来表示一个投票提案
    struct Poll {
        string proposal; // 提案内容
        uint voteCount;  // 获得的票数
    }

    // 存储所有投票提案的动态数组
    Poll[] public polls;

    // 记录每个地址是否已经投过票
    // mapping(address => bool) private hasVoted; // 这个设计有缺陷,应针对每个提案
    // 更好的设计:记录每个用户对每个提案的投票状态
    mapping(uint => mapping(address => bool)) private hasVotedForPoll;

    // 定义一个事件,当有新投票时触发
    event Voted(uint indexed _pollId, address indexed _voter);

    /**
     * @dev 创建一个新的投票提案。
     * @param _proposal 提案的描述内容。
     */
    function createPoll(string memory _proposal) public {
        polls.push(Poll({
            proposal: _proposal,
            voteCount: 0
        }));
    }

    /**
     * @dev 为指定的提案投票。
     * @param _pollId 提案的ID (即它在数组中的索引)。
     */
    function vote(uint _pollId) public {
        // 检查提案ID是否存在
        require(_pollId < polls.length, "Poll does not exist.");
        // 检查该地址是否已经对这个提案投过票
        require(!hasVotedForPoll[_pollId][msg.sender], "You have already voted for this poll.");

        // 标记该地址已投票
        hasVotedForPoll[_pollId][msg.sender] = true;
        // 对应提案的票数加 1
        polls[_pollId].voteCount++;

        // 触发投票事件
        emit Voted(_pollId, msg.sender);
    }

    /**
     * @dev 获取提案总数。
     * @return 返回当前有多少个提案。
     */
    function getPollsCount() public view returns (uint) {
        return polls.length;
    }
}

学习总结: PollStation是一个里程碑。它让我不再是简单地存储数据,而是开始构建有实际业务逻辑的应用。

  • structarray的组合,让我学会了如何组织和管理复杂的数据集合。
  • require()的使用,让我深刻体会到智能合约编程的严谨性------"Code is Law",我们必须在代码层面处理掉所有无效的操作,以防资产损失或逻辑错误。
  • event的引入,为智能合约和前端应用的交互打开了一扇窗。前端可以通过监听这些事件,来实时更新UI,而无需不断地轮询查询合约状态。

反思与展望

ClickCounter的单个状态变量,到SaveMyName的键值对映射,再到PollStation中结构体、数组和事件的综合运用,这三个合约清晰地勾勒出了一条从入门到实践的学习路径。

这个过程让我明白,学习Solidity不仅仅是学习一门新的编程语言。更重要的是,它要求我们转变思维方式------从中心化服务器的思维,转变为在去中心化、透明、不可篡改的环境下构建应用的思维。你需要时刻考虑Gas消耗、交易的原子性以及安全性问题。

这"三部曲"仅仅是开始。接下来,还有ERC20代币标准、ERC721的NFT、DeFi协议等更广阔的世界等待我们去探索。

如果你也对Web3和智能合约开发感兴趣,不妨也从这三个小合约开始,动手敲一遍代码,部署到测试网亲自交互一下。相信我,你会收获良多!

感谢阅读,期待在Web3的世界里与你相遇!

相关推荐
许强0xq1 天前
Q19: fallback 和 receive 有什么区别?
面试·web3·区块链·solidity·以太坊·evm
weixin79893765432...1 天前
Web3 基于区块链的下一代互联网(科普)
web3·区块链·智能合约·solidity·钱包
天涯学馆6 天前
Solidity合约升级:让你的区块链代码永葆青春的硬核攻略
智能合约·solidity
许强0xq13 天前
Q10: 对于地址白名单,使用 mapping 还是 array 更好?为什么?
面试·职场和发展·web3·solidity·evm·gas
许强0xq18 天前
Q3: create 和 create2 有什么区别?
面试·web3·区块链·智能合约·solidity·dapp·evm
友莘居士18 天前
EVM(以太坊虚拟机)及其运行机制详解
区块链·智能合约·solidity·以太坊·evm·以太坊虚拟机
cainiaoeba21 天前
Solidity 第四周 (上) :构建Web3应用的基石——智能合约深度解析
solidity
cainiaoeba21 天前
Solidity 第四周 (下):解构DeFi的核心引擎——智能合约深度解析
solidity
天涯学馆21 天前
Solidity代理合约:解锁区块链代码的灵活升级大法
智能合约·solidity·以太坊