Tại sao danh sách được liên kết sử dụng con trỏ thay vì lưu trữ các nút bên trong các nút


121

Tôi đã làm việc với danh sách được liên kết trước đây nhiều trong Java, nhưng tôi rất mới với C ++. Tôi đang sử dụng lớp nút này đã được cấp cho tôi trong một dự án tốt

class Node
{
  public:
   Node(int data);

   int m_data;
   Node *m_next;
};

nhưng tôi có một câu hỏi không được trả lời tốt. Tại sao cần sử dụng

Node *m_next;

để trỏ đến nút tiếp theo trong danh sách thay vì

Node m_next;

Tôi hiểu rằng tốt hơn là sử dụng phiên bản con trỏ; Tôi sẽ không tranh luận sự thật, nhưng tôi không biết tại sao nó tốt hơn. Tôi nhận được câu trả lời không rõ ràng về cách con trỏ tốt hơn cho việc cấp phát bộ nhớ và tôi tự hỏi liệu có ai ở đây có thể giúp tôi hiểu điều đó tốt hơn không.


14
@self Thứ lỗi cho tôi? Tại sao một ngôn ngữ mà mọi thứ là con trỏ lại không có danh sách liên kết?
Angew không còn tự hào về SO

41
Điều quan trọng cần lưu ý là C và C ++ khác biệt với Java như thế nào về con trỏ đối tượng so với tham chiếu. Node m_nextkhông phải là một tham chiếu đến một nút, nó là nơi lưu trữ cho toàn bộ Nodechính nó.
Brian Cain

41
@self Java có các con trỏ mà bạn không sử dụng chúng một cách rõ ràng.
m0meni

27
Rùa tất cả các con đường xuốngkhông một lựa chọn. Sự điên rồ phải kết thúc ở đâu đó.
WhozCraig,

26
Hãy quên mọi thứ bạn biết về Java. C ++ và Java xử lý bộ nhớ theo những cách cơ bản khác nhau. Đi xem câu hỏi này để biết các đề xuất sách, chọn một câu và đọc. Bạn sẽ làm cho chúng tôi tất cả một ân huệ lớn.
Rob K

Câu trả lời:


218

Nó không chỉ tốt hơn mà còn là cách duy nhất có thể.

Nếu bạn lưu trữ một Node đối tượng bên trong chính nó, điều gì sẽ sizeof(Node)là? Nó sẽ là sizeof(int) + sizeof(Node), sẽ bằng sizeof(int) + (sizeof(int) + sizeof(Node)), sẽ bằngsizeof(int) + (sizeof(int) + (sizeof(int) + sizeof(Node))) , v.v. đến vô cùng.

Một đối tượng như thế không thể tồn tại. Không thể .


25
* Trừ khi nó được đánh giá một cách lười biếng. Danh sách vô hạn là có thể, chỉ cần không đánh giá nghiêm ngặt.
Carcigenicate

55
@Carcigenicate không phải là đánh giá / thực thi một số chức năng trên đối tượng Node - mà là về cách bố trí bộ nhớ của mọi phiên bản của Node, phải được xác định tại thời điểm biên dịch, trước khi bất kỳ đánh giá nào có thể xảy ra.
Peteris

6
@DavidK Về mặt logic, không thể làm được điều này. Bạn cần một con trỏ (thực sự là một hướng dẫn) ở đây - chắc chắn rằng ngôn ngữ có thể ẩn nó khỏi bạn, nhưng cuối cùng, không có cách nào khác.
Voo

2
@David Tôi đang bối rối. Đầu tiên bạn đồng ý rằng điều đó là không thể về mặt logic, nhưng sau đó bạn muốn chiêm nghiệm nó? Loại bỏ bất kỳ thứ gì của C hoặc C ++ - điều đó là không thể trong bất kỳ ngôn ngữ nào mà bạn có thể mơ ước xa như tôi có thể thấy. Cấu trúc đó theo định nghĩa là một đệ quy vô hạn và nếu không có một số mức độ định hướng, chúng ta không thể phá vỡ điều đó.
Voo

