今回は大学や高専でC言語を習った人のための"再入門"講座です. 学校では,if文やfor文といった基本的な内容から,関数やポインタなど ちょっと難しい話も勉強したことと思います. しかし学校では本当に基本的なことしか教えてくれません. 演習や試験でよい点数をもらったからといって,「C言語をマスターした!」 と思ってしまうのは大きな誤解です(←昔の自分). 研究などで,画像処理やロボットの制御など,実際にちゃんとした目的 があって,なおかつある程度の規模のプログラムを作る場合は それなりのテクニックが必要です.たとえば,
|
きれいなプログラム・汚いプログラム
「美しい or きれいなプログラム」「汚いプログラム」という表現があります. きれいなプログラムは読みやすいだけでなくバグやエラーも発生しにくいものです. きれいなプログラムと汚いプログラムはどんなところが違うのか表1にまとめてみました.
コーディング箇所 | きれいなプログラム | 汚いプログラム |
字下げ | している | していない |
コメント | 適切な箇所に簡潔に記述 | ない/無駄に書いてある |
条件分岐 | 少なくて簡潔 | 多くて複雑 |
入れ子 | 多くても3重構造 | 4重構造以上 |
ループの終了条件 | 終了条件が1つで単純 | gotoやbreakが多くて複雑 |
変数名 | 英語名でわかりやすい | わかりにくい/一文字 |
変数の数 | 少ない | 多い |
関数名 | 英語名でわかりやすい | わかりにくい |
関数の引数 | 少ない | 多い |
関数の長さ | 短い | 長い |
グローバル変数 | 少ない/ない | 多い |
定数 | マクロ定義/列挙型 | 値が直接書かれている |
関数群 | ヘッダとソースに分離 | 一個のソースに全部記述 |
いくつ当てはまりましたか? 汚いプログラムの項目が5つ以上当てはまるようであれば,再入門の必要があるでしょう.
さて,きれいなプログラムが何なのかを一言でいうと
きれいなプログラム=他人や未来の自分が見たときに読めるプログラム |
これに尽きると思います. 汚いプログラムを書いてしまうと,その時点で理解していても何日か時間をおいて再び触れる時には いとも簡単に理解不能になります.フローチャートを並べたメモなどを残していても全く役に立ちません. それではきれいなプログラムを書くテクニックを見ていきましょう.
きれいなプログラムを書くには
まずは妙な自己流を捨て,世間一般に使われているような(暗黙の)ルールに従うことから始めます.
字下げ・改行をきちんとする
基本中の基本です.これができていないプログラムは誰も読みたがりません. 字下げをする理由は言うまでもなく条件文やループの入れ子構造を わかりやすくするためです.デバッグの時に苦労したくなければ きちんと字下げする癖をつけましょう. VisualC++では自動で字下げをするようにカーソル移動してくれるので活用してください.
条件文やループ文の改行位置については以下のような2種類が見受けられます.
// タイプ@ for ( i = 0; i<n; i++ ) { /* 処理 */ } // タイプA for ( i = 0; i<n; i++ ) { /* 処理 */ }
int func() { /* 処理 */ }
命名規則に従ってコーディングする
一般的に次のような(暗黙の)ルールがあります.
- マクロ名:全部大文字
- 変数名:全部小文字
- 関数名:全部小文字または単語の先頭文字だけ大文字
- 外部変数(グローバル変数)は「g」で始まる名前にする
- 構造体(あるいはクラス)のメンバ変数は「m」で始まる名前にする
- 引数は参照のみは「in」,書き換えをするのは「out」,両方あるのは「io」で始まる名前にする
このようにするのは,言うまでもなく混同を避けるためです. 変数名は結構大事なもので,規模が大きくなるにつれその重要性は増します. a,b,cなどの一文字の変数名は絶対避けるべきですが,座標値を意味するx,y,zや 配列の添え字のi,j などは例外でしょう.逆に名前が長すぎるのも避けましょう.
定数の扱い方:マクロ定義と列挙型
- 配列の大きさを意味する定数値や,よく使うメッセージ(定型文)はマクロ定義する
- 値よりも文字そのものに意味のある記号定数の集まりは列挙型でまとめると良い
マクロ定義とは,コンパイラへの
命令のひとつである#defineのことです.たとえばソース中で
#deifne SIZE 100
#define ERROR_MESSAGE "エラーです"
printf(ERROR_MESSAGE);
定義した数値自体に意味はなく,その定数名そのものに意味があり,なおかつそれが 何かの集まりであれば「列挙型」を使うのが良いです.具体例を以下に示します.
【例】ジャンケンの入力判定プログラム
ジャンケンですから取りうる入力値は,「グー」「チョキ」「パー」の3種類です.
マクロ定義を使うアイデアなら
#define HAND_GU 0 #define HAND_CHOKI 1 #define HAND_PA 2
int player = HAND_CHOKI; //プレイヤーの手をチョキにする if ( player == HAND_GU ) printf("グーを出しました");
これでも十分ですが,ここでHAND_GUというのは「グー」を意味する文字であることが重要で, 定義されている0という数値はさほど重要ではありません.そこで列挙型を使って次のように定数をまとめてみます.
typedef enum{ GU, CHOKI, PA } janken_hand;
janken_hand player = CHOKI; //プレイヤーの手をチョキにする if ( player == GU ) printf("グーを出しました");
マクロ定義と違ってjanken_hand型はGU,CHOKI,PAの3種類の値しか取らないことを 明示することができるので,たとえば,
janken_hand get_hand();
変数の集まりは構造体でまとめる
- 複数個の変数は関係のあるもの同士を構造体でまとめよう
変数が無駄に多い場合は,関係のあるもの同士をまとめることができないか考えてみることが 必要です.例えば色を表現する際には赤・緑・青の3色を使って表現しますが,これを
unsigned char red,green,blue;
typedef struct tagCOLOR{ unsigned char R; unsigned char G; unsigned char B; } COLOR;
変数を構造体でまとめることによって次のような効果が得られます.
- 変数の数が減る
- 関数の引数の数も減る
- 複数のデータのコピーが容易になる
- その結果行数が減るのでプログラムが短くなる
- プログラムが読みやすくなる
ちょっと一休み
さて,きれいなプログラムを書くテクニックについて述べましたが,これだけのルールを守るだけで
- 他人が読めるプログラムを作るテクニック
- バグの出にくいプログラムを作るテクニック
拡張性・汎用性のあるプログラムを書くには
拡張性・汎用性のあるプログラムを書くためには,関数が作れることが必要になります. C++においてはクラスを使うことによってさらにパワーのあるプログラムが組めるようになります. また大規模なプログラムを作る際にこうした部分を序盤できちんと作っておけば,後々 スムーズにコトが進められて楽ができます.
関数化ということ
プログラミングとは「関数を作っていく作業である」とよく言われます. main関数に全ての処理を詰め込んでしまうようではいけません. ある目的の処理をこなしている複数の行を,関数というひとつの処理単位に置き換えてください. 関数の長さは一目で見渡せる画面1ページ分が目安だとよく言われます. 長くなり過ぎないように上手に細分化してください. また,似たような処理はコピペで作らずに共通部分を関数にできないか考えましょう. この場合,差異のある部分を引数にすれば良いのです. 引数が多くなってしまうのであれば,構造体を使ってまとめられないか考えてみると良いでしょう.
関数化によるメリットは
- プログラムの構造がわかりやすくなる
- デバッグがしやすくなる
- 拡張しやすくなる
- 汎用性が向上する
- グローバル変数を駆逐できる
- 後々楽できる
関数を作ったら忘れる前に関数の仕様をコメントするようにしましょう. これをおろそかにすると,後々痛い目見ることになります.
【コメントの一例】
/*------------------------------------------- 【名前】GrayFilter 【機能】画像のグレイスケール化を行う 【引数】inimage 入力画像 outimage 出力画像 threshold 閾値 【戻値】成功:0,失敗:-1 【備考】特になし -------------------------------------------*/
クラスというデータ構造
クラスはCではなく,C++の機能です.詳しくは説明しませんが, クラスを使うことによって関連する複数のデータと関数をまとめることができます. 言うなれば,構造体の拡張版といったところでしょうか. クラスを使ったプログラミングはオブジェクト指向プログラミングというものに分類されます. クラスに興味のある人はC++について勉強されてください.
さて,クラスを勉強してわかった気になっても,実践で使うのはなかなか難しいです. まず何をクラスにするのか?が一番の悩みどころです. 文献[3]では,1つの指針として次のように述べています.
グローバル変数があって,そのグローバル変数を複数の関数で参照 しているなら,それらを1つのオブジェクトにする. (グローバル変数を複数の関数が参照するということは,それらが 関連する仲間であるからにほかならない)つまりこれが意味するところは,関数化によっても駆逐できないグローバル変数をクラスという枠組みに 取り込むことで結果的になくすことができる,ということになります.また, クラスのアクセス制御という考え方を使えば,ある範囲ではグローバル変数的な使い方をしつつ, ある範囲からはアクセス不可能な変数を作ることが可能です(いわば「安全な」グローバル変数).
クラスを使ったオブジェクト指向プログラミングについては, 設計手法がいくつか提案されていて, 代表格のUML法(Unified Modeling Language)などは多数本が出版されています.
バグの原因はどこに?
コンパイルできるけど,挙動がおかしい…いわゆるバグですが, 経験上次のことが言えます.
- コンパイル時の「警告」にバグの要因が潜んでいることが多い
- コピペ改変でコーディングした箇所はバグが発生しやすい
- 計算が合わないときは,演算時の「型」に問題がある
- ローカルで宣言できる配列の大きさに限界がある
- 関数にはエラー判定を組み込むべき
コンパイル時の「警告」にバグの要因が潜んでいることが多い
コンパイル時の「警告」は『ローカル変数は 1 度も使われません.』といった危険度の低いものから 『値を返さないコントロール パスがあります』といった危険度の高いものまでいろいろあります(これらはVisualC++6.0の例). バグの予防策として,警告は必ず殲滅する癖をつけてください.
コピペ改変でコーディングした箇所はバグが発生しやすい
次にコピペ改変でのミスですが,これは僕を含めてみんなよくやります. 似たような処理だからといって,「コピー&ペーストして変数の部分だけ変更すればいいや」 という感じのノリでコーディングしてると,変更し忘れや変更ミスによってバグが発生します. バグ発生直前にコピペ作業をしていた場合は,真っ先にその箇所を疑ってください.
計算が合わないときは,演算時の「型」に問題がある
型の上限値・下限値や自動型変換について勉強が足りないと,ドツボにはまる ことになります.次のプログラムを見てください.
unsigned char data1 = 255; unsigned char data2 = 2; unsigned char sum; sum = data1 + data2; printf("sum:%d\n",sum);
sum:1
割り算はバグが発生しやすい箇所です. 以下は単純な1÷9の計算ですが,型やキャストの仕方によって計算結果は異なります.
printf("ans1:%lf\n",1/9); printf("ans2:%lf\n",(double)(1/9)); printf("ans3:%lf\n",(double)1/9); printf("ans4:%lf\n",1.0/9); printf("ans5:%lf\n",1/9.0); printf("ans6:%lf\n",1/(double)9); printf("ans7:%lf\n",(int)(1.0)/(int)(9.0));
ans1:0.000000 ans2:0.000000 ans3:0.111111 ans4:0.111111 ans5:0.111111 ans6:0.111111 ans7:0.000000
型の優先順位:char<short≦int≦long<float<double
あと,割り算におけるバグの1つに「ゼロ割」があります. 割り算の時に0で割ることがないよう注意してください.
ローカルで宣言できる配列の大きさに限界がある
ローカルで宣言した自動変数はスタック上に領域を確保するので,
あまり大きな領域(配列)を確保しようとすると,あふれてしまいその時点でプログラムが異常終了します.
大きな配列を使いたい場合は
のどれかで対処してください.
関数にはエラー判定を組み込むべき
バグと戦うためには,関数作りの際に処理の成否を出力するような仕組みをつくる癖をつけてください. 一般的によく使われているのは,戻り値を成功:0,失敗:-1とする方法です. メモリを動的に確保するような場合や,そのようにして確保したメモリを参照する場合は, ヌルポインタに対するエラー判定が必須になります.怠ると青い画面を見る羽目になりますよ….
役立つ本の紹介
最後は本コーナーでしめたいと思います. 先日所有しているC言語の本を数えたら洋書を含めて17冊(+2冊注文中)もありました. 買いすぎです.バカです.今回はそのなかでも堂々と薦められるものを紹介します.
Cプログラミング診断室
さらに美しく健康的なプログラムのために
藤原博文(著)/技術評論社/ISBN: 4774117870/2003年07月
Software Designという雑誌に10年以上前に連載されていたコーナー. 1993年に本になり,増刷を繰り返し一度絶版になった後,WEBサイトに全内容が公開されました. そして2003年に改訂版が出版されたというベスト・ロングセラーです. 徹底的に悪いプログラム(職業プログラマが書いたもの)を例に, どこが悪いかを指摘し改善方法を処方するという,「反面教師」な内容です. C言語上達の秘訣は,良いプログラム・悪いプログラムがどんなものかを知ることです. これを読んだ後で,後輩の書いたプログラムを見てみると,悪いプログラムの本質は10年経った今でも そう変わらんのだなぁと実感できます.書籍版のほうが読みやすいのでオススメ.
Web版
Cプログラミング診断室(Amazon)
平林雅英(著)/技術評論社/ISBN:4774104329/1997年05月
関数リファレンスに加えプログラミングに関する専門用語などが数多く収録されています.
printf() [プリント エフ] といった感じで読みも書いてあるあたりに好感を覚えます.
各関数においてプログラミング例も書かれています.
付録には,プログラミング用語と英語名の英和対訳一覧,ヘッダ別の関数リスト(索引),
文字コード体系一覧,Cプログラミング・トラブル診断室,記号一覧など,
とにかく必要以上にいろいろな情報が所狭しと載っています.
便利なことこの上ない本ですが,少々小さめの文字で凝縮して書かれているので,
ほんと国語辞書みたいな本です.
新ANSI C言語辞典(Amazon)
〜クラスとメンバ関数手習い指南〜
柏原正三(著)/翔泳社/ISBN:479810776X/2004年10月
CからC++に移行する人向けの本です.入門書を読んでC++をはじめたはいいが,
どこが悪いか誰も指摘してくれない!という状況で困っているひとに読んでもらいたい本です(経験者は語る).
内容は,C++のソースを書く導入部から,クラスの作り方,参照渡し,例外などをカバーしています.
ただし,この本だけでC++の勉強をするのは厳しいので別途入門書を用意する必要があります.
本書はC++をはじめたC経験者づまづきそうな場所について配慮がなされていて,
各トピックの構成は以下のようになっています.
1.問題(バグ・コンパイルエラー)のあるプログラムの提示
2.問題点はどこにあるのか?
3.指南(解説)
4.まとめ
5.修正後のプログラム
いわゆる「反面教師」方式の構成ですが,"まとめ"のおかげで詳しい解説を読まずとも 要点をおさえることができます.個人的にプログラミング本らしくないカラフルな装丁が好きです.C言語版もあります.
C++美しいプログラミング見本帖(Amazon)
実用性のある入門書としては,次の本を推薦します.
新・C言語入門 シニア編林晴比古(著)/ソフトバンクパブリッシング/ISBN:4797325623/2004年02月
新C++言語入門 シニア編〈上〉基本機能
林晴比古(著)/ソフトバンクパブリッシング/ISBN:4797316608/2001年05月
新C++言語入門 シニア編〈下〉クラス機能
林晴比古(著)/ソフトバンクパブリッシング/ISBN:4797316616/2001年05月
初心者に何を薦めるかについてはいろんな意見があるようですが,僕は「林晴比古さん」派です (対抗馬としては,マナちゃんこと高橋麻奈さんの「やさしい」シリーズですかね). 隅々まで書かれていて,見やすく,そしてわかりやすいので好きです. ビギナー編やスーパービギナー編などもありますが, C言語をある程度かじった人であれば臆せずシニア編を買ってもいいと思います. 結構高いですので,本屋で手にとってみて気に入ったら買ってみてください.
おわりに
長々と書いてしまいましたが,参考になりましたでしょうか? ここに書いた内容の半分は,僕がここ1〜2年で本やインターネットを頼りに独学で学んだこと, そして大学で後輩の指導にあたっているときに気づいたことです. 学校の授業ではif文やfor文という必要最低限のことしか教えてくれず,マクロ定義のことなんか誰も教えてくれませんでした. プログラミング上達の鍵は,本を読むことよりもダメだしをしてくれる誰かを見つけること, そしてうまい人のプログラムを読んでテクを盗むことです. 気づいたことがあれば適宜追加していこうと思います.今回はこのへんで!
参考文献
[2]真紀俊男:『プログラミングの禁じ手Web版 C言語編』,C MAGAZINE,ソフトバンクパブリッシング
[3]塚越一雄:『決定版はじめてのC++』,技術評論社,1999年