⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 第13章 函数(二).htm

📁 用非常通俗的语言介绍了C++和C
💻 HTM
📖 第 1 页 / 共 5 页
字号:
      <P> </P>
      <P>完整程序请见下载的课程实例。<BR></P>
      <H4><A name=13.2.4>13.2.4</A> 参数默认值</H4>
      <P> </P>
      <P>在手头的某本C++教材里,有关本节内容的第一句话是:“参数默认值也称默认参数值”。对着这话我愣了半天才算明白。所以在后面课程里,有些地方我说“参数默认值”有些地方我又会胡里胡涂说成“默认参数值”。你可不别像我一样去“研究”二者的区别呵。个人认为,从词法角度上看,“参数默认值”更准确些。</P>
      <P> </P>
      <P>C++支持在定义或声明函数时,设置某些参数的默认值,这一点C不允许。</P>
      <P> </P>
      <P>比如我们为卖萝卜的大娘的写一个计价函数。这个函数需要三个参数:顾客交多钱?买多少斤萝卜?及萝卜的单价。返回值则是大娘应该找多少钱。例如,顾客交了100元,他买5斤萝卜,单价是1.00元/斤。那么函数就会计算并返回95,表示应该找给顾客95元钱。</P>
      <P> </P>
      <P>//函数定义如下:</P>
      <P>float GiveChange(float money, float count, float price)</P>
      <P>{</P>
      <P>&nbsp;&nbsp; return money - count * price; //找钱 = 已付款 - 数量 * 单价</P>
      <P>}</P>
      <P> </P>
      <P>当我们在程序中需要使用该函数时,我们大致是这样调用:</P>
      <P> </P>
      <P>float change = GiveChange(100,5,1);</P>
      <P> </P>
      <P>看上去一切很完美--确实也很完美。不过C++允许我们锦上添花。并且不是一朵只为了好看的“花”。</P>
      <P>现实情况是,萝卜的价钱是一个比较稳定的数--当然并不是不会变,在时出现亚洲经济风暴,萝卜价还是发变--总之是会变,但很少变。</P>
      <P>碰上这种情况,我们每回在调用函数时都写上最后一个参数,就有些亏了,这时,我们可以使用“参数的默认值”。</P>
      <P> </P>
      <P>//首先,函数的定义做一点改动:</P>
      <P>float GiveChage(float money, float count, float price = 1.0)</P>
      <P>{</P>
      <P>&nbsp;&nbsp; .....</P>
      <P>}</P>
      <P> </P>
      <P>看到变化了吗?并不是指函数体内我打了省略号,而是在函数参数列表中,最后一个参数的定义变为:float price = 
      1.0。这就默认参数值,我们指定价格默认为1元。</P>
      <P> </P>
      <P>然后如何使用呢?</P>
      <P>以后在代码中,当需要计算找钱时,如果价钱没有变,我们就可以这样调用:</P>
      <P> </P>
      <P>float change = GiveChange(100,5); //没有传递最后一个参数。</P>
      <P> </P>
      <P>是的,我没有写最后一参数:价钱是多少?但编译器发现这一点时,会自动为我填上默认的1.0。</P>
      <P>如果在代码的个别地方,大娘想改一改价钱,比如某天笔者成了顾客,大娘决定按1斤2毛钱把萝卜卖给我:</P>
      <P>我给大娘5毛钱,买2斤:</P>
      <P>float changeForBCBSchool = GiveChange(0.5, 2 ,0.2); //你一样可以继续带参数</P>
      <P> </P>
      <P>我想,这个实例很直观,但必须承认这个例子并没有体现出参数默认值的种种优点。不过不管如何,你现在应该对参数的默认值有感性认识。</P>
      <P> </P>
      <P>下面学习有关参数默认值的具体规定。</P>
      <P> </P>
      <P>1、必须从<B>最右边</B>开始,然后<B>连续</B>地设置默认值。</P>
      <P> </P>
      <P>如果理解这句话?</P>
      <P>首先我们看关键词“最右边”。也就是说假如一个函数有多个参数,那么你必须从最后一个参数开始设置默认值。</P>
      <P>如:</P>
      <P>void foo(int a, int b, bool c);</P>
      <P> </P>
      <P>那么,下面的设置是正确的:</P>
      <P>void foo(int a, int b, bool c = false); //ok,c是最后一个参数</P>
      <P>//而,下面是错误的:</P>
      <P>void foo(int a, int b = 0, bool c);&nbsp;&nbsp;&nbsp; 
      //fail,b不是最后一参数</P>
      <P> </P>
      <P>然后我们看“连续”。也就是说,从最右边开始,你可以连续地向左设置多个参数的默认值,而不能跳过其中几个:</P>
      <P>如:</P>
      <P> </P>
      <P>下面的的设置是正确的:</P>
      <P>void foo(int a, int b=0, bool c = false); //ok ,连续地设置c,b的默认值</P>
      <P>同样,这也是正确的:</P>
      <P>void foo(int a=100, int b=0, bool c = false); //ok ,连续地设置c,b,a的默认值</P>
      <P>//而,这样设置是错误的:</P>
      <P>void foo(int a=100, int b, bool c = false); //fale,不行,你不能跳过中间的b。</P>
      <P> </P>
      <P>2、如果在函数的声明里设置了参数默认值,那么就不参在函数的定义中再次设置默认值。</P>
      <P> </P>
      <P>函数的“声明”和“定义”你可能又有些胡涂了。好,就趁此再复习一次。</P>
      <P> </P>
      <P>所谓的“定义”,也称为“实现”,它是函数完整的代码,如:</P>
      <P>//函数定义如下(函数定义也称函数的实现):</P>
      <P>float GiveChange(float money, float count, float price)</P>
      <P>{</P>
      <P>&nbsp;&nbsp; return money - count * price; //找钱 = 已付款 - 数量 * 单价</P>
      <P>}</P>
      <P> </P>
      <P>而函数的“声明”,则是我们上一章不断在说的函数的“名片”,它用于列出函数的格式,函数的声明包含函数的“返回类型,函数名,参数列表”,惟一和函数定义不一样的,就是它没有实现部分,而是直接以一分号结束,如:</P>
      <P>//声明一个函数:</P>
      <P>float GiveChange(float money, float count, float price); 
      //&lt;---注意,直接以分号结束。</P>
      <P> </P>
      <P>现在和参数默认值有关的是,如果你在函数声明里设置了默认值,那就不用,也不能在函数定义处再设置一次。</P>
      <P>如,下面代码正确:</P>
      <P>----------------------------------------</P>
      <P>//定义:</P>
      <P>float GiveChange(float money, float count, float price)</P>
      <P>{</P>
      <P>&nbsp;&nbsp; return money - count * price; //找钱 = 已付款 - 数量 * 单价</P>
      <P>}</P>
      <P> </P>
      <P>//声明:</P>
      <P>float GiveChange(float money, float count, float price = 1.0); </P>
      <P>----------------------------------------</P>
      <P>而下面的代码有误:</P>
      <P>//定义:</P>
      <P>float GiveChange(float money, float count, float price = 1.0)</P>
      <P>{</P>
      <P>&nbsp;&nbsp; return money - count * price; //找钱 = 已付款 - 数量 * 单价</P>
      <P>}</P>
      <P> </P>
      <P>//声明:</P>
      <P>float GiveChange(float money, float count, float price = 1.0); </P>
      <P>----------------------------------------</P>
      <P> </P>
      <P>3、默认值可以最常见的常数,或全局变量,全局常量,甚至可以是一个函数的调用。</P>
      <P>关于题中的“全局”,我们还没有学习,这时理解就是在程序运行区别稳定存在的变量或常量。下面举一个让我们比较狐疑的,使用函数作来参数默认的例子:</P>
      <P> </P>
      <P>//某个返回double的函数:</P>
      <P>double&nbsp; func1();</P>
      <P>double&nbsp; func2(double a, double b = func1()); 
      //func1()的执行结果将被用做b的默认值。</P>
      <P> </P>
      <H3><A name=13.3>13.3</A> 函数重载</H3>
      <P>重,重复也。载者,承载也。</P>
      <P>“重复”一词不用解释,“承载”不妨说白一点,认为就是“承负”。</P>
      <P>函数的“重载”,意为可以对多个功能类似的函数使用相同的函数名。</P>
      <H4><A name=13.3.1>13.3.1</A> 重载的目的</H4>
      <P>有这个需要吗?不同的函数取相同的名字?这不会造成混乱?在现实生活中,我们可一点也不喜欢身边有哪两个人同名。</P>
      <P>当然有这个必要。“函数名重载”是C++对C的一种改进(因此C也不支持重载)。</P>
      <P> </P>
      <P>想一想那个求“二数较大者”的max函数吧。如果不支持函数名重载,那么就会有以下不便:</P>
      <P> </P>
      <P>int max(int a, int b);</P>
      <P>这是前面我们写的,用以实现两数中较大者的函数。比如你传给它20,21,那么,它将很好地工作,返回21。现在,我们想求 20.5,和21.7 两个实数中较大者?对不起,max函数要求参数必须为int类型,所以传给它20.5,21.7:</P>
      <P>float larger = max(20.5,21.7);</P>
      <P>编译器不会让这行代码通过。它会报错说“参数不匹配”。</P>
      <P> </P>
      <P>好吧,我们只好为实数类型的比较也写一个参数,但C语言不允函数重名,所以我们只好另起一个名字:</P>
      <P>float maxf(float a, float b);</P>
      <P> </P>
      <P>你可能会就,那就不要int版的max,只要这个float版本的:</P>
      <P>float max(float a, float b);</P>
      <P>因为,实数版本的完全可以处理整数。说得没错,但这不是一个好办法,其一我们已知道实数和整数相比,它有计算慢,占用空间大的毛病;其二,float版本的max函数,其返回值必然也是float类型,如果你用它来比较两个整数:</P>
      <P>int larger = max(1, 2);</P>
      <P>编译器将不断警告你,“你把一个float类型的值赋值一个int类型的变量”。编译器这是好心,它担心你丢失精度,但这会让我们很烦,我们不得不用强制类型转换来屏蔽这条警告消息:</P>
      <P>int larger = (int) max(1,2);</P>
      <P>这样的代码的确不是好代码。</P>
      <P> </P>
      <P>好吧,就算你能容忍这一切,下一问题是,我想写了一个求3个整数谁最大的函数。这回你没有理由因为要写三个参数的版本,就把两个参数的版本扔了。只好还是换名:</P>
      <P>int max_3(int a, int b, int c);</P>
      <P> </P>
      <P>看着 max_3这个函数名字,我不禁想起前几天在yahoo申请免费电子信箱,我想叫 nanyu@yahoo.com.cn 
      ,它却坚持建议我改为:nanyu1794@yahoo.com.cn (1794?一去就死?),折腾了我两个半小时,我才找到一个可以不带一串数字,又让我能接受点的呢称。</P>
      <P>结论是:不允许重名的世界真的有些烦。C++看到了这一点,所以,它允许函数在某些条件的限制下重名。这就是函数重载。</P>
      <P> </P>
      <P>前面有关max()的问题,现在可以这样解决:</P>
      <P> </P>
      <P>//整数版的max()</P>
      <P>int max(int a, int b);</P>
      <P> </P>
      <P>//单精度实数版的max()</P>
      <P>float max(float a, float b);</P>
      <P> </P>
      <P>//双精度实数版的max();</P>
      <P>double max(double a, double b);</P>
      <P> </P>
      <P>//甚至,如果你真的有这个需要,你还可以来一个这种版本的max();</P>
      <P>double max(int a, double b);</P>
      <P> </P>
      <P>//接下来是三个参数的版本:</P>
      <P>int max(int a, int b, int c);</P>
      <P>double max(double a, double b, double c);</P>
      <P> </P>
      <P>上面林林总总的求最大值函数,名字都叫max();好处显而易见:对于实现同一类功能的函数,只记一个名字,总比要记一堆名字要来得舒服。</P>
      <H4><A name=13.3.2>13.3.2</A> 函数重载的规则</H4>
      <P>有一个问题,那么多max函数,当我们要调用其中某一个时,编译器能知道我们到底在调用哪一个吗?</P>
      <P>如何让编译器区分出我们代码中所调用的函数是哪一个max,这需要有两个规则。</P>
      <P> </P>
      <P><B>实现函数重载的规则一:</B>同名函数的参数必须不同,不同之处可以是<B>参数的类型</B>或<B>参数的个数</B>。</P>
      <P> </P>
      <P>如果你写想两个同名函数:</P>
      <P><B>错误一:</B></P>
      <P>int max(int a, int b);</P>
      <P>int max(int c, int d);</P>
      <P> </P>
      <P>看上去这两个函数有些不同,但别忘了,形参只是形式,事实上两个声明都可以写成:</P>
      <P>void max(int, int);</P>
      <P>所以记住:仅仅参数名不一样,不能重载函数。</P>
      <P> </P>
      <P>错误二:</P>
      <P>float max(int a, int b);</P>
      <P>int max(int a, int b);</P>
      <P>两个函数不同之处在返回类型,对不起,C++没有实现通过返回值类型的不同而区分同名函数的功能。</P>
      <P>所以记住:仅仅返回值不一样,不能重载函数。</P>
      <P> </P>
      <P>正因为函数的重载机制和函数的参数息息相关,所以我们才把它紧放在“函数参数”后面。但函数重载并不能因此就归属于“参数”的变化之一,以后我们会学习不依赖于参数的重载机制。</P>
      <P> </P>
      <P><B>实现函数重载的规则二:</B>参数类型的匹配程度,决定使用哪一个同名函数的次序。</P>
      <P> </P>
      <P>若有这三个重载函数:</P>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -