#include <stdio.h> int main(void) { int i; int j; char c; char d; printf ( "&i = %p\n", &i ); printf ( "&j = %p\n", &j ); printf ( "&c = %p\n", &c ); printf ( "&d = %p\n", &d ); printf ( "sizeof(*(&i)) = %ld\n", sizeof(*(&i)) ); printf ( "sizeof(*(&c)) = %ld\n", sizeof(*(&c)) ); /* &i, &c が、単にアドレス値だけ持っているわけではない.. */ /* => 実行時にはない */ /* sizeof は(関数のようにみえるが関数ではなく..) コンパイル時で、計算される printf ( "sizeof(*(&c)) = %ld\n", sizeof(*(&c)) ); => printf ( "sizeof(*(&c)) = %ld\n", 1 ); */ return 0; }
#include <stdio.h> int main(void) { printf ( "sizeof(char) = %d\n", sizeof(char) ); printf ( "sizeof(int) = %d\n", sizeof(int) ); printf ( "sizeof(double) = %d\n", sizeof(double) ); return 0; }
#include <stdio.h> int main(void) { printf ( "離散型のサイズ:\n" ); printf ( "sizeof(char) = %lu\n", sizeof(char) ); printf ( "sizeof(short) = %lu\n", sizeof(short) ); printf ( "sizeof(int) = %lu\n", sizeof(int) ); printf ( "sizeof(long) = %lu\n", sizeof(long) ); printf ( "連続型のサイズ:\n" ); printf ( "sizeof(float) = %lu\n", sizeof(float) ); printf ( "sizeof(double) = %lu\n", sizeof(double) ); return 0; }
#include <stdio.h> #define SIZE 3 int main(void) { int iary[SIZE]; /* 配列の変数宣言なので固定サイズ */ int i; for ( i = 0; i < SIZE; i++ ) { printf ( "%d 番目の整数値を入力してください : ", i ); scanf ( "%d", &iary[i] ); /* scanf ( "%d", iary + i ); */ } printf ( "入力した数値を逆順に出力します : \n" ); for ( i = 0; i < SIZE; i++ ) { printf ( "iary[%d] = %d\n", SIZE - i - 1, iary[SIZE - i - 1] ); } return 0; }
#include <stdio.h> #include <malloc.h> /* malloc/free を使う場合宣言 */ int main(void) { int asize; /* 確保するサイズ */ int *ip; /* int 型へのポインタ変数 */ /* 確保したメモリの領域の先頭アドレス */ int i; printf ( "入力するデータ数を指定してください : " ); scanf ( "%d", &asize ); ip = (int *) malloc ( sizeof(int) * asize ); /* int 型の変数 asize 個分のメモリを確保 */ for ( i = 0; i < asize; i++ ) { printf ( "%d 番目の整数値を入力してください : ", i ); scanf ( "%d", &ip[i] ); /* scanf ( "%d", ip + i ); */ } printf ( "入力した数値を逆順に出力します : \n" ); for ( i = 0; i < asize; i++ ) { printf ( "ip[%d] = %d\n", asize - i - 1, ip[asize - i - 1] ); } free ( ip ); /* 確保したメモリを解放する */ return 0; }
前回の内容 : データ構造 (6) 講義 ポインタ型 変数のアドレスを利用して、 変数の内容を間接的に操作するためのデータ型 値 変数のアドレス値 : printf ( "%p" ) で出力する値 変数に対応するメモリの先頭セルのアドレス(番地) そのアドレスに置かれている変数の型の情報ももっている この型情報 ( コンパイル時の情報 / 実行時にはない ) sizeof(int) コンパイル時点 「int」という型情報をもっている この型情報を利用して、コンパイラが、 sizeof(int) を 4 に変換する 実行時点 「4」という整数値しかもっていない コンパイル時 ポインタ値 -+-- アドレス値 ( 実行時 ) | +-- 型情報 ( 「どんな型」の変数のアドレスか ) => * を付けると、その型の変数としてふるまうモノになる +---+-- どんな範囲の値/どんな演算 | +-- 型のサイズ : sizeof (型名) => その型の値を保存する変数が メモリ上で、占めるセル数 アドレス演算子「&」 変数(のようふるまうモノ:メモリのセル並び)のポインタ値を得る 間接(参照)演算子「*」 ポインタ値から変数を得る !! ポインタ値は、常に、対応する変数と対に考える !! 変数(メモリのセル並び)に関する情報がポインタ値 !! 変数の素描 !! 名前で区別 !! 値を参照したり、代入したりできるもの !! # 実態 : メモリセルの並び !! ポインタ値をもっているもの !! # 名前を使って操作する代わりに !! # ポインタ値を使って操作できる !! # => 変数名を使って操作できる 変数にできる事 変数名 値の代入と参照 値でないの値の操作ができない ポインタ値 値の代入と参照 値なので、値の操作ができる !! 値の操作 !! 関数へ渡す/値として返す !! 色々な計算ができる 「変数」を導入した理由 変数宣言 変数名 名前なので、区別がしやすい 変数を作る(メモリの一部を占有) ポインタ値 値のなので、区別がしにくい 変数を作ってから取り出す情報 型変換と型の昇格 型が違う 型 値の取りうる範囲 ( その型の値が入る集合 ) char (ASCII Code で表現される)「文字」なので char 型の値の集合 = { ... 'A', 'B', .., 'a', .. } int 整数値 ( - 2^31 ? 2^31 - 1 ) int 型の値の集合 = { n \in Z | -2^31 <= n <= 2^31-1 } double ( 実数値の一部 ) double 型の値の集合 { .., -0.1, 0.0, 1.0, 2.0, 3.5, ... } その型の値に対する演算の集まり int 型 : 四則 ( +, -, *, / ) や余り ( % ) !! int 型の割り算の結果は int 値 ( 小数点以下、切り捨て ) double 型 : 四則 !! double 型の割り算の結果は double 型 ( 小数点数以下も計算 ) # どんな演算ができるかだけででなく、 # 演算する内容(それを実行するプログラムが違う)が異なる 一つの式の中に、複数の型が含まれている場合の計算 !! 数学の場合は、自然に、大きな集合の中で計算するだけ !! 例: 部分空間の要素の計算は、全体の空間の要素の計算に一致する !! C 言語の場合は、上記の関係が保証されれない !! 例 : int 型にも、double 型にも 1 (1.0), 2 (2.0) !! 1/2 -> 0 !! 1.0/2.0 -> 0.5 型が混在する場合は、 (一つの型の中で[矛盾なく]計算するため) 値の型変換 ( 狭い方から広い方への変換 : 型の昇格 ) が行われる 1/2 -> 0 ( int 型 x int 型 -> int 型 ) 1.0/2.0 -> 0.5 ( double 型 x double 型 -> double 型 ) 1/2.0, 1.0/2.0 int 型 x double 型 -> double 型 x double 型 # int 型 => double 型 (昇格) 1/2.0 -> 1.0/2.0 -> 0.5 1.0/2 -> 1.0/2.0 -> 0.5 (自然に[コンパイラが..]行う) 型の昇格 => 狭い方から広い方へ 逆の場合や、自動的に起きない場合に強制したい場合の型変換 => キャスト演算子を使う キャスト演算 表現 : 「(型名)」 例: (char), (int), (double) 意味 : 値の前に先行しておく事により、 その値に対して、(可能なら..)型変換を行い、 その型(と値)を持つ、値にする 例: 1.0 => double 型の 1.0 (int)1.0 => int 型の 1 (int) 1.5 => int 型の 1 (double)1 => double 型の 1.0 1/2 => int 型の 0 ((double)1)/2 => double 型の 0.5 -> 1.0/2 -> 1.0/2.0 -> 0.5 自然な型変換の場合は、 狭い方から広い方なので、情報が失われない 強制した場合は、逆の事がおき、情報落ちが生じる可能性がある 例 : (int)1.5 -> 1 ( 小数点以下の情報が失われる ) # これらの操作は、コンパイル時に行われる # 実行時では、型の情報は存在しない.. # 型を使った操作 ( キャストなど.. ) は、コンパイル時にすべて、やっている 1 bit の情報 => 2 つのもの内の一つを表現 1 byte = 8 bit => 2^8 = 256 通りの区別ができる 4 byte = 4 * 8 bit = 32 bit => 2^32 = 2^2 * 2^30 = 2^2 * (2^10)^3 '=, 4 * (10^3)^3 = 4 * 10^9 byte 数が多い => 区別できる値が増える => (離散型の場合は) 表現できる値の範囲が増える => (連続型の場合は) 表現の精度も高める事ができる 例: 2 bit => 2^2 = 4 通り 範囲 精度 { 0, 1, 2, 3 } 0 ? 3 1 刻み { 0, 0.25, 0.5, 0.75 } 0 ?0.75 0.25 刻み !! 連続型の場合は、同じ bit (byte) 数でも、 !! 実は、範囲を重視するか精度を重視するかによって、 !! 異なる「浮動小数点数」になる可能性がある !! => IEEE の規格で固定されていて、現在は一通り 色々な基本型 離散型(整数型)の色々な基本系 1 = char <= short (int) <= int <= long (int) 基本、型サイズが(広義)単調増加 型サイズが大きくなると、表現範囲が増える printf ( "%d" ) => int 型の出力 printf ( "%d", 'A' ) 'A' (char 型) => 65 (int 型) => %d で出力 long 型を出力する時には、 "%ld" を使う 連続型(浮動小数点数型)の色々な基本系 float <= double 型サイズが大きくなると、表現範囲と精度が良くなる signed (符合付き) と unsigned (符合無し) 整数型での数の表現の差の違い 符合無しの場合は、0 ? char (符号あり) : -128 ? 127 ( 256 = 2^8 通り ) unsigned char (符号無し) : 0 ? 255 ( 256 = 2^8 通り ) 注 char も実は 0 ? 255 でなく -128 ? 127 # 昔の C コンパイラは char を無条件に unsigned にしているものもあった # 今は(規格上) signed になっている 基本型名の前に unsigned を先行させる事により、符合無しにできる 符合付き 符合無し char unsigned char short int unsigned short int int unsigned int long in unsigned long int unsigned 型を出力する時には、 "%u" を使う unsigned long 型を出力する時には、 "%lu" (sizeof の結果は unsigned long 型) 昇格(型が混在しているときの自動型変換) ともに、離散型あるいは、連続型同士の場合は、型サイズの大きい方へ 離散型と連続型が混在する場合は、連続型にする == 変数宣言 メモリ上の(未使用な..セル)を、その変数のために割り当て、 その(割り当てられたセル)をその変数名で参照(代入)できるようにする機能(を指定する表現) 例: int i; sizeof(int) 個のセル(メモリの一部)が確保され、 以下 i と書くと、このセル(の並び)を指定する事になる !! 変数宣言には、メモリの空いている場所を、 !! (変数として利用するために..)確保する機能を持つ 複数のセル並び ( 変数のようなもの.. ) を、 変数宣言せずに得る事ができれば、 変数名の無い、変数のようなものが実現できる !! 変数宣言 !! 自動的に適切なサイズの変数を確保 !! しかも、名前で参照できる(追加サービス)がある !! 必要なならば & で、ポインタ値もえられる 変数宣言は、コンパイル時の表現 => 実行時点で(個数が..) 変えられない (静的な情報) (単純)変数の個数は、宣言した個数だけ 配列の宣言でも、配列の要素数は、宣言時の固定 # メモリは有限なので、 # 使いっぱなしはこまる # 変数宣言では、不要になったら、自動的に回収される # コンパイラが自動的に行う(ように指示)する # => 後始末の心配はいらない # <= 後始末の自動化を楽にするために、固定長 # # GC : 後始末の複雑な自動化 (固定じゃなくてもよい) # => 動的に変数(のようなもの..)を増やしたい # 実行時で変数の個数や配列の要素を増やしたい 次の二つのライブラリ関数を利用して、動的なメモリ確保が可能 malloc => 空き領域から、指定したサイズをセルを確保する # これを変数として利用す場合は、キャストを行う free => malloc で確保したセルを開放する # 後しまつを自分でする必要がある
課題プログラム内の「/*名前:ここ*/」の部分を書き換え「/*この部分を完成させなさい*/」の部分にプログラムを追加して、プログラムを完成させます。
Download : 20201211-01.c
/* * 20201211-01-QQQQ.c * */ #include <stdio.h> #include <math.h> /* * */ #define EPS (1e-300) /* 1/i! の下限 */ /* * float 型での計算 */ float fexp() { int i; float fe = 1.0; /* fe = 1 + \sum_{i=1}^n 1/(i!) */ float fs = 1.0; /* fs = 1/(i!), 1/(0!) = 1/1 = 1 */ for ( i = 1; ; i++ ) { /* 1/(i!) = (1/((i-1)!))/i */ /* ** この部分を完成させなさい */ fe = fe + fs; if ( (double)fs < EPS ) { return fe; } } } /* * double 型での計算 */ double dexp() { int i; double ds = 1.0; double de = 1.0; for ( i = 1; ; i++ ) { ds = ds / (double)i; /* ** この部分を完成させなさい */ if ( (double)ds < EPS ) { return de; } } } /* * main */ int main ( void ) { /* fexp の結果 */ printf ( "fexp = %40.35f\n", fexp() ); /* dexp の結果 */ /* ** この部分を完成させなさい */ /* math.h の中の定数 */ printf ( "M_E = %40.35f\n", M_E ); return 0; }
$ ./20201211-01-QQQQ.exe fexp = 2.71828198432922363281250000000000000 dexp = 2.71828182845904553488480814849026501 M_E = 2.71828182845904509079559829842764884 $
Download : 20201211-02.c
/* * DATE-02-QQQQ.c * * 動的なメモリの確保 * キーボードより正の整数を幾つか入力して、その要素が入った配列を返す * 0 以下の整数が入力されたら、終了とする * 配列のサイズは、正の整数の個数 + 1 とし、最後の要素には 0 を入れる */ /* * 利用方法 * コンパイル * cc -o BASENAME.exe FILENAME * 実行 * ./BASENAME.exe */ #include <stdio.h> #include <malloc.h> /* calloc/free を利用するので必要 */ /* * read_n_integers */ int *read_n_integers( int size ) { int num; /* キーボードから入力された数値を保存する */ int *value; /* 確保された配列の先頭要素へのポインター */ printf ( "正の整数値を入力してください(0 以下だと入力を終了します):" ); scanf ( "%d", &num ); if ( num <= 0 ) { /* 入力が全部終ったので、配列を作成する */ /* 配列のサイズは、引数で指定された個数 + 1 となる */ if ( ( value = (int *)calloc ( size + 1, sizeof ( int ) ) ) != NULL ) { /* 動的メモリは取り出せるとは限らないので、結果をチェック */ value[ size ] = 0; /* 最後の要素として 0 を代入 */ } /* else {} */ /* NULL が帰った場合は、そのまま、値として返す */ } else { /* 入力が終っていないので、更に、値を読むために再帰呼び出し */ if ( ( value = read_n_integers( size + 1 ) ) != NULL ) { /* 結果が NULL でなければ、配列が作られている */ /* size 番目の要素を配列に記録 */ /* ** この部分を完成させなさい */ } /* else {} */ /* NULL が帰った場合は、そのまま、値として返す */ } /* いずれの場合でも value を返す */ /* ** この部分を完成させなさい */ } /* * main */ int main ( int argc, char *argv[] ) { int *array; /* n 個数の要素をもつ配列の先頭をもつ */ int i; /* read_n_integers を呼び出して、n 個の整数値を入力する */ /* 引数には、入力済のデータ数を指定するので、最初は 0 を指定する */ if ( ( array = read_n_integers( 0 ) ) != NULL ) { /* read_n_integers は、NULL を返す可能性がある */ /* 入力された要素を画面に出力 */ for ( i = 0; array[i] > 0; i++ ) { printf ( "%d th data = %d\n", i, array[i] ); } /* malloc/calloc で確保したメモリは、必ず free で解放する */ /* ** この部分を完成させなさい */ } /* else {} */ /* NULL の場合はエラーなので、何もしない */ return 0; }
12 34 5 6 90 -1
$ ./20201211-02-QQQQ.exe 正の整数値を入力してください(0 以下だと入力を終了します):12 正の整数値を入力してください(0 以下だと入力を終了します):34 正の整数値を入力してください(0 以下だと入力を終了します):5 正の整数値を入力してください(0 以下だと入力を終了します):6 正の整数値を入力してください(0 以下だと入力を終了します):90 正の整数値を入力してください(0 以下だと入力を終了します):-1 0 th data = 12 1 th data = 34 2 th data = 5 3 th data = 6 4 th data = 90 $