前回の内容 : データ構造 (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 *) にできる