配列 | Programming Place Plus 新C++編

トップページ新C++編

このページの概要 🔗

このページでは、純粋な意味での配列(生の配列)を取り上げます。これまでは std::vector や std::string を配列と呼んで使ってきました。こうした方法は安全であり、便利な機能の恩恵を受けられるので、原則としてはこれが適切な選択ですが、効率を求める場合や、C言語のプログラムとの連携を図る場合などには、生の配列を使う必要があります。

このページの解説は C++14 をベースとしています

以下は目次です。要点だけをさっと確認したい方は、「まとめ」をご覧ください。



配列 🔗

配列 (array) は、同じ型のオブジェクトを並べたデータ構造で、前のページで説明したように、メモリ上では隙間なく連続的に並ぶという性質を持ちます。

ここまでのページでは、std::vector や std::string を配列として使ってきましたが、これらは配列そのものというよりは、配列を取り扱いやすくしたクラスです。つまりデータメンバとして配列を保持するという仕組みになっています。このページで取り上げるのは、純粋な意味での配列です。std::vector などと区別を付けるために、「生の配列」のように表現されることがありますが、紛らわしくなってしまう場面を除いて、基本的に「配列」と呼ぶことにします。

配列は、要素そのもののためのメモリしか使わないうえに、隙間なく並ぶ保証があるため、メモリを無駄なく利用できます。また、そうした構造は処理効率の面でも優れる傾向があります。反面、std::vector や std::string が備える便利な機能はありませんし、範囲外アクセスへの備えもありません。まず第一には安全であることを重視すべきであって、原則として std::vector や std::string を優先して使うべきです。しかし効率を求めるときなど、あえて生の配列を使うという選択もありえます。

std::vector よりも、より生の配列に近い std::array[1] というクラスも存在します。

【上級】生の配列はC言語から受け継いだ機能なので、std::vector や std::string がないC言語のプログラムとのあいだで連携を図るときにも必要性があります。

配列を定義する 🔗

配列は、これまでと同じ方法で定義や宣言を行ってから使用します。配列でない変数との違いは「要素数」の指定があることです。

型名 配列名[要素数];
型名 配列名[要素数] 初期化子;
型名 配列名[] 初期化子;

「型名」には要素の型を指定します。宣言された配列自体は、配列型 (array types) と呼ばれ、要素数と合わせて int[5] のような感じで表記されることがあります。

「初期化子」に = を含むかどうかは選択できます。

「要素数」は 1以上の整数になる定数式でなければならず[2]、要素数はあとから変更できません。つまり、コンパイル時点で要素数は確定しており、要素の追加や削除は不可能です。

int a1[5] = {4, 6, 2, 5, 3};
int a2[5] {4, 6, 2, 5, 3};
int a3[] = {10, 20, 30};  // 要素数 3
int a4[] {10, 20, 30};  // 要素数 3
int a5[5];  // 注意。要素の初期値は想定通りか?

ストレージ期間に関するルールも同様に存在します(「メモリとオブジェクト」のページを参照)。自動ストレージ期間を持つ場合、初期化子を与えないと、要素が初期化されていません。静的ストレージ期間を持つ場合は、プログラムの実行開始直後にゼロ初期化される保証があります。

【上級】初期化子を与えていない場合、要素がクラス型であれば、それぞれのデフォルトコンストラクタが呼ばれます。

配列は、集成体(「構造体」のページを参照)に分類されるため、「初期化子」には {} を用いた記法を使うことができ、集成体初期化が行えます(「構造体」のページを参照)。つまり、{} の内側に書き並べた初期化子を、要素に順番に適用していきます。「要素数」の指定に対して、初期化子のほうが多い場合はコンパイルエラーになります。初期化子のほうが少ない場合は、残りの要素は値初期化されます。そのため、すべての要素が 0 である配列を次のように定義できます。

int a[10] {};

【C言語プログラマー】C言語では、{} の内側に最低でも1つ初期化子が必要です(初期化子の手前の = も必要)。

