見出し画像

C言語教室 第12回(続) - 回答提出

こちらの記事の課題回答です。

以下、
・引数チェックなし
・const なし
・エラー処理なし
です!

では、順番に。


課題1

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

コード

int area_rect(int side1, int side2)
{
    return (side1 * side2);
}

テストと結果

int area = area_rect(4, 5);
printf("area of rectangle (4, 5) = %d\n", area);

area = area_rect(65535, 65535);
printf("area of rectangle (65535, 65535) = %d\n", area);
area of rectangle (4, 5) = 20
area of rectangle (65535, 65535) = -131071

コメント

オーバーフローあり。


課題2

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

コード

#define M_AREA_RECT(side1, side2) ((side1) * (side2))

テストと結果

area = M_AREA_RECT(4, 5);
printf("area of rectangle (4, 5) = %d\n", area);
area = M_AREA_RECT(65535, 65535);
printf("area of rectangle (65535, 65535) = %d\n", area);
area of rectangle (4, 5) = 20
area of rectangle (65535, 65535) = -131071

コメント

コンパイル時にワーニングあり。
演算するとオーバーフローするよって。

c_12.c:159:12: warning: overflow in expression; result is -131071 with type 'int' [-Winteger-overflow]
    area = M_AREA_RECT(65535, 65535);
           ^
c_12.c:5:44: note: expanded from macro 'M_AREA_RECT'
#define M_AREA_RECT(side1, side2) ((side1) * (side2))
                                           ^                  1 warning generated.

課題3

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

コード

#define PI 3.14159265359
float area_cycle(int radius)
{
    return (PI * radius * radius);
}

テストと結果

float farea = area_cycle(4);
printf("area of cycle (4) = %f\n", farea);

farea = area_cycle(65535);
printf("area of cycle (65535) = %f\n", farea);
area of cycle (4) = 50.265484
area of cycle (65535) = 13492626432.000000

コメント

float になったらオーバーフローしなくなった。

  • 2023.1.31 改訂

(2 * PI * radius * radius) → (PI * radius * radius)

ちなみに、計算結果が変かも。
電卓計算結果

π×4×4=50.265482457437
π×65,535×65,535=13,492,625,932.83132

半径が「65535」のときの計算結果が大きくずれすぎ。
う~ん。


課題4

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

コード

void clear_array(int a[], int len)
{
    int i = 0;
    for (i = 0; i < len; ++i)
    {
        a[i] = 0;
    }
}

テストと結果

