C言語入門 Introduction of The C Programming Language |
|
第2版 | 著作:日台健一 1996-1997 |
目次 |
はじめに |
C言語は、(例えばBASICやFortranと比べて)非常に機械に近い非人間的な言語です。よって、コンピュータの内部構造まで教えたくなるのですが、ここではそこをぐっとこらえます。本稿の目標としては、大学の簡単な課題がこなせる程度になることとしましょう。(言語は道具であり、目的ではありません。C言語のすべての機能を網羅するよりC言語を学習するものにとって何が必要かを考えて編集しました。)
第1の難関 |
「C言語を覚えたい」という人にとって、最善の修得方法は、とにかくいろんなプログラムを見て自分で書いてみる、だと思います。興味があれば自分一人でも(資料さえあれば)自然に覚えるものです(私がそうでした)。
C言語を喋るということはコンピュータと対話することであって、そこにコンピュータがなければ意味がありません。何が言いたいかというと、例えば、「ディレクトリって何?」という人はいると思いますが、その人はまず何らかの形でコンピュータと関係し、コンピュータの知識を付ける必要があります。「コンピュータは使わないけどとりあえずC言語を覚えようかな」というのは、あまり意味のないことで、恐らく上達も難しいと思われます(まあ、個人的な教授はしますが)。
プログラミングレッスン1 |
この章では、一通りの基本的な知識を摘んでみることにします。詳しい解説は省きますが、だいたいの動作は理解できると思います。最初はとにかく簡単なプログラムを読むことで、そこに書いてあるコードが何を意味するのかを理解してください(説明文よりもソースコードをよく読んでください)。
簡単なプログラム |
画面に好きな文字を表示するだけのプログラムです。コメントをよく読んでください。文の最後にはセミコロン(;)を書きます。また、printf()中の文字列の\nは、改行を表します。以下は出力結果。
変数を使う |
今度は変数を使います。変数とは数を入れておける箱のようなものです。変数を使用するには、(中カッコの中の)一番はじめに宣言を書かねばなりません。宣言は、同じ型(「よく使うデータ型一覧」を参照)であるならカンマ(,)で区切っていくつでも書くことができます。変数の名前には、アルファベット、数字そしてアンダースコア(_)を使うことができます。ただし、先頭の1文字に数字は使えません。
変数の値をprintf()で表示するために、%dや%cといった文字列を使用しています。%で始まる文字列は、カンマで区切った後に書いてある変数の値と置き換わります。
型 | 扱える範囲 (16bitマシンの場合) | printf中の表記 |
---|---|---|
char | -127〜128(文字) | %c(文字を表示), %d(値を表示) |
int | -32768〜32767 | %d |
long | -2147483648〜2147483647 | %ld |
float | 有効桁数6桁の実数 | %f(%e) |
double | 有効桁数15桁の実数 | %f(%e) |
型の前にunsignedと書くことで、扱える範囲を正の整数にすることができます(char/int/longの場合)。そうすると、負の整数は扱えなくなりますが、その分、正の範囲が2倍使えるようになります。unsignedをprintf()で出力するとき、intは%u、longは%luと表記します。
とつぜんですが、文字はシングルクォーテェーション(')で囲み、文字列はダブルクォーテェーション(")で囲みます。char型に入るのは文字で、文字列ではありません。ここでちょっと難しくなりますが、文字について説明します。
一言で説明するならば、「文字は数字である」となります。例えば、'A'という文字は65という数字と等価であり、'B'という文字は66と等価であるのです。はたまた、'1'は49だったりします。前のソースで、c='A'という式がありますが、これはc=65と書き換えても全く同様に動作します。
文字にはこのような性質があるのですが、文字列はまた別の概念で表現されています。これは大変難しいので、本稿での説明は省略します。
計算をしよう |
今度は整数でなく、実数(小数点以下がある数)の計算をするために、double型(floatでも可)を使用しています。
また、計算の優先度もあるので、加算を乗算より先に行いたいときはカッコでくくるようにしましょう。
|
|
単項とは、1つの変数に対して効果のある演算子のことを指します(例:a++)。単項演算子以外は、常に2つの項を必要とします(例:a+b)。
論理演算とは、普段はif文(後述)などの条件式で使用します。また、「ビットごとの〜」演算子は普段は使いませんが、一応書いておきました。これについて本稿での説明は割愛します。
「〜の代入」演算子は、次のように使います。
キーボード入力を受け付ける |
scanf()は、キーボードからの入力を変数に格納する関数(命令)です。入力されるものが、整数なのか、文字なのか、はたまた実数(浮動小数点数という)なのかをコンピュータに教えなければならないのですが、それをprintf()と同じように%で始まる文字列で指定します。ここで注意しなければならないのは、格納する変数の頭に&をつけなければならないことです。これには意味があるのですが、今は「決まり」と覚えてください。
条件分岐をする (ifとswitch) |
if文は、カッコの中の条件が成立していれば、次の中カッコを実行します。条件が成立していない場合は、else以降の中カッコを実行します。必要がなければelse以降は省略することが可能です。
if文の書式は次のようになります。
switch文は、カッコの中の値に対応するcaseの位置にジャンプします。処理の終わりにはbreak文を書かないと次のcaseまで続けて実行してしまうので、必ずbreak文を書くようにしましょう(意図的に書かない場合もある)。どのcaseにも対応しない場合はdefaultにジャンプします。defaultも無い場合は何も処理しません。
switch文の書式は次のようになります。
繰り返し処理 (forとwhile) |
まず、一番はじめにi=0が実行されます。次にi<20を評価し、条件が成立していればprintf()を実行します。それが終わるとi++を実行し、またi<20の評価に移ります。それが成立していれば、後はprintf() => i++ => i<20と、繰り返すことになります。もちろん、 i<20が成立しなくなったら、for文は終了します。
あまりいい例ではありませんが、forをwhileで書き換えると次のようになります。
最後に、do〜while文というのを見てみましょう。
for文の書式。
while文の書式。
do〜while文の書式。
上の3つのプログラムは全く同じ動作をしています。厳密に言えば最後のdo〜whileだけ少し違うのですが、それについては後述します。
しかし、同じことをする文が2つも3つも存在することに意味があるのでしょうか?実は、「動作は同じでも、それぞれの持っている意味(解釈)が違う」から意味があるのです。
for文は、「何回繰り返す」という意味合いで使いますが、while/do〜while文は、「条件が成立している間繰り返す」という意味合いを強く持ちます。これらの意味合いを使い分けられると、他人のソースコードを読むときに大変読みやすくなります。とりあえずは、forとwhileを使えるようにしましょう。
ところで、do〜whileですが、whileの双子の兄弟と思ってください。何がwhileと違うかというと、do〜whileは、評価する条件式が文の後ろにあるから、条件がどうであろうと最低1回は命令を実行することになる、ということです。
break文を使うと、ループの途中でもそのループを終了することができます。使い方としてはswitch文中のbreak文と同じです(switchはループではないことが違う)。
BASICユーザにとっては当然のように使われているgoto文ですが、C言語の世界では嫌われ者として知れ渡っています。goto文はC言語にあるのですが、正直私は使ったことが(本当に)1度もありません。また、使ったプログラムもほとんど見たことがありません。どうしてなのか私はよくわからないのですが、とにかく「gotoは使うな」という風に覚えましょう。
if文では条件が成立した場合に処理する命令を中カッコで囲んでいましたし、for文でも繰り返す命令を中カッコで囲みました。しかし、この中カッコは省略できる場合があります。
C言語には「複文」という考え方があります。文とは式や宣言の最後にセミコロン(;)を付けたもののことを言います。それに対する複文とは、中カッコで囲まれた複数の(別に1つでもいい)文から成り立つブロックのことを指します。複文の閉じ中カッコ(})の後にセミコロン(;)は必要ありません。ifやforの後には文を書く決まりなので、通常の文でも複文でもいいのです(for文やwhile文の書き方の欄で、中カッコを使っていないのはそのため)。その例を下に示します。
虫取り教室 |
ここまで、簡単なプログラムを組める程度の基礎知識を付けてきました。さて、これからいろいろなプログラムを作っていくわけですが、当然ながらプログラムは正確に書かねばなりません。どこか少しでも間違っているところがあると、コンパイル時にエラーがでます。それの原因が分からないときはコンピュータのせいにしがちですが、99.9%以上は人間の間違いでしょう(コンパイラの間違いも存在します)。そんなプログラムの間違いのことをバグ(bug:虫)といい、バグを取り除く作業のことをデバッグと言います。デバッグ作業には様々なやり方がありますが、ここで代表的なデバッグの方法を紹介しましょう。
「なぜかコンパイル時にエラーがでる」という場合は怪しそうな部分を/* */で囲み(コメントアウトするという)、再度コンパイルしてみましょう。それでコンパイルが正常に終了したら、コメントアウトした部分が間違っていることになります。うまいやり方としては、はじめは大きな部分をまとめてコメントアウトし、その後だんだん小さくして追いつめていきます。
これは初歩的なデバッグの方法ですが、実はC言語では実現しにくいこともあります。例えば、カッコの対応が間違っているとき(大変多いバグである)には通用しないかもしれません。
「プログラムは動いているけれども、結果がおかしい」という場合は、計算途中の変数の値を随時printf()で出力するようにしてみましょう。どこまでが正常でどこからが異常なのかを観察することができます。
プログラミングレッスン2 |
さて、話を元に戻してさっそく次の段階に進みましょう。いままでは本当の基礎でしたが、この章は大学の課題をこなす程度に重要な内容を含んでいます。
配列変数 |
これは、何をしているプログラムか意味を読みとれるでしょうか?文を先に読み進める前にまず考えてみてください。
C言語での配列は、ほかの言語でよく使われる小カッコ(())による表現ではなく、大カッコ([])によって表現されます。その点をのぞけば、宣言もプログラム中での参照の方法もイメージ通りに行えることが読みとれると思います。
ここで注意しなければならないのは、配列の添え字(何番目かを示す数字。この場合はiを使っているところ)は0からMAX-1まで使用できる、ということです。point配列変数の、MAX番目(point[MAX]のこと)は存在しません。宣言のところに用いる数は、その配列が「何個の要素を持つか」であって「何番目までの添え字が使えるか」ではありません。このことは、for文のiの値を見てもわかります。iは、0から始まりMAXに達する1つ手前で終わります(for文中の条件式がi<=MAXではないことに注目)。
また、配列の要素数の指定には#defineを使うのが定石です。
このプログラムでは配列の内容をキーボードから入力させていますが、あらかじめ初期化しておくことも可能です。その例を次に示します。
多次元配列はkuku[i][j]のように扱い、kuku[i,j]ではありません。これは2次元の例ですが、n次元になっても同様です。
関数を作る |
関数とは、大きな計算処理を小さな仕事に分割するための構造です(似たようなものにサブルーチンという用語があります)。C言語での関数は、数学の世界で定義されるように「いくつかの値をもらって1つの値を返す」ものです。数学ではそれが数式で表現されますが、C言語では文(命令)の集まりで表現されます。
関数は、使う前に定義をしなければなりません(本当はそうしなくてもいいのだが、今はこう覚える)。つまり、main()の前に書きます。関数の定義の書式は次のようになります。
関数の名前には、変数の名前と同じ規則が適用されます(「変数を使う」の節を参照)。
返値は、どの型でも自由に使えます。返値が必要ない場合は、void型を指定します。省力するとvoid型が適用されます。返値の(値の)指定にはreturn文を使います。
return文が実行されると、その時点で呼び出しもとの関数に実行が戻ります(main()関数ならプログラムが終了します)。return文は関数内にいくつあってもよいのですが、返値がvoidでない関数には必ず1つ以上のreturn文が必要です。次にreturn文の書式を示します。
余談ですが、printf()やscanf()も関数です。C言語には、標準の関数が用意されていて、多くの処理はそれらの関数を用いることによって実現されます。
変数のスコープ |
変数には、その変数の有効な範囲(スコープ)が決められます。例えば、ある関数内で宣言した変数はほかの関数から参照することはできません。そのため、同じ名前の変数を別々の関数内で書くことができます(例えば、iという変数はループカウンタ用によく使います)。
また、関数の外に変数の宣言をおくと、どこの関数からも参照できる変数(グローバル変数)になります。グローバル変数は一見大変便利に見えます。しかし、グローバル変数が本当に必要な場合はそれほどありません。はじめのうちは変数をグローバルにしてしまいがちですが、グローバル変数は極力使わないようにしましょう。変数の値をほかの関数で使いたいときは引数で渡すようにします。
関数に渡すのは変数の値のコピーで、変数そのものではありません。よって、引数で渡した変数が、その関数内で変更されることはありません(逆に、変数そのものを渡す方法もあります)。
上のプログラムの実行結果は次のようになります。
数値処理の達人 |
さて、ここまで勉強してきましたが、これほどでは大学の課題がこなせないでしょう。何が足りないか?足りないものはいろいろあると思いますが、その中の1つに「複雑な計算ができない」というのがあると思います。ここでは数値処理に必要な知識について解説します。
数学関数 |
これは0.1ラジアン刻みのsinを求めるプログラムです。特に難しいところはありませんが、2行目の#include <math.h>を忘れずに書きましょう。次に、よく使われるであろう関数をいくつかあげます。どれも、1つあるいは2つ以上のdouble引数をとり、doubleを返します。
関数名 | 内容 |
---|---|
sin(x) | xのsin。単位はラジアン。 |
cos(x) | xのcos。単位はラジアン。 |
atan2(y,x) | y/xのarctan。単位はラジアン。 |
exp(x) | 指数関数ex |
log(x) | xの自然対数(基数はe)。(x>0) |
log10(x) | xの普通対数(基数は10)。(x>0) |
pow(x,y) | xy |
sqrt(x) | xの平方根。(x>=0) |
fabs(x) | xの絶対値。 |
桁をそろえて表示 |
前のソースでは、出力結果(一部)は以下のようになります。
無駄な(必要のない精度の)0の表示が目立ちますが、これをきれいに表示するにはprintf()の文字列を次のようにします。
これで、次のようになります。
変わったのは、%fを%3.1fや010.8fにした点です。小数点を境にして、左側の数字は、全部で何桁表示するかを指定します(この中には、小数点を表示する桁数も含まれる)。そして右側の数字は、全部の桁数のうち小数点以下は何桁表示するかを指定します。また、(余分な)頭の0は、(小数点以上の)出力する必要のない桁にもとりあえず0を出力させることを意味します(上の例はナンセンスであるが)。出力結果と見比べてください。また、intやlongなどの整数(小数点以下がない)の場合は、小数点以下の指定は不要です。
今度は、%fを%eにしてみましょう(「よく使うデータ型一覧」を参照)。コードと出力結果は次のようになります。
e-01とは、×10-1を意味します。また、全体の桁数の指定は、小数点と指数部を含んだ桁数になります(この場合は7)。
乱数 |
乱数を使うには、rand()関数を使います。rand()は、0〜RAND_MAXの間の値を返値とします。RAND_MAXとは、#defineであらかじめ定義されている定数のことです。具体的には215-1に変換されます。よって、必要な範囲の乱数にするには加工が必要です(剰余を使うのはその常套手段)。
また、乱数はrand()を呼び出す前に初期化しなければなりません。乱数の初期化にはsrand()関数を使います。srand()は、上記のソースのように使います(このように書けばまず間違いない)。
#include <stdlib.h>と#include <time.h>を書くのを忘れないように。
8進数と16進数 |
C言語では、普段我々が使っている10進数以外にも、8進数と16進数を扱うことができます(残念ながら2進数は扱うことはできません)。
8進数は、数の頭に(一見、意味のなさそうな)0を付けることで表現します。例えば、010は10進数の8になります(余談ですが、0という数は10進数で表現できないことになります)。
16進数は、数の頭に0x(または0X)を付けることで表現します。例えば、0x1fは10進数の31になります。
printf()やscanf()での8進数の扱いは%o。16進数は%xになります。
最後に |
ここまででC言語入門はおしまいです。これだけの量で終わらせるC言語入門はあまりないと思いますが、冒頭でも書いたように「大学の課題をこなせる」程度にはまあまあの量だと思います(専攻にもよりますが)。
しかし正直なところ、これではせっかくC言語の学習を始めたのに中途半端すぎます。もっともっと説明したいところは山のようにありますが(特にポインタ関連は全滅)、今後の学習は独学もしくは直接私に質問をする形で行ってください。文章での解説は専門の入門書に譲ることにします。しかし、本稿の目的が「課題をこなせる程度」とかなり特化した範囲になったこともあって、要望があれば応用編としてさらに各分野に特化したC言語入門を行うこともあるかもしれません。