พอยน์เตอร์พอยน์คืออะไร?


149

ฉันไม่ได้พูดถึงพอยน์เตอร์ให้เป็นค่าคงที่ แต่เป็นพอยต์พอยต์ของตัวเอง

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

ดังนั้นประเด็นของการมีส่วนหัวของฟังก์ชั่นที่บอกว่า:

void foo(int* const ptr);

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

void foo(int* ptr);

ทำงานได้ดีเช่นกัน! เนื่องจากตัวชี้ถูกคัดลอกแล้วและตัวชี้ในผู้โทรจะไม่ได้รับผลกระทบแม้ว่าคุณจะแก้ไขการคัดลอก ดังนั้นข้อดีของ const คืออะไร


29
จะเป็นอย่างไรถ้าคุณต้องการรับประกันในเวลารวบรวมว่าตัวชี้ไม่สามารถและไม่ควรปรับเปลี่ยนให้ชี้ไปที่สิ่งอื่นใด
Platinum Azure

25
ตรงจุดเดียวกันเป็นconstพารามิเตอร์ใด ๆ
David Heffernan

2
@ PlatinumAzure ฉันมีประสบการณ์การเขียนโปรแกรมไม่กี่ปี แต่การปฏิบัติซอฟต์แวร์ของฉันยังไม่เพียงพอ ฉันดีใจที่ฉันทำคำถามนี้เพราะฉันไม่เคยรู้สึกว่าต้องการให้คอมไพเลอร์ชี้ให้เห็นข้อผิดพลาดเชิงตรรกะของฉันเอง ปฏิกิริยาเริ่มต้นของฉันในขณะที่ฉันส่งคำถามคือ "ทำไมฉันถึงต้องสนใจถ้าตัวชี้ถูกแก้ไข? มันจะไม่ส่งผลกระทบต่อผู้โทร" จนกว่าฉันจะอ่านคำตอบทั้งหมดที่ฉันเข้าใจว่าฉันควรจะใส่ใจเพราะถ้าฉันพยายามที่จะแก้ไขมันฉันและตรรกะการเข้ารหัสของฉันผิด บอกฉันที
R. Ruiz

3
@ R.Ruiz แม้แต่ผู้ที่มีประสบการณ์มากที่สุดของเราก็สามารถรับประกันได้ว่ามีความถูกต้องเป็นพิเศษ เนื่องจากวิศวกรรมซอฟต์แวร์เป็นรูปแบบของวิศวกรรมที่อาจมีระยะทางไกลกว่าเล็กน้อย (HTTP 502s แบบสุ่มการเชื่อมต่อที่ช้าภาพที่ไม่สามารถโหลดได้ในบางครั้งอาจไม่ใช่โอกาสพิเศษ แต่เครื่องยนต์ที่ล้มเหลวในเครื่องบินไม่สามารถยอมรับได้และรุนแรง) โปรแกรมคนที่มีความรีบเร่งเกินควร อาร์กิวเมนต์เดียวกันที่จัดให้มีการทดสอบหน่วยการเขียนจะอธิบายการใช้const-correctness ค้ำประกัน เพียงแค่ทำให้เรารู้สึกมั่นใจว่ารหัสของเราถูกต้องอย่างแน่นอน
Platinum Azure

3
คำถามที่เกี่ยวข้อง: stackoverflow.com/questions/219914/…
Raedwald

คำตอบ:


207

const เป็นเครื่องมือที่คุณควรใช้ในการติดตามแนวคิด C ++ ที่สำคัญมาก:

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

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

void foo(int* ptr)
{
    ptr = 0;// oops, I meant *ptr = 0
}

ถ้าคุณใช้ int* constptrนี้จะสร้างข้อผิดพลาดคอมไพเลอร์เพราะคุณกำลังจะเปลี่ยนค่าเป็น การเพิ่มข้อ จำกัด ผ่านทางไวยากรณ์เป็นสิ่งที่ดีโดยทั่วไป เพียงแค่ไม่ได้ใช้มันไกลเกินไป - constตัวอย่างที่คุณให้เป็นกรณีที่คนส่วนใหญ่ไม่รำคาญใช้


