I ♥ WordPress

[時間割:C言語]『デバッグではじめるCプログラミング』
2009/02/18 23:23 posted by kunkichi

またここ数日忙しくて更新できてないのですが、一応ちゃんとやってるよという報告もかねて(笑)

時間割の達成状況をちょっと見直してみたいと思います。

  • 「Perl」の時間は順調とはいわないまでもある程度こなせてる感じ
  • 「サーバ」は引き続きRADIUSを。一応本は読み終わってるので、知識定着のためのまとめをやってこうかなと。あと、念願のクアッドコアCPUを手に入れたので、いろいろと試していきたいなと。

ということで、問題は「C言語」。

これまで、はじめてのC言語 完全入門に沿ってやってきたのだけど、途中からどうも面白味に欠ける気がしてきて、モチベーション低下&ちょっとサボりがち。ということで、てこ入れのために、書籍を変えました。選んだのはこれ。

デバッグではじめるCプログラミング
山本 貴光
翔泳社
売り上げランキング: 262121

ゲームを作る(なつかしのROGUE系RPG!angbandとかやったなぁ、、、)という目標を設定して、実際に設計しつつ実装していくというなかなか実践的な内容。本の中で紹介されているソースコードはわざとちょこちょこ間違いがあって、それをデバッグしつつ学ぶというところが特徴的で面白い本です。ここ2、3日で一気に8割方読み終えるぐらいに読みやすいし、サンプルプログラムに目標がちゃんとあるのはゴールが見える感があってモチベーションも維持しやすいしね。

ただ、はじめてのC言語 完全入門ほど細かい説明(とはいうもののこれもポインタとか配列のところの説明はやっぱり足りない気がするなぁ、具体的にどういうシチュエーションでどう使ったらどういうメリットがあるか?っていうところまではサンプルプログラムには含まれてるとは思えないし。)があるわけではないので、一気に読み進めれたのははじめてのC言語 完全入門をある程度読んでいたからってのもあるね。そういう意味では無駄にはなってないってことだとプラスに考えるようにしてます。

あと少しで読み終えれそうなので、一通り読み終えたら、サンプルのゲームをカスタマイズしつつ実際に作ってみたいと思ってます。一応Windows環境でのプログラミングを想定しているようなので、UNIXでやるならどうするか?とか、これをPerlで書くならどうするか?ってとこまでできると面白いかも。

ま、あんまり細かく勉強成果を報告できるような感じではなさそうなのですが、なるべくアップできるようにがんばります、汗

[時間割:C言語]『はじめてのC言語完全入門』その16
2009/01/24 00:40 posted by kunkichi

ここんところ疲れが出たみたいで、本読みつつ知らないうちに落ちてるという毎日で少しアップをサボり気味、、、また気分入れ替えて出直しです。ということで金曜日は『C言語』。今回は文字列の応用編。

はじめてのC言語 完全入門 (標準プログラマーズライブラリ)
塚越 一雄
技術評論社
売り上げランキング: 420963


  • stdlib.h ・・・ 数値への変換#include <stdlib.h>
    • 文字列をintに変換int i = atoi("12345");
      i += 5;
      printf("%d\n",i);
      結果:12350
    • 文字列をlongに変換long l = atol("1000000000");
      l += 1000000000;
      printf("%ld\n",l);
      結果:2000000000
    • 文字列をflotに変換double f = atof("3.141");
      f += 0.245;
      printf("%f\n",f);
      結果:3.386000atofはfloat型でなくdouble型に変換するので注意
    • 数値と文字が両方含まれる場合
      • 文字は変換できないのでその手前までを変換しようとする。int i = atoi("12345abcde");
        printf("%i\n",i);
        結果:12345最初から文字があって数字が続く場合int i = atoi("12345abcde");
        printf("%i\n",i);
        結果:0
      • 先頭の空白文字は無視されるint i = atoi("     12345");
        printf("%i\n",i);
        結果:12345
  • strings.h ・・・ 文字列操作#include <strings.h>
    • 文字列のコピー
      • そのままコピーchar str1[] = "abcdefghijklmnopqrstuvwxyz";
        char str2[256];
        strcpy(str2,str1);
        printf("%s\n",str2);
        結果:abcdefghijklmnopqrstuvwxyzstrcpyの引数はコピー先、コピー元の順なので注意。
      • コピーする文字数を指定してコピーchar str1[] = "abcdefghijklmnopqrstuvwxyz";
        char str2[256];
        strncpy(str2,str1,10);
        str2[10] = '\0';
        printf("%s\n",str2);<code>結果:<code>abcdefghij
        strncpyの場合、コピー元文字数とコピーする文字数に注意。
        「コピー元文字数<コピーする文字数」の場合は上記のように最後にNULL文字を付与してやる必要がある。
        逆に「コピー元文字数>コピーする文字数」の場合は残りの文字をNULL文字が埋めるので気にしなくてよい。
    • 文字の連結char str1[] = "abcdefghijklmnopqrstuvwxyz";
      char str2[] = "1234567890";
      strcat(str2,str1);
      printf("%s\n",str2);
      結果:1234567890abcdefghijklmnopqrstuvwxyzstrcatは第1引数の後ろに第2引数をくっつける。
      連結先は連結した文字列を含める十分なサイズを持ってないと行けないとあるけど、上のように書くと特に意識しなくても連結されるんだけどなぁ、、、

