第八章 函数探幽(2)引用变量
(二)引用变量
1.引用变量的典型用途
是用作函数的形参,通过将引用变量用作参数,函数将传递变量的引用,从而可以使用原始数据,而不是副本。引用是原始变量的别名,这是引用的最初含义,c++通过&符号来声明引用,&符号同时还是取地址运算符,但是在这里它只是类型标识符的一部分。
2.创建引用变量。
引用和指针是有区别的,其中一个区别就是引用必须在声明的时候就初始化,一旦与某个变量联系起来,就会一直“效忠”于它,引用不能像指针那样先声明再给指针赋值。引用更像是指针常量,只能指向一个固定的地址(这里可以设想引用是内置*的指针常量,只能指向一个变量)。引用的本质是原变量的别名,使用&来表示引用,比如int & a=b;表示a是b的别名。用变量名称来初始化引用,从而将名跟别名联系起来,初始化之后就不会更改,会一直指向初始指向的位置。
3.将引用用作函数参数(结构和对象的参数)
这是引用最常用的情况,使用引用作为参数,比用指针更容易理解,因为它是原变量的别名嘛,就相当于原变量。函数的定义中将引用作为形参参数,这样在调用函数的时候,形参参数中的变量的名称会当做一个引用,也就是函数会创建一个变量名称的别名,这样我们在函数中依然可以修改和操纵原来的变量。而用指针做参数的时候,我们调用函数时传递的是指针的地址,同样可以修改原始数据,但引用比指针更容易理解。在调用的时候,按引用传递和按值传递从外表看起来是相同的,但实际的内在机理差别非常大,只能通过原型或定义来看二者到底是按值传递还是引用。函数调用的时候用实参来初始化引用形参。
4.引用的属性和特别之处
引用会改变传递给他的参数的值(如果我们在调用的函数中修改这些值的话),而按值传递则不会,如果我们不想改变值但又想使用引用的化,可以使用常量引用。如const int & a,表示a这个引用不可以修改原变量的值,这种情况下其实最好是采用按值传递来实现。
临时变量,引用参数和const:如果函数的形参被声明为const引用,那么会在两种情况下生成临时变量(尽管可能会出现警告,但这是可以允许的,同时只有声明为const引用的时候才能够生产临时变量),第一种是实参的类型正确,但不是左值(左值指的是可以被引用的对象,比如变量,数组元素,结构对象等,都是可以通过一个固定的地址来访问的);另一种情况是实参是左值,他们的类型不正确,但可以转化为正确的类型。此时会生成临时匿名变量,函数会将引用指向这些变量,并且这些变量只在函数调用期间存在,函数调用完后会被销毁。只有被声明为const的引用才可以创建临时匿名变量,这是因为const的目的是使用变量的值而不是修改它们,因此创建临时变量不会造成任何不利的影响,也不会更改引用数据的内容,反而可以使函数可以接受的参数更多,使函数更加通用。形参被声明为const引用,那么函数的行为类似于按值传递(或者说是按值传递的一种替换形式)。
将引用声明为常量数据引用的好处有三个:
一是使用const可以避免无意中修改原始数据的编程错误;
二是使用const可以使函数可以接受const和非const实参,否则只能够使用非const数据;
三是使用const引用可以使函数正确生成和使用临时变量。
c++新设置了一种右值引用,符号是&&,以前的&引用被称为左值引用。引入右值引用的目的是让库设计人员能够提供有些操作的更有效的实现。
5.将引用用于结构
将引用用于结构和类是采用引用方式的初衷,将引用用于结构或类等,用法和变量是一样的,只要使用引用运算符&就可以了。返回值也可以是引用,只要我们在声明的时候声明它就行。
如果要返回一个常量引用,只需要在声明返回值的时候加上const,结果就像返回一个右值一样(也就是不可以对其进行赋值等操作,左值指的是一个可以改变的内存变量,而右值相当于仅仅是字面值)。当我们不希望对返回的引用进行修改的时候,应该返回一个常量引用,这也是程序设计的小技巧。将返回类型声明为const引用会避免程序的模糊性,可以有效防止我们犯一些低级错误。返回引用的时候使用的语句就是return结构或类对象的名称,如果我们仅看这一点的话不能区分返回的是引用还是变量的复制,因此需要结合函数原型和函数定义来对返回值的类型进行判定。
返回引用的时候要注意的问题:第一是应该避免返回函数结束后不再使用的内存的引用,也就是避免返回临时变量的引用。为了防止这种情况发生,应该返回一个作为参数传递给函数的引用,同样,也应该避免返回指向临时变量的指针。第二是使用new来分配新的存储空间,这种方式可以返回new出来的变量或对象的引用,但是这种方式并不好,很容易忘记delete掉new分配的空间。
当结构对象或类对象采用成员列表的形式初始化时,如果指定的初始值比成员少,那么余下的成员将被设置为零。
6.将引用应用于类对象
string,istream,ostream,ifstream,ofstream类创建的对象。
string类对象可以使用+运算符直接将string对象或字符串常量(相加的元素里面至少有一个string对象)进行相加,方便了我们的编程。比如:std::string a=”hello”;a+=”,how are you”;是可以的。
可以将c风格的字符串用作string类对象const引用参数,这时传递的是字符串的第一个字符的地址,也就是字符串名。这是有两个方面的原因,一个是string类方法能够使用字符串字面值来构建一个string对象,也就是使用char *类型的数据生成string对象。第二是引用参数声明的是const引用,当类型不匹配的时候会进行类型转换并生成临时变量,将临时变量的引用传递给了函数的形参。
7.对象,继承和引用
将特性从一个类传递给另一个类的语言特性被称作是继承。
继承的特性带来一些特点:第一是派生类继承了基类的方法;第二是基类的引用可以指向派生类的对象,而无需进行强制类型转换。这样,可以定义一个接受基类引用作为参数的函数,当函数调用时候可以使用基类对象,也可以使用派生类对象。比如ostream & a参数就可以接受cout,也可以接受ofstream对象。
另外,对于cout(ostream类的对象)和ofstream类的对象,可以使用很多格式化输出,比如头文件中有#include<iostream> #include <fstream>#include <cstdlib>,则可以使用能够控制输出格式的方法,ios_base::fmtflags initial; initial=os.setf(ios_base::fixed);其中ios_base::fmtflags一个数据类型,它可以定义能存储全部输出格式的变量。
setf()函数是ostream类的一个方法,可以设置各种格式,参数都是ios_base::开头的符号常量,其中,ios_base::fixed表示使用定点表示法(也就是使用小数计数法而不是科学计数法),ios_base::showpoint表示显示小数点,即使小数点后面是0。此外还有两个ostream类的方法,precision(n)函数,括号里面的数字表示有效数字的位数,precision是精度的意思,这个函数可以使用cout.precision(n)这种用法,也可以使用cout<<precision(n)这种用法;另一个是width(n)函数,这个函数设置下一次输出的数据的宽度,然后回复为默认值0,也就是显示实际的宽度。
os.setf()函数会返回设置新格式之前的所有格式,将其存入initial中,以便在函数最后回复原来的设置:os.setf(initial);比如开始声明一个ios_base::fmtflags initial;然后初始化为setf()函数的返回值,initial=os.setf(ios_base::fixed);之后可以使用initial来恢复原来的设置。
8.何时使用引用参数
(1)使用引用参数的原因主要有两个:
一个是可以修改调用函数中的数据对象;另一个是当对象较大时候,使用引用可以提高程序的运行速度(不必复制实参)。
(2)什么时候使用指针,引用,按值传递呢?
可以参考下面的一些指导原则:
对于不需要修改原始数据的函数:
简单的内置数据类型或者简单的结构,可以使用按值传递;
如果是数组,则需要使用const指针;
如果是较大的结构,则使用const指针或const引用。
如果是类对象,则要使用const引用,传递类对象参数的标准方式是传递引用。
对于需要修改原始数据的函数:
简单数据类型,需要使用指针;
复杂结构类型,可以使用指针,也可以使用引用;
如果是数组,只能使用指针;
如果是类对象,则使用引用。