คำอธิบายตัวชี้ C บนนิ้วมือ มันหมายความว่าอะไรใน C: ตัวชี้คืออะไร ตัวชี้เป็นอาร์กิวเมนต์ของฟังก์ชัน

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

พอยน์เตอร์คืออะไรและทำไมจึงจำเป็น?

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

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

ไวยากรณ์ของตัวชี้

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

Data_type *ตัวชี้ชื่อ;

โดยที่ data_type เป็นชนิดข้อมูล pointerName คือชื่อของพอยน์เตอร์

ตัวอย่างเช่น เรามาประกาศตัวชี้ที่เก็บที่อยู่ของเซลล์หน่วยความจำที่มีจำนวนเต็ม:

Int *ตัวชี้จำนวนเต็ม;

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

// การประกาศตัวชี้และตัวแปรอย่างง่ายในหนึ่งบรรทัด int *pointer1, // นี่คือตัวแปรตัวชี้; // นี่คือตัวแปรปกติประเภท int // การประกาศสองพอยน์เตอร์ในหนึ่งบรรทัด int *pointer1, // นี่คือพอยน์เตอร์ชื่อ pointer1 *pointer2; // นี่คือพอยน์เตอร์ชื่อ pointer2

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

มีสองวิธีในการใช้ตัวชี้:

  1. ใช้ชื่อตัวชี้โดยไม่มีสัญลักษณ์ * ด้วยวิธีนี้คุณจะได้รับที่อยู่ที่แท้จริงของตำแหน่งหน่วยความจำที่ตัวชี้อ้างอิง
  2. ใช้ชื่อพอยน์เตอร์ที่มีสัญลักษณ์ * เพื่อรับค่าที่จัดเก็บไว้ในหน่วยความจำ ในบริบทของพอยน์เตอร์ สัญลักษณ์ * มีชื่อทางเทคนิค: การดำเนินการ dereference โดยพื้นฐานแล้ว เรากำลังอ้างอิงไปยังที่อยู่หน่วยความจำบางส่วนเพื่อรับค่าที่แท้จริง นี่อาจเป็นเรื่องยากที่จะเข้าใจ แต่ในอนาคตเราจะพยายามเข้าใจทั้งหมดนี้

ประกาศตัวชี้การรับที่อยู่ของตัวแปร

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

Int var = 5; // การประกาศตัวแปรอย่างง่ายด้วยการเริ่มต้นเบื้องต้น int *ptrVar; // ประกาศพอยน์เตอร์ แต่ยังไม่ได้ชี้ไปที่สิ่งใดเลย ptrVar = // ตอนนี้พอยน์เตอร์ของเราอ้างถึงที่อยู่ในหน่วยความจำที่เก็บหมายเลข 5 ไว้

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

#รวม int main() ( int var; // ตัวแปรจำนวนเต็มปกติ int *ptrVar; // ตัวชี้จำนวนเต็ม (ptrVar ต้องเป็นประเภท int เนื่องจากจะอ้างถึงตัวแปรประเภท int) ptrVar = // กำหนดที่อยู่ของตัวชี้ ตำแหน่งหน่วยความจำโดยที่ค่าของตัวแปร var scanf("%d", &var); // ตัวแปร var มีค่าที่ป้อนจากแป้นพิมพ์ printf("%d\n", *ptrVar); // ส่งออก ค่าผ่านตัวชี้ getchar(); )

ผลลัพธ์ของโปรแกรม:

ใน บรรทัดที่ 10, printf() พิมพ์ค่าที่เก็บไว้ในตัวแปร var ทำไมสิ่งนี้ถึงเกิดขึ้น? เอาล่ะ มาดูโค้ดกันดีกว่า ใน บรรทัดที่ 5เราประกาศตัวแปรประเภท int ใน บรรทัดที่ 6— ตัวชี้ ptrVar เป็นค่าจำนวนเต็ม จากนั้นตัวชี้ ptrVar ได้รับการกำหนดที่อยู่ของตัวแปร var ในกรณีนี้เราใช้ตัวดำเนินการกำหนดที่อยู่ จากนั้นผู้ใช้ป้อนตัวเลขที่จัดเก็บไว้ในตัวแปร var โปรดจำไว้ว่านี่คือตำแหน่งเดียวกับที่ ptrVar ชี้ไป เนื่องจากเราใช้เครื่องหมายแอมเปอร์แซนด์ในการกำหนดค่าให้กับตัวแปร var ในฟังก์ชัน scanf() จึงควรชัดเจนว่า scanf() เริ่มต้นตัวแปร var ผ่านทางที่อยู่ ตัวชี้ ptrVar ชี้ไปยังที่อยู่เดียวกัน

