ทำไมต้องใช้พอยน์เตอร์? [ปิด]


356

ฉันรู้ว่านี่เป็นคำถามพื้นฐานจริง ๆ แต่ฉันเพิ่งเริ่มต้นด้วยการเขียนโปรแกรม C ++ ขั้นพื้นฐานบางอย่างหลังจากเขียนโค้ดโปรเจ็กต์ด้วยภาษาระดับสูง

โดยทั่วไปฉันมีสามคำถาม:

  1. เหตุใดจึงใช้พอยน์เตอร์แทนตัวแปรปกติ
  2. ฉันควรใช้พอยน์เตอร์เมื่อใดและที่ไหน
  3. คุณใช้พอยน์เตอร์กับอาร์เรย์ได้อย่างไร

5
พูดคุยกันก่อนหน้านี้ในคำถามนี้หวังว่าจะช่วยได้!
Doug T.

4
สำหรับรายชื่อของหนังสือให้ดูstackoverflow.com/questions/388242/... หลังจาก Java ฉันพบAccelerated C ++มีประโยชน์มาก
Jabba

163
เราใช้พอยน์เตอร์เพราะง่ายกว่าที่จะให้ที่อยู่กับใครบางคนในบ้านของคุณมากกว่าให้สำเนาบ้านของคุณให้กับทุกคน
Rishi Dua

14
@RishiDua นั่นเป็นคำอธิบายเดียวที่ดีที่สุดของตัวชี้ที่ฉันเคยเจอ ขอบคุณสำหรับการที่จะได้เพิ่มขึ้น :) ความเข้าใจของฉัน
จอช

2
พอยน์เตอร์สามารถใช้เมื่อคุณต้องการส่งคืนค่า / วัตถุมากกว่าหนึ่งรายการ
Gaurav

คำตอบ:


172
  • เหตุใดจึงใช้พอยน์เตอร์แทนตัวแปรปกติ

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

  • ฉันควรใช้พอยน์เตอร์เมื่อใดและที่ไหน

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

  • คุณใช้พอยน์เตอร์กับอาร์เรย์ได้อย่างไร

ด้วยความพยายามน้อยและสับสนมาก ;-) ถ้าเราพูดถึงชนิดข้อมูลอย่างง่ายเช่น int และ char จะมีความแตกต่างกันเล็กน้อยระหว่างอาร์เรย์และตัวชี้ การประกาศเหล่านี้คล้ายกันมาก (แต่ไม่เหมือนกัน - เช่นsizeofจะคืนค่าต่างกัน):

char* a = "Hello";
char a[] = "Hello";

คุณสามารถเข้าถึงองค์ประกอบใด ๆ ในอาร์เรย์เช่นนี้

printf("Second char is: %c", a[1]);

ดัชนี 1 ตั้งแต่อาร์เรย์เริ่มต้นด้วยองค์ประกอบ 0 :-)

หรือคุณทำได้เช่นกัน

printf("Second char is: %c", *(a+1));

ต้องการตัวดำเนินการตัวชี้ (*) เนื่องจากเรากำลังบอก printf ว่าเราต้องการพิมพ์อักขระ หากไม่มี * การแสดงอักขระของที่อยู่หน่วยความจำเองจะถูกพิมพ์ ตอนนี้เรากำลังใช้ตัวละครแทน หากเราใช้% s แทน% c เราจะขอให้ printf พิมพ์เนื้อหาของที่อยู่หน่วยความจำที่ชี้โดย 'a' บวกหนึ่ง (ในตัวอย่างข้างต้น) และเราไม่ต้องใส่ * ข้างหน้า:

printf("Second char is: %s", (a+1)); /* WRONG */

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

char* a = "Hello";
int b = 120;
printf("Second char is: %s", b);

สิ่งนี้จะพิมพ์สิ่งใดก็ตามที่พบในที่อยู่หน่วยความจำ 120 และทำการพิมพ์จนกว่าจะพบอักขระว่าง มันผิดและผิดกฎหมายในการดำเนินการคำสั่ง printf นี้ แต่มันอาจจะทำงานได้เนื่องจากตัวชี้เป็นประเภท int ในหลายสภาพแวดล้อม ลองนึกภาพปัญหาที่คุณอาจเกิดขึ้นหากคุณใช้ sprintf () แทนและกำหนดวิธีนี้ "char array" นานเกินไปให้กับตัวแปรอื่นซึ่งมีการจัดสรรพื้นที่ จำกัด เท่านั้น คุณน่าจะจบลงด้วยการเขียนทับสิ่งอื่นในหน่วยความจำและทำให้โปรแกรมของคุณพัง (ถ้าคุณโชคดี)

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

char* x;
/* Allocate 6 bytes of memory for me and point x to the first of them. */
x = (char*) malloc(6);
x[0] = 'H';
x[1] = 'e';
x[2] = 'l';
x[3] = 'l';
x[4] = 'o';
x[5] = '\0';
printf("String \"%s\" at address: %d\n", x, x);
/* Delete the allocation (reservation) of the memory. */
/* The char pointer x is still pointing to this address in memory though! */
free(x);
/* Same as malloc but here the allocated space is filled with null characters!*/
x = (char *) calloc(6, sizeof(x));
x[0] = 'H';
x[1] = 'e';
x[2] = 'l';
x[3] = 'l';
x[4] = 'o';
x[5] = '\0';
printf("String \"%s\" at address: %d\n", x, x);
/* And delete the allocation again... */
free(x);
/* We can set the size at declaration time as well */
char xx[6];
xx[0] = 'H';
xx[1] = 'e';
xx[2] = 'l';
xx[3] = 'l';
xx[4] = 'o';
xx[5] = '\0';
printf("String \"%s\" at address: %d\n", xx, xx);

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


8
ตัวอย่างของคุณมี 120 ข้อผิดพลาด ใช้% c ไม่ใช่% s จึงไม่มีข้อผิดพลาด มันพิมพ์อักษรตัวพิมพ์เล็ก x เท่านั้น นอกจากนี้การอ้างสิทธิ์ในครั้งต่อไปของคุณที่ตัวชี้เป็นประเภท int นั้นไม่ถูกต้องและเป็นคำแนะนำที่ไม่ดีมากที่จะให้โปรแกรมเมอร์ C ที่ไม่มีประสบการณ์กับพอยน์เตอร์
.. GitHub หยุดช่วยน้ำแข็ง

13
-1 คุณเริ่มต้นได้ดี แต่ตัวอย่างแรกนั้นผิด ไม่มันไม่เหมือนกัน ในกรณีแรกaคือตัวชี้และในกรณีที่สองaคืออาร์เรย์ ฉันพูดถึงมันแล้วหรือยัง? มันไม่เหมือนกัน! ตรวจสอบด้วยตัวคุณเอง: เปรียบเทียบ sizeof (a) ลองกำหนดที่อยู่ใหม่ให้กับอาร์เรย์ มันจะไม่ทำงาน
sellibitze

42
char* a = "Hello";และchar a[] = "Hello";ไม่เหมือนกันพวกมันค่อนข้างต่างกัน หนึ่งประกาศตัวชี้อาร์เรย์อื่น ๆ ลองsizeofและคุณจะเห็นความแตกต่าง
Patrick Schlüter

12
"ไม่ผิดหรือผิดกฎหมายในการใช้คำสั่ง printf นี้" นี่เป็นสิ่งที่ผิดธรรมดา คำสั่ง printf นั้นมีพฤติกรรมที่ไม่ได้กำหนด ในการใช้งานหลายอย่างมันจะทำให้เกิดการละเมิดการเข้าถึง พอยน์เตอร์ไม่ได้เป็นประเภท int แต่เป็นพอยน์เตอร์ (และไม่มีอะไรอื่น)
Mankarse

19
-1, คุณได้รับข้อเท็จจริงมากมายที่นี่และฉันจะบอกว่าคุณไม่สมควรได้รับจำนวน upvotes หรือสถานะคำตอบที่ยอมรับ
asveikau

50

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

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

ลองพิจารณาตัวอย่างต่อไปนี้:

ใช้การอ้างอิง:

public void doSomething()
{
    int i = 10;
    doSomethingElse(i);  // passes i by references since doSomethingElse() receives it
                         // by reference, but the syntax makes it appear as if i is passed
                         // by value
}

public void doSomethingElse(int& i)  // receives i as a reference
{
    cout << i << endl;
}

ใช้ตัวชี้:

public void doSomething()
{
    int i = 10;
    doSomethingElse(&i);
}

public void doSomethingElse(int* i)
{
    cout << *i << endl;
}

16
อาจเป็นความคิดที่ดีที่จะพูดถึงว่าการอ้างอิงนั้นปลอดภัยกว่าเพราะคุณไม่สามารถผ่านการอ้างอิงที่เป็นโมฆะได้
SpoonMeiser

