web-dev-qa-db-ja.com

なぜポインタを使うの?

これは本当に基本的な質問ですが、高水準言語を使ったいくつかのプロジェクトをコーディングした後で、基本的なC++プログラミングを始めたばかりです。

基本的に3つ質問があります。

  1. なぜ通常の変数の上にポインタを使うのですか?
  2. いつ、どこでポインタを使うべきですか?
  3. 配列でポインタをどのように使用しますか?
323
Anonymous
  • なぜ通常の変数の上にポインタを使うのですか?

簡単に言うと、そうではありません。 ;-)ポインタはあなたが他に何も使うことができないところで使われるべきです。それは、適切な機能の欠如、データ型の欠如、または純粋なパフォーマンスのためです。さらに詳しく...

  • いつ、どこでポインタを使うべきですか?

ここでの簡単な答えは次のとおりです。 Cでは、文字列などの複雑なデータ型はサポートされていません。関数に「参照によって」変数を渡す方法もありません。それがあなたがポインタを使わなければならないところです。また、事実上あらゆるもの、リンクリスト、構造体のメンバーなどを指すようにすることもできます。しかし、それについてはここでは説明しません。

  • 配列でポインタをどのように使用しますか?

少しの努力と多くの混乱で。 ;-) intやcharのような単純なデータ型について話しても、配列とポインタの間にはほとんど違いはありません。これらの宣言は非常に似ています(しかし同じではありません - 例えばsizeofは異なる値を返します)。

char* a = "Hello";
char a[] = "Hello";

あなたはこのように配列内の任意の要素に到達することができます

printf("Second char is: %c", a[1]);

配列が要素0で始まるため、添字1。

それとも、あなたは等しくこれを行うことができます

printf("Second char is: %c", *(a+1));

Printfに文字を印刷したいことを伝えているので、ポインタ演算子(*)が必要です。 *を指定しないと、メモリアドレス自体の文字表現が印刷されます。今は代わりにキャラクターそのものを使っています。 %cの代わりに%sを使用した場合、printfに 'a'で示されるメモリアドレスの内容に1を加えたものを印刷するように要求したので、*を指定する必要はありませんでした。前に:

printf("Second char is: %s", (a+1)); /* WRONG */

しかし、これは2番目の文字だけを表示するのではなく、代わりに次のメモリアドレスのすべての文字をnull文字(\ 0)が見つかるまで表示します。そしてこれが物事が危険になり始めるところです。誤って%sフォーマッタを使ってcharポインタの代わりにinteger型の変数を試して印刷した場合はどうなりますか?

char* a = "Hello";
int b = 120;
printf("Second char is: %s", b);

これにより、メモリアドレス120にあるものがすべて印刷され、ヌル文字が見つかるまで印刷が続行されます。このprintfステートメントを実行するのは間違っていて違法ですが、ポインターは実際には多くの環境でint型であるため、とにかくうまくいくでしょう。代わりにsprintf()を使用し、この方法で長すぎる "char配列"を別の変数に代入した場合に発生する可能性がある問題を想像してみてください。あなたはたぶんメモリの他の何かに上書きしてしまい、あなたのプログラムをクラッシュさせるでしょう(あなたが運が良ければ)。

ああ、それを宣言するときにchar配列/ポインタに文字列値を代入しないのであれば、値を渡す前に十分な量のメモリを割り当てなければなりません。 malloc、callocなどを使用する。これは、配列内の1つの要素/ 1つの単一のメモリアドレスを指すように宣言しただけだからです。だからここにいくつかの例があります:

char* x;
/* Allocate 6 bytes of memory for me and point x to the first of them. */
x = (char*) malloc(6);
x[0] = 'H';
x[1] = 'e';
x[2] = 'l';
x[3] = 'l';
x[4] = 'o';
x[5] = '\0';
printf("String \"%s\" at address: %d\n", x, x);
/* Delete the allocation (reservation) of the memory. */
/* The char pointer x is still pointing to this address in memory though! */
free(x);
/* Same as malloc but here the allocated space is filled with null characters!*/
x = (char *) calloc(6, sizeof(x));
x[0] = 'H';
x[1] = 'e';
x[2] = 'l';
x[3] = 'l';
x[4] = 'o';
x[5] = '\0';
printf("String \"%s\" at address: %d\n", x, x);
/* And delete the allocation again... */
free(x);
/* We can set the size at declaration time as well */
char xx[6];
xx[0] = 'H';
xx[1] = 'e';
xx[2] = 'l';
xx[3] = 'l';
xx[4] = 'o';
xx[5] = '\0';
printf("String \"%s\" at address: %d\n", xx, xx);

