Basic of Solidity (solidity基础)

news2024/9/28 7:28:29

目录

1.first contract

·申明编译器版本

·定义合约

·合约构造函数

·定义变量

·定义函数

2.data type

·值类型(Value Types)

·引用类型(Reference Types)

·映射类型(Mapping Types)


Solidity是一种用于编写智能合约的编程语言。智能合约是在区块链平台上执行的自动化合约,它们定义了参与方之间的规则和条件,并确保这些规则在合约中被执行。

Solidity最初是为以太坊区块链平台设计的,但也被其他以太坊虚拟机(EVM)兼容的区块链平台广泛采用。它提供了一套丰富的库和功能,使开发者能够在智能合约中实现复杂的逻辑和业务流程。

使用Solidity编写的智能合约可以实现多种功能,包括创建和管理数字资产(如代币)、实施多方签名、执行投票和选举、创建去中心化应用程序(DApps)等。

1.first contract

一个合约通常由状态变量(合约数据)合约函数组成。我们以最简单的Counter 计数器作为入门合约

·申明编译器版本

编写合约首先要做的是声明编译器版本, 告诉编译器使用什么版本的编译器来编译

pragma solidity >=0.8.0;  //使用大于等于0.8.0 版本的编译编译 Counter 合约

·定义合约

Solidity 使用 contract 定义合约,和其他语言的类(class)很类似,合约本身也是一个数据类型, 称为合约类型,除此之外合约还可以定义事件、自定义类型等。

contract Counter {   //定义了一个名为 Counter 的合约
}

·合约构造函数

构造函数是在创建合约时执行的一个特殊函数,用来初始化合约, constructor 关键字声明的一个构造函数。

如果没有初始化代码编译器会添加一个默认的构造函数constructor() public {}

状态变量的初始化,也可以在声明时进行指定,未指定时,默认为0。

contract Base {
    uint x;
    address owner;
    constructor(uint _x) public {
       x = _x;
       owner = msg.sender;
    }
}

·定义变量

Solidity 是一个静态类型语言,在定义每个变量时需要声明该变量的类型,定义变量按格式: 变量类型 变量可见性 变量名。变量可见性是可选的,没有显示申明可见性时,会使用缺省值 internal

uint public counter;     //声明了一个变量名为 counter,类型为 uint(256位无符号整数)

合约中的变量会在区块链上分配一个存储单元。在以太坊中,所有的变量构成了整个区块链网络的状态,所以合约中变量通常称为状态变量

但有两个特殊的“变量“, 他们不在链上分配存储单元:

1)常量

constant 用来声明一个常量,不占用合约的存储空间,在编译时使用对应的表达式值替换常量名,即使用constant修饰的状态变量,只能使用在编译时有确定值的表达式来给变量赋值。

contract C {
    uint constant x = 32**22 + 8;
    string constant text = "abc";
}
2)不可变量

不可变量在构造函数中进行赋值,构造函数是在部署的时候执行,因此这是运行时赋值。

Solidity 中使用 immutable 来定义一个不可变量,它不会占用状态变量存储空间,在部署时,变量的值会被追加的运行时字节码中,因此它比使用状态变量便宜的多,同样带来了更多的安全性(确保了这个值无法再修改),因此不可变量在很多时候非常有用,比如保存创建者地址、关联合约地址等。

contract Example {    
    uint immutable decimals;
    uint immutable maxBalance;
    constructor(uint _decimals, address _reference) public {
       decimals = _decimals;
       maxBalance = _reference.balance; 
    }
​
}

·定义函数

function关键字用来定于函数

  function count() public {          //名为 count() 函数,对counter状态变量加 1
        counter = counter + 1;
    }

由于状态变量的改变,因此调用这个函数会修改区块链状态,这时我们就需要通过一个交易来调用该函数,调用者为交易提供 Gas,验证者(矿工)收取 Gas 打包交易,经过区块链共识后,counter变量才真正算完成加1 ;

我们还可以根据需要定义函数的参数与返回值以及指定该函数是否要修改状态,一个函数定义形式可以这样表示:

function 函数名(<参数类型> <参数名>) <可见性> <状态可变性> [returns(<返回类型>)]{ 
}
1)函数返回值

在Solidity 中,返回值与参数的处理方式是一样的,代码中 返回值 result 也称为输出参数,我们可以在函数体里直接为它赋值,或直接在 return 语句中提供返回,返回值可以仅指定其类型,省略名称:

function addAB(uint a, uint b) public returns (uint) {
      ....
    return counter + a + b;
}

