c++ - 変数 - c言語 配列 要素数 省略




C++で配列を使うには? (4)

5.配列を使用するときの一般的な落とし穴。

5.1落とし穴:タイプセーフなリンクを信頼する。

確かに、翻訳単位の外にアクセスできる名前空間のスコープ変数であるグローバルがEvil™であると言われましたか、自分で見つけました。 しかし、本当にEvil™がどれほど真実か知っていましたか? 次の2つのファイル[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の両方とうまくリンクします。

型が一致しないので、プログラムを実行するとプログラムがクラッシュします。

正式な説明:このプログラムにはUndefined Behavior(UB)があり、クラッシュするのではなく、ハングアップするだけで何もしないか、アメリカ、ロシア、インドなどの大統領に電子メールを送ることができます。中国、スイス、鼻のデーモンがあなたの鼻から飛び出します。

実際の説明: main.cppでは、配列は配列と同じアドレスに置かれたポインタとして扱われます。 32ビット実行可能ファイルの場合、配列の最初のint値はポインタとして扱われます。 つまり、変数main.cpp(int*)1が含まれているか、含まれているように見えます。 これにより、プログラムはアドレス空間の最下部でメモリにアクセスします。これは、通常は予約されトラップを引き起こします。 結果:クラッシュします。

コンパイラは、C ++ 11§3.5/ 10では、宣言のための互換性のある型の要件について、このエラーを診断できないという権利があります。

[N3290§3.5/ 10]
型識別に関するこの規則の違反は、診断を必要としない。

同じ段落には、許可されるバリエーションが詳述されています。

...配列オブジェクトの宣言では、大規模配列の境界(8.3.4)の有無によって異なる配列型を指定できます。

この許容されるバリエーションには、1つの翻訳単位に配列として名前を宣言すること、および別の翻訳単位にポインタとして宣言することは含まれません。

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へのポインタを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) sizeof(*array)と等価です。これは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))、file runtime_detect ion.cpp、line 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:\カウント> _

どのように動作する:配列は、 n_itemsへの参照によって渡されるので、最初の要素へのポインタには減衰せず、関数はその型によって指定された要素の数だけを返すことができます。

C ++ 11では、これをローカルタイプの配列にも使用できます。また、配列の要素数を見つけるための型セーフなC ++イディオムです。

5.4 C ++ 11およびC ++ 14 constexprconstexpr配列サイズ関数の使用。

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の使用で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バージョンを使って考えてみ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でコンパイルされ、 gcc.godbolt.org/でオンラインコンパイラでテストされ、clang 3.0とclang 3.2でもテストされますが、clangではテストされません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 9thダッシュ

条件式 eは、抽象マシン(1.9)のルールに従ったeの評価が次の式の1つを評価しない限り、 コア定数式です。
Â

  • 参照が前の初期化を有していなければ、参照型の変数またはデータメンバを参照するid式。
    • それは定数式で初期化されます。
    • これは、eの評価内で寿命が始まったオブジェクトの非静的データメンバーです。

1つは常により冗長に書くことができます

// 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関数のオーバーロードが必要ですが、コンパイル時には配列サイズのコンパイル時表現が必要です。 また、C ++ 11とC ++ 14でもうまく機能する古典的なC ++ 03ソリュ​​ーションは、関数の結果を値としてではなく関数の結果のによって報告させることです 。 たとえば、次のようになります。

// 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 );
}

戻り値の型の選択についてstatic_n_items:結果が直接値として表され、元の問題が再導入されるstd::integral_constantため、このコードは使用しません。クラスの代わりに、関数が配列への参照を直接返すようにすることができます。しかし、誰もがその構文に精通しているわけではありません。std::integral_constantconstexprSize_carrier

命名について:このconstexpr無効解法の問題の解決策の一部は、コンパイル時定数を明示的に選択することです。

うまくいけば、あなたのconstexpr問題に関連するoops-there-was-a-a-reference-related- issueはC ++ 17で修正されるだろうが、それまではSTATIC_N_ITEMS上記のようなマクロがclangやVisual C ++コンパイラ、安全性。

関連:マクロはスコープを尊重しないので、名前の衝突を避けるために名前接頭辞を使用することをお勧めしますMYLIB_STATIC_N_ITEMS

C ++はCから継承した配列で、事実上どこでも使用されています。 C ++は使いやすく、エラーが起こりにくい抽象化を提供します(C ++ 98以降のstd::vector<T> std::array<T, n>C++11以降のstd::array<T, n> )。しかし、Cで書かれたライブラリとやり取りするときは、配列の仕組みをしっかりと把握しておく必要があります。

