บทที่ 7 ตัวแปรพอยเตอร์(Pointers)

ตัวแปรพอยเตอร์ (Pointers) 

Pointer คือตัวแปรดัชนีที่ เก็บค่าตำแหน่งแอดเดรสของหน่วยความจำ ซึ่งตัวแปรพอยเตอร์นั้น จะมีเครื่องหมายดอกจันทร์ (*) นำหน้าเสมอ ดังตัวอย่างต่อไปนี้

int *Num;
float *GreatNum;
char *Name;

ตัวแปรพอยเตอร์มีประโยชน์ในการลดปริมาณหน่วยความจำที่ต้องใช้ในการเขียนโปรแกรม โดยการส่งข้อมูลในรูปพอยเตอร์ เข้าไปในฟังก์ชันที่โปรแกรมเรียกพร้อมกันหลายฟังก์ชัน แทนการส่งข้อมูลในรูปตัวแปรธรรมดา ซึ่งต้องใช้ตัวแปรหลายตัว
ตัวแปรพอยเตอร์มีลักษณะคล้ายตัวแปรตารางอาเรย์ แต่ที่แตกต่างกันคือ ตัวแปรตารางอาเรย์จะเก็บเฉพาะค่าต่างๆ ที่เป็นชนิดกันเดียวกับตัวแปรอาเรย์แต่ ตัวแปรพอยเตอร์จะเก็บเฉพาะค่าตำแหน่ง Address ตัวแปรเท่านั้น โดยไม่ได้มีการจัดเตรียมหน่วยความจำแบบไดนามิกส์ (Dynamic Memory Allocation) ไว้
การเข้าถึงตำแหน่งแอดเดรสทำได้โดย ใช้เครื่องหมายแอมเปอร์แซนด์ (&) ซึ่งจะแสดงให้เห็นดังตัวอย่างต่อไปนี้ ในที่นี้กำหนดให้ตัวแปร Andy อยู่ในตำแหน่ง Address ที่ 1776 ดังตังอย่างในภาพที่ 6.1

5533

Andy = 25; // Assigning Value 25 to Andy
Fred = Andy; // Assigning Value 25 (Value of Andy) to Fred
Ted = &Andy // Assigning Value 1776 (Address of Andy) to Ted via &

อย่างไรก็ดี ถ้าต้องการให้ตัวแปร Ted สามารถ ดึงค่า จากตัวแปร Andy ได้ ให้ ใช้สัญลักษณ์ ตัวอ้างอิงโดยอ้อม (Indirect operand) ซึ่งเป็นนำเครื่องหมายดอกจันทร์ (*) นำหน้าตัวแปรที่เก็บค่าแอดเดรสตัวแปร ดังตัวอย่างในภาพที่ 6.2

5534
Beth = *Ted // Beth มีค่าเท่ากับ ค่าที่ตัวแปร Ted ชี้ซึ่งเท่ากับ 25
ตัวพอยเตอร์จะใช้กับตัวแปรชนิดใดก็ได้โดยที่ ตัวแปรพอยเตอร์ดังกล่าวจะได้รับการกำหนดหน่วยความจำตามลักษณะชนิดตัวแปรด้วย เช่น ตัวแปรพอยเตอร์ประเภท int จะได้รับการกำหนดหน่วยความจำไว้ที่ 4 ไบต์
ตัวอย่างการกำหนด ค่า Pointer จะแสดงให้เห็นดังนี้ โดยผลการกำหนดค่าจะแสดงให้เห็นในภาพที่ 6.3 สังเกตให้ดีจะพบว่า ตำแหน่งแอดเดรสหน่วยความจำเลื่อนไป 4ไบท์ ระหว่างตัวแปร j กับ k และ ตัวแปร k กับ ptr

5535

จากตัวแปรพอยเตอร์สู่ตัวแปรอาเรย์ (Pointer to Array) 