同时,Solidity 支持函数有多个返回值:

function f() public pure returns (uint, bool, uint) {
        return (7, true, 2);
    }
     function g() public {
        *// 获取返回值*
        (uint x, bool b, uint y) = f();
     }    
}
2)视图函数

用 view 修饰的函数, 称为视图函数, 它只能读取状态,而不能修改状态,调用视图函数时,只需要当前链接的节点执行,就可返回结果

function cal(uint a, uint b) public view returns (uint) {
    return a * (b + 42) + now;
}//cal() 函数不修改状态,它不需要提交交易,也不需要花费交易费

如果视图函数在一个会修改状态的函数中调用,那么视图函数会消耗 Gas,如:

 function set(uint a, uint b) public returns (uint) {
            return cal(a, b);
    }

因为外部调用视图函数时 Gas 价格为0, 而在修改状态的函数中,Gas 价格随交易设定

3)纯函数

用 pure 修饰的函数, 称为纯函数, 它既不能读取也不能修改状态,仅用作计算

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

2.data type

·值类型(Value Types)

值类型变量表示可以用32个字节表示的数据,包含以下类型,在赋值或传参时,总是进行拷贝。

1)整型Integers

当要表示一个数值时,通常用整型来表达

int/unit

int/uint 表示有符号和无符号不同位数整数。支持关键字uint8uint256uintint默认对应的是uint256int256

关键字末尾的数字以8步进,表示变量所占空间大小,整数取值范围跟空间有关, 比如uint32类型的取值范围是 0 到 2^32-1(2的32次方减1),当没有为整型变量赋值时,会使用默认值 0。

整型支持的运算符包括以下几种:

  • 比较运算符: <=(小于等于)、<(小于) 、==(等于)、!=(不等于)、>=(大于等于)、>(大于)

  • 位操作符: &(和)、|(或)、^(异或)、~(位取反)

  • 算术操作符:+(加号)、-(减)、-(负号),*/, %(取余数), **(幂)

  • 移位: <<(左移位)、 >>(右移位)

2)地址类型address

Solidity 合约程序里,使用地址类型来表示我们的账号,另外合约和普通地址,都可以用address 类型表示,地址类型有两种:

  • address:保存一个20字节的值(以太坊地址的大小)。

  • address payable:表示可支付地址,在地址格式上,其实与address 完全相同,也是20字节,拥有的两个成员函数transfersend ,可以向该地址转账。当需要向地址转账时,可以使用以下代码把address 转换为address payable

    address payable ap = payable(addr);

【注】:做此区分,显示的表达,一个地址可以接受ETH, 表示其有处理ETH的逻辑(EOA 账户本身可转账ETH);如果不做区分,当我们把 ETH 转到一个地址上时,恰巧如果后者是一个合约地址又没有处理ETH的逻辑,那么 ETH 将永远锁死在该合约地址上,任何人都无法提取和使用它。

地址类型的一些成员函数

<address>.balance : 返回地址的余额, 余额以wei为单位 (uint256)。

<address payable>.transfer(uint256 amount) : 用来向地址发送数值为amount的以太币(wei),transfer 函数只使用固定的 2300 gas , 发送失败时抛出异常。

<address payable>.send(uint256 amount) returns (bool): sendtransfer 函数一样,同样使用固定的 2300 gas , 但在发送失败时返回false,不抛出异常。

eg:

contract testAddr {
   
   // 如果合约的余额大于等于10,而x小于10,则给x转10 wei
    function testTrasfer(address payable x) public {
       address myAddress = address(this);//把合约转换为地址类型
       if (x.balance < 10 && myAddress.balance >= 10) {  //.balance获取余额
           x.transfer(10);  //转账
       }
    }
}
3)合约类型

合约是一个类型,我们可以通过这个合约类型来创建合约(即部署合约),然后与合约里的函数交互,比如调用一个合约的函数可以创建一个另一个合约:

pragma solidity ^0.8.0;
​
contract Hello {
  function sayHi() public view returns  (uint) {
      return 10;
  }
}
​
contract HelloCreator {
    uint public x;
    Hello public h;
​
•    function createHello() public returns (address) {
•        h = new Hello();
•        return address(h);
  }
}

合约类型数据成员:

对于某个合约c有

(1)type(C).name :获得合约的名字。

(2)type(C).creationCode:获得创建合约的字节码。

(3)type(C).runtimeCode:获得合约运行时的字节码。

【问】:如何区分合约和外部地址

答:区分一个地址是合约地址还是外部账号地址,关键是看这个地址有没有与之相关联的代码。EVM提供了操作码EXTCODESIZE,用来获取地址相关联的代码大小(长度),如果是外部账号地址,则没有代码返回。因此我们可以使用以下方法判断合约地址及外部账号地址。

function isContract(address addr) internal view returns (bool) {
  uint256 size;
  assembly { size := extcodesize(addr) }
  return size > 0;
  }

如果是在合约外部判断,则可以使用web3.eth.getCode()(一个Web3的API),或者是对应的JSON-RPC方法——eth_getcode。getCode()用来获取参数地址所对应合约的代码,如果参数是一个外部账号地址,则返回“0x”;如果参数是合约,则返回对应的字节码,下面两行代码分别对应无代码和有代码的输出。

>web3.eth.getCode(“0xa5Acc472597C1e1651270da9081Cc5a0b38258E3”) 
“0x”
>web3.eth.getCode(“0xd5677cf67b5aa051bb40496e68ad359eb97cfbf8”) “0x600160008035811a818181146012578301005b601b6001356025565b8060005260206000f25b600060078202905091905056” 

通过对比getCode()的输出内容,就可以判断出是哪一种地址。

·引用类型(Reference Types)

引用类型用来表示复杂类型,占用的空间超过32字节,在申明一个引用类型的变量,需要指定该变量的位置,拷贝时开销很大,因此可以使用引用的方式,通过多个不同名称的变量指向一个值,包括数组结构体

在定义引用类型时,有一个额外属性来标识数据的存储位置:

memory(内存): 变量在运行时存在,其生命周期只存在于函数调用期间,gas开销较小。

storage(存储):保存状态变量,只要合约存在就一直保存在区块链中,gas开销最大。

calldata(调用数据):存储函数参数的特殊数据位置,用来接收外部数据,是一个不可修改的、非持久的函数参数存储区域,gas开销最小。

【注】:不同引用类型在进行赋值的时候,只有在不同的数据位置赋值时会进行一份拷贝,而在同一数据位置内通常是增加一个引用

1)数组

def

数组和大多数语言一样, 在一个类型后面加上一个[],表示可以存储一组该类型的值。数组类型有两种:固定长度和动态长度

    // 状态变量缺省位置为 storage 
    uint [10] tens; // 固定长度的数组
    uint [] numbers;  // 动态长度的数组
    address [10] admins;  //admins最多有10个地址
    
    // 作为参数,使用 calldata 
    function copy(uint[] calldata arrs) public {
        numbers = arrs;  //  赋值时,不同的数据位置的变量会进行拷贝。 
    }
    
    // 作为参数,使用 memory 
    function handle(uint[] memory arrs) internal {
    }
}

数组的初始化可以在声明时进行,还可以用new关键字进行声明,创建基于运行时长度的内存数组,使用 new 创建内存数组时,会根据长度在内存中分配相应的空间;如果变量是在存储中,则表示分配一个起始空间,在之后运行过程中可以扩展该空间

数组成员:

  • length:表示当前数组的长度(只读)。

  • push():用来添加新的零初始化元素到数组末尾,并返回元素的引用,以便修改元素的内容,如:x.push().t = 2x.push() = b,只对存储(storage)中的动态数组有效。

  • push(x):添加给定元素到数组末尾。 没有返回值,只对存储(storage)中的动态数组有效

  • pop():从数组末尾删除元素,数组的长度减1,会在移除的元素上隐含调用delete,及时释放不使用的空间,节约gas。pop()没有返回值,只对存储(storage)中的动态数组有效。

特殊的数组类型:

string:一个字符串也可以是一个字符数组,但不支持数组的push&pop方法

bytes:动态分配大小字节的数组,类似于byte[],但是bytes的gas费用更低。bytes 也可以用来表达字符串, 但通常用于原始字节数据;支持push&pop

    //声明
    bytes bs;
    bytes bs0 = "12abcd";
​
    string str1 = "TinyXiong";
    string name;

【注】:字符串s通过bytes(s)转为一个bytes,通过下标访问bytes(s)[i]获取到的不是对应字符,而是获取对应的UTF-8编码;Solidity 语言本身提供的string功能比较弱,并没有提供一些实用函数

数组gas消耗

function sum() public {
        uint len = numbers.length;
        for (uint i = 0; i < len; i++) {
            total += numbers[i];
        }

分析上述sum()函数可以看出gas消耗是随着numbers 元素线性增长的,如果numbers 元素非常多,sum() 消耗 gas 会超过区块 gas 限制而无法执行,常见的解决方案:

  1. 将非必要的计算转移到链下进行。

  2. 想办法控制数组的长度。

  3. 想方法分段计算,让每段的计算工作量 Gas 可控。

2)结构体

