コンテンツにスキップ

Week 1 (2020/10/1)

今日やること

  • repl.itを用いた環境構築
  • ターミナルおよびコマンドの解説
  • コンパイルの実行
  • 基本のデータ型
  • GitHubの準備

プログラミングとは

本講義では、C言語を題材に、プログラミングの基本の基本を勉強します。 WindowsやMacといったOS、FirefoxやEdgeといったブラウザ、ポケモンといったゲームなど、 世の中の全てのソフトウェアは、プログラミングという作業によって作られています。 プログラミングを習得することで、皆さんはコンピュータ上で自由にデータを処理したり、 計算を行ったり、検索エンジンを作ったり、ゲームを作ったりすることが出来るようになります。

プログラミングは、現実の問題を解決するために必須の技術です。 皆さんはこれから先、研究という活動を通じて現実の問題を解くことになります。 例えば、もしあなたが電子回路の研究をするなら、 ある回路素子の電圧値を継続的に測定し、そのデータから数学的な関係を導出したいかもしれません。 その際、データをまとめて関数をフィッティングする作業は、プログラミングによって実現されます。 もしあなたが情報系の研究をするなら、カメラで撮った画像をまとめて、物体の三次元形状を復元するかもしれません。 そのために線形方程式を解くのなら、それはプログラミングによって行います。 このように、現実の問題を解く上で、プログラミングは必ず必要になります。 それは研究だけに限らず、この先みなさんが直面するありとあらゆる問題について、プログラミングは重要な役割を果たします。 特に本講義では電気系の最初の情報系講義なので、どの分野でも必要となる基本的な内容を教えます。 なるべくわかりやすく解説しますので、難しければいつでも質問してください。

C言語

世の中には様々なプログラミング言語がありますが、本講義ではC言語を扱います。 皆さんは教養の講義で既にPythonという言語に触れたと思います。PythonはCと比較すると高レベルな言語です。 逆に、CはPythonに比べると低レベルな言語です。ここで高いとか低いと言っているのは、どちらが簡単だとかいうわけではありません。 より低レベル(低水準、定レイヤ)であるとは、よりコンピュータのハードウェアに近いレベルを容易にコントロールすることが出来る、という意味です。 具体的に言えば、コンピュータのメモリなどに直接アクセスできるのがC言語です。 よって、メモリのレイアウトを意識して高速なコードを書いたりすることができます。 逆にPythonでは、そういうローレベルな作業は隠蔽されており、通常はユーザは意識しません。 Pythonに比べると、Cは融通が利かず、コード分量が多くなり、また難しいという印象を持つと思います。 しかし、Cを理解することは、 コンピュータの内部の動作の本質を理解することにつながります。 また、同じ処理を行うとき、ローレベルな言語のほうが性能が出る場合が多いです(高速、低メモリ消費)。

C言語を習得すれば、ほかの言語を学ぶことは簡単です。 特に、本講義の最大の目的であるポインタという概念を理解すれば、他の言語を学ぶことは怖くありません。 本講義では、全員がポインタを理解することを目的とします。 世の中の言語には流行廃りがありますが、 C言語を取得することの重要性は松井が学生のころから今まで全く変わっていません。

repl.itを用いた環境構築

それでは早速始めましょう!まずは環境を構築します。例年であれば皆さんは情報教育棟で備え付けのコンピュータでプログラミングをしてもらうのですが、 今年はコロナウイルスの影響で対面講義が出来ないため、完全にリモートで講義を行います。 よって、各自の環境をそろえることが出来ないため、ブラウザベースでプログラミング環境を構築します。

  1. repl.it というサービスを利用します。まずはウェブサイトに移動してください。
  2. 右上の「Start Coding」のボタンを押してください。Languageを選ぶ画面になるので、「C」を選択してください。そのあと「Create repl」を押してください。
  3. こうすることで、コンピュータが新しく一台生成されるというイメージです(このコンピュータ1台のことを「repl」と呼んでいるようです)。そして、そのコンピュータに、ブラウザからアクセスしている、という状態だと思ってください。今後、この画面でプログラミングを行っていきます。

上のような画面に辿り着くはずです。

  • ここで、左側は現在開いているフォルダの中身を表示しています。windowsでいうエクスプローラ、MacでいうFinderです。 ここでは、「main.c」というファイルが一つあります。
  • 真ん中の画面はエディタです。windowsでいうメモ帳です。ここでは選択されているmain.cの中身が表示されています。 この真ん中の部分でファイルを編集していきます。
  • 右側はターミナルというものです。ここにコマンドを打ち込み、様々な処理を行います。

コラム

