6日でできる 新C言語入門|C言語のscanfで&が必要な理由

scanfが欲しいのは、値そのものではなく書き込み先の場所。
&が必要な理由を理解して、入力処理とポインタの関係をすっきり整理しよう。

C言語でキーボードから値を入力するときによく使う関数がscanfです。

scanfを使い始めると、整数や小数を入力するときには変数名の前に&を付けるのに、文字列を入力するときには&を付けない、という違いに出会います。

たとえば、整数を入力するときはscanf("%d", &age);のように書きます。
一方、文字列を入力するときはscanf("%s", name);のように、配列名だけを書くことが多いです。

この違いは、最初はとても分かりにくく感じるかもしれません。ですが、理由はシンプルです。

scanfは、入力された値をどこへ書き込めばよいのかを知る必要があります。そのため、scanfには値そのものではなく、書き込み先のアドレスを渡します。

整数や実数の普通の変数は、変数名だけでは値そのものを表します。そのため、&を付けて変数のアドレスを渡します。

一方、文字列を保存するchar配列では、配列名が多くの場面で先頭要素のアドレスとして扱われます。そのため、scanfに配列名を渡すだけで、入力文字列を書き込む先頭アドレスを渡せます。

この記事では、scanfで&が必要になる理由、文字列入力で&を付けない理由、配列名とポインタの関係、ポインタ変数を使った入力、配列の各要素へ入力する方法を、サンプルプログラムと図を使ってやさしく解説していきます。

scanfは入力値を書き込む場所を必要とする

scanfは、キーボードから入力された値を変数へ保存するための関数です。

ここで大切なのは、scanfが変数の値を見るだけではなく、その変数の中身を書き換えるということです。

たとえば、整数を入力してscore変数に保存したい場合、scanfはscoreがメモリ上のどこにあるのかを知る必要があります。

そのため、scoreではなく&scoreを渡します。

書き方意味
scorescoreに入っている値
&scorescoreが保存されているアドレス
scanfの第2引数入力値を書き込む場所

scanfに値だけを渡しても、scanfはどこへ入力値を書き込めばよいのか分かりません。

そのため、数値型の変数へ入力するときは、変数のアドレスを渡します。

scanf("%d", &score);

この&scoreが、score変数の場所を表しています。

図:scanfは値ではなく書き込み先のアドレスを使う

この図から分かること

この図から分かるのは、scanfが入力値を保存するために、変数の場所を必要としているということです。

scoreの値そのものを渡しても、scanfは書き込み先を判断できません。&scoreを渡すことで、scoreがあるメモリ上の場所をscanfへ知らせます。その場所に、入力された値が書き込まれます。

数値入力では&を付けてアドレスを渡す

整数や小数などの普通の変数では、変数名だけを書くと値そのものを表します。

たとえば、scoreというint型変数がある場合、scoreは変数の中身を表します。
一方、&scoreはscoreのアドレスを表します。

scanfで整数を入力するときは、入力値を書き込む先が必要なので、&scoreを渡します。

プロジェクト/ファイル名: Lesson55_1/main.c

#include <stdio.h>

int main(void) {
    int score;

    printf("点数を入力してください: ");

    // scoreのアドレスを渡して、入力値を書き込んでもらう
    scanf("%d", &score);

    printf("入力された点数は%d点です。\n", score);

    return 0;
}

実行結果

点数を入力してください: 85
入力された点数は85点です。

このプログラムでは、score変数に入力値を保存しています。

scanf("%d", &score);

ここで&scoreを渡しているのは、scanfにscoreの場所を知らせるためです。

scanfは、キーボードから読み取った85という値を、&scoreで示された場所へ書き込みます。

Visual Studioでscanfを使う場合、環境設定によって警告が表示されることがあります。学習用として動作確認する場合は、必要に応じてSDLチェックの設定も確認しておきましょう。

&を付け忘れるとどうなるのか

数値入力で&を付け忘れると、scanfに正しい書き込み先アドレスを渡せません。

たとえば、次のような書き方は危険です。

scanf("%d", score);

scoreは値そのものです。
scanfが必要としているのは、値を書き込む場所です。

つまり、scoreではなく&scoreを渡す必要があります。

目的正しい考え方
scoreの値を表示したいscoreを使う
scoreへ入力値を書き込みたい&scoreを渡す
scanfに必要なもの書き込み先のアドレス

printfでは値を表示するため、scoreを渡します。
scanfでは値を書き込むため、&scoreを渡します。

この違いを意識すると、printfとscanfの引数の違いも理解しやすくなります。

文字列入力では配列名をそのまま渡す

文字列を入力するときは、char型の配列を用意します。

たとえば、名前や商品コードのような文字列を保存したい場合です。

char name[32];

このnameはchar型の配列です。

C言語では、配列名は多くの場面で配列の先頭要素のアドレスとして扱われます。

