reference : C++ Primer Plus
2.1 创建引用变量
int rats= 101;
int &rodents = rats; //rodents是rats的引用。
rodents的类型为int &,即指向int变量的引用。上述引用申明允许将rats和rodents互换,他们指向相同的值和内存单元。
&不是地址运算符,而是类型标识符的一部分,就像char*指的是指向char的指针一样。
rodents和rats的地址相同,即&rodents和rats相同。
引用与指针:
引用必须在申明时进行初始化,而指针可以不用这样做。
引用更接近const指针,必须在创建时初始化,一旦与某个变量关联起来,就将一直效忠于它。
也就是说:
int &rodents = rats;
实际上是下述代码的伪装表示:
int *const pr = &rats; //*在const左边,地址不可变。
其中引用rodents扮演的角色和*pr相同。
例子:程序清单8.3
#include "iostream"
using namespace std;
void main()
{
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; //rodents依然是rats的引用,把bunnies赋给rodents,也就是把bunnies赋给rats,rodents就是rats。
cout << "bunnies = " << bunnies;
cout << ", rats = " << rats;
cout << ", rodents = " << rodents << endl;
cout << "bunnies address = " << &bunnies;
cout << ", rats address = " << &rats;
cout << ", rodents address = " << &rodents << endl;
cin.get();
}
补充说明:
int rats = 101;
int *pt = &rats;
int &rodents = *pt;
int bunnies = 50;
pt = &bunnies;
将rodents初始化为*pt使得rodents指向rats。接下来将pt改为指向bunnies,并不能改变这样的事实,即rodents引用的是rats。
2.2 将引用用作函数参数
其他的详见书本。
注意:前面说过,引用必须在申明时进行初始化。函数调用使用实参初始化形参,因此函数的引用参数被初始化为函数调用传递的实参。
2.3 引用的属性和特别之处
#include "iostream"
using namespace std;
double refcube(double &ra)
{
ra *= ra * ra;
return ra;
}
void main()
{
double side = 3.0;
cout << refcube(side + 2.0) << endl; //在现代的C++中这是错误的,大多数编译器会编译不通过:非常量引用的初始值必须为左值。
而有些较老的编译器将发出这样的警告:
Warning Temporary used foe parameter 'ra' in call to refcube(double &)
这样做的结果是:由于side+2.0不是double类型的变量,因此程序将创建一个临时无名变量,并将其初始化为表达式side+2.0的值。然后,ra成为该临时变量的引用。
long fuck = 3;
cout << refcube(fuck) << endl; //编译不通过:无法用“long”类型的值初始化“double &”类型的引用(非常量限定)
同上,在有些老编译器中这样做是可以的,这些老编译器会创建一个临时double变量,并将其初始化为3,然后在函数refcube中操作该临时变量,而fuck保持不变。
cin.get();
}
如果将函数refcube改写成如下形式,则函数调用refcube(side + 2.0)和refcube(fuck)就可以编译通过了。
double refcube(const double &ra) //当然此时就不能改变ra的值了。
{
return ra * ra * ra;
}
#include "iostream"
using namespace std;
double refcube(const double &ra)
{
return ra * ra * ra;
}
void main()
{
double side = 3.0;
double *pd = &side;
double &rd = side;
long edge = 5L;
double lens[4] = { 2.0, 5.0, 10.0, 12.0 };
cout << "side = " << side << endl;
cout << side << " , refcube(side) = " << refcube(side) << "\n\n" << endl; //ra is side
cout << "lens[2] = " << lens[2] << endl;
cout << " ,refcube(lens[2]) = " << refcube(lens[2]) << "\n\n" << endl; //ra is rd is side
cout << "rd = " << rd << endl;
cout << " , refcube(rd) = " << refcube(rd) << "\n\n" << endl; //ra is *pd is side
cout << "*pd = " << *pd << endl;
cout << " , refcube(*pd) = " << refcube(*pd) << "\n\n" << endl; //ra is temporary variable
cout << "edge = " << edge << endl;
cout << " , refcube(edge) = " << refcube(edge) << "\n\n" << endl; //ra is temporary variable
cout << "refcube(7.0) = " << refcube(7.0) << "\n\n" << endl; //ra is temporary variable
cout << "side + 2.0 = " << side + 2.0 << endl;
cout << " , refcube(side + 2.0) = " << refcube(side + 2.0) << "\n\n" << endl; //ra is temporary variable
cin.get();
}
其实double refcube(double &ra) 和double refcube(const double &ra)是可以重载的。
下面就看看这两个重载函数的调用情况,main函数不同上。
double refcube(double &ra)
{
cout << "引用 ";
ra *= ra * ra;
return ra;
}
double refcube(const double &ra)
{
cout << "常引用 ";
return ra * ra * ra;
}
2.4 将引用用于结构
引用非常适合用于结构和类。确实,引入引用主要是为了用于这些类型的,而不是基本的内置类型。
将引用用于参数传递
就打印显示函数display来讲,按值传递的display(结构名 &参数名)与按引用传递的display(const 结构名 &参数名)相比,前者要复制原始结构的拷贝,而后者不用,所以后者可以节省时间和内存。
假设有如下结构定义:
struct free_throws
{
std::string name;
int made;
int attempt;
float percent;
};
//函数功能:将source的内容累加到target中。
free_throws &accumulate(free_throws &target, const free_throws &source)
{
target.attempt += source.attempt;
target.made += source.made;
...
return target;
}
void display(const free_throws &ft);
请看如下函数调用:
display(accumulate(team, two));
上述代码是什么意思呢?首先,将结构对象team传给了accumulate(),这意味着在函数accumulate()中,target指向的是team。函数accumulate()修改team,再返回指向它的引用。
如果返回类型被声明为free_throws而不是free_throws &,上述返回语句将返回target(也就是team)的拷贝。但返回类型为引用,这意味着返回的是最初传递给accumulate()的team对象。
接下来,将accumulate()的返回值作为参数传递给了display(),这意味着将team传递给了display()。display的参数为引用,这意味着函数display()中的ft指向的是team。所以,下述代码:
display(accumulate(team, two));
与下面的代码等效:
accumulate(team, two);
display(team);
上述逻辑也适用如下语句:
accumulate(accumulate(team, three), four);
因此,该语句与下面的语句等效:
accumulate(team, three);
accumulate(team, four);
再来看看下面的语句:
free_throws dup;
accumulate(dup, five) = four;
这条语句将值赋给函数调用,这是可行的,因为函数的返回值是一个引用。如果函数accumulate()是按值返回的,则这条语句将不能通过编译。由于返回的是指向dup的引用,因此上述代码与下面的代码等效:
accumulate(dup, five);
dup = four;
第二条语句消除了第一条语句所做的工作,因此在原始赋值语句使用accumulate()的方式并不好。
将引用用于返回值
来看下面这条语句:
free_throws dup = accumulate(team, five);
如果accumulate()返回一个结构,而不是指向结构的引用,将把整个结构复制到一个临时位置,再将这个拷贝复制给dup。但在返回值为引用时,将直接把team复制到dup,其效率更高。
返回引用时需要注意的问题
返回引用时最重要的一点是,应避免返回函数终止时不再存在的内存单元的引用。您应避免编写下面这样的代码:
const free_throws &clone(free_throws &ft)
{
free_throws newguy;
newguy = ft;
return newguy;
}
该函数返回一个指向临时变量的引用,函数运行完毕后它将不再存在。
为避免这种问题,最简单的方法是返回一个作为参数传递给函数的引用。上述的accumulate()正是这样做的。
我这里插一句话哈,若将上述函数的返回类型声明为const free_throws,而不是const free_throws &,那么像下面这样的函数调用也是可以的:
const free_thorws rev = clone(one);
这里我有一个不明白的地方,就是下面的语句为何在运行时不会崩溃:
{
const free_thows &rev = clone(one);
cout << rev.name.c_str() << endl; //为何这里不会崩溃。
}
由于此时clone()返回的是局部变量,而rev正是这个局部变量的引用,而该局部变量会在clone()返回后被释放,但是为何cout << rev.name.c_str() << endl还是能够正常运行。
另一种方法是用new来分配新的存储空间。可以将上述的clone()函数代码改写成如下:
const free_throws &clone(free_throws &ft)
{
free_throws *pt = new free_throws;
*pt = ft;
return *pt;
}
const free_throws &jolly = clone(three);
注意下面这条语句:
free_throws &jolly = clone(three);
它在编译时会报错,因为不能将const引用赋给非const引用,但反过来却可以。
再看下面这条语句:
free_throws jolly = clone(three);
这时jolly虽然不是常量,但却可以编译通过。这是因为此时jolly有自己的内存空间,而不是指向clone()返回的引用。
为何将const用于引用返回类型
前面说过这样的语句:
accumulate(dup, five) = four;
这条语句为何能够通过编译呢?在赋值语句中,左边必须是可以修改的左值。也就是说,在赋值表达式中,左边的子表达式必须标识一个可以修改的内存块。在这里,函数返回指向dup的引用,它确实标识的是一个这样的内存块,因此这条语句是合法的。
另一方面,常规(非引用)返回类型是右值——不能通过地址访问的值,如字面值(10.0)和表达式(x + y)。这种返回值位于临时内存单元中,运行到下一条语句时,它们可能不再存在。
假设您要使用引用返回值,但又不允许执行像给accumulate()赋值这样的操作,只需将返回类型声明为const引用:
const free_throws &accumulate(free_throws &target, const free_throws &source);
2.5 将引用用于类对象
对于下面的函数声明:
string version1(const string &s1, const string &s2);
请看下面的函数调用:
string input = "fuckyou";
string result = version1(input, "***");
由于input的类型为string,因此让s1指向它没有问题。然而,程序怎么能够接受接受将char指针赋给string引用呢?
这里有两点需要说明。首先,string类定义了一种char*到string的转换功能。其次是临时变量的创建,这一点与之前所说的同理。
疑问:
const string func(const string &str)
{
string rev = str;
rev += "fuckyou";
return rev;
}
const char * func2(const string &str)
{
string rev = str;
rev += "fuckyou";
return rev.c_str();
}
void main()
{
string str = func("lily"); //这个可以编译通过
cout << str.c_str() << endl;
char *p = func2("lucy"); //编译不通过,无法从“const char *”转换为“char *”
cout << p << endl;
cin.get();
}
疑问:为何上述两条语句,一条编译通过,而另一条编译却不通过。
2.6 对象、继承和引用
继承的另一个特征是,基类引用可以指向派生类对象,而无需进行强制类型转换。这中特征的一个实际结果是,可以定义一个接受基类引用作为参数的函数,调用该函数时,可以将基类对象作为参数,也可以将派生类对象作为参数。
2.7 何时使用引用参数
全是文字,不想敲了,参见课本P274