ちなみに、repl.itは上記のようにアカウント登録無しで使うと、完全に使い切りの環境になり、タブを閉じると環境が消えます。 無料アカウントを作っておくと、プログラミングした内容をアカウント上に保存することが出来ます。 なのでうっかり閉じてしまわないように、アカウントを作っておくことを強くオススメします。 注意として、2020/9現在、無料アカウントの範囲では、repl.itでのコードは自動的にpublic設定となります。そのため、全コードは自動公開されてしまい誰でも見ることが出来てしまいます。 そのため、week2では皆さんのアカウントをeducationチームに招待して、private設定で使えるようにしますので、今日の最後のインストラクションに従ってください。 educationチームに入れる関係上、repl.itのアカウント名は他の学生にも開示されることになりますので、注意してください。

やってみよう(3分)

repl.itにアクセスし、上記の画面に辿り着きましょう

ターミナルとコマンド

次に、ターミナルとコマンドについて勉強しましょう。 ターミナルとコマンドは、プログラミングを行ううえで避けては通れない概念です。 ターミナルというのは処理を受け付けたり結果を表示する画面です。そこにコマンドという命令を書き込むことで、処理を実行します。 普段みなさんがコンピュータ上でファイルを操作したりアプリケーションを扱うときは、 マウスを使ってポインタを動かしたり、アイコンをクリックすると思います。このように、視覚的に物事を操作する仕組みをGraphical User Interface (GUI)といいます。 一方で、マウスを用いず、キーボードによるテキスト入力のみで処理を行う方式を、Command-line Interface (CLI) あるいはCharacter-based User Interface (CUI)と言います。 CLIでは、ターミナルにコマンドを打ち込むことで様々な処理を行います。

世の中には非常に多くのコマンドがあります。今日は基本となる以下のコマンドを勉強します。これらは基本の基本なので、何も考えなくても入力できるように覚えてしまうとよいです。

  • ls: List. ファイル一覧の表示
  • pwd: Print working directory. 現在の位置を表示
  • cd: Change directory. ディレクトリの移動
  • cat: Concatenate. ファイルの中身の表示
  • touch: ファイルの生成
  • mkdir: Make directory.ディレクトリの作成
  • mv: Move. ファイルの移動
  • cp: Copy. ファイルのコピー
  • rm: Remove. ファイルの削除

ls

lsとは、現在のフォルダの中にあるファイルを表示するコマンドです。「list」の略です。 ターミナルに以下を入力してください。

$ ls
ここで、$はターミナルの先頭を表す表記ですので無視し、lsとだけ打ち込んでください。全角ではなく、半角英数字です。すると、以下のようなものが表示されます
main.c
現在注目しているフォルダの中にはmain.cしかないため、それのみが表示されます。 ちなみに、現在注目しているフォルダのことを「カレントディレクトリ」と呼びます。 「ディレクトリ」というのはwindowsでいう「フォルダ」と同じ意味です。「現在のフォルダ」という、そのままの意味ですね。

pwd

次に、カレントディレクトリの位置を表示するコマンド、pwdを実行してみましょう。Print working directory の略だそうです。

$ pwd
すると以下のようなものが表示されます(ここの表示内容は、各自の環境によって違います)
/home/runner/IllustriousSatisfiedDowngrade
ここで、一番最初の/(スラッシュ)は、このコンピュータの最も「上」の位置を表します。そこにhomeというディレクトリがあり、 その下にrunnerがあり、その下にIllustriousSatisfiedDowngradeがある、という意味です。 自分は現在そのIllustriousSatisfiedDowngradeというディレクトリにいます。 なので、現在のカレントディレクトリに配置されているmain.cというファイルの位置は、正確に書くと

  • /home/runner/IllustriousSatisfiedDowngrade/main.c

と書けます。このような指定の方式を絶対パス指定と呼びます。これは、/日本/東京/文京区/本郷/家家家のように地名を正確に指定していることに相当します。 一方で、カレントディレクトリは.(ピリオド一個)で表すことが出来ます。これを用いると、上記のmain.cの位置は次のようにも書けます

  • ./main.c (カレントディレクトリの下のmain.cという意味)

この表記を相対パス指定と呼びます。この表記は一意には定まらない(カレントディレクトリに依存する)のですが、簡単にファイルの位置を表現できます。 今いる場所/家家家 という意味です。本郷のラーメン屋の議論をするとわかっている際は、/日本/東京/... などと書く必要がないので、相対パスのほうがわかりやすい、ということです。

cd

次に、ディレクトリ間を移動するコマンド、cdをやってみましょう。Change directoryの略です。 cd 移動先 で移動出来ます。 ここで、..(ピリオド二つ)は、「カレントディレクトリから一個上のディレクトリ」を 表します(../でもいいです)。そこに移動してみましょう。

