玩以太坊链上项目的必备技能(类型-引用类型-Solidity之旅三)

这篇具有很好参考价值的文章主要介绍了玩以太坊链上项目的必备技能(类型-引用类型-Solidity之旅三)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在前文我们讲述了值类型,也就说再修改值类型的时候,每次都有一个独立的副本,如:string 类型的状态变量,其值是无法修改,而是拷贝出一份该状态的变量,将新值存起来。对于处理稍微复杂地值类型时,拷贝将变得愈发大了,也正是介于此,才考虑到将数据存放在内存(memory)或是存放在存储(storage)

在 Solidity 中,数组(array)和 结构体(struct)属于引用类型

更改数据位置或类型转换将始终产生自动进行一份拷贝,而在同一数据位置内(对于 存储(storage) 来说)的复制仅在某些情况下进行拷贝。

数据位置和赋值行为

所有的引用类型,如数组(array0结构体(struct)类型,都有别同于其他类型,那便是引用类型有额外地属性——数据位置。

数据位置,顾名思义就是数据存放的位置在哪里?是在内存(memory)中还是在存储(storage)中。

当然咯,大多时候数据都有默认的存放位置。也可显式地修改其数据存放的位置,只需在类型后添加memorystorage

函数参数/形参(包括函数的返回参数)数据位置默认在memory局部变量数据位置则默认在storage,但状态变量数据位置被强制在storage

还有一个调用数据(calldata)存储方式,用于存放外部函数(external)的参数/形参,其效果跟memory差不离。

指定数据存放的位置是非常的重要,因为它们将会影响其赋值行为。

  • 在 存储storage 和 内存memory 之间两两赋值(或者从 调用数据calldata 赋值 ),都会创建一份独立的拷贝。
  • 从 内存memory 到 内存memory 的赋值只创建引用, 这意味着更改内存变量,其他引用相同数据的所有其他内存变量的值也会跟着改变。
  • 从 存储storage 到本地存储变量的赋值也只分配一个引用。
  • 其他的向 存储storage 的赋值,总是进行拷贝。 这种情况的示例如对状态变量或 存储storage 的结构体类型的局部变量成员的赋值,即使局部变量本身是一个引用,也会进行一份拷贝(译者注:查看下面 ArrayContract 合约 更容易理解)。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.4.0;

contract StorageExam {
    uint[] x; // x 的数据存储位置是 storage

    // memoryArray 的数据存储位置是 memory
    function f(uint[] memory memoryArray) public {
        x = memoryArray; // 将整个数组拷贝到 storage 中
        uint[] storage y = x;  // 分配一个指针(其中 y 的数据存储位置是 storage)
        y[7]; // 返回第 8 个元素
        y.pop(); // 通过 y 修改 x
        delete x; // 删除数组 x,同样也会删除数组 y

        // 下面的方法不起作用;它需要在存储中创建一个新的临时未命名数组
        // 未命名的数组,但存储是 "静态 "分配的。
        // y = memoryArray;
        // 同样,"删除y "也是无效的,因为对本地变量的赋值只能从现有的存储对象中进行。
        // 它将 "重置 "指针,但是没有任何合理的位置可以让它指向
        // delete y;

        g(x); // 调用 g 函数,同时移交对 x 的引用
        h(x); // 调用 h 函数,同时在 memory 中创建一个独立的临时副本
    }

    function g(uint[] storage ) internal pure {}
    function h(uint[] memory) public pure {}
}

归纳为:

强制指定的数据位置:

  • 外部函数的参数(不包括返回参数): calldata
  • 状态变量: storage

默认数据位置:

  • 函数参数(包括返回参数): memory
  • 所有其它局部变量: storage

数组(array)

数组是用来存放一组数据(整数、字符串、地址等),它是一种常见的数据类型,而在 Solidity 中,数组可分为编译时的固定大小,动态大小的两种数组。

固定大小数组声明格式:

T[k] // T 为元素类型  k则是数组的大小
uint[7] arr;
address[50] address1;

动态大小数组声明格式:

T[] //T为元素类型  由于是动态分配的 所以只需[]
unit[] arr2;
bytes1[] arr3;
address[] arr4;
bytes arr5;  //注意 bytes本身就是数组

一个长度为 5,元素类型为 uint 的动态数组的数组(二维数组),应声明为 uint[][5] (注意这里跟其它语言比,数组长度的声明位置是反的)。作为对比,如在Java中,声明一个包含5个元素、每个元素都是数组的方式为 int[5][]

在Solidity中, X[3] 总是一个包含三个 X 类型元素的数组,即使 X 本身就是一个数组,这和其他语言也有所不同,比如 C 语言。

数组下标是从 0 开始的,且访问数组时的下标顺序与声明时相反。

如果有一个变量为 uint[][5] memory x, 要访问第三个动态数组的第7个元素,使用 x[2][6],要访问第三个动态数组使用 x[2]。 同样,如果有一个 T 类型的数组 T[5] a , T 也可以是一个数组,那么 a[2] 总会是 T 类型。

数组元素可以是任何类型,包括映射或结构体。对类型的限制是映射只能存储在 存储storage 中,并且公开访问函数的参数需要是 ABI 类型。

状态变量标记 public 的数组,Solidity创建一个 getter函数 。 小标数字索引就是 getter函数 的参数。

访问超出数组长度的元素会导致异常(assert 类型异常 )。 可以使用 .push() 方法在末尾追加一个新元素,其中 .push() 追加一个零初始化的元素并返回对它的引用。

bytesstring 类型的变量是特殊的数组。 bytes 类似于 bytes1[],但它在 调用数据calldata 和 内存memory 中会被“紧打包”(译者注:将元素连续地存在一起,不会按每 32 字节一单元的方式来存放)。 stringbytes 相同,但不允许用长度或索引来访问。

Solidity没有字符串操作函数,但是可以使用第三方字符串库,我们可以比较两个字符串通过计算他们的 keccak256-hash ,可使用 keccak256(abi.encodePacked(s1)) == keccak256(abi.encodePacked(s2)) 和使用 string.concat(s1, s2) 来拼接字符串。

我们更多时候应该使用 bytes 而不是 bytes1[] ,因为Gas 费用更低, 在 内存memory 中使用 bytes1[] 时,会在元素之间添加31个填充字节。 而在 存储storage 中,由于紧密包装,这没有填充字节。 作为一个基本规则,对任意长度的原始字节数据使用 bytes,对任意长度字符串(UTF-8)数据使用 string

可以将数组标识为 public,从而让 Solidity 创建一个 getter。 之后必须使用数字下标作为参数来访问 getter。

创建数组规则
  • 使用new在内存(memory)中创建动态数组,需声明长度,且在声明后不能修改数组的大小
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.4.16;

contract Arr {
    function f(uint len) public pure {
        uint[] memory a = new uint[](7);
        bytes memory b = new bytes(len);
        // 这里我们有 a.length == 7 以及 b.length == len
        a[6] = 8;
    }
}
  • 数组字面常数是一种定长的 内存(memory) 数组类型,它的基础类型是由其中元素的类型决定。 例如,[1, 2, 3] 的类型是 uint8[3] memory,因为其中的每个字面常数的类型都是 uint8。 正因为如此,有必要将上面这个例子中的第一个元素转换成 uint 类型。 目前需要注意的是,定长的 内存memory 数组并不能赋值给变长的 内存memory 数组
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.4.16;

contract C {
    function f() public pure {
        g([uint(1), 2, 3]);
    }
    function g(uint[3] _data) public pure {
        // ...
    }
}
// SPDX-License-Identifier: GPL-3.0
// 这段代码并不能编译。
pragma solidity ^0.4.0;

contract C {
    function f() public {
        // 这一行引发了一个类型错误,因为 unint[3] memory
        // 不能转换成 uint[] memory。
        uint[] x = [uint(1), 3, 4];
    }
}
数组成员
  • length: 数组有一个包含元素数量的length成员,memory数组的长度在创建后是固定的。
  • push(): 动态数组bytes拥有push()成员,可以在数组最后添加一个0元素。
  • push(x): 动态数组bytes拥有push(x)成员,可以在数组最后添加一个x元素。
  • pop(): 动态数组bytes拥有pop()成员,可以移除数组最后一个元素。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.4.16;

contract ArrayContract {
    uint[2**20] m_aLotOfIntegers;
    // 注意下面的代码并不是一对动态数组,
    // 而是一个数组元素为一对变量的动态数组(也就是数组元素为长度为 2 的定长数组的动态数组)。
    bool[2][] m_pairsOfFlags;
    // newPairs 存储在 memory 中 —— 函数参数默认的存储位置

    function setAllFlagPairs(bool[2][] newPairs) public {
        // 向一个 storage 的数组赋值会替代整个数组
        m_pairsOfFlags = newPairs;
    }

    function setFlagPair(uint index, bool flagA, bool flagB) public {
        // 访问一个不存在的数组下标会引发一个异常
        m_pairsOfFlags[index][0] = flagA;
        m_pairsOfFlags[index][1] = flagB;
    }

    function changeFlagArraySize(uint newSize) public {
        // 如果 newSize 更小,那么超出的元素会被清除
        m_pairsOfFlags.length = newSize;
    }

    function clear() public {
        // 这些代码会将数组全部清空
        delete m_pairsOfFlags;
        delete m_aLotOfIntegers;
        // 这里也是实现同样的功能
        m_pairsOfFlags.length = 0;
    }

    bytes m_byteData;

    function byteArrays(bytes data) public {
        // 字节的数组(语言意义中的 byte 的复数 ``bytes``)不一样,因为它们不是填充式存储的,
        // 但可以当作和 "uint8[]" 一样对待
        m_byteData = data;
        m_byteData.length += 7;
        m_byteData[3] = byte(8);
        delete m_byteData[2];
    }

    function addFlag(bool[2] flag) public returns (uint) {
        return m_pairsOfFlags.push(flag);
    }

    function createMemoryArray(uint size) public pure returns (bytes) {
        // 使用 `new` 创建动态 memory 数组:
        uint[2][] memory arrayOfPairs = new uint[2][](size);
        // 创建一个动态字节数组:
        bytes memory b = new bytes(200);
        for (uint i = 0; i < b.length; i++)
            b[i] = byte(i);
        return b;
    }
}

结构体(struct)

Solidity 中的结构体与 c 语言、golang 很相似,通过构造结构体来定义一种新类型。

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.4.11;

contract CrowdFunding {
    // 定义的新类型包含两个属性。
    struct Funder {
        address addr;
        uint amount;
    }

    struct Campaign {
        address beneficiary;
        uint fundingGoal;
        uint numFunders;
        uint amount;
        mapping (uint => Funder) funders; //这是 映射  后续会讲到
    }

    uint numCampaigns;
    mapping (uint => Campaign) campaigns; //这是 映射  后续会讲到

    function newCampaign(address beneficiary, uint goal) public returns (uint campaignID) {
        campaignID = numCampaigns++; // campaignID 作为一个变量返回
        // 创建新的结构体示例,存储在 storage 中。我们先不关注映射类型。
        campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
    }

    function contribute(uint campaignID) public payable {
        Campaign storage c = campaigns[campaignID];
        // 以给定的值初始化,创建一个新的临时 memory 结构体,
        // 并将其拷贝到 storage 中。
        // 注意你也可以使用 Funder(msg.sender, msg.value) 来初始化。
        c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value});
        c.amount += msg.value;
    }

    function checkGoalReached(uint campaignID) public returns (bool reached) {
        Campaign storage c = campaigns[campaignID];
        if (c.amount < c.fundingGoal)
            return false;
        uint amount = c.amount;
        c.amount = 0;
        c.beneficiary.transfer(amount);
        return true;
    }
}

