引用

本文深入探讨了C++中的引用概念,包括引用的声明、初始化、与指针的区别,引用在函数参数传递中的作用,引用与结构和类的结合使用,以及引用返回值时的注意事项。详细解释了如何正确地使用引用以提高代码的效率和灵活性。
摘要由CSDN通过智能技术生成

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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值