$ cd ..
lsをを打ってカレントディレクトリのファイルを確認してみましょう
$ ls
IllustriousSatisfiedDowngrade  _test_runner.py
一個上にあがったので、先ほどまでカレントディレクトリだったIllustriousSatisfiedDowngradeが見えますね。また、_test_runner.pyというパイソンファイルもあるようです。 また、現在位置も確認してみましょう。
$ pwd
/home/runner
一個上にあがったことがわかりますね。 ここから、今度は一段下って、最初の位置に戻りましょう
$ cd IllustriousSatisfiedDowngrade
こうして再度lspwdをすると、最初の位置にもどってきたことがわかります。 ちなみに、上のような長いコマンドは全て入力する必要はありません。 ある程度入力したあとにTABキー(Tab)を入力することで、自動補完されます。 すなわちここでは、c, d, スペース、I(アイ), l(エル), 程度を打ったあとにTABキーを押すことで、残りが自動的に入力されます (C+D+Space+I+L+Tab)。あるいは、c, dを押したあとにTABを二度押すと候補がサジェストされるので、その後Iを打ってTAB・・・という風にもできます (C+D+Space+Tab+Tab)。 このTab補完は非常に便利なだけではなくタイプミスを減らすので、覚えておいてください。

ちなみに、repl.itでは、左側のファイルビューアの画面は、常に初期のカレントディレクトリの内容が表示されるようです。

やってみよう(5分)

上記のコマンドを実行し、ls, cd, pwdを使ってディレクトリを色々移動してみましょう。

cat

次に、ファイルの操作に関するコマンドを見ていきましょう。まずは、ファイルの中身を表示するコマンド、catを見ていきましょう。 これはconcatenateの略だそうです。cat 対象ファイル名 で、対象ファイルの中身を表示します。 初期のカレントディレクトリに移動した上で、以下のコマンドを実行します

$ cat main.c
すると以下のような出力になります。
#include <stdio.h>

int main(void) {
  printf("Hello World\n");
  return 0;
}
これは、main.cの中身をターミナル上で表示した、ということです。このようにして、エディア(メモ帳)を開かずとも、ターミナル上でファイルを確認することができます。 同様にして、一個上のディレクトリにある_test_runner.pyの中身も見てみましょう
$ cat ../_test_runner.py
すると、このpythonファイルが以下のように出力されます
import unittest
import unit_tests
import os
import json
from time import sleep

suite = unittest.TestLoader().loadTestsFromTestCase(
    unit_tests.UnitTests)
# === 以下、いろいろ書いてある ===
これはどうやらrepl.itが使用するスクリプトのようですね。大事そうなので、このファイルには触れないことにしましょう。 ちなみに、先ほどは「カレントディレクトリの一個上にあるファイル」という意味で、../_test_runner.pyという相対パスの表記を用いました。 ここは以下のようにすることもできます。

  • cat /home/runner/_test_runner.py (絶対パスによる指定)
  • cd ../ してから cat _test_runnner.py (ディレクトリを移動してから実行)

touch

次に、ファイルを生成するコマンド、touchを使ってみましょう。 もとのカレントデイレクトリに戻ったうえで、 以下のコマンドで、hoge.cというファイルを作りましょう

$ touch hoge.c
これによりファイルが生成されました。確認してみましょう。
$ ls
hoge.c  main.c
出来ていますね。ここでは、左側のファイルビューアでもhoge.cが生成されていることが確認できます。それをクリックすると、 真ん中のエディタ画面に、その中身が表示されます。現在、何も書かれていないですね。 ここに適当に何かを書いてみましょう。repl.itのエディタは自動セーブ機能があるので、「上書き保存」しないでも自動的に保存されます。 何か書いたあと、ターミナルでcat hoge.cとすると、その中身が表示されます。

mkdir

次に、mkdirコマンドでディレクトリを作ってみましょう。Make directoryの略です。

$ mkdir sample_directory
これにより、sample_directoryというディレクトリが出来ます。左側のビューアからも確認できますし、lsと打つことでも確認できます。

mv

次は、mv (move)コマンドを試しましょう。これは、ファイルを別の場所に移動するコマンドですが、それを利用してファイルの名前を変えることもできます。 mv 元のファイル 移動先のファイル名 という使い方をします。

$ mv hoge.c fuga.c
このようにすることで、hoge.cfuga.cにリネームされました。 次に、このhuga.cをsample_directoryの中にもっていきましょう。
$ mv fuga.c sample_directory/fuga.c
左側のビューアから、実際にファイルが移動していることを確認してみてください。

cp

次にcp によるコピーを行ってみましょう。使い方はcp コピー元 コピー先です。

$ cp main.c main2.c
これにより、main.cと同じ名前のmain2.cが出来ているはずです。

rm

次に、今作ったデータとディレクトリを削除してみましょう。これにはrmコマンドを使います。removeの略です。

$ rm main2.c
これで、先ほど作ったmain2.cが消えました。rmコマンドで消したものはゴミ箱に保存などされず、完全に消えてしまいます。 なので、うっかり間違えて重要なファイルを消さないように注意しましょう。 ディレクトリを消すときは、-rというオプション(recursiveの略です)をつけます。再帰的に、ディレクトリを消し、その中身も消していく、という意味です。 ここでオプションとは、コマンドのあとに記入するもので、コマンドに指示を与えます。
$ rm -r sample_directory
これにより、先ほど作ったディレクトリが消えました。

上記のようなファイル・ディレクトリ操作の処理は、左側のビューアからファイルを右クリックしたりドラッグアンドドロップしたりして 行うこともできます。windowsではエクスプローラで行うこともできます。ですが、コマンドによる処理を覚えておくことは重要です。 なぜなら、(1) コマンド処理を組み合わせることで、より複雑な処理を自動化して実行することができます。 (2) 将来的にGUIが無い状態でプログラミングをすることがあり得ます(サーバ上でプログラミングするなど)。その場合、コマンド操作は必須です。

やってみよう(15分)

  • 上記の通り、cat, touch, mkdir, mv, cp, rmを試してみましょう。
  • カレントディレクトリ以下に、以下のような構造のディレクトリおよびファイルを作ってみましょう:./aaa/bbb.py, ./aaa/ccc.py, ./aaa/ddd/fff.py
  • lsに対して、隠しファイルも含めた全てを表示するオプションが-a、詳細にプリントするオプションが-lです。これらを組み合わせてls -alとすると、現在のディレクトリ下のファイルの詳細一覧が表示されます。やってみましょう。
  • man というコマンドを使ってみましょう。マニュアルという意味です。$ man コマンドとすることで、そのコマンドの説明が表示されます。コマンドの説明はもちろんググれば出てくるのですが、manコマンドのものは公式で最新なのでより信頼がおけます。説明画面では上下キーでスクロール、Qを押すと終了します。試しに $ man ls などとして結果を眺めてみましょう。 [授業後追記] repl.itではmanコマンドが動かないようです。。。

クイズ

/xxx/yyy.txt./xxx/yyy.txtがそれぞれ何を表すか説明してください。

答え
  • /xxx/yyy.txt: コンピュータの一番上の位置から、xxxというディレクトリがあり、その下にyyy.txtとうファイルがあり、それへの絶対パス。
  • ./xxx/yyy/.txt: カレントディレクトリ以下にxxxというディレクトリがあり、その下にyyy.txtというファイルがあり、それへの相対パス。この絶対パスは何かわからない。たとえば次のようなものかもしれない:/home/matsui/xxx/yyy.txt.

Hello Worldプログラムのコンパイルと実行

プログラミングの準備はここまでで終わりです。早速プログラミングを行いましょう! プログラミングを書き、それを実行するという作業は、簡略化して言えば以下の3つのステップに分解されます。

  1. ソースコードを書く:人間が読んで理解できるテキストファイルを書きます。これは、極端に言えば、windowsのメモ帳で文章を書く行為と同じです。
  2. ソースコードをコンパイルし、実行可能ファイルを作る:人間が理解できるソースコードを、コンパイルという作業によって、機械が理解できる形式に変更します。Pythonのようなインタプリタ言語は、このステップがありません。
  3. 実行可能ファイルを実行する:得られた実行可能ファイルを、実際に実行します。

このそれぞれのステップについて、順番に見ていきましょう。

ソースコードを書く

まず最も有名な「hello, world」コードを書いてみましょう。これは、「hello, world」という文字列を出力するだけのプログラムです。 ある言語を勉強するときは、最初にそのようなコードを書くという文化があります。 ところが、なんとrepl.itでは最初にそのコードが既に書かれています。main.cがそのものです。 ここでは、一旦main.cを消しましょう。そして、あらためてhello.cというファイルを作ってみましょう

$ rm main.c      # main.c が消える
$ touch hello.c  # 空のファイルを作る

そして、画面の真ん中のエディタを使って、 main.cの中に以下を写経してください。コピペしてはダメです。ちなみに、下記のコードは教科書の「カーニハン&リッチー, プログラミング言語C」の7pに書いてあります。 今後はそれを省略して右のように表記します:(K&R, 7p)

#include <stdio.h>

main()
{
    printf("hello, world\n");
}

ソースコードをコンパイルする

次に、ソースコードをコンパイルします。次のコマンドを実行してみましょう。

$ gcc hello.c
ここで、gccはコンパイラと呼ばれるプログラムです。これはソースコードを受け取り、それをコンピュータが解釈できる実行可能ファイル に変換します。ここでは、実行可能ファイルであるa.outが生成されているはずです。

実行可能ファイルを実行する

この実行可能ファイルを実行してみましょう。以下のようにします

$ ./a.out
これは現在のローカルディレクトリにある実行可能ファイルa.outを実行しろ、という意味です。 次のような出力が得られるはずです。
hello, world
これが皆さんがコンパイルして実行した初めてのプログラムです!

やってみよう(15分)

  • 上記の通り、hello, worldプログラムを書き、コンパイルし、実行してみましょう
  • うまくいかないかもしれません。よくある理由としては以下です
    • 全角で入力してはダメです。#Include のように書いてはいけません。#includeとしましょう
    • カッコのつけ忘れ、閉じ忘れ
    • 行末のセミコロンのうち忘れ

解説

それでは、先ほどのコードを解説していきましょう。

#include <stdio.h>
#includeという表記は、別のファイルをこの部分によみこむ、 という意味です。ここでは、/usr/include/stdio.hという ファイルをここに読み込んで展開しています。 stdioはstndard input/outputの略です。 printfを使うためにはこれが必要なので、本講義の範囲ではいつでも includeすることになります。

main() {      
    // 本体
}
「main関数」の部分は、プログラムの本体を書く部分です。 この波括弧で囲まれた内容が、./a.outのときに実行されます。

ここではmainの内側では左側に半角スペース4個ぶんの空白をいれてあります(インデントをいれる、と表現します)。 ここは、空白がなくてもいいですし、たくさんあっても大丈夫です。 ですが、コードの可読性を上げるために、常に決まったルールで空白を入れるといいです。 広く使われる慣習として、波括弧の内側では4つ空白を入れるようにしましょう。 実際は、そのようなルールはエディタが自動的に機械的にそろえてくれます。

コラム

repl.itの場合は、左側の歯車マーク(Settings)からIndent Sizeを4にすればよいです。 前の行からEnterを押して改行したときは、自動的にちょうどいいインデントになると思います。手動でインデントを足すときは、Spaceを4回押すのでは無く、Tabを一度だけ押すと、自動的に 空白が4個追加されます。Shift+Tabとすると4個が消えます。

printf("hello, world\n");
printf関数です。関数というものは、数学の関数のように、カッコの内側に 引数(入力情報)が与えられ、何らかの処理を行います。 printf関数は、printf(引数) としたときに、引数の内容をターミナルに表示します。

ここで引数とされているのは "hello, world\n"です。 ここで、"あいうえお"のように二重引用符で囲まれている任意個の文字の列を文字列と呼びます。 これはその名の通り、文字を表します。ここでは日本語を使っても構いません。 最後の\nは改行を表す特殊な記号です。

また、C言語では、行の最後にはセミコロンを書きます。これを忘れるとエラーになりますので、注意してください。

やってみよう(30分)

  • $ cat /usr/include/stdio.hstdio.hの中身を眺めてみましょう。
  • #include <stdio.h>の行を消して、そのうえででコンパイルしてみましょう。エラーメッセージが表示され、コンパイルが出来なくなることを確認しましょう。エラーメッセージをよく読んでみましょう。 【授業後追記】gccは気を利かせてくれて、includeしなくてもコンパイル出来てしまうようです。。。
  • printf("あああ")のように、printfの中身をいろいろと変更してコンパイルして実行してみましょう。
  • "hello, world"のように、最後の改行記号を取り除くとどうなるか確認しましょう。
  • "hello, world\n\n"のように、最後に改行記号を二度入力するとどうなる確認しましょう。
  • 改行のほかにも、様々な特殊記号があります。\tはタブを表します。これは出力の位置ぞろえに役立ちます。"abc\tabc\n"をプリントしてみましょう。
  • 二重引用符そのもの(")のプリントは\"とします。また、バックスラッシュそのもの(\)は\\とします。次を実行してみましょうprintf("double quotation is \". back slash is \\")

クイズ

次の文字列を出力したいとします:back slash + double quotation (\") is used to print a double quotation。ここで、次のように書くとうまくいきません。それは何故でしょうか?そして、どう書き換えればいいでしょうか? printf("back slash + double quotation (\") is used to print a double quotation symbol in printf")

答え

\"も、文字列中で使いたいときは特殊文字にしなければならないため。正解: printf("back slash + double quotation (\\\") is used to print a double quotation symbol in printf")

基本データ型

それでは次に、基本となるデータ型について勉強しましょう。 あらためてdata_type.cというファイルを作りましょう。$ touch data_type.cとしてもいいですし、左側から右クリックで作ってもいいです。 その中に以下を写経してください。

#include <stdio.h>

main()
{
    int height;
    int width;
    height = 10;
    width = 5;
    int area;
    area = height * width;
    printf("Height: %d  Width: %d  Area: %d\n", height, width, area);

    char c1, c2, c3;
    c1 = 12;
    c2 = 127;
    c3 = 140;
    printf("c1: %d, c2: %d, c3: %d, c1-c2: %d\n", c1, c2, c3, c1 - c2);

    float f = 123.5;
    printf("f=%f, f/3=%f\n", f, f / 3);

    printf("address of height is %p. address of width is %p\n", &height, &width);
}

写経が終ったら、コンパイルしましょう。

$ gcc data_type.c

そして実行してみましょう。以下のような内容になるはずです(addressの値は違うものになります)

$ ./a.out 
Height: 10  Width: 5  Area: 50
c1: 12, c2: 127, c3: -116, c1-c2: -115
f=123.500000, f/3=41.166668
address of height is 0x7fff138be160. address of width is 0x7fff138be164

ちなみに、コンパイル後の実行可能ファイルの名前はデフォルトでa.outですが、次のように-o 名前 というオプション をつけることで変更して出力できますので、覚えておきましょう。

$ gcc data_type.c -o data_type  # a.outではなくdata_typeという名前のファイルが出来る
$ ./data_type  # 実行

やってみよう(10分)

上のコードを写経し、実行してみましょう

変数

それではコードを解説していきます。

int height;
int width;
height = 10;
width = 5;
ここで、int heightという表記は、height という名前の変数 を一つ準備するという意味です。 変数とは、よく「箱」だと例えられます。 箱の名前が変数名であり、それがheightです。 そして、箱のintです。 型は、箱の大きさと中に入れるものの種類を決定します。 今回は32 bitの大きさの整数を表すint型です。 よって、このheightは整数一つを表す変数です。

その箱の中に、を保持します。ここでは、height = 10という表記で、10という値をheightを入れています。 値を入れることを代入と言います。 ここでのイコールは「左辺に右辺を代入する」という意味です。「左辺と右辺は等しい」という意味である数学のイコールとは違うので注意してください。 10 = heightという書き方はできません。

変数名としては英字、数字、アンダースコアが使えます。大文字と小文字は区別されます。 また、c言語の文法として用いられるfor, int, など(予約語)は変数名として使うことはできません。

int _height;    // OK。アンダースコアは使える
int Height;     // OK。大文字も使える。Heightとheightは別物。
int height123;  // OK。先頭以外で数字は使える
int 123height;  // ダメ。先頭に数字は使えない
int for;        // ダメ。Cの予約語。

ところで、変数をまず作ることを宣言といいます。複数の変数はまとめて宣言することもできます。つまり、 上で見たheightwidthの宣言は、下記のようにまとめても書けます。

int height, width;
宣言と同時に値を代入することもできます。これを初期化と言います。 また、変数は後から別の値を代入して値を上書きすることができます。
int height = 5;  // 宣言と同時に代入(初期化)
height = 12;  // 値の上書き
ちなみに、変数を宣言して初期化していない段階では、その変数の値は未定義なので注意してください。
int height;
// この段階ではheightの値は未定
height = 5;

さて、それではコードの次の部分を見ていきましょう。

int area;
area = height * width;
printf("Height: %d  Width: %d  Area: %d\n", height, width, area);
ここでは、areaというint型変数を新たに作り、そこにheightwidthを掛け合わせたものを代入しています。 変数は、掛け算を表す*といった演算を適用することかできます。これについては来週詳しく説明します。

そして、printfを使ってその中身を表示します。ここで、printfには4つの引数が渡されています。 最初の"Height: %d Width: %d Area: %d\n"の部分は、Hello worldの例で見た通り、表示する内容です。 ここで、%dという部分は、「何らかのデータをここに表示する」というサインになっています。 2つ目の引数がheightです。これが、最初の%dの部分に表示されます。 この処理は何度も行うこ都が出来ます。次の%dには、3つ目の引数であるwidthが表示されます。 最終的に、以下の表示になります。

Height: 10  Width: 5  Area: 50

やってみよう(5分)

  • 上で述べた、禁止されている変数名(123heightfor)を使って変数を作り、コンパイルしてみましょう。どういうエラーが返ってくるか見ましょう。

次に、型について勉強しましょう。Cで使われる整数型には以下のようなものがあります。これらは全て整数を表します。

型名 サイズ (bit) 値の範囲
char (よく使う) 8 (\(-2^{7}\)から\(2^{7}-1\)), すなわち, (-128 から 127)
short 普通は16 (\(-2^{15}\)から\(2^{15}-1\)), すなわち, (-32,768 から 32,767)
int (よく使う) 普通は32 (\(-2^{31}\)から\(2^{31}-1\)), すなわち, (-2,147,483,648 から 2,147,483,647)
long 32か64 (\(-2^{31}\)から\(2^{31}-1\)), あるいは, (\(-2^{63}\)から\(2^{63}-1\))
unsigned char 8 (\(0\)から\(2^{8}-1\)), すなわち, (0 から 255)
unsigned short 普通は16 (\(0\)から\(2^{16}-1\)), すなわち, (0 から 65,535)
unsigned int 普通は32 (\(0\)から\(2^{32}-1\)), すなわち, (0 から 4,294,967,295)
unsigned long 32か64 (\(0\)から\(2^{32}-1\)), あるいは, (\(0\)から\(2^{64}-1\))

変数のサイズは、一つの変数を表すために使われるビットの数です。たとえば8ビットを使用するchar型は、8つの{0, 1}で表されます。 00000010のようなものです。8ビットなので、\(2^8=256\)個の値を表現できます。 char型は負の数も表現するため、1つのビットを符号の表現に用います。そのため、正の数が7ビット(\(2^7=128\))分、負の数も同様に7ビット分 表現できるたけ、最終的に-128から127までの整数を表現できます。 ビット数が少ないとメモリ消費量も少なくなり良いですが、そのぶん表現できる数の範囲も小さくなります。 整数型は、後述する実数型とは違い、整数を厳密に表現します。

このcharに対応して、負の数を表現しない型をunsigned charと呼びます。こちらは8ビット分全てを整数の表現に使うので、 0から255までの値を表現できます。このように、整数の各型に対応してunsignedのバージョンがあります。

最も標準的に使われる整数型はintです。これは大体プラスマイナス20億の値を表現できるため、普段の計算ではこの長さで十分です (もちろん足りなくなる場合もありますが)。本講義の範囲では普通に整数を扱う場合はintを使えばOKです。

サイズに「普通は」と書いているのはどういうことでしょうか? C言語のルールとして、たとえばintは「最低16ビット使う」というような取り決めとなっており、 具体的に何ビットを使うかは処理系に依存ということになっています。 なので、組み込みのようなメモリ消費がシビアな条件では上記と違うビット数の処理系もあるでしょう。 K&R本では以下のような記述があります「intは16ビットあるいは32ビットなのが普通である」(K&R, p44)。 K&Rが書かれたころは、intが16ビットである処理系も多かったのでしょう。 本講義で扱う範囲(一般的な64 bitコンピュータ)では、上記の値だと思って大丈夫です。

次に、実数型を見てみましょう。これらは整数に限らず、小数を表現できます。これらの表現形式を浮動小数点数と呼びます。

型名 サイズ (bit) 値の範囲
float (よく使う):単精度浮動小数点数 普通は32 \(3.4\times10^{-38}\) から 約\(3.4\times10^{38}\)
double (よく使う):倍精度浮動小数点数 普通は64 \(1.7\times10^{-308}\) から 約\(1.7\times10^{308}\)

実数型は整数型と違い、小数を近似的に表現します。たとえば、\(1/3 = 0.3333...\)は、浮動小数点では正確に表現できません。 ビット数が多いほうが、表現できる値の範囲が大きくなり、また精度よく数を表現できます。

floatdoubleどちらを使うべきでしょうか?これは問題に依存します。 数値計算など、高い精度が求められる場合はdoubleが用いられるでしょう。 精度よりもメモリ消費のほうが重要な場合はfloatのほうが好まれます。

コラム

機械学習などの分野では、さらに精度を犠牲にしてでもメモリ効率をよくしたいという要望があります。 そのため、近年は16 bitの浮動小数(半精度浮動小数点数)が注目を浴びています。 16 bitというのはかなり少ないビット数なので、その精度で大丈夫なのか?と言いたいところなのですが、 機械学習では計算は相当おおざっぱでいいのでメモリ問題のほうが重要ということのようです。 数値の型などという極めて基礎的な部分でも進化が起きる、情報科学という分野のダイナミックさが見て取れます。

それでは、もとのコードの次の部分を見ていきましょう。

char c1, c2, c3;
c1 = 12;
c2 = 127;
c3 = 140;
printf("c1: %d, c2: %d, c3: %d, c1-c2: %d\n", c1, c2, c3, c1 - c2);
その結果は以下でした。

c1: 12, c2: 127, c3: -116, c1-c2: -115

ここでは、3つのchar型変数を作り、値を代入し、表示しています。 printfの最後の部分のように、%dに代入するものはその場でc1 - c2のように計算してもよいです。

ここで、c3の値がおかしいことに気付きましたでしょうか?c3には140を代入したはずなのに、 printしてみると-116になっていますね。これは、上で述べた通り、charが表現できる限界である127を超えているからです。 この値がなぜ-116になるかについては、「型変換」の章で解説します。

float f = 123.5;
printf("f=%f, f/3=%f\n", f, f / 3);
実数型についても、整数型と同様に用いることができます。 ここで、実数型を表示するときは%dではなく%fを用いることを覚えておきましょう。

やってみよう(5分)

整数型を%fフォーマットで、また実数型を%dフォーマットで表示したときどうなるかやってみましょう。

クイズ

\(x^2 + 2x + 3\) を計算するコードを書いてみましょう。float x としたうえで、数式を記述しましょう。xを色々変えて出力してみてください。

答え

例えば以下のように書けます。xの値を色々変化させてみましょう。

float x = 0.01;
float result = x * x + 2 * x + 3;
printf("x: %f, result: %f\n", x, result);

メモリ上での変数のふるまい

それではコード中の最後の行を見てみましょう。ここでは、変数がコンピュータ上で実際にどのように表現されているかを見てみましょう。

printf("address of height is %p. address of width is %p\n", &height, &width);
この結果は例えば次のようになります。
address of height is 0x7fff138be160. address of width is 0x7fff138be164
ここでは、変数がコンピュータのメモリ上のどこに位置しているかが表示されています。 これを理解するために、変数がどのように表されるかを下図に示します。

これは、コンピュータのメモリを表現しています。メモリとは、プログラムが情報を保存するために利用できる領域のことです。 上の図のように抽象化されます。一つのブロックは、{0, 1}が8個並んだブロックになっています(8 bit = 1 byte)。 そのようなブロックがずらっと並んでいます。これがメモリです。ここに情報を書き込んだり読み込んだりします。

そして、各ブロックには自分の住所であるアドレスが割り振られています。 このアドレスを指定することで、所望のブロックを一意に指定できます。 アドレスはここでは16進数で表現されています。このアドレスは、実際には(64 bitマシンの場合は)64ビットの整数です。 その整数が順番に並んでいると思ってください。注意として、ここでは16進数なので、値が9の次はaになります。

ここで、変数heightを宣言するということは、このメモリをブロック4つがheightであるとまさに宣言するということです。 int型は32bitなので、4つのブロックを確保します。そして、その中に値を書き込みます。ここでは10なので、二進数表記で00000000|00000000|00000000|00001010となります。 ここでは、次のwidthはそのすぐ次に宣言されています。4ブロックあとなので、先頭アドレスが4増えていることがわかります。

ここで、変数の頭に&(アンド)を付けると、その変数の先頭アドレスを取得することができます。よって、ここでは、 &height0x7fff138be160となります。&)はアドレスと覚える人もいます。 この内容はprintfにおいて%pを用いることで表示できます。それが、先ほどのコードの意味です。

