第三讲:Solidity基础(一)——语言简介、合约结构、状态变量与局部变量、整型与布尔类型、地址类型

好,我们正式开始写代码了。

这一讲,我会带你快速过一遍 Solidity 最核心的基础概念。别担心,我不会扔一堆枯燥的语法定义给你。咱们边聊边学,把每个知识点都跟实际开发场景挂上钩。

3.1 Solidity 语言简介——它到底是什么?

Solidity 是专门为以太坊虚拟机(EVM)设计的智能合约语言。说白了,它就是用来写「跑在区块链上的程序」的。

我个人习惯把它比作「区块链世界的 JavaScript」——语法像,但约束多得多。为什么?因为合约一旦部署,就不能随便改了。你想想看,这跟传统开发完全不是一个思路。

核心特点:

  • 静态类型:变量类型在编译时就得确定,不能像 JS 那样随便改
  • 图灵完备:理论上能算任何问题,但 Gas 限制让你不敢乱写循环
  • 面向合约:每个合约就是一个类,有状态、有方法

我记得刚接触 Solidity 时,最不习惯的就是「部署即不可变」。有一次我在测试网部署了一个合约,发现有个变量名拼错了……嗯,只能重新部署,然后通知所有人用新地址。从那以后,我写合约前都会先画个结构图。

3.2 合约结构——一个 Solidity 文件长什么样?

先看一个最简单的合约骨架:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MyFirstContract {
    // 状态变量
    uint256 public myNumber;

    // 构造函数
    constructor() {
        myNumber = 42;
    }

    // 函数
    function setNumber(uint256 _newNumber) public {
        myNumber = _newNumber;
    }
}

这个结构,你以后会写几百遍。我来拆解一下:

  • SPDX 许可证:开源协议声明,不写的话编译器会警告。我一般用 MIT
  • pragma 指令:指定 Solidity 编译器版本。^0.8.0 表示兼容 0.8.x 所有小版本
  • contract 关键字:定义一个合约,类似 class
  • 状态变量:永久存储在区块链上的数据
  • 构造函数:部署时执行一次,用来初始化
  • 函数:合约的行为逻辑

我的习惯:每个合约文件只放一个主要合约。辅助合约或库可以放一起,但主逻辑一定要独立。这样审计和升级都方便。

3.3 状态变量 vs 局部变量——一个存链上,一个用完就丢

这是新手最容易混淆的概念。我直接说结论:

  • 状态变量:定义在合约内部、函数外部。数据永久存储在区块链上,每次调用都会消耗 Gas
  • 局部变量:定义在函数内部。只在函数执行期间存在,不消耗存储 Gas

看个例子:

contract VariableDemo {
    // 状态变量 —— 存在链上
    uint256 public storedData;

    function doSomething(uint256 _input) public {
        // 局部变量 —— 只在函数内有效
        uint256 temp = _input * 2;
        
        // 把局部变量的值赋给状态变量
        storedData = temp;
    }
}

为什么会这样设计?因为区块链的存储空间非常昂贵。你想想看,每个状态变量都要被全网所有节点保存。所以我的原则是:能不存链上就别存

我曾经踩过的坑:在一个 NFT 项目里,我把每个用户的临时计算中间结果都存成了状态变量。结果部署后 Gas 费高得离谱。后来改成用局部变量计算完再存最终结果,Gas 直接降了 60%。

3.4 整型与布尔类型——最常用的两种基础类型

整型(int / uint)

Solidity 的整型分两种:

  • uint:无符号整数(只能是非负数),从 uint8 到 uint256
  • int:有符号整数(可正可负),从 int8 到 int256

默认的 uint 和 int 就是 uint256 和 int256。位数越大,能表示的范围越大,但 Gas 消耗也略高。

contract IntegerDemo {
    uint8 public smallNumber = 255;   // 最大 255
    uint256 public bigNumber = 1e30;  // 随便多大
    int public signedNumber = -42;    // 可以是负数
}

重要提醒:Solidity 0.8.0 之后,整数运算默认带溢出检查。比如 uint8 最大是 255,你加 1 就会 revert。以前版本会静默溢出,导致过很多安全漏洞。

布尔类型(bool)

布尔类型就两个值:true 和 false。常用于条件判断和状态标记。

contract BooleanDemo {
    bool public isActive = true;
    bool public isPaused = false;

    function toggle() public {
        isActive = !isActive;  // 取反
    }

    function check() public view returns (bool) {
        return isActive && !isPaused;  // 逻辑运算
    }
}

我个人习惯用布尔值做「开关」——比如合约是否暂停、某个功能是否启用。但注意,不要滥用,因为每次读取状态变量都要 Gas。

3.5 地址类型——区块链世界的身份证

地址类型是 Solidity 独有的,用来存储以太坊账户地址。它分两种:

  • address:普通地址,20 字节
  • address payable:可接收以太币的地址,多了 transfer 和 send 方法
contract AddressDemo {
    address public owner;
    address payable public treasury;

    constructor() {
        owner = msg.sender;  // 部署者地址
        treasury = payable(0x123...);  // 需要显式转换
    }

    function getBalance() public view returns (uint256) {
        return address(this).balance;  // 合约自身余额
    }

    function sendEther() public {
        // 只有 payable 地址才能转账
        treasury.transfer(1 ether);
    }
}

避坑指南:我曾经在写多签钱包时,忘记把地址转成 payable 就调用 transfer,结果编译报错。记住:只有 payable 地址才能接收 ETH。普通地址想转钱?先强制转换:payable(yourAddress)。

地址类型还有一些内置成员:

  • .balance:查询地址余额(单位 wei)
  • .transfer():发送 ETH(2300 Gas,够用但有限)
  • .send():类似 transfer,但返回 bool 而不是 revert
  • .call():最灵活的转账方式,可以指定 Gas 和 data

嗯,这里要注意:现在主流做法是用 call 代替 transfer 和 send,因为后者的 Gas 限制太死。这个我们后面讲转账时会详细说。

3.6 本章小结

这一讲我们覆盖了 Solidity 最基础的几个概念:

  • Solidity 是静态类型、图灵完备的智能合约语言
  • 合约结构包含许可证、pragma、合约体、状态变量和函数
  • 状态变量存链上,局部变量只在函数内有效
  • 整型分 uint 和 int,布尔就 true/false
  • 地址类型是区块链的核心,payable 才能收 ETH

下一讲我们会深入函数修饰符、可见性、以及更复杂的数据类型。到时候你会看到,这些基础概念是怎么组合起来构建真实项目的。

记住:写合约就像搭积木,基础类型就是最小的那块。打好地基,后面才稳。