情報処理Ⅱ 第12回 2005年01月18日(火) 本日学ぶこと 前処理指令 関数プロトタイプ ライブラリ関数の活用 関数は すでにあるものを使うか? 関数として定義するか? 関数形式マクロとして定義するか? 2 前処理とコンパイル(1) ソースファイル (前処理前) 前処理 ソースファイル (前処理後) コンパイル アセンブル オブジェクト ファイル リンク 実行ファイル 前処理・コンパイル・アセンブル・リンクの各処理は通常,コ ンパイラ(ccなど)が一手に引き受ける. 3 前処理とコンパイル(2) 前処理は, 狭義には,「コンパイルに先立って行われる処理」であり,した がってコンパイルとは別 広義には,ccでコンパイルすれば自動的に処理してくれる,と いう意味でコンパイル作業の一部 前処理のコマンド(プリプロセッサ)は,cpp Cの前処理以外にも使用可能 4 前処理指令 (Preprocessing directive) マクロ定義(#define) 「プリプロセッサ 指令」ともいう オブジェクト形式マクロ ⇒「定数」の定義 関数形式マクロ ⇒「関数もどき」の定義 ソースファイルの取り込み(#include) 条件付きコンパイル(#if ... #endif など) 5 オブジェクト形式マクロ(1) 語の置き換えを行う. #define 置換対象 置換内容 #define WORD_SIZE 6 と記述すると,それ以降 int a[WORD_SIZE]; は int a[6]; と同じ意味になる. プログラム修正により変わり得る定数値があるときに,よく用 列挙型のほうがいいかも いられる. 配列の上限値,他と区別する値など. うまく使うことで,定数値を変えるときのプログラム修正箇所を 少なくできる. 6 オブジェクト形式マクロ(2) 注意点 前提: #define WORD_SIZE 6+1 単純に置き換える. • int a[WORD_SIZE * 2]; は,int a[6+1 * 2]; に置き換えられる(意図した動作ではない). ⇒ #define WORD_SIZE (6+1) とすればよい. 語のみを置き換える. • print_WORD_SIZE( ) といった「語の一部」や, printf("WORD_SIZE"); といった文字列定数は,置き 換えない. 7 オブジェクト形式マクロ(3) 注意点(続き) 予約語も置換可能. • #define char signed char は文法上問題ないが, よい書き方ではない.現在では,typedef signed char schar; とすべきである. 同一内容であれば,同じ名前のマクロを複数定義してもよい. (異なっていればコンパイルエラー.) 置換内容のない名前も定義できる. • #define DEBUG 末尾にセミコロンをつけない. • #define WORD_SIZE 6; は(たいていの場合)間違い. 8 関数形式マクロ(1) オブジェクト形式マクロとほぼ同じ書式. 置換対象に「(…)」をつける. #define pint(x) printf("%d\n",x) に対して, pint(a+1); は printf("%d\n", a+1); に置き換え られる. 複数の引数をとることもできる.そのときは,置換対象の各引 数の間にカンマを入れる. カッコ内に何も書かなければ,引数なしの関数形式マクロが定 義される. 9 関数形式マクロ(2) 注意点 単純に置き換える. • #define mul(x, y) x*y に対して, z=mul(6+1,2); としたとき,z=14ではなくz=8となる. ⇒ #define mul(x, y) ((x)*(y)) のように,置換 内容の引数と,置換内容全体にカッコをつける. 置換対象に引数を2箇所以上書くことができる.このとき,その 回数だけ置換される. • #define triple(x) ((x)+(x)+(x))に対して b=triple(++a); と書くと, b=((++a)+(++a)+(++a)); となる. 10 関数形式マクロ(3) 置換内容の中で「#引数」と書くと,引数を文字列にできる. #define pint(x) printf(#x " = %d\n", x)に対し てpint(a+1);はprintf("a+1" " = %d\n", a+1); に置き換えられる. 通常の関数定義では,変数名を 「文字列リテラルの連結」に 引数にとってその文字列を得る より,これは ことはできない. printf("a+1 = %d\n", a+1); と同じとなる. 11 関数か関数形式マクロか 関数…「機能」を正確に表現したいとき 例:int square_int(int x) { return x * x; } 引数や戻り値の型に制約される. 関数呼び出しのオーバーヘッドがある. ローカル変数や制御文を活用できる. 実引数が++aなどのときも,その評価は一度だけ. マクロ…「機能」を簡便に表現したいとき 例:#define square_int(x) ((x) * (x)) 引数や評価式に型はない. (狭義の)コンパイル前に展開され,オーバーヘッドは少ない. ローカル変数や制御文は使用しにくい. (マクロ利用側の)引数は,置換内容の回数だけ評価される. 12 前処理指令と空白・コメント ...不可 ...必須 # define pint( x ) printf ( #x " = %d\n" , x ) ...任意 一つの前処理指令は,1行で書かなければならない.ただし, 行末に「\」を置くことで,複数行で書ける. 関数形式マクロの場合,括弧の途中で改行できる. 前処理指令の中でコメント(/* */ もしくは //)を書くと, 前処理時に空白文字に置き換えられる. 13 条件付きコンパイル(1) #if 定数式 … #endif 定数式が真のときに「…」を残し,そうでなければ「…」を捨てる. 定数式の評価や「…」の取捨は,前処理時に行われる. 14 条件付きコンパイル(2) 「#if 定数式」に代えて,「#ifdef 名前」や 「#ifndef 名前」も利用可能. 「#else」や「#elif 定数式」も記述可能. 条件付きコンパイル #if 条件式1 … #elif 条件式2 … #else … #endif 参考: Cのif文 if (条件式1) { … } else if (条件式2) { … } else { … } 条件付きコンパイルは入れ子にできる. 15 他のファイルの取り込み #include <ファイル名> ライブラリ関数などが宣言されているファイルを取り込む(イン クルードする). #include "ファイル名" 自作のファイルを取り込む. 16 ヘッダファイル 関数プロトタイプ,構造体や特殊な型,定数などが宣言・定 義されているファイル. #include <stdlib.h> とすると, /usr/include/stdlib.h を取り込む(ヘッダファイルの 所在は処理系依存). ヘッダファイルの中で,他のヘッダファイルをインクルードする こともよく行われる. 慣例として ファイル名を「.h」で終わらせる. 関数は,「宣言」のみして「定義」はしない. 同一ファイルに対する複数回のインクルードがあっても,2回目 以降は処理しないようにする. 17 関数プロトタイプ (Function prototype) 構文: 型名 関数名(引数の型の並び); 「関数原型」 ともいう セミコロンを忘れずに 「引数の型の並び」は,「引数(型と変数)の並び」でもよい.こ のとき変数名は無視される. 一般に,グローバル区間に記述する. 例: int swapcase(int); 関数プロトタイプを用いることで, 関数定義の順番を気にすることなくプログラムを記述できる. • ただし,関数プロトタイプは,関数定義の前に書くこと. 関数の入出力が明確になる. 18 ヘッダファイルとライブラリ関数 既に定義されている関数や定数を利用するには,あらかじめ, 適切なヘッダファイルをインクルードしなければならない. printf なら #include <stdio.h> NULL なら #include <stdlib.h> が一般的. インクルードすべきヘッダファイル名は,manpage で知るこ とができる. man 3 printf jman 3 printf JM Project (http://www.linux.or.jp/JM/) 19 英字大小変換プログラム(1) 仕様 例 コマンドライン引数の各語の英字の大小を変換する. ./upcase Wakayama Univ. WAKAYAMA UNIV. ./downcase Wakayama Univ. wakayama univ. ./swapcase Wakayama Univ. wAKAYAMA uNIV. 一つのプログラムファイル(upcase.c)から,3つの実行 ファイル(upcase,downcase,swapcase)を作る. 20 英字大小変換プログラム(2) 1文字ごとの変換 ライブラリ関数を使用する. • 大→小: tolower • 小→大: toupper これらは自作しようと 思わないこと 関数形式マクロを用いてupcaseとdowncaseも定義しておく. swapcaseは関数で定義するのが自然. 文字列ごとの変換 main関数の中で,2重ループにより処理する. • 外側のforは,コマンドライン引数を順番に見る. • 内側のwhileは,文字列を1文字ずつ見る. 21 英字大小変換プログラム(3) 「ひとつのプログラムファイルから,3つの実行ファイルを作 る」方法 make upcase ln -s upcase downcase ln -s upcase swapcase ファイルdowncaseが作ら れ,これはファイルupcase のシンボリックリンクとなる. コマンド名に特定の文字列が含まれていれば,upcase関数 に代えて,downcaseまたはswapcaseの関数を呼び出す. コマンド名は,argv[0]. 文字列が含まれているかのチェックには,ライブラリ関数の strstrを用いる.ここではhas_string関数を(関数形式マ クロで)定義している. 22 有用なライブラリ関数(1) #include <stdio.h> を必要とするもの #include <stdlib.h> を必要とするもの int putchar(int c) … 1文字出力 int atoi(char *s) … 文字列から整数値への変換 void exit(int status) … プログラムの終了 int rand(void) … 乱数生成 #include <string.h>を必要とするもの size_t strlen(char *s) … 文字列の長さ int strcmp(char *s1, char *s2) … 2つの文字列を比較 char *strstr(char *s1, char *s2) … 文字列検索 23 有用なライブラリ関数(2) #include <ctype.h> を必要とするもの int isdigit(int c) … 文字が数字であるか判定 int tolower(int c) … 大文字を小文字に変換 int toupper(int c) … 小文字を大文字に変換 #include <math.h> を必要とするもの double exp(double x) … eのx乗 double floor(double x) … x以下で最大の整数 24 まとめ 前処理指令と関数プロトタイプをうまく使えば,人間にとって 読みやすいプログラムを書くことができる. ライブラリ関数を使うには,#includeを用いて適切なヘッ ダファイルをインクルードする. 前処理は,コンパイルの前に行われる.そのため書式はCの 文法と異なる. 25 次に学ぶこと:ファイル処理(1) UNIXの中での「ファイル」 UNIXでは,すべての入出力はファイルを読み書きすることに よって行われる.キーボードや画像でさえも,ファイルシステム 上のファイルである.(『プログラミング言語C』一部改変) Cプログラミングの中での「ファイル」 プログラムが終了しても,内容が保持されるデータ構造. • 比較:オブジェクトは,auto変数,static変数,ヒープ領 域の変数のいずれも,プログラム終了時に破棄される. • 「ストリーム」を介して,バイト列として読み書き可能. 26 次に学ぶこと:ファイル処理(2) 問題 ファイルを入力にとり,先頭に行番号をつけて出力できる? 標準入力(キーボードなど)からの入力に対して,先頭に行番 号を右揃えでつけて出力できる? 27
© Copyright 2024 ExpyDoc