智能合约-solidity语言学习
Solidity是一种静态类型语言,这意味着每个变量(状态变量和局部变量)都需要在编译时指
定变量的类型。
Solidity 提供了几种基本类型,并且基本类型可以用来组合出复杂类型。
除此之外,类型之间可以在包含运算符号的表达式中进行交互。 关于各种运算符号,可以参考 运算符优先级 。
“undefined”或“null”值的概念在Solidity中不存在,但是新声明的变量总是有一个 默认值 ,具体的默认值跟类型相关。 要处理任何意外的值,应该使用 错误处理 来恢复整个交易,或者返回一个带有第二个 bool
值的元组表示成功。
1.HelloWeb3(三行代码)
注意写合约脚本时,要规范框架:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
contract HelloWeb3{
string public _string = "Hello Web3!";
}
每行代码的含义:
1:介绍软件许可
2:声明源文件所用的solidity版本,只能编译 [0.8.4 , 0.9.0) 版本
3-4:定义一个合约。第3行创建合约(contract),并声明合约的名字 HelloWeb3。第4行是合约的内容,我们声明了一个string(字符串)变量_string,并给他赋值 “Hello Web3!”。
值类型
Types
以下类型也称为值类型,因为这些类型的变量将始终按值来传递。 也就是说,当这些变量被用作函数参数或者用在赋值语句中时,总会进行值拷贝。
布尔类型
:可能的取值为字面常量值 true
和 false
。
// 布尔运算
bool public _bool1 = !_bool; //取非
bool public _bool2 = _bool && _bool1; //与
bool public _bool3 = _bool || _bool1; //或
bool public _bool4 = _bool == _bool1; //相等
bool public _bool5 = _bool != _bool1; //不相等
整型
uint , int int
/ uint
:分别表示有符号和无符号的不同位数的整型变量。 支持关键字 uint8
到 uint256
(无符号,从 8 位到 256 位)以及 int8
到 int256
,以 8
位为步长递增。 uint
和 int
分别是 uint256
和 int256
的别名
对于整形 X
,可以使用 type(X).min
和 type(X).max
去获取这个类型的最小值与最大值
// 整数运算
uint256 public _number1 = _number + 1; // +,-,*,/
uint256 public _number2 = 2**2; // 指数
uint256 public _number3 = 7 % 2; // 取余数
bool public _numberbool = _number2 > _number3; // 比大小
地址类型
地址类型有两种形式,他们大致相同:
address
:保存一个20字节的值(以太坊地址的大小)。address payable
:可支付地址,与address
相同,不过有成员函数transfer
和send
。
这种区别背后的思想是 address payable
可以向其发送以太币,而不能先一个普通的 address
发送以太币,例如,它可能是一个智能合约地址,并且不支持接收以太币。
类型转换:
允许从 address payable
到 address
的隐式转换,而从 address
到 address payable
必须显示的转换, 通过 payable(<address>)
进行转换。
address
允许和 uint160
、 整型字面常量、bytes20
及合约类型相互转换。
只能通过 payable(...)
表达式把 address
类型和合约类型转换为 address payable
。 只有能接 收以太币的合约类型,才能够进行此转换。例如合约要么有 receive 或可支付的回退函数。 注意 payable(0)
是有效的,这是此规则的例外。
-
地址类型成员变量
查看所有的成员,可参考 地址成员。
balance
和transfer
成员
可以使用
balance
属性来查询一个地址的余额, 也可以使用transfer
函数向一个可支付地址(payable address)发送 以太币Ether (以 wei 为单位):address x = 0x123; address myAddress = this; if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);
如果当前合约的余额不够多,则
transfer
函数会执行失败,或者如果以太转移被接收帐户拒绝,transfer
函数同样会失败而进行回退
// 地址定义
address public _address = 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71;
address payable public _address1 = payable(_address);
// payable address,可以转账、查余额
// balance
uint256 public balance = _address1.balance;
// transfer
address payable addr;
addr.transfer(1);//合约向addr转账1wei
address x = 0x123;
address myAddress = this;
if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);// 向 x 转账 10?
定长字节数组
-
一个字节占两位,字节乘2是位数(16进制)
-
字节数组bytes分两种,一种定长(byte, bytes8, bytes32),另一种不定长。不定长的使用频率低,暂时先略过。
-
定长bytes可以存一些数据,消耗gas比较少。
// 固定长度的字节数组 bytes32 public _byte32 = "MiniSolidity"; bytes1 public _byte = _byte32[0];
-
MiniSolidity 字符串以字节的方式存储进数组_byte32 ,转换成16进制为:
0x4d696e69536f6c69646974790000000000000000000000000000000000000000
-
0x是象征意义,表示十六进制,四位二进制组成一位十六进制
-
一个字节占 8 bit,也就是两位十六进制
-
例如 bytes32 类型有64个16进制位。
-
_byte变量存储_byte32数组的第一个字节,即0x4d (0x是象征意义,代表十六进制)。
// SPDX-License-Identifier: SimPL-2.0
pragma solidity ^0.8.7;
contract SimpleStorage{
// Solidity是一种静态类型语言,这意味着每个变量(状态变量和局部变量)都需要在编译时指定变量的类型
// 基础数据类型 : bool,uint,int,address,bytes
// 布尔类型
bool istrue;
// 整型:有符号和无符号
uint storeData;
int a;
int b;
// 地址类型 address,address payable(可支付地址,与address相同,不过有成员transfer和send)
// 可进行相关的类型转换
address myauont;
address payable youauont;
// 地址类型可以理解为封装了一些常用方法的类。
// 地址类型成员变量 :详细的可以查看文档
// <address>.balance (uint256) 以 Wei 为单位的地址类型 Address 的余额。
// <address>.codehash (bytes32) 地址类型 Address 的codehash
// <address payable>.transfer(uint256 amount)
// 向 地址类型 Address 发送数量为 amount 的 Wei,失败时抛出异常,使用固定(不可调节)的 2300 gas 的矿工费。
// <address payable>.send(uint256 amount) returns (bool)
// 向 地址类型 Address 发送数量为 amount 的 Wei,失败时返回 false,发送 2300 gas 的矿工费用,不可调节。
// 可以使用 balance 属性来查询一个地址的余额, 也可以使用 transfer 函数向一个可支付地址(payable address)发送 以太币Ether (以 wei 为单位)
// if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);
function set(uint x) public {
storeData = x;
}
// view、pure等这些参数是函数的修饰符,view 原名 contant
// 可以将函数声明为view类型,这种情况下要保证不修改状态。
//函数可以声明为 pure ,在这种情况下,承诺不读取也不修改状态变量。
function get() public view returns (uint){
return storeData;
}
function f (uint x) public pure returns (uint) {
return x + 1;
}
// 函数重载
// 还有一些特别的函数:
}
函数
Functions
函数定义
函数 function、函数名、(传入参数)、可见性、gas 节省支付性、returns (传出参数)
函数主体{…}前面的开头内容与属性:
function function name(types name) internal|external|public|private [pure|view|payable] [returns (types name)]{
[return]
}
中括号部分可省略不写,顺序依次为:function、函数名、(传入参数)、可见性、gas 节省支付性、returns (传出参数)
1.function
声明函数时的固定用法,想写函数,就要以function关键字开头。
2.function name
函数名。
3.(types name)
圆括号里写输入到函数的现实变量类型和名字。
4.internal|external|public|private
函数可见性说明符,一共4种。没标明函数类型的,默认internal。
-
public: 内部外部均可见。(也可用于修饰状态变量,public变量会自动生成 getter函数,用于查询数值).
-
private: 只能从本合约内部访问,继承的合约也不能用(也可用于修饰状态变量)。
-
external: 只能从合约外部访问(但是可以用this.函数名()来调用
-
internal: 只能从合约内部访问,继承的合约可以用(也可用于修饰状态变量)。
5.pure|view|payable
决定函数权限/功能的关键字。payable(可支付的)很好理解,带着它的函数,运行的时候可以给合约转入ETH。
6.returns (types name)
函数返回的变量类型和名称。
函数修饰符
有决定函数何时和被谁调用的可见性修饰符,
private(只能被合约内部调用);
internal(就像private,但是它也能被继承的合约调用);
external(只能从合约外部调用);
public(可以在任何地方调用,无论内部还是外部)。
还有状态修饰符,告诉我们函数如何和区块链交互,view(我们运行这个函数不会更改和保存任何数据);pure(我们的函数不但不会往区块链写数据,它甚至不从区块链读取数据)。这两种修饰符在被从合约外部调用的时候都不话费任何gas(但是它们在被内部其他函数调用的时候会耗费gas)
internal 与external
// internal: 内部
function minus() internal {
number = number - 1;
}
// 合约内的函数可以调用内部函数
function minusCall() external {
minus();
}
pure 与 view [gas 节省支付性]
-
pure:不能读取也不能写入链上变量
-
view:只能读取但不能写入链上变量
-
不写 pure 或 view:可读可写
solidity在函数中加入这两个关键字,是为了节省智能合约中特有的gas fee。合约的状态变量存储在链上所需要的gas fee很贵,如果不改变链上状态,就不用付gas。包含pure跟view关键字的函数是不改写链上状态的,因此用户直接调用他们是不需要付gas的(合约中非pure/view函数调用它们则会改写链上状态,需要付gas)
contract SimpleStorage {
uint256 public favoriteNumber;
function retrieve() public view returns (uint256){
favoriteNumber = favoriteNumber+1; ×
return favoriteNumber;
}
}
这里就错误了,因为是view修饰的函数,因此这个函数中就不能更改和保存合约里的数据,view 是个纯函数,表示视图,不需要花费gas,因为我们不是修改状态,也就是其按钮是蓝色的原因。只call但不发送交易。
function retrieve() public pure returns (uint256){
return favoriteNumber; ×
return 7;
}
这里就错误了,它甚至不从区块链读取数据 ,从存储的地方获取。
在以太坊中,以下语句被视为修改链上状态:
- 写入状态变量。
- 释放事件。
- 创建其他合同。
- 使用
selfdestruct
. - 通过调用发送以太币。
- 调用任何未标记
view
或pure
的函数。 - 使用低级调用(low-level calls)。
- 使用包含某些操作码的内联汇编。
如上图所示,公主代表合约中的状态变量(存储在链上),上层为公主,下层人物从左往右对公主的权限依次提高,分别为纯纯牛马、看客、默认 boss
- pure,中文意思是“纯”,在solidity里理解为“纯纯牛马”。包含pure关键字的函数,不能读取也不能写入存储在链上的状态变量。就像小怪一样,看不到也摸不到公主。
- view,“看”,在solidity里理解为“看客”。包含view关键字的函数,能读取但也不能写入状态变量。类似马里奥,能看到碧池,但终究是看客,不能入洞房。
- 不写pure也不写view,函数既可以读取也可以写入状态变量。类似马里奥里的默认boss,可以对公主为所欲为…
例如我们在合约里定义一个状态变量 number = 5
。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
contract FunctionTypes{
uint256 public number = 5;
定义一个add()
函数,每次调用,每次给number + 1
。
// 默认
function add() external{
number = number + 1;
}
如果add()包含了pure关键字,例如 function add() pure external,就会报错。因为pure(纯纯牛马)是不配读取合约里的状态变量的,更不配改写。
那pure函数能做些什么?举个例子,你可以给函数传递一个参数 _number,然后让他返回 _number+1。
// pure: 纯纯牛马
function addPure(uint256 _number) external pure returns(uint256 new_number){
new_number = _number+1;
}
如果add()
包含view
,比如function add() view external
,也会报错。因为view
能读取,但不能够改写状态变量。可以稍微改写下方程,让他不改写number
,而是返回一个新的变量。
// view: 看客
function addView() external view returns(uint256 new_number) {
new_number = number + 1;
}
上述+1的函数例子中,pure 需要在函数定义时传入需要的参数, view 则不需要,可以直接读取已有的参数。不过两者都不能改变链上已有的参数。
payable [gas 节省支付性]
// payable: 递钱,能给合约支付eth的函数
function minusPayable() external payable returns(uint256 balance) {
minus(); //-1
balance = address(this).balance;
}
我们定义一个external payable
的minusPayable()
函数,间接的调用minus()
,并且返回合约里的ETH
余额(this
关键字可以让我们引用当前合约的地址)。 我们可以在调用minusPayable()
时,往合约里转入1个ETH
我们可以在返回的信息中看到,合约的余额是1 ETH
函数输出
主要还是用命名式返回,直接 returns(参数类型 参数名)
Solidity
有两个关键字与函数输出相关:return
和returns
。
Solidity
函数输出主要包括:类型返回、命名式返回、解构式赋值。
返回值 return和returns
他们的区别在于:
-
returns
加在函数名后面,用于声明返回的变量类型及变量名; -
return
用于函数主体中,返回指定的变量。
类型返回
// 类型返回
function returnMultiple() public pure returns(uint256, bool, uint256[3] memory){
return(1, true, [uint256(1),2,5]);
}
上面这段代码中,我们声明了returnMultiple()
函数将有多个输出:returns(uint256, bool, uint256[3] memory)
,接着我们在函数主体中用return(1, true, [uint256(1),2,5])
确定了返回值。
命名式返回
我们可以在returns
中标明返回变量的名称,这样solidity
会自动给这些变量初始化,并且自动返回这些函数的值,不需要加return
。
// 命名式返回
function returnNamed() public pure returns(uint256 _number, bool _bool, uint256[3] memory _array){
_number = 2;
_bool = false;
_array = [uint256(3),2,1];
}
在上面的代码中,我们用returns(uint256 _number, bool _bool, uint256[3] memory _array)声明了返回变量类型以及变量名。这样,我们在主体中只需要给变量_number,_bool和_array赋值就可以自动返回了。
当然,你也可以在命名式返回中用return来返回变量:
// 命名式返回,依然支持return
function returnNamed2() public pure returns(uint256 _number, bool _bool, uint256[3] memory _array){
return(1, true, [uint256(1),2,5]);
}
解析式赋值
solidity
使用解构式赋值的规则,支持读取函数的全部或部分返回值。
- 读取所有返回值:声明变量,并且将要赋值的变量用
,
隔开,按顺序排列
uint256 _number;
bool _bool;
uint256[3] memory _array;
(_number, _bool, _array) = returnNamed();
-
读取部分返回值:声明要读取的返回值对应的变量,不读取的留空。下面这段代码中,我们只读取
_bool
,而不读取返回的_number
和_array
:``` solidity (, _bool2, ) = returnNamed(); ```
数组和结构体
Arrays &Structs
数组
//数组和其他语言中的一样
uint256[] listOfFavoriteNumbers ; // [77,78,90] 与其他编程语言数组一样
结构体
// 定义结构体,相当于构建自己的类型,类似于类
struct Person{
string name;
uint256 favoriteNumber;
}
Person[] person;
直接赋值
Person public pat = Person("pat",7);
或者按属性名赋值
Person public pat = Person({name:"pat",favoriteNumber:7});
数组的添加,跟其他语言数组的操作一样
// 数组的添加
function addPerson(string memory _name,uint256 _favoriteNumber) public {
Person memory newPerson = Person(_name,_favoriteNumber);
listOfPeople.push(newPerson);
listOfPeople.push(Person(_name,_favoriteNumber));
}
// 删除人 ,是从数组最后开始删除
function deletePerson() public {
listOfPeople.pop();
}
错误和警告
Errors & Warnings
遇到Errors & Warnings,一些搜索平台
- AI Frens
- ChatGPT
- Just know that it will often get things wrong, but it's very fast!
- Phind
- Like ChatGPT, but it searches the web
- Bard
- Other AI extensions
- ChatGPT
- Github Discussions
- Ask questions and chat about the course here!
- Stack Exchange Ethereum
- Great place for asking technical questions about Ethereum
- Peeranha
- Decentralized Stack Exchange!
变量存储属性
5. 变量存储属性(storage/memory/calldata) 与作用域分类(状态变量、局部变量、全局变量)_海阔平的博客-CSDN博客
Memory,Storage,Calldata(Intro)
6个地方存储数据
EVM can access and store information in six places:
- Stack
- Memory
- Storage
- Calldata
- Code
- Logs
solidity中 calldata、memory、storage
目前,引用类型包括struct(结构体)、array(数组)和mapping(映射),使用引用类型必须明确地提供存储该类型的数据位置:
memory(生存期存在于function(函数)内,超过作用域即失效);
storage(生存期同contract(合约)一致,状态变量强制为storage);
calldata(不可修改、非持久的函数参数存储区域,用于存储函数参数,只读,不会永久存储一个数据位置,external function(外部函数)的传入参数(不包括返回参数)强制为calldata,效果类似memory);
变量存储属性(storage/memory/calldata) 与作用域分类(状态变量、局部变量、全局变量)
Solidity中的引用类型
引用类型(Reference Type):包括数组(array),结构体(struct)和映射(mapping),这类变量占空间大,赋值时候直接传递地址(类似指针)。由于这类变量比较复杂,占用存储空间大,我们在使用时必须要声明数据存储的位置。
数据存储位置
-
storage 是默认属性,存在链上消耗gas多
-
memory和calldata存在内存消耗gas少
-
memory 的变量可修改
-
calldata的变量不可修改
-
solidity数据存储位置有三类:storage,memory和calldata。不同存储位置的gas成本不同。storage类型的数据存在链上,类似计算机的硬盘,消耗gas多;memory和calldata类型的临时存在内存里,消耗gas少。大致用法:
storage:默认属性,合约里的状态变量默认都是storage,存储在链上。
memory:函数里的参数和临时变量一般用memory,存储在内存中,不上链。
calldata:和memory类似,存储在内存中,不上链。与memory的不同点在于calldata变量不能修改(immutable),一般用于函数的参数。
memory 暂时存在,临时变量
// memory
function addPerson(string memory _name,uint256 _favoriteNumber) public {
_name = "cat"; //正确的,可以更改
listOfPeople.push(Person(_name,_favoriteNumber));
}
calldata 不能修改它
function addPerson(string calldata _name,uint256 _favoriteNumber) public {
_name = "cat";
listOfPeople.push(Person(_name,_favoriteNumber));
}
TypeError: Type literal_string "cat" is not implicitly convertible to expected type string calldata.
--> Simple.sol:43:16:
|
43 | _name = "cat";
| ^^^^^
映射
Mappings
类似于字典
//定义mapping
mapping(string => uint256) public nameToNumber;
function addPerson(string memory _name,uint256 _favoriteNumber) public {
listOfPeople.push(Person(_name,_favoriteNumber));
nameToNumber[_name] = _favoriteNumber; //类似于字典
}
测试部署
测试部署合约到测试网Sepolia
连接MetaMask:
用网页版,不要用本地编译环境。
EVM
类似于JVM之于Java
EVM实际上是一个标准:如何编译和如何部署智能合约到区块链。
标签:function,函数,uint256,solidity,智能,bool,address,合约,public From: https://www.cnblogs.com/moshanghuai/p/17731926.html