8
ขอบคุณนี่เป็นคำตอบที่ทำให้ฉันมั่นใจ คุณใส่ const เพื่อให้คอมไพเลอร์เตือนคุณเกี่ยวกับข้อผิดพลาดที่ได้รับมอบหมายของคุณเอง ตัวอย่างของคุณสมบูรณ์แบบในการแสดงแนวคิดนี้เพราะนั่นเป็นข้อผิดพลาดทั่วไปของพอยน์เตอร์ กว่าที่คุณ!
R. Ruiz

25
"ช่วยคอมไพเลอร์ช่วยคุณ" เป็นมนต์ฉันมักจะสวดมนต์สำหรับเรื่องนี้
เฟล็กโซ

1
+1 แต่นี่เป็นกรณีที่น่าสนใจที่สามารถโต้แย้งได้: stackoverflow.com/questions/6305906/ …
Raedwald

3
ดังนั้นจึงเป็นคำตอบเดียวกับที่คุณให้กับ "ทำไมทำให้ตัวแปรเป็นแบบส่วนตัวเมื่อคุณไม่สามารถใช้มันนอกคลาสได้"
Lee Louviere

นี่อาจเป็นหัวข้อปิดเล็กน้อย แต่ตัวชี้ const นี้อยู่ในหน่วยความจำที่ไหน ฉันรู้ว่า const ทั้งหมดนั่งอยู่ในหน่วยความจำทั่วโลก แต่ฉันไม่แน่ใจ 100% เกี่ยวกับ const ที่คุณถูกส่งผ่านเป็น func var ขอบคุณและขออภัยหากปิดหัวข้อ
Tomer

77

ฉันใช้เฉพาะจุดเท่านั้น constข้อโต้แย้งเพราะสิ่งนี้ช่วยให้ตรวจสอบคอมไพเลอร์เพิ่มเติม: ถ้าฉันตั้งใจกำหนดค่าอาร์กิวเมนต์ภายในฟังก์ชั่นอีกครั้งคอมไพเลอร์กัดฉัน

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

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

ตัวอย่าง:

// foo.h
int frob(int x);
// foo.cpp
int frob(int const x) {
   MyConfigType const config = get_the_config();
   return x * config.scaling;
}

ขอให้สังเกตว่าทั้งอาร์กิวเมนต์และตัวแปรโลคัลเป็นconstอย่างไร ไม่จำเป็นแต่ด้วยฟังก์ชั่นที่มีขนาดใหญ่กว่าเล็กน้อยสิ่งนี้ช่วยฉันจากการทำผิดพลาดซ้ำ ๆ


17
+1จากconstคนที่คลั่งไคล้คนอื่น อย่างไรก็ตามฉันชอบคอมไพเลอร์ของฉันที่จะเห่าใส่ฉัน ฉันทำผิดพลาดมากเกินไปและพวกเขาจะกัดไม่ดี
sbi

2
+1 ฉันขอแนะนำconstกลยุทธ์" หากไม่มีเหตุผลที่ดี" มีข้อยกเว้นที่ดีบางประการเช่นคัดลอกและสลับ
Flexo

2
ถ้าฉันออกแบบภาษาใหม่วัตถุที่ประกาศจะเป็นแบบอ่านอย่างเดียว ("const") โดยค่าเริ่มต้น คุณต้องการไวยากรณ์พิเศษบางอย่างซึ่งอาจเป็นคำหลัก "var" เพื่อทำให้วัตถุสามารถเขียนได้
Keith Thompson

