《新编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

>新编C++语言习题与解析

新编C++语言习题与解析
作者: 李春葆 等
副标题: 新编C++语言习题与解析
isbn: 7302306206
书名: 新编C++语言习题与解析
页数: 323
定价: 38.00元
出版社: 清华大学出版社
出版年: 2013-5