pointer - c++ std::array




我如何在C++中使用數組? (4)

C ++繼承了C語言中的數組,幾乎在任何地方都使用它們。 C ++提供了易於使用且易於出錯的抽象(從C ++ 98開始,自C ++ 98和std::array<T, n>之後的std::vector<T> ),所以對數組的需求不會出現的頻率與在C中的頻率相同。但是,當您閱讀遺留代碼或與用C語言編寫的庫進行交互時,應該牢牢掌握陣列的工作方式。

本常見問題分為五個部分:

  1. 類型級別的數組和訪問元素
  2. 數組的創建和初始化
  3. 分配和參數傳遞
  4. 多維數組和指針數組
  5. 使用數組時常見的缺陷

如果您覺得此常見問題解答中缺少重要內容,請撰寫答案並將其作為附加鏈接鏈接到此處。

在下面的文本中,“數組”意味著“C數組”,而不是類模板std::array 。 假定C聲明符語法的基本知識。 請注意,如下所示,手動使用newdelete對於例外是非常危險的,但這是另一個常見問題的主題。

(注意:這是一個Stack Overflow的C ++常見問題解答的入口,如果你想批評在這個表單中提供常見問題的想法,那麼開始所有這些的meta上的貼子應該是這樣做的地方。那個問題在C ++聊天室中進行監控,常見問題的想法首先出現在C ++聊天室中,所以你的答案很可能會被那些提出這個想法的人閱讀。)


5.使用數組時常見的陷阱。

5.1陷阱:信任類型不安全的鏈接。

好的,你已經被告知或者已經發現你自己,全局變量(可以在翻譯單元之外訪問的命名空間範圍變量)是Evil™。 但是你知道他們是如何真正的邪惡? 考慮下面的程序,包含兩個文件[main.cpp]和[numbers.cpp]:

// [main.cpp]
#include <iostream>

extern int* numbers;

int main()
{
    using namespace std;
    for( int i = 0;  i < 42;  ++i )
    {
        cout << (i > 0? ", " : "") << numbers[i];
    }
    cout << endl;
}

// [numbers.cpp]
int numbers[42] = {1, 2, 3, 4, 5, 6, 7, 8, 9};

在Windows 7中,它編譯和鏈接MinGW g ++ 4.4.1和Visual C ++ 10.0。

由於類型不匹配,程序在運行時會崩潰。

在正式的解釋中:該計劃具有未定義行為(UB),而不是崩潰,因此它可以掛起,或者什麼都不做,或者可以向美國,俄羅斯,印度和美國總統發送威脅性的電子郵件。中國和瑞士,並且使鼻惡魔從你的鼻子飛出。

實踐中的解釋:在main.cpp ,數組被視為一個指針,放置在與數組相同的地址處。 對於32位可執行文件,這意味著數組中的第一個int值被視為指針。 即,在main.cppnumbers變量包含或似乎包含(int*)1 。 這會導致程序在地址空間的最底部訪問內存,這通常是保留和陷阱引起的。 結果:你遇到了崩潰。

編譯器完全有權不診斷這個錯誤,因為C ++ 11§3.5/ 10說,關於兼容類型聲明的要求,

[N3290§3.5/ 10]
在類型標識上違反此規則不需要診斷。

同一段詳述了允許的變化:

...數組對象的聲明可以指定數組類型由於存在或不存在主數組bound(8.3.4)而有所不同。

允許的變體不包括在一個翻譯單元中聲明一個數組作為數組,並且作為另一個翻譯單元中的指針。

5.2陷阱:做過早優化( memset &friends)。

尚未寫入

5.3陷阱:使用C語言來獲取元素的數量。

憑藉深厚的C經驗,編寫...

#define N_ITEMS( array )   (sizeof( array )/sizeof( array[0] ))

