C – Pointers พอยน์เตอร์ ภาษา C
ตัวชี้ในภาษา C นั้นง่ายและสนุกในการเรียนรู้ งานการเขียนโปรแกรม C บางงานสามารถทำได้ง่ายกว่าด้วยพอยน์เตอร์ และงานอื่นๆ เช่น การจัดสรรหน่วยความจำแบบไดนามิก ไม่สามารถทำได้โดยไม่ต้องใช้พอยน์เตอร์ ดังนั้นจึงจำเป็นต้องเรียนรู้คำแนะนำเพื่อเป็นโปรแกรมเมอร์ C ที่สมบูรณ์แบบ มาเริ่มเรียนรู้กันเลยในขั้นตอนที่ง่ายและสะดวก
ดังที่คุณทราบ ทุกตัวแปรคือตำแหน่งหน่วยความจำ และทุกตำแหน่งหน่วยความจำมีที่อยู่ที่กำหนดไว้ ซึ่งสามารถเข้าถึงได้โดยใช้ตัวดำเนินการเครื่องหมายและ (&) ซึ่งหมายถึงที่อยู่ในหน่วยความจำ ลองพิจารณาตัวอย่างต่อไปนี้ ซึ่งพิมพ์ที่อยู่ของตัวแปรที่กำหนดไว้ −
#include <stdio.h>
int main () {
int var1;
char var2[10];
printf("Address of var1 variable: %x\n", &var1 );
printf("Address of var2 variable: %x\n", &var2 );
return 0;
}
เมื่อโค้ดด้านบนถูกคอมไพล์และรัน มันจะให้ผลลัพธ์ดังต่อไปนี้ −
Address of var1 variable: 62fe1c
Address of var2 variable: 62fe10
พอยน์เตอร์คืออะไร?
พอยน์เตอร์ เป็นตัวแปรที่มีค่าที่อยู่ของตัวแปรอื่นเช่นที่อยู่ตรงกับที่ตั้งหน่วยความจำ เช่นเดียวกับตัวแปรหรือค่าคงที่ใดๆ คุณต้องประกาศ พอยน์เตอร์ ก่อนที่จะใช้เพื่อเก็บที่อยู่ของตัวแปรใดๆ รูปแบบทั่วไปของการประกาศตัวแปรพอยน์เตอร์คือ −
type *var-name;
ในที่นี้ type คือประเภทพื้นฐานของพอยน์เตอร์ ต้องเป็นชนิดข้อมูล C ที่ถูกต้อง และ var-name เป็นชื่อของตัวแปรพอยน์เตอร์ เครื่องหมายดอกจัน * ที่ใช้ประกาศตัวชี้เป็นเครื่องหมายดอกจันเดียวกับที่ใช้สำหรับการคูณ อย่างไรก็ตาม ในคำสั่งนี้ เครื่องหมายดอกจันถูกใช้เพื่อกำหนดตัวแปรเป็นพอยน์เตอร์ ดูการประกาศตัวชี้ที่ถูกต้องบางส่วน −
int *ip; /* pointer to an integer */
double *dp; /* pointer to a double */
float *fp; /* pointer to a float */
char *ch /* pointer to a character */
ชนิดข้อมูลจริงของค่าของพอยน์เตอร์ทั้งหมด ไม่ว่าจะเป็นจำนวนเต็ม ทศนิยม อักขระ หรืออย่างอื่น จะเหมือนกัน คือเป็นเลขฐานสิบหกแบบยาวที่แสดงที่อยู่หน่วยความจำ ความแตกต่างเพียงอย่างเดียวระหว่างพอยน์เตอร์ของชนิดข้อมูลที่แตกต่างกันคือชนิดข้อมูลของตัวแปรหรือค่าคงที่ที่ พอยน์เตอร์ ชี้ไป
วิธีการใช้พอยน์เตอร์?
มีการดำเนินการที่สำคัญสองสามอย่าง ซึ่งเราจะทำโดยใช้ตัวชี้บ่อยครั้งมาก (a) เรากำหนดตัวแปรตัวชี้ (b) กำหนดที่อยู่ของตัวแปรให้กับตัวชี้และ (c) ในที่สุดเข้าถึงค่าที่อยู่ที่มีอยู่ในตัวแปรตัวชี้ ทำได้โดยใช้ตัวดำเนินการเอกพจน์*ที่คืนค่าของตัวแปรที่อยู่ตามที่อยู่ที่ระบุโดยตัวถูกดำเนินการ ตัวอย่างต่อไปนี้ใช้การดำเนินการเหล่านี้ −
#include <stdio.h>
int main () {
int var = 20; /* actual variable declaration */
int *ip; /* pointer variable declaration */
ip = &var; /* store address of var in pointer variable*/
printf("Address of var variable: %x\n", &var );
/* address stored in pointer variable */
printf("Address stored in ip variable: %x\n", ip );
/* access the value using the pointer */
printf("Value of *ip variable: %d\n", *ip );
return 0;
}
เมื่อโค้ดด้านบนถูกคอมไพล์และรัน มันจะให้ผลลัพธ์ดังต่อไปนี้ −
Address of var variable: 62fe14
Address stored in ip variable: 62fe14
Value of *ip variable: 20
ตัวชี้ NULL
แนวทางปฏิบัติที่ดีในการกำหนดค่า NULL ให้กับตัวแปรพอยน์เตอร์ ถือเป็นแนวทางปฏิบัติที่ดีเสมอ ในกรณีที่คุณไม่มีที่อยู่ที่แน่นอนที่จะกำหนด นี้จะทำในเวลาของการประกาศตัวแปร ตัวชี้ที่กำหนดเป็น NULL เรียกว่าตัวชี้ค่า null
ตัวชี้ NULL เป็นค่าคงที่ที่มีค่า 0 ที่กำหนดไว้ในไลบรารีมาตรฐานหลายแห่ง พิจารณาโปรแกรมต่อไปนี้ −
#include <stdio.h>
int main () {
int *ptr = NULL;
printf("The value of ptr is : %x\n", ptr );
return 0;
}
เมื่อโค้ดด้านบนถูกคอมไพล์และรัน มันจะให้ผลลัพธ์ดังต่อไปนี้ −
The value of ptr is 0
ในระบบปฏิบัติการส่วนใหญ่ โปรแกรมไม่ได้รับอนุญาตให้เข้าถึงหน่วยความจำที่ที่อยู่ 0 เนื่องจากระบบปฏิบัติการสงวนหน่วยความจำนั้นไว้ อย่างไรก็ตาม ที่อยู่หน่วยความจำ 0 มีความสำคัญเป็นพิเศษ เป็นสัญญาณว่าตัวชี้ไม่ได้ตั้งใจให้ชี้ไปที่ตำแหน่งหน่วยความจำที่สามารถเข้าถึงได้ แต่ตามแบบแผน หากตัวชี้มีค่าว่าง (ศูนย์) จะถือว่าไม่ชี้ไปที่ค่าใดเลย
ในการตรวจสอบหาตัวชี้ null คุณสามารถใช้คำสั่ง ‘if’ ดังนี้ −
if(ptr) /* succeeds if p is not null */
if(!ptr) /* succeeds if p is null */
รายละเอียดพอยน์เตอร์
พอยน์เตอร์มีแนวคิดมากมายแต่ง่าย และมีความสำคัญต่อการเขียนโปรแกรม C แนวคิดตัวชี้ที่สำคัญต่อไปนี้ควรมีความชัดเจนสำหรับโปรแกรมเมอร์ C ทุกคน −
พอยน์เตอร์เลขคณิต
พอยน์เตอร์ ใน c คือที่อยู่ ซึ่งเป็นค่าตัวเลข ดังนั้น คุณสามารถดำเนินการทางคณิตศาสตร์บนพอยน์เตอร์ได้เช่นเดียวกับที่คุณทำกับค่าตัวเลข มีตัวดำเนินการเลขคณิตสี่ตัวที่สามารถใช้กับพอยน์เตอร์ได้: ++, –, + และ –
เพื่อให้เข้าใจเลขคณิตของพอยน์เตอร์ ให้เราพิจารณาว่า ptr เป็นตัวชี้จำนวนเต็มที่ชี้ไปยังที่อยู่ 1000 สมมติว่าเป็นจำนวนเต็ม 32 บิต ให้เราดำเนินการเลขคณิตต่อไปนี้บนตัวชี้:
ptr++
หลังจากการดำเนินการข้างต้น ptr จะชี้ไปที่ตำแหน่ง 1004 เนื่องจากทุกครั้งที่ ptr เพิ่มขึ้น ptr จะชี้ไปที่ตำแหน่งจำนวนเต็มถัดไปซึ่งมีขนาด 4 ไบต์ถัดจากตำแหน่งปัจจุบัน การดำเนินการนี้จะย้ายตัวชี้ไปยังตำแหน่งหน่วยความจำถัดไปโดยไม่กระทบต่อค่าจริงที่ตำแหน่งหน่วยความจำ หาก ptr ชี้ไปที่อักขระที่มีที่อยู่คือ 1000 การดำเนินการข้างต้นจะชี้ไปที่ตำแหน่ง 1001 เนื่องจากอักขระถัดไปจะอยู่ที่ 1001
การเพิ่มพอยน์เตอร์
เราชอบใช้ พอยน์เตอร์ ในโปรแกรมของเรามากกว่าในอาร์เรย์ เนื่องจากตัวชี้ตัวแปรสามารถเพิ่มได้ ซึ่งแตกต่างจากชื่ออาร์เรย์ที่ไม่สามารถเพิ่มได้เนื่องจากเป็น พอยน์เตอร์ แบบคงที่ โปรแกรมต่อไปนี้จะเพิ่มตัวชี้ตัวแปรเพื่อเข้าถึงแต่ละองค์ประกอบที่ประสบความสำเร็จของอาร์เรย์ −
#include <stdio.h>
const int MAX = 3;
int main () {
int var[] = {10, 100, 200};
int i, *ptr;
/* let us have array address in pointer */
ptr = var;
for ( i = 0; i < MAX; i++) {
printf("Address of var[%d] = %x\n", i, ptr );
printf("Value of var[%d] = %d\n", i, *ptr );
/* move to the next location */
ptr++;
}
return 0;
}
เมื่อโค้ดด้านบนถูกคอมไพล์และรัน มันจะให้ผลลัพธ์ดังต่อไปนี้ −
Address of var[0] = 62fe00
Value of var[0] = 10
Address of var[1] = 62fe04
Value of var[1] = 100
Address of var[2] = 62fe08
Value of var[2] = 200
การลดค่าพอยน์เตอร์
ข้อควรพิจารณาเดียวกันนี้ใช้กับการลดค่าพอยน์เตอร์ ซึ่งจะลดค่าของตัวชี้ด้วยจำนวนไบต์ของประเภทข้อมูลดังที่แสดงด้านล่าง:
#include <stdio.h>
const int MAX = 3;
int main () {
int var[] = {10, 100, 200};
int i, *ptr;
/* let us have array address in pointer */
ptr = &var[MAX-1];
for ( i = MAX; i > 0; i--) {
printf("Address of var[%d] = %x\n", i-1, ptr );
printf("Value of var[%d] = %d\n", i-1, *ptr );
/* move to the previous location */
ptr--;
}
return 0;
}
เมื่อโค้ดด้านบนถูกคอมไพล์และรัน มันจะให้ผลลัพธ์ดังต่อไปนี้ −
Address of var[2] = 62fe08
Value of var[2] = 200
Address of var[1] = 62fe04
Value of var[1] = 100
Address of var[0] = 62fe00
Value of var[0] = 10
การเปรียบเทียบตัวชี้
พอยน์เตอร์ อาจถูกเปรียบเทียบโดยใช้ตัวดำเนินการเชิงสัมพันธ์ เช่น ==, < และ > หาก p1 และ p2 ชี้ไปที่ตัวแปรที่เกี่ยวข้องกัน เช่น อิลิเมนต์ของอาร์เรย์เดียวกัน p1 และ p2 ก็สามารถเปรียบเทียบได้อย่างมีความหมาย
โปรแกรมต่อไปนี้แก้ไขตัวอย่างก่อนหน้านี้ – อย่างใดอย่างหนึ่งโดยการเพิ่มตัวชี้ตัวแปรตราบใดที่ที่อยู่ซึ่งชี้นั้นน้อยกว่าหรือเท่ากับที่อยู่ขององค์ประกอบสุดท้ายของอาร์เรย์ซึ่งก็คือ &var[MAX – 1]
#include <stdio.h>
const int MAX = 3;
int main () {
int var[] = {10, 100, 200};
int i, *ptr;
/* let us have array address in pointer */
ptr = &var[MAX-1];
for ( i = MAX; i > 0; i--) {
printf("Address of var[%d] = %x\n", i-1, ptr );
printf("Value of var[%d] = %d\n", i-1, *ptr );
/* move to the previous location */
ptr--;
}
return 0;
}
เมื่อโค้ดด้านบนถูกคอมไพล์และรัน มันจะให้ผลลัพธ์ดังต่อไปนี้ −
Address of var[2] = 62fe08
Value of var[2] = 200
Address of var[1] = 62fe04
Value of var[1] = 100
Address of var[0] = 62fe00
Value of var[0] = 10
อาร์เรย์ของพอยน์เตอร์
ก่อนที่เราจะเข้าใจแนวคิดของอาร์เรย์ของพอยน์เตอร์ ให้เราพิจารณาตัวอย่างต่อไปนี้ซึ่งใช้อาร์เรย์ของจำนวนเต็ม 3 ตัว:
#include <stdio.h>
const int MAX = 3;
int main () {
int var[] = {10, 100, 200};
int i;
for (i = 0; i < MAX; i++) {
printf("Value of var[%d] = %d\n", i, var[i] );
}
return 0;
}
เมื่อโค้ดด้านบนถูกคอมไพล์และรัน มันจะให้ผลลัพธ์ดังต่อไปนี้ −
Value of var[0] = 10
Value of var[1] = 100
Value of var[2] = 200
อาจมีสถานการณ์เมื่อเราต้องการรักษาอาร์เรย์ ซึ่งสามารถเก็บพอยน์เตอร์ไปยัง int หรือ char หรือประเภทข้อมูลอื่นๆ ที่มีอยู่ ต่อไปนี้เป็นการประกาศอาร์เรย์ของพอยน์เตอร์เป็นจำนวนเต็ม −
int *ptr[MAX];
มันประกาศ ptr เป็นอาร์เรย์ของพอยน์เตอร์จำนวนเต็ม MAX ดังนั้น แต่ละองค์ประกอบใน ptr จะถือตัวชี้ไปที่ค่า int ตัวอย่างต่อไปนี้ใช้จำนวนเต็ม 3 จำนวนซึ่งเก็บไว้ในอาร์เรย์ของพอยน์เตอร์ดังนี้ −
#include <stdio.h>
const int MAX = 3;
int main () {
int var[] = {10, 100, 200};
int i, *ptr[MAX];
for ( i = 0; i < MAX; i++) {
ptr[i] = &var[i]; /* assign the address of integer. */
}
for ( i = 0; i < MAX; i++) {
printf("Value of var[%d] = %d\n", i, *ptr[i] );
}
return 0;
}
เมื่อโค้ดด้านบนถูกคอมไพล์และรัน มันจะให้ผลลัพธ์ดังต่อไปนี้ −
Value of var[0] = 10
Value of var[1] = 100
Value of var[2] = 200
คุณสามารถใช้อาร์เรย์ของพอยน์เตอร์ ไปยังอักขระเพื่อจัดเก็บรายการสตริงได้ดังนี้ −
#include <stdio.h>
const int MAX = 4;
int main () {
char *names[] = {
"Zara Ali",
"Hina Ali",
"Nuha Ali",
"Sara Ali"
};
int i = 0;
for ( i = 0; i < MAX; i++) {
printf("Value of names[%d] = %s\n", i, names[i] );
}
return 0;
}
เมื่อโค้ดด้านบนถูกคอมไพล์และรัน มันจะให้ผลลัพธ์ดังต่อไปนี้ −
Value of names[0] = Zara Ali
Value of names[1] = Hina Ali
Value of names[2] = Nuha Ali
Value of names[3] = Sara Ali
Pointer to Pointer
พอยน์เตอร์ไปยังพอยน์เตอร์เป็นรูปแบบของทางอ้อมหลายทางหรือสายของ พอยน์เตอร์ โดยปกติ พอยน์เตอร์จะมีที่อยู่ของตัวแปร เมื่อเรากำหนดตัวชี้ไปยังพอยน์เตอร์ พอยน์เตอร์ตัวแรกจะมีที่อยู่ของพอยน์เตอร์ ตัวที่สอง ซึ่งชี้ไปยังตำแหน่งที่มีค่าจริงดังที่แสดงด้านล่าง
ตัวแปรที่เป็นตัวชี้ไปยัง พอยน์เตอร์ จะต้องประกาศเช่นนั้น ทำได้โดยวางเครื่องหมายดอกจันเพิ่มเติมหน้าชื่อ ตัวอย่างเช่น ประกาศต่อไปนี้ประกาศตัวชี้ไปยังตัวชี้ประเภท int −
int **var;
เมื่อค่าเป้าหมายถูกชี้ไปทางอ้อมโดยตัวชี้ไปยังตัวชี้ การเข้าถึงค่านั้นต้องใช้ตัวดำเนินการเครื่องหมายดอกจันสองครั้ง ดังแสดงในตัวอย่างด้านล่าง:
#include <stdio.h>
int main () {
int var;
int *ptr;
int **pptr;
var = 3000;
/* take the address of var */
ptr = &var;
/* take the address of ptr using address of operator & */
pptr = &ptr;
/* take the value using pptr */
printf("Value of var = %d\n", var );
printf("Value available at *ptr = %d\n", *ptr );
printf("Value available at **pptr = %d\n", **pptr);
return 0;
}
เมื่อโค้ดด้านบนถูกคอมไพล์และรัน มันจะให้ผลลัพธ์ดังต่อไปนี้ −
Value of var = 3000
Value available at *ptr = 3000
Value available at **pptr = 3000
การส่ง พอยน์เตอร์ไปยังฟังก์ชันใน C
การเขียนโปรแกรม C อนุญาตให้ส่ง พอยน์เตอร์ ไปยังฟังก์ชัน ในการดำเนินการดังกล่าว ให้ประกาศพารามิเตอร์ของฟังก์ชันเป็นประเภทตัวชี้
ต่อไปนี้คือตัวอย่างง่ายๆ ที่เราส่ง unsigned long pointer ไปยังฟังก์ชันและเปลี่ยนค่าภายในฟังก์ชันซึ่งสะท้อนกลับมาในฟังก์ชันการเรียก:
#include <stdio.h>
#include <time.h>
void getSeconds(unsigned long *par);
int main () {
unsigned long sec;
getSeconds( &sec );
/* print the actual value */
printf("Number of seconds: %ld\n", sec );
return 0;
}
void getSeconds(unsigned long *par) {
/* get the current number of seconds */
*par = time( NULL );
return;
}
เมื่อโค้ดด้านบนถูกคอมไพล์และรัน มันจะให้ผลลัพธ์ดังต่อไปนี้ −
Number of seconds: 1637062225
ฟังก์ชันซึ่งรับพอยน์เตอร์ ยังสามารถยอมรับอาร์เรย์ดังแสดงในตัวอย่างต่อไปนี้ −
#include <stdio.h>
/* function declaration */
double getAverage(int *arr, int size);
int main () {
/* an int array with 5 elements */
int balance[5] = {1000, 2, 3, 17, 50};
double avg;
/* pass pointer to the array as an argument */
avg = getAverage( balance, 5 ) ;
/* output the returned value */
printf("Average value is: %f\n", avg );
return 0;
}
double getAverage(int *arr, int size) {
int i, sum = 0;
double avg;
for (i = 0; i < size; ++i) {
sum += arr[i];
}
avg = (double)sum / size;
return avg;
}
เมื่อโค้ดข้างต้นถูกคอมไพล์และดำเนินการ โค้ดดังกล่าวจะทำให้เกิดผลลัพธ์ดังต่อไปนี้ −
Average value is: 214.400000
ส่งคืนพอยน์เตอร์จากฟังก์ชันใน C
เราได้เห็นแล้วว่าการเขียนโปรแกรม C อนุญาตให้ส่งคืนอาร์เรย์จากฟังก์ชันได้อย่างไรในบทที่แล้ว ในทำนองเดียวกัน C ยังอนุญาตให้ส่งคืน พอยน์เตอร์ จากฟังก์ชัน ในการทำเช่นนั้น คุณจะต้องประกาศฟังก์ชันที่ส่งคืน พอยน์เตอร์ ดังตัวอย่างต่อไปนี้ −
int * myFunction() {
.
.
.
}
จุดที่สองที่ต้องจำไว้คือ ไม่ควรส่งคืนที่อยู่ของตัวแปรโลคอลนอกฟังก์ชัน ดังนั้น คุณจะต้องกำหนดตัวแปรโลคอลเป็นตัวแปรสแตติก
ตอนนี้ ให้พิจารณาฟังก์ชันต่อไปนี้ซึ่งจะสร้างตัวเลขสุ่ม 10 ตัวและส่งกลับโดยใช้ชื่ออาร์เรย์ซึ่งแสดงถึงตัวชี้ กล่าวคือ ที่อยู่ขององค์ประกอบอาร์เรย์แรก
#include <stdio.h>
#include <time.h>
/* function to generate and return random numbers. */
int * getRandom( ) {
static int r[10];
int i;
/* set the seed */
srand( (unsigned)time( NULL ) );
for ( i = 0; i < 10; ++i) {
r[i] = rand();
printf("%d\n", r[i] );
}
return r;
}
/* main function to call above defined function */
int main () {
/* a pointer to an int */
int *p;
int i;
p = getRandom();
for ( i = 0; i < 10; i++ ) {
printf("*(p + [%d]) : %d\n", i, *(p + i) );
}
return 0;
}
เมื่อโค้ดข้างต้นถูกคอมไพล์และดำเนินการ โค้ดดังกล่าวจะทำให้เกิดผลลัพธ์ดังต่อไปนี้ −
22743
5361
163
3199
12097
6761
11103
23331
22033
28678
*(p + [0]) : 22743
*(p + [1]) : 5361
*(p + [2]) : 163
*(p + [3]) : 3199
*(p + [4]) : 12097
*(p + [5]) : 6761
*(p + [6]) : 11103
*(p + [7]) : 23331
*(p + [8]) : 22033
*(p + [9]) : 28678