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