#include <stdio.h> int main(void) { int a; int b; char c; a = 1; b = 2; c = 'A'; printf ( "a = %d, &a = %p\n", a, &a ); printf ( "b = %d, &b = %p\n", b, &b ); printf ( "&a + 1 = %p (%ld)\n", &a + 1, sizeof(int) ); printf ( "&c = %p, &c + 1 = %p (%ld)\n", &c, &c + 1, sizeof(char) ); printf ( "&a + 1 = %p, *(&a + 1) = %d\n", &a + 1, *(&a + 1) ); /* 以下の命令はひどい命令 */ printf ( "前 : b = %d\n", b ); *(&a + 1) = 100; /* 変数 b の値が変化する */ /* この代入で「b」と表現がないのに.. */ /* 変数 b の値が変化する !! */ printf ( "後 : b = %d\n", b ); return 0; }
#include <stdio.h> int main(void) { int a = 10; double b = 10.0; /* 実数の観点からは、次の計算はともに 10/3 の計算 */ printf ( "a/3 = %d\n", a/3 ); /* 整数同士の割り算の結果は整数になる */ printf ( "b/3.0 = %f\n", b/3.0 ); /* 浮動小数点数同士の割り算は浮動小数点数 */ /* もし、整数型変数 a の値に対し、浮動小数点数の割り算をしたい場合 */ /* a の値を浮動小数点数に「型変換」すればよい */ /* 具体的には 「10」という整数値を「10.0」という浮動小数点数にする */ /* => キャスト演算子を用いる */ /* キャスト演算子 構文 : (型名) (double) : 浮動小数点数への型変換キャスト演算子 (int) : 整数型への型変換キャスト演算子 意味 : 式の前にキャスト演算子を置く事により、 その式の値を指定した型に変換する (double)10 => 10.0 # 整数型の 10 を浮動小数点数の 10.0 に変換 (int) 10.0/3.0 => 3 # 浮動小数点数型の 3.333 を整数型の 3 に変換 # 小数点以下の切り捨て */ printf ( "((double)a)/3.0 = %f\n", ((double)a)/3.0 ); /* ((double)a)/3.0 -> ((double)10)/3.0 -> 10.0/3.0 -> 3.333 */ printf ( " (double)a /3 = %f\n", (double)(a/3) ); /* (double)(a/3) -> (double)(10/3) -> (double)(3) -> 3.0 */ printf ( "(double)((int)3.3) = %f\n", (double)((int)3.3) ); /* (double)((int)3.3) -> (double) 3 -> 3.0 !! 3.3 -> 3.0 */ return 0; }
#include <stdio.h> int main(void) { /* 同じ型の計算は、同じ型になる事が多い */ printf ( "整数型同士 : 10/3 = %d (整数型)\n", 10/3 ); printf ( "浮動小数点数型同士 : 10.0/3.0 = %f (浮動小数点数型)\n", 10.0/3.0 ); /* 型の昇格(異なる型間ので演算) */ /* 整数型同士の計算は整数だが、 整数型と浮動小数点数型の混在は、浮動小数点数の計算になる */ printf ( "整数/浮動小数点数 : 10/3.0 = %f (浮動小数点数)\n", 10/3.0 ); printf ( "浮動小数点数/整数 : 10.0/3 = %f (浮動小数点数)\n", 10.0/3 ); return 0; }
#include <stdio.h> int main(void) { int a; a = 10; printf ( "a = %d\n", a ); /* ??????? & ??????A????? * ????????????? */ printf ( "*(&a) = %d\n", *(&a) ); /* ?l???Q?? */ return 0; }
#include <stdio.h> void sub ( int *p ) { *p = 999; /* 代入 */ /* 変数 p の値は変更していない */ /* 変更しているのはあくまでも、 変数 p が持っているポインタ値に 対応している(ポインタ値がさしている)変数の値を変更している */ } int main(void) { int a; int b; a = 10; b = 20; printf ( "前: a = %d\n", a ); sub ( &a ); /* p <- &a; *p <- 999 => *(&a) <- 999 a <- 999 ポインタ値を関数の実引数に指定して、関数に渡すと、 そのポインタ値を経由して(間接的に..)、その変数の値が変更できる 変数の変更を関数で行う事ができる cf. scanf で変数に & をつけたか ? 変数に(キーボードから入力した値を)代入したかったから scanf の中で、* を利用して、代入している */ printf ( "後: a = %d\n", a ); printf ( "前: b = %d\n", b ); sub ( &b ); printf ( "後: b = %d\n", b ); return 0; }
#include <stdio.h> void sub ( int v ) { printf ( "v = %d\n", v ); } int main(void) { int a; int b; a = 10; b = 20; sub ( a ); /* v = a ( v = 10 ) */ sub ( b ); /* v = b ( v = 20 ) */ return 0; }
#include <stdio.h> int main(void) { int a; a = 10; printf ( "a = %d\n", a ); /* 変数名に & をつけ、さらに * をつけると元に戻る */ printf ( "*(&a) = %d\n", *(&a) ); /* 値を参照 */ printf ( "前: a = %d\n", a ); *(&a) = 20; /* 値の参照のみならず、代入にも(代わり)使える */ /* a = 20 と同じ結果 */ printf ( "後: a = %d\n", a ); return 0; }
前回の内容 : データ構造 (5) 講義 メモリモデルと番地(アドレス) メモリモデル(コンピュータの中のメモリの実態) セル(1 byte の情報が記録できるモノ)の並びがメモリ 個々のセルには、番地が振られている セルを参照する場合は、番地を利用する 値を取り出したい場合 値を保存(代入)したい場合 cf. C 言語の変数との違い 変数の保存できるサイズは、色々 char -> 1 byte int -> 4 byte 変数を参照する場合は「変数名」を利用する メモリと C 言語の変数の関係 1 つ以上の並んだセルが、変数に対応 番地と変数名が対応 !! C言語の変数は、直接、コンピュータのメモリに対応 !! => 効率が良い !! <= 危険 C 言語の変数と、メモリの間の関係を利用して、 (直観的には..) メモリを直接操作するような命令がある 間接(参照)演算子「*」とアドレス演算子「&」 & : 変数名から、番地(のようなもの)を作る仕組み * : 番地(のようなもの)から、変数名を作る仕組み C 言語の変数 メモリの世界 1 byte int a; +-------+ 100 | | a +-------+ | | +-------+ | | +-------+ | | +-------+ int b; 104 | | b +-------+ | | +-------+ | | +-------+ | | +-------+ & a -----------> &a = 100 b &b = 104 * a <-> *100 <---------- 100 b <-> *104 <---------- 104 番地(のようなもの) printf の "%p" で出力すると、「番地」が表示される C 言語では、&でえられた「番地のようなもの」を「ポインタ値」 !! 「番地」と「ポインタ値」の違い !! 「番地」は単なる整数値 !! 「ポインタ値」は「番地」に「型情報」を付けたもの ポインタ値 +---- 番地(実行の時の情報) : 整数値 | +---- 型情報(コンパイル時の情報) | +---+--- サイズ ( いくつのセルに対応するか ) | sizeof(型名) +--- どのような演算を行うか ? !! 型情報は、変数が持つ情報 !! 変数の宣言の時に定まる 例: 整数型変数 a ( int a; ) に対するポインタ値 &a は %p で出せる番地の情報 整数型なので、 サイズ ( sizeof(int) : いくつのセルを占有するか ) 計算をするときには、整数計算をする 型変換と型の昇格 型変換 : ある型の値を別の型の値にする => ある型の値を、別の型の対応する値に変換する 例: 整数値と浮動小数点数値 => ともに、実数値を表現している 整数値の 1 と浮動小数点数の 1.0 は、同じ実数値 1 を表現 !! 型の変換が必要な理由 !! => (型情報の中に、演算の情報が含まれるので) !! 異なる型の演算がしたい場合に、型変換が必要 型変換を明示的に行う場合 => キャスト演算子を用いる キャスト演算子 構文 : (型名) (double) : 浮動小数点数への型変換キャスト演算子 (int) : 整数型への型変換キャスト演算子 意味 : 式の前にキャスト演算子を置く事により、 その式の値を指定した型に(型)変換する (double)10 => 10.0 # 整数型の 10 を浮動小数点数の 10.0 に変換 (int) 10.0/3.0 => 3 # 浮動小数点数型の 3.333 を整数型の 3 に変換 # 小数点以下の切り捨て !! 一般に、「型変換」は、 !! 値を変えずに、型のみを変えようとするが、 !! 対応する値がない場合は、値そのものも変わる 型変換が暗黙の内に行われてしまう場合 1. 複数の型が混在する式の場合、 一方の型の値を、自動的に型変換する a) サイズの小さい方から大きい方に変換する char (1) -> int (4) b) 整数型から浮動小数点数型 int -> double !! 計算をするときには、基本 int 型か浮動小数点数型の事が多い => 型の昇格 2. 代入が行われる場合、 右辺(式の値)の型を左辺(変数)の型に変換する !! 関数呼び出しの実引数の指定は、 !! 結果的に、仮引数変数への代入になる !! => ここでも必要に応じて、型変換が起きる == ポインタを利用したプログラム 確かに、ちょっと複雑(便利さとのトレード) 特に、間違っている場合に、その挙動の予測ができにくい => 間違ったの時のリカバーが難しい !! 挙動が変だったら、「ポインタを疑え」 !! (正しく記述された..)ポインタの振る舞いを理解するには、 !! メモリモデルを、念頭に考えればよい !! ポインタは非常に便利 !! (失敗すると面倒なので..) 多用しない !! => 必要な場合のみ使う !! 「必要」:ポインタを使わないとできない事を利用する場合 変数名に「&」をつけると、「ポインタ値」が得られる ポインタ値は具体的には(実行時に) 変数が対応しているメモリセルの番地になっている # どの番地になるかは、実行時にならないと決まらない # 番地の値そのものは、意味がない # !! しかし、ポインタ値から計算されたポインタ値(自分自身を含む) # !! を操作する事には意味がある # !! => ポインタ値をコピー(代入)して使う ポインタ値の型 &を付けた変数の型に * をつけたもの 例: int a; ( int 型変数 ) &a -> 「int *」型になる (int へのポインタ型/int ポインタ/int 型へのポインタ) # *(&a) -> int 型 double b; ( double 型変数 ) &b -> 「double *」型になる !! 「char *」を以前は、『文字列型』 !! 実は、「char へのポインタ型」である !! 実際は、「char の配列型」を表している 配列名は、その型へのポインタ値を持つ定数 例: int a[10]; 配列名 a の型 : (int *) 型 配列名 a の値 : &a[0] (ポインタ値) 配列名 a は定数値 ( 変更できない ) !! a[0] (a[n]) は変数 !! a 自身は定数 ポインタ値を保持する変数も宣言できる ポインタ値を取る変数の事を、ポインタと呼ぶ ポインタ変数が保持するポインタ値によって、 どの変数を「指している(pointing)」かを示す事ができる cf. 配列の添え字 => 指している要素の位置を保持する事により、 配列の要素を指し示す事ができる !! 添え字は、式 !! => 柔軟性がある !! 便利なので多用される 変数名に & を付けたもの(ポインタ値)は、 それに * を付ける事により、「その変数『自身』」と同じ振る舞いをする 「int a;」 の時(「a」 が変数名の時) 「a」と「*(&a)」は、「同じ振る舞い」をする 「&*ポインタ値」は「ポインタ値」と同じ(値) !! & と * は(互いに..)逆演算子になっている ポインタ値の計算 cf. 「変数」そのものは(代入以外)計算の対象ではない => 計算の対象は、「変数の値」 # 「ポインタ値」は「値」なので、「計算の対象」になる ポインタ値 + 整数値 => ポインタ値 ポインタ値 +- 番地(アドレス値) | +- 型情報 ( (型名 *) ) + 整数値する事により 番地は、「+ sizeof(型) * 整数値」 となる 型情報はそのまま 例: char vc; char *cp = &vc; /* アドレス値 100 */ /* cp +- 100 | +- (char *) */ cp + 3 +- 100 + sizeof(char) * 3 = 103 | +- (char *) int vi; int *ip = &vi; /* アドレス値 100 */ /* ip +- 100 | +- (int *) */ ip + 3 +- 100 + sizeof(int) * 3 = 112 | +- (int *) # +n だけでなく -n も可能 ポインター値どうしの引き算もできる => 結果は、整数値 # 引き算は、足し算の逆演算になっている p, q がともに、同じポインタ型の時 q - p は整数になるが、その値は q = p + (q-p) が成立するような形での (p-q) の値 int *p = 100; int *q = 112; q - p = (112-100)/sizeof(int) = 3 p + 3 = 100 + sizeof(int)*3 = 112 = q p[n] <-> *(p + n) int *p; (int *) (char *)p (char *) にできる
課題プログラム内の「/*名前:ここ*/」の部分を書き換え「/*この部分を完成させなさい*/」の部分にプログラムを追加して、プログラムを完成させます。
Download : 20201204-01.c
/* * 20201204-01-QQQQ.c * 三角形の形をした二次元配列 * * a[0][0] * a[1][0] a[1][1] * a[2][0] a[2][1] a[2][2] * .. * a[9][0] a[9][1] a[9][2] .. a[9][9] * */ #include <stdio.h> /* * */ #define ARRAY_SIZE 10 #define BASE_SIZE ((ARRAY_SIZE+1)*ARRAY_SIZE/2) /* * main */ int main ( void ) { int b[BASE_SIZE]; /* 三角形の領域 */ int *a[ARRAY_SIZE]; /* 三角形の配列 */ int i; int j; /* 領域の初期化 */ for ( i = 0; i < BASE_SIZE; i++ ) { b[i] = 1000 + i; } /* 三角形の領域の構築 */ a[0] = &b[0]; /* 先頭 ( a[0] = b の方が感じが出ている ) */ for ( i = 1; i < ARRAY_SIZE; i++ ) { /* 三角形の配列を作る */ /* ** この部分を完成させなさい */ } /* 三角形の領域の参照 */ for ( i = 0; i < ARRAY_SIZE; i++ ) { printf ( "%d : ", i ); for ( j = 0; j < i + 1; j++ ) { printf ( "%d ", a[i][j] ); } printf ( "\n" ); } return 0; }
$ ./20201204-01-QQQQ.exe 0 : 1000 1 : 1001 1002 2 : 1003 1004 1005 3 : 1006 1007 1008 1009 4 : 1010 1011 1012 1013 1014 5 : 1015 1016 1017 1018 1019 1020 6 : 1021 1022 1023 1024 1025 1026 1027 7 : 1028 1029 1030 1031 1032 1033 1034 1035 8 : 1036 1037 1038 1039 1040 1041 1042 1043 1044 9 : 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 $