見出し画像

(答案提出) C言語教室 第12回(続) - 課題と演習 - 2023.02.08更新

kznさん主催の、C言語教室の第12回。
今回は、中間試験です。

これから益々難易度が上がり、手に負えなくなりそうなので、
私としては、ここで点を稼いでおかないといけません。
がんばらんば。

では、3、2、1、スタート!

動作確認環境はこちら


課題1の答案と実行結果

課題1
長方形の短辺と長辺の長さを引き数として、長方形の面積を求める関数を書きなさい。なお長さは整数でよい。

https://note.com/kazushinakamura/n/n9605d5921b49?from=notice
#include <stdio.h>
#define S_SIDE 4
#define L_SIDE 5

int test01(int x, int y) {
  return x * y;
}

void main() { 
  printf("--- Test01 results ---\n");
  printf("short_side_length = %d\n", S_SIDE);
  printf("long_side_length  = %d\n", L_SIDE);
  printf("area of rectangle = %d\n", test01(S_SIDE, L_SIDE));
}
--- Test01 results ---
short_side_length = 4
long_side_length  = 5
area of rectangle = 20


課題2の答案と実行結果

課題2
課題1で書いた関数を引き数付きマクロを使って書き直しなさい。

https://note.com/kazushinakamura/n/n9605d5921b49?from=notice
#include <stdio.h>
#define TEST02(x,y) (x * y)
#define S_SIDE 6
#define L_SIDE 7

void main() {
  printf("--- Test02 results ---\n");
  printf("short_side_length = %d\n", S_SIDE);
  printf("long_side_length  = %d\n", L_SIDE);
  printf("area of rectangle = %d\n", TEST02(S_SIDE, L_SIDE));
}
--- Test02 results ---
short_side_length = 6
long_side_length  = 7
area of rectangle = 42


課題3の答案と実行結果

課題3
円の半径を引き数として、円の面積を求める関数を書きなさい。なお半径は整数で良いが面積は小数とする。円周率はマクロを使って定義しなさい。

https://note.com/kazushinakamura/n/n9605d5921b49?from=notice
#include  <stdio.h>
#define PI 3.14
#define RADIUS 4

double test03(int x) {
  return x * x * PI;
}

void main() { 
  printf("--- Test03 results ---\n");
  printf("radius = %d\n", RADIUS);
  printf("area of circle = %f\n", test03(RADIUS));
}
--- Test03 results ---
radius = 4
area of circle = 50.240000

→→→ あれ、C言語に自乗の演算子って、無かったんでしたっけ?


課題4の答案と実行結果

課題4
整数の配列と要素の数を引き数で渡し、配列のそれぞれの要素にゼロを代入する関数を書きなさい。

https://note.com/kazushinakamura/n/n9605d5921b49?from=notice
#include <stdio.h>
#include <stdlib.h>
#define SAMPLE 10

void test04(int* x, int y) {
  for (int i = 0; i < y; i++) {
     *x++ = 0;
  }
}

void main() { 
  int s = SAMPLE;
  int *a;

  a = (int*)malloc(sizeof(int) * s);
  if (a == NULL) exit(0);

  test04(a, s); //配列の要素を全てゼロにします
  
  printf("--- Test04 results ---\n");
  for (int i = 0; i < s; i++) {
    printf("%d\n", *a++ );
  }
  free(a);
}
--- Test04 results ---
0
0
0
0
0
0
0
0
0
0

●2023.02.03修正;ポインタ変数 *x は int型なので、NULLをセットするのはダメとのご指導をいただきました。有難うございます。以下、text04*()関数を流用している課題5〜8についても修正しました。

課題5の答案と実行結果

課題5
整数の配列と要素の数を引き数で渡し、配列のそれぞれの要素にランダムな値を代入する関数を書きなさい。ランダムな値を得るには stdlib.h で定義されている rand() を使うこと。

https://note.com/kazushinakamura/n/n9605d5921b49?from=notice
#include <stdio.h>
#include <stdlib.h>
#define SAMPLE 10
#define RANGE 10

void test04(int* x, int y) {
  for (int i = 0; i < y; i++) {
     *x++ = 0;
  }
}

void test05(int *x, int y) {
  for (int i = 0; i < y; i++) {
    *x++ = rand() % RANGE;
  }
}