26
ใช่นั่นอาจเป็นข้อได้เปรียบที่ใหญ่ที่สุดของการใช้การอ้างอิง ขอบคุณที่ชี้นำ เล่นสำนวนไม่มีเจตนา :)
trshiv

2
คุณสามารถส่งผ่านการอ้างอิงที่เป็นโมฆะได้ มันไม่ง่ายอย่างที่จะผ่านตัวชี้ว่าง
n0rd

1
เห็นด้วยกับ @ n0rd หากคุณคิดว่าคุณไม่สามารถผ่านการอ้างอิงเป็นโมฆะคุณผิด มันง่ายกว่าที่จะผ่านการอ้างอิงที่ห้อยอยู่กว่าการอ้างอิงแบบ null แต่ในที่สุดคุณก็สามารถทำได้ง่ายพอ การอ้างอิงไม่มีกระสุนเงินป้องกันวิศวกรจากการยิงตัวเองในเท้า เห็นมันมีชีวิตอยู่
WhozCraig

2
@ n0rd: การทำเช่นนั้นเป็นพฤติกรรมที่ไม่ได้กำหนดอย่างชัดเจน
Daniel Kamil Kozar

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

นี่คือตัวอย่างใน C:

char hello[] = "hello";

char *p = hello;

while (*p)
{
    *p += 1; // increase the character by one

    p += 1; // move to the next spot
}

printf(hello);

พิมพ์

ifmmp

เพราะมันใช้ค่าสำหรับตัวละครแต่ละตัวและเพิ่มทีละตัว


because it takes the value for each character and increments it by one. อยู่ในตัวแทน ASCII หรืออย่างไร
Eduardo S.

28

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

คำอธิบายที่ดีที่สุดของตัวชี้และตัวชี้ทางคณิตศาสตร์ที่ผมเคยอ่านอยู่ใน K & R ของภาษา C Programming เป็นหนังสือที่ดีสำหรับการเริ่มต้นการเรียนรู้ภาษา C ++ เป็นc ++ รองพื้น


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

นี่อาจเป็นคำอธิบายที่ดีที่สุดที่ฉันได้อ่าน คนมีวิธีการ "มากกว่าแทรกซ้อน" สิ่ง :)
Detilium

23

ให้ฉันลองตอบคำถามนี้ด้วย

พอยน์เตอร์มีความคล้ายคลึงกับการอ้างอิง กล่าวอีกนัยหนึ่งพวกเขาไม่ได้คัดลอก แต่เป็นวิธีการอ้างอิงถึงค่าดั้งเดิม

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

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

ตอนนี้จะเกิดอะไรขึ้นถ้าคุณต้องการแก้ไขค่าเดิม? คุณสามารถใช้สิ่งนี้:

MyType a; //let's ignore what MyType actually is right now.
a = modify(a); 

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

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

MyType *p = NULL; //empty pointer
if(p)
{
    //we never reach here, because the pointer points to nothing
}
//now, let's allocate some memory
p = new MyType[50000];
if(p) //if the memory was allocated, this test will pass
{
    //we can do something with our allocated array
    for(size_t i=0; i!=50000; i++)
    {
        MyType &v = *(p+i); //get a reference to the ith object
        //do something with it
        //...
    }
    delete[] p; //we're done. de-allocate the memory
}

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

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

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

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

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

เพียงจำความแตกต่างนี้ได้: การอ้างอิงเป็นตัวชี้ที่ถูกต้อง ตัวชี้ไม่ถูกต้องเสมอไป

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


คุณสามารถเขียนคลาส wrapper ที่ดีสำหรับ IO ที่ใช้หน่วยความจำซึ่งคุณสามารถหลีกเลี่ยงการใช้พอยน์เตอร์ได้
einpoklum

13

นี่คือสิ่งที่แตกต่างกันเล็กน้อย แต่มีความชาญฉลาดว่าทำไมคุณลักษณะหลายประการของ C จึงสมเหตุสมผล: http://steve.yegge.googlepages.com/tour-de-babel#C

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

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


12

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

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

unsigned char *pVideoMemory = (unsigned char *)0xA0000000;

อุปกรณ์ฝังตัวจำนวนมากยังคงใช้เทคนิคนี้


ไม่ใช่เหตุผลที่จะใช้ตัวชี้ - โครงสร้างแบบช่วงคล้าย - ซึ่งถือความยาว - มีความเหมาะสมมากกว่า วันนี้มันเป็น gsl::spanและเร็ว ๆ std::spanนี้ก็จะเป็น
einpoklum

10