แล้วเข้า. บรรทัดที่ 10การดำเนินการ "ลดการอ้างอิง" จะดำเนินการ - *ptrVar โปรแกรมจะอ่านที่อยู่ที่ถูกจัดเก็บไว้ในพอยน์เตอร์ผ่านตัวชี้ ptrVar ไปยังเซลล์หน่วยความจำที่ต้องการตามที่อยู่ และส่งกลับค่าที่เก็บไว้ที่นั่น

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

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

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

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

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

คำถาม

ฉันสังเกตเห็นว่าบ่อยครั้งที่โปรแกรมเมอร์ที่ฉันเห็นโค้ดใช้ตัวชี้ไปยังวัตถุบ่อยกว่าวัตถุเหล่านี้เอง เช่น พวกเขาใช้โครงสร้างต่อไปนี้:

วัตถุ *myObject = วัตถุใหม่;

วัตถุ myObject;

เช่นเดียวกับวิธีการ ทำไมแทน:

MyObject.testFunc();

เราควรเขียนสิ่งนี้:

MyObject->testFunc();

ตามที่ผมเข้าใจ มันทำให้ความเร็วเพิ่มขึ้น เพราะ... เราเข้าถึงหน่วยความจำโดยตรง ขวา? ป.ล. ฉันเปลี่ยนจากจาวา

คำตอบ

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

เพื่อให้ทราบว่าพอยน์เตอร์คืออะไรใน C ++ ต่อไปนี้เป็นโค้ดสองส่วนที่คล้ายกัน:

วัตถุ object1 = วัตถุใหม่ (); // วัตถุใหม่ Object object2 = วัตถุใหม่ (); // วัตถุใหม่อีกวัตถุ object1 = object2; // ตัวแปรทั้งสองอ้างถึงวัตถุที่ถูกอ้างอิงก่อนหน้านี้โดย object2 // หากวัตถุที่อ้างอิงโดย object1 เปลี่ยนแปลง // object2 ก็จะเปลี่ยนไปเช่นกันเนื่องจากเป็นวัตถุเดียวกัน

สิ่งที่ใกล้เคียงที่สุดใน C ++ คือ:

วัตถุ * object1 = วัตถุใหม่ (); // หน่วยความจำถูกจัดสรรสำหรับวัตถุใหม่ // หน่วยความจำนี้ถูกอ้างอิงโดย object1 Object * object2 = new Object(); // เช่นเดียวกับวัตถุที่สองลบ object1; // C++ ไม่มีระบบรวบรวมขยะ ดังนั้นหากยังไม่เสร็จสิ้น // โปรแกรมจะไม่สามารถเข้าถึงหน่วยความจำนี้ได้อีกต่อไป // อย่างน้อยก็จนกว่าโปรแกรมจะรีสตาร์ท // สิ่งนี้เรียกว่าหน่วยความจำรั่ววัตถุ 1 = วัตถุ2; // เช่นเดียวกับใน Java object1 ชี้ไปยังตำแหน่งเดียวกันกับ object2

อย่างไรก็ตาม นี่เป็นสิ่งที่แตกต่างไปจากเดิมอย่างสิ้นเชิง (C++):

วัตถุวัตถุ1; // วัตถุใหม่ วัตถุ object2; // อีก object1 = object2; // การคัดลอก object2 ไปยัง object1 โดยสมบูรณ์ // แทนที่จะกำหนดตัวชี้ใหม่เป็นการดำเนินการที่มีราคาแพงมาก

แต่เราจะได้รับความเร็วจากการเข้าถึงหน่วยความจำโดยตรงหรือไม่?

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

การกระจายแบบไดนามิก

ถ้อยคำของคำถามนำเสนอสองวิธีในการสร้างวัตถุ และความแตกต่างที่สำคัญคืออายุขัยของพวกเขา (ระยะเวลาการเก็บรักษา)ในหน่วยความจำโปรแกรม การใช้วัตถุ myObject; คุณต้องอาศัยการตรวจจับอายุการใช้งานอัตโนมัติ และวัตถุจะถูกทำลายทันทีที่ออกจากขอบเขต แต่ Object *myObject = new Object; รักษาวัตถุให้คงอยู่จนกว่าคุณจะลบออกจากหน่วยความจำด้วยตนเองด้วยคำสั่งลบ ใช้ตัวเลือกหลังเมื่อจำเป็นจริงๆ เท่านั้น และดังนั้นจึง เสมอเลือกที่จะกำหนดอายุการเก็บรักษาของวัตถุโดยอัตโนมัติหากเป็นไปได้.

โดยทั่วไป การกำหนดอายุการใช้งานแบบบังคับจะใช้ในสถานการณ์ต่อไปนี้:

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