void main() { 
  int s = SAMPLE;
  int* a;

  a = (int*)malloc(sizeof(int) * s);
  if (a == NULL) exit(0);

  test04(a, s); //配列の要素を全てゼロにします
  test05(a, s); //配列に乱数を割り付けます

  printf("--- Test05 results ---\n");
  for (int i = 0; i < s; i++) {
    printf("%d\n", *a++);
  }

  free(a);
}
--- Test05 results ---
5
8
0
0
5
9
1
7
9
4

→→→ 一般的には時間を乱数のシードとして与えると思うのですが、そのためにtime.hをincludeしようとすると、Web版だと実行時にスクリプトエラーになりましたので、このコードには含めていません。

※ 課題に指示はありませんでしたが、乱数の範囲は0-9に限定しました。


課題6の答案と実行結果

課題6
課題5で作った配列を引き数で渡して、この配列に含まれる値の最小値を返す関数を書きなさい。

https://note.com/kazushinakamura/n/n9605d5921b49?from=notice
#include  <stdio.h>
#include  <stdlib.h>
#define SAMPLE 10
#define RANGE 10

void test04(int* x, int y) {
  for (int i = 0; i < y; i++) {
     *x++ = 0;
  }
}

void test05(int *x, int y) {
  for (int i = 0; i < y; i++) {
    *x++ = rand() % RANGE;
  }
}

int test06(int *x, int y) {
  int min = INT_MAX;
  
  for (int i = 0; i < y; i++) {
    min = (x[i] < min) ? x[i] : min;
  }

  return min;
}

void main() { 
  int s = SAMPLE;
  int *a;
  int min;
  
  a = (int*)malloc(sizeof(int) * s);
  if (a == NULL) exit(0);

  test04(a, s); //配列の要素を全てゼロにします
  test05(a, s); //配列に乱数を割り付けます
  min = test06(a, s); //配列の最小値を求めます

  printf("--- Test06 results ---\n");
  for (int i = 0; i < s; i++) {
    printf("%d\n", *a++);
  }

  printf("minimum = %d\n", min);

  free(a);
}
--- Test06 results ---
5
6
9
5
5
6
4
4
8
9
minimum = 4

※サンプル数が大きいと、最小値がほぼ0になるので、サンプル数を10にしました。


課題7の答案と実行結果

課題7
課題6で作った関数を参考にして、配列に含まれる値の最小値と、最小値であった要素がいくつあったのかを返す関数を書きなさい。

https://note.com/kazushinakamura/n/n9605d5921b49?from=notice
#include  <stdio.h>
#include  <stdlib.h>
#define SAMPLE 10
#define RANGE 10

void test04(int* x, int y) {
  for (int i = 0; i < y; i++) {
     *x++ = 0;
  }
}

void test05(int *x, int y) {
  for (int i = 0; i < y; i++) {
    *x++ = rand() % RANGE;
  }
}

int test06(int *x, int y) {
  int min = INT_MAX;
  
  for (int i = 0; i < y; i++) {
    min = (x[i] < min) ? x[i] : min;
  }
    
  return min;
}

void test07(int *x, int *y, int z) {  
  for (int i = 0; i < z; i++) {
    y[ x[i] ]++;
  }
}

void main() { 
  int s = SAMPLE;
  int *a;
  int min;
  int count[RANGE];
  
  a = (int*)malloc(sizeof(int) * s);
  if (a == NULL) exit(0);

  test04(a, s); //配列の要素を全てゼロにします
  test04(count, RANGE); //配列の要素を全てゼロにします

  test05(a, s); //配列に乱数を割り付けます
  min = test06(a, s); //配列の最小値を求めます
  test07(a, count, s); // 各要素毎の出現回数をカウントし、配列countに格納します
  
  printf("--- Test07 results ---\n");
  for (int i = 0; i < s; i++) {
    printf("%d\n", *a++);
  }

  printf("minimum = %d\n", min);

  printf("count = %d\n", count[min]);

  free(a);
}
--- Test07 results ---
5
9
7
7
2
8
2
3
5
9
minimum = 2
count = 2

●2023.01.28修正;乱数の出現回数を格納する配列countの定義をmain()に移動し、更に配列の中身を初期化するにはtest04()を使う様に、ロジックを見直しました。


課題8の答案と実行結果