由於array會衰減指向所需的第一個元素,所以表達式sizeof(a)/sizeof(a[0])也可以寫為sizeof(a)/sizeof(*a) 。 它的意思是相同的,不管它如何寫,它是查找數組的數字元素的C語言

主要陷阱:C語言不是類型安全的。 例如,代碼...

#include <stdio.h>

#define N_ITEMS( array ) (sizeof( array )/sizeof( *array ))

void display( int const a[7] )
{
    int const   n = N_ITEMS( a );          // Oops.
    printf( "%d elements.\n", n );
}

int main()
{
    int const   moohaha[]   = {1, 2, 3, 4, 5, 6, 7};

    printf( "%d elements, calling display...\n", N_ITEMS( moohaha ) );
    display( moohaha );
}

傳遞一個指向N_ITEMS的指針,因此很可能產生錯誤的結果。 編譯為Windows 7中的32位可執行文件,它生成...

7個元素,調用顯示...
1個元素。

  1. 編譯器將int const a[7]重寫為int const a[]
  2. 編譯器將int const a[]重寫為int const* a
  3. 因此N_ITEMS被一個指針調用。
  4. 對於32位可執行文件sizeof(array) (指針的大小)是4。
  5. sizeof(*array)等價於sizeof(int) ,對於32位可執行文件也是4。

為了在運行時檢測到這個錯誤,你可以做...

#include <assert.h>
#include <typeinfo>

#define N_ITEMS( array )       (                               \
    assert((                                                    \
        "N_ITEMS requires an actual array as argument",        \
        typeid( array ) != typeid( &*array )                    \
        )),                                                     \
    sizeof( array )/sizeof( *array )                            \
    )

7個元素,調用顯示...
斷言失敗:(“N_ITEMS需要一個實際的數組作為參數”,typeid(a)!= typeid(&* a)),文件runtime_detect ion.cpp,第16行

此應用程序已請求運行時以不尋常的方式終止它。
請聯繫應用程序支持團隊以獲取更多信息。

運行時錯誤檢測比沒有檢測更好,但是它浪費了一點處理器時間,可能還有更多的程序員時間。 在編譯時檢測更好! 如果你很高興不用C ++ 98支持本地類型的數組,那麼你可以這樣做:

#include <stddef.h>

typedef ptrdiff_t   Size;

template< class Type, Size n >
Size n_items( Type (&)[n] ) { return n; }

#define N_ITEMS( array )       n_items( array )

用g ++編譯這個定義代入第一個完整的程序,我得到了...

M:\ count> g ++ compile_time_detection.cpp
compile_time_detection.cpp:在函數'void display(const int *)'中:
compile_time_detection.cpp:14:錯誤:沒有匹配函數調用'n_items(const int *&)'

M:\ count> _

它是如何工作的:該數組通過引用傳遞給n_items ,因此它不會衰減指向第一個元素,並且該函數可以返回該類型指定的元素數量。

使用C ++ 11,您也可以將其用於本地類型的數組,這是用於查找數組元素數量的類型安全的C ++習慣用法

5.4 C ++ 11和C ++ 14陷阱:使用constexpr數組大小函數。

對於C ++ 11及更高版本來說,這很自然,但正如您將看到的危險一樣,要替換C ++ 03函數

typedef ptrdiff_t   Size;

template< class Type, Size n >
Size n_items( Type (&)[n] ) { return n; }

using Size = ptrdiff_t;

template< class Type, Size n >
constexpr auto n_items( Type (&)[n] ) -> Size { return n; }

其中重要的變化是使用constexpr ,它允許這個函數產生一個編譯時間常量

例如,與C ++ 03函數不同,可以使用這樣的編譯時間常量來聲明與另一個相同大小的數組:

// Example 1
void foo()
{
    int const x[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 4};
    constexpr Size n = n_items( x );
    int y[n] = {};
    // Using y here.
}

但考慮使用constexpr版本的代碼:

// Example 2
template< class Collection >
void foo( Collection const& c )
{
    constexpr int n = n_items( c );     // Not in C++14!
    // Use c here
}

auto main() -> int
{
    int x[42];
    foo( x );
}

陷阱:截至2015年7月,上述代碼使用MinGW-64 5.1.0編譯時-pedantic-errors ,並且使用-pedantic-errors上的在線編譯器進行gcc.godbolt.org/ ,也使用了鏗鏘3.0和3.2版本,但不使用鏗鏘3.3,3.4.1,3.5.0,3.5.1,3.6(rc1)或3.7(實驗)。 對於Windows平台很重要,它不能用Visual C ++ 2015進行編譯。原因是關於在constexpr表達式中使用引用的C ++ 11 / C ++ 14聲明:

C ++ 11 C ++ 14 $ 5.19 / 2第九短跑

條件表達式 e是一個核心常量表達式,除非根據抽像機器(1.9)的規則對e進行評估,否則將評估以下表達式之一:

  • 除非引用有前面的初始化,否則引用引用類型的變量或數據成員的id表達式
    • 它用一個常量表達式或者初始化
    • 它是一個對象的非靜態數據成員,其生命週期始於e的評估;

人們總是可以寫得更詳細

// Example 3  --  limited

using Size = ptrdiff_t;

template< class Collection >
void foo( Collection const& c )
{
    constexpr Size n = std::extent< decltype( c ) >::value;
    // Use c here
}

...但是當Collection不是原始數組時,這會失敗。

要處理可以是非數組的集合,需要n_items函數的可重載性,但是對於編譯時,需要編譯時間表示數組大小。 And the classic C++03 solution, which works fine also in C++11 and C++14, is to let the function report its result not as a value but via its function result type . For example like this:

// Example 4 - OK (not ideal, but portable and safe)

#include <array>
#include <stddef.h>

using Size = ptrdiff_t;

template< Size n >
struct Size_carrier
{
    char sizer[n];
};

template< class Type, Size n >
auto static_n_items( Type (&)[n] )
    -> Size_carrier<n>;
// No implementation, is used only at compile time.

template< class Type, size_t n >        // size_t for g++
auto static_n_items( std::array<Type, n> const& )
    -> Size_carrier<n>;
// No implementation, is used only at compile time.

#define STATIC_N_ITEMS( c ) \
    static_cast<Size>( sizeof( static_n_items( c ).sizer ) )

template< class Collection >
void foo( Collection const& c )
{
    constexpr Size n = STATIC_N_ITEMS( c );
    // Use c here
    (void) c;
}

auto main() -> int
{
    int x[42];
    std::array<int, 43> y;
    foo( x );
    foo( y );
}

About the choice of return type for static_n_items : this code doesn't use std::integral_constant because with std::integral_constant the result is represented directly as a constexpr value, reintroducing the original problem. Instead of a Size_carrier class one can let the function directly return a reference to an array. However, not everybody is familiar with that syntax.

About the naming: part of this solution to the constexpr -invalid-due-to-reference problem is to make the choice of compile time constant explicit.

Hopefully the oops-there-was-a-reference-involved-in-your- constexpr issue will be fixed with C++17, but until then a macro like the STATIC_N_ITEMS above yields portability, eg to the clang and Visual C++ compilers, retaining type safety.

Related: macros do not respect scopes, so to avoid name collisions it can be a good idea to use a name prefix, eg MYLIB_STATIC_N_ITEMS .


分配

沒有特別的原因,數組不能被分配給另一個。 改為使用std::copy

#include <algorithm>

// ...

int a[8] = {2, 3, 5, 7, 11, 13, 17, 19};
int b[8];
std::copy(a + 0, a + 8, b);

這比真正的數組賦值可以提供更多的靈活性,因為可以將更大的數組的切片複製到更小的數組中。 std::copy通常專用於原始類型以提供最佳性能。 std::memcpy不太可能執行得更好。 如果有疑問,請測量。

雖然不能直接分配數組,但可以分配包含數組成員的結構和類。 這是因為數組成員是由編譯器提供的賦值運算符以成員方式複制的。 如果您為自己的結構或類類型手動定義賦值運算符,則必須回退到數組成員的手動複製。

參數傳遞

數組不能通過值傳遞。 您可以通過指針或引用來傳遞它們。

通過指針傳遞

由於數組本身不能通過值傳遞,通常指向第一個元素的指針是通過值傳遞的。 這通常被稱為“通過指針”。 由於數組的大小不能通過該指針獲取,因此必須傳遞指示數組大小的第二個參數(傳統C解決方案)或指向數組最後一個元素(C ++迭代器解決方案)之後的第二個指針, :

#include <numeric>
#include <cstddef>

int sum(const int* p, std::size_t n)
{
    return std::accumulate(p, p + n, 0);
}

int sum(const int* p, const int* q)
{
    return std::accumulate(p, q, 0);
}

作為一種語法選擇,您也可以將參數聲明為T p[] ,並且它僅在參數列表的上下文中表示與T* p完全相同的內容:

int sum(const int p[], std::size_t n)
{
    return std::accumulate(p, p + n, 0);
}

您可以將編譯器視為僅在參數列表的上下文中將T p[]重寫為T *p 。 這個特殊規則部分是對數組和指針的混淆造成部分責任。 在任何其他環境中,將某些東西聲明為數組或指針會產生巨大的差異。

不幸的是,你也可以在一個數組參數中提供一個大小,這個參數被編譯器默默地忽略。 也就是說,以下三個簽名完全等效,如編譯器錯誤所示:

int sum(const int* p, std::size_t n)

// error: redefinition of 'int sum(const int*, size_t)'
int sum(const int p[], std::size_t n)

// error: redefinition of 'int sum(const int*, size_t)'
int sum(const int p[8], std::size_t n)   // the 8 has no meaning here

通過參考傳遞

數組也可以通過引用傳遞:

int sum(const int (&a)[8])
{
    return std::accumulate(a + 0, a + 8, 0);
}

在這種情況下,數組大小很重要。 由於編寫一個只接受8個元素數組的函數幾乎沒有用處,所以程序員通常會編寫像模板這樣的函數:

template <std::size_t n>
int sum(const int (&a)[n])
{
    return std::accumulate(a + 0, a + n, 0);
}

請注意,您只能使用實際的整數數組調用這樣的函數模板,而不能使用指向整數的指針。 數組的大小是自動推斷的,並且對於每個大小n ,從模板實例化不同的函數。 你也可以編寫非常有用的函數模板,從元素類型和大小中抽像出來。


類型級別的數組

數組類型表示為T[n] ,其中T元素類型n是正數大小 ,即數組中元素的數量。 數組類型是元素類型和大小的產品類型。 如果其中一種或兩種成分不同,您會得到一種獨特的類型:

#include <type_traits>

static_assert(!std::is_same<int[8], float[8]>::value, "distinct element type");
static_assert(!std::is_same<int[8],   int[9]>::value, "distinct size");

請注意,大小是類型的一部分,也就是說,不同大小的數組類型是不兼容的類型,它們完全無關。 sizeof(T[n])等於n * sizeof(T)

陣列與指針衰減

T[n]T[m]之間唯一的“連接”是兩種類型都可以隱式轉換T* ,並且此轉換的結果是指向數組的第一個元素的指針。 也就是說,在任何需要T*地方,你可以提供一個T[n] ,編譯器會默默地提供這個指針:

                  +---+---+---+---+---+---+---+---+
the_actual_array: |   |   |   |   |   |   |   |   |   int[8]
                  +---+---+---+---+---+---+---+---+
                    ^
                    |
                    |
                    |
                    |  pointer_to_the_first_element   int*

