ひぐぺん工房トップへ おかげさまで28周年!

ひぐぺん工房(松浦健一郎・司ゆき) - HigPen Works
Follow @higpenworks
・今までの仕事 ・書籍 ・最近の業務 ・対応可能言語 ・お見積

『C言語[完全]入門』
Q&A

以下の回答で問題が解決しなかった場合には、 こちらから ご連絡ください。

補足


2024/01/15
Q.増刷や改訂の予定を知りたい
A.
増刷や改訂に関しては、出版社が方針を決定し、著者の私共は出版社から直前に連絡を受ける…という状況です。
※お問い合わせへの回答メールが戻ってきてしまうので、こちらで回答させていただきました。

2024/01/15
Q.未研磨・未改装の本を入手したい
A.
未研磨・未改装の本の入手方法については、申し訳ありません、私共には心当たりがありません(研磨・改装について、あまり意識したことがありませんでした)。
※お問い合わせへの回答メールが戻ってきてしまうので、こちらで回答させていただきました。

2024/02/03
Q.「n<(w-4)/2*(h-3)/2」と「m[(w-4)+(h-3)*w]='G'」の意味(Chapter19, p.662)
A.
dig.cに記述された、以下の式について補足します。
(1) n<(w-4)/2*(h-3)/2
(2) m[(w-4)+(h-3)*w]='G'

wは迷路の幅、hは迷路の高さです。p.658のように、迷路の周囲には文字+、各行の末尾には改行文字∖n、配列の末尾にはヌル文字∖0を配置してあります。例えば次のような迷路では、wは6、hは5です。説明のために、非常に小さな迷路の例にしました。以下で□は格子点を表します。
+++++∖n
+###+∖n
+#□#+∖n
+###+∖n
+++++∖n∖0

式(1)(2)に含まれる(w-4)は、wから「+を2個、#を1個、∖nを1個、で合計4個」を取り除いた文字数を計算しています。上記の例では、(w-4)は2です。これを2で割って「(w-4)/2」を計算すると1となり、幅方向の格子点の個数(上記の例では1個)が求まります。

式(1)(2)に含まれる(h-3)は、hから「+を2個、#を1個、で合計3個」を取り除いた文字数を計算しています。上記の例では、(h-3)は2です。これを2で割って「(h-3)/2」を計算すると1となり、高さ方向の格子点の個数(上記の例では1個)が求まります。

幅方向の格子点の個数と、高さ方向の格子点の個数を乗算すれば、全ての格子点の個数が求まります。したがって式(1)の「(w-4)/2*(h-3)/2」は、全格子点の個数を表します。式(1)の「n<(w-4)/2*(h-3)/2」は、処理済みの格子点の個数(n)が、全格子点の数に満たない限り、処理を続けるための条件式です。

式(2)の「(w-4)+(h-3)*w」は、右下端の格子点(ゴールの位置)に相当する、配列の添字を計算しています。右下端の格子点は、左から(w-4)桁目、上から(h-3)行目の位置にあります(いずれも0から数えます)。配列には1行あたりw個の文字があるので、「(h-3)*w」で(h-3)行目の左端の添字が計算できます。これに(w-4)を加算して「(w-4)+(h-3)*w」とすれば、右下端の格子点(ゴールの位置)の添字になります。

以下はwが8、hが7の例です。上記の方法で式(1)(2)を計算し、格子点の個数やゴールの位置が正しく求まることを確認してみてください。
+++++++∖n
+#####+∖n
+#□#□#+∖n
+#####+∖n
+#□#□#+∖n
+#####+∖n
+++++++∖n∖0

2024/04/09
Q.「path」か「*path」か(Chapter17, p.600)
A.
ext.cの「printf("%s/%s\n", path, entry->d_name)」における「path」は、「*path」ではなく、掲載されている「path」が正しいです。このプログラムの「path」と「*path」は、次のような意味です。
printf関数において、変換指定の%sに対する引数は文字列(文字を指すアドレス)です。そのため*path(文字コード)ではなく、path(文字を指すアドレス)を渡しています。

上記を理解するには、文字列・配列・アドレス・ポインタといった知識を連携させる必要があるので、少し難易度が高いです。本書における関連項目を以下に挙げますので、ご参照いただけましたら幸いです。

