Download : sample-001.c
/* * 2021/12/10 sample-001.c * * ポインター型変数の利用 * */ #include <stdio.h> #include <malloc.h> /* * 利用方法 * コンパイル * cc -o sample-001.exe sample-001.c * 実行 * ./sample-001.exe */ /* * main 関数 */ int main ( int argc, char *argv[] ) { int v; /* 整数型の変数を確保 */ int *p = &v; /* ポインター型変数 p に v のポインター値を代入する */ /* *p は、変数 v と全く同じに振る舞う */ *p = 1; printf ( "*p = %d\n", *p ); printf ( "v = %d\n", v ); *p = *p + 10; printf ( "*p = %d\n", *p ); printf ( "v = %d\n", v ); return 0; }
$ ./sample-001.exe *p = 1 v = 1 *p = 11 v = 11 $
Download : sample-002.c
/* * 2021/12/10 sample-002.c * * 動的領域の確保 (単純変数) * */ #include <stdio.h> #include <malloc.h> /* calloc/free を利用する場合に必要 */ /* * 利用方法 * コンパイル * cc -o sample-002.exe sample-002.c * 実行 * ./sample-002.exe */ /* * main 関数 */ int main ( int argc, char *argv[] ) { int *p; /* 整数型変数の領域を 1 つ分確保 */ p = (int *)calloc ( 1, sizeof(int) ); if ( p == NULL ) { /* メモリの確保に失敗した場合は NULL が返る */ printf ( "整数領域のメモリ確保に失敗しました。\n" ); } else { /* 以下は、*p を普通の整数型変数と同じ様に利用できる */ *p = 1; printf ( "*p = %d\n", *p ); *p = *p + 10; printf ( "*p = %d\n", *p ); /* 動的に確保したメモリは最後に必ず、free で解放する */ free ( p ); } return 0; }
$ ./sample-002.exe *p = 1 *p = 11 $
Download : sample-003.c
/* * 2021/12/10 sample-003.c * * ポインター型変数の利用(2) * */ #include <stdio.h> /* * 利用方法 * コンパイル * cc -o sample-003.exe sample-003.c * 実行 * ./sample-003.exe */ /* * main 関数 */ int main ( int argc, char *argv[] ) { int a[10]; /* 整数型の配列を確保 */ int *p = a; /* ポインター型変数 p に a の先頭の要素のポインター値を代入する */ /* p は、配列名 a と全く同じに振る舞う */ p[2] = 1; printf ( "p[2] = %d\n", p[2] ); printf ( "a[2] = %d\n", a[2] ); p[4] = p[2] + 10; printf ( "p[4] = %d\n", p[4] ); printf ( "a[4] = %d\n", a[4] ); return 0; }
$ ./sample-003.exe p[2] = 1 a[2] = 1 p[4] = 11 a[4] = 11 $
Download : sample-004.c
/* * 2021/12/10 sample-004.c * * 動的領域の確保 (配列) * */ #include <stdio.h> #include <malloc.h> /* * 利用方法 * コンパイル * cc -o sample-004.exe sample-004.c * 実行 * ./sample-004.exe */ /* * main 関数 */ int main ( int argc, char *argv[] ) { int *p; /* 整数型変数の領域を 10 つ分確保 */ p = (int *)calloc ( 10, sizeof(int) ); if ( p == NULL ) { /* メモリの確保に失敗した場合は NULL が返る */ printf ( "整数領域のメモリ確保に失敗しました。\n" ); } else { /* 以下は、p を普通の整数型配列名と同じ様に利用できる */ p[2] = 1; printf ( "p[2] = %d\n", p[2] ); p[4] = p[2] + 10; printf ( "p[4] = %d\n", p[4] ); /* 動的に確保したメモリは最後に必ず、free で解放する */ free ( p ); } return 0; }
$ ./sample-004.exe p[2] = 1 p[4] = 11 $
Download : sample-005.c
/* * 2021/12/10 sample-005.c * * 不定長の入力 (最大値が存在) * */ #include <stdio.h> /* * 利用方法 * コンパイル * cc -o sample-005.exe sample-005.c * 実行 * ./sample-005.exe */ #define MAX_OF_INPUT_SIZE 10 /* 入力できるデータ数 */ /* * main 関数 */ int main ( int argc, char *argv[] ) { int array[MAX_OF_INPUT_SIZE]; /* 整数型の配列を確保 */ int n; /* 入力される整数値の個数 */ int i; printf ( "データ数を入力してください : " ); scanf ( "%d", &n ); if ( ( 0 <= n ) && ( n < MAX_OF_INPUT_SIZE ) ) { for ( i = 0; i < n; i++ ) { printf ( "%d 番目の数値を入力してください : ", i + 1 ); scanf ( "%d", &array[i] ); } printf ( "入力された内容は以下の通りです。\n" ); for ( i = 0; i < n; i++ ) { printf ( "%d 番目の数値は %d です。\n", i + 1, array[i] ); } } else { printf ( "データ数(%d)が異常です。\n", n ); } return 0; }
5 453 32 -239 0 294
$ ./sample-005.exe < sample-005.in データ数を入力してください : 5 1 番目の数値を入力してください : 453 2 番目の数値を入力してください : 32 3 番目の数値を入力してください : -239 4 番目の数値を入力してください : 0 5 番目の数値を入力してください : 294 入力された内容は以下の通りです。 1 番目の数値は 453 です。 2 番目の数値は 32 です。 3 番目の数値は -239 です。 4 番目の数値は 0 です。 5 番目の数値は 294 です。 $
Download : sample-006.c
/* * 2021/12/10 sample-006.c * * 不定長の入力 (動的に領域を確保) * */ #include <stdio.h> #include <malloc.h> /* * 利用方法 * コンパイル * cc -o sample-006.exe sample-006.c * 実行 * ./sample-006.exe */ /* * main 関数 */ int main ( int argc, char *argv[] ) { int *parray; /* 整数型の配列の先頭 */ int n; /* 入力される整数値の個数 */ int i; printf ( "データ数を入力してください : " ); scanf ( "%d", &n ); if ( 0 <= n ) { if ( ( parray = (int *)calloc( n, sizeof(int) ) ) != NULL ) { for ( i = 0; i < n; i++ ) { printf ( "%d 番目の数値を入力してください : ", i + 1 ); scanf ( "%d", &parray[i] ); } printf ( "入力された内容は以下の通りです。\n" ); for ( i = 0; i < n; i++ ) { printf ( "%d 番目の数値は %d です。\n", i + 1, parray[i] ); } free ( parray ); } else { printf ( "整数領域のメモリ確保に失敗しました。\n" ); } } else { printf ( "データ数(%d)が異常です。\n", n ); } return 0; }
15 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
$ ./sample-006.exe < sample-006.in データ数を入力してください : 15 1 番目の数値を入力してください : 1 2 番目の数値を入力してください : 2 3 番目の数値を入力してください : 3 4 番目の数値を入力してください : 4 5 番目の数値を入力してください : 5 6 番目の数値を入力してください : 6 7 番目の数値を入力してください : 7 8 番目の数値を入力してください : 8 9 番目の数値を入力してください : 9 10 番目の数値を入力してください : 0 11 番目の数値を入力してください : 1 12 番目の数値を入力してください : 2 13 番目の数値を入力してください : 3 14 番目の数値を入力してください : 4 15 番目の数値を入力してください : 5 入力された内容は以下の通りです。 1 番目の数値は 1 です。 2 番目の数値は 2 です。 3 番目の数値は 3 です。 4 番目の数値は 4 です。 5 番目の数値は 5 です。 6 番目の数値は 6 です。 7 番目の数値は 7 です。 8 番目の数値は 8 です。 9 番目の数値は 9 です。 10 番目の数値は 0 です。 11 番目の数値は 1 です。 12 番目の数値は 2 です。 13 番目の数値は 3 です。 14 番目の数値は 4 です。 15 番目の数値は 5 です。 $
/* * DATE-02-QQQQ.c * * 動的なメモリの確保 * キーボードより正の整数を幾つか入力して、その要素が入った配列を返す * 0 以下の整数が入力されたら、終了とする * 配列のサイズは、正の整数の個数 + 1 とし、最後の要素には 0 を入れる * * 沢山の整数値を入力して、処理したい * => 「複数個の整数値を保存できるもの」に入力データを保存する * <= 整数型の配列を利用するのが第一候補 * 配列のサイズは、プログラムの中で指定 * => コンパイル時に確定 * <= 自動的に確保/解放するには、コンパイラにこの情報を与える必要がある * <= 配列のサイズを超える情報は記録できない * => 無駄に沢山(利用しないかもしれない)配列の領域を確保するの無駄 * <= 必要領域を、実行時に動的に確保する */ /* * 利用方法 * コンパイル * cc -o BASENAME.exe FILENAME * 実行 * ./BASENAME.exe */ #include <stdio.h> #include <malloc.h> /* calloc/free を利用するので必要 */ /* * read_n_integers * キーボードから整数値を読み込んで、 * 動的に確保した、領域に、その値を保存する * 事前に、何個入るかはわからない * => 読み終わった時点で、サイズが確定する事に注意 */ int *read_n_integers( int size ) { int num; /* キーボードから入力された数値を保存する */ int *value; /* 確保された配列の先頭要素へのポインター */ printf ( "正の整数値を入力してください(0 以下だと入力を終了します):" ); scanf ( "%d", &num ); if ( num <= 0 ) { /* 入力が全部終ったので、配列を作成する */ /* 配列のサイズは、引数で指定された個数 + 1 となる */ if ( ( value = (int *)calloc ( size + 1, sizeof ( int ) ) ) != NULL ) { /* calloc malloc との違い 1. 整数型の引数が二つで、結果的に、その二つの 引数の値の積 byte の領域を確保する 2. その領域を 0 で初期化 calloc も malloc も領域が確保できた場合は、 その領域の先頭アドレス値を返す 領域の確保に失敗した場合は、 NULL という特別なポインター値をかえす NULL 値は適切(いみのある)な領域をささない事が保証されている */ /* 動的メモリは取り出せるとは限らないので、結果をチェック */ value[ size ] = 0; /* 最後の要素として 0 を代入 */ } /* else {} */ /* NULL が帰った場合は、そのまま、値として返す */ } else { /* 入力が終っていないので、更に、値を読むために再帰呼び出し */ if ( ( value = read_n_integers( size + 1 ) ) != NULL ) { /* 結果が NULL でなければ、配列が作られている */ /* size 番目の要素を配列に記録 */ value [size] = num; } /* else {} */ /* NULL が帰った場合は、そのまま、値として返す */ } /* いずれの場合でも value を返す */ return value; } /* * main */ int main ( int argc, char *argv[] ) { int *array; /* n 個数の要素をもつ配列の先頭をもつ */ int i; /* read_n_integers を呼び出して、n 個の整数値を入力する */ /* 引数には、入力済のデータ数を指定するので、最初は 0 を指定する */ if ( ( array = read_n_integers( 0 ) ) != NULL ) { /* read_n_integers は、NULL を返す可能性がある */ /* 入力された要素を画面に出力 */ for ( i = 0; array[i] > 0; i++ ) { printf ( "%d th data = %d\n", i, array[i] ); } /* malloc/calloc で確保したメモリは、必ず free で解放する */ free ( array ); /* malloc/calloc で確保した領域を解放する */ } /* else {} */ /* NULL の場合はエラーなので、何もしない */ return 0; }
#include <stdio.h> int main(int argc, char *argv[]) { int x, x2; /* 整数型の変数 x を宣言 */ /* メモリの一部 ( sizeof(int) 個のセル ) が確保される その 4 つセルの型は「int (整数)型」とした扱う */ double y, y2; /* 浮動小数数型の変数 y を宣言 */ /* メモリの一部 ( sizeof(double) 個のセル ) が確保される その 8 つセルの型は「double(浮動小数点数)型」とした扱う */ /* +-----------+ 0x7fffc63c4a60 x | | int 型 : 4 byte 分 +-----------+ | | +-----------+ | | +-----------+ | | +-----------+ 0x7fffc63c4a64 x2 | | int 型 : 4 byte 分 +-----------+ | | +-----------+ | | +-----------+ | | +-----------+ 0x7fffc63c4a68 y | | double 型 : 8 byte +-----------+ | | +-----------+ | | +-----------+ | | +-----------+ | | +-----------+ | | +-----------+ | | +-----------+ | | +-----------+ 0x7fffc63c4a70 y2 | | double 型 : 8 byte +-----------+ | | +-----------+ | | +-----------+ | | +-----------+ | | +-----------+ | | +-----------+ | | +-----------+ | | +-----------+ */ x = x2; /* メモリのコピーをしている */ /* コピーされるセルの個数は 4 byte */ y = y2; /* メモリのコピーをしている */ /* コピーされるセルの個数は 8 byte */ /* 同じ代入文なのに、やっている事が違う */ /* 違い => 代入文の操作対象となる 変数 (x,x1), (y,y1) 型が異なる */ /* この型の違いをコンパイラが判断して、 実施にプログラムが実行される 実行時の命令が変化する */ /* C 言語上の表現では同じ命令なのに、 型によって、異なる振る舞い */ printf ( "変数 x のポインター値(アドレス値) = %p\n", &x ); printf ( "変数 x2 のポインター値(アドレス値) = %p\n", &x2 ); printf ( "変数 y のポインター値(アドレス値) = %p\n", &y ); printf ( "変数 y2 のポインター値(アドレス値) = %p\n", &y2 ); return 0; }
#include <stdio.h> int main(int argc, char *argv[]) { int x; /* メモリ上に、 4 byte 分の領域が確保される */ /* => アドレス値がきまる */ printf ( "&x (変数 x のポインタ値[アドレス値]) = %p\n", &x ); /* 変数 x 自身の値は、初期化を行ってないので、 ここでは不確定 アドレス値は、宣言(有効になった時点)と同時に利用可能 有効になった時点 : 実行時に変数が有効になるのと同時定まる */ x = 1234; printf ( "x = %d\n", x ); printf ( "&x (変数 x のポインタ値[アドレス値]) = %p\n", &x ); /* アドレス値と、変数値は独立 */ x = 987; printf ( "x = %d\n", x ); printf ( "&x (変数 x のポインタ値[アドレス値]) = %p\n", &x ); /* アドレス値と、変数値は独立 */ /* ポインター値の操作 */ printf ( "&x = %p, &x + 1 = %p\n", &x, &x + 1 ); /* ポインター値に、+1 をすると、新しいポインター値が作られる が、そのアドレス値は、 sizeof(型) だけ増える */ /* ポインター値の操作 (アドレス値の変更 : 整数値の加減) */ printf ( "(&x + 1) + 1 = %p\n", (&x + 1) + 1 ); return 0; }
#include <stdio.h> /* \sum_{i=1}^n \frac{1}{i} = 1/1 + 1/2 + 1/3 + ... + 1/n -> 無限 ( n -> 無限 ) */ int main(int argc, char *argv[]) { double sum = 0.0; int i; int n; printf ( "n を入力してください : " ); scanf ( "%d", &n ); for ( i = 1; i <= n; i++ ) { /* sum = sum + 1/i; X */ /* 1/i は、i が整数型なので、 整数割り算になる i > 1 の時に、 1/i は 0 => +0 しても結果がかわらない */ /* 1/i だとまずいのでどうするか ? キャストを行い i の値の型を、浮動小数点数型にする */ sum = sum + 1/(double)i; /* キャスト「(double)」により、 整数型変数 i が持つ整数値が、浮動小数点数の型を持つようように変更され、 割り算が、浮動小数点数の割り算になる (この時に分子の 1[整数値を持つ]も浮動小数点数に昇格する */ } printf ( "sum of 1/1 + 1/2 + .. + 1/%d = %f\n", n, sum ); return 0; }
#include <stdio.h> int main(int argc, char *argv[]) { int i; i = 0x12345678; /* 16 進数で、整数値を表現 */ /* 「0x」で始める事による整数を 16 進数で表現可能 */ /* 計算は 10 進数で行う */ printf ( "i=%x\n", i ); /* 整数値を 16 進数表現出力する */ printf ( "100(十進)=%x(十六進)\n", 100 ); printf ( "100(十六進)=%d(十進)\n", 0x100 ); /* i +-------+ | | +-------+ | | +-------+ | | +-------+ | | +-------+ i = 0x12345678; i +-------+ |0x78 | <- (char *)(&i) +-------+ |0x56 | <- ((char *)(&i)+1) +-------+ | | +-------+ | | +-------+ */ printf ( "((char *)&i)[0] = %x\n", ((char *)&i)[0] ); /* ((char *)&i)[0] <-> *(((char *)&i)+0) <-> *(((char *)&i)) * を使ってデータを取り出す時に、 char 型へのポインタ値(として、キャストされている)なので 1 byte ( sizeof(char) ) だけ取り出される */ printf ( "((char *)&i)[1] = %x\n", ((char *)&i)[1] ); printf ( "((char *)&i)[2] = %x\n", ((char *)&i)[2] ); printf ( "((char *)&i)[3] = %x\n", ((char *)&i)[3] ); /* int 型の変数(i)に対応したセル(4つ[sizeof(int)])を キャストをつかって、別々にあつかう事ができる */ return 0; }
#include <stdio.h> void rec( int n ) { int i = n; /* main 中の変数 i とは別物 */ if ( n == 0 ) { printf ( "&i = %p, n = %d\n", &i, n ); } else { printf ( "&i = %p, n = %d\n", &i, n ); /* [c] */ rec ( n - 1 ); printf ( "&i = %p, n = %d\n", &i, n ); /* [d] */ } } void rec2( int n ) { int i = n; /* main 中の変数 i とは別物 */ int j; /* j の値は代入していない */ if ( n == 0 ) { printf ( "&i = %p, n = %d\n", &i, n ); printf ( "&j = %p, n = %d\n", &j, n ); } else { printf ( "&i = %p, n = %d\n", &i, n ); /* [c] */ printf ( "&j = %p, n = %d, j = %d\n", &j, n, j ); /* [c] */ rec ( n - 1 ); printf ( "&i = %p, n = %d\n", &i, n ); /* [d] */ printf ( "&j = %p, n = %d\n", &j, n ); /* [d] */ } } int main(int argc, char *argv[] ) { int i = 3; /* 変数 i には、0x7fffdd61c744 */ printf ( "&i = %p\n", &i );/* [a] 変数 i には、0x7fffdd61c744 */ rec ( i ); printf ( "&i = %p\n", &i );/* [b] */ rec2 ( i ); printf ( "&i = %p\n", &i );/* [b] */ return 0; } /* &i = 0x7fffdd61c744 [a] &i = 0x7fffdd61c714, n = 3 [c:n=3] &i = 0x7fffdd61c6e4, n = 2 [c:n=2] &i = 0x7fffdd61c6b4, n = 1 &i = 0x7fffdd61c684, n = 0 &i = 0x7fffdd61c6b4, n = 1 &i = 0x7fffdd61c6e4, n = 2 [d:n=2] &i = 0x7fffdd61c714, n = 3 [d:n=3] &i = 0x7fffdd61c744 [b] */ /* &i = 0x7ffff3dd1434 [a] &i = 0x7ffff3dd1404, n = 3 rec の i &i = 0x7ffff3dd13d4, n = 2 &i = 0x7ffff3dd13a4, n = 1 &i = 0x7ffff3dd1374, n = 0 &i = 0x7ffff3dd13a4, n = 1 &i = 0x7ffff3dd13d4, n = 2 &i = 0x7ffff3dd1404, n = 3 &i = 0x7ffff3dd1434 [b] &i = 0x7ffff3dd1400, n = 3 &j = 0x7ffff3dd1404, n = 3 rec2 の j &i = 0x7ffff3dd13d4, n = 2 &i = 0x7ffff3dd13a4, n = 1 &i = 0x7ffff3dd1374, n = 0 &i = 0x7ffff3dd13a4, n = 1 &i = 0x7ffff3dd13d4, n = 2 &i = 0x7ffff3dd1400, n = 3 &j = 0x7ffff3dd1404, n = 3 &i = 0x7ffff3dd1434 */ /* &i = 0x7fffd62466f4 &i = 0x7fffd62466c4, n = 3 &i = 0x7fffd6246694, n = 2 &i = 0x7fffd6246664, n = 1 &i = 0x7fffd6246634, n = 0 &i = 0x7fffd6246664, n = 1 &i = 0x7fffd6246694, n = 2 &i = 0x7fffd62466c4, n = 3 &i = 0x7fffd62466f4 &i = 0x7fffd62466c0, n = 3 &j = 0x7fffd62466c4, n = 3, j = 3 <= rec の i 値が表示 &i = 0x7fffd6246694, n = 2 &i = 0x7fffd6246664, n = 1 &i = 0x7fffd6246634, n = 0 &i = 0x7fffd6246664, n = 1 &i = 0x7fffd6246694, n = 2 &i = 0x7fffd62466c0, n = 3 &j = 0x7fffd62466c4, n = 3 &i = 0x7fffd62466f4 => rec で利用された変数領域が、 rec2 で、再利用されている ( 同じアドレスが使われている ) => 変数は、自動的に割り当てられるだけでなく、 自動的に、解放されている */
#include <stdio.h> #include <malloc.h> /* malloc, free のために必要 */ int *get_array(int n) { int *ip; /* (int *) 型の変数 ip の宣言 */ /* 変数 ip には、 (int *) 型の値(ポインタ値が入る) */ /* ポインタ値を持つ変数の事を「ポインタ」と呼ぶ事が多い */ int i; ip = (int *)malloc ( n * sizeof(int) ); /* malloc : 指定されたサイズ(byte 数) の領域を 確保し、その先頭のアドレス値を(void *)型のポインタ値として返す !! 「(void *)」型の値は、ポインタ値(アドレス値)だが、 !! そのアドレスにあるセルの型は(形式的には)「void」である !! => これは、「型」が不明(その値を直接操作できない) !! <= キャストを利用して、操作できるようにする int ip[n]; の宣言と同じ状況が生まれる */ for ( i = 0; i < n; i++ ) { ip[i] = i * i; /* *(ip + i) : ip が (int *) 型 ip + i は アドレス値が ip + (i * sizeof(int)) 型情報は (int *) 型 これに * をつければ int 型の変数 */ } return ip; /* ポインター値を返す */ } int main(int argc, char *argv[]) { int *p; int i; p = get_array ( 10 ); /* 領域が確保 */ for ( i = 0; i < 10; i++ ) { printf ( "p[%d]=%d\n", i, p[i] ); /* 関数 get_array の中で(malloc で)確保した領域 が、関数の終了後も利用できる => 利用可能な範囲 (プログラムが終わるか..) その領域を解放するまで 解放するには、 その領域を関数 free に指定すればよい */ } free ( ip ); /* 関数 malloc が返した値(ポインタ値)と同じ「アドレス値」 を free に渡す必要がある free ( ip + 5 ); 気持ち : get_arry で整数値 10 個分確保 そのうちの後ろの 5 個分だけ返す <= できない 領域の確保と、解放は、1:1 にする必要がある もし、沢山確保して、一部開放するような事がしたいならば、 はじめから、小さな一部を沢山確保して、 不要なものをその確保した単位で解放する malloc で確保した領域は、 不要になった時点で free する必要がある そうしないと、 空きがなくなってしまう ( ゴミだらけになる ) */ return 0; }
2021/12/17 ソフトウェア概論 [前回の復習] 前回の内容 : データ構造 (6) データ構造 データの組み合わせる仕組み データに構造を持たせ、複数の要素を組み合わせて、 目的のデータを表現する仕組み 組み合わせ方法 データ型とデータ型の直積を取る 構造体 複数のデータ型を組み合わせる 配列 同じ型のデータ型を複数並べる C 言語でのデータ構造 データの組み合わせではなく、変数の組み合わせを作る 変数にそれぞれ、データを記録させる事により、 その(構造化された)変数に入っているデータが、 基本となるデータを組み合わせた複雑なデータとして表現される 例: 二次元平面上の点 => x 座標と y 座標 ( それぞれ浮動小数点数 ) の対 点 P の表現 ( 1.0, -2.4 ) 点 Q の表現 ( 0.0, 0.0 ) ... 実数 x 実数 ( 二つの実数の直積空間 ) の要素 # C 言語では、この「実数値の対」を直接表現する事ができない # => 実数の値の対を保存できるような変数を作る # この変数に値を入れることにより、その変数の値として、 # 目的の表現を得る struct { 構造体の宣言 double x; x, y 座標のデータを保存する変数 double y; } P, Q; x,y の変数の対で、それぞれが座標値を保持する事により (間接的に..) 座標値の対を表現する 点 P の表現 ( 1.0, -2.4 ) P.x = 1.0; P.y = -2.4; 点 Q の表現 ( 0.0, 0.0 ) Q.x = 0.0; Q.y = 0.0; 現実の世界(データ型) 点の表現 実数 x 実数 +----- (1.0, -2.4) 1.0 -----------+ | -2.4-+ | | (0.0, 0.0) 0.0 | | | 0.0 | | | | | | | | | 構造体(struct) | | +-------> P, P.x <-------|---+ 変数 P.y<+ Q Q.x Q.y C 言語では、データ型 <=> 変数の組み合わせの仕方 配列名の正体 「ポインター値」を持つ データ型 => 変数の組み合わせ 変数 => メモリ(セルの集まり)の一部 セル自身は、1 byte ( = 8bit [2^8] : 0 ? 255 ) 記録する機能 セルは「記憶」しかいない そこに 何が記録されていて、 それが、 どのような表現なのか、 どのように扱われるか => セルには情報がない C のコンパイラが、 その変数 ( セルに対応 ) の「型情報」として 知っていて、そのように扱う 変数[メモリ]をどのように扱うかは、[メモリに対応する]変数の型できまる 変数 => メモリ 先頭番地[メモリのセルに一つずつ振られている番号]をもっている ----+ アドレス値 +--+ セルの個数 ( sizeof ) ----+ (実行時) | x | +-- ポインター値 型 +-- 型情報 -----+ セルに保存する表現の方法 | (コンパイル時) セルの値をどのように扱うか ----+ ポインタ値のアドレス値を表示するには、 printf の "%p" 書式を利用する 変数には、必ず、対応するメモリ(セルの並び)があるので、 変数から、対応するセルの並びのポインター値を取るには、 変数名の前に「&」を付けるとよい [ポインター値とメモリモデル] ポインター値 +-- アドレス値 ( %p ) / メモリ上の番地 | 実行時の情報 +-- 型情報 +--- サイズ +--- 演算の仕方 コンパイル時の情報 アドレス値と型 (sizeof 演算子) セルの属性 アドレス値 : 先頭セルの番地 sizeof : セルの個数 <-> 連続したセルの個数 「&」演算子 変数 ( セルの並びに対応 ) からポインター値 アドレス値 : 変数に対応するセルの番地 型情報 : 変数型 T 型の変数から取り出されるポインター値の型 (T *) で表現する => ポインター型 例: char * => char 型の変数から作られるポインター char 型へのポインター型 int * => int 型の変数から作られるポインター int 型へのポインタ型 !! ポインター値が持つアドレス値は、 !! 対応する変数の(メモリ上の)位置を表す !! 「位置」を表す気持ちが「へ」で表現されている 配列名 配列を宣言する時に作られる int a[3]; +-------+ a ->a[0]| | +-------+ | | +-------+ | | +-------+ | | +-------+ a[1]| | +-------+ | | +-------+ | | +-------+ | | +-------+ a[2]| | +-------+ | | +-------+ | | +-------+ | | +-------+ 配列名が表現する(ポインタ)値は、 配列の先頭の要素(変数 a[0]) へのポインタ値に a = &(a[0]) より一般的には、 配列名 <-> &(配列名[0]) という関係が成立する [本日の内容] ポインター値の操作 ポインター値 +-- アドレス値 +-- 型情報 ポインター値に対する、整数の加減算は、 => ポインター値のアドレス値を変更する操作に相当する 型情報の変更 => キャストを利用する (型)キャスト(演算子) C 言語では、値に対し、その値(型情報をもっている)を、別の型として扱うように指定できる (値に対する)型の(強制的な..)指定の事を、 (型)キャスト # 本当なら、型は自動的に決まる # => 普通は、値に対して型を指定する必要はない # 場合によって、(自然に[コンパイラが判断して..]決まる)型が不適切な場合 # 適切な型情報を(コンパイラに)指定して、適切な処理を行う必要がある場合がある 例: 割り算の計算 整数同士の割り算は、整数値になる 1/2 -> 0 0.5 にしたい場合は、1.0/2.0 の形で浮動小数点数の割り算にする必要がある キャストの仕方(キャストの表現) 値(をもとめる式)の前に、「(型表現)」を書く事により、 値の型を強制的に「『型表現』が表す『型』」に、 する(キャスト) 1/((double)2) => 1/2.0 ( 演算の一方が浮動小数点型だと、他方も自動的に浮動小数点数型に変わる(型の昇格)) => 1.0/2.0 => 0.5 ポインター値のもつ型情報を変更するには、 キャストを利用する すでに、適切な型を持つポインター値にたいして、 キャストする事に意味があるか ? => 多くの場合意味がない ( できる事をしっている事は重要 ) <= もともと、適切な型を持っていない場合に役立つ [動的なメモリの確保] ローカル変数の宣言 => メモリの一部が自動的に確保され、利用可能 関数(宣言されたブロック)が終了すると、 自動的に無効になる (auto 変数) 変数とメモリの対応関係は、 その変数が宣言されている(ブロックを含む)関数が、 よびだされた時 ローカル変数は、自動的に領域が確保され、 自動的に、解放される => メモリ領域という資源を、効率的に活用(無駄にしない) => その変数に保存されている値は、 その変数が有効な間しか、利用できない # 変数に割り当てられた領域が解放されると、 # その領域に記録されている値(変数の値)は、 # 保証されない ( そもそも普通には参照できない ) # (再利用されてしまうと、値が書き変わる) => もし、 ある値を(保存するメモリ領域)を、 関数をまたいで、利用したい場合 => ローカル変数に保存するのでは、問題がある => ローカル変数を経由せず、 直接、(情報を保存する)メモリを確保できると便利 => malloc 関数の役割
課題プログラム内の「/*名前:ここ*/」の部分を書き換え「/*この部分を完成させなさい*/」の部分にプログラムを追加して、プログラムを完成させます。
Download : 20211217-01.c
/* * 20211217-01-QQQQ.c * * 三角形の形をした配列 * */ /* * 利用方法 * コンパイル * cc -o BASENAME.exe FILENAME * 実行 * ./BASENAME.exe */ #include <stdio.h> #define ARRAY_SIZE_N 10 /* * * 0 a[0][0] * 1 a[1][0], a[1][1] * 3 a[2][0], a[2][1], a[2][2] * 6 .. * a[9][0], a[9][1], a[9][2], .., a[9][9] * */ int get_tri ( int *ary, int n, int m ) { /* ** この部分を完成させなさい */ } void set_tri ( int *ary, int n, int m, int value ) { /* ** この部分を完成させなさい */ } /* * main */ int main ( int argc, char *argv[] ) { int triary[ARRAY_SIZE_N*(ARRAY_SIZE_N+1)/2]; int n; int m; int i; for ( n = 0; n < ARRAY_SIZE_N; n++ ) { for ( m = 0; m <= n; m++ ) { set_tri ( triary, n, m, n * 100 + m ); } } for ( i = 0; i < ARRAY_SIZE_N*(ARRAY_SIZE_N+1)/2; i++ ) { printf ( "triary[%d] = %d\n", i, triary[i] ); } for ( n = 0; n < ARRAY_SIZE_N; n++ ) { for ( m = 0; m <= n; m++ ) { printf ( "triary[%d][%d] = %d\n", n, m, get_tri( triary, n, m ) ); } } return 0; }
$ ./20211217-01-QQQQ.exe triary[0] = 0 triary[1] = 100 triary[2] = 101 triary[3] = 200 triary[4] = 201 triary[5] = 202 triary[6] = 300 triary[7] = 301 triary[8] = 302 triary[9] = 303 triary[10] = 400 triary[11] = 401 triary[12] = 402 triary[13] = 403 triary[14] = 404 triary[15] = 500 triary[16] = 501 triary[17] = 502 triary[18] = 503 triary[19] = 504 triary[20] = 505 triary[21] = 600 triary[22] = 601 triary[23] = 602 triary[24] = 603 triary[25] = 604 triary[26] = 605 triary[27] = 606 triary[28] = 700 triary[29] = 701 triary[30] = 702 triary[31] = 703 triary[32] = 704 triary[33] = 705 triary[34] = 706 triary[35] = 707 triary[36] = 800 triary[37] = 801 triary[38] = 802 triary[39] = 803 triary[40] = 804 triary[41] = 805 triary[42] = 806 triary[43] = 807 triary[44] = 808 triary[45] = 900 triary[46] = 901 triary[47] = 902 triary[48] = 903 triary[49] = 904 triary[50] = 905 triary[51] = 906 triary[52] = 907 triary[53] = 908 triary[54] = 909 triary[0][0] = 0 triary[1][0] = 100 triary[1][1] = 101 triary[2][0] = 200 triary[2][1] = 201 triary[2][2] = 202 triary[3][0] = 300 triary[3][1] = 301 triary[3][2] = 302 triary[3][3] = 303 triary[4][0] = 400 triary[4][1] = 401 triary[4][2] = 402 triary[4][3] = 403 triary[4][4] = 404 triary[5][0] = 500 triary[5][1] = 501 triary[5][2] = 502 triary[5][3] = 503 triary[5][4] = 504 triary[5][5] = 505 triary[6][0] = 600 triary[6][1] = 601 triary[6][2] = 602 triary[6][3] = 603 triary[6][4] = 604 triary[6][5] = 605 triary[6][6] = 606 triary[7][0] = 700 triary[7][1] = 701 triary[7][2] = 702 triary[7][3] = 703 triary[7][4] = 704 triary[7][5] = 705 triary[7][6] = 706 triary[7][7] = 707 triary[8][0] = 800 triary[8][1] = 801 triary[8][2] = 802 triary[8][3] = 803 triary[8][4] = 804 triary[8][5] = 805 triary[8][6] = 806 triary[8][7] = 807 triary[8][8] = 808 triary[9][0] = 900 triary[9][1] = 901 triary[9][2] = 902 triary[9][3] = 903 triary[9][4] = 904 triary[9][5] = 905 triary[9][6] = 906 triary[9][7] = 907 triary[9][8] = 908 triary[9][9] = 909 $