13
@benjamin Tôi thực sự đã chỉ ra (bởi vì tôi biết nếu không thì ai đó sẽ đưa ra điều này - cũng không giúp được gì) rằng Haskell đã phân bổ các côn vào thời điểm tạo và do đó điều này hoạt động bởi vì những côn đó cung cấp cho chúng ta sự chuyển hướng mà chúng ta cần. Đây chẳng qua là một con trỏ với dữ liệu bổ sung được ngụy trang ...
Voo

178

Trong Java

Node m_node

lưu trữ một con trỏ đến một nút khác. Bạn không có sự lựa chọn về nó. Trong C ++

Node *m_node

có nghĩa là giống nhau. Sự khác biệt là trong C ++, bạn thực sự có thể lưu trữ đối tượng thay vì một con trỏ tới nó. Đó là lý do tại sao bạn phải nói rằng bạn muốn một con trỏ. Trong C ++:

Node m_node

có nghĩa là lưu trữ nút ngay tại đây (và điều đó rõ ràng không thể hoạt động cho một danh sách - bạn kết thúc với một cấu trúc được xác định đệ quy).


2
@SalmanA Tôi đã biết điều này. Tôi chỉ muốn biết tại sao nó sẽ không hoạt động nếu không có con trỏ, đó là những gì câu trả lời được chấp nhận giải thích tốt hơn nhiều.
m0meni

3
@ AR7 Cả hai đều đưa ra cùng một cách giải thích, chỉ theo hai cách tiếp cận khác nhau. Nếu bạn đã khai báo nó là một biến "thông thường", thì lần đầu tiên một hàm tạo được gọi, nó sẽ khởi tạo biến đó thành một phiên bản mới. Nhưng trước khi nó kết thúc việc khởi tạo nó - trước khi phương thức khởi tạo đầu tiên kết thúc - phương thức khởi tạo Nodecủa chính thành viên đó sẽ được gọi, nó sẽ khởi tạo một thể hiện mới khác ... và bạn sẽ nhận được đệ quy giả vô tận. Nó không thực sự là một vấn đề về kích thước theo nghĩa đen và hoàn toàn nghiêm ngặt, vì nó là một vấn đề về hiệu suất.
Panzercrisis

Nhưng tất cả những gì bạn thực sự muốn chỉ là một cách để chỉ ra cái nào tiếp theo trong danh sách, chứ không phải cái Nodenào thực sự nằm trong cái đầu tiên Node. Vì vậy, bạn tạo một con trỏ, về cơ bản là cách Java xử lý các đối tượng, trái ngược với các nguyên thủy. Khi bạn gọi một phương thức hoặc tạo một biến, Java không lưu trữ bản sao của đối tượng hoặc thậm chí chính đối tượng đó; nó lưu trữ một tham chiếu đến một đối tượng, về cơ bản là một con trỏ với một chiếc găng tay trẻ em quấn quanh nó. Đây là những gì cả hai câu trả lời về cơ bản đang nói.
Panzercrisis

nó không phải là vấn đề về kích thước hay tốc độ - nó là một vấn đề bất khả thi. Đối tượng Node được bao gồm sẽ bao gồm một đối tượng Node sẽ bao gồm một đối tượng Node ... Trên thực tế, không thể biên dịch nó
pm100

3
@Panzercrisis Tôi biết rằng cả hai đều đưa ra lời giải thích giống nhau. Tuy nhiên, cách tiếp cận này không hữu ích đối với tôi vì nó tập trung vào những gì tôi đã có: cách con trỏ hoạt động trong C ++ và cách con trỏ được xử lý trong Java. Câu trả lời được chấp nhận giải quyết cụ thể tại sao không sử dụng con trỏ sẽ là không thể vì không thể tính được kích thước. Mặt khác, điều này khiến nó mơ hồ hơn là "một cấu trúc được định nghĩa đệ quy." Tái bút giải thích của bạn mà bạn vừa viết giải thích nó tốt hơn cả hai: D.
m0meni

38

C ++ không phải là Java. Khi bạn viết

Node m_next;

trong Java, điều đó cũng giống như viết

Node* m_next;

trong C ++. Trong Java, con trỏ là ẩn, trong C ++, con trỏ là rõ ràng. Nếu bạn viết

Node m_next;