หากจำเป็นต้องใช้การจัดสรรแบบไดนามิกคุณควรห่อหุ้มโดยใช้ตัวชี้อัจฉริยะ (คุณสามารถอ่านได้ในบทความของเรา) หรือประเภทอื่นที่รองรับสำนวน "การได้รับทรัพยากรกำลังเริ่มต้น" (คอนเทนเนอร์มาตรฐานรองรับสิ่งนี้ - นี่คือสำนวนตาม ทรัพยากรใด: บล็อกหน่วยความจำ, ไฟล์, การเชื่อมต่อเครือข่าย ฯลฯ - เมื่อได้รับจะถูกเตรียมใช้งานในตัวสร้างแล้วถูกทำลายอย่างระมัดระวังโดยตัวทำลาย) พอยน์เตอร์อัจฉริยะได้แก่ std::unique_ptr และ std::shared_ptr

ป้ายบอกทาง

อย่างไรก็ตาม มีหลายกรณีที่การใช้พอยน์เตอร์นั้นสมเหตุสมผลไม่เพียงแต่จากมุมมองการจัดสรรหน่วยความจำแบบไดนามิกเท่านั้น แต่ยังมีทางเลือกอื่นเกือบทุกครั้งโดยไม่ต้องใช้พอยน์เตอร์ ซึ่งคุณควรเลือก เหมือนเมื่อก่อนสมมติว่า: เลือกใช้ทางเลือกอื่นเสมอ เว้นแต่มีความจำเป็นต้องใช้พอยน์เตอร์เป็นพิเศษ.

กรณีที่การใช้พอยน์เตอร์ถือได้ว่าเป็นตัวเลือกที่เป็นไปได้มีดังต่อไปนี้:

  • ความหมายอ้างอิง. บางครั้งอาจจำเป็นต้องเข้าถึงวัตถุ (ไม่ว่าจะจัดสรรหน่วยความจำอย่างไร) เนื่องจากคุณต้องการเข้าถึงฟังก์ชันในวัตถุนี้ ไม่ใช่สำเนา - เช่น เมื่อคุณจำเป็นต้องดำเนินการผ่านโดยการอ้างอิง อย่างไรก็ตาม ในกรณีส่วนใหญ่ การใช้ลิงก์ที่นี่ก็เพียงพอแล้ว ไม่ใช่ตัวชี้ เนื่องจากนั่นคือสิ่งที่สร้างลิงก์ไว้ โปรดทราบว่าสิ่งเหล่านี้แตกต่างเล็กน้อยจากที่อธิบายไว้ในจุดที่ 1 ข้างต้น แต่ถ้าคุณสามารถเข้าถึงสำเนาของออบเจ็กต์ได้ ก็ไม่จำเป็นต้องใช้ข้อมูลอ้างอิง (แต่โปรดทราบว่าการคัดลอกออบเจ็กต์เป็นการดำเนินการที่มีราคาแพง)
  • ความแตกต่าง. การเรียกใช้ฟังก์ชันภายใน polymorphism (คลาสอ็อบเจ็กต์ไดนามิก) สามารถทำได้โดยใช้การอ้างอิงหรือตัวชี้ ขอย้ำอีกครั้งว่าควรใช้การอ้างอิง
  • วัตถุเสริม. ในกรณีนี้ คุณสามารถใช้ nullptr เพื่อระบุว่าไม่มีการระบุวัตถุได้ หากเป็นอาร์กิวเมนต์ของฟังก์ชัน ก็ควรนำไปใช้กับอาร์กิวเมนต์เริ่มต้นหรือโอเวอร์โหลดจะดีกว่า หรือคุณสามารถใช้ประเภทที่สรุปลักษณะการทำงานนี้ เช่น boost::Optional (แก้ไขใน C++14 std::Optional)
  • ปรับปรุงความเร็วในการคอมไพล์. คุณอาจต้องแยกหน่วยการคอมไพล์ (หน่วยการรวบรวม). การใช้พอยน์เตอร์อย่างมีประสิทธิภาพอย่างหนึ่งคือการประกาศล่วงหน้า (เพราะในการใช้อ็อบเจ็กต์ คุณต้องกำหนดมันก่อน) สิ่งนี้จะช่วยให้คุณสามารถแบ่งพื้นที่หน่วยการคอมไพล์ออกได้ ซึ่งอาจส่งผลเชิงบวกต่อการเร่งเวลาการคอมไพล์ ซึ่งช่วยลดเวลาที่ใช้ในกระบวนการนี้ได้อย่างมาก
  • การโต้ตอบกับห้องสมุดC หรือ C-like. ที่นี่คุณจะต้องใช้พอยน์เตอร์แบบดิบเพื่อปลดปล่อยหน่วยความจำในวินาทีสุดท้าย คุณสามารถรับพอยน์เตอร์แบบดิบจากพอยน์เตอร์อัจฉริยะได้ เช่น ด้วยการดำเนินการรับ หากไลบรารีใช้หน่วยความจำซึ่งต้องปลดปล่อยด้วยตนเองในภายหลัง คุณสามารถวางเฟรมตัวทำลายในตัวชี้อัจฉริยะได้

