なぜ配列の次元はその型の一部なのですか?


14

C ++入門書を読んでいるときに、「配列内の要素の数は配列の型の一部です。」という文に出くわしました。だから私は次のコードを使用して調べたいと思いました:

#include<iostream>

int main()
{
    char Array1[]{'H', 'e', 'l', 'p'};
    char Array2[]{'P', 'l', 'e', 'a', 's', 'e'};

    std::cout<<typeid(Array1).name()<<std::endl;        //prints  A4_c
    std::cout<<typeid(Array2).name()<<std::endl;        //prints  A6_c

    return 0;
}

興味深いことに、2つの配列のtypeidの結果は、なんらかの違いがあることを示しています。

  • 舞台裏で何が起こっているのですか?
  • 配列のサイズを含む型を持つ必要があるのはなぜですか?サイズを変えてはいけないというだけのことですか?
  • これは配列の比較にどのように影響しますか?

コンセプトを深く理解したいだけ。


3
それは厳密ではありません必要なタイプでサイズ情報が含まれるように、それは便利です
byxor

配列に関するチュートリアルで説明します(1)。配列を比較する組み込みの方法がないため、(3)の意味がわかりません。
HolyBlackCat

回答:


20

舞台裏で何が起こっているのですか?

動的に割り当てられないものは、定義上、同種の要素の固定サイズのコンテナーです。Nタイプの要素のT配列はN、タイプのオブジェクトの連続したシーケンスとしてメモリに配置されますT


配列のサイズを含む型が配列に必要なのはなぜですか?

配列の型にそのサイズを含めることが「必要」であるとは思いません。実際には、ポインターを使用してTオブジェクトの連続したシーケンスを参照できます。このようなポインタは、配列に関するサイズ情報を失います。

ただし、これは便利です。型の安全性が向上し、コンパイル時に有用な情報がエンコードされ、複数の方法で使用できます。例として、配列への参照を使用して、異なるサイズの配列にオーバーロードできます

void foo(int(&array)[4]) { /* ... */ }
void foo(int(&array)[8]) { /* ... */ }

または、定数式として配列のサイズを把握する

template <typename T, std::size_t N>
constexpr auto sizeOf(const T(&array)[N]) { return N; }

これは配列の比較にどのように影響しますか?

そうではありません。

2つの数値(intオブジェクトなど)を比較するのと同じ方法でCスタイルの配列を比較することはできません。ある種の辞書式比較を記述し、さまざまなサイズのコレクションに対してそれが何を意味するかを決定する必要があります。std::vector<T>が提供し、同じロジックを配列に適用できます。


おまけ: C ++ 11以降はを提供しますstd::array。これは、コンテナのようなインターフェイスを持つCスタイルの配列のラッパーです。他のコンテナ(などstd::vector<T>)との整合性が高く、すぐに使える辞書式比較もサポートしているため、Cスタイルの配列よりも推奨されます。


2
「ある種の辞書式比較を記述し、異なるサイズのコレクションに対してそれが何を意味するかを決定する必要があります。」あなたはstd::equal(配列のために定義されている経由std::beginで)使用することができstd::endます。その場合、異なるサイズの配列は等しくありません。
2019年

3
範囲が配列である範囲ベースのforループは、コンパイル時に配列のサイズを読み取る必要があることに注意する価値があるかもしれません-(ありがたいことに!)しかし、それはサイズに基づいたオーバーロードよりもはるかに多く現れるようです。
ミロブラント

8

作成時にオブジェクトに割り当てられるスペースの量は、そのタイプに完全に依存します。私が話している割り当ては、newまたはからの割り当てではありませんmallocが、コンストラクタを実行してオブジェクトを初期化できるように割り当てられている領域です。

(たとえば)として定義された構造体がある場合

struct A { char a, b; }; //sizeof(A) == 2, ie an A needs 2 bytes of space

次に、オブジェクトを作成するとき:

A a{'a', 'b'};

オブジェクトを構築するプロセスは、プロセスと考えることができます。

  • 2バイトのスペースを割り当てます(スタック上ですが、この例では重要ではありません)。
  • オブジェクトのコンストラクターを実行します(この場合はオブジェクトにコピー'a''b'て)

必要な2バイトのスペースは、オブジェクトのタイプによって完全に決定されることに注意してください。関数への引数は関係ありません。したがって、配列のプロセスは同じですが、必要なスペースの量は配列内の要素の数によって異なります。

char a[] = {'a'}; //need space for 1 element
char b[] = {'a', 'b', 'c', 'd', 'e'}; //need space for 5 elements

したがって、aおよびのタイプは、1文字に十分なスペースが必要であり、5文字に十分なスペースba必要であるという事実を反映している必要がありbます。つまり、これらの配列のサイズは突然変更できません。5要素の配列が作成されると、常に5要素の配列になります。サイズが変化する可能性のある「配列」のようなオブジェクトを作成するには、動的メモリ割り当てが必要です。これは、本をある時点でカバーする必要があります。


0

これは、ランタイムライブラリの内部的な理由によるものです。たとえば、次のステートメントを検討するとします。

unsigned int i;
unsigned int *iPtr;
unsigned int *iPtrArr[2];
unsigned int **iPtrHandle;

次に、何が問題なのかが明らかになります。たとえば、のアドレス指定はunsigned int *sizeof operatorまたはのアドレス指定に関係する必要がありunsigned intます。

ここに表示される残りの部分についてはより詳細な説明がありますが、これは主に、宣言された型のプレーン言語テキストを印刷するプログラムに関する、CernighanとRitchieによるCプログラミング言語、第2版の内容の要約です。ストリング。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.