「要素数」の指定を省略([] 自体は省略できません)した場合は、「初期化子」の内容から要素数が判断されます。初期化子を与えるのであれば、「要素数」の指定は省略して、コンパイラに判断させたほうが間違いが起こりにくいでしょう。

ほかの配列を使って初期化することはできません。

int a1[] {0, 1, 2};
int a2[3] = a1;  // コンパイルエラー

また、「型名」に auto や参照型を指定することもできません。

auto a1[] = {0, 1, 2};  // コンパイルエラー

int v1 {10};
int v2 {20};
int& a2[] {v1, v2};  // コンパイルエラー

構造体(クラス)のデータメンバとして配列を宣言することも可能です。

struct S {
    int a[10];
    double b;
};

データメンバに初期化子を与える構文も利用できますが、Visual Studio 2015 での事情があるため、新C++編では利用を控えています(「構造体」のページを参照)。

いつものように、未初期化状態は危険なので避けるのが原則ですが、std::vector や std::string を使わずに、あえて生の配列を使うという状況では、効率が重要かもしれません。その場合、初期化にかかるコストを避けるために初期化しないという選択もありえます。入力を受け取るためのバッファのようなものも、あえて初期化せずに使う例として挙げられます。

int data[10000];  // 直後に get_data関数が中身を入れるので、あえて初期化を避ける
get_data(data);

配列の要素へのアクセス 🔗

配列の要素へは、[] で表される添字演算子 (subscripting operator) によってアクセスします。

配列名[添字];

「添字」には、アクセスしたい要素が、先頭を 0 としたときに何番目に当たるものかを指定します。要素数が N の配列に対して、有効な添字は 0~(N-1) の範囲です。この範囲外の要素へのアクセスは未定義の動作です。

#include <iostream>

int main()
{
    int array[] {10, 20, 30};

    array[1] = 99;
    for (int i = 0; i < 3; ++i) {
        std::cout << array[i] << "\n";
    }
}

実行結果:

10
99
30

イテレータ 🔗

配列のすべての要素を順番にアクセスするのなら、範囲for文を使うのが手軽で安全です。

#include <iostream>

int main()
{
    int array[] {10, 20, 30};

    for (int e : array) {
        std::cout << e << "\n";
    }
}

実行結果:

10
20
30

範囲for文が使えるということは、std::begin関数や std::end関数(「イテレータ」のページを参照)を使うこともできるということです。std::cbegin関数や std::cend関数も使えます(「イテレータ」のページを参照)。

#include <iostream>

int main()
{
    int array[] {10, 20, 30};

    for (auto it = std::begin(array); it != std::end(array); ++it) {
        *it += 5;
    }
    for (auto it = std::cbegin(array); it != std::cend(array); ++it) {
        std::cout << *it << "\n";
    }
}

実行結果:

15
25
35

同様に、std::rbegin関数、std::rend関数、std::crbegin関数、std::crend関数もそれぞれ使えます。

+-+=-= を使って要素間を移動できます。範囲外アクセスには注意してください。

#include <iostream>

int main()
{
    int array[] {10, 20, 30, 40, 50};

    auto it = std::begin(array);
    std::cout << *it << "\n";

    it++;
    std::cout << *it << "\n";

    it += 3;
    std::cout << *it << "\n";

    it--;
    std::cout << *it << "\n";
}

実行結果:

10
20
50
40

配列の要素数を取得する 🔗

配列の要素を for文を使って順番にアクセスしたいとき、ループの条件のところに要素数が必要になります。

int array[] {5, 7, 6, 3, 6};

for (int i = 0; i < 5; ++i) {
    std::cout << i << ": " << array[i] << "\n";
}

ここで i < 5 としているのは、array の要素数が 5 だからですが、このような指定の仕方をすると、変更に弱いプログラムになってしまいますし、危険でもあります。後から要素を増やしたら、忘れずに修正しなければいけませんし、かぞえ間違えてしまうかもしれません。そこで、配列の要素数は計算によって求めるようにします。