เมื่อเรียนภาษา C ผู้เริ่มต้นมักจะมีคำถามที่เกี่ยวข้องกับคำแนะนำ ฉันคิดว่าทุกคนมีคำถามเดียวกันโดยประมาณ ดังนั้นฉันจะอธิบายคำถามที่เกิดขึ้นสำหรับฉัน

ตัวชี้มีไว้เพื่ออะไร?

เหตุใดจึงเขียนว่า "ตัวชี้ประเภท" เสมอ และอะไรคือตัวชี้ประเภท uint16_tแตกต่างจากตัวชี้ประเภท uint8_t?

แล้วใครเป็นคนคิดดัชนีล่ะ?

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

หากต้องการประกาศตัวแปรให้เป็นตัวชี้ คุณต้องนำหน้าชื่อด้วย * และเพื่อให้ได้ที่อยู่ของตัวแปรที่ใช้ & (ตัวดำเนินการที่อยู่ unary)
ถ่าน = "เป็น"; ถ่าน *p =
ในกรณีนี้ p มีที่อยู่ของตัวแปร a แต่สิ่งที่น่าสนใจก็คือ ในการทำงานกับตัวชี้ต่อไป คุณไม่จำเป็นต้องเขียนเครื่องหมายดอกจัน แต่จำเป็นเฉพาะเมื่อประกาศเท่านั้น.
ถ่าน = "เป็น"; ถ่านข = "ข"; ถ่าน *p = p =
ในกรณีนี้ p มีที่อยู่ของตัวแปร b แต่ หากเราต้องการรับค่าที่อยู่ ณ ที่อยู่นี้ เราจำเป็นต้องใช้ตัวดำเนินการ dereference, เครื่องหมายดอกจันเดียวกัน *
ถ่าน new_simbol = 0; ถ่าน = "เป็น"; ถ่าน *p = new_simbol = *p;
ดังนั้นตัวแปร new_simbol จะมีรหัส ASCII ของสัญลักษณ์ "a"

ตอนนี้เรามาดูคำถามโดยตรงว่าตัวชี้จำเป็นสำหรับอะไร ลองนึกภาพว่าเรามีอาร์เรย์ที่เราต้องการใช้งานในฟังก์ชัน ในการส่งต่ออาร์เรย์ไปยังฟังก์ชัน คุณต้องคัดลอกมัน นั่นคือ เสียหน่วยความจำ ซึ่ง MK มีเพียงเล็กน้อยอยู่แล้ว ดังนั้นวิธีแก้ปัญหาที่ถูกต้องกว่านั้นคือไม่ต้องคัดลอกอาร์เรย์ แต่ต้องส่งที่อยู่ของอาร์เรย์นั้น องค์ประกอบและขนาดแรก
ม. =(1,2,3...);
คุณสามารถทำเช่นนี้
เป็นโมฆะ foo (ถ่าน * m, ขนาด uint8_t) ( )
หรือไม่ก็
เป็นโมฆะ foo (ถ่าน m, ขนาด uint8_t) ( )
เนื่องจากชื่อของอาร์เรย์ประกอบด้วยที่อยู่ขององค์ประกอบแรก จึงไม่มีอะไรมากไปกว่าตัวชี้ คุณสามารถเลื่อนผ่านอาร์เรย์ได้โดยใช้การดำเนินการทางคณิตศาสตร์อย่างง่าย ตัวอย่างเช่น เพื่อให้ได้ค่าขององค์ประกอบที่ห้าของอาร์เรย์ คุณต้องเพิ่ม 4 ลงในที่อยู่อาร์เรย์ (ที่อยู่ขององค์ประกอบแรก) และใช้ตัวดำเนินการ Dereference .
ม. = *(ม. + 4);

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

ดังนั้น โดยการระบุประเภทของตัวชี้ เราจะบอกคอมไพลเลอร์ว่า นี่คือที่อยู่ของจุดเริ่มต้นของอาร์เรย์ องค์ประกอบหนึ่งของอาร์เรย์ใช้เวลา 2 ไบต์ มีองค์ประกอบดังกล่าว 10 รายการในอาร์เรย์ ดังนั้น เราควรจัดสรรหน่วยความจำเท่าใด อาร์เรย์นี้? 20 ไบต์ - คอมไพเลอร์ตอบสนอง เพื่อความชัดเจน เอาล่ะ พอยน์เตอร์ประเภท void ไม่ได้กำหนดว่าจะใช้พื้นที่เท่าใด- นี่เป็นเพียงที่อยู่ ลองลดให้เหลือพอยน์เตอร์ประเภทต่างๆ แล้วดำเนินการส่งต่อ


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

คำถามสุดท้ายคือใครเป็นคนคิดตัวชี้นี้ เพื่อให้เข้าใจปัญหานี้ เราต้องหันไปหาแอสเซมเบลอร์ เช่น AVR แล้วเราจะพบคำแนะนำที่นั่น
st X, r1 ;บันทึกเนื้อหาของ r1 ใน SRAM ที่ที่อยู่ X โดยที่ X คือคู่ของรีจิสเตอร์ r26, r27 ld r1,X ; โหลดเนื้อหาของ SRAM ลงใน r1 ที่ที่อยู่ X โดยที่ X คือคู่ของรีจิสเตอร์ r26, r27
เห็นได้ชัดว่า X มี ตัวชี้(ที่อยู่) และปรากฎว่าไม่มีคนชั่วร้ายที่มีตัวชี้เพื่อหลอกทุกคนการทำงานกับพอยน์เตอร์ (ที่อยู่) ได้รับการสนับสนุนในระดับเคอร์เนล MK

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


/* เพื่อนๆ พบข้อผิดพลาดมากมายในบทความ ขอบคุณคนที่ร่วมแสดงความคิดเห็น ทั้งนี้หลังจากอ่านบทความแล้วอย่าลืมอ่านความคิดเห็นอีกครั้ง */

1. ข้อมูลทั่วไป

แล้วพอยน์เตอร์คืออะไร? พอยน์เตอร์เป็นตัวแปรเดียวกัน เพียงแต่ถูกเตรียมใช้งานไม่ได้ด้วยค่าของประเภทข้อมูลประเภทใดประเภทหนึ่งในภาษา C++ แต่ด้วยที่อยู่ ซึ่งเป็นที่อยู่ของตัวแปรบางตัวที่ถูกประกาศไว้ก่อนหน้าในโค้ด ลองดูตัวอย่าง:

โมฆะ main() ( int i_val = 7; )
# ข้างล่างนี่ แน่นอนว่าฉันโกหกพวกคุณ ตัวแปร i_val เป็นแบบคงที่ และจะถูกจัดสรรอย่างชัดเจนบนสแต็ก พื้นที่บนฮีปได้รับการจัดสรรสำหรับออบเจ็กต์ไดนามิก สิ่งเหล่านี้เป็นสิ่งสำคัญ! แต่ในบริบทนี้ข้าพเจ้าได้กล่าวกับตนเองแล้วว่าจะปล่อยวางทุกสิ่งตามที่เป็นอยู่ เพราะฉะนั้นอย่าสาบานมากเกินไป

เราประกาศตัวแปรประเภท ภายในและนี่คือการเริ่มต้น จะเกิดอะไรขึ้นเมื่อคอมไพล์โปรแกรม? ใน RAM ในฮีป พื้นที่ว่างจะถูกจัดสรรตามขนาดที่สามารถวางค่าของตัวแปรของเราที่นั่นได้อย่างอิสระ i_val. ตัวแปรจะใช้หน่วยความจำจำนวนหนึ่งซึ่งอยู่ในหลายเซลล์ขึ้นอยู่กับประเภทของมัน เมื่อพิจารณาว่าแต่ละเซลล์มีที่อยู่ เราสามารถค้นหาช่วงของที่อยู่ซึ่งมีค่าของตัวแปรตั้งอยู่ได้ ในกรณีนี้ เมื่อทำงานกับพอยน์เตอร์ เราต้องการเพียงที่อยู่เดียว - ที่อยู่ของเซลล์แรก ซึ่งจะทำหน้าที่เป็นค่าที่เราเริ่มต้นตัวชี้ ดังนั้น:

