C言語で数字の出現回数をカウントするプログラムの理解について

このQ&Aのポイント
  • C言語で実装されたプログラムについての質問です。
  • プログラムでは、入力された文字列に含まれる数字の出現回数をカウントする処理が行われています。
  • 関数内で行われている処理の一部が理解できず、具体的な動作や意図について知りたいです。
回答を見る
  • ベストアンサー

明解C言語 入門編 P219 について

以下のプログラムの理解について、質問です。 入力した文字列に、0 ~ 9 の数字がそれぞれ何度含まれていたかをカウントするプログラムです。 (明解C言語 入門編 P219) ========== プログラム ========== #include <stdio.h> void str_dcount(const char str[], int cnt[]) { unsigned i = 0; while(str[i]) { // str に '\0' が現れるまで繰り返し if(str[i] >= '0' && str[i] <= '9') cnt[str[i] - '0']++; // !!!! ココです !!!! i++; } } int main(void) { int i; int dcnt[10] = {0}; char str[100]; printf("文字列を入力してください : " ); scanf("%s", str); // 文字列を str へ str_dcount(str, dcnt); // 引数  str = 入力した文字列, dcnt = すべて '0' puts("数字文字の出現回数 "); for(i = 0; i < 10; i++) printf("'%d' : %d\n", i, dcnt[i]); return 0; } ========= 実行結果 ========= 文字列を入力してください : 3.14159 数字文字の出現回数 '0' : 0 '1' : 2 '2' : 0 '3' : 1 '4' : 1 '5' : 1 '6' : 0 '7' : 0 '8' : 0 '9' : 1 ========== 質問内容 ========== 関数 str_dcount 内の 『 cnt[str[i] - '0']++; 』 がどのような処理を行っているのか、理解ができません。 以下のように考えたのですが、それでは上手く動かないとの結論に至りました。 たとえば、i = 0 で、str[100] = 3.14159 であるとき、 /* ループ1回目 */ cnt[0] = str[0] - 0; /* 3 - 0 で cnt[0] には 3 が入る。 */ cnt[0++]; /* ループ2回目 */ cnt[1] = str[1] - 1; /* "." - 1 となる。 数字ではないので減算できずエラーになるのでは? */ cnt[1++]; /* ループ3回目 */ cnt[2] = str[2] - 2; /* 1 - 2 なので、なぜか負の数になってしまう。。 */ cnt[2++]; 配列の添え字が、配列になっている cnt[str[i]] までは理解できますが、 以下の2点が解っていないために理解ができていないようです。 1) cnt[str[i] - '0']++ ; の - '0' が何を行っているのか 配列には数字が入っており、そこから文字 '0' を減算しているのでしょうか? 数字から文字を減算できるのでしょうか? 2)  ++  はどの値を増加させているのでしょうか? 宜しくお願いします。

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

  • ベストアンサー
  • asuncion
  • ベストアンサー率33% (2126/6288)
回答No.1

