เหตุใด C ++ และ Java ทั้งสองจึงใช้แนวคิดของ“ การอ้างอิง” แต่ไม่เข้าใจ


26

ใน C ++ อาร์กิวเมนต์การอ้างอิงไปยังฟังก์ชันอนุญาตให้ฟังก์ชันทำการอ้างอิงถึงสิ่งอื่น:

int replacement = 23;

void changeNumberReference(int& reference) {
    reference = replacement;
}

int main() {
    int i = 1;
    std::cout << "i=" << i << "\n"; // i = 1;
    changeNumberReference(i);
    std::cout << "i=" << i << "\n"; // i = 23;
}

อะนาล็อกอาร์กิวเมนต์อ้างอิงคงที่ไปยังฟังก์ชันจะแสดงข้อผิดพลาดเวลารวบรวมถ้าเราพยายามเปลี่ยนการอ้างอิง:

void changeNumberReference(const int& reference) {
    reference = replacement; // compile-time error: assignment of read-only reference 'reference'
}

ตอนนี้ด้วย Java เอกสารบอกว่าฟังก์ชั่นข้อโต้แย้งของประเภทที่ไม่ใช่ดั้งเดิมคือการอ้างอิง ตัวอย่างจากเอกสารอย่างเป็นทางการ:

public void moveCircle(Circle circle, int deltaX, int deltaY) {
    // code to move origin of circle to x+deltaX, y+deltaY
    circle.setX(circle.getX() + deltaX);
    circle.setY(circle.getY() + deltaY);

    // code to assign a new reference to circle
    circle = new Circle(0, 0);
}

จากนั้นวงกลมจะได้รับการอ้างอิงไปยังวัตถุวงกลมใหม่ที่มี x = y = 0 การมอบหมายใหม่นี้ไม่มีความคงทนอย่างไรก็ตามเนื่องจากการอ้างอิงถูกส่งผ่านโดยค่าและไม่สามารถเปลี่ยนแปลงได้

สำหรับฉันแล้วนี่ไม่ได้ดูการอ้างอิง C ++ เหมือนกันทั้งหมด มันไม่เหมือนกับการอ้างอิง C ++ ปกติเพราะคุณไม่สามารถอ้างถึงอย่างอื่นได้และไม่เหมือนกับการอ้างอิง C ++ const เพราะใน Java โค้ดที่จะเปลี่ยน (แต่จริงๆแล้วไม่ได้) การอ้างอิงนั้นไม่ได้รวบรวมคอมไพล์ ข้อผิดพลาดครั้ง

สิ่งนี้คล้ายกับพฤติกรรมของพอยน์เตอร์ C ++ คุณสามารถใช้มันเพื่อเปลี่ยนค่าของวัตถุที่มีปลายแหลม แต่คุณไม่สามารถเปลี่ยนค่าของตัวชี้ในฟังก์ชั่นได้ เช่นเดียวกับพอยน์เตอร์ C ++ (แต่ไม่ใช่กับการอ้างอิง C ++) ใน Java คุณสามารถส่งผ่าน "null" เป็นค่าสำหรับอาร์กิวเมนต์

ดังนั้นคำถามของฉันคือ: ทำไม Java ใช้ความคิดของ "อ้างอิง"? เป็นที่เข้าใจกันหรือไม่ว่าพวกเขาไม่คล้ายกับการอ้างอิง C ++? หรือว่าพวกเขาคล้ายกับการอ้างอิง C ++ จริง ๆ และฉันขาดอะไรไป


10
โปรดทราบว่าในตัวอย่าง C ++ แรกของคุณคุณไม่ได้ทำการอ้างอิง "ชี้ไปที่สิ่งอื่น" คุณกำลังเปลี่ยนค่าของตัวแปรที่เป็นจุดอ้างอิงแทน นี่เป็นแนวคิดคล้ายกับการกลายพันธุ์สถานะของวัตถุที่อ้างถึงด้วยการอ้างอิง Java
Xion

@Xion yep ตอนนี้ฉันได้อ่านความคิดเห็นของคุณแล้วฉันเห็นว่าคุณหมายถึงอะไรและเห็นด้วยกับ 98% ของสิ่งที่คุณพูด :) ปัญหาใน Java คือคุณต้องสามารถเข้าถึงสถานะของวัตถุใด ๆ และทั้งหมดเพื่อให้แนวคิด บรรลุสิ่งที่ C ++ ทำได้โดยเพียงให้คุณตั้งค่าการอ้างอิงกับค่าของการอ้างอิงอื่น
Shivan Dragon

คำตอบ:


48

ทำไม? เนื่องจากแม้ว่าคำศัพท์ที่สอดคล้องกันโดยทั่วไปจะดีสำหรับอาชีพทั้งหมดนักออกแบบภาษาไม่เคารพการใช้ภาษาของนักออกแบบภาษาคนอื่น ๆ เสมอโดยเฉพาะอย่างยิ่งหากภาษาอื่น ๆ นั้นถูกมองว่าเป็นคู่แข่ง

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

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

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


