• 検索結果がありません。

, ポインタの配列 計算機言語 I 第 14 回ポインタへのポインタ

N/A
N/A
Protected

Academic year: 2021

シェア ", ポインタの配列 計算機言語 I 第 14 回ポインタへのポインタ"

Copied!
6
0
0

読み込み中.... (全文を見る)

全文

(1)

計算機言語

I

14

ポインタへのポインタ

,

ポインタの配列

この資料

: http://www.math.u-ryukyu.ac.jp/~suga/gengo/2019-1/14.pdf

レポートへのツッコミ

レポートでは

,

今回の授業で話そうとした内容が既に書かれていました

.

その部分は

,

授業の後半に述べ ます

.

素朴なプログラムを書いた方へのツッコミを入れておきます

.

それは

,

データの入れ替えを利用したバブル ソートで

, seiseki_t

型の

40

個の配列を整列させるものです

.

そのときに

,

「構造体は

,

代入によるコピーが 可能」ということを使うとソースはすっきりとします

.

void swap(seiseki_t *x, seiseki_t *y) {

seiseki_t temp;

temp = *x;

*x = *y;

*y = temp;

}

という関数

swap()

を用意しておくと

, int

型の値を入れ替える時と同様に

,

swap(&x, &y);

, seiseki_t

型の変数

, x, y

の値が入れ替わります

.

ただし

,

構造体のコピーは

, CPU

の動作としては

,

各メンバ単位でひとつひとつコピーしますので

,

動作が 遅くなります

.

したがって

,

上のようなプログラムは

,

できる限り書かないようにします

.

ポインタへのポインタ

「ポインタ型変数」も識別子である

.

という認識は重要です

.

識別子なら

, C

の言語規則から

,

プログラム実 行中にはそのアドレスが定まるのです

.

表題のポインタへのポインタとは

,

ポインタ型変数を指すポインタのことです

.

当然

,

この値を変数値として 持つ変数を使うことができ

,

ポインタ型変数のアドレスを保持する変数が考えられます

.

これは

,

現実のプログ ラムでは

,

「ポインタ型変数の配列の先頭要素を指すポインタ」としてよく利用されます

.

実際

,

教科書の例で

(2)

,

行列を

2

重配列ではなく

,

「行ベクトルの並び」として実現する方法が書かれています

.

このような変数を考える理由は

,

主に次の

2

つです

.

2

重配列のような変数を動的に確保するのに都合が良い

.

ある種のプログラムの実行速度を改善する

.

教科書には

,

上の例が書かれています

.

例えば

, m × n

行列の計算をするプログラムを書くとして

,

m, n(

列のサイズ

)

が実行時にしか決まらない」とします

.

これをプログラムする一つの方法は

, m × n

の一次元配

列を

malloc()

で確保して

,

それを行列のように扱うプログラムを書くことです

.

しかし

,

これはプログラム

が複雑になるとともに

,

下にある実行速度に影響するプログラムになったりします

.

そこで

,

教科書にあるように

, n

次元の行ベクトルが

m

個並んだものとして

,

行列を定めます

.

すなわち

,

p[m]

を準備し

, p[i]

,

i + 1

行の行ベクトルを指すポインタとするのです

.

この時

,

識別子

p

は配列 変数

p[m]

の先頭要素

p[0]

へのポインタを保持しますから

,

ポインタへのポインタとなるわけです

.

このようにして書かれたのが

,

教科書

,

プログラム

10.5

です

.

プログラムの中で

, sizeof(double *)

の部 分は

, double

型ポインタの大きさの意味になります

. double

型の大きさではありません

.

教科書

,

プログラム

10.6

,

あまり意味のない工夫なので

,

飛ばしてください

.

教科書

,

プログラム

10.7

,

実行してみてください

.

ポインタ配列のアドレス増の様子は

,

重要です

.

ーーーーーーーーーーーーー

さて

,

上で挙げたポインタ配列を使うふたつめの理由について述べます

.

「ある種のプログラムの実行速度を上げる」例として

,

「要素の入れ替え」があります

.

例えば

,

連立一次方程 式を解くプログラムを書くとします

(

工学系ではよくある話

).

連立一次方程式の解法は

,

皆さんが

1

年次の時 に線型代数学で学んだ「掃き出し法」をプログラムします

.

解の公式であるクラメルの公式はプログラミング では全く使えません

.

掃き出し法のひとつの操作に「行を入れ替える」というものがあります

.

これを

2

次元 配列で実装すると

,

とても遅いプログラムになりますが

,

上のようなポインタ配列で実装すると

,

コンピュータ の速度は劇的に速くなります

(

ホワイトボードで説明します

).

前回のレポート問題について

:

「ポインタのポインタ」の応用

上の事を前回のレポート問題で実装してみます

. seiseki_t

型の構造体を入れ替えるのではなく

,

それらへ のポインタの指す値を入れ替えることにより

,

整列を実行するわけです

.

プログラムでは

, data[40]

seiseki_t

型へのポインタを保持するポインタ変数の配列にします

.

従っ

,

各個人のデータを読むたびに

, seiseki_t

型のためのメモリを

malloc()

で確保し

,

そこへのアドレスを

data[i]

に代入していきます

.

整列アルゴリズムは

,

単純なバブルソートにしました

.

注意して欲しいのは

,

構造体へのポインタに対する矢印演算子

->

です

. a

が何らかの構造体へのポインタ なら

, a -> member

, a

が指す構造体内の

member

という変数値を参照します

.

また

,

要素を入れ替える

swap()

では

,

まさにポインタへのポインタが引数となります

.

(3)

#include <stdio.h>

#include <stdlib.h>

typedef struct seiseki{

char number[10];

int score;

} seiseki_t;

void swap(seiseki_t **x, seiseki_t **y);

void sort(seiseki_t *x[]);

int main() {

FILE *fp;

seiseki_t *data[40];

int i;

if ((fp=fopen("input.data","r"))==NULL){

fprintf(stderr, "

ファイルを開けません

.\n");

exit(1);

}

for (i=0; i< 40; i++){

if ((data[i] = (seiseki_t *) malloc(sizeof(seiseki_t))) == NULL){

fprintf(stderr, "

メモリを確保できません

.\n");

exit(1);

}

fscanf(fp, "%s%d", data[i]->number, &(data[i]->score));

}

fclose(fp);

sort(data);

for (i=0; i< 40; i++){

printf("%s %d\n", data[i]->number, data[i]->score);

}

return 0;

}

(4)

void swap(seiseki_t **x, seiseki_t **y) {

seiseki_t *temp;

temp = *x;

*x = *y;

*y = temp;

}

void sort(seiseki_t *data[]) {

int i, j;

for (i=39; i > 0; i--){

for (j=0; j < i ; j++){

if (data[j]->score < data[j+1]->score){

swap(&data[j], &data[j+1]);

} } } }

ライブラリ関数

qsort()

今の形のコンピュータが実用に使われるようになった当初から

,

データの整列処理に多く利用されることが 判明しました

.

そのため

,

様々な整列アルゴリズムが開発されています

.

整列アルゴリズムは

,

次の観点で比較されています

.

整列のための計算量

(

整列の速さ

)

整列に必要なメモリ

.

安定性

(

同じ値であったとき

,

元のデータの並びを変化させる可能性があるか否か

.)

これらすべで良い結果が得られるアルゴリズムは

,

いまだに発見されていません

(

おそらく無いと思います

,

無い事を証明できるかは知らない

.).

しかし

,

概ね上手くいくアルゴリズムがあり

,

その中でも

Hoare(

)

1960

年に開発した

quicksort

というものがあります

.

特別に速い整列方法

(

データの形によっては

,

別な方法がある

)

がない場合とか

,

データサイズが大きすぎて

1

次記憶に収まりきらないなどを除けば

,

これ を利用するのが普通です

. quicksort

の特徴は

,

次です

.

データは

1

次記憶上に全てあることを想定し

,

データサイズ程度の記憶容量しか必要としない

.

安定ではない

.

平均的な計算量は

O(n log n)

である

.(

最悪は

O(n

2

).

バブルソートは

,

これが常に

O(n

2

))

再帰的なプログラムのため

,

データ量が少ないと

,

再帰呼び出しのオーバーヘッドで却って遅くなる

.

(5)

C

では

,

標準ライブラリ関数として

, qsort()

というものが用意されています

.

その内容は

,

必ずしも

quicksort

の実装ではなく処理系依存ですが

,

多くの場合

, quicksort

の最後の欠点を少し改善したプログラム

が実装されているようです

. (

ソート数が少なくなった時点で

,

再帰をやめて別のアルゴリズムを使うようにし ている

).

qsort()

の使い方は

,

マニュアルコマンドでわかりますが

,

その読み方を少し解説します

.

Bash 3-2$ man 3 qsort

次のプログラムは

, qsort()

を用いて

, seiseki_t data[40];

score

の小さい並べ替えた例です

.

較関数

compare

については

,

白板で解説します

.

compare()

にカウンタを入れて

,

私のデータで計測したところ

, 166

回のデータ比較で並び替えが終了しま

した

.

バブルソートだと

, 40 × 39

2 = 780

回のデータ比較が必要ですから

, qsort()

の方が効率よく整列を実

行している事がわかります

.

#include <stdio.h>

#include <stdlib.h>

typedef struct seiseki{

char number[10];

int score;

} seiseki_t;

int compare(const void *x, const void *y);

int main() {

FILE *fp;

seiseki_t *data[40];

int i;

if ((fp=fopen("data.txt","r"))==NULL){

fprintf(stderr, "Cannot open file\n");

exit(1);

}

for (i=0; i< 40; i++){

if ((data[i] = (seiseki_t *) malloc(sizeof(seiseki_t))) == NULL){

fprintf(stderr, "Cannot allocate memory\n");

exit(1);

}

fscanf(fp, "%s%d", data[i]->number, &(data[i]->score));

(6)

}

fclose(fp);

qsort(data, sizeof(data)/sizeof(data[0]), sizeof(data[0]), compare);

for (i=0; i< 40; i++){

printf("%s %d\n", data[i]->number, data[i]->score);

}

return 0;

}

int compare(const void *x, const void *y) {

return (*(seiseki_t **)x)->score - (*(seiseki_t **)y)->score;

}

レポート問題

:

締め切り

, 7

29

(

) 10:00 AM(JST)

行列のサイズ

m, n

を入力から受け取って

, m × n

行列を作成するプログラムを書け

.

プログラムは

,

次の仕 様にすること

.

行列のサイズ

m, n

は標準入力から受け取る

.

行列の成分は

, int

型にする

.

行列のサイズが決まれば

,

各行単位で

,

行ベクトルの入力をするようにし

,

それをメモリ内に保持する

.

最後に上で入力した行列を出力する

.

出力は

,

行単位でする

.

例えば

,

行列

(

1 2 3

4 5 6

)

の出力は

1 2 3 4 5 6

となるようにする

.

件名

: gengo2019-1 report14-1.

参照

関連したドキュメント

前章 / 節からの流れで、計算可能な関数のもつ性質を抽象的に捉えることから始めよう。話を 単純にするために、以下では次のような型のプログラム を考える。 は部分関数 (

が前スライドの (i)-(iii) を満たすとする.このとき,以下の3つの公理を 満たす整数を に対する degree ( 次数 ) といい, と書く..

これはつまり十進法ではなく、一進法を用いて自然数を表記するということである。とは いえ数が大きくなると見にくくなるので、.. 0, 1,

方式で 45 ~ 55 %、積上げ方式で 35 ~ 45% 又は純費用方式で 35 ~ 45 %)の選択制 (※一部例外を除く)

目標を、子どもと教師のオリエンテーションでいくつかの文節に分け」、学習課題としている。例

関西学院大学手話言語研究センターの研究員をしております松岡と申します。よろ

今回の調査に限って言うと、日本手話、手話言語学基礎・専門、手話言語条例、手話 通訳士 養成プ ログ ラム 、合理 的配慮 とし ての 手話通 訳、こ れら

上であることの確認書 1式 必須 ○ 中小企業等の所有が二分の一以上であることを確認 する様式です。. 所有等割合計算書