โมฆะ main())( // 1 int i_val = 7; int* i_ptr = &i_val; // 2 void* v_ptr = (int *)&i_val )
การใช้ที่อยู่เอกนารีดำเนินการ & เราจะดึงที่อยู่ของตัวแปร i_valและกำหนดให้กับตัวชี้ ที่นี่คุณควรใส่ใจกับสิ่งต่อไปนี้:

  1. ประเภทที่ใช้ในการประกาศตัวชี้จะต้องตรงกับประเภทของตัวแปรที่เรากำหนดที่อยู่ให้กับตัวชี้
  2. คุณสามารถเลือกประเภทที่ใช้เมื่อประกาศพอยน์เตอร์ได้ เป็นโมฆะ. แต่ในกรณีนี้ เมื่อเริ่มต้นการทำงานของตัวชี้ คุณจะต้องแปลงเป็นประเภทของตัวแปรที่ตัวชี้ชี้ไป
  3. ไม่ควรสับสนตัวดำเนินการรับที่อยู่กับการอ้างอิงถึงค่าบางค่า ซึ่งจะแสดงด้วยสัญลักษณ์ด้วย & .
ตอนนี้เรามีตัวชี้ไปยังตัวแปรแล้ว ฉัน_วาเราสามารถดำเนินการด้วยค่าของมันได้ ไม่เพียงแต่ใช้ตัวแปรโดยตรงเท่านั้น แต่ยังใช้ตัวชี้ไปที่ตัวแปรด้วย เรามาดูวิธีการทำงานด้วยตัวอย่างง่ายๆ:

#รวม ใช้เนมสเปซมาตรฐาน; ถือเป็นโมฆะ main())( int i_val = 7; int* i_ptr = &i_val; // แสดงค่าของตัวแปร i_val cout<< i_val << endl; // C1 cout << *i_ptr << endl; // C2 }

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

(*i_ptr)++; // ผลลัพธ์เทียบเท่ากับการดำเนินการเพิ่มตัวแปรเอง: i_val++ // เช่น ในกรณีนี้ i_val จะเก็บค่าไว้ไม่ใช่ 7 แต่เป็น 8

2. อาร์เรย์

มาดูตัวอย่างกันดีกว่า - พิจารณาอาร์เรย์หนึ่งมิติคงที่ที่มีความยาวที่แน่นอนและเริ่มต้นองค์ประกอบต่างๆ:

โมฆะ main())( const int size = 7; // ประกาศ int i_array; // การเริ่มต้นองค์ประกอบอาร์เรย์สำหรับ (int i = 0; i != size; i++)( i_array[i] = i; ) )
ตอนนี้เราจะเข้าถึงองค์ประกอบอาร์เรย์โดยใช้พอยน์เตอร์:

Int* arr_ptr = i_array; สำหรับ (int i = 0; i != size; i++)( cout<< *(arr_ptr + i) << endl; }
เกิดอะไรขึ้นที่นี่: เรากำลังเริ่มต้นตัวชี้ arr_ptrที่อยู่ของจุดเริ่มต้นของอาร์เรย์ i_array. จากนั้น ในลูป เราจะส่งออกองค์ประกอบ โดยเข้าถึงแต่ละองค์ประกอบด้วยที่อยู่เริ่มต้นและออฟเซ็ต นั่นคือ:

*(arr_ptr + 0) เป็นองค์ประกอบศูนย์เดียวกัน ชดเชยศูนย์ ( ฉัน = 0),
*(arr_ptr + 1) - ก่อน ( ฉัน= 1) และอื่นๆ

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

Int* arr_ptr_null = &i_array; สำหรับ (int i = 0; i != size; i++)( cout<< *(arr_ptr_null + i) << endl; } Пройдем по элементам с конца массива:
int* arr_ptr_end = &i_array; สำหรับ (int i = 0; i != size; i++)( cout<< *(arr_ptr_end - i) << endl; } Замечания:

  1. สัญกรณ์ array[i] เทียบเท่ากับสัญกรณ์ *(array + ฉัน). ไม่มีใครห้ามใช้พวกมันร่วมกัน: (array + ฉัน) - ในกรณีนี้การกระจัดจะไปที่ ฉันและอีกหนึ่งอย่าง อย่างไรก็ตาม ในกรณีนี้ ก่อนนิพจน์ (array + ฉัน) ใส่ * ไม่จำเป็น. การมีวงเล็บเหลี่ยม “ช่วยชดเชยสิ่งนี้
  2. ติดตาม “การเคลื่อนไหว” ของคุณผ่านองค์ประกอบของอาร์เรย์ - โดยเฉพาะอย่างยิ่งหากคุณต้องการใช้วิธีการแสดงภาพอนาจาร เช่น (อาร์เรย์ + i)[j]

3. การจัดสรรหน่วยความจำแบบไดนามิก

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

ขนาด int = -1; // การกระทำบางอย่างเกิดขึ้นที่นี่โดยที่ // เปลี่ยนค่าของขนาดตัวแปร int* dyn_arr = new int;
จะเกิดอะไรขึ้นที่นี่: เราประกาศตัวชี้และเริ่มต้นด้วยจุดเริ่มต้นของอาร์เรย์ที่ผู้ดำเนินการจัดสรรหน่วยความจำ ใหม่บน ขนาดองค์ประกอบ ควรสังเกตว่าในกรณีนี้เราสามารถใช้เทคนิคเดียวกันเมื่อทำงานกับพอยน์เตอร์เช่นเดียวกับอาร์เรย์แบบคงที่ สิ่งที่คุณควรเรียนรู้จากสิ่งนี้ก็คือ หากคุณต้องการโครงสร้างบางประเภท (เช่น อาร์เรย์) แต่คุณไม่ทราบขนาดของมันล่วงหน้า ให้ประกาศโครงสร้างนี้และเริ่มต้นใหม่ในภายหลัง ฉันจะยกตัวอย่างที่สมบูรณ์กว่านี้ในภายหลัง แต่สำหรับตอนนี้เรามาดูตัวชี้สองตัวกัน

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

ขนาด int คงที่ = 7; // อาร์เรย์สองมิติขนาด 7x7 int** i_arr = new int*; สำหรับ (int i = 0; i != ขนาด; i++)( i_arr[i] = int ใหม่; )
แล้วตัวชี้สามตัวล่ะ? อาเรย์ไดนามิกสามมิติ คุณบอกว่ามันไม่น่าสนใจ สิ่งนี้สามารถดำเนินไปอย่างไม่สิ้นสุด โอเคถ้าอย่างนั้น. จากนั้นลองจินตนาการถึงสถานการณ์ที่เราจำเป็นต้องวางวัตถุไดนามิกของบางคลาส ห้องเรียนของฉันในอาเรย์ไดนามิกสองมิติ มีลักษณะอย่างไร (ตัวอย่างแสดงให้เห็นการใช้พอยน์เตอร์เท่านั้น คลาสที่แสดงในตัวอย่างไม่มีความหมายเชิงความหมายใดๆ):

คลาส MyClass( สาธารณะ: int a; สาธารณะ: MyClass(int v)( this->a = v; ); ~MyClass())(); ); เป็นโมฆะ main())( MyClass*** v = MyClass ใหม่**; for (int i = 0; i != 7; i++)( v[i] = MyClass ใหม่*; for (int j = 0; j ! = 3; j++)( v[i][j] = new MyClass(i*j); ) ) ) ที่นี่จำเป็นต้องใช้พอยน์เตอร์สองตัวเพื่อสร้างเมทริกซ์ที่วัตถุจะตั้งอยู่ ตัวที่สาม - จริง ๆ แล้วเพื่อวางวัตถุไดนามิกที่นั่น (ไม่ มายคลาส เอ, ก มายคลาส*). นี่ไม่ใช่ตัวอย่างเดียวของการใช้พอยน์เตอร์ประเภทนี้ เราจะกล่าวถึงตัวอย่างเพิ่มเติมด้านล่าง