このFAQは5つの部分に分かれています:

  1. 型レベルの配列と要素へのアクセス
  2. 配列の作成と初期化
  3. 代入とパラメータ渡し
  4. 多次元配列とポインタ配列
  5. 配列使用時の一般的な落とし穴

このFAQに重要なものがないと感じたら、答えを書いてここにリンクしてください。

次のテキストでは、「配列」はクラステンプレートstd::arrayではなく「C配列」を意味します。 C宣言子構文の基本知識が想定されます。 以下に示すようにnewdeleteを手動で使用deleteことは、例外にもかかわらず非常に危険ですが、それは別のFAQのトピックです。

(注:これはStack OverflowのC ++ FAQへのエントリであるため、このフォームでFAQを提供するという考えを批判したい場合は、これをすべて開始したメタに対する投稿がそれを行う場所になります。その質問はC ++のチャットルームで監視されています 。ここでFAQのアイディアが最初に始まったので、あなたの答えはアイデアを思いついた人に読まれる可能性が非常に高いです。)


割り当て

特別な理由がない限り、配列を互いに割り当てることはできません。 代わりに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 ++イテレータの解)を示す2番目のパラメータを渡す必要があります。 :

#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 書き換えると考えることができます。 この特別なルールは、配列とポインタについての混乱の原因の一部になっています。 他のすべてのコンテキストでは、何かを配列またはポインタとして宣言することは大きな違いをもたらします。

残念ながら、コンパイラによって暗黙的に無視される配列パラメータでサイズを指定することもできます。 つまり、以下の3つのシグネチャは、コンパイラエラーによって示されるとおり、まったく同じです。

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に対して、異なる関数がテンプレートからインスタンス化されます。 要素の型とサイズの両方から抽象化した非常に便利な関数テンプレートを書くこともできます。


配列の作成と初期化

他の種類のC ++オブジェクトと同様に、配列は名前付き変数に直接格納できます(サイズはコンパイル時定数でなければなりません.C ++ではVLAをサポートしません )。または、ヒープに匿名で格納し、ポインタ(実行時にサイズが計算できるのはその時だけです)。

自動配列

コントロールの流れが静的でないローカル配列変数の定義を通過するたびに、自動配列( "スタック上にある"配列)が作成されます。

void foo()
{
    int automatic_array[8];
}

初期化は昇順に行われます。 初期値は要素タイプT依存することに注意してください。

  • TPOD場合(上記の例のintと同様)、初期化は行われません。
  • それ以外の場合、 Tデフォルトコンストラクタはすべての要素を初期化します。
  • Tがアクセス可能なデフォルトコンストラクタを提供しない場合、プログラムはコンパイルされません。

あるいは、初期値は、 配列初期化子で明示的に指定することができます。これは、中括弧で囲まれたコンマ区切りのリストです。

    int primes[8] = {2, 3, 5, 7, 11, 13, 17, 19};

この場合、配列イニシャライザ内の要素の数は配列のサイズと等しいため、手動でサイズを指定することは冗長です。 コンパイラによって自動的に推論されます:

    int primes[] = {2, 3, 5, 7, 11, 13, 17, 19};   // size 8 is deduced

サイズを指定し、より短い配列初期化子を提供することも可能です:

    int fibonacci[50] = {0, 1, 1};   // 47 trailing zeros are deduced

その場合、残りの要素はzero-initialized 。 C ++では空の配列イニシャライザ(すべての要素がゼロで初期化されます)を使用できますが、C89では(少なくとも1つの値が必要です)ことはできません。 また、配列初期化子は配列の初期化にのみ使用できます。 後で譲渡に使用することはできません。

静的配列

静的配列(データセグメント内に存在する配列)は、 staticキーワードと配列変数で名前空間のスコープ( "グローバル変数")で定義されたローカル配列変数です。

int global_static_array[8];

void foo()
{
    static int local_static_array[8];
}

(名前空間スコープの変数は暗黙的に静的であることに注意してください。 staticキーワードを定義に追加することは、 まったく異なる、非推奨の意味です)。

静的配列が自動配列とは異なる動作をする方法は次のとおりです。

  • 配列イニシャライザのない静的配列は、さらに初期化される前にゼロ初期化されます。
  • スタティックPODアレイは正確に1回初期化され、 通常は実行時に初期値がベイクされます。この場合、実行時に初期化コストは発生しません。 ただし、これはスペース効率の高いソリューションではありませんが、標準では必要ありません。
  • 静的な非POD配列は、制御フローが定義を通過する最初に初期化されます。 ローカル静的配列の場合、関数が呼び出されないと、決して起こらないことがあります。

(上記のどれも配列に固有のものではありません。これらの規則は、他の種類の静的オブジェクトにも同様に適用されます)。

配列データメンバー

