第 6 章 C 言語入門
6.15 配列とポインタ(その2・関数とポインタ)
6.15.4 関数へのポインタ
Cでは,変数へのポインタだけではなく,関数へのポインタも利用できる. 関数識別子は関数が定義され ているテキストセグメント内の関数の先頭アドレスを持つオブジェクトと考えれば,関数のポインタは容 易に理解できる.
int型の値を返す関数へのポインタを表す変数は, int (*p)() ;
と定義する. ここで,int (*p)() という書き方が本質的である. 関数を表す演算子()と間接演算子*の 優先順位は,()の方が高く,int *p()とするとintへのポインタを返す関数と認識される.
Example 6.15.7 この例は,sumという関数を,ポインタpに代入している.
int sum(int a, int b) {
return a+b ; }
main() {
int a, b ; int (*p)() ; a = 1 ; b = 2 ; p = su m ;
printf("%d\n",p(a,b)) ; }
関数へのポインタは以下のような場合に便利に利用できる.
Example 6.15.8 次のプログラムはコマンドライン引数から1+2などという入力を取り,その計算結果を 出力するものである.
#include <stdio.h>
#include <stdlib.h>
extern int calc(const char *) ; extern int _chrstr(char, char *) ; extern int _add(int,int) ;
extern int _sub(int,int) ; extern int _mul(int,int) ; extern int _div(int,int) ; extern int _mod(int,int) ; char op[]="+-*/%" ;
int main(int argc, char **argv) {
if (!(argc-1)) return 0 ;
printf("%d\n",calc(*(argv+1))) ; }
int calc(const char *arg) {
char p[100], *q, *r ;
char a[100], b[100], operator ; int i = 0, j, int_a, int_b ; int (*func)() ;
82詳しくは,getchar,strtod,strtolなどのオンラインマニュアルを見よ.
Id: C7-1.tex,v 1.6 2001-03-21 21:23:27+09 naito Exp
while(*(p+i) = *(arg+i)) i += 1 ; q = p ; while(_chrstr(*q,op)) q++ ; j = q-p ; i = 0 ;
while((*(a+i) = *(p+i)) && (i < j)) i += 1 ; *(a+i) = 0 ; operator = *q ; q++ ; r = q ;
while(_chrstr(*q,op)) q++ ; i = 0 ;
while((*(b+i) = *(r+i)) && (i < j)) i += 1 ; *(r+i) = 0 ; int_a = atoi(a) ; int_b = atoi(b) ;
switch (operator) {
case ’+’: func = _add ; break ; case ’-’: func = _sub ; break ; case ’*’: func = _mul ; break ; case ’/’: func = _div ; break ; case ’%’: func = _mod ; break ; default: func = NULL ;
}
return func(int_a,int_b) ; }
int _chrstr(char c, char *p) {
while(*p && *p != c) p++ ; if (*p) return 0 ;
else return 1 ; }
int _add(int a, int b) { return a+b ; } int _sub(int a, int b) { return a-b ; } int _mul(int a, int b) { return a*b ; } int _div(int a, int b) { return a/b ; } int _mod(int a, int b) { return a%b ; }
また,このプログラム中の文字列解析部分は,strspn関数などの文字列解析関数で代用する方が容易であ る. (ここでは,ポインタの解説のため,わざわざ上のように書いてある.)上のプログラム中のcalc関数 をそのように書き換えると,以下のようになる.
int calc(const char *arg) {
char p[100], operator ; int int_a, int_b ; int (*func)() ; size_t len ;
int_a = atoi(strncpy(p,arg,len=strspn(arg,digit))) ; operator = *(arg+len) ;
int_b = atoi(strncpy(p,arg+len+1,strspn(arg+len+1,digit))) ; switch (operator) {
case ’+’:
func = _add ; break ; case ’-’:
func = _sub ; break ; case ’*’:
func = _mul ; break ; case ’/’:
func = _div ; break ; case ’%’:
func = _mod ; break ; default:
func = NULL ; }
return func(int_a,int_b) ; }
Id: C7-1.tex,v 1.6 2001-03-21 21:23:27+09 naito Exp
関数へのポインタが本質的な役割を果たすものとして, 次のような例がある. 例えば,int型の配列の中 で, 適当な順序に対して最も大きなものを求める関数を考えてみよう.
Example 6.15.9 int型の要素数10個の配列の中で通常の順序で最も大きな数を求める.
#include <stdio.h>
int get_max(int *) ;
int main(int argc, char **argv) {
int a[10]={2,1,3,7,6,5,0,9,4,8} ; int result ;
result = get_max(a) ; printf("%d\n",result) ; return 0 ;
}
int get_max(int *a) {
int i ; int max ; max = a[0] ; for(i=0;i<10;i++)
max = (max < a[i]) ? a[i] : max ; return max ;
}
これでは,関数get max内に要素数10が書かれている.
Example 6.15.10 int型の要素数10個の配列の中で通常の順序で最も大きな数を求める. (改良版1)
#include <stdio.h>
int get_max(int *, unsigned int) ; int main(int argc, char **argv) {
int a[10]={2,1,3,7,6,5,0,9,4,8} ; int result ;
result = get_max(a,10) ; printf("%d\n",result) ; return 0 ;
}
int get_max(int *a, unsigned int nel) {
int i ; int max ; max = a[0] ; for(i=0;i<nel;i++)
max = (max < a[i]) ? a[i] : max ; return max ;
}
次に整数要素の順序として,通常と逆順の順序をいれたらどうなるだろうか?最も単純なものは, int get_min(int *a, unsigned int nel)
{
int i ; int min ; min = a[0] ; for(i=0;i<nel;i++)
Id: C7-1.tex,v 1.6 2001-03-21 21:23:27+09 naito Exp
min = (min > a[i]) ? a[i] : min ; return min ;
}
と定義してしまう方法である. しかし,これでは2つの順序の入れ方に対して,別の関数を用意しなければ ならない. そのために,次のような例を考えてみよう.
Example 6.15.11 この例では, 関数へのポインタを利用して,順序を与える関数をget max内から独立 させている.
#include <stdio.h>
int get_max(int *, unsigned int, int (*)(int, int)) ; int max_func(int, int) ;
int main(int argc, char **argv) {
int a[10]={2,1,3,7,6,5,0,9,4,8} ; int result ;
result = get_max(a,10,max_func) ; printf("%d\n",result) ;
return 0 ; }
int get_max(int *a, unsigned int nel, int (*func)(int, int)) {
int i ; int max ; max = a[0] ;
for(i=0;i<nel;i++) {
if (func(a[i],max) > 0) max = a[i] ; }
return max ; }
int max_func(int a, int b) {
return a-b ; }
この中で, get maxの第3引数は,2つのint型の引数をとり,int型を返す関数である. get maxは第3 引数の関数func に対して,func(a,b)>0 がa >bとなる順序によって, 最も大きな値を返すことにな
る. get maxの第3引数の関数を取り替えることにより,どのような順序を入れることも自由になる.
この手法は, 配列の与えられた順序による並び替えを行うqsort関数で利用されている, Cにおける極 めて重要な手法の一つである.
6.15.5 ポインタの演算(比較)
2つのポインタは次のいずれかの条件が満たされるとき,それを比較することが出来る.
• それが指し示すものの型が同一. ただし const などの修飾子を除いて考える. この時の結果は, 2つ のオブジェクトのメモリ内での相対位置によって決定される.
• 同じ構造体のメンバーを指すとき. この時の結果は,メンバーがあとに書かれたものを指すポインタの 方が,相対位置が高いとする.
• 同じ配列の要素を指すとき. この時の結果は配列の添字と同一の順序となる.
これ以外のポインタの比較を行うと結果は不定になる.
int a[4] ; int *p, *q ;
p = &a[0] ; q = &q[2] ;
Id: C7-1.tex,v 1.6 2001-03-21 21:23:27+09 naito Exp
とすると,p<qが成り立つ.
Example 6.15.12 以下のプログラムでは,aはbssセグメント,bはstackセグメント,cはdataセグ メントにあり,関数main はtextセグメントにある.
#include <stdio.h>
int a ; int c = 1 ;
int main(int argc, char **argv) {
int b ;
int *pa, *pb, *pc ; int (*pf)() ;
pa = &a ; pb = &b ; pc = &c ; pf = main ; printf("pa = %p\n", pa) ;
printf("pb = %p\n", pb) ; printf("pc = %p\n", pc) ; printf("pf = %p\n", pf) ;
if (pa < pb) printf("pa < pb\n") ; else printf("pa > pb\n") ;
if (pb < pc) printf("pb < pc\n") ; else printf("pb > pc\n") ;
if (pc < pa) printf("pc < pa\n") ; else printf("pc > pa\n") ;
return 0 ; }