上面的合约只是一个简化版的众筹合约,但它已经足以让我们理解结构体的基础概念。 结构体类型可以作为元素用在映射和数组中,其自身也可以包含映射和数组作为成员变量。

尽管结构体本身可以作为映射的值类型成员,但它并不能包含自身。 这个限制是有必要的,因为结构体的大小必须是有限的。

注意在函数中使用结构体时,一个结构体是如何赋值给一个局部变量(默认存储位置是 存储(storage) )的。 在这个过程中并没有拷贝这个结构体,而是保存一个引用,所以对局部变量成员的赋值实际上会被写入状态。

当然,你也可以直接访问结构体的成员而不用将其赋值给一个局部变量,就像这样, campaigns[campaignID].amount = 0

// spdx-license-identifier: gpl-3.0 pragma solidity 0.4.24; contract lenet5,区块链,区块链,solidity,结构体,引用类型,值拷贝文章来源地址https://www.toymoban.com/news/detail-821589.html

到了这里,关于玩以太坊链上项目的必备技能(类型-引用类型-Solidity之旅三)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 以太坊智能合约开发:Solidity 语言中的数据类型

    本文我们介绍Solidity语言的数据类型,重点是值类型,包括布尔类型、整型、地址类型、字节类型、字符串类型和枚举类型。并且通过两个智能合约例子,用于演示这些数据类型的声明与使用方法。 访问 Github 仓库 获取更多资料。 Solidity中关于数据类型的定义如下: Solidity是

    2024年02月02日
    浏览(69)
  • 【项目管理】AI时代项目经理必备技能

    👉博__主👈:米码收割机 👉技__能👈:C++/Python语言 👉公众号👈:测试开发自动化【获取源码+商业合作】 👉荣__誉👈:阿里云博客专家博主、51CTO技术博主 👉专__注👈:专注主流机器人、人工智能等相关领域的开发、测试技术。 项目经理在AI时代仍然是非常关键的角色

    2024年02月08日
    浏览(37)
  • 在本地以太坊私链上,使用go调用智能合约,获取事件日志

    完整go项目文件目录      

    2024年02月11日
    浏览(58)
  • 谁在以太坊区块链上循环交易?TuGraph+Kafka的0元流图解决方案

    都在说数据已经成为新时代的生产资料。 但随着大数据和人工智能等技术的发展,即便人们都知道数据的价值日益凸显,却无法凭借一己之力获取和分析如此大规模的数据。 要想富,先修路。要想利用新时代的数据致富,也必须要有趁手的工具。只有合适的工具才能完成大

    2024年02月11日
    浏览(40)
  • 以太坊智能合约语言Solidity - 3 数组

    1字节(Byte) = 8位 (bit), bytes32 = 256位,bytes1 实质上就等于 int8 固定长度的数组一旦被定义就无法再更改,并且长度在一开始就会被显式定义 我们再来创建一个新的文件用来编写代码 字节数组无法进行基本运算,但是可以比较 字节数组还支持其他一些逻辑运算,具体计算结果

    2023年04月08日
    浏览(62)
  • 以太坊智能合约开发:Solidity 语言快速入门

    在本文中,我们从一个简单的智能合约样例出发,通过对智能合约源文件结构的剖析与介绍,使大家对Solidity语言有一个初步的认识。最后,我们将该智能合约样例在 Remix 合约编译器中编译、部署,观察其执行结果。 在开始之前,我们先对Solidity有个初步的了解,即Solidity是

    2023年04月09日
    浏览(51)
  • 自定义的搭建solidity开发环境(以太坊)

    环境地址    github: GitHub - yinzhiqing/templete-sol: solidity platform(hardhat)    gitlab: zqy / templete-sol · GitLab 本项目利用openzapplin solc web3js hardhat nodejs 在ubuntu下搭建solidity合约开发环境.大多数功能实现了自动化(脚本)执行. 特点: 1.开发环境可充分使用 2.合约可升级 3.记

    2024年02月01日
    浏览(38)
  • 第四章 以太坊智能合约solidity介绍

    Solidity 是一门面向合约的、为实现智能合约而创建的高级编程语言,设计的目的是能在以太坊虚拟机上运行。 本章大概介绍合约的基本信息,合约的组成,语法方面不做过多的介绍,个人建议多阅读官方文档效果更佳,后续的章节会开发ERC20代币合约案例以便于更好的学习智

    2024年02月06日
    浏览(52)
  • 以太坊智能合约开发:Solidity语言中的映射

    本文我们介绍Solidity语言中的映射,包括映射的基本定义、语法、映射的变量声明和基本读写操作。并且通过两个智能合约例子演示了映射的定义与基本操作。 Solidity中关于映射的一些定义: 映射以键-值对(key = value)的形式存储数据; 键可以是任何内置数据类型,包括字节

    2024年02月05日
    浏览(56)
  • 基于以太坊的智能合约开发Solidity(基础篇)

    参考教程:基于以太坊的智能合约开发教程【Solidity】_哔哩哔哩_bilibili (1)程序编译完成后,需要在虚拟机上运行,将合约部署好后便可执行刚刚编写的函数。(注意, 合约一旦部署,就会永久存在于区块链上,且不可篡改 ,不过可以销毁) (2)执行完成后,可以得到以

    2024年02月04日
    浏览(58)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包