2024/05/09
Q.find_files関数の動作(Chapter17, p.602-604)
A.
find_files関数の動作について補足します。例えば、CSample/chapter17において「ext2 ..」を実行すると、find_files関数は以下のように動作します。説明のために、このfind_files関数の呼び出しを「0段目のfind_files関数」と呼びます。
  1. 「DIR* dir=opendir(path);」において、pathは".."なので、CSampleフォルダ以下にあるファイルとディレクトリの一覧を取得します。
  2. 取得する項目は、例えば以下の通りです(途中までを掲載しました)。これらの項目は「ext ..」(p.601)で確認できます。
    .
    ..
    chapter10
    chapter11
    chapter12

  3. 「while ((entry=readdir(dir))!=NULL) {」において、0番目の項目「.」を取得します。
  4. 「if (strcmp(".", entry->d_name)!=0 && strcmp("..", entry->d_name)!=0) {」は、entry->d_nameが「.」なので、不成立になります。
  5. 「while ((entry=readdir(dir))!=NULL) {」において、1番目の項目「..」を取得します。
  6. 「if (strcmp(".", entry->d_name)!=0 && strcmp("..", entry->d_name)!=0) {」は、entry->d_nameが「..」なので、不成立になります。
  7. 「while ((entry=readdir(dir))!=NULL) {」において、2番目の項目「chapter10」を取得します。
  8. 「if (strcmp(".", entry->d_name)!=0 && strcmp("..", entry->d_name)!=0) {」は、entry->d_nameが「chapter10」なので、成立します。
  9. 「sprintf(s, "%s/%s", path, entry->d_name);」において、sは「../chapter10」になります。
  10. 「find_files(s);」において、「../chapter10」を引数にして、find_filesを再帰呼び出しします。
再帰呼び出しされたfind_files関数は、以下のように動作します。このfind_files関数の呼び出しを「1段目のfind_files関数」と呼びます。
  1. 「DIR* dir=opendir(path);」において、pathは「../chapter10」なので、CSample/chapter10フォルダ以下にあるファイルとディレクトリの一覧を取得します。
  2. 取得する項目は、例えば以下の通りです(途中までを掲載しました)。これらの項目は「ext ../chapter10」で確認できます。
    .
    ..
    array.c
    array2.c
    array3.c

  3. 「while ((entry=readdir(dir))!=NULL) {」において、0番目の項目「.」を取得します。
  4. 「if (strcmp(".", entry->d_name)!=0 && strcmp("..", entry->d_name)!=0) {」は、entry->d_nameが「.」なので、不成立になります。
  5. 「while ((entry=readdir(dir))!=NULL) {」において、1番目の項目「..」を取得します。
  6. 「if (strcmp(".", entry->d_name)!=0 && strcmp("..", entry->d_name)!=0) {」は、entry->d_nameが「..」なので、不成立になります。
  7. 「while ((entry=readdir(dir))!=NULL) {」において、2番目の項目「array.c」を取得します。
  8. 「if (strcmp(".", entry->d_name)!=0 && strcmp("..", entry->d_name)!=0) {」は、entry->d_nameが「array.c」なので、成立します。
  9. 「sprintf(s, "%s/%s", path, entry->d_name);」において、sは「../chapter10/array.c」になります。
  10. 「find_files(s);」において、「../chapter10/array.c」を引数にして、find_filesを再帰呼び出しします。
さらに再帰呼び出しされたfind_files関数は、以下のように動作します。このfind_files関数の呼び出しを「2段目のfind_files関数」と呼びます。
  1. 「DIR* dir=opendir(path);」において、pathは「../chapter10/array.c」なので、ディレクトリとしては開けません。
  2. 「if (dir) {」は不成立になります。
  3. 「else {」以下の「puts(path)」において、「../chapter10/array.c」を出力します。
ここで「1段目のfind_files関数」に戻り、CSample/chapter10フォルダ以下にあるファイルとディレクトリの一覧について、処理を続けます。全て処理したら、「0段目のfind_files関数」に戻ります。

「0段目のfind_files関数」に戻ったら、CSample以下にあるファイルとディレクトリの一覧について、処理を続けます。chapter11などのフォルダについては、chapter10と同様に、再帰呼び出しを行います。

全て処理したら、main関数に戻ります。通常の関数呼び出しと同様に、戻り先は関数を呼び出した箇所です。このプログラムの場合は「find_files(argv[1]);」という箇所に戻ります。これでmain関数も終わりです。

chapter10やchapter11などのディレクトリに関しては、再帰呼び出しが一時的に深くなりますが、サブディレクトリを処理したら、最後は「0段目のfind_files関数」に戻ってきます。無制限に再帰呼び出しが深くなってしまうということは無いので、ご安心ください。

各段階のfind_files関数は、スタック上に別々の記憶領域を持っていることに注意してください。例えば、「0段目のfind_files関数」の処理中に「1段目のfind_files関数」を呼び出しますが、この間に「0段目のfind_files関数」の状態は保存されています。したがって「0段目のfind_files関数」に戻った後に、ファイルとディレクトリの一覧を、まだ処理していなかった箇所から引き続き処理できます。

p.604における「../chapter10/array」などの表示は、前述のように「else {」以下の「puts(path)」で行います。なお、p.604に掲載した「../chapter10/array」は、ディレクトリではなく、macOS/Linuxにおける実行ファイルです。

2024/07/18
Q.「*p++」の解釈(Chapter13, p.480)
A.
array5.cの「while (*p) printf("%d ", *p++);」について、「*p++」は「(*p)++」ではなく、「*(p++)」と解釈されます。「*(p++)」の動作は次の通りです。後置インクリメントでは、加算が後で行われる(加算の結果が後で反映される)ことがポイントです。

(1) 例えば、pに1000番地が入っているとします。
(2) 「p++」を処理します。++は後置インクリメントなので、「p++」は加算前の1000番地を返し、実際にpに1を加算するのは後で行います。
(3) 「*(p++)」を処理します。(2)より「p++」は1000番地なので、「*(p++)」は1000番地の値(int型)を取り出します。
(4) pに1を加算します。pはint*型なので、実際にはsizeof(int)、本書の環境では4が加算され、pは1004番地になります。

もし「(*p)++」と解釈すると、「p(int*型)に1を加算する」のではなく、「pが指している値(int型)に1を加算する」という動作になります。これはarray5.cとは異なる動作です。

通常は、優先順位が高い演算子を先に計算しますが、後置インクリメントの場合は加算を後で行う(加算の結果を後で反映する)ため、少し難解かもしれません。「*p++」の場合、優先順位は*よりも++の方が高いため「*(p++)」と解釈しますが、*は加算前のpの値を使い、++によるpの加算は後で行います。

最終更新 2024/07/18
トップページへ
©ひぐぺん工房 禁無断転載
最新刊『Java[完全]入門』 
このサイトはリンクフリーです。
このサイトはChromeで動作検証しています。ブラウザにかかわらず表示に乱れがありましたらどうぞお知らせ下さい。メールを送る