什麼是 Array? Link to heading

std::array 是 C++11 引入的固定大小陣列容器,在編譯期確定大小,封裝了傳統 C 陣列並提供 STL 容器的介面。

與其他容器的主要區別

  • 與 C 陣列:提供 STL 容器介面(迭代器、size()、at() 等),更安全
  • vector:大小固定,不能動態擴展,但無記憶體分配開銷
  • deque:連續記憶體,固定大小,性能更好但無法增長
  • list:連續記憶體,支援隨機訪問

適用場景

  • 元素數量在編譯期已知且固定
  • 需要 STL 容器介面的固定陣列
  • 需要在棧上分配且避免堆記憶體分配
  • 需要將陣列作為值傳遞或返回
  • 性能關鍵且大小固定的場景

內部實現原理 Link to heading

底層數據結構 Link to heading

std::array 本質上是對原生 C 陣列的封裝,內部實作極為簡單:

template<typename T, size_t N>
struct array {
    T elements[N];  // 原生 C 陣列

    // 提供 STL 容器介面
    size_t size() const { return N; }
    T& operator[](size_t i) { return elements[i]; }
    // ... 其他成員函式
};

記憶體布局示意圖 Link to heading

std::array<int, 5> arr = {1, 2, 3, 4, 5};

記憶體布局(棧上):
┌──────────────────────────────┐
│  arr[0]  arr[1]  arr[2]  arr[3]  arr[4] │
│    1       2       3       4       5     │
└──────────────────────────────────────────┘
  連續記憶體,無額外開銷

C 陣列: int arr[5] = {1, 2, 3, 4, 5};
記憶體布局(完全相同):
┌──────────────────────────────┐
│  arr[0]  arr[1]  arr[2]  arr[3]  arr[4] │
│    1       2       3       4       5     │
└──────────────────────────────────────────┘

特點

  1. 零開銷封裝std::array 的大小和性能與原生 C 陣列完全相同
  2. 棧分配:預設在棧上分配(除非是全域或靜態變數)
  3. 編譯期大小:大小必須是編譯期常數
  4. 連續記憶體:所有元素連續儲存,緩存友好

關鍵操作的實現機制 Link to heading

訪問操作

arr[i]  // 直接指標運算:*(arr.elements + i)
        // 時間複雜度:O(1)

邊界檢查

arr.at(i)  // 檢查 i < N,超出範圍拋出 std::out_of_range
           // 時間複雜度:O(1)

大小查詢

arr.size()  // 返回模板參數 N(編譯期常數)
            // 時間複雜度:O(1),通常被編譯器優化為常數

基本使用 Link to heading

頭文件和宣告 Link to heading

#include <array>
#include <iostream>

std::array<int, 5> arr1;               // 預設初始化(值未定義)
std::array<int, 5> arr2 = {};          // 零初始化
std::array<int, 5> arr3 = {1, 2, 3};   // 部分初始化(剩餘為 0)
std::array<int, 5> arr4{1, 2, 3, 4, 5};// 完整初始化(C++11)

構造方法 Link to heading

#include <array>
#include <iostream>

int main() {
    // 預設構造(元素未初始化)
    std::array<int, 5> arr1;

    // 零初始化
    std::array<int, 5> arr2 = {};
    // arr2: {0, 0, 0, 0, 0}

    // 列表初始化
    std::array<int, 5> arr3 = {1, 2, 3, 4, 5};
    // arr3: {1, 2, 3, 4, 5}

    // 部分初始化(剩餘為 0)
    std::array<int, 5> arr4 = {1, 2};
    // arr4: {1, 2, 0, 0, 0}

    // C++11 統一初始化(可省略 =)
    std::array<int, 5> arr5{10, 20, 30, 40, 50};

    // 拷貝構造
    std::array<int, 5> arr6 = arr5;

    // 從 C 陣列構造(需要手動複製)
    int cArr[] = {1, 2, 3, 4, 5};
    std::array<int, 5> arr7;
    std::copy(std::begin(cArr), std::end(cArr), arr7.begin());

    return 0;
}

常用成員函式 Link to heading

函式功能時間複雜度
operator[]訪問元素(無邊界檢查)O(1)
at(index)訪問元素(有邊界檢查)O(1)
front()返回第一個元素O(1)
back()返回最後一個元素O(1)
data()返回指向底層陣列的指標O(1)
size()返回元素個數(編譯期常數)O(1)
empty()判斷是否為空(編譯期常數)O(1)
fill(val)所有元素填充為 valO(n)
swap(other)交換兩個陣列O(n)
begin()/end()返回迭代器O(1)

注意std::array 沒有 push_back()insert()erase() 等動態操作,因為大小固定。

性能分析 Link to heading

時間複雜度表格 Link to heading

操作arrayvectorC 陣列
隨機訪問O(1)O(1)O(1)
頭部插入不支援O(n)不支援
尾部插入不支援O(1) 攤銷不支援
查找O(n)O(n)O(n)
排序O(n log n)O(n log n)O(n log n)

空間複雜度分析 Link to heading

記憶體開銷

sizeof(std::array<int, 5>)  // = 5 * sizeof(int) = 20 bytes
sizeof(int[5])              // = 5 * sizeof(int) = 20 bytes

// 零開銷!std::array 沒有任何額外開銷

與 vector 比較

std::array<int, 5> arr;     // 20 bytes(棧)
std::vector<int> vec(5);    // 20 bytes(堆)+ 24 bytes(vector 物件)

// array 更輕量,且在棧上分配

何時性能最優/最差 Link to heading

性能最優

  • 大小固定且已知
  • 需要棧上分配(避免堆記憶體開銷)
  • 需要與 C 介面互動
  • 性能關鍵路徑(無動態分配)
  • 小型陣列(棧空間充足)

性能最差

  • 需要動態改變大小(根本不支援)
  • 陣列很大(棧溢出風險)
  • 需要頻繁拷貝大型陣列(因為 array 是值語義)

常見操作示例 Link to heading

訪問元素 Link to heading

#include <array>
#include <iostream>

int main() {
    std::array<int, 5> arr = {10, 20, 30, 40, 50};

    // 方法1:下標訪問(無邊界檢查,最快)
    std::cout << "arr[0] = " << arr[0] << std::endl;
    std::cout << "arr[4] = " << arr[4] << std::endl;

    // 方法2:at() 訪問(有邊界檢查,更安全)
    std::cout << "arr.at(0) = " << arr.at(0) << std::endl;

    try {
        std::cout << arr.at(10) << std::endl;  // 超出範圍
    } catch (const std::out_of_range& e) {
        std::cout << "錯誤: " << e.what() << std::endl;
    }

    // 方法3:front() 和 back()
    std::cout << "第一個元素: " << arr.front() << std::endl;
    std::cout << "最後一個元素: " << arr.back() << std::endl;

    // 方法4:data() 取得原生指標
    int* ptr = arr.data();
    std::cout << "ptr[0] = " << ptr[0] << std::endl;

    return 0;
}

輸出

arr[0] = 10
arr[4] = 50
arr.at(0) = 10
錯誤: array::at: __n (which is 10) >= _Nm (which is 5)
第一個元素: 10
最後一個元素: 50
ptr[0] = 10

修改元素 Link to heading

#include <array>
#include <iostream>

int main() {
    std::array<int, 5> arr = {1, 2, 3, 4, 5};

    // 修改單個元素
    arr[0] = 100;
    arr.at(1) = 200;

    // 使用 fill() 填充所有元素
    std::array<int, 5> arr2;
    arr2.fill(99);

    // 輸出
    std::cout << "arr: ";
    for (int val : arr) {
        std::cout << val << " ";
    }
    std::cout << std::endl;

    std::cout << "arr2: ";
    for (int val : arr2) {
        std::cout << val << " ";
    }
    std::cout << std::endl;

    return 0;
}

輸出

arr: 100 200 3 4 5
arr2: 99 99 99 99 99

遍歷陣列 Link to heading

#include <array>
#include <iostream>

int main() {
    std::array<int, 5> arr = {1, 2, 3, 4, 5};

    // 方法1:範圍 for 迴圈
    std::cout << "方法1: ";
    for (int val : arr) {
        std::cout << val << " ";
    }
    std::cout << std::endl;

    // 方法2:傳統 for 迴圈 + 下標
    std::cout << "方法2: ";
    for (size_t i = 0; i < arr.size(); ++i) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;

    // 方法3:迭代器
    std::cout << "方法3: ";
    for (auto it = arr.begin(); it != arr.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    // 方法4:反向遍歷
    std::cout << "方法4(反向): ";
    for (auto it = arr.rbegin(); it != arr.rend(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    return 0;
}

輸出

方法1: 1 2 3 4 5
方法2: 1 2 3 4 5
方法3: 1 2 3 4 5
方法4(反向): 5 4 3 2 1

排序與查找 Link to heading

#include <array>
#include <algorithm>
#include <iostream>

int main() {
    std::array<int, 7> arr = {5, 2, 8, 1, 9, 3, 7};

    // 排序
    std::sort(arr.begin(), arr.end());
    std::cout << "排序後: ";
    for (int val : arr) {
        std::cout << val << " ";
    }
    std::cout << std::endl;

    // 二分查找(需要先排序)
    bool found = std::binary_search(arr.begin(), arr.end(), 7);
    std::cout << "是否找到 7: " << (found ? "是" : "否") << std::endl;

    // 查找第一個出現位置
    auto it = std::find(arr.begin(), arr.end(), 8);
    if (it != arr.end()) {
        std::cout << "找到 8,位於索引: "
                  << std::distance(arr.begin(), it) << std::endl;
    }

    // 反轉
    std::reverse(arr.begin(), arr.end());
    std::cout << "反轉後: ";
    for (int val : arr) {
        std::cout << val << " ";
    }
    std::cout << std::endl;

    return 0;
}

輸出

排序後: 1 2 3 5 7 8 9
是否找到 7: 是
找到 8,位於索引: 5
反轉後: 9 8 7 5 3 2 1

多維陣列 Link to heading

#include <array>
#include <iostream>

int main() {
    // 2D 陣列:3x4 矩陣
    std::array<std::array<int, 4>, 3> matrix = {{
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    }};

    // 訪問元素
    std::cout << "matrix[1][2] = " << matrix[1][2] << std::endl;

    // 遍歷 2D 陣列
    std::cout << "矩陣內容:" << std::endl;
    for (size_t i = 0; i < matrix.size(); ++i) {
        for (size_t j = 0; j < matrix[i].size(); ++j) {
            std::cout << matrix[i][j] << " ";
        }
        std::cout << std::endl;
    }

    // 使用範圍 for
    std::cout << "使用範圍 for:" << std::endl;
    for (const auto& row : matrix) {
        for (int val : row) {
            std::cout << val << " ";
        }
        std::cout << std::endl;
    }

    return 0;
}

輸出

matrix[1][2] = 7
矩陣內容:
1 2 3 4
5 6 7 8
9 10 11 12
使用範圍 for:
1 2 3 4
5 6 7 8
9 10 11 12

交換陣列 Link to heading

#include <array>
#include <iostream>

void printArray(const std::array<int, 5>& arr, const std::string& name) {
    std::cout << name << ": ";
    for (int val : arr) {
        std::cout << val << " ";
    }
    std::cout << std::endl;
}

int main() {
    std::array<int, 5> arr1 = {1, 2, 3, 4, 5};
    std::array<int, 5> arr2 = {10, 20, 30, 40, 50};

    std::cout << "交換前:" << std::endl;
    printArray(arr1, "arr1");
    printArray(arr2, "arr2");

    // 使用 swap() 成員函式
    arr1.swap(arr2);

    std::cout << "\n交換後:" << std::endl;
    printArray(arr1, "arr1");
    printArray(arr2, "arr2");

    // 也可以使用 std::swap
    std::swap(arr1, arr2);

    std::cout << "\n再次交換後:" << std::endl;
    printArray(arr1, "arr1");
    printArray(arr2, "arr2");

    return 0;
}

輸出

交換前:
arr1: 1 2 3 4 5
arr2: 10 20 30 40 50

交換後:
arr1: 10 20 30 40 50
arr2: 1 2 3 4 5

再次交換後:
arr1: 1 2 3 4 5
arr2: 10 20 30 40 50

迭代器支持 Link to heading

支援的迭代器類型 Link to heading

std::array 提供隨機訪問迭代器(random access iterator):

#include <array>
#include <iostream>

int main() {
    std::array<int, 5> arr = {1, 2, 3, 4, 5};

    auto it = arr.begin();

    // 隨機訪問迭代器支援所有操作
    ++it;           // 前進
    --it;           // 後退
    it += 3;        // 跳躍
    it -= 2;        // 反向跳躍
    *it;            // 解參考
    it[2];          // 下標訪問

    std::cout << "it[2] = " << it[2] << std::endl;

    // 迭代器運算
    auto it2 = arr.begin() + 3;
    int distance = it2 - arr.begin();
    std::cout << "距離: " << distance << std::endl;

    return 0;
}

迭代器失效問題 Link to heading

array 的特點:由於大小固定,迭代器幾乎永不失效

#include <array>
#include <iostream>

int main() {
    std::array<int, 5> arr = {1, 2, 3, 4, 5};

    auto it1 = arr.begin();
    auto it2 = arr.begin() + 2;

    // 修改元素不會使迭代器失效
    arr[0] = 100;
    arr[2] = 300;

    std::cout << "*it1 = " << *it1 << std::endl;  // 100(已修改)
    std::cout << "*it2 = " << *it2 << std::endl;  // 300(已修改)

    // fill() 不會使迭代器失效
    arr.fill(99);
    std::cout << "*it1 = " << *it1 << std::endl;  // 99

    // swap() 會使迭代器失效!
    std::array<int, 5> arr2 = {10, 20, 30, 40, 50};
    arr.swap(arr2);
    // 現在 it1 指向 arr2 的元素!

    return 0;
}

迭代器失效規則總結

操作失效範圍
operator[]/at()永不失效
fill()永不失效
swap()所有迭代器失效(指向交換後的另一個容器)

常見陷阱與注意事項 Link to heading

1. 大小必須是編譯期常數 Link to heading

// ❌ 錯誤:執行期變數
int n = 5;
// std::array<int, n> arr;  // 編譯錯誤!

// ✅ 正確:編譯期常數
const int N = 5;
std::array<int, N> arr1;

constexpr int M = 10;
std::array<int, M> arr2;

std::array<int, 5> arr3;  // 字面常數

2. 大型陣列可能導致棧溢出 Link to heading

// ❌ 危險:可能棧溢出(1MB 陣列)
std::array<int, 250000> hugeArray;  // 局部變數在棧上

// ✅ 解決方案1:使用 static(放在靜態儲存區)
static std::array<int, 250000> hugeArray;

// ✅ 解決方案2:使用 vector(堆分配)
std::vector<int> vec(250000);

// ✅ 解決方案3:放在全域或類別成員

3. 未初始化的陣列內容未定義 Link to heading

// ❌ 錯誤:元素值未定義
std::array<int, 5> arr1;
std::cout << arr1[0] << std::endl;  // 未定義行為

// ✅ 正確:零初始化
std::array<int, 5> arr2 = {};
std::cout << arr2[0] << std::endl;  // 保證是 0

// ✅ 正確:使用 fill()
std::array<int, 5> arr3;
arr3.fill(0);

4. 傳遞陣列時是值拷貝 Link to heading

void processArray(std::array<int, 1000> arr) {  // ⚠️ 拷貝整個陣列!
    // 處理...
}

// ✅ 推薦:使用 const 參考
void processArray(const std::array<int, 1000>& arr) {
    // 處理...
}

// ✅ 如果需要修改:使用參考
void modifyArray(std::array<int, 1000>& arr) {
    // 修改...
}

5. 不同大小的 array 是不同型別 Link to heading

std::array<int, 5> arr1 = {1, 2, 3, 4, 5};
std::array<int, 6> arr2 = {1, 2, 3, 4, 5, 6};

// ❌ 編譯錯誤:不同型別
// arr1 = arr2;

// ❌ 編譯錯誤:函式無法接受不同大小的 array
void func(std::array<int, 5> arr);
// func(arr2);  // 錯誤

// ✅ 解決方案:使用模板
template<size_t N>
void func(const std::array<int, N>& arr) {
    // 可以接受任意大小的 array
}

6. 空陣列是合法的 Link to heading

// ✅ 合法:大小為 0 的陣列
std::array<int, 0> emptyArray;

std::cout << "size: " << emptyArray.size() << std::endl;     // 0
std::cout << "empty: " << emptyArray.empty() << std::endl;   // true

// ⚠️ 不能訪問元素(未定義行為)
// emptyArray[0];    // 未定義
// emptyArray.front();  // 未定義

實際應用場景 Link to heading

場景 1:固定大小的緩衝區 Link to heading

網路程式設計中常用固定大小的緩衝區。

#include <array>
#include <iostream>
#include <cstring>

class NetworkBuffer {
private:
    static constexpr size_t BUFFER_SIZE = 1024;
    std::array<char, BUFFER_SIZE> buffer;
    size_t dataLength;

public:
    NetworkBuffer() : dataLength(0) {
        buffer.fill(0);
    }

    bool write(const char* data, size_t len) {
        if (dataLength + len > BUFFER_SIZE) {
            std::cerr << "緩衝區已滿" << std::endl;
            return false;
        }

        std::memcpy(buffer.data() + dataLength, data, len);
        dataLength += len;
        return true;
    }

    const char* data() const {
        return buffer.data();
    }

    size_t size() const {
        return dataLength;
    }

    void clear() {
        buffer.fill(0);
        dataLength = 0;
    }

    void print() const {
        std::cout << "緩衝區內容 (" << dataLength << " bytes): ";
        for (size_t i = 0; i < dataLength; ++i) {
            std::cout << buffer[i];
        }
        std::cout << std::endl;
    }
};

int main() {
    NetworkBuffer buf;

    buf.write("Hello, ", 7);
    buf.write("World!", 6);
    buf.print();

    buf.clear();
    buf.write("New data", 8);
    buf.print();

    return 0;
}

輸出

緩衝區內容 (13 bytes): Hello, World!
緩衝區內容 (8 bytes): New data

場景 2:查找表(Lookup Table) Link to heading

編譯期已知的對映關係可以使用 array 實作查找表。

#include <array>
#include <iostream>
#include <string>

class MonthInfo {
private:
    static constexpr size_t NUM_MONTHS = 12;

    // 月份名稱查找表
    static constexpr std::array<const char*, NUM_MONTHS> monthNames = {
        "January", "February", "March", "April", "May", "June",
        "July", "August", "September", "October", "November", "December"
    };

    // 月份天數查找表(平年)
    static constexpr std::array<int, NUM_MONTHS> daysInMonth = {
        31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
    };

public:
    static const char* getMonthName(int month) {
        if (month < 1 || month > 12) {
            return "Invalid";
        }
        return monthNames[month - 1];
    }

    static int getDaysInMonth(int month, bool isLeapYear = false) {
        if (month < 1 || month > 12) {
            return -1;
        }

        int days = daysInMonth[month - 1];
        if (month == 2 && isLeapYear) {
            days = 29;
        }
        return days;
    }

    static void printCalendarInfo() {
        std::cout << "月份資訊(平年):" << std::endl;
        for (int i = 1; i <= 12; ++i) {
            std::cout << i << ". " << getMonthName(i)
                      << " - " << getDaysInMonth(i) << " 天" << std::endl;
        }
    }
};

// 初始化靜態成員
constexpr std::array<const char*, 12> MonthInfo::monthNames;
constexpr std::array<int, 12> MonthInfo::daysInMonth;

int main() {
    MonthInfo::printCalendarInfo();

    std::cout << "\n二月天數(閏年): "
              << MonthInfo::getDaysInMonth(2, true) << std::endl;

    return 0;
}

輸出

月份資訊(平年):
1. January - 31 天
2. February - 28 天
3. March - 31 天
4. April - 30 天
5. May - 31 天
6. June - 30 天
7. July - 31 天
8. August - 31 天
9. September - 30 天
10. October - 31 天
11. November - 30 天
12. December - 31 天

二月天數(閏年): 29

場景 3:矩陣運算(小型固定矩陣) Link to heading

3D 圖形學中常用 4x4 矩陣。

#include <array>
#include <iostream>
#include <iomanip>

class Matrix4x4 {
private:
    std::array<std::array<float, 4>, 4> data;

public:
    // 構造單位矩陣
    Matrix4x4() {
        for (int i = 0; i < 4; ++i) {
            for (int j = 0; j < 4; ++j) {
                data[i][j] = (i == j) ? 1.0f : 0.0f;
            }
        }
    }

    // 訪問元素
    float& at(int row, int col) {
        return data[row][col];
    }

    float at(int row, int col) const {
        return data[row][col];
    }

    // 矩陣乘法
    Matrix4x4 operator*(const Matrix4x4& other) const {
        Matrix4x4 result;
        for (int i = 0; i < 4; ++i) {
            for (int j = 0; j < 4; ++j) {
                result.data[i][j] = 0;
                for (int k = 0; k < 4; ++k) {
                    result.data[i][j] += data[i][k] * other.data[k][j];
                }
            }
        }
        return result;
    }

    // 縮放矩陣
    static Matrix4x4 scale(float sx, float sy, float sz) {
        Matrix4x4 mat;
        mat.data[0][0] = sx;
        mat.data[1][1] = sy;
        mat.data[2][2] = sz;
        return mat;
    }

    // 平移矩陣
    static Matrix4x4 translate(float tx, float ty, float tz) {
        Matrix4x4 mat;
        mat.data[0][3] = tx;
        mat.data[1][3] = ty;
        mat.data[2][3] = tz;
        return mat;
    }

    // 輸出
    void print() const {
        for (int i = 0; i < 4; ++i) {
            for (int j = 0; j < 4; ++j) {
                std::cout << std::setw(8) << std::setprecision(2)
                          << data[i][j] << " ";
            }
            std::cout << std::endl;
        }
    }
};

int main() {
    std::cout << "單位矩陣:" << std::endl;
    Matrix4x4 identity;
    identity.print();

    std::cout << "\n縮放矩陣 (2, 3, 4):" << std::endl;
    Matrix4x4 scaleMatrix = Matrix4x4::scale(2, 3, 4);
    scaleMatrix.print();

    std::cout << "\n平移矩陣 (5, 6, 7):" << std::endl;
    Matrix4x4 translateMatrix = Matrix4x4::translate(5, 6, 7);
    translateMatrix.print();

    std::cout << "\n複合變換(縮放 × 平移):" << std::endl;
    Matrix4x4 combined = scaleMatrix * translateMatrix;
    combined.print();

    return 0;
}

輸出

單位矩陣:
    1.00     0.00     0.00     0.00
    0.00     1.00     0.00     0.00
    0.00     0.00     1.00     0.00
    0.00     0.00     0.00     1.00

縮放矩陣 (2, 3, 4):
    2.00     0.00     0.00     0.00
    0.00     3.00     0.00     0.00
    0.00     0.00     4.00     0.00
    0.00     0.00     0.00     1.00

平移矩陣 (5, 6, 7):
    1.00     0.00     0.00     5.00
    0.00     1.00     0.00     6.00
    0.00     0.00     1.00     7.00
    0.00     0.00     0.00     1.00

複合變換(縮放 × 平移):
    2.00     0.00     0.00    10.00
    0.00     3.00     0.00    18.00
    0.00     0.00     4.00    28.00
    0.00     0.00     0.00     1.00

與其他容器的選擇 Link to heading

對比表格 Link to heading

特性arrayvectorC 陣列deque
大小固定
編譯期大小
隨機訪問✅ O(1)✅ O(1)✅ O(1)✅ O(1)
棧分配
STL 介面
邊界檢查✅ at()✅ at()✅ at()
零開銷
可動態增長

選擇建議 Link to heading

選擇 array 當

  • ✅ 元素數量在編譯期已知且固定
  • ✅ 需要棧分配(避免堆記憶體開銷)
  • ✅ 需要 STL 介面的固定陣列
  • ✅ 性能關鍵且大小固定
  • ✅ 需要零開銷封裝

選擇 vector 當

  • ✅ 元素數量會動態變化
  • ✅ 需要在尾部頻繁插入/刪除
  • ✅ 陣列很大(避免棧溢出)
  • ✅ 大小在執行期決定

選擇 C 陣列當

  • ✅ 與 C 介面互動
  • ✅ 不需要 STL 介面
  • ✅ 極度追求效能(省略 array 的封裝)

選擇 deque 當

  • ✅ 需要在頭尾兩端插入/刪除
  • ✅ 需要隨機訪問但也需要動態大小

實務建議 Link to heading

DO(應該做的) Link to heading

1. ✅ 使用 array 而非 C 陣列 Link to heading

// ✅ 推薦:使用 std::array
std::array<int, 5> arr = {1, 2, 3, 4, 5};
std::cout << "size: " << arr.size() << std::endl;
std::sort(arr.begin(), arr.end());

// ❌ 避免:使用 C 陣列(無 STL 介面)
int cArr[5] = {1, 2, 3, 4, 5};
// cArr.size();  // 錯誤!
std::sort(std::begin(cArr), std::end(cArr));  // 需要 std::begin/end

2. ✅ 使用 at() 進行邊界檢查(除錯模式) Link to heading

std::array<int, 5> arr = {1, 2, 3, 4, 5};

// ✅ 開發階段:使用 at() 捕獲錯誤
try {
    int val = arr.at(10);  // 拋出異常
} catch (const std::out_of_range& e) {
    std::cerr << "索引錯誤: " << e.what() << std::endl;
}

// ✅ 發佈階段:使用 [] 提升效能
// int val = arr[2];  // 無邊界檢查,更快

3. ✅ 使用統一初始化避免 most vexing parse Link to heading

// ✅ 推薦:統一初始化
std::array<int, 5> arr1{1, 2, 3, 4, 5};

// ❌ 可能有問題:使用 = {} 在某些情境下有歧義
// std::array<int, 5> arr2 = {1, 2, 3, 4, 5};  // 通常 OK,但不一致

4. ✅ 傳遞 array 時使用 const 參考 Link to heading

// ✅ 推薦:const 參考(避免拷貝)
void processArray(const std::array<int, 100>& arr) {
    for (int val : arr) {
        // 處理...
    }
}

// ❌ 避免:值傳遞(拷貝整個陣列)
// void processArray(std::array<int, 100> arr) {
//     // 400 bytes 拷貝!
// }

5. ✅ 使用 constexpr 定義大小 Link to heading

// ✅ 推薦:使用 constexpr
constexpr size_t ARRAY_SIZE = 10;
std::array<int, ARRAY_SIZE> arr;

// ✅ 也可以:直接使用字面常數
std::array<int, 10> arr2;

// ❌ 避免:魔術數字散布各處
// std::array<int, 10> arr1;
// for (int i = 0; i < 10; ++i) { ... }

6. ✅ 利用 data() 與 C 介面互動 Link to heading

#include <cstring>

std::array<char, 100> buffer;

// ✅ 推薦:使用 data() 取得原生指標
std::memset(buffer.data(), 0, buffer.size());

// C 函式
extern "C" void c_function(char* buf, size_t len);
c_function(buffer.data(), buffer.size());

7. ✅ 使用 fill() 初始化所有元素 Link to heading

std::array<int, 1000> arr;

// ✅ 推薦:使用 fill() 批次初始化
arr.fill(0);

// ❌ 避免:逐個賦值
// for (size_t i = 0; i < arr.size(); ++i) {
//     arr[i] = 0;
// }

8. ✅ 多維陣列使用型別別名簡化 Link to heading

// ✅ 推薦:使用 using 定義別名
using Matrix3x3 = std::array<std::array<int, 3>, 3>;

Matrix3x3 mat = {{
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
}};

// ❌ 避免:每次都寫完整型別
// std::array<std::array<int, 3>, 3> mat = ...

DON’T(不應該做的) Link to heading

1. ❌ 不要在棧上分配大型 array Link to heading

// ❌ 危險:可能棧溢出(400KB)
void func() {
    std::array<int, 100000> hugeArray;  // 局部變數在棧上
}

// ✅ 解決方案1:使用 static
void func() {
    static std::array<int, 100000> hugeArray;
}

// ✅ 解決方案2:使用 vector
void func() {
    std::vector<int> vec(100000);  // 堆分配
}

// ✅ 解決方案3:改為類別成員
class MyClass {
    std::array<int, 100000> data;  // 跟隨物件分配
};

2. ❌ 不要嘗試在執行期決定大小 Link to heading

int n;
std::cin >> n;

// ❌ 錯誤:編譯失敗
// std::array<int, n> arr;

// ✅ 正確:使用 vector
std::vector<int> vec(n);

3. ❌ 不要忘記零初始化 Link to heading

// ❌ 錯誤:元素值未定義
std::array<int, 5> arr1;
std::cout << arr1[0] << std::endl;  // 未定義行為

// ✅ 正確:零初始化
std::array<int, 5> arr2 = {};
std::cout << arr2[0] << std::endl;  // 保證是 0

4. ❌ 不要混淆不同大小的 array Link to heading

std::array<int, 5> arr1;
std::array<int, 6> arr2;

// ❌ 錯誤:不同型別
// arr1 = arr2;  // 編譯錯誤

// ✅ 如果需要不同大小,使用模板或 vector
template<size_t N>
void processArray(const std::array<int, N>& arr) {
    // 可以處理任意大小
}

5. ❌ 不要對 array 使用動態操作 Link to heading

std::array<int, 5> arr = {1, 2, 3, 4, 5};

// ❌ 錯誤:array 沒有這些函式
// arr.push_back(6);   // 編譯錯誤
// arr.insert(arr.begin(), 0);  // 編譯錯誤
// arr.resize(10);     // 編譯錯誤

// ✅ array 的大小是固定的,如需動態操作使用 vector

6. ❌ 不要忽略 swap() 的成本 Link to heading

std::array<int, 10000> arr1, arr2;

// ⚠️ swap() 會逐個交換元素,O(n) 複雜度
arr1.swap(arr2);  // 交換 10000 個元素

// 💡 提示:vector::swap() 只交換指標,O(1)
std::vector<int> vec1(10000), vec2(10000);
vec1.swap(vec2);  // 僅交換指標,非常快

7. ❌ 不要返回 array 的原生指標 Link to heading

// ❌ 危險:返回指向局部變數的指標
int* getArrayData() {
    std::array<int, 5> arr = {1, 2, 3, 4, 5};
    return arr.data();  // 危險!arr 在函式結束時銷毀
}

// ✅ 正確:返回整個 array(會複製)
std::array<int, 5> getArray() {
    std::array<int, 5> arr = {1, 2, 3, 4, 5};
    return arr;  // RVO 優化,效率可接受
}

// ✅ 或者:使用參考參數
void fillArray(std::array<int, 5>& arr) {
    arr = {1, 2, 3, 4, 5};
}

8. ❌ 不要假設 array 和 C 陣列完全相同 Link to heading

std::array<int, 5> arr = {1, 2, 3, 4, 5};

// ⚠️ array 不會隱式轉換為指標
void cFunc(int* ptr);
// cFunc(arr);  // 錯誤!

// ✅ 使用 data() 明確轉換
cFunc(arr.data());

// ⚠️ sizeof 行為不同
int cArr[5];
std::cout << sizeof(cArr) / sizeof(int) << std::endl;  // 5
std::cout << sizeof(arr) / sizeof(int) << std::endl;   // 5(但語意不同)

// ✅ 使用 size() 更清晰
std::cout << arr.size() << std::endl;  // 5

小結 Link to heading

核心概念總結 Link to heading

  1. 零開銷封裝std::array 是對 C 陣列的零開銷封裝,提供 STL 介面
  2. 固定大小:大小必須在編譯期確定,不能動態改變
  3. 棧分配:預設在棧上分配,避免堆記憶體開銷
  4. 連續記憶體:所有元素連續儲存,緩存友好,支援隨機訪問
  5. 安全性:提供 at() 邊界檢查,比 C 陣列更安全

關鍵記憶點 Link to heading

  • 適合場景:大小固定、編譯期已知、需要 STL 介面、性能關鍵
  • 不適合場景:需要動態大小、陣列很大(棧溢出)、執行期決定大小
  • 🔧 核心優勢:零開銷、棧分配、STL 介面、類型安全
  • ⚠️ 主要問題:大小固定、大型陣列可能棧溢出、不同大小是不同型別

一句話總結 Link to heading

std::array 是固定大小的陣列容器,在編譯期確定大小並提供零開銷的 STL 介面封裝,適合大小已知且固定的場景,結合了 C 陣列的效能和 STL 容器的安全性與便利性。