unsigned i = 0; while(str[i]) { // str に '\0' が現れるまで繰り返し if(str[i] >= '0' && str[i] <= '9') cnt[str[i] - '0']++; // !!!! ココです !!!! i++; str[]に"3.14159"が入っていたとき、 str[0] == '3'であるから、if文の条件を満たします。 str[0] - '0'は、'3' - '0'ですから、3という数値です。 たいていの文字コードでは'0', '1, '2', ..., '9'の順に番号が振ってあるはずですから、 '0'を基準としていくつ離れているか('3' - '0'の式の部分)が、その数字に相当します。 cnt[3]++;は、配列の要素cnt[3]の値を1だけ増やします。 これで、"3.14159"の先頭文字である'3'の個数が1個増えたことになります。 i++;によって、次の文字'.'を対象とします。 これは、数字ではありませんので、if文をスルーします。 i++;によって、次の文字'1'を対象とします。 ここの処理は、先ほどの'3'の場合と同じです。cnt[1]を1増やします。 以下同様です。

jussmen_1979
質問者

お礼

丁寧なご回答有難うございました。 おかげさまで理解することが出来ました。

その他の回答 (3)

回答No.4

数字の文字コードは連続していると仮定(念のため) > /* ループ1回目 */ > cnt[0] = str[0] - 0; /* 3 - 0 で cnt[0] には 3 が入る。 */ > cnt[0++]; str[0]は'3'で、str[i]-'0'='3'-'0'=3です。この値がcntの添え字になります。 なので、cnt[3]++;が実行されます。++の位置も違いますね。 > /* ループ2回目 */ > cnt[1] = str[1] - 1; /* "." - 1 となる。 数字ではないので減算できずエラーになるのでは? */ > cnt[1++]; その前のif文で'.'は排除されています。 > /* ループ3回目 */ > cnt[2] = str[2] - 2; /* 1 - 2 なので、なぜか負の数になってしまう。。 */ > cnt[2++]; str[2]-'0'ですよね。なんで-2になってるんですか? '2'-'0'=2なので、cnt[2]++;が実行されます。

jussmen_1979
質問者

お礼

ご回答有難うございました。おかげさまで理解が進みました。 ベストアンサーにすることができず恐縮ですが、皆様の 回答を読むことで理解度が高まり、感謝しております。

noname#208507
noname#208507
回答No.3

文字データは特定の数値で符号化されています. ひとまず,ASCIIコード表と照らし合わせるのが分かりやすいでしょう. 1) - '0' が何を行っているのか  '0'はASCIIコードで48(十六進数で0x30)です.  '3'は同じく51(十六進数で0x33)です.  よって '3'-'0' は 51-48 を意味し,結果は数値の 3 です. 2) ++ はどの値を増加  上の例('3'-'0')なら,'3'の度数(cnt[3])を増やします. /* ループ1回目 */ cnt[3]++; /* '3'-'0' = 51-48 = 3 より */ /* ループ2回目 */ /*  if(str[i] >= '0' && str[i] <= '9') は  if(str[i] >= 48 && str[i] <= 57) であり,  '.' == 46 なので 偽となる */ /* ループ3回目 */ cnt[1]++; /* '1'-'0' = 49-48 = 1 より */ 以下同様.

jussmen_1979
質問者

お礼

ご回答有難うございました。おかげさまで理解が進みました。 ベストアンサーにすることができず恐縮ですが、皆様の 回答を読むことで理解度が高まり、感謝しております。

  • f272
  • ベストアンサー率46% (8011/17123)
回答No.2

if(str[i] >= '0' && str[i] <= '9') cnt[str[i] - '0']++; // !!!! ココです !!!! これを if(str[i] >= '0' && str[i] <= '9'){ printf("%d回目,str[i]=%d,str[i]-'0'=%d,before:cnt[%d]=%d",i,str[i],str[i]-'0',str[i]-'0',cnt[str[i] - '0']); cnt[str[i] - '0']++; // !!!! ココです !!!! printf(",after:cnt[%d]=%d\n",str[i]-'0',cnt[str[i] - '0']); } これに書き直して実行ささせてみなさい。

jussmen_1979
質問者

お礼

ご回答有難うございました。おかげさまで理解が進みました。 ベストアンサーにすることができず恐縮ですが、皆様の 回答を読むことで理解度が高まり、感謝しております。

