Dokusyo-nissi Bessitu 2004-12-02 φ(-_-) ■ [lang]プログラミング言語 C ・関数の中で宣言された変数はその関数に固有のものであり、局所的な変数である 。それは他の関数からは直接アクセスできない。 ・ (関数の)ルーティン内の局所変数は関数が呼び出された間だけ存在し、その関数 から制御が離れると - 抜け出ると - 消えてしまう。これを自動変数と呼ぶ。 ・自動変数は関数の呼び出しによって現われたり消えたりするので、一度呼び出さ れてから再度呼び出されるまでの間、変数の値は保持されない。 ・そのためそれぞれの関数の始めで (変数の)値をセットしておく必要がある。 ・そうしないと変数の値には不定値 - 予期しないデータ - が入ってしまうことに なるからである。 ・自動変数の代わりに、すべての関数に対して外部的な - 参照可能な - 変数を宣 言することもできる。それは他の関数の中でもその変数名を指定することによって 直接アクセスができる広域的な変数である。 ・この外部変数は関数間のデータの受渡しを目的とした引数リストの代わりとして 用いられる。 ・関数の呼び出しから(次の)呼び出しまでの間もこの変数はセットされた値を保持 している。 ・外部変数は関数の外側で (そのタイプとともに)定義しなければならない。このと き、実際の記憶の割り当ても行われる。 ・また外部変数はそれを使用するそれぞれの関数内でも宣言する必要がある。この とき、明示的に (変数には)予約語 extern を付けて宣言を行う。 ・ただし、(外部変数の)定義が使用される関数以前にソースファイルに記述されて いるのであれば、extern は省略することができる。 ・実際、通常はソースファイルの最初のところで全部の外部変数を定義しておいて extern は省略している(ことが多い)。 ・プログラムをいくつかのファイルに分割し、特定のファイルで (すべての)外部変 数を定義した場合には、(他のファイルで参照するためには) extern 宣言が必要に なる。 ・このように変数や関数の extern 宣言をまとめたファイルをヘッダと呼び、(呼び 出す側の)ファイルのコードの最初に #include "ヘッダファイル名" と記述するこ とによって取り込むことができる。 ・このヘッダファイルのファイル名には .h という接尾子を付けておく規則になっ ている。 ・ (また関数自体を外部変数として扱うときには)それが引数のない関数であっても 、引数リストには予約語 void を明示的に書き入れておくようにする。 ・ (なぜなら C の)標準規格では空のリストの場合、引数リストのチェックをまっ たく行わないからである。 ・ (これまでの説明で)外部変数の参照について宣言と定義を使い分けていたことに 注意してほしい。 ・定義が、変数が実際につくられるかまたは記憶の割り当てをされるかという指示 を与えるのに対して、宣言では、その(変数の)タイプが指定されるだけであって、 割り当てはまだされていない状態をさしているからである。(p38-41) 2004-12-07 φ(-_-) ■ [lang]プログラミング言語 C ・特定の文字列(パターン)を含む行(ライン)を捜し出してプリントするプログラム を組み立ててみよう。 ・このプログラムの基本的な構造は次の3つの部分に分けることができる。 1. while (ラインが存在している) 2. if (パターンを含んでいる) 3. (プリントする) ・これらすべてを関数 main() の中で記述することもできるが、各々の部分をそれ ぞれ別の関数にすることによってうまく(プログラムを)組み立てるのがよりよい方 法である。 ・関数をじょうずに使えば、他の部分に関係しない操作の評価を隠すことができる し、また全体を明確化してプログラムの変更を容易に行うことができる。 ・それにこうして工夫した関数はそれ自体、他のプログラムへ転用することも可能 である。 ・ (ラインが存在している)には以前使った関数 get_line() が使用できる。*1 ま た(プリントする)では関数 printf() がすでに用意されている。 ・したがって残っているのは、そのラインがある特定のパターンを含んでいるかど うかを捜し出すルーティンをつくることである。 ・それは文字列 str の中で文字列 target の始まる位置、つまりインデックスを返 し、str が target を含まないときには -1 を返す関数 str_index() を用意するこ とによって解決できる。 ・ C の配列は 0 から始まるのでこのインデックスは 0 または正の整数である。し たがって -1 のような負の(返り)値は (target が)見つからなかったことを表すの に適している。 ・また後で複雑なパターン照合が必要になったときには、この関数 str_index() だ けを書き換えればよいことになる。(p82-84) それぞれの関数を別々にコンパイルして、後で pattern という実行ファイルを作成して みる。 まずヘッダーファイル、 1. /* file : header1.h */ 2. #define MAX_LINE 1000 3. int get_line(char line[], int max); 4. int str_index(char source, char search_for); 5. extern char pattern[] = "char"; 6. /* ↑ target の文字列を "char" にしてみた */ 次に関数 get_line() と str_index()、 1. /* file : part1.c */ 2. #include 3. /* 4. * 今回は while文を使って 5. */ 6. int_get_line(char str[], int lim) 7. { 8. int c, i; 9. i = 0; 10. while (--lim > 0 && (c = getchar()) != EOF && c != '\n') { 11. str[i++] = c; 12. } 13. if (c == '\n') { 14. str[i++] = c; 15. } 16. str[i] = '\0'; 17. return i; 18. } 1. /* file : part2.c */ 2. #include 3. /* 4. * 各ラインでパターンとマッチしたら 5. * そのインデックスを返す 6. * 見つからないときは -1 を返す 7. */ 8. int str_index(char str, char target) 9. { 10. int i, k, j; 11. for (i = 0; str[i] != '\0'; i++) { 12. for (j = i, k = 0; target[k] != '\0' && str[j] == target[k]; j++, k++) { 13. ; 14. } 15. */ ↑パターンにマッチしないラインは飛ばす */ 16. if (k > 0 && target[k] == '\0') { 17. return i; 18. } 19. } 20. return -1; 21. } 最後は関数 main()、 1. /* file : part3.c */ 2. #include 3. #include "header1.h" 4. /* 5. * 順次各ラインを照合していって 6. * 関数 str_index() の返り値が -1 でなければ 7. * そのパターンにマッチしたラインをプリントする 8. */ 9. main() 10. { 11. char line[MAX_LINE]; 12. extern char pattern[]; 13. int found; 14. found = 0; 15. while (get_line(line, MAX_LINE) > 0) { 16. if (str_index(line, pattern) >= 0) { 17. printf("%s", line); 18. found++; 19. } 20. } 21. return found; 22. } 3つのファイルをそれぞれコンパイルして、次に実行ファイル pattern を作成。 $cc -c part1.c $cc -c part2.c $cc -c part3.c $ls header1.h part1.o part2.o part3.o . . . $cc -o pattern part1.o part2.o part3.o $./pattern < part1.c として確認。 (注意) $cc -c part3.c のとき、警告(warning)の表示がでてもオブジェクトファイルは作成さ れますので . . . *1:←ちょっと改造するけど 2004-12-19 φ(-_-) ■ [lang]プログラミング言語 C ・三項演算子では式1 ? 式2 : 式3 のうち式1 が最初に評価される。 ・もしそれが 0 ではない - 真 - なら式2 が評価されてこの条件式の値となる。0 であれば式3 が評価されてその値となる。 ・ (つまり) 評価されるのは式2 と式3 のどちらか一方のみである。 ・例えば a と b どちらか大きなほうの値を z にセットするには、 z = (a > b) ? a : b; という式になる。 ・もし式2 と式3 とが異なる型であれば、結果の値の型は (他の条件式と同様) 「 型の変換規則」にしたがって決定される。 ・ f が float型で n が int型の場合、式 (n > 0) ? f : n; では n はその正負にかかわりなく float型に変換される。 ・上の式1 は (実際には) カッコを必要としない。しかし (プログラムを記述する 際は) 付けたほうがわかりやすくなる。 ・三項演算子によってより簡潔なプログラムを書くことができる。 ・例えば、次のループ文では配列の n個の要素を 1行に 10個ずつ空白で区切って並 べ、各行は (最後の行を含めて) 改行文字で終了してプリントする。 for (i = 0; i < n; i++) { printf("%6d%c", a[i], (i%10 == 9 || i == n - 1) ? '\n' : ' '); } ・適切な例をもう一つ。(複数個のときは item の後ろに 's' が付き、単数のとき には付かない↓) printf("You have %d item%s\n", n, n == 1 ? "" : "s"); [演習] 大文字を小文字に変換する関数 lower を if/else文と三項演算子とでそれ ぞれ作成せよ。(p63-64) 最初は if/else文、 1. /* lower.c */ 2. #include 3. int lower(int); 4. main() 5. { 6. int c; 7. char a = 'A'; 8. c = a; 9. printf("%c\n", lower(c)); 10. return 0; 11. } 12. int lower(int c) 13. { 14. if (c >= 'A' && c <= 'Z') 15. return c + 'a' - 'A'; 16. else 17. return c; 18. } 次は三項演算子、 1. /* lower_a.c */ 2. #include 3. int lower(int); 4. main() 5. { 6. int c; 7. char a = 'A'; 8. c = a; 9. printf("%c\n", lower(c)); 10. return 0; 11. } 12. int lower(int c) 13. { 14. c = (c >= 'A' && c <= 'Z') ? c = 'a' - 'A' : c; 15. } (追記) コードのまちがいをこっそり訂正。 (さらに追記) 「ブランク」というのがなんかおかしいので「空白」に変えてみる。 2004-12-22 φ(-_-) ■ [lang]プログラミング言語 C ・次の関数は整数配列を整列させるためのシェルソートである。 ・ 1959年に D.L.Shell によって考えられたこのソートアルゴリズムの基本概念は 初期の段階で、単純な交換配列のようなとなりあった要素間の比較を行うのではな く、まず遠くの離れた要素間の比較を行う ことにある。 ・これは大量の (データの) 無秩序をすばやく整列させるという利点をもち、した がって (ソートの) 後半ではあまり多量の仕事を必要としない。 ・ (なぜなら) 比較する要素間の間隔がしだいに小さくなり、それが 1 になるとこ の整列法は事実上、(単純な) 隣接交換法となる (からである)。 1. void shell_sort(int v[], int n) 2. } 3. int gap, i, j, tmp; 4. for (gap = n/2; gap > 0; gap /= 2) { 5. for (i = gap; i < n; i++) { 6. for (j = i - gap; j >= 0 && v[j] > v[j+gap]; j -= gap) { 7. tmp = v[j]; 8. v[j] = v[j+gap]; 9. v[j + gap] = tmp; 10. } 11. } 12. } 13. } ・ここには 3つの入れ子になったループがある。 ・もっとも外側のループでは比較される要素間の gap をその値が 0 になるまで n/ 2 から 2 で割りながら小さくしていくよう制御している。 ・真ん中のループでは要素にそって進行している。 ・もっとも内側のループでは gap分だけ離れた 2つずつの要素を比較して整列して いない要素を (tmp を使って) 入れかえている。 ・ gap は (その間隔を) 1 になるまで減らしていくので最終的には正しい整列にな る。 ・ for文の一般性(?)により、もっとも外側のループでは順序だてて進行していない にもかかわらず、他のループと同一の形式をとっていることに注意してほしい。 ・ C の演算子の 1つにコンマがある。この演算子は for文の中でもっともよく使わ れる。 ・コンマで分けられた 2つの式は左から右へと計算され、右側の式の型と値が (そ のままこの 2つの式全体の) 型と値になる。 ・このように多くの式を for文中の各部分に記述することができ、インデックス中 の並列処理が可能になる。 ・ (当然のことだが) 関数引数や宣言中の変数を分離するときのコンマは演算子で はないので、左から右へ順に評価されるということは (C では) 保証されていない 。 ・このコンマ演算子は (できれば) ひかえめに使うべきである。そのもっとも適切 な使用法としては相互に強く関連づけられた構文や多段階にわたる計算を 1つの式 に含めなければならない場合のマクロがある。 ・例として文字列 s をその位置で逆順にする関数 reverse() を示してみよう。 1. include 2. void reverse(char s[]) 3. { 4. int c, i, j; 5. for (i = 0, j = strlen(s) - 1; i < j; i++, j--) { 6. c = s[i]; 7. s[i] = s[j]; 8. s[j] = c; 9. } 10. } 11. /* strlen() は文字列の長さを数える関数だった . . .よね ? */ ・関数 reverse() の場合、要素の変換は 1つの (まとまった) 操作と考えることが できるのでコンマ演算子を上のように使うのは適切だと思われる。(p75-76)