JoelがStackOverflowポッドキャスト#34で指摘しているように、Cプログラミング言語(別名:K&R)では、Cの配列のこのプロパティについて言及されています。a[5] == 5[a]
Joelは、それはポインタ演算のせいであると言いますが、私はまだ理解していません。なぜa[5] == 5[a]
ですか?
C標準では、[]
演算子を次のように定義しています。
a[b] == *(a + b)
したがって、次のa[5]
ように評価されます。
*(a + 5)
そして5[a]
評価します:
*(5 + a)
a
配列の最初の要素へのポインタです。a[5]
は、から5要素離れた値a
であり、と同じ*(a + 5)
であり、小学校の数学から、それらが等しいことがわかります(加算は可換です)。
配列アクセスはポインタの観点から定義されているためです。a[i]
は*(a + i)
、可換であるという意味で定義されます。
他の答えでは何かが見落とされていると思います。
はい、p[i]
定義上、と同等です*(p+i)
。これは(加算が可換であるため)、と同等です*(i+p)
。これも([]
演算子の定義により)と同等i[p]
です。
(array[i]
では、配列名は暗黙的に配列の最初の要素へのポインターに変換されます。)
しかし、この場合、加算の可換性はそれほど明白ではありません。
両方のオペランドが同じ型である場合、または共通の型にプロモートされる異なる数値型である場合でも、可換性は完全に理にかなっていますx + y == y + x
。
ただし、この場合は、一方のオペランドがポインターで、もう一方のオペランドが整数であるポインター演算について具体的に説明しています。(整数+整数は別の演算であり、ポインター+ポインターは意味がありません。)
C標準の+
演算子の説明(N1570 6.5.6)は次のように述べています。
さらに、両方のオペランドが算術型であるか、一方のオペランドが完全なオブジェクト型へのポインタであり、もう一方が整数型である必要があります。
それは同じように簡単に言ったかもしれません:
さらに、両方のオペランドが算術型であるか、左側のオペランドが完全なオブジェクト型へのポインタであり、右側のオペランドが整数型である必要があります。
その場合、i + p
との両方i[p]
が違法になります。
C ++の用語では、実際には2セットのオーバーロードされた+
演算子があり、大まかに次のように説明できます。
pointer operator+(pointer p, integer i);
そして
pointer operator+(integer i, pointer p);
そのうち最初のものだけが本当に必要です。
では、なぜこのようになっているのでしょうか。
C ++は、この定義をCから継承しました。CはBから取得しました(配列インデックスの可換性は、1972年のユーザーズリファレンスBに明示的に記載されています)、BCPL(1967年のマニュアル)から取得しました。以前の言語(CPL?Algol?)。
したがって、配列のインデックス付けは加算の観点から定義され、その加算は、ポインターと整数であっても可換であるという考えは、何十年も前にCの祖先言語にまでさかのぼります。
これらの言語は、現代のCよりも強く型付けされていませんでした。特に、ポインタと整数の区別はしばしば無視されていました。(初期のCプログラマーは、unsigned
キーワードが言語に追加される前に、ポインターを符号なし整数として使用することがありました。)したがって、オペランドが異なるタイプであるために加算を非可換にするという考えは、これらの言語の設計者にはおそらく思い浮かばなかったでしょう。ユーザーが2つの「もの」を追加したい場合、それらの「もの」が整数、ポインター、またはその他のものであるかどうかにかかわらず、それを防ぐのは言語次第ではありませんでした。
そして何年にもわたって、そのルールへの変更は既存のコードを壊していたでしょう(1989年のANSI C標準は良い機会だったかもしれませんが)。
ポインタを左側に、整数を右側に配置する必要があるようにCやC ++を変更すると、既存のコードが破損する可能性がありますが、実際の表現力が失われることはありません。
つまり、後者の形式はIOCCCの外部に表示されるべきではありませんがarr[3]
、今では3[arr]
まったく同じ意味を持っています。
そしてもちろん
("ABCD"[2] == 2["ABCD"]) && (2["ABCD"] == 'C') && ("ABCD"[2] == 'C')
これの主な理由は、Cが設計された70年代に、コンピューターに多くのメモリがなかったため(64KBが多かった)、Cコンパイラーが構文チェックをあまり行わなかったためです。したがって、「X[Y]
」はやや盲目的に「*(X+Y)
」に翻訳されました
これは、「+=
」および「++
」の構文についても説明しています。" A = B + C
"の形式のすべてが同じコンパイル済み形式でした。ただし、BがAと同じオブジェクトである場合は、アセンブリレベルの最適化を利用できました。しかし、コンパイラーはそれを認識するのに十分な明るさではなかったので、開発者は(A += C
)をしなければなりませんでした。同様に、もしC
いた1
、別のアセンブリレベルの最適化が利用可能であった、と再び開発者は、コンパイラがそれを認識していなかったので、それを明示しなければなりませんでした。(最近ではコンパイラーがそうしているので、これらの構文は最近ほとんど不要です)
誰もダイナの問題について言及していないようですsizeof
:
ポインターに追加できるのは整数のみで、2つのポインターを一緒に追加することはできません。そうすれば、整数へのポインター、またはポインターへの整数を追加するときに、コンパイラーは、考慮に入れる必要のあるサイズのビットを常に認識します。
文字通り質問に答えること。それは必ずしも真実ではありませんx == x
double zero = 0.0;
double a[] = { 0,0,0,0,0, zero/zero}; // NaN
cout << (a[5] == 5[a] ? "true" : "false") << endl;
プリント
false
この醜い構文は「便利」であるか、同じ配列内の位置を参照するインデックスの配列を処理するときに少なくとも非常に楽しいものになる可能性があることがわかりました。ネストされた角括弧を置き換えて、コードを読みやすくすることができます。
int a[] = { 2 , 3 , 3 , 2 , 4 };
int s = sizeof a / sizeof *a; // s == 5
for(int i = 0 ; i < s ; ++i) {
cout << a[a[a[i]]] << endl;
// ... is equivalent to ...
cout << i[a][a][a] << endl; // but I prefer this one, it's easier to increase the level of indirection (without loop)
}
もちろん、実際のコードではそのユースケースはないと確信していますが、とにかく面白いと思いました:)
素敵な質問/回答。
Cポインタと配列は同じではないことを指摘したいだけですが、この場合、違いは本質的ではありません。
次の宣言を検討してください。
int a[10];
int* p = a;
ではa.out
、シンボルa
は配列の先頭のp
アドレスにあり、シンボルはポインタが格納されているアドレスにあり、そのメモリ位置のポインタの値は配列の先頭です。
Cのポインタについては、
a[5] == *(a + 5)
そしてまた
5[a] == *(5 + a)
したがって、それは本当です a[5] == 5[a].
答えではありませんが、考えるべき食べ物です。クラスにオーバーロードされたインデックス/添え字演算子がある場合、式0[x]
は機能しません。
class Sub
{
public:
int operator [](size_t nIndex)
{
return 0;
}
};
int main()
{
Sub s;
s[0];
0[s]; // ERROR
}
intクラスにアクセスできないため、これを行うことはできません。
class int
{
int operator[](const Sub&);
};
テッド・ジェンセンによる「Cのポインターと配列に関するチュートリアル」で非常に良い説明があります。
テッドジェンセンはそれを次のように説明しました:
実際、これは真実です。つまり、どこに書い
a[i]
て*(a + i)
も問題なく置き換えることができます。実際、コンパイラーはどちらの場合も同じコードを作成します。したがって、ポインタ演算は配列のインデックス付けと同じであることがわかります。どちらの構文でも同じ結果が得られます。これは、ポインタと配列が同じものであると言っているのではなく、そうではありません。配列の特定の要素を識別するために、2つの構文を選択できると言っているだけです。1つは配列のインデックス付けを使用し、もう1つはポインター演算を使用して同じ結果を生成します。
さて、この最後の式、その一部を見ると
(a + i)
、+演算子を使用した単純な加算であり、Cの規則は、そのような式は可換であると述べています。つまり、(a + i)は(i + a)
。と同じです。したがって、と*(i + a)
同じくらい簡単に書くことができます*(a + i)
。しかし*(i + a)
、から来た可能性がありますi[a]
!これらすべてから、次のような奇妙な真実が生まれます。char a[20];
書き込み
a[3] = 'x';
書くのと同じです
3[a] = 'x';
私は質問が答えられることを知っています、しかし私はこの説明を共有することに抵抗できませんでした。
コンパイラ設計の原則を覚えています。a
がint
配列で、サイズint
が2バイトで、のベースアドレスa
が1000であると仮定しましょう。
どのようa[5]
に機能しますか->
Base Address of your Array a + (5*size of(data type for array a))
i.e. 1000 + (5*2) = 1010
そう、
同様に、cコードを3番地コードに分解5[a]
すると、->になります。
Base Address of your Array a + (size of(data type for array a)*5)
i.e. 1000 + (2*5) = 1010
したがって、基本的に両方のステートメントがメモリ内の同じ場所を指しているため、a[5] = 5[a]
。
この説明は、配列の負のインデックスがCで機能する理由でもあります。
つまり、私がアクセスa[-5]
した場合、それは私に与えます
Base Address of your Array a + (-5 * size of(data type for array a))
i.e. 1000 + (-5*2) = 990
990の位置にあるオブジェクトが返されます。
Cアレイ、arr[3]
及び3[arr]
同じであり、その等価ポインタ表記である*(arr + 3)
と*(3 + arr)
。しかし、逆に[arr]3
か[3]arr
正しくないと、構文エラーになります、など(arr + 3)*
と(3 + arr)*
有効な式ではありません。その理由は、間接参照演算子は、アドレスの後ではなく、式によって生成されるアドレスの前に配置する必要があるためです。
cコンパイラで
a[i]
i[a]
*(a+i)
配列内の要素を参照するさまざまな方法です!(まったく奇妙ではありません)
今少し歴史。他の言語の中でも、BCPLはCの初期の開発にかなり大きな影響を及ぼしました。BCPLで次のような配列を宣言した場合:
let V = vec 10
これは実際には10ではなく11ワードのメモリを割り当てました。通常はVが最初で、直後のワードのアドレスが含まれていました。したがって、Cとは異なり、Vという名前はその場所に移動し、配列の0番目の要素のアドレスを取得します。したがって、BCPLでの配列の間接参照は、次のように表されます。
let J = V!5
J = !(V + 5)
配列のベースアドレスを取得するためにVをフェッチする必要があったため、実際には(BCPL構文を使用して)実行する必要がありました。したがってV!5
、と5!V
同義でした。事例観察として、WAFL(Warwick Functional Language)はBCPLで記述されており、私の記憶の限りでは、データストレージとして使用されるノードにアクセスするために前者ではなく後者の構文を使用する傾向がありました。確かにこれは35年から40年前のことなので、私の記憶は少し錆びています。:)
ストレージの余分な単語を省き、名前が付けられたときにコンパイラに配列のベースアドレスを挿入させるという革新は後で起こりました。Cの歴史論文によると、これは構造がCに追加された頃に起こりました。
!
BCPLでは、単項接頭演算子とバイナリ中置演算子の両方があり、どちらの場合も間接参照を行っていたことに注意してください。バイナリ形式には、間接参照を実行する前に2つのオペランドの追加が含まれているだけです。BCPL(およびB)の単語指向の性質を考えると、これは実際には非常に理にかなっています。Cではデータ型を取得する際に「ポインタと整数」の制限が必要にsizeof
なり、モノになりました。
さて、これは言語サポートのためにのみ可能である機能です。
コンパイラはa[i]
として解釈し*(a+i)
、式はに5[a]
評価され*(5+a)
ます。加算は可換であるため、両方が等しいことがわかります。したがって、式はに評価されtrue
ます。
Cで
int a[]={10,20,30,40,50};
int *p=a;
printf("%d\n",*p++);//output will be 10
printf("%d\n",*a++);//will give an error
ポインタp
は「変数」であり、配列名a
は「ニーモニック」または「シノニム」であるためp++
、有効ですがa++
無効です。
a[2]
対等である2[a]
このの両方の内部動作が内部のように計算「ポインタ演算」であるため、*(a+2)
イコール*(2+a)
特徴的なスターのコリン・エッグレスフィールドは、RomaDrama Liveでのスリル満点のファンとの出会いについて料理しました!加えて、大会での彼のINSPIREプログラム。
ノーザンエクスポージャーが90年代の最も人気のある番組の1つになった理由を確認するには、Blu-rayまたはDVDプレーヤーをほこりで払う必要があります。
ドミニカのボイリング湖は、世界で2番目に大きいボイリング湖です。そこにたどり着くまでのトレッキングは大変で長いですが、努力する価値は十分にあります。
今日のホワイトハウスの記者会見で、報道官のサラ・ハッカビー・サンダースは、スポーツセンターのホストであるイェメル・ヒルのドナルド・トランプに関する最近のツイートについてコメントするよう求められ、大統領と彼の政策を白人至上主義者として説明した。ヒルは彼女のつぶやきのためにESPNによって公に叱責されました。
基本的に頭のあらゆる部分から髪の毛を整えたり、ブロードライしたり、まっすぐにしたり、脱毛したりする必要がある場合は、このレミントンゴールドボックスが最適です。今日だけ、Amazonは、すでに人気のあるShortcut Pro Self-HaircutKitやPearlPro Ceramic Flat Ironのように、グルーミングをはるかに簡単にするヘアツールをマークダウンしています。
トゥパックシャクール(ティムモーゼンフェルダー/ゲッティイメージズ); マヤアンジェロウ(マーティンゴッドウィン/ゲッティイメージズ)移動、テイラースウィフト。ケンドールとカイリーは、必要な数の座席を持っています。
写真:Tesla Motorsフロリダに住んでいて、Tesla Model S、Model X 60、またはModel 60Dを使用している場合、車の自律性は50 km(約30マイル)高くなります。これは失敗ではなく、ハリケーンイルマによる避難作業を容易にするために会社自身が命じた自治権の一時的な延長です。
Zendaya shared a sweet photo in honor of boyfriend Tom Holland's 26th birthday Wednesday
シーレン「Ms.JuicyBaby」ピアソンは、先月脳卒中で入院した後、「もう一度たくさんのことをする方法を学ばなければならない」ため、言語療法を受けていることを明らかにしました。
オスカー受賞者の世紀半ばの家には、3つのベッドルーム、2つのバス、オーシャンフロントの景色があります。
Bioscoutは、農家を運転席に置くという使命を負っています。Artesian(GrainInnovate)やUniseedと並んで、最新のシードラウンドでチームを支援できることをうれしく思います。問題真菌症による重大な作物の損失は、農民にとって試練であることが証明されています。
遠隔医療は、パンデミック後の時代では新しいものではなく、時代遅れの分野でもありません。しかし、業界を詳しく見ると、需要と供給の強力な持続可能性と、米国で絶え間ない革命となる強力な潜在的成長曲線を示しています。
2021年は、世界的なベンチャーキャピタル(VC)の資金調達にとって記録的な年でした。DealStreetAsiaによると、東南アジアも例外ではなく、この地域では年間で記録的な25の新しいユニコーンが採掘されました。
計算に対する私たちの欲求とムーアの法則が提供できるものとの間には、指数関数的に増大するギャップがあります。私たちの文明は計算に基づいています—建築と想像力の現在の限界を超える技術を見つけなければなりません。