00:00
好,那接下来我们就继续来讲函数的其他的一些跟类型相关的一些限定它的一些修饰符。接下来我们要讲的这一部分叫做函数的状态可变性。这一部分跟之前函数的可见性,他们经常就放在一起,然后我们就都都觉得这个就叫函数类型,对吧,但其实这是完全不一样的东西。前面的那一部分,Public external external,还有private,这些是函数在哪里可见,在哪里可以调用它们,那它们分别又会返回一个对应的就是函数的值类型,那就是我们所说的内部函数还是外部函数。那关于它的状态可变性呢,指的是另外一回事,不是说他看不看到的问题,而是说在调用它的时候会发生什么事情。首先我们就看到最常见的。
01:02
呃,最常我我们最常见的可能是payable是吧,最先接触到的应该是payable payable是一个状态可变性,是表示允许从消息调用中接收以太币,这个大家都已经知道了,我们的水龙头合约如果要想去接收以太币的话,就必须加一个de payable类型的回回退函数对吧?那刚才我们在那个就是那个蜜蜜罐合约里边,我们在做尝试的时候也会发现,一开始如果想要给他发一个币的话,必须是给他指定一个payable类型的constructor。构造函数必须要有,所以大家要注意就是一旦我们考虑到想要接收以太币的时候,别人想去给他转以太的时候,就在合约里边,一般就是说如果想给合约转语态,那我们一般就是要在constructor里边。
02:02
或者是在回退函数里面。它它俩的区别在什么地方呢?回退函数是表示我们平常如果发起一个交易。就是转币没有任何的数据,就是对着一个地址转币,这个地址给定合约地址,我们要想这样能给他转过去的话,他就一定要有一个payable类型的。回退函数。这是我们说到的这个。就是转币的回跟回退函数有关系的地方,那什么时候要求它的constructor是payable呢?是我们在部署合约的时候。部署的同时就要就想要给他转一笔钱,这个时候是需要有一个就是constructor是payable类型才可以,好这是payable类型啊,那平常的话,我们基本上就是说,除了它的特两个特殊的函数,一个constructor,一个回退函数,别的都是就我们自己定义的那些普通的方式了,对吧,那我们就是如果调用这个方法的时候,同时我们还想给这个合约转币,那这个方法就一定要是payable类型的。
03:20
比如我们前面的。前面的那个蜜罐函数家还记,呃,蜜罐合约大家还记得吗?就是这个guess方法,它后面为什么要有一个payable呢?不是说他因为要给我们发币,所以他得payable。Here说的是可支付的,对吧,那是他自己是可支付的。所以它定义成payable的意思是要让我们给它打过态去,就是我们要带着messages value过去,所以它必须得是pay,是这一点,当然了就是,呃,前面大家看到我在调的时候总在报一个pay错误,其实那个我又看了一下啊,应该是。
04:06
正常来讲,我们自己的账户,一个外部账户肯定是配到我的账户账户对吧,他肯定是可以接收以太的嘛,但为什么会报错呢?那个时候应该就是有些同学发现的,它里边的钱不够了,就是我们一开始给定义的。和我们转过去的币可能比,我们想要提那一次提出来的时候让他给我们转两倍,可能他不够了,遇到这种情况的时候他会报错,所以呃,就是大家注意一下这个payable的用法,呃,另外几个我们大家也经常见,就是简单的说一句就可以,一个view view是表示不允许修改状态,View意思是试图对吧,就是看见就是,所以它的意思就是说只能看不能写。只可读,不可写,不允许修改,修改我们的状态变量。与之对应的还有一个更加严苛的函数类型,就是我们所说的Q纯函数。
05:09
那纯函数指的就是说你不光不可以写状态,甚至连读都不能读,简单说就是这个函数跟我们前面定义的状态没关系。他只是做一些做一些什么事呢?那可能就是说加加减减这样的,只做一些纯函数的计算,我们给给就是传入两个参数,一个UNA,一个unit b,返回一个A加B,那这种这就是标准的纯函数,它的。呃,大家知道纯函数的定义对吧?纯函数的定义是什么呢?就是说我给定同样的输入,它永远给我同就是同样的输出。这样的函数就是纯函数,跟系统状态无关的函数,这就是纯函数,那我们如果要是一个get的话,我们get一个当前的系统,系统的这个呃,状态变量。
06:03
叫贝塔的一个变量,那我们每一次即使传进去相同的参数,它有可能返回的状态就不一样了,对吧?所以这样就不是纯函数,这是纯函数的概念啊。呃,我们经常见的是前面三个,最后还有一个叫constant,就是平常我们定义常量的那个关键词,也可以定义函数是constant。它的含义呢,就是一般来说它不用来修饰这个函数啊,但是也可以,它如果修饰函数的话,就是跟VIVO一样。就大家可以认为constant就是view不允许修改,好,呃呃,这里多说了一句,就是constant也可以用来修饰状态变量,那就相当于就是我们认为的常量了,就是不允许赋值,初始化之后再也不允许赋值了,只能读不能写。这些都比较简单,我们过了就完了,呃,这个就是大家列在这里,大家注意一下就可以,什么情况下叫做修改呢?叫做写呢?首先写入状态变量,修改状态变量,这肯定是写。
07:07
然后是如果里边我们用到了it一个event的时候,这个系统会认为它是修改的状态,为什么呢?因为大家知道,呃,整个事件的这个机制是跟DM的lo机制有关的,日志机制有关的,所以说他一定要写日制,所以只要触发的事件,这一定就是修改状态。呃,然后里边如果要创建了其他合约,那是修改的状态,因为他知道他创建合约的时候,会把这个就是合约创建在EVM上面对吧。还有就是如果他使用了disrut自毁,用了这个这个函数里边调用了self disrut,这也是修改状态,那是肯定的,把自己都毁了嘛。还有就是说,如果通过调用发送了以太币。就是如果他调用了什么什么点transfer,什么什么点散,这种都叫做改修改状态,后面还有就是说,其实就是你调用任何没有被标记成view或者是Q的函数。
08:15
都被认为是修改状态,所以这也就是为什么remix里边我们即使是一个。感觉好像没有修改什么东西的一个函数,假如说我们把这个去掉啊。呃,这个这个当然是帕啊。即使是我们这样的一个。一个函数,大家也会发现,可这是C啊。部署一下一。即使是这样的一个函数,在remix里边也认为它是红色的,红色代表什么,就是它要修改状态的,对吧?嗯,对,即使我们看这里面什么都没做,他没有修改状态啊,但是整个编译器和E都会认为,不光是remix这么认为,就是整个以太坊系统就认为你如果不加view的话,那它就是一个要修改。
09:12
状态的一个方法来好,又点错了。这个时候我们就看到它下面的这个就是一个只读的一个状态,对吧?好,呃,最后就是还有低级调用,如果我们用了前面讲过的低级调用,靠。Static call delegate call,如果用了这些调用方法的话,都被认为修改的状态,最后就是包含特定操作码的内联汇编,这个就更复杂了,大家就是只要知道就可以了,我们一般不会用到内联汇编的,这是状态的修改,那同样就还有读取了。什么叫做读取状态呢?它稍微少一点,就这么几种啊。首先读取状态变量,我们只要访问的状态变量,用到的状态变量,这就是读取了。
10:02
还有就是用到了this balance或者是address.balance访问了balance,也叫访问状态。第三点,如果访问了block或者是transaction,或者是message里面的任意成员。除了message.c和message。也就是说,如果我们访问了messenger send mesger value,这些都属于访问了状态。状态读取的状态啊,最后呢,还有一条就是你只要没有把它标记成Q,只要不是纯函数,那默认它都是读取了的。最后就也是你如果要是内联汇编的话,那肯定是读取了,好,这是状态可变性了,呃,接下来我们还是就是多花一点时间说一下这个函数修饰器吧。有些地方可能会把这个它的英文叫mod fire,有些地方可能会把它叫函数修改符,或者说函数修饰符之类的东西,大家看到就是只要它是mod fire,那就是说的是同一同一件事情啊,修饰器mod fire呢,它是用来改变函数行为的,什么意思呢?
11:16
它一般用法就是说添加到某个函数的上面,把一个固定的操作添加到某一个函数上面。一般用来怎么用呢?就是在执行某个函数之前,我们先去检查一下某个条件,假如说我们很多个函数都要执行同样的条件检查的话,那我们就不如不如把这些条件全筛,就是提取出来放在一起,对吧?然后S里边特意给了我们就是把条件都筛出来放在一起的这样一个前置条件的选择器,这个东西就叫做。函数修改修饰函数修饰器。Mofi,那mofi其实最常见的用途是把它的前置条件拿出来啊,但事实上它其实你不光是前置条件可以,它甚至插在任何的地方都可以,它变成后就是后置的一些处理也是可以的。
12:14
呃,我们看一下就是,而且就是monfi,它是可继承属性,它可以被自己的子合约来继承,而且可以被自己的子合约覆盖,就是派生出来的合约是可以覆盖复写它的。呃,另外就是说,如果一个函数是可以有多个multi fire可以有一个,也也可以有好几个,如果有的话,以空格隔开。然后就会按照它的顺序定义的顺序依次执行。说了这么多,这个定义到底是个什么东西呢?上上代码吧。就有代码,大家一看就就清楚啊好看一下这个合约contract purchase purchase应该是购买对吧,购买的意思,所以这就是一个购买的合约,那当然了,要购买的就有卖家,有买家,这里就首先定义了一个卖家啊,Address PU,一个sell。
13:08
然后大家就看到下面有一个mod fire关键字,Mod fire写在这里。后面呢,大家看说mofi是函数修饰符,函数修饰器,其实它跟函数的定义是一样的,对吧,后边也定义了一个名称,然后有括号,也就是说它其实还可以有参数的。然后后面就跟函数一样画括号括起来,然后里边写的是require。Ms messger standard等于standard,然后后面大家看啊,这是一个require的一个写法,就是前面我们跟大家课间的时候提到过一句,就是前面是我们的判定的一个,呃,一个表达式判定它是否为真,Require是要求它为真,如果不为的话。会。
14:00
抛出异常,然后回退,这个时候它会返回一个消息,就是我们定义的第二个。第二个参数,一般我们就定义一个自己比较方便理解的一个错误信息就可以,所以这里说only seven can call,就是只有卖家可以靠这个函数。那下面大家看到有一个下划线,这是个什么东西呢。这可以理解成是一个占位符,它的意思就是说,如果用我当修饰器的函数。把他自己的函数题,他他就要把我的内容就要放进去了,我就把它修饰了,那么他自己原本的那些函数题里边的命令,那些代码到哪里去执行呢?到我的。这个占位符这里来执行。好,那下面就是一个调用,我们看一下啊,Function about,然后public view,这是一个只读的一个函数,后面直接跟only。
15:06
这就是用到了一个函数修饰器。那它的代码会怎么样呢?它就会直接在下面直接先执行require mes send等于cell。然后接下来他自己在下面写的代码会在占位符这里就是在他后面去执行,这就是函数形式器。好,我们在这里写一下这个试一试啊,来感觉一下这个东西到底怎么用。好,这个就又删了。呃,我们就照着他这个定义好的,这个来吧,是吧。呃,首先它应该是定义了什么东西啊,它有一个cell,然后定义了一个修饰器叫叫OS对吧?Address,然后下面是一个风神,是风神啊mod。
16:12
然后only sell,好,接下来我们看它这里用到了一个。MESSAGES3恒等于然后给一个报错信息对吧?Only我们就简单写吧,就这样了对吧?好,接下来我们看它的调用。哎,这里是怎么了phone?哦,我们总部写了一个messger sell,应该是直接用它自动补全了,所以就有了问题啊,诶大家看啊。
17:06
它会提示一个错误。Modify body does not contain,下划线就没有包含下划线。所以他就说了,你的这一个函数修饰器里面必须得有下划线,因为要不然我怎么知道你的代码要插到什么位置呢,对吧,你的站位符都没有,所以大家注意啊,这里一个下划线就可以了。这就是表示前面的这一部分代码,先执行那被它修饰的函数的代码,在后面去执行战略符,这里去执行好,接下来我们看定义了一个function。我们就随便定义一个函数吧,这个不用太在意。它后面我们可以直接加上only,这个就是七对吧,好。在里面,比方说我们执行个什么东西吧。呃,我们去执行一个,比方说这个这个东西我们还是。
18:04
有一个返回值吧,Returns一个,好,那么我们这里。直接return,呃,我们return一个200万,就当时我们的一个状态码,对吧,大家看这里报错了啊。Expect on,所以这个我们不应该放在这里啊,Modfi它是相当于是在函数的,就是函数类型说明定义的后面跟着,不能放在那个返回值的后面啊,返回值肯定是在最后才返回的嘛,好,所以这个我们就已经定义好了,我们看一下它的这个warning,好,War他说这个你可以用view来定义,好,我们先定。呃,这里就是大家注意,就是它的这个warning最好大家都看一眼,因为我们前面已经知道了,在至少我们现在这个4.25的版本里面,呃,像前面我们发现的这种应用类型的指针,Storage类型的指针是报warning不报错的,所以经常就会有这样的坑,所以大家尽量把汪都看一下,能解决的尽量解决掉,但是有一些我们看了之后觉得没什么,那就那就不用管了,对吧,好,那现在。
19:27
我们去把这些删掉,我们去deploy一下。好,那现在我们可以看到。这个没有定义啊,这个这个还是稍微的差了一点点,好,我们constructor你先定义一个这个是。等于点三好重新部署一下。
20:02
现在的sal。我们知道,就是我们自己对吧。应该是这样对吧。所以我们现在如果调一下F的话。它会返回200。没问题,对不对,那现在我们换一个账户试一下。换一个账户去掉下来,诶,看这边是不是返回。这里已经出错了,对吧,大家可以看到,刚才可能大家没有看的很明显,我重来一遍。F。这里报错。Call to,这个error v error,大家注意看,下面就有reason provided by the contract合约里边给出的理由是,原因是only s就是我们写在这里的报错理由,所以大家可以看它直接调用,我们调用F的时候,它输出了我们在函数修饰器里边的。定义的这个报错出错信息,所以这说明它是首先判断了我们这一条require,对吧?这就是函数修饰器的用恒,所以我们之前写的合约里面其实没怎么用函数修饰器,我们的很多那些条件判断,Require的那些判断,假如说有重复应用的地方的话,或者说它的那个判断还很多的话,我们完全可以把它全提炼出来,放在这个函数修饰器里。
21:26
其实很简单,很好用,对吧,才看到,呃,当然了,就有些同学可能就发现,那我既然可以有前置的条件,然后把这个站位服务好放这里,我是不是也可以把站位放放别的处呢?就是放到战位服放后面了,对吧,我假如说把这个战位放前面会怎么样,可以吗?也可以对吧。那如果我这样的话会怎么样?对,这样的话就会先执行函数函数体,最后再require。
22:01
那大家说像我这个调用就会怎么样了,这这是我们当前的这个账户对吧。这个200没毛病,我们换一个账户试试。诶,大家看它还会运行到这里来,所以就是说这个它相当于是什么呢?即使我们即使我们的函数返回了,它也是返回到这里来,尽管是他先执行,但是我们这个没看出它到底是谁先执行是吧?呃,我们想想用什么方法能知道他先执行。呃,这样我们再定义一个变量吧,一个A,好,这是我们的一个指代的一个东西啊,在这里我们就A等于100。我们这里给一个数值吧,A等于啊,不用给,肯定是零对吧?好A等于100,他报了一个warning,这个定义成了will就不能改状态变量,所以我们就把它去掉,好A等于100,然后我们RETURN200,所以大家可以看到就是。
23:19
嗯,我们这里再来一个啊,A等于300。我们用public来看一下他的状态。这样我们应该就能看出来,通过A的状态就能看出来他们谁先执行对吧?好,我们看一下,首先A是多少,A是零,这个没问题啊。然后我们首先调用一下F。大家觉得现在调用完F之后,A的值应该是多少?
24:00
A的值应该是多少?这里给定值二两百啊,就是就是这是刚才我们调用的返回对吧,调用的返回是200,那A的值应该是多少呢?对A的值肯定是300,因为它调用完了之后,肯定是走到这儿的,对吧?好,那我们要看一下。换一个账户之后。诶,这个直接报错了。到,所以他直接已经报了这个only sell的错对吧,因为我们这个是不能执行的,所以大家可以看到这个时候,呃,当然了,这个时候他应该还是300啊,因为我们那个整个交易给回退了,对吧,交易回退了,所以在这里我们最好还是用两个变量,可能看的会清楚一点。B。
25:03
Deploy。好,看一下,A是零,B是零。那我们的seller是当前的,我们直接换一个来调啊。大家说这个如果要是。呃,啊,但是这个其实也也也是会回退,对吧,大家应该想到了这个,只要是会回退,那能想到我们用一个什么样的方法能把它证明它的这个调用顺序吗。这个还真是跟我之前想的还是不太一样,我想着应该是我们这样就可以把它的就是顺序能够测定出来,但是现在这样的话,因为直接这个就就已经回退了,那我们其实是测定不出来的,对吧?嗯,对,其实我们可以在这个里面去加变量对吧?对,在这里其实我们。你在我。引用一个外部变量,呃,这个其实的问题在于什么呢?是因为它其实在这个运行的过程当中,它其实是整个的交易,这是所有的都打包在一起完成的,所以嗯。
26:18
换用目测的话,它就会失败嘛,失败的话整个交易是回退的,大家可以看一下我这边EF这里是回退对吧,失败那这个A肯定和B还是零,它是没有去改变的,所以是这样一个状态。呃,不,不过我觉得就是这个不重要啊,我们可以先讲后面这个,大家下来感兴趣的话,可以再去把它改一改,看看我们这个程序改成什么样,就能够测定它这个代码执行的顺序,因为我们前面说了,这个占位符所代表的位置就是下面函数先要执行的位置,它执行完了然后再执行下边。那我们看就是之前我们是先require,然后再再做下面的判断,那现在的话就是先这样,然后再做下面。
27:06
当然这个这个确实是顺序不太好测定,因为最后的状态都是统一改变的,对吧。可能我们还是要看这个变成A之后,我们其实就不用去require了,其实就是说我们执行完了,到底是300还是100就对了,对吧,只要判断这个就可以,其实我们想多了,不要require。大家说这个执行完了之后是300还是100。S啊。执行完了A应该是多少?Error。大家觉得是300对吧?对,因为这个看着是在后面嘛,那肯定是300,那我们要证明的是,那我把它挪前面去呢。对,假如说这样部署完了之后,一式是100,那其实就没什么问题了,对吧。
28:04
A是零,B是零。还是我们自己调一下,大家看一下A是不是100。诶,这个A变100了。所以其实就是说这个占位符就表示我们下面函数体的位置,然后它的执行顺序就是从mofi里边开始执行,执行到这个占位符的时候,执行下面的函数体。就是这样的一个过程,好了,大家可以自己去下去玩一玩,把这个实现实现啊,反正我们一开始确实想太多了,一直想纠结于用这个require,所以说它总价回退,我们就测试不了这个这个状况,其实我们测顺序跟require就没关系,对吧。
我来说两句