C言語の入出力関数の違い

このQ&Aのポイント
  • C言語の入出力関数の違いを解説します。
  • C言語で使用される入出力関数のメリット・デメリットについてまとめました。
  • どの入出力関数を使うべきかについての正しい考え方を紹介します。
回答を見る
  • ベストアンサー

C言語の入出力関数の違い

C言語を勉強しています。 自分が知っている入出力を行う関数は printf scanf fprintf fscanf sprintf sscanf gets puts getc putc fgets fputs fgetc fputc なのですがそれぞれのメリット・デメリットの違いがまだあいまいです。 というかまだ上の4つくらいしかまともに使ったことがありません。 自分の考えでは、 ・上の4つは書式を指定でき、ファイルから読み込みするときなどは fgetsよりもfscanfが使いやすいと思っています。 ・スペースを読み込みたいときはscanfではなくgetsを使わなければならない。 ・システム開発でscanfを使うことは危険なのでほとんどない。 間違った考え方をしていたり、どのようなデータのときに どの関数を使うのが正しい、常識、と知っている方いらっしゃいましたら 教えてください。

noname#198479
noname#198479

質問者が選んだベストアンサー

  • ベストアンサー
  • jacta
  • ベストアンサー率26% (845/3158)
回答No.4

まずはそれぞれの関数の仕様を正確に把握しましょう。 それを怠っていると、実際以上に物事が複雑に見えてしまい、難度を上げてしまいます。 どの関数を使うかの基準ですが... ちょっとした実験プログラムや使い捨てのツールなどであれば、使いやすい関数を使うのが一番楽です。 このような場合は、文字列の読み込みはgets、数値入力ならscanfというように、入力部分は楽をして、本質的な部分に注力する方が得策です。 ある程度以上信頼性が求められる状況であれば、機能面だけではなく、エラー処理のことも考えて使う関数を選ばなければなりません。 たとえば、scanfやfscanfは、文字列を読み込むのであれば、(たとえ空白文字を含む文字列の入力であっても)特に問題なく使うことができます。ただし、入力された文字列に対してバッファサイズが不足していた場合は、読み込めなかった残りの文字をどうするか、十分注意しなければなりません。 一方で、scanfやfscanfでは、数値(整数値も浮動小数点数値も)を読み込む場合、オーバーフローの検出がまったくできません(未定義の動作になります)。また、"%lc"や"%ls"でマルチバイト文字(列)を読み込むときも、表現形式に異常があってもエラーを検出できません。ですので、信頼性に配慮するなら、(ワイドではない)文字列以外の入力にscanfやfscanfを使うべきではありません。これはsscanfでも同様です。 fgetsには大きな問題はありませんが、バッファサイズが足りずに1行分の文字列全体を読み込めなかった場合の対応に注意しなければなりません。ここのところを無視して、getsをやめて単純にfgetsに置き換えることを勧める人が少なくありませんので、気を付けてください。 入力に関していえば、fgetc(またはgetc)だけを使うのが、もっとも細かい制御が可能です。ただし、マルチスレッド環境の場合には排他制御を考慮しなければなりません。処理系によっては、getc_unlockedとflockfile/funlockfileを組み合わせる方が便利でしょう。 wprintfやwscanfなどのワイド文字版の関数もありますが、この場合はロケールの影響に注意しなければなりません。ロケールについては、普通のprintfでも影響を受けます。たとえば、浮動小数点数の小数点文字とか、ワイド文字から多バイト文字への変換とか。 ところで... 将来ある程度以上の権限を持てるようになった場合、scanfではなくfgetsとsscanfを使うべきだと盲目的に信じているようなプログラマは使うべきではありません。 前述したように、scanfと同じ問題はsscanfも抱えているので用途は制限されますし、また、問題があっても状況次第では使えるからです。 にも関わらず、そうしたことを妄信しているプログラマは、仕様を正しく理解する努力が足りないか、日常的に実験プログラムを書いて勉強する習慣がないかであり、結果としてまともな基礎力が期待できません。

noname#198479
質問者

お礼

