Transcript ppt file

プログラミング入門2
第10回 動的な領域確保
情報工学科 篠埜 功
今日の内容
• callocによる動的な領域確保について
• mallocという、callocに似たライブラリ関数もあ
るが、この演習ではcallocのみ紹介する。
動的な記憶域確保
静的(static)な記憶域確保
• これまでの方法
– 配列の要素数は固定。
– あらかじめ十分な大きさの配列を確保しておく必
要があった。
• 今回紹介する方法 動的(dynamic)な記憶域確保
– 問題に応じて、適切なサイズの配列を確保する
には、プログラムの実行時に確保を行う必要が
ある。
– 余分なメモリの使用を避けることができる。
静的(static) --- プログラムのコンパイル時
動的(dynamic) --- プログラムの実行時
ヒープ領域(heap)
• プログラムからはヒープ領域(heap)を用いることがで
きる。
• ヒープ領域を使うには、mallocあるいはcallocという
ライブラリ関数を呼び出すことにより領域を確保する。
使い終わったら、freeというライブラリ関数を呼び出
すことにより解放する。解放することにより、それ以
降のmallocあるいはcallocの呼び出し時に再利用可
能になる。
(注意)ヒープ領域は、データ構造の授業で習う
木構造のヒープとは関係がない。
calloc関数
• ヒープ領域から実行時に記憶域を確保する。
• 引数として、データ型のサイズsize(第2引数)と、その個数
n(第1引数)を受け取り、1つの要素の大きさがsizeで長さn
の配列の領域を確保する。確保した領域のすべてのビット
が0で初期化される。配列の確保に成功した場合は、その
配列の先頭要素へのポインタを返し、失敗した場合は、ヌ
ルポインタを返す。返り値の型はvoid * 型である。返り値
をポインタ型の変数に代入するとき、キャストする必要は
ない(キャストしてもよいが)。
• データ型のサイズは、sizeof (型式) で取得できる。
• calloc関数を使うためにはstdlib.hをインクルードする必要
がある。
ヌルポインタ
ヌルポインタ(null pointer)は、どこも指さないポインタであり、
何かを指しているポインタとは異なることが保証されている。
整数値0は、任意のポインタ型へ変換でき、その結果がヌル
ポインタである。
ヌルポインタを表すため、ヌルポインタ定数(0か、あるいは
(void *) 0)がマクロNULLとしてstddef.hに定義されている。
(stdlib.hなどのいくつかの他のヘッダーファイルにも定義され
ている。)
ヌルポインタは、キャスト無しで任意のポインタ型の変数に代
入したり、任意のポインタ型の値と比較してよい。自動的に型
変換される(暗黙の型変換)。
例(打ち込んで確認)
#include <stdio.h>
int型1個分の記憶域(長さ1の
#include <stdlib.h>
int型の配列)をヒープ領域から
int main(void)
割り当てる
{
callocの返り値はキャスト
int *p;
無しでpに代入してよい
p = calloc (1, sizeof (int));
NULLはキャスト無しでpと比較してよい
if( p == NULL )
printf ("記憶域の確保に失敗しました。\n");
else {
*p = 15;
printf ("*p = %d\n", *p );
}
return 0;
}
解説
• calloc関数による記憶域の動的な確保
int * p;
p = calloc (1, sizeof (int) );
int型へのポインタ型の
変数を宣言
int * p;
calloc関数呼び出し時にint型の長さ1の配
列の領域が確保され、その先頭要素への
ポインタがpに代入される。
p = calloc (1, sizeof (int) );
...
p
p
sizeof演算子
sizeof演算子は、型式(type expression)を引数にとる。評
価結果は、その型のサイズである。
構文
sizeof (型式)
意味
sizeof (t) の評価結果はtのサイズである
型式は、int, double, char等の基本型、int [3]等の配列
型、struct {int px; int py;} 等の構造体型、int *等のポ
インタ型、 typedefで定義した型名、あるいはこれらの
組み合わせなどである。詳しくは教科書あるいは規格
書を参照。
例(打ち込んで確認)
#include <stdio.h>
typedef struct {
int x;
int y;
} point;
int main (void) {
printf ("int: %d\n", sizeof(int));
printf ("int[3] : %d\n", sizeof(int[3]));
printf ("struct {int x; int y;} : %d\n",
sizeof(struct {int x; int y;}));
printf ("point: %d\n", sizeof(point));
printf ("point *: %d\n", sizeof(point *));
return 0;
}
void へのポインタ型
calloc関数の返り値はvoid *型(voidへのポインタ型)である。
void *型のポインタを他のポインタ型変数に代入したり、他の
ポインタ型のポインタをvoid *型の変数に代入したりできる(暗
黙の型変換が行われるのでキャストは不要)。
int, char, double, 構造体 など、さまざまな型の配列の領域
を確保するためにcalloc関数が用いられるので、void *型で
返している。
(キャストしない例) int *p;
p = calloc (1, sizeof (int) );
(キャストする例) int *p;
p = (int *) calloc (1, sizeof (int) );
(補足)C++では、void*型のポインタを他のポインタ型の変
数に代入するときにはキャストが必要。
free関数 : 記憶域の解放
• 動的に確保した記憶域は、不要になった時点でfree関数を呼
び出して解放する。それによって、それ以降のcallocあるいは
mallocの呼び出しで再利用可能な状態になる。
• stdlib.hというヘッダーファイルを読み込んで使う。
• 引数にポインタpを受け取り、pが指す先の領域を解放する。
返り値はない。ただし、pがヌルポインタのときは何も行わな
い。pはcalloc, malloc, あるいはreallocによって以前に割り当
てられた領域へのポインタでなければならない(もしそうでな
い場合は動作は未定義)。pが、freeやreallocによって既に解
放された領域を指している場合も動作は未定義。
(注意)reallocは、解放と割り当ての両方を行うライブラリ関
数である。この演習では、malloc, reallocの説明はしない。
例
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *p;
p = calloc( 1, sizeof(int) );
if (p == NULL)
printf ("記憶域の確保に失敗しました。\n");
else {
*p = 15;
printf("*p = %d\n", *p );
free(p);
}
return 0;
}
記憶域解放
free(p);
p = calloc( 1, sizeof(int) );
記憶域確保
確保した領域へキーボードからの入力を書き込む例
(打ち込んで確認)
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int * p;
p = calloc (1, sizeof(int));
if(p == NULL)
printf ("記憶域の確保に失敗しました。\n");
else {
printf ("整数を入力して下さい:");
scanf ("%d", p);
printf ("*p = %d\n", *p);
free(p);
}
return 0;
}
1次元配列の動的確保
• 配列宣言の例
int x[10];
配列の要素数は定数式でなければならない。
要素数を変数とすることは1990年のISO規格では
許されていない。
(注)1999年のISO規格(C99)では許されているが。
実行時に領域を確保することにより、適切な長
さの配列を用いることができる。
例(打ち込んで確認)
#include <stdio.h>
#include <stdlib.h>
int main (void) {
int no, i=0;
int * p;
printf("確保する配列の要素数:");
scanf("%d", &no);
p = calloc (no, sizeof (int));
if (p == NULL)
printf ("記憶域の確保に失敗しました。\n");
else {
for (i=0; i < no; i=i+1)
p[i] = i;
for (i=0; i < no; i=i+1)
printf("p[%d] = %d\n", i, p[i] );
free (p);
}
return 0;
}
(5を入力した場合)
p[0]
sizeof(int) * 5
p[1]
p[2]
p[3]
p[4]
…
p
基本課題1
文字列(アルファベットのみ)をキーボードから受け取り、
それを逆順に表示するプログラムを作成せよ。文字列
を格納する領域は、キーボードから文字数の上限を受
け取り、callocで確保せよ。(上限以上の文字が入力さ
れた場合の対処は自由とする。)
(注意)文字列の形で格納する場合、最後にヌル文字
が必要である。ただ、この問題では逆順に表示できさえ
すればよく、ヌル文字を追加で格納するかどうかは自
由とする。
[実行例]
文字数の上限を入力してください: 10
文字列を入力してください: abcde
edcba
基本課題2
n人(nは実行時にキーボードから入力)の試験の点数をキーボー
ドから入力し、それらの平均点をdouble型で表示するプログラム
を書け。ただし、callocを用いて長さnのint型の領域を確保し、そこ
へn人の点数を格納せよ。平均値を計算する部分は、その領域の
先頭要素へのポインタおよび長さnを受け取って平均値を返す以
下のような関数として定義せよ。
double average (int * p, int n) { … }
[実行例]
何人分入力しますか: 5
1人目の点数を入力: 79
2人目の点数を入力: 65
3人目の点数を入力: 80
4人目の点数を入力: 95
5人目の点数を入力: 50
平均点は73.800000点です
発展課題1
受験者n人(nは実行時にキーボードから入力)の氏名およ
び数学、英語の2科目の試験の点数をキーボードから受け
取り、氏名、各科目の点数、合計点を一覧表にして表示し
たい。これを行うプログラムを、callocを用いて書け。
各受験者の氏名と点数を入力する部分、合計点を計算す
る部分、一覧表示をする部分は、別々の関数として定義し、
それらをmain関数から呼び出す形でプログラムを記述せよ。
(実行例) 人数を入力してください: 2
氏名: 芝浦太郎
数学: 90
英語: 90
氏名: 芝浦次郎
数学: 100
英語: 100
氏名
数学 英語 合計
芝浦太郎
90
90 180
芝浦次郎 100 100 200
一覧表示で縦をそろえるには、
printfの変換指定を、文字列
の場合は%-8s, 整数の場合
は%4dのようにすればよい。
詳しくは教科書p.318を参照。
あるいはmanコマンドで
$ man –S 3 printf
で調べればよい。
発展課題2
発展課題1の表示を、合計点の高い順に表
示するように変更せよ。
発展課題3
英文をキーボードから入力し、その英文中の単語の数を表示
するプログラムを作成せよ。英字以外の文字(空白、ピリオド、
コンマ、クエスチョンマーク等)は区切り文字とし、単語数には
カウントしない。文字列を格納する領域は、キーボードから文
字数の上限を受け取り、callocで確保せよ。
[実行例]
% ./a.out
文字数の上限を入力してください: 20
英文を入力してください: This is a pen.
単語数は4です。
(参考)これは第6回発展課題1の類題で、文字列を格納する領域をcalloc
で確保するようにした問題
構造体配列の動的確保(pointでの例)
(0) point構造体を定義
(1) point構造体へのポインタ型の変数pを宣言しておく。
point *p;
(2) 配列の要素数をキーボードから受け取り、Nに格納する。
(3) p = 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;
scanfについて
int n;
char c;
scanf (“%d”, &n);
scanf (“%c”, &c);
のようなプログラムにおいて、例えば5を入力すると、nに5
が代入されるが、入力のためにreturnキーを押しており、
改行文字が残っているため、scanf(“%c”, &c);で改行文字
が読み取られる。
なので、次の文字を読み取るためには、以下のようにさら
にもう一度scanfで読み取る必要がある。
scanf (“%d”, &n);
%cは空白や改行文字も読み取り対象となる
scanf (“%c”, &c);
ので、%dで読み取ったあとに%cで読み取る
scanf (“%c”, &c);
場合は注意が必要。
scanfについて(続き)
scanfで%dが指定されている場合は、数が出てくるま
で、改行や空白が読み飛ばされる。
int n1;
int n2;
scanf (“%d”, &n1);
scanf (“%d”, &n2);
のようなプログラムだと、1回目のscanfで数を入れた
後は改行文字が残っているが、次のscanfでは残っ
ていた改行文字は読み飛ばされ、その後の数字が
読み取られる。(その数字の後に改行文字があった
らそれは残る。)
参考課題1
n個(nは実行時にキーボードから入力)のint型の数をキー
ボードから受け取り、それらの和を画面上に表示するプロ
グラムを作成せよ。ただし、callocを用いて長さnのint型の
領域を確保し、そこへキーボードからのn個の入力を格納
せよ。和を計算する部分は、その領域の先頭要素へのポイ
ンタおよび長さnを受け取って和を返す以下のような関数と
して定義せよ。
int sum (int * p, int n) { … }
[実行例]
いくつ入力しますか: 5
1個目の数字を入力: 3
2個目の数字を入力: 6
3個目の数字を入力: 1
4個目の数字を入力: 8
5個目の数字を入力: 7
合計は25です
参考課題 解答例
#include<stdio.h>
#include<stdlib.h>
int sum (int * p, int n) {
int i, sum=0;
for (i=0; i<n; i++)
sum = sum + p[i];
return sum;
}
/* 続き */
int main (void) {
int n,i;
int * p;
printf("いくつ入力しますか: ");
scanf("%d", &n);
p = calloc (n, sizeof (int));
if (p == NULL)
printf ("記憶域の確保に失敗しました。\n");
else {
for(i=0; i<n; i++){
printf("%d個目の数字を入力: ", i+1);
scanf("%d", &p[i]);
}
printf("合計は%dです\n",sum(p,n));
free (p);
}
return 0;
}