@ Keith ฉันอยากจะพูดว่า "แน่นอน" ทุกสิ่งทุกอย่างโง่เขลา ณ จุดนี้ แต่น่าเสียดายที่นักออกแบบภาษาส่วนใหญ่ดูเหมือนจะไม่เห็นด้วยกับเรา…ที่จริงฉันโพสต์ไปแล้วมาก (ในคำถามที่ถูกลบไปแล้วในตอนนี้
Konrad Rudolph

1
@ KubaOber ฉันไม่เห็นว่ามันเป็นแง่ร้ายเลย หากภายหลังคุณพบว่าคุณต้องแก้ไขสิ่งที่ส่งผ่านภายในเนื้อความของฟังก์ชั่นจากนั้นเพียงแค่วางconstจากการโต้แย้งและแสดงความคิดเห็นว่าทำไม งานพิเศษเล็ก ๆ น้อย ๆ นั้นไม่เพียงแสดงให้เห็นว่าการทำเครื่องหมายข้อโต้แย้งทั้งหมดเป็นสิ่งที่ไม่ใช่constโดยค่าเริ่มต้นและเปิดตัวคุณเองถึงข้อผิดพลาดที่อาจเกิดขึ้น
underscore_d

20

คำถามของคุณสัมผัสกับบางสิ่งที่กว้างกว่านี้: อาร์กิวเมนต์ของฟังก์ชันควรเป็น const หรือไม่

ค่าคงที่ของอาร์กิวเมนต์ค่า (เช่นตัวชี้ของคุณ) เป็นรายละเอียดการนำไปใช้และไม่ได้เป็นส่วนหนึ่งของการประกาศฟังก์ชัน ซึ่งหมายความว่าฟังก์ชั่นของคุณเป็นแบบนี้เสมอ:

void foo(T);

มันขึ้นอยู่กับผู้ใช้งานของฟังก์ชั่นไม่ว่าเธอต้องการใช้ตัวแปรอาร์กิวเมนต์ฟังก์ชั่นขอบเขตในการเปลี่ยนแปลงหรือในทางคงที่:

// implementation 1
void foo(T const x)
{
  // I won't touch x
  T y = x;
  // ...
}

// implementation 2
void foo(T x)
{
  // l33t coding skillz
  while (*x-- = zap()) { /* ... */ }
}

ดังนั้นทำตามกฎง่ายๆที่จะไม่ใส่constในการประกาศ (ส่วนหัว) และวางไว้ในคำนิยาม (การใช้งาน) หากคุณไม่ต้องการหรือต้องการแก้ไขตัวแปร


5
ฉันเห็นด้วยกับเรื่องนี้ - แต่ฉันค่อนข้างอึดอัดกับความคิดที่ทำให้การประกาศและคำจำกัดความแตกต่างกัน สำหรับสิ่งอื่น ๆ นอกจากconstนี้การประกาศและ (ส่วนต้นแบบของ) คำจำกัดความโดยทั่วไปจะเหมือนกัน
Keith Thompson

@ KeithThompson: ดีคุณไม่ต้องทำถ้าคุณไม่ต้องการ อย่าเพิ่งconstประกาศ มันขึ้นอยู่กับคุณทั้งหมดหากคุณต้องการเพิ่มคุณสมบัติในการใช้งาน
Kerrek SB

1
อย่างที่ฉันพูดฉันเห็นด้วยว่าการconstประกาศ แต่ไม่ใช่คำจำกัดความที่สมเหตุสมผล มันรู้สึกเหมือนผิดพลาดในภาษาที่เป็นกรณีเดียวที่ทำให้การประกาศและคำนิยามที่ไม่เหมือนกันทำให้รู้สึก (มันเป็นความผิดพลาดเพียงอย่างเดียวของ C)
Keith Thompson

16

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


ฉันไม่รู้ ดังนั้นถ้าฉันพยายามโอเวอร์โหลดโมฆะ foo (int * const ptr) กับโมฆะ foo (int * t ptr) ฉันจะได้รับข้อผิดพลาดของคอมไพเลอร์ ขอบคุณ!
R. Ruiz

@ R.Ruiz หากการชะลอตัวของคุณเป็นโมฆะ foo (int * t ptr) และคำจำกัดความของคุณเป็นโมฆะ foo (int * const ptr) คุณจะไม่ได้รับข้อผิดพลาดของคอมไพเลอร์
เทรเวอร์ Hickey

1
@ TrevorHickey พวกเขาจะ ... แต่ไม่ใช่ด้วยเหตุผลที่พวกเขาคิด int* t ptrเป็นข้อผิดพลาดทางไวยากรณ์ หากไม่มีสิ่งนั้นทั้งสองจะเหมือนกันสำหรับจุดประสงค์มาก
underscore_d

14

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

โดยพื้นฐานแล้วมันเหมือนกับการมีint const the_answer = 42โปรแกรมของคุณ


1
มันมีความสำคัญอย่างไรกับจุดที่ชี้ไปมันคือตัวชี้ที่จัดสรรไว้ใน stack? ฟังก์ชั่นไม่สามารถทำให้ยุ่ง มันสมเหตุสมผลมากกว่าที่จะใช้พอยน์เตอร์แบบอ่านอย่างเดียวเมื่อจัดการกับพอยน์เตอร์ถึงพอยน์เตอร์ มันไม่ใช่สิ่งเดียวกันกับ int const! ฉันจะลบล้างสิ่งนี้เพียงเพื่อข้อความที่ทำให้เข้าใจผิด const intและint constเทียบเท่าในขณะที่const int*และint* constมีสองความหมายที่แตกต่างกัน!
Lundin

1
@lundin ลองสมมติว่าฟังก์ชั่นใหญ่และมีสิ่งอื่น ๆ อยู่ในนั้น โดยบังเอิญอาจทำให้ชี้ไปที่สิ่งอื่น (บนสแต็กของฟังก์ชั่น) นี่ไม่ใช่ปัญหาสำหรับผู้โทร แต่แน่นอนสำหรับผู้โทร ฉันไม่เห็นสิ่งใดที่ทำให้เข้าใจผิดในคำพูดของฉัน: " สำหรับผู้เขียนฟังก์ชั่นนี้มันอาจเป็นตาข่ายนิรภัย"
cnicutar

1
@Lundin เกี่ยวกับint constส่วนหนึ่ง; ฉันใส่ชนิดก่อนหน้า const (มันฟังดูไม่เป็นธรรมชาติ) สักพักและฉันก็ตระหนักถึงความแตกต่าง บทความนี้อาจพิสูจน์ว่ามีประโยชน์ ตัวฉันเองมีเหตุผลที่แตกต่างกันเล็กน้อยในการเปลี่ยนมาใช้สไตล์นี้
cnicutar

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

Lundin ฉันอ่านด้วย! ฉันเห็นด้วย const int เป็นสไตล์ที่ดีกว่า @cnicutar คำตอบแรกของคุณที่มีต่อ Lundin คือสิ่งที่ฉันกำลังมองหาเมื่อฉันส่งคำถามนี้ ดังนั้นปัญหาไม่ได้อยู่ในผู้โทร (ตามที่ฉันคิดอย่างไร้เหตุผล) แต่ const เป็นหลักประกันสำหรับผู้โทร ฉันชอบความคิดนี้ขอบคุณ
R. Ruiz

14

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

คำหลัก const เป็นที่เรียกว่า "ประเภทรอบคัดเลือก" คนอื่นและvolatile restrictอย่างน้อยผันผวนตามกฎ (สับสน) เช่นเดียวกับ const


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

แต่ยังมีวัตถุประสงค์อื่นในระบบใด ๆ ที่มีหน่วยความจำแบบอ่านอย่างเดียวคือเพื่อให้แน่ใจว่าตัวแปรบางตัวได้รับการจัดสรรภายในหน่วยความจำเช่น - มันอาจเป็น EEPROM หรือแฟลชเป็นต้น สิ่งเหล่านี้เรียกว่า NVM ความทรงจำที่ไม่ลบเลือน ตัวแปรที่จัดสรรใน NVM จะยังคงปฏิบัติตามกฎทั้งหมดของตัวแปร const

มีหลายวิธีในการใช้constคำหลัก:

ประกาศตัวแปรคงที่

ซึ่งสามารถทำได้ทั้งแบบ

const int X=1; or
int const X=1;

ทั้งสองรูปแบบมีความเทียบเท่าสมบูรณ์ สไตล์หลังถือว่าเป็นรูปแบบที่ไม่ดีและไม่ควรใช้

เหตุผลที่แถวที่สองถือเป็นรูปแบบที่ไม่ดีอาจเป็นเพราะ "ตัวระบุระดับการจัดเก็บ" เช่นแบบคงที่และภายนอกยังสามารถประกาศได้หลังจากชนิดจริงint staticฯลฯ แต่การทำเช่นนั้นสำหรับตัวระบุระดับชั้นเก็บข้อมูลจะถูกระบุว่าเป็นคุณลักษณะที่ล้าสมัย โดยคณะกรรมการ C (ร่าง ISO 9899 N1539, 6.11.5) ดังนั้นเพื่อความมั่นคงเราไม่ควรเขียนตัวระบุประเภทในลักษณะนั้นเช่นกัน มันไม่มีจุดประสงค์อื่นนอกจากสร้างความสับสนให้ผู้อ่าน แต่อย่างใด

ประกาศตัวชี้ไปยังตัวแปรคงที่

const int* ptr = &X;

ซึ่งหมายความว่าเนื้อหาของ 'X' ไม่สามารถแก้ไขได้ นี่เป็นวิธีปกติที่คุณประกาศตัวชี้แบบนี้ส่วนใหญ่เป็นส่วนหนึ่งของพารามิเตอร์ฟังก์ชันสำหรับ "const correctness" เนื่องจาก 'X' ไม่จำเป็นต้องถูกประกาศเป็น const จึงอาจเป็นตัวแปรใด ๆ ก็ได้ กล่าวอีกนัยหนึ่งคุณสามารถ "อัปเกรด" ตัวแปรเป็น const ในทางเทคนิค C ยังอนุญาตให้มีการลดระดับจาก const เป็นตัวแปรธรรมดาโดย typecasts ที่ชัดเจน แต่การทำเช่นนี้ถือว่าเป็นการเขียนโปรแกรมที่ไม่ดีและคอมไพเลอร์มักจะให้คำเตือนกับมัน

ประกาศตัวชี้คงที่

int* const ptr = &X;

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

void func (int*const* ptrptr)

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

ตัวชี้คงที่สามารถใช้เพื่อให้แน่ใจว่าตัวแปรตัวชี้ถูกประกาศในหน่วยความจำแบบอ่านอย่างเดียวตัวอย่างเช่นคุณอาจต้องการประกาศตารางการค้นหาโดยใช้ตัวชี้บางประเภทและจัดสรรใน NVM

และแน่นอนตามที่ระบุโดยคำตอบอื่น ๆ ตัวชี้คงที่ยังสามารถใช้เพื่อบังคับใช้ "ความถูกต้อง const"

ประกาศตัวชี้ค่าคงที่กับข้อมูลคงที่

const int* const ptr=&X;

นี่เป็นประเภทพอยน์เตอร์สองแบบที่อธิบายไว้ข้างต้นรวมกันพร้อมกับคุณลักษณะทั้งหมดของทั้งคู่

ประกาศฟังก์ชันสมาชิกแบบอ่านอย่างเดียว (C ++)

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

void MyClass::func (void) const;

8

... วันนี้ฉันรู้ว่าพอยน์เตอร์ถูกส่งผ่านไปยังฟังก์ชั่นตามตัวอักษรซึ่งสมเหตุสมผล

(imo) มันไม่สมเหตุสมผลเลยที่จะเป็นค่าเริ่มต้น ค่าเริ่มต้นที่สมเหตุสมผลมากขึ้นคือการส่งผ่านเป็นตัวชี้ที่ไม่สามารถกำหนดใหม่ได้ ( int* const arg) นั่นคือฉันจะต้องให้พอยน์เตอร์ที่ส่งผ่านเป็นอาร์กิวเมนต์มีการประกาศโดยนัย const

ดังนั้นข้อดีของ const คืออะไร

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

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

void func(int* const arg) {
    int* a(arg);
    ...
    *a++ = value;
}

เพิ่มท้องถิ่นนั้นฟรีจริงและลดโอกาสสำหรับข้อผิดพลาดในขณะที่การปรับปรุงการอ่าน

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

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


4
+1 สำหรับสิ่งนี้คือค่าเริ่มต้น C ++ เหมือนกับภาษาส่วนใหญ่มีวิธีที่ผิด แทนที่จะมีconstคำหลักควรมีmutableคำหลัก (แต่ก็มี แต่มีความหมายที่ผิด)
Konrad Rudolph

2
แนวคิดที่น่าสนใจที่มี const เป็นค่าเริ่มต้น ขอบคุณสำหรับคำตอบ!
R. Ruiz

6

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

การลงทะเบียนชิปต่อพ่วงแบบ 16 บิตแบบอ่านอย่างเดียวอาจมีลักษณะดังนี้:

static const unsigned short *const peripheral = (unsigned short *)0xfe0000UL;

จากนั้นคุณสามารถอ่านการลงทะเบียนฮาร์ดแวร์ได้โดยไม่ต้องใช้ภาษาแอสเซมบลี:

input_word = *peripheral;


5

int iVal = 10; int * const ipPtr = & iVal;

เช่นเดียวกับตัวแปร const ปกติตัวชี้ const จะต้องเริ่มต้นเป็นค่าเมื่อมีการประกาศและค่าของมันไม่สามารถเปลี่ยนแปลงได้

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

* ipPtr = 6; // ได้รับอนุญาตเนื่องจาก pnPtr ชี้ไปที่ int ไม่ใช่ค่าคงที่


5

คำถามเดียวกันสามารถถามเกี่ยวกับประเภทอื่น ๆ (ไม่เพียงตัวชี้):

/* Why is n const? */
const char *expand(const int n) {
    if (n == 1) return "one";
    if (n == 2) return "two";
    if (n == 3) return "three";
    return "many";
}

ฮ่า ๆ คุณพูดถูก กรณีของพอยน์เตอร์มาถึงใจของฉันก่อนเพราะฉันคิดว่ามันพิเศษ แต่ก็เหมือนกับตัวแปรอื่น ๆ ที่ถูกส่งผ่านตามค่า
R. Ruiz

5

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

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


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

4

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

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


4

ฉันเดาว่าข้อดีคือคอมไพเลอร์สามารถดำเนินการเพิ่มประสิทธิภาพที่ก้าวร้าวมากขึ้นภายในฟังก์ชันโดยที่ตัวชี้นี้ไม่สามารถเปลี่ยนแปลงได้

นอกจากนี้ยังหลีกเลี่ยงเช่น ผ่านตัวชี้นี้ไปยังฟังก์ชั่นย่อยซึ่งยอมรับการอ้างอิงตัวชี้ที่ไม่ใช่ const (และสามารถเปลี่ยนตัวชี้เช่นvoid f(int *&p)) ได้ แต่ฉันเห็นด้วยว่ามีประโยชน์ค่อนข้าง จำกัด ในกรณีนี้


+1 สำหรับการปรับให้เหมาะสม อย่างน้อยหนังสือก็บอกว่าจริง สิ่งที่ฉันต้องการคือการเข้าใจว่าการเพิ่มประสิทธิภาพเหล่านั้นคืออะไร
R. Ruiz

4

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

#include <new>
#include <string.h>

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const GetArray(){ return Array; }
};

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' '; //You can still modify the chars in the array, user has access
    Temp.GetArray()[1] = ' '; 
    printf("%s\n",Temp.GetArray());
}