割り当てられたメモリのfree()を実行した後でも変数xを使用することができますが、そこに何があるのか​​わかりません。また、2番目のメモリ割り当てが最初のメモリと同じスペースで実行されるという保証がないため、2つのprintf()が異なるアドレスを与える可能性があることにも注意してください。

156
Tooony

ポインターを使用する理由の1つは、呼び出された関数内で変数またはオブジェクトを変更できるようにするためです。

C++では、ポインタよりも参照を使用することをお勧めします。参照は本質的にポインタですが、C++はある程度事実を隠し、あたかもあなたが値渡ししているように見せます。これにより、渡しているセマンティクスを変更することなく、呼び出し側関数が値を受け取る方法を簡単に変更できます。

次の例を見てください。

参照を使用する:

public void doSomething()
{
    int i = 10;
    doSomethingElse(i);  // passes i by references since doSomethingElse() receives it
                         // by reference, but the syntax makes it appear as if i is passed
                         // by value
}

public void doSomethingElse(int& i)  // receives i as a reference
{
    cout << i << endl;
}

ポインタを使う:

public void doSomething()
{
    int i = 10;
    doSomethingElse(&i);
}

public void doSomethingElse(int* i)
{
    cout << *i << endl;
}
47
trshiv
  1. ポインタを使用すると、複数の場所からメモリ内の同じ領域を参照できます。つまり、ある場所でメモリを更新でき、その変更はプログラム内の別の場所から確認できます。データ構造内のコンポーネントを共有できるようにすることで、スペースを節約することもできます。
  2. アドレスを取得し、メモリ内の特定の場所にアドレスを渡す必要がある場合は、ポインタを使用する必要があります。配列をナビゲートするためにポインタを使うこともできます。
  3. 配列は、特定の型で割り当てられた連続したメモリのブロックです。配列の名前には、配列の開始点の値が含まれています。あなたが1を加えると、それはあなたを2番目の場所に連れて行きます。これにより、配列へのアクセスに使用する明示的なカウンタを持たずに、配列を下にスライドするポインタをインクリメントするループを作成できます。

これがCの例です。

char hello[] = "hello";

char *p = hello;

while (*p)
{
    *p += 1; // increase the character by one

    p += 1; // move to the next spot
}

printf(hello);

版画

ifmmp

なぜなら、それは各文字の値を取り、それを1つ増やします。

36
Kyle Cronin

ポインタは、他の変数への間接参照を取得する1つの方法です。変数のを保持する代わりに、アドレスを教えてくれます。 。配列の最初の要素(そのアドレス)へのポインタを使用すると(次のアドレス位置への)ポインタをインクリメントすることで次の要素を素早く見つけることができるので、これは配列を扱うときに特に便利です。

私が読んだポインタとポインタ算術の最も良い説明はK&Rの Cプログラミング言語 にあります。 C++を学び始めるためのよい本は C++ Primer です。

24
Bill the Lizard

私も試してみましょう。

ポインタは参照と似ています。言い換えれば、それらはコピーではなく、むしろ元の値を参照する方法です。

何よりもまず、通常はポインタを使用する必要があります多くのことを扱っているのは組み込みハードウェアを使用です。デジタルIOピンの状態を切り替える必要があるかもしれません。割り込みを処理していて、特定の場所に値を格納する必要があるかもしれません。あなたは写真を撮ります。しかし、ハードウェアを直接扱っておらず、どのタイプを使用するのか疑問に思っているのなら、読んでください。

通常の変数ではなくポインタを使用するのはなぜですか?クラス、構造体、配列などの複雑な型を扱っている場合、答えはより明確になります。通常の変数を使用するのであれば、コピーを作成することになるかもしれません(コンパイラは状況によってはこれを防ぐのに十分賢いので、C++ 11も助けになりますが、ここではその説明は省きます)。

元の値を変更したい場合はどうなりますか?あなたはこのようなものを使うことができます:

MyType a; //let's ignore what MyType actually is right now.
a = modify(a); 

それはうまくいくでしょう、そしてあなたがあなたがポインタを使っている理由を正確に知らないならば、あなたはそれらを使うべきではありません。 「おそらくもっと速い」という理由に注意してください。あなた自身のテストを実行し、それらが実際に速いなら、それらを使ってください。

しかし、メモリを割り当てる必要があるという問題を解決しているとしましょう。メモリを割り当てるときは、割り当てを解除する必要があります。メモリ割り当ては成功するかもしれませんしないかもしれません。これがポインタ便利になる - 彼らオブジェクトの存在をテストすることを可能にしますを割り当て、そしてメモリが割り当てられたオブジェクトにアクセスすることを可能にしますポインタの参照を解除します。

MyType *p = NULL; //empty pointer
if(p)
{
    //we never reach here, because the pointer points to nothing
}
//now, let's allocate some memory
p = new MyType[50000];
if(p) //if the memory was allocated, this test will pass
{
    //we can do something with our allocated array
    for(size_t i=0; i!=50000; i++)
    {
        MyType &v = *(p+i); //get a reference to the ith object
        //do something with it
        //...
    }
    delete[] p; //we're done. de-allocate the memory
}

これが、なぜポインタを使うのかの鍵となります。--- 参照は、参照している要素がすでに存在していることを前提としています。ポインタはしません。

ポインターを使用する(または少なくともそれらを処理する必要がある)もう1つの理由は、それらが参照の前に存在していたデータ型だからです。したがって、ライブラリを使用して、より優れていることがわかっていることを実行すると、単に長い間使用されてきたという理由だけで、これらのライブラリの多くがいたるところでポインタを使用します。それらのうちのC++より前に書かれたものです。

ライブラリを使用していない場合は、ポインタを使用しないようにコードを設計することができますが、ポインタは言語の基本的な種類の1つであるため、より早く使用するほど快適になります。あなたのC++スキルは移植可能です。

保守性の観点から、ポインタを使用するときは、それらの有効性をテストして有効でない場合に対処する必要があるか、単に有効であると見なして実際に有効であるという事実を受け入れる必要があります。仮定が破られると、プログラムはクラッシュするか、さらに悪いことになります。別の言い方をすると、何か問題が発生したときにコードの複雑さを増すか保守作業を増やすことをポインターに選択することですそして、ポインターによってもたらされるエラーのクラス全体に属するバグを見つけようとしていますメモリ破損のように。

そのため、コード全体を制御する場合は、ポインタを使用せずに参照を使用し、可能であれば参照を固定してください。これはあなたにあなたのオブジェクトの寿命について考えることを強いるでしょうし、そしてあなたのコードを理解しやすくすることになるでしょう。

この違いを覚えておいてください。参照は基本的に有効なポインタです。ポインタは必ずしも有効ではありません。

だから私は無効な参照を作成することは不可能だと言っているのでしょうか?いいえ、できません。C++ではほとんど何でもできるからです。意図せずにやるのは難しいですし、意図しないバグがいくつあるかに驚くでしょう。

21
Carl

Cの多くの機能が理にかなっている理由について、少し異なりますが、洞察に富んだものを次に示します。 http://steve.yegge.googlepages.com/tour-de-babel#C

基本的に、標準的なCPUアーキテクチャはフォンノイマンアーキテクチャであり、そのようなマシン上でメモリ内のデータ項目の位置を参照し、それを使って算術演算できることは非常に便利です。アセンブリ言語の変形を知っていれば、これが低レベルでどれほど重要かをすぐに理解できます。

C++はポインタをあなたのために管理することがあり、それらの効果を「参照」の形で隠してしまうので、ポインタを少し混乱させます。あなたがストレートCを使うなら、ポインタの必要性ははるかに明白です:参照による呼び出しをする他の方法はありません、それは文字列を格納するための最良の方法です、それは配列を通して繰り返すための最良の方法など.

13
Dan Lenski

ポインタの用途の1つ(他の人の投稿ですでに取り上げられていることについては説明しません)は、割り当てられていないメモリにアクセスすることです。これはPCプログラミングにはあまり役に立ちませんが、組み込みプログラミングでメモリマップドハードウェアデバイスにアクセスするために使用されます。

