pointer C picture

C — Pointer คืออะไร | ฉบับเด็กมหาลัย

มันคือตัวแปรที่เก็บแค่ที่ตำแหน่งของตัวแปรเพียงเท่านั้น

Piravit Chenpittaya
4 min readOct 18, 2020

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; (address at 2100)

ถ้ายังนึกภาพไม่ออกลองจิตนาการขึ้นมาว่าตัวแปรของเราเป็นกล่อง เช่น 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 เก็บตำแหน่งของตัวนั้นไว้)(ชี้)

  1. ประกาศ integer x=1, pointer ptr1 และ ptr2
  2. ptr1 เก็บค่าตำแหน่งของ x (ชี้ไปที่ตำแหน่งของ x )
  3. ptr2 เก็บค่าตำแหน่งที่ ptr1 เก็บไว้ (เส้นสีเขียว) แต่ ptr1 เก็บค่าตำแหน่งของ x ทำให้ ptr2 ก็เก็บค่าของ x ด้วย (เส้นสีน้ำตาล)
    หรือ ถ้าอธิบายในรูปแบบการชี้ก็จะเป็น ptr2 ชี้ตำแหน่งเดียวกับ ptr1ซึ่ง ptr1 ชี้ไปตำแหน่งของ x ทำให้ ptr2 ก็ชี้ไปตำแหน่งของ x ด้วย
  1. แก้ค่าที่ ptr1 ชี้ไว้ให้เป็น 7 (โดยค่าที่ ptr1 เก็บไว้คือตำแหน่งของตัวแปร x แล้วเมื่อใช้ Star (*) จะเป็นการ Dereference ทำให้เข้าถึงค่านั้นๆได้) (write to 7)
  2. 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

--

--

Piravit Chenpittaya
Piravit Chenpittaya

Written by Piravit Chenpittaya

call me karn | Computer of Engineering : PSU | IG: karn.svg | git: https://github.com/karnzx /

Responses (1)