面试:按引用分配的实际应用(与按引用传递相反)

Interview: practical uses of assign-by-reference (as opposed to pass-by-reference)

我曾经接受采访,被问及通过引用分配变量的目的是什么(如下例所示):

int i = 0;

int &j = i;

我的回答是 C++ 引用像 C 指针一样工作,但不能假定 NULL 值,它们必须始终指向内存中的具体对象。当然,使用引用时语法不同(不需要指针间接运算符,对象属性将通过点 (.) 而不是箭头 (->) 运算符访问)。也许最重要的区别是,与指针不同,您可以使指针指向不同的东西(即使在它指向与另一个指针相同的东西之后),与引用不同,如果一个引用被更新,那么另一个引用指向同一事物的也被更新为指向同一个对象。

但我接着说上面对引用的使用非常无用(也许这就是我出错的地方),因为我看不到通过引用分配的实际优势:因为两个引用都结束了指向同一件事,你可以很容易地用一个参考来做,并且想不出一个不是这种情况的情况。我接着解释说,引用可用作按引用传递的函数参数,但不适用于赋值。但是面试官说他们一直在他们的代码中通过引用分配,并让我不及格(然后我继续为一家公司工作,该公司是该公司的客户,但那不是重点)。

不管怎样,几年后,我想知道我哪里做错了。

首先,为了那家公司,我希望这不是他们不雇用你的唯一原因,因为这是一个微不足道的细节(不,你真的不知道为什么一家公司不雇用你)。

正如评论中提到的,引用永远不会改变它们在其生命周期内所引用的内容。设置后,引用指向同一位置,直到 "dies".

现在,引用对于简化表达式非常有用。假设我们有一个包含大量复杂内容的 class 或结构。像这样说:

struct A
{
    int x, y, z;
};

struct B
{
    A arr[100];
};

class C
{
 public:
    void func();
    B* list[20];
};

void C::func()
{
    ... 
    if (list[i]->arr[j].x == 4 && list[i]->arr[j].y == 5 &&
        (list[i]->arr[j].z < 10 || list[i]->arr[j].z > 90))
    {
       ... do stuff ...
    }
}

里面有很多 list[i]->arr[j] 的重复。所以我们可以使用参考重写它:

void C::func()
{
    ... 
    A &cur = list[i]->arr[j];
    if (cur.x == 4 && cur.y == 5 &&
        (cur.z < 10 || cur.z > 90))
    {
       ... do stuff ...
    }
}

上面的代码假定 do stuff 实际上是以某种方式修改 cur 元素,如果不是,您应该使用 const A &cur =... 代替。

我经常使用这种技术,以使其更清晰、更少重复。

在这种将引用分配给同一作用域中原始类型的局部变量的特殊情况下,分配非常无用:使用 j 无法做的事情无法使用i。它也有一些轻微的负面影响,因为可读性会受到影响,优化器可能会感到困惑。

这是分配引用的一种合法用途:

class demo {
private:
    map<int,string> cache;
    string read_resource(int id) {
        string resource_string;
        ... // Lengthy process for getting a non-empty resource string
        return resource_string;
    }
public:
    string& get_by_id(int id) {
        // Here is a nice trick
        string &res = cache[id];
        if (res.size() == 0) {
            // Assigning res modifies the string in the map
            res = read_resource(id);
        }
        return res;
    }
};

以上,引用类型的变量 res 指的是检索到或新建的地图元素。如果字符串是新创建的,代码会调用 "real" getter,并将其结果赋给 res。这也会自动更新缓存,从而节省我们在 cache 映射中的另一次查找。