ポインタと配列

2009年5月18日は°で掲示されるC / C + +

このシリーズの最初の記事では、我々は約少し話しましたポインタ 第二に、我々は話す参照 今日は、ポインタと配列(または行列)との間の親密な関係(ui!を)探求する。

リコール配列

配列や行列、または配列、それは、同種のデータのセットを表す同じ型(int型、float型、等)すなわちために使用される数学的な抽象化です。 この抽象化は、行と列を持つテーブル形式で編成されています。 配列の各要素は、与えられた要素e(i、j)は"行i"、"列j"の唯一の要素を表すように、独自の座標を(行と列)がある。

次のように配列宣言の構文は次のとおりです。

  / /一次元の行列またはベクトル
 10 ] ; // 10 elementos, do 0 ao 9 IVET int型 [10] 0から9まで/ /の10個の要素、
 23 ] ; // 23 elementos, do 0 ao 22 cvet のchar [23]は 0から22〜/ / 23要素、

 / / 2次元行列
 2 ] [ 3 ] ; // 2 linhas (0 a 1) e 3 colunas (0 a 2) IMAT  、int [2] [3] / / 2行(0-1)と3つの列(0-2)
 10 ] [ 2 ] ; // 10 linhas (0 a 9) e 2 colunas (0 a 1) DMAT 二重 [10] [2] / / 10行(0-9)と2つの列(0-1) 

各配列要素は、他の独立しており、次の構文のようにアクセスできます。

 10 ] ; int imat [ 3 ] [ 4 ] ; // Alterando o quarto elemento do vetor ivet. // Lembre-se que começa-se a contar a partir do elemento zero ivet [ 3 ] = 13 ; // Lendo o segundo elemento do vetor ivet int num = ivet [ 1 ] ; // Alterando o elemento da primeira linha, segunda coluna de imat imat [ 0 ] [ 1 ] = 42 ; // Lendo o elemento na terceira linha, quarta coluna de imat int foo = imat [ 2 ] [ 3 ] ; IVET int型 [10];。IMAT INT [3] [4] / / IVETベクトルの4番目の要素を変更する/ /一つの要素ゼロ IVET から [3] = 13 / /カウントを開始することを忘れないでください配列の2番目の要素を読み込み します。int a = IVET IVET [1] / /最初の行の要素を変更すると、番目の列IMAT IMAT [0] [1] = 42 / / 3行目に要素を読み込み、番目の列IMAT IMAT int fooのように = [2] [3]; 

この記事の中で我々が背景の行列を議論しませんが、我々は、かなり直感的に配列とポインタの関係を調査します。

行列のサイズ

配列は同じタイプの複数の値を保持できる抽象化なので、何がそれをサイズ? それがメモリ内で占有する容量?

以下のコードを考えてみます。

  17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
  10 ] ; IVET int型 [10];
 13 ] ; cvet のchar [13];
 20 ] ; dvet ダブル [20];

 3 ] [ 4 ] ; CMAT のchar [3] [4];
 5 ] [ 4 ] ; IMAT INT [5] [4];

 "sizeof(int) = " << sizeof ( int ) << endl ; 裁判所 <<"はsizeof(int )="<<sizeof(int )<<endl を。
 "sizeof(char) = " << sizeof ( char ) << endl ; 裁判所 <<"はsizeof(char) ="<<sizeof演算 (CHAR)<<endlを。
 "sizeof(double) = " << sizeof ( double ) << endl ; 裁判所 <<"はsizeof(ダブル )="<<sizeof (ダブル)<<endl を。

 "sizeof(ivet) = " << sizeof ( ivet ) << endl ; 裁判所 <<"はsizeof(IVET)="<<sizeof 演算 (IVET)<<endlを。
 "sizeof(cvet) = " << sizeof ( cvet ) << endl ; 裁判所 <<"はsizeof(cvet)="<<sizeof 演算 (cvet)<<endlを。
 "sizeof(dvet) = " << sizeof ( dvet ) << endl ; 裁判所 <<"はsizeof(dvet)="<<sizeof 演算 (dvet)<<endlを。
 "sizeof(cmat) = " << sizeof ( cmat ) << endl ; 裁判所 <<"はsizeof(CMAT)="<<sizeof 演算 (CMAT)<<endlを。
 "sizeof(imat) = " << sizeof ( imat ) << endl ; 裁判所 <<"はsizeof(IMAT)="<<sizeof 演算 (IMAT)<<endlを。 