課題8
引き数で渡された文字列の中に、もっとも多く含まれる文字と、その数を返す関数を書きなさい。含まれる文字の数が同じ場合には、どの文字を返しても良い。

https://note.com/kazushinakamura/n/n9605d5921b49?from=notice
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define INT_MIN -2147483648
#define SAMPLE_TEXT "The Quick Brown Fox Jumps Over The Lazy Dog" 
#define LETTERS 255

void test04(int* x, int y) {
  for (int i = 0; i < y; i++) {
     *x++ = 0;
  }
}

void test08_01(char *x, int *y, int z) {
  for (int i = 0 ; i < z; i++) {
    y [ (unsigned char) x[i] ]++;
  }
}

char test08_02(int *x) {
  int max = INT_MIN;
  int index = 0;
  
  for (int i = 0; i < 0xff; i++) {

    if ( x[i] > max) {
      max = x[i];
      index = i;
    }
  }
    
  return (char)index;
}

/* 表示文字以外を”\x”で始まる16進コードの文字列に変換する  - 第10回課題で作成した関数を流用*/
void char_to_str(char *s, char c)
{
  if (isgraph(c))
    {
      sprintf(s, "%c\0", (unsigned char)c);
    }
  else
    {
      sprintf(s, "\\x%02x\0", (unsigned char)c);
    }
}

void ctrl_to_str(char *dst, char *src)
{
    int len = strlen(src);
    int i = 0;
    char s[5] = {0};

    for (i = 0; i < len; i++)
      {
        if (src[i] == '\\') 
          {
            s = "\\\\";
          }
        else
          {
            char_to_str(s, src[i]);
          }

        strcat(dst, s);
      } 
}

void main() {
  int count[LETTERS];
  char txt[] = SAMPLE_TEXT;
  char letter, *out1, out2[];

  out1 = (char*)realloc(NULL, (strlen(txt) * 4 + 1));
  if (out1 == NULL) exit(-1);

  test04(count, LETTERS); //配列の要素を全てゼロにします

  ctrl_to_str(out1, txt);

  test08_01(txt, count, strlen(txt)); //発生頻度をカウントします
  letter = test08_02(count); //最大使用頻度の文字を検索します
  char_to_str(out2, letter); //非表示文字を編集します

  printf("--- Test08 results ---\n");
  printf("source text = \"%s\"\n", out1);
  printf("most used letter = \"%s\"\n", out2);
  printf("frequency of use = %d\n", count [(unsigned char)letter] );
  free(out1);
}

●2023.01.29修正:
1. 文字の発生頻度を格納する配列countを初期化するのはtest04()に統一。
2. 文字の発生頻度をカウントするロジックをtest08_01()として分離。
3. 最大使用頻度の文字を検索するロジックをtest08_02()とする。
4. 変数定義の見直し。

●2023.02.08修正:kznさんのご指摘に基づいて修正しました。
1. SAMPLE_TEXT を、char[]で定義する。
2. 符号なし型への型変換する。(2箇所)

void test08_01(char *x, int *y, int z) {
  for (int i = 0 ; i < z; i++) {
    y [ (unsigned char) x[i] ]++;
  }
}
printf("frequency of use = %d\n", count [(unsigned char)letter] );

3. 表示可能な文字以外は、"\x99"の形式で表示する。

--- Test08 results ---
source text = "The\x20Quick\x20Brown\x20Fox\x20Jumps\x20Over\x20The\x20Lazy\x20Dog"
most used letter = "\x20"
frequency of use = 8

課題にある「引き数で渡された文字列」が、表示可能な文字だけとは限らないので、全文字種をカウントしました。
サンプルの英文だと、最も頻度が多いのはスペースだったりして面白くありません。そこで、サンプルの文字列から強制的にスペースを排除して、再度実行しました。

--- Test08 results ---
source text = "TheQuickBrownFoxJumpsOverTheLazyDog"
most used letter = "e"
frequency of use = 3

こちらの方が、機能しているっぽい。


課題9の答案と実行結果

課題9
引き数で渡された3つの文字列を動的に確保した領域に連結して返す関数を書きなさい。2つの文字列を連結する関数を書いて使っても良い。stdlib.h にある realloc() も参考にすること。