這種轉換被稱為“數組到指針的衰減”,它是混淆的主要來源。 在這個過程中數組的大小會丟失,因為它不再是類型的一部分( T* )。 Pro:在類型級別上忘記數組的大小允許一個指針指向任何大小數組的第一個元素。 Con:給出一個指向數組第一個(或任何其他)元素的指針,沒有辦法檢測到該數組的大小或指針指向的位置是否與數組邊界相關。 指針非常愚蠢

數組不是指針

只要編譯器被認為是有用的,編譯器就會默默地生成一個指向數組第一個元素的指針,也就是說,只要一個操作在一個數組上失敗,但在一個指針上成功。 從數組到指針的這種轉換是微不足道的,因為生成的指針只是數組的地址。 請注意,指針不是作為數組本身的一部分(或內存中的其他任何位置)存儲的。 數組不是指針。

static_assert(!std::is_same<int[8], int*>::value, "an array is not a pointer");

一個數組不會衰變為指向其第一個元素的重要上下文是當&運算符應用於它時。 在這種情況下, &運算符產生一個指向整個數組的指針,而不僅僅是指向其第一個元素的指針。 雖然在這種情況下 (地址)是相同的,但是指向數組的第一個元素的指針和指向整個數組的指針是完全不同的類型:

static_assert(!std::is_same<int*, int(*)[8]>::value, "distinct element type");

下面的ASCII藝術解釋了這種區別:

      +-----------------------------------+
      | +---+---+---+---+---+---+---+---+ |
+---> | |   |   |   |   |   |   |   |   | | int[8]
|     | +---+---+---+---+---+---+---+---+ |
|     +---^-------------------------------+
|         |
|         |
|         |
|         |  pointer_to_the_first_element   int*
|
|  pointer_to_the_entire_array              int(*)[8]

注意指向第一個元素的指針只指向一個整數(描述為一個小方塊),而指向整個數組的指針指向一個由8個整數組成的數組(描述為一個大方塊)。

同樣的情況出現在課堂上,可能更為明顯。 指向對象的指針和指向其第一個數據成員的指針具有相同的 (相同的地址),但它們是完全不同的類型。

如果您不熟悉C語言的語法, int(*)[8]類型的括號是必不可少的:

  • int(*)[8]是一個指向8個整數數組的指針。
  • int*[8]是一個8個指針的數組,每個int*類型的元素。

訪問元素

C ++提供了兩種語法變體來訪問數組中的各個元素。 他們都沒有優於對方,你應該熟悉這兩者。

指針算術

給定一個指向數組第一個元素的指針p ,表達式p+i產生一個指向數組的第i個元素的指針。 之後通過取消引用該指針,可以訪問各個元素:

std::cout << *(x+3) << ", " << *(x+7) << std::endl;

如果x表示一個數組 ,那麼數組到指針的衰減將會啟動,因為添加一個數組和一個整數是沒有意義的(對數組沒有加操作),但是添加一個指針和一個整數是有意義的:

   +---+---+---+---+---+---+---+---+
x: |   |   |   |   |   |   |   |   |   int[8]
   +---+---+---+---+---+---+---+---+
     ^           ^               ^
     |           |               |
     |           |               |
     |           |               |
x+0  |      x+3  |          x+7  |     int*

(請注意,隱式生成的指針沒有名稱,所以我寫了x+0以標識它。)

另一方面,如果x表示指向數組的第一個(或任何其他)元素的指針,則數組到指針的衰減是不必要的,因為i要添加的指針已經存在:

   +---+---+---+---+---+---+---+---+
   |   |   |   |   |   |   |   |   |   int[8]
   +---+---+---+---+---+---+---+---+
     ^           ^               ^
     |           |               |
     |           |               |
   +-|-+         |               |
x: | | |    x+3  |          x+7  |     int*
   +---+

請注意,在所描述的情況下, x是一個指針變量 (可通過x旁邊的小方塊辨別),但它也可以是返回指針(或任何其他T*類型表達式)的函數的結果。

