solidity中的函数详解

news2024/12/27 1:38:37

1.概念

在Solidity中,函数是智能合约的基本构建块,用于实现特定的业务逻辑。以下是Solidity函数的一些关键特性和详细解释:

函数定义;

函数由 function 关键字开始,后跟函数的名称、参数列表和返回值。函数可以是内部的(internal)或外部的(external),状态变更的(state-changing)或只读的(read-only)。

function functionName(parameterType param1, parameterType param2) public returns (returnType return1, returnType return2) {
    // 函数体
}

函数类型

•  内部函数(Internal functions): 只能在当前合约或继承它的合约中调用。
•  外部函数(External functions): 可以被其他合约或事务调用。

 内部函数:

内部函数只能在定义它们的合约内部以及继承它们的合约内部被调用。它们不能被外部合约直接调用。内部函数通常用于合约内部逻辑的实现,这些逻辑不希望被外部合约直接访问。

以下是内部函数的一些特点:

 定义方式:默认情况下,如果没有指定可见性,函数会被视为内部函数。也可以显式地使用internal关键字来定义。

 调用方式:可以直接通过函数名调用,不需要使用this关键字。

  •  性能:内部函数的调用比外部函数更高效,因为它们不会进行跨合约调用。
  •  用途:通常用于实现合约的辅助功能,如计算逻辑、数据验证等。
pragma solidity ^0.8.0;

contract InternalExample {
    // 内部函数,默认为internal
    function helper() internal pure returns (uint) {
        return 42;
    }

    function doSomething() public {
        // 直接调用内部函数
        uint result = helper();
        // 使用result进行其他操作
    }
}

contract InheritedContract is InternalExample {
    function callHelper() public pure returns (uint) {
        // 继承的合约可以调用内部函数
        return helper();
    }
}

在上面的例子中,helper函数是内部函数,它只能在InternalExample合约内部或继承它的合约(如InheritedContract)内部被调用。 

外部函数:

外部函数可以从其他合约或通过事务调用。它们通常用于合约之间的交互。

以下是外部函数的一些特点:

  •  定义方式:使用external关键字来定义。
  •  调用方式:从合约内部调用时,必须使用this关键字或者直接作为交易的一部分调用。从合约外部调用时,直接使用合约地址和函数签名。
  •  性能:外部函数调用可能会更昂贵,因为它们可能涉及到跨合约调用
  •  用途:用于合约之间的通信和交互,例如,调用其他合约的函数。
pragma solidity ^0.8.0;

contract ExternalExample {
    // 外部函数
    function externalFunction() external pure returns (uint) {
        return 42;
    }
}

contract CallerContract {
    ExternalExample externalExample;

    constructor(address _externalExampleAddress) {
        externalExample = ExternalExample(_externalExampleAddress);
    }

    function callExternalFunction() public view returns (uint) {
        // 从合约内部调用外部函数
        return externalExample.externalFunction();
    }
}

 在上面的例子中,externalFunction是一个外部函数,它可以在其他合约(如CallerContract)中被调用。在CallerContract中,我们首先创建一个ExternalExample合约的实例,然后通过这个实例调用externalFunction。

2.状态可见性

函数可以修改合约的状态,这通过以下关键字指定:

  1. view:函数不会修改状态
  2. pure:函数即不读取也不修改状态
  3. payable:函数可以接受以太币

2.1 view

view修饰符用于声明一个函数为“视图函数”。视图函数不会改变合约的状态,即它们不会修改任何状态变量,也不会发送以太币。它们仅用于检索数据。view函数不会消耗任何Gas(燃料)并且不会修改区块链状态

以下是一些关于view修饰符的要点:

  1.  视图函数可以读取合约的状态变量。
  2.  视图函数不能修改状态变量。
  3.  视图函数不能触发事件。
  4.  视图函数不能调用任何非视图或非纯函数。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SimpleStorage {
  uint public x;

  function getX() public view returns (uint){
    return x;
  }

}

它们只能调用其他viewpure函数,而不能调用普通函数。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SimpleStorage {
    //状态变量
    uint public storedData;
    //构造函数,在合约创建的时候执行 
    constructor(uint initialValue){
        storedData=initialValue;
    }
    //view函数,读取存储到数据
    function retrieve() public view returns (uint){
        return storedData;
    }
    //普通函数,修改存储到数据
    function update(uint x)public {
        storedData = x;
    }
    //另一个view函数,执行计算但是不修改状态
    function calculateSquare() public view returns (uint){
        return storedData *storedData;
    }
}

