これは本当に基本的な質問ですが、高級言語でいくつかのプロジェクトをコーディングした後、基本的なC ++プログラミングから始めました。
基本的に私は3つの質問があります:
- 通常の変数でポインタを使用する理由
- いつ、どこでポインタを使用すればよいですか?
- 配列でポインタをどのように使用しますか?
これは本当に基本的な質問ですが、高級言語でいくつかのプロジェクトをコーディングした後、基本的なC ++プログラミングから始めました。
基本的に私は3つの質問があります:
回答:
短い答えは:しないでください。;-)ポインターは、他に何も使用できない場所で使用されます。これは、適切な機能の欠如、データ型の欠如、または純粋なパフォーマンスのためです。詳細はこちら...
ここでの短い答えは、他に何も使用できないところです。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ポインタではなく整数型の変数を誤って出力しようとした場合はどうなりますか?
char* a = "Hello";
int b = 120;
printf("Second char is: %s", b);
これにより、メモリアドレス120で見つかったものはすべて印刷され、null文字が見つかるまで印刷されます。このprintfステートメントを実行することは誤りであり、違法ですが、ポインタは実際には多くの環境でint型であるため、おそらくいずれにしても機能します。代わりにsprintf()を使用して、このように長すぎる「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番目の割り当てが最初のメモリと同じスペースで実行される保証がないため、2つのprintf()が異なるアドレスを与える可能性があることに注意してください。
a
前者の場合はポインタであり、後者の場合a
は配列です。もう言った?同じではありません!自分で確認してください:sizeof(a)を比較して、新しいアドレスを配列に割り当ててみてください。それは動作しません。
char* a = "Hello";
そして、char a[] = "Hello";
同じではありません、彼らはかなり異なっています。1つはポインタを宣言し、もう1つは配列を宣言します。を試してみてsizeof
、違いがわかります。
ポインターを使用する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;
}
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ずつ増やすためです。
because it takes the value for each character and increments it by one
。アスキーの表現ですか?
ポインターは、別の変数への間接参照を取得する1つの方法です。変数の値を保持する代わりに、変数のアドレスを知らせます。配列の最初の要素(アドレス)へのポインタを使用すると、ポインタを(次のアドレス位置へ)増やすことで次の要素をすばやく見つけることができるため、これは配列を扱うときに特に役立ちます。
私が読んだポインターとポインター演算の最も良い説明は、K&RのThe C Programming Languageにあります。C ++の学習を始めるのに適した本はC ++ Primerです。
これも試してみましょう。
ポインタは参照に似ています。言い換えれば、それらはコピーではなく、元の値を参照する方法です。
何よりもまず、通常ポインターを頻繁に使用する必要があるのは、組み込みハードウェアを扱う場合です。デジタル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++ではほとんど何でもできるので、完全に可能です。意図せずに行うのはさらに難しく、意図しないバグの数に驚くでしょう:)
これは少し異なりますが、Cの多くの機能が理にかなっている理由について洞察に満ちたものです:http : //steve.yegge.googlepages.com/tour-de-babel#C
基本的に、標準のCPUアーキテクチャはフォンノイマンアーキテクチャであり、そのようなマシンでメモリ内のデータ項目の場所を参照し、それを使って計算できると非常に便利です。アセンブリ言語のバリアントを知っている場合は、これが低レベルでどれほど重要であるかがすぐにわかります。
C ++は、ポインタを管理し、「参照」の形でその効果を隠すため、ポインタを少し混乱させます。ストレートCを使用する場合、ポインターの必要性ははるかに明白です。参照による呼び出しを行う他の方法はありません。これは、文字列を格納する最良の方法であり、配列を反復処理する最良の方法です。
ポインターの1つの用途(既に他の人の投稿で取り上げられていることについては触れません)は、割り当てられていないメモリにアクセスすることです。これはPCプログラミングにはあまり役に立ちませんが、メモリマッピングされたハードウェアデバイスにアクセスするための組み込みプログラミングで使用されます。
DOSの昔は、ポインタを次のように宣言することで、ビデオカードのビデオメモリに直接アクセスできました。
unsigned char *pVideoMemory = (unsigned char *)0xA0000000;
多くの組み込みデバイスはまだこの手法を使用しています。
gsl::span
、すぐにそうなりますstd::span
。
ほとんどの場合、ポインタは配列(C / C ++の場合)です。これらはメモリ内のアドレスであり、必要に応じて(「通常」の場合)配列のようにアクセスできます。
それらはアイテムのアドレスなので、小さいです。アドレスのスペースのみを使用します。それらは小さいので、関数に送るのは安価です。そして、それらはその関数がコピーではなく実際のアイテムで機能することを可能にします。
動的ストレージ割り当て(リンクリストなど)を行う場合は、ヒープからメモリを取得する唯一の方法であるため、ポインタを使用する必要があります。
std::array
。
C ++では、サブタイプpolymorphismを使用する場合は、ポインターを使用する必要があります。この投稿を参照してください:ポインターなしのC ++ポリモーフィズム。
本当に、あなたがそれについて考えるとき、これは理にかなっています。サブタイプポリモーフィズムを使用する場合、最終的には、実際のクラスがわからないため、どのクラスまたはサブクラスのメソッドの実装が呼び出されるかが事前にわかりません。
不明なクラスのオブジェクトを保持する変数を持つというこの考えは、スタックにオブジェクトを格納するC ++のデフォルト(非ポインター)モードと互換性がありません。このモードでは、割り当てられたスペースの量がクラスに直接対応します。注:クラスに3つではなく5つのインスタンスフィールドがある場合、より多くのスペースを割り当てる必要があります。
goto
命令は、ターゲットマシンの命令の裏側でも使用されます。私たちはまだそれらを使用することを主張していません。
場所全体に大きなオブジェクトをコピーすると、時間とメモリが浪費されるためです。
これが私の答えです。私はエキスパートになるつもりはありませんが、書こうとしているライブラリの1つでポインタが優れていることを発見しました。このライブラリ(OpenGLのグラフィックAPI :-)で、頂点オブジェクトを渡して三角形を作成できます。drawメソッドはこれらの三角形オブジェクトを取得し、私が作成した頂点オブジェクトに基づいてそれらを描画します。大丈夫。
しかし、頂点座標を変更するとどうなりますか?頂点クラスでmoveX()を使用して移動しますか?さて、わかりました。ここで三角形を更新する必要があります。頂点を移動するたびに三角形を更新する必要があるため、メソッドを追加してパフォーマンスを無駄にしています。まだ大したことではありませんが、それほど大きくありません。
さて、大量の頂点と大量の三角形を持つメッシュがあり、メッシュが回転、移動している場合などはどうでしょう。これらの頂点を使用するすべての三角形と、シーン内のすべての三角形を更新する必要があります。これは、どの頂点がどの頂点を使用するのかわからないためです。これは非常にコンピュータに負荷がかかります。風景の上にいくつかのメッシュがある場合、なんてことです!これらの頂点が常に変化しているため、ほぼすべてのフレームですべての三角形を更新しているので、私は困っています!
ポインターを使用すると、三角形を更新する必要はありません。
三角形クラスごとに* Vertexオブジェクトが3つある場合、ジリオンの三角形にはそれ自体が大きい3つの頂点オブジェクトがないため、部屋を節約するだけでなく、これらのポインターは常に、意図されている頂点をポイントします頂点が変化する頻度。ポインターは依然として同じ頂点を指しているため、三角形は変化せず、更新プロセスの処理がより簡単になります。私があなたを混乱させたとしても、私はそれを疑うつもりはありません、私は専門家であるふりをせず、私の2セントを議論に投げ込みます。
基本的な考え方は、言語の多くの制限(配列、文字列の使用、関数での複数の変数の変更など)は、データのメモリ位置を操作することで取り除くことができるということです。これらの制限を克服するために、ポインターがCで導入されました。
さらに、ポインタを使用すると、大きなデータ型(多くのフィールドを持つ構造体など)を関数に渡す場合に、コードをより高速に実行してメモリを節約できることもわかります。渡す前にそのようなデータ型のコピーを作成すると、時間がかかり、メモリを消費します。これが、プログラマーがビッグデータ型のポインターを好むもう1つの理由です。
2番目の質問については、通常、プログラミング中にポインターを使用する必要はありませんが、これには1つの例外があり、それはパブリックAPIを作成する場合です。
人々がポインターを置き換えるために一般的に使用するC ++構成の問題は、使用するツールセットに大きく依存しますが、たとえば、Visual Studio 2008で静的ライブラリをコンパイルする場合は、ソースコードに対して必要なすべてのコントロールがある場合に問題ありません。新しいプロジェクトが後方互換性のない新しいバージョンのSTLにリンクされているため、Visual Studio 2010でそれを使用しようとすると、大量のリンカーエラーが発生します。DLLをコンパイルして別のツールセットで使用するインポートライブラリを提供すると、事態はさらに悪化します。その場合、プログラムは明らかな理由もなく遅かれ早かれクラッシュします。
したがって、あるライブラリから別のライブラリに大きなデータセットを移動する目的で、使用する同じツールを他のユーザーに強制的に使用させたくない場合は、データをコピーするはずの関数への配列へのポインタを与えることを検討できます。 。これについての良い部分は、それがCスタイルの配列である必要さえないということです。例えば、std :: vectorを使用し、最初の要素&vector [0]のアドレスを与えることによってポインターを与えることができます。 std :: vectorは、配列を内部的に管理します。
C ++でポインタを使用するもう1つの理由はライブラリに関連しているため、プログラムの実行時にロードできないdllを用意することを検討してください。インポートライブラリを使用すると、依存関係が満たされず、プログラムがクラッシュします。これは、たとえば、アプリケーションとともにdllでパブリックAPIを提供し、他のアプリケーションからアクセスしたい場合です。この場合、APIを使用するには、DLLをその場所からロードする必要があり(通常はレジストリキーにあります)、DLL内の関数を呼び出せるように関数ポインターを使用する必要があります。APIを作成する人たちは、このプロセスを自動化し、必要なすべての関数ポインターを提供するヘルパー関数を含む.hファイルを提供できるほど優れている場合があります。
ポインタには多くの理由があります。言語間互換性を維持したい場合は、DLLでC名をマングリングすることが特に重要です。