การเขียนโปรแกรม D - ตัวชี้
ตัวชี้การเขียนโปรแกรม D นั้นง่ายและสนุกในการเรียนรู้ งานการเขียนโปรแกรม D บางอย่างดำเนินการได้ง่ายขึ้นโดยใช้พอยน์เตอร์และงานการเขียนโปรแกรม D อื่น ๆ เช่นการจัดสรรหน่วยความจำแบบไดนามิกจะไม่สามารถทำได้หากไม่มีสิ่งเหล่านี้ ตัวชี้อย่างง่ายแสดงอยู่ด้านล่าง
แทนที่จะชี้ไปที่ตัวแปรโดยตรงตัวชี้จะชี้ไปที่ที่อยู่ของตัวแปร ดังที่คุณทราบทุกตัวแปรคือตำแหน่งหน่วยความจำและตำแหน่งหน่วยความจำทุกแห่งจะมีที่อยู่ที่กำหนดไว้ซึ่งสามารถเข้าถึงได้โดยใช้ตัวดำเนินการเครื่องหมายและ (&) ซึ่งหมายถึงที่อยู่ในหน่วยความจำ พิจารณาสิ่งต่อไปนี้ซึ่งพิมพ์ที่อยู่ของตัวแปรที่กำหนดไว้ -
import std.stdio;
void main () {
int var1;
writeln("Address of var1 variable: ",&var1);
char var2[10];
writeln("Address of var2 variable: ",&var2);
}
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
Address of var1 variable: 7FFF52691928
Address of var2 variable: 7FFF52691930
ตัวชี้คืออะไร?
ก pointerเป็นตัวแปรที่มีค่าเป็นที่อยู่ของตัวแปรอื่น เช่นเดียวกับตัวแปรหรือค่าคงที่คุณต้องประกาศตัวชี้ก่อนจึงจะสามารถใช้งานได้ รูปแบบทั่วไปของการประกาศตัวแปรพอยน์เตอร์คือ -
type *var-name;
ที่นี่ typeเป็นประเภทฐานของตัวชี้ ต้องเป็นประเภทการเขียนโปรแกรมที่ถูกต้องและvar-nameคือชื่อของตัวแปรพอยน์เตอร์ เครื่องหมายดอกจันที่คุณใช้เพื่อประกาศตัวชี้เป็นเครื่องหมายดอกจันเดียวกับที่คุณใช้ในการคูณ อย่างไรก็ตาม; ในคำสั่งนี้เครื่องหมายดอกจันจะถูกใช้เพื่อกำหนดตัวแปรเป็นตัวชี้ ต่อไปนี้เป็นการประกาศตัวชี้ที่ถูกต้อง -
int *ip; // pointer to an integer
double *dp; // pointer to a double
float *fp; // pointer to a float
char *ch // pointer to character
ชนิดข้อมูลจริงของค่าของพอยน์เตอร์ทั้งหมดไม่ว่าจะเป็นจำนวนเต็มจำนวนทศนิยมอักขระหรืออื่น ๆ จะเหมือนกันเป็นเลขฐานสิบหกแบบยาวที่แสดงแอดเดรสหน่วยความจำ ข้อแตกต่างเพียงอย่างเดียวระหว่างพอยน์เตอร์ของชนิดข้อมูลที่แตกต่างกันคือชนิดข้อมูลของตัวแปรหรือค่าคงที่ที่ตัวชี้ชี้ไป
การใช้พอยน์เตอร์ในการเขียนโปรแกรม D
มีการดำเนินการที่สำคัญไม่กี่อย่างเมื่อเราใช้คำแนะนำบ่อยมาก
เรากำหนดตัวแปรตัวชี้
กำหนดที่อยู่ของตัวแปรให้กับตัวชี้
ในที่สุดก็เข้าถึงค่าตามที่อยู่ที่มีอยู่ในตัวแปรตัวชี้
สิ่งนี้ทำได้โดยใช้ตัวดำเนินการยูนารี *ที่ส่งคืนค่าของตัวแปรที่อยู่ตามที่อยู่ที่ระบุโดยตัวถูกดำเนินการ ตัวอย่างต่อไปนี้ใช้ประโยชน์จากการดำเนินการเหล่านี้ -
import std.stdio;
void main () {
int var = 20; // actual variable declaration.
int *ip; // pointer variable
ip = &var; // store address of var in pointer variable
writeln("Value of var variable: ",var);
writeln("Address stored in ip variable: ",ip);
writeln("Value of *ip variable: ",*ip);
}
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
Value of var variable: 20
Address stored in ip variable: 7FFF5FB7E930
Value of *ip variable: 20
ตัวชี้ Null
เป็นแนวทางปฏิบัติที่ดีเสมอในการกำหนดค่า NULL ให้กับตัวแปรตัวชี้ในกรณีที่คุณไม่มีที่อยู่ที่แน่นอนที่จะกำหนด สิ่งนี้ทำได้ในช่วงเวลาของการประกาศตัวแปร ตัวชี้ที่กำหนดค่าว่างเรียกว่า anull ตัวชี้
ตัวชี้โมฆะคือค่าคงที่ที่มีค่าเป็นศูนย์ที่กำหนดไว้ในไลบรารีมาตรฐานต่างๆรวมถึง iostream พิจารณาโปรแกรมต่อไปนี้ -
import std.stdio;
void main () {
int *ptr = null;
writeln("The value of ptr is " , ptr) ;
}
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
The value of ptr is null
ในระบบปฏิบัติการส่วนใหญ่โปรแกรมไม่ได้รับอนุญาตให้เข้าถึงหน่วยความจำที่อยู่ 0 เนื่องจากหน่วยความจำนั้นสงวนไว้โดยระบบปฏิบัติการ อย่างไรก็ตาม; ที่อยู่หน่วยความจำ 0 มีความสำคัญเป็นพิเศษ เป็นการส่งสัญญาณว่าตัวชี้ไม่ได้ตั้งใจให้ชี้ไปยังตำแหน่งหน่วยความจำที่สามารถเข้าถึงได้
ตามแบบแผนถ้าตัวชี้มีค่า null (ศูนย์) จะถือว่าชี้ไปที่ความว่างเปล่า ในการตรวจสอบตัวชี้ค่าว่างคุณสามารถใช้คำสั่ง if ได้ดังนี้ -
if(ptr) // succeeds if p is not null
if(!ptr) // succeeds if p is null
ดังนั้นหากพอยน์เตอร์ที่ไม่ได้ใช้ทั้งหมดได้รับค่า null และคุณหลีกเลี่ยงการใช้พอยน์เตอร์ว่างคุณสามารถหลีกเลี่ยงการใช้พอยน์เตอร์ที่ไม่ได้กำหนดค่าเริ่มต้นในทางที่ผิดโดยไม่ได้ตั้งใจ หลายครั้งตัวแปรที่ไม่ได้กำหนดค่าเริ่มต้นจะมีค่าขยะบางอย่างและยากที่จะแก้ไขข้อบกพร่องของโปรแกรม
ตัวชี้เลขคณิต
มีตัวดำเนินการเลขคณิตสี่ตัวที่สามารถใช้กับพอยน์เตอร์ได้: ++, -, + และ -
ในการทำความเข้าใจเลขคณิตของตัวชี้ให้เราพิจารณาตัวชี้จำนวนเต็มที่มีชื่อว่า ptrซึ่งชี้ไปที่แอดเดรส 1000 สมมติว่าเป็นจำนวนเต็ม 32 บิตให้เราดำเนินการคำนวณทางคณิตศาสตร์ต่อไปนี้บนตัวชี้ -
ptr++
จากนั้น ptrจะชี้ไปที่ตำแหน่ง 1004 เนื่องจากแต่ละครั้งที่เพิ่ม ptr มันจะชี้ไปที่จำนวนเต็มถัดไป การดำเนินการนี้จะย้ายตัวชี้ไปยังตำแหน่งหน่วยความจำถัดไปโดยไม่ส่งผลกระทบต่อค่าจริงที่ตำแหน่งหน่วยความจำ
ถ้า ptr ชี้ไปที่อักขระที่มีที่อยู่คือ 1,000 จากนั้นการดำเนินการข้างต้นจะชี้ไปที่ตำแหน่ง 1001 เนื่องจากอักขระถัดไปจะมีให้ที่ 1001
การเพิ่มตัวชี้
เราชอบใช้ตัวชี้ในโปรแกรมของเราแทนอาร์เรย์เนื่องจากตัวชี้ตัวแปรสามารถเพิ่มขึ้นได้ซึ่งแตกต่างจากชื่ออาร์เรย์ที่ไม่สามารถเพิ่มขึ้นได้เนื่องจากเป็นตัวชี้คงที่ โปรแกรมต่อไปนี้จะเพิ่มตัวชี้ตัวแปรเพื่อเข้าถึงแต่ละองค์ประกอบที่สำเร็จของอาร์เรย์ -
import std.stdio;
const int MAX = 3;
void main () {
int var[MAX] = [10, 100, 200];
int *ptr = &var[0];
for (int i = 0; i < MAX; i++, ptr++) {
writeln("Address of var[" , i , "] = ",ptr);
writeln("Value of var[" , i , "] = ",*ptr);
}
}
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
Address of var[0] = 18FDBC
Value of var[0] = 10
Address of var[1] = 18FDC0
Value of var[1] = 100
Address of var[2] = 18FDC4
Value of var[2] = 200
พอยน์เตอร์เทียบกับอาร์เรย์
ตัวชี้และอาร์เรย์มีความสัมพันธ์กันอย่างมาก อย่างไรก็ตามพอยน์เตอร์และอาร์เรย์ไม่สามารถใช้แทนกันได้อย่างสมบูรณ์ ตัวอย่างเช่นพิจารณาโปรแกรมต่อไปนี้ -
import std.stdio;
const int MAX = 3;
void main () {
int var[MAX] = [10, 100, 200];
int *ptr = &var[0];
var.ptr[2] = 290;
ptr[0] = 220;
for (int i = 0; i < MAX; i++, ptr++) {
writeln("Address of var[" , i , "] = ",ptr);
writeln("Value of var[" , i , "] = ",*ptr);
}
}
ในโปรแกรมด้านบนคุณสามารถดู var.ptr [2] เพื่อตั้งค่าองค์ประกอบที่สองและ ptr [0] ซึ่งใช้ในการตั้งค่าองค์ประกอบซีรอ ธ ตัวดำเนินการเพิ่มสามารถใช้ได้กับ ptr แต่ใช้กับ var ไม่ได้
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
Address of var[0] = 18FDBC
Value of var[0] = 220
Address of var[1] = 18FDC0
Value of var[1] = 100
Address of var[2] = 18FDC4
Value of var[2] = 290
ชี้ไปที่ตัวชี้
ตัวชี้ไปยังตัวชี้คือรูปแบบของการเหนี่ยวนำหลายทิศทางหรือตัวชี้โซ่ โดยปกติตัวชี้จะมีที่อยู่ของตัวแปร เมื่อเรากำหนดตัวชี้ไปยังตัวชี้ตัวชี้ตัวแรกจะมีที่อยู่ของตัวชี้ตัวที่สองซึ่งชี้ไปยังตำแหน่งที่มีค่าจริงตามที่แสดงด้านล่าง
ตัวแปรที่เป็นตัวชี้ไปยังตัวชี้จะต้องถูกประกาศเช่นนี้ ซึ่งทำได้โดยการใส่เครื่องหมายดอกจันเพิ่มเติมไว้หน้าชื่อ ตัวอย่างเช่นต่อไปนี้เป็นไวยากรณ์เพื่อประกาศตัวชี้ไปยังตัวชี้ประเภท int -
int **var;
เมื่อค่าเป้าหมายถูกชี้ทางอ้อมโดยตัวชี้ไปยังตัวชี้การเข้าถึงค่านั้นจะต้องใช้ตัวดำเนินการเครื่องหมายดอกจันสองครั้งดังที่แสดงด้านล่างในตัวอย่าง -
import std.stdio;
const int MAX = 3;
void main () {
int var = 3000;
writeln("Value of var :" , var);
int *ptr = &var;
writeln("Value available at *ptr :" ,*ptr);
int **pptr = &ptr;
writeln("Value available at **pptr :",**pptr);
}
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
Value of var :3000
Value available at *ptr :3000
Value available at **pptr :3000
การส่งตัวชี้ไปยังฟังก์ชัน
D ช่วยให้คุณส่งตัวชี้ไปยังฟังก์ชัน ในการดำเนินการดังกล่าวเพียงแค่ประกาศพารามิเตอร์ฟังก์ชันเป็นชนิดตัวชี้
ตัวอย่างง่ายๆต่อไปนี้ส่งตัวชี้ไปยังฟังก์ชัน
import std.stdio;
void main () {
// an int array with 5 elements.
int balance[5] = [1000, 2, 3, 17, 50];
double avg;
avg = getAverage( &balance[0], 5 ) ;
writeln("Average is :" , avg);
}
double getAverage(int *arr, int size) {
int i;
double avg, sum = 0;
for (i = 0; i < size; ++i) {
sum += arr[i];
}
avg = sum/size;
return avg;
}
เมื่อโค้ดด้านบนถูกคอมไพล์เข้าด้วยกันและดำเนินการจะให้ผลลัพธ์ดังต่อไปนี้ -
Average is :214.4
กลับตัวชี้จากฟังก์ชัน
พิจารณาฟังก์ชันต่อไปนี้ซึ่งส่งคืนตัวเลข 10 ตัวโดยใช้ตัวชี้หมายถึงที่อยู่ขององค์ประกอบอาร์เรย์แรก
import std.stdio;
void main () {
int *p = getNumber();
for ( int i = 0; i < 10; i++ ) {
writeln("*(p + " , i , ") : ",*(p + i));
}
}
int * getNumber( ) {
static int r [10];
for (int i = 0; i < 10; ++i) {
r[i] = i;
}
return &r[0];
}
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
*(p + 0) : 0
*(p + 1) : 1
*(p + 2) : 2
*(p + 3) : 3
*(p + 4) : 4
*(p + 5) : 5
*(p + 6) : 6
*(p + 7) : 7
*(p + 8) : 8
*(p + 9) : 9
ชี้ไปที่อาร์เรย์
ชื่ออาร์เรย์เป็นตัวชี้ค่าคงที่ขององค์ประกอบแรกของอาร์เรย์ ดังนั้นในการประกาศ -
double balance[50];
balanceเป็นตัวชี้ไปที่ & สมดุล [0] ซึ่งเป็นที่อยู่ขององค์ประกอบแรกของสมดุลอาร์เรย์ ดังนั้นส่วนของโปรแกรมต่อไปนี้จึงกำหนดp ที่อยู่ขององค์ประกอบแรกของ balance -
double *p;
double balance[10];
p = balance;
ถูกกฎหมายที่จะใช้ชื่ออาร์เรย์เป็นตัวชี้ค่าคงที่และในทางกลับกัน ดังนั้น * (balance + 4) จึงเป็นวิธีที่ถูกต้องในการเข้าถึงข้อมูลอย่างสมดุล [4]
เมื่อคุณจัดเก็บที่อยู่ขององค์ประกอบแรกใน p แล้วคุณสามารถเข้าถึงองค์ประกอบอาร์เรย์โดยใช้ * p, * (p + 1), * (p + 2) และอื่น ๆ ตัวอย่างต่อไปนี้แสดงแนวคิดทั้งหมดที่กล่าวถึงข้างต้น -
import std.stdio;
void main () {
// an array with 5 elements.
double balance[5] = [1000.0, 2.0, 3.4, 17.0, 50.0];
double *p;
p = &balance[0];
// output each array element's value
writeln("Array values using pointer " );
for ( int i = 0; i < 5; i++ ) {
writeln( "*(p + ", i, ") : ", *(p + i));
}
}
เมื่อโค้ดด้านบนถูกคอมไพล์และเรียกใช้งานจะให้ผลลัพธ์ดังนี้ -
Array values using pointer
*(p + 0) : 1000
*(p + 1) : 2
*(p + 2) : 3.4
*(p + 3) : 17
*(p + 4) : 50