結果は非常に合理的です。 行列が占有する領域は、要素型のサイズ(行x列がxをsizeof(type))を掛けた要素数と等しくなります。 今、各要素が独立である場合、それは各々がメモリ内の別の場所を占めることが想定され、それ以外の場合、他の一つの要素を上書きします。 ので、各要素が独自のメモリアドレスを持っていることでしょうか?

配列とメモリアドレス

:利便性のために、我々は、可能なアドレスのサイズが1バイトのみを与えている文字の配列を、分析し、

  15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
  5 ; const intに最大= 5;
 max ] = { 'A' , 'B' , 'C' , 'D' , 'E' } ; cvet のchar [MAX] = {'A'、'B'、'C'、'D'、'E'};

 / /コンテンツ、データの値とアドレスを表示します。
 "Índice \t Valor \t Endereço do elemento \n " ) ;  printf("インデックス \ t\ tのアドレス要素\ nを");
 int i = 0 ; i < max ; i ++ ) { するfor(int i = 0;<マックスは、i + +){
     "%d \t %c \t %p \n " , i, cvet [ i ] , & cvet [ i ] ) ;  printf("%d \ T%C \ T%P \ nを"、私、cvet [i]は、&cvet [I]);
 }

 / /配列のアドレスを表示します。
 "Endereço da matriz: %p \n " , & cvet ) ; printf(" 配列のアドレス :%P \ nを"、&cvet);

 / /配列のアドレスを表示するもう一度
 "Endereço da matriz: %p \n " , cvet ) ; printf(" 配列のアドレス :%P \ nを"、cvet); 

要素のアドレスはシーケンシャルであり、すなわち、各要素は、前者の隣に保存されます。 さらに2つの興味深い事実があります。

  1. 25行目に示されている配列(&cvet)、、のアドレスは配列の最初の要素と同じです。
  2. 自体が、回線28に示すように、ポインタとして解釈することができる変数をCvet。

C + +では、共通の配列がポインタとして名前が解釈できるメモリの連続した​​ブロック(キャスト)つまり、その最初の要素を指す。 さらに、それは限りの宛先へのポインタは、配列要素の型と同じであるとして配列を指すように有効なポインタです。 ポインタへの配列のatrubuição中に、コンパイラは暗黙の型変換を行います。 ターゲットポインタは、配列が占有するメモリ領域へのポインタとして解釈されるべきである。

それほどはっきりしていない結果の一つは、キャストでその暗黙のうちに、情報がエリア、メモリアレイであることが失われます。 ので、情報は、アレイのサイズによって失われます。 ポインタの観点から、それはあまりにも任意のサイズのメモリの任意のブロックの先頭を指しています。 終了し、ポインタへの配列のために行くことはより制限の少ない、より低レベルのためのより限定的とより抽象的な高レベルの抽象化から行くことを意味します。

一方、配列へのポインタを代入しようと互換性のないタイプのコンパイルエラーを生成します。 配列はポインタで、nのメモリブロック(バイト)のみ与えられた、アドレスを持っています。 コンパイラは、ポインタが1,2、または200バイトの領域を指しているかどうか事前に知る方法がない。

  15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
  300 ; const intに最大= 300;
 max ] ; cvet のchar [MAX];
 pc = 0 ; char *型 PC = 0;

 " \n Antes da atribuição \n " ) ;  printf("\ nの前に代入する\ n");
 "cvet = %p \n " , cvet ) ; printf("cvet =%P \ nを"、cvet);
 "pc = %p \n " , pc ) ; printf("PC =%P \ nを"、PC);
 "sizeof(cvet) = %lu \n " , sizeof ( cvet ) ) ;  printf("sizeof演算 (cvet)=%luは\ nを"、 sizeof(cvet));
 "sizeof(pc) = %lu \n " , sizeof ( pc ) ) ;  printf("sizeof演算 (CP)=%luは\ nを"、 sizeof(CP));

 PC = cvet;

 " \n Depois da atribuição \n " ) ;  printf("\ nを代入した後、\ nを");
 "cvet = %p \n " , cvet ) ; printf("cvet =%P \ nを"、cvet);
 "pc = %p \n " , pc ) ; printf("PC =%P \ nを"、PC);
 "sizeof(cvet) = %lu \n " , sizeof ( cvet ) ) ;  printf("sizeof演算 (cvet)=%luは\ nを"、 sizeof(cvet));
 "sizeof(pc) = %lu \n " , sizeof ( pc ) ) ;  printf("sizeof演算 (CP)=%luは\ nを"、 sizeof(CP)); 

代入(25行) ポインタ pcの前にいることに注意してください ゼロなので、それが初期化されました。 サイズは、配列がわずか300バイトとポインタ8(私のマシンはAMD 64ですが)があることを示すので。 割り当てた後は、両方が同じメモリ領域への"ポイント"を渡すが、サイズは変更しないでください。 そこに暗黙的キャストはchar *にchar [300]からであり、このゲームでポインタ pc は、それが指すメモリ領域のサイズを知ることができない。 しかし、配列はcvetいかなる実存的危機なく、正確に彼が何であるか知って続けます。

ポインタは、算術 - 俗、だから何?

なぜ、私は行列内のデータが並んで配置されていることがわかっている場合、私は次のアドレスにジャンプするポインタを使用し、次の要素にアクセスできます。 これは、ポインタ演算と呼ばれています。

  15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
  6 ; const intに最大= 6; 
 max ] = { 'B' , 'L' , 'A' , 'B' , 'O' , 'S' } ; cvet のchar [MAX] = {'B'、'L'、'、'B'、'O'、'S'};

 pc = cvet ; char *型 PC = cvet;

 int i = 0 ; i < max ; i ++ ) { するfor(int i = 0; I <最大;+ +){
     "%c" , * ( pc + i ) ) ; printf("%C"、*(PC + I));
 }
 " \n " ) ;  printf("\ N");

 int i = 0 ; i < max ; i ++ ) { するfor(int i = 0;<マックスは、i + +){
     "%c" , * pc ++ ) ; printf("%c" 、* PC + +);
 }
 " \n " ) ;  printf("\ N");


 / /ここで整数と
 max ] = { 1 , 2 , 3 , 4 , 5 , 6 } ; IVET INT [MAX] = {1、2、3、4、5、6};
 pi = ivet ; int型 *π= IVET;

 int i = 0 ; i < max ; i ++ ) { するfor(int i = 0;<マックスは、i + +){
     "%p = %d \n " , pi, * pi ++ ) ; printf("%P =%d個\ n"を、π、π* + +);
 }
 " \n " ) ;  printf("\ N");

 二次元で/ /
 2 ] [ 3 ] = { { 'B' , 'L' , 'A' } , { 'B' , 'O' , 'S' } } ; CMAT のchar [2] [3] = {{'B'、'L'、'、A'}、{'B'、'O'、'S'}};
 ppc ; char *の PPC。

 char * ) cmat ; PPC =(char *)を CMAT。

 int i = 0 ; i < 2 ; i ++ ) { するfor(int i = 0、I <2は、i + +){
     int j = 0 ; j < 3 ; j ++ ) { するfor(int j = 0、j<3 J + +){ のための
         "%c" , * ( ppc + 3 * i + j ) ) ; printf("%C"、*(PPC + 3 * I + J));
     }
     " \n " ) ;  printf("\ N");
 }
 " \n " ) ;  printf("\ N"); 

18行目でポインタ pc は、現在のアレイcvetを指しているその結果、その最初の要素に、文字'B'。 21行目ではそれが文字'B'格納されたアドレスですPCの内容は、、つまりは、逆参照して増加しています。 iの値がゼロの最初のパスでは、その'B'を逆参照するための値です。 次の手順では、次のアドレスが元の配列に格納されている他の文字に逆参照されています。 それは、多かれ少なかれ、構文cvet [i]を使用する場合、コンパイラが内部的に何をするかです。 配列の抽象化を使用すると、メモリその*(PC + i)の連続した領域を扱うの友好的な方法を提供します

しかし、もし配列の抽象化は、そのポインタ演算を使用する方が簡単です?

一つの答えは26行です。 彼女は、その21行目と同じことをしますが、少し速く。 21行目の構文では、または同様に、配列の構文は、任意のデータへのアクセスは非常に大まかなコマンドで要約することができます。

  1. 配列のベースアドレスを取る。
  2. インデックスの値に対処するために追加します。
  3. この新しいアドレスを逆参照。

