thisdesuのブログ

C言語はじめました

にほんブログ村 IT技術ブログ C/C++へ

可変引数

関数を使ってると引数の個数を可変にしたいなーって思う時があります。これまた難しいのですが、よく使う関数printf系が可変なので身近で使っているから簡単に出来そうなんて思う今日この頃。
可変長引数はコンパイラなど環境依存と言いますがお行儀依存しますので、今回のサンプルが使えないのは覚悟です。はい。

関数定義は、[...]と点を3つほど書けば以後引数は可変ですって事です。

int _myprint(char *name, ...);


取り出し方

va_list  : 可変引数の構造体
va_start : 開始位置を構造体へ設定
va_arg   : 値を取り出し
va_end   : 終了宣言


■サンプルコード

#include <stdio.h>
#include <stdarg.h>

int _myprint(char *name,int cnt, ...) {
    va_list ap;
    int i;
    int val;

    va_start(ap, cnt);

    for(i=0;i<cnt;i++) {
        val = va_arg(ap,int);
        printf("<%s>[%d]=%d\n",name,i,val);
    }

    va_end(ap);

    return(0);
}

int main() {
    _myprint("test1",3,11,22,33);
    _myprint("test2",5,1,22,333,4444,55555);

    return(0);
}


■結果
$ gcc main.c
$ ./a.out
<test1>[0]=11
<test1>[1]=22
<test1>[2]=33
<test2>[0]=1
<test2>[1]=22
<test2>[2]=333
<test2>[3]=4444
<test2>[4]=55555

配列の個数

静的に定義した構造体の配列の個数を後で知りたいときに便利です。
仕組み的にはアドレス辺りで説明しましたが、静的にとられた値は全体の領域がsizeofで取れるので、構造体1つのsizeofで割れば良いだけです。これはマクロなんかで用意しておく定番ですね。

■サンプルコード

#include <stdio.h>

// 構造体
typedef struct _user {
  int    no;
  char *name;
  char *post;
  char *addrress;
} USER;


int main() {
    USER user[] = {
         {1,  "first",    "001-0001",    "Tokyo/Japan"    },
         {2,  "second",    "002-0002",    "Osaka/Japan"    },
         {3,  "third",    "003-0003",    "Nagoya/Japan"    },
    };

    // 配列数の計算
    printf("count:%d\n", sizeof( user ) / sizeof( user[0] ) );

    return(0);
}


■結果

$ gcc main.c
$ ./a.out
count:3

実践編:リスト2

前回のリスト形式の拡張で、特定の値から前の値に手繰れるように拡張します。
前回は新たに作成した時に、一つ前の情報に次の情報を保存しましたが、今回は、作成した情報に1つ前の構造体のアドレスを保存しておく事により、特定の情報を見れれば、前の情報へ手繰れるっと言う仕組みです。

■サンプルコード

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>

// 特定の情報
typedef struct _item {
  int    val;
  struct _item *prev;
  struct _item *next;
} ITEM;

// 情報の追加用関数
ITEM *_add(ITEM *top,int val) {
    ITEM *item;

    // 領域作成
    item = (ITEM*)malloc(sizeof(ITEM));
    if (item==0x00){
        return 0x00;
    }
    // 初期化
    memset(item,0x00,sizeof(ITEM));

    // 値を設定
    item->val = val;
    item->prev = 0x00;
    item->next = 0x00;

    // 最初の情報なら終わり
    if (top == 0x00) {
        return item;
    }
    // 最後の情報までnext手繰る
    for( ; top != 0x00 && top->next != 0x00 ; top = top->next) ;

    // nextに作成したアドレスを設定
    top->next = item;
    // 作成したitemにprevのアドレスを設定
    item->prev = top;

    return item;
}

int main() {
    ITEM *top = 0x00;
    ITEM *tmp = 0x00;
    ITEM *lst = 0x00;

    // 値の追加、2回目以降は最初のアドレスを教えあげる
    if ((top = _add(0x00,123)) == 0x00) { printf("error\n");return(1); }
    if ((tmp = _add(top,456)) == 0x00)  { printf("error\n");return(1); }
    if ((tmp = _add(top,789)) == 0x00)  { printf("error\n");return(1); }

    // 最初の値からLISTで進んで(next)出力
    for( tmp=top; tmp != 0x00 ; tmp = tmp->next) {
         printf("val:%d\n",tmp->val);
         lst = tmp;
    }

    // 最後の値からLISTで戻って(prev)出力
    for( tmp=lst; tmp != 0x00 ; tmp = tmp->prev) {
         printf("val:%d\n",tmp->val);
    }


    // 最後はfreeしておく
    for( tmp=top; tmp != 0x00 ; tmp = tmp->next) {
         free(tmp);
    }

    return(0);
}


■結果

$ gcc main.c
$ ./a.out
val:123
val:456
val:789
val:789
val:456
val:123

実践編:リスト

そろそろC言語の言語から良く使う手法等のステップアップして実践編へ進みます。(と言うより言語ネタは尽きてきた)
特定の値をリスト形式にしたい場合に、特定の値の中に次の値に辿れる様にする事があります。この特定の値とは構造体で好きな形として定義してやり、この構造体の中に同じ構造体のポインタを保存出来るようにしてあげます。一つの情報を作成したら、次が出来た時点で次の構造体のアドレスを保存してあげる事により、特定の情報を見れれば、次の情報へ手繰れるっと言う仕組みです。

■サンプルコード

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>

// 特定の情報
typedef struct _item {
  int    val;
  struct _item *next;
} ITEM;

