มือให้หัดโค้ด เวลาไป search ใน internet เกี่ยวกับ Pointers ในภาษาโปรแกรมมิ่ง เราจะเจอแต่ความ abstract ดูแล้วตาลายไปหมดเลย เลื่อน ๆ ไปแล้วแล้วก็ไม่ได้เข้าใจมากขึ้นเท่าไร แต่ไม่เป็นไร วันนี้ผมจะมาเล่าให้ฟังแบบคนธรรมดาคุยกันครับ
Memory Address
ใน memory ของคอมพิวเตอร์เรา ประกอบด้วย block ซึ่งเราจะแทนมันด้วยถัง ถังเยอะ ๆ เลย แต่ละถังจะแปะเลข address เอาไว้อยู่ เอาไว้ใช้ระบุความแตกต่าง และแต่ละถังเก็บ value ได้ก้อนเดียว
เวลาเราสร้างตัวแปรแล้วใส่ value ให้มัน จะเหมือนเราเอา value ไปเก็บลงในถัง แล้วจดไว้ว่า ถ้าอยากได้ค่าของตัวแปรชื่อนี้ ต้องไปหาที่ถังเลขนี้นะ
ตัวอย่างเช่น เวลาประกาศ i = 1
มันก็จะไปหยิบถังมาใบนึง ใส่เลข 1 เข้าไป แล้วก็จดใส่กระดาษไว้ว่า ค่าของตัวแปรชื่อ i
จะเก็บไว้กับถังที่มี address นี้นะ
เวลาเราอยากได้ค่าจากตัวแปร i
เราก็จะหยิบกระดาษมาดู ว่าเราจะต้องไปหาถังเลขไหน แล้วหยิบ value ในถังมาดูเลย เช่น ถ้าตั้งไว้ว่า i = 1
เวลา print i
ออกมา ก็ได้ค่าเป็น 1 อันนี้ปกติ
เหมือนกันแต่ไม่ใช่
ถ้าสมมติเราเขียนว่า j = i
แล้วเอามาเทียบ value กัน มันจะบอกว่าค่าเท่ากัน แต่ถ้าถามว่ามันคือตัวเดียวกันไหม อันนี้ไม่ใช่
เพราะสิ่งที่เกิดขึ้นเมื่อประกาศตัวแปร j
คือ หยิบค่าในถัง i
มาดูว่ามันเป็นเท่าไร แล้วจำค่านั้นไว้ เอามาใส่ในถังใหม่อีกถังนึง
ทำให้ตอนนี้เรามี 2 ถัง ที่ของข้างในหน้าตาเหมือนกัน ถามว่า i
“เท่ากับ” j
ไหม ใช่
แต่ว่าใน 2 ถังนี้ มันกำลังหมายถึง ของก้อนเดียวกันไหม ไม่ ถ้าเราแก้ค่าของในถัง i
ค่าในถัง j
จะไม่เปลี่ยนตาม
ถ้าอยากให้เรียกถัง j
แล้วสามารถพาไปดูของในถัง i
ได้ ต้องทำยังไงดี งั้นเราต้องใช้ address เพื่อให้มันนำทางไปหาถังที่เก็บค่าของ i
ให้ได้
สิ่งนี้แหละ คือ pointer
Pointers
Pointers เป็นตัวแปรที่จะเก็บ address ใน memory ในที่นี้คือ เลขบนถัง แทนที่ปกติจะเก็บ value ทั่วไป
พอเรารู้ address แล้ว มันก็พาเราไปหาถังที่เราต้องการ และหยิบของข้างในดูได้ ว่ามันเก็บ value อะไรไว้
ที่เราต้องทำก็แค่ไปหา address ของตัวแปร i
ออกมาใส่ใน j
ให้ได้ แล้วทีนี้ทำยังไง
ถ้าในภาษา C หรือ Golang เราใส่ & เติมไปข้างหน้าตัวแปรได้เลย จะได้ค่า address บน memory ของตัวแปรนั้น ๆ มา อย่างเช่น j = &i
ทีนี้ตัวแปร j
จะแปลงร่างเป็น pointer เรียบร้อย พร้อมเอาไปใช้งาน ถ้า print ออกมา จะได้ค่าเป็นเลข address เลย
ตอนนี้เราหาถังเจอแล้ว แล้วทำยังไงถึงจะหยิบของในถังออกมาดูได้ เราสามารถทำได้โดยใส่ * ด้านหน้า pointer จะได้เป็น *j
พอเอาไป print เราจะได้ของที่เก็บไว้ใน i
แล้ว เย่
ด้วยสิ่งนี้ เวลาเราแก้ไข value ผ่าน pointer j
ค่าในตัวแปร i
จะเปลี่ยนไปโดยปริยาย เพราะทุกคนกำลังมองมาที่ถังเดียวกัน
ทำทำไม?
แล้วทำไมต้องทำขนาดนี้ด้วยล่ะ ถ้าอยากเรียกใช้ หรือแก้ไขค่าใน i
เราไม่เรียกตรง ๆ เลยไม่ดีกว่าเหรอ
มันจะมีบางเคส ที่เราเรียกตรง ๆ ไม่ได้ เช่น ตัวแปรเรามันเป็นตัวแปรแบบ global scope แล้วเราเรียก function ที่จะให้มันไปแก้ไขค่าในตัวแปร ถ้าเราโยนตัวแปรจากข้างนอก เข้าไปแบบปกติตรง ๆ เลย มันก็จะแก้ไขแค่ใน function scope แต่ตัวที่อยู่ข้างนอก ไม่รับรู้อะไรด้วย
เพราะเราไม่ได้ไปแก้ถังนั้น มันสร้างถังใหม่ตั้งแต่ตอนเราโยนตัวแปรเข้าไปใน function แล้ว
เราเลยต้องโยน address ของตัวแปรข้างนอกเข้าไปแทน เพื่อบอกให้ function รู้ ว่านายต้องไปแก้ไขค่าที่ถังนี้นะ
เอาแบบเห็นเป็นโค้ดเลย จะได้แบบนี้ (ผมดัดแปลงมาจากโค้ดของ BornToDev นะครับ)
void addFiveByValue(int number)
{
number += 5;
}
void addFiveByPointer(int * number)
{
* number += 5;
}
int main()
{
int myNum = 1;
int * myPointer = &myNum; // หยิบ address ของ myNum มาเก็บไว้ที่ myPointer
addFiveByValue(myNum); // เรียก function ที่ใช้ตัวแปรปกติ
printf("myNum (not using pointer): %d\n", myNum);
// myNum (not using pointer): 1
addFiveByPointer(myPointer); // เรียก function ที่ใช้ pointer
printf("myNum (using pointer): %d\n", myNum);
// myNum (using pointer): 6
return 0;
}
อย่าสับสน
สิ่งหนึ่งที่ทำให้ผมสับสนเรื่อง pointer โดยไม่จำเป็น คือ หน้าตาของ type มันนี่แหละ เพราะถ้าเราจะประกาศ pointer ที่จะพาเราไปหาค่า integer ถ้าเป็นภาษา C จะเขียนว่า int *
ดูแค่นี้จะไม่ค่อยสับสน แต่ถ้าเป็นหน้าตาเต็ม ๆ มันจะเป็นแบบนี้ เขียนได้ 3 แบบ
int * j;
int* j;
int *j;
ตัวที่พาผมงง คือ int *j
เพราะหน้าตามันเหมือนเวลาเรากำลังหยิบ value ออกมาจาก address แต่เอ๊ะตอนนี้เราเพิ่งประกาศตัวแปรนะ มันใช้ยังไงกันแน่เนี่ย
ถีงหน้าตาจะเหมือนกัน แต่เวลาเราเขียน อยากให้ดูดี ๆ ว่าเรากำลังประกาศตัวแปรอยู่ หรือกำลังไปหยิบ value ออกมา จะได้ไม่สับสนแบบผมครับ
จบแล้ว
Pointer จะเป็นเรื่องแรก ๆ เลย ที่เราจะได้เรียนเวลาศึกษาภาษา C และด้วยความที่มันลงไประดับ low level มาก ๆ ไปถึง memory เลย จึงอาจจะยาก และ mind blow ไปมาก ๆ สำหรับมือใหม่ รวมถึงผมตอนที่กำลังหัดด้วย
คิดว่าน่าจะดี ถ้าเราสามารถเล่าสิ่งนี้เป็นด้วยภาษามนุษย์คุยกัน เลยอยากลองเอามาเล่าให้ทุกคนฟังครับ ไม่รู้ว่าจะเข้าใจขึ้น หรืองงกว่าเดิม ฮ่าาาา