前回の内容 : データ構造 (6) 講義 配列と文字列 共通部分 文字の並び 添え字で文字が参照できる 文字列の最後 EOS がある 内容を参照する(変更しない場合) : 共通して利用可能 違う部分 文字配列 文字列 要素の書きかえ 可能(変数) 不能(書きかえ不能属性) C 言語での表現 配列として宣言 "で挟む 利用の仕方 配列名を利用する "で挟んだ対象そのもの 内容を変更する場合は、 文字配列しか扱えない C 言語では、 文字配列を用いて、「文字列」の処理を行う C 言語以前と違う C 言語以前 文字列の扱いは、不便だった C 言語登場 文字列が文字配列を用いて、便利に扱えるようになった 文字列は、C 言語の基本型ではない ( 文字列配列を経由する .. ) # 文字配列を用いて、文字列を操作する事により、 # C 言語の配列への理解が深まる C 言語以後 文字列を基本型として扱う言語がほとんど 前回まとめ 文字配列を用いて、「文字列」の操作が可能になった ! 「文字列」の正体が判明した 「配列名」や「文字列」が持つ「値」が色々と活躍する == [2021/12/10] ポインター 『「配列名」や「文字列」が持つ「値」』は、ポインター値 (メモリモデル)値を記録するための仕組み コンピュータは情報を記録するために「メモリ」もっている メモリーイメージ +---+ 個々のセルには | | <= 一個のセルには 1 byte の情報が記録 個別に番地 +---+ (アドレス) | | が振られている +---+ | | => 直観的には、巨大な文字配列 +---+ | | +---+ | | +---+ | | +---+ C 言語の文字型の変数 char c1; char c2; +---+ c1 | | +---+ c2 | | +---+ c1 = 'A'; +---+ c1 |'A'| <= 一つのセルには 1 byte の情報 +---+ => ASCII 文字 1 つ分の情報を記録する c2 | | +---+ char 型の変数は、メモリの一つのセルに対応 "abc" +---+ |'a'| +---+ |'b'| +---+ |'c'| +---+ |EOS| +---+ 長さ 3 の文字列は、4 つのセルからなっている C 言語の整数型の変数 int i1; int i2; +---+ | | <= 1 つのセルは 1 byte の情報しかもてない +---+ 1 byte = 8 bit = 2^8 個区別 ( 0 〜 255 [=256-1=2^8-1] ) | | => 1 つのセルでは、 +---+ C 言語の一つの整数型変数の機能を実現できない | | +---+ | | +---+ => C 言語の整数型の変数は 4 つのセルをまとめて、実現している ! 4 つ ( 今使っているコンパイラの場合 ) ! 型がいくつのセルを使うかは、 ! sizeof を利用して知る事ができる ! sizeof(char) = 1 ! sizeof(char) <= sizeof(int) int i1; int i2; +---+ i1 | | <= 連続した 4 つのセルで +---+ 一つの整数型の変数を実現する | | +---+ | | +---+ | | +---+ i2 | | +---+ | | +---+ | | +---+ | | +---+ ポイント: C 言語の変数は、いくつか(1以上)の並んだセルで表現される !! 最近の言語は、もっとたくさんのセルを利用する !! => 表現したい型の値以外の情報を持っている !! C 言語の表現は、必要最低限 => 効率はよい !! <= 最低限なので、(他の情報がない..) 機能に乏しい 配列の表現 => 変数が並んだもの => 連続したセルで実現されている int iary[3]; +---+ iary:0x7fffd8169100 | | iary[0] +---+ | | +---+ | | +---+ | | +---+ 0x7fffd8169104 | | iary[1] +---+ | | +---+ | | +---+ | | +---+ | | iary[2] +---+ | | +---+ | | +---+ | | +---+ iary + 1 iary => *iary <-> iary[0] iary + 1 => *(iary + 1) <-> iary[1] iary, iary + 1 <= セルの番地 iary (配列名) : 配列(の要素となる変数)が入っているセルの先頭の番地 iary + n : iary の n 番目の要素(変数)のセルの先頭の番地 ポインター値 番地(%p)を持っていて、 さらに、(見えないくて、コンパイラだけが知っている..) その番地にある変数の「型の情報」をもっている 例: char cary[10]; cary 番地の情報をもつ(実行時) -> %p で表示可能 char 型へのポインタ値(コンパイル時) sizeof で知る事ができる 型の情報を利用して 「*」演算子で、ポインター値から変数(に割り当てられているメモリ上のセル)を参照することが可能になる ポインター値 + アドレス値 ( 実行時 ) => メモリ上のセルの番地 !!! 関数の引数に渡す事ができる「値」は、アドレス値だけ !!! <= 型の情報 !!! 仮引数変数の宣言から型情報を取り出す + 型情報 ( コンパイル時 ) + サイズ => その型の変数が占める byte 数 ( sizeof ) + そのセル(が表現している変数)に入っている値の型 => その値を操作する時に、型情報が利用される プログラミングの場合は、 この二つの値(番地(アドレス)値と、型情報の両方を意識する必要がある) !! 配列名が表す値は、ポインター値 == ポインタ値の操作 ポインタ値の取り出し方 一番簡単な方法 : 配列名(ポインター定数) # 文字列 <-> 文字配列名と同じ振る舞いをする(ポインター値をもっている) 配列の要素 int iary[10]; iary[3] <= 配列の要素 *(iary+3) <-> iary という配列名(ポインタ値)の 3 番目の要素 <-> *((iary + 3) + 0) <-> (iary + 3)[0] iary + 3 も「配列名」と同じ性質をもつ ポインター値に、整数値を加えたものも、ポインター値になる ポインター値に「整数値」を「加える」と 新しいポインター値が作られる 元のポインター値 新しいポインター値 番地 A A + N * sizeof(Type) 型情報 Type Type !! => 配列の一部のアドレス値をもつポインター値が作れる !! !!! そうでない(配列の一部でない)アドレス値も作れる !! !!! => 危険 !! !!! そのポインター値を使って(「*」で)配列の要素でないものが参照できてしまう !! !!! !!! それはメモリの一部なので、セキュリティ上のリスク !! !!! !!! いまのところは OS が、それを監視しているので大丈夫 == C 言語変数は、メモリ上のセルに対応している char 型 => 1 個数 ( sizeof(char) byte ) int 型 => 4 個数 ( sizeof(int) byte ) セルには、必ずアドレスが割り当てられている 配列の要素だけでなく、普通の(単純)変数にも、当然、アドレスがある 変数(名)から、ポインター値(アドレス値)を取り出す演算子 & &変数名 => 変数が割り当てられているセルのアドレス値が得られる 型としては、ポインター型になる ポインター値を関数に渡すと、 関数の中から、ポインター値を経由して、 元の変数の値も変更できる # 配列の挙動と同じ # 配列名(ポインター値)を渡すと、 # それから、配列の要素のポインター値が計算され、 # その計算されたポインター値を使って、要素の値が参照される scanf でなぜ、変数名に & をつけるかというと scanf に、その変数へのポインターを値を渡し、 scanf が入力値を、その変数に代入するために、 利用されている