C — Pointer คืออะไร | ฉบับเด็กมหาลัย
มันคือตัวแปรที่เก็บแค่ที่ตำแหน่งของตัวแปรเพียงเท่านั้น
A pointer is a variable whose value is the address of another variable.
Pointer คือ ตัวแปรที่เก็บตำแหน่งของข้อมูลในหน่วยความจำ
ไม่รู้ทำไม่รุ้สึกว่าภาษาอังกฤษมันเข้าใจง่ายกว่าไทยเยอะเลย ¯\_(ツ)_/¯
แต่ pointer มีความสามารถในการเข้าถึงข้อมูลจากตำแหน่งนั้นได้ไงละ
— การชี้ในที่นี้เปรียบเสมือนกับ การที่ pointer เก็บค่าตำแหน่งของตัวแปรนั้นๆไว้ เลยเหมือนกับการที่ pointer กำลังชี้ไปยังข้อมูลตรงนั้น แปลว่าถ้าชี้ไปที่ number แปลว่า pointer เก็บค่าตำแหน่งของ number ไว้int *ptr = &number;
ซึ่งอ่านแล้วอาจจะงงได้ ถ้างงกลับดูความหมายของมันตรงนี้อีกทีนะครับ
มันดียังไง?
คือ pointer ช่วยทำให้ performance ของโปรแกรมดีขึ้นในงานบางอย่างถ้าเราใช้ pointer จะทำได้ง่ายกว่าเร็วกว่า หรือการส่งค่าไปใน function ปกติจะแทนลงไปเลยซึ่งมันจะเป็นการก็อปค่าเพื่อสร้างตัวแปรที่มีค่าเท่ากันแล้วนำไปใช้ในฟังก์ชั่นอีกที แต่ pointer จะเป็นการเอาค่าที่ตำแหน่งนั้นๆมาใช้เลย ลดการสร้างค่าที่สิ้นเปลือง และเพิ่มประสิทธิภาพ หรือการที่เราจะจองที่อยู่ของหน่วยความจำให้ค่าๆหนึ่ง (memory allocation) ก็จำเป็นต้องใช้ Pointer เท่านั้น
จากนิยามด้านบนการแทนค่าให้ pointer เราจะให้ค่าด้วยตำแหน่ง (address) เท่านั้น
ตำแหน่งคืออะไรยังไงนะ?
ถ้ายังนึกภาพไม่ออกลองจิตนาการขึ้นมาว่าตัวแปรของเราเป็นกล่อง เช่น int number = 50;
หมายความว่าเรามีกล่องชื่อว่า number ชนิด Integer ที่มีข้อมูลเท่ากับ 50 อยู่ที่ตำแหน่ง 2100 ในหน่วยความจำ หรืออาจเป็นอย่างอื่นเช่น 0x1204 ก็ได้เพราะตำแหน่งนี้คอมเป็นตัวจัดการให้เราเอง ซึ่งทุกตัวแปรจะมีตำแหน่งของมันไว้ใช้อ้างอิงเพื่อเข้าถึงข้อมูลอีกทีนั้นเอง
— ที่นี้เราจะรู้หรือหยิบตำแหน่งของข้อมูลนั้นออกมาได้ไง ?
โดยการใช้สัญลักษณ์ ampersand (&) ไงละ! ถ้าลอง
int number = 50;
printf("address of number is : %d ", &number);
ก็ได้จะตำแหน่งของ ตัวแปร number ออกมานั้งเอง
address of number is : 2100
หรือถ้าใช้ format “%x” จะได้ออกมาประมาณ 0x1204
** คือไม่ต้องสนตำแหน่งของตำแปรมากนะให้คอมจัดการเถอะ
การประกาศตัวแปร pointer
แน่นอนก่อนใช้ก็ต้องประกาศตัวแปรก่อน ซึ่งรูปแบบของมันก็คือการใส่ Star ( * ) เติมเข้าไป ก็จะเป็น pointer of type เลย เรียกว่า การDeclaration
type *var-name;
ก็จะเป็น pointer ที่เก็บค่าตำแหน่งของ type นั้นๆ
ตัวอย่าง การประกาศ Pointer
int *a; // a คือ pointer
int* b,c // ทั้ง b และ c คือ pointers
int d, *e // แค่ e ที่เป็น pointer
char *p // p เป็น pointer ที่ชี้ไปยังตัวแปร char
แต่เมื่อต้องการใช้ก็ควรจะแทนค่าให้มันก่อนนะ หรือก็ควรตั้ง default เป็น NULL
การเข้าถึงข้อมูลของตำแหน่ง หรือตัวที่ pointer ชี้อยู่ Dereference
— แล้วเราจะแก้หรือหยิบข้อมูลที่มันเก็บตำแหน่งข้อมูลนั้นไว้ได้ยังไง?
โดยการใช้สัญลักษณ์ Star ( * ) เหมือนการการประกาศตัวแปรเลยใส่ไว้ข้างหน้า ซึ่งเราจะเรียนมันว่า การDereference คือการเข้าถึงค่าของตำแหน่งนั้น หรือก็คือ data ที่อยู่ในกล่องจากรูป (read)
และถ้าใช้กับการ แทนค่า จะเป็นการเขียนทับข้อมูลที่ตัวแปรนั้นเลย (write)
note < & กับ * > จะเหมือน หยินหยาง คืออยู่ด้วยกันจะหักล้างกันอย่างเช่น&*x มีค่าเท่ากับ x
int x = 1, *ptr1, *ptr2;// Declaration
ptr1 = &x; // write
ptr2 = ptr1; //read pointer เก็บตำแหน่งอยู่แล้วแทนตรงได้เลยไม่ต้องใช้ *&
*ptr1 = 7; // Dereference to write
printf("ptr1 : %d\n", *ptr1); // Dereference to read
printf("ptr2 : %d", *ptr2); // Dereference to read---OUTPUT---ptr1 : 7
ptr2 : 7
อธิบายตามแต่ละบรรทัด
(เส้นสีน้ำตาลคือเส้นที่ ptr2 เก็บตำแหน่งของตัวนั้นไว้)(ชี้)
- ประกาศ integer x=1, pointer ptr1 และ ptr2
- ptr1 เก็บค่าตำแหน่งของ x (ชี้ไปที่ตำแหน่งของ x )
- ptr2 เก็บค่าตำแหน่งที่ ptr1 เก็บไว้ (เส้นสีเขียว) แต่ ptr1 เก็บค่าตำแหน่งของ x ทำให้ ptr2 ก็เก็บค่าของ x ด้วย (เส้นสีน้ำตาล)
หรือ ถ้าอธิบายในรูปแบบการชี้ก็จะเป็น ptr2 ชี้ตำแหน่งเดียวกับ ptr1ซึ่ง ptr1 ชี้ไปตำแหน่งของ x ทำให้ ptr2 ก็ชี้ไปตำแหน่งของ x ด้วย
- แก้ค่าที่ ptr1 ชี้ไว้ให้เป็น 7 (โดยค่าที่ ptr1 เก็บไว้คือตำแหน่งของตัวแปร x แล้วเมื่อใช้ Star (*) จะเป็นการ Dereference ทำให้เข้าถึงค่านั้นๆได้) (write to 7)
- 6. (บรรทัด printf) ปริ้นออกมาค่าของตำแหน่งที่ ptr1, ptr2 ออกมาโดยการ Dereference (read)
ก่อนไปต่อ ขออธิบายก่อนว่าการใช้ * จะทำให้เราเข้าถึงข้อมูลที่ตำแหน่งนั้นได้ เหมือนกับที่ *ptr1 = 7;
คือแก้ข้อมูลที่ตำแหน่งที่ ptr1 เก็บอยู่ให้เป็น 7 หรือถ้าใช้
*ptr1 += 1;
ก็จะเป็นการเพิ่มค่าให้ข้อมูลตรงนั้นกลายเป็น 8 นั้นเอง
แต่ถ้าเราเอา * ออก เช่น ptr1 += 1;
มันจะเป็นการเลื่อนตำแหน่งของหน่วยความจำแทนจากสมมติตอนแรก 2000 จะกลายเป็น 2001 ซึ่งจะคนละความหมายกับที่ใส่ * เลยเพราะจำไว้ว่า pointer เก็บตำแหน่งของข้อมูลในหน่วยความจำเท่านั้น ความสามารถตรงนี้เราสามารถเอามาใช้กับ array ได้โดยการเลื่อนนั้นเอง แบบ i++;
Array กับ Pointer
จริงๆแล้ว array นี่ก็เป็น pointer ชนิดหนึ่งนะเป็น constant pointer แปลว่า pointerที่คงที่
— คงที่ยังไง?
คือมันจะเป็น pointer ที่ชื่อที่ชี้อยู่หัวของอาเรย์เสมอ!
แบบว่าสมมติสร้างอาเรย์ขึ้นมาตัวหนึ่งที่มีขนาดเท่ากับ 5 int a[5];
ซึ่งปกติอาเรย์จะต่อกันแปลว่าตำแหน่งของมันก็จะต่อกันด้วย
ปกติเราจะเข้าถึงข้อมูลตัวแรกเราก็ใช้ a[0] แต่ถ้าใช้ *a ก็จะได้ค่า 0 เท่ากันเลย!
แล้วหากเราต้องการตัวที่สาม ก็แค่เลื่อนตำแหน่งไปอีกสองคือ a[2] หรือ *(a+2)
เพราะ a คือตำแหน่งที่ 2000 ถ้า *a คือ Dereference ตำแหน่งของ 2000 ก็จะได้ค่า 0
หรือถ้าบวกไปสองเป็น 2002 ใส่ * ก็จะได้ค่าของตำแหน่งที่ 2002 หรือก็คือ 2 นั้นเอง
หรือถ้าจะเลื่อนตำแหน่งธรรมดาแบบ i++;
เราเลื่อน pointer ที่ชี้ที่อาเรย์เลยก็ได้โดย a++;
แต่ควรสร้าง pointer เพื่อเป็นตัวเลื่อนจะดีกว่าเพราะ ตัวของอาเรย์เป็น constant pointer ดังนั้นมันควรจะอยู่ที่หัวของอาเรย์เสมอ
— ว่ากันว่าระหว่างการเลื่อนแบบ a[i] i++;
(array indices) กับใช้รูปแบบ pointer a++;
การที่เราใช้รูปแบบของ pointer มันจะเร็วกว่าเพราะสุดท้าย complier จะแปลงโค้ดให้อยู่รูปแบบของ machine language ซึ่ง pointer ใกล้เคียงมากกว่าเลยจะทำให้ทำงานได้เร็วกว่า
— ตัวอย่างการใช้ pointer เพื่อผลรวมของอาเรย์
int sum(int *a, int *b){
int total = 0;
while(a <= b){
total += *a;
a++;
}
return total;
}
ให้ a เป็นหัวของอาเรย์, b เป็นท้ายของอาเรย์ sum(&a, &(a + len(a) — 1));
วนลูปเพิ่มค่าให้ total โดยการ dereferenceแล้วเลื่อน a ไปทำแบบนี้ไปเรื่อยๆจนถึง b
Pointer to Structure
สมมติ structure point เก็บค่า x, y ตัวอย่างส่วนของโค้ดตามด้านล่าง
struct point{
int x;
int y;
};
typedef struct point Point;
main(){
Point p;
Point *ptr = &p;
...
}
ซึ่งถ้าเราจะเข้า x, y ถึงแบบปกติจาก p ก็จะเป็น p.x
และ p.y
แต่ถ้าเป้น ptr ที่เป็น pointer เราจำเป็นต้องทำการ Dereference ก่อนหมายความว่ามันจะกลายเป็น (*ptr).x
และ (*ptr).y
{ใส่วงเล็บด้วยป้องกันไม่ให้ทำเข้าถึงข้อมูลด้วย dot (.) ก่อน Dereference (*)}
— Special operator “->”
แต่ดู ๆ แล้วมันดูยากดังนั้นเราจะใช้สัญลักษณ์พิเศษเพื่อเข้าถึงแทนนั้นก็คือ “->”
กลายเป็น ptr->x
กับ ptr->y
แทน
ผมหวังว่าผู้ที่หลงเข้ามาอ่านจะเข้าใจใน pointer มากขึ้นนะครับ
ref: ข้อมูลจากวิชาเรียน PROGRAM & DATA STRUCTURES