访谈:按参考分配(而不是按参考传递)的实际用途

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

提问人:John Sonderson 提问时间:2/8/2015 更新时间:2/8/2015 访问量:94

问:

有一次,我参加了一次面试,被问到通过引用分配变量的目的是什么(如下例所示):

int i = 0;

int &j = i;

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

但后来我继续说,上面引用的使用是相当无用的(也许这就是我出错的地方),因为我看不到按引用分配的实际优势:由于两个引用最终都指向同一件事,你可以很容易地用一个引用来做,并且想不出这种情况的情况。我接着解释说,引用作为逐个引用函数参数很有用,但在赋值中却没有用。但面试官说他们总是在代码中引用分配,并且让我不屑一顾(然后我继续为这家公司的客户工作,但这不是重点)。

无论如何,几年后,我想知道我可能在哪里出错了。

C++ 引用 变量赋值 运算符赋值

评论

1赞 didierc 2/8/2015
“如果一个引用被更新,那么指向同一事物的其他引用也会更新为指向同一个对象。我想知道你从哪里得到的。

答:

3赞 Mats Petersson 2/8/2015 #1

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

正如评论中提到的,参考文献在其一生中永远不会改变它们所指的内容。一旦设置,引用将引用相同的位置,直到它“死亡”。

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

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 stuffcurconst A &cur =...

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

评论

0赞 John Sonderson 2/8/2015
指针也可以完全相同,只是使用引用可以少键入几个字符,因此在这些情况下,引用是可取的。好吧,至少我很满意我在面试中没有遗漏太多。问候。
1赞 Mats Petersson 2/9/2015
是的,当然,您几乎总是可以使用指针和引用做同样的事情。有些事情不能用引用来完成(例如,将其重新赋值以指向其他内容)。
2赞 Sergey Kalinichenko 2/8/2015 #2

在将引用分配给同一作用域中基元类型的局部变量的这种特殊情况下,赋值是非常无用的:没有任何事情是你不能使用的。它也有一些轻微的负面影响,因为可读性会受到影响,优化器可能会感到困惑。ji

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

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;
    }
};

上面,引用类型的变量是指映射中检索或新创建的元素。如果字符串是新创建的,则代码将调用“实际”getter 并将其结果分配给 。这也会自动更新缓存,从而节省我们在地图中的另一次查找。resrescache

评论

0赞 John Sonderson 2/8/2015
好的,稍微少一点的打字和轻微的编译器优化,编译器无论如何都可以自己执行。因此,基本上,它只对避免写入字符串属性和数组元素访问的长链有用。谢谢。
1赞 Sergey Kalinichenko 2/8/2015
@JohnSonderson 不过,这不仅仅是“减少输入”:替换为 可以避免对 进行第二次查找,这很重要。res = read_resource(id);cache[id] = read_resource(id);Log(N)map
0赞 John Sonderson 2/9/2015
是的,这是真的,但我对编译器优化的评论指的是编译代码并不总是与您键入的内容相对应。如果编译器发现它被多次引用,它将优化代码并重写它,以便创建对的引用,并将其他变量替换为此类引用。然后编译代码,产生相同的结果。cache[id]cache[id]cache[id]
1赞 Sergey Kalinichenko 2/9/2015
@JohnSonderson 不允许编译器优化函数调用。想一想:如果同时访问地图,并且在两次访问之间对地图进行了更改,该怎么办?编译器无法知道它;程序员会这样做。因此,手动添加引用是避免第二次函数调用的唯一方法。
0赞 John Sonderson 2/9/2015
我猜编译器无法确定一段代码是否要用作多线程解决方案的一部分,而 C++11 现在有一个包含文件和关联的函数调用。谢谢你指出这一点。<thread>