trong C ++, bạn đặt một thể hiện Nodengay bên trong đối tượng mà bạn đang định nghĩa. Nó luôn ở đó và không thể bị bỏ qua, nó không thể được cấp phát newvà không thể loại bỏ nó. Hiệu ứng này là không thể đạt được trong Java, và nó hoàn toàn khác với những gì Java làm với cùng một cú pháp.


1
Để có được thứ gì đó tương tự trong Java có thể sẽ được "mở rộng" nếu SuperNode mở rộng Node, SuperNodes bao gồm tất cả các Thuộc tính của Node và phải dành tất cả không gian bổ sung. Vì vậy, trong Java bạn không thể thực hiện "Nút mở rộng Node"
Falco

@Falco Đúng, kế thừa là một dạng bao gồm tại chỗ các lớp cơ sở. Tuy nhiên, vì Java không cho phép đa kế thừa (không giống như C ++), bạn chỉ có thể kéo vào một thể hiện của một lớp đã tồn tại trước đó thông qua kế thừa. Đó là lý do tại sao tôi sẽ không nghĩ đến việc thừa kế như một sự thay thế cho việc bao gồm các thành viên tại chỗ.
cmaster - phục hồi monica

27

Bạn sử dụng một con trỏ, nếu không thì mã của bạn:

class Node
{
   //etc
   Node m_next; //non-pointer
};

… Sẽ không biên dịch, vì trình biên dịch không thể tính kích thước của Node. Điều này là do nó phụ thuộc vào chính nó - có nghĩa là trình biên dịch không thể quyết định nó sẽ tiêu tốn bao nhiêu bộ nhớ.


5
Tệ hơn nữa, không tồn tại kích thước hợp lệ: Nếu loại k == sizeof(Node)giữ và loại của bạn có dữ liệu, nó cũng sẽ phải giữ kích thước đó sizeof(Node) = k + sizeof(Data) = sizeof(Node) + sizeof(Data)và sau đó sizeof(Node) > sizeof(Node).
bitmask