ตัวแปรอาเรย์มีความเกี่ยวข้องสัมพันธ์กับตัวแปรพอยเตอร์คือ ชื่อตัวแปรอาเรย์ (Array identifier) จะมีค่าเทียบได้กับตำแหน่งแอดเดรสของตารางช่องแรกซึ่งเทียบได้กับตำแหน่งแรก ตัวแปรพอยเตอร์ จะมีค่าเท่ากับแอดเดรสของตัวแปรแรกที่ตัวแปรพอยเตอร์ชี้ ดังตัวอย่างต่อไปนี้

int Num[20];	int *Ptr;

	การกำหนดค่าต่อไปนี้ถือว่าใช้ได้เหมือนกัน
	Ptr = Num;	มีลักษณะเท่าเทียมกับ	Ptr = &Num[0];

ความแตกต่างจะเกิดขึ้นเมื่อ มีการกำหนดค่าตัวต่อไป โดยที่เราสามารถกำหนดค่าแอดเดรสใหม่ให้ตัวแปร Ptr ได้ทันที ขณะที่ ตัวแปร Num จะชี้ไปที่ตำแหน่งแรกของตัวแปรจำนวนเต็ม ซึ่งมี 20 ตำแหน่งเสมอ ดังนั้น ตัวแปร Ptr จึงเป็นตัวแปรพอยเตอร์ที่เปลี่ยนตำแหน่งการชี้ได้ (Variable Pointer) ขณะที่ตัวแปร Num หรือชื่อตัวแปรอาเรย์เป็นตัวแปรพอยเตอร์ที่ตำแหน่งการชี้คงที่ตายตัว (Constant pointer) ดังตัวอย่างต่อไปนี้

int Num[20];	int *y;	int *Ptr;
	Ptr = Num;
	*(Ptr+1)	จะแสดงค่า Num[1];
	*(Ptr+i)	จะแสดงค่า Num[i];
	y = &Num[0];
	y++;		// ตัวแปรนี้จะชี้ไปที่ Num[1] เท่านั้น

ตัวอย่างต่อไปนี้แสดงให้เห็นถึงการ ใช้ตัวแปรพอยเตอร์ชี้ไปที่ตัวแปรตารางอาเรย์ ซึ่งผลที่ได้จะแสดงให้เห็นในภาพที่ 6.4

5536

ให้ผู้อ่านลองเปลี่ยนคำสั่งในบรรทัด B เป็น printf(“ptr + d = %dn“”, i.*ptr++); ก่อนการทดสอบครั้งต่อไป เพื่อดูว่าโปรแกรมจะให้ผลออกออกมาผิดจากโปรแกรมเดิม เมื่อทดสอบแล้ว ให้ ลองแก้บรรทัด B อีกครั้งโดยแก้เป็น printf(“ptr + d = %dn“”, i.*(++ptr));

ในการกำหนดค่าให้ตารางอาเรย์แต่ละช่องโดยใช้ตัวแปรพอยเตอร์ นั้นทำได้โดยการใช้วงเล็บครอบตัวแปรอาเรย์ที่ตามด้วยเครื่องหมายบวกและตัวเลขแสดงดัชนีพร้อมใช้เครื่องหมายดอกจันทร์ (*) นำหน้า เนื่องจากวิธีดังกล่าวจะเหมือนกับการใช้เครื่องหมาย [] และเลขดัชนี เพื่อเข้าถึงข้อมูลในตัวแปรอาเรย์ดังตัวอย่างต่อไปนี้

*(array+5) = 50 จะมีค่าเทียบเท่ากับ array[5] = 50;

ส่วนในการกำหนดและเปลี่ยนแปลงค่าโดยใช้ตัวแปรพอยเตอร์นั้นทำได้ดังโปรแกรมต่อไปนี้

