C言語プログラミング

プログラミングの授業で取り上げなかった関数も演習では使用する場合がありま す。そこで、そのような関数やプログラムの書き方について、簡単に紹介します。 ここで出てくる使い方はほんの一例ですから、参考書等でいろいろ勉強すること を忘れないで下さい。

インデントを付けよう!

プログラムを作成する際には、必ずインデントを入れ、見やすくなるように心が けましょう。つまり、各行の先頭には適宜空白を入れ、段差を付けることにより、 流れや処理のまとまりがわかるようにします。また、コメント (/* */)を入れる ことを忘れてはいけません。
emacs / xemacs上では、各行でTabキーを押せば、自動的にインデントして くれます。このときに、インデントがずれる場合は、構文に誤りがある可能性が あります。このように、誤りの発見も容易になるので、絶対行ってください。
#include <stdio.h>
#define N 10

int main(void) {
   int i, sum=0;

   /*** 0からNまでの和 ***/
   for( i=0 ; i<=N ; i++ ) {
      printf("%d\n", i);
      sum += i;
   }

   /*** 合計を表示 ***/
   printf("Sum= %d\n", sum );
}

複合演算子

値を1だけ増やすのではなく、0.1や2ずつ増やしたい場合もあります。このよう な場合には、
x += 0.1;  /** x = x + 0.1; と同じ **/
n += 2;    /** n = n + 2;   と同じ **/
を用います。「+」の代わりに*,/,-などの他の演算子でも同様のことができます。 演習では、この表記を使って書いて下さい。
問題です。次のプログラムの一部を実行したとき、y,zの値はいくつになってい るでしょうか? このようにインクリメント演算子の位置によって、計算順序が異 なりますから、うまく使い分けて下さい。
x = 2;
y = x++;  /** y=x; x++; と同じ **/
z = ++x;  /** x++; z=x; と同じ **/

割り算

割り算の演算子は / ですね。では、以下のプログラムの一部を実行したとき、 それぞれの値はいくつになるでしょうか? 変数の型がこの場合、重要になります。
int    n=10, m=3;
double x=10, y, z;

y = n / m;  /** (1) **/
z = x / m;  /** (2) **/
どちらも mで割り、double型の変数に代入していますので、同じ値になると思わ れるでしょう。しかし、実行結果は異なります。それは、整数同士の割り算の結 果は整数になる、つまり(整数)/(整数)=(整数) になるからです。(1)の場合、nとmはどちらも整数型ですから、10/3の値は3にな ります。その後、実数型の変数yに代入されます。 一方、(2)の場合、xは実数型ですから、10/3の値は3.333...と実数になります。 変数を用いず、数字で書いた場合も同様です。10/3=3となりますが、 10.0/3=3.33...となります。

実数の精度

x=0.03; として、xを100回足した結果を表示してみてください。期待どおりに 表示されるでしょうか? 100*x=2 となります。実数(特に小数)は、 丸め誤差を含むため。厳密に計算できない場合もあります。
int main( void ) {
  int   y,i;
  float x=0.03,z;
  for(i=0; i<100; i++ )
    z += x;
  y = z;
  printf("x=%.10f\t100*x=%d\n", x, y);
  return 0;
}

ループ

ループには、for文、while文、do〜while文がありますね。それぞれの文に向い た使い方がありますから、適切に使い分けて下さい。

for文

基本的に、一定回数のループを行う場合に使用します。N回処理を実行する場合 は以下のようになります。
#define N 100


for (i=0; i<N ; i++ ) {
   ...
}
printf("%d\n", i); /** (1) **/
ループ回数Nのような定数は、プログラムの先頭でdefineしておきます。 定数をプログラム中に埋めこんではいけません。 さて、for文の外のprintf()で、ループ変数iを表示した場合、いくつになってい るでしょうか?(上記(1)のところ) i<N を満たさない最初の値はNですから、i=Nになっています。
ちなみに、上記のfor文は以下のように書いた場合と同じです。
i=0;
while ( i<N ) {
   ...
   i++;
}

while文とdo文

一定回数ではなく、ある条件が成り立っている間、ループする場合、while文 またはdo文を使用します。 do文の場合は、条件判定までに少なくとも1回はループ内の処理がされます。
i=0;
while ( i<N ) { /** N=0 のときは実行されない **/
   ...
   i++;
}

i=0;
do { /** N=0 のときも実行される **/
   ...
   i++
} while(i<N);
どのループ文を使用すべきかの目安は以下のようになります。

配列

配列を宣言するときは、その大きさNを指定しなければなりませんね。これは一般 に定数になります。従って、#define N 100のように定義しておきましょう。
配列は0番目からN-1番目まで使用できますから、注意して下さい。
#define N 100

int X[N], i;
for (i=0 ; i<N ; i++ )
     X[i] = i*i;

関数malloc()

配列は一定の大きさを予め確保する場合に向いていますが、大きさが実行時に決 まる場合などには利用できません。このような場合は、自分でメモリの領域を確 保する必要があります。メモリ領域を確保する関数には関数malloc()があります ので、これを使って下さい。
関数malloc()の引数は使用するメモリのバイト数になります。従って、int型の 領域をN個確保する場合、以下のようになります。
int A[N];        /** 配列の場合 (int型でN個) **/
int *x;          /** int型のポインタ x **/

x = malloc(sizeof(int)*N);
変数型のサイズは機種依存性があるので、sizeof()を用 いて間接的に求めます。演習室のPCの場合は、sizeof(int)は4バイトに なります。これをN個用意するので、sizeof(int)*N バイトをmallocで確保します。
確保したメモリ領域は、使用しなくなった時点で解放 しなければなりません。以下のようにポインタxを用いて、確保した領域 を解放します。
free(x);

文字列の操作: strlen(), strcmp()など

文字列はchar型の配列かchar型のポインタを用いて操作することができます。 文字列の長さを求める関数 strlen()、文字列のどうしを比較する関数 strcmp()など様々な関数が用意されています。これらを使う場合は、string.h をincludeする必要があります。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>   /*** 追加 ***/
   ...
char str[BUFSIZ], *s="abc";
   ...
scanf("%s", str);     /*** 文字列を読み込む ***/
printf("%d %d\n", strlen(str), strlen(s));
if ( strcmp(str,s) == 0 ) printf("match!\n");

参考: man string

コマンド引数 : main(int argc, char *argv)

プログラムにデータを入力する場合、実行した後にデータを入力するよりも、コ マンドの引数としてデータを渡した方が格好良いです。以下のようにすると、引 数で値を渡すことができます。argc には引数の個数が代入されます。*argv[]に は引数が文字列として代入されます。argv[0]はコマンド名自身になり、 argv[1]が1番目の引数になります。
int main(int argc, char *argv) /*** argc は引数の個数、*argv[]に引数が入る ***/
{
  if ( argc < 3 ) {            /*** 引数の個数が 1+2個なければ使用例を表示 ***/
    printf("Usage: %s string1 string2\n", argv[0]); /*** argv[0]はコマンド名 ***/
    exit(EXIT_FAILURE);
  }
  if ( strcmp(argv[1],argv[2]) == 0 ) printf("match!\n");
}
--------------------------------------------
実行例
      $ ./a.out abc
      Usage: ./a.out string1 string2
      $ ./a.out abc abc
      match!
      $ ./a.out abc abcd
      $

様々な関数の呼び出し方

関数はプログラムをする上で、欠かせない機能です。同じ処理を複数書くよ りも関数にして、呼び出せば1回書けばすみます。また、意味のある単を関数と してまとめると、処理の流れが分かりやすくなり、バグ(誤り)を減らすことがで きます。関数と一口に言っても、いろいろな定義の仕方があるので、適切な書き 方を知って下さい。

引数なし、返り値なし

引数も返り値もない関数は、例えばコマンドの使い方を表示するだけの関数 などに用いられます。 あまり役に立ちませんが、単に一定の内容を表示するだけの関数は以下のよ うになります。
void Usage(void) {
   printf("Usage: command arguments\n");
}
int main(int argc, char *argv) {
   if ( argc < 1 ) Usage();
   ...
}	

引数あり、返り値なし

引数で与えられた変数の内容を単に表示する場合などは以下のように書きま す。
void dump(int x, double f) {
   printf("x=%d F(x)=%f\n\n", x, f);
}
int main(void) {
   dump( 3, 1.2 );
}	

引数なし、返り値あり

引数が無いのに、返り値がある関数を書くことはあまりありませんが、 乱数を発生させるrand(), drand48()などはこの分類です。 以下の関数は呼び出す毎に1ずつ大きな値を返します。
int countup(void) {
   static int cnt=0;
   return cnt++;
}
int main(void) {
   int i;
   for( i=0 ; i<10 ; i++ ) 
       printf("%d\n", countup());
}	

引数あり、返り値あり

よく使われるのがこの形でしょう。数学の関数と同じタイプになります。 配列x[N]の中味の和を求め、その値を返す関数は以下のようになります。
int sum( int x[N] ) {
   int i, ret=0;
   for(i=0; i<N; i++) ret += x[i];
   return ret;
}
int main(void) {
   int a[N] = {1,2,3,4,5,6,7,8,9,10};
   printf("Sum= %d\n", sum(a));
}	
さて、関数の返り値に複数の変数の値を返したい場合や、配列を返したい場合は どうすれば良いでしょうか? 最後の「引数あり、返り値あり」でできそうだと思 うかも知れませんが、この場合は「引数あり、返り値なし」のタイプになります。 値を返すには、関数自体の返り値の他に、引数を介して値を返す方法があります。 いま述べた例は、このような場合に相当します。

引数を介して値を返す方法

これにはポインタを用います。2数の和と差の両方を返すには以下のようにな ります。
void add_sub( int x, int y, int *add, int *sub ) {
   *add = x+y;
   *sub = x-y;
}
int main(void) {
   int wa, sa;
   add_sub( 5, 3, &wa, &sa );
}	
配列を返す場合は、配列自体がポインタと同様に扱えるので、以下のように なります。
void array_add( int x[N], int y[N], int add[N] ) {
   int i;
   for(i=0; i<N; i++) add[i] = x[i]+y[i];
}
int main(void) {
   int x[N], y[N], z[N];
   array_add( x, y, z);
}	
以上で、関数の基本的な使い方は終了です。もちろん、ポインタを使って、 引数を介して値を返しながら、関数自体の返り値がある関数も書くことができま す。

構造体

いくつかの変数をまとめて一体にして扱うと便利なことが多くなります。例 えば、座標を表す変数P(x,y)を1組で表現できるとプログラムがわかりやすくな ります。このようにまとめて1つの構造をつくるものを構造体と言います。
座標P(x,y)を表す構造体
struct Point {
   int x;
   int y;
} P;

プログラムの中では、P.x, P.yで参照します。

typedef しておこう

一度宣言した構造体を一ヶ所だけで使用するならば、上記のように宣言すれ ば良いですが、別の関数で使用したい場合には、同じ構造体をまた書くのは面倒 です。この場合、新しい型として定義していまいましょう。次の例では、int型 の変数を2つ持つ構造体の型として、Point型を定義しています。
typedef struct {
   int x;
   int y;
} Point;

プログラム中では、Point P;でPoint型の変数Pを宣言すること ができます。

構造体のポインタ

上記のPoint型で、今度はポインタを使ってみます。次の例では、Point型の ポインタQを宣言します。
Point *Q;
Q = (Point *) malloc(sizeof(Point));
値を保持するための領域はmalloc()で確保しています。さて、メンバxやyに どのようにアクセスすればよいでしょうか? Qはポインタなので、 *Q.xと書けばよさそうですが、これではダメです。このように書 くと、*(Q.x)と認識されます。ですから、(*Q).xと 書けば、これまで通り参照できます。

しかし、この書き方はあまり使われません。構造体のポイ ンタの場合は、Q->x と表記します。こっちを使いましょう。

自己参照をもつ構造体

リスト構造などの様々なプログラムで、自分自身をメンバにもつ構造体が使 われています。自分自身を定義するのに自分自身を使うのは、再帰呼び出しと似 ていますね。
次の定義は、構造体Listのメンバが、int型のdataと構造体List自身へのポイ ンタである場合を示しています。なお、typedefしているので、List型として 以後は単にListだけで使用できます。
typedef struct List {
  int   data;
  struct List *next;
} List;
リスト構造サンプルプログラム を参照してください。

Last modified: Tue Jul 12 18:58:21 JST 2016