C ( ) C ( ) C C C C C 1 Fortran Character*72 name Integer age Real income 3 1 C mandata mandata ( ) name age income mandata ( ) mandat

22 

Loading.... (view fulltext now)

Loading....

Loading....

Loading....

Loading....

全文

(1)

C

言語の実習

(

中級編

)

郡司 修一

平成

14 年 5 月 23 日

概 要 このテキストは C 言語の実習 (初級編) を読んだ人が C 言語のより進んだ機能を修得 するために用意されたものである。このテキストまで進めば 、一応 C 言語はある程度マ スターし たと言って良い。ただし 、初級編にし ても中級編にしても C 言語らしいプログ ラミングスタイルというものは、あまり紹介していない。そのため、C で書かれた他人の プログラムを読むのはしんどいはずである。しかし 、ここまで読み進めば 、C の参考書を すらすら読めるはずなので 、参考書を買って読んでみることを薦める。

1

構造体

プログラムを組んでいて、ある一組の情報を扱いたい場合が登場する事があるであろう。例 えば人の名前、その人の年齢、その人の収入を扱うプログラムがあった場合、Fortranでは以 下のように変数を定義するのが通常の方法だと思う。 Character*72 name Integer age Real income 確かに上の様に定義して、それぞれの変数に値を代入したり、操作するプログラムを書けば 問題ないが 、以上の3つのデータは本来1組のものである。しかし 、上の書き方ではそれら が一組のデ ータであることは分かりづらい。そこでCでは構造体というものを使い、それら が一組のデータであるという事を明示的に書く方法がある。以下ではその構造体に関して学 ぶことにする。

1.1 基本

まず以下のプ ログラムを見てもらいたい。これらは一番簡単な構造体の例である。まず構 造体の定義であるが 、それは3行目から7行目に書かれている。ここではmandataという新 しい構造体を定義している。構造体を定義するとは 、複数の変数を一組にしたものを新しい 変数の型とし て定義しているようなものである。ここではmandataという新しい型(構造体)

の要素は 、nameという文字配列とageという整数とincomeという実数で構成されている事 を定義している。次にメインプログラムの中でまずmandataという新しい型(構造体)の変数 を定義している。ここではmandata1という構造体変数が定義されている。この構造体変数 の要素を取り出すには 、ド ットを付けて、それぞれの要素の変数名を書けば 良い。このド ッ トの事をド ット演算子と呼ぶ。

(2)

#include <stdio.h> #include <string.h> struct mandata { char name[51]; int age; float income; }; main() {

struct mandata mandata1;

strcpy(mandata1.name ,"Shuichi Gunji"); mandata1.age = 39; mandata1.income = 100000.0; printf("name=%s\n",mandata1.name); printf("name=%d\n",mandata1.age); printf("income=%f\n",mandata1.income); } また以上の様な構造体変数の値を初期化するには 、以下の様にすればよい。

struct mandata mandata1={"Shuichi Gunji",39,100000.0};

以上では構造体の要素にアクセスするのに、ド ット演算子を使ったが 、それ以外にポ イン タを使って、構造体の要素にアクセスする事もできる。以下はその例である。以下のプ ログ ラムでは 、まずmandata1という構造体変数を定義した後、すぐに構造体変数のポ インタ(p) を定義している。そして、mandata1という構造体変数の先頭アドレスを構造体ポインタ変数 のpに代入している。 #include <stdio.h> #include <string.h> struct mandata { char name[51]; int age; float income; }; main() {

struct mandata mandata1; struct mandata *p;

p = &mandata1;

strcpy(p->name ,"Shuichi Gunji"); p->age = 39;

p->income = 100000.0;

(3)

printf("name=%d\n",p->age); printf("income=%f\n",p->income); } 以上で、アドレスの受け渡しができたので、pという構造体ポ インタ変数で構造体の要素にア クセスする事が原理的にできることになる。今までの知識からすれば 、この構造体の要素に アクセスするには 、以下のようにすれば 良いはずであり、実際に以下の様にしても構造体の 要素にアクセスできる。しかし 、ここでいささか紛らわしい自体が発生する。(*p).ageと書 くのは正しいが*p.ageと書くのは間違いである。*p.ageと書くと、これは*(p.age)とプログ ラムでは判断されてしまうからである(ド ット演算子の方が 、*演算子よりも結合度が強い)。 strcpy((*p).name,"Shuichi Gunji"); (*p).age = 39; (*p).income = 100000.0; そこでこの様な誤解をなるべく少なくするために 、アロー演算子というものが 定義されてい る。サンプルプ ログラムでは 、このアロー演算子が使われている。もし 構造体のageにアク セスしたければ 、p− >ageと書けば これはちゃんと構造体の要素にアクセスする事ができる。

