เหตุใดรายการที่เชื่อมโยงจึงใช้พอยน์เตอร์แทนการจัดเก็บโหนดภายในโหนด


121

ฉันเคยทำงานกับรายการที่เชื่อมโยงมาก่อนใน Java แต่ฉันยังใหม่กับ C ++ มาก ฉันใช้คลาสโหนดนี้ที่มอบให้ฉันในโปรเจ็กต์ได้ดี

class Node
{
  public:
   Node(int data);

   int m_data;
   Node *m_next;
};

แต่ฉันมีคำถามหนึ่งที่ตอบไม่ดีนัก ทำไมจึงต้องใช้

Node *m_next;

เพื่อชี้ไปที่โหนดถัดไปในรายการแทนที่จะเป็น

Node m_next;

ฉันเข้าใจว่าควรใช้เวอร์ชันตัวชี้จะดีกว่า ฉันจะไม่เถียงข้อเท็จจริง แต่ไม่รู้ว่าทำไมมันดีกว่า ฉันได้รับคำตอบที่ไม่ชัดเจนนักว่าตัวชี้นั้นดีกว่าสำหรับการจัดสรรหน่วยความจำอย่างไรและฉันก็สงสัยว่าใครที่นี่จะช่วยให้ฉันเข้าใจสิ่งนั้นได้ดีขึ้น


14
@ ตัวเองให้อภัยฉัน? ทำไมภาษาที่ทุกอย่างเป็นตัวชี้ไม่มีรายการที่เชื่อมโยง?
Angew ไม่ภูมิใจอีกต่อไปเมื่อ

41
สิ่งสำคัญคือต้องสังเกตว่า C และ C ++ แตกต่างจาก Java อย่างไรในแง่ของตัวชี้วัตถุเทียบกับการอ้างอิง Node m_nextไม่ใช่การอ้างอิงถึงโหนด แต่เป็นการจัดเก็บข้อมูลทั้งหมดNodeเอง
Brian Cain

41
@self Java มีพอยน์เตอร์ที่คุณไม่ได้ใช้อย่างชัดเจน
m0meni

27
เต่าตลอดทางลงจะไม่ได้เป็นตัวเลือก ความบ้าคลั่งต้องจบลงที่ไหนสักแห่ง
WhozCraig

26
โปรดลืมทุกสิ่งที่คุณรู้เกี่ยวกับ Java C ++ และ Java จัดการหน่วยความจำในรูปแบบที่แตกต่างกันโดยพื้นฐาน ไปดูคำถามนี้สำหรับคำแนะนำหนังสือเลือกหนึ่งข้อและอ่าน คุณจะทำให้พวกเราทุกคนเป็นที่โปรดปราน
Rob K

คำตอบ:


218

ไม่ใช่แค่ดีขึ้น แต่เป็นวิธีเดียวที่เป็นไปได้

ถ้าคุณเก็บNode วัตถุไว้ในตัวมันจะsizeof(Node)เป็นอย่างไร? มันจะsizeof(int) + sizeof(Node)เท่ากับsizeof(int) + (sizeof(int) + sizeof(Node))ซึ่งจะเท่ากับsizeof(int) + (sizeof(int) + (sizeof(int) + sizeof(Node)))ฯลฯ ถึงอินฟินิตี้

วัตถุเช่นนั้นไม่สามารถดำรงอยู่ได้ มันเป็นไปไม่ได้


25
* เว้นแต่จะได้รับการประเมินอย่างเฉื่อยชา รายการที่ไม่มีที่สิ้นสุดเป็นไปได้ไม่ใช่แค่การประเมินที่เข้มงวด
Carcigenicate

55
@Carcigenicate ไม่ได้เกี่ยวกับการประเมิน / เรียกใช้ฟังก์ชันบางอย่างบนวัตถุ Node แต่เกี่ยวกับเค้าโครงหน่วยความจำของทุกอินสแตนซ์ของโหนดซึ่งต้องกำหนดในเวลาคอมไพล์ก่อนจึงจะสามารถทำการประเมินได้
Peteris

6
@DavidK มันเป็นไปไม่ได้ในทางเหตุผลที่จะทำเช่นนี้ คุณต้องมีตัวชี้ (เป็นทิศทางจริงๆ) ที่นี่ - ต้องแน่ใจว่าภาษาสามารถซ่อนจากคุณได้ แต่ในที่สุดก็ไม่มีทางทำได้
Voo

2
@ เดวิดฉันสับสน ก่อนอื่นคุณยอมรับว่ามันเป็นไปไม่ได้ในเชิงเหตุผล แต่คุณต้องการพิจารณาหรือไม่? ลบอะไรก็ได้ของ C หรือ C ++ - มันเป็นไปไม่ได้ในภาษาใด ๆ ที่คุณจะฝันถึงเท่าที่ฉันเห็น โครงสร้างนั้นเป็นไปตามความหมายของการเรียกซ้ำที่ไม่มีที่สิ้นสุดและหากไม่มีระดับของการเหนี่ยวนำเราก็ไม่สามารถทำลายสิ่งนั้นได้
Voo

13
@benjamin ฉันชี้ให้เห็นจริง ๆ (เพราะฉันรู้ว่าจะมีคนนำเรื่องนี้ขึ้นมา - ก็ไม่ได้ช่วยอะไร) ที่ Haskell จัดสรรช่วงเวลาแห่งการสร้างและด้วยเหตุนี้จึงได้ผลเพราะเสียงเหล่านั้นทำให้เรามีทิศทางที่เราต้องการ นี่ไม่ใช่อะไรนอกจากตัวชี้ที่มีข้อมูลพิเศษปลอมตัว ...
Voo

178

ใน Java

Node m_node

จัดเก็บตัวชี้ไปยังโหนดอื่น คุณไม่มีทางเลือกเกี่ยวกับเรื่องนี้ ใน C ++

Node *m_node

หมายถึงสิ่งเดียวกัน ความแตกต่างคือใน C ++ คุณสามารถจัดเก็บวัตถุได้เมื่อเทียบกับตัวชี้ นั่นเป็นเหตุผลที่คุณต้องบอกว่าคุณต้องการตัวชี้ ใน C ++:

Node m_node

หมายถึงเก็บโหนดไว้ที่นี่ (และเห็นได้ชัดว่าไม่สามารถใช้งานได้กับรายการ - คุณจะจบลงด้วยโครงสร้างที่กำหนดซ้ำ)


2
@SalmanA ฉันรู้เรื่องนี้แล้ว ฉันแค่อยากรู้ว่าทำไมมันถึงใช้ไม่ได้หากไม่มีตัวชี้ซึ่งเป็นสิ่งที่คำตอบที่ยอมรับนั้นอธิบายได้ดีกว่ามาก
m0meni

3
@ AR7 พวกเขาทั้งคู่ให้คำอธิบายแบบเดียวกันภายใต้สองวิธีที่แตกต่างกัน หากคุณประกาศว่าเป็นตัวแปร "ปกติ" ครั้งแรกที่มีการเรียกตัวสร้างมันจะสร้างอินสแตนซ์นั้นเป็นอินสแตนซ์ใหม่ แต่ก่อนที่จะเสร็จสิ้นการสร้างอินสแตนซ์ก่อนที่คอนสตรัคเตอร์ตัวแรกจะเสร็จสิ้น - คอนสตรัคเตอร์ของสมาชิกNodeจะถูกเรียกใช้ซึ่งจะสร้างอินสแตนซ์ใหม่อีกตัว ... และคุณจะได้รับการเรียกซ้ำแบบหลอกไม่รู้จบ มันไม่ได้จริงๆมากเป็นปัญหาขนาดในแง่สมบูรณ์อย่างเข้มงวดและตัวอักษรตามที่มันเป็นปัญหาประสิทธิภาพการทำงาน
Panzercrisis

แต่ทั้งหมดที่คุณต้องการจริงๆเป็นเพียงวิธีการชี้ไปที่แล้วแต่อย่างใดอย่างหนึ่งต่อไปในรายการที่ไม่ได้เป็นที่เป็นจริงภายในครั้งแรกNode Nodeดังนั้นคุณจึงสร้างพอยน์เตอร์ซึ่งโดยพื้นฐานแล้ว Java จะจัดการกับอ็อบเจ็กต์อย่างไรเมื่อเทียบกับแบบดั้งเดิม เมื่อคุณเรียกใช้เมธอดหรือสร้างตัวแปร Java จะไม่เก็บสำเนาของอ็อบเจ็กต์หรือแม้แต่อ็อบเจ็กต์เอง มันเก็บข้อมูลอ้างอิงไปยังวัตถุซึ่งโดยพื้นฐานแล้วเป็นตัวชี้ที่มีถุงมือตัวเล็กพันอยู่รอบ ๆ นี่คือสิ่งที่ทั้งสองคำตอบเป็นหลัก
Panzercrisis

