为什么基于指针交换两个值在函数范围之外不起作用?
Why swapping two values based on pointers don't work outside function scope?
我已经很多年没有用C++编程了,所以我决定在指针上刷新我的记忆。
在两个数字之间交换的经典示例中,示例是
void swapPointersClassic(double *num1, double *num2)
{
double temp;
temp = *num1;
*num1 = *num2;
*num2 = temp;
}
这允许我们像swapPointersClassic(&foo, &bar);
一样进行函数调用,并且因为我们传入变量 foo 和 bar 的内存地址,函数将检索值并进行交换。但我开始怀疑,为什么我不能做到以下几点?
void swapPointers(double *num1, double *num2)
{
double *temp;
temp = num1;
num1 = num2;
num2 = temp;
}
这对我来说似乎更有意义,因为我们只需要创建足够的临时存储来存储内存地址 num1(而不是用于存储双精度值 *num1 的完整临时存储)。但是,函数作用域似乎限制了指针交换的效果。在调用swapPointers(&foo, &bar);
后,我可以看到在函数swapPointers中,foo和bar确实被交换了。一旦我们退出交换指针函数,foo和bar就不再交换。谁能帮我理解为什么会这样?这种行为让我想起了典型的按值传递方法,但我们在这里通过指针传递。所以这意味着我们只能触摸这些指针所指向的值,而不能触摸指针本身?
事实上,你并没有真正通过指针。按值传递两个指针。传递指针本身是不够的 - 您必须取消引用它们。取消引用指针(副本)的行为使魔术发生,并且它是实现范围遍历的实际位置。
修改函数的参数是函数的本地参数,即使参数本身是指针也是如此。您必须取消引用它们才能访问它们指向的内存(同样,"通过指针传递"不仅仅是传递指针 - 它是传递指针并正确使用它们)。
此外,请考虑以下事项。如果您的第二种方法有效,并且交换了两个指针,那么这意味着变量的地址被交换了。这不太有意义。
顺便说一下,在C++中,我们有一个真正的按引用传递调用约定,所以有无需弄乱指针:
void swap(double &a, double &b)
{
double temp = a;
a = b;
b = temp;
}
float a = 1, b = 2;
swap(a, b);
(这只是一个实现问题,编译器很可能会通过实际使用指针来实现这种行为,但至少你不会头疼。
如果你想交换指针,而不是它们的值,你需要传递指针的指针:
void swapPointers(double** num1, double** num2)
{
double* temp = *num1;
*num1 = *num2;
*num2 = temp;
}
您可以在以下位置查看示例: http://ideone.com/iklCta
您甚至可以使用引用:
void swapPointers(double*& num1, double*& num2) {
double* temp = num1;
num1 = num2;
num2 = temp;
}
示例:http://ideone.com/w4k9mV
当您处理大型对象(例如:图像数据)并且不想移动大量内存而只想移动引用时,这很有用。
这是一个很好的问题,但是一旦你弄清楚了,请使用std::swap
或std::iter_swap
而不是自己写
。如果foo
和bar
是指针,则调用std::swap(foo, bar)
将在两个指针之间交换地址。调用std::swap(*foo, *bar)
或std::iter_swap(foo, bar)
将取消引用指针并交换指针指向的对象。
您正在按值传递指针本身;num1
和num2
是调用方用来调用函数的指针的本地副本。因此,当函数退出时,交换在调用站点上不可见,因为修改仅对函数的局部变量进行。
相反,更改函数以引用指针,交换代码将按预期工作。
void swapPointers(double*& num1, double*& num2) { ... }
现在,函数参数是调用方传递给函数的指针的别名,无论对它们执行什么操作,都会影响调用方上下文中的指针。
当你打电话时
swapPointersClassic(&foo, &bar)
您正在获取堆栈上两个项目的地址。这些值作为临时值传入,并按值复制。在你的函数体中,你交换这些临时指向的位置,但不移动foo和bar指向的位置。
最重要的是,foo和bar在堆栈上直播。您无法更改变量在 C++ 中堆栈中的位置。值映射到堆栈上的某个位置并驻留在那里。您可以获取指向该值所在位置的指针以执行逐个指针传递(类似于按引用传递),但仅通过更改该临时指针指向的位置,您不会更改指向的对象所在的位置。
当您将double *num1
传递到函数中时,您将按值传入指针。这会在函数作用域中创建一个变量,该变量保存双精度的地址。分配该值时,您将更改函数作用域中指针的值。
就像您必须传入指向双精度的指针,取消引用它,并分配以交换双精度一样,您必须传入指向指针的指针,取消引用它,然后分配以交换指针。
指针101:指针是一张纸,上面写着房子的地址。
通过写 &bar,你正在把这个叫做酒吧的房子写在一张匿名的废纸上。
然后,使用该指针作为参数调用函数。 这里发生的事情是该函数制作了一张纸的另一个副本。
然后,您的交换函数会获取两个这样的本地副本,并交换它们上写入的地址。
因此,这在函数之外没有任何作用。 清楚?
现在基本上,即使地址功能中没有副本,您正在使用的只是带有房屋地址的废纸。 对此类纸张的任何更改实际上都无法移动存储在房屋酒吧或foo中的数据。
由于这个问题的正确答案已经发布,我只想说清楚一些事情。
使用引用交换两个变量的值通常是不好的做法,应避免。 原因是不应重新分配引用。查看以下代码:
1. void swap(double &a, double &b)
2. {
3. double temp = a;
4. a = b; //Not what you think
5. b = temp;
6. }
7.
8. float a = 1, b = 2;
9. swap(a, b);
在第 4 行和第 5 行,您显然违反了通用规则 - "从不重新分配引用"。
如果您确实需要更改变量的值,请始终更喜欢"通过指针传递"而不是"通过引用传递"。 以下代码是有意义的,并且是 IMO 的良好做法。
1. void swapPointersClassic(double *num1, double *num2)
2. {
3. double temp;
4. temp = *num1;
5. *num1 = *num2;
6. *num2 = temp;
7. }
8.
9. float a = 1, b = 2;
10. swap(&a, &b);
同意引用更干净、更易于使用,并且它们在隐藏信息方面做得更好,正如您在第一个示例中看到的那样。 但是,不能重新分配引用。如果需要先指向一个对象,然后再指向另一个对象,则必须使用指针。
经验法则:
- 如果引用有效,请不要使用指针。
- 不要尝试将引用重新分配给其他变量。你不能。
- 通过函数指针定义类范围之外的方法
- 是否可以依赖函数范围的静态变量来执行程序关闭期间调用的方法?
- 重载运算符的范围是什么?它是否会影响作为类成员的集合的插入函数?
- 在函数范围内在堆栈上分配的数组在离开函数时是否总是被释放?
- 在函数内创建的对象的范围 - 如果在函数外部存储和访问引用,它们是否有效?
- 给定一个C++嵌套的私有结构类型,是否有从文件范围静态函数访问它的策略
- 程序中的布尔函数返回输入的范围无论如何都是无效的
- 有没有办法在另一个函数中加入线程?(即超出其自身范围)
- 从函数返回范围视图时,带有std::span:中间对象所有权的C++Ranges-v3
- 在可变参数函数中转发特定范围的参数
- std::线程不是全局变量,但在到达创建它的函数的末尾时不会超出范围?
- 使函数参数默认为周围范围
- 在 gcc/clang (C++) 中获取函数范围之外的标签地址
- C++ 在编译过程中 strtok 函数 Eclipse 说没有在范围内声明?
- 包含来自另一个文件的函数会导致范围错误(openFoam)
- 定义一个 void f(void) 函数,但使用来自同一范围的变量?
- 私有在函数定义/实现的返回值范围内是什么意思 (c++)?
- 函数范围的静态变量如何导致与共享库中函数代码的未来使用不兼容
- 成员变量在超出BeginPlay函数虚幻引擎的范围时丢失值c++
- 将自适应阈值应用于范围函数 opencv c++