96
C : 2010/3/3

C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

  • Upload
    others

  • View
    3

  • Download
    0

Embed Size (px)

Citation preview

Page 1: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

C言語入門の入門

最終更新日: 2010/3/3

Page 2: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

はじめに

実際に役に立つ小さなプログラムを自分で作ることができるようになることを目標とし、C 言語を使ってプログラミングの初歩を学ぶことにします。C の機能を強化した C++ が実際にはよく使われているので、これも少しだけ勉強することにします。C 言語の機能のうちプログラミング入門には不必要な部分は省き、プログラミングの基礎をマスターすることに徹します。また、C++で追加された機能の中からは、便利でしかも簡単に使える機能を少しだけ選んで扱います。

プログラミングができるようになるには、いろいろな定石やセンスを身に付けることが必要で、

既存のプログラムをたくさん見て模倣することから始めなければなりません。コンピューター上で

実際にプログラムを作って動作させて注意深く観察し、体験的に理解します。

第1章ではひじょうに簡単なプログラムから始めます。 プログラムを実行するまでのワークス

テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。プ

ログラムはとても基本的で、出て来る文法事項は本当に少ししかありませんが、それだけでもかな

り使えることがわかるでしょう。見過ごしていた間違いや誤解を修正するため、この章は1度読ん

だ後でもう1度繰り返して読むことをすすめます。

第2章では、実用的プログラムに必要な基本的機能について取り上げます。一つは、外部との情

報のやりとりに必要な文字データの扱いです。文字によるコミュニケーションは人間どうしやプロ

グラムと人間との間だけではなく、プログラムどうしの通信にも使われます。 もう一つはプログ

ラムの実行速度を調整したりするために時間を扱う方法です。

第3章ではいろいろな図を描くプログラムで腕を磨いてもらいます。図によれば、プログラムの

動作を感覚的に素早く理解することができます。丸や三角や点のような単純な図形を配置して色を

付けたりすることから始め、「繰り返し」を使うことによって単純な図形とは異なる複雑な模様を

生成します。

第4章では数値計算の例として、常微分方程式の数値解法を取り上げています。数値計算では計

算の速度と精度が重要であり、そのためにアルゴリズム (計算方法)の工夫が行われます。ここで利用している 2 種類のアルゴリズムは、いずれも単純でわかりやすいものですが、問題は、アルゴリズムを理解した上で、それを実際のプログラムとしてどう仕上げるかです。ここでは、プログ

ラムを機能単位で部分に分割し、それらの間で情報をやりとりする方法を示しています。また、複

合的なデータの扱い方と、演算記号の機能拡張法も出て来ます。プログラミング技術を本格的に学

ぶには、ここで出て来るような抽象的概念の扱いに慣れることが必要です。

一般的な UNIX 環境では、このテキストの 第1章 や 第2章、第4章に出て来るプログラムがそのまま実行できるでしょう。 第3章 のプログラムを実行するには特別なグラフィックスのライ

ブラリーの設定が必要ですが、テキストではすでにこれが設定済みのシステムでの作業を前提にし

て説明します。グラフィックスのライブラリーをあらたに設定する方法については、最後の付録で

説明してあります。

なお、ディレクトリやファイルの扱い方、テキストエディターの操作法については、すでに知っ

ているものとします。

この文書の PDF ファイルには、主なプログラムのソースファイルが添付してあります。

Page 3: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

目 次

第 1章 C 言語の超基本 5

1.1 Emacs の自動整形機能 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51.2 C言語とは . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51.3 C++ の処理系 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61.4 プログラミングの基本概念 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

練習問題 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81.5 Hello プログラム . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81.6 コンパイルと実行 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

練習問題 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101.7 プログラムの構造 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

1.7.1 メイン関数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111.7.2 関数定義と文字列 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121.7.3 ヘッダーファイルとライブラリー . . . . . . . . . . . . . . . . . . . . . . . 131.7.4 区切り記号 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141.7.5 終了コード . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14練習問題 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

1.8 関数の活用 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15練習問題 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

1.9 数値の型 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181.10 Cの出力と C++の出力 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191.11 グラフのプロット . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

練習問題 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231.12 Mathematica を使っている人へ . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23問題 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231.13 中間まとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

第 2章 入出力と時間の制御 26

2.1 文字 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262.1.1 16進法 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26練習問題 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282.1.2 ASCII コード . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282.1.3 C 言語での文字の扱い . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

2.2 文字列 (strings) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332.2.1 文字列変数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342.2.2 終端記号 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 352.2.3 文字列変数の宣言 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

2

Page 4: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

2.2.4 文字列の原始的操作 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362.2.5 文字列関数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38練習問題 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39

2.3 コマンドライン引数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392.3.1 コマンドライン引数の表示 . . . . . . . . . . . . . . . . . . . . . . . . . . . 40練習問題 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412.3.2 コマンドライン引数からの数値入力 . . . . . . . . . . . . . . . . . . . . . . 412.3.3 コマンドライン引数で翼の形を変える . . . . . . . . . . . . . . . . . . . . 422.3.4 総和を求めるプログラム . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43

2.4 C の標準入力 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 442.5 C++ のストリーム入力 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 462.6 実時間動作のプログラム . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

練習問題 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 512.7 ここまでのまとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51

2.7.1 ソースプログラムと実行ファイル . . . . . . . . . . . . . . . . . . . . . . . 512.7.2 変数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 522.7.3 基本的な型 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 522.7.4 演算子 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 532.7.5 文 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 542.7.6 繰り返し文 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 542.7.7 関数の利用 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 552.7.8 関数定義 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 562.7.9 main 関数の引数と返り値 . . . . . . . . . . . . . . . . . . . . . . . . . . . 56

第 3章 簡易グラフィックスによるプログラミング練習 58

3.1 実行環境の準備 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 583.2 点と線 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59

練習問題 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 603.3 カラー . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61

練習問題 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 633.4 四角形 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63

練習問題 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 643.5 配列と任意多角形 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64

練習問題 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 653.6 円 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 663.7 点で描くプログラム . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66

練習問題 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 683.8 2次元配列 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 683.9 参照渡し引数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 693.10 配列と for 文 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 713.11 再帰図形 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72

3

Page 5: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

第 4章 数値解析の基礎 76

4.1 解析解と数値解 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76練習問題 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77

4.2 数値解の計算手順 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 774.3 既成アプリケーション (Scilab)での計算 . . . . . . . . . . . . . . . . . . . . . . . 77

練習問題 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 794.4 Euler 法のプログラム . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 794.5 Euler 法の誤差の評価 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 814.6 Euler 法の完全なプログラム . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 824.7 ループの制御法 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 834.8 Runge-Kutta法 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 844.9 ベクトル値関数の微分方程式 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 854.10 振り子の運動シミュレーション . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86

4.10.1 インクルード指令と諸パラメーターの定義 . . . . . . . . . . . . . . . . . . 864.10.2 tDepend 型のための諸定義 . . . . . . . . . . . . . . . . . . . . . . . . . . 864.10.3 他の変更 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 894.10.4 動作テスト . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 904.10.5 Duffing 方程式 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91

付 録A Emacs の中でコンパイルするには 93

付 録B グラフィックライブラリーの準備 94

B.1 XviG . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94B.1.1 XviGの変更 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95B.1.2 xmkmf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95

B.2 sgraph . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95

4

Page 6: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

第1章 C 言語の超基本

1.1 Emacs の自動整形機能

Emacs はテキストの整形機能を持っていて、C 言語などのソースプログラムをあらかじめ決められた字下げ (indentation)の規則にあわせて整形することができます。字下げというのは文のブロックの左側に空白を入れて位置を調整することです。プログラムテキストをきちんと整形する

と、プログラムの構成がわかりやすくなります。複雑なプログラムはもちろん、どんなに短いプロ

グラムでも、見にくいことは致命的です。きちんと整形しないと、プログラムの完成に何倍もの時

間がかかります。

Emacs で C のソースプログラムを編集している時は、�� ��Tab キーを押すと、現在カーソルのあ

る行の字下げが正しく調整されます。また、�� ��Ctrl

�� ��J と押すと改行して次の行の正しい字下げの位

置にカーソルが移動します。とりあえずはこの2つのキー操作を憶えておくと良いでしょう。

Emacs の整形機能をそのまま使うとおそらく字下げが2文字単位になりますから、ちょっと見にくいかもしれません。このテキストに出てくるプログラム例では字下げが 4文字単位になるようにしています。このテキストと同じ規則を使うために、下の内容をホームディレクトリの .emacsというテキストファイルの中に追加して下さい1。

(defun my-c-mode-common-hook ( )(c-set-style "stroustrup")

)(add-hook ’c-mode-common-hook ’my-c-mode-common-hook)

2行目の ”stroustrup”というのはスタイル名といって整形の仕方を示すもので、他に ”whitesmith”とか ”gnu” とか ”linux” などがあります。 この設定は C と C++ のプログラムの編集にだけ影響し、他の種類の文書の編集には影響しません。話せば長くなるのでこの内容の説明はしません。

ちなみに、Stroustrup さんは C++ を始めた人で、この人の C++ に関する本の中のプログラム例はこのスタイルで書かれています。

上の設定を書いて保存したら、Emacsを何もパラメーターを付けずに起動してみて下さい。Emacsのエラーメッセージは、emacs ウィンドウの下部に表示されます。何か異常が生じた場合は、設定を慎重に見直して下さい。この設定は Lisp 言語で、括弧を正確に書く必要があります。2種類のクオーテーションマークにも注意が必要です。ダブルクォートとシングルクォートです。

1.2 C言語とは

インタープリター言語はプログラムを書きながらその場で動作を確かめることができるので手

軽で便利ですが、実行速度が遅いので大量の計算が必要な場合に問題があります。コンパイラーは

1たとえば、端末で cd; emacs .emacs$ と打てばこのファイルを編集することができる。

5

Page 7: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

プログラムを徹底的に分析して実行効率の高い機械語に翻訳してから実行するので、コンパイラー

言語を使うとインタープリター言語を使う場合と比べて桁違いの実行速度を得ることができます。

また、プログラムの処理内容を詳細に分析するので、文法的な誤りを減らすのにも寄与します。

C言語の文法も長い間使い続けられている内に変化してきました。C言語の考案者達による標準の教科書は最初に 1980年頃に出ました2が、C言語が広く使われるようになるにつれて文法規則が進歩してきました。現在は、先の本の第2版でも説明されている ANSI C を土台にした文法が使われています。たとえば、次のプログラムは最大公約数を計算するプログラム (の一部)を古いC と ANSI C で書いたものです。

------------- 古い C -------------------GCD ( x, y )int x, y;{

if ( y > 0 )return GCD ( y, x % y );

elsereturn x;

}

-------------- ANSI C -----------------int GCD ( int x, int y ){

if ( y > 0 )return GCD ( y, x % y );

elsereturn x;

}

何か微妙に違うようですね。古い文法で書かれたプログラムはめったにお目にかからないので、

ここでは古い方は完全に無視することにします。ANSI C は ISO の世界標準としても認められ、ISO/IEC 9899:1990 になりました。これは通常、C89 といいます。その後これに多少の機能が追加され、C95 と C99 が制定されました。現在は C でも複素数の式を使うことができます。C99は、日本工業規格の『JIS X3010-2003 プログラミング言語 C』として翻訳されています。

C言語をベースに新しい研究成果を取り入れ、新しい言語を作ろうとする動きもあります。1980年代に登場した C++ 言語はその一つです。C++ も ISO で標準が定められています (ISO/IEC14882:2003)。C++ 言語は C 言語の文法のほとんどの部分を含んで語彙を大幅に増やしたような構造になっています。C++ 言語を処理できる処理系があれば C 言語でのプログラミングにも問題なく利用できます。汎用コンピューターの Cコンパイラーのほとんどは C++ にも対応しています。ここでは、C++の機能のほんの一部を使います。ただし、プログラミングの入門が目的なので、C と C++ の違いのようなことについては一々述べません。

1.3 C++ の処理系

C++ の処理系にはいろいろなものがあります。たとえば、GNU という非営利団体が作って配布している GNU C コンパイラーは、比較的高品質であり、しかも多くのオペレーティングシステムやプロセッサー (中央処理装置)で動作するので、よく使われています。Linux でも標準の C++処理系は GNU です。

2B.W.Kernighan and D.M.Ritchie, The C Programming Language, Prentice Hall, 1978

6

Page 8: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

コンパイラーのコマンドに -v か --version だけを付けて動かすと、そのコンパイラーが何で

あるかがわかります。 たとえば、端末で

cc --version

と打てばよいのです。

make というコマンドはプログラムのファイルの名前や更新時間などから判断して必要なコマンドを自動的に実行してコンパイル処理を行うものです。 これを使うと、コンパイラーのコマンド

の使い方のような細かいことを忘れることができるので、仕事が楽になります。 このテキストで

も make を使って楽をするつもりです。

1.4 プログラミングの基本概念

初めてプログラミング言語を学ぶ場合は次に説明することが理解できるようになることを目的と

するとよいでしょう。これらはプログラムの基本的な構成要素です。

リテラル

名前

変数

データ型

逐次処理

サブプログラム

サブプログラムの引数

繰り返し処理

条件処理

リテラル (literal) とは「そのもの」ということですが、数値そのものや文字列そのもののような、プログラム中で直接表現されたデータのことです。それに対して、データに名前を付け、その

名前でデータを示したものは、リテラルではありません。数値は正の整数だけではなく負の数や小

数点付き数や10の冪乗倍を伴った数や8進数や16進数があります。文字列は有限の数の文字を

並べたものです。今あなたが読んでいる文章の段落や行は C の文字列として扱うことができます。名前とはプログラムの中で使われる単語で、データや一連の処理などに付けられます。 初めか

ら備わっている名前以外に、プログラム中で新しい名前を作ることができます。 名前を作るとき

には、使える文字に関する規則に従う必要があります。 C 言語ではアルファベットの大文字、小文字と数字およびアンダースコア ( _ )が使えます。一つの名前の中にスペースを含むことはできません。何かに名前を付けると、具体的な中身に関係なくそれを名前で簡単に表現することができ

ます。ということはそれを「抽象化」して扱うということです。犯人が特定できたら名前で呼べば

よいのと同じです。

変数とは、計算結果に名前を付けて記憶し、後で利用することができるようにする仕掛けです。

しかし、変数には記憶機能以上の役割があります。変数に記憶している内容を使って、同じ文で場

合によって異なることを行うことができるのです。たとえば、「x+1 を計算して表示しなさい」と言うだけで、x が 1 の場合の指示とか 2 の場合の指示を別々に与える必要はありません。そういう意味で、変数は抽象化のための仕掛けの一つです。「抽象化」がまた出て来ました。

7

Page 9: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

データ型とは、数値とか文字というようなデータの種類のことです。足したり引いたりすること

は数値では意味がありますが、文字では意味がないように、データの種類によって実行できる処理

が違います。変数が記憶しているデータを変更することを代入と呼びますが、その際、データ型が

変わってしまうと困ります。C では、変数に記憶するデータの型を途中で勝手に変えることは少しの例外を除いては許されていません。

文とは命令の単位です。プログラムの中のどの部分が一つの文になっているのかがわからないと

プログラムを読むことができません。文はあるパターンに従って単語を集めたものです。このパ

ターンを理解することが文法の理解になるのですが、実際には、ほんの少しの記号とキーワード

の使い方に慣れれば、後は簡単です。複数の文をまとめて「複文」というものを作ることもできま

す。C では復文も一つの文ですから、複文が複数集まって全体で一つの文になることもあります。逐次処理とは複数の文を並べた順番に実行することです。左から右、上から下という順番が基本

です。

サブプログラムとはひとかたまりの処理に名前を付けたものです。プログラミング言語には、サ

ブプログラムを作る規則やサブプログラムを呼び出して実行する規則があります。これによって複

雑な処理をより単純な処理の組合せに分解して表現します。機能を分割し名前を付けて抽象化する

ことによって、思考が明解になります。

引数 (ひきすう)とはデータをサブプログラムに与える仕掛けです。同じサブプログラムでも与えるデータによって異なる処理を行うことができます。これによって一つのサブプログラムをいろ

いろなところで再利用することが容易になります。「再利用」は生産性を上げる重要な手段です。

繰り返し処理とは同じ事を繰り返して行うことです。プログラミング言語は計算結果などによる

任意の回数の繰り返しを制御することができるようになっています。

条件処理とは、行うことを、計算結果などによって選択することです。プログラミング言語には

条件処理を記述するための文法が用意されています。

練習問題

プログラミングにおける「抽象化」にはどのようなことが含まれますか。

1.5 Hello プログラム

文字列を表示するだけの単純なプログラムから始めましょう。

C のプログラムはテキストエディターで書いてファイルに保存し、それをコンパイラーで処理して機械語の実行ファイルを生成してからそれを実行します。始めにプログラムのファイルをしまっ

ておくためのディレクトリを作ります。これは、端末で mkdir コマンドを使って行うこともできますし、ファイルマネージャでマウスを使って行うこともできます。ディレクトリ名はなんでも結

構ですが、後で見て何が入っていそうか想像がつくような名前にするのが大切ですね。以下ではこ

れを作業用ディレクトリーと呼びます。

作業用ディレクトリーが用意できたら、端末で cd コマンドを使ってそこに移動します。pwd コマンドで確認して下さい。

emacs hello.cc &

と打って Emacs エディターを起動し、下のプログラムを書き込みます。ファイル名 hello.cc に

拡張子 .cc を付けるのは、C++ のプログラムが入っていることを明示するためです。プログラム

8

Page 10: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

はスペルの他に大文字、小文字の区別、括弧の種類、区切り記号; に注意して入力して下さい。()

のように、中身が何も無くて括弧だけというところが何カ所かあります。数字のゼロのように見え

るかもしれませんが、括弧のペアになっていますので気を付けて下さい。emacs では、�� ��Ctrl

�� ��\ で

日本語変換のモードを切り替えることができるはずです。

// 簡単なプログラム その 1

#include <stdio.h>

void Aisatu (){

printf ( "Hello!\n" );}

void Gokigen (){

printf ( "How are you?\n" );}

int main (){

Aisatu ();Gokigen ();return 0;

}

一番上の // で始まっている行は「コメント」といって、ただのメモ書きです。プログラム自体に

は関係ありませんから省いても大丈夫です。プログラムの前でも途中でも、// から始めることに

よって自由にメモ書きを入れることができます。/* と */ で挟んでメモ書きを入れる方法もあり

ます。こちらは、複数行に渡るコメントを入れるのに適しています。

プログラムを打ち込んだら、�� ��Ctrl

�� ��x�� ��Ctrl

�� ��s で保存してください。しかし、編集画面を終了し

てはいけません。emacs を動かしたままにしておけば、コンパイルや実行の段階で問題が生じても、すぐに修整を行うことができます。

emacs の起動のときに & を付け忘れると、端末ウィンドウで作業を行うことができません。この場合は、端末ウィンドウで

�� ��Ctrl�� ��z とやってから bg と打つというわざがあります。これで &

を付けて起動したのと同じ状態になります。 端末ウィンドウでの�� ��Ctrl

�� ��z は、実行中のプログラ

ムを中断する働きがあります。bg コマンドは、中断したプログラムを「バックグラウンド」で再開します。「バックグラウンド」での実行は、アンパサンド付きで起動したのと同じ状態です。

1.6 コンパイルと実行

hello.cc は C++ の「ソースファイル」です。これをコンパイラーに読み込ませて分析させると機械語の実行ファイルが出来ます。文法のことは後回しにしてコンパイル、実行を先に行なってし

まいましょう。コンパイルにはいろいろなやり方がありますが、ここでは make コマンドを使って簡単に済ませます。

端末で、カレントディレクトリにソースファイルがあるのを確かめてください ( ls コマンドで確かめるとよい )。端末を新しく起動するのではなく、今作業を行っている端末ウィンドウを使いま

9

Page 11: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

す。もし間違って端末ウィンドウを終了してしまっていたら、新しく端末を起動して作業用ディレ

クトリーに移ってから作業を継続してください。作業用ディレクトリーの正確な名前を忘れた場合

は、ls コマンドで調べたり、�� ��Tab キーによる補完を使ったりしてください。

作業用ディレクトリでソースファイル hello.cc があるのを確かめたら、

make hello

と打って下さい。make コマンドは、カレントディレクトリにどんなファイルがあるのかを見て、指定された hello というものを作る方法を自動的に判断し、必要な処理を行なってくれます。今の場合は hello.cc があるのを見て、C++ のコンパイラーを使って hello.cc から hello という名前の実行ファイルを作ってくれます。C++ のコンパイラーが自動的に起動される様子はターミナルにちゃんと表示されているでしょう。この操作がコンパイルです。

もしプログラムに何か間違いがある場合はエラーメッセージが表示されます。Error ... のように表示されますからすぐわかるでしょう。プログラムの何行目でエラーが判明したかも表示されま

すから、それを参考にして間違いを見付けて直してください。 実際に間違いがあるのは、エラー

が見つかった行かそれより上の行です。エラーが複数ある場合は、はじめに表示されるものに対処

します。カッコやセミコロンの間違いの他、空白に「全角」の空白を使って気付かないこともあり

ます。修正したら、保存し直してから再度コンパイルを行い、もし再度エラーが出たらまた修正し

ます。

コンパイルが成功したら、hello というファイルが新しく出来たのを確認して下さい。ls -l で

表示してみると、左端の - とか w とかが並んでいるところ (ファイルモード)が他のファイルとは違っていて x が入っているのに気付くでしょう。ディレクトリではない普通のファイルの場合、x

は実行可能ファイルであることを示します。ファイルマネージャでは、hello ファイルのアイコンを右クリックして、コンテキストメニューからおそらく「プロパティ」を選び、「アクセス権」を

見ればよいでしょう。

このファイル hello を Emacs で表示してみて下さい。わけのわからない記号が並んでいるでしょう。これはテキストファイルではなく、機械語のファイルなのです。テキストファイル以外のファ

イルはバイナリファイルと言います。Emacs ではめちゃくちゃに表示されますが、バイナリとは二進数のことです。機械語は二進数なのかなあと思ってもらえば結構です。

プログラムを実行するには、端末で、ファイルのある場所とファイルの名前をキーボードから打

ちます。今の場合、カレントディレクトリーに hello というファイルがありますから、

./hello

と打ちます。./ はカレントディレクトリーのことです。するとHello!How are you?

と表示されるでしょう。

練習問題