ซึ่งผลิต:

ใส่ข้อมูล
ใส่ข้อมูล

แต่ถ้าเราลองทำสิ่งนี้:

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' ';
    Temp.GetArray()[1] = ' ';
    printf("%s\n",Temp.GetArray());
    Temp.GetArray() = NULL; //Bwuahahahaa attempt to set it to null
}

เราได้รับ:

ข้อผิดพลาด: lvalue จำเป็นต้องเป็น operand ด้านซ้ายของการมอบหมาย // Drat แพ้รู้อีกครั้ง!

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

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' ';
    Temp.GetArray()[1] = ' ';
    printf("%s\n",Temp.GetArray());
    delete [] Temp.GetArray(); //Bwuahaha this actually works!
}

เรายังสามารถลบการอ้างอิงหน่วยความจำของตัวชี้แม้ว่าเราจะไม่สามารถปรับเปลี่ยนตัวชี้ได้

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

แก้ไข:

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

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const &GetArray(){ return Array; } //Note & reference operator
        char * &GetNonConstArray(){ return Array; } //Note non-const
};

int main()
{
    TestA Temp;
    Temp.GetArray() = NULL; //Returns error
    Temp.GetNonConstArray() = NULL; //Returns no error
}

จะกลับมาเป็นครั้งแรกที่ทำให้เกิดข้อผิดพลาด:

ข้อผิดพลาด: การกำหนดตำแหน่งแบบอ่านอย่างเดียว 'Temp.TestA :: GetArray ()'

แต่ครั้งที่สองจะเกิดขึ้นอย่างสนุกสนานแม้จะมีผลกระทบที่อาจเกิดขึ้นภายใต้

เห็นได้ชัดว่าคำถามจะถูกยก 'ทำไมคุณต้องการกลับไปอ้างอิงถึงตัวชี้' มีอินสแตนซ์ที่หายากซึ่งคุณจำเป็นต้องกำหนดหน่วยความจำ (หรือข้อมูล) ให้กับตัวชี้ดั้งเดิมที่เป็นปัญหาโดยตรง (ตัวอย่างเช่นการสร้าง malloc / free หรือ new / free front-end) ของคุณเอง แต่ในกรณีเหล่านั้นเป็นการอ้างอิงที่ไม่ใช่แบบ const . การอ้างอิงถึงตัวชี้ const ฉันไม่เจอสถานการณ์ที่จะรับประกันได้ (เว้นแต่ว่าอาจจะเป็นตัวแปรอ้างอิง const ที่ประกาศไว้แทนที่จะเป็นประเภทการส่งคืนหรือไม่)

พิจารณาว่าเรามีฟังก์ชั่นที่ใช้ตัวชี้ const (หรืออันที่ไม่ใช่):

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const &GetArray(){ return Array; }

        void ModifyArrayConst(char * const Data)
        {
            Data[1]; //This is okay, this refers to Data[1]
            Data--; //Produces an error. Don't want to Decrement that.
            printf("Const: %c\n",Data[1]);
        }

        void ModifyArrayNonConst(char * Data)
        {
            Data--; //Argh noo what are you doing?!
            Data[1]; //This is actually the same as 'Data[0]' because it's relative to Data's position
            printf("NonConst: %c\n",Data[1]);
        }
};

