2017-01-28 18 views
4

配列表記法よりもポインタ表記を使用する利点はありますか?私は、ポインタ記法がより良い特別なケースがあるかもしれないことに気づいていますが、配列記法がもっ​​とわかりやすいようです。私の教授は、「Cだからこそ」という表記法が好きだと言いましたが、それはマーキングするものではありません。そして文字列を文字列として宣言することとの違いは文字配列としてポインタを文字列として宣言することです - 私は一般的には配列全体をループすることについて話しています。配列表記とポインタ表記C

+0

[Char配列とCharポインタ(C言語での文字配列)の可能な複製](0120-17753) – DyZ

+0

時間を割いてポインタ。最初は、あなたはどんな混乱もなく、もっと混乱させることはありませんが、後で、あなたは何ができるのか驚くでしょう。私が考えることができる最も単純な例:ポインタが指しているもののメモリを(したがって、サイズを変更して)再割り当てすることができます。一方、単純な配列を作成すると、サイズは変更できません – Fureeish

+0

@DYZそれはchar配列とcharポインタの違いです。私はそこに違いがあることを知っている、私はちょうどint配列を介してループのようなもののためにポインタ表記を使用するための実用的な理由があるかどうかを知りたい。 –

答えて

4

単純なループを書くと、通常、配列とポインタの両方のフォームが同じマシンコードにコンパイルされます。

特に非定数ループ終了条件には違いがありますが、特定のコンパイラおよびアーキテクチャ用にループを最適化しようとする場合にのみ重要です。

したがって、に頼る現実世界の例を考えてみましょう。

これらのタイプは、別個の参照カウントデータストレージと、動的に決定されたサイズの倍精度浮動小数点行列を実装する:

struct owner { 
    long   refcount; 
    size_t  size; 
    double  data[]; /* C99 flexible array member */ 
}; 

struct matrix { 
    long   rows; 
    long   cols; 
    long   rowstep; 
    long   colstep; 
    double  *origin; 
    struct owner *owner; 
}; 

考え方は、マトリックスを必要とするとき、あなたはそれを記述ことですタイプstruct matrixのローカル変数を使用します。参照されるすべてのデータは、C99フレキシブルアレイメンバーの動的に割り当てられたstruct owner構造体に格納されます。行列が不要になったら、明示的に「削除」する必要があります。これにより、複数の行列が同じデータを参照することができます。行ベクトル、列ベクトル、または対角ベクトルを別々に持つこともできます(同じデータ値を参照するため)。

空行列を作成するか、別の行列が参照する既存のデータを参照して、行列がデータに関連付けられている場合、所有者構造refcountが増分されます。行列が削除されるたびに、参照されたオーナー構造refcountが減分されます。 refcountがゼロになると、オーナー構造が解放されます。これは、使用した各マトリックスを「落とす」ことだけを覚えておく必要があることを意味し、参照されるデータは、できるだけ早く(不要に)管理され、リリースされるでしょう。

これはすべて、シングルスレッドプロセスを前提としています。マルチスレッド処理はかなり複雑です。もしm.origin[r*m.rowstep + c*m.colstep]を使用し、0 <= r < m.rows0 <= c < m.colsと仮定すると、行列struct matrix m、行r、カラムcの要素にアクセスする

行列を転置したい場合は、m.rowsm.colsm.rowstepm.colstepを入れ替えるだけです。すべての変更は、(所有者構造に格納された)データが読み込まれる順序です。

は(マトリクス状に、行0、列0に現れる二重にそのorigin点に注意してください;及びrowstepcolstepが負であり得ることをこれは、そうでなければ鈍い正規データに奇妙な「ビュー」のすべての種類を可能にします)

C99のフレキシブルアレイメンバーがいない場合、つまり、ポインタしか持たず、配列表記もない場合は、オーナー構造dataのメンバーが必要ですポインタ。これは、ハードウェアレベルで追加のリダイレクトを意味します(データアクセスを少し遅くする)。 dataが指すメモリを割り振るか、または所有者の構造自体に続くアドレスを指すためにトリックを使用する必要がありますが、適切には2重に揃えられます。

多次元配列には、基本的にすべての次元のサイズ(または1次元以外のすべて)のサイズが分かっている場合に使用されますが、コンパイラが索引付けを処理するのはいいですが、ポインタを使用するメソッドよりも常に簡単です。例えば、上記のマトリックス構造の場合には、我々は常に確かに、それは最初のパラメータ、m、3回を評価欠点を持っている

#define MATRIXELEM(m, r, c) ((m).origin[(r)*(m).rowstep + (c)*(m).colstep]) 

のように、いくつかのヘルパープリプロセッサマクロを定義することができます。 (つまり、MATRIXELEM(m++,0,0)は実際にはmを3回増分しようとします)。この特定のケースでは、mは通常、驚きを最小限に抑えるローカル変数struct matrixです。例えば、

struct matrix m1, m2; 

/* Stuff that initializes m1 and m2, and makes sure they point 
    to valid matrix data */ 

MATRIXELEM(m1, 0, 0) = MATRIXELEM(m2, 0, 0); 

そのようなマクロで「余分」括弧を使用して、計算を使用する場合、行として例えばi + 4*jため、指標の計算が((i + 4*j)*m.rowstepなくi + 4*j*m.rowstep)正しいことを保証します。プリプロセッサマクロでは、これらの括弧は実際には「余分な」ものではありません。正しい計算を保証することに加えて、 "余分な"括弧を付けることは、マクロライターがそのような算術関連のバグを慎重に避けていることを他のプログラマーにも伝えます。 (コードを読んでいる他の開発者に "保証"を伝えれば、たとえ文法の不一致が必要ない場合でも、括弧を入れるのは良いフォームだと思う)

これは、この文章の後で、私の最も重要なポイントにつながります。ポインタの表記よりも配列表記を使用すると、人間のプログラマによって私たちによってが簡単に表現され、理解されます。逆もまた同様です。 "Foo"[1]は、明らかに'o'に等しいが、*("Foo"+1)はそれほど明白ではない。 (また、どちらも1["foo"]ではありませんが、C標準化の責任を負うことができます)。

上記の例に基づいて、私は補足的な2つの表記法を検討します。特に単純なループでは重複が大きくなります。その場合は、ただ一つ選んでも構いませんが、両方の表記を利用することができ、1つの熟練に基づいていないものを選ぶことはできますが、最も感覚的です。可読性と保守性は、私の謙虚な意見ではなく、どのCプログラマーにとっても重要なスキルです。

2

実際には、Cの関数に配列の引数を渡すと、実際にその先頭にポインタを渡します。配列を渡すには実際の長さ(秒)を渡すことが含まれるため、配列を渡すことはそのコピーを暗に意味するため、これは常識的に配列を渡すことはありません。 つまり、実際には配列の先頭(C++のstd::vector::begin()など)を指すイテレータを渡しますが、配列自体を渡すふりをしています。実際には非常に混乱しています。だから、ポインタを使用することは、それらが実際にはるかに明確な方法で起こっていることを表しており、間違いなく優先されるべきです。

アレイ表記の利点があるかもしれませんが、私はそれらが上記の欠点を超えているとは思わない。まず、配列表記を使用すると、単一値へのポインタと連続ブロックへのポインタの違いが強調されます。次に、渡された配列の予想サイズを独自の参照用に指定することができます。しかし、そのサイズは実際には式や関数に渡されないか、何かが非常に混乱しているかどうかが何らかの形でチェックされます。