上のプログラムの一番下にコメントとして (つまり // を左に入れて)あなたの名前とプログラム作成日時を追加してコンパイルと実行を行い、コメントがプログラムの実行に影響しないことを

確かめなさい。

10

Page 12: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

1.7 プログラムの構造

ではプログラムの構造がどうなっているのかを実験もしながら見ていくことにしましょう。図

1.1も参考にしてください。編集ウィンドウで hello.cc プログラムを見て下さい。コメントを除

くと空行で大きく4つのブロックにわかれているのがわかりますね? 空行が必要だというわけで

はないのですが、プログラムの構造が見てわかりやすいのでわざわざ空行を入れておきました。

図 1.1: C言語プログラムの構造

1.7.1 メイン関数

最後のブロックから見ましょう。最後のブロックは int main ()から始まっていて、Aisatu();

と Gokigen(); と return 0; が括弧 { } の中に入っています。そんな風には見えなかったかもし

れませんが、これが真相なのでそう思ってよく見て下さい。この部分はメイン関数の定義と呼びま

す。メインルーチンともいいます。

Aisatu(); と Gokigen(); は「関数」すなわちコマンドで、それぞれ何かの仕事をするもので

す。return 0; は他と形式が違って括弧 () がありませんが、まあ、一種の特殊なコマンドだと思えばよろしい。

Aisatu(); が何をするものかを Aisatu(); を取ってしまうことによって調べましょう。プロ

グラムの最後のブロックから Aisatu(); という部分を消してから保存し直して下さい。消すのは

int main () の2行下の行の Aisatu と () と ; です。直して保存したら、コンパイルを行なう

前に ./hello と打って実行してみて下さい。hello.cc は新しくなっていますが hello はまだ元

のままなので、さきほどと同じ表示が出るでしょう。次に make hello でコンパイルをやり直して

から実行して下さい。これで表示しなくなったものを Aisatu(); が担当していたことになります。

11

Page 13: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

次に最後のブロックの中に Aisatu(); を戻して下さい。ただし、前の場所ではなく、次のよう

に Gokigen (); の下に戻します。

int main (){

Gokigen ();Aisatu ();return 0;

}

保存してから、前と同じ手順でコンパイルし、新しい hello を実行してみてください。Hello! とHow are you? の順序が逆になりましたね。上から順に実行しているからこうなるのです。「あたりまえだ」って?

次に return 0; を Aisatu (); の前に移してから、保存、コンパイル、実行をやって下さい。

プログラムには Aisatu (); が入っているのに Hello! が出なくなったでしょう。return 0; はプ

ログラムの実行を中断するものだからこうなるのです。return に 0 が付いているのにはわけがあるのですが、ここでは、こうするものだと思っていて下さい。

1.7.2 関数定義と文字列

プログラムの他の部分の説明を簡単にしておきましょう。3番めのブロックは Gokigen という

関数の中身を定義しています。void は関数の「戻り値」の種類を指定するものです。一般には、関

数は処理した結果を呼び出してくれたところに返します。これは関数の戻り値といいますが、C では戻り値を何も持たない関数もあります。このようなものは「関数」らしくはありませんが、空っ

ぽのデータを返すものとして統一的に取り扱います。void は戻り値が無いことを示します。ちな

みに、void とは英語で空虚という意味です。Gokigen 関数の作業内容は printf ( "How are you?\n"); だけです。これは How are you?

と表示します。printf というのも関数ですが、これは C の標準関数であり、あらかじめ定義されています。括弧 ( ) の中は関数に与える引数で、ここでは文字列"How are you?\n" を指定して

います。

Cの文字列は、空白も含めて任意の文字を並べ、ダブルクォート " で左右を囲んで作ります。\n

のところは表示に出ていないように見えますが、実は \n は「改行」を行う特殊文字を表していて、

実際に改行を起こしているのです。証拠を見るため、 \n を消してプログラムを走らせて下さい。

C では改行までプログラマーが意識して行なう必要があるということです。 \n を消したら、後で

元に戻しておいてください。

Aisatu関数は2番目のブロックで定義されています。関数の名前と表示内容が違うだけで Gokigen

とそっくりなので説明は無用ですね。

int main() 以下も Aisatu などの関数定義と同じような見かけになっていますが、これも実は

関数の定義になっています。main 関数です。C のプログラムを走らせると main という名前の付いた関数が自動的に呼び出されて実行を行なうことになっています。他の関数は main から直接ま

たは間接に呼び出されないと実行されることはありません。実行の順番はどの関数が先に定義され

ているかとは無関係で、呼び出されている順番に従います。main の戻り値の型が void ではなく

int になっていることについての説明は後で出て来ます。

12

Page 14: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

もう一つちょっとした実験をしてみましょう。実はこれが重要です。今、プログラムは Aisatu関

数を実行しないで終了してしまうようになっているので、How are you? としか表示されません。

そこで、次のように Gokigen 関数の定義の中で Aisatu 関数を呼び出すようにしてみましょう。

void Gokigen (){

Aisatu ();printf ( "How are you?\n");

}

Gokigen 関数の定義をこのように直してから、コンパイル、実行をやって下さい。

関数の中から別の関数を呼び出すことが出来ることが証明されましたね。Aisatu 関数は main

関数から直接には呼び出されていませんが、Gokigen 関数を通じて間接的に呼び出されて実行さ

れるのです。return 0; の下の Aisatu(); は無用の長物になっているので消してしまっても影響

無いはずですね。それも確かめてみて下さい。

1.7.3 ヘッダーファイルとライブラリー

次に、プログラムの一番上にある #include <stdio.h> についてです。stdio.h はファイル名

です。これは自分のファイルではなくコンパイラーの処理系に含まれているシステムのファイル

で、他にも stdlib.h とか math.h とか仲間がたくさんあります。これらのファイルはヘッダー

ファイルと言います。どこにあるかは別に秘密でもなんでもありません。シェル (端末)で

ls /usr/include

と打ってみて下さい。たくさんある中にたぶん stdio.h もあるでしょう。ヘッダーファイルは/usr/include ディレクトリだけではなく他の場所にもありますが、プログラムで要求されるとコンパイラーが探し出して利用するようになっています。ヘッダーファイルの中を覗きたいと思っ

たら less や emacs で見ることができます。たとえば次のように打てばよいのです。

less /usr/include/stdio.h

less を終了するには�� ��q と打ちます。ls コマンドなどを使わず、ファイルブラウザーでこのファイ

ルを見つけて開くことも可能です。

ヘッダーファイルにはみんなが使える関数などの定義(の一部)が種類別に分けて入れられてい

ます。stdio.h には printf() 関数を始めプログラムの入出力を受け持ついろいろな関数があり

ます。stdio は standard input/output のことです。Aisatu()関数の定義の中で printf() 関数

を使っていますが、これが可能なのは printf() 関数の定義が入っている stdio.h ファイルを読

み込むように指定しているからです。#include <stdio.h> がその指定なのです。この行を消し

てコンパイルしてみて下さい。ちゃんとエラーになるでしょう。

ヘッダーファイルに入っている定義は完全なものではなく定義の一部です。stdio.h の中をよ

く見ると、関数の定義については頭の部分だけが書かれていて実際の処理方法は書かれていない

ことに気が付くでしょう。ヘッダーというのはもともとこの関数の頭の部分のことなのです。コン

パイラーの仕事の能率をあげるために、本体はまた別のところにしまわれています。こちらはオブ

ジェクトライブラリーと言います。ライブラリーのファイルは /lib や /usr/lib などに入ってい

13

Page 15: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

ます。ファイル名には、libc.a のように lib が始めに付いています。これらはテキストファイルではないので emacs などで読むことはできません。 ただ、

nm /usr/lib/libc.a | less

のようにするとどのような関数が入っているかを垣間見ることができるでしょう。less コマンドで表示しているので、上下の矢印キーでスクロールし

�� ��q で終了します。

1.7.4 区切り記号

セミコロン ; についてですが、C言語ではこれは何かの最後を示すために付けるものだと憶えておいてください。何かと何かの間の区切りを示す目的でセミコロンを使うような言語もあります

が、Cでは一つ一つの物の終りを示します。セミコロンを使うところでコロン : を間違って使って

しまうとエラーになりますが、コロンはコロンでまた特別な意味を持っているのでエラーメッセー

ジの意味はよくわからないかもしれません。コンマ , も単なる区切り以外の意味を持つ場合があ

り、こちらは間違って使っても文法的には正しいためにエラーにならないかもしれません。 これ

はより危険だということですから、コンマには気を付けましょう。

1.7.5 終了コード

main 関数の戻り値について説明しておきましょう。int が付いているので main の戻り値の型

は void ではなく int です。 int は整数 (integer)のことです。したがって、main がどこかから呼び出されると、ある整数値を返すということになります。関数が値を返す場合にはそれを return

に付けて指定します。 今の場合は return 0; と指定されていますから戻り値は無条件に 0 になります。main はどこから呼び出されているのでしょうか?簡単に言うと、UNIX のシステムからです。 ./hello とタイプすると、システムはプログラムを実行ファイルから読み込んでその中の

main 関数を呼び出します。main 関数が終了することによってプログラムが終了すると、システム

は main 関数が返した値を保存します。これをプログラムの終了コードといいます。終了コード に

は 0 と 1桁か 2桁の正の整数を使います。終了コードを使うとプログラムがうまく動いたかどうかなどの判断ができます。正常に終了した

場合の終了コードは 0 にすることになっています。異常事態によってプログラムを終了させるような場合は 0 以外の値を戻すようにします。この異常終了の終了コードは普通 1 ですが、特別な目的のためにそれ以外の値を使うプログラムもあります。 終了コードはシェルスクリプトで条件

分岐のために使われます。 また、$? というシェル変数には最後にシェルから起動したプログラム

の終了コードが入るようになっています。 たとえば、シェルで次のようにコマンドを入れてみて

下さい。dateecho $?date --eerrrroorrecho $?

最初の date コマンドは正常に実行され直後の echo コマンドでは 0 が表示されますが、2番目のdate コマンドには変なオプションを付けてわざとエラーになるようにしていますから、次の echoコマンドでは 0 でない値 (たぶん 1) が表示されます。

14

Page 16: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

練習問題

1. C の文字列 "How are you?\n" はどのような文字列ですか?それは "How are you?" とど

のように違いますか?

2. 先程の hello プログラムでは return に与える数値を変えると終了コードが変わるはずです。端末で ./hello と打って実行した後で echo $? と打てば、この終了コードが表示されます。

return 0; の 0 を 1 とか 10 に変更し、本当に効果が現れるかどうかをテストしなさい。

1.8 関数の活用

「関数」というと数学の関数をすぐ思い出すでしょう。 これは何か数値が与えられるとある規

則に基づいてそれに対応する値が決まるというものです。たとえば f(x) = 2x + 1 と定義された関数は、与えられた値の 2倍に 1を加えた値を計算して出します。 f(1) は 3、f(−2) は −3 ですね。 f(1) における 1 のように、関数に与える値を引数 (ひきすう)といい、f(1) の値 3 を関数値または戻り値または返り値といいます。

ここで大事なことは、関数の定義として計算方法を指定するときには 1 とか −2 とかの具体的な引数の値に対する計算手順を記述する必要は無く、任意の引数のかわりとして x という記号を

使って f(x) = 2x + 1 のように計算手順を記述すればよいということです。このように、関数の定義を記述するために実際の引数のかわりに使う記号を仮引数といいます。これに対して、f(1) の1 のように関数を使用する場合に指定する引数は実引数と呼びます。仮引数は x を使うことと決まっているわけではありません。別のものでも全く構いません。た

とえば、f(z) = 2z + 1 と定義された関数 f は前のものと全く同じで、f(1) は 3 で f(−2) は −3です。 計算の規則が同じなのだから当り前です。

C言語の関数でも、与えられたデータに基づいて計算して結果を返すようなものを作ることができます。データは引数として関数に伝え、計算結果は関数の戻り値として返します。上の f(x) と同じ計算をする C の関数の定義は次のようになります。

double f ( double x ) // 仮引数名は x、結果も引数も型は double{

return 2 * x + 1; // 2x+1 を結果とする}

行の途中から // で始まるコメントが入っています。コメントの範囲は // から その行末までと決

まっています。 この部分はもちろんプログラムに影響しません。

始めの double は関数の戻り値の型を規定するもので、括弧の中の double は引数の型を規定するものです。int は 整数だけを扱う場合に使いますが、double は実数の場合に使います。実数ですから 1 でも 2 でも良いし 1.111 でも 3.14159 でも行けます。 x は仮引数です。

f(x) = 2x+1 では簡単すぎておもしろくないので、式を少し複雑にして f(x) = x(x− 1)(x+1)に変えましょう。Cでの定義は次のようになります。

15

Page 17: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

double f ( double x ) // 仮引数名は x、結果も引数も型は double{

return x*(x-1)*(x+1); // x(x-1)(x+1) を結果とする}

関数の定義を書いただけでは何も実行しないことを思い出してください。この関数を呼び出して使

用する main 関数を作る必要があります。たとえば、f(1) はゼロになるはずですが、それを確かめるプログラムは次のようになります。

#include <stdio.h>

double f ( double x ){

return x * (x - 1) * (x + 1);}

int main(){

printf ( " %f %f \n", 1.0, f(1.0) );return 0;

}

これを kansuu.cc というファイル名で保存し、make kansuu でコンパイルし、./kansuu で実行

してください。

main 関数の中の printf には括弧の中に3つの引数が与えられています。printf の1番目の引

数は文字列 " %f %f \n" ですが、これは数値の表示形式を指定するためのもので、1.10節で説明します。2番目の 1.0 はただの数値、3番目の f(1.0) は関数値  f(1) を意味します。これで2つの数値の表示がおこなわれます。1.0 のように小数点を付けて書かなくてはならないのは、このデータが整数型ではなく「実数型」であることを示すためです。2カ所にある 1.0 をたとえ

ば -1.0 に変えて実行してみてください。

関数のグラフを描くのに使うため、このプログラムを改良して数百点分の関数値を計算して表示

するようにしましょう。これはコンパイラー言語が得意とする大量の繰り返し計算です。プログラ

ムは次のようになります。変数を使用し、繰り返しの構文を使っています。

16

Page 18: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

#include <stdio.h>

// double型の値を使って計算して double型の結果を得る関数 f の定義//double f ( double x ){

return x * (x - 1) * (x + 1); // f(x)=x(x-1)(x+1)}

// メイン関数//int main(){

double x = -2.0; // 初期値 -2 の実数変数 x を宣言while ( x < 2.0 ) // x が 2 を越えない限り次を繰り返す{

printf ( " %f %f \n", x, f(x) ); // x と f(x) を表示x = x + 0.01; // x を 0.01 増やす

}return 0;

}

main 関数ではまず x という double 型の変数の値を −2.0 にしています。変数を使う時はこのように型と変数名を書いてさらに初期値を与えます。これは変数の使用宣言と初期化です。最後にセ

ミコロンが必要です。この変数の名前は x ですが関数 f の仮引数である x とは関係ないので安心してください。main 関数の中の x をすべて別の名前、たとえば v としてもプログラムの意味は全く変わりません。

C では、関数の中で用意した変数や関数の仮引数はすべて自動的にローカル変数になり、その関数定義の中だけで有効です。 関数のローカル変数にその関数以外の場所からアクセスすることは

できません。 ただし、仮引数には関数呼び出しを通じて実引数の値がセットされます。 仮引数は

事務所の窓口、他のローカル変数は事務所内の金庫や引き出しのようなものです。ローカル変数の

名前はその関数以外の場所で使っている名前と重複してもかまいません。

while は 条件が成り立っている限り何かを繰り返すものです。条件は while のすぐ後の ( ) の中に書きます。繰り返すべきものはその次の { } の中に書きます。{ } の中全体が繰り返して実行

されます。上のプログラムでは、 while の繰り返しの中の2番目の文は x の値に 0.01 を足してそれで x の値を更新しています3。 x の値は繰り返しのたびに増えて行き、いずれ 2 以上になりますから、そこで繰り返しは終了し、main() 関数は戻り値 0 を返して終了します。このプログラムを保存して、コンパイル、実行を行って下さい。−2.0 から 1.99 までの x の値と

それに対する f(x) の値が表示されたでしょう。次のように、less を使うと全体を上下にスクロールさせながら詳しく見ることが出来ます。

./kansuu | less

| の左側のコマンドの出力が右側のコマンドの入力になります。less では上下の矢印キーで表示を上下にスクロールすることができます。ページ単位で大きくスクロールするにはスペースキーと bキーを使います。x = -1 や x = 0 や x = 1 のところで f(x) が 0 になっているのを確かめてくだ

3 = で左辺と右辺を結んだ形で代入をあらわしている。代入されるものがいつでも左である。それで、変数のように代入されることができるものを左辺値または leftside-value または l-value と呼ぶ。

17

Page 19: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

さい。less を終了するには q キーを打ちます。なお、while のくりかえしに関しては、もし繰り返すものが一つしか無い場合は { } を省いて

もよいことになっています。逆に、もし { } が無ければ繰り返すものは一つだけになります。 次

のように条件の後にセミコロンを付けてしまうと、そのセミコロンが繰り返しの対象とみなされま

す。 これはなにもしない空文です。

while ( x < 2.0 ); // これはだめだ!

この文では、くりかえしの中で x の値が変化しないので無限ループになってしまい、プログラムが終了しません。上のプログラムの while の右にセミコロンを入れるだけでこれをテストすることができます。プログラムを強制終了させるには、端末で

�� ��Ctrl�� ��c を打って下さい。

練習問題

1. f(x) = 2 ∗ x + 1 と g(z) = 2 ∗ z + 1 で定義される数学の関数 f と g はどのような意味で等

しいのでしょうか? はっきりと言ってみなさい。

2. 上では 3点でゼロになる関数を定義しましたが、同様な関数で、4点 x=-2, x=-1, x=1, x=2でゼロになり、x=0 で 1 になるものを考え、C の関数としての定義を書いてみなさい。

3. 上のプログラムの出力は正確には何行にわたるでしょうか?まずプログラムの実行過程を考えて予測を行ない、次にプログラムを実行して出力の行数を実際に数えなさい。( ./kansuu | wc

のように wc コマンドを使えばよい。) もし予測が間違っていたら、考え方のどこが間違っていたかを考えなさい。

1.9 数値の型

実数と整数を別々の種類のデータとして扱うのは、普通のコンピューターのハードウェアの構造

から来ていて、そういう扱いにした方が計算の能率が高くなるからです。コンピューターの内部

で数値を表現するには機械語と同様二進数を使っています。二進数の各桁はビットと呼びます。各

ビットはメモリー中の記憶素子に記録されたり、配線を通って伝達されたりします。データを表現

するのに必要なビットの数が増えると、記憶装置に入るデータ量が少なくなったり伝達効率が低く

なったりしますから、なるべく少ないビット数で精度良くデータを表現するように工夫されてい

ます。

整数の場合は、上限値と下限値を決めてその範囲のすべての値がある数のビットの組合せで一対

一に表現できるようにしてあります。値が有限個なので有限個のビットで正確に表現ができます。

一方、実数では有限の範囲を決めても数学的にはその間に無限個の実数が存在しますから、有限個

のビットでどの数も正確に表現できるようにするわけにはいきません。それで、実数の場合は、値

の範囲を決めるだけではなく、精度も決めて表現法ができあがっています。

なお、Cでは整数でも値の範囲が違う複数のタイプが存在しますし、同様に実数にも値の範囲と精度が違う複数のタイプがあります。それらの中で int と double が標準です。たとえば、32ビットと64ビットのパソコンの GNU コンパイラーでは、int 型の整数は 32 ビットで表現されます。32ビットだと、232 = 4, 294, 967, 296(約 42億)個の整数が表現できますが、半分ずつに分けて、ゼロ以上の整数に 2147483648個、負の整数にも 2147483648個を使用します。したがって、int 型の整数の範囲は、−2147483648から 2147483647までです。

18

Page 20: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

1.10 Cの出力とC++の出力

さきほどのプログラムでは、printf 関数に引数として文字列と、x と、f(x) が与えられていま

す。printf の最初の引数として与えられた文字列はタダモノではありません。これはそのまま表示されるのではなく % があると特別な効果を持ちます。それは表示文字列の一部を「他のデータ」で

置き換えるという効果です。「他のデータ」は2番目以降の引数として与えます。% の後にいろい

ろな記号や数値を組み合わせて置換えの方法を指定するのですが、この部分を書式指定文字列と呼

んでいます。

上のプログラムでは、printf の最初の引数の中に書式指定文字列 %f が2回入っています。始め

の (左の) %f は2番目の引数である x の値で置き換わり、次の%f は3番目の引数である f(x) の値

で置き換わります。最初の引数の他の部分はそのまま出力されます。例えば、x の値が 1.5 とすると、f(x) は 1.875 ですから、printf ( " %f %f\n", x, f(x) ) は一つの空白と 1.500000と四つの空白と 1.875000 と改行を出力します。ただし数値の表示桁数は処理系によって異なっていることがあります。

書式指定文字はデータのタイプごとに違いますし、同じデータタイプでも出力の形式が異なる複

数の書式指定文字があります。%f は実数を小数点付きで表示するように指定するものです。C の書式指定は独特で非常に細かい指定が可能ですが、実数の場合は %f、整数の場合は %d、それから

文字列の場合は %s を使うと覚えておけば最低限の役に立ちます。非常に大きくなったり0に非常

に近くなったりする可能性のある実数を表示しなければならないときには %g が役に立ちます。%g

は実数を必要に応じて指数形式で表示します。表示桁数などを指定することもできます。たとえば

%12.4f は実数を小数点付きで表示しますが 12文字分のスペースの中に小数部 4桁で右詰めで表示します。これは符号や小数点も含めてこの文字数以内になることを前提にして使用します。詳し

くは printf 関数のmanページ (man printf で表示される)などを見て下さい。C の暗号のような書式指定文字列を使う方法に対して、C++ ではストリーム出力というものを使うもっと簡便な方法もあります。ストリーム出力が使えるようにするために、まず、さきほどの

プログラムの始めの方に

#include <iostream>using namespace std;

と書いてください。iostream は input/output stream(流れ)のことです。using ... の行は、C++の初心者が楽をするためのおまじないです。#include <stdio.h> はいらないので、取ってしま

うか、// を付けてコメントにしてください。それから printf ( " %f %f\n",x, f(x) ); を次

のに入れ換えてコンパイル、実行をやってみてください。

cout << x << " " << f(x) << "\n";

coutは標準出力ストリームと呼ばれるものです。これは一種の変数ですが単純な変数ではなくデー

タを端末に出力する機能をもった「オブジェクト」です。<< は挿入演算子と言い、データを出力

ストリームに送ります。挿入演算子とデータは上のようにいくつでも繰り返すことができます。こ

の例では、x の値、空白文字、f(x) の値、改行文字が順に出力ストリームに送られ表示されます。

実数値や文字列のような異なる型のデータを混ぜて使うことができます。

複数のデータを出力するときにそれらをコンマでつないで並べて書く言語もありますが、C++のストリーム出力では << でつながなくてはなりません。C や C++ ではコンマは文脈によって異

19

Page 21: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

なる意味を持つのでてこずることがあります。たとえば

cout << x << y;

と書くところを間違って

cout << x, y;

と書いてしまっても文法的にエラーにはなりませんが期待していない表示が出ます。とにかくコン

マには気を付ける必要があります。

ストリーム出力で表示方法の細かい指定を行うには C++ の深い知識が必要になりますが、表示幅の指定だけなら簡単で効果があります。このためには

#include <iomanip>

をプログラムの始めの方に書き足してください。iomanip は input/output manipulation のことです。using ... のおまじないは一度で十分です。それから、 cout ... のところを次のように書き直してコンパイルして実行してみてください。

cout << setw(10) << x << setw(16) << f(x) << "\n";

setw は set width のことです。setw(10) で次の x の表示が入る幅を指定し、setw(16) で f(x) の

表示が入る幅を指定しています。これで、表示が縦に揃います。十分広い幅を取ったので、空白文

字の出力が不要になりましたが、もし x の表示と f(x) の表示がぶつかってしまう恐れがあるな

ら空白を一つだけ間に出力すると安心です。数値データの出力にこれ以上の細かい指定が必要な場

合はむしろ printf 関数を使ったほうが手軽でしょう。

なお、printf と cout を混ぜて使うことは可能ですが、その場合は表示の前後関係が狂うことがあります。これを防ぐには、出力を行なう前に一度だけ次を実行するようにします。

iostream::sync_with_stdio();

これは、iostream クラスに属する関数 sync_with_stdio を呼び出しています。C++ ではこのように「クラス」の名前を付けて指定する必要のある関数もあります。 しかしここではまだクラ

スについての説明はしません。sync_with_stdio は、 “synchronize with standard I/O” のつもりです。

1.11 グラフのプロット

上のプログラムの計算結果をグラフにしてみましょう。グラフを表示したり印刷したりできるプ

ログラムはいろいろ存在し、これらはグラフの目盛を付けるような複雑な仕事を自動でこなしてく

れます。 ここでは gnuplot を使いましょう。ほかに、GNU Plotting Utilities という名前でグラフに関係のあるいくつかのプログラムを集めたものも手軽で使いやすいでしょう。

あらかじめ kansuu を実行して計算結果を保存しておく必要などはありません。端末ウィンドウで、カレントディレクトリに実行ファイル kansuu があるのを確かめてから、 gnuplot と打って

gnuplot を起動して下さい。gnuplot> というプロンプトが出ますから、そこに次のコマンドを入

力して下さい。

plot ’< ./kansuu ’

20

Page 22: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

クォーテーションマークはシングルクォートです。 グラフは別のウィンドウが開いてそこに表示

されます。 このウィンドウは以後開けっぱなしで結構です。

グラフに小さな点を使うには、

plot ’< ./kansuu ’ with dots

データ点を線でつなぐには

plot ’< ./kansuu ’ with lines

とやります。.... with impulses でグラフを描いても面白いでしょう。

gnuplot でもシェルのようにコマンドの履歴機能が使えます。上向き矢印キーで前のコマンドを呼び戻すことができるので活用して下さい。オンラインヘルプの機能も組み込まれています。

help plot などとやればコマンドの説明が英語で表示されます。help モードから出るには、q やEnter キーを押します。gnuplot については、日本語の本も出ていますし、web でも詳しく調べることができます。

-6

-4

-2

0

2

4

6

-2 -1.5 -1 -0.5 0 0.5 1 1.5 2

’<./kansuu’

端末ウィンドウをもう一つ開けてあれば、gnuplot を動かしたまま、プログラムの変更とコンパイルができ,結果をすぐにグラフにすることができます。端末ウィンドウでは、作業用ディレクト

リーがカレントディレクトリーになっている必要があります。次のように関数を修正してコンパイ

ルし、グラフを表示してみて下さい。前のグラフとどこが違うでしょうか?

double f ( double x ){

return x * (x - 1.5) * (x + 1.5);}

おまけで、もう一つ違うプログラムでデータを作ってグラフを見て下さい。平面上を飛び移りな

がら移動する点を考えます。現在の位置を式に入れて計算して次の位置を決めます。このような関

係式を漸化式といいます。

21

Page 23: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

#include <iostream>#include <iomanip>using namespace std;

int main(){

double x = 1; // x座標double y = 0; // y座標int i = 30000; // 総繰り返し回数while ( i > 0 ){

cout << setw(16) << x << setw(16) << y << "\n";double next_x = - 0.97 * x + y - 5 + 5 / (1 + x * x); //次の xdouble next_y = -0.995 * x; //次の yx = next_x; //更新y = next_y; //更新i = i - 1; // 残り繰り返し回数を減らす

}return 0;

}

このプログラムを tsubasa.cc というファイル名で保存し、

make tsubasa

でコンパイルします。コンパイルがうまくいったら、gnuplot の中で

plot ’< ./tsubasa’ with dots

とやればなかなかの絵を見ることができます。

グラフを印刷するには、グラフを PostScript 形式で出力します。PostScript はプリンターに印刷データを送るためのデータ形式で、一般的に使われているものです。印刷の前に出来上がりの様

子を確認したり別の文書の中に挿入したりすることが多いですから、そういうことができるように

PostScript データを一旦ファイルに収めます。gnuplot では次のように操作します。

set term postscript portrait # PostScript 形式、用紙縦向きset output ’tsubasa.ps’ # 出力ファイル名plot ’< ./tsubasa ’ with dots # 通常の plot コマンドset output # 出力ファイルを閉じるset term x11 # X 画面への出力に戻す

22

Page 24: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

これでカレントディレクトリーの tsubasa.ps というファイルにグラフが保存されます。それを確かめるには ggv または gv または evince コマンドを使います。端末で

evince tsubasa.ps

のように打てばよいのです。

練習問題

1. 上のプログラムの中の繰り返し回数を増減するとグラフで表示される点の数も増減することを確かめなさい。

2. 上のプログラムで xと yの初期値を x=0, y=0 にすると、いくら計算を繰り返してもこれらの値はゼロのままであることが予測されますね。プログラムを修正して走らせてこのことを

確認しなさい。また、xや yの初期値をゼロとは少し違ういろいろな値にした場合のグラフ表示を見て、何か気付いたことを述べなさい。

1.12 Mathematica を使っている人へ

Mathematica でも外部プログラムの出力を使ってグラフを描くことが出来ます。自分で調べる手間を省きたければ、kansuu と tsubasa があるディレクトリでMathematica を起動して次のように入力してみて下さい (各行末で

�� ��Shift�� ��Enter を押す)。

data1 := ReadList["!./kansuu", Number, RecordLists->True]data2 := ReadList["!./tsubasa", Number, RecordLists->True]ListPlot[data1]ListPlot[data2]

data1、data2 は := で定義されているので、プログラムを修正してコンパイルし直した後、グラフを描き直すには ListPlot コマンドだけで出来ます。

Mathematica でもプログラミングができるのになぜ C を使うのでしょうか?こういう問題の答えに興味のある人は、Mathematica で同等のプログラムを作り、プログラムの見かけや実行速度を比較してみて下さい。結局は好きずきだという結論になるかもしれませんがね。

問題

繰り返しにかかる時間

繰り返し計算にかかる時間は繰り返しの数に比例しているかもしれません。上の tsubasaプログラムの中の繰り返し回数をもっと増やし、いろいろに変更して実行時間を時計などで測り、この仮

説がどの程度正しいかを調べなさい。

time コマンドを使って時間を測る場合は、time ./tsubasa とやって実行し、実行後に表示される3つの時間の内、real と付いたものを見るとよろしい。