そのため、scanfで文字列を入力するときは、nameだけで書き込み先の先頭アドレスを渡せます。

scanf("%31s", name);

ここでは、nameが&name[0]と同じように先頭アドレスとして使われます。

書き方意味
namename配列の先頭要素のアドレスとして使える
&name[0]name[0]のアドレス
scanf("%31s", name);name配列へ文字列を書き込む
scanf("%31s", &name[0]);name配列の先頭へ文字列を書き込む

%sで文字列を入力するときは、配列名だけで先頭アドレスを渡せるため、普通は&を付けません。

文字列を入力して表示するプログラム

次のプログラムでは、商品名を入力し、入力された文字列を表示します。

プロジェクト/ファイル名: Lesson55_2/main.c

#include <stdio.h>

int main(void) {
    char itemName[32];

    printf("商品名を入力してください: ");

    // itemNameは配列名なので、先頭アドレスとして渡せる
    // 最大31文字まで入力し、最後の1文字分は終端文字用に残す
    scanf("%31s", itemName);

    printf("入力された商品名は%sです。\n", itemName);

    return 0;
}

実行結果

商品名を入力してください: apple
入力された商品名はappleです。

このプログラムでは、itemNameというchar配列を用意しています。

char itemName[32];

文字列を入力するときは、scanfにitemNameを渡しています。

scanf("%31s", itemName);

itemNameは配列名なので、先頭要素のアドレスとして使えます。つまり、scanfはitemName配列の先頭から文字を書き込めます。

また、%31sとしているのは、配列の大きさを超えて入力される危険を減らすためです。char itemName[32]では、最大31文字までを入力し、残り1文字を文字列の終端に使えるようにしています。

なぜ文字列では&を付けないのか

文字列入力で&を付けない理由は、配列名がすでに先頭アドレスとして使えるからです。

数値変数では、変数名だけでは値を表します。

int score;
scanf("%d", &score);

一方、char配列では、配列名が先頭要素のアドレスとして扱われます。

char itemName[32];
scanf("%31s", itemName);

ここでitemNameは、itemName[0]のアドレスとして使えます。

注意したいのは、配列名に&を付けた場合です。

scanf("%31s", &itemName);

これは、itemName[0]のアドレスではなく、配列全体のアドレスという別の型になります。%sが想定するのは、文字を書き込む先頭要素へのポインタです。そのため、文字列入力では通常、配列名をそのまま渡します。

図:文字列入力では配列名が先頭アドレスになる

この図から分かること

この図から分かるのは、文字列入力では、char配列の配列名が書き込み先の先頭アドレスとして使われるということです。

scanfは、入力された文字列をitemName[0]から順番に書き込みます。itemNameは先頭要素のアドレスとして扱えるため、数値変数のように&を付ける必要はありません。

数値変数と文字列配列の違いを整理する

scanfで&が必要かどうかは、渡しているものがすでにアドレスなのか、値なのかで考えると分かりやすくなります。

入力対象scanfへの渡し方理由
int型変数score&scorescoreだけでは値なので、アドレスにする必要がある
double型変数weight&weightweightだけでは値なので、アドレスにする必要がある
char配列itemNameitemName配列名が先頭アドレスとして使える
int配列の要素scores[0]&scores[0]scores[0]は値なので、アドレスにする必要がある

数値変数には&を付ける。
文字列用のchar配列には、配列名をそのまま渡す。

まずはこの形で覚えるとよいでしょう。

ただし、配列の要素1つに入力する場合は、その要素は普通の変数のように値を表します。したがって、&scores[i]のようにアドレスを渡します。

ポインタ変数をscanfに渡すこともできる

scanfに必要なのは、入力値を書き込むためのアドレスです。

そのため、すでに有効なアドレスを持っているポインタ変数なら、scanfにそのまま渡せます。

次のプログラムでは、double型変数のアドレスをポインタ変数に保存し、そのポインタをscanfに渡しています。

プロジェクト/ファイル名: Lesson55_3/main.c

#include <stdio.h>

int main(void) {
    double temperature;
    double* pTemperature = &temperature;

    printf("気温を入力してください: ");

    // pTemperatureはtemperatureのアドレスを持っているため、そのまま渡せる
    scanf("%lf", pTemperature);

    printf("入力された気温は%.2f度です。\n", temperature);

    return 0;
}

実行結果

気温を入力してください: 23.5
入力された気温は23.50度です。

このプログラムでは、pTemperatureにtemperatureのアドレスを代入しています。

double* pTemperature = &temperature;

pTemperatureは、temperatureのアドレスを持つポインタ変数です。

そのため、scanfの第2引数としてpTemperatureを渡せます。

scanf("%lf", pTemperature);

このとき、scanfはpTemperatureが指しているtemperatureへ入力値を書き込みます。

つまり、次の2つは同じ書き込み先を指定しています。

