《新编C++语言习题与解析》试读:3.1 基本知识点
第3章 引 用
本章学习要点
深入掌握引用的相关概念和使用方法。
深入掌握常引用的相关概念和使用方法。
灵活运用引用的相关知识进行综合程序设计。
3.1 基本知识点
3.1.1 引用的概念
引用是一个别名。当建立引用时,程序用另一个已定义的变量或对象(目标)的名字初始化它。从那时起,引用作为目标的别名而使用,对引用的改动实际上就是对目标的改动。
建立引用的一般格式如下:
数据类型 & 引用名=已定义的变量名;
例如,以下语句说明rd是对double型数的引用,初始化为引用d:
double d;
double &rd=d;
引用具有如下特点:
引用能够使用任何合法变量名。
引用不是变量,引用必须初始化。
引用不是值,不占存储空间。声明引用时,目标的存储状态不会改变。所以引用只有声明,没有定义。
引用只有在声明时带有“&”,以后就像普通变量一样使用,不能再带“&”。
引用主要用作函数参数。
例如,有以下程序:
#include <iostream.h>
void main()
{ int n=100;
int &rn=n;
n++;
cout << "n=" << n << endl;
cout << "rn=" << rn << endl;
rn++;
cout << "n=" << n << endl;
cout << "rn=" << rn << endl;
}
该程序中说明rn是n的引用,因此,rn和n的值完全同步操作,其执行结果如下:
n=101
rn=101
n=102
rn=102
由于指针也是变量,所以也可以引用指针变量。其使用格式如下:
数据类型 * &指针引用名=指针
同样,先要定义“指针”,它是基类型为“数据类型”的指针变量。例如,有以下程序:
#include <iostream.h>
void main()
{ int n=10,*pn=&n,*&rn=pn;
(*pn)++;
cout << "n=" << n << endl;
(*rn)++;
cout << "n=" << n << endl;
}
该程序中,指针pn指向n,rn是pn的引用,执行(*pn)++;使n值增1,同样,执行(*rn)++;使n值增1。其执行结果如下:
n=11
n=12
并不是对任何类型的数据都可以引用,在下列情况下不能进行引用:
对void进行引用是不允许的。
不能建立引用的数组。
没有引用的引用。
引用不能用类型来初始化,例如,int &rn=int;是错误的。
没有空引用(空引用没有任何意义)。
3.1.2 引用作为函数参数
1. 引用传递参数
在作为函数参数时,引用具有指针的威力,又具有传值方式的简单性与可读性。而且使用引用可以从调用的函数返回多个值。
例如,有以下程序:
#include <iostream.h>
void f(int a[],int n,int &max,int &min)
{ max=min=a[0];
for (int i=1;i<n;i++)
{ if (max<a[i]) max=a[i];
if (min>a[i]) min=a[i];
}
}
void main()
{ int a[10]={2,5,2,9,0,8,6,1,7,4};
int max,min;
f(a,10,max,min);
cout << "Max:" << max << endl;
cout << "Min:" << min << endl;
}
上述程序中,函数f使用了两个引用,即Max和Min用于返回值。其执行结果如下:
Max:9
Min:0
2. 对象引用作为函数参数
在实际应用中,使用对象引用作为函数参数要比使用对象指针作为函数参数更普遍。这是因为使用对象引用作为函数参数,既具有用对象指针作为函数参数的优点,又具有用对象作为函数参数的简单性与可读性。
例如,有以下程序:
#include <iostream.h>
class Sample
{ int x,y;
public:
Sample(){ x=y=0; }
Sample(int x1,int y1) { x=x1;y=y1;}
void copy(Sample s) { x=s.x;y=s.y; }
void setxy(int x1,int y1) { x=x1;y=y1; }
void disp() { cout << "(" << x << "," << y << ")" << endl; }
};
void fun(Sample s1,Sample &s2)
{ s1.setxy(12,18);
s2.setxy(23,15);
}
void main()
{ Sample s(5,10),t;
s.disp();
t.copy(s);
t.disp();
fun(s,t);
s.disp();
t.disp();
}
上述程序中,main函数调用fun(s,t)后,由于fun函数只有第二个参数使用了对象引用,所以s对象不改变,而t对象改变了。其执行结果如下:
(5,10)
(5,10)
(5,10)
(23,15)
在函数调用过程中,实参到形参传递数据时,是将实参复制给形参,注意这种复制是浅复制,例如,有以下程序:
#include <iostream.h>
#include <string.h>
class MyClass
{ char *pstr;
int size;
public:
MyClass() {} //默认构造函数
MyClass(char a[],int n) //重载构造函数
{ pstr=new char[n+1]; //分配n+1个字符空间
strcpy(pstr,a); //复制字符串
size=n;
}
~MyClass() //析构函数
{ delete [] pstr; }
void Copy(MyClass obj) //复制
{ this->pstr=new char[obj.size+1];
strcpy(this->pstr,obj.pstr);
this->size=obj.size;
}
void Disp() //输出pstr
{ cout << pstr << endl; }
};
void main()
{ MyClass s("ABC",3),s1;
cout << "s:"; s.Disp();
s1.Copy(s);
cout << "s1:"; s1.Disp();
}
上述程序中定义了一个MyClass类,其中Copy成员函数用于将形参obj对象复制给当前调用的对象,并采用深复制。可是在程序执行时却出现错误。这是因为执行s1.Copy(s)时,由于Copy成员函数的形参obj不是引用型参数,需要将实参对象s复制给形参对象obj,这种复制是浅复制,也就是说obj.pstr和s.pstr都指向同一个内存空间,当Copy函数执行完毕,调用obj的析构函数将所指空间释放了,而程序结束时,再调用s的析构函数时出现错误。
改正的方法十分简单,只需将Copy成员函数的形参obj改为引用型参数即可。因为改为引用型参数后,obj和s共享同一内存空间,当Copy函数执行完毕,不会调用obj的析构函数将所指空间释放,只有在程序结束时调用s的析构函数才释放所指空间。
在设计一个类时,如果含有指针数据成员,那么成员函数中对象形参通常采用引用类型,否则会出现错误。
3.1.3 引用返回值
引用表达式是一个左值表达式,因此它可以出现在形参和实参的任何一方。若一个函数返回了引用,那么该函数的调用也可以被赋值。普通函数返回值时,要生成一个值的副本。而引用返回值时,不生成值的副本。
例如,有以下程序:
#include <iostream.h>
int n=0;
int & f(int m)
{ n+=m;
return n;
}
void main()
{ f(10)+=20;
cout << "n=" << n << endl;
}
上述程序中,由于f函数返回全局变量n的引用,所以可以作为左值直接进行“+=”运算。f(10) += 20等价于n = n + 20 = 10 + 20 = 30。其执行结果如下:
n=30
并不是所有函数都可以返回引用。一般地,当返回值不是本函数的局部变量时可以返回一个引用;否则,当函数返回时,该引用的变量就会被自动释放,所以对它的任何引用都将是非法的。在通常的情况下,引用返回值只用在需要对函数的调用重新赋值的场合,也就是对函数的返回值重新赋值的时候。
例如,有以下程序:
#include <iostream.h>
#include <string.h>
class MyClass
{ char *pstr;
int size;
public:
MyClass() {} //默认构造函数
MyClass(char a[],int n) //重载构造函数
{ pstr=new char[n+1]; //分配n+1个字符空间
strcpy(pstr,a); //复制字符串
size=n;
}
~MyClass() //析构函数
{ delete [] pstr; }
MyClass Copy(MyClass &obj) //复制
{ this->pstr=new char[obj.size+1];
strcpy(this->pstr,obj.pstr);
this->size=obj.size;
return *this; //返回当前对象,即用修改后的对象替代原对象
}
void Disp() //输出pstr
{ cout << pstr << endl; }
};
void main()
{ MyClass s("ABC",3),s1;
cout << "s:"; s.Disp();
s1.Copy(s);
cout << "s1:"; s1.Disp();
}
MyClass类中Copy成员函数用于将形参obj对象复制给当前调用的对象,并采用深复制。但在程序执行时出现错误。这是因为执行s1.Copy(s)时,this指针指向对象s1,通过this指针修改s1对象的pstr和size数据成员,当执行return *this时返回this所指对象,希望用它替换原来的s1对象,但由于没有采用引用,达不到希望的效果。
改正的方法十分简单,只需将Copy成员函数的返回类型改为MyClass对象引用即可,这样s1和返回对象共享同一内存空间。
引用返回值经常用于运算符重载中,这将在第5章中介绍。
3.1.4 常引用
使用const修饰符可以说明引用,被说明的引用为常引用。其定义格式如下:
const 数据类型 & 引用名;
例如:
int x=2;
const int &n=x;
其中,n是一个整型变量的常引用,不能通过该常引用更新对象。如:
n++; //错误
但执行x++是正确的。
在实际应用中,常引用往往用作函数的形参,这样该函数中不能更新该参数所引用的对象,从而保护实参不被修改。
例如,有以下程序:
#include <iostream.h>
class Sample
{ int n;
public:
Sample(int i) { n=i; }
int getn() const { return n; }
};
int add(const Sample &s1,const Sample &s2)
{ int sum=s1.getn()+s2.getn();
return sum;
}
void main()
{ Sample s1(100),s2(200);
cout << "sum=" << add(s1,s2) << endl;
}
本程序的执行结果如下:
sum=300
只有常成员函数才能操作常数据成员,没有使用const关键字说明的成员函数不能用来操作常数据成员。上述程序中的getn被说明为常成员函数,因此,它可以对常引用s1和s2进行操作,如果不将getn函数说明为常成员函数,则以下语句:
int sum=s1.getn()+s2.getn();
出现如下的编译错误:
'getn' : cannot convert 'this' pointer from 'const class Sample' to 'class Sample &' Conversion loses qualifiers