Programming Place Plus トップページ – C言語編 – 第24章
問題① 次のプログラムの間違いを指摘してください。
// main.c
#include "sub.h"
int main(void)
{
();
getString();
putString}
// sub.c
#include <stdio.h>
#include "sub.h"
extern char gStr[80];
void getString()
{
( gStr, sizeof(gStr), stdin );
fgets}
void putString()
{
( gStr );
puts}
// 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()
{
( gStr, sizeof(gStr), stdin );
fgets}
void putString()
{
( gStr );
puts}
実行結果:
abc
abc
static を使うのなら、sub.c の方に付けます。こうすると内部結合になるので、外部には公開しないため、sub.h の方の宣言は削除します。
// sub.c
#include <stdio.h>
#include "sub.h"
static char gStr[80]; // 内部結合になった
void getString()
{
( gStr, sizeof(gStr), stdin );
fgets}
void putString()
{
( gStr );
puts}
// 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;
( "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() );
printf}
// 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;
/* 負数も扱えるように、絶対値を求めておく */
= abs( num );
num
/* 1桁になるまで、10 で割ることを繰り返す */
while( num >= 10 ){
/= 10;
num ++;
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 を交換して、互いの関数を使ってみてください。さまざまな部品化のやり方があることが分かると思います。
return 0;
を削除(C言語編全体でのコードの統一)’2018/2/26 全面的に文章を見直し、修正を行った。
’2009/7/25 新規作成。
Programming Place Plus のトップページへ