C言語関係掲示板

過去ログ

No.263.関数呼び出し

[戻る] [ホームページ]


No.1587

関数呼び出し
投稿者---チェリー(2002/05/22 23:27:03)


受け取った文字列が数字以外のとき再入力を促すプログラム解説ありがとうございます。
で、この発展なのですが、受け取った文字列、つまり、入力変数、yearのチェックに引き続いて、次の入力変数、monthのチェックも同じように行いたいので、先頭文字列、二文字目以降の数字以外のチェックの部分を関数chekにして呼び出そうと思うのですが、以下、自作のプログラムでは、上手くいきません、どこが、おかしいのでしょうか?
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<ctype.h>
int main(void);
void calendar(int,int);
int final(int,int);
int chek(int);
int main(void)
{


char str[128];
int i,err;
int year,month;

do {
printf("年を入力してください : ");

/* 全部数値なら内容チェック */
if (chek(err) == 0) {←ここで、chek関数を呼び出し、errを戻り値として、返しているのですが
year = atoi(str);
if (year<=0 || year>9999) {
err = 1; /* 負数、5桁以上はエラー */
}
}

} while (err != 0) ;/* エラーならば再入力 */


do {
printf("月を入力してください : ");

/* 全部数値なら内容チェック */
if ( chek(err)== 0) {
month = atoi(str);
if (month<=0 || month>12) {
err = 1; /* 負数、5桁以上はエラー */
}
}

}while (err != 0); /* エラーならば再入力 */



return(0);
}
int chek(int err)←この部分でchek関数を宣言してます
{
char str[128];
int i;
int year,month;
gets(str);


if(str[0]=='0')
{
err=1;
}
else
{
err=0;
}
i=1;
/* 数値チェック */
while (err==0 && str[i] != '\0') {
if (isdigit(str[i])==0) {
err++; /* 数値以外はエラー */
}
i++;
}
return(err);
}

No.1588

Re:関数呼び出し
投稿者---kikk(2002/05/23 02:16:24)


ども。


変数errの使い方をみるに、おそらく値の引渡しに関してまちがって
理解しているのではないかと。

11−2. 関数間のデータ授受の方法
http://www9.plala.or.jp/sgwr-t/sec11-2.htm

にも書いてありますが、呼び出し側と呼び出され側の変数は別物です。
呼び出し時に代入が行われると思ってください。

で、やりたいことを実現するには、