ส่วนใหญ่พอยน์เตอร์เป็นอาร์เรย์ (ใน C / C ++) - เป็นแอดเดรสในหน่วยความจำและสามารถเข้าถึงได้เหมือนอาเรย์หากต้องการ (ในกรณี "ปกติ")

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

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


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

2
พอยน์เตอร์ไม่ใช่อาร์เรย์ อ่านมาตรา 6 ของcomp.lang.c คำถามที่พบบ่อย
Keith Thompson

พอยน์เตอร์ไม่ใช่อาร์เรย์จริงๆ นอกจากนี้ยังมีการใช้งานไม่มากในอาร์เรย์ดิบทั้ง - ไม่แน่นอนหลังจากที่ C ++ std::array11
einpoklum

@ einpoklum - พอยน์เตอร์อยู่ใกล้พอที่อาร์เรย์จะเทียบเท่าในการดำเนินการอ้างอิงและวนซ้ำผ่านอาร์เรย์ :) .... ด้วย - C ++ 11 คือ 3 ปีจากการปล่อยเมื่อคำตอบนี้ถูกเขียนในปี 2008
warren

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

9

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

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

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


9

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


นั่นเป็นเหตุผลที่จะใช้การอ้างอิง (หรือที่มากที่สุด - wrappers อ้างอิง) ไม่ใช่ตัวชี้
einpoklum

6

ใน C ++ ถ้าคุณต้องการใช้polymorphismชนิดย่อยคุณต้องใช้พอยน์เตอร์ ดูโพสต์นี้: C ++ Polymorphism โดยไม่มีพอยน์เตอร์

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

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


โปรดทราบว่าหากคุณใช้ '&' เพื่อส่งอาร์กิวเมนต์โดยการอ้างอิงทางอ้อม (เช่นตัวชี้) ยังคงเกี่ยวข้องกับเบื้องหลัง '&' เป็นเพียงน้ำตาลประโยคที่ (1) ช่วยให้คุณประหยัดปัญหาในการใช้ไวยากรณ์ตัวชี้และ (2) ช่วยให้คอมไพเลอร์เข้มงวดมากขึ้น (เช่นห้ามพอยน์เตอร์พอยน์เตอร์ว่าง)


ไม่คุณไม่ต้องใช้พอยน์เตอร์ - คุณสามารถใช้การอ้างอิงได้ และเมื่อคุณเขียน "พอยน์เตอร์เกี่ยวข้องกับเบื้องหลัง" - นั่นไร้ความหมาย gotoคำแนะนำยังใช้หลังฉาก - ในคำแนะนำของเครื่องเป้าหมาย เรายังไม่เรียกร้องให้ใช้พวกเขา
einpoklum

@ einpoklum-reinstateMonica หากคุณมีชุดของวัตถุที่คุณต้องการให้ดำเนินการกำหนดแต่ละองค์ประกอบให้กับตัวแปรชั่วคราวแล้วเรียกเมธอด polymorphic ของตัวแปรนั้นแล้วใช่คุณต้องมีตัวชี้เพราะคุณไม่สามารถเชื่อมโยงการอ้างอิงได้
Sildoreth

หากคุณมีการอ้างอิงคลาสพื้นฐานไปยังคลาสที่ได้รับและเรียกวิธีเสมือนในการอ้างอิงนั้นการแทนที่คลาสที่ได้รับจะถูกเรียก ฉันผิดหรือเปล่า?
einpoklum

@ einpoklum-reinstateMonica นั่นถูกต้อง แต่คุณไม่สามารถเปลี่ยนแปลงสิ่งที่อ้างอิงวัตถุ ดังนั้นหากคุณวนลูปมากกว่ารายการ / ชุด / อาร์เรย์ของวัตถุเหล่านั้นตัวแปรอ้างอิงจะไม่ทำงาน
Sildoreth

5

เพราะการคัดลอกวัตถุขนาดใหญ่ไปทั่วสถานที่ทำให้เสียเวลาและความทรงจำ


4
และพอยน์เตอร์จะช่วยได้อย่างไร ผมคิดว่าคนที่มาจาก Java หรือสุทธิไม่ทราบความแตกต่างระหว่างกองและกองเพื่อให้คำตอบนี้สวยไร้ประโยชน์ ..
เสื่อ Fredriksson

คุณสามารถผ่านการอ้างอิง ที่จะป้องกันการคัดลอก
Martin York

1
@MatsFredriksson - แทนที่จะส่งผ่าน (คัดลอก) โครงสร้างข้อมูลขนาดใหญ่และคัดลอกผลลัพธ์กลับมาอีกครั้งคุณเพียงชี้ไปที่ตำแหน่งที่อยู่ใน RAM จากนั้นปรับเปลี่ยนโดยตรง
John U

