情報処理Ⅱ 第9回:2003年12月16日(火)
今回からのルール 構文は,このフォント 使用例は,このフォント
本日のテーマ 前処理(preprocessing) 代表的なライブラリ関数 ライブラリ関数の名前がわかれば,それを自分で使えるようになる! # から始まる構文 代表的なライブラリ関数 ライブラリ関数の名前がわかれば,それを自分で使えるようになる!
DEBUGPRINT printf("!\n"); 問題(授業がつまらない人の ために) 以下のDEBUGPRINTはどういう意味か? そうであることをどのように確認すればよいか? 配布資料のプログラムは仕様通りに動かない.どこをどのように修正すればよいか? DEBUGPRINT printf("!\n");
前処理とコンパイル(1) 前処理・コンパイル・アセンブル・リンクの各処理は通常,コンパイラ(ccなど)が一手に引き受ける. ソースファイル (前処理前) ソースファイル (前処理後) 前処理 コンパイル アセンブル オブジェクト ファイル 実行ファイル リンク 前処理・コンパイル・アセンブル・リンクの各処理は通常,コンパイラ(ccなど)が一手に引き受ける.
前処理とコンパイル(2) 前処理は, 前処理のコマンド(プリプロセッサ)はcpp 狭義には,「コンパイルに先立って行われる処理」であり,したがってコンパイルとは別 広義には,ccでコンパイルすれば自動的に処理してくれる,という意味でコンパイル作業の一部 前処理のコマンド(プリプロセッサ)はcpp Cの前処理以外にも使用可能
前処理指令 (Preprocessing directive) マクロ定義(#define) オブジェクト形式マクロ ⇒「定数」の定義 関数形式マクロ ⇒「関数もどき」の定義 マクロ定義無効(#undef) ソースファイルの取り込み(#include) 条件付きコンパイル(#if ... #endif)
オブジェクト形式マクロ(1) 語の置き換えを行う. プログラム修正により変わり得る定数値があるときに,よく用いられる. #define 置換対象 置換内容 #define WORD_SIZE 6 と記述すると,それ以降 int a[WORD_SIZE]; は int a[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"); といった文字列定数は,置き換えない.
関数形式マクロ(1) オブジェクト形式マクロとほぼ同じ書式. 置換対象に「(…)」をつける. 注意すべき使用例 #define printfint(x) printf("%d\n",x) に対して,printfint(a); は printf("%d\n", a); に置き換えられる. 注意すべき使用例 #define add(x, y) x+y に対して, printf("%d", add(1,-1)); や printf("%d", !add(1,1)); は意図通りに動作しない. #define add(x, y) ((x)+(y)) としなければならない. このxは変数ではない!
関数形式マクロ(2) 置換内容の中で # を用いると,置換対象を文字列にできる. ## を用いると,前後の語を連結できる. #define printfint(x) printf(#x " = %d\n", x) に対してprintfint(a); は printf("a"" = %d\n", a); に置き換えられる(これは printf("a = %d\n", a); と同じ). ## を用いると,前後の語を連結できる. #define N1 100 #define N2 1000 #define N(i) N##i に対して, N(2) は,N2 に,そして 1000 に置き換えられる. 順に変換(展開)される.
マクロ利用時の注意点 C言語のキーワードも置換可能. #define char signed char は文法上問題ないが,よい書き方ではない.より好ましい例: #define schar signed char typedef signed char schar; 同一内容であれば,同じ名前のマクロを複数定義してもよい.(異なっていればコンパイルエラー.) 置換内容のない名前も定義できる. #define DEBUG 末尾にセミコロンをつけない. #define WORD_SIZE 6; は(たいていの場合)間違い. schar型を新たに定義する.意味は,signed charと同じ.
例題1(デバッグ) うまく動かない. 関数形式マクロを定義して,途中の値を出力するよう埋め込む.
ソースファイルの取り込み #include <ファイル名> #include "ファイル名" ライブラリ関数などが宣言されているヘッダファイルを取り込む(インクルードする). #include "ファイル名" 自作のファイルを取り込む.
ヘッダファイル ライブラリ関数の関数プロトタイプ,構造体や特殊な型,定数などが宣言・定義されているファイル. #include <stdlib.h> とすると,/usr/include/stdlib.h を取り込む(ヘッダファイルの所在は処理系依存). 自作するときは,ファイル名を「.h」で終わらせるのが慣例.
ヘッダファイルとライブラリ関数 既に定義されている関数や定数を利用するには,あらかじめ,適切なヘッダファイルをインクルードしなければならない. printf なら #include <stdio.h> NULL なら #include <stdlib.h> が一般的. インクルードすべきヘッダファイル名は,manpage で知ることができる. man 3 printf jman 3 printf JM Project (http://www.linux.or.jp/JM/)
例題1(改良) ライブラリ関数 isdigit を使用して,文字が数字('0'~'9' のいずれか)か否かを判定する. 定数や関数形式マクロを定義して,値の意味や処理の内容をわかりやすくする.
条件付きコンパイル(1) #if 定数式 … #endif 定数式(前処理時に評価できる式)が真のときに,「…」を残し,そうでなければ「…」を捨てる.
条件付きコンパイル(2) 「#if 定数式」に代えて,「#ifdef 名前」や「#ifndef 名前」も利用可能. 「#else」や「#elif 定数式」も記述可能. 条件付きコンパイルは入れ子にできる. #if 条件式1 … #elif 条件式2 #else #endif 条件付きコンパイルの基本 if (条件式1) { … } else if (条件式2) { } else { } 参考: Cのif文
# define printfint( x ) printf ( #x " = %d\n" , x ) 前処理指令と空白・コメント ...空白は必須 # define printfint( x ) printf ( #x " = %d\n" , x ) ...空白は任意 一つの前処理指令は,1行で書かなければならない.ただし, 行末に「\」を置くことで,複数行で書ける. 関数形式マクロの場合,括弧の途中で改行できる. 前処理指令の中でコメント(/* */ もしくは //)を書くことができる.これは前処理時に空白文字に置き換えられる.
有用なライブラリ関数(1) #include <stdio.h> を必要とするもの putchar(c) … 1文字出力 #include <stdlib.h> を必要とするもの atoi(str) … 文字列からint型数値への変換 exit(0) … プログラムの終了 rand( ) … 乱数生成 #include <string.h>を必要とするもの strcmp(str1, str2) … 2つの文字列を比較 strchr(str, c) … 文字検索
有用なライブラリ関数(2) #include <ctype.h> を必要とするもの isdigit(c) ... 文字が数字であるか判定 tolower(c) … 大文字を小文字に変換 #include <math.h> を必要とするもの exp(x) … eのx乗 floor(x) ... x以下で最大の整数(反対は ceil)
まとめ 前処理指令をうまく使えば,人間にとって読みやすいプログラムを書くことができる. 定数・関数マクロの定義 (#define) 条件付きコンパイル (#if) ライブラリ関数を使うには,#includeを用いて適切なヘッダファイルをインクルードする.
今後の予定 2004年1月13日(火):第10回 2004年1月20日(火):おさらい 2004年1月27日(火):試験 ストリーム,特に標準入力と標準出力について 落ち穂拾い 2004年1月20日(火):おさらい 2004年1月27日(火):試験 2004年2月3日(火)に補講(プログラミング能率向上のノウハウ;採点対象外)をすれば出席する人はどのくらいいますか?
発展的な話題:関数名の表記法 printf printf() printf(3) 最も単純な表記法. ただし,コマンド名と一致するケースがある. printf() 関数呼び出しであることを明示する表記法. printf(3) 「詳細を知りたければ,man 3 printfを実行して読むこと」を含めた表記法. manpageはこの形式.Webでもときどき見かける.