玩以太坊链上项目的必备技能(函数及其可见性和状态可变性-Solidity之旅十三)

news2025/1/12 8:54:29

状态变量可见性

在这之前的文章里,给出的例子中,声明的状态变量都修饰为public,因为我们将状态变量声明为public后,Solidity 编译器自动会为我们生成一个与状态变量同名的、且函数可见性public的函数!

在 Solidity 中,除了可以将状态变量修饰为public,还可以修饰为另外两种:internalprivate

  • public

    对于 public 状态变量会自动生成一个,与状态变量同名的 public修饰的函数。 以便其他的合约读取他们的值。 当在用一个合约里使用是,外部方式访问 (如: this.x) 会调用该自动生成的同名函数,而内部方式访问 (如: x) 会直接从存储中获取值。 Setter函数则不会被生成,所以其他合约不能直接修改其值。

  • internal

    内部可见性状态变量只能在它们所定义的合约和派生合同中访问, 它们不能被外部访问。 这是状态变量的默认可见性。

  • private

    私有状态变量就像内部变量一样,但它们在派生合约中是不可见的。

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

contract stateVarsVisible {
   uint public num;
   function showNum() public returns(uint){
      num += 1;
      return num;
   }
}

contract outsideCall {
   function myCall() public returns(uint){
      //实例化合约
      stateVarsVisible sv = new stateVarsVisible();
      //调用 getter 函数
      return sv.num();
   }
}

在这里插入图片描述

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

contract stateVarsVisible {
   uint internal num;
   function showNum() public returns(uint){
      num += 1;
      return num;
   }
   function fn() external returns(uint){
      return num;
   }
}
contract sub is stateVarsVisible {
   function myNum() public returns(uint){
      return stateVarsVisible.num;
   }
}
contract outsideCall {
   function myCall() public returns(uint){
      //实例化合约
      stateVarsVisible sv = new stateVarsVisible();
      //外部合约 不能访问
      //return sv.num();
   }
}

在这里插入图片描述

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

contract stateVarsVisible {
   uint private num;
   function showNum() public returns(uint){
      num += 1;
      return num;
   }
   function fn() external returns(uint){
      return num;
   }
}
contract sub is stateVarsVisible {
   function myNum() public returns(uint){
   //派生合约 无法访问 基合约 的状态变量
      return stateVarsVisible.num;
   }
}

在这里插入图片描述

函数可见性