// 情報の追加用関数
ITEM *_add(ITEM *top,int val) {
    ITEM *item;

    // 領域作成
    item = (ITEM*)malloc(sizeof(ITEM));
    if (item==0x00){
        return 0x00;
    }
    // 初期化
    memset(item,0x00,sizeof(ITEM));

    // 値を設定
    item->val = val;
    item->next = 0x00;

    // 最初の情報なら終わり
    if (top == 0x00) {
        return item;
    }
    // 最後の情報までnext手繰る
    for( ; top != 0x00 && top->next != 0x00 ; top = top->next) ;

    // nextに作成したアドレスを設定
    top->next = item;

    return item;
}

int main() {
    ITEM *top = 0x00;
    ITEM *tmp = 0x00;

    // 値の追加、2回目以降は最初のアドレスを教えあげる
    if ((top = _add(0x00,123)) == 0x00) { printf("error\n");return(1); }
    if ((tmp = _add(top,456)) == 0x00)  { printf("error\n");return(1); }
    if ((tmp = _add(top,789)) == 0x00)  { printf("error\n");return(1); }

    // 最初の値からLISTで出力
    for( tmp=top; tmp != 0x00 ; tmp = tmp->next) {
         printf("val:%d\n",tmp->val);
    }

    // 最後はfreeしておく
    for( tmp=top; tmp != 0x00 ; tmp = tmp->next) {
         free(tmp);
    }

    return(0);
}


■結果

$ gcc main.c
$ ./a.out
val:123
val:456
val:789

関数ポインタ

状態により実行する関数を変えたい!動的に変えたい!などの場合に、実行する箇所で関数ポインタを元に実態の関数を呼ぶようにします。
qsortのブログでもサラッと利用しててスルッと説明を簡略しましたが、目的は同じです。

例:四則演算の関数をポインタを変えて実行します。

#include <stdio.h>

int _plus(int a, int b) {
  return (a+b);
}

int _minus(int a, int b) {
  return (a-b);
}

int _multi(int a, int b) {
  return (a*b);
}

int _divi(int a, int b) {
  return (a/b);
}

typedef struct _cal{
  int no;
  char name[6];
  int (*func)();
} CAL;

int main() {
  int i;
  CAL cal[] = { {10,"+",&_plus},{20,"-",&_minus},{30,"*",&_multi},{40,"/",&_divi} };

  for(i=0; i<4; i++) {
    printf("(%d)(%d) 10 [%s] 2 = %d\n",i,cal[i].no,cal[i].name,cal[i].func(10,2));
  }
}


実行結果

$ gcc -g main.c
$ ./a.out
(0)(10) 10 [+] 2 = 12
(1)(20) 10 [-] 2 = 8
(2)(30) 10 [*] 2 = 20
(3)(40) 10 [/] 2 = 5

ビットシフト

名前のとおりビット単位で全bitをシフトします。右(小さく)へシフトさせる場合に[>>]を、左(大きく)へシフトさせる場合には[<<]を指定します。
ビット幅を超えたbitはなくなります。逆に出現したら(逆側から出てきたbit)0です。

#include <stdio.h>

void _print(unsigned char val) {
  unsigned char msk = 128; // 10000000
  for( ; msk; msk >>= 1) {
    if ( val & msk )  putchar('1');
    else          putchar('0');
  }
}

int main() {
    unsigned short i=1;

    i = 3;
    printf("%2d:",i);_print(i);printf("\n");

    // 左へ1bitシフト
    i <<= 1;
    printf("%2d:",i);_print(i);printf("\n");

    // 左へ1bitシフト
    i <<= 1;
    printf("%2d:",i);_print(i);printf("\n");

    // 右へ2bitシフト
    i >>= 2;
    printf("%2d:",i);_print(i);printf("\n");

    // シフトして1が1つ消えます。
    i >>= 1;
    printf("%2d:",i);_print(i);printf("\n");

    // シフトして全部消えます。
    i >>= 1;
    printf("%2d:",i);_print(i);printf("\n");

    // 0からシフトしても0が出てくるだけで、0です
    i <<= 1;
    printf("%2d:",i);_print(i);printf("\n");
}


結果

$ gcc main.c
$ ./a.out
 3:00000011
 6:00000110
12:00001100
 3:00000011
 1:00000001
 0:00000000
 0:00000000

ビット計算2

ビットを扱うときに、0と1で表示したいなーと誰もが思うはず。たぶん。きっと。かな。。
ここで良くググると見つかる、2進数を表示する関数を書いてみました。

関数[_print]です。1byte(8bit)限定ですです。
予め8bit目が1の値(msk)を用意して(10進数で128)。指定値(val)とmskと論理積(AND)すると、1になるか0になるかを判断して結果をprintfします。
そしてmskの値を1bitつづシフト(>>)して全ビット(8bit)分繰り返します。

#include <stdio.h>

void _print(unsigned char val) {
  unsigned char msk = 128; // 10000000
  for( ; msk; msk >>= 1) {
    if ( val & msk )  putchar('1');
    else          putchar('0');
  }
}

int main() {
    unsigned short i;
    for(i=0;i<32;i++){
    printf("%2d:",i);
    _print(i);printf("\n");
    }
}


実行

$ gcc main.c
$ ./a.out
 0:00000000
 1:00000001
 2:00000010
 3:00000011
 4:00000100
 5:00000101
 6:00000110
 7:00000111
 8:00001000
 9:00001001
10:00001010
11:00001011
12:00001100
13:00001101
14:00001110
15:00001111
16:00010000
17:00010001
18:00010010
19:00010011
20:00010100
21:00010101
22:00010110
23:00010111
24:00011000
25:00011001
26:00011010
27:00011011
28:00011100
29:00011101
30:00011110
31:00011111