C言語基礎|配列のコピー

配列を扱っていると、わりと早い段階でこういう場面に出会います。

  • 元データは残したまま、加工した配列を作りたい。
  • 並びを逆にした配列を別に持ちたい。
  • 条件に合う要素だけ抽出して、新しい配列に詰めたい。

つまり 配列をコピーしたいんですね。

ところがC言語では、ここでひっかけポイントがあります。
配列は代入演算子で丸ごとコピーできません。
だから、基本は「要素を1個ずつコピーする」スタイルになります。

まず重要:配列は b = a; ができない

普通の変数なら b = a; でコピーできますが、配列はダメです。

対象代入できる?
int 変数できるx = y;
配列できないb = a; はエラー

なので配列コピーは、こういう形が基本になります。

for (i = 0; i < n; i++)
    b[i] = a[i];

サンプルプログラム

元の a と b の例を、もう少し実務寄りにしてみます。
ここでは「5日分の作業時間(分)」を入力して、バックアップ配列にコピーして表示します。
表示メッセージも別の日本語に置き換えています。

プロジェクト名:chap5-8-1 ソースファイル名:chap5-8-1.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

// 配列の全要素を別の配列にコピー(バックアップ)
#include <stdio.h>

#define N 5

int main(void)
{
    int work[N];      // コピー元
    int backup[N];    // コピー先

    printf("%d日分の作業時間(分)を入力してください。\n", N);
    for (int i = 0; i < N; i++) {
        printf("%d日目:", i + 1);
        scanf("%d", &work[i]);
    }

    for (int i = 0; i < N; i++)
        backup[i] = work[i];    // 要素ごとにコピー

    puts("\n元データとバックアップを表示します。");
    puts(" day  work  backup");
    for (int i = 0; i < N; i++)
        printf("%4d%6d%8d\n", i + 1, work[i], backup[i]);

    return 0;
}

図で理解:配列コピーは「2つの配列を同時に走査」

コピー処理は「同じ添字の要素を順番に代入していく」だけです。

この図が示しているのは「値が同じになる」だけじゃなく、
配列そのものは別物(別の領域)ということです。
だからコピー後に work[0] を変えても、backup[0] は勝手に変わりません。

条件を満たす要素だけコピー(抽出コピー)

全要素コピーを応用すると、よく使うテクとして「フィルタ(抽出)」ができます。
このときの主役が count 変数です。

  • コピー先の「次に入れる位置」を管理する
  • 条件に合ったときだけ b[count] に入れて、直後に count を増やす

サンプル

「温度データのうち、0以上(正常値)だけログ配列に詰める」みたいな雰囲気の例にします。

プロジェクト名:chap5-8-2 ソースファイル名:chap5-8-2.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

// 配列の要素のうち0以上の要素を別の配列にコピー
#include <stdio.h>

#define N 5

int main(void)
{
    int temp[N];     // コピー元
    int normal[N];   // コピー先

    printf("%d個の温度を入力してください(負の値も入力可)。\n", N);
    for (int i = 0; i < N; i++) {
        printf("temp[%d]:", i);
        scanf("%d", &temp[i]);
    }

    int count = 0;
    for (int i = 0; i < N; i++) {
        if (temp[i] >= 0)
            normal[count++] = temp[i];
    }

    puts("\n0以上の値だけ取り出しました。");
    for (int i = 0; i < count; i++)
        printf("normal[%d] = %d\n", i, normal[i]);

    return 0;
}

count++ の意味を表で整理(ここ超重要)

normal[count++] = temp[i]; は、初心者がつまずきやすいところなので丁寧にいきます。

書き方起きることイメージ
normal[count] = temp[i]; count++;代入してから count を1増やす安全で分かりやすい
normal[count++] = temp[i];上と同じ意味(後置インクリメント)1行で書ける

後置インクリメント count++ は「式の評価が終わった後に増える」ので、
代入先としては「増える前の count」が使われます。

例:count が 0 のときに代入すると…

  1. normal[0] に代入
  2. その後 count が 1 になる

どの命令・演算子が何をしているか(書式つき)

for 文(配列走査)

書式

for (初期化; 条件式; 更新) {
    処理
}

何をする?
添字を増やしながら、配列要素を先頭から順番に処理します。コピー処理の基本形です。

if 文(条件付きコピー)

書式

if (条件) 文;

または

if (条件) {
    文;
}

何をする?
条件が真のときだけコピーします。抽出コピーで大活躍します。

代入演算子 =

書式

左辺 = 右辺;

何をする?
右辺の値を左辺へ入れます。
配列全体には使えないけど、要素(b[i])にはもちろん使えます。

後置インクリメント ++

書式

count++

何をする?
式の評価が終わった後に 1 増やします。
抽出コピーでは「入れたら次へ」が自然に書けます。

printf / scanf / puts

  • scanf(書式, 格納先アドレス); は入力
  • printf(書式, 値...); は表示
  • puts(文字列); は1行表示(改行つき)

“配列コピー”でありがちなミス(先に潰す)

ミス何が起きる?対策
b = a; と書くコンパイルエラーループで要素コピー
count を増やし忘れる同じ場所に上書きされ続けるnormal[count++] を使う
count の上限を意識しない配列範囲外アクセスコピー先のサイズを確保、条件を厳密に

演習問題

演習5-9

配列 a の要素の並びを逆順にしたものを b にコピーするプログラムを作成せよ。

解答例

プロジェクト名:chap5-8-3 ソースファイル名:chap5-8-3.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

#include <stdio.h>

#define N 5

int main(void)
{
    int work[N];
    int rev[N];

    printf("%d個の整数を入力してください。\n", N);
    for (int i = 0; i < N; i++) {
        printf("work[%d]:", i);
        scanf("%d", &work[i]);
    }

    for (int i = 0; i < N; i++)
        rev[i] = work[N - 1 - i];   // 逆順コピー

    puts("\n逆順にコピーしました。");
    puts(" i  work  rev");
    for (int i = 0; i < N; i++)
        printf("%2d%6d%6d\n", i, work[i], rev[i]);

    return 0;
}