- プログラミング言語の「データ型」って何?
- どうして型を書かなきゃいけないの?
「データ型とはデータの種類のこと」とよくいわれます。
でも、この説明だけでは、よくわかりませんよね。
この記事ではプログラミングの「データ型」に関する疑問にわかりやすくお答えします。
プログラミングの「データ型」を理解するには「コンピュータの仕組み」から理解するのが近道です。
ここでは「コンピュータがどのようにデータを記録しているのか」という基本から詳しく解説します。
わかりやすく説明するので、ぜひ最後まで読んで、データ型を完全に理解してください。
データ型とは何か?
プログラミング言語における「データ型」とは、
コンピュータ内部での値の扱われ方を制限するとともに、
変数や関数の引数・戻り値への適用可能性を定義するもの
です。
これだけではよくわかりませんよね?
順を追って詳しく説明しますが、まずはざっくりと説明しましょう。
まず、プログラミング言語が扱うデータには「数値」や「文字列」「日付」などの種類があります。
このようなデータの種類を表すのが「データ型」です。
データ型は、コンピュータがそのデータをどのように扱うべきかを定義します。
また、一部のプログラミング言語では「変数」や「関数の引数・戻り値」を宣言するときに、データ型を書かなくてはいけません。
たとえば、このように書きます。
int age = 10; // 整数型
string name = "Taro"; // 文字列型
// 引数と戻り値が文字列型
string Hello(string name) {
return $"Hello {name}";
}
このように書くことで「変数ageには整数しか代入できないよ」「関数Hello()は引数に文字列を受け取り、文字列を返すよ」ということを定義できます。
データ型は変数や関数の引数・戻り値に入るべき値を定義するのです。
これがデータ型についてのざっくりとした説明ですが、これだけではピンとこないかもしれません。
そこで、ここからは「どうしてデータ型が必要なのか?」を説明します。
データ型が必要な理由を知れば「データ型とは何か」を完全に理解できます。
わかりやすく説明するので、頑張ってついてきてください。
どうしてデータ型が必要なのか?
プログラミング言語にデータ型が必要な理由は3つあります。
- コンピュータに適切な大きさのメモリを確保させるため
- コンピュータにデータの処理方法を限定させるため
- 開発をしやすくするため
それぞれ詳しく説明しましょう。
1. コンピュータに適切な大きさのメモリを確保させるため
データ型の役割としてもっとも重要なことが「コンピュータに適切な大きさのメモリを確保させる」ことです。
詳しく説明しましょう。
データと変数
プログラミングでは「数値」や「文字列」「日付」など、いろいろな種類のデータを扱います。
それらのデータにラベルをつけたものを「変数」といいます。
たとえば、
name = "Taro"
というプログラムでは「”Taro”」という文字列に「name」というラベルをつけています。
このとき、nameを「変数」といいます。
このように「変数」とデータを「=」で結び、データにラベルをつけることを「代入する」といいます。
ここでは、変数「name」に文字列「”Taro”」を代入しているというわけです。
コンピュータはメモリにデータを記憶する
さて、変数には「型」があります。
「型」があることで、変数に入りうるデータの種類や大きさをコンピュータが理解できます。
どういうことでしょうか?
ここで、コンピュータの内部に目を向けてみましょう。
コンピュータは、変数に代入した値を「メモリ」に記憶しています。
メモリとはデータを記憶する部品です。
プログラマーが変数にデータを代入したとき、コンピュータはメモリにデータを書き込みます。
反対に、変数を使うときは、メモリからデータを読み込みます。
実は、メモリのデータを読み書きするとき「データ型」がないと困ったことになるのです。
コンピュータは「0」と「1」しか扱えない
コンピュータは電気で動く機械です。
そのため、電気が「流れていない(OFF)」か「流れている(ON)」の2つの状態しか扱えません。
物理的に「電気が流れていないよ」「電気が流れているよ」という2つのことしか理解できないのです。
プログラマーがコンピュータについて考えるときは、電気の「OFF」と「ON」を数値の「0」と「1」に置き換えて考えます。
そのほうが考えやすいからです。
「OFF」の状態が「0」、「ON」の状態が「1」です。
このように「0」と「1」だけですべての数を表す方法を「2進法」といいます。
コンピュータでは「2進法」をよく使います。
要するに、コンピュータは物理的には「0」と「1」の2つの値しか扱えないのです。
「0」と「1」だけで大きなデータを記憶している
コンピュータは「0」と「1」の2つの値しか扱えないのに、どうしてもっと大きな数字を扱えるのでしょうか?
ここで2進法の考え方が役に立ちます。
コンピュータのメモリを「たくさんのスイッチが1列にならんだもの」とイメージしてみましょう。
それらのスイッチの「OFF(0)」と「ON(1)」の組み合わせで、データを記憶するのです。
たとえば「x = 7」のように変数を宣言するとしましょう。
このとき「7」という整数は、2進法で「111」と表せます。
以下のように10進数を2進数に変換できるからです。
0 → 000
1 → 001
2 → 010
3 → 011
4 → 100
5 → 101
6 → 110
7 → 111
※10進数から2進数への変換についてはこちらを参考にしてください。
整数7は、2進法で「111」と表せるので、横並びになっているスイッチを順に「ON, ON, ON」とすれば、「7」という値を記憶することができます。
[OFF OFF OFF OFF OFF OFF .....] ← コンピュータのメモリ(たくさんのスイッチが並んだもの)
↓ 「x = 7」と変数を宣言する
[ON ON ON OFF OFF OFF .....] ← 整数7は2進法で「111」なので、左から順に「ON ON ON」とする
↓ONを1に、OFFを0に置き換えて考える
[1 1 1 0 0 0 .....]
↓2進数の「111」は、10進数に変換すると「7」
整数の7が記録されているとわかる
今度は、整数の「5」をコンピュータのメモリに記憶することを考えましょう。
整数「5」をコンピュータのメモリに記憶するときは、メモリのスイッチを「ON OFF ON」にすればよいです。
整数の5は2進法で「101」と表せるからです。
[OFF OFF OFF OFF OFF OFF .....] ← コンピュータのメモリ(たくさんのスイッチが並んだもの)
↓ 「x = 5」と変数を宣言する
[ON OFF ON OFF OFF OFF .....] ← 整数5は2進法で「101」なので、左から順に「ON OFF ON」とする
↓ONを1に、OFFを0に置き換える
[1 0 1 0 0 0 .....]
↓2進数の「101」は、10進数で「5」
整数5が記録されているとわかる。
コンピュータでは、このようにメモリの電気的な状態(スイッチのONとOFF、数値の0と1)を使って、あらゆる種類のデータを記憶できます。
メモリにデータを記憶して読み出す
続いて、複数の変数にデータを代入する場合を考えてみましょう。
はじめに、変数「x」に整数7を代入します。
x = 7
整数7は2進数で「111」と表せます。
なので、メモリの電気的な状態は「ON ON ON」になればいいです。
[ 1 1 1 0 0 0 0 0 .....] ← メモリの状態
ここまでは先ほどと同じですね。
次に、別の変数「y」に整数5を代入しましょう。
y = 5
整数5は2進法で「101」と表せます。
そのため、メモリの電気的な状態は「ON OFF ON」になればいいですね。
メモリはONとOFFのスイッチが一列に並んだ部品なので、途中に区切りなどはありません。
そのため、x = 5 と y = 7を順番にメモリに記憶すると以下のようになります。
[ 1 1 1 1 0 1 0 0 .....] ← メモリの状態
ここではデータを左詰めにして記憶することにします。
コンピュータが変数「x」の値を読み出すときは、メモリの左側から3つの値を読み出します。
左から3つの値は「111」なので、これを10進数に変換すれば、整数7が取り出せます。
[ 1 1 1 1 0 1 0 0 .....] ← 変数xのデータ
続いて、変数「y」の値を読み出しましょう。
変数「y」の値を読み出すには、メモリの左側から3つ(変数xの部分)を飛ばし、4番目のスイッチから3つを読み出します。
[1 1 1 1 0 1 0 0 .....] ← 変数yのデータ
4〜6番目は、2進数「101」 なので、これを10進数に変換すれば、整数5が取り出せます。
これを10進数に変換すると整数「5」になります。
こうして変数「x」の値(整数7)と、変数「y」の値(整数5)を読み出すことができました。
記憶したデータを変更するときの問題点
さて、現在のメモリの状態は以下です。
[ 1 1 1 1 0 1 0 0 .....] ← メモリの状態
左側から1〜3番目に変数「x」の値、4〜6番目に変数「y」の値が入っています。
ここで、変数「x」の値を書き換えてみましょう。
変数「x」に整数「50000」を再代入します。
x = 50000
このとき、コンピュータはメモリの値をどのように書き換えればいいでしょうか?
いま、メモリの状態は以下で、変数「x」に使えるメモリは、左から1〜3番目だけです。
[ 1 1 1 1 0 1 0 0 .....] ← 変数xに使えるのは左から3つだけ
実は、こうなってしまったら変数「x」を書き換えることはできません。
3つのスイッチ(=3桁の2進数)の組み合わせでは「0〜7」までの整数しか表せないからです。
3桁の2進数で扱える数は8個(=2 x 2 x 2)だけです。
この状態になると、変数「x」に8以上の値を再代入することはできないのです。
データ型を使って必要なメモリを確保する
以上の問題は、整数7をメモリに記憶した後、その真横に整数5を記憶して、メモリを埋め尽くしてしまったことが原因でした。
すぐ隣のメモリが、別の変数のために使われてしまっているので、もっと大きな数字に書き換えることができなくなってしまったのです。
実は、こういった問題を避けるために「データ型」があります。
プログラミング言語の「データ型」があれば、必要なメモリをあらかじめ確保できるのです。
たとえば、「整数型(int型)」というデータ型では、変数を宣言したときにあらかじめ「2の32乗個分のメモリ領域を確保します。
int x = 7;
というプログラムを書いたとき、コンピュータは以下のように2の32乗個分の隣り合ったメモリ領域を変数「x」専用のメモリとして確保します。
[1 1 1 0 0 0 0 0 0 0 0 0 ...... 0 0 0 0 0 00 0 0 ] ← 黄色部分「2^32個」のメモリは変数「x」専用
こうすることで、整数型である変数xは「2^32」種類の値を自由に使えるようになります。
こうしておくと「int x = 7;」を宣言した後に「int y = 5」を宣言し、それから「x = 50000」を再代入しても、変数yに影響が出る心配がありません。
変数xがあらかじめ確保しているメモリの幅(黄色部分)が十分にあるからです。
[ 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 ..... 0 0 0 1 0 1 0 0 0 0 .... 0 0 0] ← x = 7, y = 5
[ 1 1 0 0 0 0 1 1 0 1 0 1 0 0 0 0 ..... 0 0 0 1 0 1 0 0 0 0 .... 0 0 0] ← x = 50000, y = 5
このように、変数にデータ型をつけることで「その変数がとりうる値の大きさ」をあらかじめコンピュータに教えることができます。
使うメモリの大きさをあらかじめ教えて確保することで、データをスムーズに読み書きできるようになるのです。
こういうわけで「データ型」は、プログラミング言語になくてはならないものなのです。
2. コンピュータにデータの処理方法を限定させるため
データ型には、コンピュータにデータの「取り扱い方」を教える役割もあります。
私たちが普段何気なく使っているデータには種類があり、種類によって扱い方も異なります。
たとえば、「数値」と「数値」は足し算できますが、「数値」と「日付」は足し算できません。
「2023年2月20日」と「整数7」を合計してくださいといわれても、できません。
概念的に無理だからです。
しかし、コンピュータはこのような概念を理解できません。
なぜなら、コンピュータが扱えるのは数値の「0」と「1」だけだからです。
仮に「2023年2月20日」という日付が、2進法で「1001101001011000001001100」と表せるとしましょう。
整数7は、2進法で「111」と表せます。
コンピュータには、この2つの値を足し算することができます。
合計は「1001101001011000001010011」です。10進法で「20230227」になります。
しかし、本来、足し算できるはずのないものを足し算されても、私たちは困ります。
たとえば「2023年2月20日」に「整数20」を足して、その合計は「2023年2月40日」ですといわれても、そんな日付は存在しませんよね?
人間としては、コンピュータにも「日付は日付」「数値は数値」として扱ってほしいわけです。
変数に「データ型」をつけることで、コンピュータにその制限を教えることができます。
整数型と日付型は足し算できない、という制約を与えることができるのです。
データ型を指定することで、コンピュータに「互いに計算できないデータ」を計算しないよう制限できます。
このように「データの処理方法を限定する」という意味でもデータ型は重要な役割を果たしています。
3. 開発をしやすくするため
ここまでは「データ型があるとコンピュータにとって何がいいのか」という話をしてきました。
一方、データ型があると人間にとっても都合がいい場面があります。
それは「変数にどんな値が入るのかわからないとき」です。
たとえば、新人プログラマの太郎くんがこんな関数を書きました。
reserve(seat, value, movie)
これは映画のチケット予約ができる関数です。
ある日、太郎くんの同僚のボブが
「映画のチケット予約をこのアプリに組み込みたいけど、開発する時間がないんだ….」
と困っていたので、太郎くんはこのreserve関数を教えてあげました。
同僚のボブは「開発の手間が省けた!」と喜びました。
しかし、いざ開発をしようとすると関数の使い方がわかりません。
reserve(seat, value, movie)
ボブ: 「seatって何?座席の数?座席コード?valueって何??movieは文字列?IDなの?ところで、この関数は戻り値あるの?」
困ってしまったボブは、太郎くんの関数を使うのをあきらめて、自力で開発することにしました。
このように、変数に入る値がわからないと使いにくいことがあります。
こんなとき、データ型があれば使い方のヒントになります。
たとえば、太郎くんの関数に次のようにデータ型が書いてあれば、ボブは救われたかもしれません。
bool reserve(
SeatCode seat,
DateTime value,
MovieId movie
)
データ型を見れば「seatにはSeatCode型の値が入る」「valueはDateTime型なので、おそらく上映時間が入る」「movieはMovieId型なので映画のIDを渡せばよさそう」と推測できます。
このようにしてデータ型が役に立つのです。
もちろん、もっと上手い変数名をつけて、関数の使い方もコメント(docstring)や文書にまとめたほうがいいのは、いうまでもありません。
しかし、変数にデータ型が明示されているだけで開発はやりやすくなるのです。
特に、大規模なチームでの開発になると、変数名をつけるのが下手なプログラマや、文書を残すのが苦手なエンジニアは一定数現れます。
すべての変数にデータ型を明示することが強制されていれば、データ型をヒントに開発ができます。
このように、データ型には開発をしやすくする役割があるのです。
データ型にはどんな種類があるか?
さて、ここでは、データ型の種類をカンタンにまとめます。
データ型は大きく分けると「組み込み型」と「ユーザー定義型」の2種類があります。
組み込み型
組み込み型とは「プログラミング言語に最初から組み込まれているデータ型」です。
ほとんどのプログラミング言語には、共通のデータ型が存在します。
プログラミング言語によって型の名称は異なりますが、よく似ています。
ここでは、有名なプログラミング言語には、たいてい組み込まれているデータ型を紹介します。
文字型/文字列型(char / str, string, Stringなど)
文字や文字列を扱う型です。
char / str, string, Stringなど表記されます。
数値型(int, float, Numberなど)
intやfloatなど、数値を表現するデータ型です。
intは整数型、floatは浮動小数点数型などと呼ばれます。
JavaScriptのNumberのように整数、小数を問わずひとつの型で表現しているものもあります。
整数型(int)
-1, -2, 0, 1, 2 …. のような整数を扱う型です。
C言語のshort int, long intのように扱える範囲が異なる複数の型がある場合もあります。
小数を扱う数値型(float, Decimalなど)
小数を扱う数値型には、浮動小数点数型、固定小数点数型、十進型などがあります。
いずれも小数をコンピュータで表現するための型ですが、それぞれ実現方法が異なり、扱える値の範囲や精度、計算の速度などに違いがあります。
論理型(bool, boolean, Booleanなど)
真偽値(true, false)を表現する型です。
Bool, bool, Booleanなどの表記があります。
void型
何もないことを表す型です。
言語によってはnullやNoneなどの名称で同様のことを表現するものあります。
配列型(Array, Listなど)
複数の変数や値をひとつにまとめて扱うための型です。
ArrayやListなどの名称がつきますが、内部の実装方法は言語によって様々です。
日付型(Date, DateTimeなど)
日付を扱う型です。
言語によっては、日付と時刻をひとつにまとめて扱う型も存在します。
ユーザー定義型
クラス(class)や構造体(struct)のように、複数の型をひとつの型にまとめて定義する型です。
ユーザー定義型を上手く使うことで、開発がしやすくなったり、堅牢なシステムを作りやすくなります。
型宣言と型付け
型宣言が必要な言語と不要な言語
変数や関数の引数に型を明示することを型宣言といいます。
str name = "Taro" ← 「str」が型宣言
型宣言は、必須の言語とそうでない言語があります。
例として、C言語やJavaは型宣言が必須ですが、Pythonでは型宣言は必須ではありません(型ヒントを書くことは可能です)。
JavaScriptでは型宣言ができません。
静的型付け言語
プログラミング言語は「静的型付け言語」と「動的型付け言語」に分類できます。
静的型付け言語とは、プログラムの実行前、つまりプログラムを書いた時点かコンパイルするタイミングで型が決まる言語です。
C言語やJavaは静的型付け言語です。
動的型付け言語
一方、動的型付け言語とは、プログラムの実行時に変数に代入された値によって自動的に型が決まる言語です。
PythonやJavaScriptは動的型付け言語です。
静的型付け言語と動的型付け言語
一般的には、静的型付け言語のほうが、安全で堅牢なシステムを作りやすいといわれています。
開発段階で厳密に型を定義するので、実行時にトラブルが起きにくいためです。
また、型の定義を強制できるため、大規模な開発に向いているといわれます。
一方で、動的型付け言語は、開発速度を重視する場合にメリットがあります。
静的型付け言語では厳密に型を書かなくてはならないため、プログラミングの自由度が下がります。
プログラミングの際に考えることが増えるため、開発に時間がかかってしまうのです。
動的型付け言語であれば、型をある程度「雑に」書いても動作するため開発時間を短縮しやすくなります。
型ヒント
動的型付け言語の多くは型を書く必要がありません。
しかし、型を明示できる言語もあります。
たとえば、Pythonにはバージョン3.5以降「型ヒント(Type Hints)」の機能が使えます。
これにより、変数や関数の引数、戻り値に型を明示できるようになりました。
プログラミングの自由度は保ちながらも、型をヒントにした開発がしやすくなっています。
まとめ
この記事では、プログラミング言語における「データ型」について解説しました。
あらためてデータ型の役割は以下の3つです。
- コンピュータに適切な大きさのメモリを確保させること
- コンピュータにデータの処理方法を限定させること
- 開発をしやすくすること
コンピュータのメモリの仕組みから説明したので、少し難しかったかもしれませんが、一度理解できるとデータ型の存在意義がよくわかると思います。
理解できなかった方も、繰り返し読んでみて、ぜひ雰囲気だけでもつかんでおいてください。
いつか役に立つことがくるはずです。
コメント