void main(void)
{
	int i, j=1,k=2;
	int *ptr1, *ptr2;	// Declare both variables as integer pointers
	float value[100], result[100];
	float *ptr3, *ptr4;	// Declare both variables as integer pointers

	prt1 = &j;		// Defining ptr1 pointing to j	
ptr2 = &k;		// Defining ptr2 pointing to k
	ptr3 = value;		// ptr3 contain the address for the 1st element of value
	ptr4 = &value[0];	//  ptr4 contain the address for the 1st element of value

	// Value manipulation
*ptr1 = *ptr1+2 	/* การบวกค่าที่ตัวแปรพอยเตอร์ ptr ชี้ไป อีก 2 */
*ptr2 = *ptr1		/* กำหนดให้ค่าที่ ตัวแปร ptr2 ชี้ เท่ากับค่าที่ตัวแปร prt1 ชี้ */
k = *ptr2		/* กำหนดให้ k มีค่าเท่ากับ ค่าที่ตัวแปร ptr2 ชี้ */	
}

การเปลี่ยนแปลง ตัวแปรพอยเตอร์ด้วยวิธีการทางคณิตศาสตร์ สามารถทำได้ดังตัวอย่างต่อนี้

float *ptr3, *ptr4;	float table[100];		float Pi = 3.1415927;
ptr3 = &table[0]; // กำหนดให้ ptr3 เก็บค่าตำแหน่งแอดเดรส ของตาราง table ช่องแรก
ptr3++;	// เปลี่ยนค่าตำแหน่งแอดเดรส ใน ptr3 เป็นแอดเดรสตาราง table ช่องที่2 
*ptr3 = Pi;		// ข้อมูลช่องที่ 2 ของตาราง table มีค่าเท่ากับ Pi   
ptr3+=25;		// กำหนดให้ ptr3 ชี้ไปที่ตำแหน่งแอดเดรสตาราง table ช่องที่ 26 
*ptr3 = 2.2222;		// ข้อมูล ตาราง table ช่องที่ 26 มีคาเท่ากีบ 2.2222  

ptr3 = table;		// กำหนดให้ ptr3 ชี้ไปที่ table ช่องแรก
	for(int ii =0; ii < 100; ii++)
	{
		*ptr3++ = 37.0 // กำหนดให้ข้อมูลทุกช่องของ ตาราง table เท่ากับ 37 
}
ptr3 = &table[0];	//  ptr3 contain the address for the 1st element of value
ptr4 = &table[0];	//  ptr4 contain the address for the 1st element of value

ข้อพึงระมัดระวังคือ เครื่องหมาย ++ และ -– นั้นมีลำดับความสำคัญที่สูงกว่า * ดังนั้น

*ptr++ มีค่าเท่ากับ *(ptr++) ซึ่งตัวแปรทั้ง 2 หมายถึงการเพิ่มตัวเลขตำแหน่ง แอดเดรส ไม่ใช้เพิ่มค่าที่ตัวพอยเตอร์ชี้

ส่วนกรณี *p++ = *q++ นั้น การกำหนดให้ค่าที่ตัวแปรพอยเตอร์ p มึค่าเท่ากับ ค่าที่ตัวแปรพอยเตอร์ q ชี้ จากน้นจึงมีการเลื่อนตำแหน่งแอดเดรสของตัวแปรพอยเตอร์ทั้ง 2 ไปอีก 1 ช่วง ดังตัวอย่างต่อไปนี้

*p = *q; p++; q++;

ในการเตรียมพื้นที่ตารางแบบไดนามิกให้ตัวแปรพอยเตอร์นั้นทำได้ดังนี้

กรณี C: int *ptr;
Ptr = (int *)malloc(6* sizeof(int));

กรณี C++: int *ptr;
ptr = new int[6];

ส่วนการคืนหน่วยความจำเมื่อสิ้นสุดการทำงานของโปรแกรมทำได้ดังนี้ ซึ่งต้องทำทุกครั้งเมื่อใช้คำสั่งออกจากโปรแกรม มิฉะนั้นเครื่องคอมพิวเตอร์ จะเกิดอาการค้าง (Hang) เมื่อปิดโปรแกรมเพราะไม่ได้คืนหน่วยความจำอย่างที่ควรจะทำ

กรณี C: free(ptr);

กรณี C++: delete[] ptr;

