กลับไปหน้าบทความ

อ่าน 6 นาที

เอกสารอ้างอิง (C++)

ซี++/CS1 maint: หลายชื่อ: รายชื่อผู้แต่ง/CS1 maint: ชื่อตัวเลข: รายชื่อผู้แต่ง

ในภาษาโปรแกรมC++ reference คือ ชนิดข้อมูล อ้างอิงแบบ ง่ายๆ ที่มีประสิทธิภาพน้อยกว่าแต่ปลอดภัยกว่า ชนิด ข้อมูลพอยเตอร์ที่สืบทอดมาจากภาษา Cชื่อ" C++...

เอกสารอ้างอิง (C++)

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

ไวยากรณ์และศัพท์เฉพาะ

การอ้างอิงค่า L

คำประกาศในแบบฟอร์ม:

ทีแอนด์อาร์

โดยที่Tเป็นประเภทและrเป็นตัวระบุกำหนดตัวระบุที่มีประเภทเป็นการอ้างอิงค่า lTถึง[ 1 ]

ตัวอย่าง:

int a = 5 ; int & refA = a ;ภายนอกภายใน& อ้างอิงB ;

ในที่นี้refAและrefBเป็นประเภท "การอ้างอิงค่า lvalue ไปยังint"

int & foo ();

fooเป็นฟังก์ชันที่ส่งคืน "lvalue reference to int"

void bar ( int & p );

barเป็นฟังก์ชันที่มีพารามิเตอร์อ้างอิงpซึ่งเป็น "ค่าอ้างอิง lvalue ถึงint"

คลาสMyClass { int & b ; };

MyClassเป็นโครงสร้างclassที่มีสมาชิกซึ่งมีค่าอ้างอิงถึง lvalueint

int fn () { return 42 ; }int ( & fnRef )() = fn ; int ( && fnRef2 )() = fn ; // โดยพื้นฐานแล้วเทียบเท่ากับข้างต้น

fnเป็นฟังก์ชันที่ส่งคืนค่า (ชนิดที่ไม่ใช่การอ้างอิง) intและfnRefเป็นชื่อเรียกแทนของfn

const int & ref = 65 ;

const int& refเป็นการอ้างอิง lvalue ที่const intชี้ไปยังพื้นที่จัดเก็บข้อมูลที่มีค่า 65

int a [ 3 ]; int ( & alvr )[ 3 ] = arr ; int ( && arvr )[ 3 ] = std :: move ( a ); using Triplet = int [ 3 ]; int ( && aprvl )[ 3 ] = Triplet {}; // Triplet{} เป็นอาร์เรย์ prvalue int * const & pclv = a ; // เหมือนกับ int* const& pclv = &a[0]; int *&& prv = a ; // int*& alv = a; // ข้อผิดพลาด: การเริ่มต้นค่าอ้างอิง lvalue ให้กับชนิดที่ไม่ใช่ const ด้วย rvalue

alvrเป็นการอ้างอิงถึงอาร์เรย์ เมื่อเริ่มต้นการอ้างอิงถึงอาร์เรย์ การแปลงอาร์เรย์เป็นตัวชี้จะไม่เกิดขึ้น แต่จะเกิดขึ้นเมื่อเริ่มต้นการอ้างอิงถึงตัวชี้ เนื่องจากผลลัพธ์ของการแปลงอาร์เรย์เป็นตัวชี้จะเป็นค่า prvalue ดังนั้นเฉพาะการอ้างอิง lvalue constและการอ้างอิง rvalue เท่านั้นที่สามารถเริ่มต้นด้วยผลลัพธ์นั้นได้ ในทำนองเดียวกัน เมื่อเริ่มต้นการอ้างอิงถึงฟังก์ชัน การแปลงฟังก์ชันเป็นตัวชี้จะไม่เกิดขึ้น (ดูfnRefด้านบน) แต่จะเกิดขึ้นเมื่อเริ่มต้นการอ้างอิงถึง ตัว ชี้ฟังก์ชัน

int fn () { return 42 ; }int ( * const & pFnRef )() = fn ; // เหมือนกับ int (*const &pFnRef)() = &fn; int ( *&& pFnRef2 )() = fn ;

การอ้างอิงค่า R

คำประกาศในแบบฟอร์ม:

ทีแอนด์อาร์;