err=chek();
if (err==0) {

等すればよいかと。chek()の引数は無し(void)でOK。
そのかわり、chek()の中で int err; としてください。


ついでに。
上記ページの(2)に

call by reference(アドレスによる渡し)

とありますが、一般にいわれるcall by reference(参照渡し)と、C言語の
ポインタを介しての引渡し(アドレス渡し、ポインタ渡し)は異なったもの
です。C言語のアドレス渡しの対訳はcall by addressないしcall by pointer
が適切かと。

ちなみに、チェリーさんの理解は参照渡しに相当します。C++等の他の言語
とはちがって、C言語では参照渡しはできないので、当然、思ったとおりには
なりませんけど。


では。

No.1590

Re:関数呼び出し
投稿者---チェリー(2002/05/23 04:21:42)




解説ありがとうございます。
>err=chek();
>if (err==0) {

>等すればよいかと。chek()の引数は無し(void)でOK。
>そのかわり、chek()の中で int err; としてください。
私のやりたいことの説明が、不明確なために、上手くいかないのかもしれませんが、やはりできませんでした。やはり、123と年入力した後、本当は、月入力へいきたいのに、再入力が促されてしまいます。
<PRE>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<ctype.h>
int main(void);
void calendar(int,int);
int final(int,int);
int chek();
int main(void)
{


char str[128];
int i,err;
int year,month;

do {
printf("年を入力してください : ");
err=chek();

/* 全部数値なら内容チェック */
if (err == 0) {
year = atoi(str);
if (year<=0 || year>9999) {
err = 1; /* 負数、5桁以上はエラー */
}
else
{
err=0;
}
}

} while (err != 0) ;/* エラーならば再入力 */


do {
printf("月を入力してください : ");
err=chek();

/* 全部数値なら内容チェック */
if (err== 0) {
month = atoi(str);
if (month<=0 || month>12) {
err = 1; /* 負数、5桁以上はエラー */
}
}

}while (err != 0); /* エラーならば再入力 */



return(0);
}
int chek()
{
char str[128];
int i,err;
int year,month;
gets(str);


if(str[0]=='0')
{
err=1;
}
else
{
err=0;
}
i=1;
/* 数値チェック */
while (err==0 && str[i] != '\0') {
if (isdigit(str[i])==0) {
err++; /* 数値以外はエラー */
}
i++;
}

return(err);
}
</PRE>
>ちなみに、チェリーさんの理解は参照渡しに相当します。C++等の他の言>語
>とはちがって、C言語では参照渡しはできないので、当然、思ったとおり>には
>なりませんけど。
っておっしゃってますが、そもそも、年入力と月入力のエラーチェックを関数としてまとめ、呼び出すのはムリなのでしょうか?



No.1591

Re:関数呼び出し
投稿者---Kaji(2002/05/23 16:07:35)


こんにちわ。

このプログラムでは、main関数とcheck関数とで別々に
strを宣言しているのでそれが問題になっていると思います。
check関数の中でstrに入力を取得しているのに
この文字列を数値に変換しているのはmain関数内です。
check関数内では文字列がstrに入っていてもmain関数のstrとは別なので。

これに対する対策として以下の方法があると思います。

1.check関数の中でgetsを行うのではなくmain関数で行う。
check関数へは引数として文字列を渡す。

gets(str)
err = check(str)

2.check関数の返り値を、エラー時に-1、正常な数値の場合には
その数値を返す。
while(1) {
printf("年を入力してください : ");

year = check()
if(year <= 0 || year > 9999) {
continue;
}
break;
}

3.strをグローバル変数にする


特に検証していないので間違っているかも知れませんが・・

No.1592

Re:関数呼び出し
投稿者---kikk(2002/05/23 16:11:00)


ども。


main()の中のstr[]とchek()の中のstr[]は別のものです。
なので、少し設計しなおす必要があります。

>>ちなみに、チェリーさんの理解は参照渡しに相当します。C++等の他の言>語
>>とはちがって、C言語では参照渡しはできないので、当然、思ったとおり>には
>>なりませんけど。
>っておっしゃってますが、そもそも、年入力と月入力のエラーチェックを関数としてまとめ、呼び出すのはムリなのでしょうか?

年入力と月入力のエラーチェックは参照渡しをつかわないと絶対に実現
できないという処理では決してないので、そんなことはありません。

共通の処理を関数にまとめるという考えはいいです。でも、何を渡して
何を返すのかをよく考えないで関数を作ってしまうと、あとでいろいろ
困ります。


で。
現状のものをなるべく手を加えないでなんとか、ということになると、
chek()のstr[]を返すという方法を思いつくかもしれませんが、関数が2つ
以上の値を返すことはできません。

解決方法はいくつかあります。

1つめはstr[]かerr(あるいは両方)をグローバル変数にする方法。
これはあまりすすめませんが。。

2つめは、入力のstr[]への格納を呼び出し側(mani()側)で行ってから
str[]をchek()に引数として引渡し、chek()はerrを返すようにする、
というもの。
# これだと関数名と機能がマッチします

3つめは、引数と返り値は2と同じようにして、読み込みもchek()内で
行う方法。この場合、関数から戻ったときに呼び出し側のstr[]に入力が
得られます(得られるようにします)。
# これは標準ライブラリの入力系の関数に近い仕様です

2の場合、配列を関数に渡すことになるのですが、これは実はアドレス渡し
になります。しかしながら、書き込みを行わないのでそのことを意識する
ことなくできてしまうと思います。なんとなくできてしまうので、わかった
つもりになってしまう可能性はありまする。実装ですが、chek()側は
chek(str[128])とし、呼び出し側はchek(str)とします。

3の場合ですが、ポインタと配列の使い方をかじっておかないと、一度に
たくさんのことを詰め込みすぎて、プログラマが死ぬ可能性があります。
本質的には2もそうなんですが。

2にせよ3にせよ、ちゃんとした理解のためには多少なりとも配列とポインタ
のおべんきょが必要です。ここのホームページはかなりよくできてるので
順に読んでいけばだいじょぶです。good luck!


では。

No.1595

Re:関数呼び出し
投稿者---チェリー(2002/05/23 22:07:08)


kikkさん、Kajiさん、ありがとうございました。
解説拝見しましたが、超初心者の私には、難しそうです(汗)
品質を考えると、現状の私には、年・月エラー共通処理をくりかえすプログラムしか、書けなそうです。でも、ポインタのとこしっかり勉強していずれは、共通処理の部分、関数呼び出しで、書けるようになりたいです。ここで、一つまた、別の質問なのですが、よく、グローバル変数を使ってはいけないと伺うのですが、その理由を教えていただきたいです。コンパイラ・エラーが出ると、私は、よく、ローカル変数から、グローバル変数にかえてしまってるのですが…

No.1597

グローバル変数の弊害
投稿者---kikk(2002/05/24 05:16:54)


ども。


>で、一つまた、別の質問なのですが、よく、グローバル変数を使ってはいけないと伺うのですが、その理由を教えていただきたいです。コンパイラ・エラーが出ると、私は、よく、ローカル変数から、グローバル変数にかえてしまってるのですが…

12章 記憶クラス
http://www9.plala.or.jp/sgwr-t/sec12.htm

にも書いてありますが、どこで値が書き換わるかが把握できない、
というのがいちばんの理由になります。あとは、名前の衝突の問題
ですね。こっちはグローバル変数というよりはブロック中の変数に
関するものです。任意のブロック内の先頭において(C++やC99では
制限緩和)、変数を宣言することができるのですが、外側で宣言されて
いる変数と同じ名前の変数を宣言することも可能です。この場合、
ブロック内では内部のほうの変数を指すことになります。グローバル
変数はその名のとおり、どこからでも見えるので、名前が衝突する
可能性があり、衝突した場合、プログラマはきっと混乱します。
複数人でプログラムを作ると、衝突の可能性が把握できないので、
問題が深刻になりやすくなります。



/* LV0(global) */
int x=0;
int main() { /* LV1 */
int x=1;
printf("%d\n",x);
{ /* LV2 */
int x=2;
printf("%d\n",x);
}
printf("%d\n",x);
return 0;
}


結局、ひとことでいうと、保守がたいへんになるから、ということに
なります。

ローカル変数の場合は考えなければならない範囲が基本的に関数内に
おさえられますし。絶対にグローバル変数でなければ実現できないという
場面はそう多くはないので、可能な限りローカルにしましょう。

なお、上記のページには書いてありませんが、グローバル変数の通用範囲
はファイル外まで及びます。分割コンパイルをするときなどは注意が必要
です。ファイル内に制限したい場合は、staticをつければOKです。
というか、常にstaticは付けるようにしておいて、ファイルのそとからも
参照したいものだけstaticなし(あるいはexternをつける)にすべきです。


余談ですが、ときどき、グローバル変数の名前はG_とか_Gとか_globalを
前やうしろにつけて、グローバル変数であることを明示するような
コーディングをしてあるプログラムをみかけます。


では。

p.s. 質問ごとに新規の書き込みをしたほうがみんながシアワセになれます

No.1602

Re:関数呼び出し
投稿者---かずま(2002/05/25 12:35:46)


> 年入力と月入力のエラーチェックを関数としてまとめ、呼び出すのはムリなのでしょうか?

入力全体を関数にまとめてみたらいかがでしょう。
#include <stdio.h>
#include <stdlib.h>

int get_int_range(const char *prompt, int min, int max)
{
    char buf[1024], *p; int val;

    for (;;) {
        fputs(prompt, stdout);
        if (fgets(buf, sizeof buf, stdin) == NULL) exit(1);
        val = strtol(buf, &p, 10);
        if (p != buf && *p == '\n' && val >= min && val <= max)
            return val;
    }
}

int main()
{
    int year, month;

    year = get_int_range("年を入れてください(1〜9999): ", 1, 9999);
    month = get_int_range("月を入れてください(1〜12): ", 1, 12);
    printf("%d年 %d月\n", year, month);
    return 0;
}