ไม่ใช่ปัญหาขนาดหรือความเร็ว แต่เป็นปัญหาที่เป็นไปไม่ได้ อ็อบเจ็กต์โหนดที่รวมจะรวมอ็อบเจ็กต์โหนดที่จะรวมอ็อบเจ็กต์โหนด ... ในความเป็นจริงมันเป็นไปไม่ได้ที่จะคอมไพล์มัน
pm100

3
@ Panzercrisis ฉันรู้ว่าทั้งคู่ให้คำอธิบายเหมือนกัน อย่างไรก็ตามวิธีนี้ไม่เป็นประโยชน์สำหรับฉันเพราะมันเน้นไปที่สิ่งที่ฉันมีความเข้าใจอยู่แล้ว: พอยน์เตอร์ทำงานอย่างไรใน C ++ และวิธีจัดการพอยน์เตอร์ใน Java คำตอบที่ได้รับการยอมรับระบุว่าเหตุใดจึงไม่ใช้ตัวชี้จึงเป็นไปไม่ได้เนื่องจากไม่สามารถคำนวณขนาดได้ ในทางกลับกันสิ่งนี้ทำให้มันคลุมเครือมากขึ้นในฐานะ "โครงสร้างที่กำหนดซ้ำ" ปล. คำอธิบายของคุณที่คุณเพิ่งเขียนอธิบายได้ดีกว่าทั้งสองอย่าง: D.
m0meni

38

C ++ ไม่ใช่ Java เมื่อคุณเขียน

Node m_next;

ใน Java นั้นเหมือนกับการเขียน

Node* m_next;

ใน C ++ ใน Java ตัวชี้เป็นนัยโดยใน C ++ นั้นชัดเจน ถ้าคุณเขียน

Node m_next;

ใน C ++ คุณใส่อินสแตนซ์Nodeไว้ในวัตถุที่คุณกำหนด มันอยู่ที่นั่นเสมอและไม่สามารถละเว้นไม่สามารถจัดสรรด้วยnewและไม่สามารถลบออกได้ เอฟเฟกต์นี้ไม่สามารถทำได้ใน Java และแตกต่างอย่างสิ้นเชิงกับสิ่งที่ Java ทำด้วยไวยากรณ์เดียวกัน


1
เพื่อให้ได้สิ่งที่คล้ายกันใน Java อาจจะ "ขยาย" ถ้า SuperNode ขยาย Node SuperNodes จะรวมแอตทริบิวต์ของโหนดทั้งหมดและต้องจองพื้นที่เพิ่มเติมทั้งหมด ดังนั้นใน Java คุณไม่สามารถทำ "Node expands Node" ได้
Falco

@ Falco True การสืบทอดเป็นรูปแบบหนึ่งของการรวมคลาสพื้นฐานเข้าด้วยกัน อย่างไรก็ตามเนื่องจาก Java ไม่อนุญาตให้มีการสืบทอดหลายรายการ (ไม่เหมือนกับ C ++) คุณจึงสามารถดึงอินสแตนซ์ของคลาสที่มีอยู่ก่อนหน้าอื่น ๆ ผ่านการสืบทอดเท่านั้น นั่นเป็นเหตุผลที่ฉันไม่คิดว่าการสืบทอดจะทดแทนการรวมสมาชิกในสถานที่
cmaster - คืนสถานะ monica

27

คุณใช้ตัวชี้หรือรหัสของคุณ:

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

…จะไม่คอมไพล์เนื่องจากคอมไพเลอร์ไม่สามารถคำนวณขนาดของNode. เนื่องจากมันขึ้นอยู่กับตัวมันเองซึ่งหมายความว่าคอมไพเลอร์ไม่สามารถตัดสินใจได้ว่าจะใช้หน่วยความจำเท่าใด


5
ที่แย่กว่านั้นคือไม่มีขนาดที่ถูกต้อง: หากการk == sizeof(Node)ระงับและประเภทของคุณมีข้อมูลก็จะต้องถือสิ่งsizeof(Node) = k + sizeof(Data) = sizeof(Node) + sizeof(Data)นั้นsizeof(Node) > sizeof(Node)ไว้ด้วย
bitmask