7
ขอบคุณที่คิดถึงการอ้างอิง C ++ เป็น "นามแฝง" (สำหรับค่า / อินสแตนซ์เดียวกัน) และการอ้างอิง Java เป็น "ตัวชี้ที่ไม่มีการดำเนินการระดับต่ำสุดที่น่ารังเกียจ
Shivan Dragon

14
Java ไม่มีพอยน์เตอร์และภูมิใจในมันดังนั้นการใช้ "พอยน์เตอร์" เป็นคำศัพท์ก็ไม่มีทางเลือก สิ่งที่เกี่ยวกับNullPointerExceptionหรือความจริงที่ว่า JLS ใช้คำว่า "ตัวชี้"
Tobias Brandt

7
@TobiasBrandt: NullPointerExceptionไม่ใช่ตัวชี้ แต่เป็นข้อยกเว้นที่บ่งชี้ว่าในระดับต่ำกว่าตัวชี้ NULL ถูกส่งผ่าน / ยกเลิกการลงทะเบียน ใช้Pointerเป็นส่วนหนึ่งของข้อยกเว้นถ้ามีอะไรเพิ่มไปยังภาพที่น่ารังเกียจพวกเขามี JLS ต้องพูดถึงตัวชี้เพราะ VM ของ Java ถูกเขียนเป็นภาษาหลักที่ใช้พวกเขา คุณไม่สามารถอธิบายรายละเอียดภาษาได้อย่างถูกต้องโดยไม่ต้องอธิบายวิธีการทำงานของ internals
Elias Van Ootegem

3
@TobiasBrandt: Java ใช้ตัวชี้ทั่วสถานที่; วัตถุฮีปมีการอ้างอิงถึงสิ่งที่สำหรับการใช้งานจริงทั้งหมดเป็นตัวชี้ เป็นเพียงที่ Java ไม่เปิดเผยการดำเนินการใด ๆ กับประเภทตัวชี้ไปยังโปรแกรมเมอร์
John Bode

2
ฉันชอบคำว่า "object ID" สำหรับการอ้างอิง Java / .NET ที่ระดับบิตสิ่งต่าง ๆ อาจถูกนำมาใช้เป็นสิ่งที่คล้ายกับตัวชี้ แต่โดยทั่วไปแล้วไม่มีสิ่งใดที่ต้องการ สิ่งสำคัญคือ ID อ็อบเจ็กต์มีข้อมูลประเภทใดที่เฟรมเวิร์ก / vm ต้องการระบุวัตถุ
supercat

9

การอ้างอิงคือสิ่งที่อ้างอิงถึงอีกสิ่งหนึ่ง ภาษาต่าง ๆ มีความหมายที่เฉพาะเจาะจงมากขึ้นกับคำว่า "การอ้างอิง" ซึ่งโดยปกติแล้วจะเป็น "สิ่งที่คล้ายกับตัวชี้โดยไม่มีลักษณะเลวทั้งหมด" C ++ อธิบายความหมายเฉพาะเช่นเดียวกับ Java หรือ Perl

ใน C ++ การอ้างอิงเป็นเหมือนนามแฝง (ซึ่งสามารถนำมาใช้ผ่านตัวชี้) นี้จะช่วยให้ผ่านโดยการอ้างอิงหรือออกจากการขัดแย้ง

ใน Java การอ้างอิงเป็นพอยน์เตอร์ยกเว้นว่านี่ไม่ใช่แนวคิดการแก้ไขของภาษา: วัตถุทั้งหมดเป็นการอ้างอิงประเภทดั้งเดิมเช่นตัวเลขไม่ใช่ พวกเขาไม่ต้องการที่จะพูดว่า“ชี้” เพราะไม่มีตัวชี้เลขคณิตและมีไม่มี reified ชี้ใน Java แต่พวกเขาต้องการให้ชัดเจนว่าเมื่อคุณผ่านการObjectเป็นอาร์กิวเมนต์วัตถุที่ไม่ได้คัดลอก สิ่งนี้ยังไม่ผ่านการอ้างอิงแต่มีอะไรมากกว่านั้นเช่นการแบ่งปันผ่าน


6

ส่วนใหญ่จะกลับไปที่ Algol 68 และส่วนหนึ่งเป็นปฏิกิริยาต่อวิธีที่ C กำหนดพอยน์เตอร์

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

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

ตัวอย่างเช่นการประกาศอย่างเหมือนINT j := 7;จริงได้รับการปฏิบัติโดยคอมไพเลอร์ว่าเป็นการประกาศเช่นนั้นREF INT j = NEW LOC INT := 7นั้น ดังนั้นสิ่งที่คุณประกาศใน Algol 68 คือการอ้างอิงซึ่งโดยปกติจะเริ่มต้นเพื่ออ้างอิงถึงบางสิ่งที่ถูกจัดสรรบนฮีปและนั่นคือ (ทางเลือก) ที่เริ่มต้นเพื่อให้มีค่าที่ระบุ แตกต่างจาก Java แต่อย่างน้อยพวกเขาก็พยายามที่จะให้คุณรักษาไวยากรณ์ที่มีเหตุผลสำหรับสิ่งนั้นอย่างต่อเนื่องแทนที่จะมีสิ่งต่าง ๆ เช่นfoo bar = new foo();หรือพยายามที่จะบอกเรื่องโกหกเกี่ยวกับ "ตัวชี้ของเราไม่ใช่ตัวชี้"

ปาสกาลและส่วนใหญ่ของลูกหลาน (ทั้งทางตรงและ ... ทางจิตวิญญาณ) เปลี่ยนชื่อแนวคิดการอ้างอิง Algol 68 เป็น "ตัวชี้" แต่ยังคงความคิดของตัวเองเป็นหลักเดียวกัน: ตัวชี้เป็นตัวแปรที่จัดขึ้นอย่างใดอย่างหนึ่งnilหรือที่อยู่ของสิ่งที่คุณจัดสรร บนฮีป (กล่าวคืออย่างน้อยตามที่กำหนดไว้โดยเซ่นและเวิร์ ธ ไม่มีโอเปอเรเตอร์ "ที่อยู่ของ" ดังนั้นจึงไม่มีวิธีใดที่ตัวชี้จะอ้างถึงตัวแปรที่กำหนดไว้ตามปกติ) แม้จะเป็น "ตัวชี้" แต่ก็ไม่สนับสนุนการคำนวณทางคณิตศาสตร์

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

เมื่อ Java ถูกประดิษฐ์ขึ้นมาซันคิดว่า "Java ไม่มีตัวชี้" เป็นข้อความทางการตลาดที่เรียบง่ายและสะอาดกว่า: "เกือบทุกอย่างใน Java เป็นตัวชี้ แต่ตัวชี้เหล่านี้ส่วนใหญ่จะเหมือนกับ Pascal แทนที่จะเป็น C" เนื่องจากพวกเขาต้องการตัดสินใจว่า "ตัวชี้" ไม่ใช่คำที่ยอมรับได้พวกเขาต้องการสิ่งอื่นและขุด "การอ้างอิง" แทนแม้ว่าการอ้างอิงของพวกเขาจะละเอียด (และในบางกรณีก็ไม่ค่อยละเอียด) แตกต่างจากการอ้างอิง Algol 68

แม้ว่ามันจะออกมาแตกต่างกันบ้าง C ++ ก็ติดอยู่กับปัญหาเดียวกันโดยประมาณ: คำว่า "ตัวชี้" นั้นเป็นที่รู้จักและเข้าใจกันอยู่แล้วดังนั้นพวกเขาจึงต้องการคำที่แตกต่างออกไปสำหรับสิ่งที่แตกต่างกันนี้ แตกต่างจากสิ่งที่ผู้คนเข้าใจ "ตัวชี้" เป็นค่าเฉลี่ย ดังนั้นแม้ว่าจะแตกต่างอย่างเห็นได้ชัดจากการอ้างอิง Algol 68 แต่พวกเขาก็ใช้คำว่า "การอ้างอิง" อีกครั้ง


สิ่งที่ทำให้การอ้างอิงใน Algol 68 เจ็บปวดโดยเฉพาะอย่างยิ่งคือ dereferencing อัตโนมัติ นั่นเป็นสิ่งที่สะดวกตราบเท่าที่มัน จำกัด อยู่แค่ระดับเดียว แต่ความจริงที่ว่ามันสามารถทำได้หลายครั้งรวมกับน้ำตาล syntactic ซึ่งซ่อนบางส่วนของการอ้างอิงถึงตาไม่รู้ทำให้สับสน
AProgrammer

0

ความคิดของ 'ประเภทอ้างอิง' เป็นรูปแบบทั่วไปมันสามารถแสดงทั้งตัวชี้และการอ้างอิง (ในแง่ของ C ++) ซึ่งตรงข้ามกับ 'ประเภทค่า' การอ้างอิงของ Java เป็นตัวชี้โดยไม่มีค่าใช้จ่ายด้านไวยากรณ์ของ->การ*ยกเลิกการประชุม หากคุณพิจารณาถึงการนำ JVM ไปใช้ใน C ++ พวกเขาจะเป็นตัวชี้ตำแหน่งที่แท้จริง นอกจากนี้ C # ยังมีแนวคิดเกี่ยวกับการอ้างอิงคล้ายกับ Java แต่ก็มีพอยน์เตอร์และ 'ref' qualifier บนพารามิเตอร์ฟังก์ชันซึ่งอนุญาตให้ส่งผ่านค่าประเภทโดยอ้างอิงและหลีกเลี่ยงการคัดลอกเช่น&ใน C ++


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