第三课:Solidity语言入门——从零开始写你的第一行智能合约
好,我们正式开始写代码了。
前面两章我们搭好了环境,也部署了第一个Hello World。但说实话,那个合约太简单了,连个变量都没有。这一章,我们来真正理解Solidity这门语言的核心概念。
我个人觉得,学Solidity就像学开车——你不需要先背完整本说明书,但得知道油门、刹车、方向盘是干嘛的。这一章就是带你认识这些「基本控件」。
3.1 Solidity概述:它到底是什么?
Solidity是一门面向合约的高级语言,运行在以太坊虚拟机(EVM)上。说白了,它就是用来写智能合约的。
你可能听说过JavaScript。Solidity的语法跟它有点像,但本质完全不同。JS跑在浏览器里,Solidity跑在区块链上。一旦部署,代码就不能改了——嗯,这一点我吃过亏,后面会讲。
核心特点:
- 静态类型:每个变量必须声明类型,编译时检查
- 图灵完备:理论上能算任何问题,但受Gas限制
- 面向合约:合约就像Java里的类,有状态、有方法
- 继承支持:可以复用已有合约的代码
我记得刚学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;
}
function getNumber() public view returns (uint256) {
return myNumber;
}
}
我们来拆解一下:
- 第一行:许可证声明。MIT表示开源,你也可以用其他许可证。不加的话编译会警告,但能跑。
- 第二行:版本声明。^0.8.0表示兼容0.8.x所有小版本。我建议用^0.8.0以上,因为0.8版本修复了很多安全问题。
- contract关键字:定义合约,后面跟合约名。首字母大写是惯例。
- 花括号{}里:就是合约的「身体」了。
我的习惯:每个合约文件只放一个合约。虽然Solidity允许一个文件写多个合约,但分开管理更清晰。尤其是项目大了以后,你不想在几千行代码里找一个合约吧?
3.3 状态变量 vs 局部变量:一个天上,一个地下
这是新手最容易混淆的概念。我刚开始也搞混过,直到有一次在项目中写了个死循环,把状态变量当局部变量用,Gas烧得飞快……
状态变量:永久存储在区块链上。定义在合约内部、函数外部。每次读写都要花Gas。
局部变量:临时存储在内存中。定义在函数内部。函数执行完就消失,不花存储Gas。
看个对比:
contract VariableDemo {
// 状态变量 - 存在链上
uint256 public storedData;
function doSomething(uint256 input) public {
// 局部变量 - 存在内存
uint256 temp = input * 2;
// 把局部变量的值赋给状态变量
storedData = temp;
}
}
这里有个关键点:状态变量的读写成本远高于局部变量。写一次状态变量大约消耗20000 Gas,而局部变量几乎免费。所以,能不用状态变量就别用。
我曾经踩过的坑:在一个循环里反复修改状态变量。比如:
// ❌ 错误做法
for(uint i = 0; i < 100; i++) {
total += i; // total是状态变量,每次循环都写链上
}
// ✅ 正确做法
uint256 temp = 0;
for(uint i = 0; i < 100; i++) {
temp += i; // 局部变量,免费
}
total = temp; // 只写一次链上
这个优化能让Gas消耗降低99%。真的,不夸张。
3.4 整型与布尔类型:最基础的数据类型
Solidity里的数据类型,咱们先从最常用的两个说起。
整型(Integer)
Solidity支持有符号和无符号整型:
- uint:无符号整数(0到正无穷)。默认是uint256,即256位。
- int:有符号整数(负无穷到正无穷)。默认是int256。
- 你也可以指定位数:uint8, uint16, ..., uint256。位数越小,存储越省Gas。
举个例子:
contract IntegerDemo {
uint8 public smallNumber = 255; // 最大255
uint256 public bigNumber = 1000; // 随便多大
int public negative = -42; // 可以负数
function add(uint256 a, uint256 b) public pure returns (uint256) {
return a + b;
}
}
这里要注意:Solidity 0.8.0之后默认做了溢出检查。如果你给uint8赋值为256,编译会报错。以前版本不会报错,而是直接归零——这导致过很多安全漏洞。
避坑指南:
- 能用uint8就别用uint256,省Gas。但别为了省Gas把位数设得太小,导致溢出。
- 做加减乘除时,记得考虑边界情况。比如两个uint8相加可能超过255。
- 除法要小心:Solidity里整数除法会截断。7/2 = 3,不是3.5。
布尔类型(Boolean)
布尔类型只有两个值:true 和 false。常用于条件判断:
contract BooleanDemo {
bool public isActive = true;
function toggle() public {
isActive = !isActive; // 取反
}
function check() public view returns (string memory) {
if (isActive) {
return "Active";
} else {
return "Inactive";
}
}
}
布尔类型在Solidity里有个小坑:它不能直接用于算术运算。你不能写 true + 1,编译会报错。但在某些语言里(比如JavaScript),这是允许的。嗯,这里要注意区分。
另外,布尔变量的存储成本跟uint8一样,都是1字节。但如果你把多个布尔变量打包在一起,可以进一步省Gas。这个我们后面讲结构体时会提到。
3.5 实战小练习:写一个计数器合约
理论讲完了,咱们动手写个东西。一个简单的计数器合约,把今天学的都用上:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Counter {
// 状态变量:当前计数
uint256 public count;
// 构造函数:初始化计数为0
constructor() {
count = 0;
}
// 增加计数
function increment() public {
count += 1;
}
// 减少计数(但不能小于0)
function decrement() public {
// 先检查,再操作
require(count > 0, "Count cannot be negative");
count -= 1;
}
// 重置计数
function reset() public {
count = 0;
}
// 获取当前计数(view表示不修改状态)
function getCount() public view returns (uint256) {
return count;
}
}
这个合约里:
count是状态变量,存在链上increment、decrement、reset修改了状态变量getCount只是读取,不花存储Gasrequire是安全检查,防止计数变成负数
我的建议:把这个合约部署到测试网上试试。调用几次increment,看看count怎么变。然后打开区块链浏览器,看看交易记录——你会发现每次调用都产生了一笔交易,这就是「写链上」的代价。
3.6 本章小结
今天我们学了:
- Solidity是写智能合约的语言,部署后不可修改
- 合约由状态变量和函数组成,结构清晰
- 状态变量存链上,贵;局部变量存内存,便宜
- 整型分uint和int,注意位数和溢出
- 布尔类型用于条件判断,不能做算术
下一章我们会深入函数和修饰符,到时候你会看到更多有意思的东西。比如怎么限制只有合约拥有者才能调用某个函数——嗯,这个在真实项目中太常用了。
记住:写合约就像盖房子,地基要稳。今天学的这些基础概念,就是你智能合约生涯的地基。别急着写复杂逻辑,先把这些基础玩熟。