プログラミングC
第4回 演習問題


A問題(50点)

演習問題1(復習・確認 I)

printf を使って、ポインタの動作の確認を行う。
#include <stdio.h>

int main(){

  int data = 312;
  int *p;
  p = &data;

  printf("(1) ___\n", &data);
  printf("(2) ___\n", data);
  printf("(3) ___\n", *data);
  printf("(4) ___\n", &p);
  printf("(5) ___\n", p);
  printf("(6) ___\n", *p);
  printf("(7) ___\n", &(*p));

  return 0;
}

上記プログラムについて、(1)-(7) の printf の下線部に 適切な書式を答えなさい。 また、printfの変数部分(第2引数)が実質的に何を表しているか、以下のA-Eから適切なものを選択せよ。 これらの答えは解答例のような形で、prog01.txtに記入し、提出すること。
なお、コンパイル時にエラーになるものについては Eを選択し、その原因(理由)を記入すること。

  1. 変数dataの値
  2. ポインタ変数pの値
  3. 変数dataのアドレス
  4. ポインタ変数pのアドレス
  5. エラーまたは該当無し

解答例:
(1) B: %f
(2) E: 変数が宣言されていない
(3) C: %lf
   :

(提出ファイル名: prog01.txt)

演習問題2(確認II・ポインタによる参照)

下記のプログラムは、標準入力から入力される2つの変数 a, b の値とそれらのアドレス、 さらにそれらの値の積と商を表示するプログラムである。 ポインタ変数p, qの代入部分以外では、変数a,bを使わずに 同じ出力になるように書き換えよ。
ただし、printfの書式部分は変更しないこと。

(提出ファイル名: prog02.c)
#include <stdio.h>

int main(){
  double a, b;
  double *p, *q;
  
  /*
   * ここでポインタ変数p, qに適切な代入を行った後、
   * それ以降は変数 a, b を使わない形に書き換える
   * (以下のa,bの代入文もa,bを使わないように書き換えること)
   */
  printf("aの値を入力\n");
  scanf("%lf",&a);
  printf("bの値を入力\n");
  scanf("%lf",&b);
      
  printf("aの値: %.1f, aのアドレス: %p\n", a, &a);
  printf("bの値: %.1f, bのアドレス: %p\n", b, &b);
  printf("積: %.1f\n", a * b);
  printf("商: %.1f\n", a / b);
  return 0;
}
実行例: (アドレスの値が違うことがある)
% ./a.out 
aの値を入力
3.2
bの値を入力
6.7
aの値: 3.2, aのアドレス: 0x7ffe6a3cd938
bの値: 6.7, bのアドレス: 0x7ffe6a3cd930
積: 21.4
商: 0.5

補足: アドレスの表示は16 進数での表示であることに注意せよ。 また、状況(計算機・コンパイラ・実行環境等)によって変数のアドレスの割り当ては変化し得るので、 表示されるアドレスがいつも同じとは限らないことに注意すること。

B問題(50点)

演習問題3(文字列の基本)

標準入力から文字型配列 str1 に読み込まれた文字列を 別の文字型配列str2にコピーするプログラムを作成する。 str2にコピーする際、カギ括弧[]でその文字列を囲むと共に 母音(aiueoAIUEO)を除外する。
実行例を参考に、以下のプログラムを雛型として、完成させよ。

(ヒント)引数の文字が母音だったときに1を返し、それ以外は 0を返すような関数を作成し、利用すると良い。

(提出ファイル名: prog03.c)
#include <stdio.h>
#define N 100
 
/* 適宜プロトタイプ宣言を追加 */

int main(){
	char str1[N], str2[N];
	/* 適宜変数を追加すること */
	
	printf("Input string: ");
	scanf("%s", str1); /* 文字列を標準入力から入力 */
	
	/*
	 * 最初の開括弧 [ を代入し
	 * 入力した文字列を1文字ずつチェックし,
	 * 母音ではなかった場合str2に追加する。
	 * さらに,最後の閉括弧 ] を代入し,
	 * str2 の末尾の処理(ヌル文字追加)を行う
	 */
	
	printf("str1 : %s\n", str1);
	printf("str2 : %s\n", str2);
	
	return 0;
}

/* 適宜関数を追加 */
ただし、 実行例:
% ./a.out
input string: Aizuwakamatsu
str1: Aizuwakamatsu
str2: [zwkmts]
%

