00:00
可能已经看到,就是我在里边又加了一段代码,大家看我在C合约里边又加了一段constructor。然后我在里边直接就把他自己的balance给了一个300。大家说这样,如果说我在下边去调用的时候,我直接c.balances然后调用address c可以拿到这个值吗?大家觉得这个会返回是吗?大家读一下我现在这个代码啊,C update10,这个刚才我们已经确定了,它update是D的地址上的十,对吧,有BALANCE10,那大家说我现在返回一个c.balance里边他给的参数是address c。最会什么?零是吗?
01:02
零还是300,那大家觉得应该是就这两种情况是吧。绝对是300,好,我们来验证一下啊。Deploy。然后我们先把这个清掉,方便看。好,大家看一下这一个输出。大家看是多少对,所以大家可以看到,在这个过程当中,如果说我们直接在这个大家可以看到,就是这个合约里边,再去调用另外一个合约的时候,他会发生什么事情。首先我们在这里边定义一个合约C类型的变量C,然后去new了一个C的时候,这一步就会调用它的constructor。所以这个时候创建出来的这个C合约,它的地址上本身就有300对不对。
02:00
然后我们又调用这个C合约的update去update了,就是D合约地址上存着存了十,所以现在我们所用的这个C合约的这个实力,它上面就有两个账户是有值的,一个是自己的300,一个是D的十,所以我们在这里可以去查C,也可以去查D,查到C的就是300,如果我们查D把这个改成this的话,那返回的就是十,对吧?所以这刚才我们就是做的这样的一个操作啊,那大家一定就是前面既然提到了这个mes sender,所以我们还是在。大家刚才试的时候应该对这个是不是有了更深的认识了,我们这里的messenger send是我们在调用地合约的时候。调用它里边方法的时候传进来的,所以是我们自己的账户是哪个,这里的message center就是谁。
03:01
但是呢?C里边的这个mes center不一样,那是因为这不是我们去调的,而是D在这里去调的c.update所以C会认为。他的是D。那他这里去更改它的数数值,所以我们就会查到是D的数值变成了十,而不是别人。那呃,另另外这既然提到了这里,我们就再多说一句,呃,那我们不是还说过另外一种情况是有可能他就还是我们之前传进来这个manager center吗?那就是我们当时所说的de理gate call,就是委托调用或者说代理调用这种形式,这个我们就不在这儿说了啊,就一般我们去调一些破函数的时候会用到这种方式。好,我们把映射也已经讲完了,那接下来我们就来看一下。
04:00
嗯,接下来啊,接下来这一部分其实是比较重要的一部分内容啊,刚才我们在讲到,特别是讲到数组的时候。我们一直在强调它可以存在不同的位置,可以存在storage上面,也可以存在memory上面,有两种不同的数据存储位置,这两种位置也就代表了我们在存储一个复杂类型的时候,可以指定它的存储类型。所以大家注意啊,就是复杂类型,所谓的复杂类型就是我们所说的数组结构和映射,就我们前面讲过的,呃,数组mapping他们都有一个额外的属性,就是叫数据位置,或者我们认为就是它的存储位置,主要说的就是两类,一类叫memory,一类一类叫storage,刚才我们都已经接触到啊,那根据上下文的不同,大多数的时候呢,这个数据它是有默认的位置。
05:09
但有时候也可以通过在类型后面就是增加一个关键词,相当于我们就强制的指定它,你这个到底是story还是memory,那有什么样的这个默认位置呢?我们看一下啊函数的参数。和函数的返回参数里边,如果要有数组结构或者breaking的话,它默认的位置是memory,这个可能大家比较好理解,就是你既然是传参嘛,传过来之后我应该就在内存里面就好,我也不用去,就是持续持久化,把它存到我的这个存储空间去,也不需要去,就是,呃,就是要求它变长,我一定存进,就是传进来的参数应该是明确的是什么样的参数,对吧,所以它默认的位置就是memory。
06:02
而局部变量大家注意啊,局部变量的数据位置。默认是story。也就是说,我们在方自己的函数里边,如果定义一个数组,一个变长的数组,或者定义一个结构体。或者定义一个mapping,它默认的存储类型是storage,这个可能是有点烦我们直觉的啊,所以这一点就是要注意。当然了,另外一个是比较好理解的,就是状态变量,我们预先定义好的一些状态变量,它的数据位置强制存在storage上面,所以它一定是持久化的,对吧?所以我们的改变全能够看得出来。这里边容易坑的一个就是这个局部变量,当然了局部变量还有一些简单类型的变量,就是我们直接在里边定义了一个U,直接定义了一个一个布尔行的,这些不会放在storage里面,当然不会,就那些,它主要是放在我们说叫做占的一个地方,那那个占大家也可以理解成广义的内存吧,就是差不多的一个东西,所以那个东西是不用我们考虑的,结束之后它就回收掉了。
07:16
而这个如果是复杂类型的话,数组mapping他们在。函数里边创建声明的时候,如果我们不指定存储练习,它就是story练习。等一下我们看几个例子啊,就是这个这个地方确实坑会比较多,呃,然后另外就是最后还有一种就是所谓的第三种数据位置叫call data call data这一块是它是主要就是用来存储函数参数,它就是只读的,不会永远不会被,呃,它不会被永久存储,然后也永远不会被更改,所以它就是外部的函数,它的参数和返回参数,他在我们的存储里边,它的数据位置被强制定义为另外一种模式叫做call data。
08:05
那那我们,呃,就是普通的函数调用的话,一般情况返回的参数和自己的参数,它的默认位置是memory在内存里面的。所以大家可以看就是college,它只是区分这个内部外部调用而已,正常情况的话,大家就认为它跟memory差不多就可以了,好,这是数据位置的一个大概的一个一个概览啊,然后我们现在总结一下。数据位置相关于数据位置有哪些,这个总结呢,就是首先有些变量,有些参数是强制了它的数据位置的,它到底存在哪里,是一定是存在哪里的,不会变的,那这里边包括。外部函数的参数。存在call data里面,这是指定的,一定是存在那个地方,然后还有一个就是状态变量,我们定义的所有的状态变量。
09:02
全部存在story里面。就是持久化的这个存储。这两个是强制的,永远不会变的,这个好记。比较麻烦的是什么呢?是别的,别的既然不强制,那就是说我们可以改。它可以改,另外呢,还有一个默认的存储位置,就是如果我们不给它指定给他改的时候,它会存在哪里,我们看一下存在哪里啊,函数的参数和返回参数,它就是我们一般的这种函数调用了,不是前面说的外部函数啊。一般的函数参数和返回参数默认存在班主任里边、内存里边,而引用类型的局部变量。就是我们前面说到的数组,还有我们的mapping,还有我们的structure这些类型的局部变量,我们在函数里面定义的默认存在story,这一点强调过好我我反反复复说了好几遍了啊,就是因为这一点太容易出错了,这一点就是经常我们一开始的时候会会会出错,或者说经常被坑的地方就就在这一点。
10:12
还有另外一个就是直类型的局部变量,那其他的就在占上面了,大家也可以认为是跟memory差不多的一个东西。这我我们在之前讲EVM的时候,可能讲过这个站这个概念啊,最后还有一个特别要求就是公开可见的,就是所谓的publicly visible。也就是说,我们在后边给了public类型的函数,它的参数一定是memory类型。一定是memory,这是相当于又是一种强制,就是跟函数的可见性或者说函数的类型相关的一个要求,假如是public类型的函数,参数必须是memory。那如果说。你想让传一个参数指定把它改成story类型的话,可不可以呢?
11:06
Public类型的函数,那就不可以了,如果说你想让一个函数的参数是story类型,那你必须把这个函数参数函数本身指定成private或者是internal类型,就是私有类型或者是内部类型。这个时候就可以把它的参数。指定成story类型了。有点绕啊这个,那大家想为什么他要这么去规定这么多乱七八糟东西呢。那最后这个其实比较简单,就是你既然是公开可见的参数嘛,谁都可以去传,谁都可以去调。而里边的参数storage大家大家知道,它是我们以太坊上公开的,这个存储空间是可以持久化的,对吧。那你说谁都可以去传传参数,然后还要去调用,你这个就给你持久化一下,这个消耗就太大了,而且说我们哪有那么多资源去弄啊,这很容易引起这种就是资源的浪费和这种恶意的占用性资源,所以说为了防止这个一定是会有一个限制,你不能随便调,那正常情况传进来的参数就是memory类型,Memory类型有什么好处呢?
12:14
他在内存里边首先访问比较快,占用系统资源比较少,另外就是说呃,它相对来讲比较比较小,比较宝贵,那就是我们函数调用的时候,就是你传进来的时候,那占用给你临时分配一块干净的memory空间给你,等你调用完毕的时候,这块就释放掉了。就等待下一次调用,所以这是我们内存的一种标准的使用方式。而story它的特点是什么呢?它就是很大,我们可以理解成它就是我们的数据库,它就是我们的硬盘,这个可能不太这种理解,可能不太就是符合真实的状态,但是大家可以这么理解,所以它就是一个很大的持久化存储,那在这样一个地方,我们要去访问,肯定那个成本就比较高,对吧?大家知道去你去读硬盘的话,那肯定速度就比读内存慢多了吧。
13:08
那呃,同时它存储空间比较大,读取又比较慢,比较耗费系统资源。同时它又可以持久化,所以大家可以看到我们认为比较重要的状态变量是强制要塞到story里边的,我们不怕慢,我们要的就是把我们的状态全部记录下来,对吧?所以这一点是一定要放在里面的,呃,另外就是说。局部变量。局部变量引用类型的局部变量为什么默认会放在storage里边呢?这个简单的跟大家说一下,这就涉及到在我们的这个以太坊的内存管理,或者说存储管理里边,它的数组和结构体,还有这个mapping映射到底怎么存。
14:00
给大家提个问题,就是如果你自己设计这样的一套系统的话。你觉得我们这里有一个mapping?从一个东西到另外,呃,Mapping可能稍微复杂了点,我们想数组吧,有一个数组我们存的时候怎么存?是按顺序存是吧?它有几个元素,我们按照顺序12345678把它存着,那这个问题就来了,我们定义的数组类型里边有定长的数组。还有不定场的,还有动态类型的,那如果说我定义了这个东西,它就是一个UNIT8。八个UN类型的一个数组,那我就分配好对吧,你这里一个八个分配好就行了,我现在不定长这个,怎么去存呢?大家可以简单的去思考一下。这是存储的一个难题,以太坊是怎么解决这个问题的呢?以太坊的做法其实也是,就是大家可能能想到,我们存这个动态数组怎么去存,你既然不能够按顺序去存储了,那我们就得用其他的数据结构了。
15:09
我们可能得用链表对吧,地址把它指到别的地方去,我们还可以用哈希哈希表。哈希表有一个特点是什么呢?就是我这里的东西,可能哈希出去之后,这里是连着的东西,哈希出去之后它完全散开了,我都不知道把它散到哪里去了。以太坊最后采用的数据存储方式就是哈希表的这种方式,而他为什么采用这种方式呢?就是因为它要求的是一个很大很大的存储文件,大家可以认为它的story是一个非常大,反正就是我们一般情况是没有办法能把它全部。全部遍利完的非常大的一个存储空间,呃,这这就是为什么我们整个以太网数据会那么多嘛,对吧,在这么大的一个存储空间里边,它它为什么要要一个这么大的存储空间呢?它就是为了保证我们的对这个数据做了哈希之后,把它扔到任何一个哈希的地方,我们知道哈希表有一个问题,就是会有哈希碰撞。
16:15
对吧,就是我在这个地方去存,存着存着有可能我空间就是首先是哈西函数,如果定的不好的话,有可能会定义到同样一个地方去,那我们现在肯定没这个问题,我们用的还函数一定是就是确定的一个东西,那另外一个问题呢,就是如果我们空间比较小的话。我们一个一个去哈希,哈希到之后可能就塞满了,我们就可需要去填了,对吧,那这个是容易引起哈希碰撞,所以在以太坊里面就要求你不是容易碰撞吗?我来一个足够大。足够大的空间去存这些东西,你这下找不到了。那撞不到了,这是好,但是同时带来的一个问题就是我们没办法去便利。
17:03
那么大的一个空间,你怎么去便利?你只能够提供他的哈希,我直接去找到那个地方。所以这就是我们为什么说在以太坊里边,在solidity里边,你如果定义了一个一个mapping的话,你不可能去用这个mapping.key直接把所有的键值拿出来,没有这种用法,mapping.values直接把所有的值全拿出来,没有这种用法,因为它是在一个很大很大的存储空间里边散列进去的。所以是这样的一种存储方式,所以大家可能也就可以理解到了,就是如果说我们引用的这个数据类型,我们是一个动态长度的数据类型的话,我们在memory里边其实就没法实现,因为我们在内存里边,我们是一定要指定好给它分配多少内存空间的,对吧?你如果要是不现在直接给他指定的话,你到时候用到的时候,那你就得随时的去,就是一旦要调整它长度的时候,你还得去重新给他分配一个存储空间。
18:07
那除非你是这种这种实现,但是memory里边一般不会啊,我们一般就是说直接给你分配好。呃,如果说你要是再去月结访问的话,那相当于就内存溢出了嘛,所以大家就就肯定是不能不能用这种方式去调用,而storage里边就比较方便,我既然是用哈希,我空间足够大,我全哈希出去了,那你就随便来吧,对吧,你来一个我给你哈希一个,来一个哈希一个,只要我定义好这个哈希算法。你就几乎是可以无限来的,因为它的存储空间,我一开始根本不需要知道我占多大的存储空间,我本身要存的那个地方,可能就存一个我这个数组的头就可以了,对吧?来了之后我只要访问到这个头,那我根据你要访问的那个值,我做一个哈希运算,我就能找到对应的那个地址。那你继续,如果要是扩扩展它的长度的话,新增的内容那也是一样的算法,你只要把你的K拿过来之后,我做一个哈希就找到它的值。
19:07
所以大家可以看到是这样的一种实现方式,所以也就有了它的局部变量默认是storage类型。这里给大家解释一下啊,就是这个原理可能相对来讲会比较可能会有点难,比较深,大家如果要是感兴趣,或者说还想钻研的话,可以下来之后再去看,就官方文档啊,还有以太坊的一些东西,呃,是可以看到这些解释的,当然如果要是大家也不感兴趣,觉得这些东西不会影响到自己写合约,那我们就先。可以先放一放啊,好,接下来我们看例子了,例子可能会直观一点。呃,大家看这个可能会字比较大啊,我们就先在这儿看一下吧,大家说这个例子,先瞄一眼这个例子是干了些什么事情,定义了一个contract c,然后呢,定义了两个状态变量,这两个状态变量都是。
20:05
不定长度的数组对吧,都是变长的数组,U的数组,一个叫塔一,一个叫data塔二。那我们前面说过,状态变量是强制的。存储在哪里的?存储在哪里?有两种类型对吧?一个是永久性的,很大的那个叫storage,还有一个是临时性的,访问比较快的那个叫memory。这个状态变量应该放在哪对storage,所以大家注意啊,就是一上来状态定义状态变量,它就已经在storage里面给了一个存储空间,那前面我们也说了,像这种你要是不定长度的话,它就不是把它的值全直接放到那个固定的里面的,就是固定给他分配的那个存储空间里面只放了一个头。剩下他来的那些纸都会散列出去,好,这个是题外话,我们就看这个例子吧,下面我们看一下它的function function其实很简单,主要我们看前面这个end oneend two这两个,其实它就是调用了另外一个方法是吧,然后一个是传递了DATA1,一个是传递了DATA2,所以我们主要看最底下这个end方法吧,End函数它会传递一个。
21:22
大家看啊,会传递一个。数组。而且它是一个变长的数组。然后是叫做D。给定这个这个参参数的名称叫D,然后里边的执行也很简单,就是地点PUSH1。这个是一个什么操作。前面我们说了,就是数组是可以有这个,就是动态类型的storage数组是可以有这个push方法的,对吧,它可以把一个新的元素直接添加进去。
22:02
所以它就相当于是把一这个数给加进去了,对吧,大家想它的这样的一个一个用途。如果说我们在上面one。调用了一下,调用了一个ipad data1。会有什么样的效果?我们看它是调用了一个PA的方法,然后把data塔一给传进去了,对吧,Data塔一就当成了de,那所以这里面是不是就是data1.push。那是不是就可以把塔一的值改变了?是不是这样?所以我们可以直观的看到这个程序的目的其实很简单,它是用很就是稍微有点绕,比较复杂的方式。实现了对DATA1和data塔二的一个赋值,用另外的函数里边去给它赋值,对吧,而不是直接的我们说呃,Data塔一的呃,直接就点push,或者说DATA1的一,它它指定是城市什么没有用这种方式,好我们在这里还是啊来来敲一敲代码吧。
23:11
最好还是大家把这个代码都敲一下。会比较明确,我们先把这个D先删掉吧。然后我们定义了一个一个C,它是定义了一个U类型的动态数组,一个DATA1,大家如果要是觉得太简单的话,大家不想敲了是吧,那我就敲快一点好,然后我们定义了两个参数,呃,两个两个函数啊,我们就直接写这个吧,尽管这种方式不太好啊,大家最好那个定义那个函数的时候,最好不要用这种方式去定义,后面加数字不太好。呃,然后public。
24:07
把贝塔一传进去,好,同样我们嗯。好,这个因为没定义,所以说才报错啊,你直接把它复制了。代码。给二传进去,好风。Functionend,它这里的传入的参数是一个类型的。D啊,我们先这么写啊,大家可以看到跟我们的例子好像不太一样,大家直观的印象应该就是我定义一个这个参数,然后定义它是D,然后它是public类型,呃,不能是啊,可以是public啊,先先这么写吧,定义了这个D之后,我就直接地点push,我们随便PUSH1个数,他push了一,我们push个23吧,好。
25:05
诶,这里报错了,看一下诶大家看啊,这里就报错了,为什么呢?他报的那他报的要求就是说我们这里边大家还记得说函数的参数默认是什么类型吗。对memory的练习,然后大家看见,如果我们这里要直接去push的时候,他就会说对push这个这个成员函数,它不是memory类型的。成员函数。也就是说,Memory类型的。数组是不能调用push方法的啊,这其实跟我们前面说的一样,对吧?Memory你必须要是一开始就给它分配好到底是多少,你进来之后如果在动态去push的话,它是不允许的,所以那大家看他做了一个什么操作呢?就把这个对强制把它定义成了storage类型。
26:03
这样的话下面就可以再push了,对吧。但是这里还报一个错误。这个错是为什么呢?看一下。Location has To Be memory for publicly visible functions。前面我们说过一句,如果说函数是public类型的话,它的函数参数是强制要求是memory类型的。因为storage是要。是要改变状态变量的,对吧,是要是要直接影响到我们的永久存储的,你怕立刻类型的话,别人随便去调,随便去改状态怎么办,所以他不允许。那所以改这个错的方法就两种,你要不就把这个story删了,那还是memory,那下面还是不能用。要不我们就把这个public。对,改成internal,刚才大家看到它的提示了,改成internal,这就可以了,好,接下来我们来调用一下这个东西。
27:03
就是C是吧,好。呃,接下来这现在的这个状况有一个比较尴尬的地方是,我们也没有返回值,然后也没有东西能看到,对吧。我们这样吧,为了方便看,我们直接把上面定义成public好了,大家在做调试的时候可以用这种方法,就要不我们给返回值,要不就是把一些感兴趣的状态变量,把它定义成public,我们这里就直接能访问了。删掉,重部署一下。他用这个就是GSVM,这个部署还是很方便,我们也不用关心里面有没有钱,然后也不用关心我们的mama,还要不停的去点,对吧,直接就部署。大家可以看到现在的贝塔一这里报错了。为什么呢?大家看他报的是什么错啊,他说。呃呃,这个没报什么错,反正就是说错误的这个操作符对吧,是为什么呢?因为我们现在的DATA1还没定义。
28:05
什么东西都没有,那你直接去访问这个data塔一,那其实就越界了,对不对,就相当于是越界一样,所以他就没有东西,好,那么我们DATA2肯定访问也是报错啊,这是肯定的,那我们先AEND1,我们看一下AEND1。成功,再看一下D1。顺利的变成了23。所以大家可以看到这个过程它是怎么样去传递的,其实这就是在调用这个APA方法的时候,先把这个DATA1作为参数传进来。那作为参数传的时候,贝塔依旧付给了D,对吧?然后D去做push的时候,大家注意啊,这里D其实没有。复制一份DATA1出来。而是地址是一个贝塔一的。引用对吧。所以大家注意这样的过程啊,就是当我们在函数参数里边,或者说在下面的局部变量定义一个storage类型的变量的时候。
29:09
本身我们的状态变量也是storage类型,那如果是从storage类型到storage类型的赋值的话。那会认为后面我们定义的这个只是一个引用,而不是COPY1整份这个数据。所以大家注意,在这里我们D的改变,D的push push了23,那么贝塔一也就push了23。所以是用这样的方式去实现的这一个改变,所以大家不要觉得很奇怪,你这里d push,那怎么DATA1就变了呢?啊,它就是因为地址是DATA1的一个引用,那我们现在看data塔二,当然data塔二是还是没东西的,我们APA2大家看到。DATA2也变了,所以在data塔在PEN2来调用这个方法的时候,D就变成了data塔二的一个应用,所以D的改变吧,对,塔二也改变了。
30:05
好,大家自己实现,实现就是这一部分内容啊,就是这会涉及到大家,如果之前就是C什么的学的比较比较多,或者说比较了解的话,那对这个引用啊,或者指针的概念应该是非常的熟悉的,这个其实都是类似的东西啊,但是如果要是大家没有接触过。就是类似的这种概念的话,可能会觉得这一块稍微有点绕。呃,我们继续多讲一个吧,对,但大家想自己敲还是还是我们继续往后讲讲,继续讲吧,是吧,觉得这个也没啥意思。好,那我们接接下来准备来改错了,好大家看一下这个代码错在这个代码里面有错误,错在哪改错题。
31:06
我们先读一下这个代码,还是一个合约contract c,然后它定义了一个UN类型的,哎,它这个就是就很有意思,定义的是some variable,就是随便定义了一个什么什么变量放在这儿,它是一个unit类型。然后它又定义了一个U类型的变长数组贝塔。这两个都是状态变量,好,接下来定义了一个方法f public类型。它又定义了一个U类型的变长数字,叫做X。然后X点二。那就会把X这个变长的数组类型,会把这个二加到X里面,对不对啊,这是我们能想到的啊。然后得塔等于X,但是把X付给了得塔对吧,大家注意啊,在。
32:07
在我们这个solid的这个就是数据类型的数值里边,数组的数值里边,假如这两个都是storage类型的话。状就是局部的storage类型的变量,它如果说等于了一个,另外就是把另外一个状态类型的story变量赋给了它,那么它只是状态变量的一个引用,刚才我们已经看到了,对吧?就刚才那个DATA1 data2,但是如果说我们要把另外的一个值赋给状态变量的时候。状态变量一定是会把那个值记录下来的,因为状态变量不可能是别人的引用,对吧,它一定是有自己的存储空间的,所以只要是给状态变量的赋值,那一定会写进去。所以他最后是把这个X这里不是push了一个二吗?所以他又把这个写给了得塔。
33:06
那大家直观的想,最后这个data是应该是几。这个应该是二是吧。有同学说不是。好,我们这这个我们自己得尝试一下对吧?好,我们来一起敲一下吧,大家最好是能自己就是。来来敲啊,我直接把这个删了吧,我建议这个里边的例子大家都自己去敲一下,呃,它定义了哦,它是先订阅一个some variable,那我们这个简单,我们就定义一个A吧,A可以对吧。怎么直接就报错了呢?Not found。哦,写错了啊,大家看这个对UNA,然后呃,还定义了一个U类型的动态数组,变长数组给好,然后我们定义一个function f public。
34:21
然后在里边我们又定义了一个U类型,这个类型是个什么来着?哦,他是给了一个X的名字,咱们也叫X吧。呃,然后他做了一个操作X点对吧,二。好,最后把X付给data对吧?好,这个代码非常简单啊,要不了几分钟就就敲完了,哎,不写错了X。大家可以看到就这么简单的一个合约,那大家看这个有没有编译错呢?没编译错,它就是有个warning啊,这个warning说什么呢。
35:01
他说这个是declared as a storage pointer。什么意思?说这个X是一个storage类型的指针。诶,大家还记得我们在。函数内部指定这个定义局部变量的时候,我们前面说了它默认的存储类型是什么?Storage,对吧?默认是storage类型,我们定义了它,然后又没有指定它,指它没有给它赋值,对吧,那这是不是就是一个指针,一个一个空指针对吧。那他要是已经是一个定,就是初始化好的空指针还好。最怕的是它没有初始化。就变成了野指针。那就不知道指到哪儿去了,对吧。对,所以大家一定要注意这种坑,我们先执行一下,看看它到底是什么样子吧,跑一下啊。
36:01
Deploy。啊,这个这样啊,我们还是把它定义成public,我们来看一下,要不然这个我们都没有,没有渠道能够看到所有的东西,好deploy一下。好,现在我们首先看一下A0,诶大家看默认是付出值给的零对吧。Data,现在因为data是一个动态长度的数组,现在你没有东西,所以它就报错了,对吧,这都符合我们之前的这个认知,然后我们看一下F这个调一下。诶,掉成功了,好像没错啊,他说。没问题啊。我们看一看,大家觉得低塔应该是几。看一下诶,电12给过来了,没问题。直接是一个。那那觉得这个这个有问题吗?这个这个为什么我在那边课件里面说他是有错误的呢。
37:07
好,先这样啊,我们把这一行注掉,再跑一遍看看。好。现在什么东西都没有,对吧。F跑一下。哎,这塔还是二没有什么问题,那刚才我们助掉的这一行,大家想一下这个这个A是什么值呢?诶。刚才我们没看A的值对吧。看一下。A,视频对吧。调用一下。AAA为什么变了?好诡异的样子,为什么A会变呢?我我这个明明就跟A没关系啊,为什么A会变再掉一次,大家觉得是什么?
38:04
再掉一次是吧,嗯。点二那这个程序这个bug好诡异呀,对吧。我这边根本没有对A做操作,我定义了一个data,然后函数里面我也只是定义了一个X。X去push一个二,那我data里边应该是这个这个都是都是二才对,对吧。那这个不管怎么破,一开都跟A没关系啊,怎么把A给改了呢?而且大家看到。就是我们之前应该是已经push过一次,对吧,那。X里边。好,因为他是直接把X给到data,所以说他只有一个二是对的,对吧?对,因为每一次都是把X给过来,并没有说一次一次的push到data里面,那那所以现在我们看到那这个data塔就只有一个二,这个你还能理解,还正常对吧?那关键是这个A怎么能变成这个样子呢?我们再调一次看看。
39:08
变三了,哎,变成计数器了,这是什么毛病,这是啊,再定义一个变量,说在上面再定义一个变量是吧。我们再定一个B是吧,Public b好。我们先把它删掉,然后deploy一下,A是零,B是零都没毛病,对吧,说实话。F调用一下A变成了一。B还是零。诶,他俩行为还不一样,他俩要一样的话,我们还好理解了是吧?就好像某种机制能把所有定义的这个常量都给它加一,但是但是这不对啊,这怎么A和B完全一样,怎么他只改A不改B呢?哦,是so是个神奇的语言是吧?这其实就是so一个大坑,也是很多人在诟病的so在设计上的一个。
40:14
就是怎么说呢,可以说是一个缺陷,我也认为这个确实是可以认为是一个缺陷了,这个确实是一开始上手的人特别容易犯这个错误,就是经常一不小心就掉进这个坑了,大家先思考思考,然后自己去跑一跑,看看是不是这个样子,是不是我这儿做了什么币啊,做了什么手脚,呃,我们下午来给大家讲解这是为什么。
我来说两句