426

これが非常に基本的な質問であることは承知していますが、高水準言語でいくつかのプロジェクトをコーディングした後、基本的な C++ プログラミングを始めたところです。

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

  1. 通常の変数にポインターを使用する理由
  2. いつ、どこでポインターを使用する必要がありますか?
  3. 配列でポインタをどのように使用しますか?
4

17 に答える 17

205
  • 通常の変数にポインターを使用する理由

簡単な答えは次のとおりです。;-) ポインターは、他に何も使用できない場所で使用されます。これは、適切な機能の欠如、データ型の欠落、または純粋なパフォーマンスのためのいずれかです。以下の詳細...

  • いつ、どこでポインターを使用する必要がありますか?

ここでの短い答えは次のとおりです。他に何も使用できない場合。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 番目の文字だけを出力するのではなく、ヌル文字 (\0) が見つかるまで、次のメモリ アドレス内のすべての文字を出力します。そして、これが物事が危険になり始めるところです。%s フォーマッタを使用して、誤って char ポインタではなく整数型の変数を出力しようとした場合はどうなるでしょうか?

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 回目の printf() によって異なるアドレスが返される可能性があることにも注意してください。これは、2 回目のメモリ割り当てが最初のメモリ割り当てと同じスペースで実行されるという保証がないためです。

于 2008-10-02T17:25:54.580 に答える
55

ポインターを使用する理由の 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;
}
于 2008-10-02T15:45:08.550 に答える
45
  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 ずつインクリメントするためです。

于 2008-10-02T15:24:38.787 に答える
31

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

私が読んだポインターとポインター演算の最良の説明は、K & R のThe C Programming Languageにあります。C++ の学習を開始するのに適した本はC++ Primerです。

于 2008-10-02T15:22:28.667 に答える
25

これも答えてみる。

ポインタは参照に似ています。つまり、コピーではなく、元の値を参照する方法です。

何よりもまず、一般的にポインターを頻繁に使用する必要がある場所の 1 つは、組み込みハードウェアを扱う場合です。デジタル 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++ スキルは移植可能です。

保守性の観点から、ポインターを使用する場合は、ポインターの有効性をテストして無効な場合に対処するか、ポインターが有効であると仮定して、その事実を受け入れる必要があることにも言及する必要があります。その仮定が破られると、プログラムはクラッシュするか、さらに悪化します。別の言い方をすれば、ポインターを使用した場合の選択は、コードの複雑さを導入するか、何かが壊れて、ポインターがもたらすエラーのクラス全体に属するバグ (メモリ破損など) を追跡しようとしているときに、より多くのメンテナンス作業を行うかのどちらかです。

したがって、すべてのコードを制御する場合は、ポインターを避け、代わりに参照を使用し、できる限り const のままにします。これにより、オブジェクトの寿命について考える必要があり、最終的にはコードを理解しやすくなります。

この違いを覚えておいてください。参照は本質的に有効なポインターです。ポインターは常に有効であるとは限りません。

では、無効な参照を作成することは不可能だと言っているのでしょうか? いいえ。C++ ではほぼ何でもできるため、完全に可能です。意図せずに行うのは難しく、意図しないバグの数に驚かれることでしょう :)

于 2013-05-30T21:19:57.777 に答える
13

以下は、C の多くの機能が理にかなっている理由について、少し異なりますが洞察に満ちた見解です: http://steve.yegge.googlepages.com/tour-de-babel#C

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

C++ はポインタを管理し、「参照」の形でその効果を隠すことがあるため、ポインタを少し混乱させます。ストレート C を使用する場合、ポインターの必要性はより明白です。参照渡しを行う方法は他にありません。文字列を格納する最良の方法であり、配列を反復処理する最良の方法です。

于 2008-10-02T15:52:57.610 に答える
12

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

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

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

多くの組み込みデバイスは、今でもこの手法を使用しています。

于 2008-10-02T16:23:42.390 に答える
10

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

それらはアイテムのアドレスであるため、サイズが小さく、アドレスのスペースしか占有しません。それらは小さいので、関数への送信は安価です。そして、その機能がコピーではなく実際のアイテムで機能するようにします。

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

于 2008-10-02T15:22:39.620 に答える
9

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

于 2012-04-30T06:41:01.287 に答える
9

ポインタは、ある「ノード」を別の「ノード」に効率的にリンクまたはチェーンする機能を設計上必要とする多くのデータ構造で重要です。float のような通常のデータ型のポインターを「選択」することはありません。目的が異なるだけです。

ポインタは、高性能やコンパクトなメモリ フットプリントが必要な場合に役立ちます。

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

于 2008-10-02T15:47:01.247 に答える
7

C++ でサブタイプポリモーフィズムを使用する場合、ポインターを使用する必要があります。この投稿を参照してください: C++ Polymorphism without pointers

本当に、よく考えてみると、これは理にかなっています。サブタイプ ポリモーフィズムを使用する場合、最終的には、実際のクラスが何であるかがわからないため、メソッドのどのクラスまたはサブクラスの実装が呼び出されるかを前もってわかりません。

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


'&' を使用して引数を参照渡しする場合、間接参照 (つまり、ポインター) は依然として舞台裏で関与していることに注意してください。「&」は、(1) ポインター構文を使用する手間を省き、(2) コンパイラーをより厳密にする (ヌルポインターを禁止するなど) ことを可能にする、単なる構文糖衣です。

于 2013-05-30T19:06:02.003 に答える
6

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

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

さて、たくさんの頂点とたくさんの三角形のあるメッシュがあり、メッシュが回転したり動いたりしているとしたらどうでしょう。これらの頂点を使用するすべての三角形、およびおそらくシーン内のすべての三角形を更新する必要があります。これは、どの三角形がどの頂点を使用するかわからないためです。これは非常にコンピューターを集中的に使用します。風景の上にいくつかのメッシュがある場合は、なんてことでしょう。これらの頂点は常に変化しているため、ほぼすべてのフレームですべての三角形を更新しているため、問題が発生しています。

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

三角形クラスごとに3つの*頂点オブジェクトがある場合、無数の三角形にはそれ自体が大きい3つの頂点オブジェクトがないため、スペースを節約できるだけでなく、これらのポインターは、関係なく、常に目的の頂点を指します。頂点が変更される頻度。ポインタは引き続き同じ頂点を指しているため、三角形は変更されず、更新プロセスの処理が簡単になります。私があなたを混乱させたとしても、私はそれを疑うことはありません。私は専門家のふりをするのではなく、ただ私の2セントを議論に投入します。

于 2012-06-18T03:28:24.020 に答える
6

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

于 2008-10-02T15:22:52.847 に答える
6

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

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

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

PS:サンプル コード付きの詳細な説明については、提供されているリンクを参照してください。

于 2013-02-17T01:08:04.073 に答える
3

Java と C# では、すべてのオブジェクト参照はポインターです。C++ では、ポインターが指す場所をより詳細に制御できます。大きな力には大きな責任が伴います。

于 2008-10-02T18:02:00.577 に答える
2
  • 場合によっては、共有ライブラリ (.DLL または .so) にある関数を使用するために関数ポインターが必要になります。これには、多くの場合、DLL インターフェースが提供されている言語間で実行することが含まれます。
  • コンパイラを作る
  • 関数ポインターの配列、ベクトル、または文字列マップがある関数電卓を作成しますか?
  • ビデオ メモリを直接変更しようとする - 独自のグラフィック パッケージを作成する
  • APIを作る!
  • データ構造 - 作成している特別なツリーのノード リンク ポインター

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

于 2012-04-30T07:07:45.787 に答える
2

2 番目の質問に関しては、通常、プログラミング中にポインターを使用する必要はありませんが、これには 1 つの例外があり、それはパブリック API を作成する場合です。

ポインターを置き換えるために一般的に使用される C++ コンストラクトの問題は、使用するツールセットに大きく依存します。これは、ソース コードに対して必要なすべての制御を持っている場合には問題ありませんが、たとえば Visual Studio 2008 で静的ライブラリをコンパイルする場合Visual Studio 2010 で使用しようとすると、新しいプロジェクトが下位互換性のない新しいバージョンの STL にリンクされているため、大量のリンカー エラーが発生します。DLL をコンパイルし、人々が別のツールセットで使用するインポート ライブラリを提供すると、事態はさらに厄介になります。

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

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

于 2013-05-30T21:18:55.740 に答える