web-dev-qa-db-ja.com

ポインター演算

誰かがポインター演算の良い記事や説明(ブログ、例)を持っていますか?オーディエンスがJava CとC++を学ぶプログラマーの束であると考えてください。

58
leora

まず、 binky ビデオが役立つ場合があります。これは、ポインタに関する素晴らしいビデオです。算術については、ここに例があります:

int * pa = NULL;
int * pb = NULL;
pa += 1; // pa++. behind the scenes, add sizeof(int) bytes
assert((pa - pb) == 1);

print_out(pa); // possibly outputs 0x4
print_out(pb); // possibly outputs 0x0 (if NULL is actually bit-wise 0x0)

(NULLポインター値を含むポインターを厳密にインクリメントすることは未定義の動作であることに注意してください。ポインターの値のみに関心があるため、NULLを使用しました。

次の2つの重要な概念を示します

  • ポインターへの整数の加算/減算は、ポインターをN要素だけ前後に移動することを意味します。したがって、intが4バイトの大きさである場合、paにはプラットフォームで0を含むことができます。
  • 別のポインターによるポインターの減算は、要素によって測定された距離を取得することを意味します。したがって、paからpbを引くと、1つの要素距離があるため、1が得られます。

実際の例について。あなたが関数を書いて、人々があなたに開始と終了ポインタを提供すると仮定します(C++で非常に一般的なこと):

void mutate_them(int *begin, int *end) {
    // get the amount of elements
    ptrdiff_t n = end - begin;
    // allocate space for n elements to do something...
    // then iterate. increment begin until it hits end
    while(begin != end) {
        // do something
        begin++;
    }
}

ptrdiff_tは(end-begin)のタイプです。一部のコンパイラでは「int」の同義語の場合もありますが、別のコンパイラでは別の型の場合もあります。わからないので、一般的なtypedef ptrdiff_t

ここで、私がポインタを学びました。 http://www.cplusplus.com/doc/tutorial/pointers.html

ポインターを理解すれば、ポインターの計算は簡単です。これと通常の算術の唯一の違いは、ポインターに追加する数値に、ポインターが指している型のサイズが乗算されることです。たとえば、intへのポインタがあり、intのサイズが4バイトの場合、(pointer_to_int + 4)は、16バイト(4 int)先のメモリアドレスに評価されます。

だから書くとき

(a_pointer + a_number)

ポインター演算では、実際に何が起こっているのですか

(a_pointer + (a_number * sizeof(*a_pointer)))

通常の算術で。

57
Jeremy Ruten

nLPを適用して、アドレス算術と呼びます。 「ポインター」が恐れられ、誤解されているのは、主に、間違った人々や間違った段階で間違った方法で間違った例を教えられているためです。誰もそれを「取得」しないのも不思議ではありません。

ポインタを教えるとき、教員は「pはaへのポインタ、pの値はaのアドレス」などについて続けます。うまくいきません。ここにあなたが構築するための原料があります。それで練習すれば、生徒はそれを取得します。

'int a'、aは整数で、整数型の値を格納します。 「int * p」、pは「int star」で、「int star」タイプの値を格納します。

「a」は「a」に格納されている「what」整数を取得する方法です(「value of a」を使用しないでください)

'b = a'これが機能するには、両側が同じタイプでなければなりません。 aがintの場合、bはintを格納できる必要があります。 (したがって、______ b、空白は「int」で埋められます)

'p =&a'これが機能するには、両側が同じタイプでなければなりません。 aが整数で、&aがアドレスの場合、pは整数のアドレスを格納できる必要があります。 (したがって、______ p、空白は「int *」で埋められます)

型情報を引き出すためにint * pを異なる方法で記述します:

int * | p

「p」とは何ですか? ans: 'int *'です。したがって、「p」は整数のアドレスです。

int | * p

「* p」とは何ですか? ans:それは 'int'です。したがって、「* p」は整数です。

次にアドレス演算に進みます。

int a; a = 1; a = a + 1;

「a = a + 1」で何をしていますか? 「次」と考えてください。 aは数字なので、これは「次の数字」と言うようなものです。 aは1を保持するため、「next」と言うと2になります。

//誤った例。あなたは警告されました!!! int * p int a; p =&a; p = p + 1;

「p = p + 1」で何をしていますか?まだ「次」と言っています。今回は、pは数字ではなく住所です。だから私たちが言っているのは「次のアドレス」です。次のアドレスは、データ型、より具体的にはデータ型のサイズに依存します。

printf( "%d%d%d"、sizeof(char)、sizeof(int)、sizeof(float));

そのため、アドレスの「次」は前方にsizeof(データ型)移動します。

これは私と私が教えるために使用したすべての人々のために働いています。

6
Kinjal Dixit

ポインター演算の良い例を、次の文字列長の関数と考えます。

int length(char *s)
{
   char *str = s;
   while(*str++);
   return str - s;
}
3
arul

したがって、覚えておくべき重要なことは、ポインターは、参照解除用に入力されたWordサイズの変数にすぎないということです。つまり、void *、int *、long long **のいずれであっても、Wordサイズの変数にすぎません。これらの型の違いは、コンパイラが逆参照された型と見なすものです。明確にするために、Wordサイズは仮想アドレスの幅を意味します。これが何を意味するかわからない場合は、64ビットマシンではポインターが8バイトで、32ビットマシンではポインターが4バイトであることを思い出してください。アドレスの概念は、ポインタを理解する上で非常に重要です。アドレスは、メモリ内の特定の場所を一意に識別することができる番号です。メモリ内のすべてにアドレスがあります。この目的のために、すべての変数にアドレスがあると言えます。これは必ずしも常に正しいとは限りませんが、コンパイラはこれを想定させます。アドレス自体はバイト単位です。つまり、0x0000000はメモリの開始を指定し、0x00000001はメモリへの1バイトです。これは、ポインターに1を追加することで、1バイトをメモリーに移動することを意味します。次に、配列を取得します。 32要素の大きさのquux型の配列を作成する場合、配列の各セルはsizeof(quux)bigであるため、割り当ての開始から割り当ての開始に32 * sizeof(quux)を加えたものになります。したがって、実際にarray [n]を使用して配列の要素を指定する場合、それは*(array + sizeof(quux)* n)の単なる構文上のシュガー(省略)です。ポインター演算は、実際には参照しているアドレスを変更するだけです。これが、strlenを実装できる理由です。

while(*n++ != '\0'){
  len++;
}

ゼロに達するまで、バイトごとにスキャンしているだけです。お役に立てば幸いです!

1
gaze

それに取り組むにはいくつかの方法があります。

ほとんどのC/C++プログラマーが考える直感的なアプローチは、ポインターがメモリアドレスであるということです。 litbの例では、このアプローチを採用しています。 nullポインター(ほとんどのマシンではアドレス0に対応する)があり、intのサイズを追加すると、アドレス4が得られます。これは、ポインターが基本的に単なる空想的な整数であることを意味します。

残念ながら、これにはいくつかの問題があります。そもそも機能しない場合があります。 NULLポインターは、実際にアドレス0を使用することを保証されていません(ただし、ポインターに定数0を割り当てると、NULLポインターが生成されます)。

さらに、nullポインターをインクリメントすることはできません。より一般的には、ポインターは常に割り当てられたメモリ(または1つ前の要素)、または特別なnullポインター定数0を指す必要があります。

そのため、より正確な考え方は、ポインタは単にイテレータであり、割り当てられたメモリを反復処理できることです。これは、STLイテレータの背後にある重要なアイデアの1つです。これらはポインターとして非常に振る舞い、適切なイテレーターとして機能するように生のポインターを修正する特殊化を提供するようにモデル化されています。

これについてのより詳細な説明は here などです。

しかし、この後者のビューは、STLイテレーターを実際に説明し、ポインターがこれらの特殊なケースであると単純に言う必要があることを意味します。 std::vector<int>::iteratorのように、ポインタをインクリメントしてバッファ内の次の要素を指すことができます。他のコンテナの終了イテレータのように、配列の終わりを越えて1つの要素を指すことができます。同じバッファーを指す2つのポインターを減算して、イテレーターと同様に、イテレーターと同様に、それらの間の要素数を取得できます、ポインタが別々のバッファを指す場合、not意味のあるそれらを比較できます。 (なぜそうならないかの実際的な例として、セグメント化されたメモリ空間で何が起こるかを考えてみましょう。別々のセグメントを指す2つのポインタ間の距離はどれくらいですか?)

もちろん、実際には、CPUアドレスとC/C++ポインターの間には非常に密接な相関関係があります。しかし、それらはまったく同じではありません。ポインターには、CPUで厳密に必要ではない可能性のあるいくつかの制限があります。

もちろん、技術的には間違っていても、ほとんどのC++プログラマーは最初の理解で混乱します。通常、コードが動作し、人々がそれを取得し、先に進むと考えるようになるのに十分近いです。

しかし、Javaから来て、ポインターをゼロから学習する人にとっては、後者の説明も同じように簡単に理解でき、後でそれらに対する驚きが少なくなるでしょう。

0
jalf

これはリンクが得意なものです here Pointer Arithmeticについて

例えば:

ポインタと配列

PtrのタイプがT *であるptr + iのアドレスを計算するための式。アドレスの式は次のとおりです。

addr(ptr + i)= addr(ptr)+ [sizeof(T)* i]

32ビットプラットフォームのint型の場合、addr(ptr + i)= addr(ptr)+ 4 * i;

減算

Ptr-iも計算できます。たとえば、arrというint配列があるとします。 int arr [10]; int * p1、* p2;

p1 = arr + 3 ; // p1 == & arr[ 3 ] 
p2 = p1 - 2 ; // p1 == & arr[ 1 ] 
0
Gob00st