>信頼性に配慮するなら、(ワイドではない)文字列以外の入力にscanfやfscanfを使うべきではありません。 覚えておきます。 >処理系によっては、getc_unlockedとflockfile/funlockfileを組み合わせる方が便利でしょう。 初めて聞きました(^_^;) もう少し勉強してみます。 >fgetsとsscanfを使うべきだと盲目的に信じているようなプログラマは使うべきではありません。 そうなんですか?! いろいろな意見があるのですね(^_^;)臨機応変に使いわけるのが大切なんですね!

その他の回答 (6)

  • kmee
  • ベストアンサー率55% (1857/3366)
回答No.7

int main() { int a,b,c ; scanf("%d",&a); scanf("%d",&b); c=a+b ; print("%d + %d = %d\n",a,b,c ) ; return 0; } いかにも入門書の最初のころにありそうなプログラムです。 これを scanf(fscanf等の同系列を含む)を使わず、まだ説明されていないであろう「文字列(charの配列)」も使わず(文字列が使えないのだから、atoi等の変換関数も使えない)、「安全」なプログラムを作れ、と言われて、hello,world. を動かしたばかりの初心者にできるでしょうか? あるいは、そんなプログラムが載っていて、勉強を続ける気になるでしょうか? 使い方さえ間違えなければ、便利ですよ

noname#198479
質問者

お礼

適材適所でがんばります!

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.6

「なぜ scanf をC言語の入門で最初に教えるのか」については, 「とりあえず適切に入力する限り最も簡単だから」ということもありますな. 結局のところ, どんな方法をとってもそれぞれに「問題」が発生しうる. ちなみに fgets+sscanf だと ・scanf では読み込めるが fgets+sscanf では読み込めない入力がある ・sscanf では「読み込み位置」が変化しないことに気付かない人がいる (事実) ということもあったりします. 少なくとも, 「単純に置き換えられる」ものでもない. そもそも fgets が「1行読み込む」というものでもないので....

noname#198479
質問者

お礼

ありがとうございます。 経験積んで身につけるしかないですね。

  • jacta
  • ベストアンサー率26% (845/3158)
回答No.5

> なぜそんな危険な関数をC言語の入門で最初に教えられるんでしょうかね?(笑) 例えるなら、鉛筆は凶器になりますが、そんな危険な道具を小学一年生にも(多くの場合は幼稚園児にも)使わせます。 大切なのは、危険なものを一切排除することではなく、安全に使うにはどうすればよいかをきちんと理解させることなのです。

noname#198479
質問者

お礼

スケールの大きいたとえでわかりやすかったです(笑) 危険を理解しながら勉強していこうと思います! ありがとうございました!

  • kmee
  • ベストアンサー率55% (1857/3366)
回答No.3

まずは、どっかで関数のリファレンスマニュアルを探しましょう。読めば違いがちゃんと書いてあります。 まず、同等の機能で ・1文字入力: getchar, fgetc, getc ・1文字出力: putchar, fputc, putc ・1行入力: fgets, gets ・1行(or文字列)出力: puts, fputs ・書式付き入力: scanf,fscanf ・書式付き出力: printf,fprintf と言うかんじに分類され、それぞれ ・fで始まる: ファイルポインタを使って指定したファイルから入力/ファイルに出力する ・それ以外(getc,putcは例外): 標準入力から入力/ 標準出力に出力する という大体のルールがあります。 標準入出力というのは、実行時にすでにオープンしている特別な「ファイル」だと考えてください。 何もしなければ、標準入力は「端末でのキーボードからの入力」、標準出力は「端末の画面への出力」になっていることが多いです。入門書によく「scanfはキーボードから入力」「printfで画面に出力」とありますが、これは厳密には間違いです。 これらは、 stdin, stdout という変数に設定済みなので scantf( format,.. ) = fscanf( stdin, format, .... ) printf( format,.. ) = fprintf( stdout, format, .... ) となります。 ただ、完全に対称では無くて、fgetsとgets, fputsとputsのように、仕様に違いがあるものもあります(なので、リファレンスマニュアルで確認してください) > ・上の4つは書式を指定でき、ファイルから読み込みするときなどはfgetsよりもfscanfが使いやすいと思っています。 > ・スペースを読み込みたいときはscanfではなくgetsを使わなければならない。 > ・システム開発でscanfを使うことは危険なのでほとんどない。 これにもいろいろと誤解があります。 ・どちらが使いやすいか、は、なにをするか、によって違います。単純に決めないことです。 たとえば、「単純にテキストを一行読み出して、先頭に行番号を付けたい」と言うケースでは、fscanfではかなり難しいですが、fgetsなら簡単です。 ・(f)scanfでスペースを入力する方法はあります。%cを指定したり%[]で空白を含めるように指定したりできます。リファレンスマニュアルの「書式」をよく読んでください。 逆に(f)getsで1行読んでしまうと面倒なケースもあります。Cのプログラムのように、「1行」であることに意味の無いデータなら、読み込んだ分を「戻す」ような処理が必要でしょう。 ・(f)scanfはいろいろと誤解されている関数です。たしかに、手を抜くと大変危険です。が、真面目に対処すればとても便利な関数です。このあたりは「scanfの誤解」で検索するといろいろみつかります。 何が危険かも考えずに安易に他の関数を使う方が危険です。scanf("%s",str)をやめて、空白までgetharで読み込む関数を自作したとしても、strの大きさを考えずに処理すれば、結局同じことになります。 > どのようなデータのときにどの関数を使うのが正しい、常識 言い方悪いですけど、慣れや経験で身に付けるしか無いと思います。 「AにはB」って定石があったとして、では、ちょっと変わったA'には「Bを少し変えたB'」なのか「別なC」なのかってことになりますから。

