簡體   English   中英

C++| BST 引用節點指針與節點指針

[英]C++| BST reference-to-node-pointer vs. a node-pointer

假設我有這個 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);
    }
  }

我知道對點的引用將讓我直接訪問傳遞的指針。 但是,我堅持兩種方法之間的區別。 在第二種方法中,盡管指針本身沒有作為引用傳遞,但控制流中使用的本地副本仍然有效。

OP 問題是關於call-by-value 與 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是通過引用傳遞的。

十分簡單? 當然。 但是,如果參數類型這是完全相同的inta是由任何其它類型的置換-例如Node*或甚至Node<T>*

我從 OPs 代碼中取出相關行:

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

如果參數parent值為nullptr ,則為parent分配新創建的Node<T>的地址。 從而修改通過引用傳遞的指針(變量)。 因此,在離開函數_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<T>的地址。 因此,指針按值傳遞。 因此,(原始)變量(在調用中使用)沒有改變——並且在函數離開時仍然包含nullptr

順便提一句。 據此,創建的Node<T>的地址將丟失。 (它不再存儲在任何地方。)然而, Node<T>實例仍然駐留在它們分配的內存中——在進程結束前不可訪問——降級為一塊浪費的內存。 這是一個如何發生內存泄漏的示例。

請不要將這個事實與指針本身“模仿”傳遞引用的另一個事實混淆。 指針指向的對象(類型為Node<T> )的修改(如果它不是nullptr )將變得持久。

仔細查看_add() ,似乎只有指向對象(類型Node<T> )被修改,但從未修改指針本身。 因此,按值傳遞它是完全足夠的。

但是為了_insert()的正確工作,對parent本身的修改也必須變得持久。 因此,只有第一個選項才能正常工作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM