Solidity语言 - 结构体

这篇文章详细介绍了Solidity中的结构体(struct)及其用法,包括如何声明、实例化和作为函数参数传递结构体。通过示例代码,作者展示了如何利用结构体提高代码的可读性和效率,并提供了一个现实生活中的用例,如购票系统,帮助开发者理解结构体的实际应用。

在 Solidity 中,结构体的行为类似于 C。它们将不同的变量组合成一个单一的复合数据类型,这在组织数据和创建更复杂的数据结构中非常有用。

以下是在 Solidity 中声明一个结构体的方法。


contract StructsExample {

    struct Foo {
        uint256 a;
        uint256 b;
    }

    Foo public myFoo;
}

myFoo 是一个结构体 Foo 的公共变量,它同时存储 uint256 auint256 b。如你所见,如果我们在 remix 中部署它,myFoo 返回:

https://static.wixstatic.com/media/c0c19a_1bbba8b3b1784a26a19574db16a82851~mv2.png/v1/fill/w_560,h_542,al_c,q_95,enc_auto/myfoo.png

要在 Remix 中将结构体传递给一个接受结构体作为参数的函数(稍后我们会详细讨论),请按如下方式编码:

https://static.wixstatic.com/media/c0c19a_72d068d1006e4b898e7dc38ebd704984~mv2.png/v1/fill/w_554,h_412,al_c,q_95,enc_auto/Screenshot_2023-05-15_at_3_46_21_PM.png

所涉及的函数采用上面示例中的 Foo,它包含两个 uint256 变量。将其格式化为数组可能会有点困惑,但这就是它的工作方式。

要在 Solidity 中创建 Foo 的新实例,只需将值包装在结构体 Foo 中。

  • Foo( a , b )

要访问或分配结构体 myFoo 中的每个单独变量,请使用点表示法。

  • myFoo.a
  • myFoo.b

我们为什么使用结构体?假设我们有一个存款合约,它跟踪存款人的 namebalance


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 作为参数或返回值传递,必须遵循以下规则:

  • 作为参数传递的结构体必须声明为 memory
  • 作为返回类型的结构体也必须声明为 memory。具体形式如下。

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 作为票,但如果用户不会相互转移票,那就太过复杂了。

练习

StudentDB

了解更多与 RareSkills

查看我们的 Solidity bootcamp 以了解更多关于智能合约开发和代币标准的信息。

  • 原文链接: rareskills.io/learn-soli...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
RareSkills
RareSkills
https://www.rareskills.io/