見出し画像

C言語教室 番外編2 - 第5回の課題について

さて、C言語教室 第5回 - 文字と文字列の課題について答え合わせをしておこうと思います。

今回もAkio van der Meerさんから答案を頂いてしまいました。この記事の中でも、お褒めの言葉を頂いてしまったのですが、とても嬉しくはあるものの、何だかこそばゆい感じです。

(答案提出)C言語教室 第5回 - 文字と文字列

答案に関しては、先に私の書いたコードを説明してから、コメントさせていただきます。


今回の課題は2つありました。最初の課題は

文字列”abc”の長さを数えて表示する(長さにヌル文字は含めない)。

https://note.com/kazushinakamura/n/nbb7e6f1138d7

です。言葉足らずだったのですが、関数にしなくてmainで書いてしまっても構わないつもりでした。もちろん関数にしても構いません。

素直に配列で書けばこうなります。

#include <stdio.h>

void main() {
  char s[] = "abc";
  int i;

  for ( i = 0; s[i] != '\0'; i++ )
    ;

  printf("string:%s\n", s);
  printf("length=%d\n", i);
}

実行してみましょう。ちゃんと長さが出力されますね。

string:abc
length=3

これでも良いのですが、ポインタを使ってC言語らしくしてみましょう。

#include <stdio.h>

void main() {
  char s[] = "abc";
  int i;
  char *p = s;

  for ( i = 0; *p++ != '\0'; i++)
    ;

  printf("string:%s\n", s);
  printf("length=%d\n", i);
}

ポインタも慣れてきましたか?

string:abc
length=3

whileを使う形でも良いですね。

#include <stdio.h>

void main() {
  char s[] = "abc";
  int i = 0;
  char *p = s;

  while ( *p++ != '\0' )
    i++;

  printf("string:%s\n", s);
  printf("length=%d\n", i);
}

結果は同じになります。

string:abc
length=3

次の課題です。

文字列”abc”の中で’c’が何番目にあるか探して表示する(最初の文字であれば1)。

https://note.com/kazushinakamura/n/nbb7e6f1138d7
#include <stdio.h>

void main() {
  char s[] = "abc";
  int i = 0;
  char *p = s;
  char *q = s;

  for ( q = s; *q != '\0'; q++) {
    if ( *q == 'c' ) {
      i = q - p + 1;
      break;
    }
  }

  printf("string:%s\n", s);
  printf("'c'position=%d\n", i);
}

さて'c'は何文字目になるでしょうか。検索対象文字列であるs[]をポインタであるpにわざわざ代入しているのには訳があって、これは次回の教室で説明します。

string:abc
'c'position=3

もし文字列の中に’c’が見つからなければ0を返すようにしておきました。’c’が2つ以上あった場合は、最初の’c’の位置を返します(breakを書かなければ最後の’c’の位置になりますね)。こういう時の規則は自分で決めて構いませんが、元の文字列の終わりを超えて’c’を探し続けないように探す範囲をちゃんとおさえることを忘れないでくださいね(目的の文字が文字列の最初なら0、無ければ-1を返すようにしたほうが少しだけシンプルに書けたかも)。


さて答案にコメントさせていただきますが、C言語の世界では文字列の終端文字である'\0'は必ずあることを仮定して構いません。いろいろ調べたのですが、ライブラリで使われているような関数も'\0'を見つけるまでは、どこまでもメモリの中を探し続けるようになっています。

ですから文字列の長さを返す関数len()は、配列を使ってはいますが基本的には大丈夫だと思います。C言語ではポインタを使った形で書くことが多いので、ポインタにも慣れていただければと思います。

for (i = 0; x[i] != 0; i++) {}

ところで、終端文字を探す時に比較する相手を数字の0としているのですが、ここで型変換が発生しています。0であるとかNULLと比較する時に問題になることは無く、このような書き方をしている例も多いのですが、できればchar型である'\0'と比較して型変換がない書き方をしたほうがベターでしょう。


次の課題ですが、答案にあるコードを少しだけ変えたものを使わせていただきます。

#include <stdio.h>

int srcCh(char* x , char* y, int z) {
  int i, j = -1;

  for (i = 0; i < z; i++) {

// 文字列の最終桁に達したら(検索対象文字が見つからなかったら、0を返す)  
    if  (x[i] == 0) {
      j = 0;
      break;
    }

// 検索対象文字が見つかったら、配列要素番号+1を返す)  
   if (x[i] == y[0]) {
        j = i + 1;
      break;
    }
  }
  return j;
}

void main() {
  int ln, cp;
  char c[] = "abc";       //検索対象文字列
  char d[] = "d";         //検索文字

  ln = 3;                 //検索文字列の長さ 
  cp = srcCh(c, d, ln);   //検索文字の出現桁数

  printf("\"%s\" 、、、文字列の長さは%d\n", c, ln);
  printf("\"%s\" 、、、\'%s\'は、%d文字目\n", c, d, cp);
}

これを実行すると以下の結果が得られます。

"abc" 、、、文字列の長さは3
"abc" 、、、'd'は、-1文字目

おそらく「あっ」とは思われたと思いますが、そもそも検索対象文字列の最後には終端文字('\0')が付いていることは仮定して良いので、長さを渡す必要はありません。長さを渡して、その範囲の検索をしているため正しい長さを渡せば終端文字のひとつ前までしか調べないので、文字が見つからなかった、すなわち0を返すコードが成立することはありません。元のコードでは戻り値である変数jが初期化されていないので、文字が見つからなかった場合に戻る値は不定ということになってしまいます。初期化されていない変数を代入せずに使ってしまうのは絶対に避けなければなりません。forループを抜けてくる条件は3つあることを確認して、すべての場合にjの値が適切な値になっているかを見てください。こういう時は宣言で初期化してしまえば構わないといえば構わないのですが、一見、正しく動作しているように見えてしまうことがあるので、確認を怠ってはいけません。

もうひとつ、検索文字であるdが文字列として表現されていますが、コードでは先頭の文字だけを検索するようになっています。ここが文字列であると、対象文字列から検索文字「列」を探すように誤解されるので、1文字を探すのであれば、char型を使ってください。もし文字列を使うのであれば、少しだけコードが面倒になりますが、文字「列」が含まれているか調べるようにしてください。


実は標準ライブラリであるstrlen()を使う解答を頂いても、良く調べましたということで、それでも構わないとは思っています。ただ、こういう処理は少しアレンジして書く必要が出ることも多く、結局、自分で書く羽目になることも度々です。例えば何らかのバッファなどで、押さえてある一定の大きさの範囲で処理を行いたい場合には、終端文字を大きさを超えて探しに行かないようにしたくなります。こういう時には出来合いの関数は使えないこともあります(memXXX系の関数が使えることもありますけど)。

なお、strlen()を調べていてなかなか勉強になったので、これについては後日あらためて書いてみます。


私も思い出しながらの部分も多いので、問題や改善点などがあればぜひコメントを頂ければ幸いです。それらを反映し必要に応じて改訂したいと思っています。皆さんのご意見で直していけるのがnoteの良さだと思っているので、何卒よろしくお願いします。


ヘッダ画像は、以下のところのものを使わせていただきました。
https://photosku.com/photo/2615/








この記事が気に入ったらサポートをしてみませんか?