おっと、なんかまとめるのに時間がかかったなぁ。あんまりページ進んでないやw。
まあ少しでも毎日続けれるように、無理せず今日はここまで。

[時間割:C言語]『はじめてのC言語完全入門』その15
2009/01/18 21:13 posted by kunkichi

火曜日は仕事だったのでしょうがないとして、金曜日は帰宅早々寝ちゃったので、その分を取り返しておきます。ということで『C言語』です。引き続き文字列とポインタです。関数の引数として文字列を渡す、その関数を呼び出す、というのをやります。

はじめてのC言語 完全入門 (標準プログラマーズライブラリ)
塚越 一雄
技術評論社
売り上げランキング: 420963


  • 引数の書式は2パターン。配列で渡すか、ポインタで渡すか。
    • 配列の場合void kansu(char str[])
      {
        printf("渡された引数は%sです。¥n", str);
      }
      配列の場合はサイズ指定できないのは通常の配列と同じ。また、この時strは配列名であり定数である点に注意。
    • ポインタの場合void kansu(char *p)
      {
        printf("渡された引数は%sです。¥n", p);
      }
      この時strはポインタであり変数である点が配列で渡す場合と異なる。
  • 配列形式、ポインタ形式のそれぞれで渡された配列の引数は意味としては異なるけど、関数内では文法上同じ扱いになる。従って、
    • 配列で渡されたものを関数内でポインタとして使っても良い。
    • ポインタで渡されたものを関数内で配列として使っても良い。

    以前にやった配列とポインタのところと全く同じ。

  • 呼び出す時は、文字列の先頭アドレスを渡してあげる。つまり、
    • 文字配列で渡す場合は配列名(配列名は配列の先頭アドレスを示す)kansu(str);
    • ポインタで渡す場合はポインタ(ポインタはそもそも先頭アドレス、サイズはポインタの型が決める)kansu(p);
    • さらにもう一つ、文字列を直接指定してあげることもできる。kansu("abcdef");というのは式の中で文字列リテラルが渡されると先頭アドレスとして評価されるから。
  • ということでサンプル。書籍にもある、エラーメッセージとリターンコードを渡して、exitしちゃうプログラム。配列指定の場合とポインタ指定それぞれの場合の関数を用意してます。mainの中で呼び出す関数を変更すればそれぞれを呼び出します。まあ結果は同じなんだけど。#include <stdio.h>
    #include <stdlib.h> /* include exit() */
     
    void fatal_p(char *errmsg, int errcode);
    void fatal_a(char *errmsg, int errcode);
     
    int main()
    {
      fatal_a("異常終了!!!",255);
     
      fprintf(stdout,"%s\n", "正常終了");
      return 0;
    }
     
    void fatal_p(char *errmsg_p, int errcode)
    {
      fprintf(stderr, "%s\n", errmsg_p);
      exit(errcode);
    }
     
    void fatal_a(char errmsg_a[], int errcode)
    {
      fprintf(stderr, "%s\n", errmsg_a);
      exit(errcode);
    }
  • さっきの例では、サンプルの関数はvoidだったので何も返さないのだけど、関数で文字列を返す場合は、関数の型を文字へのポインタとして定義する必要がある。そして呼び出す際は、返された文字列を一時的に入れておく文字列用バッファを用意しておいてそこに代入する。char *kansu(〜){
      ・・・
    }
     
    int main(){
      char str[256];
      char = kansu(〜);
    }
    とあるのだけど、んー、うまく動かない、、、最後に書いてある、文字列を渡したら最後の1文字を除いた文字列を返すサンプルスクリプトをちょっといじってみた。本の中ではループで順に1文字づつ消しているのだけど、単に1文字だけ返すように書き換えてみたのだけど、バッファに返そうとするところでコンパイラがエラーを吐いちゃう。#include <stdio.h>
    #include <string.h>
     
    char *remove_last_char(char str[])
    {
      int last = strlen(str);
     
      if( last > 0 )
        str[last-1] = '\0';
     
      return str;
    }
     
    int main()
    {
      char s[] = "1234567890";
     
      char ret[256];
      ret = remove_last_char(s);
     
      printf("%s\n",p);
    }
    ビルドするとこんな感じ。$ gcc -o str_ary2 str_ary2.c
    str_ary2.c: In function ‘main’:
    str_ary2.c:19: error: incompatible types in assignment
    本のサンプルと同様に関数の結果をそのままprintfに渡してやるとちゃんと動く。#include <stdio.h>
    #include <string.h>
     
    char *remove_last_char(char str[])
    {
      int last = strlen(str);
     
      if( last > 0 )
        str[last-1] = '\0';
     
      return str;
    }
     
    int main()
    {
      char s[] = "1234567890";
     
      printf("%s\n",remove_last_char(s));
    }
    なんでかなぁ〜、、、文字へのポインタにする意味がまったくわからない。

