#6 newの魔術

 プログラムをやったことある人なら知っている…..かもしれないnew演算子。JavaやC#、C++とか、そのほかいろんな言語を触ると出てくるnew。これのすごさについて語りたい。

テキスト

Mikan本様、いつもお世話になっております。

そもそもnew演算子とは

class Hoge
{
  ...... クラスの内容......
}

Hoge hoge = new Hoge();

 new Hoge()のnew。これがnew演算子。といきなり具体例を出したですが、どうでしょう。息を吸うように使っているのでは?
 いちいちnewを書くのが面倒くさいって?いやいや、こいつの素晴らしさたるや!

プログラマーではない人への例え話チャレンジ

 チャレンジなんで、わからなかったら許してください。ちゃんとわかりたい方、あなたもプログラム始めましょう!!

 さてヤケクソの言い訳はともかく、プログラムでは変数というものを使います。変数というと、数学で使ったやつのイメージが強いが、プログラムの変数は少し違うイメージ(を私は持っている)。
 プログラムで言う変数とは

データの保管場所

である。この変数にデータを入れておくと、プログラムの中で何回も使いまわせてすごく便利。え…それって、どういうこと?


変数のイメージ

 さてここで、図を使ってみる。手書きかつ汚くて少々見づらいがご容赦を。これが変数のイメージ図。

int a = 10;

 図にもあるこのプログラムは、整数型変数 a に10を入れてねという命令。それを図にしたのが右側。こんな感じでコンピューターが処理してくれる。
つまり

  1.  intの大きさの箱を作る

  2.  10を持ってきて箱に入れる。

という2段階で変数を作る。そこまで難しい話ではないと思う。しかし、『intの大きさって何?』

箱(変数)の大きさの重要性

 この大きさは案外重要なところ。これを理解するためには、このマガジンの4つ目、メモリ管理のお話で書いたことを復習します。
(詳しく読みたい人は拙い文ですが、以下をどうぞ。)

 箱の大きさと関係してくるお話としては、メモリ・CPUにはデータと処理の間に区別がないというくだりです。だからメモリ管理ではどこが処理で、どこがデータのかチェックしないといけないわけです。
 この「どこが何を表しているのか」を管理するために大きさが必要なわけです。つまり「この場所は○○というデータが入ってるよ」というのを、プログラムがコンピューターに教えるわけです。


メモリ確保!!

とこんな感じでメモリ上に変数aを置くわけです。この時aの大きさがわからないと、どれくらい場所を取ったいいのかわからなります。だから、箱に大きさをつけて変数を作るわけですよ。

結局、箱の大きさとは?

 さて、変数は箱。この箱の大きさは重要だ!と主張してきたわけですが。重要性は分かった。が、実際のところ箱の大きさってなんだよ!というお話を。 復習にもう一度、最初のプログラムを。

int a = 10;

これ。このintが箱の大きさなわけです。みなさい、intが箱の大きさなんです。この箱の大きさをデータ型といいます。箱の大きさは、実はデータの種類を表したものなんです。

 さっきのメモリ管理の話、データと処理に区別がないという話。実は秘密がもう一つ。メモリではデータの種類にも区別がないのです。

 嘘じゃないんです。本当なんですよ!結局、どんな種類のデータでもCPU君から見たら、0と1の集合なわけで。それを区別できるように、ラベルを付けてあげるのがデータ型です。

 データ型を指定してあげることで、CPUに

「ここから、ここまでのデータは○○っていうデータです」

と教えてあげることができます。(CPUはこの命令を元からわかるように設計されてます。)


たぶんこんな感じ。

 データの種類が分かれば、その扱い方もわかる。ので、プログラム先生がCPU君に教えるわけです。これ以上の詳しいことは長くなるので書きませんが、だいたい以下のような種類のデータ型があります。

  • int型:整数の箱(例:1、100、1232、-100)

  • float型:実数の箱(例:1.00、12.25、3.14、-1.414)

  • double型:実数の箱(float型より正確に数字を保管できる)

  • char型:文字の箱(例:a、b、c) 文章は入れられない

  • long型:整数の箱(intより大きな箱。大きな数字も入る)

あともう少しだけあったりなかったり。プログラム言語によりますね。

箱をnewする

 やっとnew演算子のお話。newとはざっくりいうと、箱を新しく作ってねという指示。え、さっきのintの箱を作るときには、new使ってないじゃん。

int a = 10;

そうなんです。実はいままで紹介したデータ型は、基本データ型と呼ばれるもので、基本データ型ではnewを使いません。プログラムが勝手に箱を作ってくれるわけです。

 しかしこのデータ型、プログラム言語によっては自分で作ることができるんです。この自分で作ったデータ型をユーザー定義型と呼びます。ユーザー定義型の中には、クラス構造体があって….
 と細かく話すときりがないので、今回はクラスにフォーカス。クラスとは、『基本データ型の変数と関数をまとめて一つのデータ型としてしまおう』、というものです。

 例えば車について考えてみましょう。クラスを作るには、

  1. 車(クラス)はどんな値を持っているか・知っているべきか

  2. 車(クラス)にはどんな機能があるか

という問いが大切です。

 じゃあ車が知っておかないといけないことは何? 
例えば…

  • ガソリンの量

  • 自分の速度

とかでしょうか。

 車に必要な機能は何?

  • 走る(アクセル)

  • ブレーキ

とかでしょう。細かいことを抜けば。
 上記のデータと機能をまとめて新しい自作の箱、Carクラスを作ります。こんな感じで。

class Car
{
  int speed; // 速度
  int gas; // ガソリン量

  // アクセル機能
  void accelerate()
  {
    speed += 1; // speedに1を足す(加速)
  }
  
  // ブレーキ機能
  void breaking()
  {
    speed -= 1 // speedから1を引く(減速)
}

言語によって書き方はいろいろありますが概ね、似たようなものでしょう。

 さてこのCar型の変数を作るとき、newの真価がまみえるわけです!

Car car1 = new Car(); // Car型の変数、car1を作るで!!

見えますか!!すごいでしょ、new!!!

え…newの必要性がわからないって?
それは本筋に戻ってのお話。

(余談)クラスがないと大変だよ

 とここで一息。だいぶ文章も長くなってますし、新しい知識盛りだくさんなんで。
 さらっとクラスについて紹介しましたが、そもそもなんでクラス・構造体なんているんだって話しです。読者の中には

「基本データ型をまとめるだけなら、別に個別の変数があればいいのでは」

と思った方もいるのでは?
 しかしこれは大きな勘違いです。残念ながら、人の脳みそはそこまで大きなものを扱えません。変数が増えてくると、何がなんだかわからなくなります。例えば、さきほどの車を複数台プログラムで扱ってみましょう。

int gas_car1 = 10;
int gas_car2 = 22;
int gas_car3 = 19;

int speed_car1 = 50;
int speed_car2 = 100;
int speed_car3 = 45;

int accelerate(int speed)
{
  return speed + 1;
}

int breaking(int speed)
{
  return speed - 1;
}

試しに3台の車を扱うプログラムを書いてみました。クラスがないときは、こんな感じで各項目を書きまくらないといけないです。
 しかも、変数の宣言のし忘れなどで、すべての車がspeedとgasを持っているとも限らない。めちゃくちゃ混乱します。 

 これがクラスを使うと….

Car car1 = new Car();
Car car2 = new Car();
Car car2 = new Car();

だけで済むわけです。しかも設計図にクラスを使っているので、必ずspeedとgasは持っているし、加速と減速も確実に使えます。

 こんな風にクラスがあることで、人間がコードを理解することが格段に簡単になります。

 ただクラスが万能、というわけではないとは思います。実際、関数型言語と呼ばれる、全く違う発想を採用している言語なんかもあります。
 ただ、便利なのは間違いないですね。

What is ”new"?

 さて、いよいよnewの役割について解説したいと思います。ここで重要になってくるのが、スタックヒープです。どちらもメモリの使い方の名前。先人たちが工夫を凝らした、素晴らしいものです。

 まずスタック。データをFirst In First Outで整理するメモリの使い方。これは、箱を垂直に積み上げるイメージ。食器をしまう時とか、垂直に重ねますよね。アレをイメージしていただけると、ばっちりです。

 どんどん上に重ねていくので、最初に入れたデータが最初に取り出されます。この仕組みをうまく使うと、小さなデータを高速で操作することができます。小さなデータ――つまり、基本データ型をスタックで管理します。

 一方のヒープ。これは自由に確保できて、自由に使うことができる空間です。順番とか、大きさとか細かいことはともかく考えない。とりあえずしまい込んじゃえ!という発想。
 大きなデータも扱えるのですが、アクセスしづらいのが難点。具体的にはポインター、すなわちデータがある住所を直接に指定することでしかアクセスできません。

 クラスなどのユーザ定義型は、複数の基本データ型を組み合わせるので、大きなデータとなってしまいます。つまり、ヒープで管理します。
 そしてスタックには、クラスのポインターを保管して、必要な時に取り出す。何か遠回りな感じがしますが、これでユーザ定義型を扱えます。いやー最初にクラスを考えた人、すごいなぁ。工夫が凝ってる。

 このヒープに新しくデータの場所を確保するのが、new。

 newの処理が行われると、ヒープの大きさを変更してデータを格納してくれるわけです。コンピュータの処理の中で、ヒープの大きさはメモリのポインタで管理しています。ヒープの一番後ろの場所をポインタで示していて、それより手前のメモリはいくらでも使ってOKという方式です。
 行列の最後尾に立ってる看板みたいなものですね。この看板を後ろにずらして、データをしまう準備をしてくれるのが、newです。


newの役割

 こうやって文章で書いてみると、そんなに凄そうには見えないな…. けど実装を自分でしてみると、これのありがたさが身に沁みます。何も考えずにクラスが使えますから!!
 それに、この方法を考えた人のすごさも感じます。後知恵バイアス抜きでこれを編み出すのは、想像以上に難しい気が….

 いやでも本当にすごいじゃん。メモリの中のことを(ある程度)考えなくて済むんですよ!!ありがたやー。

 なんか文だとうまく伝えきれてないけど、これ僕的にはめちゃくちゃ革新的で、刺さりました。現場からは以上です。






この記事が気に入ったらサポートをしてみませんか?