前回、前々回の配列に関しての記事は内容的に理解するのが大変だったと思います。そこで今回は少し羽休めとしてscanf関数を用いた入力について勉強していきます。
前回の復習
前回は配列の応用ということで、文字列について詳しく勉強しました。char型の配列として実装されている文字列は文字の長さが特に決まっていないので配列を作るのも使うのも大変という話をしました。それを扱いやすくするために番兵という考え方で特殊な文字を文字列の最後につけていることを学びましたね。
後半では2次元配列について勉強しました。普通の配列と見比べながら復習してみるとわかりやすいと思うのでまだ不安がある人は復習するときに試してみてください!
前回の練習問題の解答例
A問題
解答例
#include <stdio.h>
int main() {
int name[30] = "iwachoco";
printf("%s\n", name);
return 0;
}
コンパイルしたときに配列の要素を超えている系のエラーメッセージが出てきた場合は、配列を作る時に確保する要素数を増やしてくださいね。
以下のように書くことで、エラーを回避しながらメモリも効率的に使うことができます。
#include <stdio.h>
int main() {
int name[] = "iwachoco";
printf("%s\n", name);
return 0;
}
ただし文字列の長さに合わせて要素数を確保しているので、文字列の長さを伸ばす場合は新しく配列を作り直さなければならないため、文字列長を変化させる可能性がある場合はこの書き方を避けましょう。
B問題
1月 | 2月 | 3月 | 4月 | |
---|---|---|---|---|
気温[℃] | 7 | 9 | 15 | 19 |
降水量[mm] | 100 | 50 | 40 | 20 |
この表のデータを2×4の2次元配列を作り、それぞれの要素に代入してください。(初期化でも可)
解答例
#include <stdio.h>
int main() {
int data[2][4] = {{7, 9, 15, 19}, {100, 50, 40, 20}};
return 0;
}
上では初期化の書き方をしました。初期化で書いているため要素数を省略した書き方もできます。宣言してから代入する場合は以下のように書けば良いでしょう。
#include <stdio.h>
int main() {
int data[2][4];
data[0][0] = 7;
data[0][1] = 9;
data[0][2] = 15;
data[0][3] = 19;
data[1][0] = 100;
data[1][1] = 50;
data[1][2] = 40;
data[1][3] = 20;
return 0;
}
for文を勉強すると宣言して代入する方法の方をよく使うようになってくるので、こちらも復習しておきましょうね。
scanf関数
今回は入力についてみていきます。本当は出力のprintf関数を勉強したときに一緒に紹介した方が記事の内容的にまとまって良いかなとも思ったのですが、文字列やメモリの話を全くせずにscanf関数を語ることもできないのでこのタイミングで紹介することにしました。
scanf関数を見ていく前に『アドレス演算子』というものについて勉強していきます。
アドレス演算子というのは『&』という記号を用います。この演算子を用いることで変数のアドレス(値がメモリ上のどこに格納されて居るのかという情報)を知ることができます。
ここで簡単にアドレスについて説明しておきます。(配列のところでも話している内容なのでわかる人は飛ばしてもらって構いません)
アドレスというのはメモリ上に割り当てられた住所のようなもので、変数や配列という箱の正体はメモリ上に確保された領域でした。この領域をそれぞれの変数と結びつけて値を格納できる箱を実現しているわけです。
scanf関数はそこのアドレスに直接アクセスして値を入力することができる関数になります。実際にアドレス演算子の使い方をみてもらってscanf関数を書いてもらいましょう。
#include <stdio.h>
int main() {
int number;
scanf("%d", &number); //&変数名でアドレスを取得できる
printf("%d\n", number);
printf("%p\n", &number); //%pというフォーマット指定子を用いるとアドレスを出力できる
return 0;
}
実行結果
12 //入力値
12
0x7ffeef5bc9c8 //実行環境によって違います
このソースコードが理解できることが今回のメインです。なので丁寧に見ていくことにします。
まず最初に5行目を見てください。scanf関数とアドレス演算子が出てきています。ここでは、4行目で作った変数numberのアドレスをscanf関数の中で使っています。scanf関数で何をしているのかというと、変数のアドレスにアクセスして直接メモリ上に値を格納しています。scanf関数を用いることで一方通行なプログラムが入力によって処理を変える対話的なものになります。
scanf関数の中にある”%d”のフォーマット指定子のところは、アドレス演算子をつける変数の型に合ったフォーマット指定子を用いるようにしましょうね。出力用のフォーマット指定子と入力用のフォーマット指定子は若干異なっているのでここで整理しておきましょう。
変数の型 | 出力用フォーマット指定子 | 入力用フォーマット指定子 |
---|---|---|
char型 | %c | %c |
short型 | %d | %hd |
int型 | %d | %d |
long型 | %ld | %ld |
float型 | %f | %f |
double型 | %f | %lf |
文字列(char*型) | %s | %s |
short型は滅多に使わないので、double型のみ違いを押さえておけば大丈夫ですね。scanf関数
6行目はお馴染みのint型の変数の値を出力するprintf関数です。
7行目はフォーマット指定子が『%p』になっています。これを用いることで、変数のアドレス自体を出力することができます。このとき変数にはアドレス演算子をつけてあげる必要があります。出力は16進数になっています。
以下ではもう少しscanf関数を用いたプログラムを書いて慣れていきます。
#include <stdio.h>
int main() {
/*char型の場合*/
char favorite;
printf("文字(int)を入力してください: ");
scanf("%c", &favorite);
printf("%c\n", favorite);
/*short型の場合*/
short ant;
printf("整数値(short)を入力してください: ");
scanf("%hd", &ant);
printf("%d\n", ant);
/*int型の場合*/
int day;
printf("整数値(int)を入力してください: ");
scanf("%d", &day);
printf("%d\n", day);
/*long型の場合*/
long population;
printf("整数値(long)を入力してください: ");
scanf("%ld", &population);
printf("%ld\n", population);
/*float型の場合*/
float distance;
printf("小数値(float)を入力してください: ");
scanf("%f", &distance);
printf("%f\n", distance);
/*double型の場合*/
double water;
printf("小数値(double)を入力してください: ");
scanf("%lf", &water);
printf("%f\n", water);
/*文字列の場合*/
char message[100];
printf("文字列を入力してください: ");
scanf("%s", message);
printf("%s\n", message);
return 0;
}
実行結果
文字(int)を入力してください: a //入力値
a
整数値(short)を入力してください: 2 //入力値
2
整数値(int)を入力してください: 490 //入力値
490
整数値(long)を入力してください: 1234567890 //入力値
1234567890
小数値(float)を入力してください: 1.234 //入力値
1.234000
小数値(double)を入力してください: 3.14159265358979 //入力値
3.141593
文字列を入力してください: abcdefghijklmnopqrstuvwxyz //入力値
abcdefghijklmnopqrstuvwxyz
文字型以外はscanf関数の使い方は同じで、アドレス演算子を用いています。しかし文字列だけはアドレス演算子を必要としません。何故かというと、文字列が配列だからです。
配列というのは、メモリ上に保存領域を確保しているのでした。ただ保存領域を確保しているのではなく、連続した領域を確保しているのでしたね。この『連続した』という部分が重要でこの仕組みがアドレス演算子を必要としない仕組みを作っています。
実は配列を作ると最初の要素(0番目)が格納されている場所のアドレスが配列の中に記憶されます。そのため、配列に保存されたアドレスにアクセスすると最初の要素が得られるわけです。連続した領域を確保していることから、お隣は次の要素(1番目)になっています。これを実際にソースコードで確認してみましょう。
#include <stdio.h>
int main() {
int array[3] = {1, 3, 5};
printf("%p\n", array);
printf("%p\n", &array[0]);
printf("%p\n", &array[1]);
return 0;
}
実行結果
0x7ffee2b339bc
0x7ffee2b339bc
0x7ffee2b339c0 //実行環境により異なります
このように同じ5行目と6行目のアドレスが同じになっているのがわかると思います。また、6行目と7行目の出力結果がアドレスで4つ分ずれているのがわかります。これは、int型の値を保存するのに4Byteの領域を使っているためです。(sizeof演算子を用いてsizeof(int)で出力される分だけズレたアドレスが出力されます。確認してみてください!)
この性質から分かるように、配列は値が格納されているアドレスを保持しているため参照型と呼ばれています。参照型にscanf関数で入力するときには配列名などでアドレスがわかるため、アドレス演算子(&)が不要になります。
ちなみに参照型の反対は値型で、int型変数やfloat型変数なんかがそうです。配列とポインタ変数以外はこちらに分類されます。
アドレスについてはこの講座の後半で扱うアドレスとポインタの記事でもう一度登場するので、そこでしっかり勉強しましょう。
参照型の変数や配列にscanf関数を用いて値を代入する際にはアドレス演算子は不要となる。
もう少しだけscanf関数を使ったソースコードの実験をして今回記事を締めくくりましょう!!
#include <stdio.h>
int main() {
int orange, apple;
scanf("%d %d", &orange, &apple);
printf("みかん%d個, りんご%d個\n", orange, apple);
return 0;
}
実行結果
2 4 //入力
みかん2個, りんご4個
このようにscanf関数の中のフォーマットをいじることができます。覚えておくと実装の幅が広がるでしょう!
まとめ
今回の記事ではscanf関数を用いた入力について勉強しました。また、アドレス演算子についても学びましたね。ここまで勉強をするとほんの少しですが競技プログラミングの問題に挑戦することができるようになってきます。競プロに興味があればex問題のところに載せたリンクに飛んでみてくださいね。今回のまとめをしておきましょう!
- 配列はアドレス情報を保持している。そのため参照型に分類される。(参照型の反対は値型)
- アドレス演算子を用いることで変数のアドレス値(メモリ上の場所)を取得できる。
- scanf関数を用いることで入力をすることができる。その際フォーマット指定子は入力用を使うことに注意する。
次回はいよいよif文についてみていきます。次回の記事は分量が多くなりそうなので、ここまでの記事を復習して頭を整理しておいたほうが良いかもしれません!
練習問題
A問題
入力
入力例1
出力例1
亀が2匹、鶴が3羽いるので、頭は5で足は14になります。
入力例2
出力例2
B問題
入力
入力例1
出力例1
偶数はそのまま出力します。
入力例2
出力例2
奇数は+1して偶数にします。
ex問題
ABC 001 A 積雪深差【AtCoder】
ABC 003 A AtCoder社の給料【AtCoder】
ABC 004 A 流行【AtCoder】
ABC 005 A おいしいたこ焼きの作り方【AtCoder】
最後まで記事を見ていただきありがとうございます。また別の記事でお会いできることを祈っております。