このようなことを考えなくてもプログラミングは出来るのですが、上記のようなメンタルモデルを忘れないようにしていてください。 のちにポインタを勉強するときに、このモデルの理解が必須になります。

やってみよう(10分)

  • intを2つ並べると、その先頭アドレスの差は(少なくとも)4以上にまります。ここでcharを並べた場合にどうなるかやってみましょう。

ローカル環境でのプログラミングについて

意欲のある学生は、ローカル環境でのプログラミングを推奨します。

宿題

本日の講義はここまでです。 次回までに、以下を行ってください。締切は10/7の23:59です。

  • 宿題の提出にはGitHubおよびGitHub Classroomを使います。そのため、GitHubのアカウントを作っておいてください。これは普段使っている個人用があればそれでもいいですし、学科用に新しく作ってもいいです。
  • 必須ではないですが、repl.itのアカウントを作ることを強くオススメします(自分はrepl.itを使わない。ローカル環境を整備してやる。という方は作らなくて大丈夫です)
  • ITC-LMSのアンケートの「アカウントの申告」を用いて、Githubとrepl.itのアカウント名を松井まで送信してください。この2つのアカウント名は一緒でも違っても大丈夫です。
  • Githubアカウントを登録してもらわないと、成績がつきませんので注意してください。

ちなみに、Git/GitHubとは以下のようなものになります。

  • バージョン管理(ソースコードの履歴を保存する仕組み)としてgitというソフトウェアがあります。gitは単に自分の手元でソースコードを管理する仕組みなのですが、そのgit管理されたソースコードを保存・公開するウェブサービスがGitHubです。
  • GitHubを使うことで、人々は簡単に共同でコードを編集することが出来ます。また、コードを公開したい場合、GitHub上で公開すればを使いたい人が簡単に見ることができます。例えばPythonそのもののコードは以下になります
  • GitおよびGitHubを知っていれば、これまでに開発されたコードをチェックすることができます。いわゆる「巨人の肩の上に立つ」ということです。
  • 本講義ではGit/GitHubの使い方の基礎を第七回に勉強します。それに先立ち、宿題の提出はGitHub経由で行うため、まずアカウントを作ってもらいます。
  • また、GitHubはSNS的な側面もあります。アカウントを持つと自分のページが公開され、自分が公開したいコードをそこで表示することができます。他の人のコードに質問をしたり、修正パッチを送るなどもできます。ちなみに松井のGitHubアカウントは以下になります。
  • 逆に、公開しないプライベートなコードを置いておくこともできます。これは安全なバックアップになります。
  • GitHubの類似サービスとしてBitbucketGitLabがあります。