基礎演習 3 C 言語の基礎 (5) 第 05 回 (20 年 07 月 07 日 ) メモリとポインタの概念 ビットとバイト 計算機内部では データは2 進数で保存している 計算機は メモリにデータを蓄えている bit 1bit 0 もしくは 1 のどちらかを保存 byte 1byte 1bitが8つ集まっている byte が メモリの基本単位として使用される メモリとアドレス メモリは 1byte を保存できる場所が大量にある それぞれの場所には アドレスがふられている メモリの概念図 0000 0001 0002 0003 0004 0005 0006 アドレスがふられている C 言語の変数とメモリ C 言語で用いる変数等も 実際の値はメモリ上にある メモリ上のどこに置くかとかは OS やコンパイラが適時やってくれる アドレスの参照とは C 言語では 実際にどこにデータが保存されているのか プログラム中で参照できる 0000 0001 0002 0003 0004 変数 T これは此処に入れるか 計算機 変数 T を使用と 人 0000 0001 0002 0003 0004 変数 T 計算機 0002 です 変数 T のアドレスは? 人
アドレスの参照の方法 変数の前に & をつけると その値をどこに保存しているか ( アドレス ) を参照できる例 : #include <stdio.h> pirntf("%u n",&i); &i とすることで i が保存されているアドレスを参照できる アドレスにある値の参照 どこに保存されているか はわかる 次にやりたいことは? その場所に入っている値は何? アドレスの前に * をつけると そのアドレスに保存している値を参照できる 更に C 言語では アドレスを保存する型も存在する ポインタ ポインタとアドレス ポインタ アドレスを保存している変数です このポインタを型として宣言することができます 整数型や字型と合わせて宣言されます 変数 poi はポインタである と宣言するのではない! 変数 poi は整数型の値を保存する場所のポインタ 変数 poi は字型の値を保存する場所のポインタ のように どの型を保存しているアドレスを保存しているのかを区別 例 : ポインタの宣言 (1) #include <stdio.h> int *poi_01; int arr[]; ポインタの宣言の仕方 *poi_01 は整数型である 宣言するときに * をつけると * の次の字は ポインタになる poi_01 の部分が整数型のデータを保存するアドレスを保存するポインタとなる ポインタの宣言 (2) #include <stdio.h> 整数型の変数 i を宣言 整数型の変数 *poi_01 を宣言 int i; poi_01はアドレス i; int *poi_01; サイズの整数型の配列 int arr[]; arrは配列の最初の要素のアドレス アドレスを参照するには 値を参照するには int i; &i i int *poi_01; poi_01( または &*poi_01) *pot_01 int arr[]; arr arr[0] サンプルプログラム #include <stdio.h> int unsigned int i=1; unsigned int *poi_01; unsigned int arr[]={3,2,3,4,5,6,7,8,9,; *poi_01=4; printf("000 %u %u -- -- %u %u -- -- %u %u n",i, &i, *&i); printf("001 %u %u -- -- %u %u n",*poi_01, poi_01); printf("%u -- -- %u %u -- -- %u %u n",arr, arr[0], &arr[0]);
復習 : C 言語と関数 関数の自作の基礎 C 言語の 関数 とは 入力を与えると出力を返すような " もの " です 入力 関数 出力 13/59 個々の関数について 何を何個入力として与えるか 何を出力するか は 決められています C 言語は 基本的に関数からなりたちます 関数は 自作することもできます 関数の自作とは 同様の処理をプログラムの彼方此方で使う場合 同じソースコードを何回も書くのは面倒! 関数として準備しておこう! C 言語では 自分で関数を自作することができる 自作するためには 以下が必要 関数の入出力をプロトタイプ宣言として書く 関数の実際の動作を書く 復習 : 典型的なソースファイルの構成 #include<pow.h> x=pow(); y=sqrt(); 関数が並んでいる ヘッダファイルを include どんな入出力の関数を使うのかを指定 main は必ずある 既存の関数の実際の動作は ライブラリにバイナリとして存在 関数を自作するには #include<pow.h> x=pow(); y=sqrt(); 関数が並んでいる ヘッダファイルを include どんな入出力の関数を使うのかを指定 ソースファイル中にプロトタイプ宣言として記述 mainは必ずある実際の関数の動作は ライブラリにバイナリとして存在 関数制作の計画 1. 関数の設計をする 入力は何か? 出力は何か? 処理内容は何か? 2. 関数の入出力をプロトタイプ宣言として書く 3. 関数の実際の動作を書く 入力 関数 出力 内容はソースファイル中に記述する
典型的なファイルの構成 int int sample01(int x,int y); y); どんな入出力か プロトタイプ宣言を記述 プロトタイプ宣言の記述 入力関数出力 int int int value=sample01(,12); printf("%d n",value); int int sample01(int x,int y){ y){ x=x*y*; return x; x; 既存の関数と同様に使用可能 関数の内容を記述する 19/59 戻り値の型を指定 関数名を指定 関数内で使用する変数名と 引数の型を指定 int intsample01(int max,int st); その関数の入出力を指定する 引数の型を宣言するときは 変数毎に型を宣言 double x,y,z のように まとめて宣言することはできない 戻り値は1つだけなので 型だけを指定する 20/59 実際の動作を書く部分 戻り値の型を指定関数名引数と引数の型を指定 int sample01(int max,int st){ max=max+st+2; return max; 戻り値を指定 構造戻り値の型を指定関数名 ( 引数と引数の型を指定 ){ ){ return 戻り値 ; 戻り値について 関数の戻り値の型として指定できるもの 今まで変数として宣言してきた型 返り値を返さない場合は void を指定する 関数は return の次にある値を返り値として返す 返り値が無い場合は 単にreturn; とすればよい returnが無い場合は 関数の末尾まで実行したら 戻り値を返さずに 関数が終了する mainも関数なので 返り値がある 型を指定しなかった場合は intを返す関数と解釈される return の次にある値を返り値として返す 21/59 22/59 変数と引数について 変数は それぞれの関数が持っている 同じ名前でも main が持っている x と sample01 が持っている x は別物! 関数の引数には 2パターンあります 値渡し関数に 値を渡します アドレス渡し関数に 変数のアドレスを渡します それぞれの特徴を捕らえて 適宜使い分ける必要があります 23/59 値渡しのイメージ図 値渡し : 関数 sa (int x) を main 内で sa (k) として使用 k 値を渡す 箱を準備 x x 自分の箱 x に保存 作業 返値を返す 50 廃棄 x k 24/59
アドレス渡しのイメージ図 値渡し : 関数 sa (int *x) を main 内で sa (&k) として使用 *k 場所を渡す *k x と命名 返値を返す 50 *k 値渡し 関数の引数として 値を渡す方法 関数に渡されるのは あくまでも 値のみ です 変数が渡されるわけではありません 関数内部では 渡された値を 自前の変数に代入し 処理を行います 自前の変数 tmp に を代入するよ int sample01(int tmp){ 50 int scan_value=; 値は tmp=tmp+2; value=sample01(scan_value); だよ return tmp; *k*x 作業 *k *x 25/59 26/59 アドレス渡しその 1 関数の引数として ( 変数の ) アドレスを渡す方法 関数に渡されるのは 変数のアドレス です 関数内部では 自分で使用する変数のアドレスを 引数として渡されたアドレスに設定します 関数内部と外部で名前は異なる変数かもしれませんが 保存する場所は一緒になります int scan_value=; value=sample01(&scan_value); アドレスを渡すよ そのアドレス使うよ int sample01(int *tmp){ *tmp=*tmp+2; return *tmp; 27/59 アドレス渡しその 2 int scan_value=; value=sample01(&scan_value); アドレスを渡すので & がついている 関数 main 内の scan_value のアドレス = アドレスを貰うので ポインタを宣言 int sample01(int *tmp){ *tmp=*tmp+2; return *tmp; 関数 sample01 内の *tmp のアドレス 関数 sample01 内で *tmp の値を変えると 関数 main 内の scan_value の値も変わる 28/59 アドレス渡しその 3 どういうときに使うのか? 関数に配列を渡したいとき 配列の内容を まとめて値渡しする事はできない 関数から 2 つ以上の結果を手に入れたいとき 戻り値は 1 つしか指定できない! 変数のアドレスを渡して そのアドレスに保存されている値を関数内で弄ってしまう 簡単な例 : int int v,w,value; 省省 value=sample02(v,&w); 省省 double sample02(int x, x, int int *y){ *y){ x++; x++; *y=x; return x; x; main 中の vの値 wのアドレス sample02 中の xの値 *yのアドレス sample02 中で *y の値を変更 = main 中の w の値を変更 sample02 中で x の値を変更 main 中の v の値を変更 29/59 30/59
例 : 値渡しとアドレス渡し 1-1 例 : 値渡しとアドレス渡し 1-2 その 1/2: int int sa01(int x,int *y, *y, int int *z); *z); int int int int value=50; int int *k; *k; int int x=20,y=30; *k=; printf("000 %d %d %d %d %d %d %d %d %d %d n", n", &value, &x,&y,k); value=sa01(x, &y, &y, k); k); printf("001 %d %d %d %d %d %d %d %d %d %d n", n", &value, &x,&y,k); 31/59 その 2/2: int int sa01(int x,int *y, *y, int int *z){ *z){ int int k; k; x=x*; k=x+ k=x+ *y *y + *z; *z; *y=20; *z=500; printf("002 %d %d %d %d %d %d %d %d n", n", &k,&x,y,&*z); return k; k; 32/59 例 : 値渡しとアドレス渡し 2-1 例 : 値渡しとアドレス渡し 2-2 その 1/2: int int sa01(int x,int *y, *y, int int *z); *z); int int int int value=50; int int *k; *k; int int x=20,y=30; *k=; printf("000 %d %d %d %d %d %d %d %d %d %d n", n", value, x,y,*k); value=sa01(x, &y, &y, k); k); printf("001 %d %d %d %d %d %d %d %d %d %d n", n", value, x,y,*k); 33/59 その 2/2: int int sa01(int x,int *y, *y, int int *z){ *z){ int int k; k; x=x*; k=x+ k=x+ *y *y + *z; *z; *y=20; *z=500; printf("002 %d %d %d %d %d %d %d %d n", n", k,x,*y,*z); return k; k; 34/59 スコープその 1 変数のスコープ 35/59 それぞれの場所で宣言された変数は どこで参照可能か? 関数 main の中で宣言 (value) 関数 sample01 の中で宣言 (i, value, max) 関数 main と sample01 の外 (scan_value) 36/59
スコープその 2 スコープその 3 それぞれの場所で宣言された変数は どこで参照可能か? 37/59 全体で参照できる scan_value sample01の中でのみ参照できる i,value value mainの中でのみ参照できる 38/59 スコープその 4 スコープその 5 全体で参照できる scan_value sample01の中でのみ参照できる i,value 参照できる範囲が被ってないので 名前が同じでも問題ない value mainの中でのみ参照できる 39/59 int st,value; st=sample01(value) 全体で参照できる ( 大域変数 ) sample01 の中でのみ参照できる ( ローカル変数 ) main の中でのみ参照できる ( ローカル変数 ) 大域変数を使用しないなら 関数間の値の受け渡しは 引数や戻り値を通して行う 40/59