引用变量
引用是已定义的变量的别名。引用变量的主要用途是用作函数的形参。通过将引用变量用作参数,函数将使用原始数据,而不是其副本。这样除指针之外,引用也为函数处理大型结构提供了一种非常方便的途径。对于设计类来说,引用也是必不可少的。
创建引用变量
用&来声明引用:
int rats;
int & rodents = rats;
其中,&不是地址运算符,而是类型标识符的一部分。int &指的是指向int的引用,上述引用声明允许将rats和rodents互换——它们指向相同的值和内存单元。
程序8.2
#include <iostream>
int main()
{
using namespace std;
int rats = 101;
int & rodents = rats;
cout << "rats = " << rats;
cout << ", rodents = " << rodents << endl;
rodents++;
cout << "rats = " << rats;
cout << ", rodents = " << rodents << endl;
cout << "rats address = " << &rats;
cout << ", rodents address = " << &rodents << endl;
return 0;
}
从输出可知,rats和rodents的值和地址都相同。将rodents加1将影响这两个变量(实质上是有两个名称的同一个变量)。
必须在声明引用时将其初始化,而不能像指针那样,先声明再赋值。
引用更接近与const指针,必须在创建时进行初始化:
int & rodents = rats;
//实际上是下面代码的伪装表示:
int * const pr = &rats;
//引用rodents与表达式*pr相同。
程序8.3
#include <iostream>
int main()
{
using namespace std;
int rats = 101;
int & rodents = rats;
cout << "rats = " << rats;
cout << ", rodents = " << rodents << endl;
cout << "rats address = " << &rats;
cout << ", rodents address = " << &rodents << endl;
int bunnies = 50;
rodents = bunnies; //can we change the reference?
cout << "bunnies = " << bunnies;
cout << ", rats = " << rats;
cout << ", rodents = " << rodents << endl;
cout << "bunnies address = " << &bunnies;
cout << ", rodents address = " << &rodents << endl;
return 0;
}
将bunnies赋给rodents,只是改变了变量的值,而没有改变其地址,同事rats的值也变了。
将引用用作函数参数
引用用作函数参数,使得函数中的变量名称为调用程序中的变量的别名。这种传递参数的方法称为按引用传递。
程序8.4
#include <iostream>
void swapr(int & a, int & b);
void swapp(int *p, int * q);
void swapv(int a, int b);
int main()
{
using namespace std;
int wallet1 = 300;
int wallet2 = 350;
cout << "wallet1 = $" << wallet1;
cout << " wallet2 = $" << wallet2 << endl;
cout << "Using references to swap contents:\n";
swapr(wallet1, wallet2);
cout << "wallet1 = $" << wallet1;
cout << " wallet2 = $" << wallet2 << endl;
cout << "Using pointers to swap contents again:\n";
swapp(&wallet1, &wallet2);
cout << "wallet1 = $" << wallet1;
cout << " wallet2 = $" << wallet2 << endl;
cout << "Trying to use passing by value:\n";
swapv(wallet1, wallet2);
cout << "wallet1 = $" << wallet1;
cout << " wallet2 = $" << wallet2 << endl;
return 0;
}
void swapr(int & a, int & b)
{
int temp;
temp = a;
a = b;
b = temp;
}
void swapp(int * p, int * q)
{
int temp;
temp = *p;
*p = *q;
*q = temp;
}
void swapv(int a, int b)
{
int temp;
temp = a;
a = b;
b = temp;
}
程序8.4交换两个变量的值,交换函数必须能修改调用程序中的变量的值,因此按值传递变量将不管用。而传递引用和指针则可以访问原始数据。
引用的属性和特别之处
程序8.5采用两个函数来计算参数的立方,一个函数接受double类型的参数,另一个接受double引用。
程序8.5
#include <iostream>
double cube(double a);
double refcube(double &ra);
int main()
{
using namespace std;
double x = 3.0;
cout << cube(x);
cout << " = cube of " << x << endl;
cout << refcube(x);
cout << " = cube of " << x << endl;
system("pause");
return 0;
}
double cube(double a)
{
a *= a * a;
return a;
}
double refcube(double &ra)
{
ra *= ra * ra;
return ra;
}
为了说明按引用传递的特点,程序将函数写得比较奇怪。refcube()函数修改了main()中的x值,而cube()没有,这提醒我们为何通常按值传递。
若想让函数使用引用,同时又不对这些变量进行修改,则应使用const:
double refcube(const double &ra);
如果这样做,当编译器发现代码修改了ra的值时,将生成错误消息。
临时变量、引用参数和const
如果实参和引用参数不匹配,C++将生成临时变量。
如果引用参数是const,则编译器将在下面两种情况下生成临时变量:
- 实参的类型正确,但不是左值;
- 实参的类型不正确,但可以转换为正确的类型。
左值:左值参数是可被引用的数据对象,例如:变量、数组元素、结构成员,引用和解除引用的指针都是左值。
非左值包括字面常量(用引号括起的字符串除外,它们由其地址表示)和包含多项的表达式。
C++11新增了右值引用,使用&&声明:
double && rref = std::sqrt(36.00);
double j = 15.0;
double && jref = 2.0*j + 18.5;
std::cout << rref << '\n';
std::cout << jref << '\n';
将引用用于结构
引用非常适合用于结构和类。引入引用主要是为了这些类型。
程序8.6
#include <iostream>
#include <string>
struct free_throws
{
std::string name;
int made;
int attempts;
float percent;
};
void display(const free_throws & ft);
void set_pc(free_throws & ft);
free_throws & accumulate(free_throws & target, const free_throws & source);
int main()
{
//partial initializations = remaining menbers set to 0
free_throws one = { "Ifslsa Branch", 13, 14 };
free_throws two = { "Andor Knott", 10,16 };
free_throws three = { "Minnie Max", 7, 9 };
free_throws four = { "Whily Looper", 5, 9 };
free_throws five = { "Long Long", 6, 14 };
free_throws team = { "Throwgoods", 0, 0 };
//no initialization
free_throws dup;
set_pc(one);
display(one);
accumulate(team, one);
display(team);
//use return value as argument
display(accumulate(team, two));
accumulate(accumulate(team, three), four);
display(team);
//use return value in assignment
dup = accumulate(team, five);
std::cout << "Display team:\n";
display(team);
std::cout << "Display dup after assignment:\n";
display(dup);
set_pc(four);
//ill-advised assignment
accumulate(dup, five) = four;
std::cout << "Displaying dup after ill-advised assignment:\n";
display(dup);
system("pause");
return 0;
}
void display(const free_throws & ft)
{
using std::cout;
cout << "Name: " << ft.name << '\n';
cout << " Made:" << ft.made << '\t';
cout << "Attempts: " << ft.attempts << '\t';
cout << "Percent: " << ft.percent << '\n';
}
void set_pc(free_throws & ft)
{
if (ft.attempts != 0)
ft.percent = 100.0f *float(ft.made) / float(ft.attempts);
else
ft.percent = 0;
}
free_throws & accumulate(free_throws & target, const free_throws & source)
{
target.attempts += source.attempts;
target.made += source.made;
set_pc(target);
return target;
}
程序使用了一条赋值语句:
accumulate(dup,five) = four;
这条语句将值赋给函数调用,这是可行的,因为函数的返回值是一个引用。如果函数按值返回,这条语句将不能通过编译。由于返回的是指向dup的引用,因此上述代码与下面的代码等效:
accumulate(dup,five);
dup = four;
对于下面的语句:
dup=accumulate(team,five);
如果accumulate()返回一个结构,而不是指向结构的引用,将把整个结构复制到一个临时位置,在讲这个拷贝复制给dup;但在返回值为引用时,将直接把team复制到dup,其效率更高。
返回引用的函数实际上是被引用变量的别名。
返回引用时最重要的一点是,要避免返回函数终止时不在内存单元的引用。应避免编写下面这样的代码:
const free_throws & clone2(free_throws & ft)
{
free_throws newguy;
newguy = ft;
return newguy;
}
该函数返回一个指向临时变量(newguy)的引用,函数运行完毕后它将不再存在。
为避免这种问题,可以返回一个作为参数传递给函数的引用,如程序8.6。
还可以使用new来分配新的存储空间,并返回指向该内存空间的指针。
将引用用于类对象
将类对象传递给函数时,C++通常的做法是使用引用。
程序8.7
#include <iostream>
#include <string>
using namespace std;
string version1(const string & s1, const string & s2);
const string & version2(string & s1, const string & s2);
const string & version3(string & s1, const string & s2);
int main()
{
string input;
string copy;
string result;
cout << "Enter a string: ";
getline(cin, input);
copy = input;
cout << "Your string as entered: " << input << endl;
result = version1(input, "***");
cout << "Your string enhanced: " << result << endl;
cout << "Your original string: " << input << endl;
result = version2(input, "###");
cout << "Your string enhanced: " << result << endl;
cout << "Your original string: " << input << endl;
cout << "Resetting original string.\n";
input = copy;
result = version3(input, "@@@");
cout << "Your string enhanced: " << result << endl;
cout << "Your original string: " << input << endl;
system("pause");
return 0;
}
string version1(const string & s1, const string & s2)
{
string temp;
temp = s2 + s1 + s2;
return temp;
}
const string & version2(string & s1, const string & s2)
{
s1 = s2 + s1 + s2;
//safe to return reference passed to function
return s1;
}
const string & version3(string & s1, const string & s2)
{
string temp;
temp = s2 + s1 + s2;
//unsafe to return reference to local variable
return temp;
}
version3()返回一个指向函数中声明的变量的引用,这个函数能通过编译,但编译器会发出警告,当程序试图执行该函数时将会崩溃。
对象、继承和引用
基类引用可以指向派生类对象,而无需进行强制类型转换。可以定义一个接受基类引用作为参数的函数,调用该函数时,可以将基类对象作为参数,也可以将派生类对象作为参数。例如,参数类型为ostream &可以接受ostream对象(如cout)或您声明的ofstream对象作为参数。
#include <iostream>
#include <fstream>
#include <cstdlib>
using namespace std;
void file_it(ostream & os, double fo, const double fe[], int n);
const int LIMIT = 5;
int main()
{
ofstream fout;
const char * fn = "ep-data.txt";
fout.open(fn);
if (!fout.is_open())
{
cout << "Can't open " << fn << ".Bye.\n";
exit(EXIT_FAILURE);
}
double objective;
cout << "Enter the focal length of your "
"telescope objective in mm: ";
cin >> objective;
double eps[LIMIT];
cout << "Enter the focal lengths, in mm, of " << LIMIT
<< " eyepieces:\n";
for (int i = 0; i < LIMIT; i++)
{
cout << "Eyepiece #" << i + 1 << ": ";
cin >> eps[i];
}
file_it(fout, objective, eps, LIMIT);
file_it(cout, objective, eps, LIMIT);
cout << "Done\n";
system("pause");
return 0;
}
void file_it(ostream & os, double fo, const double fe[], int n)
{
ios_base::fmtflags initial;
initial = os.setf(ios_base::fixed); //save initial formatting state
os.precision(0);
os << "Focal length of objective: " << fo << " mm\n";
os.setf(ios::showpoint);
os.precision(1);
os.width(12);
os << "f.l. eyepiece";
os.width(15);
os << "magnification" << endl;
for (int i = 0; i < n; i++)
{
os.width(12);
os << fe[i];
os.width(15);
os << int(fo / fe[i] + 0.5) << endl;
}
os.setf(initial); //restore initial formatting state
}
对于该程序,最重要的一点是,参数os(其类型为ostream &)可以指向ostream对象(如cout),也可以指向ofstream对象(如fout)。
方法setf()能够设置各种格式化状态,例如,方法调用
setf(ios_base::fixed)将对象置于使用定点表示法的模式;
setf(ios_base::showpoint)将对象置于显示小数点的模式,即使小数部分为零。
方法precision()指定显示多少位小数(假设对象处于定点模式下)。
所有这些设置都一致驳斥不变,直到再次调用相应地方法重新设置它们。
方法width()设置下一次输出操作使用的字段宽度,这种设置只在显示下一个值时有效,然后将回复到默认设置。默认的字段宽度为零,这意味着刚好能容纳下要显示的内容。
ios_base::fmtflags是存储格式化设置信息的数据类型。
何时使用引用参数
使用引用参数的原因主要有两个:
- 程序员能够修改调用函数中的数据对象;
- 通过传递引用而不是真个数据对象,可以提高程序的运行速度。
对于使用传递的值而不作修改的函数: - 如果数据对象很小,如内置数据类型或小型结构,则按值传递;
- 如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针;
- 如果数据对象是较大的结构,则使用const指针或const引用,以提高程序的效率。这样可以节省复制结构所需的时间和空间;
- 如果数据对象是类对象,则使用const引用。类设计的语义常常要求使用引用,这是C++新增这项特性的主要原因。因此,传递类对象参数的标准方式是按引用传递。
对于修改调用函数中数据的函数: - 如果数据对象是内置数据类型,则使用指针。如果看到诸如fixit(&x)这样的代码,则很明显,该函数将修改x;
- 如果数据对象是数组,则只能使用指针;
- 如果数据对象是结构,则使用引用或指针;
- 如果数据对象是类对象,则使用引用。