这篇文章详细介绍了Solidity中的结构体(struct)及其用法,包括如何声明、实例化和作为函数参数传递结构体。通过示例代码,作者展示了如何利用结构体提高代码的可读性和效率,并提供了一个现实生活中的用例,如购票系统,帮助开发者理解结构体的实际应用。
在 Solidity 中,结构体的行为类似于 C。它们将不同的变量组合成一个单一的复合数据类型,这在组织数据和创建更复杂的数据结构中非常有用。
以下是在 Solidity 中声明一个结构体的方法。
contract StructsExample {
struct Foo {
uint256 a;
uint256 b;
}
Foo public myFoo;
}
myFoo 是一个结构体 Foo 的公共变量,它同时存储 uint256 a 和 uint256 b。如你所见,如果我们在 remix 中部署它,myFoo 返回:
要在 Remix 中将结构体传递给一个接受结构体作为参数的函数(稍后我们会详细讨论),请按如下方式编码:
所涉及的函数采用上面示例中的 Foo,它包含两个 uint256 变量。将其格式化为数组可能会有点困惑,但这就是它的工作方式。
要在 Solidity 中创建 Foo 的新实例,只需将值包装在结构体 Foo 中。
要访问或分配结构体 myFoo 中的每个单独变量,请使用点表示法。
我们为什么使用结构体?假设我们有一个存款合约,它跟踪存款人的 name 和 balance。
contract DepositOnly {
mapping(address => string) public name;
mapping(address => uint256) public balance;
function deposit(
string memory _name
)
external
payable {
balance[msg.sender] += msg.value;
name[msg.sender] = _name;
}
}
在上述合约中,存款人的名字和余额存储在两个单独的 mapping 数据结构中。
在映射中的地址变量在名字和同一个 msg.sender 的余额中重复了两次,因此效率不高。
因此,结构体在这里显得非常方便,我们可以在一个结构体变量下注册名字和余额,并像这样将该变量存储在一个键值对映射中。
contract DepositOnly {
struct Person {
string name;
uint256 balance;
}
mapping(address => Person) public depositor;
function deposit(
string memory _name
)
external
payable {
depositor[msg.sender] = Person(_name, msg.value);
}
}
你看它有多方便吗?它使你的代码更简洁、更高效。
如何使用结构体
很简单,对吧?这是演示。
contract StructsExample {
struct Foo {
uint256 a;
uint256 b;
}
Foo public myFoo;
function assignMyFoo(
uint256 _a,
uint256 _b
)
public {
myFoo = Foo(_a, _b);
}
function assignA(
uint256 _a
)
public {
myFoo.a = _a;
}
function accessA()
public
view
returns(uint256) {
return myFoo.a;
}
}
如果你想将结构体 Foo 作为参数或返回值传递,必须遵循以下规则:
contract StructsExample {
struct Foo {
uint256 a;
uint256 b;
}
Foo myFoo;
function passStructAsArgument(
Foo memory foo
)
public {
myFoo = foo;
}
function returnAStruct()
public
view
returns (Foo memory) {
return myFoo;
}
}
需要注意的是,Solidity 中的结构体 不能 包含自身类型的成员。例如,这是不允许的:
struct Foo {
Foo innerFoo; // 不允许
}
数组和映射
结构体可以用作数组和映射中的值类型。例如,你可以像这样创建一个动态数组 Foo 实例:
contract StructsExample {
struct Foo {
uint256 a;
uint256 b;
}
Foo[] public arrayFoo;
}
arrayFoo 是一个由 Foo 实例组成的数组。
演示:
contract StructsExample {
struct Foo {
uint256 a;
uint256 b;
}
Foo[] public arrayFoo;
function addFooToArray(
uint256 _a,
uint256 _b
)
public {
arrayFoo.push(Foo(_a, _b));
}
function readFooFromArray(
uint256 _index
)
public
view
returns(Foo memory){
return arrayFoo[_index];
}
function readFooA(
uint256 _index
)
public
view
returns(uint256){
return arrayFoo[_index].a;
}
function modifyFooA(
uint256 _index,
uint256 _a
)
public {
arrayFoo[_index].a = _a;
}
function setFooAtIndex(
uint256 _index,
uint256 _a,
uint256 _b
)
public {
arrayFoo[_index] = Foo(_a, _b);
}
}
你还可以创建一个映射,其中键是地址而值是 Foo 实例:
contract StructsExample {
struct Foo {
uint256 a;
uint256 b;
}
mapping(address => Foo) public mappingFoo;
function insertFoo(
uint256 _a,
uint256 _b
)
public {
mappingFoo[msg.sender] = Foo(_a, _b);
}
}
到此为止,显然这里发生了什么。我们有一个地址到结构体 Foo 的映射;mappingFoo。
要将一个 Foo 实例分配给地址映射,方法如下:
function insertFoo(
uint256 _a,
uint256 _b
)
public {
mappingFoo[msg.sender] = Foo(_a, _b);
}
以及修改它的方法:
function modifyFoo(uint256 _a) public {
mappingFoo[msg.sender].a = _a;
}
实际生活示例
一个更实际的用例是在票务系统中。我们有一个 BuyTickets 合约,以 0.01 以太的价格出售一张票。一个地址不能购买超过 10 张票,还有一个函数显示某个地址的票务信息。
contract BuyTickets {
uint256 public constant TICKET_PRICE = 0.01 ether;
struct Ticket {
string name;
uint256 numberOfTickets;
}
mapping(address => Ticket) public tickets;
function buyTicket(
string memory _name,
uint256 _numberOfTickets
)
external
payable {
require(msg.value == _numberOfTickets * TICKET_PRICE, "Wrong Value");
require(_numberOfTickets <= 10, "Maximum Limit Exceeded");
require(tickets[msg.sender].numberOfTickets + _numberOfTickets <= 10, "Maximum Limit Reached");
tickets[msg.sender].name = _name;
tickets[msg.sender].numberOfTickets += _numberOfTickets;
}
function displayTicket(
address _ticketHolder
)
external
view
returns (Ticket memory) {
return(tickets[_ticketHolder]);
}
}
我们可以使用 NFTs 作为票,但如果用户不会相互转移票,那就太过复杂了。
练习
查看我们的 Solidity bootcamp 以了解更多关于智能合约开发和代币标准的信息。
- 原文链接: rareskills.io/learn-soli...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!