次に、2つの要素の内容を交換する関数 swap の実装を見てみましょう。整数型 int を例にします:
void swap(int* i1,int* i2){
int temp;
temp = *i1;
*i1 = *i2;
*i2 = temp;
}
char 型の2つを交換したい場合、パラメータ型が char の関数を書き直す必要があります。型なしポインタをパラメータとして使えるでしょうか?以下のように変更してみましょう:
void swap(void *vp1,void *vp2){
void temp = *vp1;
*vp1 = *vp2;
*vp2 = temp;
}
このコードは誤りで、コンパイルを通りません。まず、変数はvoid型なしとして宣言できません。また、この関数に渡されるパラメータがどの型なのか不明なため、型を確定して宣言できません。同時に、*型なしポインタに対して * を使用できません。システムはこのアドレスが指すオブジェクトのサイズ情報を持っていないからです。コンパイル段階で、コンパイラはこの関数に渡されるパラメータの型を知ることができません。ここでジェネリック関数を実装するには、呼び出し側で交換対象のオブジェクトのアドレス空間サイズ size を渡し、ヘッダファイル string.h で定義されている memcpy() 関数を利用して実装します。変更は以下の通りです:
void swap(void *vp1,void *vp2,int size){
char buffer[size];//注意此处gcc编译器是允许这样声明的
memcpy(buffer,vp1,size);
memcpy(vp1,vp2,size);
memcpy(vp2,buffer,size);
}
この関数を呼び出すときは、次のように呼び出すことができます(他の型の x、y にも同様に適用できます):
int x = 27,y = 2; swap(&x,&y,sizeof(int));
次に、別の機能を持つ関数を見てみましょう:
int lsearch(int key,int array[],int size){
for(int i = 0;i < size; ++i)
if(array[i] == key)
return i;
return -1;
}
この関数は配列 array 内で key 要素を探し、見つかればそのインデックスを返し、見つからなければ -1 を返します。
上記と同様に、ジェネリック関数も実装できます:
void* lsearch(void* key, void *base, int n, int elemSize){
for(int i = 0;i < n; ++i){
void *elemAddr = (char *)base+i*elemSize;
if(memcmp(key, elemAddr, elemSize) == 0)
return elemAddr;
}
return NULL;
}
コードの3行目:配列の先頭アドレスを char 型を指すポインタに強制変換しています。char 型のサイズが1バイトである特性を利用して、elemAddr がこの「ジェネリック」配列の i-1 番目の要素の先頭アドレスを指すようにしています。先ほど述べたように、このとき渡されたデータがどの型なのか分からず、システムはこの配列の1要素の長さや、次の要素に移動するために必要なバイト数を決定できないため、char を指すポインタに強制変換し、さらにパラメータで渡された要素サイズ情報と累積カウンタ i の積、すなわちオフセットアドレスを加算することで、この配列の i-1 番目の要素の先頭アドレスを得られます。これにより、渡されるパラメータがどの型を指すポインタであっても、正しい要素を指すポインタを取得でき、ジェネリックプログラミングを実現できます。
関数 memcmp() のプロトタイプ:int memcmp(void *dest, const void *src, int n)。長さ n で、先頭アドレスがそれぞれ dest、src である2つのアドレス空間の内容を比較します。
この関数は配列 base 内で key 要素を探し、見つかればそのアドレス情報を返し、見つからなければ NULL を返します。
関数ポインタを使用すれば、その動作のジェネリック化を実現できます:
void *lsearch(void *key,void *base,int n,int elemSize,int(*cmpfn)(void*,void*,int)){
for(int i = 0;i < n; ++i){
void *elemAddr = (char *)base+i*elemSize;
if(cmpfn(key,elemAddr,elemSize) == 0)
return elemAddr;
}
return NULL;
}
呼び出す関数をもう1つ定義します:
int intCmp(void* elem1,void* elem2){
int* ip1 = elem1;
int* ip2 = elem2;
return *ip1-*ip2;
}
次のような呼び出しを見てみましょう:
int array[] = {1,2,3,4,5,6};
int size = 6;
int number = 3;
int *found = lsearch(&number,array,size,sizeof(int),intCmp);
if(found == NULL)
printf("NO\n");
else
printf("YES\n");
C言語でもある程度のジェネリックプログラミングは実装できますが、これは安全ではなく、システムは限られたチェックしか行いません。プログラミングの際には十分に注意する必要があります。
原文アドレス:https://www.cnblogs.com/wuyudong/p/c-general-function1.html