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 関数の役割