D-programming-pointers

提供:Dev Guides
移動先:案内検索

Dプログラミング-ポインター

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

ポインターとは

  • ポインタ*は、値が別の変数のアドレスである変数です。 他の変数や定数と同様に、操作する前にポインターを宣言する必要があります。 ポインタ変数宣言の一般的な形式は-
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

すべてのポインターの値の実際のデータ型は、整数、浮動小数点、文字、またはその他のいずれであっても同じで、メモリアドレスを表す長い16進数です。 異なるデータ型のポインターの唯一の違いは、ポインターが指す変数または定数のデータ型です。

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をポインター変数に割り当てることは常に良い習慣です。 これは、変数宣言時に行われます。 nullが割り当てられたポインターは、 null ポインターと呼ばれます。

nullポインターは、iostreamを含むいくつかの標準ライブラリで定義されたゼロの値を持つ定数です。 次のプログラムを検討してください-

import std.stdio;

void main () {
   int  *ptr = null;
   writeln("The value of ptr is " , ptr) ;
}

上記のコードをコンパイルして実行すると、次の結果が生成されます-

The value of ptr is null

ほとんどのオペレーティングシステムでは、アドレス0のメモリにアクセスすることは許可されていません。これは、メモリがオペレーティングシステムによって予約されているためです。 しかしながら;メモリアドレス0には特別な意味があります。ポインタがアクセス可能なメモリ位置を指すことを意図していないことを示します。

慣例により、ポインターにヌル(ゼロ)値が含まれている場合、何も指していないと見なされます。 ヌルポインタを確認するには、次のようにif文を使用できます-

if(ptr)    //succeeds if p is not null
if(!ptr)   //succeeds if p is null

したがって、すべての未使用のポインターにnull値が与えられ、nullポインターの使用を避ければ、初期化されていないポインターの誤った誤用を避けることができます。 多くの場合、初期化されていない変数はいくつかのジャンク値を保持し、プログラムのデバッグが困難になります。

ポインタ演算

ポインターで使用できる4つの算術演算子があります:+、-、、および-

ポインター演算を理解するために、アドレス1000を指す ptr という名前の整数ポインターを考えてみましょう。 32ビット整数を想定して、ポインタで次の算術演算を実行してみましょう-

ptr++

次に、ptrがインクリメントされるたびに次の整数を指すため、 ptr は位置1004を指します。 この操作は、メモリ位置の実際の値に影響を与えることなく、ポインタを次のメモリ位置に移動します。

*ptr* がアドレスが1000の文字を指す場合、次の文字は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);
   }
}

上記のプログラムでは、2番目の要素を設定するためのvar.ptr [2]と、0番目の要素を設定するために使用される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

ポインターへのポインター

ポインターへのポインターは、複数の間接指定またはポインターのチェーンの形式です。 通常、ポインターには変数のアドレスが含まれます。 ポインターへのポインターを定義すると、最初のポインターには2番目のポインターのアドレスが含まれます。これは、以下に示すように、実際の値を含む場所を指します。

C ++ポインターからポインター

ポインターへのポインターである変数は、そのように宣言する必要があります。 これは、名前の前に追加のアスタリスクを配置することにより行われます。 たとえば、次は、int型のポインタへのポインタを宣言する構文です-

int **var;

ターゲット値がポインタへのポインタによって間接的に指し示されている場合、その値にアクセスするには、次の例に示すように、アスタリスク演算子を2回適用する必要があります-

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* は、配列balanceの最初の要素のアドレスである&balance [0]へのポインターです。 したがって、次のプログラムの断片は、 *balance* の最初の要素のアドレスを *p* に割り当てます-
double *p;
double balance[10];

p = balance;

配列名を定数ポインターとして使用すること、およびその逆も有効です。 したがって、*(balance + 4)は、balance [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