โดยที่Tเป็นประเภท และrเป็นตัวระบุ กำหนดตัวระบุที่มีประเภทเป็นการอ้างอิง rvalueไปยังTเนื่องจากชื่อของ การอ้างอิง rvalueนั้นเป็น lvalue เองstd::moveจึงต้องใช้ เพื่อส่งการอ้างอิง rvalue ไปยังฟังก์ชันโอเวอร์โหลดที่ยอมรับพารามิเตอร์การอ้างอิง rvalue การอ้างอิง rvalue ไปยังพารามิเตอร์เทมเพลตประเภท cv-unqualified ของเทมเพลตฟังก์ชันเดียวกัน หรือauto&&ยกเว้นเมื่ออนุมานจากรายการตัวเริ่มต้นที่อยู่ในวงเล็บปีกกา เรียกว่าการอ้างอิงส่งต่อ (เรียกว่า "การอ้างอิงสากล" ในแหล่งข้อมูลเก่าบางแหล่ง[ 2 ] ) และสามารถทำหน้าที่เป็นการอ้างอิง lvalue หรือ rvalue ขึ้นอยู่กับสิ่งที่ส่งผ่านไปยังพวกมัน[ 3 ] เมื่อพบในพารามิเตอร์ฟังก์ชัน บางครั้งจะใช้ร่วมกับstd::forward()เพื่อส่งต่ออาร์กิวเมนต์ฟังก์ชันไปยังฟังก์ชันอื่นในขณะที่รักษาหมวดหมู่ค่า (lvalue หรือ rvalue) ที่มีเมื่อส่งผ่านไปยังฟังก์ชันที่เรียก[ 4 ]

ชนิดข้อมูลที่มีรูปแบบ "อ้างอิงถึงT" บางครั้งเรียกว่าชนิดข้อมูลอ้างอิง (reference types ) ตัวระบุที่มีชนิดข้อมูลอ้างอิงเรียกว่าตัวแปรอ้างอิง (reference variables ) อย่างไรก็ตาม การเรียกพวกมันว่าตัวแปร นั้น แท้จริงแล้วเป็นการเรียกที่ไม่ถูกต้องดังที่เราจะได้เห็นต่อไป

ความหมายและข้อจำกัด

การอ้างอิงไม่ใช่วัตถุ และการอ้างอิงสามารถอ้างอิงถึงประเภทวัตถุหรือฟังก์ชันเท่านั้น อาร์เรย์ของการอ้างอิง พอยเตอร์ไปยังการอ้างอิง และการอ้างอิงไปยังการอ้างอิงนั้นไม่ได้รับอนุญาต เนื่องจากสิ่งเหล่านี้ต้องการประเภทวัตถุและจะทำให้เกิดข้อผิดพลาดในการคอมไพล์ (ในขณะที่การอ้างอิงถึงอาร์เรย์และการอ้างอิงถึงพอยเตอร์จะไม่เกิดข้อผิดพลาดหากมีการกำหนดค่าเริ่มต้นแล้ว) การอ้างอิงถึงอาร์เรย์ก็ ไม่ ถูกต้องเช่นกัน เนื่องจากอาร์เรย์ไม่ใช่ประเภทวัตถุหรือฟังก์ชันแต่การอ้างอิงถึงอาร์เรย์สามารถมีอยู่ได้int&i[4]int&*iint&&iint(&i)[4]int*&ivoidvoidvoid*

การประกาศอ้างอิงเป็นconstหรือvolatile( ) ก็ล้มเหลวเช่นกัน เว้นแต่จะใช้/ ซึ่งในกรณีนี้ / จะถูกละเว้น อย่างไรก็ตาม หากมีการอนุมานอาร์กิวเมนต์เทมเพลตและมีการอนุมานประเภทอ้างอิง (ซึ่งเกิดขึ้นเมื่อใช้การอ้างอิงแบบส่งต่อและมีการส่ง lvalue ไปยังฟังก์ชัน) หรือหาก, หรือแสดงถึงประเภทอ้างอิง ก็เป็นไปได้ที่จะรับการอ้างอิงไปยังประเภทนั้น ในกรณีนั้น กฎที่ใช้ในการกำหนดประเภทของการอ้างอิงเรียกว่าการยุบการอ้างอิงและทำงานดังนี้: สมมติว่ามีประเภทและประเภทอ้างอิงไปยังการพยายามสร้างการอ้างอิง rvalue ไปยัง จะสร้างในขณะที่การอ้างอิง lvalue ไปยังจะสร้างการอ้างอิง lvalue ไปยังกล่าวอีกนัยหนึ่ง การอ้างอิง lvalue จะแทนที่การอ้างอิง rvalue และการอ้างอิง rvalue ของการอ้างอิง rvalue จะยังคงไม่เปลี่ยนแปลงvolatileint&itypedefdecltypeconstvolatiletypedefusingdecltypeTTTRTRTRTRT