DOSの昔は、次のものへのポインタを宣言することによって、ビデオカードのビデオメモリに直接アクセスできるようになっていました。

unsigned char *pVideoMemory = (unsigned char *)0xA0000000;

多くの組み込み機器はまだこの技術を使用しています。

11
MrZebra

ほとんどの場合、ポインタは(C/C++では)配列です。それらはメモリ内のアドレスであり、必要に応じて配列のようにアクセスできます(「通常」の場合)。

それらはアイテムのアドレスなので、小さいです。それらはアドレスのスペースだけを取ります。それらは小さいので、それらを関数に送るのは安上がりです。そして彼らはその機能がコピーではなく実際のアイテムに作用することを許可します。

動的リストの割り当て(リンクリストなど)を行う場合は、ポインタを使用する必要があります。これが、メモリをヒープから取得する唯一の方法だからです。

10
warren

ポインタは、ある「ノード」を別のノードに効率的にリンクまたはチェーンする機能を必要とする多くのデータ構造で重要です。 floatのような普通のデータ型を言うと、ポインタを「選ぶ」のではなく、単に目的が異なります。

高性能やコンパクトなメモリフットプリントが必要な場合は、ポインタが便利です。

配列の最初の要素のアドレスをポインタに割り当てることができます。これにより、基礎となる割り当てバイトに直接アクセスできます。しかし、これを実行する必要がないようにするのが配列のポイントです。

9
Ash

変数に対してポインタを使用する1つの方法は、必要な重複メモリを排除することです。たとえば、大きな複雑なオブジェクトがある場合は、参照するたびにその変数を指すポインタを使用できます。変数を使用すると、コピーごとにメモリを複製する必要があります。

9
Jason

大きなオブジェクトをいたるところにコピーすると時間とメモリが無駄になるためです。

5
Andrew Medico

C++では、サブタイプ 多態性 を使用する場合、あなたが持っていますポインタを使う。この記事を参照してください: ポインタなしのC++多態性

あなたがそれについて考えるとき、本当に、これは理にかなっています。サブタイプ多型を使用する場合、実際には、実際のクラスが何であるかがわからないため、最終的には、どのクラスまたはサブクラスのメソッドの実装が呼び出されるのかわかりません。

未知のクラスのオブジェクトを保持する変数を持つというこの考え方は、スタックにオブジェクトを格納するC++のデフォルト(非ポインタ)モードとは互換性がありません。割り当てられたスペースの量はクラスに直接対応します。注:クラスに3つのインスタンスフィールドが5つある場合は、さらに多くのスペースを割り当てる必要があります。


5
Sildoreth

私の専門家になることは約束しませんが、私が作成しようとしている私のライブラリのうちの1つでポインタが優れていることがわかりました。このライブラリ(OpenGLのグラフィックAPIです:-))では、頂点オブジェクトを渡した三角形を作成できます。 drawメソッドはこれらの三角形のオブジェクトを受け取り、そして私が作成した頂点オブジェクトに基づいてそれらを描画します。大丈夫。

しかし、もし私が頂点座標を変えたらどうなるでしょう?頂点クラスのmoveX()でそれを動かしますか?まあ、さて、今私は三角形を更新する必要があります、私はより多くのメソッドを追加し、私は頂点が移動するたびに三角形を更新しなければならないのでパフォーマンスが浪費されています。それでも大したことではありませんが、それほど素晴らしいことではありません。

さて、もし私がたくさんの頂点とたくさんの三角形を持つメッシュを持っていて、そしてそのメッシュが回転したり移動したりしているとしたらどうでしょう。どの頂点がどの頂点を使用しているのかわからないので、これらの頂点を使用するすべての三角形、およびおそらくシーン内のすべての三角形を更新する必要があります。それは非常にコンピュータ集約的です、そして私が景色の上にいくつかのメッシュを持っているならば、おお、神よ!私は問題を抱えています、なぜならこれらの頂点は常に変化しているからです。

ポインタを使えば、三角形を更新する必要はありません。

