/* 1 */ /* Program No.5-4 */ /* 2 */ #include <stdio.h> /* 3 */ /* 4 */ main() /* 5 */ { /* 6 */ int x = 100, v[10]; /* 7 */ int *pt1, *pt2; /* 8 */ /* 9 */ pt1 = &x; /* 10 */ *pt1 += 1; /* 11 */ /* 12 */ printf(" x = %d\n", x); /* 13 */ /* 14 */ pt2 = &v[0]; /* 15 */ *pt2 = *pt1; /* 16 */ /* 17 */ printf(" v[0] = %d\n", v[0]); /* 18 */ /* 19 */ }
6: | int型変数xとint型配列変数v(0から9までの10個の要素を格納可能)の宣言である。xは100で初期化されている。 |
7: | 変数名の前に*を付けることにより、pt1とpt2がポインタ型の変数であることを宣言する。先頭のintは、pt1とpt2がint型のデータが格納されている場所を指すことを意味するものである。intの代わりにfloatやcharにすれば、float型のデータやchar型のデータが格納されている場所を指すことを意味する。 |
9: | &変数名で変数に割り当てられたメモリ内の番地を表現する。この場 合、変数xに割当られた番地そのものが、ポインタ変数pt1に格納される 。 |
10: | 前節で学んだ代入演算子もポインタ変数に使えることを示した例である。この文を書き換えると、*pt1 = *pt1 + 1のようになる。*変数名により、ポインタ変数が指している場所に格納されている内容を意味することになり、この場合、pt1が指している場所(つまり変数x)の内容に1を加え、その結果をpt1が指している場所に格納する。10:の出力結果をみるとわかるように、9,10:の2文で x+=1 をxという表札を使わないで番地を直接利用して実行したことになる。よって10:の結果は100+1で101になる。 |
14: | 配列変数v[0]に割り当てられたメモリ内の番地をpt2に代入する。 |
15: | pt1の指している場所(この場合x)の内容をpt2の指している場所に格納する。17:の出力結果からもわかるように、14,15:でv[0]=xを行なったことになり、結果としてxの値101が出力される。 |
ここで、
(1) pt2=pt1;
(2) *pt2=*pt1;
(3) pt2=&pt1;
の違いを明確に理解しておこう。(1)の場合は、番地そのものをコピーしているのでpt1とpt2が同じ格納場所を指すことになる。(2)の場合は、pt1とpt2がそれぞれ指している場所に格納されているデータの値が等しい。
(3)の場合は、pt1に割り当てられているメモリ内の番地をpt2に代入することになり、プログラム5−4ではpt2はint型のデータが格納されている番地を格納するように7:で宣言されているため、ワーニング(アドレスはint型であるため)となってしまう。(3)のpt2はint型のデータを指すポインタのポインタであるため、 int **pt2; のように宣言してやればよいことになる。
/* 1 */ /* Program No.5-5 */ /* 2 */ #include<stdio.h> /* 3 */ /* 4 */ void swapf(float *x, float *y); /* 5 */ /* 6 */ main() /* 7 */ { /* 8 */ float a, b; /* 9 */ /* 10 */ scanf("%f %f", &a, &b); /* 11 */ printf(" a = %f b = %f\n", a, b); /* 12 */ swapf( &a, &b ); /* 13 */ printf(" a = %f b = %f\n", a, b); /* 14 */ } /* 15 */ /* 16 */ void swapf(float *x, float *y) /* 17 */ { /* 18 */ float tmp; /* 19 */ /* 20 */ tmp = *x; /* 21 */ *x = *y; /* 22 */ *y = tmp; /* 23 */ }
123 987 a = 123.000000 b = 987.000000 a = 987.000000 b = 123.000000
8: | swap関数の呼び出し文である。&a、&bつまりaとbの番地を 引数として関数swapへ渡すことになる。これは、例4−10で解説した 番地呼び出しにあたる。 |
16: | 値を戻さない関数(void型)のヘッダ部である。*x,*yはfloat型のデータを呼び出すポインタ引数として宣言されている。x,yがポインタ型の引数であるため、番地を直接操作することにより、値を戻すことができるようになる。 |
20: | 変数tmpにxの指す内容のデータをコピーする。 |
21: | 変数yの指している内容をxの指している場所にコピーする。 |
22: | tmpの値をyの指している場所へコピーする。 |
/* 1 */ /* Program No. 5-6-1 */ /* 2 */ #include<stdio.h> /* 3 */ /* 4 */ main() /* 5 */ { /* 6 */ int v[10] = {0,1,2,3,4,5,6,7,8,9}, i; /* 7 */ /* 8 */ for(i = 0; i <= 9; i++) /* 9 */ printf("%d ", v[i]); /* 10 */ printf("\n"); /* 11 */ }
0 1 2 3 4 5 6 7 8 9
/* 1 */ /* Program No. 5-6-2 Pointer Version */ /* 2 */ #include<stdio.h> /* 3 */ /* 4 */ main() /* 5 */ { /* 6 */ int v[10] = {0,1,2,3,4,5,6,7,8,9}, i, *p; /* 7 */ /* 8 */ p = &v[0]; /* 9 */ for(i = 0; i <= 9; p++, i++) /* 10 */ printf("%d ", *p); /* 11 */ printf("\n"); /* 12 */ }
0 1 2 3 4 5 6 7 8 9
8: | 配列vのメモリ内の領域の先頭番地をpに代入する。 |
9: | iが0から9までつまり10回 10:を繰り返す。for文の第3パラメータp++は、ポインタ変数をインクリメント(1加えることを)している。この場合p++は、1番地先を指す。正しくは、1つ先の領域を指すことになる。 例えば、初めのpはv[0]を指している。p++を1度実行されると、pはv[1]wp指し、さらにp++を実行するとpはv[2]を指すことになる。 |
10: | pの指す内容つまりv[i]の内容を出力する。 |
/* 1 */ /* Program No. 5-6-2' Pointer Version */ /* 2 */ #include<stdio.h> /* 3 */ /* 4 */ main() /* 5 */ { /* 6 */ int v[10] = {0,1,2,3,4,5,6,7,8,9}, i, *p; /* 7 */ /* 8 */ p = &v[0]; /* 9 */ for(i = 0; i <= 9; i++) /* 10 */ printf("%d ", *(p+i)); /* 11 */ printf("\n"); /* 12 */ }
0 1 2 3 4 5 6 7 8 9
iが0の時、ポインタはpでv[0]の番地を指す。
iが1の時、ポインタはp++でv[1]の番地を指す。
iが2の時、ポインタは(p++)++でv[2]の番地を指す。
iが3の時、ポインタは((p++)++)++でv[3]の番地を指す。
: : :
: : :
よって、プログラム5−6−1,5−6−2さらに5−6−2’のように異なった命令でも同じ実行結果が得られることがわっかたであろう。これらの関係を次の図で示す。
プログラム5−4のpt1はint型のポインタ変数であるため、int型の領域分だけ先をさすことになる。以下のようなプログラムの一部について考えてみよう。
int v_i[10], *pi; float v_f[10], *Pf; pi = &v_i[0]; pf = &v_f[0]; ++pi; ++pf;piとpfはそれぞれint型,float型のポインタ変数である。
計算機の環境によって異なるが、int型の場合には16bit,float型の場合には32bitの領域を確保し、番地の振り方は8bitで1番地であると仮定しよう。このとき、上記プログラムでは、
1000番地 v_i[0], 2000番地 v_f[0] 1002番地 v_i[1], 2004番地 v_f[1] : : : : 1018番地 v_i[9], 2036番地 v_f[9]であるとする。float型の場合は、int型の倍の領域を確保しているため番地の割り付けも2倍になっていることに注意しよう。このような条件のもとで上記プログラムを実行すると、piはv_i[1]の番地つまり1002,pfはv_f[1]の番地つまり1004が格納される。同じ++演算を行なっても結果的には格納領域のバイト数(計算機に依存するが、例えばchar型は1バイト,int型は2バイト,float型は4バイト)分、加えられる。これは、++演算子はポインタ変数の型(int型のデータを指すのか?float型データを指すのか? など)を考慮し、次の番地を指すからである。
以上の理由から、ポインタ変数はただ番地を格納するだけのものなのに、格納先データの型(char型を指すのか? int型を指すのか? float型を指すのか?)の指定が必要なのである。
char string1[80]; のように宣言し、 string1[0] = ’S’; string1[1] = ’a’; string1[2] = ’y’; string1[3] = ’u’; string1[4] = ’r’; string1[5] = ’i’; string1[6] = ’¥0’;のように1つの配列要素に1文字を格納し、その上で操作していた。
ポインタ変数を用いると同じ操作ができる。プログラム5−7で示そう。
/* 1 */ /* Program No. 5-7 */ /* 2 */ /* 3 */ #include<stdio.h> /* 4 */ /* 5 */ main() /* 6 */ { /* 7 */ char *string1 = "Sayuri & Masahiko"; /* 8 */ char string2[] = "Zoh and Zoh'"; /* 9 */ int i; /* 10 */ /* 11 */ while(*string1 != '\0') /* 12 */ { /* 13 */ printf("%c", *string1); /* 14 */ string1++; /* 15 */ } /* 16 */ printf("\n"); /* 17 */ /* 18 */ for(i = 0; string2[i] != '\0'; i++ ) /* 19 */ printf("%c", string2[i]); /* 20 */ printf("\n"); /* 21 */ }
Sayuri & Masahiko Zoh and Zoh'
7: | string1を文字のポインタとして宣言し、初期値として文字列 "Sayuri & Masahiko" が格納されている先頭番地が格納されている。いままで文字の代入にはシングルクオーテーション’’を用いていたが、文字列の代入にはダブルクオーテーション””を用いる。””を用いると文字列の最後を示す’¥0’記号が自動的に挿入される。 |
8: | string2を文字の配列として宣言し、初期値として文字列 "Zoh and Zoh'" が格納されている。ここでも文字列の最後に’¥0’が挿入されている。 |
11〜16: | string1が指している文字列の内容を出力する。 |
11: | ポインタstring1の指す内容が’¥0’になるまで10,11を繰り返す命令である。 |
13: | string1の指す内容の文字1つを出力している。 |
14: | ポインタを1つ先の番地に進めている。 |
18〜20: | 文字配列string2の出力をする。 |
string1はポインタであるから文字列が格納されている先頭番地のみを持っている。そのため文字列の長さには関係なく文字列を扱うことができる。しかし、文字列の内容を換えようとするとメモリ内の状況が不定なので保障されないため注意が必要である。このように文字列のポインタは、あらかじめ作られた文字列の先頭番地を指すような使い方をするべきである。また、string1はポインタ変数であるから、格納される番地はstring1++のように変化させてよい。
string2は、初期化された文字列と¥0が格納された領域が確保された先頭番地が格納されている。この場合11文字(バイト)分が保証され、その中は自由に書き換えても問題はない。string2はこの11バイトの先頭番地が格納されていることと同等であるが、この番地は絶対変わることがない。
文字列,配列,ポインタの関係がおわかりいただけましたか? それでは、演習をやってみましょう。
また、上で出した文字列の結合に関する演習問題は、ポインタの指す記憶領域の確保を行なわなければならず、前回の演習問題としては不適当でした。記憶領域の確保については、もうしばらく先に行なう予定です。申し訳ございませんでした。
/* 1 */ /* program 5-8-1 (pointer ver.) */ /* 2 */ #include <stdio.h> /* 3 */ main() /* 4 */ { /* 5 */ float heikin(int *d, int n); /* 6 */ /* 7 */ int data[100], n; /* 8 */ /* 9 */ for (n = 0; n < 100; n++) /* 10 */ data[n] = 0; /* 11 */ n = -1; /* 12 */ do{ /* 13 */ n++; /* 14 */ scanf("%d", &data[n]); /* 15 */ }while(data[n] > 0); /* 16 */ printf("Heikin =%f\n", heikin(data, n)); /* 17 */ } /* 18 */ /* 19 */ float heikin(int *pdata, int n) /* 20 */ { /* 21 */ float sum = 0.0; /* 22 */ int i; /* 23 */ /* 24 */ for(i = 0; i < n; i++) /* 25 */ sum += (float)*pdata++; /* 26 */ /* 27 */ return(sum / (float)n); /* 28 */ }
9〜10: | int型の配列要素100個をすべて0で初期化する。データの初期化を必ず行なうことを奬める。初期化は、プログラマが利用する領域の確保の再確認をするとともに、万一に備えてデータをクリアしておくとよい。C言語では、配列の添字のチェックつまり配列dataの領域を操作している時、配列の添字を越えた領域外を参照した場合、逆に領域外のデータを参照している時にdataの領域を犯すような操作をした場合、エラーメッセージを出してはくれない。このような領域のチェックはプログラマの責任において行なわれなければならない。そのため、何が起きてもプログラムが暴走しないようにするための1つの手段として、データの初期化は大切である。 |
11〜15: | 0以上の数値のみをデータとして扱うこととし、負の数値が入力されるまで、データの読み込みをする。つまり負の数は入力データの終了を示すことになる。 |
14: | scanfのパラメータである&data[n]は、入力されたデータの格納番地を示している。この命令は、dataという配列の先頭番地を指すポインタからn個先の番地(ここでは&data[n]と書かれている)に整数型として読み込んだデータ(%d指令で整数型として読み込まれる)を格納することを意味している。このことは、 ポインタ=配列 と言われる1つの理由でもある。後の解説(No.14のつづき)で詳しく述べている。 |
16: | データの格納されている配列の先頭番地とデータの個数nを渡して、データの平均値がfloat型で戻される。 printf("Heikin =%f\n", heikin( data, n ) ); は、printf("Heikin =%f\n", heikin( &data[0], n ) ); と記述してもよい。配列を関数引数として用いる場合、番地呼び出ししかできず、この場合もdataの格納されている先頭番地を渡している。データの個数nは、値を渡しているので値呼び出しになっている。 |
19: | pdataをint型データが格納されているポインタ型として宣言し、配列dataの先頭番地を受け取る。nは値そのもの受け取っている。 |
21: | 総和を求めるための変数sumを宣言し、同時に0.0で初期化する。 |
24〜25: | n個のデータの総和をsumに求める。この2行は、
for( i=0; i<n; i++ ) { sum = sum + (float)*pdata; pdata++; }と記述したのと同等である。*dataでdataが指すアドレスの内容を意味し、それを(floae)でキャストしている。つまり、int型のデータを一時的にfloat型に変換し代入を行なっている。これは、sumはfloat型であるため、同一の型どうしの演算を行なうためにキャストしている。 data++は、現在dataが指すアドレスを1つ先に進める。これにより配列のデータを次々に参照することを可能にしている。演算の実行の優先順位により、上記の命令が25:一文で書ける。 |
27: | 平均=総和/データの数 であるから、sumをnで割った値を関数heikinの値としてmainに戻している。nで割る前にnを一時的にfloat型にキャスト(float)して同一型どうしの演算を実現している。 |
/* 1 */ /* program 5-8-2 (array ver.) */ /* 2 */ #include <stdio.h> /* 3 */ main() /* 4 */ { /* 5 */ float heikin(int *d, int n); /* 6 */ /* 7 */ int data[100], n; /* 8 */ /* 9 */ for (n = 0; n < 100; n++) /* 10 */ data[n] = 0; /* 11 */ n = -1; /* 12 */ do{ /* 13 */ n++; /* 14 */ scanf("%d", &data[n]); /* 15 */ }while(data[n] > 0); /* 16 */ printf("Heikin =%f\n", heikin(data, n)); /* 17 */ } /* 18 */ /* 19 */ float heikin(int adata[], int n) /* 20 */ { /* 21 */ int i; /* 22 */ float sum = 0.0; /* 23 */ /* 24 */ for(i = 0; i < n; i++) /* 25 */ sum += (float)adata[i]; /* 26 */ return( sum / (float)n ); /* 27 */ }
1 2 3 0 Heikin =2.000000
19: | mainの配列dataの先頭番地がadataに渡され、添字による操作を行なう宣言としてadata[ ]のように[ ]をつけて宣言する。 |
25: | 19での宣言により今回はデータの参照を、添字を使ってadata[i]とすることができる。ポインタで受け取った場合(プログラム5ー8ー1)は、*pdataで内容を参照し、pdata++で次のデータを指すようにしていた。配列として宣言した場合、添字のiを変化させることにより、adata[i]で内容を参照できる。プログラム5−8−1もプログラム5−8−2もともに配列dataの先頭番地が渡され、受ける方も番地を受け取っている。ただ、変数の宣言でポインタか配列の違いによってプログラムの記述方法が少し変わる以外は全く同じである。 |
/* 1 */ /* program 5-8-3 (pointer special ver.) */ /* 2 */ #include <stdio.h> /* 3 */ main() /* 4 */ { /* 5 */ float heikin(int *d); /* 6 */ /* 7 */ int data[100], n; /* 8 */ /* 9 */ for (n = 0; n < 100; n++) /* 10 */ data[n] = 0; /* 11 */ n = -1; /* 12 */ do{ /* 13 */ n++; /* 14 */ scanf("%d", &data[n]); /* 15 */ }while(data[n] > 0); /* 16 */ printf("Heikin =%f\n", heikin(data)); /* 17 */ } /* 18 */ /* 19 */ float heikin(int *pdata) /* 20 */ { /* 21 */ float sum = 0.0; /* 22 */ int n = 0; /* 23 */ /* 24 */ while(*pdata > 0) /* 25 */ { /* 26 */ sum += (float)*pdata++; /* 27 */ n++; /* 28 */ } /* 29 */ /* 30 */ return(sum / (float)n); /* 31 */ }
2 4 6 8 0 Heikin =5.000000
19: | 平均を求める関数に、より汎用性を持たせるためにデータの個数を関数内で調べ、個数nを関数の引数に用いない方法がプログラム5−8−3である。ここでは、ポインタとしてpdataを宣言しているので、計算の仕方や記述方法はプログラム5−8−1と同じである。プログラムの仕様である、非負の整数値をデータとして扱い負の数値が入力データの終わりを示すデリミッタであることを関数の内部では利用する。 |
24〜27: | sumに総和を求めると同時に、データの数をカウントしている。このとき、*pdata>0 が繰り返しの判定に用いられ、負の数値が出現するまで、繰り返される。 |
instring programming\0 | | midstr( instring, 4, 5, outstring ); ↓ outsting gramm\0
/* 1 */ /* program 5-9-1 (pointer ver.) */ /* 2 */ #include <stdio.h> /* 3 */ #include <string.h> /* 4 */ /* 5 */ main() /* 6 */ { /* 7 */ void midstr(char *instring, int m, int n, char *outstring ); /* 8 */ /* 9 */ char instring[80], outstring[80]; /* 10 */ /* 11 */ scanf("%s", instring); /* 12 */ midstr(instring, 4, 5, outstring); /* 13 */ printf(" %s :midstr(4,5) --> %s \n", instring, outstring); /* 14 */ } /* 15 */ /* 16 */ void midstr(char *pinstring, int m, int n, char *poutstring ) /* 17 */ { /* 18 */ int i; /* 19 */ /* 20 */ if( m<strlen(pinstring) ) /* 21 */ { /* 22 */ for(i = 0; i < m - 1; i++) /* 23 */ pinstring++; /* 24 */ for(i = 0; i < n && *pinstring != '\0'; i++) /* 25 */ *poutstring++ = *pinstring++; /* 26 */ } /* 27 */ *poutstring = '\0'; /* 28 */ }
PrOgRaMmInG PrOgRaMmInG :midstr(4,5) --> gRaMm
11: | 文字列を入力する。%sで文字列として読み込まれ、instringの指すメモリの位置から入力文字列を格納する。9の宣言からinstringはchar型の80個の要素を持つ配列である。scanfのパラメータとして、その配列の先頭番地をinstringを与えている。この場合、&instring[0]としても同じで、やはり配列の先頭番地を指している。 |
16: | char型を指すポインタpinstring, poutstring に文字列の先頭番地が渡される。 |
20: | 入力文字列の長さが、mより大きいときには、文字列の最後を示す\0のみを戻すように21〜26を飛ばしている。 |
22〜23: | 入力文字列の初めからm文字先をポインタpinstringが指すように、m回pinstring++ を行なう。 |
24〜25: | 入力文字列のm番目から、n個poutstringへコピーする。この2文は、
for(i = 0; i < n && *pinstring != '\0'; i++) { *poutstring = *pinstring; poutstring++; pinstring++; }と書き換えられる。pinstring, poutstring ともにアドレスであるため、その内容を指すために *inrtsing, *outstring のように操作する。 |
27: | 文字列の最後は、\0が必要なため*poutstringに \0 を代入している。 |
/* 1 */ /* program 5-9-2 (array ver.) */ /* 2 */ #include <stdio.h> /* 3 */ #include <string.h> /* 4 */ /* 5 */ main() /* 6 */ { /* 7 */ void midstr(char instring[], int m, int n, char outstring[]); /* 8 */ /* 9 */ char instring[80], outstring[80]; /* 10 */ /* 11 */ scanf("%s", instring); /* 12 */ midstr(instring, 4, 5, outstring); /* 13 */ printf(" %s :midstr(4,5) --> %s \n", instring, outstring); /* 14 */ } /* 15 */ /* 16 */ void midstr(char ainstring[], int m, int n, char aoutstring[]) /* 17 */ { /* 18 */ int i, l; /* 19 */ /* 20 */ l = strlen(ainstring); /* 21 */ for(i = 0; (i < n) && (i < l - m + 1); i++) /* 22 */ aoutstring[i] = ainstring[i + m - 1]; /* 23 */ aoutstring[i] = '\0'; /* 24 */ }
programming programming :midstr(4,5) --> gramm
16: | 配列の先頭番地として、ainstring[], aoutstring[] が受け渡される。 |
20〜23: | プログラム5−9−1と同じ処理を行なっている。この場合、配列として宣言されているため添字を扱うことができ、一度でm番目の文字を参照できる。ポインタの場合プログラム5−9−1の22〜23で行なったようにポインタをm回先へ進ませないと、m番目の文字が参照できない点が異なっている。 |
instring reverse\0 | | ↓ outstring esrever\0
/* 1 */ /* program 5-10-2 (array ver.) */ /* 2 */ #include <stdio.h> /* 3 */ #include <string.h> /* 4 */ /* 5 */ main() /* 6 */ { /* 7 */ void revstr(char st1[], char st2[]); /* 8 */ /* 9 */ char instring[80], outstring[80]; /* 10 */ /* 11 */ scanf("%s", instring); /* 12 */ revstr(instring, outstring); /* 13 */ printf("%s :revstr --> %s \n", instring, outstring); /* 14 */ } /* 15 */ /* 16 */ void revstr(char st1[], char st2[]) /* 17 */ { /* 18 */ int i, n; /* 19 */ /* 20 */ n = strlen(st1); /* 21 */ n--; /* 22 */ for(i = 0; i <= n; i++) /* 23 */ st2[i] = st1[n - i]; /* 24 */ st2[i]='\0'; /* 25 */ }
reverse reverse :revstr --> esrever
20: | 文字列の長さを調べる関数strlenを用いる。 |
21: | strlen関数は、文字列の最後を示す \0 もカウントしているため、実際の文字数は、そこから1つ減らさなければならない。 |
/* 1 */ /* program 5-10-2 (pointer ver.) */ /* 2 */ #include <stdio.h> /* 3 */ #include <string.h> /* 4 */ /* 5 */ main() /* 6 */ { /* 7 */ void revstr(char *st1, char *st2); /* 8 */ /* 9 */ char instring[80], outstring[80]; /* 10 */ /* 11 */ scanf("%s", instring); /* 12 */ revstr(instring, outstring); /* 13 */ printf("%s :revstr --> %s \n", instring, outstring); /* 14 */ } /* 15 */ /* 16 */ void revstr(char *st1, char *st2) /* 17 */ { /* 18 */ int i, n; /* 19 */ /* 20 */ n = strlen( st1 ); /* 21 */ n--; /* 22 */ for( i=0; i<n; i++ ) /* 23 */ st1++; /* 24 */ for( i=0; i<=n; i++ ) /* 25 */ *st2++ = *st1--; /* 26 */ *st2 = '\0'; /* 27 */ }
REVERSE REVERSE :revstr --> ESREVER
22〜23: | st1の文字列の最後の文字(\0 の手前の文字)の所まで、ポインタを動かす。 |
24〜25: | st1からst2へコピーされる。この際、st1のポインタは前に向かって、st2のポインタは後ろへ向かってコピー終了後にポインタが動く。 |
/* 1 */ /* program 5-10-3 (pointer ver.) */ /* 2 */ #include <stdio.h> /* 3 */ #include <string.h> /* 4 */ /* 5 */ main() /* 6 */ { /* 7 */ void revstr(char *st1, char *st2); /* 8 */ /* 9 */ char instring[80], outstring[80]; /* 10 */ /* 11 */ scanf("%s", instring); /* 12 */ revstr(instring, outstring); /* 13 */ printf("%s :revstr --> %s \n", instring, outstring); /* 14 */ } /* 15 */ /* 16 */ void revstr(char *st1, char *st2) /* 17 */ { /* 18 */ char *d; /* 19 */ /* 20 */ d = st1; /* 21 */ while(*st1 != '\0') /* 22 */ st1++; /* 23 */ st1--; /* 24 */ while(st1 >= d) /* 25 */ *st2++ = *st1--; /* 26 */ *st2 = '\0'; /* 27 */ }
REVERSE REVERSE :revstr --> ESREVER
/* 1 */ /* program 5-10-4 (ponter special ver.) */ /* 2 */ #include <stdio.h> /* 3 */ #include <string.h> /* 4 */ /* 5 */ main() /* 6 */ { /* 7 */ void revstrsp(char *st); /* 8 */ /* 9 */ char inoutstring[80]; /* 10 */ /* 11 */ scanf("%s", inoutstring); /* 12 */ printf("%s :revstrsp --> ", inoutstring); /* 13 */ revstrsp(inoutstring); /* 14 */ printf("%s \n", inoutstring); /* 15 */ } /* 16 */ /* 17 */ /* 18 */ void revstrsp(char *st) /* 19 */ { /* 20 */ void swapchar(char *a, char *b); /* 21 */ /* 22 */ char *dum; /* 23 */ /* 24 */ dum = st; /* 25 */ while(*dum++ != '\0'); /* 26 */ dum--; /* 27 */ dum--; /* 28 */ while(st < dum) /* 29 */ { /* 30 */ swapchar(st, dum); /* 31 */ st++; /* 32 */ dum--; /* 33 */ } /* 34 */ } /* 35 */ /* 36 */ void swapchar(char *a, char *b) /* 37 */ { /* 38 */ char c; /* 39 */ /* 40 */ c = *a; /* 41 */ *a = *b; /* 42 */ *b = c; /* 43 */ }
Special Special :revstrsp --> laicepS
24−27: | ポインタdumは、27が終了後には最後の文字(\0のすぐ前)を指している。 25が終了した時点では、dumは文字列終端を示す\0の次を指しているため、2回dum--を行い、最後の文字を指すようになっている。 |
28−34: | 2つのポインタstとdumによって入れ換えが行なわれる。stは文字列の終わりに向かって進み、dumは文字列に先頭に向かって進む。30は、二つのポインタの指す文字の入れ換えを行なう関数swapcharの呼び出しである。引数のstとdumに&がついていないことに注意すること。st,dumともにポインタ変数として定義されているため、プログラム5−5で定義したswapf関数の呼び出し時のように引数に&をつけなくても、st,dumはアドレスであることがわかっているからである。swapf関数の引数では、float型で宣言されていた変数のアドレスを渡しているため、&を付けていたのである。 |
36−43: | 二つのポインタの指す文字の入れ換えを行なう関数swapcharである。 |
/* 1 */ /* program 5-11 string to integer */ /* 2 */ #include <stdio.h> /* 3 */ main() /* 4 */ { /* 5 */ int str_to_int(char *moji); /* 6 */ char moji[20]; /* 7 */ /* 8 */ scanf("%s", moji); /* 9 */ printf("%d \n", str_to_int(moji)); /* 10 */ } /* 11 */ /* 12 */ int str_to_int(char *moji) /* 13 */ { /* 14 */ int n, sign, value; /* 15 */ /* 16 */ if( *moji == '-' ) /* 17 */ { /* 18 */ sign = -1; /* 19 */ moji++; /* 20 */ } /* 21 */ else sign = 1; /* 22 */ /* 23 */ value = 0; /* 24 */ while (*moji != '\0') /* 25 */ if( '0' <= *moji && *moji <= '9' ) /* 26 */ value = value*10 + *moji++ - '0'; /* 27 */ return(sign * value); /* 28 */ }
1235 1235
入力文字が、文字の"0"から"9"であれば数値に変換する。それ以外の文字は、無視するようになっている。( *moji - '0' )で文字に対応する数値を計算している。これは、文字コードを利用している。
/* 1 */ /* program 5-12-1 integer to string */ /* 2 */ #include <stdi.h> /* 3 */ main() /* 4 */ { /* 5 */ void int_to_str( int value, char *moji ); /* 6 */ int value; /* 7 */ char moji[80]; /* 8 */ /* 9 */ scanf("%d", &value); /* 10 */ int_to_str( value, moji ); /* 11 */ printf("%s \n", moji ); /* 12 */ } /* 13 */ /* 14 */ void int_to_str( int value, char *moji ) /* 15 */ { /* 16 */ int base, n; /* 17 */ /* 18 */ if( value < 0 ) /* 19 */ { /* 20 */ *moji = '-'; /* 21 */ value = -value; /* 22 */ moji++; /* 23 */ } /* 24 */ /* 25 */ for( base = 1; base<=value; base*=10 ); /* 26 */ if( value!=0) /* 27 */ base /= 10; /* 28 */ while( 1<=base ) /* 29 */ { /* 30 */ n = (int)(value/base); /* 31 */ value %= base; /* 32 */ *(moji++) = n + '0'; /* 33 */ base /= 10; /* 34 */ } /* 35 */ *moji = '\0'; /* 36 */ }
1234 1234
18−23: | 負の整数に対する処理を行なっている。負の整数の時には、文字列の先頭に"−"を入れ、渡された数値に−1をかけ算して以後の処理を非負の場合と同じになるようにしている。 |
25−27: | 入力された数値の桁数をカウントし、変数baseに10の桁数乗を代入している。これは、後の処理で上位の桁から順に数値を1つづつ取るために行なっている。数値の桁数が少ない時、文字に変換すると数値の初めに"0"がついてしまう。この"0"をと取り除くためにも桁数のカウントは必要である。 |
28−35: | 入力数値の上位桁から順に1つづつ取り出し、対応する文字に変換している。この手法はよく用いられるため、覚えておくと便利である。 |
/* 1 */ /* program 5-12-2 integer to string */ /* 2 */ #include <stdio.h> /* 3 */ #include "revstrsp.c" /* 4 */ /* 5 */ main() /* 6 */ { /* 7 */ void int_to_str(int value, char *moji); /* 8 */ /* 9 */ int value; /* 10 */ char moji[80]; /* 11 */ /* 12 */ scanf("%d", &value); /* 13 */ int_to_str(value, moji); /* 14 */ printf("%s \n", moji); /* 15 */ } /* 16 */ /* 17 */ void int_to_str(int value, char *moji) /* 18 */ { /* 19 */ int sign; /* 20 */ char *top; /* 21 */ /* 22 */ top = moji; /* 23 */ if( value < 0 ) /* 24 */ { /* 25 */ value = -value; /* 26 */ sign = -1; /* 27 */ } /* 28 */ /* 29 */ while(value / 10 > 0) /* 30 */ { /* 31 */ *moji++ = value % 10 + '0'; /* 32 */ value /= 10; /* 33 */ } /* 34 */ *moji++ = value + '0'; /* 35 */ if (sign == -1) /* 36 */ *moji++ = '-'; /* 37 */ *moji = '\0'; /* 38 */ revstrsp(top); /* 39 */ }
1234 1234
しかし、計算機内部ではこれらは物理的に続いた記憶域に取られる。
int *pa; pa = &a[0][0]; /* または pa = a; */とすると、ポインタ変数 pa で配列 a[i][j] を参照することができる。例えば、*(pa+2) とすると a[0][2] が参照でき、*(pa+9) とすると a[2][2] が参照できる。また、
pa = a[1];とすると a[1] の最初の要素へのポインタになる。*(a[1]+2) とすると a[1] の2番目に要素つまり a[1][2] を参照することになり、さらに *(*(a+1) + 2) としても a[1][2] を参照することになる。
例
char *s[4] = {"How ","are ","you","?" };
とすると、下図のような関係を作れる。