พอยน์เตอร์ไปยังพอยน์เตอร์ทำงานอย่างไรใน C คุณจะใช้มันเมื่อไหร่?
พอยน์เตอร์ไปยังพอยน์เตอร์ทำงานอย่างไรใน C คุณจะใช้มันเมื่อไหร่?
คำตอบ:
สมมติว่าคอมพิวเตอร์ 8 บิตพร้อมที่อยู่ 8 บิต (และมีหน่วยความจำเพียง 256 ไบต์) นี่เป็นส่วนหนึ่งของหน่วยความจำ (ตัวเลขที่อยู่ด้านบนคือที่อยู่):
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
| | 58 | | | 63 | | 55 | | | h | e | l | l | o | \0 | |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
สิ่งที่คุณสามารถดูได้ที่นี่คือที่อยู่ 63 สตริง "สวัสดี" เริ่มต้น ดังนั้นในกรณีนี้หากนี่เป็นเพียง "hello" ในหน่วยความจำเท่านั้น
const char *c = "hello";
... กำหนดc
ให้เป็นตัวชี้ไปยังสตริง (อ่านอย่างเดียว) "hello" และประกอบด้วย 63 ค่าc
ตัวเองจะต้องถูกเก็บไว้ที่ไหนสักแห่ง: ในตัวอย่างข้างต้นที่ตำแหน่ง 58 แน่นอนว่าเราไม่สามารถเพียงชี้ไปที่ตัวอักษร แต่ยังรวมถึงตัวชี้อื่น ๆ เช่น:
const char **cp = &c;
ตอนนี้cp
ชี้ไปc
ที่นั่นคือมันมีที่อยู่ของc
(ซึ่งก็คือ 58) เราสามารถไปได้ไกลกว่านี้ พิจารณา:
const char ***cpp = &cp;
ตอนนี้เก็บที่อยู่ของcpp
cp
ดังนั้นมันจึงมีค่า 55 (ตามตัวอย่างด้านบน) และคุณเดาได้: มันถูกเก็บไว้ที่ที่อยู่ 60
เป็นสาเหตุว่าทำไมจึงใช้พอยน์เตอร์กับพอยน์เตอร์:
t
t *
ตอนนี้ให้พิจารณาอาร์เรย์ประเภทอาร์เรย์t
: โดยปกติการอ้างอิงไปยังอาร์เรย์ 2D นี้จะมีประเภท(t *)*
= t **
และดังนั้นจึงเป็นตัวชี้ไปยังตัวชี้char **
.f
จะต้องยอมรับข้อโต้แย้งของพิมพ์ถ้ามันคือการเปลี่ยนแปลงของตัวแปรประเภทt **
t *
พอยน์เตอร์ไปยังพอยน์เตอร์ทำงานอย่างไรใน C
ตัวชี้แรกคือตัวแปรเช่นเดียวกับตัวแปรอื่น ๆ แต่ที่เก็บที่อยู่ของตัวแปร
ตัวชี้ไปยังตัวชี้เป็นตัวแปรเช่นเดียวกับตัวแปรอื่น ๆ แต่ที่เก็บที่อยู่ของตัวแปร ตัวแปรนั้นเพิ่งเกิดขึ้นเพื่อเป็นตัวชี้
คุณจะใช้มันเมื่อไหร่?
คุณสามารถใช้พวกเขาเมื่อคุณต้องการส่งกลับตัวชี้ไปยังหน่วยความจำบางอย่างบนฮีป แต่ไม่ได้ใช้ค่าส่งคืน
ตัวอย่าง:
int getValueOf5(int *p)
{
*p = 5;
return 1;//success
}
int get1024HeapMemory(int **p)
{
*p = malloc(1024);
if(*p == 0)
return -1;//error
else
return 0;//success
}
และคุณเรียกว่าแบบนี้:
int x;
getValueOf5(&x);//I want to fill the int varaible, so I pass it's address in
//At this point x holds 5
int *p;
get1024HeapMemory(&p);//I want to fill the int* variable, so I pass it's address in
//At this point p holds a memory address where 1024 bytes of memory is allocated on the heap
มีการใช้งานอื่นด้วยเช่นอาร์กิวเมนต์ main () ของทุกโปรแกรม C มีตัวชี้ไปยังตัวชี้สำหรับ argv โดยที่แต่ละองค์ประกอบเก็บอาร์เรย์ของตัวอักษรที่เป็นตัวเลือกบรรทัดคำสั่ง คุณต้องระวังแม้ว่าเมื่อคุณใช้พอยน์เตอร์พอยน์เตอร์เพื่อชี้ไปที่อาร์เรย์ 2 มิติการใช้ตัวชี้ไปยังอาร์เรย์ 2 มิตินั้นดีกว่า
ทำไมมันอันตราย
void test()
{
double **a;
int i1 = sizeof(a[0]);//i1 == 4 == sizeof(double*)
double matrix[ROWS][COLUMNS];
int i2 = sizeof(matrix[0]);//i2 == 240 == COLUMNS * sizeof(double)
}
นี่คือตัวอย่างของตัวชี้ไปยังอาร์เรย์ 2 มิติที่ทำอย่างถูกต้อง:
int (*myPointerTo2DimArray)[ROWS][COLUMNS]
คุณไม่สามารถใช้ตัวชี้ไปยังอาร์เรย์ 2 มิติได้หากคุณต้องการสนับสนุนจำนวนองค์ประกอบของตัวแปรสำหรับ ROWS และ COLUMNS แต่เมื่อคุณรู้ก่อนมือคุณจะต้องใช้อาร์เรย์ 2 มิติ
ฉันชอบตัวอย่างโค้ด "โลกแห่งความจริง" ของตัวชี้การใช้พอยน์เตอร์ใน Git 2.0 กระทำ 7b1004b :
Linus เคยกล่าวไว้ว่า:
ฉันอยากให้ผู้คนจำนวนมากเข้าใจการเข้ารหัสในระดับต่ำมาก ๆ ไม่ใช่เรื่องใหญ่ซับซ้อนเช่นการค้นหาชื่อ lockless แต่ใช้ประโยชน์จาก pointers-to-pointers และอื่น ๆ
ตัวอย่างเช่นฉันเห็นคนจำนวนมากเกินไปที่ลบรายการที่เชื่อมโยงโดยการติดตามรายการ "prev" แล้วจึงลบรายการทำสิ่งที่ชอบ
if (prev)
prev->next = entry->next;
else
list_head = entry->next;
และเมื่อใดก็ตามที่ฉันเห็นรหัสเช่นนั้นฉันเพิ่งไป "คนนี้ไม่เข้าใจตัวชี้" และเป็นเรื่องธรรมดาที่ค่อนข้างเศร้า
ผู้ที่เข้าใจพอยน์เตอร์เพียงใช้ " ตัวชี้ไปยังตัวชี้รายการ " และเริ่มต้นด้วยที่อยู่ของ list_head และเมื่อพวกเขาเข้าไปในรายการพวกเขาสามารถลบรายการโดยไม่ต้องใช้เงื่อนไขใด ๆ เพียงแค่ทำ
*pp = entry->next
การใช้การทำให้เข้าใจง่ายทำให้เราสูญเสีย 7 บรรทัดจากฟังก์ชันนี้แม้ในขณะที่เพิ่มความคิดเห็น 2 บรรทัด
- struct combine_diff_path *p, *pprev, *ptmp;
+ struct combine_diff_path *p, **tail = &curr;
คริสชี้ให้เห็นในความคิดเห็นเพื่อ 2016 วิดีโอ " Linus Torvalds ปัญหาของตัวชี้คู่ " โดยฟิลิป Buuck
kumarชี้ให้เห็นในความคิดเห็นโพสต์บล็อก " Linus ในการทำความเข้าใจตัวชี้ " ซึ่งGrisha Trubetskoyอธิบาย:
ลองนึกภาพคุณมีรายการเชื่อมโยงที่กำหนดเป็น:
typedef struct list_entry {
int val;
struct list_entry *next;
} list_entry;
คุณต้องวนซ้ำตั้งแต่ต้นจนจบและลบองค์ประกอบเฉพาะที่มีค่าเท่ากับค่าของ to_remove
วิธีที่ชัดเจนกว่านี้คือ:
list_entry *entry = head; /* assuming head exists and is the first entry of the list */
list_entry *prev = NULL;
while (entry) { /* line 4 */
if (entry->val == to_remove) /* this is the one to remove ; line 5 */
if (prev)
prev->next = entry->next; /* remove the entry ; line 7 */
else
head = entry->next; /* special case - first entry ; line 9 */
/* move on to the next entry */
prev = entry;
entry = entry->next;
}
สิ่งที่เราทำข้างต้นคือ:
- วนซ้ำในรายการจนกว่าจะถึงรายการ
NULL
ซึ่งหมายความว่าเราได้มาถึงจุดสิ้นสุดของรายการ (บรรทัดที่ 4)- เมื่อเราเจอข้อความที่เราต้องการลบ (บรรทัดที่ 5)
- เรากำหนดค่าของตัวชี้ถัดไปปัจจุบันให้กับตัวก่อนหน้า
- จึงกำจัดองค์ประกอบปัจจุบัน (บรรทัดที่ 7)
มีกรณีพิเศษด้านบน - ที่จุดเริ่มต้นของการทำซ้ำไม่มีรายการก่อนหน้า (
prev
คือNULL
) และเพื่อลบรายการแรกในรายการที่คุณต้องแก้ไขหัวตัวเอง (สาย 9)อะไรไลนัสพูดคือว่ารหัสข้างต้นอาจจะง่ายขึ้นด้วยการทำให้องค์ประกอบก่อนหน้านี้ชี้ไปยังตัวชี้มากกว่าแค่ตัวชี้
โค้ดจะมีลักษณะดังนี้:
list_entry **pp = &head; /* pointer to a pointer */
list_entry *entry = head;
while (entry) {
if (entry->val == to_remove)
*pp = entry->next;
pp = &entry->next;
entry = entry->next;
}
โค้ดด้านบนคล้ายกับตัวแปรก่อนหน้า แต่สังเกตว่าเราไม่จำเป็นต้องดูกรณีพิเศษขององค์ประกอบแรกของรายการอีกต่อไปเนื่องจาก
pp
ไม่ได้NULL
อยู่ที่จุดเริ่มต้น เรียบง่ายและฉลาดนอกจากนี้บางคนในเธรดนั้นแสดงความคิดเห็นว่าเหตุผลที่ดีกว่านั้นก็เพราะ
*pp = entry->next
อะตอม มันเป็นที่สุดอย่างแน่นอนไม่อะตอม
นิพจน์ด้านบนมีตัวดำเนินการ dereference สองตัว (*
และ->
) และหนึ่งการมอบหมายและไม่มีสามสิ่งใดในอะตอมนี้
นี่เป็นความเข้าใจผิดที่พบบ่อย แต่ก็ไม่น่าจะมีสิ่งใดใน C ที่น่าจะถือว่าเป็นอะตอม (รวมถึง++
และ--
โอเปอเรเตอร์)!
เมื่อครอบคลุมพอยน์เตอร์ในหลักสูตรการเขียนโปรแกรมที่มหาวิทยาลัยเราได้รับคำแนะนำสองประการเกี่ยวกับวิธีเริ่มเรียนรู้เกี่ยวกับพวกเขา ประการแรกคือการดูตัวชี้สนุกกับบิงกี้ อย่างที่สองก็คือคิดถึงเส้นทางดวงตาของ Haddocks 'จาก Lewis Carroll's Through the Look -Glass
“ คุณเศร้า” อัศวินพูดด้วยน้ำเสียงวิตก:“ ให้ฉันร้องเพลงคุณเพื่อปลอบโยนคุณ”
“ มันนานหรอ?” อลิซถามเพราะเธอเคยได้ยินบทกวีมากมายในวันนั้น
“ มันยาว” อัศวินพูด“ แต่มันสวยมาก ๆ ทุกคนที่ได้ยินฉันร้องเพลงมันไม่ว่าจะนำน้ำตามาสู่ตาของพวกเขาหรืออย่างอื่น -”
“ หรืออย่างอื่นอะไร” อลิซกล่าวว่าสำหรับอัศวินได้หยุดชั่วคราวในทันที
“ มิฉะนั้นแล้วคุณก็รู้ ชื่อของเพลงนี้มีชื่อว่า 'Haddocks' Eyes '”
“ โอ้นั่นคือชื่อเพลงใช่มั้ย” อลิซพูดพยายามที่จะรู้สึกสนใจ
“ ไม่คุณไม่เข้าใจ” อัศวินกล่าวมองดูเดือดร้อนเล็กน้อย “ นั่นคือชื่อที่เรียกว่า ชื่อนี้จริงๆคือ 'The Aged Aged Man'”
“ ถ้าอย่างนั้นฉันควรจะพูดว่า 'นั่นคือเพลงที่เรียกว่า'?” อลิซแก้ไขตัวเอง
“ ไม่คุณไม่ควร: นั่นเป็นอีกสิ่งหนึ่ง! เพลงนี้มีชื่อว่า 'Ways And Means' แต่นั่นเป็นเพียงสิ่งที่เรียกว่าคุณรู้!”
“ งั้นเพลงอะไรล่ะ?” อลิซกล่าวซึ่งตอนนี้สับสนอย่างสมบูรณ์แล้ว
“ ฉันกำลังจะมาถึง” อัศวินกล่าว “ เพลงจริงๆคือ 'A-sitting On A Gate': และเพลงนั้นเป็นสิ่งประดิษฐ์ของฉันเอง”
คุณอาจต้องการอ่านสิ่งนี้: พอยน์เตอร์ถึงพอยน์เตอร์
หวังว่าสิ่งนี้จะช่วยชี้แจงข้อสงสัยพื้นฐานบางอย่าง
เมื่อจำเป็นต้องมีการอ้างอิงถึงตัวชี้ ตัวอย่างเช่นเมื่อคุณต้องการแก้ไขค่า (ที่อยู่ชี้ไปที่) ของตัวแปรพอยน์เตอร์ที่ประกาศในขอบเขตของฟังก์ชันเรียกภายในฟังก์ชันที่เรียกใช้
หากคุณผ่านตัวชี้เดียวเป็นอาร์กิวเมนต์คุณจะทำการปรับเปลี่ยนสำเนาภายในเครื่องของตัวชี้ไม่ใช่ตัวชี้ดั้งเดิมในขอบเขตการโทร ด้วยตัวชี้ไปยังตัวชี้คุณปรับเปลี่ยนหลัง
ตัวชี้ไปยังตัวชี้จะเรียกว่าจับ การใช้งานครั้งเดียวมักเกิดขึ้นเมื่อวัตถุสามารถเคลื่อนย้ายได้ในหน่วยความจำหรือถูกลบออก หนึ่งมักจะรับผิดชอบในการล็อคและปลดล็อคการใช้งานของวัตถุดังนั้นมันจะไม่ถูกย้ายเมื่อเข้าถึง
มักใช้ในสภาพแวดล้อมที่ จำกัด หน่วยความจำเช่น Palm OS
พิจารณาด้านล่างรูปและโปรแกรมที่จะเข้าใจแนวคิดที่ดีกว่านี้
เป็นต่อรูปptr1เป็นตัวชี้เดียวซึ่งจะมีที่อยู่ของตัวแปรNUM
ptr1 = #
ในทำนองเดียวกันptr2เป็นตัวชี้ไปยังตัวชี้ (ตัวชี้คู่)ซึ่งจะมีอยู่ของตัวชี้ptr1
ptr2 = &ptr1;
ตัวชี้ที่ชี้ไปยังตัวชี้อื่นเรียกว่าตัวชี้คู่ ในตัวอย่างนี้ptr2เป็นตัวชี้ดับเบิล
ค่าจากแผนภาพด้านบน:
Address of variable num has : 1000
Address of Pointer ptr1 is: 2000
Address of Pointer ptr2 is: 3000
ตัวอย่าง:
#include <stdio.h>
int main ()
{
int num = 10;
int *ptr1;
int **ptr2;
// Take the address of var
ptr1 = #
// Take the address of ptr1 using address of operator &
ptr2 = &ptr1;
// Print the value
printf("Value of num = %d\n", num );
printf("Value available at *ptr1 = %d\n", *ptr1 );
printf("Value available at **ptr2 = %d\n", **ptr2);
}
เอาท์พุท:
Value of num = 10
Value available at *ptr1 = 10
Value available at **ptr2 = 10
มันเป็นตัวชี้ไปยังค่าที่อยู่ของตัวชี้ (ฉันรู้ว่าแย่มาก)
โดยพื้นฐานแล้วมันช่วยให้คุณสามารถส่งตัวชี้ไปยังค่าที่อยู่ของตัวชี้อื่นดังนั้นคุณสามารถปรับเปลี่ยนตำแหน่งตัวชี้อื่นที่ชี้จากฟังก์ชั่นย่อยเช่น:
void changeptr(int** pp)
{
*pp=&someval;
}
คุณมีตัวแปรที่มีที่อยู่ของบางอย่าง นั่นคือตัวชี้
จากนั้นคุณมีตัวแปรอื่นที่มีที่อยู่ของตัวแปรแรก นั่นคือตัวชี้ไปยังตัวชี้
ตัวชี้ไปยังตัวชี้คือดีตัวชี้ไปยังตัวชี้
ตัวอย่างความหมายที่สมบูรณ์ของ someType ** เป็นอาร์เรย์สองมิติ: คุณมีหนึ่งอาร์เรย์ที่เต็มไปด้วยพอยน์เตอร์ไปยังอาร์เรย์อื่นดังนั้นเมื่อคุณเขียน
dpointer [5] [6]
คุณเข้าถึงอาเรย์ที่มีพอยน์เตอร์ไปยังอาร์เรย์อื่นในตำแหน่งที่ 5 รับพอยน์เตอร์ (ให้ fpointer ชื่อของเขา) จากนั้นเข้าถึงอิลิเมนต์ที่ 6 ของอาเรย์ที่อ้างอิงกับอาเรย์นั้น (เช่น fpointer [6])
วิธีการทำงาน: มันเป็นตัวแปรที่สามารถเก็บตัวชี้อื่น
เมื่อใดที่คุณจะใช้: หลาย ๆ คนใช้หนึ่งในนั้นคือถ้าฟังก์ชันของคุณต้องการสร้างอาร์เรย์และส่งกลับไปยังผู้เรียก
//returns the array of roll nos {11, 12} through paramater
// return value is total number of students
int fun( int **i )
{
int *j;
*i = (int*)malloc ( 2*sizeof(int) );
**i = 11; // e.g., newly allocated memory 0x2000 store 11
j = *i;
j++;
*j = 12; ; // e.g., newly allocated memory 0x2004 store 12
return 2;
}
int main()
{
int *i;
int n = fun( &i ); // hey I don't know how many students are in your class please send all of their roll numbers.
for ( int j=0; j<n; j++ )
printf( "roll no = %d \n", i[j] );
return 0;
}
มีคำอธิบายที่เป็นประโยชน์มากมาย แต่ฉันไม่พบเพียงคำอธิบายสั้น ๆ ดังนั้น ..
ตัวชี้โดยทั่วไปคือที่อยู่ของตัวแปร รหัสสรุปสั้น ๆ :
int a, *p_a;//declaration of normal variable and int pointer variable
a = 56; //simply assign value
p_a = &a; //save address of "a" to pointer variable
*p_a = 15; //override the value of the variable
//print 0xfoo and 15
//- first is address, 2nd is value stored at this address (that is called dereference)
printf("pointer p_a is having value %d and targeting at variable value %d", p_a, *p_a);
นอกจากนี้ยังมีข้อมูลที่เป็นประโยชน์สามารถพบได้ในหัวข้อสิ่งที่หมายถึงการอ้างอิงและการอ้างอิง
และฉันก็ไม่แน่ใจเหมือนกันว่าเมื่อไรที่จะเป็นประโยชน์กับพอยน์เตอร์ แต่โดยทั่วไปคุณจำเป็นต้องใช้มันเมื่อคุณทำการจัดสรรหน่วยความจำด้วยตนเอง / แบบไดนามิก - malloc, calloc ฯลฯ
ดังนั้นฉันหวังว่ามันจะช่วยในการชี้แจงปัญหา :)