関連するQ&A

  • C言語 この記述のどこが間違っているのでしょうか?

    最大10文字の文字列入力を受け付け、 10回入力するか、「OK」が入力された時に 文字列を出力するプログラムを作ろうと思い、 その中で、1回目に『OK』が入力された時には、エラーメッセージを表示して終了するようにしたいのですが、 以下を入力したところ、うまく動作しませんでした。(見づらいのはご容赦ください) 制御文と配列を使って動作させるには、どう修正するべきでしょうか。 初心者ですが、ご教授お願い致します。 #include <stdio.h> #define MAX_INP 10 /* 入力最大回数 */ #define MAX_LGTH 5 /* 入力可能最大文字数 */ int main(void) { char str[MAX_INP][MAX_LGTH]; int cnt; /* 入力用カウンタ */ printf("\n"); for(cnt = 0 ; cnt < MAX_INP ; cnt++) /* 入力ループ */ { printf("Input String. -> "); /* 文字列入力メッセージ */ scanf("%s",&str[cnt][0]); /* 文字列入力 */ if(str[cnt][0]=='O' && str[cnt][1]=='K' && str[cnt][2]=='\0') { /* OKと入力された場合 */ break; /* ループから抜ける */ } if(cnt == 0) /*「OK」入力が1回目の場合 */ { printf("Error."); /* エラーメッセージ */ return 0; } } printf("\n"); for(cnt = 0 ; cnt < MAX_INPUT ; cnt++) /* 出力ループ */ { printf("%s\n",&str[cnt][0]); /* 文字列出力 */ } printf("\n"); return 0; } 【動作結果1】 Input string. -> abc Error. > 【動作結果2】 Input string. -> OK OK Uウ@ @ ■p 『 『 >

  • C言語で分からないところがあるのですが

    すみません。C言語のポインタで分からないことがあって来ました。 ポインタの理屈は理解してはいるのですが、いざソースコードを書いてみようということになると全く手がつけられずにいます。 以下のソースコードですが、strlen()と同じ働きをする関数mystrlen()と、strcmp()と同じ働きをする関数mystrcmpを、ポインタを使って作成するものです。どこをどうすればいいのか教えてくださいませんか。 #include <stdio.h> int main(void) { char str1[80], str2[80]; int i, j; int len1, len2; printf("第1の文字列を入力してください: "); gets(str1); printf("第2の文字列を入力してください: "); gets(str2); /* * 文字列の長さを確認する */ /* NULL文字(文字列の最後)まで読み飛ばす */ for (len1 = 0; len1 < 80 && str1[len1] != '\0'; len1++) ; /* ループ終了後、len1 に文字列の長さが入っている */ if (len1 < 80) { printf("%s は %d 文字の長さです\n", str1, len1); } else { printf ("第1の文字列が80字以上あります\n"); } /* str2 についても同様 */ for (len2 = 0; len2 < 80 && str2[len2] != '\0'; len2++) ; if (len2 < 80) { printf("%s は %d 文字の長さです\n", str2, len2); } else { printf ("第2の文字列が80字以上あります\n"); } if (len1 < 80 && len2 < 80) { for (i = 0; i < 80 && str1[i] != '\0' && str2[i] != '\0' && str1[i] == str2[i]; i++) ; if (str1[i] == str2[i]) { /* 両者同時に == '\0' のはず*/ printf("文字列は等しい\n"); } else if (str1[i] < str2[i]) { /* str1[i] == '\0' のはず*/ printf("%s は %s より小さい\n", str1, str2); } else { /* str2[i] == '\0' のはず*/ printf("%s は %s より大きい\n", str1, str2); } } /* * 十分なスペースがあれば、str2をstr1の最後に連結する */ if (len1 + len2 < 80) { /* str1 の末尾を探す */ for (i = 0; str1[i] != '\0'; i++) ; /* ループを抜けた段階では i は len1 と同じはずなので、 上記のループを作らず、i の代わりに len1 を用いるのも可 */ /* それ以降に str2 の中身をコピーする */ for (j = 0; str2[j] != '\0'; j++) { str1[i+j] = str2[j]; } str1[i+j] = '\0'; printf("%s\n", str1); } else { printf ("文字列をつなげた長さが80字以上あります\n"); } /* * str2をstr1にコピーする */ if (len1 + len2 < 80) { for (i = 0; str1[i] != '\0'; i++) { str1[i] = str2[i]; } str1[i] = '\0'; printf("%s %s\n", str1, str2); } return 0; }

  • C言語のプログラミングですが、

    C言語のプログラミングですが、 255文字以内の文字列をキーボードから入力して、下記の各処理を行うプログラム(※入力した文字データは配列に入力)はどのようにしたらいいでしょうか?(入力例:acFNkeexFFg) ・入力した文字列の最後の文字を出力 ・入力した文字列⇒逆に並べ替えて出力 ・入力した文字列⇒縦に出力(1文字ずつ) ・入力した文字列の中に「F」が何個あるか ただし、使えるのはfor文・配列・if文ぐらいでそれ以上レベルの高いものは使わないで下さい。 … char str[255]; int i,n,na,nb,nc; printf("Input Strings= "); scanf("%s",str); for(i=0;str[i]!='¥0';i++){ n=i; } printf("The last character= %c",str[n]); くらいまでしか分かりません…

  • C言語の問題で困っています。

    C言語の問題で困っています。 途中までできたのですが、この先が分かりません。 教えて頂くようお願いいたします。 【問題】 文字列の長さを求めるプログラムです。このプログラムを、入力した文字列の文字列長を求めるように変更してみましょう。  ただし、入力する文字列は半角で最大 20 文字までとし、指定された範囲外の値( 21 以上)が入力された場合は、正しい値が入力されるまで入力処理を繰り返すこと。 #include <stdio.h> int main(void) { char str[256] = "Hello"; int length, i; printf("文字列:"); scanf ("%s",str); length=0; i=0; while (str[i]!='\0') { i++; length++; } printf("\n文字列長:%d\n",length); }

  • C言語

    文字列を逆順にするプログラムを考えているのですが分かりません。(例)qwerならrewqです。入力終了は、EOFです。考えたのですが、分かりません。(コンパイルエラーです。)教えてください。宜しくお願いします。#include <stdio.h> unsigned str_length(const char str[]) { unsigned len=0; while (str[len]) len++; return (len); } void put_rstring(const char str[]) { unsigned i = str_length(str): while (i-- >0) putchar(str[i]); } int main(void) { char str[30]; int ch; printf("文字列を入力\n"); /* ----この文字列を入力したあとに、Ctrl+Zを押すと、逆から表示               で反対から、文字列が表示----*/ while (1) { ch=getchar(); if (ch==EOF) break; } printf("逆から表示"); put_rstring(str); puts("です。"); return(0); }

  • C言語、スキャン集合について

    下記のプログラムはスキャン集合を使って入力された文字列の先頭に 並んでいる数字を読み込んで捨て、その後に続く文字列を読み込んで 表示するものです。 #include <stdio.h> int main(void) { char str[80]; printf("数字に続けて文字列を入力してください\n"); scanf("%*[0-9]%s", str); printf("%s", str); return 0; } 【質問】%*[0-9] を %[^0-9] に置き換えてコンパイルして実行すると、 意味不明の文字列が出力されるのはなぜでしょうか? 置き換えても同じ結果になる気がするのですが…

  • C言語の問題が解けません!

    僕は、C言語を勉強しているのですがこの問題の答えがわかりません。 どうか教えてください。 回答お願いします。 ・問題・ 10文字以内の2つの英単語を入力し、アルファベット順で最初にくる単語を表示するプログラムを作りなさい。 /プログラム/ #include<stdio.h> #include<string.h> int main(void) { char str1[256],str2[256]; int i; printf("文字列1は?"); printf("文字列2は?"); return 0; }

  • C言語プログラミング 初心者

    現在2つの文字列を比較するstrcmp()関数と同じ動作をするプログラムを制作しています。 もちろん、strcmp()は一切用いません。 何とか作ってみたものの、文字列が等しいときなどにはうまく作動しません。 分かる方アドバイスお願いします。 以下のプログラムでは2つの50文字以下の文字列を入力することを想定しています。 #include<stdio.h> int main(void) { char str1[50],str2[50]; int a,b,i; printf("第1の文字列を入力してください: "); gets(str1); printf("第2の文字列を入力してください: "); gets(str2); /*文字列の長さを確認します*/ for(a = 0;str[a];a++) ; for(b = 0;str2[b];b++) ; printf("%s ha %d mozinonagasadesu\n",str1,a); printf("%s ha %d mozinonagasadesu\n",str2,b); /*文字列を比較します*/ for(i = 0;;i++){ if(str1[i] > str2[i]) printf("%s は %s より大きい\n"str1,str2); else if(str1[i] < str2[i]) printf("%s は %s より小さい\n"str1,str2); if(str1[i] > str2[i] || str1[i] <str2[i]) break; } for(i=0;str1[i] && str2[i];i++) if(str1[i] == str2[i] ) printf("文字列は等しい\n"); /*上のプログラミングだとstr1がaab、str2がaacのときも文字列が等しいと表示してしまうので、ぴったり等しいときのみ表示するにはどのように条件付けしたらよいでしょうか?*/ return 0; } ちなみにstr1[0]=a ,str1[1]=b, str2[0]=a, str[1]=b, str[2]=cが格納されていた場合、str1 < str2になると思うのですが正しいでしょうか? もし正しければこの場合str1[2]にはヌル文字が入っており、それがstr2[2]のcと比較されたためなのでしょうか? いろいろと質問してしまいましたが、分かる方解答お願いします。

  • C言語のプログラムのメモリリークに関して

    動的メモリ管理を用いて,キーボードからの入力を行単位で辞書順にソートして出力するプログラムで、メモリリークが有るため解消したいのですが、freeをどのように用いれば良いのかが分かりません。 動的メモリについての理解が不十分であるため、自分なりにfreeを用いてみるとプログラムが途中で止まってしまい、解消することができません。 以下のプログラムは、自分なりにfreeを用いた部分を除けば、ソート後の出力は正しく出力されます。(メモリリークは発生します。) アドバイス、間違いの指摘等していただければと思います。お願いします。 環境は、Windows XP Professional SP3 32bitでVisual C++ 2008 Expressを使用しています。 #include <stdio.h> #include <string.h> #include <crtdbg.h> #include <stdlib.h> #define MAXLINES 20000 // 最大行数 #define LINELENGTH 20 // 1行の最大文字数 /* 標準入力からの入力を,一行づつ動的に確保したメモリに格納し, そのアドレスを文字列へのポインタ配列に保存する. Ctrl+Zで入力を終了する. lines : 文字列へのポインタの配列 numMax : 最大の読み込み行数*/ int ReadLines(char *lines[], int numMax){ int cnt = 0; static char buf[LINELENGTH]; while (cnt < numMax && gets(buf)) { lines[cnt] = malloc(strlen(buf) + 1); if (lines[cnt] == NULL) { printf("Allocation error.\n"); break; } strcpy(lines[cnt], buf); cnt++; } return cnt; } /* ポインタ配列の指す文字列を標準出力に表示 lines : 文字列へのポインタの配列 num : 行数*/ void PrintLines(char *lines[], int num){ int i; for (i = 0;i < num;i++) printf("%s\n", lines[i]); } /* ポインタの配列を,辞書順にソート lines : 文字列へのポインタの配列 num : 行数*/ void Sort(char *lines[], int num){ int i,j; char temp[LINELENGTH]; for(i=0;i<num;i++){ for(j=num-1;j>i;j--){ if(strcmp(lines[j],lines[j-1])<0){ strcpy(temp,lines[j]); strcpy(lines[j],lines[j-1]); strcpy(lines[j-1],temp); } } } } int main(){ int cnt,i; static char *lines[MAXLINES]; cnt = ReadLines(lines, MAXLINES); printf("----- %d lines -----\n", cnt); Sort(lines, cnt); PrintLines(lines, cnt); /*自分なりにfreeを用いた部分*/ for(i=0;i<cnt;i++){ free(lines[i]); } //メモリリーク情報の表示 _CrtDumpMemoryLeaks(); return 0; }

  • C言語

    以下のC言語のプログラムを教えてください。 お願いします。 (1)標準入力から文字列(2 文字以上)を入力し,文字数を計上すると共に,入力された文字列の逆順に入れ替える処理を実現してください.なお,以下の要件を満たしたプログラムを作成してください. ・ 入力された文字列は,char 型の配列(要素数50)で受け取ること ・ 文字数を計上するcount 関数(引数:配列のアドレス,戻り値:文字数)を定義 し,main 関数より呼び出すこと ・ 文字列を逆順に入れ替えるreverse 関数(引数:配列のアドレス,戻り値:無し) を定義し,main 関数より呼び出すこと ・ 標準出力の処理は,main 関数で記述すること 【プロトタイプ宣言】 int count(char *str); void reverse(char *str); 【実行結果】 文字列を入力してください(2 文字以上) apple 文字数 = 5 入れ換え前 apple 入れ換え後 elppa (2)char 型の配列(要素数50)を2 つ宣言し,標準入力から2 つの文字列を入力してください.そして,格納した字列を入れ替える関数(swapstr 関数)を作成し,入れ替え前と入れ替え後の配列内の値(文字列)を配列名とともに標準出力するプログラムを作成してください. 【プロトタイプ宣言】 void swapstr(char *str1, char *str2); 【実行結果】 2 つの文字列を入力してください apple strawberry 入れ換え前 配列str1 = apple 配列str2 = strawberry 入れ換え後 配列str1 = strawberry 配列str2 = apple

専門家に質問してみよう