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


A問題(50点)

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

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

int main(){

  int num = 567;
  int *p;
  p = &num;

  printf("(a) %d\n", num);
  printf("(b) %p\n", &num);
  printf("(c) %d\n", *(&num));
  printf("(d) %p\n", p);
  printf("(e) %p\n", &p);
  printf("(f) %d\n", *p);
  printf("(g) %p\n", &(*p));

  return 0;
}

上記プログラムについて、(a)-(g) の printf 文のうち、 同じ出力になる組み合せをすべて列挙せよ。
また、それが何を表しているか、以下の1-4から該当するもの(複数の場合がある場合,複数個)を選択せよ。

  1. 変数numの値
  2. ポインタ変数pの値
  3. 変数numのアドレス
  4. ポインタ変数pのアドレス

解答例:
(a)(b) ⇒ 1, 2
(c)(d)(e) ⇒ 3

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

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

下記のプログラムは、初期値で設定した2つの変数 a, b の値とアドレス、 さらにそれらの値の積(product)と商(quotient)を表示するプログラムである。 プログラム中で宣言された2つのポインタ変数p, qを用いて、 printfによる表示で変数a, bを使わずに同じ出力になるように書き換えよ。

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

int main(){
  double a = 7.0, b = 3.5;
  double *p, *q;
  
  /*
   * ここでポインタ変数p, qに適切な代入を行う
   * さらに以下の4行を変数 a, b を使わない形に書き換える
   */

  printf("Value of a: %.1f, Address of a: %p\n", a, &a);
  printf("Value of b: %.1f, Address of b: %p\n", b, &b);
  printf("Product: %.1f\n", a * b);
  printf("Quotient: %.1f\n", a / b);
  return 0;
}
実行例: (アドレスの値が違うことがある)
% ./a.out 
Value of a: 7.0, Address of a: 0x7ff7b13f9890
Value of b: 3.5, Address of b: 0x7ff7b13f9888
Product: 24.5
Quotient: 2.0

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

B問題(50点)

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

文字 char'A', 'x', '1', 等)と、 文字列 char[]"apple", "orange", "x1y2z3"等)との関係および違いを確認する。

標準入力から文字型配列 str に読み込まれた一つの文字列に対して、 括弧()でその文字列を囲み、英数字(0-9, A-Z, a-z)はそのまま、 それ以外は '_' に変換した,別の文字型配列 str_newに格納し、 両方の配列の内容を表示するプログラムを作成する。以下のプログラムを補って完成させよ。

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

#define N ??? /* ??? にはバッファ配列に最大文字数の文字列を適切に格納可能な数値を指定する */
 
int main(){
	char str[N], str_new[N];
	
	/* 適宜変数を追加すること */
	
	printf("Input : ");
	scanf("%s", str); /* 文字列を標準入力から入力 */
	
	/*
	 * 最初の開括弧 ( を代入し
	 * 入力した文字列を1文字ずつチェックし,
	 * 英数字(すなわち0-9, A-Z, a-z)ならそのまま str_new にコピー,
	 * それ以外なら str_new に'_'を代入する,
	 * さらに,最後の閉括弧 ) を代入し,
	 * str_new の末尾の処理(ヌル文字)が必要となる
	 */
	
	printf("Before : %s\n", str);
	printf("After  : %s\n", str_new);
	
	return 0;
}
ただし、 実行例:
% ./a.out
Input: 2022Q3-Prog1(Programming-C)
Before: 2022Q3-Prog1(Programming-C)
After : (2022Q3_Prog1_Programming_C_)
%

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

国名コードをチェックするプログラムを作成する。

標準入力から3つの英字大文字からなるコードを入力し、そのコードが国名コードであるかどうかを判定する。 判定は、国名コードファイルにそのコードが存在する場合に限り、 正しいと判定する。以下のプログラムを補って完成させよ。
/home/course/prog1/public_html/2022/ex/ex04/より、country-code.txtをコピーしておくこと。

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

#define N ??? /* ??? にはバッファ配列に問題条件に応じた適切な数値を指定する */

int main(){

	char code_file[] = "???"; /* ??? には国名コードファイルのパスを指定する */
	FILE *fp;
	
	char typein[N]; /* 入力コード用文字列バッファ */
	char pickup[N]; /* 国名コード用文字列バッファ */
	
	int status; /* fscanfの戻り値を格納する変数 */
	
	/* 無限ループ */
	while(1){
		printf("Input a code: ");
		if(scanf("%s", typein) == EOF) /* ※1 補足2を参照 */
			break;
			
		/*
		 * ファイルの先頭からコードの探索を行うために,
		 * 新たにコードが入力されるごとに
		 * 国名コードファイルのオープンとクローズを繰り返す
		 */
		if((fp = fopen(code_file, "r")) == NULL){
			printf("Failed to open: %s\n", code_file);
			exit(2);
		}
		
		/* 
		 * ここに,国名コードファイルのコードを1つずつ読み込みながら,
		 * 入力されたコードと一致するかをチェックする処理を追加せよ
		 */
		
		fclose(fp);
	}
	
	printf("\n");
	return 0;
}

ただし、 実行例:
% ./a.out 
Input a code: JPN
The code "JPN" is found.

Input a code: XYZ
The code "XYZ" is not found.

Input a code: Ctrl-D
%

補足1: ダブルクォート( " )で囲った文字列の中にダブルクォートを入れたい場合には、 文字列中のダブルクォートの前にバックスラッシュ( \ )を入れて\" とする。
例: printf( "Use \"man string\"\n" );


補足2: Ctrl-D を入力した場合にどのような文字が送信されるかは stty -a というUnixコマンドで確認できる。 大学の環境では eofが送信されるはずである。 上記コードの※1では、Ctrl-D によって EOFscanf( ) に送られ、whileループから抜けるしくみになっている。


補足3: 国名コードはISO3166という国際規格で定義されています。 この国際規格ISO3166では、問題で使った3文字の国名コード (alpha-3) の他、 2文字の国名コード (alpha-2) も定義されています。 alpha-2の内容は/usr/share/zoneinfo/iso3166.tabで見ることができます。


注意: 当然のことながら、コードを書く前に、国名コードファイルの内容を確認しておくことが望ましい。

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}
0 zzz.....合計250文字の「z」.....zzz
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
%