Array and Pointer
แบ่งเป็นหัวข้อย่อยดังนี้
- อะเรย์ 1 มิติ
- อะเรย์หลายมิติ
- การผ่านค่าตัวแปรอะเรย์ ไปยัง ฟังก์ชัน
- ข้อมูลแบบโครงสร้าง (Structure)
- Pointer
- ความสัมพันธ์ระหว่างอะเรย์และพอยน์เตอร์
- ความสัมพันธ์ระหว่าง Structure และ Pointer
- การใช้ new และ delete กับอะเรย์
อะเรย์ 1 มิติ (One dimension array)
การแก้ปัญหาในทางวิทยาศาสตร์และวิศวกรรม บางครั้งข้อ มูลมีเพียง 1 ค่า เช่นการหาพื้นที่วงกลม เราใช้รัศมีของวงกลมในการหาพื้นที่นั้น จุดต่าง ๆ ในระนาบ x, y สามารถใช้ตัวแปรเพียง 2 ตัว คือ x –coordinate และ y-coordinate สำหรับหาจุดพิกัด ปัญหาบางปัญหาต้องใช้ข้อมูลแบบเดียวกันเป็นจำนวนมาก และเราก็ไม่ต้องการตั้งชื่อข้อมูลเหล่านี้ให้แตกต่างกัน เช่น เราวัดอุณหภูมิของน้ำ 10 ครั้งได้ข้อมูล 10 ค่า เราไม่ต้องการที่จะตั้งชื่อข้อมูลเหล่านี้ถึง 10 ขื่อ แต่จะใช้ข้อมูลเพียงชื่อเดียวนี้จัดการข้อมูลทั้ง 10 ค่านี้ได้ โดยจัดเก็บข้อมูลเหล่านี้ในรูปอะเรย์
อุณหภูมิที่เก็บได้จะจัดเก็บในรูปอะเรย์ดังแผนภาพต่อไปนี้
ให้ T เป็น identifier ของอะเรย์ จำแนกค่าต่าง ๆ ของอุณหภูมิทั้ง 10 ค่าโดยใช้ subscript หรือ index value ในภาษา C++ subscript จะเริ่มต้นที่ 0 และจะเพิ่มค่าทีละ 1 ข้อมูลอุณหภูมิค่าที่ 1 จะถูกเก็บไว้ใน T [0] อุณหภูมิค่าที่ 2 จะถูกเก็บไว้ใน T [1] ไปเรื่อย ๆ จนถึงอุณหภูมิค่าที่ 10 จะถูกเก็บไว้ใน T[9]
การสร้างตัวแปรแบบอะเรย์ทำได้ดังนี้
ข้อมูลที่เก็บไว้ในตัวแปรอะเรย์ เรียกว่า สมาชิก (elements) ของอะเรย์ ตัวอย่างการนิยามตัวแปรแบบอะเรย์
int NumberOfStudentEachYear[12];
อะเรย์เก็บข้อมูลชนิดจำนวนเต็มเก็บข้อมูล 12 ค่า
double distance[100]; เก็บข้อมูลชนิดจำนวนจริงแบบ double ได้ 100 ค่า
char ch[10]; เก็บข้อมูลชนิดตัวอักษร 10 ตัว
เราสามารถกำหนดค่าเริ่มต้นให้อะเรย์ขณะที่นิยามอะเรย์นั้น ทำได้โดยกำหนดค่าสมาชิกแต่ละตัวคั่นด้วยเครื่องหมายจุลภาค ปิดหัวท้ายด้วยวงเล็บปีกกา เช่น
int a[5] = {3,5,7,9, 11} ;
กำหนดให้ a เป็นตัวแปรแบบอะเรย์ชนิดจำนวนเต็ม ให้ a[0]=3, a[1]=5, a[2]=7, a[3]=9, a[4]=11,
float f[ ] = {4.2, 6.8, 3.7 };
กรณีที่ไม่ใส่ขนาดของอะเรย์ในวงเล็บ ขนาดของอะเรย์จะถูกจองไว้ในหน่วยความจำเท่ากับจำนวนสมาชิกที่กำหนดไว้ตอนเริ่มต้น ในที่นี้คือ 3
float ff[10]= { 0};
กำหนดให้อะเรย์ ff ทั้ง 10 ตัวมีค่าเป็นศูนย์
การกำหนดค่าให้กับสมาชิกอะเรย์ไม่ครบทุกตัว ตัวที่ไม่มีการกำหนดค่าจะถูกกำหนดโดยคอมไพเลอร์ให้มีค่าเป็นศูนย์
double b[5] = {3.2, 6.8, 4.1 };
จะได้ b[0]=3.2, b[1]=6.8, b[2]=4.1, b[3] = 0, b[4] =0
ถ้าประกาศตัวแปรแบบอะเรย์ แต่ไม่มีการกำหนดค่าเริ่มต้น จะได้ข้อมูลที่เป็นขยะเก็บไว้ในอะเรย์นั้น
ดังตัวอย่างโปรแกรม 5.1 ต่อไปนี้
[Source code: UnInit_a.cpp]
ผลการทำงานของโปรแกรม จะได้ค่าที่เป็นขยะ ดังรูป
กรณีที่ชนิดข้อมูลเป็นตัวอักษร
char vowels[5] = { ‘a', ‘e', ‘i', ‘o', ‘u'};
จะได้ vowels[0] = ‘a', vowels[1]='e', vowels[2]= ‘i', vowels[3] = ‘o', vowels[4]='u' หรือกำหนดเป็น
char vowels[5] = { “aeiou” };
char str[ ] = “This is a sentence”;
ไม่สามารถใช้ตัวแปรในการกำหนดขนาดของอะเรย์ได้ ตัวอย่างต่อไปนี้จึงไม่ถูกต้อง
int n = 20;
int myArray[n];
แต่เราสามารถใช้ค่าคงที่ไปกำหนดขนาดของอะเรย์ได้ เมื่อต้องการเปลี่ยนขนาดของอะเรย์ สามารถเปลี่ยนตัวเลขที่ ArraySize เพียงแห่งเดียว ไม่ต้องไปตามแก้หลายแห่ง ถ้ามีการสร้างอะเรย์ไว้หลายที่
#include <iostream>
using namespace std;
const int ArraySize=100;
int main ( ) {
int testArray[ArraySize];
int i;
for ( i=0; i < ArraySize; i++) {
testArray[i] = i;
cout << “testArray[ “ << i << “ ] = “ << testArray[i] << endl;
}
}
จะต้องพึงระวังว่าเมื่อสร้างตัวแปรอะเรย์ a มีขนาด n อะเรย์ a จะมีค่าตั้งแต่ a[0], a[1], .. ,a[n-1] เท่านั้น ไม่มี a[n] ถ้าใช้ a[n] จะพบข้อความผิดพลาดขณะคอมไพล์โปรแกรม ว่าไม่มีสมาชิกตัวที่ n หรือ out of bound error. คอมไพเลอร์บางตัวไม่มีการตรวจสอบขนาดของอะเรย์และจะนำตัวเลขที่เป็นขยะมาแสดงผล โปรแกรมเมอร์ควรตรวจสอบขนาดของอะเรย์ เพื่อป้องกันการนำข้อมูลขยะที่อยู่นอกขอบเขตของอะเรย์มาใช้งาน
โปรแกรมต่อไปนี้เป็นการแสดงผลข้อมูลที่เก็บไว้ในอะเรย์
[Source code: array_display.cpp]
อะเรย์ประเภท char จะถูกปิดท้ายด้วย null character ( \0) ไว้ท้ายอะเรย์เสมอ เพื่อแสดงว่าสิ้นสุดขอบเขตของอะเรย์เพียงเท่านี้ และจะแสดงผลเพียงเท่านี้ ดังตัวอย่างในโปรแกรม 5.2
mystr เป็นอะเรย์ประเภท char เก็บค่า Test character arrays. ไว้ในหน่วยความจำปิดท้ายด้วย null character ดังรูป
T
| e | s | t | c | h | a | r | a | c | t | e | r | a | r | r | a | y | s | . | \0 |
T
|
e
| s | t | \0 | c | h | a | r | a | c | t | e | r | a | r | r | a | y | s | . | \0 |
เมื่อกำหนด mystr[4] = ‘\0'
เมื่อให้แสดงผล mystr พบว่าข้อความที่เหลือ จะได้ Test เท่านั้น ข้อความหลัง ‘\0' จะถูกละทิ้งไม่แสดงผลทั้งหมด
[Source code: array_char.cpp]
ผลที่ได้จากการรันโปรแกรม
เราสามารถใช้คำสั่ง mystr[4] = 0; แทน mystr[4] = ‘\0'; ในบรรทัดที่ 8 ได้ การใช้เครื่องหมาย \ (backslash) บอกให้คอมไพเลอร์รู้ว่ามีตัวอักษรพิเศษตามหลัง \
ถ้าต้องการกำหนดค่า เลขศูนย์เก็บไว้ในอะเรย์จะต้องใช้คำสั่งดังนี้
mystr[4] = ‘0';
หรือ mystr[4] = 48; ซึ่งเป็นรหัสอัสกีของเลขศูนย์
โปรแกรม 5.4 เป็นการนำตัวแปรแบบอะเรย์มาใช้งาน โปรแกรมนี้จะให้ป้อนเลขจำนวนจริงหรือจำนวนเต็ม ไม่เกิน 100 ค่า แล้วแสดงผลตัวเลขที่ป้อนเข้าไป พร้อมค่าเฉลี่ย
[Source code:array_av.cpp]
เพื่อความปลอดภัยอาจตรวจสอบขอบเขตของอะเรย์ โดยแทรกคำสั่งต่อไปนี้หลังบรรทัดที่ 22
if ( size*sizeof(float) > sizeof(data) return 0;
sum =0.0;
for ( int i=0; int i < size; i++ )
………
การใช้ตัวแปรอะเรย์กับ enumeration type
เราสามารถนำข้อมูลแบบ enumeration type มาใช้ร่วมกับตัวแปรชนิดอะเรย์ได้อย่างกลมกลืน ทำให้โปรแกรมมีลักษณะอ่านง่ายและชัดเจนในตัวมันเอง
ข้อมูลแบบ enum type จะคล้ายกับข้อมูลประเภท int หรือ char ต่างกันตรงที่สมารถแสดงเป็นชื่อที่บ่งบอกถึงความหมาย ไม่จำเป็นต้องเรียงลำดับ ขึ้นอยู่กับโปรแกรมเมอร์จะกำหนด
โปรแกรม 5.5 ต่อไปนี้แสดงความยาวคลื่นของแสงสีต่าง ๆ ในหน่วย นาโนเมตร
[Source code: wavelength.cpp]
ในบรรทัดที่ 11, 12 จะเห็นว่าการใช้ enum type จะช่วยให้การเขียนคำสั่งมีลักษณะใกล้เคียงกับภาษาพูด เป็นการบอกให้แสดงค่าความยาวคลื่นของแสงสีต่าง ๆ ตั้งแต่ violet, blue, green, yellow, orange, และ red
อะเรย์หลายมิติ
อะเรย์ที่กล่าวมาจะเป็นอะเรย์เพียงแถวเดียว ถ้าอะเรย์นั้นมีหลายแถวจัดว่าเป็นอะเรย์หลายมิติ การประกาศตัวแปรที่เป็นอะเรย์หลายมิติ ทำได้โดยกำหนดขนาดให้เท่ากับจำนวนของมิตินั้น เช่น
int a[3][4];
a เป็นตัวแปรอะเรย์เก็บข้อมูลชนิดจำนวนเต็มขนาด 3 แถว 4 สดมภ์ เป็นอะเรย์ 2 มิติ
ในหน่วยความจำจะถูกจัดวางตำแหน่งดังนี้
ตัวอย่างการประกาศค่าตัวแปรที่เป็นอะเรย์ 3 มิติ
float farray[6][2][4];
เป็นอะเรย์ที่เก็บข้อมูลชนิดจำนวนจริง (float) มีขนาด 6 x 2 x 4
รูปแบบทั่วไปของการประกาศตัวแปรแบบอะเรย์มีดังนี้
Data_type ArrayName [size1][size2][….][…];
การกำหนดค่าในอะเรย์หลายมิติ มีลักษณะคล้ายกับเมตริกซ์ (Matrix) ในคณิตศาสตร์
เมตริกซ์ b มีขนาด 2 x 3 มีค่าดังนี้
เขียนเป็นอะเรย์ 2 มิติ ในภาษา C++ ได้ดังนี้
int b[2][3] = { { 3, -1, 0 },
{2, 5, 7 }};
หรือจะเขียนให้อยู่ในแถวเดียว
int b[2][3] = { 3, -1, 0, 2, 5, 7};
int calendar[12][31];
calendar เป็นอะเรย์เก็บช้อมูลชนิดจำนวนเต็มจำนวน 11 แถวแต่ละแถวมีสมาชิก 31 ตัว ( ไม่ใช่ 31 แถว แต่ละแถวมีสมาชิก 12 ตัว)
sizeof (calendar) = (31)(12)(sizeof (int)) = 372
ให้ c เป็นอะเรย์ 3 มิติ มีขนาดเป็น 2 x 4 x 3 มีจำนวนสมาชิก 24 ตัว
การกำหนดค่าเริ่มต้นของอะเรย์ 3 มิติ ทำได้ดังนี้
int c [2][4][3] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 };
หรือ
int c [2][4][3] = { { {0, 1, 2} , {3, 4, 5}, {6, 7, 8}, {9, 10, 11} },
{ {12, 13, 14}, {15, 16, 17}, {18, 19, 20}, {21, 22, 23} };
จะเขียนโปรแกรม 5.6 เืพื่อทดลองนำอะเรย์ c ไปแสดงผล
[Source code: array3d.cpp]
การผ่านค่าตัวแปรอะเรย์ไปยังฟังก์ชัน
การผ่านค่าอะเรย์ไปยังฟังก์ชันอื่นเพื่อประมวลผลเป็นการผ่านค่าแบบ reference ตัวอย่างเช่น
int myarray[ ] ; เป็นการประกาศตัวแปรอะเรย์ชื่อ myarray สมาชิกของอะเรย์แต่ละตัวจะเป็นแบบ integer ชื่อ myarray จะเก็บตำแหน่งของหน่วยความจำที่เก็บอะเรย์ คอมไพเลอร์จะรับรู้เพียงตำแหน่งของหน่วยความจำ และชนิดข้อมูลที่เก็บ ไม่จำเป็นต้องระบุจำนวนสมาชิกอะเรย์
เมื่อผ่านค่าอะเรย์ไปยังฟังก์ชัน จะเป็นการผ่านตำแหน่งของหน่วยความจำที่เก็บตัวแปรอะเรย์ตัวแรกเท่านั้น ฟังก์ชันสามารถเปลี่ยนแปลงค่าที่เก็บไว้ในอะเรย์โดยการระบุตำแหน่งหน่วยความจำที่เหมาะสม การผ่านค่าตัวแปรอะเรย์ไปยังฟังก์ชัน ควรระบุขนาดของอะเรย์ไปยังฟังก์ชันที่ถูกเรียก เพื่อป้องกันการประมวลผลอะเรย์ที่อยู่นอกเหนือขอบเขต
โปรแกรม 5.7 เป็นตัวอย่างการผ่านค่าอะเรย์แบบ 1 มิติ ไปยังฟังก์ชัน เป็นการขยายโปรแกรมการหาค่าเฉลี่ย โดยแตกงานออกเป็น 3 ฟังก์ชันนั่นเอง
[Source code:ArrayAv.cpp]
ฟังก์ชันทั้ง 3 ฟังก์ชันในโปรแกรม ArrayInput (data, n), ArrayDisplay (data, n) และ Average (data ,n) เป็นการผ่านเฉพาะชื่อตัวแปรอะเรย์ (คือ data) ไปยังฟังก์ชันนั้น ๆ และผ่านค่าจำนวนของสมาชิกอะเรย์โดยใช้ตัวแปร n
ฟังก์ชัน ArrayInput จะผ่านค่า n แบบตัวแปรอ้างอิงนั้นหมายถึงฟังก์ชัน ArrayInput จะเป็นตัวกำหนดจำนวนสมาชิกของอะเรย์ data ว่าผู้ใช้จะป้อนข้อมูลเข้ามากี่ชุด n จะมีค่าตามจำนวนชุดที่ป้อน การวนรอบ for จะกำหนดไว้ให้ผู้ใช้ป้อนข้อมูลได้ไม่เกิน MAXSIZE = 200 ชุด
ฟังก์ชัน ArrayDisplay จะนำข้อมูลในตัวแปรอะเรย์ data มาแสดงผลเท่ากับจำนวนสมาชิก n
ฟังก์ชัน Average จะหาค่าเฉลี่ยของข้อมูลที่ผู้ใช้ป้อนเข้ามา n ชุด ส่งค่าเฉลี่ยคืนกลับไปยังโปรแกรมหลัก
การผ่านค่าอะเรย์ 2 มิติ ไปยังฟังก์ชัน
ตัวอย่าง โปรแกรม 5.8 ต่อไปนี้เป็นการเก็บคะแนนของนักศึกษา 3 คน แต่ละคนทำแบบทดสอบ 5 ชุด ได้คะแนนแตกต่างกัน การเก็บคะแนนจะเก็บไว้ในตัวแปรอะเรย์ขนาด 3 x 5 ( 3 แถว 5 คอลัมน์) จากนั้นคำนวณหาคะแนนเฉลี่ยของนักศึกษาแต่ละคน และคะแนนเฉลี่ยของแบบทดสอบแต่ละชุดที่นักศึกษาทำได้
[Source code:Array2Av.cpp]
เมื่อให้โปรแกรมทำงาน จะได้ผลลัพธ์ดังรูป
การเรียงลำดับข้อมูลโดยใช้ตัวแปรอะเรย์
การเรียงลำดับข้อมูล(จากมากไปหาน้อย (descending) หรือจากน้อยไปหามาก (Ascending)) เป็นสิ่งที่หลีกเลี่ยงไม่พ้นในการแก้ปัญหาทางวิทยาศาสตร์ ตัวแปรแบบอะเรย์จะช่วยแก้ปัญหาการเรียงลำดับข้อมูลให้ง่ายขึ้น
การเรียงลำดับข้อมูลที่ง่ายที่สุดคือ การเรียงลำดับแบบ bubble sort ในการเรียงลำดับ(ในที่นี้จะเรียงจากน้อยไปมาก) จะมีการวนรอบแบบซ้อนกั้น 2 ชั้น ชั้นในสุดจะเปรียบเทียบสมาชิกกับสมาชิกตัวที่อยู่ถัดไป ถ้ามีค่ามากกว่าจะสลับค่ากัน ลักษณะจะคล้ายกับสมาชิกตัวที่น้อยเป็นฟองอากาศที่ลอยตัวขึ้นมาอยู่บนสุดของผิวน้ำ
[Source code: bubble.cpp]
รูปต่อไปนี้เป็นผลการทำงานของโปรแกรม 5.9
ข้อมูลแบบโครงสร้าง (structure)
สตรักเจอร์เป็นการนำข้อมูลที่เกี่ยวข้องกันแต่มีชนิดข้อมูลแตกต่างกันมารวมไว้เป็นกลุ่มเดียวกัน ต่างจากข้อมูลแบบอะเรย์ซึ่งต้องเป็นข้อมูลชนิดเดียวกันเท่านั้น
รูปแบบการใช้งาน
ต้องการเก็บรหัสนักศึกษา (Id) ชื่อ - สกุล (name) กลุ่มนักศึกษา (section) ระดับคะแนนเฉลี่ย (GPA) ของนักศึกษาแต่ละคน จะเป็นการสะดวกถ้านำข้อมูลของนักศึกษาแต่ละคนรวบรวมไว้เป็นชุดเดียวกัน ซึ่งทำได้โดยเก็บข้อมูลเป็นแบบ structure ดังนี้
struct StudentRecord {
long id;};
char name [40];
int section ;
float gpa;
StudentRecord เป็นตัวแปรแบบโครงสร้าง ที่มีสมาชิกที่เป็นข้อมูลประเภท char int และ float การนำ StudenRecord ไปใช้งานในการเก็บข้อมูลนักศึกษา 5 คน ทำได้โดยประกาศตัวแปรดังต่อไปนี้
StudentRecord student01, student02, student03, student04, student05;
หรือจะใช้ตัวแปรแบบอะเรย์ในการประกาศค่าก็ได้
StudentRecord students[5];
การกำหนดค่าเริ่มต้นของตัวแปรโครงสร้าง ทำได้ดังนี้
StudentRecord students[5] = { { 100001, “Dang”, 1, 3.2},
{100002, “Sompong”, 2, 2.4},
{100003, “Naree”, 1, 2.75},
{100004, “Supan”, 2, 1.89},
{100005, “Wipa”, 2, 3.00} };
ตัวอย่างต่อไปนี้เป็นการป้อนข้อมูลนักศึกษา จำนวน 3 ราย ลงในตัวแปรแบบโครงสร้าง StudentRecord จากนั้น แสดงผลข้อมูลที่ป้อนเข้าไปในโปรแกรมบนจอภาพ
[Source code:struc.cpp]
ทดลองนำข้อมูลจากตัวแปรโครงสร้างซึ่งมีการกำหนดค่าไว้แล้วในตอนต้นโปรแกรม มาแสดงผลบนจอภาพ
[Source cdoe: struc2.cpp]
ถ้าเรากำหนดค่าเริ่มต้นให้แก่ students[0], students[1], students[2], students[3] แต่ students[4] ไม่กำหนดค่าเริ่มต้นให้ จะเกิดอะไรขึ้น พบว่าคอมไพเลอร์จะใส่ค่า 0 ให้แก่ข้อมูลชนิดตัวเลข และใส่ค่า null ให้แก่ข้อมูลชนิดตัวอักษร
Pointers
เมื่อประกาศตัวแปรในโปรแกรม เช่น int i = 3; หรือ float x = 5.6; เมื่อให้โปรแกรมทำงานจะเริ่มด้วยการจัดสรรหน่วยความจำแรม เพื่อเก็บตัวแปรเหล่านี้ ตัวแปรแต่ละตัวจะถูกเก็บในหน่วยความจำซึ่งมี address เป็นตัวเลขที่แน่นอน ขนาดของหน่วยความจำที่ใช้เก็บขึ้นอยู่กับชนิดข้อมูล เช่นตัวแปรชนิด int จะใช้เนื้อที่เก็บ 2 ไบต์ ตัวแปรชนิด float จะใช้เนื้อที่เก็บ 4 ไบต์
address ของหน่วยความจำจะเป็นเสมือนบ้านเลขที่ จะเริ่มต้นที่ 0, 1, 2, … จนถึงค่าสูงสุดซึ่งขึ้นอยู่กับว่าคอมพิวเตอร์เครื่องนั้นมีหน่วยความจำเท่าใด ถ้าเครื่องคอมพิวเตอร์นั้นมีหน่วยความจำ 640 kB address ของหน่วยความจำจะมีค่าไปถึง 655,359 ถ้าเครื่องคอมพิวเตอร์นั้นมีหน่วยความจำ 1 MB จะมี address ได้ถึง 1,048,575 เมื่อโปรแกรมถูกโหลดเข้าสู่หน่วยความจำ ถ้าอยากจะรู้ว่าตัวแปรต่าง ๆ ในหน่วยความจำอยู่ที่ตำแหน่งเลขที่เท่าใด สามารถใช้ & (address of operator) หาตำแหน่งของตัวแปรนั้นได้ดังนี้
[Source code: pointer1.cpp]
ผลการทำงานของโปรแกรมจะได้ดังนี้
ในการให้โปรแกรมทำงานแต่ละครั้งหรือในแต่ละเครื่องจะได้ตำแหน่งของหน่วยความจำที่ต่างกันไปขึ้นอยู่กับขนาดของระบบปฏิบัติการของเครื่องนั้น และขึ้นอยู่กับว่าขณะนั้นมีโปรแกรมอื่นกำลังทำงานอยู่ในเครื่องหรือไม่
จากรูปจะเห็นว่าตำแหน่งของหน่วยความจำจะเรียงจากมากไปน้อย ขณะรันโปรแกรม ตัวแปรเหล่านี้จะถูกเก็บไว้ในหน่วยความจำที่เรียกว่า stack โดยเริ่มต้นจาก address ที่มีค่ามากไปสู่ address ที่มีค่าน้อย ถ้าตัวแปรนั้นเป็นแบบ external จะถูกเก็บไว้ในหน่วยความจำที่เรียกว่า heap จะเก็บตัวแปรเรียงซ้อนกันในลักษณะตรงข้ามกับ stack เลขที่ตำแหน่งจะเรียงจากน้อยไปมาก รายละเอียดตรงส่วนนี้ โปรแกรมเมอร์ไม่ต้องใส่ใจก็ได้ คอมไพเลอร์จะเป็นผู้จัดการรายละเอียดปลีกย่อยเหล่านี้ให้เรา
ระวังอย่าสับสน address of operator & ซึ่งนำหน้าชื่อตัวแปร กับ reference operator & ซึ่งตามหลังชนิดข้อมูล
ตัวแปรพอยเตอร์ ( Pointer variable)
ตัวแปรใดก็ตามที่เก็บตำแหน่งเลขที่ของตัวแปรอื่น หรือออปเจกต์อื่น เรียกตัวแปรนั้นว่าเป็นตัวแปรพอยเตอร์ คอมไพเลอร์ต้องรู้ด้วยว่าตัวแปรพอยน์เตอร์เก็บ address ของตัวแปรเป็นข้อมูลชนิดใด เพราะการเพิ่มค่า pointer ไปยัง address ต่าง ๆ จะเพิ่มตามขนาดของข้อมูล การประกาศตัวแปรพอยน์เตอร์ จึงต้องประกาศให้ตรงกับชนิดของตัวแปรนั้น ๆ ด้วย
การประกาศตัวแปรพอยน์เตอร์ ใน C++ มีรูปแบบดังนี้
data_type *pointer_var ; // define a ‘ pointer to type'
เช่น
int *ptrint; // define a pointer to int
float *ptrx; // define a pointer to float
*ptrint และ *ptrx เป็นตัวแปรพอยน์เตอร์ที่เก็บตำแหน่งเลขที่หน่วยความจำของตัวแปรที่มีชนิดข้อมูลเป็น Int และ float ตามลำดับ เครื่องหมาย * มีความหมายว่าเป็น pointer to type
ตัวอย่างต่อไปนี้เป็นการกำหนดค่า address ให้กับตัวแปรพอยน์เตอร์และใช้ประโยชน์จากตัวแปรพอยน์เตอร์ ในการแสดงค่า
[Source code: pointer2.cpp]
บรรทัดที่ 10 เป็นการกำหนดค่าให้ตัวแปรพอยน์เตอร์ ptr_int เก็บตำแหน่ง address ของ i1
เมื่อพิมพ์ค่า ptr_int จะได้ค่าหน่วยความจำของตัวแปร i1 เมื่อให้พิมพ์ค่า *ptr_int จะแสดงข้อมูลที่เก็บไว้ในหน่วยความจำที่ address นี้ จะเห็นว่าในบางครั้ง เราอาจไม่ทราบชื่อตัวแปร แต่ถ้ารู้ address ของตัวแปร ก็สามารถรู้ว่าข้อมูลของตัวแปรนั้นมีค่าเท่าใดได้ โดยอาศัยตัวแปรแบบพอยน์เตอร์
บรรทัดที่ 12 เป็นการให้ตัวแปรพอยน์เตอร์เปลี่ยนค่า address ของหน่วยความจำเป็นของ i2 ข้อมูลใน ptr_int จะกลายเป็นตำแหน่งเลขที่ของหน่วยความจำของตัวแปร i2 ดังรูป
และตัวแปร ptr_x จะเก็บ address ของตัวแปร x
เมื่อประกาศตัวแปรพอยน์เตอร์โดยไม่กำหนดค่าเริ่มต้น ค่าที่เก็บอยู่ในตัวแปรพอยน์เตอร์อาจเป็นค่าอะไรก็ได้ อาจชี้ไปยังหน่วยความจำที่ address ใด ๆ ซึ่งอาจเป็นที่เก็บข้อมูลสำคัญของระบบปฏิบัติการ ถ้ามีการเปลี่ยนค่าอาจทำให้เครื่องหยุดทำงาน การกำหนดค่าเริ่มต้นให้แก่ตัวแปรพอยน์เตอร์จึงเป็นสิ่งสำคัญ
* เป็น operator เข้าถึงข้อมูลของตัวแปรที่มี address ตรงกับ พอยน์เตอร์นั้นชี้อยู่ อาจกล่าวว่าเครื่องหมาย * คือ ‘ at address'
& จะให้ค่าเลขที่ตำแหน่งของหน่วยความจำของตัวแปรนั้น จำง่าย ๆ คือ ‘The address of ….'
ให้ทดลองเปลี่ยคำสั่งให้เป็นดังต่อไปนี้ ทดลองดูว่าเมื่อคอมไพล์จะเกิดอะไรขึ้น
ptr_int = &x;
ptr_x = &i1;
หรือประกาศตัวแปรดังนี้
int *ptr_int = &i1;
int i1;
พิจารณาส่วนหนึ่งของโปรแกรมต่อไปนี้ แล้วคาดคะเนว่าผลลัพธ์ที่ได้
int *ptr_int;
int n;
ptr_int = &n;
*ptr_int = 15;
cout << “n = “ << n << endl;
cout <<” *ptr_int = “ << *ptr_int << endl;
……….
ตัวแปรพอยน์เตอร์สามารถใช้ในการคำนวณคณิตศาสตร์
int *ptri;
int i, n =5;
ptri = &n;
i = 8*(*ptri); // 8*5 = 40
…………..
………..
หรือจะเขียนเป็น 8 * *ptri ก็ได้ เพราะ *ptri เป็น indirection operator มีลำดับความสำคัญก่อนเครื่องหมายคูณ
คำสั่งทั้งสองบรรทัดต่อไปนี้ให้ผลลัพธ์เช่นเดียวกัน
j = i;
j = *&i;
ประโยคต่อไปนี้ควรระวัง
i = 8/*ptri // divide 8 by object pointer to, assign result to i
เพราะเครื่องหมาย /* คอมไพเลอร์ (บางบริษัท) จะมองว่าเป็นเริ่มต้นของการ comment
การเพิ่มค่า pointer ดังตัวอย่างต่อไปนี้
int i = 10;
int *pi;
*pi = i;
*pi += 1; // add 1 to *pi
หรือจะใช้คำสั่ง (*pi)++; การใส่วงเล็บ จะประมวลผลเริ่มจากขวาไปซ้าย (กรณี ++ และ * )
ถ้าเขียน *pi ++ จะเป็น *(pi++)
ความสัมพันธ์ระหว่างอะเรย์และพอยน์เตอร์
อะเรย์และพอยน์เตอร์มีความสัมพันธ์ใกล้ชิดกันมาก บางครั้งการจัดการกับสมาชิกของอะเรย์โดยใช้ตัวแปรพอยน์เตอร์ จะมีประสิทธิภาพมากกว่า การกล่าวถึงชื่ออะเรย์และดรรชนีหรือ subscript โดยตรง
เมื่อประกาศตัวแปร int a [ 5 ] = { 0, 10, 20, 30, 40}; หมายถึง a เป็นตัวแปรชนิดอะเรย์เก็บจำนวนเต็มได้ 5 ค่า
int *iptr_a; ตัวแปรชื่อ ptr_a เป็นตัวแปรพอยน์เตอร์ชี้ไปยัง address ของอะเรย์ที่เก็บจำนวนเต็ม
คำสั่ง iptr = &a[0];
กำหนดให้ iptr_a ชี้ไปที่ตำแหน่ง a[0] หรือจะเขียนเป็น iptr_a = a; จะให้ผลเหมือนกัน
ตัวอย่างต่อไปนี้เป็นการอ้างถึงหรือแสดงข้อมูลในตัวแปรอะเรย์โดยใช้พอยน์เตอร์ ดังนี้
[Source code: arraypoint.cpp]
ผลลัพธ์จากการทำงานของโปรแกรมดังกล่าวจะได้ดังนี้
1) Using a[index]a[ 0] = 0 a[ 1] = 10 a[ 2] = 20 a[ 3] = 30 a[ 4] = 402) Using iptr[index]iptr[ 0] = 0 iptr[ 1] = 10 iptr[ 2] = 20 iptr[ 3] = 30 iptr[ 4] = 403) Using array name as a pointer *(a+index)*( a+ 0) = 0 *( a+ 1) = 10 *( a+ 2) = 20 *( a+ 3) = 30 *( a+ 4) = 404) Using a pointer name as a pointer *(iptr+index)*( iptr + 0) = 0 *( iptr + 1) = 10 *( iptr + 2) = 20 *( iptr + 3) = 30 *( iptr + 4) = 405) Using increasing pointer Arithmatics iptr++*( iptr) at address 0012 FF 78) = 0 *( iptr) at address 0012 FF 7 C) = 10*( iptr) at address 0012 FF 80) = 20 *( iptr) at address 0012 FF 84) = 30*( iptr) at address 0012 FF 88) = 40
การใช้ indirection operator กับการเพิ่มค่าและลดค่า
กำหนดให้ int data[ ] = { 10, 20, 30, 40, 50}; และ int *ip, temp;
ip = & data[1];
temp = *ip;
จะเห็นว่า ip เป็นตัวแปรพอยน์เตอร์ชี้ไปยังสมาชิกอะเรย์ data ตัวที่ 2 ซึ่งมีค่าเท่ากับ 20 ค่าที่เก็บไว้ในตัวแปร temp จึงเท่ากับ 20 ด้วย
ถ้าเพิ่มประโยคคำสั่งต่อไปนี้
*++ip; // *(++ip)
temp = *ip;
เป็นการเพิ่มค่าพอยน์เตอร์ ip ไปอีก 1 ตำแหน่ง พอยน์เตอร์จะชี้ไปที่สมาชิกอะเรย์ตัวที่ 3 ค่าที่เก็บไว้ใน temp จึงมีค่าเท่ากับ 30
++ *ip; // ++ (*ip)
temp = *ip;
ขณะนี้ ip ชี้อยู่ที่สมาชิกตัวที่ 3 ของ data คำสั่ง ++(*ip) เป็นการเพิ่มค่าข้อมูลที่เก็บไว้ใน address ที่ ip ชี้อยู่ จาก 30 เป็น 31 ค่าที่เก็บไว้ใน temp จึงมีค่า 31
temp = *ip++; // y = *(ip++)
เครื่องหมาย ++ จะกระทำกับ ip เพราะใส่ไว้ข้างหลัง ip หรือมี่ลักษณะเป็น postfix จึงมีผลเมื่อประโยคนี้ได้ถูกประมวลผลแล้ว นั่นคือ temp ยังคงมีค่าเท่ากับ 31 เมื่อ temp ได้รับค่าแล้ว ip จะเพิ่มค่าขึ้นอีก 1 จะชี้ไปยังสมาชิกตัวที่ 4 ซึ่งมีค่าเท่ากับ 40
temp = (*ip)++;
เป็นการเพิ่มค่าข้อมูลที่เก็บไว้ใน address ที่ ip ชี้อยู่ จาก 40 จะมีค่าเป็น 41
ชนิดข้อมูลแบบโครงสร้าง (Structure) กับ Pointer
จากตัวแปรแบบโครงสร้าง
struct StudentRecord {
long id;};
char name [40];
int section ;
float gpa;
ประกาศตัวแปรที่มีชนิดข้อมูลแบบ StudentRecord ได้ดังนี้
StudentRecord mystudent, *p_student;
p_student = &mystudent;
p_student เป็นตัวแปรพอยน์เตอร์ ชี้ไปยั้งข้อมูลชนิด StudentRecord ซึ่งเป็นตัวแปรแบบโครงสร้าง การใช้ตัวแปรพอยน์เตอร์เข้าถึงข้อมุลแบบโครงสร้างทำได้ดังนี้
(*p_student).id = 1001;
strcpy(*p_student.name = “Somsak”);
(*p_student).section = 1;
(*p_student).gpa = 2.75;
เพราะเครื่องหมายจุด . มีลำดับความสำคัญมากกว่าเครื่องหมาย * จึงต้องใส่วงเล็บที่ตัวแปรพอยน์เตอร์ เพื่อบังคับให้คอมไพเลอร์มองเห็น p_student เป็นพอยน์เตอร์ก่อน การใช้พอยน์เตอร์กับตัวแปรแบบโครงสร้าง จะนิยมใช้เครื่องหมาย -> คำสั่งชุดข้างบนจึงเขียนใหม่ได้เป็น
p_student ->id = 1001;
strcpy(p_student -> name = “Somsak”);
p_student ->section = 1;
p_student -> gpa = 2.75;
การจัดสรรหน่วยความจำขณะ run time โดยใช้พอยน์เตอร์
การใช้โปรแกรมที่เขียนด้วยภาษา C++ จะเกี่ยวข้องกับหน่วยความจำใน 3 ลักษณะ คือ จัดเก็บแบบอัตโนมัติ (automatic storage) เป็นบริเวณหน่วยความจำที่ใช้เก็บตัวแปรของฟังก์ชัน เมื่อมีการเรียกใช้ฟังก์ชัน แบบที่ 2 คือจัดเก็บแบบสถิต (static storage) เมื่อข้อมูลถูกประกาศให้เป็น static ซึ่งจะคงอยู่ตลอดเวลาเมื่อโปรแกรมทำงาน และส่วนที่จัดเก็บอย่างอิสระ (free storage) ในภาษา C เรียกส่วนนี้ว่า heap หน่วยความจำส่วนนี้จะถูกจัดสรรสำหรับคำสั่งของโปรแกรมขณะที่ประมวลผล เราสามารถจัดสรรและจัดการหน่วยความจำบริเวณนี้โดยใช้คำสั่ง new และ delete
การที่เราสามารถจัดสรรควบคุมหน่วยความจำได้ขณะที่โปรแกรมทำงานจะเป็นการใช้ทรัพยากร (หน่วยความจำ) ได้อย่างมีประสิทธิภาพ ณ ขณะนี้เราสามารถจับจองและใช้หน่วยความจำโดยวีสร้างตัวแปร นั่นคือบอกให้คอมไพเลอร์รู้ว่าเราจะต้องใช้หน่วยความจำจำนวนเท่าใดในการรันโปรแกรม ในบางกรณีทำให้เกิดการใช้พื้นที่หน่วยความจำไม่คุ้มค่า
ตัวอย่างเช่น ต้องการหาคะแนนเฉลี่ยของนักศึกษาแต่ละกลุ่ม กลุ่มนักศึกษาอาจมีมากถึง 200 คน บางกลุ่มมี 20 คน บางกลุ่มมี 10 คน การประกาศตัวแปร float score[200]; ต้องจองเนื้อที่ไว้ถึง 200 ที่ ถึงแม้ส่วนใหญ่กลุ่มนักศึกษาจะมีเพียง 20 คน หรือ 30 คน ก็ตาม
การจัดสรรหน่วยความจำแบบพลวัต หมายถึงการจองเนื้อที่หน่วยความจำเท่าที่จำเป็นในขณะที่โปรแกรมประมวลผล การจัดสรรหน่วยความจำ ทำได้โดยใช้คำสั่ง new และปล่อยคืนหน่วยความจำด้วยคำสั่ง delete ดังตัวอย่างต่อไปนี้
[Source code: NewDel1.cpp]
ในการจองเนื้อที่หน่วยความจำ ถ้าจองไม่สำเร็จ คำสั่ง new จะคืนค่ากลับเป็น null pointer ซึ่งปกติมีค่าเป็น 0 แทนด้วยสัญลักษณ์ NULL การใช้คำสั่ง new ควรมีการตรวจสอบทุกครั้งว่าสามารถจองหน่วยความจำได้สำเร็จหรือไม่ ดังนี้
………
double *e = new double;
if ( e == NULL) exit(1);
*e =2.71828;
…………
การใช้ new และ delete กับตัวแปรชนิดอะเรย์
เมื่อใช้คำสั่ง new หรือ delete เพื่อจองหรือปลดปล่อยหน่วยความจำกับตัวแปรประเภทอะเรย์ จะต้องเพิ่มวงเล็บต่อท้ายดังนี้ new[ ] และ delete [ ] อะเรย์ที่ถูกสร้างโดยคำสั่ง new เรียกว่า dynamic array
ตัวอย่างต่อไปนี้เป็นการจองหน่วยความจำเพื่อเก็บค่า double จำนวน 10 ค่า
ตัวอย่างการใช้ new และ delete อีกตัวอย่างหนึ่ง
[Source code: NewDel2.cpp, NewDel3.cpp]
ในภาษา C มีคำสั่ง malloc () และ free ( ) ซึงเทียบเท่ากับคำสั่ง new และ delete สามารถใช้ได้ใน C++ เช่นเดียวกัน แต่ new และ delete จะดูเข้าใจง่ายกว่า
main ( ) {
int *i;
i = (int *) malloc( sizeof(int));}
*i = 15273;
printf(“ %d \n “, *i);
free(i);
ข้อควรระวังการใช้คำสั่ง new
1. ไม่สามารถใช้กับค่าคงที่ได้โดยตรง เช่น
i = new 125; // จองพื้นที่หน่วยความจำ 125 byte2. การจองหน่วยความจำสำหรับอะเรย์หลายมิติมีรูปแบบที่พึงระวังดังนี้
int *k;หรือ
k = new int[4][10]; // เป็นการจองอะเรย์ k ชนิดจำนวนเต็มใช้เนื้อที่ 80 byte
int *k;
int i;
i =4;
k = new int [i][10]; // จัดว่าเป็นคำสั่งที่ถูกต้อง
i = 10;
k = new int[4][i] ; // ไม่ถูกต้องในวงเล็บหลังจะต้องเป็นตัวคงที่เสมอ
---------------------------
แบบฝึกหัดที่ 5
1. ข้อใดประกาศตัวแปรแบบอะเรย์ได้ถูกต้อง
ก. int a[10];
ข. int a;
ค. a { 10};
ง. array a[10];
เฉลย ก.
2. เลขดรรนีของอะเรย์ที่มีสมาชิก 29 ตัว คือข้อใด
ก. 29
ข. 28
ค. 0
ง. ขึ้นอยู่กับโปรแกรมเมอร์กำหนด
เฉลย ข
3. ข้อใดเป็นอะเรย์แบบ 2 มิติ
ก. array a[20][20];
ข. int a[20][20];
ค. char a[20];
ง. int a[20,20];
เฉลย ข.
4. ต้องการเช้าถึงข้อมูลของสมาชิกอะเรย์ตัวที่ 7 คือข้อใด
ก. f[6];
ข. f[7];
ค. f(7);
ง. f
เฉลย ก.
5. ข้อใดต่อไปนี้บอกถึงตำแหน่งของหน่วยความจำของสมาชิกตัวแรกของอะเรย์ foo ซึ่งมีจำนวนสมาชิก 100 ตัว
ก. foo[0];
ข. foo;
ค. &foo;
ง. foo[1];
เฉลย ข.
ุ6. จงเขียนโปรแกรมหาค่าตัวเลขที่มากที่สุดในอะเรย์พร้อมกับระบุตำแหน่งของสมาชิกอะเรย์ที่เก็บตัวเลขค่ามากที่สุด
7. จงประกาศค่าตัวแปรอะเรย์ชื่อ rainfall ซึ่งใช้เก็บปริมาณน้ำฝนที่ตกลงมาใน 1 สัปดาห์ให้ค่าเริ่มต้นของสมาชิกแต่ละตัวมีค่าเป็นศูนย์
เฉลย const int days s= 7;
float rainfall [days] = {0};
8. จงประกาศตัวแปรอะเรย์เก็บข้อมูลชนิดตัวอักษรจำนวน 100 ตัว โดยเริ่มต้นให้ศมาชิกทุกตัวมีค่าเป็น a
เฉลย char arrayOfChar[100] = {‘a'};
9. จงประกาศตัวแปรอะเรย์เพี่อใช้ระบุตำแหน่งตารางหมากรุกขนาด 8 x 8
เฉลย int chessboard [8][8];
10. จงประกาศตัวแปรอะเรย์ 2 มิติ ขนาด 10 x 10 ให้แต่ละช่องเก็บอักษร x เมื่อตอนเริ่มต้น
เฉลย char table[10][10] = { ‘x' };
11. จงเขียนประกาศตัวแปรอะเรย์ต่อไปนี้
11.1 อะเรย์เก็บจำนวนเต็ม 10 ค่า
11.2 อะเรย์เก็บจำนวนจริง 4 ค่า กำหนดค่าเริ่มต้นดังนี้ 3.2, 1.8, 7.7 และ 5.1
11.3 อะเรย์เก็บข้อความ “Hello”
12. การประกาศตัวแปรต่อไปนี้ต่างก้นอย่างไร
char word[8] = “Welcome”;
char word[8] = { ‘W', ‘e', ‘l', ‘c', ‘o', ‘m', ‘e' };
13. จงนำข้อมุลบอกความสูงในตารางต่อไปนี้เขียนเป็นอะเรย์
เพศ
|
น้อยกว่า 150 cm
|
150 – 170 cm
|
มากกว่า 170 cm
|
ชาย
| 13 % | 55 % | 22 % |
หญิง
| 15% | 75% | 10% |