嗯,接下来我们讲一讲这个流插入运算符和流提取运算符的重载。 那我们先引入一个问题。嗯,这个问题就是,诶,我们很经常写的这种形式的东西它为什么能够成立? 这不是<<算符吗?它为什么能够用在cout的上面,并且把这后面的东西都给它输出呢? 啊,於是我们就会想这个cout到底是什么东西? 还有,为什么这个<<运算符能够作用在cout上面? 那cout是什么呢?嗯,cout实际上是在iostream这个头文件里面定义的一个 对象,啊,它是哪个类的对象呢?是ostream这个类的对象。 那当然ostream这个类也是在iostream这个头文件里面定义的啦! 那这个<<运算符他为什么能够用在cout的上面呢? 那我们学过运算符重载,当然你能想象得出来。 嗯,这个<<运算符能用在cout的上面就是因为,在iostream这个 头文件里对这个<<运算符进行了适当的这个重载。 那么大家就可以想象一下这个重载应该是什么样的, 怎么重载才会是的这两个表达式都能够成立呢? 嗯,那我们知道<<运算符必须重载成一个函数,对吧?那 我们可以把它重载成ostream类的成员函数,也可以重载成一个全局函数。 当然,这,这实际上是取决于人家那个iostream头文件怎么写的,对吧? 那我们假设如果我想把这个,嗯,<<运算符重载成ostream这个类的成员函数的话。 大概应该怎么做呢? 嗯,就是我们有可能按下面这种方式 把这个左运算符重载成ostream类的成员函数,对吧? 嗯,假设这个返回值我们没考虑好就让它 是viod的吧!反正是void看起来也无伤大雅,对吧? 也许有更好的做法,但我们先让它是void。 然后呢,这是重载成成员函数了。然后它所接受的参数是什么? 很明显我们可以让它接受一个整型参数,因为我们前面用到了这个cout<<5,对吧? 那显然这个5就是一个参数。所以说我们为了对付这个表达式的话,我们需要做一次重载, 重载的这个函数它应该是int n。 然后在这个重载的这个,嗯,函数里面呢我们在前面当然要把 n给它输出了,对吧?至于n到底怎么输出,那你肯定不再是不会再用什么cout<<于之类的东西了。 假设,假设这个编写这个ostream类的这个,嗯, c++的库的设计者他,他有一些其它的什么办法能够把n 在屏幕上给它输出。那我们对这个<<运算符进行如下的重载就能够应付 这个表达式,使得这个表达式编译能够通过,而且5能够被输出了,对吧?那怎么对付这个表达式呢? 那当然就应该再做另外一次重载。啊。 就我们看cout<<5 这个表达式它等价于什么呢?它等价于cout.operator<<5,对吧? 因为这个时候<<运算符被重载成ostream类的成员函数了。那么实际上 这条语句就是在cout这个对象上面调用operator<<这个成员函数,并且以5作为参数。 那cout<<"this"当然就是cout.operator<<(”this“),对吧?那现在我们的问题就是, 如何,做一些什么样的重载才能够是这个表,这个,这个长串的东西能够成立? 如果我们是仅仅是想让这个,这个表达式和这个表达式分别都能够成立的话, 嗯,很简单,对吧?我们重载两次,一次参数是整型的;一次参数是,是 是差型的就行了。现在要让一个,嗯,这个四子联着写都能够成立我该怎么办呢? 这时候你就得考虑 这个表达式的返回值的问题了,对吧?C<<5这个表达式它的返回值如果是void, 那么这个void的还能够接着往下运算吗? 就不行了,对吧?那么cout<<5我们得让它有一个 返回值。这个返回值是什么东西才能够接着往下运算,并且把"this"也给它输出呢? 那答案很直观的一个想法就是,如果cout<<5它的返回值还是这个cout, 那么,作为返回值存在的这个cout就能够接着再往下跟”this“进行这个运算,然後就能把"this"输出了,对吧? 这是一种很直观的想法 。因此,我们就可以 修改一下前面存在左运算符的这个,这个方式。我们在这里呢把这个返回值的类型改成了ostream的引用。 注意啊,我们现在的目的是想要使得这个<<运算符 它能够返回cout。就是cout<<5这个表达式它能够返回cout。那怎么做到这一点啊? 我们就让它的返回值是ostream的引用,然後我们在这个成员函数里面 return*this。那这个*this指针是指向了这个,嗯,成员函数 做作用的对象,对吧?那我们如果,嗯,执行cout<<5,那这个this指针当然就指向了cout了,对吧? 那*this就是cout。 那这个成员函数它的返回值是一个ostream类的引用,然後我们return*this *this就是cout的话,那实际上这个函数的返回值 实际上就是 ,就是会是cout的引用,对吧? 那同理,我们,嗯,对这个, 以const char* 为参数的这一次的重载 也要把它的返回值设置成ostream引用。那么我们就可以看到 这样一条,这样一个表达式 我们把它,嗯,写成函数这样的形式它会是什么样的东西呢? 它就是cout.operator<<(5).operator<<("this")对吧? 因为我们看到前面这部分它当然就等价于cout.operator<<(5),对吧? 那现在我们让这个部分的返回值还能够接着往下运算,怎么做到这一点呢? 那大家可以知道了,如果前面这个表达式它的返回值就是cout, 那也就是说cout.operator<<(5)这个表达式它的返回值如果就是cout的话, 那么这个cout还可以在下面, 嗯,那么就接下来还可以在cout的上面执行 operator <<成员函数,并且以"this"作文参数去调用这个成员函数,那this就会被输出了。 所以上面这个表达式它所对应的函数调用的形式就是这样,啊。 前面这部分返回了cout,然後就接着这个cout的下面执行cout.operator<<("this")。 那我们前面解释了这个,嗯,<<运算符的重载。我们下面看看 如果要写这样一个程序,啊,这里有一个CStudent的它里面有一个年龄, 然後在main里面定义一个CStudent的对象,然後对年龄进行赋值。然后呢,我们 可以把s就是这个CStudent的对象交给cout进行输出。 那么交给cout进行输出的时候呢,会输出s的年龄。 也就是说这个,我们希望这个程序输出5hello,那5当然就是s的年龄了,对吧? 那我们要做些什么呢?那很明显嘛!你得重载这个<<运算符。 因为,因为,你不重载的话,cout没法跟这个s做这个运算,所以我们要重载这个<<运算符。 那怎么重载呢?嗯, 你,你,嗯, cout是ostream类的对象,ostream类早就在iostream这个头文件里面都写好了。 你不可能在为它添加什么成员函数了。所以在这里我们重载<<运算符的时候肯定只能重载成一个全局函数; 而不是把它重载成什么ostrean的成员函数,你不可能做这一点。那我们怎么做呢? 啊,把这个<<运算符重载成一个全局的函数。 啊,那重载成全局的函数它肯定就是,嗯, 操作数的这个数目等于这个函数的参数个数,对吧?所以它有两个参数。第二个参数很明显 就应该和这个,嗯, CStuden的对象能够匹配的。那第二个参数可以是CStudent的对象, 也可以是CStudent的,的引用。都能够做到类型匹配。那我们取,取它为CStudent的引用, 能够节省这个,嗯,时间和空间,对吧?因为我们不需要形成寻常对象。寻常对象要能够调用赋值过的函数是有开销的。 然後,嗯,既然是CStudent的引用,然後我们在这个程序里面又不会去修改s的值。 所以我就把这个引用变成const了,这是第二个参数。那第一个参数是什么呢? 嗯, 要执行cout<<s,对吧?那第一个参数肯定要对于cout。 那cout是ostream的对象,那我们第一个参数的类型就应该是ostream的对象;或者是ostream的引用。 在这里为了这个节省开销,我们让它成为ostream的这个引用。 啊,然後呢,我们在这里面 这是一个ostream的这个引用。那么,这个,程,程序 程序走到这里面的时候,o是谁啊?o实际上就是引用了cout。 那也就是说o就是cout,对吧?所以我们直接在这里把s.nAge 给这个cout,这个s.nAge就能够在屏幕上输出了。 然后就完,这就完成了输出的这个工作。 那,那我们这个函数它的这个返回值我们也是需要考虑一下的。不能随便写一个void。 为什么不能随便写一个void呢?因为这个表达式执行完了以后,它还要接着往下运算。 所以我们希望这个表达式它的返回值应该还是cout,这样才能够接着往下运算,对吧? 那你怎么让这个表达式它的返回值仍然是cout的呢? 那就我们就重载这个函数的时候,返回值的类型就有讲究了。啊,我们就让它是ostream的这个引用。 然后我们注意到,进到这个函数里面来的时候,这个o是cout的引用。也就是说 o等价于cout。那我们希望这个函数返回值仍然是这个cout,怎么做呢? 我们就return o就行了,因为0是cout的引用,对吧?然後,这个函数的返回值也是一个ostream的引用。 那么,return o也就是这个函数的返回值,就是cout的引用,那就等价于cout。那问题就解决了。 我额外再说一点啊,如果你这一块不写引用,写ostream的对象,实际上编译是不过的,那是有一些其他的原因, 这里就不再细说了。 那下面再看一个复杂一点的这个,这个例题啊。 就是我们假定c是Complex复数类的对象,啊,然后现在我们希望写这个cout《c, 就能够以a+bi的形式输出c的值。 然后写cin》c 就能够从键盘接受a+bi这种形式的输入, 并且使得c. real=a,c. imag=b。我们希望能做到这一点,该怎么办? 大家可以想想。这个我们在,我刚刚讲的时候都把这两个东西称为 左移、右移运算符,那我们标题又写的是,是流插入和流提取运算符啊。 那大家注意了,实际上在C++的语法里面,这两个东西就是左移,左移和右移运算符。 只不过,把它重载了以后,它经常用于输入输出,所以它就得了另外一个名字叫做流插入和流,流提取而已,啊。 本质上它还是左移右移运算符被重载了。 那现在我们再看这个题,呃,该怎么做。 肯定我们要重载左移 运算符,也要重载右移运算符,对吧。那我们得搞清楚cin到底是个什么东西。 那cout是ostream类的对象,cin是istream的对象。 就是我们希望,呃,有个Com,复数类,我们写好了,它里面有实部和虚部,然后我们可以,可以 来这个cin、c、n,呃, n是整型的,这c是一个复数的对象,我们希望这个,这条语句能够 编译通过,而且它执行的效果是这样的,就是,呃,当等待输入的时候,我们敲了13.2+133i, 再空格87,结果呢,这个13.2+133i就会被读到c里面去。 也就是说c这个复数它的实部是13.2,虚部是133,当然n的值就是87,,对吧。 然后我们接下来又可以把这个c, 这个复数对象交给cout去输出,输出完以后就是,就是a+bi的一种复数的形式, 就是13.2+133i,然后这个逗号,再87。这是我们希望达到的这个,这个,希望达到的这个效果。 那么我们就要重载左移运算符和这个右移运算符,啊。 这整个完整的程序是这样的。这复数类是有实部和虚部。 然后呢,这个复数类有一个构造函数,构造函数呢用来对这个, 呃,real,就是实部,以r,参数r 赋初值,对虚部以i 赋初值。 然后呢,我们说了这个左移右移运算符,我们要把它重载。 在这里,我们只能重载成全局函数了,对吧,你不可能再把它重载成 Ostream类或者istream类的这个成员函数,因为那两个类都已经写好了。 所以我们必须把这,这个左移运算符和右移运算符重载成一个全局的函数。 那在这个Complex类里面呢,这两个 成员变量又都是私有的,那我们这两个全局函数又要访问Complex类的私有的成员,那怎么办啊? 那解决办法就是我们 把这个重载出来的这个左移和右移运算符把它声明为Complex类的友元,所以我们在这里声明一下,friend。 啊,这个是左移运算符重载,这个是右移运算符重载,都变成友元。 然后我们来看这个左移运算符怎么重载的。 啊,那它的返回值应该是ostream的引用,理由前面已经说过了。 因为我们输出一个Complex对象以后,还能接着用左移运算符运算。 接着输,输出后面的整数啊,什么字符串之类的。所以它的返回值应该 还是一个引用,最好。然后这个程序,这个函数被执行的时候,返回值依然是这个cout的引用嘛。 它第一个参数是这样的,第二个参数是这个Complex的 对象的引用。然后在这里面呢,我们就,就把这个c, 这个复数啊,以a+bi这种形式输出。那当然就是, 这个,这个os是什么呀? 这个os当然就会是cout,对吧。所以我们就会把这个c. real给cout,然后再输出一个加号,再把这个c.imag 再,再给这个,前面这个,这个表达式的返回值,就是也是cout。 然后再输出一个i。这样以a+bi的形式就输出了这个复数c的值。然后我们return os,这个os 会是什么东西啊?会是cout的引用,对吧。我们return os使得整个函数的返回值仍然是cout。 那我们再来看看这个右移运算符该怎么重载。那我们得知道cin它是 istream的类对象,对吧。所以这个时候我们重载成全局函数的时候,返回值应该是istream 对象的引用。第一个参数是istream的引用,第二个参数呢是这个复数 对象的引用。然后,在这里面要做的事情就是要把这个a+b形式的输入, 把它里面的实部a和b分离出来,读到这个c里面去。 那a+b形式的输入,我们可以作为字符串string 对象给它读入。 这里面关于string对象的一些细节,大家可以先不用深究,啊,后面还会仔细讲到。在这里, 这个片子的主要目的还是向大家演示这个右移运算符该怎么重载。 总而言之,我们可以通过这个is,is是什么呢? 执行到这个函数的时候,is当然就是cin了,对吧。我们通过这个cin就能够把一个a+b形式的字符串读到这个 s里面,啊。然后,我们就要在s里面把a和b分离出来。那分离的方法当然就是找到这个+号, 然后呢,取+号前面的a再取+号后面的一个b。这里面是有一些这个过程,这些过程我就 不详细说了,因为不是这,这堂课的重点。总而言之,我们把这个, 呃,s里面+号前面的a和b都,+号前面的a和+号后面的b都分离出来以后, 我们在这个就把这个a分离出来放到实部里面。 然后呢,把这个b分离出来放到这个虚部里面。 那,那么a+b所代表的的复数就被读到c里面去了。 然后我们就可以return is了,这is是什么呢?就是cin,对吧。那这个,这个右移运算符它的返回值 还是cin。然后,然后,也就是说我们,呃, 从这个 键盘读取了c以后,这个表达式的返回值还是cin,那cin还能够 跟着下面的这个n继续做运算,啊,你在后面再输入别的都行也都行。