int main()
{
    TestA Temp;
    Temp.ModifyArrayNonConst("ABCD");
    Temp.ModifyArrayConst("ABCD");
}

ข้อผิดพลาดใน const สร้างข้อความดังนั้น:

ข้อผิดพลาด: การลดลงของพารามิเตอร์แบบอ่านอย่างเดียว 'ข้อมูล'

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

NonConst: A
Const: B

เห็นได้ชัดแม้ว่า A คือ 'Data [1]' มันจะถูกใช้เป็น 'Data [0]' เพราะตัวชี้ NonConst อนุญาตการดำเนินการลดลง เมื่อ const ดำเนินการตามที่คนอื่นเขียนเราจับข้อบกพร่องที่อาจเกิดขึ้นก่อนที่มันจะเกิดขึ้น

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

int main()
{
    int A = 10;
    int * const B = &A;
    *B = 20; //This is permitted
    printf("%d\n",A);
    B = NULL; //This produces an error
}

เมื่อพยายามรวบรวมให้สร้างข้อผิดพลาดต่อไปนี้:

ข้อผิดพลาด: การกำหนดตัวแปรแบบอ่านอย่างเดียว 'B'

ซึ่งน่าจะเป็นสิ่งที่ไม่ดีหากต้องการอ้างอิงอย่างต่อเนื่องกับ A ถ้าB = NULLถูกคอมเม้นท์คอมไพเลอร์จะให้เราแก้ไขอย่างมีความสุข*Bและดังนั้น A. สิ่งนี้อาจดูเหมือนไม่เป็นประโยชน์กับ ints แต่ให้พิจารณาว่าคุณมีจุดยืนเดียวของแอปพลิเคชั่นกราฟิกที่คุณต้องการตัวชี้ที่ไม่สามารถแก้ไขได้ รอบ

