本文系统讲解Solidity核心概念:
OK,书接上回,欢迎来到第三章,我们继续盘 Solidity ,今天我们讲点啥呢?看完你就知道了😎。这里是 红烧6 的 web3 频道,无论你是初学者还是有一定编程经验的开发者,这篇文章将为你提供清晰、实用的指导。
Solidity 是一种静态类型语言,变量在声明时必须明确指定类型,类型不匹配将阻止代码编译。这种严格的类型系统确保了以太坊智能合约的安全性和可预测性。
Solidity 的数据类型分为值类型(Value Types)和引用类型(Reference Types),决定了数据是按值传递(复制数据)还是按引用传递(传递内存地址)。Solidity 没有 undefined
或 null
,未初始化的变量会分配默认值,具体值取决于数据类型。
值类型在传递时复制数据,主要包括:
整数(Integers):
int8
(8 位)范围为 -128
到 +127
;int256
范围为 -(2^255)
到 +(2^255) -1
uint8
范围为 0
到 255
;uint256
范围为 0
到 2^256 - 1
int8
、int16
、int256
)uint256 a = 100;
若尝试赋值为字符串,编译器报错地址(Address):
0x0000000000000000000000000000000000000000
(address(0)
)address owner = msg.sender;
布尔值(Boolean):
true
或 false
,默认值为 false
bool isActive = true;
固定大小字节数组(Fixed-Size Byte Arrays):
bytes1
、bytes2
、bytes32
,存储固定长度的字节序列bytes32 data = 0x1234567890abcdef;
pragma solidity ^0.8.0;
contract ValueTypeExample {
// 整数类型
// 有符号整数 int 支持正负数
int8 public smallSignedInt = -127; // 8位,范围 -128 到 +127
int256 public largeSignedInt = -123456789; // 256 位,范围 -(2^255) 到 +(2^255) -1
// 无符号整数 uint 仅支持正数
uint8 public smallUnsignedInt = 255; // 8 位 范围 0 到 255
uint256 public largeUnsignedInt = 123456789; // 256 位 范围 0 到 2^256 - 1
// 地址类型
// 表示以太坊账户地址 ,默认值为 0x0
address public owner = msg.sender; // 当前调用者的地址
address public zeroAddress = address(0); // 默认地址:0x000...000
// 布尔类型
// 表示 true 或者 false 默认值为 false
bool public isActive = true; // 布尔值,初始化为 true
bool public isInitialized; // 未初始化,默认为 false
// 固定大小字节数组
// 存储固定长度的字节序列
// bytes1 合法输入方式
bytes1 public singleByte = 0xFF; // 1字节,最大值 0xFF
bytes1 public byte1Hex = 0x0A; // 十六进制,十进制 10
bytes1 public byte1Binary = bytes1(uint8(10)); // 模拟二进制,十进制 10
bytes1 public byte1Char = "A"; // ASCII 字符,字节值 0x41
// bytes32 合法输入方式
bytes32 public byte32String = "Solidity"; // 字符串转为字节
bytes32 public longBytes =
0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef;
// 错误示例:直接用十进制整数
// bytes1 public byte1Invalid = 12345; // 编译错误:Type uint256 is not implicitly convertible to bytes1
// 示例函数:操作整数
function updateIntegers(int8 newSmallInt, uint256 newLargeInt) public {
smallSignedInt = newSmallInt; // 更新 int8
largeUnsignedInt = newLargeInt; // 更新 uint256
}
// 示例函数:检查地址是否有效
function isValidAddress(address addr) public pure returns (bool) {
return addr != address(0); // 验证非零地址
}
// 示例函数:切换布尔值
function toggleActive() public {
isActive = !isActive; // 取反布尔值
}
// 示例函数:设置固定大小字节数组
function setBytes32(bytes32 newBytes) public {
longBytes = newBytes; // 更新 bytes32
}
// 遍历 bytes32,返回每个字节
function traverseBytes32() public view returns (bytes1[] memory) {
bytes1[] memory result = new bytes1[](32); // bytes32 固定 32 字节
for (uint i = 0; i < 32; i++) {
result[i] = longBytes[i]; // 访问第 i 个字节
}
return result;
}
// 获取 bytes1 的值(仅 1 字节,无需遍历)
function getByte1() public view returns (bytes1) {
return singleByte; // 直接返回
}
}
引用类型传递内存地址,主要包括:
数组(Arrays):
string[6] fixedArray;
fixedArray[0] = "a"; // 有效
fixedArray[6] = "g"; // 错误:超出范围
push()
和 pop()
,按引用传递
uint[] dynamicArray;
dynamicArray.push(1); // [1]
dynamicArray.pop(); // []
字节(Bytes):
byte[]
),按引用传递,适合高效存储function stringIntoBytes(string memory input) public pure returns (bytes memory) {
return bytes(input); // "Zubin" -> 0x5a7562696e
}
字符串(Strings):
string name = "Solidity";
结构体(Structs):
struct Person {
string name;
uint age;
bool isSolidityDev;
Job job;
}
struct Job {
string employer;
string department;
bool isRemote;
}
Person p = Person("Zubin", 41, true, Job("Chainlink Labs", "DevRel", true));
映射(Mappings):
mapping(address => uint256) balances;
默认值为 0
mapping(address => uint256) public balances;
function balanceOf(address account) public view returns (uint256) {
return balances[account]; // 不存在返回 0
}
internal
)storage
或 memory
),例如:
function inMemArray(string memory firstName) public pure returns (string[] memory) {
string[] memory arr = new string[](2);
arr[0] = firstName;
return arr;
}
Solidity 的类型系统避免了 JavaScript 动态类型(如 1 + "2" = "12"
)的错误,提高了代码可靠性。
pragma solidity ^0.8.0;
contract ReferenceTypesExample {
// 动态大小数组:存储 uint 类型,按引用传递
uint[] public dynamicArray;
// 字符串:动态大小字节数组,存储 UTF-8 编码,按引用传递
string public name = "Solidity";
// 字节:动态大小字节数组,按引用传递
bytes public data = hex"123456";
// 结构体:自定义复合类型
struct Person {
string username;
uint age;
address wallet;
}
// 映射:键值对,address 映射到 Person 结构体
mapping(address => Person) public users;
// 初始化动态数组
function initArray(uint value) public {
dynamicArray.push(value); // 添加元素
if (dynamicArray.length > 1) {
dynamicArray[0] = value; // 修改第一个元素
}
}
// 更新字符串
function setName(string memory newName) public {
name = newName; // 更新字符串
}
// 字节与字符串互转
function stringToBytes(
string memory input
) public pure returns (bytes memory) {
return bytes(input); // 转换为字节
}
// 添加用户到映射
function addUser(string memory username, uint age) public {
users[msg.sender] = Person(username, age, msg.sender);
}
// 获取用户信息
function getUser(
address userAddress
) public view returns (string memory, uint, address) {
Person memory user = users[userAddress];
return (user.username, user.age, user.wallet);
}
// 在内存中创建固定大小数组
function createMemoryArray(
string memory input
) public pure returns (string[] memory) {
string[] memory tempArray = new string[](2); // 固定大小
tempArray[0] = input;
tempArray[1] = "default";
return tempArray;
}
}
函数修饰符是 Solidity 中封装可重用逻辑的代码块,通常在函数执行前后运行验证或检查逻辑。修饰符类似函数,使用 _
(下划线)指定主函数执行位置。
优点:
示例:确保只有合约所有者调用函数:
modifier onlyOwner() {
require(msg.sender == owner, "Not the owner");
_; // 主函数代码在此执行
}
function changeOwner(address newOwner) public onlyOwner {
owner = newOwner;
}
require
在 _
前运行,确保调用者是 owner
_
表示主函数(changeOwner
)的插入点require
在 _
后,主函数先执行,可能导致逻辑错误修饰符可接受参数:
modifier validAddress(address addr) {
require(addr != address(0), "Address invalid");
_;
}
function transferTokenTo(address someAddress) public validAddress(someAddress) {
// 转账逻辑
}
修饰符封装通用验证逻辑(如权限、地址有效性),便于在多个函数中重用。
pragma solidity ^0.8.0;
contract FunctionModifierExample {
address public owner; // 合约所有者
uint public value; // 存储数值
bool public paused; // 合约暂停状态
// 构造函数:设置部署者为所有者
constructor() {
owner = msg.sender;
}
// 修饰符:仅允许所有者调用
modifier onlyOwner() {
require(msg.sender == owner, "Not the owner");
_; // 主函数插入点
}
// 修饰符:检查合约未暂停
modifier whenNotPaused() {
require(!paused, "Contract is paused");
_;
}
// 修饰符:验证地址非零
modifier validAddress(address addr) {
require(addr != address(0), "Invalid address");
_;
}
// 使用 onlyOwner 修饰符更新数值
function setValue(uint newValue) public onlyOwner whenNotPaused {
value = newValue;
}
// 使用 onlyOwner 切换暂停状态
function togglePause() public onlyOwner {
paused = !paused;
}
// 使用 validAddress 修饰符转移所有权
function transferOwnership(
address newOwner
) public onlyOwner validAddress(newOwner) {
owner = newOwner;
}
// 获取当前状态
function getStatus() public view returns (address, uint, bool) {
return (owner, value, paused);
}
}
Solidity 使用 require
、assert
和 revert
处理错误。错误发生时,EVM 回滚状态更改,返回未使用 gas(delegatecall
、call
等除外)。
require
验证条件,若为假,抛出错误并回滚交易,用于输入验证、返回值检查等。
function requireExample() public payable {
require(msg.value >= 1 ether, "You must pay at least 1 ether!");
}
msg.value < 1 ether
,抛出错误并回滚。assert
类似 require
,抛出 Panic(uint256)
错误,用于检查不变量或内部状态。
contract ThrowMe {
function assertExample() public pure {
assert(address(this).balance == 0); // 确保余额为 0
}
}
require
类似。revert
用于复杂条件,支持自定义错误,gas 消耗低且信息丰富。
contract ThrowMe {
error ThrowMe_BadInput(string errorMsg, uint inputNum);
function revertExample(uint input) public pure {
if (input < 1000) {
revert ThrowMe_BadInput("Number must be > 999", input);
}
if (input < 0) {
revert("Negative numbers not allowed");
}
}
}
require
放在函数开头,减少 gas 浪费。revert
抛出自定义错误,节省 gas。call
、send
返回 false
,需手动检查。pragma solidity ^0.8.0;
contract ErrorHandlingExample {
uint public balance;
address public owner;
// 自定义错误:提高 gas 效率和可读性
error InsufficientBalance(uint requested, uint available);
error InvalidInput(string message, uint value);
constructor() {
owner = msg.sender;
}
// 使用 require 验证输入
function deposit(uint amount) public {
require(amount > 0, "Amount must be greater than zero");
balance += amount;
}
// 使用 assert 检查不变量
function checkInvariant() public view {
assert(balance >= 0); // 余额永远不应为负
}
// 使用 revert 和自定义错误处理复杂逻辑
function withdraw(uint amount) public {
if (amount > balance) {
revert InsufficientBalance(amount, balance);
}
if (amount == 0) {
revert InvalidInput("Withdrawal amount cannot be zero", amount);
}
balance -= amount;
}
// 使用 require 限制仅所有者操作
function resetBalance() public {
require(msg.sender == owner, "Only owner can reset balance");
balance = 0;
}
// 获取当前余额
function getBalance() public view returns (uint) {
return balance;
}
}
类型转换(Type Casting)是将一种数据类型显式转换为另一种类型。Solidity 是静态类型语言,类型转换需谨慎,以避免数据丢失或意外结果。
Solidity 允许某些类型之间的显式转换。例如,将 uint256
转换为 bytes32
:
contract Conversions {
function uintToBytes() public pure returns (bytes32) {
uint256 a = 2022;
bytes32 b = bytes32(a);
return b; // 0x00000000000000000000000000000000000000000000000000000000000007e6
}
}
uint256
(256 位,32 字节)转换为 bytes32
(32 字节),无数据丢失b
的值是 2022 的 256 位二进制表示,填充前导零将大位数类型转换为小位数类型可能导致数据丢失。例如:
contract Conversions {
function explicit256To8() public pure returns (uint8) {
uint256 a = 2022; // 二进制:11111100110 (11 位)
uint8 b = uint8(a); // 仅取最后 8 位:11100110
return b; // 230
}
}
11111100110
(11 位),但 uint8
仅保留最后 8 位(11100110
),十进制为 230从有符号整数(int
)到无符号整数(uint
)转换可能导致意外结果:
contract Conversions {
function unsignedToSigned() public pure returns (int16, uint16) {
int16 a = -2022;
uint16 b = uint16(a); // -2022 转换为 63514
return (a, b);
}
}
int16
的 -2022
转换为 uint16
时,负号信息丢失,二进制被解释为正数 63514。int16
到 uint256
)会因负号不兼容而编译失败。uint8(a)
)。pragma solidity ^0.8.0;
contract TypeCastingExample {
// 演示不同类型转换
bytes1 public byte1Hex = 0x0A; // 十六进制,十进制 10
uint256 public largeNumber = 2022;
int16 public signedNumber = -2022;
// 将 uint 转换为 bytes1(需显式转换)
function setByte1FromUint(uint8 value) public {
require(value <= 255, "Value exceeds bytes1 range");
byte1Hex = bytes1(value); // 显式转换为 1 字节
}
// 将 uint 转换为 bytes2(适合更大值如 12345)
function uintToBytes2(uint16 value) public pure returns (bytes2) {
return bytes2(value); // 转换为 2 字节
}
// 示例:将 12345 转换为 bytes2
function getBytes12345() public pure returns (bytes2) {
return bytes2(uint16(12345)); // 12345 的字节表示,0x3039
}
// uint256 转换为 bytes32
function uintToBytes() public view returns (bytes32) {
bytes32 result = bytes32(largeNumber);
return result; // 0x000...07e6 (2022 的 32 字节表示)
}
// uint256 转换为 uint8,可能丢失数据
function uint256ToUint8() public view returns (uint8) {
uint8 result = uint8(largeNumber); // 2022 -> 230 (仅保留最后 8 位)
return result;
}
// int16 转换为 uint16,可能导致意外结果
function signedToUnsigned() public view returns (uint16) {
uint16 result = uint16(signedNumber); // -2022 -> 63514 (负号丢失)
return result;
}
// 地址转换为 uint256
function addressToUint(address addr) public pure returns (uint256) {
return uint256(uint160(addr)); // 地址 (160 位) 转为 uint256
}
// 更新 largeNumber 以测试转换
function setLargeNumber(uint256 newValue) public {
largeNumber = newValue;
}
// 更新 signedNumber 以测试转换
function setSignedNumber(int16 newValue) public {
signedNumber = newValue;
}
}
Solidity 当前不支持浮点数(如 93.6),尝试声明会导致编译错误:
int256 floating = 93.6; // 错误:Type rational_const 468 / 5 is not implicitly convertible to int256
为处理浮点数,Solidity 开发者通过将小数转换为整数(乘以 10 的指数)来模拟:
转换步骤:
以太坊中的应用:
function divideBy1e18() public pure returns (int) {
return 1500000000000000000 / 1e18; // 错误:无法处理 1.5
}
pragma solidity ^0.8.0;
contract FloatingPointExample {
// 定义缩放因子,通常为 10^18(类似以太坊的 wei)
uint256 public constant SCALING_FACTOR = 1e18; // 10^18,18 位小数精度
// 存储余额(以最小单位 wei 表示,模拟浮点数)
mapping(address => uint256) public balances;
// 错误:用于验证输入
error InvalidAmount(string message, uint256 value);
// 存款:输入 Ether 单位(浮点数),转换为 wei
function deposit(uint256 etherAmount) public {
if (etherAmount == 0) {
revert InvalidAmount(
"Deposit amount must be greater than zero",
etherAmount
);
}
// 模拟浮点数:将 Ether 单位乘以 10^18 转为 wei
uint256 weiAmount = etherAmount * SCALING_FACTOR;
balances[msg.sender] += weiAmount;
}
// 提取:输入 Ether 单位,转换为 wei 进行操作
function withdraw(uint256 etherAmount) public {
if (etherAmount == 0) {
revert InvalidAmount(
"Withdrawal amount must be greater than zero",
etherAmount
);
}
uint256 weiAmount = etherAmount * SCALING_FACTOR;
require(balances[msg.sender] >= weiAmount, "Insufficient balance");
balances[msg.sender] -= weiAmount;
}
// 获取余额:以 Ether 单位返回(模拟浮点数)
function getBalanceInEther(address user) public view returns (uint256) {
return balances[user] / SCALING_FACTOR; // 转换为 Ether 单位
}
// 示例:处理更精确的小数(例如 93.2355)
function addPreciseAmount(uint256 amount, uint8 decimals) public {
// amount 是整数部分,decimals 是小数位数
// 例如:93.2355 -> amount = 932355, decimals = 4
require(decimals <= 18, "Decimals exceed maximum precision");
uint256 scalingFactor = 10 ** decimals;
uint256 scaledAmount = (amount * SCALING_FACTOR) / scalingFactor;
balances[msg.sender] += scaledAmount;
}
// 示例:计算利息(模拟浮点数运算)
function calculateInterest(
address user,
uint256 rate
) public view returns (uint256) {
// rate 是百分比的整数表示,例如 5% = 500(5.00%)
// 转换为浮点数:rate / 10000
uint256 interest = (balances[user] * rate) / 10000 / SCALING_FACTOR;
return interest; // 返回 Ether 单位的利息
}
// 示例:尝试直接使用浮点数(将导致编译错误)
// function invalidFloat() public pure returns (int256) {
// int256 floating = 93.6; // 错误:Type rational_const 468 / 5 is not implicitly convertible to int256
// return floating;
// }
}
Solidity 返回大整数(如 10^18 级别的 wei),需在前端使用库(如 ethers.js)处理:
ethers.formatEther(1500000000000000000)
返回 "1.5" (v6 版本)uint256
存储大值,避免溢出fixed
/ufixed
),但目前未完全实现代码仓库:https://github.com/BraisedSix/Solidity-Learn
Solidity 的静态类型系统、函数修饰符、异常处理、类型转换和浮点数处理方法共同构成了开发可靠智能合约的基础。严格的类型系统避免了动态类型语言的错误;修饰符提高了代码复用性;require
、assert
和 revert
提供了灵活的错误处理;类型转换需注意数据丢失和符号问题;浮点数通过整数模拟实现。掌握这些概念,开发者可以编写更安全、高效的以太坊智能合约。我是红烧6,关注我,实现你的大师梦,我们下期见!
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!