すでにポインタ演算は次のようになります。

  1. デリファレンスこのアドレス;

コマンドのインクリメント(またはアドレスで行われている)、それがループの一部であるので、言うことはありません、しかし私+ +;よりも速い= B + C、。 今1 MBのデータ領域に適用される66%のこの小さなゲインを想像してみてください。 以下200万コマンドに渡り存在します!

メモリの任意の領域を操作するポインタを使用してのテクニックは、一般的に低レベルのプログラミング(マシンに最も近い)、の操作で使用されるバッファは 、他の汚いトリックの間や文字列、。 コンピュータの腸では、メモリの大きな領域を掃引の操作は、多くの場合、ポインタ演算を使用して実行されます。 このレベルでは、 ダーウィンは最高の君臨して唯一の準備が生き残るだろう。 ここから言語のみ心に純粋に理解できるという能力を与えることから始まります。

重要な観察は、全体的な経験で繰り返される行31と38との間ということです。 整数は4バイトであるため、増分が自動的に増分が自動的にはsizeof(型)に計算されるすなわち、1の4の4バイトではなく、1で作成されていることに注意してください。 ポインタをインクリメントすると、実際のデータと同様のメモリの次の領域だけではなく、次のアドレスにアクセスすることを意味します。 charのサイズが1バイトなので、charへのポインタをincremetamos時、1バイトのみを移動する。 あなたがdoubleへのポインタをインクリメントした場合、我々は、8バイトを移動する、というようになります。

別の観察は、40〜52行に示すように二次元配列を"線形化"することができるということです。 これは、例えば、プロセッサキャッシュの有効活用を、作るために、適用可能な場合、便利です。

ボイド*の汎性欲的なポインタの

以前の私は、配列のデータと同じ型になったのポインタへの配列を割り当てることのみが可能であると述べた。 私はあからさまな嘘! その理由は、このトピックの前のポストをあきらめた人のために、それはつまり、できないと信じてする方が安全であることです。 :)

ルールには2つの例外があります。 最初の明示的な型変換と適切な種類を指している先のポインタ"と考えているが"あるときです。 例は、前のコードの44行になります。

二つ目はvoidへのポインタの場合です。 voidポインタは、それが指すメモリ領域にあるデータの種類には要求を行っていないポインタです。 それは何か非常に低いレベルを、メモリの一般的な領域へのポインタです。

それは、int *、int、およびchar *型を基準にしているので、デリファレンスその前にvoidへのポインタが指すデータを、使用するには、に逆参照の有効な型への明示的なキャストをされていることを確認しなければなりませんcharは、それはvoid *に、参照されているかを推測?

  15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
  6 ; const intに最大= 6; 
 max ] = { 'B' , 'L' , 'A' , 'B' , 'O' , 'S' } ; cvet のchar [MAX] = {'B'、'L'、'、'B'、'O'、'S'};

 pv = cvet ; ボイド * PV = cvet;

 / /コンパイルエラー。
 / / * Pvの
 / /谷はsizeof(void)の?

 int i = 0 ; i < max ; i ++ ) { するfor(int i = 0;<マックスは、i + +){
     "%c" , * ( ( ( char * ) pv ) + i ) ) ;  printf("%C"、*(((char *)のPV)+ I));
 }
 " \n " ) ;  printf("\ N"); 

一つのコントロール/このエリアに含まれるデータの種類の知識、またはそのパラメータの種類について仮定を行うことができない機能を有することなく、メモリの一般的な領域を指すようにする必要があるときにvoidへのポインタは、そのように、使用されていますAPI libにpthreadsの (リンク任意)。

エンディング

我々はポインタのトピックについて掘り下げるより、我々は近いマシンになります。 多くのCのパワーのとC + +と多くの非難のも、そこから来ている。 複雑さが増加し、リスクれる。 多くの場合そこに楽しみがある!

リンク

コメント

  • レアンドロメロ http://0xc0de.wordpress.com

    よく教訓的。 私は、"汎性欲主義者の手を。"好き :)

  • フランシスコ-ブルーノ-ルイサ

    rrrrr

Disqusを搭載ブログのコメント