私は数の二乗を計算する関数を見つけました:
_int p(int n) {
int a[n]; //works on C99 and above
return (&a)[n] - a;
}
_
Nの値を返します2。質問は、それをどのように行うのですか?少しテストした後、_(&a)[k]
_と_(&a)[k+1]
_の間がsizeof(a)
/sizeof(int)
であることがわかりました。何故ですか?
明らかにハック...ですが、*
演算子を使用せずに数値を二乗する方法です(これはコーディングコンテストの要件でした)。
(&a)[n]
ロケーションのint
へのポインターと同等です
(a + sizeof(a[n])*n)
したがって、式全体は
(&a)[n] -a
= (a + sizeof(a[n])*n -a) /sizeof(int)
= sizeof(a[n])*n / sizeof(int)
= sizeof(int) * n * n / sizeof(int)
= n * n
このハックを理解するには、まずポインターの違いを理解する必要があります。つまり、同じ配列の要素を指す2つのポインターを減算するとどうなりますか?
あるポインターを別のポインターから減算すると、結果はポインター間の距離(配列要素で測定)になります。したがって、p
が_a[i]
_を指し、q
が_a[j]
_を指す場合、_p - q
_は_i - j
_と等しくなります。 。
2つのポインターが減算されるの場合、両方は同じ配列オブジェクトの要素、または配列オブジェクトの最後の要素の1つを指します。 結果は2つの配列要素の添え字の差です。 [...]。
つまり、式P
とQ
がそれぞれ配列オブジェクトのi
- thとj
- thの要素を指す場合、 式_(P)-(Q)
_の値は_i−j
_で、値がタイプ_ptrdiff_t
_のオブジェクトに適合する場合。
配列名のポインターへの変換を知っていることを期待しています。a
は配列の最初の要素へのポインターa
に変換します。 _&a
_はメモリブロック全体のアドレスです。つまり、配列a
のアドレスです。以下の図は、理解するのに役立ちます(詳細な説明については、 この回答 をお読みください):
これは、なぜa
と_&a
_のアドレスが同じであり、_(&a)[i]
_がiのアドレスである理由を理解するのに役立ちます番目 配列(a
と同じサイズ)。
だから、声明
_return (&a)[n] - a;
_
と同等です
_return (&a)[n] - (&a)[0];
_
この違いにより、ポインター_(&a)[n]
_と_(&a)[0]
_の間の要素数が得られます。これらは、それぞれn
n
要素のint
配列です。したがって、配列要素の合計は_n*n
_ = n
です。2。
注:
2つのポインターが減算されると、両方が同じ配列オブジェクトの要素、または配列オブジェクトの最後の要素の1つを指す;結果は、2つの配列要素の添え字の差です。 結果のサイズはimplementation-definedで、そのタイプ(符号付き整数型)は_
ptrdiff_t
_ヘッダーで定義された_<stddef.h>
_です。結果がそのタイプのオブジェクトで表現できない場合、動作は未定義です。
_(&a)[n]
_は、同じ配列オブジェクトの要素も配列オブジェクトの最後の要素の過去の要素も指さないため、_(&a)[n] - a
_は未定義の動作を呼び出します。
また、関数p
の戻り値の型を_ptrdiff_t
_に変更することをお勧めします。
a
は、n
int
の(変数)配列です。
&a
は、n
int
の(変数)配列へのポインターです。
(&a)[1]
は、最後の配列要素のint
one int
のポインターです。このポインターは、&a[0]
の後のn
int
要素です。
(&a)[2]
は、2つの配列の最後の配列要素を過ぎたint
one int
のポインターです。このポインターは、2 * n
の後の&a[0]
int
要素です。
(&a)[n]
は、int
配列の最後の配列要素のint
one n
のポインターです。このポインターは、n * n
の後の&a[0]
int
要素です。 &a[0]
またはa
を引くだけで、n
が得られます。
もちろん、これは(&a)[n]
が配列内または最後の配列要素の1つを超えていないため(Cのポインター演算規則で要求されるように)マシン上で動作する場合でも技術的に未定義の動作です。
同じ配列の2つの要素を指す2つのポインターがある場合、その差はこれらのポインター間の要素の数になります。たとえば、このコードスニペットは2を出力します。
_int a[10];
int *p1 = &a[1];
int *p2 = &a[3];
printf( "%d\n", p2 - p1 );
_
表現を考えてみましょう
_(&a)[n] - a;
_
この式では、a
の型は_int *
_であり、その最初の要素を指します。
式_&a
_の型はint ( * )[n]
で、イメージ化された2次元配列の最初の行を指します。タイプは異なりますが、その値はa
の値と一致します。
_( &a )[n]
_
このイメージ化された2次元配列のn番目の要素であり、タイプ_int[n]
_を持ちます。つまり、イメージ化された配列のn番目の行です。式_(&a)[n] - a
_では、最初の要素のアドレスに変換され、タイプは `int *です。
したがって、_(&a)[n]
_とa
の間には、n要素のn行があります。したがって、差は_n * n
_と等しくなります。
Expression | Value | Explanation
a | a | point to array of int elements
a[n] | a + n*sizeof(int) | refer to n-th element in array of int elements
-------------------------------------------------------------------------------------------------
&a | a | point to array of (n int elements array)
(&a)[n] | a + n*sizeof(int[n]) | refer to n-th element in array of (n int elements array)
-------------------------------------------------------------------------------------------------
sizeof(int[n]) | n * sizeof(int) | int[n] is a type of n-int-element array
したがって、
(&a)[n]
のタイプはint[n]
ポインターですa
のタイプはint
ポインターですこれで、式(&a)[n]-a
はポインターの減算を実行します。
(&a)[n]-a
= ((a + n*sizeof(int[n])) - a) / sizeof(int)
= (n * sizeof(int[n])) / sizeof(int)
= (n * n * sizeof(int)) / sizeof(int)
= n * n