因为就是说,即便a不等于b的话,a<b,和b<a也有可能
同时都不成立。就取决于你这个"<"是怎么编写的嘛。因为"<"有可能被重载, 那max_element也类似啊,它找最大的元素。
最大,什么叫最大啊?就是它不比任何其它元素都小,那它就叫最大。
那么它也是可以用这个"<"来进行作比较器的。它确实,这个版本就是用"<"作比较器的。
那我们来具体看一个例子。(怪异的例子啊),那么在这个例子当中的话呢,这个
我定义好了一个类A,然后呢,它中间包含了一个
这个int型的成员变量,然后我们还定义了一个构造函数,让它来初始化,用这个参数i来去初始化这个n。
然后大家来具体看看郭老师这个很怪异的operator<。
对,这个"<"嘛, 它确实比较独特,精神病,它每次被
调用都会输出一个called,但是它规定只有3<7,
这是true的,所有其它情况,
a1都不会小于a2。只有a1.n==3.a2.n==7的时候,这个小于号才成立。也就是说
在这个"<"的定义下面,任何两个class A的对象比大小,
返回都是false,除非,第一个是3,第二个是7。
7,啊只有3<7是成立的。那现在我们在这个数组a[ ]里面, 去求最小
元素,求这个最大元素,那我们先看看,这个求最小元素。这个
函数被调用被执行的时候会有哪些输出啊, 它输出的结果是啊,4个<called,然后输出一个3.
这是为什么呢?是不是很奇怪?对呀,因为明明这个
最小的这个数组里面明明有1和2的存在,为什么输出的居然是3呢?
就是我每次讲到这个,呵呵,在课堂里讲到这段,这段代码的时候都感觉就在台上很囧,
嗯,好啦,这个问题是这样的,就是你想一下,如果你写这个min_element,
你怎么写呢?你只能顺序遍历整个数组对吧,那你肯定要有一个,一开始要假设一个最小值,
那那很自然的你会假设,第一个3是最小值,就是一开始我们假设3最小,对吧。
接下来我们就会拿3跟其他的去比较了,比如说,3跟5比较
这个时候呢,min_element是怎么比较的呢?它是去看5<3的值是不是为真,
如果5<3为真的话,那么5就是这个
比3更小了。那这个时候最小值就要被更新成5了。当然在这个例子里面
显然不是,对呀,所以3现在依然还是最小的。然后又比较7<3,
啊还是假,所以3还是最小。然后2<3,1<3,全都是假, 啊所以经过四次比较以后,这个3依然是最小的。
就被记下来了,3是最小值。那刘老师你来说说max的情况。
那么在max这种情况下的话呢,我们是去比较这个 呃首先是应该是去比较
3和5是吧。对,先假设3是最大值,对,然后假设3是最大值,
然后去比较3和5.然后这时候你会发现说呢这个确实是满足这个
3<5不成立,然后这时候就会被
3依然最大,替换成7和3进行比较。然后因为我们在刚刚的这个operate小于号里面
定义说当满足一个是3一个是7的时候,它就自动会变成是TRUE了。
所以那么这个时候的话呢,我们就会认为说这个 呃更大的这个值是7,然后这个7的话呢会进一步去跟这个2比较,
然后它也是false,那么这个时候就会7会去跟1比较,然后所以呢最后返回的最大值就是7了。
啊,对。那就是min 和max它工作的这个过程,
呃就是这样的。有点怪是吧。但是我觉得可能知道这个还是有必要吧,所以就讲啦,就这样了啊。
OK,那我们先进一步, 再看变值算法,那么第二类呢就是这个所谓的变值算法,那么这个变值算法的话呢,它实际上刚刚
对应第一类呢我们刚刚说到它不会去修改就是这个算法作用在这个容器上的话呢它不会去修改任何容器中间的元素,
但是变值算法的话呢它就会去修改这个原区间或者是目标区间中间元素的值。
然后注意这个有一个区间的值如果被修改的话,这个区间它不能是属于关联容器的。
那为什么呢? 啊考我吧。啊你知道吧,那你说吧。对呀,这个因为本身关联容器大家也应该知道
说它实际上是一个具有排序好的这样的一个容器,那如果你
直接就通过这种变值算法去进行修改的话,那么这个容器当中的这个所有的这个排序好的内容就被
彻底打破了,如何你在做其他的操作的话,有序性破坏了啊,如果你去做其他操作的话呢,实际上就是都是,可能就会出现
不一样跟预期的结果不一样的值。你要查找可能就找不到了。
嗯,所以变值算法不能够使得关联容器上面的元素被改变。
那变值算法有这个几个啊,有这个for_each,
它用来对区间中的每一个元素都做某种操作,那这个操作它是
可以改变这个元素的值的。那这个版本跟上一个版本就在于参数 呃,
那个参数是一个,可以是一个函数或者函数对象,然后那个函数对象或者函数的那个参数它一个是const,一个不是const。
说起来挺罗嗦的,大家自己去查查吧。呵呵,然后呢,第二个这个算法的话就是copy,
copy的话呢我们通常直观上根据这个词也能想到,它就是去完成一个复制其中
一段区间到另外一块儿的这样的一个操作,那其实copy本身呢它还有一些
呃我们会在后续的例子当中看到它有一些独特之处,我们还可以通过一些其他的方式,通过COPY来实现一些
其他的这个功能,对,就是X+里面的算法吧,你看名字好像觉得它应该只能干这个事情,你说COPY你就觉得它
只能是干copy的事情,其实不是的,它里面只不过是一个代码的形式,你用 这个模板能做什么是取决于你的想象力的。
我们后面会看到COPY的习惯用法。
还有这个copy_backward,它也是复制一个区间到别处。但是它跟COPY的差别在于
它的 在目标区间是从后往前被修改的,就是拷贝的时候是从后面到前面拷贝的。
copy呢是从前面到后面拷贝的。
那郭老师你要不给大家介绍一下说为什么会考虑从后往前这种操作呢? 我不刚刚教你了吗,你说一下。
呵呵呵呵。挖坑,我想给郭老师挖坑没挖住是吧? 那么郭老师刚才在我们在第一遍录的时候,
郭老师就提到这个本身这个函数这个算法呢 它呢实际上主要是当你它主要考虑说两个这个区间本身有重叠的时候,
那么如果你都是从前往后去来进行这个拷贝的话呢,
那么可能就会出现后面的这个内容在没有拷贝之前就已经被这个覆盖掉了。
对,就重叠部分的内容还没有被拷贝到目标区间就已经被覆盖掉了。
所以呢,那么有了这个copy_backward之后的话它就是它实现的实际上是从后往前拷贝。对,就能解决这个问题。
嗯,好。然后还有这个transform它是把一个区间的元素变形以后拷贝到另外一个区间,那也就是说
你用一个区间的元素作为函数的参数算出一个什么新的值然后 把它新的值弄到另外一个区间去,基本上就是这个意思。
那么除此之外的话呢还有一系列变值算法, 其中就包括这种swap_ranges,那么它主要是用来去直接交换两个区间之间的内容的。
啊那念swap啊,我一直念swap.
到底是什么呀?swap,呵呵,好啦,那就swap吧。
还有,还有这个fill,fill是用某个值填充某个区间。然后你也可以fill_n,
就是刚开始用某个值去填充区间,那么这时候的话可以直接去替换区间中的n个元素。
generate就用某个操作的结果填充区间,它就填充的办法可以更复杂一些啦。对,它是一个操作啊。
然后完了之后的话呢,就replace的话,就是将这个区间中的某个值替换为另外一个值。
replace_if 就是把符合某种条件的值换成另外一个值。
然后还有这个replace_copy,也就是说我们把一个区间拷贝到另外一个区间的时候,
那么拷贝时候的这个值要替换成一个新值拷贝过去。就是不是原模原样的拷贝啦, 那这个跟replace_if 感觉好像蛮类似的啊。
呃replace_if 是替换一个区间里面的元素,
replace_copy是把一个区间的元素拷贝到另外一个区间,考过去的时候 还要做一些操作。replace_copy它不会改变原区间的内容。哦。
然后这个replace_copy_if 就是把一个区间里面
符合某种条件的那个值拷贝到另外一个区间。那拷贝过去
的又不是原有的东西,而是把原有的东西经过某个计算变形以后, 变成新的值。我觉得后面这几个做法实在都,
确实很少用啦,还不如自己写呢,你自己写也就是两三行的事嘛,去记它还累的半死。
OK。那这个transform的话呢,本身这个操作呢,
这个算法呢它实际上就是说,对这个我们定义好这个区间当中的每一个迭代器,都会相应的去执行这个
我们定义好的uop这样的一个操作。然后并且把这个结果呢就依次放在这个从x开始的这个地方。
嗯,对啊。然后还要求这个原区间是不会发生变化的啊,
它返回值也是一个迭代器,就是目标区间被考过去的最后一个元素的那个,后面的那个
那个位置,然后x和first可以相等。就是它的原区间和目标区间嘛,它可以重叠, 就是这个意思。嗯。
那我们来具体看这个关于这个变值算法本身的一系列的这个,啊对对对,例子。还有包括clactstorm.
这里面有一个ClessThen9,它存在了一个这个圆括号,这个函数对象类。
它实际上就规定了一种条件,这个条件就是 返回<9的值,对,看这个n是不是<9.如果是的话就返回true。
那完了之后呢还定义了几个这个,呃全局函数。
对对,呃。一个是outputSquare,主要就是用来去计算对应这个value去求这个
平方的这个值。把平方值给它输出出来。然后还有这个calculateCube,
cube还是cube?cube吧。哦,OK。然后呢它主要就是去计算这个立方的这个值。然后它就返回立方,立方的值。
然后在main里面,这个好长啊,对啊。呃。
接着读,这个这个a1里面有1,2,3,4,5,6,7,8,9,10啊,a2看上去没有什么顺序可言。
然后这个呢就把a1的内容全部搞到v里面去了。定义好了一个vector,
对,然后下面这个东西好古怪啊,太古怪了,你来说。这是什么东西啊? 这个我也没有看懂耶。呵呵,不会吧你装的。
这主要是郭老师挖坑后面具体讲这个copy这个
具有想象力的这个算法。这个东西吧,ostream_iterator是X1里面自己定义的一个内模板啦。
然后,你就知道它叫ostream_iterator就行了啊。然后把这个内模板实际化,啊
整型常数把它实际化,它的意思就是说以后你要通过这个ostream_iterator<int>去输出的东西都是
都是这个int类型的。好,这个,这个我们现在就定义了一个ostream_iterator<int>这种类型
的对象,它的名字叫做output。然后我们初始化这个对象的时候呢,给了两个参数,一个是cout,
一个是这个空格字符串。那就意味着以后我们把什么东西交给output的时候,
都会出现在 都会等价于交给cout,输出到屏幕上。而且被输出的东西,
都是一个个的整数。而且每输出完一个整数,后面都要加一个空格。
这句话就是这个意思。具体怎么实现呢?我们等会再看。
啊,下面这个东西。然后就是对应这个random_shuffle,这个算法实际上应该
不算是在这个变值算法。它叫变序算法。对。然后它主要是用来这个
随机的这个,打乱一下这个,我们刚看到对应的这个vector v中间的值。
对,这个,这个东西特别好使。因为有时候你经常会需要随机打乱一个数组,比如你写一个打牌的程序什么的。
你要随机给大家发牌,你就会把所有的牌先random_shuffle一下,对吧,这个真的特别好使。这个是伪随机吧?
当然是伪随机了,计算机不能实现真正的随机啊。然后呢,
完了之后呢,我们就让它这个cout一下,我们就可以看到说这个
我们先去看输出的结果吗?对,对对,就是这个编号为1的这个 这个输出啊。它是5 4 1
3。啊,那这块,当然你每次 (因为它是随机的嘛),每次都不一样,接下来就是问题的重点,就是这个copy
copy我们前面说到它是用来把一个区间的内容拷贝到另外一个区间里面的。
现在我们执行的这个东西,这个output它好像不是一个容器啊。它确实不是一个容 器,它也不会是一个什么区间的开始位置,但是我们确实就可以这样用copy。
然后copy以后,输出的结果,这是会导致输出的哦。输出的结果,是什么东西呢?
啊,这个输出结果实际上就是这个,刚才这个随机的结果被输出了,就是在这条copy语句里面导致它输出了。
对,你会发现,1和2之间没有任何其它的这个直接输出的语句。
通过copy来实现的。对,就是通过copy来实现这个输出的。然后我们看到输出的每一项都是整数,然后
输出的每一项后面都被加了一个空格,当然这个2后面也是有空格的啊。这也就是copy所做的事情。它为什么能做这个事情呢?
这个我们一会再讲,卖个关子。好。
接下来这个干嘛了?接下来这个copy实际上就是执行了一个copy的工作。
啊,对。它是把a2的内容拷贝到v里面去。啊,但这时候要提醒大家
一下啊,这个copy操作,它要求这个目标区间的这个位置,它本身有足够的空间。
就是比如说,你在这里copy了SIZE这个元素过去,那么在目标区间里面v.begin开始往后的地方,它应该有
至少有SIZE这么多个元素的空间。要不然就会怎么样啊?要不然就会相当于数组越界,就会出错了。
就是事先你要为目标区间已经准备好了空间,你才能拷,没有空间你是不能拷的。就是说这个事情,主要是要程序员
本身来保证的。对,是的啊。好了。然后接着我们应该把这个a2整个copy给了这个v之后的话呢,
那么我们就可以相应地在这个v上进一步做操作。对。我们就
进一步去cout这个,第二行输出的什么呢?输出的实际上是这个
count。我们在这个v之间去count 8。
出现了这个8这个元素。有几个?对,然后呢,这时候的话,我们会看到,因为现在这个
v变成了是a2这个数组。然后这时候你会发现说对于a2这个数组呢,出现了两次8。
对啊。所以这时候我们count一下的话,就可以对应输出就是2。然后同时的话,
你也可以count_if,也就是说我们按照某一个条件限制下去count这个相应的值。
那么我们对应的话呢,就是我们刚才定义好的这个CLess Then9这样的一个类。
这是个函数还是个类?嗯, 这个是个对象,(哦,这是个对象)它是个函数对象。CLess
Then9是一个 我比较喜欢中英结合啊,你就会CLess Then Nine,哎哟,高大上。我是CLess Then9。
好吧,这个CLess Then9吧,是一个类的名字,对吧。那 它后面加了一个圆括号,就成了一个对象的名字了。这个对象呢,它没有名称。
那这个CLess Then9这个对象是怎么起作用的呢? 就是说count_if,假设我们把这个对象名字叫做op吧,那么
count_if在计算是不是有某个元素符合条件的时候,
它所谓的条件是什么呢?就是op(e)=true。
就是e就是它要考察的一个个的这个v里面的元素。如果发现op(e)=true的话,
那count_if就认为哎,我找到一个元素了。那算一下,有多少个?
多少个元素满足op(e)=true这个条件呢?那op(e)是什么呢?就是op.
operator(e)。对吧。那么这个op.operator(e)是什么呢?就是前面看到的CLess Then9。
这块的这个,n<9了,对吧?所以在这里就统计出来了小于9的元素的个数。是什么呢?
小于9的元素个数有几个啊?有6个,对吧?算一下,就是6个吧。
肯定不会错啊。所以就是第三行数据就是这样了。然后你继续。
然后接着话呢,我们就要去,实际上去找这个min_element,我们去对应这个在这个v这个
数组中间的话呢,去寻找这个最小的这个值。当然我们在这个例子当中,并没有自己去定义,额外定义一个
operator<,所以我们就满足直接找了一个最小的值。那么现在这个v的话呢,就是刚才看到的这个
a2数组。是a2数组吧?对啊。这时候,a2最小值就是(最小就是数学上最小的意思)这个1。
就是1,嗯。然后呢,相应的,如果是max_element的时候的话呢? 我们找的就是最大值。我们对这个a2这个最大的话呢,就是刚才看到的这个100。
然后此外呢,还有这个accumulate,这应该是一个,
(累加)算是计算的这个,算是是数值计算的,(数值算法)数值计算的
一个算法,你就把刚才这个a2当中数组中间所有的这个值累加起来的话呢,
那么一算,计算机就得到是193。那这个accumulate的源代码是什么样的?得放到前面去了。
就比较啰嗦了。这个就先这样吧。为什么要放到前面去啊?accumulate的源代码在哪里啊?
accumulate就是累加,是吧。(这不是就是用的现成的)对对,没错,我的意思是
你要理解accumulate是怎么工作的。就得看accumulate的源代码。前面有没有出现过啊?我已经忘了。
好像没有哎。是吧,呵呵。反正它做一个累加的工作,从v.begin到v.end,
累加到初始值0上面。如果你这个初始值是0,所以就是求这个的和了。
好吧,那我们再看这个for_each。
for_each是干什么的呢?就是对于v.begin,v.end里面的每一个元素e
它都会去执行output
square(e)。它就干这个事情。
嗯,就是去计算相应的这个平方,然后输出出来。是啊,那个output square
是什么呢?我们得回忆一下,是吧?要不都忘掉了。output square在这干什么啊?
它把参数的平方给它输出了,再提醒一下啊,这里有 calculateCube是返回这个参数的立方哦。
好吧,那我们就看这个
呵呵 在哪里啊?再往前,很前很前。