ตัวชี้กับการอ้างอิง


256

สิ่งที่จะเป็นการปฏิบัติที่ดีกว่าเมื่อให้ฟังก์ชันตัวแปรต้นฉบับเพื่อทำงานกับ:

unsigned long x = 4;

void func1(unsigned long& val) {
     val = 5;            
}
func1(x);

หรือ:

void func2(unsigned long* val) {
     *val = 5;
}
func2(&x);

IOW: มีเหตุผลอะไรไหมที่จะเลือกคนอื่น?


1
แน่นอนการอ้างอิงมีค่า แต่ฉันมาจาก C ที่พอยน์เตอร์อยู่ทุกที่ ต้องมีความชำนาญกับพอยน์เตอร์ก่อนเพื่อทำความเข้าใจคุณค่าของการอ้างอิง
Jay D

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

ชอบอ้างอิง พอยน์เตอร์ของผู้ใช้เมื่อคุณไม่มีตัวเลือก
Ferruccio

คำตอบ:


285

กฎง่ายๆของฉันคือ:

ใช้พอยน์เตอร์ถ้าคุณต้องการใช้ตัวชี้ทางคณิตศาสตร์กับพวกมัน (เช่นการเพิ่มที่อยู่พอยน์เตอร์เพื่อก้าวผ่านอาร์เรย์) หรือถ้าคุณต้องผ่านตัวชี้ NULL

ใช้การอ้างอิงเป็นอย่างอื่น


10
จุด Excelent ที่เกี่ยวข้องกับตัวชี้เป็น NULL หากคุณมีพารามิเตอร์ตัวชี้คุณจะต้องตรวจสอบอย่างชัดเจนว่าไม่ใช่ NULL หรือค้นหาการใช้งานทั้งหมดของฟังก์ชันเพื่อให้แน่ใจว่าไม่มีค่า NULL ความพยายามนี้ไม่จำเป็นสำหรับการอ้างอิง
Richard Corden

26
อธิบายความหมายของเลขคณิตของคุณ ผู้ใช้ใหม่อาจไม่เข้าใจว่าคุณต้องการปรับสิ่งที่ตัวชี้ชี้ไปที่
Martin York

7
มาร์ตินฉันคิดว่าคุณส่งตัวชี้ไปยังโครงสร้าง แต่คุณรู้ว่ามันไม่ใช่โครงสร้างที่เรียบง่าย แต่เป็นอาร์เรย์ของคณิตศาสตร์ ในกรณีนี้คุณสามารถทำดัชนีโดยใช้ [] หรือทำเลขคณิตโดยใช้ ++ / - บนตัวชี้ นั่นคือความแตกต่างโดยย่อ
Nils Pipenbrinck

2
มาร์ตินคุณสามารถทำสิ่งนี้กับพอยน์เตอร์โดยตรงเท่านั้น ไม่ได้มีการอ้างอิง แน่นอนว่าคุณสามารถใช้ตัวชี้ไปยังการอ้างอิงและทำสิ่งเดียวกันในทางปฏิบัติ แต่ถ้าคุณทำเช่นนั้นคุณจะจบด้วยรหัสสกปรกมาก ..
นิลส์ Pipenbrinck

1
สิ่งที่เกี่ยวกับความหลากหลาย (เช่นBase* b = new Derived())? ดูเหมือนเป็นกรณีที่ไม่สามารถจัดการได้หากไม่มีพอยน์เตอร์
Chris Redford

72

