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


A問題(50点)

演習問題1

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

配列のアドレスの確認を行う。

1. まず、ハンドアウトLec05-3 (p103)のサンプルプログラム lec05-1int.c を自分のディレクトリにコピーしてコンパイル・実行し、ハンドアウトと似た出力が得られることを確認せよ。 (出力されるアドレスは実行例と異なるが、 一回ループするごとに出力されるアドレスの差(増分)は同じである。)

2. 上のサンプルプログラムをコピーして prog01.c というファイルを作成し、 そこに以下の配列宣言を追加しなさい。なお、宣言の中の「 ________ 」には、文字または文字列の宣言を適切に補うこと。

float f_array[] = {0.1,0.2,0.3,0.4};
double d_array[] = {1e+4, 2e+3, 3e+2, 4e+1};
______ c_array[] = {'A','B','C','D'};
______ c_2d_array[][9] = {"ab","c","defg","hij"};
______ str_array[] = {"ab","c","defg","hij"};

3.さらに、 array[] の場合と同様に、 追加された配列それぞれについての各要素のアドレスを出力する記述を追加しなさい。

4. 最後に以下の記述を追加して、 sizeof()によって各型のサイズを表示するようにせよ。

  printf("Size of each type\n");
  printf(" int: %ld,  float: %ld,  double: %ld,  char: %ld\n", sizeof(int), sizeof(float), sizeof(double), sizeof(char));
  printf(" int*: %ld, float*: %ld, double*: %ld, char*: %ld\n", sizeof(int *), sizeof(float *), sizeof(double *), sizeof(char *));

5. このプログラムを実行し、2.で追加したそれぞれの配列について、 一回ループするごとに出力されるアドレスの増え方を答えよ。 テキストファイル prog01.txt を作成して回答を書き込みなさい。

6. アドレスの増え方が上で求めたようになる理由を、4.で追加したsizeof演算子の結果や、 Lec05-4 (p103)、Lec05-11 (p105)などを参照しながら考察して prog01.txt 中に書き込み、提出しなさい。

例(1行目を5で答え、2行目を6で答える):
double d_array[] : アドレスは xx ずつ増える。
理由は、. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . であるため。

演習問題2

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

以下は、 "Computer Science and Engineering, University of Aizu" という内容を持つ文字列変数 str1 があるとき、キーボードから入力された二つの整数値 (start_posend_pos)を用いて、 文字列の start_pos 文字目から end_pos 文字目までを出力するプログラムである。 例えば、 start_pos に 22 、 end_posに 32 が与えられたとき、22文字目から32文字目までの11文字 "Engineering" が出力されるようになっている。

このような処理を実現する方法として、以下のような3通りのやり方が考えられる。

3通りの処理を実現するように、プログラムの下線部を埋めて完成させよ。

なお、この文字列の文字数は52文字である。 また、上の説明では最初の文字を1文字目と数えていて、配列の添字と数え方が違うことに注意すること。
下記プログラムで使用されているstrlen関数は、文字列の長さ(ヌル文字含まず)を返す関数である(ハンドアウトLec04-18 (p102))。

#include <stdio.h>
#include <string.h>

int main()
{
  int i, len;
  int start_pos, end_pos;
  char str1[] = "Computer Science and Engineering, University of Aizu";
  char *p, *q;
  
  len = strlen(str1);
  printf("Input start and end positions (1 - %d): ", len);
  scanf("%d%d", &start_pos, &end_pos);

  /* 通常の配列添字を使い、配列の要素を順次参照する方法 */
  for(i = ________; ________; i++) {
    printf("%c", str1[i]);
  }
  printf("\n");
  
  /* 参照するアドレスをポインタ演算で計算する方法。pは固定。 */
  p = ________;
  for(i = ________; ________; i++) {
    printf("%c", ________(p ________));
  }
  printf("\n");

  /* ポインタ変数に格納されているアドレスそのものを変えていく方法 */
  for(________; ________; ________) {
    printf("%c", *q);
  }
  printf("\n");
  return 0;
}
実行例
% ./a.out
Input start and end positions (1 - 52): 22 32
Engineering
Engineering
Engineering
% ./a.out
Input start and end positions (1 - 52): 49 52
Aizu
Aizu
Aizu
%

B問題(50点)

演習問題3

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