配列データメンバーは、所有オブジェクトの作成時に作成されます。 残念なことに、C ++ 03はメンバ初期化子リストの配列を初期化する手段を提供していないので、初期化は割り当てで偽装する必要があります:

class Foo
{
    int primes[8];

public:

    Foo()
    {
        primes[0] = 2;
        primes[1] = 3;
        primes[2] = 5;
        // ...
    }
};

あるいは、コンストラクタ本体に自動配列を定義し、その要素をコピーすることもできます:

class Foo
{
    int primes[8];

public:

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

C ++ 0xでは、 均一な初期化のおかげで、メンバ初期化子リストで配列初期化でき ます

class Foo
{
    int primes[8];

public:

    Foo() : primes { 2, 3, 5, 7, 11, 13, 17, 19 }
    {
    }
};

これは、デフォルトコンストラクタを持たない要素型で動作する唯一のソリューションです。

ダイナミックアレイ

動的配列には名前がないため、ポインタにアクセスする方法は唯一の方法です。 彼らは名前がないので、私は今から "無名配列"と呼んでいます。

Cでは、 mallocと友人を介して無名配列が作成されます。 C ++では、匿名配列の最初の要素へのポインタを返すnew T[size]構文を使用して匿名配列が作成されます。

std::size_t size = compute_size_at_runtime();
int* p = new int[size];

次のASCIIアートは、サイズが実行時に8として計算された場合のメモリレイアウトを示しています。

             +---+---+---+---+---+---+---+---+
(anonymous)  |   |   |   |   |   |   |   |   |
             +---+---+---+---+---+---+---+---+
               ^
               |
               |
             +-|-+
          p: | | |                               int*
             +---+

明らかに、匿名配列は、別々に格納されなければならない余分なポインタのために、名前付き配列よりも多くのメモリを必要とします。 (フリーストアにはさらにオーバーヘッドがあります。)

ここではアレイからポインタへの減衰は起こっていないことに注意してください。 new int[size]評価すると、実際には整数の配列が作成されますが、 new int[size]式の結果はすでに整数の配列ではなく単一の整数へのポインタではなく、未知のサイズの整数の配列 静的型システムでは、配列のサイズがコンパイル時定数である必要があるため、これは不可能です。 (したがって、私は画像内に静的な型情報を持つ無名配列に注釈を付けませんでした。)

要素の既定値については、匿名配列は自動配列と同様に動作します。 通常、匿名POD配列は初期化されませんが、値の初期化をトリガーする特殊な構文があります。

int* p = new int[some_computed_size]();

(セミコロンの直前の括弧の末尾のペアに注意してください。)また、C ++ 0xは規則を単純化し、一様な初期化のおかげで匿名配列の初期値を指定することができます。

int* p = new int[8] { 2, 3, 5, 7, 11, 13, 17, 19 };

匿名配列を使用し終えたら、システムに戻す必要があります:

delete[] p;

各無名配列を正確に1回リリースし、その後は決してそれに触れないでください。 それをすべて解放しないと、メモリリークが発生します(またはより一般的には、要素タイプ、リソースリークによって異なります)。複数回リリースすると、未定義の動作が発生します。 非配列形式をdeletedelete[]代わりにdelete (またはfree )をdelete[]て配列を解放することも、 未定義の動作です。


プログラマーは、多次元配列をポインタの配列と混同することがよくあります。

多次元配列

ほとんどのプログラマーは、名前付き多次元配列に精通していますが、多次元配列も匿名で作成できるという事実を知らない人が多くいます。 多次元配列は、しばしば「配列の配列」または「 真の多次元配列」と呼ばれます。

名前付き多次元配列

名前付き多次元配列を使用する場合、コンパイル時にすべての次元を知っている必要があります。

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]は同じ要素を示します! つまり、多次元配列を取って、それを大規模な1次元配列として扱うことができます:

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: | | |
      +---+

配列自体はメモリ内の単一のブロックとして割り当てられていることに注意してください。

ポインターの配列

別のレベルのインダイレクションを導入することで、固定幅の制限を克服することができます。

ポインタの名前付き配列

以下は、異なる長さの無名配列で初期化された5つのポインタの名前付き配列です:

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: | | | | | | | | | | |
          +---+---+---+---+---+

各ラインは今では個別に割り当てられるので、2Dアレイを1Dアレイとして見ることはもはや機能しません。

ポインタの無名配列

以下は、異なる長さの無名配列で初期化された5つの(または他の数の)ポインタの無名配列です:

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へのhポインタの配列の最初の要素へのポインタになりますが(それぞれ元の2D配列の最初の要素を指しています)、そのポインタ配列は存在しませんまだメモリ内のどこにでも。 このような変換が必要な場合は、必要なポインタ配列を手動で作成して入力する必要があります。

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