#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 $