キーボードから入力されたいくつかの単語を、まとめて一つの文字列にするプログラム を作成したい。以下の仕様に沿ってプログラムを完成させなさい。

  1. 入力用に、2次元文字配列を用いる。入力する単語の数は、マクロ NWORD で 7 と定めておく。 一単語の長さは、(ヌル文字含め)20文字以下とする。
  2. 新しい文字列のための変数は newstr という文字型1次元配列とする。
  3. 入力された単語をつないで新しい文字列 newstr を作るが、 そのとき単語と単語の間にスペース一つを入れる。 ただし、終端(最後の単語の後)にはヌル文字を入れること。
  4. 作成した newstr を丸ごと printf で出力する。 この時 printf を複数繰り返したり、printf内でスペースの挿入を行ってはならない。
  5. 最後に newstr の文字数(スペース含む)を表示して終了(実行例参照)。
  6. strcpy関数やstrlen関数を用いるとよい。strcpy関数は、第2引数に指定された文字列を、 第1引数に指定されたアドレス以降にコピーする関数である(ヒント:ハンドアウトLec04-18 (p102)、Lec05-17 (p107))。 使用するときは、最初に string.h をインクルードしておくこと。
プログラム例
#include <stdio.h>
#include ..........

#define NWORD 7

int main()
{
  char str2[NWORD][20];   /* 入力用の文字配列 */
  char newstr[120];       /* 出力用の文字配列 */
  int i;
  /* ここにその他の変数宣言 */

  printf( "Input %d words: \n", NWORD );

  for( i = 0; i < NWORD; i++ ){
    /* ここに入力された単語をstr2に読み込む処理を書く */
  }

  /* 入力されたstr2をもとに、newstrを作成する処理を書く */
  /* 単語の間にスペースを入れながら、各単語をつないだ一つの文字列にする */

  printf("%s\n", newstr);   /* 新しい文字列全体を表示 */

  /* ここで、newstrの文字数を表示 */

  return 0;
}
実行例
% ./a.out 
Input 7 words:
Computer
Science
and
Engineering,
University
of
Aizu
Computer Science and Engineering, University of Aizu
Total: 52 characters
%

演習問題4

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

以下は、N個(マクロで6と定義してある)の整数型データを入力すると、 それらを小さい順に並び換えて表示する(ソート)プログラムである。 まずこのプログラムをコンパイル・実行して、動作を理解せよ。 (分からない場合、適宜 printf を追加して変数の値などを書かせてみるとよい)
#include <stdio.h>

#define N 6

int main()
{
  int data[N];     /* 配列の宣言 */
  int tmp;
  int min_idx;
  int i, j;

  printf("Please input %d numbers\n", N);
  for(i = 0; i < N; i++)
    scanf("%d", &data[i]);

  for(i = 0; i < N - 1; i++){
    min_idx = i;
    for(j = i + 1; j < N; j++){
      if(data[j] < data[min_idx]) min_idx = j;
    }
    tmp = data[i];
    data[i] = data[min_idx];
    data[min_idx] = tmp;
  }

  printf("Sorted data\n");
  for(i = 0; i < N; i++) {
    printf("%d  ", data[i]);
  }
  printf("\n");
  return 0;
}
実行例1
% ./a.out 
Please input 6 numbers
5  3  -2  24  1  -7
Sorted data
-7 -2  1  3  5  24
%
それでは、上記のプログラムの宣言部(行番号7-10) を以下のように変更し、演習問題2の3つめの方法のように、配列要素を参照するためにint型変数を使用するのではなく、 ポインタ変数を使用する方法で、同様な動作をするプログラムを作成し、また、入力された整数型データを大きい順に並び換えて表示させてください(実行例2を参照)。
  int data[N];     /* 配列の宣言 */
  int tmp;
  int *p,*q,*max_ptr;
実行例2
% ./a.out 
Please input 6 numbers
5  3  -2  24  1  -7
Sorted data from highest to lowest
24  5  3  1  -2  -7
%

Extra問題

演習問題5(リニアサーチ、ファイル)

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

ネットワークに接続された一つ一つの計算機には、固有の ホスト名 と固有の IPアドレス というものが割り当てられている。 IPアドレスは 163.143.43.50 のように、0~255の範囲の整数4つをドットで仕切って表す決まりである。

ファイル hostlist.txt に、IPアドレス・ホスト名のリストがある (IPアドレスは架空のものである)。リスト4-1にその最初の部分を示す。 このリストには、1行の中にIPアドレス(15文字以下)、半角空白1文字、 ホスト名(255文字以下)が続けて与えられ、それらが複数行ならんでいる。

このファイルをまず読み込んでおき、標準入力から検索したいホスト名(std1dc1等) を与えると、該当するホストが存在すれば 「ホスト名 : IPアドレス」のように表示し、 存在しない場合には、 「ホスト名 : not found!」 (実行例参照)のように表示するプログラムを作成せよ。

検索時に入力する文字列は255文字以内とし、EOFが入力された場合はプログラムを終了させる。なお、EOFは、キーボードで Ctrl-D を押すか、リダイレクトで入力したファイルの終端に達すると、プログラムへ入力される。 (ハンドアウトLec02-10 (p89)参照)