using LrefInt = int & ; using RrefInt = int && ;int i = 0 ;LrefInt & r1 = i ; // r1 มีชนิดข้อมูลเป็น int& const LrefInt & r2 = i ; // r2 มีชนิดข้อมูลเป็น int& const LrefInt && r3 = i ; // r3 มีชนิดข้อมูลเป็น int&RrefInt & r4 = i ; // r4 มีชนิดข้อมูลเป็น int& RrefInt && r5 = 5 ; // r5 มีชนิดข้อมูลเป็น int&&decltype ( r2 ) & r6 = i ; // r6 มีชนิดข้อมูลเป็น int& decltype ( r2 ) && r7 = i ; // r7 มีชนิดข้อมูลเป็น int&

ฟังก์ชันสมาชิกที่ไม่ใช่แบบสแตติกสามารถประกาศได้โดยใช้ตัวระบุ ref ตัวระบุนี้มีส่วนร่วมในการแก้ไขการโอเวอร์โหลดและใช้กับพารามิเตอร์ออบเจ็กต์โดยปริยายเช่นเดียวกับconstและvolatileแต่ต่างจากสองตัวนั้นตรงที่มันไม่เปลี่ยนแปลงคุณสมบัติของthisสิ่งที่มันทำคือบังคับให้เรียกใช้ฟังก์ชันบนอินสแตนซ์ lvalue หรือ rvalue ของคลาส

