【问题标题】:C++| BST reference-to-node-pointer vs. a node-pointerC++| BST 节点指针引用与节点指针
【发布时间】:2020-10-14 03:26:56
【问题描述】:

假设我有这个 BST 模板:

template <typename T> class Node {
private:
public:
  T data;
  Node *left;
  Node *right;

  Node(T dt) : data{dt}, left{nullptr}, right{nullptr} {}
  ~Node() {
    this->data = 0;
    this->left = nullptr;
    this->right = nullptr;
  }
};

template <typename T> class BST {
private:
  Node<T> *_root;

  _insert();
  _add();
  _printOrder_In(Node<T> *parent, std::ostream& os) {
    if (!parent) return;
    _printOrder_In(parent->left, os);
    os << parent->data << ' ';
    _printOrder_In(parent->right, os);
  }

public:
  BST() : _root{nullptr} {}
  ~BST();
  
  insert();
  add();
  std::ostream& print(std::ostream& os = std::cout) {
    _printOder_In(this->_root, os);
    return os;
  }
};

为什么下面的代码在我传递节点指针引用时有效,而当我传递节点指针时无效?

// BST MEMBER FUNCTIONS:
private:
  void _insert(Node<T>* &parent, const T &val) { // works
//void _insert(Node<T>*  parent, const T &val) { // doesn't work, apparently generates nodes indefinitely
    if (!parent)
      parent = new Node<T>{val};
    else {
      if (val < parent->data)
        _insert(parent->left, val);
      else if (val > parent->data)
        _insert(parent->right, val);
      else
        return;
    }
  }

public:
  void insert(const T &val) {
    _insert(this->_root, val);
  }
};

也与这种替代方法相反,它只使用传递的指针:

// BST MEMBER FUNCTIONS:
private:
  void _add(Node<T>* parent, T val) {
    if (parent->data > val) {
      if (!parent->left) {
        parent->left = new Node<T>{val};
      } else {
        _add(parent->left, val);
      }
    } else {
      if (!parent->right) {
        parent->right = new Node<T>{val};
      } else {
        _add(parent->right, val);
      }
    }
  }

public:
  void add(T val) {
    if (this->_root) {
      this->_add(this->_root, val);
    } else {
      this->_root = new Node<T>(val);
    }
  }

我了解指向点的引用将使我能够直接访问传递的指针。但是,我对这两种方法之间的区别感到困惑。在第二种方法中,尽管指针本身没有作为引用传递,但控制流中使用的本地副本仍然有效。

【问题讨论】:

  • //void _insert(Node&lt;T&gt;* parent, const T &amp;val) { 指针parent 在您的函数中分配-它获取new Node 的地址。如果您按值提供指针parent(如在排除行中),则此分配在离开函数后丢失。通过引用提供指针可确保分配“原始”指针(即您调用函数时使用的指针)并且更改变得持久。

标签: c++ pointers reference binary-search-tree


【解决方案1】:

OPs 问题是关于call-by-value vs. call-by-reference

C 语言(C++ 的“锚”)专门提供按值调用。 可以通过使用变量的地址而不是变量本身来模拟缺少的引用调用。 (当然,resp. 函数的参数必须成为指向类型的指针,而不是类型本身。)

因此,指针是按值传递的,但它的值可用于访问函数范围之外的内容,并且修改(在其原始存储中完成)将在该函数返回后继续存在。

当 C++ 从 C 演变而来时,这一原则已被接管。 但是,C++ 添加了引用调用,就像其他类似语言(例如 Pascal)一样。

按值调用与按引用调用的简单演示:

#include <iostream>

void callByValue(int a)
{
  std::cout
    << "callByValue():\n"
    << "  a: " << a << '\n'
    << "  a = 123;\n";
  a = 123;
  std::cout
    << "  a: " << a << '\n';
}

void callByRef(int &a)
{
  std::cout
    << "callByRef():\n"
    << "  a: " << a << '\n'
    << "  a = 123;\n";
  a = 123;
  std::cout
    << "  a: " << a << '\n';
}

int main()
{
  int b = 0;
  std::cout << "b: " << b << '\n';
  callByValue(b);
  std::cout << "b: " << b << '\n';
  callByRef(b);
  std::cout << "b: " << b << '\n';
}

输出:

b: 0
callByValue():
  a: 0
  a = 123;
  a: 123
b: 0
callByRef():
  a: 0
  a = 123;
  a: 123
b: 123

解释:

  • a 的更改仅在callByValue() 中具有局部影响,因为a 是按值传递的。 (即,将参数的副本传递给函数。)
  • a 的变化修改了callByRef() 中传递的参数,因为a 是通过引用传递的。

简单吗?当然。但是,如果 a 的参数类型 int 被任何其他类型替换 - 例如Node* 甚至是Node&lt;T&gt;*

我从 OPs 代码中取出了相关行:

  void _insert(Node<T>* &parent, const T &val) { // works
    if (!parent)
      parent = new Node<T>{val};

如果参数parent 的值是nullptr,则parent 被分配新创建的Node&lt;T&gt; 的地址。因此,通过引用传递的指针(变量)被修改。因此,离开函数_insert()后修改仍然存在。

另一种选择:

  void _insert(Node<T>*  parent, const T &val) { // doesn't work, apparently generates nodes indefinitely
    if (!parent)
      parent = new Node<T>{val};

如果参数parent 的值为nullptr,则parent 被分配新创建的Node&lt;T&gt; 的地址。因此,指针是按值传递的。因此,(原始)变量(在调用中使用)没有改变——当函数离开时仍然包含nullptr

顺便说一句。据此,创建的Node&lt;T&gt; 的地址会丢失。 (它不再存储在任何地方。) 然而,Node&lt;T&gt; 实例仍然驻留在它们分配的内存中——在进程结束之前无法访问——降级为一块浪费的内存。 这是memory-leaks 可能发生的示例。

请不要将此事实与指针本身“模仿”传递引用的事实混淆。 指针指向的对象(Node&lt;T&gt; 类型)的修改(如果不是 nullptr)将变为持久性。

仔细查看_add(),似乎只有指向的对象(Node&lt;T&gt; 类型)被修改,但指针本身没有被修改。 所以,按值传递就完全够用了。

但是为了_insert() 的正确工作,parent 本身的修改也必须变得持久。 因此,只有第一个替代方案可以正常工作。

【讨论】:

  • 感谢您非常详细的解释。我刚刚意识到,在按值传递的版本中,指针的副本是在本地范围内创建和修改的,而不是原始指针本身;一旦函数关闭,这个副本就会被删除。 *&amp; 并排让我很困惑,因为这是我第一次看到它。现在已经清理干净了。
猜你喜欢
  • 2021-02-19
  • 1970-01-01
  • 2015-07-18
  • 2021-05-24
  • 2017-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-19
相关资源
最近更新 更多