注意,尽管view函数不会修改区块链的状态,但它们仍然需要执行计算,并且调用它们会消耗一定的Gas。不过,这个Gas成本比执行会改变状态的函数要低得多。此外,view函数不能直接调用非view或非pure函数,因为非view/pure函数可能会修改状态。

  • 因为读取区块链的信息消耗了计算量和gas。
  • 调用view函数是免费的,除非你在消耗gas的函数中调用它。
  • 我们可以编译,删除,部署,调用store,输入678,执行,查看它的消耗,发现加了retrieve()这行代码后,store函数的gas消耗更多了。

 

 2.2 pure

在Solidity中,`pure`函数是一种特殊的函数修饰符,它用于表明该函数不会读取或修改区块链的状态。这意味着`pure`函数在执行时不会对任何状态变量进行读取或写入操作,也不会产生任何外部可观察的效果,如发起交易或调用其他合约。

以下是关于`pure`函数的详细解释:

 1. 纯函数的特点

  •  不读取状态变量:函数体内不能包含对任何状态变量的读取操作。
  •  不修改状态变量:函数体内不能包含对任何状态变量的写入操作。
  •  不访问区块链数据:函数不能访问区块链的任何信息,如区块哈希、时间戳、交易发送者等。

 不触发事件:函数不能触发事件,因为事件是写入区块链日志的一种方式。

 不调用非`pure`函数:`pure`函数不能调用任何非`pure`函数,因为非`pure`函数可能会读取或修改状态。

pragma solidity ^0.8.0;
contract Calculator {
    // 这是一个pure函数,用于计算两个整数的和
    function add(uint x, uint y) public pure returns (uint) {
        return x + y;
    }
    
    // 这是一个pure函数,用于返回一个常量值
    function getMagicNumber() public pure returns (uint) {
        return 42;
    }
}

看图,状态为未读取 

使用`pure`修饰符可以提高函数执行的效率,因为不需要读取状态,所以执行时不会消耗gas去访问存储。

 如果一个函数实际上不满足`pure`的条件(例如,它读取了状态变量),但在声明时错误地使用了`pure`修饰符,那么在编译时或运行时可能会出现错误。

举个例子:

示例:错误使用 pure 修饰符
假设我们有一个智能合约,其中包含一个状态变量,并且我们试图创建一个计算这个状态变量值的函数。如果这个函数被错误地声明为pure,虽然它实际上读取了状态变量,那么将会出现问题

pragma solidity ^0.8.0;

contract IncorrectPureUsage {
    uint public myNumber = 10; // 状态变量

    // 错误地声明为pure
    function readNumber() public pure returns (uint) {
        return myNumber; // 这里读取了状态变量
    }
}

在这个例子中,readNumber函数尝试读取状态变量myNumber的值并返回它。尽管这个函数没有修改任何状态,它读取了状态,因此不应该被声明为pure。正确的做法是将其声明为view,因为view函数允许读取状态但不修改状态。 

 如何修复
为了修复这个问题,你应该将pure修饰符改为view,因为函数确实需要读取状态变量: 

pragma solidity ^0.8.0;