Solidity 使用 struct 关键字来定义一个自定义组合类型

同时需要为每个成员定义其类型,除可以使用基本类型作为成员以外,还可以使用数组、结构体、映射作为成员:

struct Student {
    string name;
    mapping(string=>uint) score;
    int age;
}

结构体的声明赋值

// 声明变量而不初始化
  Person person;
​
// 只能作为状态变量这样使用,按成员顺序(结构体声明时的顺序)赋值
Person person = Person(address(0x0), false, 18) ;
// 在函数内声明
Person memory person = Person(address(0x0), false, 18) ;
​
// 使用具名变量初始化,可不按成员定义的顺序赋值
Person person = Person({account: address(0x0), gender: false, age: 18}) ;
​
//在函数内声明
Person memory person =  Person({account: address(0x0), gender: false, age: 18}) ;

·映射类型(Mapping Types)

一种键值对的映射关系存储结构,定义方式为mapping,和Java的Map、Python的Dict在功能上类似。

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

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

相关文章

vue + element 笔记(vue2.0)

1.安装nodejs&#xff0c;cmd中运行 node -v 验证是否成功 2.安装cnpm&#xff0c;cmd中运行 npm install -g cnpm --registryhttps://registry.npm.taobao.org&#xff0c;cmd中 cnpm -v 验证是否成功 3.安装vue-cli&#xff0c;cmd中运行 cnpm install --global vue-cli&…

三、Java的运算符

三、运算符 运算符是一种特殊的符号&#xff0c;用以表示数据的运算、赋值和比较等。 3.1、算术运算符 - * / % (前) (后) (前)-- (后)-- % :取余运算 结果的符号与被模数的符号相同 开发中&#xff0c;经常使用%来判断能否被除尽的情况 3.2、赋值运算符 注意&#xff1a;s2…

Spring Boot 中的 Native SQL 是什么, 如何使用

在 Spring Boot 中&#xff0c;我们通常使用 ORM 框架&#xff08;例如 Hibernate 或 MyBatis&#xff09;来操作数据库。但是&#xff0c;有时候我们需要执行一些自定义的 SQL 查询或更新语句&#xff0c;这时候就需要使用 Spring Boot 中的 Native SQL。 在本文中&#xff0…

Matlab绘图系列教程-Matlab 34 种绘图函数示例(下)

Matlab绘图系列教程&#xff1a;揭秘高质量科学图表的绘制与优化 文章目录 Matlab绘图系列教程&#xff1a;揭秘高质量科学图表的绘制与优化第一部分&#xff1a;入门指南1.1 简介关于本教程的目的与范围Matlab绘图在科学研究中的重要性 1.2 准备工作安装Matlab及其工具箱 1.3 …

8、添加PolylineCollection线

本节演示添加线要素&#xff0c;使用第四节单击事件的例子&#xff0c;修改点击事件创建线要素。 1、重新修改地图默认位置 double r glm::radians(-45.0); Cesium::HeadingPitchRoll *orientation new Cesium::HeadingPitchRoll(0.0, r, 0.0); glm::dvec3 initialPosition…

map和set的封装

目录 封装逻辑 用红黑树封装set和map 红黑树的定义改变 迭代器 红黑树的迭代器 set的迭代器 map重载[]操作的实现 比较关键——得到 key的类型K 封装逻辑 用红黑树封装set和map map和set用红黑树来实现的&#xff1b;红黑树的前三个模板参数分别表示&#xff1a;得到key的…

C语言---程序环境和预处理(底层原理万字详解)

文章目录 前言&#x1f31f;一、程序的翻译环境和执行环境&#x1f31f;二、详解编译链接&#x1f30f;2.1命令&#x1f30f;2.2 板书详解&#x1f30f;2.3运行环境 &#x1f31f;三、预处理详解&#x1f30f;3.1预定义符号&#x1f30f;3.2 #define&#x1f4ab;3.2.1 #define…

第三章 SSD存储介质:闪存

3.1 闪存物理结构 闪存芯片从小到大依此是由&#xff1a;cell&#xff08;单元&#xff09;、page&#xff08;页&#xff09;、block&#xff08;块&#xff09;、plane&#xff08;平面&#xff09;、die&#xff08;核心&#xff09;、NAND flash&#xff08;闪存芯片&#…

【动手学习深度学习--逐行代码解析合集】10Dropout暂退法

