青山学院大学

前期試験 ・ 2013 年 8 月 1 日 4 時限実施 ・ ページ

授業
科目
計算機実習 I 学生番号 学科 学年 フリ
ガナ
  評点
                   氏名    
担当者 DÜRST, Martin J.、松原、松村、盛川、米澤、白川、高橋

英語の用語 (18 点)

プログラミング一般やプログラミング言語 C/C++ において使われる次の英語の用語の日本語訳と説明を書きなさい。日本語訳ではできるだけ片仮名を使わないようにしてください。

string
文字列;0 個以上の文字 (char 型の値) の後に「\0」(ナル文字) を格納した、char 型の配列

expression statement
式文;式の後にセミコロンを続けた形の文

structure
構造体;複数のデータを組み合わせたデータ型

derived type
派生型; 基本的な型から派生された型、例: 配列、ポインタ、構造体、関数

recursive function
再帰的関数、再帰を使った関数、すなわち自己呼び出しする関数

formal parameter
仮引数; 関数の定義中で使われる引数、関数内では変数と同様に振る舞う

indirection operator
間接演算子; ポインタが指す値を返す演算子

call by reference
参照渡し;関数に参照を渡す。呼出し元の変数に影響を与える

side effect
副作用;プログラムの実行による式の計算以外の結果 (入出力、グローバル変数の変更など)

式の評価 (14 点)

次のテーブルの式の値を計算しなさい。(ヒント: + が << より優先)

番号 番号
2+3*5 17 2+3*5 17
35 / 6 5 35 | 6 39
62 % 11 7 35 & 7 3
35 ^ 16 51 1 + 3, 2 2
2 - 2 ? 9 : 15 15 0x7A - 0x63 23
55 >> 3 6 3+5 * 7 38
'9' - '4' 5 (int) 0.345E4 3450
~~27 27 'a' - 'A' 32

前期試験 ・ 2013 年 8 月 1 日 4 時限実施 ・ ページ

標準入出力 (23 点)

プログラム断片の穴埋め (5 点)

次のプロブラムの断片は、標準入力から文字データを buffer 変数に読み込んで、そこから標準出力へと出力する。空欄を埋めなさい。

    char buffer[80];
    while((fgets(buffer, 80, stdin)) != NULL){
        printf("%s", buffer);
    }
  

コマンドラインの使用 (3 点)

上記のプログラム断片を含まれるプログラム cat.exe を使って、ファイル input.txt からデータを読み込んで、ファイル output.txt に書き出すコマンドラインを書きなさい。

cat.exe <input.txt >output.txt

fgets 関数の仕組み (4 点)

fgets 関数は「一行入力」と言われています。行の長さによって、上記の断片の挙動を細かく説明しなさい。

fgets の最後の二バイト分は改行とナル文字に使われるので、
行の長さが 78 バイトまでは一行ごとにループが一回回ります。
行の長さが 79 バイト以上では、一回ループが回るごとに 79 バイト分の
データが処理されます。場合によっては最後に改行文字だけの入力になります。

文字の数 (7 点)

上記のプログラム断片の fgets 関数を一回呼んだ場合、文字コードや文字種によって最大で何文字が読み込まれるのか算出しなさい。

文字コード文字種文字数 文字コード文字種文字数
Shift_JIS半角英数字78 UTF-8半角英数字78
Shift_JIS全角英数字39 UTF-8全角英数字26
Shift_JIS半角カナ78 UTF-8半角カナ26
Shift_JIS全角かな39 UTF-8全角かな26
Shift_JIS漢字39 UTF-8漢字26
Shift_JISギリシャ文字39 UTF-8ギリシャ文字39
UTF-8ヘブライ文字39 UTF-8ハングル文字26

fgets 関数の必要性 (4 点)

多くの教科書では fgets 関数のではなく、gets 関数が使われている。gets 関数はなぜ絶対に使ってはいけないのかを詳しく説明しなさい。

gets 関数は fgets 関数と同じように一行入力に使いますが、
読み込む長さの制限がありません。それにより、入力される行が長い
ときに用意されているメモリの領域を超え、隣の変数などを上書きする。それは
Segmentation Fault やウイルスの侵入につながる。

青山学院大学

前期試験 ・ 2013 年 8 月 1 日 4 時限実施 ・ ページ

授業
科目
計算機実習 I 学生番号 学科 学年 フリ
ガナ
  評点
                   氏名    
担当者 DÜRST, Martin J.、松原、松村、盛川、米澤、白川、高橋

構造体 (10 点)

次のプログラムは、学生の試験成績を管理するプログラムである。 100人分の学生情報と試験成績のデータを入力すると、試験科目ごとの平均点、全科目の平均点、を算出するようにしたい。このプログラムの空欄を埋めなさい。

#include <stdio.h>

typedef struct {
    int  number;
    char name[20];
    int  japanese;
    int  math;
    int  english;
} Student;


int main(void)
{
    double total_japanese = 0, total_math = 0, total_english = 0,
              average_japanese, average_math, average_english, average_overall;

    Student students[100];

    // 入力部分省略

    for (i=0; i < 100; i++) {
        total_japanese += students[i].japanese;

        total_math     += students[i].math   ;

        total_english  += students[i].english ;
    }
    average_japanese = total_japanese / 100;
    average_math     = total_math / 100;
    average_english  = total_english / 100;
    average_overall = (average_japanese + average_math + average_english) / 3;

    // 出力の一部部分省略
    printf("全科目の平均点: %5.2lf\n", average_overall);

    return 0;
}

プログラム開発におけるテストの使用 (10 点)

プログラム開発の場合、テストを使うのが非常に大事であることを授業で学んだ。
テストの有効な使用を細かく説明しなさい。

プログラム開発はできるだけこまめに行った方がいいです。細かい変更でも
再度コンパイル、テストをした方がいいですが、そのために手動でテストをするのではなく、
自動でテストした方がいいです。授業で習った方法としては、入力データをファイルに用意し、
期待の出力のデータもファイルで用意し、diff で自動的に期待と実際の差異をとる。
テストでは簡単な使用例だけではなく、様々なケース (例: 長い入力、空の入力)
を含め用意した方が良い。一部の入力でしか動かないプログラムは使い物になりません。
さらに、プログラムが機能的にできあがったところでも、読みやすさや更なる変更・発展のために
書き直す (refactoring の) 場合、テストを使って安心して効率よく進むことが可能。

前期試験 ・ 2013 年 8 月 1 日 4 時限実施 ・ ページ

ポインタ (合計 20 点)

参照渡し (swap 関数) (11 点)

次の左側のプログラム (A) は、swap 関数 (2 つの値を交換する関数) の典型的な間違い例である。 swap 関数を呼び出した際に、値が正しく交換されるように訂正した右側のプログラム (B) の空欄を埋めなさい。 また、2 つのプログラムの実行結果の空欄を埋めなさい。

2つのプログラム例:

/* プログラム (A) */
/* 間違った swap 関数 */
#include <stdio.h>

void swap(int a, int b) {
    int tmp;
    tmp = a;
    a = b;
    b = tmp;
}

int main(void) {
    int x=1, y=2;

    printf("x = %d, y = %d\n", x, y);
    swap(x, y);
    printf("x = %d, y = %d\n", x, y);

    return 0;
}
/* プログラム (B) */
/* 正しい swap 関数 */
#include <stdio.h>

void swap(int *a, int *b) {
    int tmp;

    tmp = *a;
    *a = *b;
    *b = tmp;
}

int main(void) {
    int x=1, y=2;

    printf("x = %d, y = %d\n", x, y);
    swap(&x, &y);
    printf("x = %d, y = %d\n", x, y);

    return 0;
}
プログラム (A) の実行結果:
x = 1, y = 2

x = 1, y = 2
プログラム (B) の実行結果:
x = 1, y = 2

x = 2, y = 1

配列引数 (9 点)

次のプログラムは配列中の値の最大値を求め、その値のインデックス(その値が配列の何番目にあるか)を返す関数とその使用例である。 右側の「int find_max(int array[], int size)」関数を正しく機能するように埋めなさい。

プログラム (2 段組):

#include <stdio.h>

/* 配列中の最大値を探す関数 */
/* 第1引数は配列の先頭ポインタ */
/* 第2引数は配列のサイズ */
int find_max(int array[], int size);

int main(void) {
    int n[] = {3, 12, -2, 4, 25, 9, 10};
    int s = 7;
    int id;

    id = find_max(n, s);

    printf("Max value is %d\n", n[id]);

    return 0;
}
int find_max(int array[], int size) {
    int i = 0;
    int max = array[0];
    int max_i = 0;
    for (i=1; i < size; i++) {
        if(array[i] > max) {
            max = array[i];
            max_i = i;
        }
    }
    return i;
}

青山学院大学

前期試験 ・ 2013 年 8 月 1 日 4 時限実施 ・ ページ

授業
科目
計算機実習 I 学生番号 学科 学年 フリ
ガナ
  評点
                   氏名    
担当者 DÜRST, Martin J.、松原、松村、盛川、米澤、白川、高橋

関数の書式と利用 (合計 18 点)

プログラムの完成 (10 点)

下記のプログラムは組合せ(combination)nCrを計算するプログラムであるが、コードが不足しているため正しく動作しない。 正しい計算結果を出力するためには、どこにどのようなコードを追加すれば良いか、答えなさい。 複数箇所の修正が必要な場合は、全て答えなさい。

挿入箇所 (行番号と詳細) 挿入内容
1行目と2行目の間 include <stdlib.h>
2行目と3行目の間 long factorial(int n);
15行目と16行目の間 return product;
20行目と21行目の間 long ans;
28行目()の中 n, r
 1) #include <stdio.h>
 2)
 3) long combinations(int n, int r)
 4) {
 5)     return factorial(n) / factorial(r)
 6)                         / factorial(n-r);
 7) }
 8)
 9) long factorial(int n)
10) {
11)     long product = 1;
12)     int i;
13)
14)     for (i = 2; i <= n; i++)
15)         product *= i;
16) }
17)
18) int main(void)
19) {
20)     int n, r;
21)
22)     printf("Input n and r: ");
23)     scanf("%d %d", &n, &r);
24)     if (n < 0 || r < 0 || n < r){
25)         printf("不正な入力です。プログラムを終了します。");
26)         exit(0);
27)     }
28)     ans = combinations();
29)     printf("nCr= %ld\n", ans);
30)
31)     return 0;
32) }

再帰的関数 (8 点)

上記のプログラムの factorial 関数を再帰的関数の形式で書き直しなさい。ただし上記の問題の訂正箇所に留意し、正しく動作するよう書き直すこと。

long factorial(int n)
{
    if (n <= 1)
        return 1;
    else
        return factorial(n-1) * n;
}

前期試験 ・ 2013 年 8 月 1 日 4 時限実施 ・ ページ

C++ とオブジェクト指向 (合計 18 点)

プログラムの穴埋め (7 点)

プラン名基本料金 (円)通話料 (円/分)無料通話分 (円)
ときどきプラン980301000
おしゃべりプラン1980202000

以下のプログラムでは、入力された1ヶ月あたりの通話時間 (分) に基いて、 2つの携帯電話の通話料金のプラン (右の表参照) を比較し、お得な方のプランの名前を出力する。 いずれのプランでも、その月の通話料が無料通話分に収まる場合は基本料金のみ、そうでない場合、 基本料金と、超過した分の通話料を合わせた金額を支払う。

// main.cpp
#include <stdio.h>
#include "Plan.cpp"
int main(void)
{
  Plan a = Plan((char *)"ときどきプラン",
                980, 30, 1000);
  Plan b = Plan((char *)"おしゃべりプラン",
                1980, 20, 2000);
  int mins;

  printf("1ヶ月あたりの通話時間(分): ");
  scanf("%d", &mins);
  Plan better_plan = a.compare(b, mins) ? a : b;
  printf("「%s」の方がお得です。\n",
    better_plan.get_name());

  return 0;
}
            
// Plan.cpp
#include "Plan.h"
Plan::Plan(char *n, int mc,
                int o, int am)
{
  name = n; monthly_cost = mc;
  overage = o; anytime_mins = am;
}

char* Plan::get_name()
{
  return name;
}

// 月ごとの支払い総額を算出
int Plan::total_cost(int minutes)
{
  int call_charges = overage * minutes;
  if(call_charge > anytime_mins)
    return monthly_cost + call_charges
                        - anytime_mins;
  else
    return monthly_cost;
}

// 別のプランと月ごとの支払い総額を比較
int Plan::compare(Plan other, int m)
{
  int my_total_cost = total_cost(m);
  int other_total_cost =
    other.total_cost(m);
  return my_total_cost <= other_total_cost;
}
            
// Plan.h
class Plan {
    char *name; // 通話プラン名
    int monthly_cost; // 基本料金 (円)
    int overage; // 通話料 (円/分)
    int anytime_mins; // 無料通話 (円)

public:
    Plan(char *n, int mc, int o, int am);
    char* get_name();
    int total_cost(int m);
    int compare(Plan other, int minutes);
};
                

Plan.h より、Plan クラスのコンストラクタの宣言を抜き出しなさい。 (2 点)

Plan(char *n, int mc, int o, int am);

コンストラクタの役割および実行される段階について答えなさい。 (2 点)

コンストラクタはクラスのインスタンスが生成される際に実行され、初期化を行う。

C における構造体と、C++ におけるクラスの大きな違いを2つ挙げなさい。 (4 点)

  1. クラスのメンバは public: を指定しない限り外からアクセスが不可能
  2. C++のクラスはメンバ関数をもつことができる

オブジェクト指向における「カプセル化」の概要とその利点について説明しなさい。 (3 点)

カプセル化は、インスタンスがメンバ変数に持つデータをクラスの外部から保護することを指す。 カプセル化を行うことで、プログラムの各部分の独立性を高め、クラス内部での変更が容易になる。

青山学院大学

前期試験 ・ 2013 年 8 月 1 日 4 時限実施 ・ ページ

授業
科目
計算機実習 I 学生番号 学科 学年 フリ
ガナ
  評点
                   氏名    
担当者 DÜRST, Martin J.、松原、松村、盛川、米澤、白川、高橋

演算子の説明 (18 点)

次の演算子それぞれの意味 (又は、存在しない場合その旨) を示しなさい。

番号演算子
(単項)
説明 番号演算子
(二項)
説明 番号演算子説明
 &アドレス演算子  &ビットごと論理積  &&論理積
 *間接演算子  *掛算  **存在しない
 -符号反転  -引き算  --ディクレメント
 <存在しない  <小なり  <<左シフト
 +(ほぼ)効果無し  +足し算  ++インクレメント
 |存在しない  |ビットごと論理和  ||論理和

プログラムの読みやすさ (合計 13 点)

プログラムを読みやすくするための三つアドバイスとその理由を書きなさい。(9 点)

  1. インデント (字下げ) をする。それにより、プログラムの構造 (関数、ループ、条件分岐) が一目瞭然になる。
  2. 変数名や関数名の分かりやすい名前。分かりやすい名前を使うと、多くの場合コメントを使わなくてもプログラムの意味が分かりやすい。
  3. 構造体や関数の使用。それにより、関係するデータや操作が関連付けられ、抽象化される。

なぜプログラムの読みやすさが大事なのかの理由を二つ列挙しなさい。(4 点)

  1. 他人 (例: 会社の同僚) がプログラムを引き継ぐ又は協力するときに大事。
  2. 自分が後日にプログラムを変更するときに読みやすさが大事。

授業へのコメント (合計 6 点)

この授業で一番分かりにくかったことを書きなさい。(具体的に書いてあれば内容にかかわらず 3 点)

@@@@

この授業で一番勉強になったことを簡単に説明しなさい。(具体的に書いてあれば内容にかかわらず 3 点)

@@@@