contract CorrectUsage {
    uint public myNumber = 10; // 状态变量

    // 正确地声明为view
    function readNumber() public view returns (uint) {
        return myNumber; // 这里读取了状态变量
    }

在早期版本的Solidity中(如0.4.x),与`pure`类似的概念是`constant`,但在0.5.0版本之后,`constant`被弃用,并推荐使用`view`和`pure`。

总的来说,`pure`函数是Solidity中一种非常有用的工具,它用于声明完全独立于智能合约状态的函数,有助于确保函数的确定性和安全性。

3.可见度标识符

函数和变量有四种可见度标识符public、private、internal和external。

  • public:public 是最高级别的可见度标识符,表示变量、函数或合约对内外部都可见。公共状态变量可以被任何人读取,并且公共函数可以被外部调用者调用。公共函数和状态变量的访问可以通过合约地址直接进行。
  • private:private 是最低级别的可见度标识符,表示只有当前合约内的其他函数才能访问该变量或函数。私有状态变量只能在当前合约中访问,私有函数只能被当前合约的其他函数调用。私有状态变量和函数对外部调用者是不可见的。
  • internal:internal 表示内部可见性,表示只有当前合约及其派生合约内的其他函数才能访问该变量或函数。内部状态变量和函数可以在当前合约及其派生合约中访问,但对外部调用者不可见。
  • external:external 可以用于函数,表示该函数只能通过外部消息调用。外部函数只能被其他合约调用,而不能在当前合约内部直接调用。此外,外部函数不能访问合约的状态变量,只能通过参数和返回值进行数据交互。
  • 如果没有显式指定变量的访问修饰符,则默认为internal,而internal关键字表示,它只对本合约和继承合约可见。

现在这个favoriteNumber变量被设置为了internal,所以我们看不到它。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

contract SimpleStorage {

    // This gets initialized to zero!
    // <- This means that this section is a comment!
    uint256 public favoriteNumber;

    function store(uint256 _favoriteNumber) public {
        favoriteNumber = _favoriteNumber;
    }
}

 然后进入部署区域,点击下面的X,把旧的合约删掉。当然只是从这个窗口删掉,但是没有从区块链删掉,因为区块链是不可更改的。当然因为这个是测试环境,所以只是某种程度上不可更改。编译,然后重新部署。

 然后发现在新的合约中有两个按钮。

其中一个橘黄色按钮是函数,还有一个favoriteNumber的按钮,这个按钮代表favoriteNumber变量,就像一个显示变量值的函数(就比如Java里面的getter函数),实际上确实就是一个getter函数,此章节后面将会详细说明。

如果现在点击这个按钮,会显示什么?因为favoriteNumber初始化的默认值是0,点击一下,我们可以看到显示的是0。现在就是说这个uint256数据类型存储的数值是0。如果现在通过store函数把变量改为678,再点击favoriteNumber按钮,可以看到数值更新为678。

public关键字

当你在Solidity中使用public关键字修饰一个状态变量时,Solidity编译器会自动生成一个类似getter函数的方法来允许外部调用者读取该变量的值。

这个自动生成的getter函数会使用与状态变量同名的函数名,并且没有参数。它的返回值类型与状态变量的类型相匹配。通过调用这个getter函数,外部调用者可以获取到状态变量的当前值。

例如,当你声明一个public的状态变量uint256 public myNumber时,Solidity编译器将会自动生成一个名为myNumber()的函数,用于获取myNumber的值。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

contract MyContract {
    uint256 public myNumber;
}

外部调用者可以通过合约地址调用这个自动生成的getter函数来获取myNumber的值。

需要注意的是,public关键字只会生成一个getter函数用于读取状态变量,而不能直接修改状态变量的值。如果你希望外部调用者能够修改状态变量的值,你可以使用setter函数或将状态变量声明为可写入的。

4.作用域

在 Solidity 中,变量和函数可以拥有不同的作用域,作用域决定了这些变量和函数的可见性和访问权限。

以下是 Solidity 中常见的作用域:

  1. 全局作用域:在合约的整个范围内可见的变量和函数属于全局作用域。这些变量和函数可以被合约内的任何地方访问。
  2. 合约作用域:在合约内部声明的变量和函数具有合约作用域,它们只能在声明它们的合约内部访问。
  3. 函数作用域:在函数内部声明的变量具有函数作用域,只能在该函数内部访问。这些变量通常被称为局部变量。
  4. 事件作用域:在 Solidity 中,事件也有其特定的作用域。事件在声明它们的合约内部可见,可以被合约内的任何函数调用来触发。

4.1 全局作用域

全局变量和函数是在整个合约都是可见的

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract GlobalScopeExample {
    uint256 public globalVariable; // 全局变量

    // 全局函数
    function globalFunction() public view returns (uint256) {
        return globalVariable;
    }

    function updateGlobalVariable(uint256 _value) public {
        globalVariable = _value;
    }
}

4.2 合约作用域

合约作用域的变量和函数只能在声明它们的合约内部访问

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ContractScopeExample {
    uint256 public contractVariable; // 合约变量

    // 合约函数
    function contractFunction() public view returns (uint256) {
        return contractVariable;
    }

    // 只能在合约内部访问
    function updateContractVariable(uint256 _value) public {
        contractVariable = _value;
    }
}

剩下的例子跟其他语言的用法是差不多的,就不一一举例了。

5.gas的消耗

每次调用这个store函数,我们都会发送一个交易。因为每次在更改区块链状态的时候,我们都会发送交易,可以在右下角Remix的日志区域,查看交易细节。

你可以看到交易消耗了多少gas,可以看到这里的数字比发送交易所用到的21000 gas要多的,那是因为这里的操作会消耗更多的计算量。

实际上我们在这里存储了一个数字,现在如果我们在函数中做更多的操作会发生什么?除了在这里存储数据外,我们在存储变量后更新这个变量,让favoriteNumber加1。因为我们加了这样一个操作,这个函数将消耗更多的计算量。

然后我们现在重新编译删掉旧合约,然后重新部署,然后再次输入678,然后查看交易细节,我们可以发现执行交易的消耗的gas变得更多了。那是因为我们做的事情变多了,这个函数消耗的计算量变多了。

43730 gas -> 44127 gas

 每个区块链计算gas的方式不同,但最简单的理解是,做越多的操作,消耗更多的gas。

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

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

相关文章

PHP变量(第④篇)

本栏目教学是php零基础到精通&#xff0c;如果你还没有安装php开发工具请查看下方链接&#xff1a; Vscode、小皮面板安装-CSDN博客 今天来讲一讲php中的变量&#xff0c;变量是用于存储信息的"容器"&#xff0c;这些数据可以在程序执行期间被修改&#xff08;即其…

【自动驾驶】《Planning-oriented Autonomous Driving》UniAD论文阅读笔记

1.参考 论文&#xff1a;https://arxiv.org/pdf/2212.10156 代码&#xff1a;https://github.com/OpenDriveLab/UniAD 2.摘要 原来的自动驾驶任务都是分为模块化的&#xff0c;感知&#xff0c;预测&#xff0c;规划等。每个独立的任务可能都优化得很好&#xff0c;但可能会…

NR工作频段

NR定义了两个频率范围&#xff0c;FR1和FR2。在很多场景下&#xff0c;对于不同的频率范围&#xff08;FR&#xff09;&#xff0c;射频规范是单独定义的。NR可以工作的频率范围&#xff0c;即FR1和FR2的频率范围&#xff0c;如下表所示。 FR频率范围FR1410 MHz – 7125 MHzFR…

苍穹外卖学习笔记(十五)

文章目录 一. 缓存菜品缓存菜品DishController.java清除缓存数据 缓存套餐Spring Cachemaven坐标常用注解 入门案例springcachedemo.sqlpom.xmlapplication.ymlCacheDemoApplication.javaWebMvcConfiguration.javaUserController.javaUser.javaUserMapper.java 套餐管理SkyAppl…

大模型之大模型压缩(量化、剪枝、蒸馏、低秩分解),推理(vllm)

目录 前言 一、模型量化&#xff08;quantization&#xff09; 1. 量化概念 2. 模型量化优点 3. 什么情况下应该/不应该使用模型量化 4. 落地挑战 5. 量化方法 5.1 量化训练(Quant Aware Training, QAT) 原理 [伪量化节点&#xff08;fake quant&#xff09;](https://blog.csd…

241007深度学习之LeNet

目录 1.LeNet介绍2.组成3.代码实现 1.LeNet介绍 LeNet是最早发布的卷积神经网络之一,他是由AT&T贝尔实验室的研究员Yann LeCun在1989年提出的(并且以其命名),目的是识别图像中手写数字.当时,Yann LeCun发表了第一篇通过反向传播成功训练卷积神经网络的研究论文,这项工作代…

macOS Sequoia 15.0.1 (24A348) 正式版 ISO、IPSW、PKG 下载

macOS Sequoia 15.0.1 (24A348) 正式版 ISO、IPSW、PKG 下载 iPhone 镜像、Safari 浏览器重大更新和 Apple Intelligence 等众多全新功能令 Mac 使用体验再升级 请访问原文链接&#xff1a;https://sysin.org/blog/macOS-Sequoia/ 查看最新版。原创作品&#xff0c;转载请保留…

Qt源码-Qt多媒体音频框架

Qt 多媒体音频框架 一、概述二、音频设计1. ALSA 基础2. Qt 音频类1. 接口实现2. alsa 插件实现 一、概述 环境详细Qt版本Qt 5.15操作系统Deepin v23代码工具Visual Code源码https://github.com/qt/qtmultimedia/tree/5.15 这里记录一下在Linux下Qt 的 Qt Multimedia 模块的设…

性能测试学习6:jmeter安装与基本配置/元件/线程组介绍

一.JDK安装 官网&#xff1a;https://www.oracle.com/ 二.Jmeter安装 官网&#xff1a;http://jmeter.apache.org/download_jmeter.cgi 下载zip包&#xff0c;zip后缀那个才是Windows系统的jmeter 三.Jmeter工作目录介绍 四.Jmeter功能 1&#xff09;修改默认配置-汉化 2&am…

51 单片机最小系统

一、51 单片机最小系统概述 51 单片机最小系统是一个基于 51 单片机的最小化电路系统&#xff0c;它包含了使单片机能够正常工作的最少元件。这个系统主要用于学习和实验目的&#xff0c;帮助学习者在没有复杂电路的情况下快速了解 51 单片机的工作原理&#xff0c;其重要性不…

TryHackMe 第7天 | Web Fundamentals (二)

继续介绍一些 Web hacking 相关的漏洞。 IDOR IDOR (Insecure direct object reference)&#xff0c;不安全的对象直接引用&#xff0c;这是一种访问控制漏洞。 当 Web 服务器接收到用户提供的输入来检索对象时 (包括文件、数据、文档)&#xff0c;如果对用户输入数据过于信…

【可视化大屏】Python Flask框架介绍

为了能显示真实数据&#xff0c;使用flask快速搭建了一个web应用&#xff0c;然后连接数据库&#xff0c;读取数据库里的数据来进行大屏可视化显示&#xff08;btw&#xff1a;数据是从车主之家网站上爬虫爬的&#xff09; 家人们&#xff01;记得使用专业版的pycharm&#xf…

在忘记密码的情况下重新访问手机?5种忘记密码解锁Android手机的方法

无需密码即可访问Android手机。 即使你忘记了密码&#xff0c;你也可以解锁你的Android手机&#xff0c;但你通常需要将手机恢复出厂设置。 您可以通过执行出厂恢复或使用“查找我的设备”网站解锁大多数Android手机。 如果你不再有密码&#xff0c;这里有五种解锁安卓手机的…

sqli-labs less-17密码重置报错注入

密码重置报错植入 来到首页面我们看到页面提示【password reset】&#xff0c;说明这是更改密码的注入&#xff0c;也就是说我们知道一个账户名&#xff0c;修改他的密码&#xff0c;所以我们可以在passwd处进行注入。 闭合方式 添加单引号 有报错 可以知道闭合方式为单引号…

【JavaEE】——初始网络原理

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 一&#xff1a;局域网 1&#xff1a;概念 二&#xff1a;局域网的连接方式 1&#xff1a;网线直连 …

Qt实现Halcon窗口显示当前图片坐标

一、前言 Halcon加载图片的窗口&#xff0c;不仅能放大和缩小图片&#xff0c;还可以按住Ctrl键显示鼠标下的灰度值&#xff0c;这种方式很方便我们分析缺陷的灰度和对比度。 二、实现方式 ① 创建显示坐标和灰度的widget窗口 下图的是widget部件&#xff0c;使用了4个label控…

前端如何让页面上的文字“立”起来

前言 最近看到了一个很有意思的 CSS 效果&#xff0c;如下图&#xff0c;是一个文字立起来 阴影 的效果&#xff0c;觉得比较有意思&#xff0c;所以分享给大家~ 实现 基础样式 首先我们把基础的文字和样式编写出来&#xff0c;代码如下 效果如下&#xff1a; 伪元素 ->…

香橙派如何连接网络,及wiringOP库

1.使用nmcli dev wifi命令扫描周围的 WiFi热点 2.找到需要连接的WiFi 输入nmcli dev wifi connect wifi_name password wifi_passwd 其中wifi_name是你的WiFi名字&#xff0c;wifi_passwd是WiFi的密码 3.wiringOP库 包含#include <wiringPi.h> 首先使用wiringPiSet…

[OS] 再探Kernel Threads -2

内核线程&#xff08;Kernel Thread&#xff09;创建和执行机制&#xff1a; 在 Linux 内核中&#xff0c;内核线程&#xff08;kthread&#xff09; 是一种特殊类型的线程&#xff0c;专门用于执行内核任务。与用户态的进程和线程不同&#xff0c;内核线程不具有用户空间&…

计算机的错误计算(一百一十六)

摘要 计算机的错误计算&#xff08;一百一十&#xff09;分析了&#xff08;二&#xff09;中例1循环迭代错误计算的原因。应读者建议&#xff0c;本节将用错数讨论其例2的错误计算原因。 例1. 已知 计算 在 的错数&#xff0c;并用实例分析计算过程中的错误数字数量。…