#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