可変引数
関数を使ってると引数の個数を可変にしたいなーって思う時があります。これまた難しいのですが、よく使う関数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