Document

プログラミング入門2
第12回
構造体の配列
データ型
関数のプロトタイプ宣言
動的な記憶域確保
芝浦工業大学情報工学科
青木 義満、篠埜 功
先週の補足1
構造体は関数の引数として渡したり、関数の返り値とし
て返したりすることができる。(配列は関数の引数として
渡したり、関数の返り値として返すことはできない。)
typedef struct {double re; double im;} complex;
complex sum (complex num1, complex num2) {
complex result;
result.re = …;
result.im = …;
return result;
}
プログラミング入門2
2
先週の補足2
構造体のサイズは、単純に各メンバーのサイズの和で
はない。
(例)
typedef struct {int x; char y; int z;} foo;
int main () {
printf (“%d\n”, sizeof (foo)); /* 12が表示される */
printf (“%d\n”, sizeof (int)); /* 4が表示される */
printf (“%d\n”, sizeof (char)); /* 1が表示される */
}
x
y
z
アラインメント(alignment)があるため。
Alignmentとは、CPUのアドレス指定において、アドレスの下位数ビット(2bit
など)を使用しないようになっている場合(これが普通)、それに対応させて、あ
る一定数(4など)の倍数のアドレスに合わせてint型などのデータを格納をす
ること。(そうなるようにコンパイラが機械語コードを生成する。)
プログラミング入門2
3
先週の補足2
先週の身体検査用の構造体のサイズは32byteだったが、メンバー
nameの配列の要素数を19にしても、構造体のサイズは32byteのまま
で変わらない。
(例)
struct {
char name[20];
int height;
double weight;
} taro;
taro.name
20byte
taro.height
宣言する変数名
taro.weight
プログラミング入門2
4byte
8byte
4
複数のデータの扱い (構造体の配列, p.282)
 構造体データを複数扱う際には,構造体の配列を
用いる。
std[0].name
 構造体型の配列の宣言
std[0]
typedef struct {
char name[20];
int height;
float weight;
} student;
std[0].height
std[0].weight
std[1].name
std[1]
student std[5];
std[1].height
std[1].weight
….
プログラミング入門2
5
構造体の配列
 ソースファイル名:list1208.c (p.283を変更)
 構造体の配列
#include <stdio.h>
#define NUMBER 5
/* 学生の人数 */
typedef struct {
char name[20]; /* 名前 */
int height;
/* 身長 */
float weight;
/* 体重 */
} student;
int main(void)
{
int i;
student std[]= {
{ "Sato", 178, 61.0 },
{ "Sanaka", 175, 60.5 },
{ "Takao", 173, 80.0 },
{ "Mike", 165, 72.0 },
{ "Masaki", 179, 77.5 },
};
puts("-----------------------------");
for (i = 0; i < NUMBER; i++)
printf("%-8s %6d%6.1f\n",
std[i].name, std[i].height, std[i].weight);
puts("-----------------------------");
return (0);
}
プログラミング入門2
6
構造体の配列を関数へ渡す方法
 ソースファイル名: struct4.c
#include <stdio.h>
#define NUMBER 5 /* 学生の人数 */
typedef struct {
char name[20]; /* 名前 */
int height;
/* 身長 */
float weight;
/* 体重 */
} student;
void print_data( student data[ ] )
=
void print_data( student data[] )
{
int i;
puts("-----------------------------");
for (i = 0; i < NUMBER; i++)
printf("%-8s %6d%6.1f\n",data[i].name, data[i].height, data[i].weight);
puts("-----------------------------");
}
in main(void)
{
student *data
どちらも配列の先頭要素のアドレスを
受け取る。
受け取ると,関数内で data[i]として
配列要素を扱える
student std[] = {
{ "Sato", 178, 61.0 },
{ "Sanaka", 175, 60.5 },
{ "Takao", 173, 80.0 },
{ "Mike", 165, 72.0 },
{ "Masaki", 179, 77.5 },
};
print_data( std );
return (0);
}
配列データを関数に渡す時には,
その配列の先頭要素のアドレスを渡す。
stdと書いてもよいし、&std[0] と書いてもよい。
プログラミング入門2
7
データ型
 データ型

整数型,文字型,浮動小数点数,倍精度型
データ型
意味
整数型
整数を表現する
文字型
文字を表現する
浮動小数点数
実数を表現する
倍精度型
精度の高い浮動小数点数を表現する
C言語で扱うことのできるデータ型
プログラミング入門2
8
各データが扱える数値範囲
 変数を用意する際,目的にあった(扱うデータの値の取りうる範囲,必要と
される精度)データ型を以下から選択して使用
データ型
ビット長
扱える数値の範囲
short
16
-32768 ~ +32767
int
32
-2147483648 ~ 2147483648
long
32
-2147483648 ~ 2147483648
unsigned short
16
0 ~ 65535
unsigned int
32
0 ~ 4294967295
unsigned long
32
0 ~ 4294967295
char
8
(-128 ~ 127)
unsigned char
8
(0 ~ 255)
float
32
3.4 x 10-38 ~ 3.4 x 10+38
double
64
1.7 x 10-308 ~1.7 x 10+308
※実際のデータサイズは,処理系によって異なる(特にint)
プログラミング入門2
9
各データ型の変換指定子
 標準入出力のための変換指定子
指定子
意味
%d(%ld)
整数の10進数として出力 (longの場合,%ld)
%u (%lu)
整数の符号なし10進数として出力
(unsigned longの場合,%lu)
%f
浮動小数点表示(float, double共通)
%c
1文字を出力
%s
文字列を出力
%p
ポインタの値(アドレス)を出力
※scanfの場合,float は%f, double は %lf で読み込み
プログラミング入門2
10
データ型の値の範囲を出力
 ソースファイル名: data.c
 各データ型の値の取りうる範囲を出力
#include <stdio.h>
#include <limits.h>
各データ型の値の範囲が定義(#define)されている
int main(void)
{
printf("char : %d to %d\n", CHAR_MIN, CHAR_MAX);
printf("unsigned char : %d to %d\n", 0, UCHAR_MAX);
printf("short : %d to %d\n", SHRT_MIN, SHRT_MAX);
printf("int : %d to %d\n", INT_MIN, INT_MAX);
printf("long : %ld to %ld\n", LONG_MIN, LONG_MAX);
printf("unsigned short : %u to %u\n", 0, USHRT_MAX);
printf("unsigned int : %u to %u\n", 0, UINT_MAX);
printf("unsigned long int: %lu to %lu\n", 0, ULONG_MAX);
return(0);
}
プログラミング入門2
11
データ型のサイズ(バイト数)を表示
 ソースファイル名: datasize.c
 各データ型の大きさ(サイズ)を表示
#include <stdio.h>
int main(void)
{
printf("sizeof(char) = %u\n", sizeof(char) );
printf("sizeof(short) = %u\n", sizeof(short) );
printf("sizeof(int) = %u\n", sizeof(int) );
printf("sizeof(long) = %u\n", sizeof(long) );
printf("sizeof(float) = %u\n", sizeof(float) );
printf("sizeof(double) = %u\n", sizeof(double) );
return(0);
}
sizeof( データ型 ) → データ型の大きさ(byte数)
プログラミング入門2
※p.180に詳しい解説
12
関数のプロトタイプ宣言
 プログラムの冒頭に,使用する関数の仕様を先に宣言しておく
→ 関数のプロトタイプ宣言
#include <stdio.h>
int main(void)
{
int x, y, z;
x = 5;
y = 10;
z = func( x, y );
printf( "x+y = %d\n", z);
return(0);
$ gcc –W –Wall test.c
のように、オプションをつけてコンパイルすると
警告がでる。
}
int func(int x, int y)
{
return (x+y) ;
}
プログラミング入門2
13
関数のプロトタイプ宣言
#include <stdio.h>
int func(int x, int y);
型をプログラムの冒頭に記述
(どんな引数を何個受け取り,どんな値を返すのか)
int main(void)
{
int x, y, z;
関数のプロトタイプ宣言
(書く習慣をつけておいた方が良い)
x = 5;
y = 10;
z = func( x, y );
printf( "x+y = %d\n", z);
return(0);
}
int func(int x, int y)
{
return (x+y) ;
}
プログラミング入門2
14
動的記憶域確保の必要性
 これまでのプログラム



配列の要素数は固定
あらかじめ大きめの配列を確保しておく
#defineマクロで、要素数を定数として宣言し、その値を変更す
ることで対応
→ 静的な配列(記憶域)の確保
 問題に応じて、適切なサイズの配列(記憶域)を確保す
る方法は?


メモリの節約
プログラムの柔軟性向上
動的な記憶域確保の必要性!
プログラミング入門2
15
calloc関数 : 記憶域の確保
 必要になったら記憶域を動的(ダイナミック)に確保し、
不要になったら解放する機能を提供
ヘッダ
#include <stdlib.h>
形式
void *calloc(size_t n, size_t size );
解説
大きさがsizeであるn個のオブジェクト(記憶域)の領域を確保す
る。
返却値
領域確保に成功した場合は、その領域の先頭へのポインタを返
し、失敗した場合は、空ポインタ(NULL)を返す。
プログラミング入門2
16
Callc関数による記憶域の確保(1)
 ソースファイル名:calloc1.c
 整数1個分の記憶域を動的に確保
#include <stdio.h>
#include <stdlib.h>
stdlib.hのインクルードを忘れずに!
int main(void)
{
int *p;
p = (int *)calloc( 1, sizeof(int) );
/*整数を1個分動的に確保*/
if( p == NULL )
puts(“記憶域の確保に失敗しました");
else {
*p = 15;
printf("*p = %d\n", *p );
}
return(0);
}
プログラミング入門2
17
Calloc関数の動作イメージ
 Calloc関数による記憶域の動的な確保
p = (int *)calloc( 1, sizeof(int) );
int型ポインタ変数を用意
500番地
sizeof( int )
p
p
Intのデータ1個を動的に確保
プログラミング入門2
18
void へのポインタ
(void *)calloc(size_t n, size_t size );
p = (int *)calloc( 1, sizeof(int) );
Calloc関数の返却値 → void * 型 (voidへのポインタ型)
動的確保の対象: int, char, double, 構造体 など、様々なオブジェクト
・ 特定の型へのポインタを返す使用 → ×
・ 融通の利く万能なポインタ void * → ○
プログラムでは、確保する変数の型に合わせて、(void *)型のポインタを
任意のポインタ型にキャスト
※上の例では、int * 型
プログラミング入門2
19
free関数 : 記憶域の解放
 動的に確保した記憶域は、不要になった時点で必ず解放
ヘッダ
#include <stdlib.h>
形式
void free( void *p );
解説
Pが指す領域を開放する。但しpがNULLであれば何も
行わない。
プログラミング入門2
20
記憶域の解放
 ソースファイル:calloc2.c
 記憶域の解放
記憶域解放
#include <stdio.h>
#include <stdlib.h>
free(p);
int main(void)
{
int *p;
p = (int *)calloc( 1, sizeof(int) );
/*整数を1個分動的に確保*/
if( p == NULL )
puts(“記憶域の確保に失敗しました");
else {
*p = 15;
printf("*p = %d\n", *p );
free(p);
/* 確保していた領域を解放 */
}
p = (int *)calloc( 1, sizeof(int) );
記憶域確保
return(0);
}
プログラミング入門2
21
確保した領域への値の書き込み
 ソースファイル名:calloc3.c
 記憶域を1個動的に確保し、キーボードから値を読込み
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *p;
p = (int *)calloc( 1, sizeof(int) );
/*整数を1個分動的に確保*/
if( p == NULL )
puts(“記憶域の確保に失敗しました");
else {
printf(“整数を入力して下さい:”);
scanf(“%d”, p);
printf("*p = %d\n", *p );
}
return(0);
}
プログラミング入門2
22
1次元配列の動的確保
 配列宣言の例
int x[10];
※もしくは、#defineで宣言した定数
配列の要素数は定数式でなければならない
→ 要素数を変数とすることは不可能
→ 開発時に要素数を決定する必要
配列の動的確保 → 実行時に要素数を決定!
実行時に、扱うデータのサイズによって最適な配列を用
意することが可能
プログラミング入門2
23
1次元配列の確保
 ソースファイル名:calloc4.c
 int型の配列を動的に確保
#include <stdio.h>
#include <stdlib.h>
p[0]
int main(void)
{
int no;
/* 配列の要素数 */
int i;
int *p;
sizeof(int) * 5
printf(“確保する配列の要素数:”);
scanf(“%d”, &no);
p = (int *)calloc( no, sizeof(int) );
p[1]
p[2]
p[3]
p[4]
/*整数を1個分動的に確保*/
if( p == NULL )
puts(“記憶域の確保に失敗しました");
else {
for(i=0; i < no; i++)
p[i] = i;
for(i=0; i< no; i++)
printf(“p[%d] = %d¥n”, i, p[i] );
free(p);
}
return(0);
p
あたかも
int p[5];
と宣言された配列が存在する
かのように処理できる!
}
プログラミング入門2
24
realloc : 確保した領域の大きさの変更
 allocで確保した記憶域の大きさを必要に応じて変更
ヘッダ
#include <stdlib.h>
形式
void *realloc( void *ptr, size_t size );
*ptr : 大きさを変更したい領域へのポインタ
size: 新しい大きさ
解説
ptrが指す記憶域の大きさをsizeバイトに変更する。変
更前後の小さい方までのオブジェクトの内容は変わら
ない。
プログラミング入門2
25
記憶域の大きさを変更


ソースファイル名:calloc5.c
確保した記憶域の大きさを途中で変更
printf("Before----\n");
for(i=0;i<no;i++)
printf("p[%d] = %d\n", i, p[i]);
printf("Input new array size : ");
scanf("%d", &no2);
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int no, no2;
int i;
int *p;
int *temp;
temp = (int *)realloc(p, no2 * sizeof(int) );
領域サイズの変更
if(temp==NULL)
puts("Cannot allocate memory.\n");
else{
p = temp;
for(i=no; i < no2; i++)
p[i] = i;
printf("After----\n");
for(i=0;i<no2;i++)
printf("p[%d] = %d\n", i, p[i]);
}
free(p); 領域の解放
printf("Input size: ");
scanf("%d", &no);
p = (int *)calloc(no, sizeof(int));
1回目の領域確保
if(p==NULL)
puts("Cannot allocate memory.\n");
else {
for(i=0;i<no;i++)
p[i] = i;
}
return(0);
}
プログラミング入門2
26
reallocの動作イメージ
p
p[0]
p[1]
p[2]
p[3]
p[4]
要素数はno
←0
←1
←2
←3
←4
p
p[0]
p[1]
p[2]
realloc関数
新たに確保
した領域
※realloc関数で確保済みの領域の大きさを変更する際には、
realloc関数の返却値が、空ポインタでないことを確認!
プログラミング入門2
p[3]
p[4]
p[5]
p[6]
p[7]
←5
←6
←7
要素数はn2
27
二次元配列の動的確保(1)


ソースファイル名:calloc6.c
height行3列の2次元配列を確保(行数固定)
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int height;
int (*p)[3];
int i, j;
要素型がintで要素数が3の配列へのポインタ
printf("Gyou :");
scanf("%d", &height);
p = (int (*)[3])calloc(height * 3, sizeof(int));
}
if(p==NULL)
puts("Cannot allocate memory.\n");
else{
for(i=0;i<height;i++)
for(j=0;j<3;j++)
p[i][j]=0;
for(i=0;i<height;i++)
for(j=0;j<3;j++)
printf("p[%d][%d] = %d\n", i, j, p[i][j]);
free(p);
}
return(0);
プログラミング入門2
28
2次元配列の動的確保 イメージ
p[0][0]
p[0]
p[0][1]
p[0][2]
p[1][0]
p[1]
p[1][1]
p[1][2]
4(height) * 3 * sizeof(int)
p[2][0]
p[2]
p[2][1]
p[2][2]
p[3][0]
p[3]
p[3][1]
p[3][2]
※n次元配列を動的に確保する際は、最高位のn次元の
要素数のみが可変。それ以外の次元の要素数は定数でなければならない
プログラミング入門2
29
二次元配列の動的確保(2)


上級者向け
ソースファイル名:calloc7.c
ダブルポインタを用いた2次元配列の動的確保(をしたように見える)
(行数列数共に可変)(2次元配列とは違い、heightの個数の領域に分か
れている。)
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int height, width;
int i,j;
int **p;
for(i=0;i<height;i++)
for(j=0;j<width;j++)
p[i][j]=0;
for(i=0;i<height;i++)
for(j=0;j<width;j++)
printf("p[%d][%d]=%d\n", i, j, p[i][j]);
Free:
for(i=0;i<height;i++)
free(p[i]);
free(p);
}
return(0);
printf("Gyou: "); scanf("%d", &height);
printf("Retsu: "); scanf("%d", &width);
p= (int **)calloc(height, sizeof(int *));
if(p==NULL)
printf("Cannot allocate memory.\n");
else{
for(i=0;i<height;i++)
p[i]=NULL;
for(i=0;i<height;i++){
p[i]=(int *)calloc(width, sizeof(int));
if(p[i]==NULL){
puts("Cannot allocate memory.\n");
goto Free;
}
}
}
プログラミング入門2
30
構造体配列の動的確保のやり方
(0) point構造体を定義
(1) point構造体へのポインタ型の変数pを宣言しておく。
point *p;
(2) Nにscanfで配列の要素数を入れる。
(3) p = (point *) calloc (N, sizeof (point)); で必要な領域を確保し、その
先頭アドレスをpに代入
(4) pを使って、確保した領域内の各要素にアクセス。
p[0], p[1] などが領域内の各構造体を表す(とプログラマが決める)。
(*p, *(p + 1), 等でも同じ)
p[0].x, p[0].y, p[1].x, …などが、領域の中に確保された各構造体のメ
ンバーを表すことになる。
p -> x, p -> y, (p+1)-> x 等、アローを使った表記でもよい。
typedef struct {
double x;
double y;
} point;
プログラミング入門2
31
今日の課題1
 構造体配列の動的確保(kadai12-1.c)
 以下のようなプログラムを作成せよ。
以下に示すように、2つのdouble型を格納する構造体pointを定義す
る。この構造体は1つの点の座標を表すために用いる。
キーボードから点の数Nを入力し、point構造体をN個格納できる連続
した領域を確保する。(構造体の配列とみなせる。)
領域確保後、全ての座標値を0.0で初期化し、その結果を表示する。
最後に、確保した領域を解放する。




typedef struct {
double x;
double y;
} point;
プログラミング入門2
32
今日の課題2
 2次元配列の動的確保(kadai12-2.c)

N行3列の行列の足し算(要素はint型)を行うプログラムの
作成






3つの行列(の領域へのポインタ)m1, m2, m3および行数N
を引数として受け取り、行列m1, m2の和をm3に格納する関
数sumを定義。
実行時にNをキーボードから入力する。
N行3列の行列の(3N個の)要素を格納するための連続した
領域を動的に3つ確保する。
領域確保後、そのうちの2つの領域(行列)に適当に初期値
を格納し、sum関数を呼び出すことにより3つ目の領域にそ
れらの和を格納する。
sum関数を呼び出したあとで結果を表示する(表示形式自
由)。
行列の要素の値の与え方は自由とする。また、行列の要素を
どのような順番で確保した領域に格納するかも自由とする。
プログラミング入門2
33