C++の配列入門:伝統的なCスタイル配列と現代的なstd::array

同一の型(例:int型)のデータを多数扱う際、それらを個別の変数として宣言するのは非効率です。例えば、12ヶ月分の月間経費を管理するために expense1, expense2, …, expense12 といった12個の変数を用意するのは現実的ではありません。

このような「同一の型のオブジェクトの集合」を効率的に扱うために、C++には配列という機能が用意されています。

目次

伝統的なCスタイル配列

C言語から引き継がれた伝統的な配列(Cスタイル配列)は、以下のように宣言します。

// 12個の double 型の要素を持つ配列
double monthlyExpenses[12];

これは、メモリ上にdouble型の領域を12個、連続して確保します。各要素にはインデックス(添字)[0] から [11] までを使ってアクセスします。

Cスタイル配列の初期化と走査

初期化は {} を使って行えます。配列の全要素を処理(走査)するには、for文が一般的に用いられます。

#include <iostream>

int main() {
    // 配列の宣言と初期化
    double monthlyExpenses[12] = {
        5.1, 4.8, 5.5, 6.0, 5.3, 7.0, 
        6.5, 5.9, 6.1, 5.8, 5.2, 5.0 
    };

    double sum = 0.0;
    
    // for文による配列の走査
    for (int i = 0; i < 12; ++i) {
        std::cout << (i + 1) << "月: " << monthlyExpenses[i] << "万円\n";
        sum += monthlyExpenses[i];
    }
    
    std::cout << "--- 年間経費 ---\n";
    std::cout << "合計: " << sum << "万円\n";
    std::cout << "平均: " << sum / 12.0 << "万円\n";
    
    return 0;
}

Cスタイル配列の問題点

Cスタイル配列はC++の基礎ですが、いくつかの重大な欠点があります。

  1. サイズが自明でない: 配列自体は、自分が要素をいくつ持つか(12という情報)を保持していません。
  2. sizeof の罠: 要素数を計算するには sizeof(monthlyExpenses) / sizeof(monthlyExpenses[0]) のような面倒な記述が必要で、特に関数にポインタとして渡されるとこの方法は機能しなくなります。
  3. コピーができない: arrayB = arrayA; のような単純な代入演算子による全要素のコピーはできません。

現代C++の推奨:std::array

C++11以降、上記のCスタイル配列の問題点をほぼすべて解決する、より安全で高機能な std::array が導入されました。これは <array> ヘッダをインクルードすることで使用できます。

std::array は、固定長(サイズがコンパイル時に決まっている)の配列を扱うための現代的なコンテナです。

#include <iostream>
#include <array>    // std::array のために必要
#include <numeric>  // std::accumulate のために必要

int main() {
    // std::array<型, 要素数>
    std::array<double, 12> monthlyExpenses = {
        5.1, 4.8, 5.5, 6.0, 5.3, 7.0, 
        6.5, 5.9, 6.1, 5.8, 5.2, 5.0 
    };

    double sum = 0.0;
    
    // (推奨) 範囲ベースfor文による走査
    // C++11以降の最も簡潔で安全な方法
    for (double expense : monthlyExpenses) {
        sum += expense;
    }
    
    // (参考) std::accumulate を使った合計計算
    // double total = std::accumulate(monthlyExpenses.begin(), monthlyExpenses.end(), 0.0);

    std::cout << "--- 年間経費 ---\n";
    std::cout << "合計: " << sum << "万円\n";
    
    // .size() メンバ関数で要素数を安全に取得できる
    std::cout << "平均: " << sum / monthlyExpenses.size() << "万円\n";
    
    return 0;
}

std::array の主要な操作

std::array を使うことで、Cスタイル配列では面倒だった操作が直感的かつ安全に行えます。

要素数と定値オブジェクト

配列の要素数は、const または constexpr を使った定値オブジェクトで表すことが推奨されます。これにより、コードの保守性が向上します。

#include <array>

// 配列のサイズを定数として定義
constexpr size_t NumMonths = 12;

int main() {
    std::array<double, NumMonths> monthlyExpenses;
    
    // .size() で要素数を取得
    for (size_t i = 0; i < monthlyExpenses.size(); ++i) {
        // ... 処理 ...
    }
}

size_t は、配列のインデックスやサイズを格納するのに適した符号なし整数型です。

配列のコピー

std::array は、代入演算子 = で全要素を簡単にコピーできます。

#include <array>
#include <iostream>

int main() {
    std::array<int, 4> originalData = {10, 20, 30, 40};
    
    // Cスタイル配列ではできなかった代入コピーが可能
    std::array<int, 4> copiedData = originalData; 

    // コピーされたことを確認
    copiedData[0] = 100;

    std::cout << "Original[0]: " << originalData[0] << "\n"; // 10
    std::cout << "Copied[0]:   " << copiedData[0] << "\n";   // 100
    
    return 0;
}

配列の要素の並びの反転

Cスタイル配列では手動で要素を入れ替える必要がありましたが、std::array は標準アルゴリズムライブラリ <algorithm> とシームレスに連携できます。

#include <array>
#include <algorithm> // std::reverse のために必要
#include <iostream>

int main() {
    std::array<int, 5> data = {1, 2, 3, 4, 5};
    
    // data.begin() から data.end() までの範囲を反転
    std::reverse(data.begin(), data.end());
    
    for (int val : data) {
        std::cout << val << " "; // 5 4 3 2 1
    }
    std::cout << "\n";
    
    return 0;
}

配列型の情報の取得

typeid 演算子(<typeinfo> ヘッダ)を使えば、std::array の型情報を調べることもできます。

#include <iostream>
#include <typeinfo> // typeid のために必要
#include <array>

int main() {
    std::array<double, 12> monthlyExpenses;
    
    // 型情報の表示 (出力内容は処理系に依存します)
    std::cout << "配列の型: " << typeid(monthlyExpenses).name() << "\n";
    std::cout << "要素の型: " << typeid(monthlyExpenses[0]).name() << "\n";

    return 0;
}

まとめ:Cスタイル配列 vs std::array

以下の比較表は、なぜ現代のC++で std::array が推奨されるかを示しています。

機能Cスタイル配列 (double a[12])std::array<double, 12>
サイズ取得sizeof を使うハックが必要。関数に渡すと失敗する。arr.size() で常に安全に取得可能。
コピーb = a; は不可。手動で要素をコピーする必要がある。b = a; で全要素のコピーが可能。
関数渡しポインタに「縮退」し、サイズ情報を失う。値渡し、参照渡しが容易で、型情報が保持される。
安全性arr[100] のような範囲外アクセスを検知しない。arr.at(i) で安全な境界チェック付きアクセスが可能。
標準ライブラリstd::reverse(a, a + 12) のように開始/終了を手動で指定。std::reverse(arr.begin(), arr.end()) で直感的に指定可能。

結論として、新しくC++でコードを記述する際は、Cスタイル配列の代わりに std::array(サイズが固定の場合)または std::vector(サイズが可変の場合)を使用することが強く推奨されます。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次