うーん、やっぱりポインタ、配列、文字配列とまだまだ整理できてないので、理解できてない。以前にやった、ポインタと配列のところ読み返してみたんだけどね〜、それでもだめか。一度整理しておく必要があるかな。ただまあそれは課題として置いておいて、どんどん進んで一通り終わらせることを優先とします。

次回は文字列の応用編。

[時間割:C言語]『はじめてのC言語完全入門』その14
2009/01/10 03:37 posted by kunkichi

金曜日は「C言語」。文字列とポインタです。難しそーだなー。

はじめてのC言語 完全入門 (標準プログラマーズライブラリ)
塚越 一雄
技術評論社
売り上げランキング: 420963


  • 文字へのポインタ宣言。char *p;
  • ただしこの時文字列のためのメモリ確保は行われていないため、以下の方法でメモリを確保する。
    • 動的メモリの確保。これは後日。
    • 配列を確保して先頭アドレスをポインタに代入。char *p;
      char str[]= "Hello, World";
      p = str;
      これでpが文字列を表す変数として使えるので、printfで出力してみる。printf("%s¥n",p);結果:Hello,Worldさらにポインタをインクリメントして、文字列の操作もできる。p++;
      printf("%s¥n",p);
      結果:ello,World
    • 文字列リテラルchar *p = "Hello, World";シンプルでわかりやすい。ただし、確保した文字列の変更はできない。printf("%s\n",p);
      *p = 'h';
      printf("%s\n",p);
      結果:Hello,World
      Bus error
      逆に、配列を確保してポインタに代入した場合は変更できる。char *p;
      char s[] = "Hello,World";
      p = s;
       
      printf("%s\n",p);
      *p = 'h';
      *(p+6) = 'w';
      printf("%s\n",p);
      return 0;
      結果:Hello,World
      hello,world

うーん、なんかピンとこないなー。配列、ポインタ、文字列とややこしいのがミックスした感じですっきりしない。
もう一度、前のところ見直してみようかな。

ちょっと短いけど今日はここまで。

[時間割:C言語]『はじめてのC言語完全入門』その13
2009/01/06 22:56 posted by kunkichi

火曜日は「C言語」。前回、ファイルからの入出をやったので、今度はファイルへの出力です。

はじめてのC言語 完全入門 (標準プログラマーズライブラリ)
塚越 一雄
技術評論社
売り上げランキング: 420963


  • 書き込み時は、オープンモードを”w”にする。FILE *ofp;
    ofp = fopen("output.txt","w")
  • ファイルへの出力にはfputcを使う。fput(c, ofp);
  • 書き込みの場合はエラー処理が重要。fputcは、EOFに達した場合も書き込みエラーの場合も両方-1を返すので、終了とエラーの区別ができない。よって、エラーが発生していたら真を返すferror関数を使って、「書き込みエラー」を取得する。fputc(c,ofp);
    if(ferror(ofp)){
      printf("書き込みに失敗しました。¥n");
      fclose(ofp);
      return XXX;
    }
    エラーが発生した場合に処理を終了する場合は、ファイルのクローズを忘れないように。
  • 前回のサンプルスクリプトに出力処理を追加。sample.txtを読み込んで、その内容をそのままresult.txtに出力する、いわばcpコマンドのような感じで。#include <stdio.h>
     
    #define INFILE  "./sample.txt"
    #define OUTFILE "./result.txt"
     
    int main()
    {
      FILE *ifp;
      FILE *ofp;
      
      if( !(ifp = fopen( INFILE, "r" )) ){
        printf("cannot open input file.\n");
        return 255;
      }
      
      if( !(ofp = fopen( OUTFILE, "w" )) ){
        printf("cannot open output file.\n");
        return 255;
      }
      
      int c;
      while( (c = fgetc(ifp)) != EOF ){
        fputc(c,ofp);
      }
      
      if(ferror(ofp)){
        printf("書き込みに失敗しました。\n");
        fclose(ofp);
        fclose(ifp);
        remove(OUTFILE);
        return 1;
      }
     
      if( fclose(ofp) ){
        printf("cannot close output file.\n");
        return 255;
      }
      
      if( fclose(ifp) ){
        printf("cannot close input file.\n");
        return 255;
      }
      
      return 0;
    }
  • getcharを使うと、標準入力から1文字読み込むことができる。fgetcと同じように返り値はintで受ける。引数はなし。
  • ferrorってイメージとしてはエラーフラグが立つという感じかな。whileのループの中でなく、ループが終了した後にチェックしているのはそういう意味かと。
  • 上記はferrorで書き込みエラーを拾ったら、出力ファイルポインタをクローズすると共にファイルを削除している。削除にはremove(ファイル名)を使用する。Perlのunlinkみたいなもんかな。
  • fputcは1文字単位なので、ちょっと使いにくい。だけど、fprintfを使えば、printfのように、文字列を特定の書式に当てはめる形で出力することができる。fprintf(ofp,"今日は%d月%d日です。¥n", month, day);引数にファイルポインタを追加する以外はprintfと全く同じ使い方。
  • fprintfを使ったファイル出力のサンプル$ cat fprintf.c
    #include <stdio.h>
     
    #define OUTFILE "result.txt"
     
    int main()
    {
      FILE *ofp;
      if(!(ofp = fopen(OUTFILE,"w"))){
        printf("cannot open outfile.\n");
        return 255;
      }
      
      int num=0;
      printf("数字を入力してください。 ");
      scanf("%d",&num);
     
      fprintf(ofp,"入力された数字は、%d です。\n",num);
      fclose(ofp);
     
      return 0;
    }
    ビルドして実行してみる。$ ./fprintf
    数字を入力してください。 356
    $ cat result.txt
    入力された数字は、356 です。
  • 標準出力、標準エラー出力にも出力できる。それぞれ組み込みのファイルポインタである、stdout、stderr を fprintfの引数にすれば良い。エラーメッセージ等はstderrに出すと良いね。

ということでファイルの出力もできるようになりました。
次は文字列とポインタです。

[時間割:C言語]『はじめてのC言語完全入門』その12
2009/01/03 04:22 posted by kunkichi

前回からずいぶん時間が経ってしまっているなぁ、、でも新年の誓いも経てたことだし、心機一転がんばります!
金曜日は「C言語」。ファイル入出力の章からです。

はじめてのC言語 完全入門 (標準プログラマーズライブラリ)
塚越 一雄
技術評論社
売り上げランキング: 420963


  • stdioライブラリのfopen関数を使用する前提とします。
  • fopenはFILE型のポインタを返すので、事前にFILE型のポインタを用意しておくこと。FILE *fp;
    fp = fopen( "sample.txt", "r" );
  • 引数はPerl等と同じく、ファイルとモードを指定する。ただし、モードの指定は、”r”, “w”, “a”, “+”の文字列を使用する。意味は推して計るべし。
  • fopenは失敗した場合に0を返すのでこれを使ってエラーチェックする。
  • ファイルからのデータ読み込みには、ファイルから1文字入力するfgetc関数を使う。fgetcが返すのは、成功した場合は読み込んだ文字を、失敗した場合は-1となる。
  • fgetcの返り値は普通に考えればcharだけどintで受ける。これは
    • charは1バイトのintとして扱える。ただし、その範囲は符号の有無によって変わる。(符号付きの場合は-128〜127、符号なしの場合は0〜255)
    • 全ての文字集合を1バイトで表す場合、成功時は0〜255を返し、-1は失敗を表す。すなわち、-1を表現できる必要がある
    • ところがcharの符号有無は実装に依存する。
    • charがunsignedの場合は、-1を返した場合に10進数では255と区別ができなくなる。
    • charがsignedの場合は、-1を返すことはできるが返せる文字コードの上限が127までとなり、全ての文字集合を表現できない。

    従って、charでは役不足なのでintを使うということ。

  • fgetcはファイルの終わりに達すると-1を返すので、これを使って、fgetcを-1が返るまでループで回せば全データを取得できる。while( (c = fgetc(fp)) != -1 ){
        printf("%c",c);
      }
    また-1の代わりに定数EOFを使ってもよい。
  • ファイルのクローズにはfcloseを使う。引数はファイルポインタのみ。
  • fopen同様、成功時は0、失敗時は1を返す。読み込みだけの場合はfclose時のチェックは不要だけど、書き込みの場合は、チェックした方が良い。

ここまでの内容をサンプルで書くとこんな感じに成りました。単にファイルを開いて出力するだけです。
#include <stdio.h>
 
#define FILENAME "./sample.txt"
 
int main()
{
  FILE *fp;
  
  if( !(fp = fopen( FILENAME, "r" )) ){
    printf("cannot open input file.\n");
    return 255;
  }
  
  int c;
  while( (c = fgetc(fp)) != EOF ){
    printf("%c",c);
  }
 
  if( fclose(fp) ){
    printf("cannot close input file.\n");
    return 255;
  }
  
  return 0;
}

出力時はprintfを使っているけど、putcharとか、他にもあるようです。

とりあえず今日はファイルからの入力まで。次はファイルへの出力をやります。

[時間割:C言語]『はじめてのC言語完全入門』その11
2008/11/21 22:59 posted by kunkichi

いかんいかん、XBOX360の「FIFA09」が出たせいでサボりまくり、、、
気分を取り直してがんばります。金曜日は「C言語」。今日は文字列です。

はじめてのC言語 完全入門 (標準プログラマーズライブラリ)
塚越 一雄
技術評論社
売り上げランキング: 420963


  • Cには文字列型は存在しない。文字列は文字型の配列を使う。なぜなら可変長だから。
  • 文字列の初期化は以下の数パターン。
    • 文字単位で初期化。#include <stdio.h>
       
      int main()
      {
        char s[5];
       
        s[0] = 'a';
        s[1] = 'b';
        s[2] = 'c';
        s[3] = '\0';
       
        printf("%s\n", s);
       
        return 0;
      }
      ポイントをいくつか。
      • 宣言するときにサイズもあわせて宣言する。サイズは代入したい文字数に1を足しておく。
      • 1を足したのは最後に文字列の終わりを示すEOS(End Of String)の分。EOSにはNULL文字を使う。文字列の場合は\0。
      • printfで出力する場合は%sで配列名を指定する。
    • {}をつかったやり方#include <stdio.h>
       
      int main()
      {
        char s[] = {'a', 'b', 'c'};;
       
        printf("%s\n", s);
       
        return 0;
      }
      この場合はサイズの宣言は不要。よしなにやってくれる。
    • 文字列リテラルで。#include <stdio.h>
       
      int main()
      {
        char s[] = "abc";
       
        printf("%s\n", s);
       
        return 0;
      }
      一番普通。
  • {}を使った場合や文字列リテラルで初期化した場合でもサイズの指定は可能。ただし、EOSの分を足すことを忘れないこと。
  • 同じ文字だけの文字列で初期化する場合は、string.hのmemset関数を使う。#include <stdio.h>
    #include <string.h>
     
    int main()
    {
      char s[4];
     
      memset(s,'a',3);  /* 1: 代入する配列、2:文字、3:文字数 */
      s[3] = '\0';          /* 最後にEOSを代入。面倒、、、 */
     
      printf("%s\n", s);
     
      return 0;
    }
  • 文字列宣言後に文字列リテラルで代入することはできない。その場合はベタに文字配列で要素ごとに代入するか、strcpyを使う(これはまだ先)。文字配列の場合はEOSを最後に代入することを忘れないこと。
  • サイズを超えた場合でも、サイズに見たない場合でも、コンパイラはエラー等を返さないので注意。特に超えた場合は、、、

うーん、やっぱり文字列の扱いは面倒だなぁ、特にサイズを意識しないといけないってのは厳しい。十分に気をつけるようにして次へ進みましょう。次回は「ファイル入出力」。やっと実際のプログラミングらしくなってきました。

[時間割:C言語]『はじめてのC言語完全入門』その10
2008/11/09 00:27 posted by kunkichi

ここ2、3日格闘していた、ポインタですが、なーんとなく少し壁は超えれたような気がするので、わかってるところだけでもちょっとまとめてみます。あくまでも、なーんとなくのレベル。

はじめてのC言語 完全入門 (標準プログラマーズライブラリ)
塚越 一雄
技術評論社
売り上げランキング: 420963


ポインタ

  • 変数は値を記憶する。ポインタ変数はアドレスを記憶する。
  • 宣言する場合は、変数名の前に*をつける。あと、ポインタにも型は必要。int *pval;
  • ポインタで指定されるアドレスは先頭アドレス。そこから何バイトのデータなのかを判断するために、「型」が必要。(型がわかればバイト数がわかる。sizeof(int)とか)。つまりポインタは型情報を持つということ。
  • ポインタへの代入は当たり前だけどアドレスを代入する。通常はアドレス演算子&を使う。変数に&をつけると、その変数の値ではなく、アドレスを参照する。int *pval;
    int val;
    pval = &val;
  • ポインタがさしている別の変数のアドレスから、値を参照する場合は、*を使って逆参照する。int val = 10;
    int *pval;
    pval=&val;
    printf("%d¥n",*pval);
    結果は”10″が出力される。
  • Cの関数では、実引数の値は仮引数にコピーされるため、関数内でどうこうしたとしても実引数には影響しない。つまり、スコープの話。
  • 関数内で実引数を直接変更したい場合はポインタ引数を使う。
    関数の宣言:void pointer_func( int *x, int *y )関数の呼び出し:pointer_func( &a, &b )関数内で値を参照:int val = *a;
  • アドレスを返すような関数でエラーを返す場合はNULLポインタを使う。

配列とポインタ

  • 配列の名前は、配列の先頭アドレスを意味するので、ポインタに代入できる。int ary[] = { 1, 2, 3 };
    int *pval;
    pval = ary;
  • 配列の先頭アドレスは、配列の最初の要素の先頭アドレスでもある。つまり、ポインタに代入した後、逆参照すれば、最初の要素の値を取得できる。※上の続き
    printf("%d¥n", *pval);
    結果は1となり、ary[0]の値となる。
  • ポインタで演算をすると、そのポインタの型が使用するメモリサイズ分だけ加算される。#include <stdio.h>
     
    int main()
    {
      int *p;
      printf("%p\n", p );
      p += 1;
      printf("%p\n", p );
     
      return 0;
    }
    結果は0x1000
    0×1004
    となる。
  • 上記を配列で使うと、2番目以降の要素の参照ができる。#include <stdio.h>
     
    int main()
    {
      int ary[] = {
        1,
        5,
        8
      };
      int *p;
     
      p = ary;
     
      printf("%p:%d\n", p,   *p     );
      printf("%p:%d\n", p+1, *(p+1) );
      printf("%p:%d\n", p+2, *(p+2) );
     
      return 0;
    }
    結果:0xbffff9f0:1
    0xbffff9f4:5
    0xbffff9f8:8
    アドレスは4バイトずつ(僕の環境では)増えて、配列の要素を順に参照している。
  • 上の例では参照するポインタを直接指定していたけど、ループを使ってポインタの値をインクリメントしながら参照してみる。#include <stdio.h>
     
    int main()
    {
      int ary[] = {
        1,
        5,
        8
      };
      int *p;
     
      p = ary;
     
      int i;
      for( i=0; i<(sizeof ary / sizeof ary[0]); i++ )
        printf( "%d:%p:%d\n", i, p, *p++ );
     
      return 0;
    }
    結果は同じ0:0xbffff9f0:1
    1:0xbffff9f4:5
    2:0xbffff9f8:8
  • ポインタを配列のように使う。上の例のforループの部分を以下のように書き換えてみる。  for( i=0; i<(sizeof ary / sizeof ary[0]); i++ )
        printf( "%d:%p:%d\n", i, &p[i], p[i]);
    結果はこれまた同じ。0:0xbffff9ec:1
    1:0xbffff9f0:5
    2:0xbffff9f4:8
    つまり、配列のようにインデックスで指定して、元の配列要素の値を取得できる。
  • 今度は逆に配列をポインタのように使う。include <stdio.h>
     
    int main()
    {
      int ary[] = {
        1,
        5,
        8
      };
     
      int i;
      for( i=0; i<(sizeof ary / sizeof ary[0]); i++ )
        printf( "%d:%d\n", i, *(ary+i) );
     
      return 0;
    }
    結果はこうなる。0:1
    1:5
    2:8
    配列の各要素に、ポインタで使った逆参照演算子*を使って値を参照することができる。
  • 配列とポインタの違いは、
    • ポインタは変数であり、メモリ上にアドレスを記録するための領域が確保される。従って値を変更することができる。
    • 配列名は、コンパイラがコンパイル中に使用する「定数」なので、メモリ上に配列名を記憶するための領域は確保されない(ただし配列の各要素を記録するための領域は確保される)。従って、値を変更することができない。
  • 関数の引数に配列を渡す場合は配列名だけを渡す。int ary[];
    sample func(ary)
  • 関数側の受け取り方は以下の2パターン。配列の要素から最大値を求めるmaxという関数をそれぞれのパターンで書いてみる。
    • 配列で受け取る。例:
      int sample_function(int a[]){
      ・・・
      }
      サンプルコード:#include <stdio.h>
       
      int max(int a[], int size);
       
      int main()
      {
        int ary[] = {
          1,
          5,
          8
        };
        int val;
        val = max( ary, sizeof ary / sizeof ary[0]);
        printf("%d\n",val);
        return 0;
      }
       
      int max(int a[], int size)
      {
        int max = a[0];
        int i;
        for(i = 1;i<size;i++)
          if(max < a[i])
            max = a[i];
        
        return max;
      }
    • ポインタで受け取る。
      例:int sample_function(int *a){
      ・・・
      }
      サンプルコード:#include <stdio.h>
       
      int max(int *a, int size);
       
      int main()
      {
        int ary[] = {
          1,
          5,
          8
        };
        int val;
        val = max( ary, sizeof ary / sizeof ary[0]);
        printf("%d\n",val);
        return 0;
      }
       
      int max(int *a, int size)
      {
        int max = a[0];
        int i;
        for(i = 1;i<size;i++)
          if(max < a[i])
            max = a[i];
        
        return max;
      }
  • 配列を関数に渡す場合、渡されるのは配列の先頭アドレスだけなので、配列の要素数については別に渡してあげる必要があるので注意。上記の例で、関数の引数に配列だけじゃなくて、配列も渡しているのはそのため。試しに配列の要素数を返す関数を作って試してみる。#include <stdio.h>
     
    int arynum(int a[]);
     
    int main()
    {
      int ary[] = {
        1,
        5,
        8
      };
      int val;
      
      val = arynum( ary );
      printf("%d\n",val);
      printf("%d\n",sizeof ary / sizeof ary[0]);
      return 0;
    }
     
    int arynum(int a[])
    {
      int num;
      num = sizeof a / sizeof a[0];
      return num;
    }
    結果は以下。1
    3

ふぅー、途中ちょっと別の本も読んでみたりしつつでしたが、何度か繰り返し読んで、サンプルコードを自分で書いてみて、そんなこんなしてるうちに何となくわかってきました。やっぱり「何度も読む」ことで見えてこなかったものが少しずつ見えてくるんだよね。とりあえず最大の難関を終えてホッとしました。

次回は「文字列と文字配列」です。

[時間割:C言語]『はじめてのC言語完全入門』その9
2008/11/04 00:34 posted by kunkichi

ここ2、3日の間、ポインタと格闘していました。ポインタ単体の意味は理解できるのだけど、配列とごっちゃになった瞬間もうわかりません、、、他の言語と比べると、C言語ではどうも配列とポインタの区別が曖昧な印象があるんだよね。

これはちょっとまずいってんで、その部分だけもう少ししっかり勉強しようと言うことで、教科書増やしました。

C言語配列+ポインタよくわかる実践学習室 (標準プログラマーズライブラリシリーズ)
谷尻 かおり 谷尻 豊寿
技術評論社
売り上げランキング: 209427


C言語ポインタ完全制覇 (標準プログラマーズライブラリ)
前橋 和弥
技術評論社
売り上げランキング: 9027


両方ともヤフオクでゲットしたので比較的懐を痛めずに済みました(笑)

ここまでの印象では他言語と特に大きく変わるところもなかったし、特につまずいたところもなかったのですが、ここはC言語の一番の山場っぽいので苦戦しそうです。もう少し格闘してみたいと思います。

[時間割:C言語]『はじめてのC言語完全入門』その8
2008/10/25 22:49 posted by kunkichi

今週の頭は少しサボっちゃいましたが、気を取り直して。サボりの元凶となったのは Touch Diamond を買ったから(笑)ですが、遊びのために買ったんじゃない、ということを証明するために早速、金曜日の会社帰りに外で勉強しました。やっぱり外ははかどるな〜、3章一気に進んじゃったし。

ということで、木・金のアップです。まずは『C言語』の方から。

はじめてのC言語 完全入門 (標準プログラマーズライブラリ)
塚越 一雄
技術評論社
売り上げランキング: 420963