【C++17】std::size関数が追加され、std::size(array) で要素数が得られるようになりました[3]

配列の要素はすべて同じ型、つまり同じ大きさですし、要素は隙間なく並んでいます。したがって、sizeof(要素の型) * 要素数 == sizeof(配列) が成り立ちます。この式を変形すれば、sizeof(配列) / sizeof(要素の型) == 要素数 を導けます。

auto size = sizeof(array) / sizeof(int);  // array の要素数

これだと int と書いてしまっていますから、あとから配列の型が変わると、修正を忘れる恐れがあります。そこでさらに工夫して、次のようにします。

auto size = sizeof(array) / sizeof(array[0]);  // array の要素数

どの要素も同じ型なので、代表して array[0] を使っておくということです。

この方法が取れるのは、生の配列には要素以外に余計なものがないからです。std::vector や std::string で同じことをすると正しい結果が得られません。sizeメンバ関数や lengthメンバ関数を使いましょう。

これはさらに次のように関数形式マクロ(「プリプロセス」のページを参照)にしておくと、使いまわしが効くようになります。

#define SIZE_OF_ARRAY(array)  (sizeof(array) / sizeof(array[0]))
auto size = SIZE_OF_ARRAY(array);  // array の要素数

【上級】関数テンプレートを使う方法もあります。この方法は C++17 では、std::size関数[3]として標準ライブラリ関数に追加されていますが、C++14 以前でも同じ関数を実装できます。

コンパイルできるプログラムは次のようになります。

#include <iostream>

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

int main()
{
    int array[] {5, 7, 6, 3, 6};

    for (std::size_t i = 0; i < SIZE_OF_ARRAY(array); ++i) {
        std::cout << i << ": " << array[i] << "\n";
    }
}

実行結果:

0: 5
1: 7
2: 6
3: 3
4: 6

文字列 🔗

要素が文字型(char など)の配列は、よく文字列 (string) と呼ばれます。原則として std::string を使うべきといえますが、効率面などの都合で、生の配列を使って表現する選択肢もあります。

文字列は文字型の配列に過ぎず、配列と同じ特性を持ちますし、同じように操作できますが、メモリ上での表現に関して特殊な部分があります。文字列の各文字はメモリ上に隙間なく連続的に並び、その末尾に 0 だけのバイトが1つ置かれます。そのため、"abcd" という文字列は一見すると 4文字のようにみえますが、実際には末尾に 0 が置かれるため、5文字分の領域を必要とします。この末尾の 0 はソースコード上では '\0' のように表現され、終端文字(ヌル終端文字、ナル終端文字) (terminate character) などと呼ばれます。

文字列の変数は、次のようにさまざまな方法で定義できます。

char s1[5] = {'a', 'b', 'c', 'd'};
char s2[5] {'a', 'b', 'c', 'd'};
char s3[] = {'a', 'b', 'c', 'd'};
char s4[] {'a', 'b', 'c', 'd'};
char s5[] = "abcd";
char s6[10] = "abcd";
char s7[5];
char s8[5] {};

s1~s4 は、1要素ずつ別個に初期化子を記述しており、要素が文字型でない場合と同じ構文です。ルールも同じで、「配列を定義する」の項で説明したように、要素数の指定に対して、初期化子のほうが多い場合はコンパイルエラーになります。要素数の指定よりも、{} の内側の初期化子が少ないとき、残りの要素は値初期化されます。char型を値初期化したときの結果は \0 であるため、うまく末尾に終端文字が入った状態になります。以下はすべて同じ結果になります。

char s[5] {'a', 'b', 'c'};
char s[5] {'a', 'b', 'c', '\0'};
char s[5] {'a', 'b', 'c', '\0', '\0'};

以下のように、要素数の指定と、{} の内側の初期化子が同数である場合、終端文字がない不完全な文字列ができてしまうことに注意が必要です。

char s[5] {'a', 'b', 'c', 'd', 'e'};  // エラーではないが、終端文字がない

そもそも1文字ずつ指定する方法は単純に面倒なので、s5 のように、文字列リテラルを初期化子にする方法がよく使われます。文字列リテラル自身が、char型の配列であり、終端文字も自動的に付加されています。そのため、"abcd" は 5文字の文字列であり、s5 の要素数は 5 になります。

【上級】正確には、文字列リテラルは const な char型の配列です。

s6 は要素数も指定しています。要素数の指定が十分に大きい場合は、メモリがその分消費されることになりますが、エラーになることはありません。要素数が小さい場合はコンパイルエラーです[4]

char s[5] = "abcde";  // エラー。終端文字を含めると 6文字ある
char s[10] = "abcde"; // OK. "abcde\0\0\0\0\0"

s7 は、自動ストレージ期間を持つ場合は未初期化になります。s8 のようにすれば、全要素が ‘\0’ で埋められた状態になります。

文字列の長さを取得する (std::strlen関数)

文字列も配列なので、「配列の要素数を取得する」の項で取り上げた方法で要素数を取得できます。しかし、この場合の結果には、終端文字の分が含まれます。std::string の lengthメンバ関数が返す値(長さ)は、終端文字を含まないので、この違いには注意が必要です。

#include <iostream>
#include <string>

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

int main()
{
    char s1[] = "Hello, World.";
    std::cout << SIZE_OF_ARRAY(s1) << "\n";

    std::string s2 {"Hello, World."};
    std::cout << s2.length() << "\n";
}

実行結果:

14
13

生の配列による文字列で長さを求めるには、std::strlen関数[5]を使う方法があります。この関数は、引数に指定した char型の配列の長さを返します。std::strlen関数を使うには、<cstring> のインクルードが必要です。

std::strlen関数が長さを調べる仕組みは、先頭から1要素ずつカウントしていくことを、終端文字が見つかるまで繰り返すというものです。そのため、終端文字が適切に存在しなければいけません。また、1文字を表すために 2バイト以上の大きさが必要な文字が含まれていると、返される結果は見た目どおりの文字数ではないことにも注意してください。

#include <cstring>
#include <iostream>

int main()
{
    char s1[] = "Hello, World.";
    std::cout << std::strlen(s1) << "\n";

    char s2[] = "こんちには";
    std::cout << std::strlen(s2) << "\n";
}

実行結果:

13
10

“こんにちは” の長さはエンコーディング形式によって異なります。

配列の代入 🔗

配列に対して、代入演算子を使って代入することできません。

int array1[] {1, 2, 3};
int array2[] {0, 0, 0};
array2 = array1;  // コンパイルエラー。左辺を配列にはできない

代わりの方法として、std::copy関数や std::copy_n関数があります。

std::copy関数 🔗

std::copy関数[6] は、コピーする要素の範囲をイテレータを使って指定します。そのため、std::vector や std::string でも使えます。

第1・2引数でコピー元の範囲をイテレータで指定し、第3引数でコピー先(先頭)を指定します。戻り値は、コピー先の側にコピーされてきた最後の要素の1つ後ろを指すイテレータです。std::copy関数を使用するには <algorithm> のインクルードが必要です。

コピー元の範囲と、コピー先になる範囲に重なりがないように注意してください。また、コピー先に十分な領域があることも必要です。

#include <algorithm>
#include <iostream>
#include <iterator>

int main()
{
    constexpr std::size_t array_size {5};
    int array1[array_size] {1, 2, 3, 4, 5};
    int array2[array_size] {};

    std::copy(std::cbegin(array1), std::cend(array1), std::begin(array2));

    for (std::size_t i = 0; i < array_size; ++i) {
        std::cout << array1[i] << ", " << array2[i] << "\n";
    }
}

実行結果:

1, 1
2, 2
3, 3
4, 4
5, 5

Visual Studio では警告が出るかもしれません。Visual Studio編>「C4996警告」のページを参考にして対応してください。

std::copy_n関数 🔗

std::copy_n関数[7] は、std::copy関数の亜種です。コピー元の範囲を指定する代わりに、先頭を指すイテレータと要素数を指定する点に違いがあります。

第1引数でコピー元の先頭をイテレータで指定し、第2引数にコピーする要素数を指定します。第3引数はコピー先(先頭)です。戻り値は、コピー先の側にコピーされてきた最後の要素の1つ後ろを指すイテレータです。std::copy_n関数を使用するには <algorithm> のインクルードが必要です。

コピー元の範囲と、コピー先になる範囲に重なりがないように注意してください。また、コピー先に十分な領域があることも必要です。

#include <algorithm>
#include <iostream>
#include <iterator>

int main()
{
    constexpr std::size_t array_size {5};
    int array1[array_size] {1, 2, 3, 4, 5};
    int array2[array_size] {};

    std::copy_n(std::cbegin(array1), array_size, std::begin(array2));

    for (std::size_t i = 0; i < array_size; ++i) {
        std::cout << array1[i] << ", " << array2[i] << "\n";
    }
}

実行結果:

1, 1
2, 2
3, 3
4, 4
5, 5

Visual Studio では警告が出るかもしれません。Visual Studio編>「C4996警告」のページを参考にして対応してください。

配列に値を埋める 🔗

配列の要素にまとめて同じ値(特に 0)を入れたいと思うことがあります。こういうケースでは、コピー元になる配列がないため、std::copy関数や std::copy_n関数は使いづらいです。代わりに std::fill関数や std::fill_n関数が利用できます。

std::fill関数 🔗

std::fill関数[8] は、2つのイテレータで指定した範囲の要素に、指定した値を入れていく関数です。std::vector や std::string でも使えます。

第1・2引数で範囲をイテレータで指定し、第3引数で各要素に入れる値を指定します。戻り値はありません。std::fill関数を使用するには <algorithm> のインクルードが必要です。

指定範囲が配列の範囲外に出ないように注意が必要です。

#include <algorithm>
#include <iostream>
#include <iterator>

int main()
{
    int array[] {1, 2, 3, 4, 5};

    std::fill(std::begin(array), std::end(array), 0);

    for (int e : array) {
        std::cout << e << "\n";
    }
}

実行結果:

0
0
0
0
0

std::fill_n関数 🔗

std::fill_n関数[9] は、std::fill関数の亜種です。範囲を指定する代わりに、先頭を指すイテレータと要素数を指定する点に違いがあります。

第1引数で対象範囲の先頭をイテレータで指定し、第2引数で要素数を指定します。第3引数で各要素に入れる値を指定します。戻り値は、値を埋めた範囲の末尾の要素の1つ後ろを指すイテレータです(第2引数が 0 のときは、第1引数をそのまま返す)。std::fill_n関数を使用するには <algorithm> のインクルードが必要です。

配列の範囲外に出ないように注意が必要です。

#include <algorithm>
#include <iostream>
#include <iterator>

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

int main()
{
    int array[] {1, 2, 3, 4, 5};

    std::fill_n(std::begin(array), SIZE_OF_ARRAY(array), 0);

    for (int e : array) {
        std::cout << e << "\n";
    }
}

実行結果:

0
0
0
0
0

配列の比較 (std::equal関数) 🔗

配列は、型や要素数が一致していたとしても、等価演算子や関係演算子による比較が想定どおりに機能しません。

int array1[] {1, 2, 3};
int array2[] {1, 2, 3};
if (array1 == array2) {}  // 不正ではないが、必ず false

こうしたコードはコンパイルは通りますが、配列の要素どうしを比較しているわけではありません。このコード例ならば普通、array1 == array2 は true になることを望むと思いますが、これは false になってしまいます。

うまく機能しない理由は、(一部例外はあるが)式の中で配列が現れたとき、ポインタ型に暗黙的に変換されているためです。つまり、array1 == array2 はまず、array1 のメモリアドレスと array2 のメモリアドレスに置き換えられており、それぞれのメモリアドレスを == で比較していることになります。array1 と array2 は別の配列であって、メモリ上でも別々の場所に配置されていますから、メモリアドレスが一致することはあり得ません。そのため、false となってしまいます。

例外の一例に sizeof演算子があります。sizeof(array) で配列全体のバイト数が得られますが、これは array がポインタ型に変換されていないことを意味しています。変換されているなら、ポインタ型の大きさになってしまうはずです。

おそらく望んでいる動作は、要素同士を1つ1つ比較した結果がどうであるかでしょう。そうであるなら、std::equal関数[10] が利用できます。std::equal関数は、2つの範囲にある値の一致を調べます。

第1、2引数で1つ目の範囲をイテレータを使って指定します。第3、4引数で、もう1つの範囲の先頭をイテレータを使って指定します。2つの範囲の値がすべて一致していたら true を、一致していなければ false を返します。2つの範囲の要素数が異なれば一致しないことになります。std::equal関数を使用するには <algorithm> のインクルードが必要です。

#include <algorithm>
#include <iostream>
#include <iterator>

int main()
{
    int array1[] {1, 2, 3, 4, 5};
    int array2[] {1, 2, 3, 4, 5};
    int array3[] {1, 2, 3};

    std::cout << std::boolalpha
              << std::equal(std::cbegin(array1), std::cend(array1), std::cbegin(array2), std::cend(array2)) << "\n"
              << std::equal(std::cbegin(array1), std::cend(array1), std::cbegin(array3), std::cend(array3)) << "\n";
}

実行結果:

true
false

文字列の比較 (std::strcmp関数) 🔗

比較する対象が文字列(std::string ではなく、char型の配列で表される文字列)の場合には、std::strcmp関数[11]を使う方法もあります。こちらは、文字列の末尾に終端文字があることを前提にしており、末尾を指示する必要がなく簡潔です。

std::strcmp関数には、比較する2つの文字列を渡します。戻り値は、両者の内容が一致していたら 0、第1引数の文字列のほうが辞書順で先に来る場合は負数、第1引数のほうが後に来る場合は 1以上の値です。一致しているときが 0、つまり「偽」であることに注意してください。また、一致していないときに 1-1 を返す実装が多いですが、保証されるのは「1以上」や「負数」であることだけなので、if (strcmp(s1, s2) == 1) のように判定してはいけません。

なお、std::strcmp関数を使うには、<cstring> のインクルードが必要です。

#include <cstring>
#include <iostream>

int main()
{
    char s1[] {"abc"};
    char s2[] {"abcde"};

    std::cout << std::strcmp(s1, s2) << "\n"
              << std::strcmp(s2, s1) << "\n"
              << std::strcmp(s1, "abc") << "\n";
}

実行結果:

-1
1
0

配列と関数 🔗

配列の比較」の項で説明したように、配列はポインタ型に変換されています。このルールの影響を大きく受けるのが、関数の引数と戻り値です。

int array[] {1, 2, 3};
f1(array);      // 配列を渡す?
array = f2();   // 配列を受け取る?

詳しい話は次のページであらためて取り上げることにしますが、結論としては、生の配列を渡したり、生の配列を受け取ったりすることはできません。std::vector や std::string を使うのが一番簡単な解決策です。

std::vector と配列 🔗

std::vector と生の配列を混在する必要に迫られる場面もあるかもしれませんが、基本的に両者はまったく別のものであって、お互いにやり取りを行うことは簡単ではありません。たとえば、両者を比較したり、一方を他方に代入したりすることはできません。

std::vector<int> vec {1, 2, 3, 4, 5};
int array[] {1, 2, 3, 4, 5};

if (vec == array) {}  // コンパイルエラー
vec = array;          // コンパイルエラー
array = vec;          // コンパイルエラー

イテレータを取得すれば std::equal関数std::copy関数などが使えるので、そうして解決できます。

std::vector<int> vec {1, 2, 3, 4, 5};
int array[] {1, 2, 3, 4, 5};

if (std::equal(std::cbegin(vec), std::cend(vec), std::cbegin(array), std::cend(array))) {}
std::copy(std::cbegin(array), std::cend(array), std::begin(vec));  // 要素数注意
std::copy(std::cbegin(vec), std::cend(vec), std::begin(array));    // 要素数注意

std::copy関数や std::copy_n関数を使うときには、コピー先に十分な領域があるかどうかに注意しなければなりません。


生の配列と std::vector を互いに変換することも一つの手ですが、オブジェクトの生成と要素のコピーが必要なので、コストは大きくなります。

イテレータを使って既存の配列の範囲を渡してやると、その範囲と同じ内容の要素を持った std::vector を作れます。

std::vector<int> copy_array(std::cbegin(array), std::cend(array));

反対方向、つまり既存の std::vector を要素を使って、生の配列を定義することはできません。いったん生の配列を定義してから、std::copy関数などでコピーするしかありません。

std::vector<int> vec {1, 2, 3, 4, 5};
int vec_copy[5] {};
std::copy(std::cbegin(vec), std::cend(vec), std::begin(vec_copy));

このケースでは、vec_copy の要素数の指定に困ります。生の配列の要素数の指定は定数式でなければならないため、int vec_copy[vec.size()]; とはできません。

std::string と配列 🔗

std::string と char型の配列については、ある程度、相互で運用しやすくなっています。

std::string s {"abcde"};
char array[] {"abcde"};

if (s == array) {}  // OK
s = array;          // OK
array = s;          // コンパイルエラー。代入の左辺を配列にはできない

std::string と char型の配列は比較できます。また、char型の配列を std::string に代入することができ、それまでの長さよりも長くなるとしても、正常に動作します。いずれにしても、char型の配列には終端文字が存在していなければなりません。

実際には、char型の配列はポインタに変換されています。std::string と char型のポインタとの間でうまくやり取りできているということになります。

std::string から char型の配列への代入はできません。これは配列に配列を代入できなかったことと同じなので、std::copy関数などを使って解決します。

std::copy(std::cbegin(s), std::cend(s), array);

array に十分な大きさがあることをよく確認してください。

まとめ 🔗


新C++編の【本編】の各ページには、末尾に練習問題があります。ページ内で学んだ知識を確認する簡単な問題から、これまでに学んだ知識を組み合わせなければならない問題、あるいは更なる自力での調査や模索が必要になるような高難易度な問題をいくつか掲載しています。


参考リンク 🔗


練習問題 🔗

問題の難易度について。

★は、すべての方が取り組める入門レベルの問題です。
★★は、自力でプログラミングができるようなるために、入門者の方であっても取り組んでほしい問題です。
★★★は、本格的にプログラマーを目指す人のための問題です。

問題1 (確認★)

次のように変数が宣言されているとき、以下の 1~5 の式の値はそれぞれどうなりますか?

int iarray[] = {3, 5, 10};
char carray[] = "Hello";
  1. sizeof(iarray[0])
  2. sizeof(iarray)
  3. sizeof(iarray) / sizeof(iarray[0])
  4. sizeof(carray)
  5. std::strlen(carray)

解答・解説

問題2 (確認★)

生の配列の要素がメモリ上でどのように配置されているか確認してください。

解答・解説

問題3 (応用★★)

標準入力から整数を5つ入力させ、生の配列に格納したあと、入力された順番とは逆の順番で出力するプログラムを作成してください。

解答・解説

問題4 (応用★★)

int型の配列に、ある指定した値を持つ要素が含まれているかどうかを調べるプログラムを以下の3通りの方法で作成してください。

  1. 添字演算子を用いる
  2. 範囲for文を用いる
  3. <algorithm> にある標準ライブラリ関数を使う

解答・解説


解答・解説ページの先頭



更新履歴 🔗




はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る