https://note.com/kazushinakamura/n/n9605d5921b49?from=notice
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define TEXT1 "Ich " 
#define TEXT2 "liebe " 
#define TEXT3 "dich."

char *test09(char *x, char *y, char *z) {
  int length = 0;
  char *a, *b, *c;

  length = strlen(x) + 1;
  a = (char*)realloc(NULL, length);
  if (a == NULL) exit(-1);
  strncpy(a, x, length);

  length += strlen(y);
  b = (char*)realloc(a, length);
  if (b == NULL) exit(-1);
  strncat(b, y, length);
  
  length += strlen(z);
  c = (char*)realloc(b, length);
  if (c == NULL) exit(-1);
  strncat(c, z, length);
  
  return c;
}

void main() {
  char t1[] = TEXT1; 
  char t2[] = TEXT2;
  char t3[] = TEXT3;
  char *dup;
  
  printf("--- Test09 results ---\n");
  printf("1st text = \"%s\"\n", t1);
  printf("2nd text = \"%s\"\n", t2);
  printf("3rd text = \"%s\"\n", t3);

  dup = test09(t1, t2, t3);
  printf("concatinated text \"%s\"\n", dup);

  free(dup);
}

●2023.02.08修正:kznさんのご指摘に基づいて、realloc()でメモリを確保できない場合の戻り値を−1としました。

--- Test09 results ---
1st text = "Ich "
2nd text = "liebe "
3rd text = "dich."
concatinated text "Ich liebe dich."


演習の答案と実行結果

演習
main関数は実は int main(int argc, char** argv) という形でコマンドラインの引き数を受け取ることが出来ます。最初の引き数にコマンドライン引き数の数、次の引き数にコマンドラインで渡された文字列へのポインタが入っている配列が渡されます。これを解釈してコマンドラインで与えられた文字列を表示するコードを書きなさい。

https://note.com/kazushinakamura/n/n9605d5921b49?from=notice

exercises.c

#include <stdio.h>

int main(int argc, char *argv[]) {
  
  for(int i = 1; i < argc; i++) {
    printf("%s\n", argv[i]);
  }

  return 0;
}

gccでコンパイルします

Last login: Sun Jan 29 09:00:17 on ttys000

The default interactive shell is now zsh.
To update your account to use zsh, please run `chsh -s /bin/zsh`.
For more details, please visit https://support.apple.com/kb/HT208050.
jm3nrhMBP-EN:~ akio$ cd \Documents
jm3nrhMBP-EN:Documents akio$ cd \c
jm3nrhMBP-EN:c akio$ gcc exercises.c

実行結果(その1)

jm3nrhMBP-EN:c akio$ ./a.out  Hello world
Hello
world

"Hello"と"world"の2つの引数を渡しましたので、想定通り、それらが連続して表示されます。

実行結果(その2)

jm3nrhMBP-EN:c akio$ ./a.out  ”Hello world”
”Hello
world”

”Hello world”とすることで一つの引数として扱ってもらうことを期待したのですが、ダブルクオテーションも含めて引数として扱われました。


解答(2023.02.03) 、、、 課題1〜7

なかなか満点を取るのは難しいです。
課題4で作成したtest04()関数を、課題5〜8でも使いまわしていたのですが、test04()関数の中でint型のポインタ変数にNULLをセットするのは好ましくないとのご指導があり、一気に軒並み減点がついてしまいました。やれやれ。

解答(2023.02.03) 、、、 課題8〜9 + 演習

課題8〜9においてもご指導をいただきました。有難うございます。

とりあえず動かしてみて動作確認はしているのですが、私の様な素人が思いつく程度のテストって経験値が浅すぎて、条件が駄々漏れですね。
とても恐ろしくって、組み込みアプリとか手がつけられそうにありません。

でも、皆さんのコメントのおかげでトライアンドエラーを繰り返すことで、少しづつ進化している様な気がします。これって、アジャイル開発って呼んでいいのかな?(笑


これからしばらくの間、自主休学します。

続きは、私が日本に帰国して一段落した後で再開したいと思ってます。



ここまで読んでいただき、有難うございました。


いいなと思ったら応援しよう!

Akio van der Meer
これまでの収益は全て、それを必要としておられる方々へ、支援機関を通して寄付させていただきました。この活動は今後も継続したいと思っています。引き続きよろしくお願いいたします。