第二课:Solidity语言入门(上)——合约结构、状态变量与局部变量、整型与布尔类型、地址类型与单位
各位同学,欢迎来到Solidity语言的第一课。说实话,每次带新人入门,我都会从这一章开始讲起。因为不管你以后写多复杂的合约,最终都离不开我们今天要聊的这些基础概念。
我个人习惯把Solidity看作是一套「区块链世界的规则说明书」。你写的每一行代码,最终都会变成链上不可篡改的规则。所以,从一开始就把基础打牢,比什么都重要。
一、合约的基本结构
先看一个最简单的合约骨架。我当年刚学的时候,就是靠这个模板反复练习,才慢慢找到感觉的。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MyFirstContract {
// 状态变量
uint256 public myNumber;
// 构造函数
constructor() {
myNumber = 100;
}
// 函数
function setNumber(uint256 _newNumber) public {
myNumber = _newNumber;
}
function getNumber() public view returns (uint256) {
return myNumber;
}
}
这个结构其实很清晰。从上到下依次是:
- SPDX许可证声明:这是开源社区的习惯,告诉别人你的代码可以怎么用。我建议你每次都写上MIT,省事。
- pragma指令:声明编译器版本。这里用的是^0.8.0,意思是兼容0.8.x的所有小版本。
- contract关键字:定义一个合约,类似于其他语言里的class。
- 状态变量:存储在链上的数据。
- 构造函数:部署时自动执行一次,用来初始化。
- 函数:合约的行为逻辑。
二、状态变量 vs 局部变量
这两个概念,说白了就是「永久存储」和「临时存储」的区别。
状态变量是写在合约最外层、函数之外的变量。它们会被永久保存在区块链上,每次调用合约都能读到。你想想看,这就像是一个公开的账本,谁都能查。
局部变量只在函数内部有效,函数执行完就消失了。它们不会消耗存储空间,只存在于内存中。
contract VariableDemo {
// 状态变量:永久存储在链上
uint256 public storedData;
function demo() public {
// 局部变量:临时存在,函数结束就没了
uint256 tempValue = 42;
storedData = tempValue;
}
}
这里有个坑,我曾经踩过。有一次我写了一个循环,在循环里频繁修改状态变量,结果gas费高得吓人。后来才意识到,应该先用局部变量计算好结果,最后再一次性赋值给状态变量。
三、整型与布尔类型
Solidity的整型分两种:有符号(int)和无符号(uint)。区别就在于能不能表示负数。
| 类型 | 范围 | 常见用途 |
|---|---|---|
| uint8 | 0 ~ 255 | 小数值、状态码 |
| uint256 | 0 ~ 2^256-1 | 余额、时间戳、ID |
| int256 | -2^255 ~ 2^255-1 | 需要负数的场景 |
| bool | true / false | 开关、权限判断 |
我个人习惯,只要不是特别需要节省空间,一律用uint256。为什么?因为EVM底层对256位的操作最友好,用更小的类型反而可能多花gas。
布尔类型就简单了,只有true和false。但要注意一点:Solidity里没有「假值」的概念。你不能像在JavaScript里那样写if (someNumber)来隐式转换,必须显式比较。
contract TypeDemo {
uint256 public balance = 1000;
bool public isActive = true;
function check() public view returns (bool) {
// 必须显式比较
if (balance > 0) {
return true;
}
return false;
}
}
四、地址类型与单位
地址类型是Solidity里最特殊的类型之一。它代表一个以太坊账户地址,长度固定为20字节(160位)。
地址类型有两种:
- address:普通地址,只能存,不能转账。
- address payable:可支付地址,可以用
.transfer()和.send()发送以太币。
contract AddressDemo {
address public owner;
address payable public beneficiary;
constructor() {
owner = msg.sender;
beneficiary = payable(0x123...); // 需要显式转换
}
function sendEther() public payable {
// 只有payable地址才能接收以太币
beneficiary.transfer(msg.value);
}
}
嗯,这里要注意。从Solidity 0.8.0开始,address不能直接转成address payable,必须用payable()函数显式转换。我刚开始升级项目时,就被这个改动坑了一把,编译一直报错。
再来说说单位。Solidity内置了以太币的单位换算:
contract UnitDemo {
uint256 public weiValue = 1 wei;
uint256 public gweiValue = 1 gwei;
uint256 public etherValue = 1 ether;
// 1 ether = 10^18 wei
// 1 gwei = 10^9 wei
}
这些单位本质上就是数字的语法糖。1 ether等价于10^18 wei,编译器会自动帮你换算。我个人建议,在合约里统一用wei做单位,避免精度丢失。前端展示时再换算成ether。
1 * 10**18来表示1个ether。结果因为运算符优先级问题,算出来的值完全不对。用内置单位1 ether就永远不会出错。
五、实战小练习
光看不练可不行。我建议你动手写一个简单的「存钱罐」合约:
contract PiggyBank {
address public owner;
uint256 public balance;
constructor() {
owner = msg.sender;
}
function deposit() public payable {
balance += msg.value;
}
function withdraw() public {
require(msg.sender == owner, "你不是主人");
payable(owner).transfer(address(this).balance);
balance = 0;
}
}
这个合约虽然简单,但包含了我们今天讲的所有知识点:状态变量、地址类型、单位换算、权限控制。你试着部署到测试网跑一跑,感受一下链上交互的流程。
好了,这一课的内容就到这里。下一章我们会继续深入,聊聊Solidity的引用类型、映射和数组。到时候你会发现,有了今天的基础,后面的内容学起来会轻松很多。