関数

  • Cの場合、関数の定義には、以下が必要。
    • 関数名
    • 関数の型。返り値の型を決める必要がある。
    • 引数。
    • 関数値。いわゆる、返り値。

    なんといっても、「型」が必要ってところが、PerlとかPHPとかとの最大の違い。

  • 返り値がない場合は、型にvoid を指定する。でもってreturnは値を指定しないか、returnそのものを省略できる。
  • ただし、void型関数の途中で終了する場合は、returnを使う。
  • 引数なしの場合は、voidを使う。voidを使わずに()だけで書いた場合は、引数がないのではなく、コンパイラが引数のチェックをしない、という意味になるので注意。
  • 関数を定義する場所は、
    • 関数が呼び出される場所よりも前に書く(Pascal方式)。シェルスクリプトはこっち。
    • 関数が呼び出される場所よりも後に書く(C方式)。ただしこの場合は、呼び出される前にプロトタイプ宣言が必要。
  • プロトタイプ宣言は、関数の使い方のチェックを行うためのもの、と言える。
  • static変数とauto変数の違い。
    • auto変数は、関数呼び出し時に初期化され、関数終了時に消滅する。#include <stdio.h>
       
      void auto_hensuu();
       
      int main()
      {
        auto_hensuu();
        printf("----------\n");
        auto_hensuu();
        return 0;
      }
       
      void auto_hensuu()
      {
        int i,num = 0;
        for(i=0; i<10; i++){
          printf("auto関数 %2d 回目: num  = %d\n", i +1 , num );
          num += 10;
        }
        return;
      }
      実行結果:$  ./kansuu
      auto関数  1 回目: num  = 0
      auto関数  2 回目: num  = 10
      auto関数  3 回目: num  = 20
      auto関数  4 回目: num  = 30
      auto関数  5 回目: num  = 40
      auto関数  6 回目: num  = 50
      auto関数  7 回目: num  = 60
      auto関数  8 回目: num  = 70
      auto関数  9 回目: num  = 80
      auto関数 10 回目: num  = 90
      ----------
      auto関数  1 回目: num  = 0
      auto関数  2 回目: num  = 10
      auto関数  3 回目: num  = 20
      auto関数  4 回目: num  = 30
      auto関数  5 回目: num  = 40
      auto関数  6 回目: num  = 50
      auto関数  7 回目: num  = 60
      auto関数  8 回目: num  = 70
      auto関数  9 回目: num  = 80
      auto関数 10 回目: num  = 90
      2回目の関数呼び出し時に変数が初期化されている。
    • static変数は、プログラム実行時に初期化され、プログラム終了時に消滅する。#include <stdio.h>
       
      void static_hensuu();
       
      int main()
      {
        static_hensuu();
        printf("----------\n");
        static_hensuu();
        return 0;
      }
       
      void static_hensuu()
      {
        int i = 0;
        static int num = 0;
        for(i=0; i<10; i++){
          printf("static関数 %2d 回目: num  = %d\n", i +1 , num );
          num += 10;
        }
        return;
      }
      実行結果:$ ./kansuu2
      static関数  1 回目: num  = 0
      static関数  2 回目: num  = 10
      static関数  3 回目: num  = 20
      static関数  4 回目: num  = 30
      static関数  5 回目: num  = 40
      static関数  6 回目: num  = 50
      static関数  7 回目: num  = 60
      static関数  8 回目: num  = 70
      static関数  9 回目: num  = 80
      static関数 10 回目: num  = 90
      ----------
      static関数  1 回目: num  = 100
      static関数  2 回目: num  = 110
      static関数  3 回目: num  = 120
      static関数  4 回目: num  = 130
      static関数  5 回目: num  = 140
      static関数  6 回目: num  = 150
      static関数  7 回目: num  = 160
      static関数  8 回目: num  = 170
      static関数  9 回目: num  = 180
      static関数 10 回目: num  = 190
      2回目の呼び出し時に1回目の処理終了時の変数をそのまま引き続き使用している。
  • 値を返さないvoid型関数は値を取るような処理では使えない。結果を代入するとか、結果を出力するとか(もちろん関数内で出力することはできる)。逆に値を返す関数は、値を取らない処理でも使える。
  • べき乗を計算するpow()を使う場合は、math.hをインクルードする。
  • 引数を取る関数は、仮引数を定義しなければいけない。その場合、仮引数の型も合わせて定義する。
  • プロトタイプ宣言している場合は、そちらにも引数をちゃんと指定すること。

スコープ

  • 関数内の変数は関数内にのみ限定される(ローカル変数)。
  • 実際には「ブロック」内、つまり{}で囲まれた範囲内に限定されているのがローカル変数。
  • 関数の外側の変数は、関数から見た場合にはグローバル変数となり、参照可能。
  • 変数名もブロック内に限定されるので、他の関数内で同じ変数名が使用されていても衝突しない。
  • ただし、グローバルな変数名と同じ名前にした場合、関数内ではローカル変数が優先される。
  • グローバル変数はファイルスコープ。

配列

  • 配列にも型の概念がある。すなわち、同じ型のデータしか入れれない。異なる型のデータを入れるのは構造体。
  • 配列は、基本の型とは異なるユーザ定義型の一つ。
  • 配列を宣言するには、型・配列名・サイズ(要素数)をあらかじめ定義する。int ary[10]
  • 初期値を与えることもできる。int ary[] = { 1, 2, 3 };この場合はサイズの指定は不要(自動的に初期値の個数がサイズとなる)
  • 配列の要素へのアクセスは、インデックスを使う。x = ary[0];
  • 配列全体のメモリ量を求めるには 配列に対して sizeof を使う。sum = sizeof ary;
  • 配列要素の単体メモリ量を求めるには 配列のインデックス0 に対して sizeof を使う。each = sizeof ary[0]
  • つまり、全体メモリ量を単体メモリで割ると、配列の要素数を求めることができる。num = sizeof ary / sizeof ary[0];forループの上限値等に設定する場合はこれをつかうとよろし。
  • forループの脱出にもbreakが使える。
  • ループの脱出にはbreakを使う。
  • 配列はあらかじめサイズを指定しておかないといけないため、途中でサイズが大きくなった場合に対応できない。こういう場合は動的メモリが必要となる。

ふぅ、たくさん進んだ分、アウトプットも多くなってしまいましたが、これで教科書の残り3分の1まで来ました。やっと終わりが見えてきたのでがんばります。次はいよいよ「ポインタ」です!

このページの先頭へ