C言語
現在、C言語の教科書に関する分割されたページがフルスクラッチにより作成されています。 ※フルスクラッチといってもこのページの内容を否定するものではありません。 このページに含まれている内容をそれがより正しいと思われるように、 また、言及されていなかった内容をできる限り補完するようにしていくつもりです。 したがって、このページを修正することは避けていただければ幸いです。 また、おおむねページ分割をおえたあとでも、こちらのページの情報が有用である場合もあるか と存じ上げますので、見易さのためにこのページの内容を削除する予定ですが、履歴のほうは 消さないようお願いいたします。 詳しくはノートをご覧ください。
目次 |
[編集] C言語
[編集] 各機能の解説
[編集] 関数呼び出し
この節でも次の例を用いて説明します。
#include <stdio.h>
int main()
{
int a;
a = 1 + 2;
printf("1+2=%d\n",a);
return 0;
}
さて、この例で4行目、5行目、7行目の3つの文は既に紹介しました(4行目は一応後述として先送りしましたが…)。残る6行目は何なのでしょうか?
今、この例は「1+2の計算をしたい」ということをC言語でやりたいという例でした。既に1+2の計算をするということは、前までの節でわかったのですから、6行目など必要なさそうに思えます。 では、そう思うならためしに6行目を抜いたプログラムを書いて実行してみてください。実行した結果はどうなりました?何にも表示されなかったはずです。
電卓では=キーを叩くことで計算結果を表示してくれましたが、C言語は違います。5行目では「足し算しろ(+)」「保管しろ(=)」という命令はあるのですが、実は6行目を抜いたことで「画面に表示しろ」という命令がなくなって、何にも表示されなかったわけです。つまり6行目は「画面に表示しろ」という命令なんだな~ってことが判ってもらえたと思います。 しかし、今までの + や = などとなんだか文の形が違いますね。実はこれは演算子ではありません。
実は「画面に表示しろ」という命令は、コンピュータにとって「足し算しろ」とか「引き算しろ」という命令よりもずっと難しい命令なのです。概略の一番最初で、関数とよばれる名前をつけられた処理のまとまりをつくり、以後その名前を書くだけで纏められた処理を実行できるという構造化の話をしました。
で、実は「画面に表示しろ」という命令は実に複雑な命令の組み合わせでできるものなので、それらの処理を実はまとめてあるのです。従って、その名前を書くだけで纏められた処理を実行できるようになっているのです。
その「名前を書くだけで纏められた処理を実行」する行為を関数呼び出し(または単に呼び出し)と呼びます。 関数呼び出しというのは「ここでこの関数を使いなさい」という命令と思ってください。つまり6行目は、「画面に表示する」関数printfをここで使いなさいという命令なのです。
さて、6行目の括弧の中は何を表しているのでしょうか?これを話すには「関数を作る」ことを説明してからの方がわかってもらえるでしょう。
[編集] 関数を作る
今までの例プログラムは
int main(){
...
}
という形をしていて、これが関数(ひいてはmain関数という名前の関数)というものであるという話は以前にしました。
今まではmain関数だけを作ってきましたが、もう一つ別の関数をつくってみることにします。ま、今までmain関数というものは作ってきたのですからそれに似せて
#include <stdio.h>
int function1(){
...
}
int main(){
...
}
と書けば、別の関数は作れるのだろうな~(しかもそれはfunction1関数という名の関数)ってのは、賢い人なら察しがつくかと思います。
まさにその通りです。これで別の関数が作れました。下の関数がmain関数という名前の関数ならば、上の関数はfunction1関数という名前の関数であるのも察しの通り。そしてこの関数をこの場で使って欲しければ、main関数を次のように書けばいいのも以前の説明から分かると思います。
int main(){
...
function1();
}
[編集] 引数と返し値
さて、関数のご利益は、同一の処理を纏められるだけにありません。似た処理を纏めることができるというご利益があるのです。似たというのはどういうことでしょう。
例えば「今ある円の半径がわかっているときに、その円の面積を求めたい(円周率は3.14とする)」。計算方法は既に知ってますね?半径をrとすれば、円の面積は 3.14 * r * r。今、半径1としたときの円の面積を計算するプログラムとして次のようなものを書いたとしましょう。
#include <stdio.h>
int main()
{
double a;
a = 3.14 * 1 * 1;
printf("%f\n",a);
return 0;
}
では、半径1と半径2の円の面積を計算したいときにどうすればいいのでしょう?ここで関数の登場です。「半径1の円の面積の計算」と「半径2の円の面積の計算」と考えると二つの違う処理なのですが、「円の面積の計算」と考えると同じ処理だなーって考える事が出来ます。 そこで半径が1だとか2だとかを伝える必要があります。その時に使うのが引数と呼ばれるものです。
#include <stdio.h>
double calc(double r){
double a;
a = 3.14 * r * r;
return a;
}
int main()
{
double a,b;
a = calc(1);
printf("%f\n",a);
b = calc(2);
printf("%f\n",b);
return 0;
}
引数というのは、calc関数の後ろにある()の中の変数rのことです。main関数の3行目のcalc(1)ではrが1となり、半径1の円の面積を計算し、5行目ではcalc(2)ではrが2となり、半径2の円の面積の計算を行っているということになります。 ちょっと待ってください。calc関数の中にある、return a;というのはなんなのでしょう。以前にも言いましたが計算機は、命令されたことしかやらないので、もしreturn a;がなければ、計算しっぱなしで計算したことを誰にも告げずに結果はどこかに葬られてしまいます(この話は以前に変数のところでもしたと思います)。なので、関数を呼び出したところに計算した結果を返してあげなければなりません。それが返し値と呼ばれるものです。
calc(1);
で、「半径1の円の面積」の計算を行ってその結果が返ってくるのですが、これもまた変数の話どうよう、誰かが覚えておかなければ闇に葬られるので、それを変数aにいれることでmain関数の中で「半径1の円の面積の計算」の結果が使えるようになるのです。
画面に表示する関数printfでも、画面に表示するものを引数として、指定することによって、表示されるものを変えることができるのです。
[編集] ポインタ
ポインタはC言語の中でも割合難しいとされている機能です。しかし、ポインタの考え方はどの言語でも用いられており、プログラミングを行う上で非常に重要な概念です。
ポインタは、ある情報が蓄えられている場所を指し示す値を与えます。例えば、ある変数aに対して、aが蓄えている情報には必ず番地がふられています。ポインタはその番地の番号を蓄える変数型です。これらの番地はそれ自体を何らかの整数であるかのように解釈することが出来、これによって情報が蓄えられている番地をprintfなどを用いて実際に見ることも出来ます。しかし、実際にポインタを使う時には、細かい番地について知る必要はなく、どのような変数に対応したポインタであるかを把握していればすむようになっています。
具体的には、ポインタ型の変数は次のように宣言します。
型 *変数名;
例えば、int型に対応するポインタは、
int *a;
などとして宣言されます。次に、ある値を変数を取り、その変数の場所を与えるには、
&変数名
という式を用います。この値はポインタ変数に代入することが出来、
int *a; int b; a = &b;
のように書くことが出来ます。これによって、bの場所がaによって指し示されています。aを用いて、bの内容を取り出すことも出来ます。具体的には、
*a
という式は、aが指し示す変数の内容(この場合はb)を取り出す式です。このような操作を、デリファレンスと呼びます。
ここで、ポインタの宣言の仕方で、
int *a, b;
などという書き方は、
int *a; int b;
という内容と同じであることに注意が必要です。2つのポインタを宣言したい時は、
int *a, *b;
と書く必要があります。
ポインタは多くの場合いくつかの決まった用途で利用されます。ここでは、それらを順にあげていきます。
- 関数に複数の返り値を持たせる
数値計算でよく用いられるFortran言語では、サブルーチンに与えられた引数は、メインルーチン内で用いられている変数と記憶場所を共有しており、サブルーチン内で与えられた変数の値が変更されると、その変更はメインルーチンにも伝わります。実際にはこれを用いて複数の値を関数内から返すことが出来る訳です。C言語の引数は基本的に値渡しであるので、サブルーチン内で値を変化させてもその変更はメインルーチン内では用いることが出来ません。そのため、複数の返り値を与える手法として、ポインタを用いる必要が出てきます。
具体的には、int型の答えを2つ返す関数fを定義する時には、
void f (int * res1, int * res2) { ... }
などと値を与えることが出来ます。ポインタ型の変数はこれをデリファレンスすることで、メインルーチン内の変数を変更できるので、このような使い方が出来ます。ここで、この関数を用いる時には、
int a, b; f(&a, &b);
のようにして、a, b などの変数ではなく変数の番地を与える必要があります。
実際には、後に導入する構造体を用いると、例えば、
struct aaa {
int res1;
int res2;
}
のように新しい型を定義することが出来るため、値を返す時にも、
struct aaa f();
の様にして明示的に複数の変数を返すことも出来ます。これらは状況によって使い分けるとよいでしょう。
- 大きな構造体を関数に与える
C言語は基本的に値渡しの言語であるので、大きな構造体を関数に与える時には、それらを全てコピーして用いようとします。これは時間のかかる作業となるので、より高速な処理を行いたい時には、構造体のポインタを与えることで、このコピーの作業を行わないようにしむけることが出来ます。具体的には、
void f(struct aaa * a) { ... }
のように関数を定義することで、このような方法を用いることが出来ます。
- 用いる領域を動的に定める
C言語にはmallocという標準関数があります。この関数は、利用者が用いることができるメモリ領域から、指定された大きさの領域を切り出す関数です。この関数は切り出した領域のポインタを返すため、それらの領域を用いるために、ポインタを用いる必要があります。mallocは多くの場合、sizeof演算子と組み合わせて用いられます。sizeof演算子はC言語の型を引数に取り、型の大きさを表すバイト数を返すオペレータです。例えば、int型へのポインタを返すmalloc文は次のようになります。
#include <stdlib.h> int *a; a = malloc(sizeof(int));
mallocは、用いたい領域の大きさをプログラムの中で定めたい時などに用いられます。例えば、利用者の入力を見て用いる領域を変化させる場合等がこの例です。 また、mallocによって切り出された領域は、プログラマによって切り出された領域であるので、例え使われなくなっても自動的に解放されることはありません。(ただし、プログラムが終了した時は別で、この時には全ての領域が解放されます。)そのため、切り出した領域を解放する方法が必要となります。この目的では、freeという標準関数を用いることが出来ます。ここでは、上の例の続きで、
free(a);
とすることで、切り出した領域を解放することが出来ます。
- 関数ポインタ
C言語では、関数へのポインタを用いて、関数を変数に'代入'することが出来ます。これは、別の関数に、引数として関数を与えたい時等に用いられます。例えば、標準関数qsortは、引数として2つの要素の比較を行う関数を取ります。このような要求を満たすために、関数へのポインタが用いられます。関数へのポインタは次のように宣言されます。例えば、void型の返り値を持ち、引数を持たない関数へのポインタpfは、
void (*pf)();
となります。ここで、*pfの周りの()は、演算子()の優先順位が、演算子*の優先順位より高いことから、省くことは出来ません。
-
- 注意
演算子の優先順位は、UNIX系のOSでは、
$man operator
で見ることが出来ます。
例えば、関数void f () {} が定義されている時には、変数pfに対して、
pf = f;
という代入が出来ます。ここで、ポインタへの代入なので、右辺は&fとすることも出来ます。しかし、上の代入と、&fとした時の動作は、同じになります。ポインタを用いて関数を呼び出すには、デリファレンス演算子*を用いて、
(*pf) ();
となります。
関数へのポインタは、C言語を用いたオブジェクト指向プログラミングなどで頻繁に用いられます。
- オブジェクト指向プログラミング
オブジェクト指向プログラミングは、データそのものに動作を与えるプログラミング手法です。例えば、スタックを考える時に、我々が行う動作は、push(置く)とpop(取り出す)だけで、そのような動作と適切なデータ領域を持ったデータを作れば、それを汎用的に用いることが出来ます。
オブジェクト指向プログラミングは、Java, C++などのオブジェクト指向をサポートする言語を用いないと行うことが出来ないと思われがちです。しかし、実際にはC言語でもオブジェクト指向を用いたプログラミングは割合普通に行われます。重要な例では、X Window System のアプリケーションや、Gtk+のウィジェット等は、C言語を用いてクラスを定義することで作成されています。
具体的には、C言語の構造体を用いてデータと関数へのポインタを1つにまとめます。先ほどの例では、
struct stack {
int * data;
void (*push)(struct stack *, int);
int (*pop)(struct stack *);
}
の様にして、data領域とpushとpopの2つのメソッドを持ったオブジェクトを作成することが出来ます。ただし、Javaのように、変数に対するアクセス制限を行うことは一般には出来ないため、これらは構造体を扱う人間が注意する必要があります。
[編集] 配列
配列はポインタと深い関係があります。まず、ポインタは演算を通じて配列同様複数のデータを管理することが出来ることを述べます。次に、ポインタを用いて、配列を扱えることについて述べます。
まず、ポインタは演算を行うことが出来ます。具体的には、int型のポインタpに対して、p+1もint型のポインタです。これは、元のポインタpが指す場所から、int型の大きさだけずれた場所を表します。この性質とmallocを用いて、複数のデータを扱うことが出来ます。int型に対応する大きさを確保するには、sizeof(int)を用いることを述べました。ここでは、複数のint型変数を確保するために確保したい変数の数をかけ算した値をmallocします。具体的には、
int * p; p = malloc(5*sizeof(int));
とすると、5つの整数だけの場所が確保されます。このとき、
*p = 1;
としてmallocされた領域の先頭に1を代入することができます。それ以降の領域に書き込む時には先ほどのポインタの演算を用います。例えば、2番目の領域に書き込む時には、
*(p+1) = 3;
とすると2番目の領域に書き込みがなされます。このように配列だけでなくポインタを用いても、複数のデータを管理することができることがわかります。
次に、配列の内容をポインタを用いて得ることが出来ることを述べます。配列で蓄えられているデータは、配列の最初に対応する番地から順に並べられています。そのため、配列の番地に対応するポインタがあれば、そのポインタを用いて配列の中身を取得することが出来ます。具体的にはint型のポインタpvとint型の配列vを用いて、 int *pv; int v[3]; pv = v; のようにポインタpvに配列vを代入することが出来ます。これは、vの先頭の場所をpvに代入する働きがあります。このとき、v[0]と*pv, v[1]と*(pv+1), ... などがそれぞれ同じ場所を表しています。
通常の利用では配列とポインタを混ぜて用いると混乱の元になることが予想されます。そのため、特殊な状況を除いて配列なら配列だけを用いる方がよいでしょう。
[編集] 文字列
[編集] ポインタのポインタ
C言語でかかれたプログラムを実行する時に、コマンドラインからプログラムに引数を与えることが出来ます。
- 例
$./a.out aaa bbb
この例は、UNIX系のシェルを用いた例です。ここでは、a.outという名のプログラムに対して、第1引数aaaと、第1引数bbbを与えています。
これらの引数は、プログラム中で扱うことが出来ます。これは、プログラムの動作を実行時に制御する目的等で用いられます。引数を用いるためには、main関数で次のような宣言を行います。
int main(int argc, char ** argv){ ... }
ここで、argcはプログラムの名前自身も含めた引数の数で、argvは、プログラムの名前と引数を文字列として与えています。
argvのデータ型はcharのポインタのポインタとなっており、やや扱いが難しい型です。例えば、argvをデリファレンスするとプログラムのの名前を表す文字列へのポインタが得られます。これは、argvはそれぞれの文字列へのポインタを蓄えたデータであり、先頭に蓄えられているデータは関数の名前であるからです。 第1以降の引数を表す文字列へのポインタを得るには、argvを先に進めることが必要です。このためにポインタの演算を用いることが出来ます。例えば、第1引数へは、*(argv + 1)で文字列へのポインタを得ることが出来ます。以降第n引数へは、*(argv + n)で文字列を得ることが出来ます。
通常文字列は文字列として扱われることが多いのですが、文字列から文字を得ることも出来ます。上の例では、
**argv
は、*argvで表される文字列へのポインタの最初に位置する文字を返します。上の例では、./a.outの先頭にある.が返されます。同様に
*(*argv +1)
は、./a.outの2文字目である/が返されます。このようにして引数として与えられた全ての文字を得ることが出来ます。
[編集] 文字列の標準関数
文字列に関しては様々な標準関数が用意されています。そのうちのいくつかは簡単に書くことが可能です。ここでは、いくつかの簡単な文字列関数を紹介します。
文字列に関する関数のヘッダファイルは、string.hと呼ばれます。そのため、文字列の関数を用いるには、
#include <string.h>
と書くことが必要になります。stringにはいくつかの関数が含まれています。詳細な説明は個々のマニュアルを参照してください。UNIX系のシステムでは、
$man string
で関数の内容を閲覧できます。
ここでは、標準関数の1つである、strlenを取り上げます。strlenは、char *型の引数を取って文字列に含まれる文字数を数えます。数えた数は、関数の戻り値として返されます。基本的な使い方は、
int a; char * s = "abc"; a = strlen(s);
となります。ここでは、sには3文字が蓄えられているので、aには3が代入されます。
実際にはstrlenは、与えられた文字型ポインタから始めて'\0'が出るまでにいくつの文字があるかを数えています。そのため、これを実際に書こうとすると、割合短く収まります。
- 例
int st_len (char *c ) {
int i=0;
while (*c != '¥0'){
++i;
++c;
}
return i;
}
ここでは、受け取った文字列へのポインタcを進めながら*cが'\0'でないかを調べています。同時に整数iをインクリメントし、文字の数を数えています。 このように文字列の標準関数のいくつかは割合簡単に書くことが出来ます。しかし、実際には自分で書く手間を考えると、標準関数を用いる方がよいでしょう。
[編集] プリプロセッサ
C言語ではプリプロセッサと呼ばれるプログラムがコンパイルの過程で用いられます。これは、コンパイルされるプログラムの機能を制御する時などに用いられます。また、プリプロセッサが扱う命令の中にはマクロと呼ばれる機能を用いるために使うことが出来る命令もあるため、プリプロセッサはいくつかの方法で用いられるプログラムとなっています。ここでは、よく用いられる例について解説します。
プリプロセッサの命令でよく用いられる命令には、
- #if ... #endif
- #include
- #define
などがあります。
- #if ... #endif
これは、コンパイルの際にこの命令で挟まれた部分を取り除くかどうかを定める命令です。#ifの直後に真偽が定まる式が続き、それが真である時には、#if ... #endifは、命令だけが取り除かれます。しかし、偽だった時には、#if ... #endifの命令を含めて、その間に書かれているプログラムの全体が取り除かれた上でコンパイルが行われます。
- 例
#if 0 aaa #endif
これは、不必要な命令をプログラム中から取り除く例です。不必要な命令としては、コメントや使われなくなった関数などがあげられます。これは、/* */を用いたコメントとほとんど同じ用いられ方をします。
- if ... #endifに似た命令として、#ifdef ... #endif、#ifndef ... #endifがあります。これらは、#defineやコンパイル時のオプションによって与えることができるパラメータが設定されているかどうかを見分け、それによってこれらの命令に挟まれた部分をコンパイルするかどうかを定めます。
- 例
#ifdef DEBUG printf(...); #endif
これは、デバッグを行っていて、プログラムで実行されている内容が知りたい時によく用いられる手法です。デバッグを行いたい時だけ、DEBUGという名前の#defineを定義します。
#ifdef i386 ... #endif
これは、(おそらく)インテルCPUを用いているコンピュータにおいてだけ用いられる機能を挟むために用いられます。対応するCPUに対してコンパイルを行う時には、i386という#defineを用います。 このようなコンパイル手法は、あらゆるハードウェア間の互換性を実現するために用いることができ、実際に大きなオープンソースプロジェクトでは、必須の技術となっています。
- #include
これは既に何度か出てきた命令です。#includeは、
#include <ファイル名>
または、
#include "ファイル名"
の形で用いられ、ファイル名で指定されたファイルをまるごとその位置に読み込む働きをします。これは、多くの場合、プログラムで用いる関数のヘッダファイルをインクルードするために用いられます。
実際には、ヘッダファイルには関数のプロトタイプ宣言が書かれているのが通例です。プロトタイプ宣言は、用いられる関数の引数の型と数、及び戻り値の型だけが与えられた形です。実際の関数の中身は、ここでは与えられません。例えば、標準関数を用いるためには、それらは既に実行ファイルとしてコンパイルされていることが通例です。この時、それらを呼び出すためには、それらにどのような引数を与える必要があるかなどを指定する必要があります。プロトタイプ宣言はそのような情報をコンパイラに与えるために用いられます。
用いる関数のプロトタイプを知っているなら、 (UNIX系のシステムでは、$man 命令の名前 で見ることができます)ヘッダファイルをインクルードしなくても、プログラムの最初の方で用いる関数のプロトタイプを直接書き込むことで、#include なしですませることもできます。しかし、通常は#includeを用いるのがよいでしょう。
- 例
#include <stdio.h>
これは、stdio.h(standard Input/Output)に関するヘッダファイルをインクルードするための命令です。UNIX系のシステムでは、このヘッダファイルは、/usr/include/stdio.hに置かれています。
#include "aaa.txt"
<>ではなく、""で囲まれたファイル名が用いられた時には、インクルードされるファイルの場所として、最初にカレントディレクトリ内が探されます。また、#includeは、対応するファイルが.cや.hなどのよく用いられる拡張子を持っているかをチェックすることなくインクルードを行うので、注意する必要があります。
- #define
これは、マクロや定数を定義するための命令です。これは用いられ方によっては混乱を引き起こすため、C++では、この機能を用いなくてもよいようにconst, inline, テンプレートなどの機能が導入されました。これらの機能はより新しいC99と呼ばれるCの標準ではCの側にも取り入れられ、マクロを用いる必要性は減少しています。しかし、依然としてよく用いられる機能でもあります。
この命令は、#defineの後に書かれた語句を、定義された命令によって置き換える働きをします。これは、関数などと似た働きですが、#defineの場合はコンパイルされる命令自体が書きかえられる点が関数の場合と異なっています。(ファイルの内容は書きかえられないことに注意。)特に、関数に似た性質を持つ置き換えを、マクロ関数、もしくは単にマクロと呼ぶことがあります。
- 定数
この命令は、定数を定義するためによく用いられます。
- 例
#define ARRAY_SIZE 100 #defineの基本的な使い方です。これは、この命令が置かれた場所以降でARRAY_SIZEという語句が用いられたとき、これを100に置き換える働きをします。これは、(おそらく)配列の大きさを後で変更する目的で用いられています。ARRAY_SIZEは、次のように用いられます。
int v[ARRAY_SIZE];
ここで、コンパイルされる時には、プリプロセッサの働きで、この部分は
int v[100];
に置き換えられます。
定数をマクロによって定義する時には、対応する語句は全て大文字で書くことが通例です。しかし、実際には小文字で書いても問題はありません。
- マクロ関数
マクロ関数は関数呼び出しをさけることなどを目的として用いられます。現代的には似た機能を持つインライン関数を用いた方がよいでしょう。インライン関数についてはC++などを参照してください。 #defineを関数として用いる場合には、
#define 関数名(引数名1,引数名2, ... ) 置き換えたい命令
の形を取ります。