C++中引用--&知识点的总结

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. 引用的属性和特别之处
 
#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;
}

3.将引用用于结构
引用非常适合用于结构和类。确实,引入引用主要是为了用于这些类型的,而不是基本的内置类型。
 
将引用用于参数传递
就打印显示函数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);
 

猜你喜欢

转载自blog.csdn.net/ys5858588/article/details/81164969
今日推荐