質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.58%

構造体のポインタ配列とメモリ確保について

解決済

回答 3

投稿

  • 評価
  • クリップ 0
  • VIEW 2,797

masuter0413

score 50

現在次のような課題が出ています。自分で参考書などを読んで途中までかけましたが分からない部分が多数あります。
100名分の構造体の配列を予め用意するのは非常にメモリ の無駄使い。そこで、学生の構造体は必要になった時点で、 メモリから確保するようにしてみよう。但し、学生の構造体へのポインタを 保持することは必要であるから、100個のポインタ配列を利用することにする。 
作成するプログラムは次のようなものである。 
学生の名前(ローマ字)、学生番号 、中間試験の点数(100点満点)、 期末試験の点数(100点満点)、課題の点数(40点満点)、最終成績(100点満点)を 格納する構造体を考え、成績を計算し、表示するプログラムを考える。 但し、最終成績は以下の式で計算される。 を 格納する構造体を考え、成績を計算し、表示するプログラムを考える。 
最終成績=中間+期末/2 *0.6 +kadai

#include<stdio.h>
#include<stdlib.h>
#define NUM 100
struct Student {
    char name[50]; /*名前*/
    char no[10];   /*学生番号*/
    int  c_test;   /*中間*/
    int  k_test;   /*期末*/
    int  kadai;    /*課題*/
    int  seiseki;  /*最終成績*/
};
int dataRead( struct Student * );
void Seiseki( struct Student * );
void Print( struct Student * );

main(){
    struct Student *pg[NUM];    /* 学生NUM名まで*/
    int i,num;

    for(i=0; i<NUM; i++){
         pg[i] = ...            /* メモリを確保 */
         if(dataRead(pg[i])==EOF){   /*  標準入力が終了したら */
             ...
             break;
         }else{
             Seiseki(pg[i]);
         }
     }
     for(i=0; i<num;  i++){
             Print(pg[i]);
             free(pg[i]);
     }
}

int dataRead( struct Student *g ){
     return ...;                  /* 1行データを読み込む */
} 
void Seiseki( struct Student *g ){
     g->seiseki= ...;             /* 1名の最終成績を計算 */
     return;
}
void Print( struct Student *g ){
     printf("%s %s %d %d %d %d\n", ...); /* 1名表示 */
     return;
}
//自分でかけた部分
int dataRead(struct Student *g) {
    return gets(g);                  /* 1行データを読み込む */
}
void Seiseki(struct Student *g) {
    g->seiseki = ((g->c_test) + (g->k_test)) / 2 * 0.6 + (g->kadai);             /* 1名の最終成績を計算 */
    return;
}
void Print(struct Student *g) {
    printf("%s %s %d %d %d %f\n", g->name, g->no, g->c_test, g->k_test, g->kadai, (double)g->seiseki); /* 1名表示 */
    return;
}

分からない点
・seisekiはint型なのに0.6をかけたらdouble型にならないのでしょうか。

・標準入力が終了したら..とありますがそのあとどんな処理をするべきでしょうか。
・メモリを確保..とありますが100回繰り返したら結局100名分の構造体配列を用意するのと一緒なのではないのでしょうか。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

+1

本題の前にいくつか。

main()

int main(void)と書くことがC言語規格書で定められています。

`struct Student { ... };

C++だとこれでいいのですが、Cだと普通typedef struct tag_student { ... } Student;と書くように思います。

void Print( struct Student *g ){

引数の型がstruct Student *となっていますが、const struct Student *とするべきです。なぜならPrint関数は引数gが指す領域を書き換えません。constをつけないことは可読性を下げます。まあ課題の指定でこうなっているのだとは思いますが。

gets

利用してはいけません。すこしググればわかりますが、getsはどうやっても安全に利用することができません。fgetsgets_sを利用してください。

今回の課題では文字列の入力を受けてさらにパースする作業が必要になります。このときsscanfを使ってしまいがちですが、安全に数値へ変換することはできないので、errnoに気を使いつつ地道にstrtol系関数を呼ぶことになります。

C言語で安全に標準入力から数値を取得 - Qiita

・・・ということまで課題を出す側がわかって事前に指導していればいいのですが、9割9分9厘教師はC言語を理解していないので、まあsscanfを使うことになるんだろうなとは思います。やつらいい加減C言語で標準入力をやるのはラスボスに挑むに等しいと理解してくれないだろうか・・・。


seisekiはint型なのに0.6をかけたらdouble型にならないのでしょうか

鋭いです。まず

((g->c_test) + (g->k_test)) / 2

の結果はint型ですが、

((g->c_test) + (g->k_test)) / 2 * 0.6 

の結果はdoubleで、

((g->c_test) + (g->k_test)) / 2 * 0.6 + (g->kadai)

もやはりdoubleです。

これをg->seisekiに代入するときにintへの暗黙変換が行われます。暗黙変換の詳細なルールは複雑怪奇極まるので深く突っ込まないことをおすすめします。


標準入力が終了したら..とありますがそのあとどんな処理をするべきでしょうか。

課題の答えになるのでヒントだけ。

breakがあることから、EOFだったらなにかして、ループを抜ける、という処理だとわかります。ループを抜けたあと、結果を表示するループに入るわけですが、このときループ回数を司っている変数numに注目してください。


メモリを確保..とありますが100回繰り返したら結局100名分の構造体配列を用意するのと一緒なのではないのでしょうか。 

配列もmallocによる確保もメモリー確保には違いありませんが、どこにメモリー確保されるかが異なります。

C++のメモリーの話とストレージの有効期間の話をしようか - Qiita

ですがいい着眼点です。

struct Student *pg[NUM];    /* 学生NUM名まで*/

課題の指定でこうなっているのだとは思いますが、わざわざ構造体をmallocするのはこの場合冗長極まるでしょう。struct Student pg[NUM]でよかった。

mallocのようなメモリー確保は基本的に時間がかかるのです。今回のような小さなプログラムでは問題になりませんが、深いループの中でのメモリー確保は時として高速化の妨げとなることもあります。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/10/21 09:56

    丁寧なご回答ありがとうございます。
    とりあえずメモリの確保のところはじぶんでできました。
    pg[i] = (struct Student*)malloc(sizeof(struct Student)); /* メモリを確保 */
    if (pg[i] == NULL) {
    printf("メモリを確保できませんでした。\n");
    exit(1);
    }
    あと、標準入力が終了したら以下のような処理でよろしいでしょうか。
    if (dataRead(pg[i]) == EOF) { /* 標準入力が終了したら */
    num = i;
    break;
    }
    最後に、そもそもの質問なのですが、このプログラムの動作は一気に数人分のデータを標準入力してエンターが押されたときに表示の処理に移るという仕組みなのでしょうか。

    キャンセル

  • 2018/10/21 11:34

    > 最後に、そもそもの質問なのですが、このプログラムの動作は一気に数人分のデータを標準入力してエンターが押されたときに表示の処理に移るという仕組みなのでしょうか。

    いや、改行区切りでEOFが来るかNUMを超えるまで入力を受けるという状態でしょう。

    キャンセル

+1

・seisekiはint型なのに0.6をかけたらdouble型にならないのでしょうか。

なります。
double型の値をint型の変数に代入するのは問題ありません。小数部が切り捨てられて代入されます。

・標準入力が終了したら..とありますがそのあとどんな処理をするべきでしょうか。

それを考えるのが問題では?

・メモリを確保..とありますが100回繰り返したら結局100名分の構造体配列を用意するのと一緒なのではないのでしょうか。 

はい。その通りです。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+1

seisekiはint型なのに0.6をかけたらdouble型にならないのでしょうか。

整数型に0.6掛けたものはdoubleになっています。
が代入時に暗黙の変換が行われ整数型に変換されます。

・標準入力が終了したら..とありますがそのあとどんな処理をするべきでしょうか。

入力が失敗した = 何名分入力したかが確定した
なので、何名分のデータがあるかを表示するために必要なことはなんだと思いますか?

メモリを確保..とありますが100回繰り返したら結局100名分の構造体配列を用意するのと一緒なのではないのでしょうか。

100名分確保したら、むしろポインタを置く場所の分だけメモリは浪費します。
しかし、果たして100名分も常に確保する必要がありますか?


あなたが書いたコードについて

   return gets(g);                  /* 1行データを読み込む */

getsだけでは構造体にデータを入れる事はできません。
また、gに確保してあるメモリは76文字分程度になるので
getsを用いるには不安があります。

    printf("%s %s %d %d %d %f\n", g->name, g->no, g->c_test, g->k_test, g->kadai, (double)g->seiseki); /* 1名表示 */

前述した通り、seisekiはint型なのでdouble型として表示しようとしても特に意味は無く
小数点以下は0として表示されるでしょう。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.58%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る