4. ตัวชี้เป็นอาร์กิวเมนต์ของฟังก์ชัน

ขั้นแรก เรามาสร้างอาร์เรย์ไดนามิกขนาด 4x4 สองตัวและเริ่มต้นองค์ประกอบด้วยค่าบางค่า:

เป็นโมฆะ f1(int**, int); ถือเป็นโมฆะ main())( const int size = 4; // การประกาศและการจัดสรรหน่วยความจำ // สำหรับพอยน์เตอร์อื่นๆ int** a = new int*; int** b = new int*; // การจัดสรรหน่วยความจำสำหรับค่าตัวเลข ​​for (int i = 0; i != size; i++)( a[i] = new int; b[i] = new int; // การเริ่มต้นจริงสำหรับ (int j = 0; j != size; j++ )( a[i ​​][j] = i * j + 1; b[i][j] = i * j - 1; ) ) ) เป็นโมฆะ f1(int** a, int c)( สำหรับ (int i = 0; i != c; i++)( สำหรับ (int j = 0; j != c; j++)( cout.width(3); cout<< a[i][j]; } cout << endl; } cout << endl; }
การทำงาน f1แสดงค่าอาร์เรย์บนหน้าจอ: อาร์กิวเมนต์แรกของมันคือตัวชี้ไปยังอาร์เรย์สองมิติส่วนที่สองคือมิติของมัน (ระบุค่าหนึ่งเพราะเพื่อความเรียบง่ายเราตกลงที่จะทำงานกับอาร์เรย์โดยที่จำนวนแถวเกิดขึ้นพร้อมกัน จำนวนคอลัมน์)

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

  1. ตัวเลือกที่หนึ่ง เราผ่านคำแนะนำที่แท้จริง และ เป็นพารามิเตอร์ฟังก์ชัน:

    โมฆะ f2(int** a, int** b, int c)( for (int i = 0; i != c; i++)( for (int j = 0; j != c; j++)( a[i ][j] = b[i][j]; ) ) ) หลังจากเรียกใช้ฟังก์ชันนี้ในร่างกายแล้ว หลัก - f2(ก, ข, 4)เนื้อหาของอาร์เรย์ a และ b จะเหมือนกัน

  2. ตัวเลือกที่สอง แทนที่ค่าตัวชี้: เพียงกำหนดค่าของตัวชี้ b ให้กับตัวชี้ a

    เป็นโมฆะ main())( const int size = 4; // การประกาศและการจัดสรรหน่วยความจำ // สำหรับพอยน์เตอร์อื่นๆ int** a = new int*; int** b = new int*; // การจัดสรรหน่วยความจำสำหรับค่าตัวเลข ​​for (int i = 0; i != size; i++)( a[i] = new int; b[i] = new int; // การเริ่มต้นจริงสำหรับ (int j = 0; j != size; j++ )( a[i ​​][j] = i * j + 1; b[i][j] = i * j - 1; ) ) // สิ่งนี้จะใช้ได้ที่นี่ a = b; )
    อย่างไรก็ตาม เรามีความสนใจในกรณีที่อาร์เรย์ถูกประมวลผลในบางฟังก์ชัน สิ่งแรกที่เข้ามาในใจคืออะไร? ส่งพอยน์เตอร์เป็นพารามิเตอร์ไปยังฟังก์ชันของเราและทำสิ่งเดียวกันที่นั่น: กำหนดให้กับพอยน์เตอร์ ค่าตัวชี้ . นั่นคือใช้ฟังก์ชันต่อไปนี้:

    เป็นโมฆะ f3(int** a, int** b)( a = b; ) มันจะได้ผลไหม? ถ้าเราอยู่ในฟังก์ชั่น f3มาเรียกใช้ฟังก์ชันกัน f1(ก, 4)แล้วเราจะเห็นว่าค่าอาเรย์เปลี่ยนแปลงไปจริงๆ แต่: ถ้าเราดูที่เนื้อหาของอาร์เรย์ โดยหลัก - เราจะพบสิ่งที่ตรงกันข้าม - ไม่มีอะไรเปลี่ยนแปลง แล้วสาเหตุคืออะไร? ทุกอย่างง่ายมาก: ในฟังก์ชั่น f3เราไม่ได้ทำงานกับตัวชี้เอง แต่ด้วยสำเนาในเครื่อง! การเปลี่ยนแปลงทั้งหมดที่เกิดขึ้นในฟังก์ชัน f3- ได้รับผลกระทบเฉพาะสำเนาของตัวชี้ในเครื่องเท่านั้น แต่ไม่ใช่ตัวชี้เอง . ลองดูตัวอย่างต่อไปนี้:

    เป็นโมฆะ false_eqv(int, int); void main())( int a = 3, b = 5; false_eqv(a, b); // ค่าของ a มีการเปลี่ยนแปลงหรือไม่ // ไม่แน่นอน ) false_eqv(int a, int b)( a = b; ) ดังนั้น ฉันคิดว่าคุณคงเข้าใจว่าฉันกำลังทำอะไรอยู่ ตัวแปร คุณไม่สามารถกำหนดค่าให้กับตัวแปรด้วยวิธีนี้ได้ - ท้ายที่สุดแล้ว เราได้ส่งผ่านค่านิยมโดยตรง ไม่ใช่โดยการอ้างอิง เช่นเดียวกับพอยน์เตอร์ - การใช้พวกมันเป็นอาร์กิวเมนต์ในลักษณะนี้ ทำให้เราสูญเสียความสามารถในการเปลี่ยนค่าอย่างเห็นได้ชัด
    ตัวเลือกที่สาม หรือการทำงานกับข้อผิดพลาดตามตัวเลือกที่สอง:

    เป็นโมฆะ f4(int***, int**); เป็นโมฆะ main())( const int size = 4; int** a = new int*; int** b = new int*; for (int i = 0; i != 4; i++)( a[i] = int ใหม่; b[i] = int ใหม่; for (int j = 0; j != 4; j++)( a[i][j] = i * j + 1; b[i][j] = i * j - 1; ) ) int*** d = f4(d, b); ) เป็นโมฆะ f4(int*** a, int** b)( *a = b; )
    ดังนั้นโดยหลักแล้วเราจึงสร้างพอยน์เตอร์ขึ้นมา ไปที่ตัวชี้ และนี่คือสิ่งที่เราส่งผ่านเป็นอาร์กิวเมนต์ไปยังฟังก์ชันการแทนที่ ตอนนี้, การตัดสิทธิ ข้างใน f4และเทียบเคียงกับค่าพอยน์เตอร์ เราแทนที่ค่าของตัวชี้จริง และไม่ใช่สำเนาในเครื่อง เป็นค่าของตัวชี้ .

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

    ลบ(ก); ลบ(ข); // เพียงเท่านี้สำหรับอาร์เรย์สองมิติของเรา Delete(v); // ดังนั้นจึงไม่มีอาร์เรย์สองมิติอีกต่อไปที่มีวัตถุไดนามิก Delete(dyn_array); // ดังนั้นอาร์เรย์หนึ่งมิติจึงถูกลบ

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