4
@bitmask ไม่มีขนาดที่ถูกต้องมีอยู่ในจำนวนจริง หากคุณอนุญาตให้ทรานซินไฟต์aleph_0ทำงานได้ (แค่
ขี้

2
@k_g มาตรฐาน C / C ++ กำหนดว่าค่าที่ส่งคืนของsizeofเป็นชนิดอินทิกรัลที่ไม่ได้ลงนามดังนั้นจึงมีความหวังที่จะเปลี่ยนขนาดหรือขนาดจริง (เป็นคนอวดรู้มากขึ้น!: p)
Thomas

@ โทมัส: ใคร ๆ ก็อาจชี้ให้เห็นว่ามีแม้กระทั่งตัวเลขธรรมชาติ (ไปมากกว่า - ยอดนิยม: p)
bitmask

1
ในความเป็นจริงNodeไม่ได้กำหนดไว้ก่อนที่จะสิ้นสุดตัวอย่างข้อมูลนี้ดังนั้นคุณจึงไม่สามารถใช้ภายในได้ การอนุญาตให้ตัวชี้ไปข้างหน้าประกาศโดยปริยายไปยังชั้นเรียนที่ยังไม่ได้ประกาศนั้นเป็นการโกงเล็กน้อยที่ได้รับอนุญาตจากภาษาเพื่อทำให้โครงสร้างดังกล่าวเป็นไปได้โดยไม่จำเป็นต้องใช้ตัวชี้อย่างชัดเจนตลอดเวลา
osa

13

หลัง ( Node m_next) จะต้องมีโหนด มันจะไม่ชี้ไปที่มัน จากนั้นจะไม่มีการเชื่อมโยงขององค์ประกอบ


3
ที่แย่กว่านั้นมันจะเป็นไปไม่ได้ในทางเหตุผลที่วัตถุจะมีสิ่งที่เป็นประเภทเดียวกัน
Mike Seymour

ในทางเทคนิคจะไม่มีการเชื่อมโยงเพราะมันจะเป็นโหนดที่มีโหนดที่มีโหนดเป็นต้น
m0meni

9
@ AR7: ไม่การกักกันหมายความว่ามันอยู่ภายในวัตถุอย่างแท้จริงไม่ได้เชื่อมโยงกับมัน
Mike Seymour

9

วิธีการที่คุณอธิบายไม่เพียง แต่เข้ากันได้กับ C ++ เท่านั้น แต่ยังเข้ากันได้กับ (ส่วนใหญ่) ภาษาเซต C การเรียนรู้ที่จะพัฒนา C-style linked-list เป็นวิธีที่ดีในการแนะนำตัวเองเกี่ยวกับเทคนิคการเขียนโปรแกรมระดับต่ำ (เช่นการจัดการหน่วยความจำด้วยตนเอง) แต่โดยทั่วไปแล้วไม่ใช่แนวทางปฏิบัติที่ดีที่สุดสำหรับการพัฒนา C ++ สมัยใหม่

ด้านล่างนี้ฉันได้ใช้สี่รูปแบบในการจัดการรายการใน C ++

  1. raw_pointer_demoใช้แนวทางเดียวกับของคุณ - การจัดการหน่วยความจำด้วยตนเองจำเป็นต้องใช้ตัวชี้ดิบ การใช้ C ++ ที่นี่มีไว้สำหรับ syntactic-sugarเท่านั้นและแนวทางที่ใช้นั้นเข้ากันได้กับภาษา C
  2. ใน shared_pointer_demoการจัดการรายการยังดำเนินการด้วยตนเอง แต่การจัดการหน่วยความจำเป็นไปโดยอัตโนมัติ (ไม่ใช้ตัวชี้ดิบ) สิ่งนี้คล้ายกับสิ่งที่คุณเคยสัมผัสกับ Java
  3. std_list_demo ใช้ไลบรารีมาตรฐาน listคอนเทนเนอร์สิ่งนี้แสดงให้เห็นว่าสิ่งต่างๆจะง่ายขึ้นเพียงใดหากคุณพึ่งพาไลบรารีที่มีอยู่แทนที่จะใช้ไลบรารีของคุณเอง
  4. std_vector_demoใช้vectorคอนเทนเนอร์ไลบรารีมาตรฐาน สิ่งนี้จะจัดการที่เก็บรายการในการจัดสรรหน่วยความจำที่ต่อเนื่องกัน กล่าวอีกนัยหนึ่งก็คือไม่มีตัวชี้สำหรับแต่ละองค์ประกอบ ในบางกรณีที่ค่อนข้างรุนแรงสิ่งนี้อาจไม่มีประสิทธิภาพอย่างมีนัยสำคัญ สำหรับกรณีทั่วไป แต่นี้เป็นคำแนะนำวิธีปฏิบัติที่ดีที่สุดสำหรับการจัดการรายชื่อใน C

หมายเหตุ: จากทั้งหมดนี้มีเพียงรายการที่raw_pointer_demoต้องการเท่านั้นที่จะทำลายรายการอย่างชัดเจนเพื่อหลีกเลี่ยงหน่วยความจำ "รั่ว" อีกสามวิธีจะทำลายรายการและเนื้อหาโดยอัตโนมัติเมื่อคอนเทนเนอร์อยู่นอกขอบเขต (ในตอนท้ายของฟังก์ชัน) ประเด็นคือ: C ++ สามารถเป็น "Java-like" ได้มากในเรื่องนี้ - แต่ถ้าคุณเลือกที่จะพัฒนาโปรแกรมของคุณโดยใช้เครื่องมือระดับสูงตามที่คุณต้องการ


/*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();
}

Node_referenceประกาศดังกล่าวข้างต้นอยู่หนึ่งในที่สุดความแตกต่างของภาษาระดับที่น่าสนใจระหว่าง Java และ C ++ ใน Java การประกาศอ็อบเจ็กต์ประเภทNodeจะใช้การอ้างอิงไปยังอ็อบเจ็กต์ที่จัดสรรแยกกันโดยปริยาย ใน C ++ คุณมีตัวเลือกการอ้างอิง (ตัวชี้) เทียบกับการจัดสรรโดยตรง (สแต็ก) ดังนั้นคุณต้องจัดการกับความแตกต่างอย่างชัดเจน ในกรณีส่วนใหญ่คุณจะใช้การจัดสรรโดยตรงแม้ว่าจะไม่ใช่สำหรับองค์ประกอบรายการ
Brent Bradburn

ไม่ทราบว่าทำไมฉันไม่ได้ขอแนะนำความเป็นไปได้ของการเป็นมาตรฐาน :: deque
Brent Bradburn

8

ภาพรวม

มี 2 ​​วิธีในการอ้างอิงและจัดสรรอ็อบเจ็กต์ใน C ++ ในขณะที่ใน Java มีเพียงวิธีเดียว

เพื่ออธิบายสิ่งนี้ไดอะแกรมต่อไปนี้แสดงวิธีจัดเก็บวัตถุในหน่วยความจำ

1.1 C ++ รายการที่ไม่มีพอยน์เตอร์

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 ++ ที่ใช้ในตัวอย่างนี้คล้ายกับไวยากรณ์ใน Java แต่การจัดสรรหน่วยความจำแตกต่างกัน

1.2 รายการ C ++ โดยใช้พอยน์เตอร์

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 (...)

หากคุณตรวจสอบความแตกต่างระหว่างทั้งสองวิธีคุณจะเห็นว่าในเทคนิคแรกรายการที่อยู่จะถูกจัดสรรภายในลูกค้าในขณะที่วิธีที่สองคุณต้องสร้างที่อยู่แต่ละรายการอย่างชัดเจน

คำเตือน: Java จัดสรรออบเจ็กต์ในหน่วยความจำเหมือนเทคนิคที่สองนี้ แต่ไวยากรณ์ก็เหมือนกับวิธีแรกซึ่งอาจทำให้ผู้มาใหม่ใช้ "C ++" สับสนได้

การดำเนินงาน

ตัวอย่างรายการของคุณอาจคล้ายกับตัวอย่างต่อไปนี้

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].
.......................................

สรุป

เนื่องจากรายการที่เชื่อมโยงมีจำนวนรายการที่แปรผันหน่วยความจำจึงถูกจัดสรรตามความจำเป็นและตามที่มีอยู่

UPDATE:

ควรค่าแก่การกล่าวถึงเนื่องจาก @haccks แสดงความคิดเห็นไว้ในโพสต์

ซึ่งในบางครั้งการอ้างอิงหรือตัวชี้อ็อบเจ็กต์จะระบุรายการที่ซ้อนกัน (aka "UML Composition")

และบางครั้งการอ้างอิงหรือตัวชี้อ็อบเจ็กต์จะบ่งชี้ถึงรายการภายนอก (aka "UML Aggregation")

แต่ไอเท็มที่ซ้อนกันของคลาสเดียวกันไม่สามารถใช้กับเทคนิค "no-pointer"


7

หมายเหตุด้านข้างถ้าสมาชิกตัวแรกสุดของคลาสหรือโครงสร้างเป็นตัวชี้ถัดไป (ดังนั้นไม่มีฟังก์ชันเสมือนหรือคุณลักษณะอื่นใดของคลาสที่จะหมายถึงถัดไปไม่ใช่สมาชิกคนแรกของคลาสหรือโครงสร้าง) แสดงว่าคุณ สามารถใช้คลาสหรือโครงสร้าง "ฐาน" ที่มีเพียงตัวชี้ถัดไปและใช้รหัสทั่วไปสำหรับการดำเนินการรายการที่เชื่อมโยงขั้นพื้นฐานเช่นผนวกแทรกก่อนดึงจากด้านหน้า ... เนื่องจาก C / C ++ รับประกันว่าแอดเดรสของสมาชิกตัวแรกของคลาสหรือโครงสร้างจะเหมือนกับแอดเดรสของคลาสหรือโครงสร้าง คลาสโหนดฐานหรือโครงสร้างจะมีเพียงตัวชี้ถัดไปที่จะใช้โดยฟังก์ชันรายการที่เชื่อมโยงพื้นฐานจากนั้นการพิมพ์จะถูกใช้ตามความจำเป็นในการแปลงระหว่างประเภทโหนดฐานและประเภทโหนด "ที่ได้รับ" หมายเหตุด้านข้าง - ใน C ++ หากคลาสโหนดฐานมีเพียงตัวชี้ถัดไป


6

เหตุใดจึงควรใช้พอยน์เตอร์ในรายการที่เชื่อมโยง

เหตุผลก็คือเมื่อคุณสร้างNodeอ็อบเจ็กต์คอมไพเลอร์ต้องจัดสรรหน่วยความจำสำหรับอ็อบเจ็กต์นั้นและขนาดของอ็อบเจ็กต์จะถูกคำนวณ
ขนาดของตัวชี้ไปยังประเภทใด ๆ เป็นที่รู้กันว่าคอมไพเลอร์ดังนั้นจึงสามารถคำนวณขนาดตัวชี้อ้างอิงตนเองของวัตถุได้

หากNode m_nodeจะนำมาใช้แทนแล้วคอมไพเลอร์มีความคิดเกี่ยวกับขนาดของไม่มีNodeและมันจะติดอยู่ในการเรียกซ้ำไม่มีที่สิ้นสุดsizeof(Node)ของการคำนวณ จำไว้เสมอ: ชั้นเรียนไม่สามารถมีสมาชิกประเภทของตัวเองได้


5

เพราะสิ่งนี้ในC ++

int main (..)
{
    MyClass myObject;

    // or

    MyClass * myObjectPointer = new MyClass();

    ..
}

เทียบเท่ากับสิ่งนี้ในJava

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

โดยที่ทั้งคู่สร้างวัตถุใหม่MyClassโดยใช้ตัวสร้างเริ่มต้น


0

เหตุใดรายการที่เชื่อมโยงจึงใช้พอยน์เตอร์แทนการจัดเก็บโหนดภายในโหนด

มีคำตอบที่ไม่สำคัญแน่นอน

หากไม่ได้เชื่อมโยงโหนดหนึ่งไปยังโหนดถัดไปโดยตัวชี้จะไม่เชื่อมโยงรายการเชื่อมโยงกับรายการ

การมีอยู่ของรายการที่เชื่อมโยงกันเป็นเพราะเราต้องการที่จะเชื่อมโยงวัตถุเข้าด้วยกัน ตัวอย่างเช่นเรามีวัตถุจากที่ไหนสักแห่ง ตอนนี้เราต้องการใส่วัตถุจริงนั้น (ไม่ใช่สำเนา) ที่ท้ายคิวเช่น ซึ่งทำได้โดยการเพิ่มลิงค์จากองค์ประกอบสุดท้ายที่อยู่ในคิวไปยังรายการที่เรากำลังเพิ่ม ในแง่เครื่องจักรการเติมคำด้วยที่อยู่ขององค์ประกอบถัดไป

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.