การเขียนโปรแกรม 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