バックグラウンド
前回の記事では C++ のイテレータについて紹介しました。この記事では C++ の配列の概念を紹介します。配列はベクトルに似たデータ構造ですが、パフォーマンスと柔軟性のトレードオフでパフォーマンスを選択します。柔軟性の程度は、すべて同じタイプのオブジェクトのコンテナであるという点でベクトルと同じであり、添字を介してアクセスすることもできます。違いは、配列のサイズが固定されているため、配列に要素を追加することができず、サイズが固定されているからこそ実行時のパフォーマンスが向上するということです。
配列を定義して初期化する
配列は複合型であり、a[d] に似た形式で定義できます。ここで、a は配列の名前、d は配列の容量、d は 0 より大きくなければなりません。配列は配列型の一部であるため、配列の容量は次のようになります。これはコンパイル時に判明し、配列の容量は定数式である必要があります。次に、配列宣言のいくつかの形式を示します。
unsigned cnt = 42; //不是一个常量表达式
constexpr unsigned sz = 42; //是常量表达式
int arr[10]; //声明一个容量为10的整型数组
int *parr[sz]; //42个指向整形指针的数组
string bad[cnt]; //这是个错误声明,因为cnt不是常量表达式
デフォルトでは、配列内の要素はデフォルトで初期化されます。
リストを使用して配列を初期化できます。この方法では、配列を定義するときに配列の容量を無視できます。配列の容量を指定する場合、リストの初期化中に初期化される要素の数は、設定された容量値を超えることはできません。設定された配列の数よりも小さいです。値が指定されていない要素には、デフォルトの初期化値が使用されます。例は次のとおりです:
const unsigned sz = 3;
int a1[sz] = {0, 1, 2};
int a2[] = {0, 1, 2}; //可以忽略数组的容量
int a3[5] = {0, 1, 2}; //等价于{0, 1, 2, 0, 0}
string a4[3] = {
"hi", "bye"}; //等价于{
"hi", "bye", ""}
int a5[2] = {0, 1, 2}; //错误
文字配列の定義
文字配列には、文字配列を文字で初期化する追加の初期化メソッドがありますが、文字列は null 文字で終わるため、配列の容量を定義するときに null 文字を考慮する必要があることに注意してください。
char a1[] = "C++"; //其等价于{
'C', '+', '+', '\0'}
char a2[6] = "Daniel" //错误,其未考虑到null字符
❝一部のコンパイラは配列のコピーをサポートしていないため、ある配列を使用して別の配列を直接初期化すると、エラーが報告される可能性があることに注意してください。
❞
複雑な配列宣言を理解する
ベクトルと同様に、配列もポインタの配列など、すべての型に対応できます。配列はオブジェクトであるため、配列へのポインタと参照を定義できます。配列へのポインタまたは参照は、次の方法で定義できます。
int *ptre[10]; //ptre是一个数组,其中的元素是10个指向整型的指针
int (*parray)[10] = &arr; //parray是一个指针,其指向的对象是一个容量为10的整型数组
int (&arrRef)[10] = arr; //arrRef是一个引用,其指向的是一个容量为10的整型数组
❝ステートメントを理解するときは、左から右、内側から外側の順序に従うことができます。
❞
ポインタと配列
C++ では、ポインタと配列の関係は非常に密接です。一般に、配列を使用すると、コンパイラは自動的にそれをポインタに変換します。一般に、オブジェクトへのポインタを取得するにはアドレス演算子を使用します。配列を使用すると、コンパイラは配列の最初の要素へのポインタを自動的に取得します。
string nums = {
"one", "two", three};
string *p = &nums[0]; //p指向nums的第一个元素
string *p2 = nums //等价于string *p = &nums[0];
❝ほとんどの式では配列オブジェクトを使用し、実際には配列の最初の要素へのポインタを取得します。
❞
この影響により、配列に対する操作のほとんどは、実際にはポインターに対する操作になります。より明らかなのは、auto と配列を使用して変数を初期化するときに、実際には配列ではなくポインターを宣言することです。
int ia[] = {0, 1, 2, 3, 4};
auto ia2(ia); //ia2是一个整形指针,指向ia的第一个元素
ia2 = 43 //错误,不可以将int赋值给一个指针
auto ia3(&ia[0]) //这样看起来更清楚,ia3是整型指针
前述の decltype を使用する場合、この変換は発生しないことに注意してください。decltype(ia) によって返される型は、10 個の整数の配列です。
decltype(ia) ia3 = {0, 1, 2, 3, 4};
ia3 = p; // 错误,不可以将一个整型指针赋值给一个数组
ia3[4] = i; //正确,可以对数组的元素赋值
ポインタはイテレータです
ポインタは反復子でもあり、配列要素へのポインタは、前述したベクトルおよび文字列の反復子の操作もサポートします。たとえば、自動インクリメント操作を通じて、ある要素から次の要素に移動できます。
int arr[] = [0, 1, 2, 3, 4, 5];
int *p = arr; //现在p指向arr[0]
++p; //现在p指向arr[1]
イテレータを使用してベクトル内の要素を走査できるのと同じように、ポインタを使用して配列内の要素を走査することもできます。上記の方法で配列の最初の要素へのポインタを取得できます。配列の最後の要素? それ以降の存在しない要素については、次のことができます。
int *e = &arr[6];
最後の要素の次の要素のアドレスのみを取得できます。
for (int *b = arr; b != e; ++b)
cout<< *b<<endl
上記の方法で配列の最初の要素のアドレスと最後の要素の次のアドレスを取得することはできますが、これは良い方法ではなく、配列を取得するための新しい関数 begin と end が新しい仕様で提供されています。最初の要素のアドレスと最後の要素の次のアドレス:
int ia[] = {0, 1, 2, 3, 4};
int *beg = begin(ia);
int *last = end(ia);
ポインタの算術演算
配列要素へのポインタは、イテレータに関する記事で説明したすべてのイテレータ操作を使用できます。ポインタを使用して整数値を加算または減算すると、新しいポインタが取得されます。このポインタは、前後のいくつかの位置にある要素を指します。元の配列要素の場合、特定の位置は加算または減算の値によって異なります。
constexpr size_t sz = 5;
int arr[sz] = {1, 2, 3, 4, 5};
int *p1 = arr; //等价于*p1 = &arr[0]
int *p2 = p1 + 4; //p2指向arr[4]
sz を配列に追加すると、コンパイラは arr を配列の最初の要素へのポインタに変換するため、次のように、p は配列の最後の要素を指す次の要素になります。配列では間違いが起こります:
int *p = arr + sz; //小心使用,没有解引用
int *p3 = arr + 10; //错误,数组只有5个元素,虽然编译器可能无法检测到这个错误
反復子と同様に、2 つのポインターが同じ配列内の要素である場合、2 つのポインターを減算した結果は、2 つのポインター間の距離になります。
auto n = end(arr) - begin(arr);
逆参照とポインタの算術演算
上記の紹介で、ポインタにも算術演算があることがわかりました。では、括弧の中から、前の複雑なポインタに対応するポインタの算術演算か要素の算術演算かをどのように判断するか:
int ia = {0, 2, 4, 6, 8};
int last = *(ia + 4); //先看括号内,所以这是指针的元素暗,last = ia[4] = 8
int last2 = *ia + 4; //ia指向ia[0], 所以last2 = ia[0] + 4 = 4
添字とポインタ
配列は実際には配列の最初の要素を指すポインターであることがわかります。そのため、配列に対する添え字演算は実際にはポインターに対する算術要素演算であり、ia[2] は *(ia + 2) と同等です。
int ia = {0, 2, 4, 6, 8};
int *p = &ia[2]; //p指向ia[2]的指针
int j = p[-2]; p[-2]等价于*(p - 2), 所以j = ia[0]
やっと
この記事では主に C++ 配列に関する内容について説明しますが、その他の記事については公式アカウント QStack を参照してください。