複数ファイルによるプログラム 解答ページ | Programming Place Plus C言語編 第24章

トップページC言語編第24章

問題①

問題① 次のプログラムの間違いを指摘してください。

// main.c
#include "sub.h"

int main(void)
{
    get_string();
    put_string();
}
// sub.c
#include <stdio.h>
#include "sub.h"

extern char g_str[80];


void get_string()
{
    fgets(g_str, sizeof(g_str), stdin);
}

void put_string()
{
    puts(g_str);
}
// sub.h
#ifndef SUB_H_INCLUDED
#define SUB_H_INCLUDED

extern char g_str[80];

void get_string();
void put_string();

#endif


グローバル変数g_str は 2箇所で extern を伴った宣言が記述されていますが、肝心の定義がどこにもないことが誤りです。extern 付けて宣言を行ったら、必ずどこかに extern の付かない定義が必要です。

問題②

問題② 問題①のプログラムを、extern指定子を生かす形と、static指定子を使う形の2通りに修正してください。


extern を生かすのなら、sub.c の方の extern は消して「定義」に変えてしまい、sub.h の方の extern は残しておけば良いです。

// sub.c
#include <stdio.h>
#include "sub.h"

char g_str[80];  // 定義になった


void get_string()
{
    fgets(g_str, sizeof(g_str), stdin);
}

void put_string()
{
    puts(g_str);
}

実行結果:

abc
abc

static を使うのなら、sub.c の方に付けます。こうすると内部結合になるので、外部には公開しないため、sub.h の方の宣言は削除します。

// sub.c
#include <stdio.h>
#include "sub.h"

static char g_str[80];  // 内部結合になった


void get_string()
{
    fgets(g_str, sizeof(g_str), stdin);
}

void put_string()
{
    puts(g_str);
}
// sub.h
#ifndef SUB_H_INCLUDED
#define SUB_H_INCLUDED

void get_string();
void put_string();

#endif

実行結果:

abc
abc

問題③

問題③ この章の最初の方で、max関数と min関数を持った utility.c と utility.h を作成しました。同じように、汎用的に使えそうな関数をこれらのファイルに追加し、便利な関数群を作ってください。


// main.c
#include <stdio.h>
#include "utility.h"

int main(void)
{
    int a = -50;
    int b = 400;

    printf("MAX: %d\n", max(a,b));
    printf("MIN: %d\n", min(a,b));
    printf("ABS: %d, %d\n", abs(a), abs(b));
    printf("DIVISOR: %d\n", is_divisor(a,b));
    printf("DIGIT: %d %d\n", get_digit(a), get_digit(b));
    printf("LEAPYEAR: %d %d\n", is_leap_year(a), is_leap_year(b));
    printf("CHAR_IS_SIGNED?: %d\n", check_char_is_signed());
}
// utility.c
#include <limits.h>
#include "utility.h"


/* 大きい方の値を返す */
int max(int a, int b)
{
    if (a >= b) {
        return a;
    }
    return b;
}

/* 小さい方の値を返す */
int min(int a, int b)
{
    if (a <= b) {
        return a;
    }
    return b;
}

/* 絶対値を返す */
int abs(int num)
{
    if (num < 0) {
        return -num;
    }
    return num;
}

/* 約数かどうか判定する */
bool is_divisor(int a, int b)
{
    if (b == 0) {
        /* b が 0 の場合、いかなる数の約数にもならない */
        return false;
    }

    return (a % b == 0);
}

/* 桁数を返す */
int get_digit(int num)
{
    int digit = 1;

    /* 負数も扱えるように、絶対値を求めておく */
    num = abs(num);

    /* 1桁になるまで、10 で割ることを繰り返す */
    while (num >= 10) {
        num /= 10;
        digit++;
    }

    return digit;
}

/* 閏年かどうか判定する */
bool is_leap_year(int year)
{
    return ((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)));
}

/* char型が符号付きかどうか調べる */
bool check_char_is_signed()
{
    return (CHAR_MIN < 0);
}
// utility.h
#ifndef UTILITY_H_INCLUDED
#define UTILITY_H_INCLUDED

#include <stdbool.h>

/*
    大きい方の値を返す。
    戻り値
        引数a と b のうちの大きい方の値を返す。
*/
int max(int a, int b);

/*
    小さい方の値を返す。
    戻り値
        引数a と b のうちの小さい方の値を返す。
*/
int min(int a, int b);

/*
    絶対値を返す。
    引数
        num:    元の値。
    戻り値
        numの絶対値。
*/
int abs(int num);

/*
    約数かどうか判定する。
    引数
        a:  元の値。
        b:  元の値。
    戻り値
        b が a の約数のときには真、それ以外のときには偽を返す。
*/
bool is_divisor(int a, int b);

/*
    桁数を返す。
    引数:
        num:    対象の値。
    戻り値:
        桁数。
*/
int get_digit(int num);

/*
    閏年かどうか判定する。
    引数
        year: 判定する西暦年。
    戻り値
        year が閏年ならば真、そうでなければ偽を返す。
*/
bool is_leap_year(int year);

/*
    char型が符号付きかどうか調べる。
    戻り値
        このコンパイラにおいて、
        char型が符号付きであれば真、符号無しであれば偽を返す。
*/
bool check_char_is_signed();

#endif

実行結果:

MAX: 400
MIN: -50
ABS: 50, 400
DIVISOR: 0
DIGIT: 2 3
LEAPYEAR: 0 1
CHAR_IS_SIGNED?: 1

ここで挙げた関数群は、これまでの章で登場したものからの抜粋です。現段階の知識だけでも、これ以上の種類の汎用的な関数を作成できるはずです。また、max関数や min関数に関しても、double型バージョンを作るといったことも考えられます。

abs関数に関しては、実は標準ライブラリ関数にあります(⇒リファレンス)。

また、コメントに関してですが、関数の詳しい説明はヘッダファイル側に書くべきです。ヘッダファイルは、言ってみればカタログですから、使い方や何をしてくれるのかといったことは、こちらに書いてあるべきです。

また、使う側からすれば、#include のときにヘッダの名前を指定するだけであって、具体的な定義の部分には感知しないのが普通ですから、使う側が見ているのはヘッダファイルだけと思った方が良いです。これは、標準ヘッダでも同様です。

※もし学校の授業等で、このページを見ていたら、ぜひ、周囲の人と utility.c と utility.h を交換して、互いの関数を使ってみてください。さまざまな部品化のやり方があることが分かると思います。


参考リンク


更新履歴

≪さらに古い更新履歴を展開する≫



第24章のメインページへ

C言語編のトップページへ

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る