指针到指针和引用到指针






4.65/5 (72投票s)
解释了使用指针到指针和引用到指针来修改传递给函数的指针的原因
目录
引言
本文解释了使用指向指针的指针和指向指针的引用来修改传递给函数的指针的原因,以便更好地理解它们的用法。为简洁起见,我分别使用术语 ptr-to-ptr 和 ref-to-ptr 来表示它们。在本文中,我将不讨论如何将 ptr-to-ptr 用作二维数组或指针数组。请注意,ptr-to-ptr 可用于 C 和 C++,而 ref-to-ptr 仅可用于 C++。
为什么我们需要它们?
当我们使用“指针传递”将指针传递给函数时,函数只会收到指针的副本。我们可以说“指针传递”是按值传递指针。在大多数情况下,这不会造成问题。但当您在函数中修改指针时,问题就来了。您只是在修改指针的副本,而不是修改变量本身,原始指针保持不变,仍然指向旧变量。下面的代码演示了这种行为。
int g_n = 42;
void example_ptr()
{
int n = 23;
int* pn = &n;
std::cout << "example_ptr()" << std::endl;
std::cout << "Before :" << *pn << std::endl; // display 23
func_ptr(pn);
std::cout << "After :" << *pn << std::endl; // display 23
}
void func_ptr(int* pp)
{
pp = &g_n;
}
这不仅是 C/C++ 的问题。Python 也是如此。在下面的示例中,在 fill_up
方法中,字典 instruments
重新初始化了。方法调用后,instruments
仍然是空的。因为 instruments
的副本被传递给了 fill_up
。您可以将 instruments
视为按副本传递的指针。初始化代码就像调用 C++ new
来实例化字典一样。fill_up
中的 instruments
指向新的内存,而外部的 instruments
仍然指向原始内存。在表面之下,计算机语言使用地址来加载和获取数据。即使在不支持指针和地址的 Python 语言中,也无法避免底层使用地址。在 Java 中,引用是伪装的指针或间接指针,带有元信息,会自动解引用,并在垃圾回收后更新其地址。
#!/usr/bin/python
def fill_up(instruments):
instruments = {} # This line caused the problem
instruments["1"] = 1
instruments = {}
fill_up(instruments)
print "len(instruments):%d" % len(instruments)
输出显示调用后 instruments
具有零个元素。
len(instruments):0
下面的代码中移除了初始化
#!/usr/bin/python
def fill_up(instruments):
instruments["1"] = 1
instruments = {}
fill_up(instruments)
print "len(instruments):%d" % len(instruments)
现在,输出正确显示了 instruments
的长度!
len(instruments):1
指针的指针的语法
这是调用带有 ptr-to-ptr 参数的函数的方式。
int g_n = 42;
void example_ptr_to_ptr()
{
int n = 23;
int* pn = &n;
std::cout << "example_ptr_to_ptr()" << std::endl;
std::cout << "Before :" << *pn << std::endl; // display 23
func_ptr_to_ptr(&pn);
std::cout << "After :" << *pn << std::endl; // display 42
}
void func_ptr_to_ptr(int** pp)
{
*pp = &g_n;
}
指向指针的引用的语法
现在让我们看看如何调用带有 ref-to-ptr 参数的函数。许多 C++ 开发者将这种类型误认为是 指向引用的指针。C++ 类型是通过 从右到左读取 来推断的!
int g_n = 42;
void example_ref_to_ptr()
{
int n = 23;
int* pn = &n;
std::cout << "example_ref_to_ptr()" << std::endl;
std::cout << "Before :" << *pn << std::endl; // display 23
func_ref_to_ptr(pn);
std::cout << "After :" << *pn << std::endl; // display 42
}
void func_ref_to_ptr(int*& pp)
{
pp = &g_n;
}
返回指针的语法
或者您可以直接返回指针。
int g_n = 42;
void example_ret_ptr()
{
int n = 23;
int* pn = &n;
std::cout << "example_ret_ptr()" << std::endl;
std::cout << "Before :" << *pn << std::endl; // display 23
pn = func_ret_ptr();
std::cout << "After :" << *pn << std::endl; // display 42
}
int* func_ret_ptr()
{
return &g_n;
}
返回引用的语法
或者您可以直接返回引用。
int g_n = 42;
void example_ret_ref()
{
int n = 23;
int* pn = &n;
std::cout << "example_ret_ref()" << std::endl;
std::cout << "Before :" << *pn << std::endl; // display 23
pn = &func_ret_ref();
std::cout << "After :" << *pn << std::endl; // display 42
}
int& func_ret_ref()
{
return g_n;
}
两者之间的偏好?
现在我们已经看到了 ptr-to-ptr 和 ref-to-ptr 的语法。它们之间有什么优势吗?恐怕没有。对其中一种或两种的使用,对某些程序员来说只是个人偏好。一些使用 ref-to-ptr 的人说语法“更干净”,而一些使用 ptr-to-ptr 的人说 ptr-to-ptr 的语法让读者更容易理解你在做什么。
不要误解指针的指针参数
不要将每个 ptr-to-ptr 参数都误认为是纯粹的 ptr-to-ptr。一个例子是当有人将 int main(int argc, char *argv[])
写成 int main(int argc, char **argv)
时,其中 **argv
实际上是一个指针数组。务必先查看库文档!
指向指针的引用类型(RTTI)
您不能使用 RTTI 来找出 ref-to-ptr 的类型。因为 typeid()
不支持引用类型。
void test(int*& rpInt)
{
std::cout << "type of *&rpInt: " << typeid(rpInt).name()
<< std::endl;//will show int *
}
结论
您可能会问,您是否会在项目中同时使用 ptr-to-ptr 和 ref-to-ptr,以及了解它们是否必要。嗯,作为开发人员,我们使用他人开发的库和技术。一个例子是 COM 使用 ptr-to-ptr 通过 CoCreateInstance()
和 IUnknown::QueryInterface()
返回接口指针。在您的开发生涯的某个阶段,您肯定会遇到它们。了解它们很有好处。
历史记录
- 2018/01/11:添加 链接 以显示 C++ 类型是通过从右到左读取来推断的,以便显示
*&
是指向引用的指针,而不是指向指针的引用 - 2017/08/23:展示具有与 C++ 相同问题的 Python 代码
- 2016/10/02:更简单的示例
- 2009/04/29:更新了说明,并在 C++/CLI 部分添加了指向句柄的跟踪引用
- 2003/09/01:首次发布