1.2 構造体を関数に渡す

構造体を関数の引数とし て渡すことができる。この例を以下のサンプルプログラムに示す。 以下のサンプルプログラムは構造体をそのまま引数とし て与えた例であり、値渡しになって いる。もし 関数内で構造体の値を変えたい場合には 、渡した構造体をそのまま、返り値とし て戻すか 、後で説明する構造体のポ インタ渡しを行わなくてはいけない。 #include <stdio.h> #include <string.h> struct mandata { char name[51]; int age; float income; };

void disp( struct mandata mandata1); main()

{

struct mandata mandata1;

strcpy(mandata1.name ,"Shuichi Gunji"); mandata1.age = 39;

mandata1.income = 100000.0; disp(mandata1);

}

(4)

printf("name=%s\n",mandata1.name); printf("name=%d\n",mandata1.age); printf("income=%f\n",mandata1.income); } 構造体を返り値とし て戻すには 、上のプ ログラムを以下のように変更すれば 良い。以下のプ ログラムは 、書き換える箇所だけ示している。 ....

struct mandata disp( struct mandata mandata1); main()

{ ....

mandata1=disp(mandata1); }

struct mandata disp(struct mandata mandata1){ ...

mandata1.age =30; /* 構造体の要素ageを書き換えている */ return mandata1; /* mandata1の値を戻している。*/

} 次にアド レスで渡すサンプルプログラムを示す。以下では構造体変数としてmandata1と、 構造体ポ インタ変数*pの2つをまず定義している。その後、構造体変数mandata1の先頭ア ドレスを構造体ポ インタ変数pに代入している。そして、そのポ インタ変数を関数に渡して いる。関数の方では 、引数を構造体ポ インタ変数にして、アロー演算子を使って読み書きを 行っている。一応以下は、関数で変更した値が メインプログラムで変更されているかを確か めるために 、関数中でageの値を書き換えてみた。 #include <stdio.h> #include <string.h> struct mandata { char name[51]; int age; float income; };

void disp( struct mandata *p); main()

{

struct mandata mandata1; struct mandata *p;

p = &mandata1;

strcpy(mandata1.name ,"Shuichi Gunji"); mandata1.age = 39;

(5)

mandata1.income = 100000.0; disp(p);

printf("mandata.age= %d\n",mandata1.age); }

void disp(struct mandata *p){ printf("name=%s\n",p->name); printf("age=%d\n",p->age); printf("income=%f\n",p->income); p->age = 30; }

1.3 構造体の配列

構造体を配列にする事ができる。この例を以下に示す。以下のプログラムでは、まずmandata という構造体を定義している。そし てメインプログラムでは、構造体の配列を定義している。 ここら辺の書き方は、struct mandataという新しい変数の型が作られたと考え、mandata1[3]

という3つの配列が定義されたと考えれば良い。次にそれぞれの要素にデータを入力している が 、構造体自体が配列になっているので 、mandata1.age[0]とかではなく、mandata1[0].age としなくてはいけない。またプ ログラムの最後では 、念のため配列の3番目に位置する要素 を全て画面に打ち出している。 #include <stdio.h> #include <string.h> struct mandata { char name[51]; int age; float income; }; main() {

struct mandata mandata1[3];

strcpy(mandata1[0].name ,"Shuichi Gunji"); mandata1[0].age = 39;

mandata1[0].income = 100000.0;

strcpy(mandata1[1].name ,"Fuyuki Tokanai"); mandata1[1].age = 31;

mandata1[1].income = 80000.0;

strcpy(mandata1[2].name ,"Hirohisa Sakurai"); mandata1[2].age = 53;

mandata1[2].income = 200000.0;

(6)

printf("name=%d\n",mandata1[2].age); printf("name=%f\n",mandata1[2].income); } 以上のプ ログラムを構造体のポ インタで表現することもできる。この例を以下に示す。以 下の例では、mandata1という構造体の配列の先頭のアドレスをまず構造体ポインタ変数に代 入している。そし て、構造体のポインタ変数の値を1づつ増やし ていき、そこにデータを入 力している。 #include <stdio.h> #include <string.h> struct mandata { char name[51]; int age; float income; }; main() {

struct mandata mandata1[3]; struct mandata *p;

p=mandata1;

strcpy(p->name ,"Shuichi Gunji"); p->age = 39;

p->income = 100000.0;

strcpy((p+1)->name ,"Fuyuki Tokanai"); (p+1)->age = 31;

(p+1)->income = 80000.0;

strcpy((p+2)->name ,"Hirohisa Sakurai"); (p+2)->age = 53; (p+2)->income = 200000.0; printf("name=%s\n",mandata1[2].name); printf("age=%d\n",mandata1[2].age); printf("income=%f\n",mandata1[2].income); }

1.4 応用

以下は構造体の配列を関数に渡したり、構造体の構造体を使ったプ ログラムである。これ はかなり応用的な部分が強いが 、案外よく使うので 、ここにサンプルプログラムを示す。ま ず初めのプログラムが構造体の配列を関数に渡すプログラムである。ここでは 、わざ と構造 体の配列を構造体のポ インタで受ける例と、構造体の配列を構造体の配列で受ける例を示し た。この様に構造体の配列を関数に渡す場合は 、構造体を渡す事と配列を渡す事を単に複合

(7)

させれば 良いことが分かる。 #include <stdio.h> #include <string.h> struct mandata { char name [51]; int age; float income; };

void datin(struct mandata *p); void disp(struct mandata mdat[]); main()

{

struct mandata mdat[3];

datin(mdat); disp(mdat); }

void datin(struct mandata *p){ strcpy(p->name,"Shuichi Gunji"); p->age=39;

p->income=100000.0; }

void disp(struct mandata mdat[]){ printf("name=%s\n",mdat[0].name); printf("age=%d\n",mdat[0].age); printf("income=%f\n",mdat[0].income); } 次に構造体の構造体というものを扱ってみる。以下にサンプルプログラムを示す。このプ ログラムではまず、saimokuと名付けられた構造体が定義されている。要素は3つである。次 にmandataという構造体が定義されているが 、この要素には、char型とint型の変数の他に、

saimokuという構造体の新しい型が定義されている。この様に構造体の中に構造体を入れて 定義することが可能である。またその様な要素に値を代入する場合には 、ド ット演算子を2 回使う方法とアロー演算子とド ット演算子を使う方法が存在するが 、プログラムでは 、最初 に2回ド ット演算子を使う方法が示されており、次にアロー演算子とド ット演算子を使う方 法が示されている。 #include <stdio.h> #include <string.h> struct saimoku { float income1; float income2; float income3;

(8)

};

struct mandata { char name [51]; int age;

struct saimoku income; };

main() {

struct mandata mdat; struct mandata *pmdat;

strcpy(mdat.name,"Shuichi Gunji"); mdat.age= 39; mdat.income.income1=100000.0; mdat.income.income2=50000.0; mdat.income.income3=20000.0; pmdat = &mdat; printf("mdat.income.income1=%f\n",pmdat->income.income1); printf("mdat.income.income2=%f\n",pmdat->income.income2); printf("mdat.income.income3=%f\n",pmdat->income.income3); } また上のプログラムを以下のように変更すると、2度アロー演算子を使って、データの要素 にアクセスできる。 #include <stdio.h> #include <string.h> struct saimoku { float income1; float income2; float income3; }; struct mandata { char name [51]; int age;

struct saimoku *pincome; };

main() {

struct mandata mdat; struct mandata *pmdat; struct saimoku temp;

(9)

pmdat = &mdat; mdat.pincome = &temp;  /* この初期化を忘れずに  */ strcpy(pmdat->name,"Shuichi Gunji"); pmdat->age= 39; pmdat->pincome->income1=100000.0; pmdat->pincome->income2=50000.0; pmdat->pincome->income3=20000.0; printf("income1=%f\n",pmdat->pincome->income1); printf("income2=%f\n",pmdat->pincome->income2); printf("income3=%f\n",pmdat->pincome->income3); printf("mdat.pincome->income1=%f\n",mdat.pincome->income1); printf("mdat.pincome->income2=%f\n",mdat.pincome->income2); printf("mdat.pincome->income3=%f\n",mdat.pincome->income3); }

2

分割コンパイル

大きなプ ログラムを作成する場合、プログラムを幾つものファイルに分割する場合が多い。 その様な時にそれぞれのプ ログラムの書き方はさほど 変わらないが 、幾つか注意することが ある。まず最初に以下のサンプルプ ログラムを考える。以下のサンプルプログラムには メイ ンプログラムだけが記述されている。そして、そのメインプログラムから関数dispが呼ばれ ている。以下のサンプルプログラムには 、dispという関数の本体が書かれていないが 、プロ トタイプ宣言を書くべきである。なぜならmain関数でdispを呼んでおり、その関数がど の 様な関数であるのかが最初に書いていないと 、トラブルのもとになるからである。 #include <stdio.h> void disp(int i); main() { int i=3; printf("i=%d\n",i); i=2; disp(i); } 次に別のファ イルにdispという関数の本体が以下のように書かれているとする。このプ ログ ラムには逆にdispという関数のプロト タイプ宣言は書かれていない。なぜならこのファイル に書かれているプログラムから 、dispという関数を呼んでいないからである。この様にファ イルの中で、自分の作った関数を呼ぶ場合だけ、プロトタイプ宣言が必要になるのである。ま た先ほどのメインプログラムにも、disp関数だけを記述したファイルにも、stdio.hが インク

(10)

ルード されている。それは2つのファ イルの中でそれぞれprintf関数を呼んでおり、この関 数を使うためのプ ロト タイプ宣言がそれぞれでなされていると、トラブルが少なくなるから である。

#include <stdio.h> void disp(int i){

printf("i=%d\n",i); }

以上2つのファイルをsample1.cとsample1-1.cとしたときに、実行ファイルsample1を製作 するには 、以下のようなコマンド を打つ。

kdeve% gcc -o sample1 sample1.c sample1-1.c

もう一度整理すると以下の事を気を付けてソースファ イルを書くようにすべきである。 1.それぞれのファイルにCの標準関数が使われているなら 、それぞれのファ イルの先頭で stdio.hをincludeするようにする。 2.自分で作った関数がファ イルの中で使われているならば 、そのファ イルの先頭付近でそ の関数のプ ロト タイプ 宣言を書く。 以上の2つの規則に従わなくても 、実行ファ イルを作ることは原理的にできるが 、以上の規 則はプログラムを安全に書く際に重要である。以下に2つのファ イルの中身を示すが 、この プログラムは間違っているにも関わらず、実行ファ イルが作れてし まう。そし て実行ファイ ルを動かした時にプログラムはつぶれてし まう。以下がsample1.cである。 #include <stdio.h> void disp(int i); main() { int i=3; printf("i=%d\n",i); i=2; disp(i); } 以下がsample1-1.cである。 /*#include <stdio.h> */ /*これを書かなかったとする*/ void disp(int i){

printf(i,"i=%d\n"); /* 引数の与え方が間違えている */ } プログラムを幾つも書いていれば 、自分だけの有用な関数が増えてくる。この関数をまと めておいてうまく使う事ができる。例えば独自にdispとindatという非常にしばしば 使う関 数を作ったとする。この2つのプ ログラムを以下のように並べて書いたファイルをgunji.hと して定義する。

(11)

void disp(char dummy[]); void disp( char dummy[]){ ...

}

void indat(int i); void indat(int i){ ... } そし て 、これらの関数をもし 使いたいのであれば 、以下のようにプ ログ ラムを書けばよい。 以下のプ ログラムで最初にgunji.hという自作関数が書かれたファ イルを取り込んでいるが 、 gunji.hがダブルクオーテーションで囲まれている事に注意すること。この様に自作関数を includeする場合には 、そのファ イルネームをダブルクオーテーションで書くという規則が ある。 #include <stdio.h> #include "gunji.h" main() { int j=2; char temp[50]="ABCD"; indat(j); disp(temp); }

3

変数のスコープの続き

先ほど 複数のファ イルでプ ログラムを作る際の注意を書いた。この様に複数のファ イルで プログラムが構成された場合、変数のスコープに関して、注意することが幾つか出てくる。こ こでは以前学んだ変数のスコープの復習をおこないながら 、さらに進んだ関数のスコープに 関して説明する。 ローカル変数とは、ある特定のプログラム領域でのみ、有効な変数の事である。以下のプロ グラムの雛形では 、aという変数はmainプログラムでだけ使える変数であり、bは関数disp でだけ使える変数である。またcはプログラム全体で使用することができる。この様に変数 には 、その有効範囲というものが 存在し 、それをスコープ と呼ぶ。通常中カッコの中の先頭 に定義されている変数は 、その中カッコの中だけで有効な変数となる。また中カッコの外に 完全に出ている変数は、このファ イル全体で使用することができるのである。 #include <stdio.h> int c; void disp(void); main()

(12)

{ int a; } void disp(void){ int b; } もし ローカルな変数とグローバルな変数の名前が同じである場合には 、ローカルな変数が有 効となる。例えば 以下のプ ログラムの雛形では 、main関数の中で記述されているaはmain 関数で定義されているaが有効になる。またdisp関数の中ではdisp関数の中で定義されてい るbが有効になる。 #include <stdio.h> int a,b,c;  /*グローバル変数*/ void disp(void); main() { int a;  /*mainプログラムの中でだけ有効なローカル変数*/ a=1; /*ローカルなaである*/ b=2; /*グローバルなbである。*/ } void disp(void){ int b;  /*dispの中だけで有効なローカルなb である。*/ a=3;  /*グローバルなaである*/ b=4;  /*ローカルなbである*/ c=5;  /*グローバルなcである*/ } では 、もし2つのファイルからプログラムが構成されている場合には、2つのファイルを横断 するグローバルな変数というものを定義することができるのか考えてみる。以下にsample2.c とsample2-1.cという2つのプ ログラムを示す。以下の2つのプ ログラムでは 、global aとい 変数がでてくるが 、この変数は両者のファ イルで共通に使える変数である。以下を見ると分 かるが 、ある一つのファ イルの先頭付近に 、グローバル変数の定義をまず書く。そし てそれ 以外のファイルでは、ファイルの先頭にexternという文字を書いてからグローバル変数を定 義する。「extern int 変数」とは文は「 外部に定義されている整数の...」を参照せよという宣 言だとし て解釈できる。 /* sample2.c  */ #include <stdio.h> int global_a; void temp(void);

(13)

main() { temp(); .... } /* sample2-1.c  */ #include <stdio.h> extern int global_a; void temp(void){ ....

}

次の例では 、externという宣言を使ったちょっと変わった例を示す。以下の例では確かに、

global aという変数はsample2-1.cの中で宣言されているが 、ファイルの先頭ではなく、main

プログラムの中で宣言されている。この場合sample2-1.cのdispという関数の中ではglobal a

という変数を使えなくすることができる。 /* sample2.c  */ #include <stdio.h> int global_a; void temp(void); main() { temp(); .... } /* sample2-1.c  */ #include <stdio.h> void disp(void); void temp(void){ extern int global_a; .... } void disp(void){ .... } 今までの例で 、グローバル変数が非常に便利だということは分かったと思うが、2人の人間 がプログラムを独自に組んで 、最後に両者を合体させる時に、面倒な事が起こる場合がある。 例えば 、Aさんは自分のプログラムでだけ通用するグローバル変数としてiという変数を使っ ていたとする。しかし 一方Bさんも自分のプログラムだけで通用するグローバル変数として iを定義してし まったとする。通常この様にすると、2つの変数の値は独立ではなくなってし

(14)

まい、まずいことが 起きる。これを防ぐ ために以下の方法がある。以下のようにstaticとい う文を付けて変数を定義すれば 、それはこのファイルだけで通用するグローバル変数であり、 他のファ イルからは参照できないようにすることができる。 /* sample2.c  */ #include <stdio.h> static int i; void temp(void); main() { temp(); .... } /* sample2-1.c  */ #include <stdio.h> static int i; void temp(void){ .... }

4

関数から値を返す時の注意

以下の様なプログラムを作ったとする。しかし 、これは非常に予想外の動き方をする。この プログラムで何がおかしいのか考えてみることにする。このプログラムはmain関数とindat とindat1の2つの関数からできている。またこの2つの関数はポ インタを返す関数(値では なくアド レスを返す関数)である。まず、indatという関数を使って、iに10という値が代入 されている。そし てメインプログラムでiの値を確認している。確かにその結果最初iは10 にセットされている。次にiという変数には全く関係のないkという変数にindat1という関 数を使って、2が代入されている。そして、もう一度iの値を画面に打ち出している。しかし 、 このときに画面に打ち出されたiの値は2になっている。iの値を変更していないのに 、なぜ iの値がkに代入したはずの2という値にセットされてし まったのかというのが 、このプログ ラムの一番の問題点である。なぜこの様な結果が出てし まうのかは 、関数とポ インタに関し て熟知していないと分からないと思う。またある程度Cに慣れてきて 、色々なプログラムが 書けるようになると、以下のような間違いを犯しやすい。 #include <stdio.h> int *indat(void); int *indat1(void); main() { int *i,*k;

(15)

i=indat(); printf("i=%d\n",*i); k=indat1(); printf("i=%d\n",*i); } int *indat(){ int dummy; int *p=&dummy; *p=10; return p; } int *indat1(){ int dummy; int *p=&dummy; *p=2; return p; } このプ ログラムが思った通りの動作をし ないのは 、関数内でポ インタ変数を定義して、その ポ インタ変数のアド レスをmain関数に渡しているためである。この様な場合にコンピュー ターの中で行われる事を順番に示す。 1.まず制御がindatに移ると、テンポラリーにdummyという変数とポインタ変数pのメモ リー領域が確保される。 2.ポインタ変数の指すアドレスに10という値を格納する。そのpの指すアド レスが 、main 関数に渡される。

3. pの指すアド レスがmain関数に渡された時点で 、indat関数の中で確保したdummyと

pのデ ータ領域が解放される。ここでこの領域が解放されてし まうことが 問題なのであ る。一般に関数内の変数のメモリー領域は特別な指定をしない限り、関数から抜けると 解放されてし まう。

4.従って、メインプログラムでiというポインタ変数のアドレスはもはやしっかり確保され ておらず、他の変数がその領域を確保してし まう可能性がある。

5. main関数では次にindat1という関数を呼ぶが 、このindat1では同様にdummyとpと いう変数のために メモリー領域が確保される。この確保されたメモリー領域は、先ほど 開放されたメモリー領域と同じ 場所である場合が多い。

以上の様な問題を避けるには、次の事を覚えておくと良いであろう。「 関数内でローカルに確 保された変数のアド レスを引数として与えない方が良い。Cの仕様をちゃんと理解するまで は 、アド レスを戻り値にするようなプログラムは書かない方が良い。」

(16)

5

ビット 演算

変数の型はそれぞれの決まった大きさのメモリー領域を確保する。例えば 以下のプログラム を走らせると、それぞれの変数の型が何バイトのメモリー領域を確保しているのかが分かる。 #include <stdio.h> main() {

printf("byte of int =%d\n",sizeof(int)); printf("byte of char =%d\n",sizeof(char)); printf("byte of float =%d\n",sizeof(float)); printf("byte of double =%d\n",sizeof(double)); }

大抵の計算機では 、int型は4バイト、char型が1バイト、float型が4バイト、double型が8

バイトという値を返してくると思う(そうでない計算機もたまに存在する)。1バイトは8ビッ トであり、そのビ ット毎のデ ータを操作するための演算子がCでは用意されており、これを ビット演算子と呼ぶ。このビット演算子は、データ収集系のプログラムを構築する際には、欠 かせないものなので 、以下にそれを説明する。 まず注意することは 、ビ ット演算子は整数に対してしか使用することはできないという事 である。先ほど の例では整数は4バイトで表現される。この4バイトというのは、計算機の メモリー上では以下の図1の一番上の図の様に確保されている。1バイトは8ビットであり、 1byte目 2byte目 3byte目 4byte目 一つの四角は1ビットを表す。 1000 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2進数で1000を表し、10進数では8に相当する 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 10進数では−2147483648に相当する 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 10進数では−2147483647に相当する 1 0 1 図1: 整数がコンピューターでど の様に格納されているかを示す図   1ビ ットは0か1のいづれかが代入される。そし て、この値を2進数であると考えて、値が 格納されている。上から2番目の図には10進数で8という値が格納されている状況での各 ビ ットに入っている値を示している。4バイトとは232乗に相当するので 、通常のint型の整 数では、4294967296個の数を表現できるが 、整数は負の数も存在するため、−2147483648か ら2147483647までの数を取ることになる。図1には 、−2147483648という数がコンピュー

(17)

ターの内部でど の様に表現されているかを示している。一番上位のビ ットが1である時は負 の値を示す。しかし 、負の数を取らないように整数を定義することもできる。つまり一番上 位のビ ットが1でもこれは負の整数では無いという様にコンピューターに解釈させる方法で ある。これは以下のように書く。 unsigned int i; 上の様に定義すると、iという数は0から4294967295までの数を取ることができる。またビッ ト演算子も大抵この様な符号無しの整数に対して行われる場合が多い。 それでは本題のビ ット演算子に関して説明する。ビ ット演算子には 、以下の表1に示され ているようなものがある。以上の演算子をプ ログラム中で使っている例を示す。 演算子の記号    説明    用例

&  ビ ットAND   a= b & 0x0000FFFF   |    ビ ットOR    a = b | 0x0000FFFF<<    ビ ット左シフト    a = b << 2 >>    ビ ット右シフト    a = b >> 1 表 1:ビ ット演算子 #include <stdio.h> main() {

unsigned int i,j;

i=0x0000000F; /* 0xで始まる数は、16進数を表す。        10進数ではこの数は15になる。*/ j= i & 0x00000008; /*ビットANDの例 */ printf("j=%d\n",j); i=0x0000000F; j= i | 0x00000010;  /*ビットORの例 */ printf("j=%d\n",j); i=0x00000001; j= i <<2;  /*左シフトの例 */ printf("j=%d\n",j); i=0x00000010; j= i >>2;  /*右シフトの例 */ printf("j=%d\n",j); } 以上のプ ログラムを走らせると同時に 、以下の図2の説明を参照し 、正し く計算が行われて いるか調べてみよ。

(18)

1000 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ビットANDの例 0x00000008 0x0000000F 000000000000000000000000000011 1 1 両方とも1の時1で それ以外はゼロ 1000 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 演算結果 10000 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ビットORの例 0x00000010 0x0000000F 000000000000000000000000000011 1 1 両方とも0の時0で それ以外は1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 演算結果 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 左シフト(2)の例 0x00000001 0000000000000000000000000000 1 左方向に2つづらす 演算結果 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 右シフト(2)の例 0x00000010 0000000000000000000000000001 0 右方向に2つづらす 演算結果 0 0 0 1 0 図 2:ビ ット演算子の意味とその結果  

6

様々な関数

今まで最低限の関数しか紹介しなかったが 、非常に便利な関数がCには多く存在する。し かし 、これらを全部覚えるのは非常に大変なので 、以下の表2に幾つか有用な関数をまとめ ておく。ざ っと目を通してこの様な関数が存在することを知ることが 重要であり、実際に使 うときには参考書の関数の説明を読んで、使い方を勉強すると良いと思う。 またsystemという関数は非常に有用なので 、以下に簡単な使い方を示す。systemという 関数は、プログラム中でUNIXのコマンド を使いたいときに使用されるものである。例えば 、 以下のような使い方をする。 #include <stdio.h> #include <stdlib.h> /*これが必要*/ main() { system("ls -al"); } このプログラムを実行すると分かるが 、コマンド ラインからls -alとコマンド を打ったのと変

(19)

関数    機能    必要とされるヘッダーファ イル   atoi 文字列を数字に直す    stdlib.h fgets    ファイルから1行読み込む    stdio.h   fputs    ファイルに1行書き込む    stdio.h   gets    キーボード から打たれた文字列を変数に格納    stdio.h   rand    乱数発生    stdlib.h   srand    乱数発生    stdlib.h   strcpy    文字列を文字配列にコピ ーする    string.h   strlen    文字列の長さを計算する    string.h   表2: 今まであまり紹介しなかった関数 わりない動作をする。つまり上のsystem関数のダブルクオーテーションマークの中に実際の UNIXのコマンド を打てば 、それがその通りに実行されるのである。

7

こんなことができるのか

ここでは 、非常に不思議なCプログラムを幾つか紹介する。そし て、なぜこの様な書き方 ができてし まうのかという事を通して、C言語に対する理解を深めることにする。

7.1 配列

例えば以下のようなプログラムがあったとする。明らかにこれは間違っていそうであるが 、 実際正し く動くのである。 #include <stdio.h> main() { int dat[5]; 0[dat]=3;  /*こんな書き方できるわけはない?  */ printf("dat[0]=%d\n",dat[0]); } このプ ログラムでおかしいと思われるのは 、0[dat]である。当然正し くはdat[0]であると思 われるが 、しかしプログラムは正しく動いている。ここでdat[0]とはど ういう意味か考えて みる。実はCの内部ではdat[0]というのは*(dat+0)という事を表している。つまり配列の先 頭アド レス(今はdatに代入されている)にゼロを足し 算して、そのアド レスが指す場所の値 を意味している。そこで0[dat]も同様に考えてみる。これは*(0+dat)という事を意味してい ることになる。これは先ほど の*(dat+0)と同じである。つまり、これでもプログラムは動く のである。当然この様な書き方は 、するべきではない。しかし 、この様な書き方ができる背 景に何があるのかを知ることは非常に重要なことである。

(20)

7.2 文字列

文字配列を初期化する際に 、以下のような事を行ったと思う。

char dummy[50] = "ABCDE";

上の様な書き方は何の問題もないのに 、なぜ以下のような書き方は禁止されるのだろうか。 char dummy[50]; dummy = "ABCDE"; /* コンパイルエラーが 起きる*/ dummy[] = "ABCDE"; /* コンパイルエラーが起きる*/ dummy[50] = "ABCDE"; /* コンパイルエラーが起きる*/ 以上の例を見ると配列に文字を簡単に詰めるには 、初期化でしか行えない様に思われる(strcpy 関数を使うことはできるけど)。なぜCではこの様な書き方ができないのかを考えてみる。 まず上の例で=”ABCDE”となっている箇所が沢山あるが 、これの意味するものは 、実は以 下のようなものである。まずコンピューターは メモリーの適当な箇所にABCDEという文字 を格納するための領域を確保する。そし てその先頭のアドレス(Aという文字が格納されたア ドレス)が”ABCDE”の値になるのである。この事を知っていると、上の例で3つともコンパ イルエラーが起こるのが直ぐ 分かる。まず以下の最初の例を考える。この場合まずdummyと いう文字配列が メモリー上にちゃんと確保される。そして2行目の右辺はABCDEという文 字列を確保したメモリー上の先頭アド レスである。左辺はdummyという文字列の先頭アド レスである。今ど ちらのアド レスもちゃんとすでに確保されている。従ってこの例では 、す でに確保されているdummyという文字列の先頭アド レスを書き換えようとしているのであ る。一度決められた変数のアドレスの値を変更することはできないので 、これは禁止される のである。 char dummy[50]; dummy = "ABCDE"; 2番目の例(dummy[])も上の例と同様である。3番目の例(dummy[50])の左辺は 、アド レス ではなく、配列の要素である。3番目の例では*(dummy+50)のことである。右辺はアドレス を表しているので 、アド レスをデ ータに詰めることはできないのである。ちなみに初期化の 際だけ文字列の代入が許されるのは 、まだ”ABCDE”という文字列の先頭アド レスもdummy という文字配列の先頭アド レスも決まっていないからである。、先頭アド レスを決める段階 で 、ABCDEという文字列の先頭アド レスをdummyという文字配列の先頭配列に一致させ て初期化しているのである。 以上の事を考えれば 次の様なプログラムは問題ないのが 分かる。以下のプ ログラムは、ま ず”ABCDE”という文字列の先頭アド レ スをpcharという文字型のポ インタ変数に代入して いる。ポインタ変数の値に 、アド レスを代入することは可能なので 、以下の様な事をし ても 問題ない。次に 、pcharが指すアド レスの値を一つづつ進めて、その場所にある値をdummy の配列要素に詰め込んでいる。そして文字列の終わりであるゼロ文字が見つけるまで、配列 要素への詰め込みが 行われているのである。 #include <stdio.h> main()

(21)

{ char *pchar; dummy[50]; int i; pchar ="ABCDE"; for(i=0; *(pchar+i)!=’\0’;i++){ dummy[i] = *(pchar+i); } printf("dummy=%s\n",dummy); } 通常、文字列を文字配列に詰めるには 、strcpyという関数を安易に使うことになるが 、この 様な方法でも詰められるのである。

7.3 式の評価 (関係演算子)

if文やwhile文のカッコの中には、条件式を書くが 、条件式であるとは思えない記述をたま に見かける。例えば 以下のプ ログラムは無限ループになる。 #include <stdio.h> main() { while(1){;} } ここではwhile分のカッコの中に1と書かれているが 、これはど う見ても条件式であるとは 思えない。そこで 、なぜこの様な記述ができるのかを考えてみる。まず本来if文やwhile文 に書かれている条件式は値を持つのである。つまり次の様にプログラムが書かれていたとし たら 、a > bが正しいのであれば 、以下のwhile文のカッコの中には1が代入される。そして もし 間違っていれば 、カッコの中には0が代入されるのである。 #include <stdio.h> main() { int a,b; while(a>b){;} } while(1)とうい書き方は 、その条件式が評価されて出力された値を直接書いてし まう事に相 当する。このことを理解すると以下のような文も書けるのが 分かる。以下のプ ログラムを走 らせると、最初に画面には0が出力され 、次に画面には1が出力されるのである。 #include <stdio.h> main() {

(22)

int a=1,b=2; int c; c= (a>b); printf("c=%d\n",c); c= (a<b); printf("c=%d\n",c); } 以上の様に><は演算子としての役割を果たす。これらを関係演算子と呼ぶ。

7.4 ポインタ変数に関して

ポインタ変数に関しては 、初級編で紹介したが 、ここではより進んだ事を考えてみたいと 思う。ポ インタ変数は以下のように定義するが 、これは非常に混同しやすい。 int *p; この様な書き方だと、あたかも*pという変数を定義したかの様に見える。しかし 、意味はそ うではなくあくまでpという変数を定義したのである。そし て、そのpという変数にはアド レスの値が代入される事を意味している。そのため、むしろ以下のような定義ができれば 、以 下のように書くのが 筋だと思う(当然こんな定義の方法はC言語には存在しない)。 int-address p; 大体ポ インタ変数の初期化は 、以下のようにする。この例で分かるように、明らかに定義し ているのは 、アド レスを格納する変数pなのである。 int temp; int *p = &temp; もともと*というのは演算子で、pというポインタ変数に格納されているアド レスが指し 示す 領域のデ ータを取ってくる事を意味している。 またアド レスを格納するのであれば 、整数型とか文字型とかの定義は必要ないのではない かと思うであろう。しかし 、それは実際必要になる。例えば 以下の例を見て欲しい。確かに 上から2行目までは、pにtempという整数型の変数のアド レスを代入しているだけなので 、 ここまで見た段階では 、pに型があるというのは変な話である。しかし 、*pというものを考 えると、やはり型が存在しなくてはいけないのが分かる。つまり、*pはpに格納されている アド レスが指している箇所のデ ータを取ってくるのだが 、このデ ータが 何バイトで構成され ているのか分からなければ 、正しいバイト数を取ってくる事ができない。例えば 、指してい るアドレスが文字型の変数であれば 、1バイトとってこなくてはいけないし 、整数型の変数で あれば4バイト取ってこなくてはいけないのである。 int temp=5; int *p = &temp; printf("*p=5d\n",*p);

Updating...

参照

Updating...