前面的文章,我们多多少少有见到在函数参数列表后的一些关键字,那便是函数可见性修饰符。对于函数可见性这一概念,有过现代编程语言的经历大都知晓,诸如,public(公开的)private(私有的)protected(受保护的)用来修饰函数的可见性,Java、PHP`等便是使用这些关键字来修饰函数的可见性。

当然咯,Solidity 函数对外可访问也做了修饰,分为以下 4 种可见性:

  • external

外部可见性函数作为合约接口的一部分,意味着我们可以从其他合约和交易中调用。 一个外部函数 f 不能从内部调用(即 f 不起作用,但 this.f() 可以)。

  • public

public 函数是合约接口的一部分,可以在内部或通过消息调用。

  • internal

内部可见性函数访问可以在当前合约或派生的合约访问,外部不可以访问。 由于它们没有通过合约的ABI向外部公开,它们可以接受内部可见性类型的参数:比如映射或存储引用。

  • private

private 函数和状态变量仅在当前定义它们的合约中使用,并且不能被派生合约使用。

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

contract FunctionVisible {
   uint private num;
   function privateFn(uint x) private returns(uint y){ y = x + 5; }
   function setNum(uint x) public { num = x;}
   function getNum() public returns(uint){ return num; }
   function sum(uint x,uint y) internal returns(uint) { return x + y; }
   function showPri(uint x) external returns(uint){ x += num; return privateFn(x); }

}
contract Outside {
   function myCall() public {
      FunctionVisible fv = new FunctionVisible();
      uint res = fv.privateFn(7); // 错误:privateFn 函数是私有的
      fv.setNum(4);
      res = fv.getNum();
      res = fv.sum(3,4); // 错误:sum 函数是 内部的
    
   }
}

在这里插入图片描述

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

contract FunctionVisible {
   uint private num;
   function privateFn(uint x) private view returns(uint y){ y = x + num; }
   function setNum(uint x) public { num = x;}
   function getNum() public  view returns(uint){ return num; }
   function sum(uint x,uint y) internal pure returns(uint) { return x + y; }
   function showPri(uint x) external view returns(uint){ x += num; return privateFn(x); }

}
contract Sub is FunctionVisible {
   function myTest() public pure returns(uint) {
        uint val = sum(3, 5); // 访问内部成员(从继承合约访问父合约成员)
        val = privateFn(6);  //privateFn函数是私有的,即便是派生合约也不能访问
        return val;
    }
}

在这里插入图片描述

getter 函数具有外部(external)可见性。如果在内部访问 getter(即没有 this. ),它被认为一个状态变量。 如果使用外部访问(即用 this. ),它被认作为一个函数。

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

contract C {
    uint public data;
    function x() public returns(uint) {
        data = 3; // 内部访问
        uint val = this.data(); // 外部访问
        return val;
    }
}

在这里插入图片描述

如果你有一个数组类型的 public 状态变量,那么你只能通过生成的 getter 函数访问数组的单个元素。 这个机制以避免返回整个数组时的高成本gas。 可以使用如 myArray(0) 用于指定参数要返回的单个元素。 如果要在一次调用中返回整个数组,则需要写一个函数,例如:

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

contract C {
 
  uint[] public myArray;

  // 自动生成的Getter 函数
  /*
  function myArray(uint i) public view returns (uint) {
      return myArray[i];
  }
  */

  // 返回整个数组
  function getArray() public view returns (uint[] memory) {
      return myArray;
  }
}

在这里插入图片描述

合约之外的函数

在 Solidity 0.7.0 版本之后,便可以将函数定义在合约之外,我们称这种函数为"自由函数",其函数可见性始终隐式地为internal,它们的代码包含在所有调用它们的合约中,类似于后续会讲到的函数。

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

function sum(uint[] memory arr) pure returns (uint s) {
    for (uint i = 0; i < arr.length; i++)
        s += arr[i];
}

contract ArrayExample {
    bool found;
    function f(uint[] memory arr) public {
        //编译器会将 合约外函数的代码添加到这里
        uint s = sum(arr);
        require(s >= 10); //后续会讲到
        found = true;
    }
}

在这里插入图片描述

在合约之外定义的函数仍然在合约的上下文内执行。 它们仍然可以调用其他合约,将其发送以太币或销毁调用它们的合约等其他事情。 与在合约中定义的函数的主要区别为:自由函数不能直接访问存储变量 this 、存储和不在他们的作用域范围内函数。

函数参数与返回值

与其它编程语言一样,函数可能接受参数作为输入。但 Solidity 和 golang 一样,函数可以返回任意数量的值作为输出。

1、函数入参

函数的参数变量这一点倒是与声明变量是一样的,如果未能使用到的参数可以省略参数名。

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

contract Simple {
    uint sum;
    function taker(uint a, uint b) public {
        sum = a + b;
    }
}

在这里插入图片描述

2、返回值

Solidity 函数返回值与 golang 函数返回很类似,只不过,Solidity 使用returns关键字将返回参数声明在小括号内。

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

contract Simple {
    function arithmetic(uint a, uint b) public pure returns (uint sum, uint product)
    {
        sum = a + b;
        product = a * b;
    }
}

返回变量名可以被省略。 返回变量可以当作为函数中的局部变量,没有显式设置的话,会使用 :ref: 默认值 <default-value> 返回变量可以显式给它附一个值(像上面),也可以使用 return 语句指定,使用 return 语句可以一个或多个值。

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

contract Simple {
    function arithmetic(uint a, uint b)
        public
        pure
        returns (uint , uint )
    {
        return (a + b, a * b);
    }
}

状态可变性

view

我们在先前的文章会看到,有些函数在修饰为public后,有多了view修饰的。而函数使用了view修饰,说明这个函数不能修改状态变量(State Variable),只能获取状态变量的值,由于view修饰的函数不能修改存储在区块链上的状态变量,这种函数的gas fee不会很高,毕竟调用函数也会消耗gas fee

而以下情况被认为是修改状态的:

  1. 修改状态变量。
  2. 产生事件。
  3. 创建其它合约。
  4. 使用 selfdestruct
  5. 通过调用发送以太币。
  6. 调用任何没有标记为 view 或者 pure 的函数。
  7. 使用低级调用。
  8. 使用包含特定操作码的内联汇编。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

contract Simple {
    function f(uint a, uint b) public view returns (uint) {
        return a * (b + 42) + block.timestamp;
    }
}

在这里插入图片描述

Solidity 0.5.0 移除了 view的别名constant

Getter 方法自动被标记为 view

pure

若将函数声明为pure的话,那么该函数是既不能读取也不能修改状态变量(State Variable)

除了上面解释的状态修改语句列表之外,以下被认为是读取状态:

  1. 读取状态变量。
  2. 访问 address(this).balance 或者 <address>.balance
  3. 访问 blocktxmsg 中任意成员 (除 msg.sigmsg.data 之外)。
  4. 调用任何未标记为 pure 的函数。
  5. 使用包含某些操作码的内联汇编。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

contract Simple {
    function f(uint a, uint b) public pure returns (uint) {
        return a * (b + 42);
    }
}

在这里插入图片描述

特别的函数

receive 接收以太函数

一个合约最多有一个 receive 函数, 声明函数为: receive() external payable { ... }

不需要 function 关键字,也没有参数和返回值并且必须是 external 可见性和 payable 修饰. 它可以是 virtual 的,可以被重载也可以有 后续会讲到的 修改器modifier 。

在对合约没有任何附加数据调用(通常是对合约转账)是会执行 receive 函数. 例如 通过 .send().transfer(), 如果 receive 函数不存在, 但是有 payable 的 接下来会讲到的 fallback 回退函数 那么在进行纯以太转账时,fallback 函数会调用.

如果两个函数都没有,这个合约就没法通过常规的转账交易接收以太(会抛出异常).

更糟的是,receive 函数可能只有 2300 gas 可以使用(如,当使用 sendtransfer 时), 除了基础的日志输出之外,进行其他操作的余地很小。下面的操作消耗会操作 2300 gas :

  • 写入存储
  • 创建合约
  • 调用消耗大量 gas 的外部函数
  • 发送以太币
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

contract Simple {
    event Received(address, uint);
    receive() external payable {
        emit Received(msg.sender, msg.value);
    }
}

在这里插入图片描述

Fallback 回退函数

合约可以最多有一个回退函数。函数声明为: fallback () external [payable]fallback (bytes calldata input) external [payable] returns (bytes memory output)

没有 function 关键字。 必须是 external 可见性,它可以是 virtual 的,可以被重载也可以有 后续会讲到的 修改器modifier

如果在一个对合约调用中,没有其他函数与给定的函数标识符匹配 ,fallback会被调用。 或者在没有 receive 函数 时,而没有提供附加数据对合约调用,那么fallback 函数会被执行。

fallback 函数始终会接收数据,但为了同时接收以太时,必须标记为 payable

如果使用了带参数的版本, input 将包含发送到合约的完整数据(等于 msg.data ),并且通过 output 返回数据。 返回数据不是 ABI 编码过的数据,相反,它返回不经过修改的数据。

更糟的是,如果回退函数在接收以太时调用,可能只有 2300 gas 可以使用。

与任何其他函数一样,只要有足够的 gas 传递给它,回退函数就可以执行复杂的操作。

payablefallback函数也可以在pure·以太转账的时候执行, 如果没有 receive 以太函数 推荐总是定义一个receive函数,而不是定义一个payable 的fallback函数,

如果想要解码输入数据,那么前四个字节用作函数选择器,然后用 abi.decode 与数组切片语法一起使用来解码ABI编码的数据:

(c, d) = abi.decode(_input[4:], (uint256, uint256));

请注意,这仅应作为最后的手段,而应使用对应的函数。

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

contract Test {
    // 发送到这个合约的所有消息都会调用此函数(因为该合约没有其它函数)。
    // 向这个合约发送以太币会导致异常,因为 fallback 函数没有 `payable` 修饰符
    fallback() external { x = 1; }
    uint x;
}


// 这个合约会保留所有发送给它的以太币,没有办法返还。
contract TestPayable {
    uint x;
    uint y;

    // 除了纯转账外,所有的调用都会调用这个函数.
    // (因为除了 receive 函数外,没有其他的函数).
    // 任何对合约非空calldata 调用会执行回退函数(即使是调用函数附加以太).
    fallback() external payable { x = 1; y = msg.value; }

    // 纯转账调用这个函数,例如对每个空empty calldata的调用
    receive() external payable { x = 2; y = msg.value; }
}

contract Caller {
    function callTest(Test test) public returns (bool) {
        (bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));
        require(success);
        //  test.x 结果变成 == 1。

        // address(test) 不允许直接调用 send ,  因为 test 没有 payable 回退函数
        //  转化为 address payable 类型 , 然后才可以调用 send
        address payable testPayable = payable(address(test));

        testPayable.transfer(2 ether);
        // 以下将不会编译,但如果有人向该合约发送以太币,交易将失败并拒绝以太币。
        // test.send(2 ether);
        return true;
    }

    function callTestPayable(TestPayable test) public returns (bool) {
        (bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));
        require(success);
        // 结果 test.x 为 1  test.y 为 0.
        (success,) = address(test).call{value: 1}(abi.encodeWithSignature("nonExistingFunction()"));
        require(success);
        // 结果test.x 为1 而 test.y 为 1.

        // 发送以太币, TestPayable 的 receive 函数被调用.

        // 因为函数有存储写入, 会比简单的使用 send 或 transfer 消耗更多的 gas。
        // 因此使用底层的call调用
        (success,) = address(test).call{value: 2 ether}("");
        require(success);

        // 结果 test.x 为 2 而 test.y 为 2 ether.

        return true;
    }

}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/101626.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ASP.NET Core 3.1系列(19)——EFCore中的添加实体操作

1、前言 前面介绍了EFCore中关于查询和执行原生SQL的操作&#xff0c;这篇博客就来介绍一下EFCore中添加实体的相关操作。关于添加实体&#xff0c;EFCore提供了多种方法供开发者使用。但EFCore中针对实体的一系列操作最终都会被转换成SQL&#xff0c;因此这些方法之间也存在着…

设计模式之模版方法模式

Template method design pattern 模版方法模式的概念、模版方法模式的结构、模版方法模式的优缺点、模版方法模式的使用场景、模版方法模式的实现示例、模版方法模式的源码分析 1、模版方法模式的概念 模版方法模式&#xff0c;即定义一个算法骨架&#xff0c;而将一些步骤延迟…

ARM——指令集仿真环境搭建

目录 一、ARM指令集仿真环境搭建 1.1指令和指令集 指令 指令集 1.2汇编的本质 汇编 C语言 1.3为什么学习汇编 1.4仿真 硬件仿真 软件仿真 1.5Keil 1.6环境搭建 1.7汇编工程创建 二、汇编 2.1汇编中的符号 2.2ARM指令集 2.3简单的ARM程序 一、ARM指令集仿真环境搭…

Cyanine5 NHS ester |分子量:616.19|分子式:C36H42ClN3O4

Cyanine5 NHS ester |分子量&#xff1a;616.19|分子式&#xff1a;C36H42ClN3O4 外观&#xff1a;暗蓝色粉末 分子量&#xff1a;616.19 分子式&#xff1a;C36H42ClN3O4 溶解性&#xff1a;易溶于有机溶剂&#xff08;DMF,DMSO和氯化物&#xff09;&#xff0c;难溶于水 …

Mob社会化分享集成ShareSDK

如何在项目已经集成 SMSSDK 的情况下集成 ShareSDk 到项目中&#xff0c;需要使用创建 module 的方式引入 ShareSDk&#xff0c;主要内容如下&#xff1a; 1.下载ShareSDK 2.引入 ShareSDK 3.创建 MainLibs Module 4.创建 OneKeyShare Module 5.在项目中引入 Module 6.配…

Unity Addressables资源管理 主设置面板

Addressables资源管理总目录 0.主设置菜单位置 位置1 位置2 1.Profiles 路径配置选项 这个是全局路径配置的选择 可以点击 Manager Profiles 打开路径配置面板 打包路径设置 2.Diagnostics 诊断设置 Send Profiler Events 打开这个选项&#xff0c;才能在Event Viewer窗口…

记录java枚举类在数据库、前后端交互时的序列化方式

实体类枚举属性持久化到数据库 1.EnumValue 2.配置 mybatis-plus:configuration:default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler 或 mybatis-plus:typeEnumsPackage: xxx 实体类中枚举属性自动转为EnumValue标记的属性值 从数据…

C++数学与算法系列之排列和组合

1. 前言 本文将聊聊排列和组合&#xff0c;排列组合是组合学最基本的概念&#xff0c;在程序运用中也至关重要。 排列问题&#xff1a;指从给定个数的元素中取出指定个数的元素进行排序。 组合问题&#xff1a;指从给定个数的元素中仅仅取出指定个数的元素&#xff0c;不排序…

Docker镜像

镜像是一种轻量级、可执行的独立软件包&#xff0c;它包含运行某个软件所需的所有内容&#xff0c;我们把应用程序和配置依赖打包好形成一个可交付的运行环境(包括代码、运行时需要的库、环境变量和配置文件等)&#xff0c;这个打包好的运行环境就是image镜像文件。 只有通过这…

【设计模式】工厂方法模式Factory(Java)

文章目录1. 定义2. 类图3. Java实现案例3.1 抽象类&#xff1a;Pizza和PizzaStore3.2 具体披萨&#xff1a;北京两种上海两种共四种3.3 具体披萨店&#xff1a;北京店和上海店3.4 测试主方法1. 定义 工厂方法模式定义了一个创建对象的接口&#xff0c;但由子类决定要实例化的类…

基于JAVA的XX公司固定资产管理系统的设计与实现

开发工具(eclipse/idea/vscode等)&#xff1a; 数据库(sqlite/mysql/sqlserver等)&#xff1a; 功能模块(请用文字描述&#xff0c;至少200字)&#xff1a; 本课题研究对象是中小企业财务管理系统&#xff0c;设计采用自己开发实践和所学知 识&#xff0c;系统部分主要分为以下…

汽车主机厂Adams/Car悬架动力学开发最全攻略

​​​​​​​一、写在前面 实际经历告诉我们&#xff0c;当我们接触一个新事物或学习一项新的技能时&#xff0c;入门往往是最为困难的&#xff0c;迷茫、彷徨、无助…… 正是基于同样的经历&#xff0c;在掌握Adams/Car软件的应用后&#xff0c;作者即开始构思如何将自己的…

论文投稿指南——中文核心期刊推荐(电工技术)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

linux后台自定义后台服务service(已filebeat举例)

文章目录一、配置攥写1&#xff09;安装filebeat和配置相关修改2&#xff09;常用命令二、启动顺序1&#xff09;命令循序2&#xff09;systemctl添加自定义系统服务&#xff08;服务填写指南&#xff09;3&#xff09;linux的systemctl命令详解及使用教程三、遇到的坑点和报错…

One-Hot 独热编码

1. 什么是独热编码 独热编码&#xff0c;又称一位有效编码。采用N位状态寄存器来对N个状态进行编码&#xff0c;直观来说就是有多少个状态就有多少比特&#xff0c;除了有效的比特为1外&#xff0c;其他都为0. 2. 独热编码过程 &#xff08;1&#xff09;将分类值映射到整数…

Simulcast与SVN

什么是Simulcast: 一个客户端向服务器发送高清&#xff0c;标清&#xff0c;低清三种视频流&#xff0c;服务器根据其他接收客户端的带宽情况分发不同的视频流。Simulcast不仅有客户端的工作&#xff0c;还有服务器的工作。 开启Simulcast的三种方式: Munging SDP方式 添加assr…

网络实验①——同Vlan下相互通信

实验要求&#xff1a; pc0与pc1互通pc2与pc3互通实验步骤&#xff1a; A交换机配置&#xff1a; enable config t hostname switch-A vlan 10 vlan 20 exit interface f0/1 switch access vlan 10 no shutdown interface f0/2 switch access vlan 20 no shutdown interface f0/…

无线耳机哪个品牌好一点?真无线蓝牙耳机推荐品牌

蓝牙耳机随着近几年的快速发展&#xff0c;已经成为了人们外出时必不可少的数码产品之一。而现如今&#xff0c;市面上的蓝牙耳机品牌众多&#xff0c;挑选起来让人眼花缭乱&#xff0c;人们在选择时不免陷入纠结。那么&#xff0c;无线耳机哪个品牌好一点&#xff1f;下面&…

day23【代码随想录】翻转二叉树、对称二叉树、相同的树、另一棵树的子树、完全二叉树的结点个数

文章目录前言一、翻转二叉树&#xff08;力扣226&#xff09;1、递归法1、使用前序遍历2、使用后序遍历2、迭代法1、层序遍历二、对称二叉树&#xff08;力扣101&#xff09;三、相同的树&#xff08;力扣100&#xff09;四、另一棵树的子树&#xff08;力扣572&#xff09;五、…

[附源码]Nodejs计算机毕业设计教师信息采集系统Express(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分…