1つの三角形クラスに3つのVertexオブジェクトがあったとしても、1兆の三角形はそれ自体大きい3つのvertexオブジェクトを持っていないのでスペースを節約できるだけでなく、これらのポインタは常に目的のVerticesを指すことになります。頂点はどれくらいの頻度で変化します。ポインタはまだ同じ頂点を指しているので、三角形は変わりません。また、更新プロセスはより扱いやすくなります。私があなたを混乱させたとしても、私はそれを疑うことはありません、私は議論に私の2セントを投げるだけで、私は専門家になるふりをしません。

4
Jeremy Hahn

C言語でのポインタの必要性が説明されています ここ

基本的な考え方は、言語の多くの制限(配列、文字列の使用、関数内での複数の変数の変更など)は、データのメモリ位置を操作することで解消できるということです。これらの制限を克服するために、ポインターがCに導入されました。

さらに、ポインタを使用すると、コードを高速に実行してメモリを節約できることがあります(構造体のような大きなデータ型を関数に渡す場合は、このようなデータ型のコピーを作成すると時間がかかりメモリを消費します)。これがプログラマがビッグデータ型のポインタを好むもう一つの理由です。

シモンズ:サンプルコードとの詳細な説明については リンク提供 を参照してください。

3
vaibhav kumar

JavaとC#では、すべてのオブジェクト参照はポインタです。c++の利点は、ポインタの指す場所をより細かく制御できることです。覚えておいてください大きな力で大きな責任があります。

3
Marioh

2番目の質問に関しては、一般にプログラミング中にポインタを使用する必要はありませんが、これに対する1つの例外があり、それはあなたが公共のAPIを作るときです。

ポインタを置き換えるために一般的に使用されるC++構成体の問題は、使用するツールセットに非常に依存します。ただし、たとえばVisual Studio 2008で静的ライブラリをコンパイルする場合は、ソースコードを制御できます。新しいプロジェクトは、後方互換性のない新しいバージョンのSTLとリンクされているため、ビジュアルスタジオ2010で使用しようとすると、大量のリンカエラーが発生します。 DLLをコンパイルして別のツールセットで使用するインポートライブラリを指定すると、状況がさらに悪くなります。その場合は、明らかな理由もなくプログラムが遅かれ早かれクラッシュするためです。

したがって、あるライブラリから別のライブラリに大きなデータセットを移動する目的で、他のユーザに同じツールを使用させたくない場合は、データをコピーすることになっている関数に配列へのポインタを渡すことを検討できます。 。これについての良い部分は、それがCスタイルの配列である必要すらないということです。例えば、std :: vectorを使用して、最初の要素のアドレス&vector [0]を与えることによってポインタを与えることができます。内部的に配列を管理するためのstd :: vector.

C++でポインタを使用するもう1つの正当な理由はライブラリに関するものです。プログラムの実行時にロードできないDLLを使用することを検討してください。インポートライブラリを使用すると、依存関係が満たされずプログラムがクラッシュします。あなたがあなたのアプリケーションと一緒にdllでパブリックAPIを与えて、あなたが他のアプリケーションからそれにアクセスしたいときにこれが当てはまります。この場合、APIを使用するには、その場所(通常はレジストリキー内)からDLLを読み込む必要があります。その後、DLL内の関数を呼び出すことができるように関数ポインタを使用する必要があります。このプロセスを自動化し、必要な関数ポインタをすべて提供するためのヘルパー関数を含む.hファイルを提供するのに十分なほど、APIを作成している人たちがいいでしょうが、そうでない場合はLoadLibraryおよびGetProcAddressを使用できます。それらを取得するには、UNIX上のdlsym(関数のシグネチャ全体を知っていることを考慮してください)。

1
Radu Chivu
  • 場合によっては、共有ライブラリ(.DLLまたは.so)内にある関数を使用するために関数ポインタが必要です。これには、DLLインタフェースが提供されることが多い言語間での処理の実行が含まれます。
  • コンパイラを作る
  • 関数ポインタの配列、ベクトル、または文字列のマップがある科学計算機を作成します。
  • ビデオメモリを直接変更しようとしている - あなた自身のグラフィックパッケージを作る
  • APIを作ろう!
  • データ構造 - あなたが作っている特別な木のためのノードリンクポインタ

ポインタにはたくさんの理由があります。言語間の互換性を維持したい場合は、C言語の名前の変換を行うことがDLLで特に重要です。

1
Jim Michaels