ฉันคิดว่าคุณจะได้รับประโยชน์จากการสร้างแนวทางการเขียนโค้ดฟังก์ชั่นต่อไปนี้:

  1. ในขณะที่ตำแหน่งอื่น ๆ ทั้งหมดจะเสมอconst-correct

    • หมายเหตุ: ซึ่งหมายความว่าเฉพาะค่าที่เกิน (ดูรายการที่ 3) และค่าที่ส่งผ่านตามค่า (ดูรายการที่ 4) อาจไม่มีตัวconstระบุ
  2. ส่งผ่านค่าโดยตัวชี้เท่านั้นถ้าค่า 0 / NULL เป็นอินพุตที่ถูกต้องในบริบทปัจจุบัน

    • เหตุผลที่ 1: ในฐานะผู้โทรคุณจะเห็นว่าสิ่งที่คุณส่งผ่านจะต้องอยู่ในสถานะใช้งาน

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

    • เหตุผลที่ 3: เหตุผลที่ 1 และ 2 จะเป็นคอมไพเลอร์บังคับ ตรวจสอบข้อผิดพลาดในเวลารวบรวมถ้าคุณทำได้

  3. หากฟังก์ชั่นอาร์กิวเมนต์เป็นค่าออกให้ส่งโดยอ้างอิง

    • เหตุผล: เราไม่ต้องการแยกรายการ 2 ...
  4. เลือก "pass by value" over "pass by const Reference" เฉพาะในกรณีที่ค่าเป็น POD ( โครงสร้างข้อมูลเก่าธรรมดา ) หรือขนาดเล็กพอ (หน่วยความจำที่ชาญฉลาด) หรือวิธีอื่น ๆ ที่ราคาถูก (ตามเวลา) เพื่อคัดลอก

    • เหตุผล: หลีกเลี่ยงสำเนาที่ไม่จำเป็น
    • หมายเหตุ: ขนาดเล็กพอและถูกพอไม่ใช่ค่าที่แน่นอน

มันไม่มีแนวทางเมื่อ: ... "เมื่อใช้ const &" ... แนวทาง 2 ควรเขียน "สำหรับ [ใน] ค่าเพียงผ่านตัวชี้ถ้า NULL ถูกต้องมิฉะนั้นใช้อ้างอิง const (หรือสำหรับ" วัตถุขนาดเล็กคัดลอก) หรือการอ้างอิงหากเป็นค่า [ออก] ฉันกำลังตรวจสอบโพสต์นี้เพื่อเพิ่ม +1
paercebal

รายการที่ 1 ครอบคลุมกรณีที่คุณอธิบาย
Johann Gerell

เป็นการยากที่จะส่งผ่านพารามิเตอร์โดยอ้างอิงถ้าไม่ใช่ค่าเริ่มต้นที่สร้างขึ้นได้ มันค่อนข้างธรรมดาในรหัสของฉัน - เหตุผลทั้งหมดที่มีฟังก์ชั่นสร้างว่าวัตถุไม่ได้เป็นเพราะมันไม่สำคัญ
MSalters

@MSalters: ถ้าคุณจะจัดสรรหน่วยความจำภายในฟังก์ชั่น (ซึ่งฉันคิดว่าเป็นสิ่งที่คุณหมายถึง) แล้วทำไมไม่กลับไปชี้ที่หน่วยความจำที่จัดสรร?
Kleist

@Kleist: ในนามของ @MSalters มีเหตุผลที่เป็นไปได้มากมาย สิ่งแรกคือคุณอาจจัดสรรหน่วยความจำให้เต็มแล้วเช่นขนาดที่std::vector<>กำหนดไว้ล่วงหน้า
Johann Gerell

24

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

ในขณะที่มีความสามารถที่แตกต่างกัน (ไม่ว่าจะเป็น NULL หรือไม่ก็ตาม) ด้วยตัวชี้ความแตกต่างในทางปฏิบัติที่ใหญ่ที่สุดสำหรับพารามิเตอร์เอาต์พุตคือไวยากรณ์อย่างแท้จริง คู่มือสไตล์ C ++ ของ Google ( https://google.github.io/styleguide/cppguide.html#Reference_Arguments ) ยกตัวอย่างเช่น mandates เฉพาะตัวชี้สำหรับพารามิเตอร์เอาต์พุตและอนุญาตเฉพาะการอ้างอิงที่มีค่า การให้เหตุผลเป็นหนึ่งในความสามารถในการอ่าน: สิ่งที่มีไวยากรณ์ของค่าไม่ควรมีความหมายเชิงพอยน์เตอร์ ฉันไม่ได้แนะนำว่าสิ่งนี้ถูกหรือผิด แต่ฉันคิดว่าประเด็นในที่นี้คือมันเป็นเรื่องของสไตล์ไม่ใช่ความถูกต้อง


หมายความว่าการอ้างอิงมีไวยากรณ์ค่า แต่หมายถึงความหมายของตัวชี้?
Eric Andrew Lewis เมื่อ

ดูเหมือนว่าคุณกำลังส่งสำเนาตั้งแต่ส่วน "ส่งผ่านการอ้างอิง" จะปรากฏเฉพาะจากคำจำกัดความ funciton (ไวยากรณ์ค่า) แต่คุณไม่ได้คัดลอกค่าที่คุณผ่านคุณเป็นหลักผ่านตัวชี้ภายใต้ประทุนซึ่งช่วยให้ ฟังก์ชั่นเพื่อปรับเปลี่ยนค่าของคุณ
phant0m

อย่าลืมว่าคู่มือสไตล์ Google C ++ น่ารังเกียจมาก
Deduplicator

7

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


2
หากคุณปฏิบัติตามหลักเกณฑ์ของ Johann Gerell การอ้างอิงที่ไม่ใช่สมาชิกจะประกาศตัวแปรที่เปลี่ยนแปลงเช่นกันดังนั้นตัวชี้ไม่มีประโยชน์ดังกล่าวที่นี่
Alexander Kondratskiy

4
@AlexanderKondratskiy: คุณไม่มีจุด ... คุณไม่สามารถดูได้ทันทีที่ไซต์การโทรว่าฟังก์ชันที่เรียกใช้รับพารามิเตอร์เป็นพารามิเตอร์constหรือไม่constอ้างอิง แต่คุณสามารถดูว่าพารามิเตอร์ผ่าน ala &xvs. xและใช้หรือไม่ ความเชื่อมั่นที่จะเข้ารหัสว่าพารามิเตอร์มีแนวโน้มที่จะถูกแก้ไขหรือไม่ (ที่กล่าวว่ามีบางครั้งที่คุณต้องการส่งconstตัวชี้ดังนั้นความมั่นใจจึงเป็นเพียงคำแนะนำบางอย่างที่สงสัยว่าอาจมีการแก้ไขบางอย่างอาจแก้ไขได้เมื่อไม่อันตรายน้อยกว่าที่คิดว่าจะไม่เกิดขึ้น .... )
Tony Delroy

5

หากคุณมีพารามิเตอร์ที่คุณอาจจำเป็นต้องระบุว่าไม่มีค่าเป็นเรื่องปกติที่จะทำให้พารามิเตอร์เป็นค่าตัวชี้และส่งผ่านค่า NULL

ทางออกที่ดีในกรณีส่วนใหญ่ (จากมุมมองด้านความปลอดภัย) คือการใช้เพิ่ม :: ตัวเลือก สิ่งนี้ช่วยให้คุณผ่านค่าที่เป็นทางเลือกโดยการอ้างอิงและยังเป็นค่าตอบแทน

// Sample method using optional as input parameter
void PrintOptional(const boost::optional<std::string>& optional_str)
{
    if (optional_str)
    {
       cout << *optional_str << std::endl;
    }
    else
    {
       cout << "(no string)" << std::endl;
    }
}

// Sample method using optional as return value
boost::optional<int> ReturnOptional(bool return_nothing)
{
    if (return_nothing)
    {
       return boost::optional<int>();
    }

    return boost::optional<int>(42);
}


4

ตัวชี้

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

    BaseType* ptrBaseType;
    BaseType objBaseType;
    ptrBaseType = &objBaseType;
  • & เป็นตัวดำเนินการเอกที่จะส่งคืนที่อยู่หน่วยความจำของตัวถูกดำเนินการ

  • ตัวดำเนินการ Dereferencing (*) ใช้เพื่อเข้าถึงค่าที่เก็บไว้ในตัวแปรที่ตัวชี้ชี้ไป

       int nVar = 7;
       int* ptrVar = &nVar;
       int nVar2 = *ptrVar;

การอ้างอิง

  • การอ้างอิง (&) เปรียบเสมือนนามแฝงของตัวแปรที่มีอยู่

  • การอ้างอิง (&) เป็นเหมือนตัวชี้ค่าคงที่ที่ถูกอ้างอิงโดยอัตโนมัติ

  • โดยปกติจะใช้สำหรับรายการอาร์กิวเมนต์ของฟังก์ชันและค่าส่งคืนของฟังก์ชัน

  • การอ้างอิงจะต้องเริ่มต้นเมื่อมันถูกสร้างขึ้น

  • เมื่อมีการเริ่มต้นการอ้างอิงไปยังวัตถุมันไม่สามารถเปลี่ยนแปลงได้เพื่ออ้างอิงไปยังวัตถุอื่น

  • คุณไม่สามารถมีการอ้างอิงเป็นโมฆะ

  • การอ้างอิง const สามารถอ้างถึง const int มันทำกับตัวแปรชั่วคราวที่มีค่าของ const

    int i = 3;    //integer declaration
    int * pi = &i;    //pi points to the integer i
    int& ri = i;    //ri is refers to integer i – creation of reference and initialization

ป้อนคำอธิบายรูปภาพที่นี่

ป้อนคำอธิบายรูปภาพที่นี่


3

การอ้างอิงเป็นตัวชี้โดยนัย โดยทั่วไปคุณสามารถเปลี่ยนค่าที่จุดอ้างอิงเป็น แต่คุณไม่สามารถเปลี่ยนการอ้างอิงให้ชี้ไปที่สิ่งอื่น ดังนั้น 2 เซนต์ของฉันคือถ้าคุณต้องการเปลี่ยนค่าของพารามิเตอร์ที่ส่งเป็นข้อมูลอ้างอิง แต่ถ้าคุณต้องการเปลี่ยนพารามิเตอร์ให้ชี้ไปที่วัตถุอื่นส่งผ่านมันโดยใช้ตัวชี้


3

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


พิมพ์ผิด: ความหมายไม่ใช่ความหมาย; +1 ฉันเห็นด้วยเกี่ยวกับความเป็นไปได้ในการไฮไลต์แทนที่จะเขียน (C #) หรือ & (ในกรณี C ไม่มีการอ้างอิง)
peenut

2

ผ่านการอ้างอิง const เว้นแต่มีเหตุผลที่คุณต้องการเปลี่ยน / เก็บเนื้อหาที่คุณกำลังผ่าน

นี่จะเป็นวิธีที่มีประสิทธิภาพมากที่สุดในกรณีส่วนใหญ่

ตรวจสอบให้แน่ใจว่าคุณใช้ const ในแต่ละพารามิเตอร์ที่คุณไม่ต้องการเปลี่ยนแปลงเนื่องจากไม่เพียง แต่ปกป้องคุณจากการทำสิ่งที่โง่ในฟังก์ชั่นเท่านั้น แต่ยังช่วยให้ผู้ใช้รายอื่นทราบถึงสิ่งที่ฟังก์ชันทำ ซึ่งรวมถึงการสร้างตัวชี้ const เมื่อคุณต้องการเปลี่ยนสิ่งที่ชี้ไปที่ ...


2

ตัวชี้:

  • สามารถมอบหมายnullptr(หรือNULL )
  • ที่ไซต์การโทรคุณต้องใช้ &หากประเภทของคุณไม่ใช่ตัวชี้ทำให้คุณต้องแก้ไขวัตถุของคุณอย่างชัดเจน
  • พอยน์เตอร์สามารถฟื้นตัวได้

อ้างอิง:

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

จุดเล็ก ๆ สำหรับผู้ที่ไม่ทราบ: nullptr หรือ NULL เป็นเพียงแค่ 0. stackoverflow.com/questions/462165/…
Serguei Fedorov

2
nullptr ไม่เหมือนกับ 0 ลองใช้ int a = nullptr stackoverflow.com/questions/1282295/what-exactly-is-nullptr
Johan Lundberg

0

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

การอ้างอิงมีประโยชน์อย่างยิ่งสำหรับการระบุอาร์กิวเมนต์ของฟังก์ชัน

สำหรับข้อมูลเพิ่มเติมโปรดดู "A Tour of C ++" โดย "Bjarne Stroustrup" (2014) หน้า 11-12

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