import std ;struct A { A () = default ;void display () const & { std :: println ( "lvalue" ); }void display () const && { std :: println ( "rvalue" ); } };int main () { A a ; a . display (); // พิมพ์ "lvalue" std :: move ( a ). display (); // พิมพ์ "rvalue" A (). display (); // พิมพ์ "rvalue" A && b = std :: move ( a ); b . display (); // พิมพ์ "lvalue"(!) }

ความสัมพันธ์กับตัวชี้

การอ้างอิงในภาษา C++ แตกต่างจากตัวชี้ในหลายประเด็นสำคัญ:

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

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

  • int & k ; // คอมไพเลอร์จะแจ้งข้อผิดพลาด: error: `k' ถูกประกาศเป็นแบบอ้างอิงแต่ไม่ได้กำหนดค่าเริ่มต้น

มีการแปลงอย่างง่ายระหว่างพอยเตอร์และรีเฟอร์เมนต์: ตัวดำเนินการ address-of ( &) จะให้พอยเตอร์ที่อ้างอิงถึงวัตถุเดียวกันเมื่อนำไปใช้กับรีเฟอร์เมนต์ และรีเฟอร์เมนต์ที่เริ่มต้นจาก dereference ( *) ของค่าพอยเตอร์จะอ้างอิงถึงวัตถุเดียวกันกับพอยเตอร์นั้น ในกรณีที่สามารถทำได้โดยไม่ก่อให้เกิดพฤติกรรมที่ไม่กำหนดความเท่าเทียมกันนี้สะท้อนให้เห็นถึงการใช้งานทั่วไป ซึ่งโดยพื้นฐานแล้วจะคอมไพล์รีเฟอร์เมนต์ให้เป็นพอยเตอร์ซึ่งจะถูก dereference โดยปริยายทุกครั้งที่ใช้งาน แม้ว่าโดยปกติจะเป็นเช่นนั้น แต่มาตรฐาน C++ ไม่ได้บังคับให้คอมไพเลอร์ใช้งานรีเฟอร์เมนต์โดยใช้พอยเตอร์

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

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

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

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

การใช้แหล่งอ้างอิง

การใช้งานการอ้างอิงที่สะดวกที่สุดคือการอ้างอิงพารามิเตอร์ของฟังก์ชัน การอ้างอิงช่วยให้คุณสามารถอ่านและเปลี่ยนแปลงค่าของอาร์กิวเมนต์ของฟังก์ชัน (พารามิเตอร์จริง) โดยไม่ต้องใช้ตัวดำเนินการ dereference *ทุกครั้งที่ใช้พารามิเตอร์นั้น ตัวอย่างเช่น:

void square ( int x , int & out ) { out = x * x ; }

จากนั้น การโทรต่อไปนี้จะใส่เลข 9 เข้าไปy:

int y ; square ( 3 , y );

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

สี่เหลี่ยมจัตุรัส( 3 , 6 );
  • การส่งคืนค่าอ้างอิง lvalue ช่วยให้สามารถกำหนดการเรียกใช้ฟังก์ชันให้กับ:
int & preInc ( int & x ) { return ++ x ; // "return x++;" จะผิด}ล่วงหน้า( y ) = 5 ; // เช่นเดียวกับ ++y, y = 5
  • ในการใช้งานหลายๆ รูปแบบ กลไกการส่งผ่านพารามิเตอร์แบบปกติมักเกี่ยวข้องกับการคัดลอกข้อมูลที่สิ้นเปลืองสำหรับพารามิเตอร์ขนาดใหญ่ การอ้างอิงที่มีคุณสมบัติเฉพาะ (references) constเป็นวิธีที่มีประโยชน์ในการส่งผ่านอ็อบเจ็กต์ขนาดใหญ่ระหว่างฟังก์ชันโดยหลีกเลี่ยงค่าใช้จ่ายเพิ่มเติมนี้:
    void slow ( BigObject x ) { // ... }void fast ( const BigObject & x ) { // ... }บิ๊กออบเจ็กต์y ;slow ( y ); // ช้า, คัดลอกค่า y ไปยังพารามิเตอร์ x fast ( y ); // เร็ว, ให้สิทธิ์การเข้าถึงแบบอ่านอย่างเดียวโดยตรงต่อค่า y

หากfFastฟังก์ชันต้องการสำเนาของxที่สามารถแก้ไขได้ ฟังก์ชันจะต้องสร้างสำเนาขึ้นมาอย่างชัดเจน แม้ว่าจะสามารถใช้เทคนิคเดียวกันนี้กับพอยเตอร์ได้ แต่จะต้องแก้ไขทุกจุดที่เรียกใช้ฟังก์ชันเพื่อเพิ่มตัว&ดำเนินการ address-of ( ) ที่ยุ่งยากลงในอาร์กิวเมนต์ และจะยากต่อการยกเลิกเช่นกัน หากวัตถุมีขนาดเล็กลงในภายหลัง

พฤติกรรมแบบพหุรูป

เมื่อพิจารณาความสัมพันธ์ระหว่างการอ้างอิงและตัวชี้ (ในบริบทของ C++) การอ้างอิงจะแสดงคุณสมบัติแบบโพลีมอร์ฟิก ดังที่คาดไว้:

import std ;คลาสA { public : A () = default ;virtual void display () { std :: println ( "This is class A" ); } };คลาสB : สาธารณะA { สาธารณะ: B () = ค่าเริ่มต้น;virtual void display () { std :: println ( "This is class B" ); } };int main () { A a ; A & refA = a ;B b ; A & refB = b ;refA.display ( ) ; refB.display ( ) ; }

โค้ดด้านบนเป็นภาษา C++ ที่ถูกต้อง และสร้างผลลัพธ์ดังต่อไปนี้:

นี่คือเกรดเอนี่คือคลาส B
  • เอกสารอ้างอิงใน C++ FAQ Lite
  • คำอธิบายเกี่ยวกับข้อมูลอ้างอิงการส่งต่อ
  • คณะกรรมการเทคนิคร่วม ISO/IEC JTC 1, คณะอนุกรรมการ SC 22, กลุ่มงาน WG 21 มาตรฐานสากล ISO/IEC 14822 (PDF) (ฉบับร่างของคณะกรรมการ){{cite book}}CS1 maint: multiple names: authors list ( link ) CS1 maint: numeric names: authors list ( link )
ดึงข้อมูลมาจาก " https://en.wikipedia.org/w/index.php?title=Reference_(C%2B%2B)&oldid=1357169829 "

สรุปเนื้อหา

ข้อมูลสำคัญจากบทความ

ข้อมูลสำคัญเกี่ยวกับ เอกสารอ้างอิง (C++)

ในภาษาโปรแกรมC++ reference คือ ชนิดข้อมูล อ้างอิงแบบ ง่ายๆ ที่มีประสิทธิภาพน้อยกว่าแต่ปลอดภัยกว่า ชนิด ข้อมูลพอยเตอร์ที่สืบทอดมาจากภาษา Cชื่อ" C++...

ความหมายและข้อจำกัด

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

ความสัมพันธ์กับตัวชี้

การอ้างอิงในภาษา C++ แตกต่างจากตัวชี้ในหลายประเด็นสำคัญ:

การใช้แหล่งอ้างอิง

การใช้งานการอ้างอิงที่สะดวกที่สุดคือการอ้างอิงพารามิเตอร์ของฟังก์ชัน การอ้างอิงช่วยให้คุณสามารถอ่านและเปลี่ยนแปลงค่าของอาร์กิวเมนต์ของฟังก์ชัน (พารามิเตอร์จริง) โดยไม่ต้องใช้ตัวดำเนินการ dereference * ทุกครั้งที่ใช้พารามิเตอร์นั้น ตัวอย่างเช่น: