平成27年秋期試験午後問題 問9

午前試験免除制度対応!基本情報技術者試験のeラーニング【独習ゼミ】

問9 ソフトウェア開発(C)

次のCプログラムの説明及びプログラムを読んで,設問1,2に答えよ。

〔プログラムの説明〕
 入退室が制限されているエリア(以下,制限エリアという)の入退室の状況を記録したファイルを読み込んで,入退室状況を印字するプログラムである。
 X社では,ICカードを用いた入退室システムを導入して,制限エリアの入退室を管理している。図1に,X社の制限エリアのレイアウトを示す。ドアは5か所(ドア番号11,12,21,22,31)あり,各ドアの内外にカードリーダーが設置されている。入室時と退室時には,カードリーダーにICカードをかざしてドアを開錠する。各ドアのカートリーダーにICカードをかざす都度,その入退室の状況を記録した1行の入退室レコードが 入退室ログに書き込まれる。
 制限エリアには,機密度に応じたレベルが設定されている。レベルは,外部からその部屋へ入るまでに開錠して通過する必要があるドアの数で表す。ドア番号は,10の位がレベルを表す。入退室できるレベルは,ICカードごとに設定されている。
pm09_1.png
  • 入退室レコードは33桁の固定長の文字列で,次に示す項目から成る。
    pm09_2.png
    1. カードIDは,ICカードを識別するIDであり,英数字から成る。
    2. 日付と時刻は,カードリーダーにICカードをかざした日付と時刻である。
    3. 入退は,"I"(入室)又は"O"(退室)である。
    4. 可否は,"A"(開錠)又は"R"(拒否)である。
    5. 名前は,ICカードの使用者を識別する名前であり,英数字と空白から成る。
    6. レコードの終端には,改行文字が付いている。
  • 入退室ログから印字したい日付・時間帯の入退室レコードを抽出し,カードID,日付及び時刻(先頭の18桁)で昇順に整列したファイルを Access.Log とする。図2に,Access.Log のレコードの例を示す。
    pm09_3.png
  • Access.Log から読み込んだ1レコードごとに,カードID,名前,日時,ドア番号,入退,可否から成る1行を,図3に示す様式で印字する。日時以降の項目は,ICカードをかざしたドアのレベルに応じて19,39,59桁目のいずれかから印字する。
    pm09_4.png
    1. カードID及び名前は,同一カードIDが続くとき,先頭の行だけに印字する。
    2. 日時は,"月 - 日 時:分"の形式で印字する。
    3. 入退は,"I"なら"IN"と,"O"なら"OUT"と印字する。
    4. 可否は,"A"なら何も印字せず,"R"なら"IN"又は"OUT"の直前に"(R)"を印字する。
  • ライブラリ関数 strcmp(s1,s2) は,文字列 s1 と s2 を比較し,s1<s2 のとき負の値を,s1=s2 のとき0を,s1>s2 のとき正の値を,それぞれ返す。また,ライブラリ関数 strcpy(s1,s2) は,文字列 s2 を s1 に複写する。
pm09_5.png

設問1

プログラム中の に入れる正しい答えを, 解答群の中から選べ。
a,b に関する解答群
  • cardID = lastID
  • lastID = cardID
  • logEOF = 0
  • logEOF = EOF
  • strcpy(cardID,"  ")
  • strcpy(cardID,lastID)
  • strcpy(lastID,"  ")
  • strcpy(lastID,cardID)
c に関する解答群
  • door[0] - '0'-1
  • door[0] - '0'
  • door[0] - '0'+1
  • door[0] - '3'
解答選択欄
  • a:
  • b:
  • c:
  • a=
  • b=
  • c=

解説

aについて〕
[a]は、fscanf関数の実行結果が EOF の場合に実行されます。EOF(End Of File)は、ファイルの終端に達したことを意味するデータなので、logFile がファイルの終端に達したときに行うべき処理が入ります。

main関数を見るとwhile文の繰返し条件として logEOF != EOF が設定されています。ファイルが終端に達したときにはそれ以上読み込むことができませんから、[a]では変数 logEOF に EOF を格納して、main関数の繰返し処理を中止する必要があります。

a=エ:logEOF = EOF

bについて〕
この分岐処理では、2つのカードIDが等しいときに空白を出力し、異なるときに cardID と name を出力しています。これは、図3の印字結果における「カードID 名前」列を出力する処理であることがわかります。

[b]は、strcmp関数の戻り値が0でない場合に実行される処理です。strcmp関数の戻り値が0以外になるのは、cardID が lastID と異なる場合、すなわち Accees.Log の行が別のカードIDに移ったときです(アクセスログはカードIDの昇順に整列されているので)。読み込んだ行のカードIDが1つ前の行のカードIDと同じ場合、「カードID 名前」列には空白を出力するので、次行以降の比較のために cardID の値を lastID に退避(保存)させておく必要があります。strcpy関数は、第2引数の値を第1引数に複写するので、「ク」の「strcpy(lastID,cardID)」が適切です。※charは配列であるためイコールによる代入はできません。

【別解】
char型配列 lastID は宣言のみされており、プログラム中で中身の書き換えがされていません。よって[b]には lastID の書き換え(代入、コピー)処理が入る可能性が高いです。選択肢中、lastID を意味がある値で更新しているのは「ク」だけです。

b=ク:strcpy(lastID,cardID)

cについて〕
[c]の下部にあるwhile文は、「レベル1」列と「レベル2」列の空白を出力する処理です。door には、ドア番号を表す2桁の数字(の文字データ)が格納されており door[0] はその10の位、すなわち制御エリアのレベルを表しています。

図3の印字結果を見ると、ドアのレベルによって印字位置が異なっており、次のように空白の出力で場所を調整しています。
  • 「レベル1」列にデータを印字する場合、空白を0回出力
  • 「レベル2」列にデータを印字する場合、空白を1回出力
  • 「レベル3」列にデータを印字する場合、空白を2回出力
つまり、図3のように印字するためには「レベルの値-1」回だけ空白の出力を実行すれば良いことになります。よって「ア」の「door[0] - '0'-1」が適切です。

c=ア:door[0] - '0'-1

※ここで、'0'を引いている理由は door[0] の値がchar型だからです。ASCIIコードでは文字'1'は49、文字'2'は50、文字'3'は51が割り当てられているので、文字'0'(48)を引くことで整数化しています。

設問2

次の記述中の に入れる正しい答えを,解答群の中から選べ。

 プログラムを変更して,入退室順序の整合性を検査する処理を追加する。
 各入退室者は,最初は外部(レベル0)にいて,最後は外部に出るものとする。正しく入退室していれば,制限エリアのレベルは0から始まり,ドアを通過する都度,レベルが1ずつ変化し,最後は0に戻るはずである。しかし,抽出した日付・時間帯よりも前(後)に入室(退室)している場合や,ICカードをかざさずに人の後についてドアを通過した場合などには,順序の不整合が起きる。
 なお,カードリーダーにICカードをかざしてドアが開錠した場合は,必ず入退室するものとする。
  • 順序の不整合が起きた場合は,本来入退室の記録があるべき行のレベル1の印字位置に "***** Level x-->y"と印字する。これは,入退室者がこの前後にレベルxからyヘ移動したはずであるが,その記録がないことを意味する。
  • 図4に,不整合がある入力レコードの例を示す。図4のレコードを用いて変更後のプログラムを実行すると,印字結果は,図5のようになる。
pm09_6.png
 プログラムを,次のように変更する。
  • プログラムに,次の文を追加する。
    pm09_7.png
  • プログラムの末尾に,次の二つの関数を追加する。
    pm09_8.png
d,e に関する解答群
  • 行③
  • 行④
  • 行⑤
  • 行⑥
  • 行⑦
  • 行⑧
f,g に関する解答群
  • afterLevel
  • beforeLevel
  • doorLevel
  • level
解答選択欄
  • d:
  • e:
  • f:
  • g:
  • d=
  • e=
  • f=
  • g=

解説

dについて〕
checkLevel 関数は、「現在読み込んだレコードのレベル」と「前回読み込んだレコードのレベル」を比較して、整合性が取れているか判断し処理する関数になります。整合性が取れていない場合はレベルが変化した旨のメッセージを出力する処理をします。

上記性質と図5の印字結果より、checkLevel 関数の実行箇所は以下の条件を満たさなければなりません。
  • 「カードID 名前」列の空白を出力した後
  • 「レベル」列の空白を出力する前
よって、正解は行⑦の後になります。

d=オ:行⑦

eについて〕
clearLevel 関数では、ある特定条件のどちらかを満たす場合、変数 level に0を代入し印字処理を実施しています。その条件とは以下の2つの条件です。
  • ファイルの終端を読み込んだのに、変数 level が0より大きい場合
    →つまり、level0への退出記録がない場合です。
  • レコードのカードIDに変化があったのに、変数 level が0より大きい場合
    →つまり、以前のカードID保持者(変数 lastID)の退出記録がない場合です。
上記条件より、clearLevel 関数の実行は Access.Log のレコード内容を出力し、その次のレコードを読み込んだ直後(レコードが切り替わるタイミング)に実行する必要があります。よって、正解は行④の後になります。

e=イ:行④

fgについて〕
checkLevel 関数内のif文の条件式
if (strcmp(dir, "I") == 0){
 beforeLevel = doorLevel - 1;
} else {
 afterLevel = doorLevel - 1;
}
に着目すると、ドアのレベルをベースにして、入室の場合には beforeLevel を1小さくし、退室の場合には afterLevel を1小さくしています。この2つの変数は、ドアレベルと入退室の別から判断される入退室前のエリアレベル、入退室後のエリアレベルを表します(正常時)。例えば、ドアのレベルが2(door[0] == 2)だと、各変数の値は、
入室(dir が"I")
beforeLevel = 1、afterLevel = 2
退室(dir が"O")
beforeLevel = 2、afterLevel = 1
となります。afterLevel は、当該入退室後に利用者がいるであろうエリアであり、checkLevel 関数の最後では、この afterLevel を大域変数 level に代入しています。本来であれば、次のレコードを読み込んだときに level から別のエリアに移動するはずなので、level は beforeLevel と一致しなければなりません。一致しないということは、lebel から beforeLevel へのログが存在しないということです。

不整合のログは、「入退室者がこの前後にレベルxからレベルyに移動したはずであるが、その記録がない」ことを示すので、xの位置には level、yの位置には beforeLevel を指定するのが適切です。

f=エ:level
 g=イ:beforeLevel

Pagetop