มันใช้เป็นตัวแปร (แก้ตัวโดยไม่ตั้งใจปุน) แต่ใช้อย่างถูกต้องมันเป็นเครื่องมืออื่นในกล่องเพื่อช่วยในการเขียนโปรแกรม


2
Temp.GetArray() = NULLล้มเหลวเนื่องจากTemp.GetArray()เป็นค่า rvalue ไม่ใช่เพราะเป็นค่าที่ผ่านการรับรอง นอกจากนี้ฉันเชื่อว่า const-qualifier นั้นถูกดึงออกจากประเภทการส่งคืนทั้งหมด
Oscar Korz

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

3

ไม่มีอะไรพิเศษเกี่ยวกับพอยน์เตอร์ที่คุณจะไม่ต้องการให้มันเป็น เช่นเดียวกับที่คุณมีintค่าคงที่ของสมาชิกคลาสคุณสามารถมีตัวชี้คงที่ด้วยเหตุผลที่คล้ายกัน: คุณต้องการตรวจสอบให้แน่ใจว่าไม่มีใครเปลี่ยนแปลงสิ่งที่ชี้ไป การอ้างอิง C ++ ค่อนข้างที่อยู่นี้ แต่พฤติกรรมของตัวชี้ที่สืบทอดมาจาก C.



3

ประเภทของการประกาศตัวแปรใด ๆ เช่น -
(1) การประกาศตัวแปรคงที่
DataType const varibleName;

 int const x;
    x=4; //you can assign its value only One time
(2) ประกาศตัวชี้ไปยังตัวแปรคงที่
const dataType* PointerVaribleName=&X;
 const int* ptr = &X;
     //Here pointer variable refer contents of 'X' that is const Such that its cannot be modified
dataType* const PointerVaribleName=&X;
 int* const ptr = &X;
     //Here pointer variable itself is constant  Such that value of 'X'  can be modified But pointer can't be modified


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