演習問題4(文字列同士の比較)

世界の空港には国際空港運送協会(IATA)により3文字の空港コードが 定められている。 今、日本にある空港のコードとその名称がファイルIATA-jp.txtに 格納されているものとして、 標準入力から入力された空港コードに対して、該当する空港があれば空港名を 表示し、なければ無い旨を表示するプログラムを作成したい。 ただし、空港コードの入力は英字3文字(大文字)であり、 control+dでプログラムは終了するものとする。 なお、ファイルは途中(あるいは最後)まで 読み込むとそれ以前のデータにはアクセスできないので、 一度クローズして再度オープンするか、またはrewind()関数を 使ってデータの読み込み位置をファイルの先頭に戻す処理が 必要になる(rewind()関数を使って見たい人は自分で調べてみて下さい)。 以下はfopen(), fclose()を繰り返すタイプのプログラムである。 不足部分を補って完成させなさい。変更部分が多くなるがrewind()を 使うタイプに変更しても構わない。 また、文字列の比較には文字列用ライブラリ関数を使って良い。
/home/course/prog1/public_html/2024/ex/ex04/より、 IATA-jp.txtをコピーしておくこと。

(提出ファイル名: prog04.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
  FILE *fp;
  char code[4], airport[30]; /* 空港コードと空港名用文字配列 */
  char buf[4]; /* キーボード入力用文字配列 */
  int status; /* fscanfの戻り値用 */
  /* 適宜変数は追加して良い */

  while(1) {
    printf("Input IATA code: ");
    if (scanf("%s",buf)==EOF) break;
  
    /* ファイルオープン */
    if ((fp=fopen("IATA-jp.txt","r"))==NULL) {
      printf("Code file open error!!\n");
      exit(2);
    }
    
    /* ここに、空港コードを読み込みながら、一致する
    ものがあるかをチェックする処理と、結果を表示する
    処理を追加する */

    fclose(fp);
  }
  printf("\n");

  return 0;
}
実行例:
% ./a.out 
Input IATA code: HND
HND is TOKYO(Haneda) Airport.
Input IATA code: FKS
FKS is FUKUSHIMA Airport.
Input IATA code: MIE
MIE is not found.
Input IATA code: ITM
ITM is OSAKA(Itami) Airport.
Input IATA code: control+d
%

Extra問題

演習問題5(文字列の挿入)

文字列のn文字目の後ろに、標準入力から入力した単語を挿入するプログラムを作成せよ。プログラムの動作は以下のようにすること。

また以下のようなエラーがあった場合には、エラーを表示して次の入力を受け付けること。

(提出ファイル名: prog05.c)

実行例:

% ./a.out
Current string: [Wakamatsu]
input-> 0 Aizu
Current string: {Aizu}[Wakamatsu]
input-> 17 City
Current string: {Aizu}[Wakamatsu]{City}
input-> 11 abcde
Current string: {Aizu}[Waka{abcde}matsu]{City}
input-> 23 apple
Current string: {Aizu}[Waka{abcde}matsu{apple}]{City}
input-> 150 end
Error in position
Current string: {Aizu}[Waka{abcde}matsu{apple}]{City}
input-> 0 zzz.....合計250文字の「z」.....zzz  <= (注)全体の文字数が255文字を越えるようなデータ(文字列)を与える。
Error in length
Current string: {Aizu}[Waka{abcde}matsu{apple}]{City}
input-> Ctrl-D
%

演習問題6(簡易テキストエディタ)

テキストファイル myedit_in.txt を開き、そのファイルの編集を行って、結果を myedit_out.txt に保存するプログラムを作成する。

このプログラムはリスト6-1と、演習問題5を参考にして作成する。ファイルの編集は表に挙げたコマンドを与えることで行うが、 編集するテキストファイルの大きさは改行文字を含めて1023文字以下とする。コマンドを認識できなかったり、 実行した結果がバッファのサイズを超えてしまう場合はERROR と表示して、プログラムを終了する。 実装するコマンドを以下の表に挙げる。このプログラムの実行例はリスト6-2であり、コマンドを入力することでテキストを編集する。

コマンド パラメータ 意味 備考
print なし バッファの内容を表示する
insert num str num: 整数 str: 文字列 バッファの num 文字目に、
文字列 str を挿入する。
num が負の数だった場合は、
バッファの後端に文字列 str を挿入する。
cut num1 num2 num1: 整数 num2: 整数 num1 文字目から
num2 文字を削除する。
space num num: 整数 バッファの num 文字目に空白を挿入する。 num が負の数だった場合、
バッファの後端に空白を挿入する。
enter num num: 整数 バッファの num 文字目に改行を挿入する。 num が負の数だった場合、
バッファの後端に行を挿入する。
undo なし バッファを直前の編集前に戻す。 遡ることができる履歴は、
1 つ前まででよい。
quit なし 実行結果を myedit_out.txt に保存し、
プログラムを終了する。

まず上記のコマンドを認識するプログラムを、リスト6-1を参考にして作成する。 下記の手順に従い、プログラムの確認と拡張を行うこと。

  1. すべてのコマンドとパラメータを読み込み、認識したコマンドを表示する

リスト6-1: エディタプログラムのスケルトン(skeleton_prog06.c)

#include .....
#include .....
#include .....

#define MAXLEN 1024

int main(){
	int i;
	char c;
	FILE *fp;
	char buffer[MAXLEN];
	char query[MAXLEN];
	char buffer_prev[MAXLEN];
	int status;
	int arg1, arg2;
	int in_len, buf_len;
	
	/* ファイルのオープン */
	fp = ........;
	if(fp == NULL){
		printf("Cannot open input file.\n");
		exit(1);
	}
	
	/* データの読み出し(最後に'\0'を忘れないこと) */
	buf_len = 0;
	while(1){
		c = fgetc(fp);
		if(c == EOF){
			buffer[buf_len++] = '\0';
			break;
		}
		else{
			buffer[buf_len++] = c;
		}
	}
	fclose(fp);
	
	/* コマンド入力のためのループ */
	while(1){
		status = scanf("%s", query);
		if(status != 1)
			break;

		/* 現在のテキストの長さを取得する */
		buf_len = strlen(buffer);

		/* それぞれのコマンドのチェック */
		if(strcmp("print", query) == 0){ /* printコマンド */
			/* printコマンドの処理を以下に書く */
			continue;
		}
		else if(strcmp("insert", query) == 0){ /* insertコマンド */
			if(scanf("%d %s", &arg1, query) == 2){
				/* insertコマンドの処理を以下に書く */
				continue;
			}
		}
		/* 以下同様に,他のコマンドをプログラムを書く */
		
		/* 該当するコマンドが見付からないとき */
		printf("ERROR\n");
		exit(1);
	}
	
	return 0;
}

コマンドを識別する部分が完成したら、次にコマンドの実行部を作成する。

  1. quitコマンドをを追加する。
  2. print, insertコマンドを実装する。
  3. space, enterコマンドを実装する
  4. cutコマンドを実装する
  5. undoコマンドを実装する(遡れるのは 1 回まででよい)

動作の例をリスト6-2に示す。 scanf( ) の使い方によっては、 ERROR の表示が複数出現するが、 バッファが正しく操作できて、結果を保存できていればプログラムが正しいと評価する。

(提出ファイル名: prog06.c

リスト6-2:実行例

% cat > myedit_in.txt
(改行を入力)
Ctrl-D
# (この処理を行うことで,一行分の空行のみを内容とするテキストファイル myedit_in.txt が作成される)
% ./a.out 
print

insert 0 #include<stdio.h>
print
#include<stdio.h>
insert -1 main(){printf("HelloWorld\n");return
space -1
insert -1 0;}
enter -1
print
#include<stdio.h>
main(){printf("HelloWorld\n");return 0;}
space 18
insert 18 int
print
#include<stdio.h>
int main(){printf("HelloWorld\n");return 0;}
enter 29
enter 53
enter 63
print
#include<stdio.h>
int main(){
printf("HelloWorld\n");
return 0;
}
cut 30 24
print
#include<stdio.h>
int main(){
return 0;
}
undo
print
#include<stdio.h>
int main(){
printf("HelloWorld\n");
return 0;
}
quit
% cat myedit_out.txt
#include<stdio.h>
int main(){
printf("HelloWorld\n");
return 0;
}
%./a.out
c 1 1
ERROR
%./a.out
cut abc 1
ERROR
%./a.out
cut 2 1
ERROR
%