Joel がStack Overflow ポッドキャスト #34で指摘しているように、C プログラミング言語(別名: K & R) では、C の配列のこのプロパティについて言及されています。a[5] == 5[a]
ジョエルはポインタ演算が原因だと言っていますが、私にはまだわかりません。なぜa[5] == 5[a]
ですか?
Joel がStack Overflow ポッドキャスト #34で指摘しているように、C プログラミング言語(別名: K & R) では、C の配列のこのプロパティについて言及されています。a[5] == 5[a]
ジョエルはポインタ演算が原因だと言っていますが、私にはまだわかりません。なぜ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)
そしてもちろん
("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
別のアセンブリレベルの最適化が利用可能であり、コンパイラがそれを認識しなかったため、開発者はそれを明示的にする必要がありました。(最近ではコンパイラーがそうするので、これらの構文は最近ほとんど不要です)
Dinah の問題について、誰も言及していないように思われることの 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]
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
なり、モノになりました。
C コンパイラは常に配列表記をポインター表記に変換するためです。
a[5] = *(a + 5)
また5[a] = *(5 + a) = *(a + 5)
、両方とも等しいです。