先頭へ戻る

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

Programming Place Plus トップページ -- C言語編 -- 第24章

先頭へ戻る

問題①

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

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

int main(void)
{
    getString();
    putString();

    return 0;
}
// sub.c
#include <stdio.h>
#include "sub.h"

extern char gStr[80];


void getString()
{
    fgets( gStr, sizeof(gStr), stdin );
}

void putString()
{
    puts( gStr );
}
// sub.h
#ifndef SUB_H_INCLUDED
#define SUB_H_INCLUDED

extern char gStr[80];

void getString();
void putString();

#endif


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

問題②

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


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

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

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


void getString()
{
    fgets( gStr, sizeof(gStr), stdin );
}

void putString()
{
    puts( gStr );
}

実行結果:

abc
abc

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

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

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


void getString()
{
    fgets( gStr, sizeof(gStr), stdin );
}

void putString()
{
    puts( gStr );
}
// sub.h
#ifndef SUB_H_INCLUDED
#define SUB_H_INCLUDED

void getString();
void putString();

#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", isDivisor(a,b) );
    printf( "DIGIT: %d %d\n", getDigit(a), getDigit(b) );
    printf( "LEAPYEAR: %d %d\n", isLeapYear(a), isLeapYear(b) );
    printf( "CHAR_IS_SIGNED?: %d\n", checkCharIsSigned() );

    return 0;
}
// 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 isDivisor(int a, int b)
{
    if( b == 0 ){
        /* b が 0 の場合、いかなる数の約数にもならない */
        return false;
    }

    return ( a % b == 0 );
}

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

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

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

    return digit;
}

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

/* char型が符号付きかどうか調べる */
bool checkCharIsSigned()
{
    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 isDivisor(int a, int b);

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

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

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

#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 を交換して、互いの関数を使ってみてください。さまざまな部品化のやり方があることが分かると思います。


参考リンク


------------------------------------------------------------------------

更新履歴

'2018/2/26 全面的に文章を見直し、修正を行った。

'2009/7/25 新規作成。



第24章のメインページへ

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

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
Twitter でツイート Twitter をフォロー LINE で送る
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー