情報処理II

情報処理Ⅱ
第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