这个数组啊是我们大家常用的一种东西对吧 大家在使用这种数组的时候可能会有一种难言之隐 有时候这个数组开大了呢,开大了呢会浪费空间,开小 了可能又不够用,最好就是需要多小东西这个数组的用量就是多少 这才是完美的,对吧,可是我们数组是固定的又很难办 当然带家都想听解决办法,所以我们用动态内存分配 来解决这个问题,但是用动态内存分配领略出来的东西呢 又要实实际际的去体例着它,这挺麻烦的有没有 最好是有一种什么的动态可变常列的数组 这个数组呢,需要放多少东西,它自动就把空间变得那么大 不需要空间那么多的时候它自动又把空间缩小,这才是最好用的东西 对吧,那你真的有这好东西吗?当然有 我们学了这个印象对象里面这个印象重改的概念 加入语言里面就能够实现这样一个数,现在我们来看一看 我们说的是可变长整型数组,这个例子啊 假设我们写了这可变长整型数组类,它的名字叫做这个CArray, 它这个能够把下面这个来使用,比如我们定了一个Carray的对象a, a 就代表一个数组。一开始定义的时候a 是一个空的数组。 什么元素都没有,那接下来呢,要用数组的循环,要用数组的这个 push back 成员函数,把5个整数塞进去了,就0到4都塞进去 再说这数组里面呢,都变成有5个元素了 再接下来呢我们定了两个对象a2,a3 它们分别都代表一个空的数组 然后呢我们让这个a2等于a, 那我们希望这效果 a2是新的数组,它里面的内容跟a 的内容是一模一样的 但是a2和a 呢这两组数组的存组空间是隔开的 互相不影响的,这以后,a2是a的负责品 所以我们接下来的事情是把a2所有的内容给它输出 在这里呢我们变了一个数组呢要用到了a点length 这个a的长度,那a2的长度也就是a的长度,这两个是相等的对吧 那我们就让i, 从0走到a点length减1 然后呢就变了a的整个数组,然后把a2数组的每一个元素都给输出 那么这个循环我们希望它输出的结果是0,1,2,3,4 因为a2里面放的就是1234嘛, 再接下来呢我们让a2等于a3. 这个时候我们希望的结果是什么呢 是a2变成和a3一样,那a3呢是一个空数组,那这个时候a2也变成一个空数组 但是要注意,a2 它原来所占有的存空间应该被释放掉 下面这个循环 我们希望它是没有任何输出的 为什么它能做到没有任何输出呢 因为这个时候a2已经变成一个空数组了 既然是一个空数组里面就没有元素, a2点length当然也是0 所以这个循环里面其实不可以进行任何 因此整个循环没有输出,接下来换了一行,输出这里也换了一行 然后呢我们让a下标为3的这个元素等于100,这a 是一个数组嘛 它下标为3的元素被扶正成100。下面我们会看到这个变化 下来我们再来一个对象a4 , a4是一个新的数组,它是用复制过的函数初始化的 那这样我们看的话,a4是等于a 的一个复制品,那a4的内容就应该跟a一模一样 但是a跟a4是分别存储在不同的地方的 a跟a4里面的数组存储在不同空间里面 好那a4跟a既然是一模一样,接下来呢我们就把这个a4的内容全部给它输出 那当然输出结果当然是什么0, 1, 2, 100, 4,为什么是100呢 因为当初a 下标为3的那个元素被改了对吧 好下边我们整个CArray,CArray呢就是整个可变长整型数组类 我们希望能在这个程序里面能够使用它达到我们想要的结果 那我们写这个CArray的时候要做哪些工作呢 首先我们第一点想到的是,我们肯定写一个类我们要写 构造函数对吧,还要写析构函数真的,然后像这种什么push_back, length这样的成员函数,也当然肯定要写的对吧 然后除此之外我们还要解决一个问题 就是这样CArray的对象,它代表一个数组,它里面需要存放一个数组 那这个数组到底存在那呢?当然我们会需要有一片动态分配的存组空间 来放这个数组,对吧,因此我们就能够得出一个结论,那就是 首先我们要用动态分配的内存来存放数组元素 那这一片动态分配的内存你肯定要有一个指针要指向对吧 那我们这个CArray里面就有一个指针成员变量是吧 然后呢这个动态分配内存要释放,那在这个CArray里面什么地方 释放动态分配的内存最合适啊,当然就是在CArray里面的析构函数里面就释放这个内存最合适 对吧,好了然后,然后我们看到 这有一条复制引句,这复制引句复制号 我们是不是需要重载啊 这个我们参考前面这个前靠背生考背这个例子,我们知道这个 这样复制号要重载了 然后呢我们看到,这个a2它实在是个对象,但是我们把它用起来的时候 就跟数组的用法是一样的,对吧,因为以前我们看到这个中标号 能怎么使用啊,中号的外面我们从以前上的知识,只能是一个数组的名字 那a2它实际上不是一个数组的名字,它是一个对象的名字 那我们要让这个对象名字加中号也能够成义的话我们得做什么 我们是不是要重载这个中拔号,对吧,重载这个中号 好所以我们知道要重载这个中号,那还要做些什么事情呢? 我们看看在这,这里用到了这里的CArray的复制 的函数 这个复制的函数能不能用什么呢,大家想想以前所想的前拷贝生拷贝就知道了 它不能用缺省的,为什么呢,可以再解释一下 我们要自己写一个复制构造函数,这是我们要做的一些事情 当然还有些别的。现在我们来看一看这个CArray这个类应该怎么 编写,首先,前面说了我们需要一个指向动态分配的存储空间 因为我们这个数组不是有一个length存在函数对吧,它能够返回数组的长度 那怎么知道数组的长度啊?我们就要用一个size来编,记住这个元素 的个数,然后还要什么呢,我们需要构造函数 有一个普通的构造函数有一个复制构造函数还需要析构函数 这普通的函数这个s呢参数是代表你,一个数组对象,数组化的时候 它里面已经就包含了元素了,这样意思我们等会再解释 然后等会我们还要这个push back 这个成员函数 它用来干嘛呀,用来在数组的尾部添加一个元素 我们当然还要一个印象呼,它用来做完数组之间的这个复制 实现它需要时间生拷贝的这个操作 然后这个length成员函数返回数组元素个数当然就这么写 接下来还有,一个很重要的这个中号 我们把它改成一个CArray的 成员函数,那中问号是双问双符,大家问的这个双问双辅 它的操作符在那呢?一个操作数在中号外面,一个操作数在中号里面 那现在这个双问双符我们把它存改成这个类的成员函数以后 那它就变成应该只有一个参数了对吧 那这唯一的参数应该是什么呢 当然就相当于数组的下标,那个下标呢就是一个整型数 所以这个时候operator 中号就它只有一个整型数i i 就代表数组元素的下标,那我们这个operator ,这个中号能有什么的目的呢 需要能够实现 这样的例句,能够愿意通过而且呢 达到我们想要的这个效果,那你比方说 说n 等 于a i , 那你就应该能够取出 a i 的复制给n, 对吧,所以这个a i 这个表达式啊 它应该能够发挥,1里面所存放的数组 里面下标为i 的原数的值对吧 那 a 里面存放的数组, a 放在那呢,当然放在有批价那一片零序的存储空间 对吧,那下标为i 的的那个元素,实际上就是什么啊 实上就是这个ptr i, 那我们n 等于 a i, 那我们最希望产生的结果当然就是 n 等于ptr i, 对吧,因此,我们在这里就应该 return ptr i 就行了,但是这里的返回值是什么类型的呢? 我们还是要思考一的下,啊,到底是什么类型。 哎,公布答案,哎,返回值的类型应该是int的引用。 首先从这个类型匹配的角度来讲,你这里return的是ptr[i]。ptr[i]是一个 int,对吧?ptr[i]是个int那么你这个地方从类型匹配的角度来讲就要么是int;要么是int的引用。 那,嗯,正确答案是int的引用。那返回值为int为什么不行呢? 啊,返回值为int的话,要实现n=a[i]这个目的是没有问题的。 但是你想要实现a[i]=4这个目的呢就不行了。为什么啊? 嗯,我们知道这个一个函数的 调用的返回值如果它不是引用的话,我们是不能够把一个函数调用的返回值写在等号 左边的,对吧?就是非引用的函数返回值不可以作为左值使用。 所以,只要这个函数它返回值不是引用,那么你写这条语句编译的时候就会出错。 啊,另外, 很重要的一点,就是我们写这一条语句,嗯,a[i]=4我们是不是要希望能够修改a[i]的值,对吧? 也就是说我们希望达到的目的是,a这个数组里面下标为 i的那个元素,它的值应该被修改。 那,那这个a数组里面下标为i的那个元素是什么啊?是不是就ptr[i]啊? 啊,那么如果 我们希望这条语句能修改ptr[i]的话,那,那这个 这个赋值号左边应该是什么呀?当然就应该是ptr[i]的引用,对吧? 嗯,只有左边是ptr[i]的引用,那么我们让这个 a[i]等于ptr[i]的引用,然后再让它赋值4的话那就会导致ptr[i]被修改了。 所以这个时候我们要把返回值定义成引用。 唉。 再往下看。 现在我们要说到这个构造函数了。啊。这个构造函数呢他有一个参数s. 代表这个,嗯,数组下面被初始化的时候它里面就已经包含多少个元素了。 前面看到这个s有一个确审值是0。就是说你一个数组 如果,数组对象如果被初始化的时候没有指定,嗯,这个参数的话,那这个数组就是空的,里面没有任何元素。 那好,就是说s=0,这个数组就应该是个空的。拿这个ptr呢我们就让它等于NULL。啊,它不是,不指向任何地方。 那,嗯,这个后面呢这个初始化列表已经用s去初始化size了,就size 会等于是s了。 嗯,那,那如果这个s是不等于0的话呢,那我们就要动态分配一个存储空间,对吧? 因为这是一个整型数组,所以我们new一个 整数数组出来。嗯,这个new出来的整型数组呢,它里面有s的元素。 然后我们把这个整型数组的地址赋值给ptr,这就完成了这个构造函数的这个作用。 那下面我们再看,啊,我们还需要写这个 赋值构造的函数。啊,赋值构造的函数呢要完成 嗯,前几章所说的那个深拷贝的这个这个工作。就是说 它需要使得被初始化的那个的对象的内容,变成跟这个a一模一样。 啊,当然这两个对象它不能指向同样的存储空间,对吧? 我们举,嗯,形象的来看看。假设这有一个a1,啊,a1它里面的ptr指向一片 存储空间。这存放的是数组a1的内容,啊。如果们来执行的这个,嗯,CArray a2 (a1); 这时,我们说a2就是a(1)的一个赋值品。那这时我们想要达到的效果是什么呢? 就是你先想,如果我们不自己去写赋值构造函数。 那么执行完这条语句以后,a2会变成什么样? 什么样啊?那它就是这个样。啊,就是a2里面的ptr 会和a1里面的ptr指向同一个地方。为什么呀?因为我们 不写赋值构造函数的话,编译器自动生成的那个赋值构造函数啊 它会执行赋值的功能。这里所说的赋值的工作, 只是把a1的成员变量赋值到a2里面去。那a1的成员变量ptr 被赋值到a2里面去,那自然a2.ptr就等于a1.ptr。那也就是说, a2的ptr和a1的ptr都指向了同一片存储空间。 对吧?那这个时候我们前面,嗯,前面学的知识,这个是会有问题的。 啊,这是不对的。那么我们正确的结果应该是什么样呢? 嗯,就是这样。就是a2的ptr指向新的一片存储空间, 然後呢,这片新的存储空间的内容,嗯,跟a1ptr指向的存储空间是一模一样的,对吧? 嗯,这个才是我们需要达到的这个效果。那么为了达到这个目的我们应该怎么做呢? 就要看,啊,这个是赋值构造函数的写法。 嗯,在这里呢我怕们要首先判断一下这个a.ptr是不是空。如果a.ptr 是空,也就是说a本来就是一个空数组的话,那我们得让被初始化的这个对象也变成空数组。对吧,所以我们就简单的 把ptr变成NULL,然後size等于0就返回了。那如果 a它不是一个空数组,那我们就要把,嗯,那我们就要把被初始化对象里面的ptr 把它让它指向一片新分配出来的存储空间。然後,a数组里面的 内容拷贝到新分配出来的这个存储空间里面去,对吧?所以就是ptr=new int这样的,啊。 然後呢把,这个就是把a.ptr所指向的 那部分的内容拷贝到ptr所指向的地方去。那一共用拷贝 多少个字节的内容呢?就是这个sizeof(int)*a.size ),对吧?然後我们 把size的成员变量也给他赋值一下。啊,这个就是赋值构造函数。 嗯,那接下来再看,嗯,还有这个 析构函数。那析构函数做什么呢?答案就是释放动态的存储空间。 那释放之前我们也得判断一下,啊,这个ptr是不是空指针。嗯,不是空指针你才去定义的,对吧? 好了,接下来还有这个,嗯, 我们前面说的这个赋值号也需要把它在重载一下。嗯, 重载的目的是用来实现两个数组对象之间的赋值。那两个数组对象之间的 赋值呢,我们希望达到那种深拷贝的这个,这个,这个效果。也就是说,你写了a1 等于a2的话,那,嗯,a1 的存储空间里面放置的东西就应该和a2的东西一样; 但是a1和a2呢,它们用来放东西的存储空间又是各自独立的,对吧? 所以这个跟那个赋值构造函数是挺像的。但这边有个小trick,就是我们首先的判断一下, 这个,这个 a的ptr 和被初始化,和被赋值的这个赋值化左边的这个对象的ptr是不是相等的。啊,因为 搞不好会出现a等于a这样的赋值,对吧?那你如果不做这个特判,那可能就会出错。 所以我们先判断一下,如果ptr=a.ptr那我们就什么都不干,直接return*this就行了。 就是 这个函数她的返回值是CArray的引用。这是为了符合这个 赋值号的这个使用的这个惯例。因为在C++语言里面 嗯,赋值号这样一个赋值表达式它的返回值本来就是那个赋值号 左边的那个,那个变量的引用。啊,我们遵循这样的习惯,所以,这个 赋值号被重载以后的返回值仍然是CArray的引用。 嗯,那我们为了类型匹配,当然就return*this, 对吧? 好了,接下来我们还要看到,如果a.ptr是NULL的话; 那我们就要把,嗯,被赋值的这个对象也变成一个空数组。 那怎么变成一个空数组啊?当然就要ptr-NULL就行了,对吧?但在此之前呢,如果 被赋值的这个数组本身它已经不是空的,那我们就得要 回收它的存储空间。嗯,所以在,在这个前面我们还得先判断一下如果ptr 不是空,就说明这个被赋值的这个,这个数组啊,它原来,嗯,有东西,那我们就要把它的存储空间给它收回。 那这里面还有一点技巧啊,就是,嗯,就是 如果我们每做一下数组,两个数组对象之间的赋值都需要重新分配存储空间的话, 实际上也是有点浪费。所以我们可以这样,我们判断一下,嗯, 两个size。这个size是赋值号左边的那个数组对象, 它的大小,对吧?a.size是赋值号右边的那个, 嗯,数组对象的这个大小。那如果被赋值的那个 数组的容量已经比赋值号右边的那个 还要大,也就是原有的空间已经足够大的话,那我们就干脆不要分配 新的空间。我们就在原有的空间上把新的内容拷贝过来不就行了,对吧? 所以说,所以说仅当这个原有的空间不够用的时候; 我们才去释放原有的空间,并且重新分配新的空间。 那如果原有的空间足够用了,那我们就直接把新的内容给考过来就行了。 那,memcpy,拷贝新的内容过来就行了。然后,当然size只需要赋值一下,啊,return*this这就可以了。 那最后呢,还有一个,嗯,这个push_back的成员函数 是我们必须写的。啊,push_back的作用就是在数组的尾部添加一个元素。 啊,那我在这里给的是一个写起来简单,但是 效率比较低的做法。啊,效率高的做法呢,嗯,大家可以自己回去实现一下。 嗯,在数组尾巴添加一个元素,那这个时候呢正数组的空间就要重新分配了。 那我们首先先判断啊,如果这个数组原来还是有东西的,那我们就重新分配 这个,嗯, 存储空间。啊,这重新分配的存储空间呢,要比原来的这个数组元素个数还要多一,对吧? 这新分配的存贮空间,那我们用一个临时的指针指着。 然后呢,我们要把这个,嗯,原来数组的内容拷贝过来。因为, push_back是在数组的尾巴加一个元素,对吧?所以你要把数组原来的内容给拷贝过来。 拷贝到这个临时指针所指向的地方。然後呢,我们再把原来那片存储空间给释它放掉。 啊,然後在让这个ptr指向刚才那个临时的指针。啊 ,那临时的指针已经指向新的数组空间。 那,新的数组空间比原来那个空间多一个元素,对吧? 嗯,这是分配空间。 那,这是,ptr原来指向一片存储空间的时候,我们要 重新分配空间。那如果ptr原来就是空的呢,那我们就直接分配一个元素的存储空间就行了。 那,这些分配空间的事情全部做完以后,我们就要把这个 嗯,v这个元素放到这个新分配的存储空间里面去了,对吧? 那这条语句就能够在新分配空间的最后的那个位置把v放进去。啊,那这个push_back呢它显然是 嗯,比较低效的。因为你每一次往这个数组添加一个元素,它都要重新分配存储空间。这个 时间上是有比较大的开销。嗯,那比较好的做法是什么呢? 嗯,就是说我这个,空,空间嘛可以预先分配多一点。 比如说这个数组哪怕是只有一个元素,我一开始也给他32个元素的存储空间, 那么,这个数组的元素个数1 变到32的过程中,我这个空间都不需要重新分配。 对吧?然後你加第33个元素进去的时候,我一下就让这个数组的空间里面变成有64个。 那从33到64这部分,嗯,元素添加的时候呢,也都不需要再重新分配空间了。啊。 然後,你要添加到6,第65个的时候,我让数组空间一下子变到128个。啊 以此类推。这样我们重新分配存储空间的次数就会大大减少。当然 这个效率也就,嗯,提高了。 那实际上这个c++,嗯,嗯,标准模版空间有一个vector就是这个可变长的数组, 它的,它也有push_back的成员函数,然後它push_back的实现方法, 就跟我刚才说的这样,采用一种比较搞笑的实现发方法。