そろそろC++やります
そろそろC++をやります。そろそろC++をやりたいからです。
何回やるかはわかりません。基礎を理解するまではやろうと思います。
という感じでやっています。
※ 初心者がメモレベルで記録するので、技術記事としてはお力になれないかもしれません。
内容
スタック配列
要素数は、整数リテラルや定数式として、コンパイラが割り当てる領域を認識している必要があります。実行時に計算される値を使用することはできません。
// 整数リテラル int arCount = 23; int ar[arCount]; // 定数式 int brCount = 13 + 57; int br[brCount];
こちらの記事や、こちらの記事、こちらの記事でも、配列のサイズは定数でなくてはならない、と書いてありました。
しかし、これらの記事で紹介されている、scanf
やcin
を用いた外部入力による配列の要素数の決定でも、自分の環境ではエラーが出ず、動的に配列サイズを変えて配列が作れました。
// エラーが出ずに実行ができる。 int num; scanf("%d", &num); int a[num]; int n; cin >> n; int array[n];
自分のコンパイル環境はこちらです。
$ g++ --version Apple clang version 14.0.3 (clang-1403.0.22.14.1) Target: arm64-apple-darwin22.3.0 Thread model: posix
エラーは出ずに使えたのですが、ドキュメントにダメと書いてあるため、今後使うのは止めることとします。
配列に規定値を定めない場合は、そのメモリの位置にあるランダムな値が入ります。
int ar[3]; for (int i = 0; i < 3; i++) { cout << ar[i] << endl; } // Output: // -427810655 // 2620700 // 1
配列の最初の要素は0番で、最後の要素はn-1番です。
スタックベースの配列は、ヒープベースの配列(次に紹介)よりも割り当てとアクセスが高速です。ただし、スタックの要素数は制限され過剰に使用することはできません。過剰さはプログラムによって異なります。
ヒープ配列
ヒープ配列を用いるのは、
場合に使います。
使用方法としては、ポインターを定義し、new []
による初期化を行います。
int *arHeap; arHeap = new int[4];
arHeap
はスタック領域におかれ、arHeap[0] ~ arHeap[3]
の実態はヒープ領域におかれます。
1. ポインタ宣言時はStack領域
arHeapを宣言した時は、スタック領域にarHeapのメモリが確保されます。この時点では変数の宣言のみなので、メモリの中身は未定状態になっています。
int *arHeap;
2. new[]での配列の割り当てはHeap領域
new []
を用いて、配列の割り当てを行った時に、Heap側に配列の要素のメモリ確保が行われます。そして、new []
演算子は、最初の要素へのポインターを返すため、スタック上のarHeap
の中身がarHeap[0]
のポインター(アドレス)に置き換わります。
arHeap = new int[4];
これがメモリを動的に確保できる原理になっています。
配列を取り扱う上で気をつける点として、
- 後からメモリの削除を行えるように、アドレスを保存する。
- 配列にアクセスする際に、配列の範囲を越えないようにする。
ことが、あります。
一度new []
してヒープ領域のメモリを確保した際にdelete[]
でメモリの開放を行う必要があります。
int *arHeap; arHeap = new int[2]; arHeap[0] = 13; arHeap[1] = 16; cout << hex << &arHeap[1] << endl; cout << dec << arHeap[1] << endl; delete[] arHeap; cout << hex << &arHeap[1] << endl; cout << dec << arHeap[1] << endl; // Output: // 0x128e068e4 // 16 // 0x128e068e4 // 0
delete[] arHeap;
でメモリの開放を行っており、その後arHeap[1]
にアクセスした時に、中身が0で初期化されています。
最後に
最後に、メモリの位置確認用のスクリプトを載せておきます。観察してみると、配列の要素だけ、メモリが別の場所にあることがわかると思います。
int main() { int a = 100; cout << "a address :"; cout << hex << &a << endl; int *arHeap; arHeap = new int[4]; arHeap[0] = 13; arHeap[1] = 16; arHeap[2] = 56; arHeap[3] = 25; cout << "arHeap address :"; cout << hex << &arHeap << endl; cout << "arHeap value :"; cout << hex << arHeap << endl; int b = 100; cout << "b address :"; cout << hex << &b << endl; for (int i = 0; i < 4; i++) { cout << "==== arHeap[" << i << "] ====" << endl; cout << dec << arHeap[i] << endl; cout << hex << &arHeap[i] << endl; } return 0; }
a address :0x16b99edc8 arHeap address :0x16b99edc0 arHeap value :0x145e068e0 b address :0x16b99edbc ==== arHeap[0] ==== 13 0x145e068e0 ==== arHeap[1] ==== 16 0x145e068e4 ==== arHeap[2] ==== 56 0x145e068e8 ==== arHeap[3] ==== 25 0x145e068ec
参考リンク
感想
スタックの配列からヒープの配列まで行いました。同じ配列という枠組みではありますが、メモリの部分までみると、全く別のことをしていて、スタックの配列とヒープの配列は別物だ、という印象を持ちました。
要素数に制約があるけど、高速でアクセスできるスタックか、自由に可変的に使えるヒープか、使い分ける必要がありそうとも思いました。
次回は、配列の初期化や、多次元配列を行います。