gnuplotの中で実行する場合と単独で実行する場合とで実行時間は異なるでしょうか?

23

Page 25: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

実数計算の誤差

C の標準ライブラリーにある sqrt という関数は実数の平方根を計算する関数です。使い方をこの関数についての man ページを見て確かめ、次のことを順に行うプログラムを作りなさい。そのとき実数の表示桁数や表示形式についてよく検討し、変数の型によって精度が違うことの証拠が得

られるようにし、証拠にもとづいて推理を行い結論をまとめなさい。

なお、printf ではなく C++ のストリーム出力を使いたい場合は、setprecision() というマニ

ピュレーターで表示精度を指定するとよい。たとえば、cout << "The square root of 2 is "

<< setprecision(20) << droot << endl;

1. Number という名前の定数が適当な実数値、たとえば 2.0 をあらわすようにする。(後でこの値をいろいろ変えてみる)

2. Number の平方根を計算して double 型の変数 droot に記憶しその値を

The square root of 2 is 1.4142135623730951455

のような形式で表示する。小数点以下は 20桁ぐらい表示する。

3. droot の値を 2乗して得た値を double 型の変数 dsquare に記憶し、上と同様に説明付きで表示する。

4. dsquare の値と Number の値との差を適当な説明を付けて表示する。

5. float 型の変数 froot と fsquare について 2~4 と同じことを行う。

名前の付け方

名前は定数、変数、関数、引数、ファイルといったいろいろなものを表すために使われます。名

前はそれが表すものの種類や使用目的を反映するように決めるべきです。その際、一貫した方針の

元で名前を作ることが大切です。一つのプログラムの中だけでの一貫性から一つの組織が作るプロ

グラムの全てにわたる一貫性まであります。C のプログラムで使う名前の作り方の原則を自分なりに定めて文章にまとめなさい。そのとき、大文字、小文字の使い方、名前の長さ、品詞による区

別、区切り方などについて使用目的毎に考えるとよいでしょう。

関数のグラフ

物理の教科書に載っているような、抵抗を受けて時間とともにだんだんと小さくなる振動のグラ

フを描こうと思います。振動の振幅 A と 振動の変移 x は時間 t の関数であり、次のようにあら

わせます。

A = M exp(−t/S)

x = A cos(2π t/T )

S は振動の持続時間、T は振動の周期、M は初期振幅で、これらは定数です。S と T と M の値

を適当に決めて、 x の t による変化をあらわすグラフを描きなさい。このとき、なるべく滑らか

で精度の高いグラフを得るように工夫しなさい。

24

Page 26: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

1.13 中間まとめ

ここまでに出てきた事項のまとめです。

• プログラム作成の手順

プログラム作成のプロジェクトを立ち上げるには、まずわかりやすい名前を考えて作業ディ

レクトリーを作る。作業はこのディレクトリーに移って行う。ディレクトリーを作るコマン

ドは mkdir,ディレクトリーに移るコマンドは cd,カレントディレクトリーを調べるコマ

ンドは pwd, ファイルのリストを表示するコマンドは ls -l.

ソースプログラムはテキストエディターで書いてファイルに収める。ファイル名には拡張子

.cc を付ける。make コマンドでコンパイルし、実行ファイルをディレクトリーといっしょに指定して実行する。

たとえば、

emacs xyz.cc &

でソースプログラムの編集を始める。emacs の中で�� ��Ctrl

�� ��x�� ��Ctrl

�� ��c で保存、端末で

make xyz

とやって実行ファイル xyz を作り、

./xyz

でそれを実行。

• データ型

整数をあらわす型は int、実数をあらわす型は double が代表的である。小数点も指数も無い数値リテラルは int 型とみなされる。小数点があるか、12e-9 のように指数がある場合は

double とみなされる。文字列リテラルは ダブルクォート ” で囲まれた文字の並びである。文字列リテラルでは改行を \n であらわす。

• 関数定義

関数の定義とは一連の処理に名前を付けること。関数が呼び出されるときにデータを受け渡

すため引数と戻値を使う。

• main 関数

他の関数定義とは違って、main関数はプログラムが実行されるときに自動的に呼び出される。

• while 文

基本的な繰返し構文。

• C++ のストリーム出力

C++ のストリーム出力は簡単に使える。iostream ヘッダーと必要なら iomanip ヘッダー

をインクルードする。namespace の指定も忘れずに。

25

Page 27: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

第2章 入出力と時間の制御

プログラムは普通それだけで独立して動作するのではなく、人間など外部と関係しながら動作し

ますから、外部の実在する世界との連絡を考慮してプログラムを作る必要があります。たとえば、

ソースプログラムの中に式を書けば計算はできるわけですが、 printf の様な出力機能を呼び出すようにしなければ結果を見ることができず役には立ちませんね。 ここではプログラミングのこの

面に焦点を当て、実行時に外部からデータを入力するプログラムと、時間を測りながら決まったテ

ンポで動作するプログラムをとりあげます。

プログラムは計算の結果を出力するだけではなく、計算に使うデータを実行中に外部から入力で

きるようにすることもできます。そのようにすると、いろいろ異なるデータを使った計算や異なる

条件のもとでの計算がひとつのプログラムでできるので、プログラム作りの苦労のしがいがありま

すし、他の人に使ってもらえる可能性も増えます。実行中に外部からの入力を受け付けるようにす

るには、人間が入力したデータを解釈して内部で使えるようにする変換の機能と、でたらめな入力

が来た場合の対処を行う例外的処理の機能が必要になります。

プログラムの実行速度はコンピューターの性能によって左右されます。 また、普通は一つのコ

ンピューターの上で複数のプログラムが同時に動いているので、同じコンピューターを使っても、

場合によってプログラムの実行速度が変化します。 したがって、プログラムの実行速度を正確に

予測することはできないということになります。しかし、人間や外部の機械と共同して動くゲーム

や制御プログラムなどは、一定のテンポや反応速度を保って動作する必要があります。コンピュー

ターに内蔵されているタイマーをプログラムから利用することによって、それが可能になります。

2.1 文字

プログラムと外部とのやりとりには、人間の言語をまねて文字を使うのが最も一般的です。その

ようにすると、人間とのやりとりに都合が良いことはもちろんですが、プログラマーの間でデー

タの解釈が違うのを防ぎ、違うプログラムの間でのデータの互換性を保つことが簡単になるから

です。

まず 1文字 1文字の表現方法、次に単語や文のような文字列の扱いについて調べましょう。

2.1.1 16進法

ディジタルの世界ではすべての情報は究極的にはビットの集まりとして表現されます。ビットの

どのような組合せがどのような情報をあらわすのかをあらかじめ決めておき関係者みんながそれを

使うようにすることで、情報を伝達することができるようになります。

文字情報の場合は、使用する文字の集合およびその中の各文字とビット列との対応を決めます。

これをコード系と呼んでいます。 ある文字に対応するビット列がその文字のコードです。 コード

はビットの列なので、それをそのまま表現するとたとえば、

26

Page 28: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

OFF/ON/OFF/OFF/ON/ON/ON/OFF

のような感じになるでしょう。 OFF と ON のかわりに 0 と 1 を使って

01001110

のようなあらわし方をしても同じことです。このあらわし方は 2進法ですから、ビット列と 2進数は同じです。

ON と OFF を使っても 0 と 1 を使っても、このようなあらわし方は長たらしくてスペースのロスになります。かと言って、頑張ってこれを 10進法に直して1 78 とあらわすと、元のビット列に戻すにも頑張りが必要になりエネルギーと時間のロスになります。そこで 16進表示が登場しま

す。 16進表示はビット列から簡単に得ることができます。 それには、ビット列を右端から 4ビット毎に区切って、各区切りを 1つの桁とみなします。 一つの区切りは 4ビットですから、24 = 16とおりの状態があり得ます。したがってこのように区切ったこと自体で 16進表現が得られたことになりますが、さらに 16とおりの状態に適当に数字や文字をあてはめて、文字を節約します。たとえば、上の 01001110 を 4ビット毎に区切ると

0100/1110

のように、0100 と 1110 が 16進表示の各桁になりますが、0100は 10進で 4、1110は 10進で 14ですから、上のビット列は

4/14

のようにあらわすこともできます。 これは、各桁は 10進数であらわしている点で、 時刻を 60進法で 4:14 のようにあらわすのと同様です。 16進法の各桁の重みは、右から、160 = 1, 161 = 16,162 = 256, 163 = 4096, 164 = 65536, ... です。 たとえば、16進の 4/14 を 10進法に直すには、14 × 1 + 4 × 16 = 78 と計算すればよいのです。

16進法の各桁のあらわし方として、0から 9まではそのままにし、10から 15まではアルファベットに置き換える方法もあります。大文字を使う場合も小文字を使う場合もあります。下表参照。

4ビットのビット並びと16進数の対応-----------------------------------------------------OFF/OFF/OFF/OFF 0000 0 0 0OFF/OFF/OFF/ON 0001 1 1 1OFF/OFF/ON/OFF 0010 2 2 2OFF/OFF/ON/ON 0011 3 3 3OFF/ON/OFF/OFF 0100 4 4 4OFF/ON/OFF/ON 0101 5 5 5OFF/ON/ON/OFF 0110 6 6 6OFF/ON/ON/ON 0111 7 7 7ON/OFF/OFF/OFF 1000 8 8 8ON/OFF/OFF/ON 1001 9 9 9ON/OFF/ON/OFF 1010 10 A aON/OFF/ON/ON 1011 11 B bON/ON/OFF/OFF 1100 12 C cON/ON/OFF/ON 1101 13 D dON/ON/ON/ON 1110 14 E eON/ON/ON/ON 1111 15 F f-----------------------------------------------------

1 01001110 = 0 × 27 + 1 × 26 + 0 × 25 + 0 × 24 + 1 × 23 + 1 × 22 + 1 × 21 + 0 × 20 = 72

27

Page 29: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

このあらわし方だと、4/14 の 4 はそのままですが、14 は E になり、4/14 は

4E

とあらわされます。 各桁は常に 1文字ですから / のような桁の区切りは不要です。16進数を 2進数に直すには、2進数から 16進数を得る方法の逆をやるだけです。つまり、上の表を使って、16進数の各桁を 4けたの 2進数に直し、全体をつなげばよいのです。

練習問題

1. 3桁の 16進数 1EA を 2進法と 10進法であらわしなさい。

2. 2進法を 8進法に変換するときにも 16進法の場合と同様に「区切る」方法が使えます。 その方法を説明しなさい。

2.1.2 ASCII コード

文字をあらわす様々なコード系がありますが、その中で現在最も広く通用するものは「ASCIIコード」 (American Standard Code for Information Interchange) です。 これは英語の文章を作るのに必要な文字をあらわすことができます。各国の規格は ASCII をほぼ含むように作られているので、英語でなら世界中どことでも通信ができるようになっています。

ASCII コードの文字集合は英語のアルファベットとアラビア数字といくつかの記号、それに 30個程の制御文字からなり、全部で 128文字あります。27 = 128 ですから 7 ビットで表現できます。16進表記では左の 3ビットと右の 4ビットに分けます。 たとえばアルファベット大文字の N は先程の例で使われた 4E がそのコードです。 4/14 でも同じですね。 下の表は ASCII コードの概略です。

--------------------------------------------------------------16進コード 文字種類--------------------------------------------------------------00...1F, 7F 制御文字20 スペース (1文字分の空白)21...2F, 3A...40, 5B...60, 7B...7E 記号30...39 数字 (0 から 9)41...5A 大文字 (A から Z)61...7A 小文字 (a から z)--------------------------------------------------------------

ASCII コードの完全な対応は man ページで 8進法 (octal)、10進法 (decimal)、16進法 (hexadec-imal)で見ることができます。項目名は ascii です。日本語の man ページもあるでしょう。

man ascii

と打てば表示されます。制御文字は名前で表されます。

実際の文字データがコードでどのようにあらわされるのか調べるために、次のような 3行のデータファイルを用意してください。ファイル名は characters とします。

0123456789ABCWXYZ abcwxyz!"#$

28

Page 30: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

Emacs でこれを打ち込んで保存すればよいでしょう。次に、シェルで

cat characters

とやると、内容が文字のまま表示されます。これはあたりまえ。次にコードを表示するため、大文

字、小文字に注意して次のように打ってください。

hexdump -C characters

次のような結果が得られます。00000000 30 31 32 33 34 35 36 37 38 39 0a 41 42 43 57 58 |0123456789.ABCWX|00000010 59 5a 20 61 62 63 77 78 79 7a 0a 21 22 23 24 0a |YZ abcwxyz.!"#$.|

30 31 ... と 59 5a ... は各文字のコードを 16 進であらわしたものです。左端の 00000000 と00000010はファイルの先頭からの位置を 16進で表しています。こういう表示をダンプといいます。数字の並びと大文字の並びの間にコード 0a の制御文字がはさまっていることに気が付きましたか? これは行と行の間に入る改行文字です。ascii man ページによると、この文字名は LF です。LF は ’Line Feed’ で、紙を 1行分送るという意味です。また、大文字の並びと小文字の並びの間にあるコード 20(16進) はスペース、文字名 SPACE です。

2.1.3 C 言語での文字の扱い

C では文字をあらわすための専用の型は無く、文字を対応する文字コードの値の整数として扱います。整数ですから数式の中で使うことができます。たとえば、文字 A のコードに整数 1 を加えて得られる値は A の次の文字である B のコードですから、 1 を加える操作は次の文字を得るという機能があります。しかし、コード表の最後の文字のコードに 1 を加えても対応する文字はありません。C にはそのような意味の無い操作を自動的にチェックする機能はありません。文字を整数で扱うので int 型の変数で文字を記憶することができます。 しかし ASCII 文字のコードの範囲は 0 から 127 までしかありませんから、大量の文字を int 型で記憶すると不経済です。 そこで、char 型というもっと短い整数型も使えるようになっています。 char は charactersを縮めたものですが、文字コードの値しか入れられないというわけではなく、-128 から 127 までの整数を入れることができます。コンピューター内では char 型は 8 ビットを使って表現されます。8 ビットは 1 バイトともいいます。( 8、16、32、64 は CPU が一度に処理する情報の単位なので、数値型の大きさは処理効率を考慮してこれらの中から選ばれる。)

C で文字を表示するには数値コードを対応する文字として表示するように指示する必要があります。次のプログラムは A から Z までの大文字アルファベットを表示しますが、同時にそれらに対応するコードを 10進数として表示します。コードは 10進では 65 から 90 までです。 たとえば、oomoji.cc というファイルにこのプログラムを収め make oomoji とやると実行ファイル oomoji

ができますから、 ./oomoji で実行します。./oomoji | less とやると出力の全体を見るのに便

利です。

29

Page 31: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

// 文字と文字コードを表示するプログラム//#include <cstdio>using namespace std;int main(){

int c = 65;while( c <= 90 ){

printf ( "%c %d\n", c, c );c++;

}return 0;

}

“c++” は c = c+1 を短く書く記法です。 整数変数 c は初期値が 65 で、 while による繰り返しの中で 1 ずつ増やし、c が 90 を越えると終了しますから、printf が受け取る c の値は 65, 66, 67,... , 90 と変化して行きます。printf の書式制御文字列の %c は整数を文字として出力するように指

示するものです。 %d は 10進表示を指示するものでしたね。 このように、同じ値でも書式指定によって文字になったり数値になったりして表示されるわけです。

このプログラムのように、繰り返し処理では、変数の初期設定と、変数の値の更新を行うことが

多いので、それらを含めて指定することができる for 文もよく使われます。下のプログラムは上

のプログラムと全く同じ意味です。

// 文字と文字コードを表示するプログラム//#include <cstdio>using namespace std;int main(){

for(int c = 65 ; c <= 90 ; c++)printf ( "%c %d\n", c, c );

return 0;}

for 文ではキーワード for のすぐ後の括弧の中に3つの式をセミコロン ; で区切って入れます。こ

れらは変数の初期設定、繰り返し条件、および変数の更新です。 このセミコロンの使い方は特殊

ですから注意して下さい。 3番目の式の後にはセミコロンを付けません。 while 文と同じく for 文でも繰り返されるものが複数の文である場合は、それらを中括弧で囲んで複文にする必要がありま

すが、今の例では printf だけですから中括弧は不要です。文字コードを 16進数で表示するように変更しましょう。 printf での表示を 2 桁の 16進表示に変えるには上のプログラムの書式指定の %d を %X に変えます。プログラム中の定数も 16 進数で書くことにします。 それには、65 を 0x41 に、90を 0x5a に置き換えます。 プログラム中では、

このように 0 と x を付けて 16進数であることを示します。小文字を使うのは一種の習慣です。

30

Page 32: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

// 文字と文字コードを表示するプログラム (16進表示を使用)//#include <cstdio>using namespace std;int main(){

for(int c = 0x41 ; c <= 0x5a ; c++)printf ( "%c %X\n", c, c );

return 0;}

次のように、ソースプログラム中の文字コードを数値ではなく、文字そのものであらわすことも

できます。

// 文字と文字コードを表示するプログラム (文字定数を使用)//#include <cstdio>using namespace std;int main(){

for(int c = ’A’ ; c <= ’Z’ ; c++)printf ( "%c %X\n", c, c );

return 0;}

’A’ のように文字をシングルクォートで囲んだものはその文字のコードを意味し、文字定数と言

います。 たとえば、’A’ は 65 とも 0x41 とも同じです。 (ただし後で述べますがデータ型に関してはちょっとした違いもあります。)今、c は int 型変数としていましたが、これを char 型に変えてみて下さい。char でも int でも計算は同様に行われますし、出力形式は書式制御で決めていますから char でも int でも値が同じなら出力も同じになります。

さて、C++ でも char 型が整数の一種であることには何も変更はありませんが、出力ストリーム cout による出力は、従来方式の printf による出力とは違って書式指定ではなくデータの型によって自動的に出力形式が制御されるようになっています。 それを次のプログラムで確かめてみ

ましょう。

31

Page 33: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

// 文字と文字コードを表示するプログラム (cout, char)//#include <iostream>using namespace std;int main(){

for(char c = ’A’ ; c <= ’Z’ ; c++)cout << c << " " << int(c) << "\n";

return 0;}

始めに cout へ c を送っていますが、これは char 型なので文字としての表示になります。 int(c)は char 型である c の型を int に変換するもので、型変換といいます。型の解釈が変わるだけで値が変わるわけではありません。int 型にしたので、これを cout に送ると普通の整数としての表示になります。これは文字コードです。(10進数での表示になります。)整数を 16進で表示するには次のように hex という マニピュレーターを使います。このマニピュレーターには引数がありません。 このようなマニピュレーターを使うには iostream 以外のヘッダーファイルはいりません。 setw も使えば桁数の指定ができますが、その場合にはヘッダーファイル iomanip がいります。

// 文字コードと文字を表示するプログラム (C++, char, hex)//#include <iostream>using namespace std;int main(){

for(int c = ’A’ ; c <= ’Z’ ; c++)cout << char(c) << " " << hex << c << "\n";

return 0;}

このプログラムでは、c は int 型にしましたから、文字としての表示のためには char(c) と書く必要がありますが、コードの表示は int(c) ではなく c でできます。今度は大文字アルファベットではなく制御文字でやってみましょう。制御文字はそのままでは画

面に表示できませんが hexdump コマンドを使って表示する予定にします。したがって混乱を避けるため数値としての出力はやめておきます。表によると、制御文字のコードは 00から 1Fまでと 7Fです。 7F だけが他から離れていますから別途に出力する必要があります。これらのコードの表現は 16進ですから、C では 0x を付ければそのまま使えます。次のプログラムを controlchars.cc

に保存してコンパイルし、controlchars という実行ファイルを作って下さい。

32

Page 34: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

// 制御文字を出力するプログラム//#include <iostream>using namespace std;int main(){

for(int c = 0x00 ; c <= 0x1f ; c++)cout << char(c);

cout << char(0x7f);return 0;

}

次のように、hexdump コマンドを通すとこのプログラムが出力する文字のコードを表示します。

./controlchars | hexdump -C

プログラムの中で 0x7f を出力しているところでは、コードを char 型として cout へ送るためにchar(0x7f) という書き方をしています。0x7f は数値定数であり、数値定数は 16進数でも 10進数でも通常 int とみなされるからです。

C++ では ’A’ のような文字定数は期待どおり char 型になります。文字定数は表示可能な文字だけではなくスペースや制御文字をあらわすこともできます。 たとえばスペースは ’ ’、改行文

字は ’\n’ です。’\n’ のようにあらわすことができるのはタブ (’\t’) や 改ページ (’\f’) などいくつかの制御文字だけですが、どの文字でも 16進コードを使って ’\x7f’ のようにあらわすことができます。C++ では 0x7f は int 型、’\x7f’ は char 型で、両者は値は同じですが型が異なります2。

上のプログラムを真似れば、コード 0x00 から コード 0x7f までの文字をすべて出力するプログ

ラムもできます。 そのプログラムの出力を hexdump コマンドに通して適当に表示すると ASCIIの文字集合の全体がわかります。

この場合、変数 c を int 型ではなく char 型にすると困ったことになります。char 型の最大値は127=0x7f ですから、 for 文の繰り返し条件を c <= 0x7f にするとプログラムが終了しなくなる

のです。 賢いコンパイラーならこのことを警告して来ますが、実行ファイルは作られます。

C では、型の範囲を越えて値を増加させようとしても正しくない値になるだけでエラーで終了したりはしませんから、暴走状態になります。 そうなったら

�� ��Ctrl�� ��c で強制終了しなければなり

ません。 このプログラムの出力を hexdump コマンドに通しさらに less コマンドにつないでいる場合は、

�� ��Ctrl�� ��c ではなく

�� ��q を打って全体を止めることができます。別にコンピュータが壊れる

わけでも大切なデータが無くなるわけでもありませんから、安心して暴走させてみてください。

2.2 文字列 (strings)

数値の表示には複数の数字が使われますし、英単語や英語の文の表示には複数のアルファベット

や記号が使われます。 コンピューターはこのような文字の並び、すなわち文字列を処理している

わけです。当然、C でも文字列の処理は大事な機能です。ですから、数値を文字列に変換したり、文字列どうしをつないだり、その他文字列に関するあらゆる機能が標準関数として用意されてい

ます。2C では、文字定数は int 型として扱われます。また、型の変換は (char)c のように書きます。

33

Page 35: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

プログラム中で、 "Mojiretsu" とか "This is a pen." のように、ダブルクォートで囲って書

くと文字列定数になります。 文字列定数の表示については前の章ですでに出て来ていますね。 し

かし、文字列になんらかの処理を行うには文字列を記憶する仕掛けが必要です。 これが文字列変

数です。 C の文字列変数は char型整数を並べて配列という構造にしたものです。 一般に、配列変数は、同じ型の変数を複数並べてひとかたまりにし、その全体に一つの名前を付けたものです。

その各要素は全体に付けた名前と要素を示す通し番号とで指定します。

2.2.1 文字列変数

まず、文字列変数を作りその中を調べてみましょう。次のプログラムをmojiretu.cc という名前で保存してコンパイルし、mojiretu という実行ファイルを作って実行してください。

// 文字列の中を表示する。//#include <iostream> // cout と hex に必要#include <iomanip> // setw に必要using namespace std;int main(){

char s[ ] = "An example"; // 文字列変数 s の初期化int n = sizeof s; // s のサイズを記憶するcout << " Size of s: " << n << "\n"; // サイズを表示cout << " String in s: " << s << "\n"; // 中の文字列を表示for (int i = 0; i < n ; i++) // 要素 0 から n-1 まで{

cout << " Element " << dec << i // 要素番号と<< setw(4) << hex << int(s[i]) // 16進コードと<< " " << s[i] // 文字と<< ’\n’; // 改行を出力

} // forループおわりreturn 0;

} // main 関数おわり//

このプログラムの出力は次のようになります。Size of s: 11String in s: An exampleElement 0 41 AElement 1 6e nElement 2 20Element 3 65 eElement 4 78 xElement 5 61 aElement 6 6d mElement 7 70 pElement 8 6c lElement 9 65 eElement 10 0

main 関数の中の 1行目は初期値 "An example" を持った文字列変数 s を宣言しています。 型名char と配列名 s と中身の無い括弧 [ ] で、 s が文字型の配列であることを示し、その後の等号

と文字列定数で s の初期値を指定しています。sizeof s は、s の大きさをバイト数で測った値を

34

Page 36: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

意味します。 この値は整数変数 n に記憶され、後で cout を使って表示されます。 1文字は 1バイトを占めますから、文字型の配列 s の大きさは要素数と同じです。

for 文では配列の各要素の番号 i と要素の値 s[i] の整数表現およびその文字表現を出力してい

ます。 配列の要素番号はインデックスとも言い、C ではゼロから始まります。 したがって、最後の要素のインデックスは n ではなく、n-1 です。 だから for 文の初期設定には、 i=1 ではなく

i=0 が使われ、 繰り返し条件には、 i<=n ではなく i<n が使われているのです。インデックス iに対応する要素は verb—s[i]— と書くわけですが、これは char 型ですからそのコードを表示するには int 型に変換する必要があります。 s[i] をそのまま cout に送ると文字として表示されます。

2.2.2 終端記号

配列の最後にコードゼロの文字が知らない間に入っていることに注意して下さい。これは制御文

字で、ASCII では NUL と呼ばれます。 文字列の初期値をいろいろ変えて試してみなさい。 い

つも最後に NUL が入り、配列のサイズはそれを含んだ文字数になることがわかります。文字列変数における NUL の役割を調べるために、上のプログラムの main 関数の中の

char s[ ] = "An example";

の直後に次の文を挿入してみて下さい。

s[5] = 0; // 第 5要素に 0を代入

これで保存、コンパイル、実行すると、次のような表示が得られます。Size of s: 11String in s: An exElement 0 41 AElement 1 6e nElement 2 20Element 3 65 eElement 4 78 xElement 5 0Element 6 6d mElement 7 70 pElement 8 6c lElement 9 65 eElement 10 0

文字列としての表示が 5文字目までで切れて “An ex” になったことと、配列の始めから 6番目の要素である要素 5 に 0 が入ったことが前との違いですね。 しかし配列のサイズは前と同じです。

C では、配列を一旦宣言すると後でその要素数を変更することはできないことになっています。これはプログラムの実行効率を重視するためです。 そこにいろいろな長さの文字列データを格納

するために、NUL で文字列の終りを示すことにしてあるのです。これを終端記号と呼びます。終端記号自体は文字列には含まれませんし、終端記号より後ろにある文字も文字列には関係ありませ

