Chinaunix首页 | 论坛 | 博客
  • 博客访问: 377900
  • 博文数量: 62
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 557
  • 用 户 组: 普通用户
  • 注册时间: 2013-08-01 14:04
文章分类

全部博文(62)

文章存档

2014年(1)

2013年(61)

分类: C/C++

2013-12-06 11:47:15

  在本文中,主要讨论函数(尤其是与类相关的函数——成员函数或友元函数)返回值类型何时用引用的问题。


  一、函数返回值为引用的典型案例

  在做输入输出重载时,重载函数返回流对象,如:

  1. //例程1:复数类中运算符的重载  
  2. #include   
  3. using namespace std;  
  4. class Complex  
  5. {  
  6. public:  
  7.     Complex(){real=0;imag=0;}  
  8.     Complex(double r,double i){real=r;imag=i;}  
  9.     Complex operator-();  
  10.     //实现输入、输出的运算符重载  
  11.     friend ostream& operator << (ostream& output,Complex& c);  
  12.     friend istream& operator >> (istream& input,Complex& c);  
  13.     //实现加减乘除的运算符重载  
  14.     friend Complex operator+(Complex &c1, Complex &c2);  
  15.     friend Complex add(double d1, Complex &c2);  
  16. private:  
  17.     double real;  
  18.     double imag;  
  19. };  
  20.   
  21. //实现输出的运算符重载  
  22. ostream& operator << (ostream& output,Complex& c)  
  23. {   output<<"("<
  24.     if(c.imag>=0) output<<"+";    
  25.     output<"i)";      
  26.     return output;  
  27. }  
  28.   
  29. //实现输入的运算符重载  
  30. istream& operator >> (istream& input,Complex& c)  
  31. {   int a,b;  
  32.     char sign,i;  
  33.     do  
  34.     {   cout<<"input a complex number(a+bi或a-bi):";  
  35.         input>>a>>sign>>b>>i;  
  36.     }  
  37.     while(!((sign=='+'||sign=='-')&&i=='i'));  
  38.     c.real=a;  
  39.     c.imag=(sign=='+')?b:-b;  
  40.     return input;  
  41. }  
  42.   
  43. //复数相加:(a+bi)+(c+di)=(a+c)+(b+d)i.   
  44. Complex operator+(Complex &c1, Complex &c2)  
  45. {  
  46.     Complex c;  
  47.     c.real=c1.real+c2.real;  
  48.     c.imag=c1.imag+c2.imag;  
  49.     return c;  
  50. }  
  51.   
  52. Complex add(double d1, Complex &c2)  
  53. {  
  54.     Complex c;  
  55.     c.real=d1+c2.real;  
  56.     c.imag=c2.imag;  
  57.     return c;   
  58. }  
  59.   
  60. int main()  
  61. {  
  62.     Complex c1,c2,c3;  
  63.     double d=11;  
  64.     cout<<"c1: ";   
  65.     cin>>c1;            
  66.     cout<<"c2: ";  
  67.     cin>>c2;  
  68.     cout<<"c1="<
  69.     cout<<"c2="<
  70.     c3=c1+c2;  
  71.     cout<<"c1+c2="<
  72.     c3=add(d,c1);  
  73.     cout<<"d+c1="<
  74.   
  75.     system("pause");  
  76.     return 0;  
  77. }  

  在例程1的第11和12行,运算符重载函数的返回值类类被声明为引用;在第22行和30行,这两个函数的实现中,分别返回了输入流对象 input和输出流对象output(要注意到这两个对象是作为形式参数出现的,且都为引用)。实际上,<<和>>运算符对其他 类型重载时也是这样处理的。这样处理的好处在于,函数返回的输入/出流还可以继续用于其他数据的输入/出,使我们能用cin>>i,j;和 cout<

  以例程1中的第69行(cout<<"c1="<

  cout<<"c1="<

  1. //例程2:给函数的返回值赋值  
  2. #include   
  3. #include  
  4. using namespace std;  
  5. char &get_val(string &str, string::size_type ix)  
  6. {  
  7.     return str[ix];  
  8. }  
  9.   
  10. int main()  
  11. {  
  12.     string s("a value");  
  13.     cout<//输出 a value  
  14.     get_val(s,0)='A';   //函数调用一般是不能作为赋值运算的左值的,但这儿居然没有错,只因为返回值为引用  
  15.     cout<//输出为 A value,不可思议的改变  
  16.     system("pause");  
  17.     return 0;  
  18. }  
  正如注释中所讲,返回值为引用,函数调用get_val(s,0)居然可以作为赋值表达式的左值,就这样,其引用的空间(s[0])中所存储的字符被赋值为‘A’,“引用是被返回元素的同义词”,此处完全等同于s[0]='A'。

  程序从语法上讲没有问题,运行结果也达到了举例的目的。本例仅在于展示这种用法,理解引用作为函数返回值。在工程中,这种风格的程序当然不推荐使用,当不希望引用返回值被修改时,将返回值声明为const,即:

  const char &get_val(string &str, string::size_type ix)


  三、加法运算的重载结果也定义为引用,如何?

  对于例程1,将其中的加法重载函数的返回值也定义为引用(当然,这样做纯属撞错),结果会怎样?先给出这样的程序,请注意在第14、15、44和52行中增加的&。

  1. //例程3:复数类中运算符的重载,加法函数返回引用  
  2. #include   
  3. using namespace std;  
  4. class Complex  
  5. {  
  6. public:  
  7.     Complex(){real=0;imag=0;}  
  8.     Complex(double r,double i){real=r;imag=i;}  
  9.     Complex operator-();  
  10.     //实现输入、输出的运算符重载  
  11.     friend ostream& operator << (ostream& output,Complex& c);  
  12.     friend istream& operator >> (istream& input,Complex& c);  
  13.     //实现加减乘除的运算符重载  
  14.     friend Complex& operator+(Complex &c1, Complex &c2);  
  15.     friend Complex& add(double d1, Complex &c2);  
  16. private:  
  17.     double real;  
  18.     double imag;  
  19. };  
  20.   
  21. //实现输出的运算符重载  
  22. ostream& operator << (ostream& output,Complex& c)  
  23. {   output<<"("<
  24.     if(c.imag>=0) output<<"+";    
  25.     output<"i)";       
  26.     return output;  
  27. }  
  28.   
  29. //实现输入的运算符重载  
  30. istream& operator >> (istream& input,Complex& c)  
  31. {   int a,b;  
  32.     char sign,i;  
  33.     do  
  34.     {   cout<<"input a complex number(a+bi或a-bi):";  
  35.         input>>a>>sign>>b>>i;  
  36.     }  
  37.     while(!((sign=='+'||sign=='-')&&i=='i'));  
  38.     c.real=a;  
  39.     c.imag=(sign=='+')?b:-b;  
  40.     return input;  
  41. }  
  42.   
  43. //复数相加:(a+bi)+(c+di)=(a+c)+(b+d)i.   
  44. Complex& operator+(Complex &c1, Complex &c2)  
  45. {  
  46.     Complex c;  
  47.     c.real=c1.real+c2.real;  
  48.     c.imag=c1.imag+c2.imag;  
  49.     return c;  
  50. }  
  51.   
  52. Complex& add(double d1, Complex &c2)  
  53. {  
  54.     Complex c;  
  55.     c.real=d1+c2.real;  
  56.     c.imag=c2.imag;  
  57.     return c;   
  58. }  
  59.   
  60. int main()  
  61. {  
  62.     Complex c1,c2,c3;  
  63.     double d=11;  
  64.     cout<<"c1: ";   
  65.     cin>>c1;            
  66.     cout<<"c2: ";  
  67.     cin>>c2;  
  68.     cout<<"c1="<
  69.     cout<<"c2="<
  70.     c3=c1+c2;  
  71.     cout<<"c1+c2="<
  72.     c3=add(d,c1);  
  73.     cout<<"d+c1="<
  74.   
  75.     system("pause");  
  76.     return 0;  
  77. }  
  这个程序运行结果可能与例程1的结果是一样的。的确,在我运行中,结果没有出现过异常。这个程序在VS2008下编译时会有两个警告:

  1>d:\c++\vs2008 project\example\example\example.cpp(49) : warning C4172: 返回局部变量或临时变量的地址
  1>d:\c++\vs2008 project\example\example\example.cpp(57) : warning C4172: 返回局部变量或临时变量的地址

  这两个警告道出了危险所在:第49行和57行返回临时变量c之后,c 的空间将被释放,也就意味着可以由系统进行再分配,作其他用途使用了。而返回值类型为引用时,返回的值仍然在使用着这一块内存区域。结果只能是“可能”正 确,毫无保障。好比付款买了房(对方收条都没有开),房产证却放在房管局大厅中,哪一天你被赶出家门,那是活该的。这个简单的例子没有出问题纯属意外,因 为操作简单,那片空间还没有被重新分配。

  那什么时候更可能出问题呢?如果返回的对象中包含动态分配的空间(见《何时需要自定义复制构造函数?》),出问题几乎是肯定的。

  但是,最值得警惕的还是那些出问题可能性更小的时候,“不以恶小而为之”,一贯正常,只有很小几率出的错的情况更可怕。

  最重要的,理解了上述道理,要做到:千万不要返回对局部变量的引用

  还有一条类似的:千万不要返回局部变量的指针。


  四、钻个牛角尖:operate+就要返回引用

  重温本文第一部分最后的加粗字:(2)返回的引用值本身也必须是引用,一般是在调用函数中存在的,以引用型形式参数的方式传递到函数中的变量(例程1中的input和output为引用)。要应付这种胡搅蛮缠式的要求,我们也只能围绕这个要求想办法。

  给出的一种解决办法是:

  1. //例程4:复数类中运算符的重载,加法函数返回引用  
  2. #include   
  3. using namespace std;  
  4. class Complex  
  5. {  
  6. public:  
  7.     Complex(){real=0;imag=0;}  
  8.     Complex(double r,double i){real=r;imag=i;}  
  9.     Complex operator-();  
  10.     //实现输入、输出的运算符重载  
  11.     friend ostream& operator << (ostream& output,Complex& c);  
  12.     //实现加减乘除的运算符重载  
  13.     friend Complex& operator+(Complex &c1, Complex &c2);  
  14. private:  
  15.     double real;  
  16.     double imag;  
  17. };  
  18.   
  19. //实现输出的运算符重载  
  20. ostream& operator << (ostream& output,Complex& c)  
  21. {   output<<"("<
  22.     if(c.imag>=0) output<<"+";    
  23.     output<"i)";       
  24.     return output;  
  25. }  
  26.   
  27. //复数相加:(a+bi)+(c+di)=(a+c)+(b+d)i.   
  28. Complex& operator+(Complex &c1, Complex &c2)  
  29. {  
  30.     c1.real=c1.real+c2.real;  
  31.     c1.imag=c1.imag+c2.imag;  
  32.     return c1;  
  33. }  
  34.   
  35. int main()  
  36. {  
  37.     Complex c1(3,4),c2(5,-10),c3;  
  38.     cout<<"c1="<
  39.     cout<<"c2="<
  40.     c3=c1+c2;  
  41.     cout<<"c1="<
  42.     cout<<"c2="<
  43.     cout<<"c1+c2="<
  44.   
  45.     system("pause");  
  46.     return 0;  
  47. }  
 这种实现中,c1在调用operate+()前已经存在,是作为引用进行参数传递的。这样的变量能够保证程序不会出现意外。但是,会出的代价是,c1的 值在参与加法运算时被改变了。在第40行执行了c3=c1+c2后,第41行显示的c1的值同c3的值相同,是相加后的结果。

  这种安排也只能接受这种结局。事实上,学习计算机的同学也要接受这种风格,在有些语言(例如,汇编以及被冠以高雅称号的函数式语言)中,运算就是这么完成的。c1+c2怎么完成?add c1, c2; 其结果如何取出?结果就保存到第一个运算量中。

  牛角尖再钻深些,不能这样做!我只能为返回引用再使一招了:提前定义保存结果的变量(例c),并将之作为参数传递到函数中。付出的代价是,加运 算的运算量成了3个,operate+()形式是不能用了(运算符重载不能改变其目数)。实际上,返回的那个引用也没有什么意思了,结果已经由引用 c 带回来了,返回值甚至可以为void。这种设计太差了,程序依然贴在下面,读者不看也罢。

  1. //例程5:复数类中运算符的重载,加法函数返回引用  
  2. #include   
  3. using namespace std;  
  4. class Complex  
  5. {  
  6. public:  
  7.     Complex(){real=0;imag=0;}  
  8.     Complex(double r,double i){real=r;imag=i;}  
  9.     Complex operator-();  
  10.     //实现输入、输出的运算符重载  
  11.     friend ostream& operator << (ostream& output,Complex& c);  
  12.     //实现加减乘除的运算符重载  
  13.     friend Complex& add(const Complex &c1, const Complex &c2, Complex &c3); //为确保两个运算量不被改变,特加了const  
  14. private:  
  15.     double real;  
  16.     double imag;  
  17. };  
  18.   
  19. //实现输出的运算符重载  
  20. ostream& operator << (ostream& output,Complex& c)  
  21. {   output<<"("<
  22.     if(c.imag>=0) output<<"+";    
  23.     output<"i)";       
  24.     return output;  
  25. }  
  26.   
  27. //复数相加:(a+bi)+(c+di)=(a+c)+(b+d)i.   
  28. Complex& add(const Complex &c1, const Complex &c2, Complex &c3)  
  29. {  
  30.     c3.real=c1.real+c2.real;  
  31.     c3.imag=c1.imag+c2.imag;  
  32.     return c3;  
  33. }  
  34.   
  35. int main()  
  36. {  
  37.     Complex c1(3,4),c2(5,-10),c,c3;  
  38.     cout<<"c1="<
  39.     cout<<"c2="<
  40.     c3=add(c1,c2,c);  
  41.     cout<<"c1="<
  42.     cout<<"c2="<
  43.     cout<<"c="<
  44.     cout<<"c1+c2="<
  45.   
  46.     system("pause");  
  47.     return 0;  


阅读(1989) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~