1.言葉の定義
2.言葉の定義(続き)
3.シェルコマンド
4.プログラムの作成
5.配列
6.数値の読み書き
7.関数
8.アドレスとポインター
9.ポインターの利用
10.構造体
11.コマンドラインの引数
12.テキストファイルの入出力
13.バイナリーファイルの入出力
bit(ビット):最小のデータ単位。すなわち、0と1あるいはonとoff。
binary (2進数):0と1のみで表す数の世界。8桁の2進数があった場合、2進数表示と10進数表示の関係は下のようになる。2進数を4桁で区切ってあるのは読みやすくするため。
0000 0000 (2進数)= 0 (10進数)すなわち、例えば、われわれが通常用いている5という10進数の数字は、計算機のなかでは、0000 0101というような形で蓄えられ、また処理される。また8桁の2進数数字では、10進数の0から255までの256個の数値を表すことができる。
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進数)
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"を付けることがある。
|
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進数のいくつの数値を表すことができるか。
(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の流れを解釈するかは、プログラムで指示しなくてはならない。
計算機の行うことは、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 |
プログラムを作成するには、
(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 --------------------- */> bcc32 ex2.c#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 ------------------- */
> gcc -o ex2 ex2.c
> ex2 < ex1.c
とすれば、何が起こるか?
Exercise 2の説明
int c;
C言語のプログラムで変数を使用するには、その宣言をしなくてはならない。この宣言により、"c"と名づけた整数用の記憶場所がよういされ、そこに数値を入れたり、そこから数値を読み取ったりすることが可能となる。
while( condition ){あるcondition(条件)が正しい場合、{ }の中のstetementを実行する。
statement;
statement;
}
a == 2 は正しい。 (==は等号"="を二つならべたもの。等しいという意味)c=getchar();
a == b は正しい。a != 2 は正しくない (!= は等しくないという意味)
a != 0 は正しい。
a != b は正しくない。
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
== // 等しい
!= // 等しくない
>=
<=
&& // かつ
プログラムでは変数に名前をつけて、その変数名を使って操作を記述する。
たとえば、
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
とすれば、何が起こるか?
/* ----------------- 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; // はじめは合計は0while( 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)であるこ
とを示す。
同じような操作を繰り返してする場合、その操作を一つのまとまった部分として独立させることがある。この部分は、関数(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には何ら影響を及ぼさない。
アドレスとポインターは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 ----------*/
まずはポインターを用いずに配列で、文字列のコピーをする。
//////////////////////////////////////
//
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言語らしいが、内容は変わらない。
無理にポインターを用いる必要はない。
構造体の定義の例。
typedef struct {personという構造体は、文字列name、整数age、実数height, weight、文字blood_typeから成り立っている。typedefを用いて構造体を定義すると、
char name[64];
int age;
double height;
double weight;
int blood_type;
} person;
person a;という構文で変数aを宣言できる。構造体のそれぞれの要素は、
a.nameなどの書き方で表される。ただし構造体へのポインターを用いた場合、すなわち
a.age
a.height
person a; // 構造体の宣言として、ポインターpaが構造体aのポインターである場合、構造体の要素は、
person *pa; //ポインター宣言
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);
}
//---------------------------------------------------------------------------
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
などを試してみよ。
テキストファイルとは、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 --------------------
int open(const char *path, int access [, unsigned mode]); //ファイルを開くこれらの関数では、ファイルを示すintであるファイルハンドルを使用する。ファイルハンドルはopenの帰り値として得られる。ストリームをファイルハンドルに対応付けるには、
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); //ファイルを閉じる。
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);