4
@bitmask không tồn tại kích thước hợp lệ trong số thực . Nếu bạn cho phép transinfinites, aleph_0hoạt động. (Chỉ là quá
lãng mạn

2
@k_g Chà, tiêu chuẩn C / C ++ quy định rằng giá trị trả về của sizeoflà một kiểu tích phân không dấu, vì vậy hy vọng có kích thước vô hạn hoặc thậm chí là thực. (thậm chí còn vĩ đại hơn !: p)
Thomas

@Thomas: Người ta thậm chí có thể chỉ ra rằng có cả Số tự nhiên. (Đi qua đỉnh -pedantic: p)
bitmask

1
Trong thực tế, Nodethậm chí không được xác định trước khi kết thúc đoạn mã này, vì vậy bạn không thể thực sự sử dụng nó bên trong. Việc cho phép một con trỏ khai báo chuyển tiếp một cách ngầm định đến một lớp chưa được khai báo là một sự gian lận nhỏ được ngôn ngữ cho phép để tạo ra các cấu trúc như vậy, mà không cần phải truyền con trỏ một cách rõ ràng.
osa

13

Cái sau ( Node m_next) sẽ phải chứa nút. Nó sẽ không chỉ vào nó. Và sau đó sẽ không có liên kết của các phần tử.


3
Tệ hơn nữa, về mặt logic sẽ không thể cho một đối tượng chứa một thứ gì đó cùng loại.
Mike Seymour

Về mặt kỹ thuật sẽ không vẫn có liên kết bởi vì nó sẽ là một nút chứa một nút chứa một nút, v.v.?
m0meni

9
@ AR7: Không, ngăn chặn có nghĩa là nó nằm bên trong đối tượng, không liên kết với nó.
Mike Seymour

9

Cách tiếp cận mà bạn mô tả không chỉ tương thích với C ++ mà còn với (chủ yếu) ngôn ngữ con C của nó . Học cách phát triển danh sách liên kết kiểu C là một cách tốt để giới thiệu bản thân với các kỹ thuật lập trình cấp thấp (chẳng hạn như quản lý bộ nhớ thủ công), nhưng nói chung nó không phải là phương pháp hay nhất để phát triển C ++ hiện đại.

Dưới đây, tôi đã triển khai bốn biến thể về cách quản lý danh sách các mục trong C ++.

  1. raw_pointer_demosử dụng phương pháp tương tự như của bạn - cần quản lý bộ nhớ thủ công với việc sử dụng các con trỏ thô. Việc sử dụng C ++ ở đây chỉ dành cho cú pháp-đường và cách tiếp cận được sử dụng tương thích với ngôn ngữ C.
  2. Trong shared_pointer_demodanh sách quản lý vẫn được thực hiện thủ công, nhưng quản lý bộ nhớ là tự động (không sử dụng con trỏ thô). Điều này rất giống với những gì bạn có thể đã trải qua với Java.
  3. std_list_demosử dụng vùng listchứa thư viện tiêu chuẩn . Điều này cho thấy mọi thứ trở nên dễ dàng hơn bao nhiêu nếu bạn dựa vào các thư viện hiện có hơn là sử dụng thư viện của riêng bạn.
  4. std_vector_demosử dụng vùng vectorchứa thư viện tiêu chuẩn . Điều này quản lý việc lưu trữ danh sách trong một phân bổ bộ nhớ liền kề. Nói cách khác, không có con trỏ đến các phần tử riêng lẻ. Đối với một số trường hợp khá khắc nghiệt, điều này có thể trở nên kém hiệu quả đáng kể. Tuy nhiên, đối với các trường hợp điển hình, đây là phương pháp hay nhất được khuyến nghị để quản lý danh sách trong C ++ .

Lưu ý: Trong số tất cả những điều này, chỉ có điều raw_pointer_demothực sự yêu cầu danh sách được hủy một cách rõ ràng để tránh "rò rỉ" bộ nhớ. Ba phương thức khác sẽ tự động hủy danh sách và nội dung của nó khi vùng chứa vượt ra khỏi phạm vi (ở phần kết của hàm). Vấn đề là: C ++ có khả năng rất giống Java về mặt này - nhưng chỉ khi bạn chọn phát triển chương trình của mình bằng cách sử dụng các công cụ cấp cao theo ý của bạn.


/*BINFMTCXX: -Wall -Werror -std=c++11
*/

#include <iostream>
#include <algorithm>
#include <string>
#include <list>
#include <vector>
#include <memory>
using std::cerr;

/** Brief   Create a list, show it, then destroy it */
void raw_pointer_demo()
{
    cerr << "\n" << "raw_pointer_demo()..." << "\n";

    struct Node
    {
        Node(int data, Node *next) : data(data), next(next) {}
        int data;
        Node *next;
    };

    Node * items = 0;
    items = new Node(1,items);
    items = new Node(7,items);
    items = new Node(3,items);
    items = new Node(9,items);

    for (Node *i = items; i != 0; i = i->next)
        cerr << (i==items?"":", ") << i->data;
    cerr << "\n";

    // Erase the entire list
    while (items) {
        Node *temp = items;
        items = items->next;
        delete temp;
    }
}

raw_pointer_demo()...
9, 3, 7, 1

/** Brief   Create a list, show it, then destroy it */
void shared_pointer_demo()
{
    cerr << "\n" << "shared_pointer_demo()..." << "\n";

    struct Node; // Forward declaration of 'Node' required for typedef
    typedef std::shared_ptr<Node> Node_reference;

    struct Node
    {
        Node(int data, std::shared_ptr<Node> next ) : data(data), next(next) {}
        int data;
        Node_reference next;
    };

    Node_reference items = 0;
    items.reset( new Node(1,items) );
    items.reset( new Node(7,items) );
    items.reset( new Node(3,items) );
    items.reset( new Node(9,items) );

    for (Node_reference i = items; i != 0; i = i->next)
        cerr << (i==items?"":", ") << i->data;
    cerr<<"\n";

    // Erase the entire list
    while (items)
        items = items->next;
}

shared_pointer_demo()...
9, 3, 7, 1

/** Brief   Show the contents of a standard container */
template< typename C >
void show(std::string const & msg, C const & container)
{
    cerr << msg;
    bool first = true;
    for ( int i : container )
        cerr << (first?" ":", ") << i, first = false;
    cerr<<"\n";
}

/** Brief  Create a list, manipulate it, then destroy it */
void std_list_demo()
{
    cerr << "\n" << "std_list_demo()..." << "\n";

    // Initial list of integers
    std::list<int> items = { 9, 3, 7, 1 };
    show( "A: ", items );

    // Insert '8' before '3'
    items.insert(std::find( items.begin(), items.end(), 3), 8);
    show("B: ", items);

    // Sort the list
    items.sort();
    show( "C: ", items);

    // Erase '7'
    items.erase(std::find(items.begin(), items.end(), 7));
    show("D: ", items);

    // Erase the entire list
    items.clear();
    show("E: ", items);
}

std_list_demo()...
A:  9, 3, 7, 1
B:  9, 8, 3, 7, 1
C:  1, 3, 7, 8, 9
D:  1, 3, 8, 9
E:

/** brief  Create a list, manipulate it, then destroy it */
void std_vector_demo()
{
    cerr << "\n" << "std_vector_demo()..." << "\n";

    // Initial list of integers
    std::vector<int> items = { 9, 3, 7, 1 };
    show( "A: ", items );

    // Insert '8' before '3'
    items.insert(std::find(items.begin(), items.end(), 3), 8);
    show( "B: ", items );

    // Sort the list
    sort(items.begin(), items.end());
    show("C: ", items);

    // Erase '7'
    items.erase( std::find( items.begin(), items.end(), 7 ) );
    show("D: ", items);

    // Erase the entire list
    items.clear();
    show("E: ", items);
}

std_vector_demo()...
A:  9, 3, 7, 1
B:  9, 8, 3, 7, 1
C:  1, 3, 7, 8, 9
D:  1, 3, 8, 9
E:

int main()
{
    raw_pointer_demo();
    shared_pointer_demo();
    std_list_demo();
    std_vector_demo();
}

Các Node_referencetuyên bố trên địa chỉ một trong những sự khác biệt ngôn ngữ cấp thú vị giữa Java và C ++. Trong Java, khai báo một đối tượng kiểu Nodesẽ sử dụng ngầm định một tham chiếu đến một đối tượng được cấp phát riêng. Trong C ++, bạn có quyền lựa chọn tham chiếu (con trỏ) so với cấp phát trực tiếp (ngăn xếp) - vì vậy bạn phải xử lý sự khác biệt một cách rõ ràng. Trong hầu hết các trường hợp, bạn sẽ sử dụng phân bổ trực tiếp, mặc dù không phải cho các phần tử danh sách.
Brent Bradburn

Không biết tại sao tôi cũng không đề xuất khả năng của std :: deque .
Brent Bradburn

8

Tổng quat

Có 2 cách để tham chiếu và cấp phát đối tượng trong C ++, trong khi trong Java chỉ có một cách.

Để giải thích điều này, các sơ đồ sau đây cho thấy cách các đối tượng được lưu trữ trong bộ nhớ.

1.1 Các mục C ++ không có con trỏ

class AddressClass
{
  public:
    int      Code;
    char[50] Street;
    char[10] Number;
    char[50] POBox;
    char[50] City;
    char[50] State;
    char[50] Country;
};

class CustomerClass
{
  public:
    int          Code;
    char[50]     FirstName;
    char[50]     LastName;
    // "Address" IS NOT A pointer !!!
    AddressClass Address;
};

int main(...)
{
   CustomerClass MyCustomer();
     MyCustomer.Code = 1;
     strcpy(MyCustomer.FirstName, "John");
     strcpy(MyCustomer.LastName, "Doe");
     MyCustomer.Address.Code = 2;
     strcpy(MyCustomer.Address.Street, "Blue River");
     strcpy(MyCustomer.Address.Number, "2231 A");

   return 0;
} // int main (...)

.......................................
..+---------------------------------+..
..|          AddressClass           |..
..+---------------------------------+..
..| [+] int:      Code              |..
..| [+] char[50]: Street            |..
..| [+] char[10]: Number            |..
..| [+] char[50]: POBox             |..
..| [+] char[50]: City              |..
..| [+] char[50]: State             |..
..| [+] char[50]: Country           |..
..+---------------------------------+..
.......................................
..+---------------------------------+..
..|          CustomerClass          |..
..+---------------------------------+..
..| [+] int:      Code              |..
..| [+] char[50]: FirstName         |..
..| [+] char[50]: LastName          |..
..+---------------------------------+..
..| [+] AddressClass: Address       |..
..| +-----------------------------+ |..
..| | [+] int:      Code          | |..
..| | [+] char[50]: Street        | |..
..| | [+] char[10]: Number        | |..
..| | [+] char[50]: POBox         | |..
..| | [+] char[50]: City          | |..
..| | [+] char[50]: State         | |..
..| | [+] char[50]: Country       | |..
..| +-----------------------------+ |..
..+---------------------------------+..
.......................................

Cảnh báo : Cú pháp C ++ được sử dụng trong ví dụ này, tương tự như cú pháp trong Java. Nhưng, việc phân bổ bộ nhớ là khác nhau.

1.2 Các mục C ++ sử dụng con trỏ

class AddressClass
{
  public:
    int      Code;
    char[50] Street;
    char[10] Number;
    char[50] POBox;
    char[50] City;
    char[50] State;
    char[50] Country;
};

class CustomerClass
{
  public:
    int           Code;
    char[50]      FirstName;
    char[50]      LastName;
    // "Address" IS A pointer !!!
    AddressClass* Address;
};

.......................................
..+-----------------------------+......
..|        AddressClass         +<--+..
..+-----------------------------+...|..
..| [+] int:      Code          |...|..
..| [+] char[50]: Street        |...|..
..| [+] char[10]: Number        |...|..
..| [+] char[50]: POBox         |...|..
..| [+] char[50]: City          |...|..
..| [+] char[50]: State         |...|..
..| [+] char[50]: Country       |...|..
..+-----------------------------+...|..
....................................|..
..+-----------------------------+...|..
..|         CustomerClass       |...|..
..+-----------------------------+...|..
..| [+] int:      Code          |...|..
..| [+] char[50]: FirstName     |...|..
..| [+] char[50]: LastName      |...|..
..| [+] AddressClass*: Address  +---+..
..+-----------------------------+......
.......................................

int main(...)
{
   CustomerClass* MyCustomer = new CustomerClass();
     MyCustomer->Code = 1;
     strcpy(MyCustomer->FirstName, "John");
     strcpy(MyCustomer->LastName, "Doe");

     AddressClass* MyCustomer->Address = new AddressClass();
     MyCustomer->Address->Code = 2;
     strcpy(MyCustomer->Address->Street, "Blue River");
     strcpy(MyCustomer->Address->Number, "2231 A");

     free MyCustomer->Address();
     free MyCustomer();

   return 0;
} // int main (...)

Nếu bạn kiểm tra sự khác biệt giữa cả hai cách, bạn sẽ thấy rằng trong kỹ thuật đầu tiên, mục địa chỉ được phân bổ trong khách hàng, trong khi cách thứ hai, bạn phải tạo từng địa chỉ một cách rõ ràng.

Cảnh báo: Java cấp phát các đối tượng trong bộ nhớ như kỹ thuật thứ hai này, tuy nhiên, cú pháp giống như cách thứ nhất, điều này có thể gây nhầm lẫn cho những người mới làm quen với "C ++".

Thực hiện

Vì vậy, ví dụ danh sách của bạn có thể tương tự như ví dụ sau.

class Node
{
  public:
   Node(int data);

   int m_data;
   Node *m_next;
};

.......................................
..+-----------------------------+......
..|            Node             |......
..+-----------------------------+......
..| [+] int:           m_data   |......
..| [+] Node*:         m_next   +---+..
..+-----------------------------+...|..
....................................|..
..+-----------------------------+...|..
..|            Node             +<--+..
..+-----------------------------+......
..| [+] int:           m_data   |......
..| [+] Node*:         m_next   +---+..
..+-----------------------------+...|..
....................................|..
..+-----------------------------+...|..
..|            Node             +<--+..
..+-----------------------------+......
..| [+] int:           m_data   |......
..| [+] Node*:         m_next   +---+..
..+-----------------------------+...|..
....................................v..
...................................[X].
.......................................

Tóm lược

Vì Danh sách được Liên kết có số lượng mục thay đổi, nên bộ nhớ được cấp phát theo yêu cầu và khi có sẵn.

CẬP NHẬT:

Cũng đáng đề cập, như @haccks đã nhận xét trong bài đăng của anh ấy.

Đôi khi, các tham chiếu hoặc con trỏ đối tượng, chỉ ra các mục lồng nhau (còn gọi là "Thành phần UML").

Và đôi khi, các tham chiếu hoặc con trỏ đối tượng, chỉ ra các mục bên ngoài (hay còn gọi là "Tổng hợp UML").

Tuy nhiên, các mục lồng nhau của cùng một lớp, không thể được áp dụng với kỹ thuật "no-pointer".


7

Lưu ý nhỏ, nếu thành viên đầu tiên của một lớp hoặc cấu trúc là con trỏ tiếp theo (vì vậy không có hàm ảo hoặc bất kỳ tính năng nào khác của lớp có nghĩa là tiếp theo không phải là thành viên đầu tiên của một lớp hoặc cấu trúc), thì bạn có thể sử dụng một lớp hoặc cấu trúc "cơ sở" chỉ với một con trỏ tiếp theo và sử dụng mã chung cho các thao tác cơ bản với danh sách liên kết như nối thêm, chèn trước, truy xuất từ ​​phía trước, .... Điều này là do C / C ++ đảm bảo rằng địa chỉ của thành viên đầu tiên của một lớp hoặc cấu trúc giống với địa chỉ của lớp hoặc cấu trúc. Lớp hoặc cấu trúc nút cơ sở sẽ chỉ có một con trỏ tiếp theo được sử dụng bởi các chức năng danh sách liên kết cơ bản, sau đó việc đánh máy sẽ được sử dụng khi cần thiết để chuyển đổi giữa kiểu nút cơ sở và kiểu nút "dẫn xuất". Lưu ý bên cạnh - trong C ++, nếu lớp nút cơ sở chỉ có một con trỏ tiếp theo,


6

Tại sao sử dụng con trỏ trong danh sách liên kết lại tốt hơn?

Lý do là khi bạn tạo một Nodeđối tượng, trình biên dịch phải cấp phát bộ nhớ cho đối tượng đó và kích thước của đối tượng đó được tính toán.
Kích thước của con trỏ tới bất kỳ kiểu nào được biết đến với trình biên dịch và do đó với kích thước con trỏ tự tham chiếu của đối tượng có thể được tính toán.

Nếu Node m_nodeđược sử dụng thay thế thì trình biên dịch không có ý tưởng về kích thước của Nodevà nó sẽ bị mắc kẹt trong một phép tính đệ quy vô hạnsizeof(Node) . Luôn nhớ rằng: một lớp không thể chứa một thành viên thuộc kiểu riêng của nó .


5

Bởi vì điều này trong C ++

int main (..)
{
    MyClass myObject;

    // or

    MyClass * myObjectPointer = new MyClass();

    ..
}

tương đương với điều này trong Java

public static void main (..)
{
    MyClass myObjectReference = new MyClass();
}

trong đó cả hai đều tạo một đối tượng mới MyClassbằng cách sử dụng hàm tạo mặc định.


0

Tại sao danh sách được liên kết sử dụng con trỏ thay vì lưu trữ các nút bên trong các nút?

Tất nhiên có một câu trả lời tầm thường.

Nếu họ không liên kết một nút với nút tiếp theo bằng một con trỏ, chúng sẽ không được liên kết với danh sách .

Sự tồn tại của danh sách được liên kết như một sự vật là vì chúng ta muốn có thể liên kết các đối tượng lại với nhau. Ví dụ: chúng ta đã có một đối tượng từ đâu đó. Bây giờ chúng ta muốn đặt đối tượng thực tế đó (không phải là một bản sao) ở cuối hàng đợi, chẳng hạn. Điều đó đạt được bằng cách thêm liên kết từ phần tử cuối cùng đã có trên hàng đợi đến mục nhập mà chúng tôi đang thêm. Theo thuật ngữ máy móc, đó là điền một từ với địa chỉ của phần tử tiếp theo.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.