[Up][Next]

計算機事始め

第1部  C言語


1.言葉の定義
2.言葉の定義(続き)
3.シェルコマンド
4.プログラムの作成
5.配列
6.数値の読み書き
7.関数
8.アドレスとポインター
9.ポインターの利用
10.構造体
11.コマンドラインの引数
12.テキストファイルの入出力
13.バイナリーファイルの入出力
 

1. 言葉の定義

コンピューターのデータは、0と1、あるいはonとoffというデータが並んだものということができる。

bit(ビット):最小のデータ単位。すなわち、0と1あるいはonとoff。

binary (2進数):0と1のみで表す数の世界。8桁の2進数があった場合、2進数表示と10進数表示の関係は下のようになる。2進数を4桁で区切ってあるのは読みやすくするため。

0000 0000 (2進数)= 0 (10進数)
0000 0001 (2進数)= 1 (10進数)
0000 0010 (2進数)= 2 (10進数)
0000 0011 (2進数)= 3 (10進数)
0000 0100 (2進数)= 4 (10進数)
0000 0101 (2進数)= 5 (10進数)
0000 0110 (2進数)= 6 (10進数)
0000 0111 (2進数)= 7 (10進数)
0000 1000 (2進数)= 8 (10進数)
0000 1001 (2進数)= 9 (10進数)
0000 1010 (2進数)= 10 (10進数)
0000 1011 (2進数)= 11 (10進数)
0000 1100 (2進数)= 12 (10進数)
0000 1101 (2進数)= 13 (10進数)
0000 1110 (2進数)= 14 (10進数)
0000 1111 (2進数)= 15 (10進数)
0001 0000 (2進数)= 16 (10進数)
      .....
0010 0000 (2進数)= 32 (10進数)
      .....
0100 0000 (2進数)= 64 (10進数)
      .....
1000 0000 (2進数)= 128 (10進数)
      ......
1111 1111 (2進数)= 255 (10進数)
  すなわち、例えば、われわれが通常用いている5という10進数の数字は、計算機のなかでは、0000 0101というような形で蓄えられ、また処理される。また8桁の2進数数字では、10進数の0から255までの256個の数値を表すことができる。
 

byte(バイト):   8 bitsのこと。0から127(10進数)までの整数を表すことができる。計算機の情報は、bit単位で扱われることは少なく、byte単位あるいはそれ以上の大きい単位で扱われることが多い。例えば、ディスクの容量などで用いられる10ギガバイトなど。

word(ワード):16bits、2 bytesのこと。 最近はあまり使われない言葉。
 

hexadecimal (16進数):  1 byteは8 bitsのことであるが、0と1の2進数で表すと量が多くなるし、実際何のことかよく分からないので、16進数で表されることがよくある。1 byteは16進数の2桁の数字で表わされる。16進数を10進数から区別するため、16進数の頭に"0x"を付ける、または最後に"H"を付けることがある。
 
 

2進数
16進数 10進数
0000 0000 00 0
0000 0001 01 1
0000 0010 02 2
0000 0011 03 3
0000 0100 04 4
0000 0101 05 5
0000 0110 06 6
0000 0111 07 7
0000 1000 08 8
0000 1001 09 9
0000 1010 0A 10
0000 1011 0B 11
0000 1100 0C 12
0000 1101 0D 13
0000 1110 0E 14
0000 1111 0F 15
0001 0000 10 16
0010 0000 20 32
0100 0000 40 64
0111 1111 7f 127
1111 1111 ff 255


octal (8進数):  16進数の場合は、a-fを用いなくてはならない。数字文字だけでということになると、8進数を用いることがある。

問題

1. 16桁の2進数では、10進数のいくつの数値を表すことができるか。
2. 32桁の2進数では、10進数のいくつの数値を表すことができるか。
 

2.言葉の定義(続き)

計算機の仕事は、

    (1) いくつものbyteデータを取り込み、
    (2) それらに何らかの処理をして(たとえば足しあわせるとか)、
    (3) 処理されたbyteデータを出力したり(画面あるいはプリンターに結果を出す)、記憶装置に貯えたりする、

と要約できる。このことは、”計算機は、byteの流れ(stream)を取り込み、処理し、byteの流れとして出力する”と表現されることが多い。
 

計算機の取り扱うデータは、byteの流れであるということを上で述べた。しかし実際取り扱うデータの基本的な要素は、
    (1) 文字
    (2) 整数
    (3) 実数
である。
 
 

文字(Character):アルファベット(AからZ、およびaからz)、数字(0から9)、記号(,.<>?#$%&()など)、空白(スペース、タブ)、改行など、英語での文章に用いられる文字の数は100程度である。したがって英語で用いられる文字は、1 byte(= 8 bits)との対応をつけておけばよい。ASCIIコードは1 byteの数値と文字の関係を定めた規則。(ASCIは、American Standard Code for Information Interchangeの略で、アスキーと読む。)

ASCIIコードの例

文字 A  =  41H (16進数) = 65 (10進数)
文字 F  =  46H (16進数) = 70 (10進数)
文字 a  =  61H (16進数) = 97 (10進数)
文字 c  =  63H (16進数) = 99 (10進数)

タブ      =  9H (16進数) = 9 (10進数)
スペース  =  20H (16進数) = 32 (10進数)


 
0 1 2 3 4 5 6 7
0 nul dle space 0 @ P ` p
1 soh dc1 ! 1 A Q a q
2 stx dc2 " 2 B R b r
3 etx dc3 # 3 C S c s
4 eot dc4 $ 4 D T d t
5 enq nak % 5 E U e u
6 ack syn & 6 F V f v
7 bell etb ' 7 G W g w
8 bs can ( 8 H X h x
9 tab em ) 9 I Y i y
A lf sub * : J Z j z
B vt esc + ; K [ k {
C ff fs , < L \ l |
D cr gs - = M ] m }
E so rs . > N n ~
F si us / ? O o del

 

たとえば、"Okazaki"という文字列は、0x4F 0x6B 0x61 0x7A 0x61 0x6B 0x69 というbyteの列として扱われる。ASCIIコードの0x00から0x1Fまでは、通信やプリンターの制御などに用いられる。

漢字を含めた日本語の文字の数は数千あるので、1 byteでは表せず、1文字に2 bytes用いて表す。その表し方(エンコード)には、JIS方式、Shift-JIS方式、EUC方式の3種類がある。一般的にパソコンに用いられているのはShift-JIS方式、UNIXに多く使われるのはEUC方式であるが、インターネットなどの通信には、JIS方式が標準である。 メールのやり取りのときに生じる、いわゆる”文字化け”は、多くの場合エンコードの違いによることが多い。最近はunicodeという方式が用いられるようになってきている。unicodeにもいろいろな規格があるが、ここでは詳細には触れない。
 

整数(Integer):

最近では、32 bitsすなわち4 bytesで一つの整数を表すことが多い。232 = 4,294,967,296(10進数)である。負の数も考えると、-2,147,483,648(10進数)から2,147,483,647(10進数)までの数値を表すことができる。

以前は、多くの場合整数は16 bits(2 bytes)で表された。この場合-32,768から32,767までの整数であったので、日常の計算の場合でもこの範囲を超えてしまうことがあったが、32-bit整数の場合は先ず心配がない。

 

実数(Float、Double):

実数は整数とは異なる形で扱われる(詳細は省略)。現在の計算機では、実数は4 bytesまたは8 bytesで表され、前者をfloat、Real、後者をdouble、LongRealなどと呼ぶ。精度を落とさずに計算するにはdoubleを用いたほうが好ましいが、大量の数を扱う場合は、記憶容量の関係からfloatが好まれる場合もある。計算速度はfloatを用いてもdoubleを用いても大差はない。

さて、このように文字・整数・実数などのデータにはいろいろな大きさのものがある。では計算機は区切りのないbyteの流れをどのようにして理解するのだろうか?たとえば、0x41 0x42 0x43 0x44というbyteの流れがある時、それは文字列"ABCD"と解釈することができるし、また32-bit 整数の1,145,258,561(はじめのbyteが最下位、最後のbyteが最上位)、あるいは実数の781.035のいずれとも解釈できる。どのようにbyteの流れを解釈するかは、プログラムで指示しなくてはならない。
 

3.シェルコマンド


計算機の行うことは、bytesの流れを入力(input)より受け取り、処理し、bytesの流れとして出力(output)に出すことである。入力は、キーボード、マウス、ファイル、接続された機器などであり、また出力は画面、ファイル、プリンターなどである。

C言語、UNIX、MS-DOS(もしくはWindowsにおけるDOS窓、コマンドウインドウ)においては、標準入力とはキーボードのことであり、標準出力とは画面のことである。

ファイルを画面に出す命令には、TYPE (Windows/MS-DOSの場合)、cat (UNIXの場合)があり、あるテキストファイル abc.txtがある場合、その内容を画面に出すには、type abc.txt あるいは cat abc.txt とすればよい。出力は標準出力である画面に出される。(MS-DOS/Windowsの場合は大文字小文字の区別はないが、UNIXでは大文字と小文字は別の文字として理解される)。

またディレクトリーを表示するには、DIR (Windows/MS-DOSの場合)、ls (UNIXの場合)といったコマンド(命令)が用いられるが、これらのコマンド(一種のプログラムともいえる)の出力は、標準出力である。
 

ここで覚えておくと便利なことを1つ。
MS-DOS、UNIXには標準入出力の切り替えという便利な機能がある。標準出力をファイル(たとえばabc.txt)にきり変えて、ディレクトリーのリストをファイルにして保存するには、


(Windows/MS-DOSの場合)

    dir > abc.txt
(UNIXの場合)
    ls > abc.txt

というように、">"で出力をファイルにきりかえることができる(この機能はredirectionと呼ばれる)。もしプリンターがlprという名のデバイスであれば、

(Windows/MS-DOSの場合)
    dir > lpr
(UNIXの場合)
    ls > lpr

とすることにより、ディレクトリーのリストを印刷することができる。

ファイルに追加して出力するには、">"の代わりに、">>"を用いる。
 
 

よく使われるコマンド MS-DOS
Windows
UNIX
ディレクトリーを変える cd cd
ディレクトリーを新しく作る mkdir mkdir
ディレクトリーを削除する rmdir rmdir
ディレクトリーの内容の表示 dir ls
ファイルのコピー copy cp
ファイルの名前を変える rename mv
ファイルの移動 move mv
ファイルの削除 del rm
ファイルの検索 find
テキストファイルの表示 type cat
現在のディレクトリーの表示 cd pwd

4. プログラムの作成


プログラムを作成するには、
    (1) ソースコードを書く
    (2) ソースコードをコンパイルし、必要であればリンクする。
という2つの操作が必要である。ソースコードを書くには、エディターが用いられる。Windowsではnotepad、UNIXではviやemacsが用いられる。

現在のプログラム開発環境は、上記のような原始的な環境から随分進歩しており、統合プログラム環境などと呼ばれる環境でプログラム開発を行うことが普通である。ただしUNIX、Linuxのプログラム開発環境は未発達のままである。

ファイルの意味などがよく理解できるようにするために、まずは原始的な方法でプログラムの作成を試みる。
 

Windowsの場合、先ず、(スタート)->(プログラム)->(コマンドウィンドウ)でDOS窓を開く。次に自分のディレクトリーに移る。
> cd \user\bert
あるいは
> cd \user\erwin

自分がどのディレクトリーにいるかを知るにもcdを使うが、その場合は"cd"のみ。UNIXの場合、この機能はpwdを用いる。

> dir
として、このディレクトリーにはどのようなファイルがあるか確認しておこう。

ex1.cという名のソースファイルを作るなら、
> notepad ex1.c
とすれば、ファイルが開かれる(新しく作製しますかと聞いてくるが)。

C言語では /* と */で囲まれた部分は、コメント(注意書き)と解釈され、コンパイラーは無視する。
また、//があると、その行のそれより後はコメントと解釈される(古いコンパイラーではこの機能はない)。

ex1.cに以下の内容を入れる。(cut&pasteで移せるはず)
 

/*  -----------------   ex1.c --------------------- */

#include <stdio.h>

int main()
{

    printf("This is a test program\n");

    return 0;

}

/*  ---------------   End of Exercise 1 ------------------- */


notepadで(ファイル)->(上書き保存)でファイルをsaveする。

>bcc32 ex1.c

とすれば、ソースコードがコンパイルされ、実行可能なプログラムex1.exeが作製される。
bcc32は、Borland社C++builderのコンパイラー。コマンド入力で用いるこのコンパイラーはBorlandからダウンロード可能。

この代わりにMicrosoftのコンパイラー

c:\program files\devstudio\vc\bin\cl.exe
を用いてもよい。

Linuxの場合、コンパイラはgccを用いる。出力ファイルの名前のオプション-oを付けています。

> gcc -o ex1 ex1.c


プログラムを走らせるには、ex1とすればよい。 (Linuxの場合は、./ex1とする必要あり。)
 

Exercise 1の説明
#include <stdio.h>
今のところ一種のおまじないと覚えておく。

int main()
Cプログラムには、mainといわれる部分がなくてはならない(C++で書いたWindowsプログラムでは見当たらないけれども...)。intはこのプログラムが終了するときに、このプログラムを呼んだプログラムへ整数の値を返すことを意味している。何も数値を返さない場合は、intの代わりにvoidとする。intも何もない場合は、intであると解釈される(コンパイラーによって異なるかもしれない)。

printf
フォーマットして標準出力に出力する命令。フォーマットするとは、たとえば数値の場合、それを文字列に変換し、文字の場合はASCIIコードにしたがって内部での数値を文字を変換すること。

C言語の場合、一つの文は";"で終わり、必ずしも行を変える必要はない。ただし、"#"で始まる行などは改行しないと何が起こるか不明。
 

次の練習をex2.cというファイルに作ろう。
 

/*  -----------------   Exercise 2 --------------------- */

#include <stdio.h>

int main()
{
    int c;

    while( (c=getchar()) != EOF){

        if( (c >= 'a') && ( c <= 'z'))  c = c -  ('a'-'A');

        putchar(c);
    }
    return 0;
}
 

}

/*  ---------------   End of Exercise 2 ------------------- */
 

> bcc32 ex2.c
もしくは

> gcc -o ex2 ex2.c


とコンパイルする。

> ex2 < ex1.c

とすれば、何が起こるか?
 

Exercise 2の説明

int c;
C言語のプログラムで変数を使用するには、その宣言をしなくてはならない。この宣言により、"c"と名づけた整数用の記憶場所がよういされ、そこに数値を入れたり、そこから数値を読み取ったりすることが可能となる。

while( condition ){
   statement;
   statement;
}
あるcondition(条件)が正しい場合、{ }の中のstetementを実行する。
int a, b; a=2; b=2; としたとき、
a == 2  は正しい。 (==は等号"="を二つならべたもの。等しいという意味)
a == b  は正しい。

a != 2 は正しくない (!= は等しくないという意味)
a != 0  は正しい。
a != b は正しくない。

c=getchar();
getchar() は標準入力より一文字入力すること。したがってc=getchar();は標準入力より1文字入力しそれをcに格納すること。
 

while( (c=getchar()) != EOF){  ...  }
標準入力より1文字入力し、それをcに格納するが、そのcがEOF(end of file;ファイルの最後)でない限り、{}の中のことを行いなさい。

if( (c >= 'a') && ( c <= 'z'))  c = c -  ('a'-'A');

if(condition) statement;
もし条件が正しければ、次のstatementをしなさい。

(c >= 'a') && ( c <= 'z')
(先ほど入力した)cが、小文字の'a'と等しいかより大きく(ASCIIコードで数値が大きい)、かつ(&&)、小文字の'z'と等しいかより小さい場合は、(すなわちcが小文字であるなら)、大文字と小文字のASCIIコードでの差は文字によらず同じだから、'a'と'A'の差を小文字から引けば大文字になる。
 

putchar(c);
1文字を標準出力に出す。
 
 

大文字と小文字を入れかえる。

 
/*  -----------------   ex3.c --------------------- */
/*  -----------------   Exercise 3 --------------------- */

#include <stdio.h>

int main()
{
    int c;

    while( (c=getchar()) != EOF){

        if( (c >= 'a') && ( c <= 'z')){
            c -=   ('a'-'A');
        }else if( (c >= 'A') && ( c <= 'Z') ){
            c +=   ('a'-'A');
        }
        putchar(c);
    }
    return 0;
}

/*  -----------------   End of Exercise 3 --------------------- */
 


 
 

 x += 3; はx=x+3と同じ。これは方程式ではなく、xに3を出した数値をxに新たに入れることを意味する。x=x+3という書き方は正しいが、x+3=xという書き方は間違い。
 c -= a; は c=c-a; と同じ意味。

putchar(c)は標準出力への出力であるから、">"を使ってファイルへ出力することができる。

たとえば、abc.txtの大文字と小文字を入れ替えるには、
 

ex3 < abc.txt > abc2.txt

とすればよい。
 

------------------------------------------------------------
------------------------------------------------------------
今まで出てきたもので覚えておくこと。

#include <stdio.h>   // 頭におくおまじない

int main() {    }        // プログラムにはmainがなくてはならない

int c;                     // 変数の宣言

while (condition){ statement}        // 繰り返し

if(condition) { statement}             // 条件

if(condition) { statement }  else { statement }

if(condition) { statement }  else if(condition) { statement }
 

printf      //   標準出力へのフォーマット出力
getchar   //  標準入力からの1文字入力
putchar   //   標準出力への1文字出力

EOF    // end of file

==     //  等しい
!=      //  等しくない
>=
<=
&&     // かつ
 
 

5. 配列(array)


プログラムでは変数に名前をつけて、その変数名を使って操作を記述する。
たとえば、

int a, b;
int x, y;

x = 3.6;
y = a*x + b;

のように書き表す。"*"は掛け算の記号。割り算には"/"を用いる。
 

しかし、データが10,000個あるといちいち名前を付けるのは困難なので、数(または文字)の並んだものとして扱う。これを配列という。

10個の整数を宣言するときは、

int a[10];
と書く。同様に
 
char s[128];
float x[1000], y[1000];


のように宣言する。配列の個々のあたいは、

x[3]とかy[678]のように書く。
ここで注意すべきことは、int a[10]; と宣言したときに、配列の最初の要素は、a[0]であり、最後の要素はa[9]であるということである (注意: Fortran、Pascalの場合は、1から始まる)。
 

 
/*  -----------------   ex4.c --------------------- */
/*  -----------------   Exercise 4 --------------------- */

#include <stdio.h>
#include <string.h>

int main()
{
    int c;
    int i,n;
    char s[128],t[128];

    while( gets(s) != NULL){
        n=strlen(s);
        t[n]=0;
        for(i=0;i<n;i++)t[n-1-i] = s[i];
        printf("%s\n",t);
    }
    return 0;
}

/*  -----------------  End of Exercise 4 --------------------- */


 
 

解説:
#include <string.h>  文字列に関係する関数(この場合strlen)を使うためのおまじない。

gets()  標準入力より1行の文字列(文字の配列)を入力する。

strlen()  文字列の長さを返す。文字列の最後には0が付けてある。

for(i=0 ; i< n ;i++){  statement}
   繰り返しのための制御文。先ずi=0 とし iがnより小さい間、{  }の中の命令を行い、そしてiを1だけ増やす。

   この書き方は、
    i=0;
    while(i<n){
        statement

        i++;
    }
    と同じ。

 ex4.cをコンパイルし、テキストファイル(たとえばabc.txt)を使って、

ex4 < abc.txt

とすれば、何が起こるか?
 
 
 

6.  数値の読み書き

 
/* ----------------- Exercise 5 --------------------- */
/*
ファイル ex5.c テキストファイルから数字を読み込み、数値の数、
合計、平均を出力する。
*/

#include <stdio.h>
#include <string.h>

int main()
{
    int i,n,sum;
    double a;
    char s[128];

    n=0; // まずは数えた数を0とする
    sum = 0; // はじめは合計は0

    while( gets(s) != NULL){ // 標準入力より一行読み込む
        i = atoi(s); // 整数に変換
        sum += i; // 合計に加算
        n++; // 数えた数を1つ増やす
    }
    a = sum; // int(整数)をdouble(実数)に変換する
    a = a/n; // 平均を計算
    printf("KAZU = %d\n",n);
    printf("GOUKEI = %d\n",sum);
    printf("HEIKIN = %f\n", a);
    return 0;
} /* ----------------- End of Exercise 5 --------------------- */


解説

#include <string.h> 関数 atoi()を使うためのおまじない。

atoi() 文字列を整数に変換する。

printf() フォーマットして標準出力に出す。例を見てわかるように
    printf( <フォーマットを指定する文字列>,<データ>);
という形になる。%dとか%fとかがそれぞれのデータに対応する。
  %d は整数データを10進数表示
  %x は整数データを16進数表示
  %fは実数データ
  %eも実数データであるが、 1.34e03のような形式で表す (これは1340.0のこ と)
  %gも実数データ ある範囲内では%fを使用し、それを越えると自動的に%e形式 で表示
  %cは文字データ(1文字)
  %sは文字列データ
\nは改行を指定する
出力幅をフォーマットで定義することができる。
整数を10桁は幅で表示するには: %10d
左詰で12桁幅で表示するには: %-12d
小数点以下を4桁、全体を8桁幅で表示: %8.4f などなど

練習:
1.データファイルabc.datを添付した。 notepadでabc.datの内容を見てみよ。
2.ex5.cをコンパイルして、 ex5 < abc.dat とする。
3. i = atoi(s); を sscanf(s,"%d",&i); に変えてみよ。 sscanf(s,"%d",&i);は文字列sを"%d"形式(すなわち整数形式)で数値に変換 し、その値を変数iの場所に格納する。&は場所(アドレス、address)であるこ とを示す。

7. 関数


同じような操作を繰り返してする場合、その操作を一つのまとまった部分として独立させることがある。この部分は、関数(function)、プロシージャ(procedure)、サブルーチン(subroutine)とか呼ばれる。

これまで用いてきたprintf()、strlen()、gets()などは関数の例である。
関数を用いるには、

(1)宣言
(2)定義
の両方が必要である。

宣言の形式は、

戻す値のタイプ 関数名 (変数タイプ 変数名、 ......);


int putchar( int c );
int heikin(int a, int b);


最後に";"があるのに注意。宣言はその関数が用いられている部分での使われ方が正しいかをチェックするために用いられる。

関数が実数doubleを返す場合は、

double kansu(....);
となる。また関数が値を返さない場合は、
void kansu(....);


というように表す。
 

定義の部分の例は、

int heikin(int a, int b)
{
int c;
c = (a + b)/2;
return c;
}


さて関数を用いる場合には、( )の中に指定した変数の値をコピーして関数に渡たす。C言語の場合、もしint a=57;という変数があり、関数 int kansu(int y);に、kansu(a)として用いた場合、aの値がコピーされて関数に渡されるため、関数の中で"a"がどうされようとも、元のaには何ら影響を及ぼさない。
 
 

8. アドレス(address)とポインター(pointer)


アドレスとポインターはC言語学習の山場。

関数には、コピーされた数値が渡される。たとえば1000個の配列の数値1000個を渡すことは、あまり実用的ではない。変数には、メモリー上のある部分が割り当てられているから、そのメモリーの位置(アドレス)の値をコピーして関数に渡せばよいという考え方ができる。

実際、関数gets( )は、文字配列のアドレスを関数に渡し、その文字配列のために用意されたメモリーに文字を入れる。
 

変数を、

int i;
と宣言したとき、変数iのアドレスは、&iと表す。

またアドレスを変数として宣言することもあり、このような変数をポインターと呼ぶ。ポインターとしての宣言は、

int* p;
というように表す。この場合、pは整数へのポインターである。文字列へのポインターは同様に、
char* s;
と宣言する。関数 get()の場合は、
char *gets(char *s);
と宣言されている。
 

int aとint bがあるときにそれらの数値を入れ替える関数swapを考えてみよう。
 

void swap(int a, int b)
{
    int tmp;

    tmp = a;
    a =b;
    b = tem;
}
 

としても、変数の値のコピーが関数へ渡されることをかんがえれば、これはうまくはたらかないであろうことは予想される。試してみよう。
 

/* -------------  ex6.c ----------*/
#include <stdio.h>

void swap(int a, int b);
 

void swap(int a, int b)
{
    int tmp;

    tmp = a;
    a =b;
    b = tem;
}
 

void main()
{
    int x, y;

    x = 2,
    y = 5;

   printf("1: x = %d, y = %d\n", x,y);

   swap(x,y);

   printf("2: x = %d, y = %d\n", x,y);
}
 

/* -------------end of  ex6.c ----------*/
 

それでは、アドレスを関数に渡せばどうなるであろうか?

/* -------------  ex6a.c ----------*/
#include <stdio.h>

void swap(int* a, int* b);
 

void swap(int* a, int* b)
{
    int tmp;

    tmp = *a;
    *a =*b;
    *b = tmp;
}
 

void main()
{
    int x, y;

    x = 2,
    y = 5;

   printf("1: x = %d, y = %d\n", x,y);

   swap(&x,&y);

   printf("2: x = %d, y = %d\n", x,y);
}
 

/* -------------end of  ex6a.c ----------*/
 
 

9. ポインターの利用


まずはポインターを用いずに配列で、文字列のコピーをする。
 

//////////////////////////////////////
//                ex7.c
//                文字列sを文字列dにコピーする。
//                まずはポインターを用いずに配列で。
//////////////////////////////////////

#include <stdio.h>
#include <string.h>
 

void StringCopy(char *d, char *s)
{
    int i;
    i=0;
    while( s[i] != 0){
        d[i] = s[i];
        i++;
    }
    d[i] = 0;
}
 

void main()
{
    char a[128],b[128];

    strcpy(a,"This is String A.");     // 文字列に"..."を代入する。
    strcpy(b,"THIS IS STRING B");

    printf("ORIGINAL:\n");
    printf("String a = %s\n",a);
    printf("String b = %s\n",b);

    StringCopy(b,a);

    printf("RESULTS:\n");
    printf("String a = %s\n",a);
    printf("String b = %s\n",b);
}
 

// ------------   end of ex7.c ---------------
 

基本的には同じ内容であるが、配列の部分をポインターで表す。
main()の部分は同じ。

//////////////////////////////////////
//                ex7a.c
//                文字列sをdにコピーする。
//                ポインターを用いて
//////////////////////////////////////

#include <stdio.h>
#include <string.h>
 

void StringCopy(char *d, char *s)
{
    char  *p, *q;                   // 文字へのポインターを宣言

    p = s;                     // ポインターを文字列の先頭に持ってくる。
    q = d;

    while( *p != 0){        // ポインターの指す内容(*p)が0でない限り、
        *q++ = *p++;      //  pの指す内容(*p)をqの指す内容(*q)に入れ、そのあとpとqを一つづつ増やす。
    }
    *q = 0;                   // 文字列の最後を0とする
}
 
 

void main()
{
    char a[128],b[128];

    strcpy(a,"This is String A.");
    strcpy(b,"THIS IS STRING B");

    printf("ORIGINAL:\n");
    printf("String a = %s\n",a);
    printf("String b = %s\n",b);

    StringCopy(b,a);

    printf("RESULTS:\n");
    printf("String a = %s\n",a);
    printf("String b = %s\n",b);
}

// ------------   end of ex7a.c ---------------
 

ポインターを用いて書いたほうがC言語らしいが、内容は変わらない。
無理にポインターを用いる必要はない。
 
 
 

10.構造体(structure)

変数をまとまったものとして扱う手段としては、配列(array)があった。配列の場合は同じタイプの変数を扱う。異なるタイプの変数をまとまったものとして扱う手段として、構造体(structure)がある。構造体はFILEのようにはじめから定義されているものもあるが、多くの場合は自分で定義して用いる。

構造体の定義の例。

typedef struct {
    char name[64];
    int age;
    double height;
    double weight;
    int blood_type;
} person;
personという構造体は、文字列name、整数age、実数height, weight、文字blood_typeから成り立っている。typedefを用いて構造体を定義すると、
person a;
という構文で変数aを宣言できる。構造体のそれぞれの要素は、
a.name
a.age
a.height
などの書き方で表される。ただし構造体へのポインターを用いた場合、すなわち
person a;         // 構造体の宣言
person *pa;      //ポインター宣言
pa = &a;
として、ポインターpaが構造体aのポインターである場合、構造体の要素は、
pa->name
pa->age
pa->height
と書き表される。これらは内容的に、
(*pa).name
(*pa).age
(*pa).height
と同じであるが、一般的に"->"の表記が好まれる。
 

関数に構造体の内容を渡す場合、構造体へのポインターを渡さなくてはならない。ポインターを渡されることにより、関数ないで、構造体のすべての要素を操作することができるようになる。
 
 

//////////////////////////////////////
//                exercise8.c
//                構造体
//////////////////////////////////////

#include <stdio.h>
#include <string.h>
 

// 構造体personを定義する。

typedef struct {
    char name[64];
    int age;
    char blood_type;
} person;
 

// 構造体の内容を表示する関数。構造体へのポインターを受け取る。

void DisplayPerson(person* a)
{
    printf("name: %s\n", a->name);
    printf("age:  %d\n", a->age);
    printf("blood type: %c\n",  a->blood_type);
}
 

void main()
{

    person bert, erwin;

    strcpy(bert.name,"Bert Sakmann ");            // データを入れる。
    bert.age = 16;
    bert.blood_type = 'O';

    strcpy(erwin.name,"Erwin Neher");          // データを入れる。
    erwin.age = 14;
    erwin.blood_type = 'O';

    DisplayPerson(&bert);                         // データを表示する
    DisplayPerson(&erwin);

 }
 
 

//---------------------------------------------------------------------------
 
 

11.コマンドラインの引数


MS-DOSプロンプトでプログラムを起動するとき、引数を与えることができる。たとえば、ファイルabc.txtを画面に表示させるには、

type abc.txt
とするが、abc.txtが引数にあたる。

コマンドラインの引数を受け取るには、mainの部分を、

int main(int argc, char** argv)
とする。int argcは引数の数、char** argvは引数(実際は文字列)の内容を示す。例えば、
ex abc.txt 100 35.6
というコマンドラインの場合、
argc = 4
argv[0] = "ex"                         // 文字列
argv[1] = "abc.txt"                   // 文字列
argv[2] = "100"                       // 文字列
argv[3] = "35.6"                      // 文字列
という内容になる。
 

実際それを試してみよう。
 

//////////////////////////////////////
//                ex9.c
//                コマンドラインの引数
//////////////////////////////////////

#include <stdio.h>

void main(int argc, char** argv)
{
    int i;

    printf("argc = %d\n",argc);

    for(i=0;i<argc;i++){
        printf("argv[%d] = %s\n",i, argv[i]);
    }
 }
 
 

//----------end of ex9.c --------------------
 
 

コンパイルして、
ex9
ex9 12 abc 444
ex9 "12 abc" 444
などを試してみよ。
 

12.テキストファイルの入出力

これまでの入出力は、標準入力と標準出力に限っていたが、実際的なプログラムではいろいろなファイルを開いてそれらの読み書きをすることが必要である。MS-DOSに限らずどのようなOSでも、ファイルはテキストファイルとバイナリーファイルの2種類に大別される。

テキストファイルとは、type、catなど画面表示をするプログラムで内容を表示することができるファイルである。バイナリーファイルはそれ以外のファイルであり、数値などがASCIIコードに変換されず、2進数の形で蓄えられている。

テキストファイルの長所は特別なプログラムなしにその内容を知ることができるし、またnotepadのようなエディターで内容を変更することができる。しかし多量の数値データを扱う場合、データの大きさが大きくなってしまう、読み込み、書き出しの度に2進数とASCIIコードとの変換が必要であり、それに伴って計算精度が落ちる可能性がある。

バイナリーファイルは、ファイルの大きさは小さくできるが、そのデータファイルに適したプログラムがないと、データファイルの内容を知ることができない。
 

テキストファイルの取り扱いから。とりあえず必要な関数は下記のとおり。

FILE *fopen(const char *filename, const char *mode); // ファイルを開く
int fgetc(FILE *stream);             //ファイルから1文字読み込む
char *fgets(char *s, int n, FILE *stream);  // ファイルから1行読み込む
int fputc(int c, FILE *stream);    //  ファイルに1文字書きこむ
int fprintf(FILE *stream, const char *format[, argument, ...]); //  ファイルに書く
int fclose(FILE *stream);     // ファイルを閉じる
まずは、ファイルを開いて表示するという簡単なプログラムから。
表示だけでは余りに芸がないから、行の先頭に行番号を入れる。
 

//////////////////////////////////////
//                ex10.c
//                ファイルを開いて表示する
//////////////////////////////////////

#include <stdio.h>

int main(int argc, char** argv)
{
    FILE *fd;
    int i;
    char s[256];

    if(argc<2){                   // ファイル名が2番目の引数でなくてはならない
        fprintf(stderr,"Usage : ex10 filename\n");
        return -1;
    }

    fd = fopen(argv[1], "r");   // "r"は読み込み専用で開くことを意味する  (注1)
    if(fd == NULL){             // もしファイルがなければ、NULLを返す。成功すれば
                                // 構造体FILEへのポインターを返す。
        fprintf(stderr,"? file: %s not found ?\n", argv[1]);
        return -1;
    }

    i=1;                        // 行番号は1からスタート
    while( fgets(s,255,fd) != NULL){    // 1行読み込み、
        printf("%d:%s",i,s);    // 行番号と文字列を標準出力に出す
        i++;                    // 行番号を1だけ増す
    }

    fclose(fd);                 // ファイルを閉じる
    return 0;

}
 

//----------end of ex10.c --------------------

(注1)この部分は、下のように書くのが普通。
    if((fd = fopen(argv[1], "r")) == NULL){
       fprintf(stderr,"? file: %s not found ?\n", argv[1]);
        return -1;
    }
 

例題 y=a*x*x + b*x + cの値の表を作る。
係数a、b、cはコマンドラインから読み込む。

//////////////////////////////////////
//                ex11.c
//                ファイルを開いて書きこむ
//////////////////////////////////////

#include <stdio.h>

int main(int argc, char** argv)
{
    FILE *fd;
    int i;
    double x;
    double a, b, c;
 

    char s[256];

    if(argc != 5){                   // 5つの引数がなくてはならない
        fprintf(stderr,"Usage : ex11 fileename a b c\n");
        return -1;
    }

    if( sscanf(argv[2],"%lf",&a) == 0){     // doubleで読み込むため%lfとなっている
                                            // %fだと正しく変換しない。
        fprintf(stderr,"? a: wrong value\n");
        return -1;
    }
    if( sscanf(argv[3],"%lf",&b) == 0){
        fprintf(stderr,"? b: wrong value\n");
        return -1;
    }
    if( sscanf(argv[4],"%lf",&c) == 0){
        fprintf(stderr,"? c: wrong value\n");
        return -1;
    }

    printf("a=%f, b=%f, c=%f\n",a,b,c);

    if((fd = fopen(argv[1], "r")) != NULL){  // ファイルはすでに存在。上書きを防ぐため中止
        fprintf(stderr,"? file already present ?\n");
        fclose(fd);
        return -1;
    }

    if((fd = fopen(argv[1], "w")) == NULL){  // "w"は書きこみ専用で開くことを意味する
        fprintf(stderr,"? failed to open file ?\n");
        return -1;
    }

    i=0;                        // 行番号は1からスタート
    while( (x = 0.01*i ) <= 1.0 ){
        fprintf(fd, "%d %f %f\n",i,x, a*x*x+b*x+c);
        i++;
    }

    fclose(fd);                 // ファイルを閉じる
    return 0;

}
 

//----------end of ex11.c --------------------
 

13.バイナリーファイルの入出力

計測データなど多量のデータはバイナリーの形のままでファイルに保存されることが多い。この場合のファイル操作に用いる関数は、テキストファイルの場合と異なる。
int open(const char *path, int access [, unsigned mode]); //ファイルを開く
long lseek(int handle, long offset, int fromwhere);  //ファイルポインタの位置の移動
int read(int handle, void *buf, unsigned len);  // ファイルから読む
int write(int handle, void *buf, unsigned len); //ファイルに書き込む
int close(int handle);                          //ファイルを閉じる。
 
これらの関数では、ファイルを示すintであるファイルハンドルを使用する。ファイルハンドルはopenの帰り値として得られる。ストリームをファイルハンドルに対応付けるには、
FILE *fdopen(int handle, char *type);
を使用する。ストリームの表現でバイナリーファイルを操作するために、
int fseek(FILE *stream, long offset, int whence);
size_t fread(void *ptr, size_t size, size_t n, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t n, FILE *stream);
が用意されている。
 
 

[Up][Next]