《c++入门经典》笔记12
第12章 创建引用
12.1什么是引用
引用是一个别名。创建引用时,使用另一个对象(目标)的名称来初始化它,从此以后该引用就像是目标的另一个名称,对引用执行的任何操作实际上针对的就是目标。
有些书上说引用就是指针,这不正确。虽然引用常常是使用指针实现的,但是只有编译器开发人员关心这一点,作为程序员,必须区分这两种概念。
指针是存储另一个对象的地址的变量,而引用时对象的别名。
12.2创建引用
要创建引用,需要指定目标对象的类型、引用运算符(&)和引用名。
程序清单12.1 Reference.cpp
#include <iostream>
int main()
{
int intOne;
int &rSomeRef = intOne;
intOne = 5;
std::cout << "intOne: " << intOne << std::endl;
std::cout << "rSomeRef: " << rSomeRef << std::endl;
rSomeRef = 7;
std::cout << "intOne: " << intOne << std::endl;
std::cout << "rSomeRef: " << rSomeRef << std::endl;
return 0;
}
12.3将地址运算符用于引用
如果请求返回引用的地址,就将返回它指向的目标的地址。这是引用的特征:他们是目标的别名。
程序清单12.2 Reference2.cpp
#include <iostream>
int main()
{
int intOne;
int &rSomeRef = intOne;
intOne = 5;
std::cout << "intOne: " << intOne << std::endl;
std::cout << "rSomeRef: " << rSomeRef << std::endl;
std::cout << "&intOne: " << &intOne << std::endl;
std::cout << "&rSomeRef: " << &rSomeRef << std::endl;
return 0;
}
通常,使用引用时,不将地址运算符用于它,而像使用目标变量那样使用引用。
程序清单12.3 Assignment.cpp
#include <iostream>
int main()
{
int intOne;
int &rSomeRef = intOne;
intOne = 5;
std::cout << "intOne: " << intOne << std::endl;
std::cout << "rSomeRef: " << rSomeRef << std::endl;
std::cout << "&intOne: " << &intOne << std::endl;
std::cout << "&rSomeRef: " << &rSomeRef << std::endl;
int intTwo = 8;
rSomeRef = intTwo;
std::cout << "
intOne: " << intOne << std::endl;
std::cout << "intTwo: " << intTwo << std::endl;
std::cout << "rSomeRef: " << rSomeRef << std::endl;
std::cout << "&intOne: " << &intOne << std::endl;
std::cout << "&intTwo: " << &intTwo << std::endl;
std::cout << "&rSomeRef: " << &rSomeRef << std::endl;
return 0;
}
12.4可引用的目标
因为对象也是一种变量,所以可引用任何对象,包括用户定义的对象。可以像使用对象那样使用对象的引用:访问成员数据和成员函数时,使用类成员访问运算符(.)与内置类型的引用一样,指向对象的引用也是对象的别名。
12.5空指针和空引用
指针未初始化或被删除时,应将其赋为nullptr,但引用不一样,引用不能为空,让引用指向空对象的程序是非法的。
12.6按引用传递函数参数
前面知道了函数的两个局限性:参数按值传递;return语句只能返回一个值。
通过将值按引用传递给函数,可消除这两种局限性。在c++中,按引用传递时通过两种方式完成的:使用指针和使用引用。他们的语法不同,但效果相同:不是在函数作用域内创建备份(也就是不是值拷贝),而是将原始对象传递给函数。
程序清单12.4 ValuePasser.cpp
#include <iostream>
void swap(int x, int y);
int main()
{
int x = 5, y = 10;
std::cout << "Main. Before swap,x: " << x << " y: " << y << std::endl;
swap(x, y);
std::cout << "Main. Before swap,x: " << x << " y: " << y << std::endl;
return 0;
}
void swap(int x, int y)
{
int temp;
std::cout << "Swap. Before swap,x: " << x << " y: " << y << std::endl;
temp = x;
x = y;
y = temp;
std::cout << "Swap. After swap,x: " << x << " y: " << y << std::endl;
}
main()中的值都没变,可见值拷贝并不能改变原参的值
使用指针实现swap()
程序清单12.5 PointerSwap.cpp
#include <iostream>
void swap(int *x, int *y);
int main()
{
int x = 5, y = 10;
std::cout << "Main. Before swap,x: " << x << " y: " << y << std::endl;
swap(&x, &y);//将地址作为参数传递
std::cout << "Main. Before swap,x: " << x << " y: " << y << std::endl;
return 0;
}
void swap(int *px, int *py)//参数声明为指针
{
int temp;
std::cout << "Swap. Before swap,*px: " << *px << " *py: " << *py << std::endl;
temp = *px;
*px = *py;
*py = temp;
std::cout << "Swap. After swap,*px: " << *px << " *py: " << *py << std::endl;
}
使用引用实现swap()
c++的目标之一时,避免函数的调用者操心函数的工作原理,而将注意力放在函数的功能和返回值上。传递指针将负担转嫁给了调用方,而这种负担原本不应该由调用方来承担:调用方必须知道将要交换的对象的地址传入。
明白引用语法的负担应由函数的实现方承担。为此,可使用引用。
程序清单12.6 ReferenceSwap.cpp
#include <iostream>
void swap(int &x, int &y);
int main()
{
int x = 5, y = 10;
std::cout << "Main. Before swap,x: " << x << " y: " << y << std::endl;
swap(x, y);
std::cout << "Main. Before swap,x: " << x << " y: " << y << std::endl;
return 0;
}
void swap(int &rx, int &ry)//参数声明为引用
{
int temp;
std::cout << "Swap. Before swap,rx: " << rx << " ry: " << ry << std::endl;
temp = rx;
rx = ry;
ry = temp;
std::cout << "Swap. After swap,rx: " << rx << " ry: " << ry << std::endl;
}
可见两种方式(指针传地址、引用传原参)达到的效果是一样的,但是引用传递中,调用方只需传递变量,且在函数内部,需要使用的特殊符号减少了,降低了程序的复杂性。引用将常规变量方便而易于使用的特点和指针的强大融为一体。
12.7理解函数头和原型
函数原型的另一个重要用途:通过查看原型中声明的参数(函数原型通常放在头文件中),程序员知道swap()的参数是按指针还是引用传递的,从而将正确调用他们。
在c++中,类的使用者(其他类中使用该类的函数)依赖于头文件来获悉需要的所有信息。头文件相当于类或函数的接口,而实际实现对使用者是隐藏的。这让程序员能够将主要精力放在要解决的问题上,而使用类或函数时无需关心它是如何实现的。
12.8返回多个值
(哦,这说和没说是一样的,还是只能return一个东西)
一种解决办法是将多个对象按引用传入函数,然后在函数中将正确的值赋给这些对象。由于按引用传递让函数能够修改原始对象,因此这相当于让函数能够返回多项信息。这种函数未使用函数的返回值,可将其(指返回值)用于报告错误。
另一种办法是使用指针或引用来实现。
程序清单12.7 ReturnPointer.cpp
#include <iostream>
short factor(int, int *, int *);
int main()
{
int number, squared, cubed;
short error;
std::cout << "Enter a number(0 - 20): ";
std::cin >> number;
error = factor(number, &squared, &cubed);
if (!error)
{
std::cout << "number: " << number << "
";
std::cout << "square: " << squared << "
";
std::cout << "cubed: " << cubed << "
";
}
else
std::cout << "Error encountered!!
";
return 0;
}
short factor(int n, int *pSquared, int *pCubed)
{
short vaule = 0;
if (n > 20)
{
vaule = 1;
}
else
{
*pSquared = n * n;
*pCubed = n * n * n;
vaule = 0;
}
return vaule;
}
按引用返回值
虽然程序ReturnPointer.cpp可行,但是如果使用引用而不是指针,将更容易理解和维护。
程序清单12.8 ReturnReference.cpp
#include <iostream>
enum ERR_CODE//枚举
{
SUCCESS,
ERROR
};
ERR_CODE factor(int, int &, int &);
int main()
{
int number, squared, cubed;
ERR_CODE result;
std::cout << "Enter a number(0 - 20): ";
std::cin >> number;
result = factor(number, squared, cubed);
if (result == SUCCESS)
{
std::cout << "number: " << number << "
";
std::cout << "square: " << squared << "
";
std::cout << "cubed: " << cubed << "
";
}
else
std::cout << "Error encountered!!
";
return 0;
}
ERR_CODE factor(int n, int &pSquared, int &pCubed)
{
short vaule = 0;
if (n > 20)
{
return ERROR;
}
else
{
pSquared = n * n;
pCubed = n * n * n;
return SUCCESS;
}
}