#define MAX_NUMBERS 256
int a[MAX_NUMBERS] = {255, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
print_array(a, MAX_NUMBERS);
clear_array(a, MAX_NUMBERS);
print_array(a, MAX_NUMBERS);
00ff 0001 0002 0003 0004 0005 0006 0007
0008 0009 000a 000b 000c 000d 000e 000f

0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000

コメント

標準ライブラリ「memset」を使うのもありかも。


課題5

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

コード

void get_rand(int a[], int len, int max)
{
    int i = 0;
    int seed = time(NULL) + clock();
    srand(seed);
    for (i = 0; i < len; ++i)
    {
        a[i] = rand() % (max+1);
    }
}

テストと結果

#define MAX_NUMBERS 256
get_rand(a, MAX_NUMBERS, 255);
print_number(a, MAX_NUMBERS);
printf("\n");
get_rand(a, MAX_NUMBERS, 255);
print_number(a, MAX_NUMBERS);
printf("\n");
 33  74   1  63  27  35  77  83 118   2  13 231
181 209  71 124  38  56  53  50 182 191 166 197
(後略)

165  38 253 166  64  92  57  47 238 137  56 228
166 234  10 165 148 121 142 130  27 180 170  91
(後略)

コメント

あまりにも大きな数値ばかりが並ぶので、勝手に上限設定。

(1)「srand」なしだと、実行する度に毎回同じ値になる。

$ ./a.out
 103 198 105 115  81 255  74 236  41 205 186 171
(後略)

   8 112 212 178 138  41  84  72 154  10 188 213
(後略)

$ ./a.out
 103 198 105 115  81 255  74 236  41 205 186 171
(後略)

   8 112 212 178 138  41  84  72 154  10 188 213
(後略)

(2)「srand(time(NULL))」だと、2回の実行結果が同じ値になる。

$ ./a.out
188 201 206 245  20 184 104 163 196 117 217 176               
(後略)

188 201 206 245  20 184 104 163 196 117 217 176               
(後略)

とかく、コンピューターは乱数が不得手である。
まずは標準ライブラリを眺めてみる。
「clock」。
なんだ、これは。
「使用したプロセッサ時間」。
少なくとも、連続で2回実行した場合、1回目と2回目で違う値になりそう。プラットホーム依存しそうな気もするけど。とりあえず、今の環境では使えそうなのでこれにする。
後は。
「time」に、足す? 掛ける? それとも Xor ?
Xor も面白そうではあるけど、シンプルに「足す」ことにした。


課題6

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

コード

int get_min(int a[], int len)
{
    int i = 0;
    int min = a[0];
    for (i = 1; i < len; ++i)
    {
        if (a[i] < min)
        {
            min = a[i];
        }
    }
    return min;
}

テストと結果

int min = get_min(a, MAX_NUMBERS);
printf("min = %d\n", min);
min = 2

コメント

len = 0 のケースは考慮しない。


課題7

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

コード

int count_min(int a[], int len, int* min)
{
    int i = 0;
    int count = 0;
    *min = a[0];
    for (i = 1; i < len; ++i)
    {
        if (a[i] == *min)
        {
            count++;
            continue;
        }

        if (a[i] < *min)
        {
            *min = a[i];
            count = 1;
            continue;
        }
    }
    return count;
}

テストと結果

int count = count_min(a, MAX_NUMBERS, &min);
printf("min = %d (%d times)\n", min, count);
min = 2 (1 times)

コメント

2つ目の「continue」はいらないか。
1つ目の「continue」は、2つ目の「if」文を「else」ブロックにするのでもいいのだけど・・・。どちらがいいだろうか。単純に好き嫌いで選んで「continue」。明白な根拠もなし。


課題8

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

コード

int count_char(char str[], char* max_char)
{
    int i = 0;
    int max_count = 0;
    int count[0x100] = {0};
    for (i = 0; str[i] != '\0'; ++i)
    {
        ++(count[str[i]]);
    }

    max_count = count[0];
    *max_char = 0;
    char c = 0;
    for (c = 1; c < 0x80; ++c)
    {
        if (max_count < count[c])
        {
            max_count = count[c];
            *max_char = c;
        }
    }
    return max_count;
}

テストと結果

char max_char = 0;
int max_count = 0;
char str[] = "#define M_AREA_RECT(side1, side2) ((side1) * (side2))";
max_count = count_char(str, &max_char);
printf("str = %s\n", str);
printf("mac_char = %c (%d times)\n", max_char, max_count);

コメント

「count」は「0x100」個。
最大個数のチェックは「count[0]~count[0x7f]」。
「count」の配列要素数を「0x100」としたのは、文字の範囲チェックをしたくなかったから。「count[0x80]~count[0xff]」がカウントされることはまずないだろうけど、もしあってもオーバーランしないように。

個数のチェックは0x7f以下を全て対象にした。
制御コードも含む。

1つの関数にループが2つも出てくるのは好きではない。関数分けるのが理想。サボってしまった。


課題9

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

コード

char *str_ra_cat(char* s1, char* s2)
{
    size_t l1 = 0;
    size_t l2 = 0;
    if (s1 != NULL) {l1 = strlen(s1);}
    if (s2 != NULL) {l2 = strlen(s2);}
    char* s0 = realloc(s1, (l1 + l2 + 1));
    strncat(s0, s2, l2);
    return s0;
}

char *str_ra_catcat(char* s1, char* s2, char* s3)
{
    char* s1_new = str_ra_cat(NULL,   s1);
    char* s2_new = str_ra_cat(s1_new, s2);
    char* s3_new = str_ra_cat(s2_new, s3);
    return s3_new;
}

テストと結果

char s1[] = "abcdef";
char s2[] = "ABCDEF";
char s3[] = "012345";
printf("s1 = %s\n", s1);
printf("s2 = %s\n", s2);
printf("s3 = %s\n", s3);
char* s0 = str_ra_catcat(s1, s2, s3);
printf("s0 = %s\n", s0);
free(s0);
s1 = abcdef
s2 = ABCDEF 
s3 = 012345
s0 = abcdefABCDEF012345

コメント

『2つの文字列を連結する関数を書いて使っ』たら、面白いコードになった。
「realloc」の第1引数は NULL でもいいらしい。
でも、「strlen」は NULL はダメのようなので、結局は NULL チェックが必要。


演習

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

コード

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

    return 0;
}

テストと結果

$ ./a.out aaa bbb ccc ddd 91234 -p
argv[0] = ./a.out
argv[1] = aaa
argv[2] = bbb
argv[3] = ccc
argv[4] = ddd
argv[5] = 91234
argv[6] = -p
$ 

コメント

特になし。


感想

疲れた~(笑)。


追記

2023/1/30
いちゃもんもつけにくいだろうから、ソースファイルをつけます。
あまり褒められたコードでもないけど。


この記事が気に入ったらサポートをしてみませんか?