【动手学习深度学习】逐行代码解析合集 10Dropout暂退法 视频链接&#xff1a;动手学习深度学习–Dropout暂退法 课程主页&#xff1a;https://courses.d2l.ai/zh-v2/ 教材&#xff1a;https://zh-v2.d2l.ai/ 1、暂退法原理 2、从零开始实现暂退法 import torch from torch i…

微服务网关技术选型:Zuul2、Gateway、OpenResty、Kong

1、简介 当使用单体应用程序架构时&#xff0c;客户端&#xff08;Web 或移动端&#xff09;通过向后端应用程序发起一次 REST 调用来获取数据。负载均衡器将请求路由给 N 个相同的应用程序实例中的一个。然后应用程序会查询各种数据库表&#xff0c;并将响应返回给客户端。微…

missing-semester————1

文章目录 shell概述echoshell如何知道去哪寻找date或echo呢&#xff1f;$PATHlsman流根用户 shell概述 root1test:~$ $表示身份不是root用户 ~表示当前所在位置是"home" root1test:~$ date Sat Jul 8 02:57:44 UTC 2023输入命令&#xff0c;会被shell解析 上述执行…

静态路由配置——Cisco Packet Tracer

这里放一个用Packet Tracer 8.0实现的配置好的静态路由文件&#xff0c;配置如下 下载链接如下&#xff1a; https://wwix.lanzoue.com/ifp5T11ksnla

内嵌tomcat报错

严重: Unable to process Jar entry [module-info.class] from Jar [jar:file:/D:/javaTools/apache-maven-bin/apache-maven-3.6.1/maven-repo/com/fasterxml/jackson/core/jackson-databind/2.10.5/jackson-databind-2.10.5.jar!/] for annotations org.apache.tomcat.util.b…

NI采集卡USB-6361多通道模拟输入采集报错解决方案

文章目录 前言一、现有例程1、前面板2、程序框图 二、采集测试1、单通道采集2、多通道采集①、错误的做法②、正确的做法1&#xff09;前面板2&#xff09;程序框图3&#xff09;运行测试 总结 前言 折腾一块 USB-6361 采集卡很久了&#xff0c;之前都是单通道采集模拟信号&am…

云原生(第六篇)k8s-kubeadmin部署

master&#xff08;2C/4G&#xff0c;cpu核心数要求大于2&#xff09; 192.168.169.10 docker、kubeadm、kubelet、kubectl、flannel node01&#xff08;2C/2G&#xff09; 192.168.169.30 docker、kubeadm、kubelet、kubect…

汇总:FlatLaf-intellij-themes皮肤效果一览

关于主题包&#xff1a; FlatLaf 是一个跨平台的 Java Swing 外观库&#xff0c;提供现代化的平面化用户界面。 导包 <dependency><groupId>com.formdev</groupId><artifactId>flatlaf</artifactId><version>3.1.1</version><sco…

机器学习28:《推荐系统-I》概述

在互联网领域&#xff0c;推荐系统&#xff08;Recommendation Systems&#xff09;的应用非常广泛。在音视频方面&#xff0c;如抖音、快手、哔哩等&#xff1b;在电商平台方面&#xff0c;如京东、淘宝、拼多多等。推荐有助于帮助用户快速发现潜在感兴趣的内容&#xff08;音…

RS485或RS232转ETHERCAT连接安川ethercat总线伺服

最近&#xff0c;生产管理设备中经常会遇到两种协议不相同的情况&#xff0c;这严重阻碍了设备之间的通讯&#xff0c;串口设备的数据不能直接传输给ETHERCAT。这可怎么办呢&#xff1f; 别担心&#xff0c;远创智控YC-ECT-RS485/232来了&#xff01;这是一款自主研发的ETHER…

使用vue ui创建vue项目失败原因

每个人的失败原因都不相同&#xff0c;因为下载NodeJS文件时&#xff0c;默认下载到c盘中&#xff0c;我改变盘符到了D盘&#xff0c;因此要删除c盘中隐藏的文件&#xff0c;注意是c盘中的.npmrc文件。具体位置如下&#xff1a; 点击查看显示隐藏文件才能看到该文件 最后创建项…

磁性材料在使用时需要注意什么

为了不引起人身损伤及磁体性能不良&#xff0c;请遵循以下注意事项&#xff1a; 1、 磁体在使用过程中应确保工作场所干净&#xff0c;否则容易吸附铁屑等磁性小颗粒影响使用。 2、 磁体在充磁时&#xff0c;磁体必须固定&#xff0c;且充磁场必须大于磁体材料矫顽力的2.5倍&…