実行例はリスト4-2である。 本課題では第2回講義で学習したリニアサーチを用いること。また、 hostlist.txt はプログラムを実行するディレクトリに前もってダウンロードして置いておくものとする。 解答の際には、リスト4-3を読み、ホスト名のファイルを読み込むコードを理解すること。 その上で、このコードを修正してプログラムを完成させること。 文字列の比較(一致するかどうかのチェック)には strcmp 関数を使うこと。 (ハンドアウトLec04-18 (p102)参照。string.hのインクルードも必要である。)

リスト4-1 (全リスト hostlist.txt)

192.0.2.31      std1dc1
192.0.2.32      std1dc2
192.0.2.33      std1dc3
...

リスト4-2

実行例1:
% ./a.out
std5dc1
std5dc1 : 203.0.113.31
std6tc60
std6tc60 : not found!
Ctrl-D
%

実行例2:
%./a.out < testdata.txt(ファイルは こちら )
std1dc9 : 192.0.2.39
std2cd14 : not found!
std3tc1 : not found!
std3dc12 : 198.51.100.42
hdw3dc2 : not found!
std5dc32 : 203.0.113.62
std6dc55 : not found!
%

リスト4-3 (以下のソースファイル)

#include ....
#include ....
#include ....
/*
 * MAXDATA:最大データ数
 * LENIP  :IPアドレスの最長文字数+1
 * LENHOST:最長文字数+1
 */
#define MAXDATA 1024
#define LENIP   16
#define LENHOST 256

int main(){
  int i;
  int ndata;
  FILE *fp;
  char ip[MAXDATA][LENIP];
  char hostname[MAXDATA][LENHOST];
  char query[LENHOST];
  /* その他必要な変数を定義して良い */
  .....

  /*** データの読み出し処理 ***/
  /* ファイルのオープン */
  fp = .........;
  if (fp == NULL) {
    printf("Cannot open file!\n");
    exit(1);
  }
  /* データの読み出し */
  for( i=0; i<MAXDATA; i++ ){
    /* 2項目読めなければループを抜ける */
    if (fscanf( fp, "%s %s",
      ip[i], hostname[i] ) != 2) break;
  }
  ndata=i;
  fclose(fp);

  /*** 問合せの処理 ***/
  while(...){
   ......
   /* 文字列の配列の使用方法に注意      *
    * hostname[xxx]でxxx番目の文字列を示す */
   ....strcmp( hostname[xxx], query )....
   ......
  }

  return 0;
}

演習問題6

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

ポインタの働きの確認を行う。

以下のプログラムは、int型の配列変数 a, b とポインタ ptr を用いて、同じ値を2種類の方法で参照して printf で表示するプログラムである。一つ目の参照方法は既に書いてあり、 二つ目の方法が空欄になっている。 プログラム中のコメントにしたがいながら、下線部の空欄を埋めて、printf で各行に同じ値が2回表示されるようにしなさい。

例えば (1) では、 *ptr と同じ事を配列 a を用いて 書けばよいので、空欄には a[0] が入る。 (2) 以降を自分で考えて空欄を埋めればよい。 もちろん、空欄に左と同じものを入れる答えは不可である。ハンドアウト Lec05-6,7,9,10 を参考にするとよい。

※余力のある者は、実行する前にどのような値が表示されるか予想してメモしておき、 実際その通りになるか確かめなさい。
ポインタの表記についてのコメント

プログラム(ファイル
#include <stdio.h>

int main(){
  int a[] = {1, 2, 4, 8, 16, 32};
  int b[2][3] = {{1, 3, 9}, {-1, -3, -9}};
  int *ptr;

  ptr = a;
  printf("(1) *ptr = %d    \t= %d\n", *ptr, ______); /* aを用いて配列風に */
  printf("(2) &a[2] = %p   \t= %p\n", &a[2], ______); /* ptrでポインタ演算 */
  printf("(3) *(ptr+3) = %d\t= %d\n", *(ptr+3), ______); /* aを用いて配列風に */
  ptr = &a[2];
  printf("(4) ptr[2] = %d  \t= %d\n", ptr[2], ______); /* aを用いて配列風に */
  printf("(5) *ptr-- = %d  \t= %d\n", *ptr--, ______); /* aを用いて配列風に */
  printf("(6) *ptr = %d    \t= %d\n", *ptr, ______); /* aを用いて配列風に */
  ptr = b[0];
  printf("(7) *++ptr = %d  \t= %d\n", *++ptr, ______); /* bを用いて配列風に */
  printf("(8) *(ptr+3) = %d\t= %d\n", *(ptr+3), ______); /* bを用いて配列風に */
  return 0;
}
実行結果の表示例を最初の1行のみ示す。 = の右に同じ値が表示されるように空欄を埋めること。
% ./a.out
(1) *ptr = 1            = 1
.....