ん。cout はこの規則によって文字列の終りを認識するようになっているので、文字列としての表示が上のようになったのです。

文字列を扱う関数などは全て終端記号 NUL を文字列の終りと見倣す規則に従っています。 このような方法で文字列を扱うので、NUL を含むような文字列を扱うことはできません。 NUL は普通「何もしない」という機能を持つ制御文字です。 これはデータ通信において何も送るデータ

35

Page 37: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

が無いときの埋め草として送られる無意味な文字として使われます。 文字列にこれが入っている

必要は無いので、これを終端記号として使っても差し支えが無いのです。

文字配列に格納できる文字の数は配列の大きさによる限界があることに注意して下さい。 格納

し得る最大の文字列の文字数は終端記号を除けば配列の大きさから 1を減じた数です。

2.2.3 文字列変数の宣言

文字列の宣言にはいくつかのやりかたがあります。

char s [ ] = "morino kokagede donjarahoi";

は右辺の文字列定数を NUL も含めてちょうど格納することができる大きさの char 型配列 s を宣言しています。 初期値としてこの文字列が終端記号と共に格納されます。

char s [250] = "";

は 250 個の char 型を格納できる配列 s を宣言しています。 これには終端記号を除いて 249 文字までの長さの文字列を格納することができます。 初期値は「空文字列」です。 空文字列は最初の

文字が終端記号であるような文字列です。配列の最初の要素 s[0] には実際に NUL が代入されます。 しかし残りの要素に何が入るかは一般には不明!です。

char s [250] = "One";

この s は 249 文字までの長さの文字列を格納することができる配列で、文字列としての初期値は"One" です。終端記号 も入っていますが残りの 246 文字に何が入っているかは一般にはわかりません。

2.2.4 文字列の原始的操作

文字列中の文字を 1つずつ扱うようなプログラムを自分で書く機会は実際にはあまり無いでしょうが、文字列の構造を理解しておくことは必要です。 そのために、文字列に簡単な操作を行うプ

ログラムを作ってみましょう。 次のプログラムは初期値として与えた文字列の長さを数えるもの

です。

36

Page 38: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

// 文字列の長さを測るプログラム//#include <iostream>using namespace std;char s [100] = "ABCDEFG"; // 文字列の初期設定int main (){

cout << s << "\n"; // 文字列を表示int i = 0; // インデックスは 0からスタートwhile ( s[i] != 0 ) // NUL なら終り{ // NUL でなければ

cout << i << " " << s[i] << "\n"; // 現在位置と文字を表示i++; // 次のインデックス

} // 繰り返しcout << "Length is " << i << "\n"; // 見つかった NULの位置が長さreturn 0;

}

この while 文は配列の第ゼロ要素から順に終端文字を探しています。 探している間、調べている文字のインデックスと文字を表示しています。 見つかった終端記号のインデックスはそのまま文

字列の長さとみなすことができます。 文字列の初期値をいろいろ変えてやってみて下さい。

次のプログラムは文字列の内部を操作するもので、初期値として与えた文字列を反転します。

// 文字列を反転するプログラム//#include <iostream>using namespace std;

char s [100] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

int main (){

int n = 0; // インデックス 0から始めて、while ( s[n] != 0 ) // 終端記号を探す。n に文字列の長さを得る。

n++;int i = 0; // i は始めの文字のインデックス。int j = n - 1; // j は最後の文字のインデックス。while (i < j) // 左右からぶつかるまでつめて来る。{

cout << s << "\n"; // 途中経過を表示。char c = s[i]; // 左の文字 s[i] と 右の文字 s[j]s[i] = s[j]; // を交換する。s[j] = c; // c は交換のための一時的な置き場所。i++; // 左からつめる。j--; // 右からつめる。

}cout << s << "\n"; // 最終結果を表示。return 0;

}

2つの変数の値を交換するには、片方の元の値を置くために第 3の変数を一時的に使う必要があり

37

Page 39: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

ます。そうしなければ両方の変数は同じ値になってしまいます。上のプログラムでは s[i] と s[j] の値を入れ換える際にそうしています。

2.2.5 文字列関数

文字列の長さを測るような機能はみんなが必要とするものなので、そのようなことを行う関数が

用意されていてすぐに使うことができるようになっています。仲間の関数がいくつかあり、すべて

cstring というヘッダーファイルで定義されています。 その一覧は man ページのセクション 3 または 3c の string の項にあります。 man 3 string のようにすると表示されます。リストを見る

と、ほとんどの関数名は str から始まっていますね。 よく使うものをいくつかあげておきます。

文字列関数strlen(s) s 中の文字列の長さを int 型で返す。strcpy(dest,src) src の内容を dest 中にコピーする。strncpy(dest,src,n) src の内容を n文字まで dest 中にコピーする。strcat(dest,src) dest 中の文字列に文字列 src を追加する。strncat(dest,src,n) dest 中の文字列に文字列 src を n 文字まで追加する。strcmp(s1,s2) 文字列 s1 を s2 と辞書順で比較する。

str は string(文字列)、len は length、cpy は copy、cat は concatenate、cmp は compare から来ています。 詳しい機能についてはそれぞれの関数の man ページに書いてあります。 これらもセクション 3 または 3c です。試しに strlen を使ってみましょう。 これは与えられた文字列の長さを測って結果を返します。この関数の日本語 man ページは次のようになっています。

名前strlen - 文字列の長さを計算する

書式#include <string.h>

size_t strlen(const char *s);

説明strlen()関数は文字列 sの長さを計算する。このとき、終端文字‘\0’は計算に含まれない。

返り値strlen()関数は sの中の文字数を返す。

「書式」のところを見ると、まず string.h を #include する必要があるようですから、それに

従います。ただし、C++ の場合は、string.h の C++バージョンである cstring を使うことも

できます。「返り値」のところを見ると、この関数は size_t という型のデータを返すようです。

size_t は整数型の一つで、変数のようなメモリー中に置かれるもののバイト数をあらわすのに適

した範囲を持ちます。この範囲はマシンによって異なります。 しかし、普通は int と同じと考え

て使っても差し支えはありません。 size_t 型の値を int 型に変換することは問題ありません。 s

は引数の仮の名前ですからそれ自体には意味はありません。const char * が引数の型をあらわし

38

Page 40: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

ます。 これは、引数として文字配列を与えなければならないということと、与えられた文字配列

のデータは関数に読み込まれて使われるかもしれないが、その文字配列の内容が変更されることは

無いということを意味します。 const は constant(不変)です。 アステリスク * は引数が一つの

文字ではなく文字配列であることを意味します。

// 文字列の長さを測る関数を使って文字列の長さを表示するプログラム//#include <iostream>#include <cstring>using namespace std;

char s [100] = "ABCDEFG"; // 文字列の初期設定

int main (){

int size = sizeof(s); // 配列変数の大きさint length = strlen(s); // 中身の文字列の長さcout << s << "\n";cout << "Size: " << size << "\n";cout << "Length: " << length << "\n";return 0;

}

このプログラムでは s を 100 文字分の配列と定義しましたから、s のサイズは 100 バイトです。したがって sizeof(s) は 100 です。 これに対して、strlen(s) は s に入っている文字列の長さ

になります。 文字列の初期設定のところをいろいろいじって表示を見て下さい。

練習問題

1. 文字列変数の中身がどのような構造になっているかを図を描いて説明しなさい。

2. 先程の文字列を反転するプログラムでは始めに文字列の長さを計算しています。そこに strlen関数を使えばプログラムを少し短くすることができます。 どうすればよいでしょうか。

2.3 コマンドライン引数

C のプログラムは main 関数が呼び出されることによって実行が始まります。main 関数は引数を取ることができます。これはプログラムを起動する時に、コマンド名の後に付けたコマンドライ

ン引数をプログラムに伝達するために使うことになっています。

コマンドライン引数は空白で区切られた文字列です。たとえば、

ls -l /usr/include

というコマンドラインは ls コマンドに2つのコマンドライン引数-l と/usr/include を与えて

います。シェルは入力からコマンド名とコマンドライン引数を取り出して指定されたコマンドを起

動しますが、C のプログラムの場合は取り出されたコマンドライン引数は main 関数に引数として与えられます。シェルスクリプトでは $1 とか $2 のように書いてコマンドライン引数をあらわし

ますがそれと同様の機構です。

39

Page 41: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

2.3.1 コマンドライン引数の表示

まず、一つのコマンドライン引数を付けて起動されることを想定し、コマンドライン引数をその

まま表示するプログラムを作ってみましょう。

// 最初のコマンドライン引数を表示するプログラム#include <iostream>using namespace std;int main ( int argc, char* argv[] ){

if ( argc < 2 )return 1;

cout << "The first parameter was ... " << argv[1] << "\n";return 0;

}

argv[] の [] は中括弧です。 char の後の * を落とさないようにして下さい。このプログラムは

これで完結しています。 適当なファイル名で保存してコンパイルし実行してみて下さい。 実行す

る時は、コマンドライン引数として何かでたらめな文字列を打ち込んで下さい。

main 数の最初の引数としては、与えられたコマンドライン引数の数より 1 多い数が int 型で伝

達されます。習慣としては、この引数には “argument counter” のつもりで argc という名前を付

けて使いますが、借り引数名ですからもちろん何でもかまいません。 1 多いのはコマンド名も含めて数えていると考えて下さい。 二番目の引数はすべてのコマンドラインパラメーターからなる

配列です。 argv は “argument values” のつもりです。この文法は今の段階では概念的に難しいので、敢えて無益な説明を省きます。 どのように使うのかということだけに注目してください。

では main 関数の中身を見ましょう。 始めに if文が出て来ました。 これは

if (条件式)文

という構造になっていて、括弧の中の条件式の結果によって文が実行されたりされなかったりし

ます。 ここでは、不等号 < で argc の値が 2 より小さいかどうかを判断しています。 argc の

値が 2 より小さいということは、コマンドライン引数が一つも無いということです。 もしそうなら、return 1; で main 関数は即座に終了します。 この場合はエラー扱いにして終了コードとして return に 1 を指定しています。 コマンドライン引数の個数を想定はしますが実際にいくつになるかは実行時までわかりませんから、このようなエラー処理あるいは例外処理をプログラム中に

入れる必要があります。

if 文では条件式を括弧で囲む必要があります。 この閉じ括弧の直後にはセミコロン ; を付けて

はいけません。セミコロンがあるとそこで if 文の構造が途切れてしまいます。そうなると、条件が成立した時に実行される文がそのセミコロンだけからなるものとみなされます。 このようなセ

ミコロンは「空文」という文です。 空文は一見邪魔なだけに見えますが、積極的に何もしないと

いうことを表現するという用途があります。

さて、コマンドライン引数の数が一つ以上あれば if 文によって return 1; は実行されず次へ行

きます。 そこでは argv[1] というものを挿入演算子で cout に送って表示しています。 argv[1]

は一番目のコマンドライン引数が文字列として入った文字列変数と考えて下さい。 この文字列変

数にはもちろん終端記号も入っています。もしコマンドライン引数が二つ以上あれば argv[2] に

は二番目のコマンドライン引数が同様に入りますが、上のプログラムではそのような場合でも一番

40

Page 42: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

目のコマンドライン引数しか表示しません。

練習問題

上のプログラムを修正して、二番目のコマンドライン引数があればそれも表示するようにしな

さい。

2.3.2 コマンドライン引数からの数値入力

コマンドライン引数は常に入力したままの文字列として main 関数に伝達されます。 数値を入力したつもりでも、int とか double のような数値型になってから main 関数に行くわけではありません。 コマンドライン引数を数値として解釈して計算に使いたい場合は、文字列を解釈して数

値に変換する機能を持った関数を使います。 一番目の引数と二番目の引数を実数として解釈して

それらの差を計算して表示するプログラムは次のようになります。

// 2つの実数の差を表示するプログラム// 2つの実数はコマンドライン引数として与える。#include <cstdlib> // atof を使うのに必要#include <iostream> // cout を使うのに必要using namespace std;int main ( int argc, char* argv[] ){

if ( argc < 3 )return 1;

double value1 = atof(argv[1]); // 文字列を double 型実数に変換double value2 = atof(argv[2]); // 文字列を double 型実数に変換double difference = value1 - value2;cout << value1 << ", " << value2 << "\n";cout << "The difference is ... " << difference << "\n";return 0;

}

これを subtract.cc として保存してコンパイルし subtract コマンドを作り、

./subtract 321.123 123.321

などと打って実行してみて下さい。

このプログラム中、atof は文字列を実数値と解釈して double 型の値に変換する標準関数です。数値として解釈できない場合は結果として 0.0 を返すだけなので、atof では入力文字列が数値の

表現として適切かどうかは判断できませんが、使い方が簡単なのでよく使います。 atof を使うに

は cstdlib を #include する必要があります (C では stdlib.h)。 atof というオトーフみたい

な名前の由来はよくわかりません。 atof は double 型への変換ですが、ほかに、int 型に変換する atoi というのもあります。 これらは標準関数なので man ページに載っています。なお、入力文字列が本当に数値を正しく表しているかどうかを調べる機能も持った関数としては strtod があ

りますが、使い方は難しい。

このプログラムはコマンドライン引数の値も表示していますがコマンドライン引数そのものでは

なく double に変換した後の値を表示していることに注意して下さい。ということは、必ずしも入

41

Page 43: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

力した通りが表示されるのではないということです。たとえば、 1.234E3 のように指数付きで入

力してみてもそれがよくわかるでしょう。

2.3.3 コマンドライン引数で翼の形を変える

次のプログラムは、前に出て来た tsubasa.cc を改良してコマンドラインでパラメーター値を

指定できるようにしたものです。 点の総数、漸化式の係数 1、漸化式の係数 2 の順に指定します。

// 翼のプロットデータを計算するプログラム// コマンドライン引数// 第一引数 ... 点の総数// 第二引数 ... 係数 1// 第三引数 ... 係数 2#include <cstdlib>#include <iostream>using namespace std;int main ( int argc, char* argv[] ){

int NPoints = 30000; // 点の総数, 既定値は 3000if (argc > 1) // しかし第 1引数があればそれを使う

NPoints = atoi(argv[1]);double Coefficient1 = 0.97; // 係数 1, 既定値は 0.97if (argc > 2) // しかし第 2引数があればそれを使う

Coefficient1 = atof(argv[2]);double Coefficient2 = 0.995;// 係数 2, 既定値は 0.995if (argc > 3) // しかし第 3引数があればそれを使う

Coefficient2 = atof(argv[3]);double x = 1; // x の初期値double y = 0; // y の初期値int i = NPoints; // 計算の残り回数while ( i > 0 ) // 残り回数が 0でない限り繰り返す{ cout << x << " " << y << "\n";

double nx = - Coefficient1 * x + y - 5 + 5 / (1 + x * x);y = - Coefficient2 * x;x = nx;i = i - 1;

}return 0;

}

これを tsubasa2.cc に入れてコンパイルし tsubasa2 を作って下さい。 gnuplot を起動し、まずコマンドライン引数を付けずに

plot "< ./tsubasa2" with dots

とやってみてください。こうするとプログラム中の既定値が使われて前と同じ図が描かれます。 if文を使って、コマンドライン引数の数が足りている場合にだけコマンドライン引数を使い、そうで

なければ既定値を使うようにしてあるからです。 次に

plot "< ./tsubasa2 30000 0.97 0.995" with dots

42

Page 44: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

とやってみると、また同じ図になるでしょう。これは既定値と同じコマンドライン引数を使ったか

らです。 さてこれからがお楽しみ。 0.97 や 0.995 のところをいろいろ変えてみましょう。 係数をほんの少し変えるだけで形が非常に変化します。 第1引数は、計算時間が長いと感じたら小さ

くすればよいし、緻密な図にしたければ大きくすればよろしい。

2.3.4 総和を求めるプログラム

もう一つ、任意の個数の数値をコマンドラインパラメーターとして与えるとその総和を計算し

て表示するプログラムを作ります。 方針としては、和を入れる変数を用意してその値を最初にゼ

ロにしておき、その変数にパラメーターを変換して得られる数値を次々に足して行くとよいでしょ

う。 数値は実数として扱うことにします。 そんなの簡単だと思ったら下のプログラムを見る前に

自分で作って下さい。

// 全コマンドラインパラメーターの数値としての和を求めるプログラム//#include <cstdlib> // atof を使うのに必要#include <iostream> // cout を使うのに必要using namespace std;int main(int argc, char* argv[]){

double sum = 0.0; // 和の初期値ゼロfor ( int i = 1 ; i < argc ; i++) // パラメーター 1 から argc-1 まで

sum += atof( argv[i] ); // atof で数値にして sum に加えるcout << sum << "\n"; // 結果の表示

}

+= という記号は ++ と同様 C に特有の簡略記法で、 右辺の式の値を左辺の変数に加えることを意味します。 たとえば、 x += 3 は x = x + 3 と同じです。

このプログラムをコンパイルして total という名前の実行ファイルを作ったとしましょう。

./total 1 2 3 4 5 6 7 8 9 10

のように使うのが基本です。 少数点付きの数を指定してもちゃんと計算ができることも確かめて

下さい。ファイルに入ったデータを使って計算することも可能です。 data というファイルに数値がたくさん入っているとすると、

./total ‘cat data‘

と打てばよいのです。 ‘ は普通のクォーテーションマークではなく,バッククォートです。 こう

すると、まずバッククォートの中のコマンドが実行され、その結果得られる表示で ‘cat data‘ の

ところが置き換えられ、それから命令全体が実行されます。 cat data は data というファイルの

内容を表示するだけです。つまり、これによって data ファイルの内容をコマンドラインパラメー

ターにして total が実行されるわけです。この置き換えはシェルの機能で、コマンド置換といい

ます。

コマンド置換では改行文字は空白に変えられてすべてが 1行に収められてしまうので、ファイル中の数値は全部を 1行にまとめないで複数の行に分けてあっても大丈夫です。 コマンド置換はコマンドが表示する内容への置き換えですから、必ずコマンドを書く必要があります。 ここでは

43

Page 45: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

cat でファイルの内容をそのまま表示しているだけですが、 cat を省くことはできません。 コマンドラインというからには一行に入る程度のデータしか扱えないのではと思いがちですが、数百万

文字のデータを実際に処理することも可能なようです。

2.4 C の標準入力

今度は、起動時にコマンドラインからデータを読み込むのではなく実行中にキーボードからデー

タを入力するプログラムです。 この入力先は UNIX のリダイレクション機能を使ってファイル内のデータや別のプログラムの出力に切り替えることができます。

まず 1文字ずつ単純に読み込むプログラムです。getcharという関数は「標準入力」から 1文字読み込んで文字コードを関数の戻り値として返します。manページにはさらに、入力が終りに達するとEOFを返すと書いてあります。EOFという名前はおそらくヘッダーファイル/usr/include/stdio.h

の中で定数値 -1 として定義されています。

#define EOF (-1)

のように書いてあるところが見つかるでしょう。これはマクロ定義という記法です。負の数は文字

コードには使われないので、文字を扱う場合に特別な状況をあらわすためによく使われます。 -1

のような数値を直接用いないで EOF のような記号を用いる一つの理由はその方が意味がわかりや

すいからです。 EOF は end of file のことです。getchar を使って文字を入力して、各文字を文字

コードにして出力してみましょう。 EOF が検出されたら終りにしますが、ここではその EOF の値

も表示します。

// 1文字ずつ読み込むテスト// 行の先頭で <Ctrl>d を打つと終了#include <cstdio>using namespace std;int main(){

while( 1 ){

int c;c = getchar(); // 標準入力より 1文字入力printf("%3d ", c); // コードを 10進で表示if( c == EOF ) // EOF だったら終了

break;}

}

このプログラムを実行する前に、 cat コマンドを何もパラメーターを付けずに実行してキー入力の方法を確かめて下さい。 入力ファイル名を指定しない場合は cat は標準入力を受け付けてそれをそのまま出力します。 すぐわかるように、キーボード入力は

�� ��Enter キーを押すまでの 1行分の入力が単位になっています。 つまり、

�� ��Enter キーを押す前なら�� ��Delete キーを使って間違いを直す

ことができ、�� ��Enter キーを押す毎に入力した文字が処理されます。 また、行の始めに

�� ��Ctrl�� ��d を

打つと入力はおしまいになります。さらに�� ��Ctrl

�� ��c はいつでもプログラムを強制的に中断します。

ここまで確かめてから、cat のかわりに上で作ったプログラムを実行してみて下さい。行の終りにコード 10 の改行文字が付くことと EOF が −1 であることがわかるでしょう。 10は 16進で 0a

44

Page 46: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

ですが、これは前にも出て来た LF (line feed)です。 nl とか lf ともいいます。通常、1文字単位で読み込むのではなく、単語とか数値のようなデータを読み込んで使いたいので、目的に応じて必要な単位を読み込んでくれるような関数がいくつか用意されています。一度に

入力される文字列を「フィールド」といいます。 ここでは scanf という関数をとりあげます。 この関数は printf 関数と似ていて書式指定文字列を使って機能を制御できるように作られています。次のプログラムでは、この関数を単語を読み込むために使っています。ここでは単語とは空白を含

まない文字列のことです。

// scanf 関数のテスト// 行の先頭で <Ctrl>d を打つと終了#include <cstdio>using namespace std;int main(){

char word[11];while ( scanf( "%10s", word) != EOF )

printf( "...%s\n", word);return 0;

}

scanf は入力したデータの数を関数の戻り値として返しますが、入力の終りに達して入力するものが無くなったときにはゼロではなく EOF を返します。 そこで、「scanf の戻り値が EOF では無い」という条件を while の繰り返し条件として使っています。 scanf の第一引数は書式指定文字列で、最大 10文字の文字列を読み込むことを指示しています。読み込まれた文字列は第二引数で指定した文字列変数 word に収められ、終端記号が付加されます。 入力フィールドは空白文字や改行文字を含まない部分です。 つまり、まず空白文字などがあれば読み飛ばされてから読み込みが

始まり、再び空白文字などが現れると読み込みを停止します。 上のプログラムはこれを繰り返し

ています。 したがって、

This is a pen.

と入力すると、This と is と a と pen. がフィールドとして読み込まれ、表示されます。 書式指定文字列中の 10 によって一度に 10 文字までしか読み込まないように指定してありますので、長いフィールドは分割されてしまいます。この文字数指定が無い場合は、長い単語が入力された場合

に文字列配列の大きさを越えて文字が格納される結果、プログラムが異常終了したり動作がおかし

くなることがあります。

scanf は文字列を読み込んで数値に変換する機能もあります。次のプログラムは入力から実数を読み込んで double 型の変数に格納します。

45

Page 47: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

// scanf 関数のテスト#2// 行の先頭で <Ctrl>d を打つと終了#include <cstdio>using namespace std;int main(){

double x;while ( 1 ) // ループ開始{

int r = scanf( "%lf", &x ); // 実数を読んで xに格納するif ( r == EOF || r == 0 ) // 結果 rが EOFまたは 0の場合は終了

break; // break はループを強制終了するprintf( "...%g\n", x); // 実数値 x を表示する

}return 0;

}

scanf に指定した書式指定は小文字の l と f です。 scanf の第二引数には入力した値を格納するdouble 型の変数の名前 x を& を付けて指定します。この変数の型は書式指定によって決まっていま

す。 書式指定が %f のときは変数の型は double ではなく float でなければなりません。 float

も実数型ですが、double よりも精度が低くなります。

文字列変数以外の場合は変数名に & を付けることに注意してください。&x は、変数 x の値を関

数に伝えるのではなく、変数 x の場所を関数に伝えることを指定しています。 scanf 関数は変数

の値を使うのではなく変数に値を入れるので、関数には値ではなく場所の情報を伝えるのです。変

数などの場所情報はポインターとも呼ばれます。

printf と異なる点は二つですね。 まず、double 型に対して、%f ではなく %lf を使うこと、次

に & が必要であることです。 ただし、& は文字列変数には必要ありません。

1.234 とか 3.3E-4 のような入力は、みな実数として解釈されて入力されます。そのとき scanf

は関数の戻り値として 1 を返します。しかし実数とは違うものがあると入力は止まってしまい、scanf は戻り値として 0 を返します。 上のプログラムでは、この場合にも実行を終了させるようにしています。 || は「または」 という意味の演算子です。

2.5 C++ のストリーム入力

始めに #include <iostream> を書けば、出力用の cout だけではなく入力用の cin というオブジェクトも使えるようになります。 出力は演算子<< で行いましたが、cin からの入力には演算子>> を使います。データを格納する変数を >> の右に指定します。 変数の型によって入力文字列が

適当に解釈され変換されます。次に簡単な終了処理を組み込んだプログラムを示します。

46

Page 48: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

// cin のテスト// 実数を読み込んで2つずつの和を表示// 行の先頭で <Ctrl>d を打つか、変換できない入力に出くわすと終了//#include <iostream>#include <iomanip>using namespace std;int main(){

double x, y;while ( 1 ) // 繰り返し{

cin >> x >> y; //2つの実数を読むif (cin.good()) //うまくいったら

cout << setw(10) << x + y << "\n";//計算して表示else //だめなら

return 0; //終了}

}

入力操作がうまくいったことは cin.good() の返り値で判定しています。これは正確にはメソッド

呼び出しといいますが、一種の関数呼び出しです。

2.6 実時間動作のプログラム

計算とか表示のようなことをコンピューターが実際に行うにはもちろんある一定の時間がかかり

ますから、単純なプログラムより複雑な処理を行うプログラムの方が終了するまでに長い時間が必

要です。 time というコマンドをプログラムの実行にかかる時間を測定するのに使うことができます。 たとえば、前に作った tsubasa2 プログラムがあるディレクトリーで

time ./tsubasa2 100000 > xxxxx

とやってみてください。 3つの時間が表示されますがその中の一番長い時間が実行開始と終了の

間の時間です。 xxxxx というファイルに出力をリダイレクトしていますから、表示にかかる時間は含まれませんが、ファイルに書き込むのに必要な時間は含まれています。 ファイル操作のため

の複雑な仕事は応用プログラムではなくオペレーティングシステムが担っています。 time コマンドが表示する残りの情報は、オペレーティングシステムの動作に費された時間とそれを除いたユー

ザープログラム自体が消費した時間です。 ファイル xxxxx は後で削除して下さい。同じプログラムでもその実行にかかる時間はマシンやオペレーティングシステムやコンパイラー

の性能によって異なります。 また、同時に走っている他のプログラムによっても、自分のプログ

ラムの動作スピードが変動します。 たとえば次のようにして2つのプロセスを同時に走らせたり

すると、それがわかるでしょう。(前に作った tsubasa2 のあるディレクトリーで)

./tsubasa2 100000 > xxxx1 & time ./tsubasa2 50000 > xxxx2

いつ動作させても同じテンポで動くようなプログラムを作るには特別な工夫が必要です。 この

ようなプログラムを動作させるためにコンピューターのハードウェアには時間を測る機能が付いて

47

Page 49: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

います。 要するに時計が組み込まれているわけです。 UNIX マシンの時間測定の機能はシェルからも C のプログラムからも使うことができます。最も簡単なのは sleep というコマンドです。これは指定された秒数だけ何もしないで待つだけというコマンドです。 指定した時間が経過してか

ら sleep コマンドの実行が終了します。 この時間は同時に走っている他のプロセスには影響されません。 sleep コマンドは C の関数にも同じものがあり関数の名前は同じ sleep です。 man ページでは、それぞれセクション 1 と 3 に説明があります。 まず、シェルで次のようにコマンドを与えてみて下さい。

sleep 2; echo A; sleep 2; echo B; sleep 2; echo C; sleep 2; echo D

セミコロン ; で切っているので各コマンドは左から順に実行されます。前のコマンドが終了してから次のコマンドが実行されます。”sleep 2” の実行に 2 秒かかりますが、 echo コマンドは一瞬で終りますから、ほぼ 2秒おきに文字が表示されるわけです。 同様にして、 1秒おきに Five, Four,Three, Two, One, Fire! と表示してみて下さい。次の C++ プログラムはこれと同じことをするものです。 sleep 関数を使うために unistd.h が必要です。unistd.h の C++ 版はありません。

// 秒読み#include <iostream>#include <unistd.h>using namespace std;int main(){

sleep(1);cout << "Five" << endl;sleep(1);cout << "Four" << endl;sleep(1);cout << "Three" << endl;sleep(1);cout << "Two" << endl;sleep(1);cout << "One" << endl;sleep(1);cout << "Fire!" << endl;return 0;

}

endl は 前に出てきた setw の仲間の I/Oマニピュレーターです。これは改行文字 ’/n’ を出力に

送るとともに出力を「フラッシュ」します。 このプログラムでは挿入演算子の実行で直ちに出力

が画面に出なければならないのですが、endl を使わないで改行文字を送るだけにすると、表示は

おそらく最後にまとめて出ます。 嘘だと思うなら、endl のところを全部 ’\n’ に書き換えて実行

してみてください。

出力操作を頻繁に行なうとプログラム全体の実行速度が下がってしまうので、通常はそのまま目

的地へ送るのではなくある程度蓄えておいてから適当なタイミングで自動的に一気に送り届ける

という仕組みになっています。 データを一時蓄えておく場所をバッファーと呼び、バッファーに

溜っているデーターを排出して出力装置に送ることを「フラッシュ」と言います。

フラッシュはバッファーがいっぱいになった時や入力待ちを行う前やプログラムが終了する時に

自動的に起こります。 これでもプログラムの中で記述した順序を保って表示が出て来ることは保

48

Page 50: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

証されます。 したがって、表示内容と表示順序だけが正しければ良い場合は、バッファーの存在

を意識してプログラムを作る必要はありません。 しかし、今のプログラムのようにあるタイミン

グで表示が出て欲しい場合には、そのタイミングで強制的にバッファーのフラッシュを行なう必要

があるのです。

改行を伴わないでフラッシュだけを行なうマニピュレーターは flush です。C の標準出力をフラッシュするには fflush 関数を使って fflush(stdout); と書きます。 ANSI 規格では C の標準出力のバッファーと C++ のストリーム出力のバッファーは異なるので、混ぜて使うと自動フラッシュのタイミングが合わず表示が意図した順序で出ないかもしれません。前に述べたように、

これを防ぐにはプログラムの始まりのあたりで

iostream::sync_with_stdio();

を実行すれば良いのですが、意識的に fflush と flush を使ってタイミングを合わせることもでき

ます。 GNU の C ライブラリーでは C の標準出力と C++ のストリーム出力は同じバッファーを使っていますから、タイミングは自動的に合います。 Linux ではそうなります。 endl— や flush

はマニピュレーターですがそれらを使うのに iomanip をインクルードする必要はありません。こ

れが必要なのは setw のような引数を取るマニピュレーターを使う場合です。

sleep では正の整数で秒数を指定するようになっていますから、最小単位は1秒であり 0.1 秒とか 12.34 秒といった指定はできません。その必要があるときは usleep 関数が利用できます。 usleepではマイクロ秒単位での指定ができます。ただし数値が大きくなり過ぎるので数千秒以上の時間を

指定することはできません。 それに、マイクロ秒が単位ですが本当にマイクロ秒の精度があるわ

けではありません。精度はマシンのハードウェアによって異なり、通常のワークステーションやパ

ソコンでは大体 1ミリ秒から 10ミリ秒程度です。 usleep の使い方については man ページを見て下さい。

usleep が使えないシステムもあります。その場合は nanosleep という関数を使うことになります。 こちらは多くのシステムで利用することができます。 ただし、これは簡単には利用できませ

んし使い方はこのテキストのレベルを越えています。 そこで、かわりに次の関数 Sleep の定義を自分の C++ プログラムの中に取り込んで無批判に使って下さい。ctime は nanosleep 関数 のため、cmath は floor 関数のために必要です。

// 実数で指定した秒数だけ眠る関数、精度は 0.01秒程度だがマシンによる//#include <cmath>#include <ctime>void Sleep(double sec){

timespec req, rem;double intpart = floor(sec);double fragment = sec - intpart;req.tv_sec = time_t (intpart);req.tv_nsec = long (floor(fragment * 1000000000L));int r = nanosleep ( &req, &rem);

}

これは内部で nanosleep を使っていますがその仕組みを理解する必要はありません。 Sleep 関数を double 型の値を与えて呼び出すとその秒数だけ何もしないで待機します。与える数値は実数です。 ただしこの関数の精度も 1ミリ秒から 10ミリ秒程度しかありません。 つまり、Sleep(1.01)

49

Page 51: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

と Sleep(1.0) は同じかもしれないということです。この関数 Sleep がうまく機能するかどうか、次のプログラムで試して見ましょう。

// 伸びたり縮んだりする棒//#include <iostream>using namespace std;//////////////////////////////////////////////////ここに上の Sleep の定義をそっくり入れる。

////////////////////////////////////////////////////// 次のパラメーターの値は変更することができるconst double TimeStep = 0.1; // ステップ間の時間 (秒)const double TimeLength = 10.0; // 終了する時間 (秒)const double Frequency = 0.5; // 伸縮振動の周波数 (Hz、ヘルツ)//int main(){

double t = 0.0; // 時間の値(秒)double x; // 関数値int p = 0; // 文字の位置int newp; // 新しい文字位置while ( t < TimeLength ){

x = -cos(2*Frequency*M_PI*t);newp = int((x + 1) * 35);while (p < newp) // 伸ばす必要がある{

cout << "#" << flush; // # を1つずつ追加するp++;

}while (p > newp) // 縮める必要がある{

cout << "\b \b" << flush; // 1つずつ消して後退p--;

}Sleep(TimeStep); // タイミングを取るt += TimeStep;

}cout << endl;return 0;

}

cos という関数は三角関数のコサインです。これはラジアンを単位とする角度を与えるとその関数値を double 型で返します。cos のような数学関数を使うには cmath を #include する必要があり

ます。M_PI は円周率πの値をあらわすものですが、これを使うにも cmath が必要です。数学関数を使っているプログラムを make でコンパイルするとき、システムによっては #include <cmath>

を書いたのに関数が定義されていないという意味のエラーメッセージに出会うかもしれません。そ

の場合は

make LDLIBS="-lm" ターゲット名

とやります。

50

Page 52: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

cout << "\b \b" << flush; の中の \b は後退記号(バックスペース)という制御文字を表し

ます。これが来ると端末はカーソル位置を左へ一つだけ戻します。始めに1文字文後退し空白を一

つ出力して文字を消し再び後退することで、棒の長さを1文字分縮めています。

sleep や usleep や上で作った Sleep では誤差が避けられません。細かいきざみで制御するほど累積された誤差の影響は大きくなります。それを防ぐには経過時間を測定する必要がありますが、

それについてはここでは述べません。

練習問題

1. 集計コマンドの作成

total 123 345.89 -0.34 2.234E-1

のようにして起動するとコマンドライン引数をすべて実数と見てそれらの個数と総和と平均

値を求めてこの順で表示するコマンド total2 を C (または C++) で作りなさい。ただし、コマンドライン引数が無い場合は total2 コマンドの正しい使い方と結果の見方が表示されるようにしなさい。実数として解釈できない引数がある場合の処置などの細かいことは適当にし

なさい。

2.7 ここまでのまとめ

ここまでのまとめです。書き落としたことも含めています。

C言語のテキストを読むのに必要な基礎知識を以下にまとめる。春学期に C 言語を勉強した人はこれをざっと見て復習を行なってほしい。そうでない人は練習問題を解きながら少し時間をかけ

て読むとよい。

2.7.1 ソースプログラムと実行ファイル

C++のソースプログラムをテキストエディター (たとえば emacs)で書いてファイルに収め、これをコンパイルすると実行ファイルが作られる。ソースファイルの名前は実行ファイルの名前に拡

張子 “.cc” を付けたものにする。簡単なプログラムの場合は make コマンドでコンパイルを行な

うことができる。実行するには実行ファイルをディレクトリーを含めて指定する。

たとえば、カレントディレクトリーに xyz.cc というソースファイルを作り、シェルで

make xyz

と打つと、xyz という実行ファイルができる。これを実行するには

./xyz

と打つ。./ はカレントディレクトリー。コマンドラインパラメーターが必要なプログラムでは、

たとえば

./xyz 123 456

というように実行する。

51

Page 53: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

練習問題 下のソースプログラムから (内容は考えず)上の説明のようにして yesterday という名前の実行ファイルを生成してそれを実行してみなさい。次に、クォーテーションマークで囲んであ

る英文を書き換えて保存し直し、実行ファイルも作り直してから再度実行してみなさい。次に、も

う一度書き換えを行い、保存しないで作り直して実行してみなさい。次に、保存を行い、作り直し

をしないで実行してみなさい。最後に、作り直しを行い、実行してみなさい。

#include <iostream>using namespace std;int main(){

cout << "Yesterday is the day next before today." << endl;}

2.7.2 変数

変数は計算結果を後で使うために憶えておく仕組み。各変数は不変の名前と型および、変化する

内容を持つ。変数を始めて使うところで「宣言」を行ない名前と型と初期値を与える。たとえば、

int order_number = 10;double weight(15.6);

は、整数型で order_number という名前の変数を用意して始めの値を 10 とし、実数型で weight

という名前の変数を用意して始めの値を 15.6 としている。初期値の設定にはイコールを使ってもよいし括弧を使ってもよい。

なお、C では変数宣言を置くことのできる位置に関する制限があって、C++ ほど自由ではない。また、括弧を使う初期化の構文は無い。

変数には寿命があり、遅くともプログラムの実行が終るまでにはそのプログラム中で使っていた

変数はすべて消滅する。

練習問題 次のソースプログラムから hensuu という名前の実行プログラムを生成して実行してみなさい。また、変数の初期値を変えたり、名前や型を変えたりして実行してみなさい。

#include <iostream>using namespace std;int main(){

int order_number = 199;cout << order_number << endl;

}

2.7.3 基本的な型

int n; 標準的な整数型。32ビット CPUでは−231 =−2, 147, 483, 648から 231−1=2, 147, 483, 647まで。値に限界があることを考慮して使う必要がある。

double x; 標準的実数型。64 ビット。精度は 10進 15桁程度。精度が有限であることを考慮して使う必要がある。

char c; 8ビットの符合付き整数型。ASCII 文字をあらわすのに使う。

float y; 32ビットの実数型。短いのでメモリーの節約になるが、精度が低いのであまり使わない。

52

Page 54: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

char s[]="abcdefg"; char s[10]; char ss[]; char* sss; 文字配列。はじめの 2つは文字列の記憶領域を確保する。後の 2つは記憶領域を確保せず関数の仮引数などで記憶領域の位置指定に使う。char 以外の型でも同様にして配列を作ることができる。

void 空。関数の返り値が無いことを示す場合などに使われる。

練習問題 次のプログラムを参考にして、実数型の精度の違いを確かめてみなさい。

#include <iostream>using namespace std;int main(){

float f = 0.1;double d = 0.1;cout << "float precision: " << (f * 10 - 1) << endl;cout << "double precision: " << (d * 10 - 1) << endl;

}

練習問題 次のプログラムをいろいろに変更して、値の範囲の問題について考えなさい。

#include <iostream>using namespace std;int main(){

double x = 8.0e9; // 8000000000.0 in other wordint n;n = x;cout << x << " is converted to " << n << endl;

}

2.7.4 演算子

括弧:

( )

部分式をくくる括弧。計算の順序を変えるためだけではなく、計算の順序が一見してはっきりわか

るようにするためにも使う。多重にくくる場合でも括弧の種類は一種類だけである。

算術演算子:

- x, x * y, x / y, n % m, x + y, x - y

整数どうしの演算の結果は整数になる。割算では切捨てが強制的に行なわれる。実数どうしの場合

は結果は実数になる。整数と実数の演算では整数が実数に変換されてから計算が行なわれ、結果は

実数になる。割算の余りを求める演算子 % は整数のみが対象。

関係演算子:

x < y, x > y, x <= y, x >= y, x == y, x != y,

これらの比較式の値は、比較の結果が真であるか偽であるかによって、1 か 0 になる。

53

Page 55: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

代入演算子:

a = x, a += x, a -= x, a *= x, ...

これら代入式の左辺は変数であり、その値は変更される。代入式自体も代入されたのと同じ値を持

つ。そこで、x = y = 1; のような多重代入が可能である (右側の方から処理される)。

自動インクリメント演算子と自動デクリメント演算子:

++n, n++, --n, n--

自動インクリメントと自動デクリメントは変数に対して作用し、値を 1増やしたり減らしたりする。後置形式の式の値は計算前の変数の値になる。そのため、x = 0; y = x++; では、x は 1 になるが y は 0 になる。

練習問題 次のプログラムを参考にして、割算の結果が型によってどう変わるかを調べなさい。

#include <iostream>using namespace std;int main(){

cout << ( 10 / 3 ) << endl;cout << ( 10.0 / 3.0 ) << endl;cout << ( 10.0 / 3 ) << endl;cout << ( 10 / 3.0 ) << endl;cout << ( (10 + 0) / 3 ) << endl;cout << ( (10 + 0.0) / 3 ) << endl;

}

練習問題 自動インクリメント演算子の働きを次のプログラムで確認しなさい。

#include <iostream>using namespace std;int main(){

int n = 10;cout << "n equals " << n << endl;cout << "n++ equals " << n++ << endl;cout << "n equals " << n << endl;cout << "++n equals " << ++n << endl;cout << "n equals " << n << endl;

}

2.7.5 文

代入式や関数呼び出しのような式は、後ろにセミコロンを付けることによって一つの単純文に

なる。複数の文を中括弧 { } で囲んだものは一つの文になる。これは複文という。他に、条件文、

スイッチ文、繰り返し文もそれぞれ一つの文である。

2.7.6 繰り返し文

for 文は配列の中身を操作するのによく使われる。次のプログラムでは、文字列"ABCD" で初期

化された文字配列 s の始めの要素から終りの要素までの値を調べている。sizeof(s) は配列 s の

大きさを得るものである。配列のインデックス (要素番号)は 0 から sizeof(s)-1 までであるこ

とに注意。

54

Page 56: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

#include <iostream>using namespace std;int main(){

char s[] = "ABCD";for (int i = 0 ; i < sizeof(s) ; i++){

int code = s[i];cout << i << "---" << code << "---" << s[i] << endl;

}}

同じプログラムを for 文のかわりに while 文を用いて書くと次のようになる。変数 i の初期化

と更新を記述する位置が for 文の場合と異なる。

#include <iostream>using namespace std;int main(){

char s[] = "ABCD";int i = 0;while ( i < sizeof(s) ){

int code = s[i];cout << i << "---" << code << "---" << s[i] << endl;i++;

}}

練習問題 上の 2つのプログラムの内どれか一つを実行してみなさい。文字配列 sの初期値"ABCD"

をいろいろに変えてプログラムを実行し、配列の中に文字列がどのように記憶されているかを調べ

なさい。

2.7.7 関数の利用

ライブラリー関数を使うには、その関数を記述したヘッダーファイルを#include 命令で読み込

んだ上で、関数をその仕様に従って呼び出す。標準的ライブラリー関数の仕様と必要なヘッダー

ファイル名については man ページで調べることができる。普通のライブラリー関数についてはセクション 3にまとめられている。関数 atan2 の man ページの始めを見ると

書式#include <math.h>double atan2(double y, double x);

とあり、「 y / x の逆正接を計算 」 と書いてあるので、たとえば atan2(3,4) で 3/4 のアークタンジェントが得られるだろう。しかし、atan2(1,-1) と atan2(-1,1) は同じだろうか? また、atan2(-1,-1) と atan2(1,1) は同じだろうか? さらに、atan2(1,0) はどうなるだろうか? それを調べるには簡単なプログラムで実際に試してみればよい。たとえば、次のようなプログラムでよ

いだろう。

#include <math.h>#include <iostream>using namespace std;int main(){

cout << "atan2(1,1) = " << atan2(1.0, 1.0) << endl;

55

Page 57: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

cout << "atan2(-1,-1) = " << atan2(-1.0, -1.0) << endl;cout << "atan2(-1,1) = " << atan2(-1.0, 1.0) << endl;cout << "atan2(1,-1) = " << atan2(1.0, -1.0) << endl;cout << "atan2(1,0) = " << atan2(1.0, 0.0) << endl;

}

ヘッダーファイルを #include で指定し、引数の数、順序、型、および返り値の有無と型に矛盾し

ないように呼び出す必要がある。ただし変換が一意的に可能なら型は一致しなくてもよい。引数が

無くても括弧は必要。返り値はもしあっても無視して使わないこともできる。

練習問題 floor という関数について man ページで調べ、その機能をテストするために簡単なプログラムを作りなさい。(計算結果を表示するために cout を使うなら、iostream を #include し

using namespace std; を指定すること。)

2.7.8 関数定義

関数の定義の例を下に示す。これは引数として与えられた 2つの整数の最大公約数を計算して返り値として返す関数である (この計算法は「ユークリッドの互除法」という。しかし内容は考えなくてもよい)。関数定義の構成上、最初の 3つの要素 (返り値の型、関数名、引数)が必須要素。また return 命令で返り値を指定。

int // 返り値の型get_gcd // 関数名

( int x, int y ) // 引数の型 仮引数名 ...{

if ( y > 0 )return get_gcd( y, x % y );

elsereturn x;

}

呼び出した関数の返り値を使うには関数呼び出しを式の中に書く。たとえば get_gcd を使って 12と 244 の最小公倍数を求めるなら、

int lcm = 244 / get_gcd(12, 244) * 12;

返り値が無い関数を定義する場合、返り値の型として void を使い、return 命令には返り値を指

定しない。

練習問題 2つの整数 272205 と 42435 の最大公約数と最小公倍数を計算して表示するプログラムを上で示した関数 get_gcd を使って作りなさい。関数の定義は int main() の行の上に入れる。

2.7.9 main 関数の引数と返り値

プログラムの実行とは main 関数の実行であり、他の関数は main 関数から直接または間接に呼び出されなければ実行されない。なお、main 関数が定義されていなければ、コンパイルの時にエラーになる。

main関数は int型の返り値を持つように作らなければならない。プログラムが正常に終了した場合にはゼロ, 異常終了した場合にはゼロ以外の値をmain 関数が返すようにする。返り値は return命令で指定することができるが、return 命令を省略すると、ゼロを帰したものとみなされる。こ

56

Page 58: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

の返り値は$? というシェル変数に反映される。なお、異常終了の場合はプログラムを終了する前

にエラーメッセージや使い方の説明を表示するのがよい。

main 関数は 2つの引数を取るように作ることができる。始めの引数は int 型で、コマンドラインで与えたパラメーターの数に 1を加えた値がセットされる。2番目の引数は 文字列の配列で、コマンドラインで与えたパラメーター文字列を要素とする配列がセットされる。パラメーター文字列

を数値として利用するには、文字列を数値に変換する関数を使う必要がある。

#include <iostream>#include <cstdlib>using namespace std;int main (int argc, char* argv[]){

if(argc > 2) //パラメーターの数をチェック{

int x = atoi(argv[1]); //第 1引数を整数に変換int y = atoi(argv[2]); //第 2引数を整数に変換cout << x + y << endl;return 0; //正常終了

}else //パラメーター不足時のエラー処理{

cerr << "Two integers required." << endl;return 1; //異常終了

}}

下のように、異常終了の場合に exit 関数で終了するようにすることもできる。関数の最後に達して終了する場合は自動的にゼロが返される。

int main (int argc, char* argv[]){

if (argc <= 2){

cerr << "Two integers required." << endl;exit( 1 );

}cout << atoi(argv[1]) + atoi(argv[2]) << endl;

}

練習問題 get_gcd 関数の定義を使い、コマンドラインから与えられた 2つの整数の最大公約数を計算して表示するプログラムを作りなさい。ただし、2つの正の整数が与えられなければ、計算をするかわりに正しい使い方を表示するようにすること。(最初から完全な機能を実装するのではなく、簡単な部分から始めて少しずつ完全なものにしていくのがよい。たとえば、まず、2つのコマンドラインパラメーターをそのまま表示するプログラムを作ってみる。次に、パラメーターの数

をチェックする機能を付け加える。次に、パラメーターを数値データーに変換してそれを表示する

ようにする。それらの和を計算して表示してみてもよい。最後に最大公約数を計算する機能を追加

する。)

57

Page 59: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

第3章 簡易グラフィックスによるプログラミング練習

画面に図を表示する簡単なプログラムを作りながら C言語の練習をします。この章には文法上の新しい項目はほとんど含まれていませんから、気楽にやってください。場合によってはとばして

しまっても結構です。

コンピューターのグラフィックス画面は平面上の有限個の点の配列と考えられ、各点は横方向の

番号と縦方向の番号とで指定されます。 各点をピクセルといいます。 各ピクセルは色や明るさを

変えることができ、全体としていろいろな画像を形成します。 ピクセルの番号は座標とも言いま

すが、整数であることに注意しなければなりません。ピクセルの色も有限個の色の中から選ぶこと

ができるだけです。

文字の表示は C言語の標準関数で行うことができますが、図を表示する標準関数はありません。そのかわり、世の中にいろいろあるグラフィックスライブラリーから適当なのを選び、その中の関

数を使うことになります。ライブラリーとは、関連する関数定義などをひとまとめにして、誰でも

使えるようにしたものです。

ここのプログラムはすべてあるグラフィックスのライブラリーの機能を使ってプログラム中でグ

ラフィックスの表示を行います。「簡易グラフィックスライブラリ」と名付けたこのライブラリは

X 端末のウィンドウに基本的な図形を描く最小限の機能だけを提供するものです。これは非常に簡単に利用することができるようになっているので、プログラミング言語の勉強を始めたばかりの人

も容易に利用できるでしょう。

3.1 実行環境の準備

大学の cc環境内では、Linux上で上記のライブラリーを利用することができます。標準ライブラリーだけを使う場合は、ソースプログラム中でインクルードファイルのファイル名を指定する以

外には、何も指定する必要はありませんが、それ以外のライブラリーを使うには、インクルード

ファイルとオブジェクトライブラリーのありかおよび、オブジェクトライブラリーのファイル名を

コンパイル時に指定する必要があります。make コマンドでコンパイルする場合は、Makefile という名前の設定ファイルにこれらの情報を書いておきます。すると、make コマンドが Makefile の情報を自動的に読み込んで使ってくれます。

次のは Makefile の例です。

LOCALDIR = /NF/class0/tanigawa/local/LinuxCPPFLAGS = -I$(LOCALDIR)/includeLOADLIBES = -L$(LOCALDIR)/lib -lsgr -lxvig -lX11

58

Page 60: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

emacs で上のとおりに打ち込んで、作業ディレクトリーに Makefile という名前で保存してください。作業ディレクトリーを新しく作ったら、その度にそこにこのファイルをコピーします。

上の Makefile の1行目は、ライブラリーのファイルを含んでいるディレクトリーを指定しています。このディレクトリー内の include サブディレクトリーにインクルードファイル、lib サブディレクトリーにオブジェクトライブラリーが入っているものとしています。

確認のために、サンプルプログラムを動かしてみましょう。サンプルプログラムのソースファイ

ルは mandel.cc です。これを作業ディレクトリーにコピーし、端末で次のように打ってコンパイ

ルをして下さい。

make mandel

エラーが出なければおそらく大丈夫です。 ./mandel と打ってこのプログラムを起動し、表示され

る説明を見ていろいろ操作してみて下さい。

3.2 点と線

始めに点と線だけを描くプログラムを作ってみましょう。下のプログラムを ten-to-sen.cc と

いう名前のファイルに保存してください。

// 点と線#include <sgraph> // 簡易グラフィックスライブラリのヘッダーvoid draw() // 関数 draw の定義の開始{

SGWindow win; // ウィンドウを用意するwin.Point (300, 100); // 点を打つwin.Line (100, 100, 300, 300); // 線分を描くwin.GetCursor(); // ボタンが押されるまで待機

} // 関数 draw の定義の終了int main() // main 関数{

draw();return 0;

}// ten-sen.cc おしまい

make ten-to-sen と打ってコンパイルし、 ./ten-to-sen と打って実行して下さい。Makefile がカレントディレクトリに無いとコンパイルがうまく行きませんから注意して下さい。ten-to-sen が正常に走ると、描画ウインドウが開いて中に小さな点と直線とが描かれます。 マウスカーソルを

描画ウインドウの内部に入れてマウスボタンでクリックすると、ウインドウが閉じてプログラムが

終了します。

では、プログラムの説明です。

draw 関数の定義中で最初に SGWindow win; と書いてあるのは、描画ウインドウを一つ用意

するための宣言です。 整数変数を一つ用意するために int x; と書くのと同じと考えて下さい。

SGWindow 型の変数 win を作ったということができますが、簡易グラフィックスライブラリーで

は SGWindow は型ではなくクラスと呼び、win はクラスのインスタンスと呼びます。 クラスの

インスタンスはオブジェクトとも言います。 これらは C++ などのオブジェクト指向言語の用語です。

59

Page 61: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

win のような SGWindow クラスのインスタンスのことを、「ウインドウオブジェクト」と呼ぶことにします。 win.Point(100,300); は、ウインドウオブジェクト win に対して点を描くように

指令を与えています。オブジェクトに対するこのような指令のことを、「オブジェクトにメッセー

ジを送る」と表現します。 どのようなメッセージにどのように反応するかはクラス毎に決められ

ています。これをメソッドと呼びます。 SGWindow クラスには Point メソッドがあるので、win

のような SGWindow クラスのインスタンスは上記のようなメッセージに反応することができます。int 型や double 型など、基本の型は値を記憶することと記憶している値を読み出すことの2

つの機能しかありませんが、クラスは他のいろいろな機能 (メソッド)を持ちます。オブジェクト名とピリオドが付いていることを除いては、メソッドの呼び出しは関数呼び出しと

全く同じです。 Pointメソッドの引数は、左から、点のX座標とY座標です。簡易グラフィックスライブラリーでは、座標として指定できる数値は整数だけです。 win.Line(100,100,300,300);

は win に直線分を描かせるもので、括弧の中の引数は、左から、線分の一方の端の X座標、そのY座標、線分のもう一方の端の X座標、その Y座標です。GetCursor メソッドは、ウインドウの内部がマウスでクリックされるまで待機します。 もしこ

れが無いとプログラムは即座に終了し、同時にウインドウが閉じてしまいます。嘘だと思うなら、

この行をコメントアウトしてみましょう。

座標はウインドウに対する相対的な座標で、原点はウインドウの描画領域の左上の角です。X座標値は右に向かって大きくなりますが Y座標値は下に向かって大きくなります。 コンピューターの画面上の位置のあらわし方はこれが一般的です。 数学では普通、Y座標値は上に向かって大きくなり、向きが逆になっていますから注意して下さい。 また、画面表示は有限個のピクセルで構

成されるため、座標値には整数を使います。 座標系がどうなっているかをテストするために、線

分の位置をいろいろと変えて納得の行くまで試して下さい。

さて、ウインドウの描画領域の右端の X座標は 500、下端の Y座標は 500 ですが、ウインドウの大きさをプログラムで変えることもできます。 それには、ウインドウインスタンスの宣言のも

う一つの形式を使います。

SGWindow win ( "TEN TO SEN", 700, 350 );

プログラムの中の SGWindow win; をこれに入れ換えて、ウインドウの枠の上部に表示されるタイ

トルとウインドウの大きさを見て下さい。 新しい宣言の意味はすぐにわかるでしょう。

座標値を 10.5 のような実数にすると、コンパイル時にウォーニング (warning) が出ることがあります。Warning は警告で、コンパイル自体は最後まで行なわれ実行ファイルを走らせることもできます。この場合コンパイラーは数値の小数点以下を勝手に切り捨てて整数にしますが、本来整

数でなければならないところにうっかりして実数を使っている可能性があるので、親切なコンパイ

ラーなら警告を出します1

練習問題

線の太さを変えるには Thickness() メソッドを使います。

win.Thickness(3);

1 int のような型名を関数名のように使ってデータ型の手動変換を行うことができます。たとえば int(2.54) は 整数の 2 になります。これを型キャストと呼びます。C では (int)2.54 という書き方を使います。また、最新の C++ では、static cast〈int〉(2.54) のように書く決まりになりました。

60

Page 62: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

を win.Line(........); の前に入れてみて下さい。win.Line(........); の後では効果が無い

ことも確かめて下さい。また、win.Point(......); の前に入れても点には影響しないことを確

かめて下さい。

3.3 カラー

描画に使う色を変えるには Color() メソッドを使います。

win.Color(50);

を win.Point(300,100); の前に入れてみて下さい。 Color メソッドは点にも影響します。色は

0 から 127 までの整数で指定します。 これをカラーコードと呼びます。 次のプログラムで全部の色を使ってみましょう。

// ファイル名: all-colors.cc//#include <sgraph.h>void ColoredLines (){

SGWindow win ( "All Colors", 300, 530 );win.Thickness ( 3 );int color = 0;while ( color < 128 ){

int y = 10 + color * 4;win.Color ( color );win.Line ( 20, y, 280, y );color += 1;

}win.GetCursor();

}

int main(){

ColoredLines ();return 0;

}

始めに整数変数 color には 0 を入れていますが while 文の中で color+=1 によって color の値

を 1 ずつ増やしているので、win.Color(color) は 0 から順に 1、2、3 ... と色の設定を変えます。 color+=1 は color=color+1 を短く書いたものです。 1 増やすだけなら他に color++ とい

う書き方もあります。 また、++color という書き方もあります。 (これらの書き方が皆同じであることが本当かどうかを試してみてください。) win.Lin(...) で描く線の垂直位置は変数 y の値で与えられています。これは color の値に応じて式で計算されていて、color の値が大きいほど

線の位置は下がります。 線と線の間隔は 4 ですが線の太さは 3 ですから、少し隙間が空きます。全部の線が収まるように、ウインドウの高さは 530 に設定してあります。

while の繰り返しの中の最後のところに

win.GetCursor();

61

Page 63: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

を追加すると、マウスでクリックするかどれかのキーを押す度に線が1本描かれるようになりま

す。 さらに、printf か cout を使って color の値を表示するようにすると、カラーコードと色の対応を確かめることができるでしょう。

さて、次の関数ではどのような線が描かれるかを予想して下さい。 この関数定義を今のプロ

グラムに挿入し、main() 関数の中にこの関数への呼び出しを入れてプログラムを実行してみてください。 挿入する場所は ColoredLines 関数の定義の直前か直後です。 関数やメソッドを使

うには使いたいものがすでに定義されている必要があるので、#include <sgraph.h> の前では

ColoredCrossLines 関数は描画メソッドが何も使えませんし、main 関数の定義の後では main 関

数から ColoredCrossLines 関数を呼び出すことができません。 空行を適当に入れて見易くして

下さい。

void ColoredCrossLines (){

SGWindow win ( "All Colors", 300, 530 );win.Thickness ( 1 );int color = 0;while ( color < 128 ){

int y1 = 10 + color * 4;int y2 = 10 + (127 - color) * 4;win.Color ( color );win.Line ( 20, y1, 280, y2 );color ++;

}win.GetCursor();

}

繰り返しの構文には while の他にも for が使えます。 先の関数は for を使って次のように書き換えても全く同じです。

void ColoredCrossLinesByFor (){

SGWindow win ( "All Colors", 300, 530 );win.Thickness ( 1 );for ( int color = 0 ; color < 128 ; color ++ ){

int y1 = 10 + color * 4;int y2 = 10 + (127 - color) * 4;win.Color ( color );win.Line ( 20, y1, 280, y2 );

}win.GetCursor();

}

while の前の int color = 0 と 繰り返しの中の最後にあった color++ が for の後の括弧の中に入ってしまいました。 ここでは color++ の後にセミコロンを付けないことに注意して下さい。

そういう決まりになっているからです。 for を使うとこのように少し短く書くことができます。 for文は while 文で同じものが書けますから、使わなければならないというものではありませんが、よ

62

Page 64: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

く使われるので憶えておきましょう2。

練習問題

線分の両端の座標の計算を工夫すれば、上のプログラムを修正していろいろ面白い図形を描くこ

とができそうです。Y座標だけではなく X座標を変えても良いのです。たとえば、次のようなものを式をいろいろ変えて試してみなさい。

double angle = color * (2*3.1416/128);int x1 = int(250 + 200 * cos(angle));int y1 = int(250 + 200 * sin(angle));int x2 = int(250 + 100 * cos(angle*2));int y2 = int(250 + 100 * sin(angle*2));

三角関数を使うので実数計算になりますが、座標は int 型ですから、キャスト int() で実数から

整数への変換を行っています。また、数学関数を使うため、

#include <cmath>

を始めに書く必要があります。

3.4 四角形

多角形を描くには直線を組み合わせてもよいのですが、底辺が水平に置かれた長方形なら一つの

メソッドで簡単に描くことができます。 Rectangle メソッドです。新しいファイルに次のプログラムを入れて下さい。

// ファイル名: sikaku.cc//#include <sgraph.h>SGWindow win;void seihou (){

win.Color (32);win.Rectangle (200, 200, 300, 300);

}

int main(){

seihou ();win.GetCursor ();return 0;

}

これをコンパイル、実行して下さい。ウインドウの真中に緑の正方形が描かれたでしょう。このプ

ログラムでは、win は関数定義の外側で宣言しています。オブジェクトを関数定義の中で宣言すると、そのオブジェクトはその関数定義の内部でしか使えません。しかし関数定義の外で宣言する

とすべての関数の中でそのオブジェクトを使うことができます。こういうオブジェクトをグロー

2なお、C では、for 文で繰り返し条件に使う変数の宣言は for 文が始まる前に行わなければなりません。

63

Page 65: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

バルなオブジェクトと呼びます。(普通の変数でも同じことが成り立ちます。) このプログラムで

は seihou 関数でウインドウに正方形を描き GetCursor メソッドでウインドウがクリックされる

まで待機しています。ウィンドウオブジェクトをグローバルにしたので、GetCursor メソッドは

seihou— 関数の外側で呼び出すことができます。

Rectangle メソッドには、四角の対角線で結ばれる2つの角の座標を引数として与えます。 左から、第一の角のX座標、その角のY座標、第2の角のX座標、その角のY座標です。Rectanglメソッドに5番目の引数として 1 を与えると四角形の内部を塗りつぶすことができます。

win.Rectangle (200, 200, 300, 300, 1);

を seihou 関数の定義の中の win.Color (32); の前に入れて実行してみて下さい。 塗りつぶし

の色も Color メソッドで変えられます。win.Color(80); を入れると紫になりますが、内部を紫

にするにはさてこれをどこに入れればよいでしょうか? 予想して実際に試してみて下さい。

練習問題

正方形をもう一つ描いてみましょう。

win.Color (16);win.Rectangle (220, 220, 280, 280, 1);

この2行を seihou 関数の定義の中に含めて下さい。カラーコード 16 は黄色です。この正方形がもう一つの正方形で隠れてしまってはいけません。さて、「まともな」絵にするには上の2行をど

こに入れるのがよいでしょうか? またどうしてそこがよいのですか?

3.5 配列と任意多角形

Rectangle では長方形しか描けません。しかも、長方形でも傾いたのはだめです。任意の多角

形を描くには Polygon メソッドと「配列」を使います。この場合は全ての頂点の座標を指定する

必要があります。

64

Page 66: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

// ファイル名: polygon.cc//#include <sgraph.h>

SGWindow win("A Polygon");

void polygon (){

win.Color (32);int points [6] = { 250, 100, 100, 250, 400, 250 };win.Polygon (points, 3);

}

int main(){

polygon ();win.GetCursor ();return 0;

}

配列は複数の同じ種類のデータを並べて一まとまりにしたもので、全部ひっくるめて一つの名前を

付けます。 ここでは points がそれです。

int points [6] = { 250, 100, 100, 250, 400, 250 };

このように、要素の型 int、配列名 points、要素数 6、および各要素の初期値を指定して宣言しています。括弧の種類に注意してください。

Polygon メソッドは最初の引数として与えた配列から多角形の頂点の座標値を取り出して使います。 配列の最初の要素と 2番目の要素は第一の頂点の X座標と Y 座標になり、隣の頂点の X座標と Y座標、その隣の頂点の X座標と Y座標と続きます。 Polygon メソッドの 2番目の引数は多角形の頂点の数です。 上のプログラムでは 3 を指定していますから三角形を描くことになります。配列にはX座標とY 座標が交互に入りますから、配列の要素の数は多角形の頂点数の 2倍必要です。 Cの配列は単純で自分の要素数を覚えておく機能が無いので、プログラマーがそれを与える必要があるのです。

では、もう一つ頂点を追加して四角形を描くように変更してみて下さい。 配列の初期値を追加

するだけではだめで、配列の要素数と Polygon メソッドに与える頂点の数も変える必要があります。 配列に指定したとおりの順番で点が線で結ばれるので、辺と辺が交わっているようなもので

も描けます。

練習問題

Polygon メソッドに 3番目の引数として 1 を指定すると、中を塗りつぶした多角形ができます。試してみなさい。

65

Page 67: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

3.6 円

円は Arc メソッドでも Circle メソッドでも Ellips メソッドでも描くことができます。まず Arcメソッドを使ってみましょう。

// ファイル名: maru.cc//#include <sgraph.h>

SGWindow win("A Circle");

void maru (){

win.Color (32);win.Arc (250, 250, 100, 100, 0, 360, 1);

}

int main(){

maru ();win.GetCursor ();return 0;

}

Arc メソッドの最初の 2つの引数は円の中心の X座標と Y座標です。次の 2つの引数は両方とも半径で、同じ値を指定すると円になり違う値を指定すると楕円になります。 どんな楕円になるか

はやってみればわかります。

6番目の引数は 360 にすると完全な円が描けますが、360 より小さいと不完全な弧になります。たとえば 180 なら半円です。 5番目の引数は不完全な弧を描く場合に弧の端の位置を指定するものですが、完全な円の場合でも省略はできません。 7番目の引数として 1 を指定すると内部の塗りつぶしになりますが、これを省略すると塗りつぶしません。 不完全な弧の場合、塗りつぶしは

扇型になります。

円を描くだけなら Circle メソッドで簡単にできます。 Circle メソッドの引数は 円の中心の X座標と Y座標および円の半径です。 塗りつぶしを指定する引数を付けることもできます。 また、Ellips メソッドは楕円が描けます。引数は中心の X座標、中心の Y座標、X方向の半径、Y方向の半径です。 塗りつぶしを指定する引数を付けることもできます。

Arc メソッドでパックマンの形を描くことができます。 どうすればよいか考えて実験してみてください。 ついでにパックマンに目を付けて可愛くしてあげましょう。 方眼紙に絵を描いて座標

や半径などをどれぐらいにすればよいか調べてからプログラムするのが賢明です。 やや上級と自

認している人は、同じパックマンをあちこちにいっぱい発生させるプログラムを考えてみてはどう

でしょうか。

3.7 点で描くプログラム

前に作った tsubasa プログラム (第1章)を変更して、簡易グラフィックスライブラリを使って直接表示するようしてみましょう。 printf 関数で座標値を出力していたところを、ウインドウ内

66

Page 68: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

に点を描くように変更します。 実数の座標値をウインドウ内の位置をあらわす整数に変換してか

ら点を描きます。この部分は他の部分とは独立性が高いので関数にします。 ただし、点の位置を

伝達するために引数を使います。

// ファイル名: feather.cc//#include <sgraph.h>

// ウインドウのサイズconst int XW = 600; // 横幅const int YW = 600; // 高さ

// 実空間の限界、(-R,-R) がウインドウの左下隅、(R,R)が右上隅に来る。const double R = 20.0;

// ウインドウオブジェクトの設定SGWindow Win ("Feather", XW, YW); // ウィンドウタイトルは Feather

// 実空間の座標を指定してウインドウに点を表示する。void Plot( double x, double y ){

int ix = int( (XW/2) + (XW/(2*R)) * x ); // 原点はまんなかint iy = int( (YW/2) - (YW/(2*R)) * y ); // 縦方向はマイナスWin.Point (ix, iy);

}

int main(){

Win.Color(0); // 赤信号Win.Circle (50, 50, 40, 1); //double x = 1.0; // 始めの点double y = 0.0; //int i = 200000; // 繰り返し回数Win.Color (112); // 灰色が好きwhile ( i > 0 ) // 残り回数 > 0 か?{ Plot(x, y);

double nx = - 0.97 * x + y - 5 + 5 / (1 + x * x);y = -0.98 * x;x = nx;i --; // 残り回数を減らす

}Win.Color (60); // 青信号Win.Circle (50, 50, 37, 1); //Win.GetCursor(); // 終りますか?return 0;

}

XW と YW の宣言には const int を使っています。これは整数の定数 (constant)ということです。少し古い文法では const int を単に const と書いてもよいことになっていましたが、改訂の結果それは認められなくなりました。const double R となっているので、R は実数定数です。

const を付けなければこれらは変数になりますが、それでもプログラムは動作します。わざわざconst を付けるのは、定数であることをコンパイラーに教えてあげることによって計算の効率が良

67

Page 69: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

くなることが期待できるからです。また、const を付けて宣言すると後で値を変更することができなくなりますから、うっかり変な値を代入してしまうというようなミスを防ぐことができます。

main 関数の中に Win.GetCursor が無いと、プログラムが勝手に終了してウインドウが閉じて

しまいますから、これを忘れてはいけません。 また、このプログラムは計算の回数が多くて時間

がかかりますから、計算を始める前に赤信号を表示し、計算が終ったらそれを青信号に変えるよう

にしてあります (速いコンピューターだと赤信号は見えないかもしれない)。

練習問題

const を消してコンパイルして、エラーにならないことを確かめなさい。 また、const を入れ、main 関数の中のどこかに XW=300; などと書いてコンパイルし、エラーメッセージを見なさい。後

で元に戻して下さい。

3.8 2次元配列

次を上で作ったプログラムの main() 関数の前に挿入して、main 関数の中で Plot(x,y) のかわ

りに ColorPlot(x,y) を呼び出すように変更してから実行してみて下さい。 これは点の頻度に応

じて点に色を付けて表示するものです。

// 色付プロットint Count [XW][YW];void ColorPlot( double x, double y ){

// 実座標をウィンドウ座標に変換int ix = int( (XW/2) + (XW/(2*R)) * x );int iy = int( (YW/2) - (YW/(2*R)) * y );if (ix >= 0 && ix < XW && iy >= 0 && iy < YW) //内部?{

Count[ix][iy] ++; // 頻度更新int color = Count[ix][iy] * 2; // 色を決めるif (color > 95) // 95 までにする

color = 95;Win.Color (color); // 色を付けてWin.Point (ix, iy); // 点を描く

}}

ColorPlot 関数では、画面上の縦横 600点、合計 360000点の点の頻度を記録するために Count という名前の 2次元配列変数を使っています。 2次元配列変数の各要素は、変数名と2つの整数を

使って、たとえば Count[ix][iy] のようにあらわします。ix や iy のような、要素を指定する整

数は添字 (そえじ、index)です。Count の宣言は、2行目の

int Count [XW][YW];

です。これは Count の要素の型が int であることと、第1の添字の上限が XW-1 であることと第2の添字の上限が YW − 1 であることを意味しています。 添字の下限は 0 ですから、要素数は

68

Page 70: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

XW× YW です。

Count は関数定義の外で宣言されています。このようにしたのは、この配列が点の頻度を常に保持していて忘れないようにする必要があるからです。関数定義の外で宣言された変数はグローバ

ル変数と言い、プログラムの実行が終了するまで存在し続けます。また、グローバル変数の初期値

を指定しない場合は、自動的に 0 で初期化されます。関数の中で宣言されたローカル変数の名前がグローバル変数と同じである場合、そのローカル変

数を宣言している位置より後では、この名前はローカル変数の方を意味します。 したがって、グ

ローバル変数をアクセスする必要がある関数では、ローカル変数の名前がグローバル変数にバッ

ティングしないようにする必要があります3。

このプログラムでは、Count は ColorPlot 関数の中だけで使われているので、ColorPlot 関数のローカル変数にしたいところいですが、普通に関数内で宣言したローカル変数は値を憶えていてく

れません。これを自動変数 といいます。この問題は 静的変数で解決することができます。Countのグローバル宣言を消し、かわりに

static int Count [XW][YW];

という宣言を ColorPlot 関数の定義の中に入れるだけです。static という修飾子を付けることによって、静的なローカル変数になります。これは、グローバル変数と同様にプログラムの実行中

ずっと存在し続けますが、別の関数からアクセスすることはできません。静的変数の初期値は、何

も指定しない場合はゼロです。

さて、if 文の条件式は4つの不等式を論理積をあらわす演算子 && で結合したもので、わかりや

すく書くと次のようになります。

(ix >= 0) && (ix < XW) && (iy >= 0) && (iy < YW)

論理積は複数の条件が同時に成り立つことを意味します。そこで、この論理式は点の X 座標と Y 座標がウィンドウ内部の範囲に入っているときに真になります。 配列 Count の要素としてCount[-100][1000] のようなものは存在しません。もし、ウィンドウの外にある点が指定された

ときそのまま実行すると、存在しない配列要素をアクセスすることになり、プログラムがおかしく

なる可能性があります。 C 言語はこのような場合のチェックを自動的には行いませんから自分でチェックする必要があります。

ix >= 0 && ix < XW は 0 <= ix < XW と書き換えることはできませんから注意してください。

後者の数学での普通の解釈は Cでは通用しないので、このような式を使ってはいけません。このプログラムはウインドウの各点の頻度を記録するためにたくさんのメモリーを使うので、実

行ファイルが非常に大きくなる場合があります。 その場合は走らせた後で実行ファイルを消去し

て下さい。

3.9 参照渡し引数

実空間の座標からウィンドウ内の座標を計算する部分を独立した関数にして、それを ColorPlot関数が呼び出すようにしてみましょう。 この程度の計算では別関数にしてもしょうがないと思う

3C++ では::Count[ix][iy] ++; のように、グローバル変数の名前の前に :: を付けて、ローカル変数と区別することもできます。 この :: はスコープ解決演算子と言います。

69

Page 71: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

かもしれませんが、何事も修行ですからね。後でもっと複雑な計算をして featherプログラムをもう少しおもしろくしようと思います。

計算の結果がもし1つの値なら、return 文を使って関数の戻り値として計算結果を伝達することができるのですが、今の場合は2つの整数なのでこの単純なアプローチをとることができませ

ん。 そこで、ここでは引数の参照渡しという方法を使うことにします。参照渡しに対して、今ま

で出て来た引数の渡し方は値渡しと呼びます。 FORTRAN では常に参照渡しが行われ値渡しは使えません。 Pascal では一応値渡しと参照渡しを使い分けることができますが、データのタイプによる制限があり処理系の間での統一も取れていません。 C では参照渡しを行うことは難しいテクニックであり注意して使わないと危険です。それに対して、 C++ では、値渡しと参照渡しを安全に使い分けることができます。 ここでは C のやり方ではなく C++ のやり方を使います。次の関数定義を Plot や ColorPlot 関数の定義よりも前で、XW、YW、R が定義されているところよりも後に入れて下さい。

void RealToWin(double RealX, double RealY, int& WinX, int& WinY){

WinX = int( (XW/2) + (XW/(2*R)) * RealX );WinY = int( (YW/2) - (YW/(2*R)) * RealY );

}

さらに、Plot と ColorPlot の中の座標を計算している部分はint ix = int( (XW/2) + (XW/(2*R)) * x );int iy = int( (YW/2) - (YW/(2*R)) * y );

のようになっていますが、この部分を次のように書き直して下さい。int ix, iy;RealToWin(x, y, ix, iy);

修正はこれで完了です。コンパイルして動くことを確かめて下さい。

RealToWin(x,y,ix,iy); と呼び出していますが、御想像どうり、これで x と y の値を使って計算を行い、結果が変数 ix と iy に入ります。 ix と iy は変数でなければなりません。さもなければ計算結果を代入することができないからです。 この ix と iy を参照引数と言います。

RealToWin 関数の定義では、参照引数の型の指定に int ではなく int& を使っています。 & を

付けることによって参照引数であることを示します。 こうすると、関数内でのこの仮引数への読

み書きは、実引数に直接読み書きを行う効果を持つようになります。たとえば、RealToWin 関数内で WinX という仮引数に計算結果を代入していますが、RealToWin(x,y,ix,iy) と呼び出された場合、実際には対応する実引数である変数 ix への代入が行われます。もし & を付けないで引数の型を指定すると、関数内で仮引数に代入しても実引数には影響しま

せん。2つの & の内のどちらか一つをわざと消したプログラムを走らせてみてください。そうす

るとウィンドウ内の点の位置が正常に伝達されていないことがわかるでしょう。

座標計算を関数 RealToWin にして独立させたので、それにもう少し処理を追加してみましょう。長くなるだけなのでどんな計算をしているのかは説明しませんが、前の単純なものと差し替えてプ

ログラムを実行してみれば大体わかるでしょう。

70

Page 72: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

void RealToWin(double x, double y, int& WinX, int& WinY){

const double cx = -8; // 波の中心の実 X座標const double cy = 10; // 波の中心の実 Y座標const double freq = 2.5; // 波の振動数const double amp = 0.75; // 波の振幅x = x - cx; // 原点をずらすy = y - cy;double r = sqrt(x*x + y*y); // 中心からの距離double rr = r + cos(freq * r)*(amp/freq); // 新しい距離double rrr = rr/r; // 倍率x = x * rrr + cx; // 新しい位置、原点を戻すy = y * rrr + cy;WinX = int( (XW/2) + (XW/(2*R)) * x ); // ウィンドウ内の位置WinY = int( (YW/2) - (YW/(2*R)) * y );

}

三角関数 cos() や平方根関数 sqrt() を使っているので、プログラムの先頭に

#include <cmath>

と書いてください。

RealToWin 関数の中の const 定義の値を変えて効果を試すと面白いでしょう。Bessel(べっセル)関数を知っている人は cos の代りに j0 を使ってみてください。j0(x) は1次の第1種 Bessel 関数です。 これは cos(x) と非常に似ていますが x=0 から遠ざかるにつれて振幅が小さくなります。j0 を使う場合は amp の値を大きくしないと効果があらわれないかもしれません。

3.10 配列と for 文

配列の全要素にアクセスするとき for 文を使うという定石があります。 たとえば、A を 100個の実数からなる配列としその全要素に 1 を代入したい場合は次のようにします。

for ( int i = 0 ; i < 100 ; i++ )A[i] = 1.0;

また、B が 10x100個 の実数からなる2次元配列でその全要素を 1 にしたい場合は for 文を2重に使って次のようにします。

for ( int i = 0 ; i < 10 ; i++ )for ( int j = 0 ; j < 100 ; j++ )

B[i][j] = 1.0;

整数変数 i や j は for 文の「制御変数」といいますが、上のようにこれを for の後ろの括弧の中で宣言するのは C++ での定石で、 C では関数定義の最初の部分で関数のローカル変数として宣言します。

さて、上のプログラム feather.cc では同じ点に何度も色を付けていますが、最後に付けた色だけが残るので、それ以外の Point メソッドの呼び出しは無駄です。 端末の描画は時間がかかる処理なので、最終的な絵が完成するまでは Point メソッドを呼び出さないようにすると、処理速度の向上が期待できます。ColorPlot 関数では配列に全ての点の頻度情報を記録しています。そこで、ColorPlot では描画をしないで記録だけにしておき、最後にまとめて描画することにしましょう。

71

Page 73: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

まず、ColorPlot は頻度情報を更新するだけにするので、色を決めて点を描く部分を取り除いてください。配列 Count はグローバル変数に戻します。実際の描画は計算が全部終ってから下の関数で一括して行います。これを main() の中の適当な場所で呼び出すようにしてください。

void ColorDraw(){

for(int ix = 0 ; ix < XW ; ix++) // ウィンドウのすべてのfor(int iy = 0 ; iy < YW ; iy++) // 点について以下を実行{

int n = Count[ix][iy]; // 頻度を取り出すif (n > 0) // 頻度 > 0 なら以下を実行{

int color = n * 2; // カラーコードを計算if (color > 95) // 95 までしか使わない

color = 95;Win.Color (color); // 色を付けてWin.Point (ix, iy); // 点を描く

}}

}

この関数では、2重の for 文で2次元配列 Count に入っている頻度情報を読み出し、点に色を付けています。 配列の添字がそのまま点の座標になっていることに注意してください。 頻度 0の点を塗る必要はありませんから、 if 文で判断して頻度 0の点は塗らないようにしました。

3.11 再帰図形

同心円を描きましょう。最も外側と最も内側の円の半径は任意に与えることができるものとし、

外側から 2番目の円の半径は、最も外側と最も内側の円の半径の加算平均とします。 加算平均とは足して 2で割ったもののことです。 外側から 3番目のは外側から 2番目のと最も内側のとの加算平均で、後も同様です。外側から円を描いていくと、最も内側の円との間がどんどん詰まってい

きますが、いくら円を描いても、最も内側の円に達することはできません。 アルキメデスのウサ

ギとカメの話しを思い出しますね。 プログラムで無限個の円を描くわけにはいかないので、どこ

かで描くのをやめなければいけません。 実際には線に太さがあるので、最も内側の円の近くは円

で塗りつぶされていくら円を描いても変化が無い状態になりますから、最も内側の円に線の幅程度

まで近づいたらおしまいにします。

このような同心円を描く機能を持つ関数を定義しましょう。関数名は coaxialとし、最大円の半径がx、最小円の半径が yのとき coaxial(x,y)と呼び出すと目的の同心円が描かれるようにします。もし、最大円の半径 150、最小円の半径 100の同心円の描き方、つまり coax(150,100)の実行手順がわかっているとすると、coax(200,100)を実行するには半径 200の円を描いた後で、coax(150,100)を実行すればおしまいです。 200と 100の加算平均は 150だからです。もし coax(150,100)の手順がわからないとしても、coax(125,100)ができれば、半径 150の円を描いてから coax(125,100)をやればよいでしょう。それもわからなければ ... と進んでいくと、coax(103.125,100)、coax(101.5625,100)、coax(100.78125,100) へと進んでいきます。線の太さが1ぐらいの場合、これぐらいになると塗りつぶしの状態になりますから、coax(100.78125,100) では一番内側の円を一つ描くだけでよくなります。この描き方は自明です。この考えをそのままプログラムにすると次のようになります。た

72

Page 74: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

だし、DrawCircle という関数があらかじめ定義されていて、引数として半径を指定して呼び出すことによって単純な円が描けるようになっているとします。

void coaxial (double maximum, double minimum){

if (maximum - minimum > 1) // 差が1より大なら{

DrawCircle(maximum); //最大円を描きcoaxial((maximum + minimum)/2.0, minimum);//後は再帰呼出

} //出におまかせelse // 差が1以下なら

DrawCircle(minimum); // 最小円を描いて終り}

この if 文は else 節を伴っています。 if の後の括弧の中に入っている条件が成立すればそのすぐ後の文(この場合は複数の文が大括弧で副文としてひとまとめにされている)を実行しますが、条件

が成り立たなければ else のすぐ後の文を実行します。 線の太さを1ぐらいとしています。coaxial の中で coaxial を呼び出しています。 このように関数が内部で自分自身を呼び出すことを再帰呼び出しと言います。

実際に動作するかどうかをテストするために完全なプログラムを作るのは宿題としておきます。

DrawCircle を自分で作る必要がありますが、簡易グラフィックスライブラリーを使って図を表示するようにするとよいでしょう。その際、DrawCircle 関数は double 型の引数で呼び出されることに注意してください。言うまでもなく、ウィンドウに描く円の半径はこの引数の値に比例するよ

うに決めるべきです。 比例定数は好きなように決めてください。

coaxial 関数の処理がどのように進むのかを具体的に追ってみれば、実は単純な繰り返しを行っているだけだということがわかります。実際、coaxial 関数は繰り返しを使った次の関数で代用することができますから、確かめてみてください。

void coaxial2 (double maximum, double minimum){

while (maximum - minimum > 1){

DrawCircle(maximum);maximum = (maximum + minimum)/2;

}DrawCircle(minimum);

}

次に、平面上の2点が与えられたらその間に点を隙間無く並べて2点をつなぐような関数を考

えましょう。 まず、直線にそって点を並べる単純な方法で行きましょう。 与えられた2点 A とB が離れている場合は間にたくさんの点を並べる必要があり、すべての点の位置を計算するのは少し面倒です。 そこで2点の中点 C を取り、まず A-C 間をつなぎ次に C-B 間をつなぐことによって A-B 間をつなぐことができることに注目します。ただし、A と B の間隔が点一つ分よりも小さかった場合は、分割など行うまでもなく A か B に点を一つ描くだけでおしまいにします。 A-Bが離れている場合でも、分割を繰り返すことによって各部分はどんどん小さくなって最終的に点一

つ分になるはずです。 この考えをプログラムにします。次のプログラムは完全なソースプログラ

73

Page 75: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

ムです。

// lineplot.cc//#include <sgraph.h>#include <cmath>

// ウインドウのサイズconst int WIDTH = 500;const int HEIGHT = 500;

// ウィンドウ オブジェクトSGWindow Win ("Saiki Zukei", WIDTH, HEIGHT);

// 点を描く関数、座標変換は行っていない。void DrawPoint(double x, double y){

Win.Point ( int(x), int(y) );}

// 線を引く関数の定義、引数は点1の x,y と点2の x,yvoid DrawLine(double x1, double y1, double x2, double y2){

if ( hypot(x1-x2, y1-y2) > 1.0 ){

double x3 = (x1 + x2)/2; // 中点の x座標double y3 = (y1 + y2)/2; // 中点の y座標DrawLine(x1, y1, x3, y3);DrawLine(x2, y2, x3, y3);

}else

DrawPoint(x1, y1);}

// main 関数int main(){

Win.Color(80); // 紫色DrawLine(100, 300, 400, 100); // 点 (100,300)と (400,100)を繋ぐWin.GetCursor(); // 一旦停止return 0;

}

hypot 関数は2つの実数の自乗の和の平方根を計算するもので、2点の間隔を計算するのに使っています。 これを使うために cmath を #include で読み込んでいます。

DrawLine 関数も内部で自分自身を呼び出す再帰呼び出しを使っています。 しかし、coaxial 関数と違ってこの処理は単純な繰り返し処理に還元することはできません。それは、DrawLine 関数が自分自身を 2度呼び出しているからです。上のプログラムは2点を直線で結びましたが、もう少しおもしろい結び方を試してみましょう。

上のプログラムでは第3の点として2点の中点を取ってそこで分割していましたが、ここでは、第

3の点と元の2点が二等辺直角三角形の角になるようにしてみましょう。 ただし第3の点のとこ

ろが直角になるようにします。ちょっと数学をやってみればわかりますが、この場合は次のように

74

Page 76: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

して第3の点の座標が出て来ます。 すごく簡単です。

double x3 = (x1 + x2 + y1 - y2) / 2;double y3 = (y1 + y2 + x2 - x1) / 2;

DrawLine 関数の中で第3の点を計算しているところをこれに置き換えて、コンパイル、実行してください。 複雑なおもしろい図形が描かれたでしょう。この図形はドラゴン曲線と呼んでいます。

自分自身を2度呼び出す再帰を使うことによって複雑な図形が得られました。 このような図形は

再帰図形と呼びます。

DrawLine の中の2番目の再帰呼び出し

DrawLine(x2, y2, x3, y3);

の引数の順番を変えて

DrawLine(x3, y3, x2, y2);

とするとまた違った再帰図形が得られます。ただしウィンドウからはみ出るかもしれませんから、

main 関数の中の DrawLine の呼び出しの引数の値を変えて調整してください。この図形はどの細部を取ってもより大きな部分と相似になっているように見えるでしょう。 こ

のことを自己相似的と言います。 「フラクタル」というのも似た意味で使います。この図形は曲

線ですが、その長さはいくらでも長くなり得ます。2点間を分割すると各部分の長さは元の2点の距離の 1/

√2倍に小さくなるわけですが、全体の長さは

√2 倍に大きくなるからです。プログラム

では有限回の分割で打ち切っていますが、数学の対象としては無限回の分割でできる図形を考える

こともでき、その場合長さは無限になります。

75

Page 77: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

第4章 数値解析の基礎

はじめに

実用的な厳密解を求めることができる方程式はほとんど無いので、科学技術に数値解析の手法は

不可欠です。数値解析のために自分でプログラムを作ることはあまり無いかもしれませんし、プロ

グラムを作るにしても、既存のプログラムライブラリーを組み合わせるだけという場合が多いもの

ですが、どんな場合でも計算の精度や効率が問題になります。このような問題について良く理解す

るには、実際にプログラミングをおこなってみることが一番です。

ここでは、微分方程式の数値解法をネタにして数値解析プログラミングの練習を行います。 プ

ログラミング言語は C++ です。数値解析には伝統的に FORTRAN が使われますが、C++ でも十分対応できます。新しい文法事項としては、構造体型と演算子オーバーロードが出てきます。

4.1 解析解と数値解

時刻 t の実数値関数 y の常微分方程式

dy

dt= −yt

の解で初期条件

y = 1 (t = 0)

を満たすものを解析的に求めてみてください。微分方程式の左辺は y の t に関する微分係数で、

右辺は y と t の積の符号を変えたものです。答は

y = exp(− t2

2

)となります。exp は指数関数です。 この式は解析解です。解析解は任意の t の値に対する y の値

を正確に表現します。 これに対して、数値解は次のように有限個の t の値に対する y の値を近似

的に求めたものです。

76

Page 78: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

# t y# -------------

0.00 1.000.25 0.960.50 0.880.75 0.751.00 0.601.25 0.451.50 0.321.75 0.212.00 0.132.25 0.082.50 0.042.75 0.02

このような数値データはグラフにすると有用です。グラフ用紙に手で描くこともできますが、普通

は、gnuplot のようなプログラムを使います。

練習問題

好きな方法で、上のデータをグラフにしてみなさい。

4.2 数値解の計算手順

1階の常微分方程式を

dy

dx= f(t, y)

という形に書きます。y は従属変数で、独立変数 t の関数ですが、未知です。 f(t, y) は t と y の

関数で、これはわかっているものとします。はじめの例では、f(t, y) にあたるのは −yt ですね。

ある時刻 t での関数値 y が決まれば、f(t, y) によって y の微分係数が決まります。 微分係数

とは変化率ですから、それを使って次に y がどう変化するかをある程度予測することができます。

そこで、常微分方程式の数値解を求めるには、初期値から出発して、独立変数を少しずつ進めなが

ら次々に関数値を予測して行けばよいのです。

たとえば先の例では、 t = 0のとき y = 1はすでに決まっていますから、これを元にして t = 0.25のときの y の値を計算し、それが求まればそれを元にして t = 0.5 のときの y の値を計算します。

このようにして次々に y の値を計算して行きます。

どのステップも同じ手続きで計算すると、全体は単なる繰り返しになります。各ステップで次の

値をなるべく正確に予測することが重要です。すぐ前の値を元にして次の値を計算しますから、途

中で入った誤差は後の方に累積して現れます。各ステップでのステップ幅 (t の増分)を小さくすると1ステップの予測の精度は良くなるはずですが、そうすると逆にステップ数が多くなるので誤差

の累積の影響は大きくなります。 したがって、ステップ幅をあまり小さくしなくても高い精度が

得られる予測法が必要です。

4.3 既成アプリケーション (Scilab)での計算

よく使われる数値計算法をまとめて簡単に使えるようにしたプログラムライブラリーやアプリ

ケーションソフトが流通しています。 数値計算の実務では、仕事の能率を上げるためにも既成の

77

Page 79: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

信頼できるプログラムを探すことが重要です。 そういうものには有料のものも無料のものもあり

ますが、特殊用途のものを除いて値段の違いは、どれほど美しいグラフが描けるかとかカラー印刷

のマニュアルが付いているかどうかとか電話サポートを受け付けているかどうかといったことの違

いです。

ここでは、数値計算のアプリケーションソフトの一つである Scilab というシステムを使って微分方程式の求解をちょっとやってみましょう。 これは C のプログラムを作る前の準備体操のつもりですが、人によっては C や FORTRAN を勉強する気持が無くなるかもしれません。 もしそうなったら、C の勉強はほどほどでストップして、アプリケーションの活用に力を入れてください。

Scilab というアプリケーションは数値データをベクトルや行列の形でかたまりとして扱い、簡単な命令で大量の計算をいっぺんに行うようになっています。有理関数と文字列も扱えます。Mathe-matica のような汎用の数学ソフトに比べると、機能が絞られているだけに習得が容易です。 Scilabはただで使えるので日本語マニュアルや電話サポートはありません。 性能と安定性は十分でやた

らとたくさんの機能がつまっています。これは 各種 UNIX と Microsoft Windows で動作します。端末ウィンドウで scilab と打つと Scilab との対話用のウィンドウが開きます。(Scilab はどのマシンにも入っているコマンドではありませんから、動かなくても文句を言わないで下さい。しか

し、自分のマシンにインストールするのは簡単です。) この Scilab ウィンドウで対話を行います。まず、微分係数を計算する関数 f(t, y) = −yt を定義します。 Scilab ウィンドウで次のように入力して下さい。

deff("z = f(t,y)", "z = - t * y")

クォーテーションマークを間違えないようにしてください。 deff というのは “define function” を縮めたもののようです。 deff には関数の骨組みをあらわす文字列と内容をあらわす文字列を指定

しています。 関数がうまく定義できているかどうかを確かめるために、たとえば次のように入力

して関数を呼び出してみてください。 t = 3, y = 4 のときの関数の値が表示されるはずです。

f(3,4)

f がとる引数は2つで、独立変数の値、従属変数の値の順です。

次に、時刻のリストを定義します。

t = [0 : 0.1 : 3];

コロンとセミコロンを間違えないでください。 これは、

0 0.1 0.2 0.3 ... 2.9 3.0

のような等差数列になります。 右端にセミコロンを付けると結果を表示しませんが、変数 t には結果が格納されているので、

t

とだけ打つと変数 t に記憶された時間のリストが表示されます。次に初期条件 y=1, t=0 で微分方程式を解きます。

y = ode(1, 0, t, f);

ode というのは “ordinary differential equation (常微分方程式)“ から来ています。 ode の後の括

弧内には、従属変数の初期値、独立変数の初期値、独立変数の値のリスト、微分係数を計算する関

数の名前の順で指定します。ここでもセミコロンで表示を抑制していますが、

78

Page 80: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

y

とだけ打つと変数 y に記憶された関数値のリストが表示されます。 このリストは、 t に入ってい

る時間に対応する関数値のリストです。

plot2d(t, y, -1)

で数値解のグラフが表示されます。グラフのウィンドウはまだ消さないで下さい。plot2d の3番

目の引数はグラフに使うマークの形を指定するもので -1 から -9 までの整数です。解析解のグラ

フを重ねて描くには、Scilab ウィンドウで

plot2d(t,exp(-t**2/2))

と打ちます。 ** は冪乗の演算子で ^ を代りに使うこともできます。あまりによく一致しているの

で、本当に数値計算で解いているのかどうか疑いたくなりますね。 グラフのウィンドウで Zoomというボタンを押し、マウスでグラフの中の長方形の範囲を適当に指定するとそこが拡大表示され

ます。 UnZoom ボタンで元に戻ります。 拡大の操作を何度も繰り返すと ode の誤差がわかるでしょう。

練習問題

plot2d(t, y-exp(-t^2/2), -1) とやると、誤差のグラフが描けるかもしれないから

試してみよ。ただし、一旦、Scilab Graphic ウィンドウを消してから行うとよい。誤差の最大値はどの程度か?

最後に

exit

と打つと Scilab は終了します。

4.4 Euler 法のプログラム

では、C でのプログラムの作り方を見ましょう。まず、Euler法として知られる最も基本的な計算方法を使うことにします。 Euler 法では、ある時刻 t での関数値 y(t) とそれらから決まる微分係数の値 f

(t, y(t)

)を使って、次の時刻 t + h での関数値 y(t + h) を次のように求めます。

y(t + h) = y(t) + hf(t, y(t)

)右辺の第2項は変化率に h を掛けています。 もし変化率が時刻 t から時刻 t + h の間に変化しな

ければこの式は厳密ですが、一般には誤差が出ます。この誤差がどういう性質のものかは後で考え

ましょう。

この1ステップの予測計算を行う手続きを C++ の関数にします。時刻とその時の関数値と時間の増分をこの予測関数に与えると次の時刻の関数値を計算してくれるということにします。 つい

でに次の時刻の計算も含めておきます。微分係数を計算する関数は C の関数として独立に定義しておいて、その関数名を予測関数に与えるようにしましょう。 時刻や関数値のデータ型は doubleで良さそうですが、後から簡単に変更できるように独自の型名を使っておきます。 計算結果の受

渡しには、参照引数を使いましょう。

79

Page 81: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

// 独立変数と従属変数の型の定義typedef double tIndepend; // 独立変数の型、実は doubletypedef double tDepend; // 従属変数の型、実は double

// 微分係数を計算する関数の型の定義typedef tDepend tDerivativeFunc(tIndepend, tDepend);

// 次の時刻と関数値を計算する関数// 時刻と関数値は変数に入れて与える。結果はその変数に戻される。void Forecast(tDerivativeFunc f, tIndepend h, tIndepend& t, tDepend& y){

y = y + h * f(t, y); // 変化率一定として次の関数値を求める。t = t + h; // 時刻も更新する。

}

typedef 1というキーワードが出て来ました。これはデータ型に新しい名前を付けるものです。た

とえば、

double x;

は double 型の変数を一つ x という名前で宣言するものですが、これに typedef を付けて

typedef double x;

とすると、 double 型そのものに x という別名を付けます。このようにして新しい型名 x を作った後は、

x v;

とすると x 型の変数 v (実は double 型ですが)を宣言することができます。上のプログラムでは、tIndepend と tDepend という型名を double の別名として定義しています。

プログラムの他の部分で double を使わずに tIndepend と tDepend を使うようにすると、この最初の定義を変えることで double 以外の型にいっぺんに変更してしまうことができます。constで定数を定義するのと同じことですね。tIndepend は関数の独立変数用、tDepend は関数値 (従属変数)用としました2。

tDerivativeFunc は上のプログラムで次のように定義されています。

typedef tDepend tDerivativeFunc(tIndepend, tDepend);

セミコロンで終ることに気を付けて下さい。これは関数の宣言に似ていますがやはり typedef が

付いているので、tDerivativeFunc は関数名ではなく、関数の型をあらわす型名になっています。

関数の型は戻り値の型と引数の数や型で決まります。 つまりこれらの仕様が一致していれば中身

の処理が違っていても関数の型は同じだということです。ここでは、tDerivativeFunc 型の関数

は、 tIndepend 型の引数と tDepend 型の引数を取り tDepend 型の値を返すことが規定されてい

ます。関数の処理内容は規定されません。そのため仮引数名を書く必要はありません。処理内容

も含めた関数の実物は別のところで定義されます。

Forecast 関数はある時点の関数値から次の時刻の関数値を計算する関数です。Forecast 関数

が取る引数を説明しましょう。 関数定義の頭部は次のようです。

1typedef は “type definition” です。2tIndepend は’’type of independent variables’’ のつもり。

80

Page 82: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

void Forecast(tDerivativeFunc f, tIndepend h, tIndepend& t, tDepend& y)

最初の引数は tDerivativeFunc 型の関数です。 これは微分係数を計算する関数です。 はじめの

微分方程式の右辺の関数名にあたります。 ある時点での微分係数はその時刻とそのときの関数値

とで決まります。 Forecast 関数を呼び出すときにはこの微分係数を計算する関数が具体的に定

義されている必要があります。 その関数名を引数として Forecast 関数に与えることによって、

Forecast 関数が内部で微分係数を計算することができるようになります。 このような仕組みに

すると、Forecast 関数での微分方程式の計算手順の中に微分係数の計算手順が直接含まれないの

で、プログラムがすっきり見やすくなります。

Forecast 関数の 2番目の引数は次の時刻までの時間です。3番目と 4番目の引数には現在の時刻が入った変数と現在の関数値が入った変数を参照引数として与えますが、これらは Forecast に

よって新しい値に更新されます。& が付いているのは参照引数であり、値ではなく変数を与えるも

のでした。参照引数を使うと、呼出し元と呼び出される関数とで変数を共有することで相互に値を

伝え合うことができます。 Forecast の内部では、まず最初の引数で指定されている関数 f を呼

び出してその時点での y の変化率を求め、時間が経ってもこの変化率が変わらないとみなして時

間 h だけ経った時の関数値を求めています。 この値は元々の関数値が入っていた変数に入れて戻

しています。 新しい時刻も計算して同様に変数に入れて戻しています。

4.5 Euler 法の誤差の評価

今、大胆にも y は一定の変化率で変化するとみなして計算しましたが、実際には変化率は時間

と共に変わりますから、誤差が生じます。 この誤差の大きさは

bh2

2

よりも小さいと評価できます。これは関数 y のテーラー展開の 2次の項に対応するもので、b は y

の2階微分の大きさの最大値です。 2階微分が発散してしまうこともあるかもしれませんが、普

通はそれは特別な場所でしか起こりませんから、そのような部分を除いて考えることにすると b は

有限の値です。 この誤差は Forecast 関数による 1段の計算に伴うものですから、全部で N 段の

計算を行うなら全体の誤差はこの N 倍に収まるということになります。 t = 0 から始めて等分割で t = T まで N ステップで計算した場合、h = T/N となりますから、全体の誤差の大きさの最

大値は

bT 2

2N

となり、分割数 N を大きくすれば誤差を減らせることがわかります。ただ、計算機で実数を記憶

したり計算したりするだけで生じる誤差は N とともに増えますから、誤差を 0 にすることは不可能です。

Forecast での誤差は h2 に比例していますが、これを h3 とか h5 とか、もっと大きな冪乗にな

るようにすることができれば、N を少し増やすだけで誤差が大きく減り、精度と計算の効率を向

上させることができます。 そのようにした計算法としては Runge-Kutta 法がよく使われています。 これについては後でとりあげます。

81

Page 83: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

4.6 Euler 法の完全なプログラム

では、足りない部分を補ってプログラムを完成させましょう。 まず、微分係数を計算する関数

です。 関数の引数の型などは tDerivativeFunc の定義に合わせてやる必要があります。 関数名

は F とでもしておきましょう。

// 時刻 t, 関数値 y から y の微分係数を求める。tDepend F (tIndepend t, tDepend y){

return -(t * y);}

これはたいへん簡単ですね。引数の型と順番および戻り値の型は tDerivativeFunc の定義と同じ

にする必要があります。 これでこの関数を Forecast に最初の引数として与えることができます。

次に繰り返し関数値を求めて表示するようにします。 表示にはストリーム出力を指定子(setwのこと)付きで使いましょう。

// 解全体を求めて表示する。#include <iostream>#include <iomanip>using namespace std;void GoThrough(){

tIndepend t(0); // 初期条件 t=0tDepend y(1); // 初期条件 y=1while(1) // 繰り返し{ // ここから

cout << setw(10) << t << setw(20) << y << endl; // tと yを出力if (t >= 3.0) // 3を越えると終り

break;Forecast(F, 0.1, t, y); // tと yを更新

} // ここまで繰り返し}

tIndepend t(0); は tIndepend 型の変数 t を宣言すると共にその値を 0 に初期化しています。tIndepend t=0; と意味は同じですが、恰好を付けてみました。y の初期値は 1 です。 while(1)

はループの条件が常に 1なので無限ループになります。 break はこのループを終了させるキーワー

ドです。 break は「ブレーキをかける」ことですね。 if 文の条件が成り立つと break によって即座にループが終了します。 if 文の条件が成り立たない場合は Forecast で次の値を計算してから

ループを続けます。時刻 t は 0.1 の等間隔で増やしていますから、30 回計算したところで終了ですが、表示は初期値も含めて 31 行出ます。endl を出力して改行を入れています。

最後は main 関数です。これは GoThrough を呼び出すだけですが、異常事態が起こらない限り

return 0; で終わりにするのが決まりでしたね。 これは自分で書いてください。

以上で一応動くプログラムが完成しました。適当なファイル名euler.ccで保存し、make euler で

コンパイルし、./euler で実行してください。実行すると数値がずらっと出てきますから、これ

をグラフにしてみてください。 たとえば gnuplot で

plot "< ./euler" with linespoints

82

Page 84: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

とやればよろしい。 解析解のグラフと並べると誤差がわかります。 それには gnuplot で

plot "< ./euler", exp(-x**2/2)

とやればよいでしょう。 gnuplot では独立変数の名前は x です。これは C++ のソースプログラムとは関係ありませんから注意して下さい。また、gnuplot ではべき乗は ** であらわします。どうですか? こんな簡単な近似計算でも結構合うでしょう? 希望がわいて来ますねえ。

GoThrough ではステップ幅 (1ステップあたりの時間の増分)は 0.1 にしていますが、これを増やしたり減らしたりして全体の誤差がどう変わるかを見てください。 全体の誤差がステップ幅に

だいたい比例していることがわかるでしょう。 ステップ幅を小さくしていくと実数の計算や記憶

に伴う誤差の累積があらわれて来るはずですが、それはかなり小さいのでグラフで確かめることは

できないでしょう。 本当にこの効果をテストしたいなら、関数値をどこかの桁でわざと切り捨て

るようにするというやり方が考えられます。 たとえば、

y = floor(y*1000)/1000 ;

は yの値の小数点以下 4桁目以下を切り捨てます3。 floor関数を使うため cmathを #includeす

る必要があります。この場合は切り捨てですから関数値は小さめに出ます。この式を GoThrough

関数の繰り返しの中に入れます。ステップが細かいほど累積の効果が出て来て誤差が大きくなるの

をグラフを見て確かめることができるでしょう。

4.7 ループの制御法

break は while 文の内部の勝手なところからプログラムの流れをジャンプさせるので、あまり美しくないという意見もあります。たしかに、ループから脱出する場所が2ヶ所以上になるとわかり

にくくなりますから、break は注意して使用したほうがよろしい。構造化プログラミングの教科書などでは break のような機能を使うことを禁止して補助的変数を使ってループの条件を判定する方法が推奨されています。この場合はループの条件判定の場所はループの頭かしっぽのどちらかに限

ります。 break に相当する機能の無い言語ではそのやり方が強制されます。次の GoThrough はこ

の方法によるものです。toBreak という変数の値でループをコントロールしています。 この変数

は C++ の機能を生かして for 文の括弧の中で宣言と初期化を行いました。 こちらの GoThrough

を前のに差し替えて使ってみてください。

3floor 関数については man ページで見てください。

83

Page 85: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

void GoThrough(){

tIndepend t(0);tDepend y(1);for ( int toBreak=0 ; toBreak == 0 ; ) // toBreak != 0 で脱出{

cout << setw(10) << t << setw(20) << y << endl;if (t >= 3.0) // 脱出の条件をテストして

toBreak = 1; // 結果を toBreak に置くelse // 脱出しない場合は

Forecast(F, 0.1, t, y); // 計算を行う}

}

4.8 Runge-Kutta法

Runge-Kutta 法にはいろいろバリエーションがありますが、よく使われる基本的なものとして、4次の Runge-Kutta 法を試してみましょう。 この方法では 1ステップの誤差の大きさが h5 に大

体比例します。したがってステップ数をあまり増やすことなく高い精度を得ることができます。全

体のステップ数はステップの幅に反比例するので、全体の精度は h4 に比例します。計算の結果が

不十分ならステップ数を2倍にするだけで誤差は16分の1程度に減るというわけですね。

Euler法のかわりにRunge-Kutta法を使うには、Forecast関数での計算手順を次のようにちょっと複雑にするだけで後の部分は全く変更する必要がありません。

void Forecast(tDerivativeFunc f, tIndepend h, tIndepend& t, tDepend& y){

tDepend k1 = h * f(t, y);tDepend k2 = h * f(t + 0.5*h, y + 0.5*k1);tDepend k3 = h * f(t + 0.5*h, y + 0.5*k2);tDepend k4 = h * f(t + h, y + k3);y = y + (k1 + 2*k2 + 2*k3 + k4)/6;t = t + h;

}

こちらの Forecast に差し替えて、前と同様に、ステップ幅を変えて誤差の変化の様子を調べてください。わざと切り捨てを行う実験をやった場合は、切り捨てを取り外してテストしてください。

時間間隔が 0.1 から 0.2 に増やしても、グラフでは誤差が見えないでしょう。gnuplot で誤差だけをグラフにするには、次のようにします。

plot "< ./runge-kutta" using ($1):($2-exp(-$1**2/2)) with lp

using の後のコロン : の両側に、X 軸データと Y軸データを指定していますが、Y軸データは式になっています。$1 と $2 は、データファイルの1列目と2列目を意味します。このグラフから、

誤差の最大値を読み取ってください。

逆に時間間隔を段々と減らしてみましょう。すると、急激に誤差が減るのがわかるでしょう (ただし、累積誤差のため、あるところからは誤差が減らなくなる)。使うだけならこの計算法で誤差

84

Page 86: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

がどうして h5 に比例するのかを理解しておく必要はありません。発明者に感謝するのみです。

4.9 ベクトル値関数の微分方程式

上では従属変数は 1つの実数でしたが、いくつかの量が相互に関係しあって時間と共に変化するような場合でも、微分方程式で時間変化が規定されるなら、従属変数の数によらず Euler 法やRunge-Kutta 法で数値解を求めることができます。一般に次のような形の連立微分方程式を解くことになります。

dy1

dt= f1(t, y1, y2, ...)

dy2

dt= f2(t, y1, y2, ...)

....

....

f1(t, y1, y2, ...) や f2(t, y1, y2, ...) などは各従属変数の変化率であり、時刻とすべての従属変数の値によって決まります。すなわちこれらの量の関数です。すべての従属変数をまとめて次のように一

つの記号で表してみましょう。

Y = [ y1, y2, · · · ]

f1(t, y1, y2, · · · ) や f2(t, y1, y2, · · · ) などは次のようにまとめることができます。

F(t,Y) = [ f1(t, y1, y2, · · · ), f2(t, y1, y2, · · · ), · · · ]

また Y の時間微分 dY/dt を次のように解釈します。

dYdt

=[

dy1

dt,dy2

dt, · · ·

]従属変数 yi の数が N 個あったとすると、Y と dY/dt は N 次元ベクトル、F(t,Y) はスカラー t

と N 次元ベクトル Y のN 次元ベクトル値関数です。しかし、独立変数 t は已然としてスカラー

です。以上の記法を使えば、上の連立微分方程式は次のような 1本の方程式になってしまいます。dYdt

= F(t,Y)

Euler法の予測式は次の式であらわされ従属変数などの次元が違う以外は前と全く同じです。

Y(t + h) = Y(t) + hF(t,Y)

ここで、ベクトルどうしの足し算やスカラーとベクトルの掛け算は普通の数学と同じくベクトルの

成分ごとに行います。同様に、Runge-Kutta法の予測計算も従属変数の次元に依らず前と同じ方法でできます。

C++ にはこの N 次元ベクトルに対応するデータ型は用意されていませんが、新しい型として

作ることはできますし、それに対する足し算や掛け算のような演算子の作用を定義することもで

きます。ここでは初心者向けにあまり抽象化しない範囲でこのような C++ の機能を使ってみましょう。従属変数の数や型を固定して扱います。という事はどういう事かと言うと、問題によって

必要な従属変数の数などは違いますから、問題が違うとデータ型の定義を手直ししなければならな

いという事です。C++ の抽象化のテクニックを高度に使うとこれを避けることができますが、地獄 (煉獄)を通らなければ天国には行けませんから、ここでは高度な抽象化は避けることにします。では具体的な問題に促して話を進めましょう。

85

Page 87: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

4.10 振り子の運動シミュレーション

鉛直面内で運動している振り子の運動を考えましょう。固定された支点と重り

が質量ゼロの硬い棒で繋がっていると考えます。鉛直下向きを基準にして棒の向

きを測った角度を q とし この角度の時間変化の割合(つまり角速度)を w とす

ると、次の微分方程式が成り立ちます。

q

w=q.

dq

dt= w

dw

dt= − sin q − γw

sin q の係数が 1 になるように時刻 t の単位を決めたと考えます。すると、抵抗力がゼロでかつ振

幅が小さいときの周期は 2π になります4。γw は速度に比例する抵抗力で、γ はその比例定数で

す。角度 q の単位はラジアン (すなわち約 57.29578 度)です。[ q, w ] を2次元ベクトル Y と考えると、上で述べた形式が適用できます。すなわち、

dYdt

= F(t,Y)(

Y = [ q, w ], F(t,Y) = [ w, − sin q − γw ])

従属変数と変化率は 2次元ベクトルですが、微分方程式を解く計算手順は1次元の場合と同じですから、前に作った1次元のプログラムに必要な改造を加えて新しいプログラムを作ることにしま

す。前のプログラムのソースファイルの名前を変更するか別の名前のコピーを作ってください。新

しいソースプログラムのファイル名は pendulum.cc としましょう。

4.10.1 インクルード指令と諸パラメーターの定義

プログラムのはじめには、必要なヘッダーファイルのインクルード指令をまとめて置きましょう。ス

トリーム出力を使用するので、iostreamと iomanipのインクルード指令と、using namespace std;

を先頭に持ってきてください。また、三角関数を使うので、cmath のインクルードします。いろいろなパラメーターの定義も置きます。

#include <iostream>#include <iomanip>#include <cmath>using namespace std;const double Step=0.1, Last=139*M_PI; // ステップ幅と終了時刻const double InitPosition = 0; // 初期位置const double InitVelocity = 2.0; // 初度速const double Friction=0.015; // 制動力の強さ (gamma)

4.10.2 tDepend 型のための諸定義

次に、tDepend 型が2次元ベクトルを表すように変更します。この型は q と w という2つの成分を持った複合データです。こういう場合は構造体というのをよく使います。配列でもよいのです

4 dqdt

= w, dwdt

= −q の解は、q = cos t と q = sin t の任意の一次結合だから、周期は 2π.

86

Page 88: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

がちょっと不便です。配列では要素を番号で指定するのに対して、構造体では各成分に独自の要素

名を付けて区別します。また配列の要素の型は皆同じであるのに対して、構造体の要素の型は同じ

である必要はありません。さらに C++ では構造体型はクラスの一種であり、関数を型固有のメソッドとして持つことができますが、配列はだめです。いろいろな advantage があるので構造体を使いましょう。なお、フル装備の本物のクラスを使うことも可能ですが、そうすると考慮するこ

とが増えてしまうので、ここでは単純な構造体にしておきます。

typedef double tDepend;

となっているのを消して、代わりに次のように書き直してください。

// 従属変数は 2つの実数と初期化関数よりなる構造体型struct tDepend{

double q; // 角度double w; // 角速度tDepend(double Q=0.0, double W=0.0); // コンストラクター

};

// 初期化 (コンストラクター)の内容tDepend::tDepend (double Q, double W){

q = Q;w = W;

}

構造体型の定義は、struct というキーワード、新しい型の型名、および大括弧で囲まれた内部構造の記述よりなります。関数定義と違って最後にセミコロンが必要です。内部構造は普通の変数や関

数の宣言と同じ書き方になります。ただし、データメンバーに初期値を与えることはできません。

メソッドつまり関数メンバーについては、宣言の初めのところだけをここに書いておいて、具体

的な処理内容は後回しにすることができます。また、tDepend というメソッドの引数は 2つありますが、引数を省略した場合にはどちらも 0 とみなされるように、省略時値が指定してあります。省略時の値を決めてある引数だけが省略できますが、ある引数を省略した場合はそれより右の引数

も省略しなければなりません。

この tDepend というメソッドは、名前がクラス名 tDepend と同じです。このような名前の付いたメソッドはコンストラクター (構築子、constructor、初期化メソッド) と呼びます。コンストラクターはメンバーの初期化をおこなうメソッドです。使い方は後で出てきます。コンストラクター

には戻り値はありませんが、 void は付けないことに注意して下さい。コンストラクターの中身の定義は構造体定義の外でおこなっています。この場合は、クラス名と

2つのコロンをメソッド名の前に付けます。この :: はスコープ解決演算子と呼びます。

続けて、tDepend 型どうしの足し算のしかたを規定します。 これをしなければ、2次元ベクトルどうしの足し算をやろうとしてもエラーになります。

87

Page 89: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

// tDepend 型どうしの足し算のしかたtDepend operator + (tDepend a, tDepend b){

return tDepend(a.q + b.q, a.w + b.w);}

これは tDepend クラスのメソッドではなく通常の関数に過ぎません。

operator +

が関数の名前に相当します。この関数の引数は足しあわせるデータ、戻り値は足しあわせた結果の

値で、すべて tDepend 型です。これで、例えば Y と Z が tDepend 型のデータである場合、Y + Z

のように書いて足し算ができるようになります。

ピリオドを使って a.q のように書いたものは、構造体のメンバーを意味します。a.q は引数 a

の q というメンバーですね。operator + の内部では、引数のメンバー毎の和を計算して、それら

を tDepend に引数として与えています。tDepend は関数ではなく型名です。このように書くと、

tDepend 型のコンストラクターが呼び出されますが、型名に与えられた引数はコンストラクター

への引数になります。この結果、指定した値を使って初期化された tDepend 型のデータが生成さ

れます。これが return で戻り値として伝達され、足し算の結果が決まります。スカラーとベクトルの掛け算も同様に定義します。

// スカラーを tDepend 型に掛けるしかたtDepend operator * (tIndepend a, tDepend b){

return tDepend(a * b.q, a * b.w);}

ベクトルのスカラーによる割り算も同様に定義します。

// tDepend 型をスカラーで割るしかたtDepend operator / (tDepend a, tIndepend b){

return (1/b) * a;}

この場合、掛ける順番には意味があります。つまり、a をスカラー、b をベクトルとすると、a*b

は計算できますが、b*a は定義が無いので計算できません。

以上のように、データの種類によって演算子の意味を定義しなおすことを演算子のオーバーロー

ドと言います。挿入演算子 << もオーバーロードできます。tDepend 型のデータを挿入演算子 <<

で cout に送って値を表示することができるようにしましょう。

88

Page 90: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

// tDepend 型を出力ストリームへ出力するしかたostream& operator << (ostream& strm, tDepend a){

int n = strm.width(); //setw で指定した出力幅使うreturn strm << setw(n) << a.q << setw(n) << a.w;

}

挿入演算子の初めの引数と戻り値の型は共に ostream& です。ostream 型への参照という型です。

ここでこのわけを説明するのは時間の無駄ですので、returnの使い方も含めてこのように書くもの

だと理解しておいて下さい。width というメソッドを呼び出していますが、これは処理子 setw で

直前に指定した出力幅を読み出すものです。これによって tDepend 型のデータの表示の幅も setw

で制御できるようになっています。

4.10.3 他の変更

次に、微分係数を計算する関数 F を定義しなおします。抵抗力の強さをあらわす係数 γ の値と

して、定数 Friction を使います。

// 力学系の各種パラメーターと微分係数を計算する関数 F の定義tDepend F (tIndepend t, tDepend s){

return tDepend( s.w, -sin(s.q) - Friction*s.w );}

GoThrough は初期化と表示を少し変更します。

// 全ステップを実行void GoThrough(tIndepend step, tIndepend last){

tIndepend t(0); // 独立変数初期化 (時刻 0)tDepend y(InitPosition, InitVelocity); // 従属変数の初期化while(1){

cout<<setw(10)<<t*(1/M_PI/2)<<setw(20)<<y<<endl; // 表示if (t >= last) // 終了時刻を過ぎたら終り

break;Forecast(F, step, t, y); // 1ステップの予測

}}

振幅が小さい場合の周期は 2π と予測されるので、時刻 t は 2π を単位として表示するようにしま

した。前にも出てきましたが、M_PI は

GoThrough の引数の変更にともなって、main 関数でステップ幅と終了時刻を与えるようにします。

89

Page 91: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

int main(){

GoThrough(Step, Last); // Step ずつ Last まで進む (2000ステップ)return 0;

}

Forcast は全く修正する必要はありません。以上で、pendulum.cc は完成です。

4.10.4 動作テスト

始めに、計算が正常に行われていることを確かめます。コンパイルして

./pendulum > x1

で結果をファイル x1 に保存します。次にステップ幅のパラメータを Step=0.05 に変えてコンパイルし、

./pendulum > x2

で結果を x2 に保存します。これらの結果を比較するため、gnuplot で

plot [-1:1] "x1" using 2:3 with points, "x2" using 2:3 with points

とやって、両方の軌跡を描いてください。x1 などの各行は時刻と角度と角速度の 3つの数値から成ります。using 2:3 はその内の 2番目と 3番目すなわち角度と角速度を取り出して、それぞれ横軸と縦軸にしたグラフを描くように指示しています。2つのグラフが一致すれば、少なくともグラフにできる程度の精度は出ていると判断できます。ステップ幅がちょうど 2倍違うので、点の数も2倍違うことに注意してください。なお、このグラフは、左右の端に点が集まっているでしょ

う。これは、支点の真上に近いところに行くと加速度が小さくなることによります。

90

Page 92: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

時間変化を見るため、gnuplot で次のようにします。set gridplot "x1" with line, "x2" with line

グリッドが入ったので、振動の周期が変化しているのを見て取ることができるでしょう。振幅が小

さくなるにつれて周期が短くなります。これも、支点の真上に近いところに行くと加速度が小さく

なることによります。振幅が小さいと周期は 2π を単位として1になります。次のように、部分を

拡大して詳しく見てください。

plot [0:10] "x1" with line, "x2" with line

plot [50:60] "x1" with line, "x2" with line

4.10.5 Duffing 方程式

振り子に外力を加えて振動させると複雑な運動をおこないますが、振り子はぐるぐる回ったりし

て複雑すぎるので、ここでは次の形の Duffing 方程式で試してみましょう。振り子で sin 関数だったところが q3 という単純な形になっています。A sin t は周期的外力の項です。

dq

dt= w (4.1)

dw

dt= −q3 − rw + A sin t (4.2)

復元力 −q3 は位置の3乗なので、自由振動の周期は振幅によって変化します。振り子と違い、振

幅が小さい時の周期は無限大であり、振幅が大きくなると周期はいくらでも小さくなります。周期

的外力が働く場合は、その強さ A などを変えると解は大きく変化し、カオス的な運動を容易に観

察することができます。

振り子のプログラムをコピーして、ファイル名を duffing.cc にします。まず、外力のパラメーターをはじめの方に追加し、他のパラメーターを変更します。

// ステップ幅と終了時刻double Step=0.01, Last=30;// 初期位置と初速度const double InitPosition = 0;const double InitVelocity = 100;// 抵抗力の係数double Friction=0.3;// 外力の振幅double Amp=0;

微分係数を求める関数 F は次のように変更します。pow はべき乗を計算する関数です。

91

Page 93: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

// y の微分係数を求める関数tDepend F (tIndepend t, tDepend s){

return tDepend( s.w, -pow(s.q, 3)-Friction*s.w+Amp*sin(2*M_PI*t));}

GoThrough の中の時間の表示は t をそのまま出すようにします。コンパイルし、gnuplot で 時間-位置グラフと位置-速度グラフを作りましょう。

-15

-10

-5

0

5

10

15

0 5 10 15 20 25 30

’<./duffing’ u 1:2

-100

-80

-60

-40

-20

0

20

40

60

80

100

-15 -10 -5 0 5 10 15

’<./duffing’ u 2:3

外力はゼロですから、抵抗力によって振幅が減少していきます。それとともに周期がのびていま

す。位置-速度グラフを見ると、復元力 −q3 によってゴツンゴツンとはねかえされていることもわ

かります。

次に、外力の元での振動を計算します。初速度 InitVelocity はゼロにしてください。

-4

-3

-2

-1

0

1

2

3

4

0 5 10 15 20 25 30

Amp=40

-20

-15

-10

-5

0

5

10

15

20

-4 -3 -2 -1 0 1 2 3 4

Amp=40

-6-5-4-3-2-1 0 1 2 3 4 5

0 5 10 15 20 25 30

Amp=50Amp=40

-25

-20

-15

-10

-5

0

5

10

15

20

25

-6 -5 -4 -3 -2 -1 0 1 2 3 4 5

Amp=50Amp=40

はじめは過渡的に複雑な動きを示しますが、外力の振幅 Ampl の値によっては、時間が経つと周期1の定常な振動になります。しかし、周期が整数倍になり複雑な振動波形を示す場合もありま

す。この場合は、位置-速度グラフで、振動の中心が一つではなく二つになったように見えます。線形振動の系ではこのようなことは起こりません。

92

Page 94: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

付 録A Emacs の中でコンパイルするには

コンパイル作業を emacs の中で行う方法に触れておきましょう。emacs でプログラムを書いたり修正してさあコンパイルしてみようということになったらそのまま emacs の中で compile というコマンドを実行します。 それには

�� ��Meta�� ��x compile

�� ��Enter と

打ちます。(�� ��Meta キーはキーボードによって異なり、

�� ��◇ キーか�� ��Alt キーか

�� ��Esc キーです。)�� ��Meta�� ��x の後の compile の部分はエコー領域 (emacs の窓の中の最下行)に入り、

�� ��Enter を押す前

ならそこで編集できます1。

さて、このようにして compile コマンドを起動すると、実際にコンパイルに使うコマンドの入力を待つ状態になります。これをエコー領域に入力してから

�� ��Enter を押すのですが、エコー領域に

はすでに何かが入っているでしょうから、それを make tsubasa と書き換えます。 これで�� ��Enter

を押すと、emacs の窓が 2つにわかれ、一方の窓にコンパイル作業の様子が表示されます。文法エラーが発見されると、問題の起こった行番号とともにエラーメッセージが表示されます。

エラーメッセージをマウスのボタン 2(中ボタン)でクリックすれば、プログラム中のエラーが起こった行が表示されその行にカーソルが行きます。 同じことは、

�� ��Ctrl�� ��x ‘ と打つことでもできます。

プログラムを修正して再コンパイルするときには、前に使った make tsubasa というコマンド

を emacs が憶えていてくれるので、コンパイル用のコマンドの修正は不要です。しかし、 emacsを終了するとこのコマンドは忘れられてしまいます。 これを防ぐためには、次のように、コンパ

イルに使う make tsubasa というコマンドをあらかじめソースファイル中に書いて指定しておき

ます。普通、先頭か末尾に書きます。

// Local Variables:// compile-command: "make tsubasa"// End:

emacs はファイルを読み込む際に “Local Variables:” という行から “End:” という行までを「ローカル変数」の設定として認識します。これらの行はプログラムの一部ではないので、コメント記号

で始めて C++ コンパイラーに無視させます。emacs に指定をうまく認識させるために、各行の左端の // の部分をみな同じにしなければなりません。ローカル変数の設定を書き加えたら、emacsにそれを認識させるために、一旦保存して改めて開く必要があります。

1 emacs のコマンドはあまりにもたくさんあるので、あらかじめキー操作に割り当てられているものは一部だけです。しかし、

�� ��Meta�� ��x に続いてコマンド名を入力すればキーに割り当てられていない emacs コマンドでも実行することがで

きるようになっているのです。

93

Page 95: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

付 録B グラフィックライブラリーの準備

第3章で使用する簡易グラフィックライブラリーを、一般の Linux コンピューターに設定する仕方について説明する。

簡易グラフィックライブラリーを使用するには,XviG Graphics Library の導入も必要であ

る。ここでは,これらの作業を行うディレクトリーとして,ホームディレクトリーに src という名前のディレクトリーがあると想定する。また,サブルーチンライブラリーのファイルや実行ファイ

ルやマニュアルを入れるために,ホームディレクトリーには lib, include, bin, man というディレクトリーもあるとする。これらが無ければ、mkdir コマンドか、ファイルブラウザーを使って作っておく。

B.1 XviG

このソフトウェアには,“No financial profit is made out of it.” という利用条件が付いているので,勉強や趣味にしか使えないことに注意する。(もっと自由に使えるものが必要なら,たとえば,EggX というライブラリーがある。)

XviG はかって usenet ニュースグループ comp.sources.x に記事として投稿されたものである。現在は Google Group に保存されているので,おおもとから収集したい場合は,そちらで記事 (全部で 10編ある)を入手し,記事の冒頭に書いてある説明にしたがって,ファイル一式を取り出すことができる。あるいは,この文書に添付されているファイル xvig.tar.gz.txt を展開して取り

出すこともできる。

後者の場合は,まず,添付ファイルを適当なディレクトリーに保存する。Adobe Reader(acroread)でこの文書を見ているなら,Adobe Readerの機能で添付ファイルを保存することができる。AdobeReader が無い場合は,pdftk コマンドを使うこともできる。pdftk の場合は,この文書を適当なディレクトリーに保存し,端末でそのディレクトリーに行って,

pdftk この文書のファイル名.pdf unpack_files

とやる。Adobe Reader も pdftk も無い場合は,どちらかをインストールする。どちらのインストールも難しくない。

添付ファイル xvig.tar.gz.txt を保存したら,そのディレクトリーで,

tar xzvf xvig.tar.gz.txt

とやって展開をおこなう。あるいは,ファイル名を xvig.tar.gz に変更してからアイコンをダブ

ルクリックして適当にやれば,展開ができる (.txt を付けているのは,Adobe Reader を満足させるため)。展開でできた xvig ディレクトリーの中の COPYRIGHT および DISCLAIMER というファイルには,使用上の条件などが書かれている。また,version_1.1 というサブディレクトリー

94

Page 96: C言語入門の入門 - 京都産業大学tanigawa/materials/oyo/text/Intro_C.pdf · C言語入門 の入門 最終更新日: 2010/3/3 ... テーションの操作方法や、実行結果のグラフ表示のような周辺部分についても説明しています。

には,ソースファイルと説明書一式が入っている。この version_1.1 ディレクトリーを~/src/

(ホームディレクトリーの src ディレクトリ) にまるごとコピーして名前を xvig11 に変更する

(cp -r version_1.1 ~/src/xvig11)。

B.1.1 XviGの変更

XviG に少し手を加えたほうがよいかもしれない。ただし,これは趣味の問題であって、やらなくても差し支えはない。

まず、描画ウィンドウが開くたびに著作権表示が出るのが困るので、これを出なくする。それに

は、xvig11/src/ の中の init.c をテキストエディターで開き、64行目あたりの

printf("\n>>> XviG Graphics Package Copyright Imec (c) 1993 <<<\n\n");

という1行を消す。もちろん,ファイルを保存する。

次に、実害のない文法的問題点を直す。そのため、xvig11/src/ の中の window.c をテキスト

エディターで開き、27行目に次のように書き込む。

#include <unistd.h>

B.1.2 xmkmf

端末で xmkmf と打ってこのコマンドがあることを確かめる。万が一無い場合,Vine Linux などでは,XOrg-devel パッケージを入れる。Debian などでは xutils-dev パッケージを入れる。

B.2 sgraph

添付ファイルに sgraph.tar.gz.txt がある。これを ~/src に入れて,そのディレクトリーで

tar xzvf sgraph.tar.gz.txt

とやると,sgraph というディレクトリーができる。あるいは,ファイル名を sgraph.tar.gz に

変更してからアイコンをダブルクリックして適当にやれば,展開ができる。

端末で sgraph ディレクトリーに移り,INSTALL.eucを見て (cat INSTALL.euc)作業を続ける。作業といっても,自動化されているのでほとんど何もやらないのと同じである。要約すると,次の

ようになる。

1. make で全てのファイルのインストールを行う。

2. example ディレクトリーの中でテストを行う。

~/man ディレクトリーには, 説明書 sgraph.txt が入っている。ここにある Makefile.sgraph

を自分の作業ディレクトリーに Makefile という名前でコピーすると,自分のプログラムのコンパ

イルができるようになる。

95