索引操作符

由於語法*(x+i)有點笨拙,因此C ++提供了另一種語法x[i]

std::cout << x[3] << ", " << x[7] << std::endl;

由於加法是可交換的,所以下面的代碼完全一樣:

std::cout << 3[x] << ", " << 7[x] << std::endl;

索引操作符的定義導致以下有趣的等價性:

&x[i]  ==  &*(x+i)  ==  x+i

但是, &x[0]通常等於x 。 前者是一個指針,後者是一個數組。 只有當上下文觸發數組到指針的衰減時, x&x[0]可以互換使用。 例如:

T* p = &array[0];  // rewritten as &*(array+0), decay happens due to the addition
T* q = array;      // decay happens due to the assignment

在第一行中,編譯器檢測到指向指針的指針,該指針平凡成功。 在第二行,它檢測從數組到指針的分配。 由於這是沒有意義的(但是指向指針賦值的指針是有道理的),像往常一樣,數組到指針的衰減開始了。

範圍

一個T[n]類型的數組有n元素,索引從0n-1 ; 沒有元素n 。 然而,為了支持半開範圍(其中開始是包含性的並且末尾是排他性的 ),C ++允許計算指向(不存在的)第n個元素的指針,但是取消引用該指針是非法的:

   +---+---+---+---+---+---+---+---+....
x: |   |   |   |   |   |   |   |   |   .   int[8]
   +---+---+---+---+---+---+---+---+....
     ^                               ^
     |                               |
     |                               |
     |                               |
x+0  |                          x+8  |     int*

例如,如果你想對一個數組進行排序,以下兩種方法同樣適用:

std::sort(x + 0, x + n);
std::sort(&x[0], &x[0] + n);

請注意,提供&x[n]作為第二個參數是非法的,因為這等價於&*(x+n) ,並且子表達式*(x+n)技術上會在C ++中調用未定義的行為 (但不在C99中)。

還要注意,你可以簡單地提供x作為第一個參數。 這對我來說有點太簡單了,它也會使編譯器對模板參數的推導有點困難,因為在這種情況下,第一個參數是一個數組,但第二個參數是一個指針。 (再一次,數組到指針的衰減開始了。)


程序員經常會將多維數組與指針數組混淆。

多維數組

大多數程序員都熟悉命名的多維數組,但很多人並不知道多維數組也可以匿名創建。 多維數組通常被稱為“數組陣列”或“ 真正的多維數組”。

命名為多維數組

使用命名的多維數組時,必須在編譯時知道所有維度:

int H = read_int();
int W = read_int();

int connect_four[6][7];   // okay

int connect_four[H][7];   // ISO C++ forbids variable length array
int connect_four[6][W];   // ISO C++ forbids variable length array
int connect_four[H][W];   // ISO C++ forbids variable length array

這就是命名多維數組在內存中的外觀:

              +---+---+---+---+---+---+---+
connect_four: |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+

請注意,諸如上述的2D網格僅僅是有用的可視化。 從C ++的角度來看,內存是一個“扁平”字節序列。 多維數組的元素按行優先順序存儲。 也就是說, connect_four[0][6]connect_four[1][0]是內存中的鄰居。 實際上, connect_four[0][7]connect_four[1][0]表示相同的元素! 這意味著您可以採用多維數組並將它們視為大型的一維數組:

int* p = &connect_four[0][0];
int* q = p + 42;
some_int_sequence_algorithm(p, q);

匿名多維數組

使用匿名多維數組, 編譯時必須知道除第一個外的所有維度:

int (*p)[7] = new int[6][7];   // okay
int (*p)[7] = new int[H][7];   // okay

int (*p)[W] = new int[6][W];   // ISO C++ forbids variable length array
int (*p)[W] = new int[H][W];   // ISO C++ forbids variable length array

這就是匿名多維數組在內存中的樣子:

              +---+---+---+---+---+---+---+
        +---> |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |
      +-|-+
   p: | | |
      +---+