書き方意味
scanf("%lf", &temperature);temperatureのアドレスを直接渡す
scanf("%lf", pTemperature);temperatureのアドレスを持つポインタを渡す

どちらの場合も、scanfに渡しているのはdouble型変数へ書き込むためのアドレスです。

scanfで使う型の対応

scanfでは、書式指定子と渡すポインタの型を合わせることが大切です。

書式指定子入力対象渡すもの
%dint型変数int型変数のアドレス
%lfdouble型変数double型変数のアドレス
%ffloat型変数float型変数のアドレス
%schar配列char配列の先頭アドレス

scanfは、書式指定子を見て、どの型として入力値を読み取るかを判断します。

そのため、%dを使うならint型変数のアドレスを渡します。
%lfを使うならdouble型変数のアドレスを渡します。
%sを使うならchar配列の先頭アドレスを渡します。

型が合わないと、正しく入力できなかったり、実行時に不安定な動作になったりする可能性があります。

配列の各要素にscanfで入力する

配列の各要素へ数値を入力したい場合は、それぞれの要素のアドレスを渡します。

scores[i]は配列のi番目の値を表します。
入力値を書き込むには、&scores[i]としてアドレスを渡します。

次のプログラムでは、3日分の歩数を配列へ入力し、最後に一覧表示します。

プロジェクト/ファイル名: Lesson55_4/main.c

#include <stdio.h>

int main(void) {
    int steps[3];
    int i;

    printf("3日分の歩数を入力してください。\n");

    // 各要素のアドレスをscanfへ渡して入力する
    for (i = 0; i < 3; i++) {
        printf("%d日目: ", i + 1);
        scanf("%d", &steps[i]);
    }

    printf("入力された歩数: %d, %d, %d\n", steps[0], steps[1], steps[2]);

    return 0;
}

実行結果

3日分の歩数を入力してください。
1日目: 4200
2日目: 5800
3日目: 6100
入力された歩数: 4200, 5800, 6100

このプログラムでは、for文で配列の各要素に順番に入力しています。

scanf("%d", &steps[i]);

steps[i]は値です。
&steps[i]はその要素のアドレスです。

scanfは入力値を書き込むため、&steps[i]を受け取る必要があります。

図:配列の要素に入力するときは各要素のアドレスを渡す

この図から分かること

この図から分かるのは、配列の各要素へ数値を入力するときは、要素そのものではなく要素のアドレスを渡す必要があるということです。

steps[i]は値を表します。scanfが必要としているのは書き込み先なので、&steps[i]を渡します。文字列入力で配列名をそのまま渡す場合とは違う点に注意しましょう。

scanfで&を付けるかどうかの考え方

scanfで&が必要か迷ったときは、次のように考えると整理しやすくなります。

scanfに渡す必要があるのは、入力値を書き込む先のアドレスです。

そのため、渡そうとしているものがすでにアドレスなら&は不要です。
渡そうとしているものが値なら&を付けてアドレスにします。

渡すものそのままでアドレスか&が必要か
int型変数scoreいいえ必要
double型変数temperatureいいえ必要
char配列itemNameはい不要
int配列の要素steps[i]いいえ必要
有効なポインタ変数pTemperatureはい不要

この表を見ると、scanfのルールは単なる暗記ではなく、アドレスを渡すという考え方で説明できます。

文字列入力では入力文字数にも注意する

%sで文字列を入力するときは、配列のサイズを超えないように注意が必要です。

たとえば、char itemName[32]という配列には、終端文字も含めて32文字分の領域があります。文字列として安全に扱うためには、入力できる文字数を31文字までにしておくと安心です。

scanf("%31s", itemName);

%sは空白で入力を区切ります。そのため、空白を含む文字列を入力したい場合には別の入力方法を考える必要があります。

ここでは、scanfとアドレスの関係を学ぶことが目的なので、まずは空白を含まない短い文字列で練習すると分かりやすいです。

&を付ける理由が分かるとポインタの理解につながる

scanfで&を付ける理由は、scanfが入力値を書き込むために変数のアドレスを必要とするからです。

int型やdouble型の普通の変数では、変数名だけだと値そのものを表します。そのため、&を付けてアドレスを渡します。

一方、char配列に文字列を入力するときは、配列名が先頭要素のアドレスとして使えるため、&を付けずに配列名をそのまま渡します。

ポインタ変数に有効なアドレスが入っている場合は、そのポインタ変数をscanfに渡すこともできます。scanfに必要なのは、値を書き込むためのアドレスだからです。

また、配列の数値要素に入力する場合は、steps[i]ではなく&steps[i]を渡します。steps[i]は値であり、scanfが必要としている書き込み先ではないからです。

このように、scanfで&が必要かどうかは、渡しているものが値なのかアドレスなのかを考えると判断できます。scanfを通して、C言語のポインタと配列の関係を自然に理解していきましょう。