半山腰总是最挤的 你得去山顶看看
目录
一、Solidity的条件语句
Solidity支持条件语句,让程序可以根据条件执行不同的操作。条件语句包括:
- if
- if...else
- if...else if....
1.if语句
if (条件表达式) { 被执行语句(如果条件为真) }
2.if....else语句
if (条件表达式) { 被执行语句(如果条件为真) } else { 被执行语句(如果条件为假) }
3.if....else if 语句
if (条件表达式 1) { 被执行语句(如果条件 1 为真) } else if (条件表达式 2) { 被执行语句(如果条件 2 为真) } else if (条件表达式 3) { 被执行语句(如果条件 3 为真) } else { 被执行语句(如果所有条件为假) }
温馨提示:solidity里面只有if分支语句,没有switch分支语句
二、 Solidity的循环语句
与其他语言类似,Solidity语言支持循环结构,Solidity提供以下循环语句。
- while
- do ... while
- for
- 循环控制语句:break、continue。
1.while循环语句
while (表达式) { // 如果表达式的结果为真,就循环执行以下语句 ...... }
2.do...while循环语句
do { // 如果表达式的结果为真,就循环执行以下语句 ...... } while (表达式);
3.for循环语句
for (初始化; 测试条件; 迭代语句) { // 如果表达式的结果为真,就循环执行以下语句 ...... }
4.控制语句
Solidity 循环语句支持 continue 和 break,用来改变循环流程。
- continue – 跳出本次循环,继续执行下一次循环
- break – 跳出循环,不再执行当前循环
三、Solidity的函数
函数是一组可重用代码的包装,接受输入,返回输出。
Solidity 支持函数定义和调用。
在solidity中函数是一种类型,是值类型
1.Solidity函数的基本使用
关键字是:
function
函数定义格式:
function 函数名 (入参) 函数修饰符 returns(出参){
函数体
}
2.Solidity函数的作用
与区块链交互的手段、封装、复用
3.Solidity函数的参数
参数可以有多个
调用函数传递的参数是实参,进入函数里面的是形参,也叫入参,returns后面的参数出参
为了区分参数,通常把入参名前面加个_
示例:
uint unm=1;
function setValue (uint _num , uint _num2) public returns(uint,uint){
unm=_unm;
return (num,_num2)
}
4.Solidity函数的调用
- remix调用:合约部署后直接在remix调用 方法: remix中点击对应的函数名即可,有参数传参数
- 合约内代码调用(通常是在函数内调用) 格式 : 函数名(参数列表)
5.函数命名参数
函数调用可以使用命名参数。将参数名和参数值这样的键值对以任意顺序放进json表达式即可,或按顺序录入
好处:可以指定为某个参数赋值 ,不用按照参数原本的排序
格式:
函数名( { 该函数的形参:值 , 该函数的形参:值 ,······ } )
uint unm=1;
string test ;
function setValue (uint _num , string memory _test) public {
unm=_unm;
test =_test;
}
function testSet() public {
setValue(3,”www”); //这样一定要按顺序
setValue( { _test:”aaaa”,_num:3 } ); //这样可以颠倒顺序
}
6.函数的权限修饰符
- external外部的
- public 公开的
- internal 内部的
- private 私密的(封装)
external函数
外部函数是合约接口的一部分,仅外部访问(在内部也必须用外部访问的方式访问)
一个外部函数f不能通过内部的方式来发起调用,只能以外部形式:this.f()来调用。需要注意的是solidity的this和其他语言不一样
外部函数在接收大的数组数据时更加有效
public函数
public修饰的函数既允许以internal 的方式调用,也允许以external的方式调用
public 修饰的函数,任何账户或者合约都能调用和访问
public 的函数由于被外部合约访问,也属于是合约对外接口的一部分
internal函数
仅当前合约及所继承的合约,只允许以内部的方式调用,子合约也能调用
private函数
private修饰函数,只能在其所在的合约中调用和访问,即使作为父,子合约也无权访问
即使声明为private ,仍能被任何人查看到里面的数据,访问权限只是阻止了其他合约访问函数或者修改数据
7.函数的类型修饰符
- pure
- view
温馨提示:想要修改状态,则不加类型修饰符,即可
支付类型修饰符:
- payable
0.5.0之后淘汰的一种类型函数
constant(新版本淘汰,旧版本才有) 了解一下
声明为constant修饰的函数和view是等价的,constant实际上是view的别名
在solidity 0.5.0之后被弃用
pure类型函数
函数声明为pure,表示该函数既不能读取状态,也不能修改状态
使用pure修饰的函数也被称之为纯函数
执行pure函数,不会消耗gas
如果函数中存在以下语句,则被视为读取状态,编译器将抛出警告
- 读取状态变量
- 访问address(this).balance或address.balance
- 访问任何区块、交易、msg等特殊变量(msg.sig与msg.data允许读取)
- 调用任何未标记为
pure
的函数- 使用包含特点操作码的内联程序集
view类型函数
函数声明为view,表示该函数只能读取状态,不能修改状态
使用view修饰的函数也被称之为视图函数
执行view函数,不会消耗gas
如果函数中存在以下语句,则被认为修改了状态,编译器将抛出异常
- 修改状态变量
- 触发事件
- 创建合约
- 使用selfdestruct
- 发送以太(实行了转账)
- 调用任何不是view函数和pure修饰的函数
- 使用底层调用
- 使用包含某些操作码的内联程序集
payable类型函数
函数声明为payable,则该函数是一种可以接收以太币的特殊函数,该以太币放入该合约账户
当一个函数被payable修饰,表示调用这个函数,需要附加一些ETH,当然也可以不发(可以采取某些手段强制发)
如果接收的以太币多了,那么它将会保存在合约账户中
温馨提示:没有加payable修饰的函数,你去发送ETH给它,则调用会出错
8.函数的返回值
关键字:
returns
格式:
returns (出参列表)
通过returns (出参列表) 关键字给函数,即可让函数返回某些值,出参用于写返回值的类型和个数
2种格式
第一种:规定类型方式
function output1 (uint a , uint b) public pure returns (int ,int )
{
return (a+b , a-b);
}
第二种:声明变量形式
function output2 (uint a , uint b) public pure returns (uint r )
{
r = a+b;
}
推荐使用第一种
9.接收(保存)函数的返回值
通常定义变量等一些存储单位来存储函数的返回值以便后期使用
例如变量
uint a=0
uint b=0
单一
a=output1(1,2)
b=output2(1,2)
多个
(a,b)=output3(3,5)
(a,b)=output4(3,5)
多个返回值,只接收部分返回值
使用留空的方法即可
( ,b)=output4(3,5)
10.函数重载
重载的定义:
函数名相同,参数不同
参数不同可以是:
- 不同类型
- 不同个数
温馨提示:函数名相同,参数不同。才是重载的判断依据,而返回值类型不作为重载的判断依据
在外部接口中也存在重载的函数,如果两个外部可见的函数的solidity类型不同,但外部类型相同,则无法重载成功
contract Test { function getSum(uint a, uint b) public pure returns(uint){ return a + b; } function getSum(uint a, uint b, uint c) public pure returns(uint){ return a + b + c; } function callSumWithTwoArguments() public pure returns(uint){ return getSum(1,2); } function callSumWithThreeArguments() public pure returns(uint){ return getSum(1,2,3); } }
11.Solidity的系统函数-数学函数
Solidity 也提供了内置的数学函数。下面是常用的数学函数:
addmod(uint x, uint y, uint k) returns (uint)
计算(x + y) % k,计算中,以任意精度执行加法,且不限于2^256大小。mulmod(uint x, uint y, uint k) returns (uint)
计算(x * y) % k,计算中,以任意精度执行乘法,且不限于2^256大小。
下面的例子说明了数学函数的用法。
contract Test { function callAddMod() public pure returns(uint){ return addmod(4, 5, 3); } function callMulMod() public pure returns(uint){ return mulmod(4, 5, 3); } }运行上述程序:
首先单击
callAddMod
按钮,然后单击callMulMod
按钮查看结果。结果:
0: uint256: 0 0: uint256: 2
12.Solidity的系统函数-加密函数
Solidity提供了内置的加密函数,下面常用的加密函数
keccak256(bytes memory) returns(bytes32) 计算输入的Keccak-256散列
使用以太坊的( Keccak-256 ) 计算HASH值,紧密打包
Keccak256的别名函数sha3(...) returns(bytes32)
sha256(bytes memory) returns(bytes32) 计算输入的sha-256散列
使用 SHA-256计算HASH值,紧密打包
ripemd160(bytes memory) returns(bytes20) 计算输入的RIPEMD-160散列
使用 RIPEMD-160计算HASH值,紧密打包
ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)
从椭圆曲线签名中恢复与公钥相关的地址,或在出错时返回零。函数参数对应于签名的ECDSA值: r – 签名的前32字节; s: 签名的第二个32字节; v: 签名的最后一个字节。这个方法返回一个地址。
下面的例子说明了加密keccak256函数的用法。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Test {
function callKeccak256() public pure returns(bytes32 result){
return keccak256(abi.encodePacked("ABC"));
//注意要加abi.encodePacked()
}
}
运行上述程序,输出:
0:bytes32: result 0xe1629b9dda060bb30c7908346f6af189c16773fa148d3366701fbaa35d54f3c8
温馨提示:可以利用加密函数对字符串进行加密然后判断是否相等
四、函数进阶--函数修改器(函数修改符)
函数修改器,可以用来轻易改变一个函数的行为,控制函数的逻辑,比如常用于在函数执行前检查某种前置条件
我们可以将一些通用的操作提取出来,包装为函数修改器,来提高代码的复用性,改善编码效率。
修改器是一种合约属性,可以被继承,同时还可以被派生的合约重写override
但前提是它们被标记为
virtual
修改器的关键字:
modifier
定义格式:
modifier 函数修改器名 (参数) {
代码1 //修饰的函数执行前的代码
_; //表示被修饰的函数中的代码
代码2 //修饰的函数执行后的代码
}
使用格式
在函数修饰区写入 修改器函数名()
执行过程:
当一个函数被修改器修饰后,那么先执行修改器里面的代码段,直到遇见_;就开始执行下一个修改器,以此类推,当最后一个修改器执行到了_;就执行函数内的代码。函数内代码执行完再返回去执行剩余的代码。直到修改器内代码全部执行完毕
对于一个函数可以有多个修改器限制,在函数定义的时候依次写上,并加空格分隔,执行的时候也是依次执行。多个修改器是同时限制,也就是说必须满足所有修改器的权限,才可以执行函数体的代码.
1. _ 的作用
函数修改器中有一行代码只有下划线 _ ; 我们认为下划线 _ 代表了被修饰的函数代码。
也就是说,下划线实际上帮我们标记了被 modifier 修饰函数的执行位置。
2. 基本的函数修改器
-
//SPDX-License-Identifier: UNLICENSED
-
pragma solidity ^0.8.0;
-
contract test {
-
uint a = 3;
-
modifier requires () {
-
// 检查前置条件:判断a是否大于2,如果a大于2,那么执行get函数返回a。
-
require(a>2);
-
_;
-
}
-
function get() public view requires returns(uint){
-
return a;
-
}
-
}
温馨提示:无参的修改器在作为修饰词的时候可以不加()
3. 带参数的函数修改器
-
//SPDX-License-Identifier: UNLICENSED
-
pragma solidity ^0.8.0;
-
contract test {
-
uint public a = 3;
-
modifier requires (uint _x) {
-
require( a >_x );//前置检查a是否大于_x
-
_; // 执行get()函数
-
a = _x; //修改a的值
-
}
-
function get(uint _x) public requires(_x) returns(uint){
-
//修改器接收get函数接收的参数_x
-
return a;
-
}
-
}
代码分析
首先给get函数传入参数为2,requires修改器接收传入get函数的参数并传递给修改器函数内部
判断a 是否 大于 _x ,如果大于则执行get函数,不大于则给报错信息
若执行get函数,则返回值a= 3
接着继续执行修改器函数第三行代码
a = _x
a的值被修改为_x
查看a的值为2
4.修改器和函数里面的return
在修改器中或函数内的显式的return语句,仅仅跳出当前的修改器或函数。若被修饰的函数本身具有return 返回值,则该return将再最后执行
-
// SPDX-License-Identifier: MIT
-
pragma solidity ^ 0.8.0;
-
contract infoContract {
-
uint a = 10;
-
uint c;
-
modifier mf(uint _x) {
-
a=_x;
-
_;
-
return;
-
c=9;//不执行
-
}
-
function test(uint _x) public mf(_x) returns(uint,uint){
-
a = 5;
-
return(a,c);
-
c=2;
-
}
-
}
5.多个修改器同时修饰一个函数
对于一个函数可以有多个修改器限制,在函数定义的时候依次写上,并加空格分隔,执行的时候也是依次执行。多个修改器是同时限制,也就是说必须满足所有修改器的权限,才可以执行函数体的代码
-
//SPDX-License-Identifier: UNLICENSED
-
pragma solidity ^0.8.0;
-
contract test {
-
uint a = 10;
-
modifier mf1 (uint _x) {
-
uint c = _x;
-
_;
-
c = a;
-
a = 11;
-
}
-
modifier mf2 () {
-
uint c = a;
-
_;
-
}
-
modifier mf3() {
-
a = 12;
-
return ;
-
_;
-
a = 13;
-
}
-
function test1(uint _x) mf1(_x) mf2 mf3 public {
-
a = 1;
-
}
-
function test2() public view returns (uint) {
-
return a;
-
}
-
}
整个执行可看成
uint c = _x
uint c = a
a=12
return
_;
a=13
c=a
a=11
6.复杂示例
我们来看一个函数修改器经典的应用 OpenZeppelin 库中的 Ownable 合约,下面是其中关键的代码:
-
/// Ownable 可以判断合约的调用者是否为当前合约的owner,
-
/// 从而避免其他人随意的调用一些合约的关键操作。
-
/// 同时,owner 可以指定任何其他人为此合约新的 owner,
-
/// 显然,只有当前owner才能指定其他人为新的owner。
-
contract Ownable {
-
// 变量 owner 指定此合约的owner
-
address public owner;
-
// 发布事件 - 此合约owner已经换人(此逻辑与modifier无关,可以忽略)
-
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
-
// 构造函数 - 创建合约自动执行,初始化合约所有人为合约创建者
-
function Ownable() public {
-
owner = msg.sender;
-
}
-
// 定义一个函数修改器
-
modifier onlyOwner() {
-
// 判断此函数调用者是否为owner
-
require(msg.sender == owner);
-
_;
-
}
-
// owner可以用此函数将owner所有权转换给其他人,显然次函数只有owner才能调用
-
// 函数末尾加上onlyOwner声明,onlyOwner正是上面定义的modifier
-
function transferOwnership(address newOwner) public onlyOwner {
-
require(newOwner != address(0));
-
OwnershipTransferred(owner, newOwner);
-
owner = newOwner;
-
}
-
}
上述合约的 transferOwnership 函数用于 owner 将所有权转让给其他人,于是在末尾声明 onlyOwner 修改器,onlyOwner 将在 transferOwnership 执行前,先执行
require(msg.sender == owner);以保证此函数的调用者为 owner ,如果不是 owner 则抛出异常。
五、函数进阶--函数构造器
函数构造器也叫构造函数,是一个特殊函数,它仅能在智能合约部署的时候自动调用一次,之后就不能再次被调用。通常用来完成合约的状态变量初始化工作
如果没有实现构造函数,那合约会添加一个默认的构造函数 construct () { } 啥也没有,什么也没发生
当创建一个合约时,它的构造函数被执行一次,构造函数是可选的(可有可无)。且只允许一个构造函数。这意味着构造函数不能重载
关键字:
constructor
定义格式:
constructor (参数列表) {
构造函数体
}
1.不带参数的构造函数
-
//SPDX-License-Identifier: UNLICENSED
-
pragma solidity ^0.8.0;
-
contract test {
-
uint a;
-
constructor () {
-
a=100;//合约创建后即调用构造方法,a=100
-
}
-
}
//合约创建后即调用构造方法,a=100
给a进行初始化赋值
缺点:
写死了,固定赋值100
2.带参数的构造函数
-
//SPDX-License-Identifier: UNLICENSED
-
pragma solidity ^0.8.0;
-
contract test {
-
uint a;
-
constructor (uint _a) {
-
a=_a;//将构造函数的参数传递给a
-
}
-
}
编辑器部署合约时,如果是有参构造,那么会在合约部署之前给一个框框用于输入构造函数的参数,当合于创建后,参数会给到构造函数,然后执行构造函数
合约创建后即调用构造方法 a=_a;
给a进行初始化赋值
改善了写死,可以在部署前灵活赋值
3.构造函数通常的使用思想
-
// SPDX-License-Identifier: MIT
-
pragma solidity ^0.8.0;
-
contract Test {
-
int public a ;
-
address public owner;
-
constructor(uint _a) public{
-
// 将部署者地址存储到owner变量
-
owner = msg.sender;
-
// 将参数_a存储到a变量
-
a = _a;
-
}
-
}
a初始化了合约部署前的值
owner初始化了 合约创建者的地址
区块链开发语言智能合约
来自专栏
智能合约
尽欢i 14篇文章 12人订阅
AI必读
发布于2023-05-18著作权归作者所有