工学部 6 7 8 9 10 組 ( 奇数学籍番号 ) 担当 : 長谷川英之 情報処理演習 第 7 回 2010 年 11 月 18 日 1
今回のテーマ 1: ポインタ 変数に値を代入 = 記憶プログラムの記憶領域として使用されるものがメモリ ( パソコンの仕様書における 512 MB RAM などの記述はこのメモリの量 ) RAM は多数のコンデンサの集合体 : 電荷がたまっている (1)/ いない (0) で 0 か 1 を表現 計算機は 2 進法 (10 進法でプログラムが書けるのは C コンパイラなどのおかげ ) #include <stdio.h> main() { int a,b; } つまり, 変数を使用するためには, その変数が何番目のコンデンサを使うか アドレス そのコンデンサの状態 メモリの内容 ( 変数の値 ) を把握しておく必要がある. ポインタとはアドレスを扱うためのしくみ a=25; b=12; 変数とメモリとの関係を模式的に示したもの 変数 a b メモリの内容 25 12... アドレス 0 1 2 3... 2 変数のアドレスは, 明示しなければ自動的に割り振られる.
アドレスを扱うためには? 変数とメモリとの関係およびC 言語における表記法 変数 内容 C 言語の表記 アドレス C 言語の表記 a b 25 12 a b 0 1 &a &b 2 3...... #include <stdio.h> main() { int a,b; プログラム h07-1.c } a=25; b=12; printf( a: naiyou, address = %d, %lu n, a,&a); printf( b: naiyou, address = %d, %lu n, b,&b); 内容 a とアドレス &a を表示 b についても同様 3
これまでにない事項 printf 文中の %lu: unsigned long 型を表示するための書式 変数のアドレスは,unsigned long 型で表されるため, アドレス &a,&b の表示に必要. unsigned とは 符号なし の意味. アドレスは正の整数であるため, 符号 ± は不要. unsigned long 型などの変数型については本資料の付録を参照. 実行結果 [xxx]% gcc h05-1.c [xxx]% a.out a: naiyou, address = 25, 3221221876 b: naiyou, address = 15, 3221221872 アドレスは自動的に割り当てられた値 4
プログラム h07-1.c から分かること scanf( %d,&a); キーボード入力した整数値 (%dで指定) を, アドレス &aのメモリに格納 scanfでは, データを格納する場所 ( アドレス ) を指定している printf( %d,a); 変数 aの内容を整数型としてコンソールに表示 printfは内容の表示を行うので &aではなくa 5
ポインタ変数 ポインタ変数 : 変数のアドレスを扱うための変数 定義方法 ( アドレスを記憶する変数の ) データ型 * ポインタ変数名 ) 例 : 整数型変数 a のアドレスをポインタ変数 p に記憶させる場合 #include <stdio.h> main() { int a; int *p; } 6
ポインタ変数を用いたプログラム例 ポインタ変数 ( ここではp) を定義すると, pはアドレス格納用の変数( したがってpの型はunsigned longである ) *pはアドレスpのメモリの内容 ( 例ではアドレスpを使っている変数がint 型なので *pはint 型である ) として扱える. #include <stdio.h> main() { int a; int *p; ポインタ変数 pを定義 a=51; p=&a; プログラム h07-2.c p に変数 i のアドレスを代入 アドレス p のメモリの内容 *p を表示 (p=&a でアドレス p は a のアドレスになっているため結局は変数 a の内容と同じ ) } printf( a: naiyou, address = %d, %lu n,a,&a); printf( p: naiyou, address = %d, %lu n,*p,p); p=&a,*p=a となっているはずです. 実行して確認してみよう. 7
プログラム h07-2.c の模式的な説明 1. 変数 a を定義 (int a;) &a= アドレス 0 1 2 3 4 内容 2. 変数 a に 51 を代入 (a=51;) &a= アドレス 0 1 2 3 4 内容 51 3. ポインタ変数 p に整数型変数 a のアドレス &a を代入 (p=&a;) p,&a= アドレス 0 1 2 3 4 内容 51 アドレス p のメモリの内容 *p とはつまり, 変数 a の内容に等しい. 8
int *p; の定義方法で思い出すこと ファイルの読み込み / 書き込みの際に, FILE *fout; という定義を行った. この場合の fout はファイルポインタと呼ばれます. fout=fopen( test.txt, r ); などの処理を行ったことを憶えていると思います.fopen を実行すると, 開いたファイル test.txt の状態を示すファイル変数が設定されます. ファイルポインタ fout はそのファイル変数のアドレスを格納しておくためのものです. 9
他の型の変数にも対応可能 #include <stdio.h> main() { float i; float *p; i=10.5; p=&i; } printf( i: naiyou, address = %f, %lu n,i,&i); printf( p: naiyou, address = %f, %lu n,*p,p); 10
配列とポインタ プログラムh07-3.c #include <stdio.h> main() { char s[]= the ; char *p; } p=s+1; printf( %c n,*p); 文字列からある 1 文字を抜き出す 配列の場合, 各要素に対応するアドレスが存在 表記 意味 s[0] s[0] の値 ( この例では t ) s[1] s[1] の値 ( この例では h ) s[2] s[2] の値 ( この例では e ) s[3] s[3] の値 ( この例ではヌル文字 ) &s[0] s[0] のアドレス &s[1] s[1] のアドレス (&s[0]+1) &s[2] s[2] のアドレス (&s[0]+2) &s[3] s[3] のアドレス (&s[0]+3) s 配列 sの先頭アドレス (=&s[0]) p=s+1 (=&s[0]+1) により p は配列 s の 2 番目の要素 s[1] のアドレス &s[1] となる. アドレス p のメモリの内容 *p は,s[1]= h 11
プログラム (h05-3.c) の模式的な説明 1. 文字型配列 s を定義, 文字列 the を代入 (char s[]= the ;) &s[0]=s= &s[1]= &s[2]= &s[3]= アドレス 0 1 2 3 4 内容 t h e ヌル 2. ポインタ変数 p の値を, 配列 s の先頭アドレス +1 に設定 (p=s+1;) &s[0]=s= p,&s[1]= &s[2]= &s[3]= アドレス 0 1 2 3 4 内容 t h e ヌル アドレス p のメモリの内容 *p とはつまり,s[1] の内容 h に等しい. 12
今回のテーマ 2: ファイル入出力 ( テキストファイル ) まず, 音声データを http://www.ecei.tohoku.ac.jp/~hasegawa/ からダウンロード ここをクリック 13
今回のテーマ 2: ファイル入出力 ( テキストファイル ) 自分の学籍番号をクリックしてデータが表示されたら 名前を付けて保存 を選択 保存先は必ず自分のホームディレクトリ ( フォルダ ) にして下さい. 自分の学籍番号のディレクトリ ( フォルダ ) があるはずです. 14
ダウンロードしたデータの構造 保存したファイルを emacs で開いてみて下さい. 1 番目のデータ 2 番目のデータ 保存先されているデータの個数 N N 番目のデータ 15
#include <stdio.h> main() { int i,n; float sound[90000]; FILE *fp; } テキストファイルのデータを読み込み配列に格納 fp=fopen( b0tb0000.txt, r ); fscanf(fp, %d,&n); for(i=0;i<n;++i) fscanf(fp,"%f",&sound[i]); fclose(fp); fclose 忘れずに printf( N = %d n,n); for(i=0;i<10;++i) printf( %f n,sound[i]); 配列を宣言. 要素の数は emacs でファイルを開いたときに 1 行目に書いてあったデータの個数より多く しておく. fopenは今までと同じ使い方. 読み込みなので, 書き込みモードwではなく読み込みモードrで開くファイルからの読み込みは,fscanf. 基本的に scanfと同じ使い方. 違いは, 読込先 fpの指定. まず, 第 1 行のデータの個数 Nを読み込む. N 個のデータを for 文を用いて繰り返し読み込み. 全部表示するのは大変なので, 最初の 10 個のデータをコンソールに表示. 表示された値と, emacs でファイルを開いたときの値が一致していれば正しく読み込めています. 16
課題 Q7-20101118a 16 ページのプログラムを改良し,gnuplot で横軸時間, 縦軸音声データの値, としてグラフ表示できるよう読み込んだデータをファイルに出力するように改造して下さい. データの標本化周波数 f s は 22,050 Hz です. つまり, データとデータの間隔が 1/22,050 秒, ということです. gnuplot で表示するためのファイルの構造 1 番目のデータの時刻 (= 0 秒 ) 1 番目のデータ 2 番目のデータの時刻 2 番目のデータ 3 番目のデータの時刻 3 番目のデータ N 番目のデータの時刻 N 番目のデータ 17
課題 Q7-20101118b 16ページのプログラムを改良する. 1. 音声データを格納した配列 sound[i] (i=0, 1, 2,..., N-1) と別に配列 ei[i] (i=0, 1,2,..., N-1) を定義し, 音声データと同じ標本化周波数 f s =22050 Hzでサンプルされた周波数 f = (100 22050/N) Hzの正弦波 sin(2π f i/22050) をei[i] に格納する. 2. 音声データと正弦波 sin(2π f i/22050) の積 sound[i]*ei[i] を全ての i (i=0, 1, 2,..., N-1) について計算し, さらに別の配列 eip[i] に格納して下さい. 周波数 f の正弦波 sin(2π f t) について,i 番目の点の時刻はi/22050なのでsin(2π f i/22050) と表現しています. eip[i] をグラフで確認するなど, 適宜処理を各自の興味で加えて下さい. この処理を何に使うかは次回講義でお知らせします. 音声データの拡大図 正弦波データの拡大図 i=0 1 i=0 1 18
付録 : 変数の型 各変数は, 型により扱えるデータなどが異なる. 文字 char unsigned char 整数 short int long unsigned short unsigned int unsigned long 実数 float double 8 bit 8 bit, 符号なし 16 bit 32 bit 32 bit 16 bit, 符号なし 32 bit, 符号なし 32 bit, 符号なし 32 bit 64 bit bit 長は,2 進数における桁数に対応する. 19