00:00
好,接下来我们继续继续改错,这个又是一个问题啊,呃,大家来看一看,这个就牵扯到memory类型和storage类型,他们各种各样,就是相当于把我们之前给大家说的。哪些默认条件下存在哪里,哪些是强制需要存在哪里,然后哪些可以用push方法,哪些可以用点LAS的处理方法,全在这个例子里面,相当于有一个有一个综合啊,大家看一下这一段代码。在mix里边如果直接编译的话会报错,大家先先直接就看这个代码,我们先别放到里面去试啊,大家猜一下这个报错会在哪里报。啊。第一行就报错,是说这个好函数的第一行是吧,函数的第一行好,我们直接看函数了啊,它前面定义了一个X状态变量,函数里边是。
01:03
X等于memory array。这个函数的输入参数是一个叫做memory的一个可变数组。然后呢,第一行就把这个可变数组给了X。有问题吗?Memory memory哦,它是对这个大家还记得我们说函数的入参传传入传进函数的对参数和从函数返回的参数都是memory类型,对吧。呃,这里有一点可能没跟大家说的,就是memory类型和storage类型,他们互相之间是可以传值的。只不过呢,就会因为它的数据类型是一样的嘛,数据类型一样就可以互相传值,但是传值的时候会有一些别的一些限制,就是呃,不是说限制啊,就是会有一些别的状况出现,就是如果是一个memory类型的变量,要给一个storage类型的变量要赋值。
02:14
那么它的所有的数据的值就会形成一个完整的拷贝给到那个story变量。就相当于这个story变量,就不仅仅是它的一个引用了,就会完整的把这一份拷贝过来。而如果要是说是一个storage变量。赋值给另外一个storage变量的话啊,那这个就看到它是什么类型的storage变量了,我们知道同样都是storage storage类型,有些是状态变量,是我们强制就要求它是storage类型的,还有一些是局部变量,我们默认它是storage类型,对吧,如果是给状态变量复制。就我们外边定义好的那些状态变量,只要给它赋值,就会改写它里边的内容,所以说相当于就是有一份拷贝会拷到他的那个永久性的存储空间里面去。
03:10
但是如果要是局部变量的话,局部变量story类型,假如说拿一个状态变量给它复制,或者说拿一个别的story这类型给它复制,都是story这类型,那这个局部变量就只当一个引用。所以刚才我们有几个例子就是这样的,对吧,就是我们定义的一个storage变量,它把一个状态变量赋给它的时候,它就变成了一个引用。好,所以这里的第一句其实是可以成立的,就是把一个memory r给到X的时候。那X其实就是复制了一份这个memory array的内容。就相当于我们的状态变量里面就写那个东西去了。是这样的。然后接下来大家看它又定义了一个U类型的可变数组,叫做Y。
04:05
Y又去等于X。也没问题是吧。这就相当于建了一个引用,是不是这里是storage类型,X也是storage类型,所以付给它的时候,它是一个引用,然后我们去看,他直接去访问Y7。这个大家说可以吗?返回他的第八个元素对吧。啊,这个当然就是要看你越界没越界了,这么做肯定是可以做的,但如果越界的话就会报错。呃,如果没越界的话,那应该还好对吧?好,我们继续往下看y.lengths等于二。有没有有没有问题,也没问题对吧,因为我们说过类型的可变长度的数组,其实是可以直接用点LA去指定它的长度的。
05:05
那这样的话,假如说原先它有这么多,有有八个元素,那这不等于二就相当于把后面全删了,对吧?对,只保留前面的两个,好,那接下来我们看delete一个X。这是delete的方法,这个没有我们没有,就是delete关键字我们没有专门给大家讲过啊,在这里大家可以了解一下,就可以在solidity里边的delete相当于是什么呢?呃,它相当于大家可以认为它是一个软软删除,可以认为是,其实就是把它直接全变成零,这个就叫delete了,那如果说是一个数组的话,特别是可变类型的数组的话,其实就是把它的长度是为零了,所以就是其实就是这样一个动作啊,所以它delete X,那肯定是可以delete的,呃,接下来我们看啊,Y又等于memory。然后delete外这两句有没有问题。
06:08
有没有问题?删除了,对。外。好,我们这样吧,直接出来把它抛过去了,这个我就不再手敲了啊,因为这个其实就是一个实验。好,复制过来,我们看一下它的报错信息,这个很方便看,对吧?果然前面我们说觉得没问题的地方,他都没报错,好我们现在来看一下这里的这个提示是什么,他说type error,对吧,类型错误。果然是跟类型有关,他说一个U256。
07:01
变长数组这样的一个类型,而且是memory类型。它不能影视的转换,不能做影视类型转换,变成你期望的一个U256数组类型的storage指针。有点绕是吧,这这什么意思啊。这它所说的意思其实很简单,指的就是说我们memory,这是一个storage类型的变长数组。对吧,Story类说错了啊,Memory类型对吧?对大家大家听的很细啊,对,这是函数参数,所以它是一个memory类型的。变长数组,那么我们这时要把它付给YY是一个什么?对,Y是一个storage类型的,注意它不是真实的数组,对吧?
08:05
它是之前指向X的一个引用,它是一个指针。所以如果它是一个指针的话,这就不会发生我们所说的那种从一个就是memory变量,然后copy到一个storage变量的时候出现拷贝的那种情况,因为你是要把一个指针直接指向它的。这个时候就会发生错误了。因为什么呢?这个指针我们定义的时候是直接指向我们非常大的那个存储空间,无限几乎无限大,大家可以认为无限大的那个story存储空间的,你突然说要把它指向另外一块跟它完全没关系的,我们很小的那一块快速访问的内存,这在它一开始的定义里面,它就不是这么定义的。所以说。我们这里的0001和那里的0001地址含义完全不同,它不能直接这么指过去。
09:03
所以是这样的一个概念,如果是这样的指针的话,它是不能直接这么指的,所以这里就报了一个错。当然后面delete外,他也就就不允许这么去去删对吧?呃,大家大家就可以想一想,这个时候如果我们要改的话,怎么改呢。大家想自己试一试吧,看看怎么样能把它改的没有,没有编译错误。好,那那给大家五分钟时间,大家先把这个试一试吧,就是我们的目标就是让它变,首先就是让它变成这个没有编译错误的一个一个,呃,合约。大家还可以看到,就是下面还有两个别的方法啊,这两个方法其实是。
10:04
一个是一个internal类型的一个函数,它传入了一个story类型的变量变长数组,另外还有一个public类型的函数,它传入了一个memory类型的数组。所以大家可以看到就是下面这个,就是我们说的,如果要想传入参数,想要指定成storage类型的话,我们就一定要用internal或者是private类型的函数,而不能把它指定成public public的话,我们就不能去把参数作为storage永久存储。改为改为哎,有同学已经提出解决方案了啊。大家说的改成memory,是马达里改成memory。Y,就是这里加一个memory是吧,改成memory类型,哎,但是大家看确实。
11:03
改成我们已经想到就是为什么Y不能直接指向memory呢?因为memory是一个memory类型对吧?而Y是一个storage类型的指针,所以它不能直接指向memory。那我们就想到了,那我们把Y定义成一个memory类型不就可以了吗?那这里定义了memory类型之后,大家发现到这一步又报错了。这是为什么呢?我们看一下报错信息,Expression has To Bea哦。大家看到啊,就是它只能作为它是l value就是左值。哎,这个应该叫左值还是叫什么应该叫。Left value应该是左值对吧,就是。意思就是说y.lengths这个值不能作为左值,左值是什么?
12:01
在一个表达式里边赋值,表达式里边左值就是我们要给它赋值,对吧?对,他意思就是说它不能变,不能给它赋值。对,为什么呢?因为我们说memory类型的变量,它的长度只要是指定确定了之后,它就不能变了。这个长度是定的,所以说这里要去等于二,那就是不行的。所以大家可以看到啊,就是这个memory类型,我们是不能用这种方式去去做长度的变化的,当然如果除了这个长度的变化,大家觉得还有别的问题吗?把它一注掉之后,看起来后面就都顺了,对吧?啊,所以刚才大家可以看到,如果要是允我们允许就是说就一定要把它的这个长度要改的话,那我们就不能用这个memory练习。如果说我们允许这个把这条注掉的话,直接一个把它改成memory类型,后面就都通了,所以是这样的一个状况啊,大家可以再反复的把它敲一敲。
13:07
呃,当然有同学可能就说,那那我就是要这一步要给他定长度,我这里就得storage类型,那后面应该怎么改呢。啊,定义一个什么,那那应该怎么用,大家觉得应该是定义一个什么。然后将它给新第一个那个。好,所以就是说,呃,大家的意思是说,比方说这样吗?You memory类型是吧,然后比方说我们说这叫Z吧,然后他要他要付出值吗?不用付是吗?Z把把Z付给ZY是吗?
14:02
Memory给吧,啊,所以它还是有初值的是吧,Memory对这个当然没问题,对吧,我们定义了一个memory z,然后直接呃指向了这个memory。那后面这个Y怎么办呢?对,其实在这里我们就不应该去再用Y去指代它了,我们直接用Z就好了,对吧?对,如果我们要想处理memory的话,我们在这里不要用Y,就是另外定义一个Z去处理它没错,所以大家可以看到,假如说我们这里。不要把Y放在这里,这里要删的话,我们假如说想要删这个空间的话,我们直接测delete z就可以了,对吧,那这个过程当中,Memory read到Z的这个过程,他们是创建了一个拷贝还是只是一个应用啊。这里不是拷贝,这里还是引用,因为他们俩都是memory类型。
15:02
就是同样是memory类型的话,嗯,同样类型呃同样类型也有拷贝的情况,那种情况出现在。我们要把一个局部变量给到状态变量的时候,就会拷贝过去,就只要是操作到的状态变量,状态变量里面一定会有他的一份拷贝。就状态变量不可能当成引用了,那大家只要记住这个就可以好了,这个我们先先放到这里啊,呃,因为这一部分确实坑比较多,所以说就给大家详细的找了好几个例子来给大家讲一讲这部分,然后接下来我们再看一个例子吧,我们来一起玩一个猜数字游戏。这个合约可能其实还是比较简单的,但是这个合约就是提前先跟大家说,就是跟我们刚才学过的这些内容会有关系。我们先一起来看一下这个这个猜数字游戏啊,这个猜数数字游戏呢,是大家看到一上来定义了一个UN lucky number,那这是一个幸运数字对吧,等于52幸运52啊,Unit public last。
16:18
呃,这是一个,这是一个时间的一个标记了,大家可以先不管,我们看完就知道这个last有什么用。下面又定义了一个结构体,叫做gas的一个结构体。Guess里面包括什么呢?包括一个address player。有一个玩家,呃,玩家就是用地址来表示对吧,我们知道一个地址背后一个人嘛。然后还定义了一个number。那大家可以想到,那应该既然是GA嘛,那应该就是这个玩家,他猜了这个数,我们就存到这个里边对吧?好,这是这样的一个结构体,然后我们又定义了一个。结构体类型的数组。
17:00
GA类型的一个动态数组,Public GA history啊,大家一看这个名就知道了,那就是每个人都能猜,我们会把所有人猜的历史这个所有的这个结构体当成一个数组存在这个里面,对吧?这这还都是很顺的逻辑啊。好,接下来address owner等于message,点三三。那呃,这个定义了一个owner,其实后面也没用啊,他肯定就是说,呃,谁是这个拥有者,那之后就是说。大家先看看下面的逻辑啊,我这个其实只是截取了一部分,没有把它完整的截取出来,我们看下面它主要的这一个应用是什么呢?就是这个盖方法。Guess函数guess是做什么呢?我们调这个方法的时候,要传进去一个unit,一个number,呃,大家注意看,这个还是一个payable的方法。所以就是调这个方法的时候,我们还可以发币,转币转给他。有什么用呢,看看。然后它定义了一个GA new GA,这是一个结构体,对吧?New ga.player就是调用者。
18:07
new.number就是传进来的这个number。然后呢,GA history push new GA,把把当前的这一个GA对写进历史。If。你猜的这个number等于我们设定好的lucky number。那么我就message点三点transfer。message.value乘以二这句什么意思?你猜对的话,奖奖励你发过来仪态数量的两倍再返给你对吧,因为messenger value是你调用这个盖的时候发的仪态数嘛。对,这个意思,那message center.transfer呢,是合约要给这个三给你来发币,如果你猜对了,我给你两倍的发过去。
19:02
然后下面last等于闹,这就是时间,这个now是一个thirty里边关键字啊,就是闹表示当前的时间,说就是呃算是一个,就是大家计时的时候,方便获取的话,就用这个now就可以,所以他就把这个当前时间给到last,就更新一下。最新更新是这样。好,那么大家觉得你如果要猜的话,应该怎么调这个方法。好,我们还是在炉子里面实现一下,这个有必要大家一起来实现一下啊,好,这个删了,好我们想一下,他一开始上来是定义了一个什么东西,Lucky number是吧。这个不是public,那对这个肯定不能public让大家看到啊,Lucky no number等于这个随便啊,大家觉得自己的新闻数字是什么,随便给一个,呃,然后下面它定义了lucky number之后啊,其实这个last不重要啊,咱们都可以不管它,对吧,我们就直接定义这个结构体吧。
20:19
Guess是一个什么东西呢?里边会有一个address表示当前的这个player,对吧?然后还会有一个you来表示它猜的数number,好,然后我们会记录一个guess类型的历史,对吧?这是一个长度可变的结构体数组gas,后面给定它的这个方括号,表示它的这个数组数组数组类型。呃,这个应该是public吧,看对这个肯定是可以查得到的,Public定义一个GA好。
21:03
他还有别的参数吗?这个owner这儿没用到啊,但其实可以想象得到,他这个有了这个guess之后,他给你发,那最后是不是你猜错的那些,他最后都要给order提出来啊,对,肯定他这个游戏就是这么玩的啊,你都发那个猜对的人两倍返还别的人的,不好意思,我都说了owner是有权利把它提出来的,但是这个我们这儿看这个合约这段逻辑我们没用啊,所以我们不管这个owner了,那接下来我们就实现这个guess方法了,Function GA。我们会传进来一个你猜猜的数字,Number,这个当然是public,我们每个人都可以调的好,那接下来猜数字的时候,它会怎么样呢?首先第一步它会定义一个对,就是gas类型的本地的一个logo变量,对吧,New GA它是这么叫的。
22:05
New guess它它怎么给的,直接定义是吧,然后就new ga.player等于MSG点3NEW盖点number,就等于传进来的这个number对吧?好,接下来再把它写进我们的history里边,历史不能更改。这个直接点对吧,点push,把我们的new push进去,好,这个就已经完成我们这个操作了,接下来那就是如果你猜对了,这个你猜的number跟我们的lucky number完全一致,好。
23:01
那么怎么办呢?双倍返还对吧?完全一致就双倍返还,那就是message,点三点transfer。乘多少呢?对,是你发过来的value的两倍,Y6乘以二,星号就是乘号。好这个就完了,对吧,那后面的时间戳我们就不管了,这个基本上我们的功能已经实现了。呃,但是这个肯定会有问题啊,我们这里没有写对吧,就是如果要想本身我们这个能接收以太的话,一定得是payable类型对吧,所以一定得是payable,好,那接下来。我们,呃,我们部署一下试试好。没有问题啊,不熟。
24:01
呃,我觉得我们这里应该再加一个方法啊,就是因为我们不知道,我们猜完了之后,因为我们只知道猜的历史,不知道猜对没有,当然我们可以通过这个我们账户余额来看啊。我们先试试吧,我这里我现在还有63.99个啊,大概64个,我发十个以太试试啊,大家说我猜多少啊,这还用说才52倍对吧,他都写死了,难道不是这个吗?来我们试试啊,猜成功发送。然后少了十个。金少十个没增,看起来他没没给发,没猜对。哎,那大家说这个问题在哪,是是他的他的问题,还是咱们操作的问题。好,大家现在自己把这个合约实现一下,然后自己琢磨琢磨,看看怎么样能把他这个钱拿出来。
我来说两句