ตัวแปรพอยเตอร์และการเรียกฟังก์ชัน

การเรียกฟังก์ชันมีหลายแบบ ได้แก่
1) ฟังก์ชันที่ ไม่มีตัวแปรร่วมเป็นอาร์กิวเมนต์ และ ไม่คืนค่า (function with no parameter and no return value) ฟังกชันพวกนี้เพีบงแต่มีหน้าที่ทำงานอะไรบางอย่างตามที่ผู้เขียนโปรแกรมต้องการ เช่นกรณีฟังก์ชัน void skip3(void) เพื่อการกระโดดไปครั้งละ 3 บรรทัดเป็นต้น
2) ฟังก์ชันที่มีตัวแปรร่วมแต่ไม่คืนค่า ฟังก์ชันนี้จะทำงานตามค่าที่ตัวแปรร่วมกำหนด เช่นกรณีฟังก์ชัน void skip(int num) ซึ่งจะกระโดดครั้งละกี่บรรทัดตามจำนวนที่กำหนดลงในตัวแปร num
3) ฟังก์ชันที่ไม่มีตัวแปรร่วมแต่มีการคืนค่า ฟังก์ชันนี้มักจะให้ผู้ใช้โปรแกรมป้อนข้อมูลให้ฟังก์ชันจัดการประมวลผล ก่อนที่จะคืนค่าออกมา เช่นกรณีฟังก์ชัน float input(void) ที่จะคืนค่าหลังจากผู้ใช้โปรแกรมได้ป้อนข้อมูลให้ตามที่ฟังก์ชันสั่งให้ทำแล้ว
4) ฟังก์ชันที่มีตัวแปรร่วมและมีการ คืนค่า เช่นกรณีฟังก์ชัน float diff(float x, float y) ที่จะคืนค่าเป็นผลต่างระหว่างตัวแปร x และ ตัวแปร y

ส่วนฟังก์ชันที่มีการใช้ตัวแปรร่วม นั้น มีวิธีการเรียกอยู่ 2 แบบคือ
1) กรณี เรียกตามค่า (Call by Value) กรณีนี้การเปลี่ยนค่าตัวแปรร่วมที่เกิดขึ้นในฟังก์ชัน จะไม่มีผลต่อ ค่าตัวแปรร่วมตั้งต้นดังตัวอย่างต่อไปนี้

void skipline(int num)
{
	int i ;
	for(i = 0 ; i <=num-1 ; i++)
	{
		printf(“n”);
}
num = 100;
}
void main(void)
{
	int num1 = 60;
	printf(“%d”,num1);
	skipline(num1) ;
}

กรณี เรียกตามค่านี้ ค่า num มีการเปลี่ยนแปลง แต่ไม่มีผลต่อค่า num1 ใน main function

ตัวแปรร่วมในฟังก์ชันจะถือว่าเป็นกรณีเรียกตามค่า เมื่อการใช้ตัวแปรร่วมเป็นไปตามเงื่อนไขต่อไปนี้
• ตัวแปรร่วมนั้นแค่ส่งผ่านข้อมูลไปให้ฟังก์ชัน
• ตัวแปรร่วมดังกล่าวจะไม่เป้นตัวแปรที่คืนค่าหลังสิ้นสุดการทำงานของฟังก์ชัน

2) กรณี เรียกตามการอ้างอิง (Call by Reference) ซึ่งต้องมีการเรียกใช้ตัวแปรพอยเตอร์กรณีนี้การเปลี่ยนค่าตัวแปรร่วมที่เกิดขึ้นในฟังก์ชัน จะมีผลต่อ ค่าตัวแปรร่วมตั้งต้นดังตัวอย่างต่อไปนี้

void flip(int *x, int *y)
{
	int temp;
	temp = *x;
	*x = *y;
	*y = temp; 
}

ในการเรียกใช้ฟังก์ชัน flip นั้น จะต้องเรียกจาก แอดเดรสตัวแปร ดังนี้ flip(&a,&b)

