演習課題「標準入力から文字のドットデータを読み込む」
右のコードエリアには、n × m の領域を確保するコードがあります。
コードを 2 箇所に追加して、標準入力から読みこんだデータを確保した領域に格納してください。
ただし、入力として、
1 行目に縦と横の大きさ n、m がスペース区切りで与えられます。
2 行目以降に n 行でドットのデータがスペース区切りで与えられます。
プログラムを実行して、正しく出力されれば演習課題クリアです!
入力される値
6 22
0 0 1 1 0 0 0 0 1 1 1 1 1 0 0 0 0 1 1 1 1 0
0 1 0 0 1 0 0 0 1 0 0 0 0 1 0 0 1 0 0 0 0 1
1 0 0 0 0 1 0 0 1 1 1 1 1 0 0 0 1 0 0 0 0 0
1 1 1 1 1 1 0 0 1 0 0 0 0 1 0 0 1 0 0 0 0 0
1 0 0 0 0 1 0 0 1 0 0 0 0 1 0 0 1 0 0 0 0 1
1 0 0 0 0 1 0 0 1 1 1 1 1 0 0 0 0 1 1 1 1 0
期待する出力値
@@ @@@@@ @@@@
@ @ @ @ @ @
@ @ @@@@@ @
@@@@@@ @ @ @
@ @ @ @ @ @
@ @ @@@@@ @@@@
#09:標準入力から2次元配列のデータを読み込もう
応用として、標準入力から受け取ったドット絵のデータを、動的に確保したデータ領域に読み込んでみます。
7 16
0 0 0 0 1 1 1 0 0 0 0 0 0 1 1 1
0 0 0 1 1 1 0 0 0 0 0 1 1 1 0 0
0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0
1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1
1 0 0 1 1 1 0 0 0 1 1 1 0 0 0 1
1 1 0 0 0 0 1 1 0 0 0 0 0 0 1 1
0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0
標準入力からスペース区切りの 2 つの整数を受け取るには、次のように記述します。#include <stdio.h>
int main(void)
{
char buf[100];
fgets(buf, sizeof(buf), stdin);
int x, y;
sscanf(buf, "%d %d", &x, &y);
}
※ この方法では、決められた個数でしか区切ることができません。
今回のように n 個のデータに区切りたいときは、scanf などを使う必要があります。
2 次元配列のように使える領域 (n × m の領域) は次のように確保できます。int **enemy_image;
enemy_image = malloc(sizeof(int *) * n);
for (int i = 0; i < n; i++) {
enemy_image[i] = malloc(sizeof(int) * m);
}
この処理を追っていくと、以下のようになります。
1. はじめに、「int へのポインタへのポインタ」の enemy_image 変数を宣言しています。
2. 次に、enemy_image = malloc(sizeof(int ∗) ∗ n); としています。
これによって、enemy_image には、「int ∗」型の値を n 個格納できる領域の先頭のアドレスが代入されます。
よって、enemy_image は「int ∗ 型の値を要素としてもつ配列」のように使うことができます。
3. その後で、ループ処理で、enemy_image[i] = malloc(sizeof(int) ∗ m); としています。
これによって、各 enemy_image[i] には、int 型の値を m 個格納できる領域の先頭のアドレスが代入されます。
よって、各 enemy_image[i] は「int 型の値を要素として持つ配列」のように使うことができます。
つまり、enemy_image は「m 個の int を格納できる領域へのポインタ」を n 個格納する配列のように使うことができます。
scanf に変換指定子の「%s」や「%d」を使うと、標準入力から空白文字で区切られたデータを 1 つずつ読み込むことができます。
空白文字とは、タブ、スペース、改行などを指します。
たとえば、以下のコードでは 5 個のデータを標準入力から読み込むことができます。#include <stdio.h>
int main(void)
{
for (int i = 0; i < 5; i++) {
int x;
scanf("%d", &x);
printf("%d\n", x);
}
}
入力として次のデータが与えられたとき、10
20 30 40
50
出力は次のようになります。10
20
30
40
50
※ scanf は便利な関数ですが、以下のような問題点が指摘されています。
・「%d」を使ったとき: 数値以外が入力されると、そのデータは読み込まれず、標準入力のストリーム上に残ってしまう。
・「%s」を使ったとき: 用意した配列よりも長い文字列が入力されると、配列の領域を超えてデータが書き込まれてしまう。
このような問題点を回避するため、 fgets と sscanf の使用が推奨される場合があります。
malloc で以下のように領域を取得したとき、int **enemy_image;
enemy_image = malloc(sizeof(int *) * n);
for (int i = 0; i < n; i++) {
enemy_image[i] = malloc(sizeof(int) * m);
}
次のようにして領域を開放します。for (int i = 0; i < n; i++) {
free(enemy_image[i]);
}
free(enemy_image);
先に「free(enemy_image);」を実行すると、各 enemy_image[i] にアクセスできなくなってしまうので、
この順番で開放をおこなう必要があります。
このチャプターで作成したコードです。// 標準入力から 2 次元配列のデータを読み込もう
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char buf[100];
fgets(buf, sizeof(buf), stdin);
int n, m;
sscanf(buf, "%d %d", &n, &m);
// printf("ドット絵の大きさ: %d × %d\n", n, m);
int **enemy_image;
enemy_image = malloc(sizeof(int *) * n);
for (int i = 0; i < n; i++) {
enemy_image[i] = malloc(sizeof(int) * m);
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
int x;
scanf("%d", &x);
enemy_image[i][j] = x;
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (enemy_image[i][j] == 1) {
printf("#");
} else {
printf(" ");
}
}
printf("\n");
}
for (int i = 0; i < n; i++) {
free(enemy_image[i]);
}
free(enemy_image);
}
- C言語で2次元配列を動的に割り当てる4つの方法 - Flying
https://tondol.hatenablog.jp/entry/20090713/1247426321
- scanf
https://ja.wikipedia.org/wiki/Scanf