#include <stdio.h> #include <stdlib.h> void func_A(int x) { x = 10; } void func_B(int y[]) { y[1] = 20; } void func_C(int z) { z = 30; } /* scanf は引数に & を付けますが, そのような変数を受けとるときは, *w のように * を付けます. 呼出側, 受け側ともにアドレスの 受け渡しを指定しています. */ void func_D(int *w) { *w = 40; } int main(void) { int a; int b[10]; a = 1; printf("a=%d\n", a); func_A(a); // 変数の値を渡す. printf("a=%d\n\n", a); b[1] = 2; printf("b[1]=%d\n", b[1]); func_B(b); // 配列の名前 (=アドレス)を渡す. printf("b[1]=%d\n\n", b[1]); b[1] = 2; printf("b[1]=%d\n", b[1]); func_C(b[1]); // 変数 b[1] の値を渡す. printf("b[1]=%d\n\n", b[1]); // func_A と比較すると, // func_D は, 引数に & を付けアドレスに変換している点が異なる. a = 1; printf("a=%d\n", a); func_D( &a ); // 変数のアドレスを渡す. printf("a=%d\n\n", a); // 関数呼び出しを行った後は, 値が変わる. return EXIT_SUCCESS; }
#include <stdio.h> /* 2つの3次元ベクトル a, b の和を計算し, ベクトル c に代入する関数. 仮引数 c, a, b はアドレスを受けとるだけで, 値を保持するための記憶域の確保は行わない. */ // 配列の場合は, アドレスを受け取る. 各要素の値は受け取らない. void sum(double c[3], double a[3], double b[3]) { int i; for(i=0; i<3; i++) c[i] = a[i] + b[i]; } int main(void) { /* 宣言を行った時点で, その変数や配列の値を記憶する領域が確保される. そのブロックが終了するまでは, 他の変数がその領域を使用することはない. */ double x1[3] = {1.0, 2.0, 3.0}; double x2[3] = {4.0, 5.0, 6.0}; double x3[3]; // 呼び出し側で記憶域の確保が必要. sum(x3, x1, x2); // 配列の先頭のアドレスを渡している. printf("x1 + x2 = (%g, %g, %g)\n", x3[0], x3[1], x3[2]); return 0; }
関数の実引数として, 通常の変数や定数の値を用いることもできるし, それらのアドレスを用いることもできる.
詳細は第10章「ポインタ」ので学習することとして, アドレスやそれを操作するためのポインター変数を理解しておくとよい. 教科書の「配列」の章を, 天下り的でなく, 見通しよく理解できる。
変数を用いる場合には, メモリー(記憶域)上に必要な領域を確保し, そこに変数の値を保存する. 「値」は,そこに保存されている内容であり, 「アドレス」は, 保存されている場所である. 「ポインタ変数」はアドレスを格納するための変数である. 表1に通常の変数とポインター変数を並べて示す.
さらに, 配列まで含めたときの変数とアドレスの関係を2に示す.
以下に, アドレスを仮引数とする関数呼び出しの例を示す.
// ポインタ変数を引数とする関数 #include <stdio.h> void func(double *x) { printf("(2) &x = %p\n", &x); // x のアドレス. printf("(2) sizeof(x) = %u\n", sizeof(x)); // x のアドレス. printf("(2) x = %p\n", x); // xの値 (ここでは a のアドレス) printf("(2) *x = %f\n", *x); // *x の値. *x = 20.0; printf("(2) *x = %f\n", *x); // *x の値. } int main(void) { double a = 3.0; printf("(1) a = %f\n", a); // a の値 printf("(1) &a = %p\n", &a); // a のアドレス printf("(1) sizeof(a) = %u\n", sizeof(a)); // a のサイズ func(&a); // funcの引数のxに aのアドレス&a が代入される printf("(3) a = %f\n", a); return 0; }
次に, 配列を引数とする関数を示す. 配列名を引数とする場合, 配列のアドレスを関数に渡すことになる.
#include <stdio.h> #define NUMBER 5 /* 配列の各要素に100を加える関数 */ void func(double a[NUMBER]) { int i; for(i=0; i<NUMBER; i++) a[i] += 100.0; } int main(void) { int i; double x[NUMBER] = {0.0}; // 全ての要素を 0.0 に初期化. for(i=0; i<NUMBER; i++) printf("x[%d] = %g\n", i, x[i]); func(x); for(i=0; i<NUMBER; i++) printf("x[%d] = %g\n", i, x[i]); return 0; }
List6-17のconstの使い方は, C言語の規格上は誤りである.
List6-17はgcc でコンパイルすると, 以下のメッセージが表示される.
List6-17org.c: In function ‘main’: List6-17org.c:19: warning: passing argument 1 of ‘mat_add’ from incompatible pointer type List6-17org.c:19: warning: passing argument 2 of ‘mat_add’ from incompatible pointer typeこれは, 19行目でmat_add 関数を呼び出す際の実引数maおよび mbと, 3行目の関数定義で使われている仮引数ma およびmbの型と異なることが原因である.
#include <stdio.h> void mat_add(const int ma[2][3], const int mb[2][3], int mc[2][3]) { int i, j; for(i=0; i<2; i++) for(j=0; j<3; j++) mc[i][j] = ma[i][j] + mb[i][j]; } int main(void) { int i, j; int ma[2][3] = {{1, 2, 3}, {4, 5, 6}}; int mb[2][3] = {{7, 8, 9}, {10, 11, 12}}; int mc[2][3] = { 0 }; mat_add(ma, mb, mc); for(i=0; i<2; i++) { for(j=0; j<3; j++) printf("%3d", mc[i][j]); putchar('\n'); } return 0; }
mat_add( (const int (*)[3])ma, (const int (*)[3])mb, mc);
キャストを用いた修正版
#include <stdio.h> /* 関数呼出しの際に, 【『xxx』へのポインター】を【『constのxxx』へのポインター】に渡すことはできる. しかし, 一般に 【『xxx』へのポインター】を【『yyy』へのポインター】に渡すことはできない. 【『「int」の配列』へのポインター】を【『「constのint」の配列』へのポインター】に 渡すこともできない. */ /* C言語では, 関数に配列名を渡すときは, ポインタに変換される. この場合は, void mat_add(const int ma[2][3], const int mb[2][3], int mc[2][3]) や void mat_add(const int ma[][3], const int mb[][3], int mc[][3]) は void mat_add(const int (*ma)[3], const int (*mb)[3], int (*mc)[3]) と 解釈されている. (int ma[] は int (*ma) であることに注意.) すわなちma, mbは,【『「const int」の配列 』へのポインタ 】である. */ void mat_add(const int (*ma)[3], const int (*mb)[3], int (*mc)[3]) { int i, j; for(i=0; i<2; i++) for(j=0; j<3; j++) mc[i][j] = ma[i][j] + mb[i][j]; } int main(void) { int i, j; int ma[2][3] = {{1, 2, 3}, {4, 5, 6}}; int mb[2][3] = {{7, 8, 9}, {10, 11, 12}}; int mc[2][3] = { {0}, {0} }; /* main で宣言されている ma, mb は, 【『 「int」 の配列 』 へのポインタ】である. 一方, mat_add は 【『 「const int」 の配列 』 へのポインタ】を受け取ることに なっているので, 以下の関数呼出しでは, 型が合わない (incompatible pointer type). */ /* mat_add(ma, mb, mc); */ /* 型を合わせるために, 以下のようにキャストする. */ mat_add( (const int (*)[3])ma, (const int (*)[3])mb, mc); for(i=0; i<2; i++) { for(j=0; j<3; j++) printf("%3d", mc[i][j]); putchar('\n'); } return 0; }
#include <stdio.h> typedef const int (*CONST_MAT_PTR)[3]; typedef const int (CONST_MAT)[3]; void mat_add(const int (*ma)[3], const int (*mb)[3], int (*mc)[3]) { int i, j; for(i=0; i<2; i++) for(j=0; j<3; j++) mc[i][j] = ma[i][j] + mb[i][j]; } int main(void) { int i, j; int ma[2][3] = {{1, 2, 3}, {4, 5, 6}}; int mb[2][3] = {{7, 8, 9}, {10, 11, 12}}; int mc[2][3] = { {0}, {0} }; /* typedef により行列型を宣言しておくと, 以下のように簡潔に表現できる. */ mat_add((CONST_MAT_PTR)ma, (CONST_MAT_PTR)mb, mc); /* または, */ mat_add((CONST_MAT *)ma, (CONST_MAT *)mb, mc); for(i=0; i<2; i++) { for(j=0; j<3; j++) printf("%3d", mc[i][j]); putchar('\n'); } return 0; }
#include <stdio.h> /* 構造体の中に配列を宣言し, その中に配列を用意する. */ struct MAT { int m[2][3]; }; void mat_add(const struct MAT *ma, const struct MAT *mb, int mc[2][3]) { int i, j; // const で宣言されていても, 参照は可能. for(i=0; i<2; i++) for(j=0; j<3; j++) mc[i][j] = ma->m[i][j] + mb->m[i][j]; // ma->m[1][1] = 50; // エラー: const struct なので 代入不可. } int main(void) { int i, j; struct MAT ma = {{{1, 2, 3}, {4, 5, 6}}}; struct MAT mb = {{{7, 8, 9}, {10, 11, 12}}}; int mc[2][3] = { {0}, {0} }; mat_add(&ma, &mb, mc); for(i=0; i<2; i++) { for(j=0; j<3; j++) printf("%3d", mc[i][j]); putchar('\n'); } return 0; }
#include <stdio.h> #include <stdlib.h> int main(void) { const int a = 1; const int b = 2; int c = 3; const int *x; int *y; // y: pointer to int // [A] const int *x <- const int a は型が一致するので可. x = &a; // xの値 すなわち xの指す先を変更することはできる. /* ...ここで xを用いた処理. a の値を参照することができる. */ x = &b; // xの値 すなわち xの指す先を変更することはできる. // xが指す先の内容(const int)を変更することはできない. // *x = 10; // この行はエラー // [B] const int *x <- int c は型が異なるが型変換される. x = &c; // *x = 10; // ただし, x は const int を指すので代入は不可. // [C] int *y <- const int a は型が異なるが型変換される. // y = &a; // 不可. この代入が許されると, // *y = 10; // aの値を変更できてしまう. const で宣言した意味がなくなる. // [D] int (*y) <- int c[2] は型が一致するので可. y = &c; // これは y, c ともに const int でなく int 型を指すので可. *y = 10; // 当然代入もできる. printf("%d\n", *x); return EXIT_SUCCESS; }
#include <stdio.h> #include <stdlib.h> int main(void) { int i; const int a[2] = {1,2}; const int b[2] = {1,2}; int c[2] = {1,2}; const int (*x); int (*y); // y: pointer to int // [A] const int (*x) <- const int a[2] は型が一致するので可. x = a; // xの値 すなわち xの指す先を変更することはできる. /* ...ここで xを用いた処理. a の値を参照することができる. */ x = b; // xの値 すなわち xの指す先を変更することはできる. // xが指す先の内容(const int)を変更することはできない. // x[1] = 10; // この行はエラー // [B] const int (*x) <- int c[2] は型が異なるが型変換される. x = c; // x[1] = 10; // ただし, x は const int を指すので代入は不可. // [C] int (*y) <- const int a[2] は型が異なるが型変換される. // y = a; // この代入が許されると, // y[1] = 10; // aの値を変更できてしまう. const で宣言した意味がなくなる. // [D] int (*y) <- int c[2] は型が一致するので可. y = c; // これは y, c ともに const int でなく int 型を指すので可. y[1] = 10; // 当然代入もできる. for (i=0; i<2; i++) printf("%d\n", x[i]); return EXIT_SUCCESS; }
#include <stdio.h> #include <stdlib.h> int main(void) { int i, j; const int a[3][2] = { {1,2}, {3,4}, {5,6}}; const int b[3][2] = { {1,2}, {3,4}, {5,6}}; int c[3][2] = { {1,2}, {3,4}, {5,6} }; const int (*x)[2]; int (*y)[2]; // y: pointer to array[2] of int // [A] const int (*x)[2] <- const int a[3][2] は型が一致するので可. x = a; // xの値 すなわち xの指す先を変更することはできる. /* ...ここで xを用いた処理. a の値を参照することができる. */ x = b; // xの値 すなわち xの指す先を変更することはできる. // xが指す先の内容(const int)を変更することはできない. // x[1][1] = 10; // この行はエラー // [B] const int (*x)[2] <- int c[3][2] は型が異なるので不可. // x = c; // C言語の規格上はエラー. エラーにならないコンパイラもある. // [C] int (*y)[2] <- const int a[3][2] は型が異なるので不可. // y = a; // 不可. これが許されると, // y[1][1] = 10; // aの値を変更でしまい, a をconst で宣言した意味がなくなる. // [D] int (*y)[2] <- int c[3][2] は型が一致するので可. y = c; // これは y, c ともに const int でなく int 型なので可. y[1][1] = 10; // 当然代入もできる. for (i=0; i<3; i++) for (j=0; j<2; j++) printf("%d\n", x[i][j]); return EXIT_SUCCESS; }
(c)1999-2013 Tetsuya Makimura