มีบางกรณีที่มีการ เรียกตามค่าและเรียกตามการอ้างอิงในฟังก์ชันเดียวกันเช่นการแก้สมการกำลัง 2

bool quardsolve(float a, float b, float c, float *root1, float *root2)
{
	float disc = (b*b) – (4*a*c);
	if(disc < 0.0)
	{
		return false;
} 
else
{
	*root1 = (-b + sqrt(disc))/(2*a);
	*root2 = (-b – sqrt(disc))/(2*a);
	return true;
}
}

ตัวแปรดัชนี root1 และ root2 ทำหน้าที่เป็นตัวแปรร่วม Output ที่เรียกโดยการใช้อ้างอิง (Call by Reference) และ ตัวแปร a b c เป็นตัวแปรร่วม Input ที่ใช้กับการเรียกตามค่า (Call by Value) ถ้า การแก้สมการกำลัง 2 มีคำตอบให้คืนค่าเป็น true และ ค่า root1 และ root2 เป็นจำนวนจริง มิฉะนั้นให้ฟังก์ชันคืนค่า false ออกมาแทน เนื่องจาก ค่า root1 และ root2 นั้นได้รับการประกาศให้เป็น ตัวแปรร่วมอ้างอิง (Reference parameter) มีผลทำให้ฟังก์ชันต้นแบบ (Function Prototype) มีลักษณะเป็น
bool quardsolve(float, float, float, float *, float *)

เมื่อเรียกใช้งานฟังก์ชัน quardsolve ให้เรียกใช้งานฟังก์ชันดังนี้

float x1, y1; float test = quardsolve(1.0,2.0,1.0,&x,&y);

ซึ่งการเรียกใช้ฟังก์ชันดังกล้าวคล้ายคลึงกับการเรียกใช้คำสั่ง scanf

int number; scanf(“%d”, &number);

ตัวอย่างการเรียกใช้ฟังก์ชันที่มีตัวแปรพอยเตอร์จะแสดงให้เห็นดังตัวอย่างต่อไปนี้
1) ฟังก์ชันที่ มี ตัวแปรพอยเตอร์ที่แสดงในภาพที่ 6.5 นั้น จะอยู่ที่ฟังก์ชัน cal_cum โดยในฟังก์ชัน cal_sum มี ตัวแปร a และ b เป็น ตัวแปร input ขณะที่ตัวแปรพอยเตอร์ *r เป็นตัวแปร output เวลาเรียกใช้งานฟังก์ชัน cal_sum ในฟังก์ชัน main จะเรียกใช้งานดังนี้

double num1 = 2.4, num2 = 1.2,result;
cal_cum(num1,num2,&result);

แอดเดรสของตัวแปร result (&result) จะมีค่าเท่ากับชื่อตัวแปรพอยเตอร์ แม้ว่าตัวแปร result จะไม่ใช่ตัวแปรพอยเตอร์ก็ตาม

2) ฟังก์ชันที่มี ตัวแปรพอยเตอร์ที่แสดงในภาพที่ 6.6 นั้น จะอยู่ที่ฟังก์ชัน order ซึ่งใช้ตัวแปรพอยเตอร์ เป็น input และ output ทั้งคู่ ฟังก์ชัน order จะทำหน้าที่ จากเล็กไปหาใหญ่ โดยที่ ถ้า ค่าที่ตัวแปรพอยเตอร์ *small ชี้ขนาดใหญ่กว่าค่าที่ตัวแปรพอยเตอร์ *big ชี้ ให้ลงมือสับตำแหน่งการชี้ค่า

5537

ใส่ความเห็น

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / เปลี่ยนแปลง )

Twitter picture

You are commenting using your Twitter account. Log Out / เปลี่ยนแปลง )

Facebook photo

You are commenting using your Facebook account. Log Out / เปลี่ยนแปลง )

Google+ photo

You are commenting using your Google+ account. Log Out / เปลี่ยนแปลง )

Connecting to %s

%d bloggers like this: