こんにちは、ナナです。
プログラミングを始めると、まず出てくるのが「変数」です。
「変数」はどのプログラミング言語でも必ず出てきます。それは、「変数」がプログラミングをするうえで欠かせない要素だからなんです。
本記事では次の疑問点を解消する内容となっています。
では、「変数」に関する作り方や使い方を解説します。
変数の概要とメモリとの関係性
押忍。「変数」ってやつを使ってみたいっす。教えてくださいっす。
「変数」だね。それじゃあ、まずは「変数」を使う前に「メモリ」に関して理解しておこうね。
「変数」を正しく理解するには、「メモリ」の知識が必要となります。まずは、メモリを理解しましょう。
プログラミングで登場する「変数」とは何か
C言語に限らず、プログラミング言語では「変数」というものを使うことができます。
変数を利用することで開発者は、様々な数を記憶しながら処理を進めることができます。数を保存できる、これこそが「変数」の役割です。
記憶する必要性というのが最初はピンと来ないかもしれませんが、プログラミングの経験値が上がることで自然と身に付きます。
メモリを理解することが変数を理解することにつながる
もう一度言います。
変数を理解するには、「メモリ」というハードウェアへの理解が欠かせません。
まずは、「メモリ」というものが、どのようなものなのかを説明しましょう。
メモリに電源が入ると、搭載メモリのサイズ分だけの数を記憶できる領域が使えるようになります。
メモリは次のように数を記憶し、読み書きができます。
メモリは次の2つで構成されています。
記憶領域
1バイトという単位で存在し、任意の数値を記憶することができる。4バイトを1つの領域として扱うなど、複数の領域を大きな1つの領域として使うこともできる。
番地(アドレス/住所)
記憶領域の場所を示す情報であり数値で表現される。記憶領域と1対1で関連付けされる。搭載されたメモリのサイズ分だけ番地は存在する。アドレスや住所などとも呼ばれる。
記憶領域は存在するだけで、必ず数が記憶されています。その値が意図したものかどうかは関係なく、電気的に何かしらの数を必ず持っているのです。
「0なので値は入っていません」と勘違いする方がいますが、「0」の場合は「0」という値を記憶しているということになります。
メモリと変数の関係性
プログラムから変数を使えるようにすることを「変数定義」と言います。
一般的に変数定義の説明は「数値を覚えられる箱を作ること」と説明しますが、実際にはメモリに数値が記憶されています。
皆さんが変数を使いたいときには、変数名という名前を決める必要があります。
「変数名」とは
メモリに対して貼り付けたラベル
を表します。
変数定義を行うと、ラベルが誰も使用していないメモリの記憶領域に自動的に貼られます。
例えば変数として「num」という変数名を定義するとは、次のようなイメージとなります。
「変数」を使えるようにするとは、メモリにラベルを貼る行為によって実現します。
このようにメモリを利用するイメージを思い浮かべながら、「変数」を使いこなせると理解が速いですよ。
「変数」の作り方と使い方
「変数」というものの考え方はわかったっす。早くプログラムから「変数」を使ってみたいっす。
そうだね、それじゃあ「変数」を作ってみよう。
「変数」を作るときは書き方のルールがあるから、それをしっかり覚えることだね。じゃあ教えるね。
具体的にプログラムから変数を作るための定義方法を示します。変数定義は、次の書式で記述します。データ型に関しては後述します。
変数定義の書式
データ型 変数名;
変数の定義例
int num;
long kind;
この変数定義は、皆さんが今後プログラムを作る上で、ものすごくたくさん登場します。「データ型」+「変数名」で変数を作ることをしっかりと覚えておきましょう!
変数を使ったプログラムの紹介
次のプログラムは、変数を活用したプログラムになります。
プログラムの内容は「10×20÷5」と「10×20÷10」の、2つの演算結果を求める処理になります。
次のプログラムを、皆さんの開発環境で動かしてみましょう。
#include <stdio.h>
int main(void)
{
int calc; // 途中計算結果
int result; // 最終計算結果
calc = 10 * 20;
result = calc / 5;
printf("%d\n", result);
result = calc / 10;
printf("%d\n", result);
return 0;
}
先頭にブレークポイントを設定して、ステップ実行で順番に動かしてみましょう。次のように「40」と「20」という数が画面に出力されたことでしょう。
40
20
printf命令に変数名を指定すると、変数ラベルのあるメモリの数値が出力されます。
printf命令は、変数に覚えている数をディスプレイに出すことができます。
まずは、こんな書き方で変数の数が表示できることを覚えておきましょう。もう少し先の方で詳細な使い方は説明します。
代入演算子の意味とは
C言語プログラムにおけるイコール記号は、「代入」を表す記号です。
「代入」の意味は
右辺の情報を、左辺の変数に格納する
という命令になります。
つまり、「10×20」の演算結果となる「200」という数値を、preCalc変数に格納することになります。
「代入」を示す「=」記号は、数学的な左辺と右辺が等しいというイコールとは異なることに注意しましょう!
デバッガによる変数値の確認をしてみよう
デバッガは開発者が変数の値を確認できる仕組みを提供しています。デバッガ機能の1つである「ローカル機能」を使ってみましょう。
プログラムの先頭にブレークポイントを設定し、実行して一時停止状態にしましょう。画面下部にローカル画面が表示されます。
変数において大事なことは「変数が今何の値を記憶しているか」です。ローカル画面は変数情報を一覧化し皆さんに値を見せてくれます。
変数の持つ「値」はステップ実行を行うとともに順次更新されていきます。また、ステップ実行しながら途中で値を変更し、プログラムの動きを変化させることもできます。
デバッガの機能はどんどん使って扱い方を覚えていきましょう。開発効率がぐんと上がっていきます。困ったときはデバッガが皆さんを助けてくれるんです。
変数名には付けられない名前もあることに注意
ラベルとなる変数名はプログラム上でどんな情報を管理しているのかを示す重要な情報です。
命名ルールの範囲内で皆さんの好きな変数名を付けることができます。ルールは次のものです。
次の変数名が使用できるかどうかをプログラムの変数名に記載して確かめてみてください。
使えない名前の場合はビルドが失敗します。ビルドを行う前に使えるかどうかを予想してから確かめてみましょう。
Kind | 2000year | INT | |
_number | switch | Switch | if |
次のように、データ型には「int」を指定しましょう。
int Kind;
どんな変数名ならOKで、どんな変数名ならダメなのかを理解しておきましょう!
変数定義が可能な場所について注意
変数を定義する際に注意点があります。
実は、C言語では変数定義が記述できる場所が決まっています。
#include <stdio.h>
int main(void)
{
// {の直後の変数定義はOK
char num;
num = 100; // 処理
// 処理後の変数定義
long tmp; // ビルドエラー
tmp = 50;
return 0;
}
変数の定義は{}の先頭で記述できますが、処理の後では記述できません。
Visual StudioではC++言語で実際は動かしているためビルドエラーは発生しませんが、異なる開発環境ではビルドエラーがでることがあるため知識として知っておくとよいでしょう。
このルールは、初心者の方は結構忘れがちになります。
ビルドエラーの理由がわからなくて悩んで時間をすごすことが多いので、しっかりと覚えておきましょう。
変数のデータ型一覧と特徴の違いとは
変数定義にはデータ型と呼ばれる情報が必要になります。C言語における代表的なデータ型は次のものがあります。
データ型 | サイズ | 数値の範囲 | 説明 |
---|---|---|---|
char | 1バイト | -128~127 | 文字情報としても使用 |
unsigned char | 1バイト | 0~255 | 256種類の数を管理できる |
short | 2バイト | -32768~32767 | |
unsigned short | 2バイト | 0~65535 | 約65000程度の数を管理 |
long | 4バイト | -2147483648~2147483647 | |
unsigned long | 4バイト | 0~4294967295 | |
int | 環境依存 | 環境依存 | 環境によりサイズが変化する。 2~4バイト |
unsigned int | 環境依存 | 環境依存 | 環境によりサイズが変化する。 2~4バイト |
float | 4バイト | 単精度浮動小数 | 格納範囲小の小数点型 |
double | 8バイト | 倍精度浮動小数 | 格納範囲大の小数点型 |
各データ型のサイズとは、メモリの記憶領域を使用する個数を示しています。
皆さんがデータ型を選択することで、データ型に応じたメモリの個数が使えるようになります。
データ型によって、ラベルの長さが変わることをイメージしてください。
データ型を決めるための考え方を教えます
変数を使用したい場合、皆さんがデータ型を指定する必要があります。
プログラムに慣れていない方は、どのようにデータ型を決めればよいのか迷うことがあります。
型を決める際に、まず皆さんがやるべきことは
管理したい情報とは、いったい何なのか?
をはっきりさせることです。
これができていないと、情報がぼんやりとしてデータ型が決められません。
「リンゴの数」「身長」「人口」など具体的に、今どんな情報を変数として管理したいのかをはっきりさせましょう。
情報がはっきりすれば、小数点は必要なのか、負値は必要なのか検討ができます。あとは少し将来も見据えて、その情報が取りうる範囲を見積もります。
そうすることで、数値を記憶できるデータ型を決定するのです。
データ型の決め方に迷ったときは、この流れを確認しましょう。何回も数をこなすことで、自然とできるようになっていきます。
「変数の初期化」とはどんなこと?
変数定義と同時に、なにかしらの値を格納することを「変数の初期化」といいます。
「初期化」と「代入」は、値を格納するという意味では同じです。しかし、C言語では文法的に別のものとして扱われます。
結果が同じなのだから意識する必要があるのか、と思う方もいるかもしれません。
しかし、「初期化」と「代入」は別のものと正しく意識しましょう。
変数に対する「初期化」というのは1度しかできない特別なものであり、「初期化」の時だけ許されている文法も存在します。
そのため、「初期化」と「代入」は別のものであることを知っておく必要があるのです。
また、初期化する値のことを「初期値」と呼びます。
「初期値」は対象データが最初にどの数であるべきか示すものです。これを決めることができるのは、開発者である皆さんだけです。
0として初期化とするのか、10として初期化とするかは皆さん次第になります。
「初期化」と「代入」を区別する意識が低い方って結構いるんです。
でも、この区別は絶対できるようになってください。「初期化」と「代入」は別のものであると区別できないと、今後困るシーンが出てくるんです。
自己代入って何?
変数の演算では、元の変数値に対して演算した結果を再度書き戻すこともよく行われます。次のプログラムは元の変数の値を10倍にするものです。
#include <stdio.h>
int main(void)
{
int number = 5;
number = number * 10;
return 0;
}
このプログラムの実行結果として、変数「number」の値は「50」になります。
このように元の変数の値を利用して、変数の値を変化させることを「自己代入」と呼びます。
「自己代入」は、左辺と右辺に同一の変数名が出てくるため不可思議なものに感じるかもしれません。しかし、左辺と右辺で演算される時間軸が異なるため、成立するのです。
自己代入の算術演算子
「自己代入」は左辺と右辺に同名の変数を使うことで、次のように四則演算で記述ができます。
名称 | 書き方 | 省略系の書き方 |
---|---|---|
加算 | a = a + 10; | a += 10; |
減算 | a = a – 10; | a -= 10; |
乗算 | a = a * 10; | a *= 10; |
徐算 | a = a/ 10 | a /= 10; |
省略形の書き方は変数名を1度きり書けばよいという便利さゆえに、C言語の開発者は一般的に省略形の書き方を用います。
インクリメント・デクリメント
変数値を「1つ増加したい」「1つ減少したい」というニーズはプログラムの中によく出てきます。そのため、この2つのニーズに対しては専用の演算子が用意されています。
それが、「インクリメント」と「デクリメント」です。
名称 | 書き方 | 同等の別の書き方 |
---|---|---|
インクリメント | a++; | a = a + 1; |
デクリメント | a--; | a = a – 1; |
「インクリメント」と「デクリメント」はものすごくよく使います。絶対に名前も覚えておきましょう!
オーバーフロー
変数は型の種類により格納できる数値の範囲が決まります。では、「変数」に範囲外の数値を代入するといったい何が起きるのでしょう?
#include <stdio.h>
int main(void)
{
// 格納範囲は0-255の型
unsigned char num;
// 260を代入してしまった
num = 260;
return 0;
}
このように規定範囲外の値を代入することを「オーバーフロー」と呼びます。
通常オーバーフローは開発者が意図しないケースで発生することが多く、発生しないように型の範囲を指定する必要があります。
このプログラムを動かして、numの値がどうなるかは皆さんの環境で実際に動作させて確かめてみてください。
意図しないオーバーフローによる不具合って、初心者のうちは結構やりがちです。
オーバーフローという現象が、いったいどういうものなのかを体験しておくとよいですよ。
変数を利用するメリット
変数ってものの使い方はなんとなくわかってきたっす。でも、「変数」ってそんなにいいもんなんっすか?あんまりピンときてないっす。
プログラミングを始めたばかりだもんね。それは、変数のありがたみがまだしっくり来てないんだね。
もし、プログラミング言語から変数という機能をなくしたら、僕はプログラムできる自信ないよ。そのメリットを伝えておこうかな。
実はC言語という言語ではメモリにアクセスする際に、変数を使うことなくアクセスすることが可能です。これは他の言語ではなかなかできないことなんです。
しかし、「変数」を使用することで、開発者は様々な利便性を得ることができます。
これらのメリットを順に説明しましょう。
変数名が表現する情報の識別
メモリとは数値しか覚えることができません。しかし、数値を操作するプログラムは人が作り、読むものでもあります。
変数にラベルという適切な名前を付けることで、開発者はメモリに記憶された数値がどのような情報なのかを表現し、読み取ることができます。
メモリに名前が付けられるということは、プログラムを管理する開発者にとって大きなメリットなのです。
しっかりとした変数名を付けることは、次にそのプログラムを読む人に対する優しさなのです。
皆さんは変数を我が子と思ってちゃんとした名前をつけてあげてください。どんな情報を管理してほしいかを名前で表してあげることです。
変数によるメモリへの簡易アクセス
本来、メモリは「番地」という場所を指定して、読み書きが必要なハードウェアなんです。
そのため、メモリへの読み書きは実は面倒なものなのです。しかし、変数を使用したプログラムはメモリの番地を意識することなく、簡単にメモリへ数値の読み書きができます。
#include <stdio.h>
int main(void)
{
// numラベルをメモリに貼る
int num;
// numラベルのメモリに100を保存
num = 100;
return 0;
}
これは読み書きしたいメモリ場所を番地ではなく、ラベル名により特定できるため可能なのです。
「ラベル」によって簡単にメモリにアクセスできる視点って、「変数」と「メモリ」の関係性がわかってないと理解できないことなんです。すごく重要なことなんです。
変数ラベルによるメモリの占有権の取得
占有権の取得は、メモリを扱う上で非常に重要な仕組みです。
メモリとは広大な記憶領域であり、誰が・いつ・どの場所のメモリを使っているのか管理されていないと、保存していた数値が知らない間に書き換わったなんてことが発生するのは想像できるでしょう。
変数におけるメモリの占有権は、ラベルがついている間は有効になっています。
つまり、ラベルがついているメモリ領域は、他のラベルを付けられないということになります。
変数のラベルをどの場所のメモリに対して割り付けるかは自動で判断されるため、皆さんが意識する必要はりません。新しい変数を定義をすると占有されていないメモリに自動的にラベルが貼られます。
「変数」を作ることは、様々なメリットがあるということです。だからこそみんな変数を作るのです。便利だからこそ使われるんですよ。
Q&A:変数に関するよくある質問
変数に関するよくある質問に答えますよー!
Q:変数のデータ型はなぜこんなにたくさんあるのか?
変数のデータ型ってたくさんありすぎっす。選ぶの面倒だから統一してほしいっす。一番でっかい「long型」だけあればいいっすよ。
面倒な気持ちはわからないでもないけど、考え方が極端だね。選ばなきゃいけないじゃなくて、選べるって考え方に切り替えるといいかもね。
メモリというハードウェアは資源であり有限です。
C言語は開発者に対してメモリを無駄なく・効率よく管理してくださいというスタンスで設計されています。特に組み込み開発では、メモリ資源が少ないこともあり、不必要に「long型」でメモリを使用されると資源不足になりがちです。
例えば、1バイトと4バイトではたった3バイトしか差がないと感じます。しかし、データの数が1000個あったら、3000バイトもの差が生まれてしまいます。
このようにデータ数を大きくイメージすると、「long型」だけあればよいとはならないのです。
Q:int型という環境依存のサイズとは何なのか?
int型の解説で変なの見つけたっす。「環境依存」って書いてあるっす。サイズが環境依存ってどういうことっすか?
int型だね。うん、それは開発する環境によっては、サイズが変わることがあるデータ型なんだよ。
int型というデータ型は、対象のCPUにとって一番演算効率のよいサイズが適用されます。
多くのパソコン環境では4バイトであることが多いです。組み込み開発で使うCPUは低スペックであることも多く、int型が2バイトである場合もあります。
このように「int型」は動作環境によってサイズが変化する、ということは知っておくとよいでしょう。
Q:変数名の付け方に悩む。何を基準に名付ければいい?
変数に名前を付けるときに悩むっす。我が子のように名付けろって言われても、我が子なんていないっすから気持ちわかんないっす。
我が子のようには例えだから気にしないで。名前の付け方は最初はまじめな子ほど悩むんだよね。悩んであげるくらい真剣に考えてくれているのはうれしいね。
変数名を付けることに悩むことはよくあることです。
まず考えるべきなのは、その変数に「どんな情報を管理してほしいか?」を明確にすることです。多くの場合、その情報名を英訳した名前にします。
変数名を決める際に大事なことは、
別の人がプログラムを見たときに、どんな名前なら情報の判別がつくか?
という第三者の視点を意識することです。
Q:ローカル画面で変数の値がとんでもない値になってます。なんで?
ローカル画面で変数の値を確認してたっす。そしたらとんでもない数値が入っていたっす。こんな数値入れた覚えがないっす。壊れたんすか?
いいところに気が付いたね。しっかりローカル画面を使いこなしてるのがわかるよ。この数値はメモリにもともと入っていた偶然の数値だね。
この現象に気づいた方は、しっかりとデバッガを使った方なのでしょう。図に示された通り、確かにおかしな数値が表示されていますね。
「変数定義」を行うとメモリに対してラベルが貼りつけられる、と説明しました。しかし、メモリはラベルが貼られる前から、何らかの値をすでに持っています。
つまり、
値を持っているメモリに対して、ラベルが後から貼られる
のです。
つまり、このおかしな値はラベルを貼る前に偶然メモリが持っていた値が表示されているということです。
もちろん、この値を使うことは意味がないので、正しい値を「初期化」もしくは「代入」にて格納してあげる必要があります。
メモリと変数の関係性を正しく理解していると、このような不可思議な現象にも理由があることが理解できます。
だからこそ、変数を理解するにはメモリの理解が欠かせないのです。
課題:変数の作り方と使い方を学べたかを確認しよう
課題1
課題内容
「5」と「3」の数値を四則演算した結果を表示せよ。
例として加算は次のようにすることで出力ができる。
printf("加算:%d\n",5 + 3);
出力期待結果
加算:8
減算:2
乗算:15
徐算:1
余り:2
#include <stdio.h>
int main(void)
{
printf("加算:%d\n", 5 + 3);
printf("減算:%d\n", 5 - 3);
printf("乗算:%d\n", 5 * 3);
printf("徐算:%d\n", 5 / 3);
printf("余り:%d\n", 5 % 3);
return 0;
}
四則演算は大丈夫でしょう。
余りを求める「%」の使い方が特徴的ですね。この「%」って案外使うシーンあるんです。覚えておいて損はないですよ。
課題2
課題内容
次のデータ型の変数を定義せよ。
データ型 | 変数名 | 初期値 |
---|---|---|
char | tmp1 | 120 |
short | tmp2 | 23456 |
long | calc | なし |
tmp1とtmp2を加算した結果をcalc変数に格納し、calc変数の値を表示せよ。
出力期待結果
calc:23576
#include <stdio.h>
int main(void)
{
char tmp1 = 120;
short tmp2 = 23456;
long calc;
calc = tmp1 + tmp2;
printf("calc:%d\n", calc);
return 0;
}
変数の基本的な使い方です。初期化と代入の違いを覚えていますか?
tmp1とtmp2が「初期化」で、calcが「代入」になってますね。しっかりと違いを意識しましょう。
課題3
課題内容
次のデータ型の変数を作成せよ。
データ型 | 変数名 | 初期値 |
---|---|---|
int | self | 50 |
自己代入によりself変数に30の値を加算し、self変数の値を表示せよ。自己代入は省略形を利用すること。
出力期待結果
self:80
#include <stdio.h>
int main(void)
{
int self = 50;
self += 30;
printf("self:%d\n", self);
return 0;
}
足し算の自己代入の問題ですね。自己代入は省略形の記法に慣れておくとよいです。多くのC言語開発者は省略形を好んで使います。
課題4
課題内容
次の変数を定義しインクリメントした結果を表示せよ。さらにもう一度インクリメントした結果を表示せよ。
unsigned char num1 = 254;
出力期待結果
255
0
#include <stdio.h>
int main(void)
{
unsigned char num1 = 254;
// 1回目
num1++;
printf("%d\n", num1);
// 2回目
num1++;
printf("%d\n", num1);
return 0;
}
オーバーフローすると最大値255から最小値0へ変化します。この現象は格納範囲の数値がリンク状に周回しているイメージなんです。
0と255は一番遠い関係に見えますが、実はお隣さんなんですね。
課題5
課題内容
次の変数を定義し、デクリメントした結果を表示せよ。さらにもう一度デクリメントした結果を表示せよ。
unsigned char num1 = 1;
出力期待結果
0
255
#include <stdio.h>
int main(void)
{
unsigned char num1 = 1;
// 1回目
num1--;
printf("%d\n", num1);
// 2回目
num1--;
printf("%d\n", num1);
return 0;
}
最小値0からデクリメントすると最大値255に変化します。これもリング状になっているものとすれば自然な現象ですね。