請注意,數組本身仍被分配為內存中的單個塊。

指針陣列

您可以通過引入另一個間接級別來克服固定寬度的限制。

命名的指針數組

這是一個由五個指針組成的命名數組,它們使用不同長度的匿名數組進行初始化:

int* triangle[5];
for (int i = 0; i < 5; ++i)
{
    triangle[i] = new int[5 - i];
}

// ...

for (int i = 0; i < 5; ++i)
{
    delete[] triangle[i];
}

下面是它在內存中的外觀:

          +---+---+---+---+---+
          |   |   |   |   |   |
          +---+---+---+---+---+
            ^
            | +---+---+---+---+
            | |   |   |   |   |
            | +---+---+---+---+
            |   ^
            |   | +---+---+---+
            |   | |   |   |   |
            |   | +---+---+---+
            |   |   ^
            |   |   | +---+---+
            |   |   | |   |   |
            |   |   | +---+---+
            |   |   |   ^
            |   |   |   | +---+
            |   |   |   | |   |
            |   |   |   | +---+
            |   |   |   |   ^
            |   |   |   |   |
            |   |   |   |   |
          +-|-+-|-+-|-+-|-+-|-+
triangle: | | | | | | | | | | |
          +---+---+---+---+---+

由於每條線現在都是單獨分配的,因此將二維數組視為一維數組不再適用。

匿名的指針數組

這是一個匿名數組(或任何其他數量的)指針,它們使用不同長度的匿名數組進行初始化:

int n = calculate_five();   // or any other number
int** p = new int*[n];
for (int i = 0; i < n; ++i)
{
    p[i] = new int[n - i];
}

// ...

for (int i = 0; i < n; ++i)
{
    delete[] p[i];
}
delete[] p;   // note the extra delete[] !

下面是它在內存中的外觀:

          +---+---+---+---+---+
          |   |   |   |   |   |
          +---+---+---+---+---+
            ^
            | +---+---+---+---+
            | |   |   |   |   |
            | +---+---+---+---+
            |   ^
            |   | +---+---+---+
            |   | |   |   |   |
            |   | +---+---+---+
            |   |   ^
            |   |   | +---+---+
            |   |   | |   |   |
            |   |   | +---+---+
            |   |   |   ^
            |   |   |   | +---+
            |   |   |   | |   |
            |   |   |   | +---+
            |   |   |   |   ^
            |   |   |   |   |
            |   |   |   |   |
          +-|-+-|-+-|-+-|-+-|-+
          | | | | | | | | | | |
          +---+---+---+---+---+
            ^
            |
            |
          +-|-+
       p: | | |
          +---+

轉換

數組到指針的衰減自然延伸到數組和指針數組的數組:

int array_of_arrays[6][7];
int (*pointer_to_array)[7] = array_of_arrays;

int* array_of_pointers[6];
int** pointer_to_pointer = array_of_pointers;

但是,並沒有從T[h][w]T**隱式轉換。 如果存在這種隱式轉換,則結果將是指向T指針數組的第一個元素的指針(每個指針指向原始二維數組中的一行的第一個元素),但該指針數組不存在內存中的任何地方呢。 如果您想要這樣的轉換,您必須手動創建並填充所需的指針數組:

int connect_four[6][7];

int** p = new int*[6];
for (int i = 0; i < 6; ++i)
{
    p[i] = connect_four[i];
}

// ...

delete[] p;

請注意,這會生成原始多維數組的視圖。 如果您需要一個副本,您必須創建額外的數組並自己複製數據:

int connect_four[6][7];

int** p = new int*[6];
for (int i = 0; i < 6; ++i)
{
    p[i] = new int[7];
    std::copy(connect_four[i], connect_four[i + 1], p[i]);
}

// ...

for (int i = 0; i < 6; ++i)
{
    delete[] p[i];
}
delete[] p;






c++-faq