ดังนั้นอย่าคัดลอกวัตถุขนาดใหญ่ ไม่มีใครบอกว่าคุณต้องการตัวชี้สำหรับสิ่งนั้น
einpoklum

5

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

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

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

ด้วยพอยน์เตอร์คุณไม่จำเป็นต้องอัปเดตสามเหลี่ยม

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


4

ความต้องการพอยน์เตอร์ในภาษา C อธิบายไว้ที่นี่

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

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

PS: โปรดอ้างอิงลิงค์ที่ให้ไว้สำหรับคำอธิบายโดยละเอียดพร้อมโค้ดตัวอย่าง


คำถามนี้เกี่ยวกับ C ++ คำตอบนี้เกี่ยวกับ C.
einpoklum

ไม่คำถามมีการติดแท็ก c และ c ++ อาจจะเป็นแท็ก c ที่ไม่เกี่ยวข้อง แต่มันมาจากจุดเริ่มต้น
Jean-François Fabre

3

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


1

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

ปัญหาของการสร้าง C ++ ที่คนทั่วไปใช้แทนพอยน์เตอร์นั้นขึ้นอยู่กับชุดเครื่องมือที่คุณใช้ซึ่งใช้ได้เมื่อคุณมีการควบคุมทั้งหมดที่คุณต้องการเหนือซอร์สโค้ดอย่างไรก็ตามถ้าคุณรวบรวมไลบรารีแบบสแตติกกับ Visual Studio 2008 เป็นต้น และลองใช้มันใน visual Studio 2010 คุณจะได้รับข้อผิดพลาด linker มากมายเนื่องจากโครงการใหม่เชื่อมโยงกับ STL เวอร์ชันใหม่กว่าซึ่งไม่สามารถใช้งานร่วมกันได้ สิ่งต่าง ๆ น่าสนใจยิ่งขึ้นถ้าคุณคอมไพล์ DLL และให้ไลบรารี่นำเข้าที่ผู้คนใช้ในชุดเครื่องมือที่แตกต่างกันเพราะในกรณีนี้โปรแกรมของคุณจะพังไม่ช้าก็เร็วโดยไม่มีเหตุผลที่ชัดเจน

ดังนั้นเพื่อวัตถุประสงค์ในการย้ายชุดข้อมูลขนาดใหญ่จากห้องสมุดหนึ่งไปยังอีกห้องสมุดหนึ่งคุณสามารถพิจารณาให้ตัวชี้ไปยังอาร์เรย์ไปยังฟังก์ชันที่ควรจะคัดลอกข้อมูลหากคุณไม่ต้องการบังคับให้ผู้อื่นใช้เครื่องมือเดียวกับที่คุณใช้ . ส่วนที่ดีเกี่ยวกับสิ่งนี้คือมันไม่จำเป็นต้องเป็นอาเรย์แบบ C คุณสามารถใช้ std :: vector และให้ตัวชี้โดยให้ที่อยู่ขององค์ประกอบแรก & เวกเตอร์ [0] และใช้ std :: vector เพื่อจัดการอาร์เรย์ภายใน

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


1
  • ในบางกรณีจำเป็นต้องใช้ตัวชี้ฟังก์ชันเพื่อใช้ฟังก์ชันที่อยู่ในไลบรารีที่ใช้ร่วมกัน (.DLL หรือ. so) ซึ่งรวมถึงสิ่งต่าง ๆ ที่ดำเนินการในภาษาต่างๆซึ่งบ่อยครั้งที่มีการให้บริการส่วนต่อประสาน DLL
  • การทำคอมไพเลอร์
  • การคำนวณทางวิทยาศาสตร์ที่คุณมีอาร์เรย์หรือเวกเตอร์หรือแผนที่สตริงของตัวชี้ฟังก์ชั่น?
  • พยายามแก้ไขหน่วยความจำวิดีโอโดยตรง - สร้างแพ็คเกจกราฟิกของคุณเอง
  • ทำ API!
  • โครงสร้างข้อมูล - ตัวชี้การเชื่อมโยงโหนดสำหรับต้นไม้พิเศษที่คุณทำ

พอยน์เตอร์มีเหตุผลมากมาย มีชื่อ C mangling โดยเฉพาะอย่างยิ่งมีความสำคัญใน DLLs ถ้าคุณต้องการรักษาความเข้ากันได้ข้ามภาษา

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