第三讲: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
下一讲我们会深入函数修饰符、可见性、以及更复杂的数据类型。到时候你会看到,这些基础概念是怎么组合起来构建真实项目的。
记住:写合约就像搭积木,基础类型就是最小的那块。打好地基,后面才稳。