noname#198479
質問者

お礼

1文字入力・出力、 1行入力・出力、 ファイルから・標準入力から、 の違いはわかっていたのですが使い分けがわかりませんでした。 今までの回答を見させていただいてふと疑問に思っていたのですが、 なぜそんな危険な関数をC言語の入門で最初に教えられるんでしょうかね?(笑) 自分はscanfとprintfを最初に教えられたので。 数をこなして身につけようと思います。

  • notnot
  • ベストアンサー率47% (4848/10262)
回答No.2

gets はバッファオーバーフローを防げないので、絶対に使ってはいけない。→ fgets を使う。 scanf は使い方が非常に難しいので、使うケースはまずない。特に初心者には絶対に使わせてはいけない。→ fgets と sscanf を使う。もしくは fgets とほかの関数の組み合わせ。 fscanf,sscanf にしても書式指定の考慮漏れが無いかのテストが大変なので、十分心して使う。ほかに手があればそちら。 ほかの関数は適材適所でしょうか。

noname#198479
質問者

お礼

No1の回答者の方も初心者はfgets と sscanfがいいとおっしゃっていたので やはりこれを使う人が多いのですかね! getsはだめなんですね(´A`;) ありがとうございました。

  • qppi_tbo
  • ベストアンサー率33% (1/3)
回答No.1

printfとscanfは絶対に使いません。 ファイルから何かを読み込む場合はfgets()とsscanf()の組み合わせ。 いわるゆprintfデバッグをする場合もfprintf()を出力先:stderrで使います。 あとはたまにsprintf()を使うくらいでしょうか。 処理をある場所で意図的に止めたい場合なんかにgetchar()を使ったりもします。

noname#198479
質問者

お礼

具体的な組み合わせを教えていただきありがとうございます。

関連するQ&A

  • fscanf関数のscanf集合を使う時の不具合

    こんにちは。 現在、C言語でプログラミングをしています。 最近気が付いたのですが、fgetc、fputc、fgets、fputs、fprintfといった関数は、 呼び出されるたびにファイルの現在位置を進めるのですが、 fscanf関数において、scanf集合を使った場合だけは、 何故かいつもファイルの先頭からスキャンを開始するようです。 例えば、適当なテキストファイルtest.txtを作成し、 ------------------------------------ FILE *fp; char str[80]; fp=fopen("test.txt", "r"); while(!feof(fp)){ fscanf(fp, "%[a-zA-Z]", str) puts(str); } ------------------------------------ といったコードブロックを試してみると、永遠にforループが終了せず、 最初の英単語が何回も画面に出力されます。 このコードブロックの目的は、test.txtから、英単語だけを取り出して、順番に出力していく事です。 上のコードブロックで、whileループの最後に、fseek(fp, srtlen(str), SEEK_CUR); といったコードを挿入し、ファイルの現在位置を手動で進めてみたのですが、 どうも上手く行きませんでした。 scanf集合は便利なので、fscanf関数で使っていきたいのですが、こういった不具合があるので困っています。 上のコードブロックを上手く動作させる方法だけでもいいので、何かアドバイスを頂きたいと思います。 では、よろしくお願い致します。

  • C言語 ニュートン法

    何をすればいいかよくわからなくて躓きました わかる方どうかお願いします 問 以下に示すプログラムで、x1 = x0 - f (x0) / f '(x0)を用いて、方程式の左辺関数 f (x) (= x 2 - a ) およびその導関数 f '(x) を独立させろ。 入力変数 a はグローバル変数としてよい。 導関数 f '(x)はプログラム内ではdfと表記することとする。 また、適切なコメントも追加すること。 /* newton.c: ニュートン法 */ #include <stdio.h> // printf, fprintf, fgets, sscanf #include <math.h> // fabs double newton(double x, double (*f)(double), double (*df)(double), double eps) {   int n;   double a = 0, x, x0, err, eps = 1.0e-10;   printf("# n, x, err\n");   printf("%4d, % .15e\n", n, x);   do {     n++;     x0 = x;     x = (x0 + a/x0) / 2;     err = fabs(x-x0);     printf("%4d, % .8e, % .8e\n", n, x, err);   } while (err > eps);     return x; } int main(void) {    double a = 0, x, x0, err, eps = 1.0e-10;    char s[128];   fprintf(stderr, " a = "); fgets(s, 128, stdin); sscanf(s, "%lf", &a);   while (a <= 0.0) {    fprintf(stderr, "'a' には正の数を入れてください。\n");   fprintf(stderr, " a = "); fgets(s, 128, stdin); sscanf(s, "%lf", &a);   }   x = (a + 1.0) / 2.0;   printf("\n# sqrt(%e) = % .15e\n", a, x);    return 0; }

  • C言語の質問です

    ニュートン法のプログラムを組んだのですがPAD図が描けなくて困ってます どなたか回答お願いします /* newton.c: ニュートン法 */ #include <stdio.h> // printf, fprintf, fgets, sscanf #include <math.h> // fabs double a;    //グローバル変数 double f(double x)  //fは関数f(x) { return x * x - a; //f(x)=x~2-a } double df(double x)  //dfはf(x)の導関数f’(x) { return 2 * x; //df(x)/dx = 2x } double newton(double x, double (*f)(double), double (*df)(double), double eps) { int n = 0;     //初期化 double x0, err;    //x0は初期値 printf("# n, x, err\n"); printf("%4d, % .15e\n", n, x); do { n++; x0 = x; x = x0 - f(x0) / df(x0); //切片でy=0、x=x1 err = fabs(x-x0); printf("%4d, % .8e, % .15e\n", n, x, err); } while (err >= eps); return x; } /*メインルーチン*/ int main(void) { double x, eps = 1.0e-10; //収束条件 char s[128]; fprintf(stderr, " a = "); fgets(s, 128, stdin); sscanf(s, "%lf", &a); while (a <= 0.0) { fprintf(stderr, "'a' には正の数を入れてください。\n"); fprintf(stderr, " a = "); fgets(s, 128, stdin); sscanf(s, "%lf", &a); } x = (a + 1.0) / 2.0; x = newton(x, f, df, eps);   //ニュートン法の実装 printf("\n# √(%e) = % .15e\n", a, x); return 0; }

  • 文字取得

    教えてください! fgetsでストリームから文字をline[20]に格納できたとします。 その後、lineから1文字づつ参照していき、 カンマで区切られたデータをそれぞれ別の変数に格納したいのです。 1文字づつ参照するためにはどんな関数でできますか? getcやfgetcではできないと思うのですが・・・。

  • C言語について

    #include <stdio.h> #include <string.h> #include <time.h> typedef struct { int day; double dist; char comment[32]; } diary; void writeData(char datafile[]); void viewData(char datafile[]); char datadir[] = "dat/"; int year, month, day; int main(int argc, char* argv[]) { char datafile[11]; struct tm *date; time_t now; now = time(NULL); date = localtime(&now); year = date->tm_year + 1900; month = date->tm_mon + 1; day = date->tm_mday; sprintf(datafile, "walk%04d%02d", year, month); if ((argc > 1) && (strcmp(argv[1], "-view")==0)) { viewData(datafile); } else { writeData(datafile); } return 0; } void writeData(char datafile[]) { diary today_data; FILE *outfp; //出力ファイルのファイルポインタ char y_n = 'n'; char filename[15]; char input_str[12]; printf("%d/%d/%dの記録をしますか? (y/n) > ", year , month, day); scanf("%c", &y_n); while (getchar() != '\n') { } if(y_n != 'y') { printf("記録する日付は?(例:%d %d %d) > ", year, month, day); year = 0; month = 0; day = 0; gets(input_str); sscanf(input_str, "%d %d %d", &year, &month, &day); if((year > 9999) || (month > 12) || (month < 1) || (day > 31) || (day < 1)) { printf("日付が正しくありません\n"); return; } sprintf(datafile, "walk%04d%02d.txt", year, month); } today_data.day = day; printf("距離は? > "); scanf("%lf", &today_data.dist); while (getchar() != '\n') { } printf("コメントは?(30文字以内) > "); fgets(today_data.comment, 32, stdin); if((strlen(today_data.comment) == 31) && (today_data.comment[30] != '\n')) { while (getchar() != '\n') { } } sprintf(filename, "%s%s", datadir, datafile); if((outfp = fopen(filename, "a+")) == NULL) { printf("ファイルオープンエラー\n"); return; } fprintf(outfp, "%d %.2lf ", today_data.day, today_data.dist); if((strlen(today_data.comment) == 31) && (today_data.comment[30] != '\n')) { today_data.comment[30] = '\n'; today_data.comment[31] = '\0'; } fprintf(outfp, "%s", today_data.comment); fclose(outfp); printf("記録しました"); } void viewData(char datafile[]){ } このソースの char datadir[] = "dat/";この宣言はディレクトリを指しているんですよね? この場合の/はどおいう意味ですか? 教えてください

  • C言語のファイル入出力の問題です

    C言語の課題です ファイルの入出力を応用した関数の総和と平均値を求める問題です。 総和がどうしても0になります 原因を教えて下さい 前問にy=x/(1+x^2)を-10.0≦x≦10.0(xは0.01刻み)の範囲で計算して結果をsp4_rslt.datに出力せよという問題があります。これはそのsp4_rslt.datを最初に読み込んで、今度は0≦x≦10.0の範囲でyの総和と平均を求めなければなりません。 なのでfor文でループしてあとは計算というプログラムにしました。最後に結果をsp6_rslt.datに出力しろというものです。 けれどいくらやっても0にしかなりません。どこを修正すればいいのか教えて下さい #include <stdio.h> #include <stdlib.h> int main(void) { double x,y; double Summation, Average; FILE *fp; fp = fopen("sp4_rslt.dat", "r"); if (fp == NULL) { puts("can't open file!"); exit(-1); } for(x=0.; x<=10.0; x+=0.01){ y = x/(1+x×x); fscanf(fp,"%lf %lf",&x,&y); Summation = Summation + y ; Average = Summation / 1000.0; } fp = fopen("sp6_rslt.dat","w"); fprintf(fp,"Summation is %lf\nAverage is %lf\n",Summation ,Average); fclose(fp); return 0; }

  • fscanf(),scanf()とBuffer Overflow

    scanf("%s", buf); で、bufの長さはどれくらいに取ればよいのでしょう。 sscanf(buf,"%s",buf2); なら、sizeof(buf)以上に大きくならないでしょうが、scanf(), fscanf()で文字列を読み込むときは、Buffer Overflowの危険から逃れられないような気がしています。 私はそのため、文字列を扱うときには、この二つの関数を使わないでいるのですが、安全な使用方法はあるのでしょうか? scanf("%10s",buf); のような使い方は知っています。でもこれでは文字数が10文字だったのか、それ以上だったのか判別できません。知りたいのは最大文字数が未知の場合です。 こう使えば安全という使い方があればぜひご紹介ください。

  • C言語の変換する関数について教えてください。

    キーボードからローマ字で入力された名前の英文字を変換する関数を定義し、その関数の機能を確認するプログラムを作成する問題について教えてください。 (1)英小文字であればそれを英大文字に変換する関数 (2)英大文字であればそれを英小文字に変換する関数 (3)英小文字であればそれを英大文字に、英大文字であればそれを英小文字に変換する関数 ただし、キーボードから入力された名前を格納する配列と、変換後の名前を格納する配列を別にする。 また、名前は関数main()内で表示する #include <ctype.h> #include <stdio.h> void name_toupper(char str[]) { unsigned i = 0; while (str[i]) { str[i] = toupper(str[i]); i++; } } void name_tolower(char str[]) { unsigned i = 0; while (str[i]) { str[i] = tolower(str[i]); i++; } } int main(void) { char str[100]; printf("文字"); scanf("%s", str); name_toupper(str); printf("大文字: %s\n", str); name_tolower(str); printf("小文字: %s\n", str); return 0; } 自分で作った上のプログラムではKa siと入力すると(1)ではKA、(2)ではkaと表示されsiが消えてしまいます。原因がよくわかりません。 あと(3)ができないし、ただしを満たしているのかもあいまいです。 文字列の入力の形式:char *gets(char *buffer)を用いればどうにかなるのではと思っていますがどうですか? 説明が長くなって申し訳ありませんが教えてください。 よろしくお願いします。

  • 小数が、18行x72列、並んでいるファイルを読みます。どうしたら良いでしょうか。

    ・小数が、72列18行あります。 ・小数は、ときに負号が記載されていることがあります。 ・小数は、C言語でfprintfで、"%-7.5f " で打ち出したもので、見てみると、7桁、時に八桁のようですが。 ・区切りは、上記のように空白一つで、fprintfで書き出したはずなのですが、実際には、スペースが一つか二つ(不定。どうやら負号のない場合には空白が一つ増える・・・ということみたいですが不明)入っています。 ・72列のデータの後はすぐに改行しておらず、スペースが一つか二つはいって、改行しています。 自分で書き出したデータなのに間抜けなことですが、どうしたらC言語で安定して、読み込めるでしょうか。 fgets? fgetc? scanfは不安です。 windows2002, thinkpad, borland C v5.5無料版です。 どうぞよろしくお願いいたします。

  • scanfの入力をgets関数で読み捨てることについて

    -------------------------------------- #include<stdio.h> int main(void) { double dt=0.0,sum=0.0; char ss[80]; int ret; ret=scanf("%lf",&dt); puts(""); if(ret!=1){ gets(ss); printf("数値を入力してください\n"); puts(""); } while(dt!=999){ sum=sum+dt; ret=scanf("%lf",&dt); puts(""); if(ret!=1){ gets(ss); printf("数値を入力してください\n"); puts(""); } } printf("合計=%f\n",sum); return 0; } -------------------------------------- 以上のプログラムで、入力した数値の合計を出し「999」が入力されたら終了させ、数値以外が入力されたら、gets関数で読み捨て入力を続けていくということをしたいのですが、例えば、 ◎1----------- 2 3 4 999 合計=9.000000 --------------- ◎1のように数値のみだと正しく表示されます。 次に、 ◎2-------------------- a 整数を入力してください b 整数を入力してください 2 3 999 合計=5.000000 ------------------------ ◎2のように数値以外を先に入力し、その後に数値を入力しても正しく表示されます。 次に、 ◎3------------------- 2 3 a 数値を入力してください b 数値を入力してください 999 合計=11.000000 ----------------------- ◎3のように数値を入力した後に、数値以外を入力したら正しく表示されません。 次に、 ◎4-------------------- 2 a 整数を入力してください b 整数を入力してください 3 999 合計=9.000000 ------------------------ ◎4のように数値をまず入力しその後、数値以外を入力する。その後、数値を入力して終了させても、合計値が正しく表示されません。 まだ、バッファについて完全に理解していないということもあり、何故こうなってしまうのか分かりません。 教えていただけると嬉しいです。

専門家に質問してみよう